- Add modern CLI interface built with Zig for performance - Include TUI (Terminal User Interface) with bubbletea-like features - Implement ML experiment commands (run, status, manage) - Add configuration management and validation - Include shell completion scripts for bash and zsh - Add comprehensive CLI testing framework - Support for multiple ML frameworks and project types CLI provides fast, efficient interface for ML experiment management with modern terminal UI and comprehensive feature set.
95 lines
3.2 KiB
Zig
95 lines
3.2 KiB
Zig
const std = @import("std");
|
|
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 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 {
|
|
_ = args;
|
|
|
|
// 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();
|
|
|
|
// Use plain password for WebSocket authentication, compute hash for binary protocol
|
|
const api_key_plain = config.api_key; // Plain password from config
|
|
const api_key_hash = try crypto.hashString(allocator, api_key_plain);
|
|
defer allocator.free(api_key_hash);
|
|
|
|
// Connect to WebSocket and request status
|
|
const ws_url = std.fmt.allocPrint(allocator, "ws://{s}:9101/ws", .{config.worker_host}) catch |err| {
|
|
return err;
|
|
};
|
|
defer allocator.free(ws_url);
|
|
|
|
var client = ws.Client.connect(allocator, ws_url, api_key_plain) 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();
|
|
|
|
client.sendStatusRequest(api_key_hash) catch {
|
|
return error.RequestFailed;
|
|
};
|
|
|
|
// Receive and display user-filtered response
|
|
try client.receiveAndHandleStatusResponse(allocator, user_context);
|
|
}
|