const std = @import("std"); const c = @cImport(@cInclude("time.h")); const Config = @import("../config.zig").Config; const ws = @import("../net/ws.zig"); const crypto = @import("../utils/crypto.zig"); const errors = @import("../errors.zig"); const logging = @import("../utils/logging.zig"); const colors = @import("../utils/colors.zig"); pub const StatusOptions = struct { json: bool = false, watch: bool = false, limit: ?usize = null, watch_interval: u32 = 5, // seconds }; const UserContext = struct { name: []const u8, admin: bool, allocator: std.mem.Allocator, pub fn deinit(self: *UserContext) void { self.allocator.free(self.name); } }; fn authenticateUser(allocator: std.mem.Allocator, config: Config) !UserContext { // Validate API key by making a simple API call to the server const ws_url = try std.fmt.allocPrint(allocator, "ws://{s}:9101/ws", .{config.worker_host}); defer allocator.free(ws_url); // Try to connect with the API key to validate it var client = ws.Client.connect(allocator, ws_url, config.api_key) catch |err| { switch (err) { error.ConnectionRefused => return error.ConnectionFailed, error.NetworkUnreachable => return error.ServerUnreachable, error.InvalidURL => return error.ConfigInvalid, else => return error.AuthenticationFailed, } }; defer client.close(); // For now, create a user context after successful authentication // In a real implementation, this would get user info from the server const user_name = try allocator.dupe(u8, "authenticated_user"); return UserContext{ .name = user_name, .admin = false, .allocator = allocator, }; } pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var options = StatusOptions{}; // Parse arguments for flags var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; if (std.mem.eql(u8, arg, "--json")) { options.json = true; } else if (std.mem.eql(u8, arg, "--watch")) { options.watch = true; } else if (std.mem.eql(u8, arg, "--limit") and i + 1 < args.len) { const limit_str = args[i + 1]; options.limit = try std.fmt.parseInt(usize, limit_str, 10); i += 1; } else if (std.mem.startsWith(u8, arg, "--watch-interval=")) { const interval_str = arg[16..]; options.watch_interval = try std.fmt.parseInt(u32, interval_str, 10); } else if (std.mem.startsWith(u8, arg, "--help")) { try printUsage(); return; } else { colors.printError("Unknown option: {s}\n", .{arg}); try printUsage(); return error.InvalidArgs; } } // Load configuration with proper error handling const config = Config.load(allocator) catch |err| { switch (err) { error.FileNotFound => return error.ConfigNotFound, else => return err, } }; defer { var mut_config = config; mut_config.deinit(allocator); } // Check if API key is configured if (config.api_key.len == 0) { return error.APIKeyMissing; } // Authenticate with server to get user context var user_context = try authenticateUser(allocator, config); defer user_context.deinit(); if (options.watch) { try runWatchMode(allocator, config, user_context, options); } else { try runSingleStatus(allocator, config, user_context, options); } } fn runSingleStatus(allocator: std.mem.Allocator, config: Config, user_context: UserContext, options: StatusOptions) !void { const api_key_hash = try crypto.hashApiKey(allocator, config.api_key); defer allocator.free(api_key_hash); // Connect to WebSocket and request status const ws_url = try std.fmt.allocPrint(allocator, "ws://{s}:9101/ws", .{config.worker_host}); defer allocator.free(ws_url); var client = ws.Client.connect(allocator, ws_url, config.api_key) catch |err| { switch (err) { error.ConnectionRefused => return error.ConnectionFailed, error.NetworkUnreachable => return error.ServerUnreachable, error.InvalidURL => return error.ConfigInvalid, else => return err, } }; defer client.close(); try client.sendStatusRequest(api_key_hash); // Receive and display user-filtered response try client.receiveAndHandleStatusResponse(allocator, user_context, options); } fn runWatchMode(allocator: std.mem.Allocator, config: Config, user_context: UserContext, options: StatusOptions) !void { colors.printInfo("Starting watch mode (interval: {d}s). Press Ctrl+C to stop.\n", .{options.watch_interval}); while (true) { // Display header for better readability if (!options.json) { colors.printInfo("\n=== FetchML Status - {s} ===\n", .{user_context.name}); } try runSingleStatus(allocator, config, user_context, options); if (!options.json) { colors.printInfo("Next update in {d} seconds...\n", .{options.watch_interval}); } // Sleep for the specified interval using a simple busy wait for now // TODO: Replace with proper sleep implementation when Zig 0.15 sleep API is stable const start_time = std.time.nanoTimestamp(); const target_time = start_time + (@as(i128, options.watch_interval) * std.time.ns_per_s); while (std.time.nanoTimestamp() < target_time) { // Simple busy wait - check time every 10ms const check_start = std.time.nanoTimestamp(); while (std.time.nanoTimestamp() < check_start + (10 * std.time.ns_per_ms)) { // Spin wait for 10ms } } } } fn printUsage() !void { colors.printInfo("Usage: ml status [options]\n", .{}); colors.printInfo("\nOptions:\n", .{}); colors.printInfo(" --json Output structured JSON\n", .{}); colors.printInfo(" --watch Watch mode - continuously update status\n", .{}); colors.printInfo(" --limit Limit number of results shown\n", .{}); colors.printInfo(" --watch-interval= Set watch interval in seconds (default: 5)\n", .{}); colors.printInfo(" --help Show this help message\n", .{}); colors.printInfo("\nExamples:\n", .{}); colors.printInfo(" ml status # Show current status\n", .{}); colors.printInfo(" ml status --json # Show status as JSON\n", .{}); colors.printInfo(" ml status --watch # Watch mode with default interval\n", .{}); colors.printInfo(" ml status --watch --limit 10 # Watch mode with 10 results limit\n", .{}); colors.printInfo(" ml status --watch-interval=2 # Watch mode with 2-second interval\n", .{}); }