- Add new commands: annotate, narrative, requeue - Refactor WebSocket client into modular components (net/ws/) - Add rsync embedded binary support - Improve error handling and response packet processing - Update build.zig and completions
205 lines
7.9 KiB
Zig
205 lines
7.9 KiB
Zig
const std = @import("std");
|
|
const colors = @import("utils/colors.zig");
|
|
const crypto = @import("utils/crypto.zig");
|
|
|
|
/// User-friendly error types for CLI
|
|
pub const CLIError = error{
|
|
// Configuration errors
|
|
ConfigNotFound,
|
|
ConfigInvalid,
|
|
APIKeyMissing,
|
|
APIKeyInvalid,
|
|
|
|
// Network errors
|
|
ConnectionFailed,
|
|
ServerUnreachable,
|
|
AuthenticationFailed,
|
|
RequestTimeout,
|
|
|
|
// Command errors
|
|
InvalidArguments,
|
|
MissingCommit,
|
|
CommandFailed,
|
|
JobNotFound,
|
|
PermissionDenied,
|
|
ResourceExists,
|
|
JobAlreadyRunning,
|
|
JobCancelled,
|
|
ServerError,
|
|
SyncFailed,
|
|
|
|
// System errors
|
|
OutOfMemory,
|
|
FileSystemError,
|
|
ProcessError,
|
|
};
|
|
|
|
/// Error message mapping
|
|
pub const ErrorMessages = struct {
|
|
pub fn getMessage(err: anyerror) []const u8 {
|
|
return switch (err) {
|
|
// Configuration errors
|
|
error.ConfigNotFound => "Configuration file not found. Run 'ml init' to create one.",
|
|
error.ConfigInvalid => "Configuration file is invalid. Please check your settings.",
|
|
error.APIKeyMissing => "API key not configured. Set it in your config file.",
|
|
error.APIKeyInvalid => "API key is invalid. Please check your credentials.",
|
|
error.InvalidAPIKeyFormat => "API key format is invalid. Expected 64-character hash.",
|
|
|
|
// Network errors
|
|
error.ConnectionFailed => "Failed to connect to server. Check if the server is running.",
|
|
error.ServerUnreachable => "Server is unreachable. Verify network connectivity.",
|
|
error.AuthenticationFailed => "Authentication failed. Check your API key.",
|
|
error.RequestTimeout => "Request timed out. Server may be busy.",
|
|
|
|
// Command errors
|
|
error.InvalidArguments => "Invalid command arguments. Use --help for usage.",
|
|
error.MissingCommit => "Missing commit ID. Use --commit <id> to specify the commit.",
|
|
error.CommandFailed => "Command failed. Check server logs for details.",
|
|
error.JobNotFound => "Job not found. Verify the job name.",
|
|
error.PermissionDenied => "Permission denied. Check your user permissions.",
|
|
error.ResourceExists => "Resource already exists.",
|
|
error.JobAlreadyRunning => "Job is already running.",
|
|
error.JobCancelled => "Job was cancelled.",
|
|
error.PruneFailed => "Prune operation failed. Check server logs for details.",
|
|
error.ServerError => "Server error occurred. Check server logs for details.",
|
|
error.SyncFailed => "Sync operation failed.",
|
|
|
|
// System errors
|
|
error.OutOfMemory => "Out of memory. Close other applications and try again.",
|
|
error.FileSystemError => "File system error. Check disk space and permissions.",
|
|
error.ProcessError => "Process error. Try again or contact support.",
|
|
|
|
// WebSocket specific errors
|
|
error.InvalidURL => "Invalid server URL. Check your configuration.",
|
|
error.TLSNotSupported => "TLS (HTTPS) not supported in this build.",
|
|
error.ConnectionRefused => "Connection refused. Server may not be running.",
|
|
error.NetworkUnreachable => "Network unreachable. Check your internet connection.",
|
|
error.InvalidFrame => "Invalid WebSocket frame. Protocol error.",
|
|
error.EndpointNotFound => "WebSocket endpoint not found. Server may not be running or is misconfigured.",
|
|
error.ServerUnavailable => "Server is temporarily unavailable.",
|
|
error.HandshakeFailed => "WebSocket handshake failed.",
|
|
|
|
// Default fallback
|
|
else => "An unexpected error occurred. Please try again or contact support.",
|
|
};
|
|
}
|
|
|
|
/// Check if error is user-fixable
|
|
pub fn isUserFixable(err: anyerror) bool {
|
|
return switch (err) {
|
|
error.ConfigNotFound,
|
|
error.ConfigInvalid,
|
|
error.APIKeyMissing,
|
|
error.APIKeyInvalid,
|
|
error.InvalidArguments,
|
|
error.MissingCommit,
|
|
error.JobNotFound,
|
|
error.ResourceExists,
|
|
error.JobAlreadyRunning,
|
|
error.JobCancelled,
|
|
error.SyncFailed,
|
|
=> true,
|
|
|
|
error.ConnectionFailed,
|
|
error.ServerUnreachable,
|
|
error.AuthenticationFailed,
|
|
error.RequestTimeout,
|
|
error.ConnectionRefused,
|
|
error.NetworkUnreachable,
|
|
=> true,
|
|
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// Get suggestion for fixing the error
|
|
pub fn getSuggestion(err: anyerror) ?[]const u8 {
|
|
return switch (err) {
|
|
error.ConfigNotFound => "Run 'ml init' to create a configuration file.",
|
|
error.APIKeyMissing => "Add your API key to the configuration file.",
|
|
error.ConnectionFailed => "Start the API server with 'api-server' or check if it's running.",
|
|
error.AuthenticationFailed => "Verify your API key in the configuration.",
|
|
error.InvalidArguments => "Use 'ml <command> --help' for correct usage.",
|
|
error.MissingCommit => "Use --commit <id> to specify the commit ID for your job.",
|
|
error.JobNotFound => "List available jobs with 'ml status'.",
|
|
error.ResourceExists => "Use a different name or remove the existing resource.",
|
|
error.JobAlreadyRunning => "Wait for the current job to finish or cancel it first.",
|
|
error.JobCancelled => "The job was cancelled. You can restart it if needed.",
|
|
error.SyncFailed => "Check network connectivity and server status.",
|
|
else => null,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Error handler for CLI commands
|
|
pub const ErrorHandler = struct {
|
|
/// Send crash report to server for non-user-fixable errors
|
|
fn sendCrashReport() void {
|
|
return;
|
|
}
|
|
|
|
pub fn display(self: ErrorHandler, err: anyerror, context: ?[]const u8) void {
|
|
_ = self; // Self not used in current implementation
|
|
const message = ErrorMessages.getMessage(err);
|
|
const suggestion = ErrorMessages.getSuggestion(err);
|
|
|
|
colors.printError("Error: {s}\n", .{message});
|
|
|
|
if (context) |ctx| {
|
|
colors.printWarning("Context: {s}\n", .{ctx});
|
|
}
|
|
|
|
if (suggestion) |sug| {
|
|
colors.printInfo("Suggestion: {s}\n", .{sug});
|
|
}
|
|
|
|
if (ErrorMessages.isUserFixable(err)) {
|
|
colors.printInfo("This error can be fixed by updating your configuration.\n", .{});
|
|
}
|
|
}
|
|
|
|
pub fn handleCommandError(err: anyerror, command: []const u8) void {
|
|
_ = command; // TODO: Use command in crash report
|
|
// Send crash report for non-user-fixable errors
|
|
sendCrashReport();
|
|
|
|
const message = ErrorMessages.getMessage(err);
|
|
const suggestion = ErrorMessages.getSuggestion(err);
|
|
const is_fixable = ErrorMessages.isUserFixable(err);
|
|
|
|
colors.printError("Error: {s}\n", .{message});
|
|
|
|
if (suggestion) |sug| {
|
|
colors.printInfo("Suggestion: {s}\n", .{sug});
|
|
}
|
|
|
|
if (is_fixable) {
|
|
colors.printInfo("This is a user-fixable issue.\n", .{});
|
|
} else {
|
|
colors.printWarning("If this persists, check server logs or contact support.\n", .{});
|
|
}
|
|
|
|
// Exit with appropriate code
|
|
std.process.exit(if (is_fixable) 2 else 1);
|
|
}
|
|
|
|
pub fn handleNetworkError(err: anyerror, operation: []const u8) void {
|
|
std.debug.print("Network error during {s}: {s}\n", .{ operation, ErrorMessages.getMessage(err) });
|
|
|
|
if (ErrorMessages.getSuggestion(err)) |sug| {
|
|
std.debug.print("Try: {s}\n", .{sug});
|
|
}
|
|
|
|
std.process.exit(3);
|
|
}
|
|
|
|
pub fn handleConfigError(err: anyerror) void {
|
|
std.debug.print("Configuration error: {s}\n", .{ErrorMessages.getMessage(err)});
|
|
|
|
if (ErrorMessages.getSuggestion(err)) |sug| {
|
|
std.debug.print("Fix: {s}\n", .{sug});
|
|
}
|
|
|
|
std.process.exit(4);
|
|
}
|
|
};
|