From a1988de8b1c375108b2769bb69e1596e9c8e90d8 Mon Sep 17 00:00:00 2001 From: Jeremie Fraeys Date: Mon, 23 Feb 2026 14:09:49 -0500 Subject: [PATCH] style(cli): Standardize printUsage() formatting with tabs and ASCII symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace space-padding with consistent tab (\t) alignment in all printUsage() functions. Add ligature-friendly ASCII symbols: - => for results/outcomes (renders as ⇒ with ligatures) - ~> for modifications/changes (renders as ~> with ligatures) - -> for state transitions (renders as → with ligatures) - [OK] / [FAIL] for status indicators All symbols use ASCII 32-126 for xargs-safe, copy-pasteable output. --- cli/src/commands/annotate.zig | 27 ++- cli/src/commands/cancel.zig | 35 ++- cli/src/commands/compare.zig | 109 +++++---- cli/src/commands/dataset.zig | 105 ++++----- cli/src/commands/experiment.zig | 65 +++--- cli/src/commands/export_cmd.zig | 55 +++-- cli/src/commands/find.zig | 125 ++++------- cli/src/commands/init.zig | 40 ++-- cli/src/commands/jupyter.zig | 267 +++++++++++----------- cli/src/commands/log.zig | 20 +- cli/src/commands/prune.zig | 16 +- cli/src/commands/queue.zig | 378 +++++++++++++++----------------- cli/src/commands/run.zig | 19 +- cli/src/commands/status.zig | 26 +-- cli/src/commands/sync.zig | 23 +- cli/src/commands/validate.zig | 252 ++++++++------------- cli/src/commands/watch.zig | 21 +- 17 files changed, 709 insertions(+), 874 deletions(-) diff --git a/cli/src/commands/annotate.zig b/cli/src/commands/annotate.zig index 6989f45..140b72f 100644 --- a/cli/src/commands/annotate.zig +++ b/cli/src/commands/annotate.zig @@ -2,7 +2,6 @@ const std = @import("std"); const config = @import("../config.zig"); const db = @import("../db.zig"); const core = @import("../core.zig"); -const colors = @import("../utils/colors.zig"); const manifest_lib = @import("../manifest.zig"); /// Annotate command - unified metadata annotation @@ -16,7 +15,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { var command_args = try core.flags.parseCommon(allocator, args, &flags); defer command_args.deinit(allocator); - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); if (flags.help) { return printUsage(); @@ -96,7 +95,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { if (flags.json) { std.debug.print("{{\"success\":true,\"run_id\":\"{s}\",\"action\":\"note_added\"}}\n", .{run_id}); } else { - colors.printSuccess("✓ Added note to run {s}\n", .{run_id[0..8]}); + std.debug.print("Added note to run {s}\n", .{run_id[0..8]}); } } @@ -128,16 +127,16 @@ fn printUsage() !void { std.debug.print("Usage: ml annotate [options]\n\n", .{}); std.debug.print("Add metadata annotations to a run.\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --text Free-form annotation\n", .{}); - std.debug.print(" --hypothesis Research hypothesis\n", .{}); - std.debug.print(" --outcome Outcome: validates/refutes/inconclusive\n", .{}); - std.debug.print(" --confidence <0-1> Confidence in outcome\n", .{}); - std.debug.print(" --privacy Privacy: private/team/public\n", .{}); - std.debug.print(" --author Author of the annotation\n", .{}); - std.debug.print(" --help, -h Show this help\n", .{}); - std.debug.print(" --json Output structured JSON\n\n", .{}); + std.debug.print("\t--text \t\tFree-form annotation\n", .{}); + std.debug.print("\t--hypothesis \tResearch hypothesis\n", .{}); + std.debug.print("\t--outcome \tOutcome: validates/refutes/inconclusive\n", .{}); + std.debug.print("\t--confidence <0-1>\tConfidence in outcome\n", .{}); + std.debug.print("\t--privacy \tPrivacy: private/team/public\n", .{}); + std.debug.print("\t--author \t\tAuthor of the annotation\n", .{}); + std.debug.print("\t--help, -h\t\tShow this help\n", .{}); + std.debug.print("\t--json\t\t\tOutput structured JSON\n\n", .{}); std.debug.print("Examples:\n", .{}); - std.debug.print(" ml annotate abc123 --text \"Try lr=3e-4 next\"\n", .{}); - std.debug.print(" ml annotate abc123 --hypothesis \"LR scaling helps\"\n", .{}); - std.debug.print(" ml annotate abc123 --outcome validates --confidence 0.9\n", .{}); + std.debug.print("\tml annotate abc123 --text \"Try lr=3e-4 next\"\n", .{}); + std.debug.print("\tml annotate abc123 --hypothesis \"LR scaling helps\"\n", .{}); + std.debug.print("\tml annotate abc123 --outcome validates --confidence 0.9\n", .{}); } diff --git a/cli/src/commands/cancel.zig b/cli/src/commands/cancel.zig index e33ed58..6071d8b 100644 --- a/cli/src/commands/cancel.zig +++ b/cli/src/commands/cancel.zig @@ -3,7 +3,6 @@ const config = @import("../config.zig"); const db = @import("../db.zig"); const ws = @import("../net/ws/client.zig"); const crypto = @import("../utils/crypto.zig"); -const colors = @import("../utils/colors.zig"); const core = @import("../core.zig"); const mode = @import("../mode.zig"); const manifest_lib = @import("../manifest.zig"); @@ -25,17 +24,17 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { return printUsage(); } else if (std.mem.startsWith(u8, arg, "--")) { - core.output.errorMsg("cancel", "Unknown option"); + core.output.err("Unknown option"); return error.InvalidArgs; } else { try targets.append(allocator, arg); } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); if (targets.items.len == 0) { - core.output.errorMsg("cancel", "No run_id specified"); + core.output.err("No run_id specified"); return error.InvalidArgs; } @@ -59,7 +58,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { // Local mode: kill by PID cancelLocal(allocator, target, force, flags.json) catch |err| { if (!flags.json) { - colors.printError("Failed to cancel '{s}': {}\n", .{ target, err }); + std.debug.print("Failed to cancel '{s}': {}\n", .{ target, err }); } failed_count += 1; continue; @@ -68,7 +67,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { // Online mode: cancel on server cancelServer(allocator, target, force, flags.json, cfg) catch |err| { if (!flags.json) { - colors.printError("Failed to cancel '{s}': {}\n", .{ target, err }); + std.debug.print("Failed to cancel '{s}': {}\n", .{ target, err }); } failed_count += 1; continue; @@ -80,9 +79,9 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (flags.json) { std.debug.print("{{\"success\":true,\"canceled\":{d},\"failed\":{d}}}\n", .{ success_count, failed_count }); } else { - colors.printSuccess("Canceled {d} run(s)\n", .{success_count}); + std.debug.print("Canceled {d} run(s)\n", .{success_count}); if (failed_count > 0) { - colors.printError("Failed to cancel {d} run(s)\n", .{failed_count}); + std.debug.print("Failed to cancel {d} run(s)\n", .{failed_count}); } } } @@ -163,7 +162,7 @@ fn cancelLocal(allocator: std.mem.Allocator, run_id: []const u8, force: bool, js database.checkpointOnExit(); if (!json) { - colors.printSuccess("✓ Canceled run {s}\n", .{run_id[0..8]}); + std.debug.print("Canceled run {s}\n", .{run_id[0..8]}); } } @@ -200,13 +199,13 @@ fn cancelServer(allocator: std.mem.Allocator, job_name: []const u8, force: bool, } fn printUsage() !void { - colors.printInfo("Usage: ml cancel [options] [ ...]\n", .{}); - colors.printInfo("\nCancel a local run (kill process) or server job.\n\n", .{}); - colors.printInfo("Options:\n", .{}); - colors.printInfo(" --force Force cancel (SIGKILL immediately)\n", .{}); - colors.printInfo(" --json Output structured JSON\n", .{}); - colors.printInfo(" --help, -h Show this help message\n", .{}); - colors.printInfo("\nExamples:\n", .{}); - colors.printInfo(" ml cancel abc123 # Cancel local run by run_id\n", .{}); - colors.printInfo(" ml cancel --force abc123 # Force cancel\n", .{}); + std.debug.print("Usage: ml cancel [options] [ ...]\n", .{}); + std.debug.print("\nCancel a local run (kill process) or server job.\n\n", .{}); + std.debug.print("Options:\n", .{}); + std.debug.print("\t--force\t\tForce cancel (SIGKILL immediately)\n", .{}); + std.debug.print("\t--json\t\tOutput structured JSON\n", .{}); + std.debug.print("\t--help, -h\tShow this help message\n", .{}); + std.debug.print("\nExamples:\n", .{}); + std.debug.print("\tml cancel abc123\t# Cancel local run by run_id\n", .{}); + std.debug.print("\tml cancel --force abc123\t# Force cancel\n", .{}); } diff --git a/cli/src/commands/compare.zig b/cli/src/commands/compare.zig index cb45f4f..09b5c83 100644 --- a/cli/src/commands/compare.zig +++ b/cli/src/commands/compare.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const colors = @import("../utils/colors.zig"); const Config = @import("../config.zig").Config; const crypto = @import("../utils/crypto.zig"); const io = @import("../utils/io.zig"); @@ -47,12 +46,12 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { return printUsage(); } else { - core.output.errorMsg("compare", "Unknown option"); + core.output.err("Unknown option"); return error.InvalidArgs; } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); const cfg = try Config.load(allocator); defer { @@ -67,7 +66,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { defer allocator.free(ws_url); // Fetch both runs - colors.printInfo("Fetching run {s}...\n", .{run_a}); + std.debug.print("Fetching run {s}...\n", .{run_a}); var client_a = try ws.Client.connect(allocator, ws_url, cfg.api_key); defer client_a.close(); @@ -76,7 +75,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { const msg_a = try client_a.receiveMessage(allocator); defer allocator.free(msg_a); - colors.printInfo("Fetching run {s}...\n", .{run_b}); + std.debug.print("Fetching run {s}...\n", .{run_b}); var client_b = try ws.Client.connect(allocator, ws_url, cfg.api_key); defer client_b.close(); @@ -86,13 +85,13 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { // Parse responses const parsed_a = std.json.parseFromSlice(std.json.Value, allocator, msg_a, .{}) catch { - colors.printError("Failed to parse response for {s}\n", .{run_a}); + std.debug.print("Failed to parse response for {s}\n", .{run_a}); return error.InvalidResponse; }; defer parsed_a.deinit(); const parsed_b = std.json.parseFromSlice(std.json.Value, allocator, msg_b, .{}) catch { - colors.printError("Failed to parse response for {s}\n", .{run_b}); + std.debug.print("Failed to parse response for {s}\n", .{run_b}); return error.InvalidResponse; }; defer parsed_b.deinit(); @@ -102,11 +101,11 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { // Check for errors if (root_a.get("error")) |err_a| { - colors.printError("Error fetching {s}: {s}\n", .{ run_a, err_a.string }); + std.debug.print("Error fetching {s}: {s}\n", .{ run_a, err_a.string }); return error.ServerError; } if (root_b.get("error")) |err_b| { - colors.printError("Error fetching {s}: {s}\n", .{ run_b, err_b.string }); + std.debug.print("Error fetching {s}: {s}\n", .{ run_b, err_b.string }); return error.ServerError; } @@ -124,30 +123,30 @@ fn outputHumanComparison( run_b: []const u8, all_fields: bool, ) !void { - colors.printInfo("\n=== Comparison: {s} vs {s} ===\n\n", .{ run_a, run_b }); + std.debug.print("\n=== Comparison: {s} vs {s} ===\n\n", .{ run_a, run_b }); // Common fields const job_name_a = jsonGetString(root_a, "job_name") orelse "unknown"; const job_name_b = jsonGetString(root_b, "job_name") orelse "unknown"; if (!std.mem.eql(u8, job_name_a, job_name_b)) { - colors.printWarning("Job names differ:\n", .{}); - colors.printInfo(" {s}: {s}\n", .{ run_a, job_name_a }); - colors.printInfo(" {s}: {s}\n", .{ run_b, job_name_b }); + std.debug.print("Job names differ:\n", .{}); + std.debug.print("\t{s}: {s}\n", .{ run_a, job_name_a }); + std.debug.print("\t{s}: {s}\n", .{ run_b, job_name_b }); } else { - colors.printInfo("Job Name: {s}\n", .{job_name_a}); + std.debug.print("Job Name: {s}\n", .{job_name_a}); } // Experiment group const group_a = jsonGetString(root_a, "experiment_group") orelse ""; const group_b = jsonGetString(root_b, "experiment_group") orelse ""; if (group_a.len > 0 or group_b.len > 0) { - colors.printInfo("\nExperiment Group:\n", .{}); + std.debug.print("\nExperiment Group:\n", .{}); if (std.mem.eql(u8, group_a, group_b)) { - colors.printInfo(" Both: {s}\n", .{group_a}); + std.debug.print("\tBoth: {s}\n", .{group_a}); } else { - colors.printInfo(" {s}: {s}\n", .{ run_a, group_a }); - colors.printInfo(" {s}: {s}\n", .{ run_b, group_b }); + std.debug.print("\t{s}: {s}\n", .{ run_a, group_a }); + std.debug.print("\t{s}: {s}\n", .{ run_b, group_b }); } } @@ -156,7 +155,7 @@ fn outputHumanComparison( const narrative_b = root_b.get("narrative"); if (narrative_a != null or narrative_b != null) { - colors.printInfo("\n--- Narrative ---\n", .{}); + std.debug.print("\n--- Narrative ---\n", .{}); if (narrative_a) |na| { if (narrative_b) |nb| { @@ -164,10 +163,10 @@ fn outputHumanComparison( try compareNarrativeFields(na.object, nb.object, run_a, run_b); } } else { - colors.printInfo(" {s} has narrative, {s} does not\n", .{ run_a, run_b }); + std.debug.print("\t{s} has narrative, {s} does not\n", .{ run_a, run_b }); } } else if (narrative_b) |_| { - colors.printInfo(" {s} has narrative, {s} does not\n", .{ run_b, run_a }); + std.debug.print("\t{s} has narrative, {s} does not\n", .{ run_b, run_a }); } } @@ -178,7 +177,7 @@ fn outputHumanComparison( if (meta_a) |ma| { if (meta_b) |mb| { if (ma == .object and mb == .object) { - colors.printInfo("\n--- Metadata Differences ---\n", .{}); + std.debug.print("\n--- Metadata Differences ---\n", .{}); try compareMetadata(ma.object, mb.object, run_a, run_b, all_fields); } } @@ -191,7 +190,7 @@ fn outputHumanComparison( if (metrics_a) |ma| { if (metrics_b) |mb| { if (ma == .object and mb == .object) { - colors.printInfo("\n--- Metrics ---\n", .{}); + std.debug.print("\n--- Metrics ---\n", .{}); try compareMetrics(ma.object, mb.object, run_a, run_b); } } @@ -201,16 +200,16 @@ fn outputHumanComparison( const outcome_a = jsonGetString(root_a, "outcome") orelse ""; const outcome_b = jsonGetString(root_b, "outcome") orelse ""; if (outcome_a.len > 0 or outcome_b.len > 0) { - colors.printInfo("\n--- Outcome ---\n", .{}); + std.debug.print("\n--- Outcome ---\n", .{}); if (std.mem.eql(u8, outcome_a, outcome_b)) { - colors.printInfo(" Both: {s}\n", .{outcome_a}); + std.debug.print("\tBoth: {s}\n", .{outcome_a}); } else { - colors.printInfo(" {s}: {s}\n", .{ run_a, outcome_a }); - colors.printInfo(" {s}: {s}\n", .{ run_b, outcome_b }); + std.debug.print("\t{s}: {s}\n", .{ run_a, outcome_a }); + std.debug.print("\t{s}: {s}\n", .{ run_b, outcome_b }); } } - colors.printInfo("\n", .{}); + std.debug.print("\n", .{}); } fn outputJsonComparison( @@ -294,14 +293,14 @@ fn compareNarrativeFields( if (val_a != null and val_b != null) { if (!std.mem.eql(u8, val_a.?, val_b.?)) { - colors.printInfo(" {s}:\n", .{field}); - colors.printInfo(" {s}: {s}\n", .{ run_a, val_a.? }); - colors.printInfo(" {s}: {s}\n", .{ run_b, val_b.? }); + std.debug.print("\t{s}:\n", .{field}); + std.debug.print("\t\t{s}: {s}\n", .{ run_a, val_a.? }); + std.debug.print("\t\t{s}: {s}\n", .{ run_b, val_b.? }); } } else if (val_a != null) { - colors.printInfo(" {s}: only in {s}\n", .{ field, run_a }); + std.debug.print("\t{s}: only in {s}\n", .{ field, run_a }); } else if (val_b != null) { - colors.printInfo(" {s}: only in {s}\n", .{ field, run_b }); + std.debug.print("\t{s}: only in {s}\n", .{ field, run_b }); } } } @@ -326,22 +325,22 @@ fn compareMetadata( if (!std.mem.eql(u8, str_a, str_b)) { has_differences = true; - colors.printInfo(" {s}: {s} → {s}\n", .{ key, str_a, str_b }); + std.debug.print("\t{s}: {s} ~> {s}\n", .{ key, str_a, str_b }); } else if (show_all) { - colors.printInfo(" {s}: {s} (same)\n", .{ key, str_a }); + std.debug.print("\t{s}: {s} (same)\n", .{ key, str_a }); } } else if (show_all) { - colors.printInfo(" {s}: only in {s}\n", .{ key, run_a }); + std.debug.print("\t{s}: only in {s}\n", .{ key, run_a }); } } else if (mb.get(key)) |_| { if (show_all) { - colors.printInfo(" {s}: only in {s}\n", .{ key, run_b }); + std.debug.print("\t{s}: only in {s}\n", .{ key, run_b }); } } } if (!has_differences and !show_all) { - colors.printInfo(" (no significant differences in common metadata)\n", .{}); + std.debug.print("\t(no significant differences in common metadata)\n", .{}); } } @@ -366,9 +365,9 @@ fn compareMetrics( const diff = val_b - val_a; const percent = if (val_a != 0) (diff / val_a) * 100 else 0; - const arrow = if (diff > 0) "↑" else if (diff < 0) "↓" else "="; + const arrow = if (diff > 0) "+" else if (diff < 0) "-" else "="; - colors.printInfo(" {s}: {d:.4} → {d:.4} ({s}{d:.4}, {d:.1}%)\n", .{ + std.debug.print(" {s}: {d:.4} ~> {d:.4} ({s}{d:.4}, {d:.1}%)\n", .{ metric, val_a, val_b, arrow, @abs(diff), percent, }); } @@ -498,19 +497,19 @@ fn jsonValueToFloat(v: std.json.Value) f64 { } fn printUsage() !void { - colors.printInfo("Usage: ml compare [options]\n", .{}); - colors.printInfo("\nCompare two runs and show differences in:\n", .{}); - colors.printInfo(" - Job metadata (batch_size, learning_rate, etc.)\n", .{}); - colors.printInfo(" - Narrative fields (hypothesis, context, intent)\n", .{}); - colors.printInfo(" - Metrics (accuracy, loss, training_time)\n", .{}); - colors.printInfo(" - Outcome status\n", .{}); - colors.printInfo("\nOptions:\n", .{}); - colors.printInfo(" --json Output as JSON\n", .{}); - colors.printInfo(" --all Show all fields (including unchanged)\n", .{}); - colors.printInfo(" --fields Compare only specific fields\n", .{}); - colors.printInfo(" --help, -h Show this help\n", .{}); - colors.printInfo("\nExamples:\n", .{}); - colors.printInfo(" ml compare run_abc run_def\n", .{}); - colors.printInfo(" ml compare run_abc run_def --json\n", .{}); - colors.printInfo(" ml compare run_abc run_def --all\n", .{}); + std.debug.print("Usage: ml compare [options]\n", .{}); + std.debug.print("\nCompare two runs and show differences in:\n", .{}); + std.debug.print("\t- Job metadata (batch_size, learning_rate, etc.)\n", .{}); + std.debug.print("\t- Narrative fields (hypothesis, context, intent)\n", .{}); + std.debug.print("\t- Metrics (accuracy, loss, training_time)\n", .{}); + std.debug.print("\t- Outcome status\n", .{}); + std.debug.print("\nOptions:\n", .{}); + std.debug.print("\t--json\t\tOutput as JSON\n", .{}); + std.debug.print("\t--all\t\tShow all fields (including unchanged)\n", .{}); + std.debug.print("\t--fields \tCompare only specific fields\n", .{}); + std.debug.print("\t--help, -h\tShow this help\n", .{}); + std.debug.print("\nExamples:\n", .{}); + std.debug.print("\tml compare run_abc run_def\n", .{}); + std.debug.print("\tml compare run_abc run_def --json\n", .{}); + std.debug.print("\tml compare run_abc run_def --all\n", .{}); } diff --git a/cli/src/commands/dataset.zig b/cli/src/commands/dataset.zig index 431d38e..cbe6645 100644 --- a/cli/src/commands/dataset.zig +++ b/cli/src/commands/dataset.zig @@ -1,8 +1,6 @@ const std = @import("std"); const Config = @import("../config.zig").Config; const ws = @import("../net/ws/client.zig"); -const colors = @import("../utils/colors.zig"); -const logging = @import("../utils/logging.zig"); const crypto = @import("../utils/crypto.zig"); const core = @import("../core.zig"); const native_hash = @import("../native/hash.zig"); @@ -42,14 +40,14 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, arg, "--csv")) { csv = true; } else if (std.mem.startsWith(u8, arg, "--")) { - core.output.errorMsg("dataset", "Unknown option"); + core.output.err("Unknown option"); return printUsage(); } else { try positional.append(allocator, arg); } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); const action = positional.items[0]; @@ -87,7 +85,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } }, else => { - core.output.errorMsg("dataset", "Too many arguments"); + core.output.err("Too many arguments"); return error.InvalidArgs; }, } @@ -96,18 +94,18 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } fn printUsage() void { - colors.printInfo("Usage: ml dataset [options]\n", .{}); - colors.printInfo("\nActions:\n", .{}); - colors.printInfo(" list List registered datasets\n", .{}); - colors.printInfo(" register Register a dataset with URL\n", .{}); - colors.printInfo(" info Show dataset information\n", .{}); - colors.printInfo(" search Search datasets by name/description\n", .{}); - colors.printInfo(" verify Verify dataset integrity (auto-hashes)\n", .{}); - colors.printInfo("\nOptions:\n", .{}); - colors.printInfo(" --dry-run Show what would be requested\n", .{}); - colors.printInfo(" --validate Validate inputs only (no request)\n", .{}); - colors.printInfo(" --json Output machine-readable JSON\n", .{}); - colors.printInfo(" --help, -h Show this help message\n", .{}); + std.debug.print("Usage: ml dataset [options]\n\n", .{}); + std.debug.print("Actions:\n", .{}); + std.debug.print("\tlist\t\t\tList registered datasets\n", .{}); + std.debug.print("\tregister \tRegister a dataset with URL\n", .{}); + std.debug.print("\tinfo \t\tShow dataset information\n", .{}); + std.debug.print("\tsearch \t\tSearch datasets by name/description\n", .{}); + std.debug.print("\tverify \tVerify dataset integrity (auto-hashes)\n", .{}); + std.debug.print("\nOptions:\n", .{}); + std.debug.print("\t--dry-run\t\tShow what would be requested\n", .{}); + std.debug.print("\t--validate\t\tValidate inputs only (no request)\n", .{}); + std.debug.print("\t--json\t\t\tOutput machine-readable JSON\n", .{}); + std.debug.print("\t--help, -h\t\tShow this help message\n", .{}); } fn listDatasets(allocator: std.mem.Allocator, options: *const DatasetOptions) !void { @@ -128,7 +126,7 @@ fn listDatasets(allocator: std.mem.Allocator, options: *const DatasetOptions) !v const formatted = std.fmt.bufPrint(&buffer, "{{\"ok\":true,\"action\":\"list\",\"validated\":true}}\n", .{}) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Validation OK\n", .{}); + std.debug.print("Validation OK\n", .{}); } return; } @@ -146,7 +144,7 @@ fn listDatasets(allocator: std.mem.Allocator, options: *const DatasetOptions) !v const formatted = std.fmt.bufPrint(&buffer, "{{\"dry_run\":true,\"action\":\"list\"}}\n", .{}) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Dry run: would request dataset list\n", .{}); + std.debug.print("Dry run: would request dataset list\n", .{}); } return; } @@ -165,15 +163,13 @@ fn listDatasets(allocator: std.mem.Allocator, options: *const DatasetOptions) !v return; } - colors.printInfo("Registered Datasets:\n", .{}); - colors.printInfo("=====================\n\n", .{}); + std.debug.print("Registered Datasets:\n", .{}); // Parse and display datasets (simplified for now) if (std.mem.eql(u8, response, "[]")) { - colors.printWarning("No datasets registered.\n", .{}); - colors.printInfo("Use 'ml dataset register ' to add a dataset.\n", .{}); + std.debug.print("No datasets registered.\n", .{}); } else { - colors.printSuccess("{s}\n", .{response}); + std.debug.print("{s}\n", .{response}); } } @@ -204,7 +200,7 @@ fn registerDataset(allocator: std.mem.Allocator, name: []const u8, url: []const const formatted = std.fmt.bufPrint(&buffer, "{{\"ok\":true,\"action\":\"register\",\"validated\":true,\"name\":\"{s}\",\"url\":\"{s}\"}}\n", .{ name, url }) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Validation OK\n", .{}); + std.debug.print("Validation OK\n", .{}); } return; } @@ -213,7 +209,7 @@ fn registerDataset(allocator: std.mem.Allocator, name: []const u8, url: []const if (!std.mem.startsWith(u8, url, "http://") and !std.mem.startsWith(u8, url, "https://") and !std.mem.startsWith(u8, url, "s3://") and !std.mem.startsWith(u8, url, "gs://")) { - colors.printError("Invalid URL format. Supported: http://, https://, s3://, gs://\n", .{}); + std.debug.print("Invalid URL format. Supported: http://, https://, s3://, gs://\n", .{}); return error.InvalidURL; } @@ -226,7 +222,7 @@ fn registerDataset(allocator: std.mem.Allocator, name: []const u8, url: []const const formatted = std.fmt.bufPrint(&buffer, "{{\"dry_run\":true,\"action\":\"register\",\"name\":\"{s}\",\"url\":\"{s}\"}}\n", .{ name, url }) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Dry run: would register dataset '{s}' -> {s}\n", .{ name, url }); + std.debug.print("Dry run: would register dataset '{s}' -> {s}\n", .{ name, url }); } return; } @@ -252,10 +248,9 @@ fn registerDataset(allocator: std.mem.Allocator, name: []const u8, url: []const } if (std.mem.startsWith(u8, response, "ERROR")) { - colors.printError("Failed to register dataset: {s}\n", .{response}); + std.debug.print("Failed to register dataset: {s}\n", .{response}); } else { - colors.printSuccess("Dataset '{s}' registered successfully!\n", .{name}); - colors.printInfo("URL: {s}\n", .{url}); + std.debug.print("Dataset '{s}' registered\n", .{name}); } } @@ -277,7 +272,7 @@ fn showDatasetInfo(allocator: std.mem.Allocator, name: []const u8, options: *con const formatted = std.fmt.bufPrint(&buffer, "{{\"ok\":true,\"action\":\"info\",\"validated\":true,\"name\":\"{s}\"}}\n", .{name}) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Validation OK\n", .{}); + std.debug.print("Validation OK\n", .{}); } return; } @@ -291,7 +286,7 @@ fn showDatasetInfo(allocator: std.mem.Allocator, name: []const u8, options: *con const formatted = std.fmt.bufPrint(&buffer, "{{\"dry_run\":true,\"action\":\"info\",\"name\":\"{s}\"}}\n", .{name}) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Dry run: would request dataset info for '{s}'\n", .{name}); + std.debug.print("Dry run: would request dataset info for '{s}'\n", .{name}); } return; } @@ -317,12 +312,9 @@ fn showDatasetInfo(allocator: std.mem.Allocator, name: []const u8, options: *con } if (std.mem.startsWith(u8, response, "ERROR") or std.mem.startsWith(u8, response, "NOT_FOUND")) { - colors.printError("Dataset '{s}' not found.\n", .{name}); + std.debug.print("Dataset '{s}' not found.\n", .{name}); } else { - colors.printInfo("Dataset Information:\n", .{}); - colors.printInfo("===================\n", .{}); - colors.printSuccess("Name: {s}\n", .{name}); - colors.printSuccess("Details: {s}\n", .{response}); + std.debug.print("{s}\n", .{response}); } } @@ -344,7 +336,7 @@ fn searchDatasets(allocator: std.mem.Allocator, term: []const u8, options: *cons const formatted = std.fmt.bufPrint(&buffer, "{{\"ok\":true,\"action\":\"search\",\"validated\":true,\"term\":\"{s}\"}}\n", .{term}) catch unreachable; try stdout_file.writeAll(formatted); } else { - colors.printInfo("Validation OK\n", .{}); + std.debug.print("Validation OK\n", .{}); } return; } @@ -369,18 +361,15 @@ fn searchDatasets(allocator: std.mem.Allocator, term: []const u8, options: *cons return; } - colors.printInfo("Search Results for '{s}':\n", .{term}); - colors.printInfo("========================\n\n", .{}); - if (std.mem.eql(u8, response, "[]")) { - colors.printWarning("No datasets found matching '{s}'.\n", .{term}); + std.debug.print("No datasets found matching '{s}'.\n", .{term}); } else { - colors.printSuccess("{s}\n", .{response}); + std.debug.print("{s}\n", .{response}); } } fn verifyDataset(allocator: std.mem.Allocator, target: []const u8, options: *const DatasetOptions) !void { - colors.printInfo("Verifying dataset: {s}\n", .{target}); + std.debug.print("Verifying dataset: {s}\n", .{target}); const path = if (std.fs.path.isAbsolute(target)) target @@ -389,7 +378,7 @@ fn verifyDataset(allocator: std.mem.Allocator, target: []const u8, options: *con defer if (!std.fs.path.isAbsolute(target)) allocator.free(path); var dir = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch { - colors.printError("Dataset not found: {s}\n", .{target}); + std.debug.print("Dataset not found: {s}\n", .{target}); return error.FileNotFound; }; defer dir.close(); @@ -414,7 +403,7 @@ fn verifyDataset(allocator: std.mem.Allocator, target: []const u8, options: *con // Compute native SHA256 hash const hash = blk: { break :blk native_hash.hashDirectory(allocator, path) catch |err| { - colors.printWarning("Hash computation failed: {s}\n", .{@errorName(err)}); + std.debug.print("Hash computation failed: {s}\n", .{@errorName(err)}); // Continue without hash - verification still succeeded break :blk null; }; @@ -446,41 +435,39 @@ fn verifyDataset(allocator: std.mem.Allocator, target: []const u8, options: *con try stdout_file.writeAll(line5); } } else { - colors.printSuccess("✓ Dataset verified\n", .{}); - colors.printInfo(" Path: {s}\n", .{target}); - colors.printInfo(" Files: {d}\n", .{file_count}); - colors.printInfo(" Size: {d:.2} MB\n", .{@as(f64, @floatFromInt(total_size)) / (1024 * 1024)}); + std.debug.print("Dataset verified\n", .{}); + std.debug.print("{s}\t{d}\t{d:.2} MB\n", .{ target, file_count, @as(f64, @floatFromInt(total_size)) / (1024 * 1024) }); if (hash) |h| { - colors.printInfo(" SHA256: {s}\n", .{h}); + std.debug.print("SHA256\t{s}\n", .{h}); } } } fn hashDataset(allocator: std.mem.Allocator, path: []const u8) !void { - colors.printInfo("Computing native SHA256 hash for: {s}\n", .{path}); + std.debug.print("Computing native SHA256 hash for: {s}\n", .{path}); // Check SIMD availability if (!native_hash.hasSimdSha256()) { - colors.printWarning("SIMD SHA256 not available, using generic implementation\n", .{}); + std.debug.print("SIMD SHA256 not available, using generic implementation\n", .{}); } else { const impl_name = native_hash.getSimdImplName(); - colors.printInfo("Using {s} SHA256 implementation\n", .{impl_name}); + std.debug.print("Using {s} SHA256 implementation\n", .{impl_name}); } // Compute hash using native library const hash = native_hash.hashDirectory(allocator, path) catch |err| { switch (err) { error.ContextInitFailed => { - colors.printError("Failed to initialize native hash context\n", .{}); + std.debug.print("Failed to initialize native hash context\n", .{}); }, error.HashFailed => { - colors.printError("Hash computation failed\n", .{}); + std.debug.print("Hash computation failed\n", .{}); }, error.InvalidPath => { - colors.printError("Invalid path: {s}\n", .{path}); + std.debug.print("Invalid path: {s}\n", .{path}); }, error.OutOfMemory => { - colors.printError("Out of memory\n", .{}); + std.debug.print("Out of memory\n", .{}); }, } return err; @@ -488,7 +475,7 @@ fn hashDataset(allocator: std.mem.Allocator, path: []const u8) !void { defer allocator.free(hash); // Print result - colors.printSuccess("SHA256: {s}\n", .{hash}); + std.debug.print("SHA256: {s}\n", .{hash}); } fn writeJSONString(writer: anytype, s: []const u8) !void { diff --git a/cli/src/commands/experiment.zig b/cli/src/commands/experiment.zig index 77127a7..cdf64f6 100644 --- a/cli/src/commands/experiment.zig +++ b/cli/src/commands/experiment.zig @@ -2,7 +2,6 @@ const std = @import("std"); const config = @import("../config.zig"); const db = @import("../db.zig"); const core = @import("../core.zig"); -const colors = @import("../utils/colors.zig"); const mode = @import("../mode.zig"); const uuid = @import("../utils/uuid.zig"); const crypto = @import("../utils/crypto.zig"); @@ -35,7 +34,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { var command_args = try core.flags.parseCommon(allocator, args, &flags); defer command_args.deinit(allocator); - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); if (flags.help or command_args.items.len == 0) { return printUsage(); @@ -51,9 +50,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, subcommand, "show")) { return try showExperiment(allocator, sub_args, flags.json); } else { - const msg = try std.fmt.allocPrint(allocator, "Unknown subcommand: {s}", .{subcommand}); - defer allocator.free(msg); - core.output.errorMsg("experiment", msg); + core.output.err("Unknown subcommand"); return printUsage(); } } @@ -74,7 +71,7 @@ fn createExperiment(allocator: std.mem.Allocator, args: []const []const u8, json } if (name == null) { - core.output.errorMsg("experiment", "--name is required"); + core.output.err("--name is required"); return error.MissingArgument; } @@ -124,7 +121,7 @@ fn createExperiment(allocator: std.mem.Allocator, args: []const []const u8, json if (json) { std.debug.print("{{\"success\":true,\"experiment_id\":\"{s}\",\"name\":\"{s}\"}}\n", .{ exp_id, name.? }); } else { - colors.printSuccess("✓ Created experiment: {s} ({s})\n", .{ name.?, exp_id[0..8] }); + std.debug.print("Created experiment: {s} ({s})\n", .{ name.?, exp_id[0..8] }); } } else { // Server mode: send to server via WebSocket @@ -159,10 +156,10 @@ fn createExperiment(allocator: std.mem.Allocator, args: []const []const u8, json if (json) { std.debug.print("{{\"success\":true,\"name\":\"{s}\",\"source\":\"server\"}}\n", .{name.?}); } else { - colors.printSuccess("✓ Created experiment on server: {s}\n", .{name.?}); + std.debug.print("Created experiment on server: {s}\n", .{name.?}); } } else { - colors.printError("Failed to create experiment on server: {s}\n", .{response}); + std.debug.print("Failed to create experiment on server: {s}\n", .{response}); return error.ServerError; } } @@ -215,15 +212,11 @@ fn listExperiments(allocator: std.mem.Allocator, _: []const []const u8, json: bo std.debug.print("]\n", .{}); } else { if (experiments.items.len == 0) { - colors.printInfo("No experiments found.\n", .{}); + std.debug.print("No experiments found.\n", .{}); } else { - colors.printInfo("Experiments:\n", .{}); for (experiments.items) |e| { - const sync_indicator = if (e.synced) "✓" else "↑"; - std.debug.print(" {s} {s} {s} ({s})\n", .{ sync_indicator, e.id[0..8], e.name, e.status }); - if (e.description.len > 0) { - std.debug.print(" {s}\n", .{e.description}); - } + const sync_indicator = if (e.synced) "S" else "U"; + std.debug.print("{s}\t{s}\t{s}\t{s}\n", .{ sync_indicator, e.id[0..8], e.name, e.status }); } } } @@ -248,7 +241,6 @@ fn listExperiments(allocator: std.mem.Allocator, _: []const []const u8, json: bo if (json) { std.debug.print("{s}\n", .{response}); } else { - colors.printInfo("Experiments from server:\n", .{}); std.debug.print("{s}\n", .{response}); } } @@ -256,7 +248,7 @@ fn listExperiments(allocator: std.mem.Allocator, _: []const []const u8, json: bo fn showExperiment(allocator: std.mem.Allocator, args: []const []const u8, json: bool) !void { if (args.len == 0) { - core.output.errorMsg("experiment", "experiment_id required"); + core.output.err("experiment_id required"); return error.MissingArgument; } @@ -285,9 +277,7 @@ fn showExperiment(allocator: std.mem.Allocator, args: []const []const u8, json: try db.DB.bindText(exp_stmt, 1, exp_id); if (!try db.DB.step(exp_stmt)) { - const msg = try std.fmt.allocPrint(allocator, "Experiment not found: {s}", .{exp_id}); - defer allocator.free(msg); - core.output.errorMsg("experiment", msg); + core.output.err("Experiment not found"); return error.NotFound; } @@ -320,17 +310,15 @@ fn showExperiment(allocator: std.mem.Allocator, args: []const []const u8, json: if (synced) "true" else "false", run_count, last_run orelse "null", }); } else { - colors.printInfo("Experiment: {s}\n", .{name}); - std.debug.print(" ID: {s}\n", .{exp_id}); - std.debug.print(" Status: {s}\n", .{status}); + std.debug.print("{s}\t{s}\t{s}\n", .{ name, exp_id, status }); if (description.len > 0) { - std.debug.print(" Description: {s}\n", .{description}); + std.debug.print("Description\t{s}\n", .{description}); } - std.debug.print(" Created: {s}\n", .{created_at}); - std.debug.print(" Synced: {s}\n", .{if (synced) "✓" else "↑ pending"}); - std.debug.print(" Runs: {d}\n", .{run_count}); + std.debug.print("Created\t{s}\n", .{created_at}); + std.debug.print("Synced\t{s}\n", .{if (synced) "yes" else "no"}); + std.debug.print("Runs\t{d}\n", .{run_count}); if (last_run) |lr| { - std.debug.print(" Last run: {s}\n", .{lr}); + std.debug.print("LastRun\t{s}\n", .{lr}); } } } else { @@ -353,7 +341,6 @@ fn showExperiment(allocator: std.mem.Allocator, args: []const []const u8, json: if (json) { std.debug.print("{s}\n", .{response}); } else { - colors.printInfo("Experiment details from server:\n", .{}); std.debug.print("{s}\n", .{response}); } } @@ -366,15 +353,15 @@ fn generateExperimentID(allocator: std.mem.Allocator) ![]const u8 { fn printUsage() !void { std.debug.print("Usage: ml experiment [options]\n\n", .{}); std.debug.print("Subcommands:\n", .{}); - std.debug.print(" create --name [--description ] Create new experiment\n", .{}); - std.debug.print(" list List experiments\n", .{}); - std.debug.print(" show Show experiment details\n", .{}); + std.debug.print("\tcreate --name [--description ]\tCreate new experiment\n", .{}); + std.debug.print("\tlist\t\t\t\t\t\tList experiments\n", .{}); + std.debug.print("\tshow \t\t\t\tShow experiment details\n", .{}); std.debug.print("\nOptions:\n", .{}); - std.debug.print(" --name Experiment name (required for create)\n", .{}); - std.debug.print(" --description Experiment description\n", .{}); - std.debug.print(" --help, -h Show this help\n", .{}); - std.debug.print(" --json Output structured JSON\n\n", .{}); + std.debug.print("\t--name \t\tExperiment name (required for create)\n", .{}); + std.debug.print("\t--description \tExperiment description\n", .{}); + std.debug.print("\t--help, -h\t\tShow this help\n", .{}); + std.debug.print("\t--json\t\t\tOutput structured JSON\n\n", .{}); std.debug.print("Examples:\n", .{}); - std.debug.print(" ml experiment create --name \"baseline-cnn\"\n", .{}); - std.debug.print(" ml experiment list\n", .{}); + std.debug.print("\tml experiment create --name \"baseline-cnn\"\n", .{}); + std.debug.print("\tml experiment list\n", .{}); } diff --git a/cli/src/commands/export_cmd.zig b/cli/src/commands/export_cmd.zig index 3ec79f5..4b09369 100644 --- a/cli/src/commands/export_cmd.zig +++ b/cli/src/commands/export_cmd.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const colors = @import("../utils/colors.zig"); const Config = @import("../config.zig").Config; const crypto = @import("../utils/crypto.zig"); const io = @import("../utils/io.zig"); @@ -52,18 +51,18 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { } else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { return printUsage(); } else { - core.output.errorMsg("export", "Unknown option"); + core.output.err("Unknown option"); return error.InvalidArgs; } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); // Validate anonymize level if (!std.mem.eql(u8, anonymize_level, "metadata-only") and !std.mem.eql(u8, anonymize_level, "full")) { - core.output.errorMsg("export", "Invalid anonymize level"); + core.output.err("Invalid anonymize level"); return error.InvalidArgs; } @@ -71,7 +70,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { var stdout_writer = io.stdoutWriter(); try stdout_writer.print("{{\"success\":true,\"anonymize_level\":\"{s}\"}}\n", .{anonymize_level}); } else { - colors.printInfo("Anonymization level: {s}\n", .{anonymize_level}); + std.debug.print("Anonymization level: {s}\n", .{anonymize_level}); } const cfg = try Config.load(allocator); @@ -83,7 +82,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { const resolved_base = base_override orelse cfg.worker_base; const manifest_path = manifest.resolvePathWithBase(allocator, target, resolved_base) catch |err| { if (err == error.FileNotFound) { - colors.printError( + std.debug.print( "Could not locate run_manifest.json for '{s}'.\n", .{target}, ); @@ -94,14 +93,14 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { // Read the manifest const manifest_content = manifest.readFileAlloc(allocator, manifest_path) catch |err| { - colors.printError("Failed to read manifest: {}\n", .{err}); + std.debug.print("Failed to read manifest: {}\n", .{err}); return err; }; defer allocator.free(manifest_content); // Parse the manifest const parsed = std.json.parseFromSlice(std.json.Value, allocator, manifest_content, .{}) catch |err| { - colors.printError("Failed to parse manifest: {}\n", .{err}); + std.debug.print("Failed to parse manifest: {}\n", .{err}); return err; }; defer parsed.deinit(); @@ -134,10 +133,10 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { anonymize, }); } else { - colors.printSuccess("✓ Exported to {s}\n", .{bundle_path}); + std.debug.print("Exported to {s}\n", .{bundle_path}); if (anonymize) { - colors.printInfo(" Anonymization level: {s}\n", .{anonymize_level}); - colors.printInfo(" Paths redacted, IPs removed, usernames anonymized\n", .{}); + std.debug.print("\tAnonymization level: {s}\n", .{anonymize_level}); + std.debug.print("\tPaths redacted, IPs removed, usernames anonymized\n", .{}); } } } else { @@ -265,23 +264,23 @@ fn anonymizePath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { } fn printUsage() !void { - colors.printInfo("Usage: ml export [options]\n", .{}); - colors.printInfo("\nExport experiment for sharing or archiving:\n", .{}); - colors.printInfo(" --bundle Create tarball at path\n", .{}); - colors.printInfo(" --anonymize Enable anonymization\n", .{}); - colors.printInfo(" --anonymize-level 'metadata-only' or 'full'\n", .{}); - colors.printInfo(" --base Base path to find run\n", .{}); - colors.printInfo(" --json Output JSON response\n", .{}); - colors.printInfo("\nAnonymization rules:\n", .{}); - colors.printInfo(" - Paths: /nas/private/... → /datasets/...\n", .{}); - colors.printInfo(" - Hostnames: gpu-server-01 → worker-A\n", .{}); - colors.printInfo(" - IPs: 192.168.1.100 → [REDACTED]\n", .{}); - colors.printInfo(" - Usernames: user@lab.edu → [REDACTED]\n", .{}); - colors.printInfo(" - Full level: Also removes logs and annotations\n", .{}); - colors.printInfo("\nExamples:\n", .{}); - colors.printInfo(" ml export run_abc --bundle run_abc.tar.gz\n", .{}); - colors.printInfo(" ml export run_abc --bundle run_abc.tar.gz --anonymize\n", .{}); - colors.printInfo(" ml export run_abc --anonymize --anonymize-level full\n", .{}); + std.debug.print("Usage: ml export [options]\n", .{}); + std.debug.print("\nExport experiment for sharing or archiving:\n", .{}); + std.debug.print("\t--bundle \t\tCreate tarball at path\n", .{}); + std.debug.print("\t--anonymize\t\tEnable anonymization\n", .{}); + std.debug.print("\t--anonymize-level \t'metadata-only' or 'full'\n", .{}); + std.debug.print("\t--base \t\tBase path to find run\n", .{}); + std.debug.print("\t--json\t\t\tOutput JSON response\n", .{}); + std.debug.print("\nAnonymization rules:\n", .{}); + std.debug.print("\t- Paths: /nas/private/... → /datasets/...\n", .{}); + std.debug.print("\t- Hostnames: gpu-server-01 → worker-A\n", .{}); + std.debug.print("\t- IPs: 192.168.1.100 → [REDACTED]\n", .{}); + std.debug.print("\t- Usernames: user@lab.edu → [REDACTED]\n", .{}); + std.debug.print("\t- Full level: Also removes logs and annotations\n", .{}); + std.debug.print("\nExamples:\n", .{}); + std.debug.print("\tml export run_abc --bundle run_abc.tar.gz\n", .{}); + std.debug.print("\tml export run_abc --bundle run_abc.tar.gz --anonymize\n", .{}); + std.debug.print("\tml export run_abc --anonymize --anonymize-level full\n", .{}); } fn writeJSONValue(writer: anytype, v: std.json.Value) !void { diff --git a/cli/src/commands/find.zig b/cli/src/commands/find.zig index 02a7c22..cbf88a0 100644 --- a/cli/src/commands/find.zig +++ b/cli/src/commands/find.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const colors = @import("../utils/colors.zig"); const Config = @import("../config.zig").Config; const crypto = @import("../utils/crypto.zig"); const io = @import("../utils/io.zig"); @@ -81,7 +80,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { before = argv[i + 1]; i += 1; } else { - core.output.errorMsg("find", "Unknown option"); + core.output.err("Unknown option"); return error.InvalidArgs; } } @@ -97,7 +96,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { const ws_url = try cfg.getWebSocketUrl(allocator); defer allocator.free(ws_url); - colors.printInfo("Searching experiments...\n", .{}); + std.debug.print("Searching experiments...\n", .{}); var client = try ws.Client.connect(allocator, ws_url, cfg.api_key); defer client.close(); @@ -133,7 +132,7 @@ pub fn run(allocator: std.mem.Allocator, argv: []const []const u8) !void { var out = io.stdoutWriter(); try out.print("{{\"error\":\"invalid_response\"}}\n", .{}); } else { - colors.printError("Failed to parse search results\n", .{}); + std.debug.print("Failed to parse search results\n", .{}); } return error.InvalidResponse; }; @@ -225,9 +224,10 @@ fn buildSearchJson(allocator: std.mem.Allocator, options: *const FindOptions) ![ return buf.toOwnedSlice(allocator); } -fn outputHumanResults(root: std.json.Value, options: *const FindOptions) !void { +fn outputHumanResults(root: std.json.Value, _options: *const FindOptions) !void { + _ = _options; if (root != .object) { - colors.printError("Invalid response format\n", .{}); + std.debug.print("Invalid response format\n", .{}); return; } @@ -236,37 +236,29 @@ fn outputHumanResults(root: std.json.Value, options: *const FindOptions) !void { // Check for error if (obj.get("error")) |err| { if (err == .string) { - colors.printError("Search error: {s}\n", .{err.string}); + std.debug.print("Search error: {s}\n", .{err.string}); } return; } const results = obj.get("results") orelse obj.get("experiments") orelse obj.get("runs"); if (results == null) { - colors.printInfo("No results found\n", .{}); + std.debug.print("No results found\n", .{}); return; } if (results.? != .array) { - colors.printError("Invalid results format\n", .{}); + std.debug.print("Invalid results format\n", .{}); return; } const items = results.?.array.items; if (items.len == 0) { - colors.printInfo("No experiments found matching your criteria\n", .{}); + std.debug.print("No experiments found matching your criteria\n", .{}); return; } - colors.printSuccess("Found {d} experiment(s)\n\n", .{items.len}); - - // Print header - colors.printInfo("{s:12} {s:20} {s:15} {s:10} {s}\n", .{ - "ID", "Job Name", "Outcome", "Status", "Group/Tags", - }); - colors.printInfo("{s}\n", .{"────────────────────────────────────────────────────────────────────────────────"}); - for (items) |item| { if (item != .object) continue; const run_obj = item.object; @@ -274,72 +266,43 @@ fn outputHumanResults(root: std.json.Value, options: *const FindOptions) !void { const id = jsonGetString(run_obj, "id") orelse jsonGetString(run_obj, "run_id") orelse "unknown"; const short_id = if (id.len > 8) id[0..8] else id; - const job_name = jsonGetString(run_obj, "job_name") orelse "unnamed"; - const job_display = if (job_name.len > 18) job_name[0..18] else job_name; + const job_name = jsonGetString(run_obj, "job_name") orelse ""; const outcome = jsonGetString(run_obj, "outcome") orelse "-"; const status = jsonGetString(run_obj, "status") orelse "unknown"; - // Build group/tags summary - var summary_buf: [30]u8 = undefined; - const summary = blk: { + // Build group/tags field + var group_tags_buf: [100]u8 = undefined; + const group_tags = blk: { const group = jsonGetString(run_obj, "experiment_group"); const tags = run_obj.get("tags"); if (group) |g| { if (tags) |t| { if (t == .string) { - break :blk std.fmt.bufPrint(&summary_buf, "{s}/{s}", .{ g[0..@min(g.len, 10)], t.string[0..@min(t.string.len, 10)] }) catch g[0..@min(g.len, 15)]; + break :blk std.fmt.bufPrint(&group_tags_buf, "{s}/{s}", .{ g, t.string }) catch g; } } - break :blk g[0..@min(g.len, 20)]; + break :blk g; } - break :blk "-"; + if (tags) |t| { + if (t == .string) break :blk t.string; + } + break :blk ""; }; - // Color code by outcome - if (std.mem.eql(u8, outcome, "validates")) { - colors.printSuccess("{s:12} {s:20} {s:15} {s:10} {s}\n", .{ - short_id, job_display, outcome, status, summary, - }); - } else if (std.mem.eql(u8, outcome, "refutes")) { - colors.printError("{s:12} {s:20} {s:15} {s:10} {s}\n", .{ - short_id, job_display, outcome, status, summary, - }); - } else if (std.mem.eql(u8, outcome, "partial") or std.mem.eql(u8, outcome, "inconclusive")) { - colors.printWarning("{s:12} {s:20} {s:15} {s:10} {s}\n", .{ - short_id, job_display, outcome, status, summary, - }); - } else { - colors.printInfo("{s:12} {s:20} {s:15} {s:10} {s}\n", .{ - short_id, job_display, outcome, status, summary, - }); - } - - // Show hypothesis if available and query matches - if (options.query) |_| { - if (run_obj.get("narrative")) |narr| { - if (narr == .object) { - if (narr.object.get("hypothesis")) |h| { - if (h == .string and h.string.len > 0) { - const hypo = h.string; - const display = if (hypo.len > 50) hypo[0..50] else hypo; - colors.printInfo(" ↳ {s}...\n", .{display}); - } - } - } - } - } + // TSV output: id => outcome | status | job_name | group_tags + std.debug.print("{s} => {s}\t{s}\t{s}\t{s}\n", .{ + short_id, outcome, status, job_name, group_tags, + }); } - - colors.printInfo("\nUse 'ml info ' for details, 'ml compare ' to compare runs\n", .{}); } fn outputCsvResults(allocator: std.mem.Allocator, root: std.json.Value, options: *const FindOptions) !void { _ = options; if (root != .object) { - colors.printError("Invalid response format\n", .{}); + std.debug.print("Invalid response format\n", .{}); return; } @@ -348,7 +311,7 @@ fn outputCsvResults(allocator: std.mem.Allocator, root: std.json.Value, options: // Check for error if (obj.get("error")) |err| { if (err == .string) { - colors.printError("Search error: {s}\n", .{err.string}); + std.debug.print("Search error: {s}\n", .{err.string}); } return; } @@ -486,22 +449,22 @@ fn hexDigit(v: u8) u8 { } fn printUsage() !void { - colors.printInfo("Usage: ml find [query] [options]\n", .{}); - colors.printInfo("\nSearch experiments by:\n", .{}); - colors.printInfo(" Query (free text): ml find \"hypothesis: warmup\"\n", .{}); - colors.printInfo(" Tags: ml find --tag ablation\n", .{}); - colors.printInfo(" Outcome: ml find --outcome validates\n", .{}); - colors.printInfo(" Dataset: ml find --dataset imagenet\n", .{}); - colors.printInfo(" Experiment group: ml find --experiment-group lr-scaling\n", .{}); - colors.printInfo(" Author: ml find --author user@lab.edu\n", .{}); - colors.printInfo(" Time range: ml find --after 2024-01-01 --before 2024-03-01\n", .{}); - colors.printInfo("\nOptions:\n", .{}); - colors.printInfo(" --limit Max results (default: 20)\n", .{}); - colors.printInfo(" --json Output as JSON\n", .{}); - colors.printInfo(" --csv Output as CSV\n", .{}); - colors.printInfo(" --help, -h Show this help\n", .{}); - colors.printInfo("\nExamples:\n", .{}); - colors.printInfo(" ml find --tag ablation --outcome validates\n", .{}); - colors.printInfo(" ml find --experiment-group batch-scaling --json\n", .{}); - colors.printInfo(" ml find \"learning rate\" --after 2024-01-01\n", .{}); + std.debug.print("Usage: ml find [query] [options]\n", .{}); + std.debug.print("\nSearch experiments by:\n", .{}); + std.debug.print("\tQuery (free text):\tml find \"hypothesis: warmup\"\n", .{}); + std.debug.print("\tTags:\t\t\tml find --tag ablation\n", .{}); + std.debug.print("\tOutcome:\t\tml find --outcome validates\n", .{}); + std.debug.print("\tDataset:\t\tml find --dataset imagenet\n", .{}); + std.debug.print("\tExperiment group:\tml find --experiment-group lr-scaling\n", .{}); + std.debug.print("\tAuthor:\t\t\tml find --author user@lab.edu\n", .{}); + std.debug.print("\tTime range:\t\tml find --after 2024-01-01 --before 2024-03-01\n", .{}); + std.debug.print("\nOptions:\n", .{}); + std.debug.print("\t--limit \tMax results (default: 20)\n", .{}); + std.debug.print("\t--json\t\tOutput as JSON\n", .{}); + std.debug.print("\t--csv\t\tOutput as CSV\n", .{}); + std.debug.print("\t--help, -h\tShow this help\n", .{}); + std.debug.print("\nExamples:\n", .{}); + std.debug.print("\tml find --tag ablation --outcome validates\n", .{}); + std.debug.print("\tml find --experiment-group batch-scaling --json\n", .{}); + std.debug.print("\tml find \"learning rate\" --after 2024-01-01\n", .{}); } diff --git a/cli/src/commands/init.zig b/cli/src/commands/init.zig index 78715cd..ccf4cf1 100644 --- a/cli/src/commands/init.zig +++ b/cli/src/commands/init.zig @@ -8,7 +8,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var remaining = try core.flags.parseCommon(allocator, args, &flags); defer remaining.deinit(allocator); - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); // Handle help flag early if (flags.help) { @@ -26,31 +26,31 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { // Print resolved config std.debug.print("Resolved config:\n", .{}); - std.debug.print(" tracking_uri = {s}", .{cfg.tracking_uri}); + std.debug.print("\ttracking_uri = {s}", .{cfg.tracking_uri}); // Indicate if using default if (cli_tracking_uri == null and std.mem.eql(u8, cfg.tracking_uri, "sqlite://./fetch_ml.db")) { - std.debug.print(" (default)\n", .{}); + std.debug.print("\t(default)\n", .{}); } else { std.debug.print("\n", .{}); } - std.debug.print(" artifact_path = {s}", .{cfg.artifact_path}); + std.debug.print("\tartifact_path = {s}", .{cfg.artifact_path}); if (cli_artifact_path == null and std.mem.eql(u8, cfg.artifact_path, "./experiments/")) { - std.debug.print(" (default)\n", .{}); + std.debug.print("\t(default)\n", .{}); } else { std.debug.print("\n", .{}); } - std.debug.print(" sync_uri = {s}\n", .{if (cfg.sync_uri.len > 0) cfg.sync_uri else "(not set)"}); + std.debug.print("\tsync_uri = {s}\n", .{if (cfg.sync_uri.len > 0) cfg.sync_uri else "(not set)"}); std.debug.print("\n", .{}); // Default path: create config only (no DB speculatively) if (!force_local) { - std.debug.print("✓ Created .fetchml/config.toml\n", .{}); - std.debug.print(" Local tracking DB will be created automatically if server becomes unavailable.\n", .{}); + std.debug.print("Created .fetchml/config.toml\n", .{}); + std.debug.print("\tLocal tracking DB will be created automatically if server becomes unavailable.\n", .{}); if (cfg.sync_uri.len > 0) { - std.debug.print(" Server: {s}:{d}\n", .{ cfg.worker_host, cfg.worker_port }); + std.debug.print("\tServer: {s}:{d}\n", .{ cfg.worker_host, cfg.worker_port }); } return; } @@ -71,7 +71,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { }; if (db_exists) { - std.debug.print("✓ Database already exists: {s}\n", .{db_path}); + std.debug.print("Database already exists: {s}\n", .{db_path}); } else { // Create parent directories if needed if (std.fs.path.dirname(db_path)) |dir| { @@ -88,22 +88,22 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { defer database.close(); defer database.checkpointOnExit(); - std.debug.print("✓ Created database: {s}\n", .{db_path}); + std.debug.print("Created database: {s}\n", .{db_path}); } - std.debug.print("✓ Created .fetchml/config.toml\n", .{}); - std.debug.print("✓ Schema applied (WAL mode enabled)\n", .{}); - std.debug.print(" fetch_ml.db-wal and fetch_ml.db-shm will appear during use — expected.\n", .{}); - std.debug.print(" The DB is just a file. Delete it freely — recreated automatically on next run.\n", .{}); + std.debug.print("Created .fetchml/config.toml\n", .{}); + std.debug.print("Schema applied (WAL mode enabled)\n", .{}); + std.debug.print("\tfetch_ml.db-wal and fetch_ml.db-shm will appear during use — expected.\n", .{}); + std.debug.print("\tThe DB is just a file. Delete it freely — recreated automatically on next run.\n", .{}); } fn printUsage() void { std.debug.print("Usage: ml init [OPTIONS]\n\n", .{}); std.debug.print("Initialize FetchML configuration\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --local Create local database now (default: config only)\n", .{}); - std.debug.print(" --tracking-uri URI SQLite database path (e.g., sqlite://./fetch_ml.db)\n", .{}); - std.debug.print(" --artifact-path PATH Artifacts directory (default: ./experiments/)\n", .{}); - std.debug.print(" --sync-uri URI Server to sync with (e.g., wss://ml.company.com/ws)\n", .{}); - std.debug.print(" -h, --help Show this help\n", .{}); + std.debug.print("\t--local\t\t\tCreate local database now (default: config only)\n", .{}); + std.debug.print("\t--tracking-uri URI\tSQLite database path (e.g., sqlite://./fetch_ml.db)\n", .{}); + std.debug.print("\t--artifact-path PATH\tArtifacts directory (default: ./experiments/)\n", .{}); + std.debug.print("\t--sync-uri URI\t\tServer to sync with (e.g., wss://ml.company.com/ws)\n", .{}); + std.debug.print("\t-h, --help\t\tShow this help\n", .{}); } diff --git a/cli/src/commands/jupyter.zig b/cli/src/commands/jupyter.zig index 40fd612..bd2e6f2 100644 --- a/cli/src/commands/jupyter.zig +++ b/cli/src/commands/jupyter.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const colors = @import("../utils/colors.zig"); const ws = @import("../net/ws/client.zig"); const protocol = @import("../net/protocol.zig"); const crypto = @import("../utils/crypto.zig"); @@ -27,7 +26,7 @@ fn validatePackageName(name: []const u8) bool { fn restoreJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: bool) !void { _ = json; if (args.len < 1) { - core.output.errorMsg("jupyter.restore", "Usage: ml jupyter restore "); + core.output.err("Usage: ml jupyter restore "); return; } const name = args[0]; @@ -42,7 +41,7 @@ fn restoreJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: defer allocator.free(url); var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -50,21 +49,21 @@ fn restoreJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: const api_key_hash = try crypto.hashApiKey(allocator, config.api_key); defer allocator.free(api_key_hash); - core.output.info("Restoring workspace {s}...", .{name}); + std.debug.print("Restoring workspace {s}...", .{name}); - client.sendRestoreJupyter(name, api_key_hash) catch |err| { - core.output.errorMsgDetailed("jupyter.restore", "Failed to send restore command", @errorName(err)); + client.sendRestoreJupyter(name, api_key_hash) catch { + core.output.err("Failed to send restore command"); return; }; const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); @@ -72,17 +71,17 @@ fn restoreJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: switch (packet.packet_type) { .success => { if (packet.success_message) |msg| { - core.output.info("{s}", .{msg}); + std.debug.print("{s}", .{msg}); } else { - core.output.info("Workspace restored.", .{}); + std.debug.print("Workspace restored.", .{}); } }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - core.output.errorMsgDetailed("jupyter.restore", error_msg, packet.error_details orelse packet.error_message orelse ""); + std.debug.print("Error: {s}\n", .{error_msg}); }, else => { - core.output.errorMsg("jupyter.restore", "Unexpected response type"); + core.output.err("Unexpected response type"); }, } } @@ -170,7 +169,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, sub, "uninstall")) { return uninstallJupyter(allocator, args[1..]); } else { - core.output.errorMsg("jupyter", "Unknown subcommand"); + core.output.err("Unknown subcommand"); return error.InvalidArgs; } } @@ -178,27 +177,27 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { fn printUsage() !void { std.debug.print("Usage: ml jupyter [args]\n", .{}); std.debug.print("\nCommands:\n", .{}); - std.debug.print(" list List Jupyter services\n", .{}); - std.debug.print(" status Show Jupyter service status\n", .{}); - std.debug.print(" launch Launch a new Jupyter service\n", .{}); - std.debug.print(" terminate Terminate a Jupyter service\n", .{}); - std.debug.print(" save Save workspace\n", .{}); - std.debug.print(" restore Restore workspace\n", .{}); - std.debug.print(" install Install packages\n", .{}); - std.debug.print(" uninstall Uninstall packages\n", .{}); + std.debug.print("\tlist\t\tList Jupyter services\n", .{}); + std.debug.print("\tstatus\t\tShow Jupyter service status\n", .{}); + std.debug.print("\tlaunch\t\tLaunch a new Jupyter service\n", .{}); + std.debug.print("\tterminate\tTerminate a Jupyter service\n", .{}); + std.debug.print("\tsave\t\tSave workspace\n", .{}); + std.debug.print("\trestore\t\tRestore workspace\n", .{}); + std.debug.print("\tinstall\t\tInstall packages\n", .{}); + std.debug.print("\tuninstall\tUninstall packages\n", .{}); } fn printUsagePackage() void { - colors.printError("Usage: ml jupyter package [options]\n", .{}); - core.output.info("Actions:\n", .{}); - core.output.info("{s}", .{}); - colors.printInfo("Options:\n", .{}); - colors.printInfo(" --help, -h Show this help message\n", .{}); + std.debug.print("Usage: ml jupyter package [options]\n", .{}); + std.debug.print("Actions:\n", .{}); + std.debug.print("{s}", .{}); + std.debug.print("Options:\n", .{}); + std.debug.print("\t--help, -h Show this help message\n", .{}); } fn createJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter create [--path ] [--password ]\n", .{}); + std.debug.print("Usage: ml jupyter create [--path ] [--password ]\n", .{}); return; } @@ -226,17 +225,17 @@ fn createJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { } if (!validateWorkspacePath(workspace_path)) { - colors.printError("Invalid workspace path\n", .{}); + std.debug.print("Invalid workspace path\n", .{}); return error.InvalidArgs; } std.fs.cwd().makePath(workspace_path) catch |err| { - colors.printError("Failed to create workspace directory: {}\n", .{err}); + std.debug.print("Failed to create workspace directory: {}\n", .{err}); return; }; var start_args = std.ArrayList([]const u8).initCapacity(allocator, 8) catch |err| { - colors.printError("Failed to allocate args: {}\n", .{err}); + std.debug.print("Failed to allocate args: {}\n", .{err}); return; }; defer start_args.deinit(allocator); @@ -284,7 +283,7 @@ fn startJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { // Connect to WebSocket var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -293,53 +292,53 @@ fn startJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { const api_key_hash = try crypto.hashApiKey(allocator, config.api_key); defer allocator.free(api_key_hash); - colors.printInfo("Starting Jupyter service '{s}'...\n", .{name}); + std.debug.print("Starting Jupyter service '{s}'...\n", .{name}); // Send start command client.sendStartJupyter(name, workspace, password, api_key_hash) catch |err| { - colors.printError("Failed to send start command: {}\n", .{err}); + std.debug.print("Failed to send start command: {}\n", .{err}); return; }; // Receive response const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); // Parse response packet const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); switch (packet.packet_type) { .success => { - colors.printSuccess("Jupyter service started!\n", .{}); + std.debug.print("Jupyter service started!\n", .{}); if (packet.success_message) |msg| { std.debug.print("{s}\n", .{msg}); } }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - colors.printError("Failed to start service: {s}\n", .{error_msg}); + std.debug.print("Failed to start service: {s}\n", .{error_msg}); if (packet.error_details) |details| { - colors.printError("Details: {s}\n", .{details}); + std.debug.print("Details: {s}\n", .{details}); } else if (packet.error_message) |msg| { - colors.printError("Details: {s}\n", .{msg}); + std.debug.print("Details: {s}\n", .{msg}); } }, else => { - colors.printError("Unexpected response type\n", .{}); + std.debug.print("Unexpected response type\n", .{}); }, } } fn stopJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter stop \n", .{}); + std.debug.print("Usage: ml jupyter stop \n", .{}); return; } const service_id = args[0]; @@ -355,7 +354,7 @@ fn stopJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { // Connect to WebSocket var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -364,50 +363,50 @@ fn stopJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { const api_key_hash = try crypto.hashApiKey(allocator, config.api_key); defer allocator.free(api_key_hash); - colors.printInfo("Stopping service {s}...\n", .{service_id}); + std.debug.print("Stopping service {s}...\n", .{service_id}); // Send stop command client.sendStopJupyter(service_id, api_key_hash) catch |err| { - colors.printError("Failed to send stop command: {}\n", .{err}); + std.debug.print("Failed to send stop command: {}\n", .{err}); return; }; // Receive response const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); // Parse response packet const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); switch (packet.packet_type) { .success => { - colors.printSuccess("Service stopped.\n", .{}); + std.debug.print("Service stopped.\n", .{}); }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - colors.printError("Failed to stop service: {s}\n", .{error_msg}); + std.debug.print("Failed to stop service: {s}\n", .{error_msg}); if (packet.error_details) |details| { - colors.printError("Details: {s}\n", .{details}); + std.debug.print("Details: {s}\n", .{details}); } else if (packet.error_message) |msg| { - colors.printError("Details: {s}\n", .{msg}); + std.debug.print("Details: {s}\n", .{msg}); } }, else => { - colors.printError("Unexpected response type\n", .{}); + std.debug.print("Unexpected response type\n", .{}); }, } } fn removeJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter remove [--purge] [--force]\n", .{}); + std.debug.print("Usage: ml jupyter remove [--purge] [--force]\n", .{}); return; } @@ -422,8 +421,8 @@ fn removeJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.eql(u8, args[i], "--force")) { force = true; } else { - colors.printError("Unknown option: {s}\n", .{args[i]}); - colors.printError("Usage: ml jupyter remove [--purge] [--force]\n", .{}); + std.debug.print("Unknown option: {s}\n", .{args[i]}); + std.debug.print("Usage: ml jupyter remove [--purge] [--force]\n", .{}); return error.InvalidArgs; } } @@ -431,20 +430,20 @@ fn removeJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { // Trash-first by default: no confirmation. // Permanent deletion requires explicit --purge and a strong confirmation unless --force. if (purge and !force) { - colors.printWarning("PERMANENT deletion requested for '{s}'.\n", .{service_id}); - colors.printWarning("This cannot be undone.\n", .{}); - colors.printInfo("Type the service name to confirm: ", .{}); + std.debug.print("PERMANENT deletion requested for '{s}'.\n", .{service_id}); + std.debug.print("This cannot be undone.\n", .{}); + std.debug.print("Type the service name to confirm: ", .{}); const stdin = std.fs.File{ .handle = @intCast(0) }; // stdin file descriptor var buffer: [256]u8 = undefined; const bytes_read = stdin.read(&buffer) catch |err| { - colors.printError("Failed to read input: {}\n", .{err}); + std.debug.print("Failed to read input: {}\n", .{err}); return; }; const line = buffer[0..bytes_read]; const typed = std.mem.trim(u8, line, "\n\r "); if (!std.mem.eql(u8, typed, service_id)) { - colors.printInfo("Operation cancelled.\n", .{}); + std.debug.print("Operation cancelled.\n", .{}); return; } } @@ -460,7 +459,7 @@ fn removeJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { // Connect to WebSocket var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -470,46 +469,46 @@ fn removeJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { defer allocator.free(api_key_hash); if (purge) { - colors.printInfo("Permanently deleting service {s}...\n", .{service_id}); + std.debug.print("Permanently deleting service {s}...\n", .{service_id}); } else { - colors.printInfo("Removing service {s} (move to trash)...\n", .{service_id}); + std.debug.print("Removing service {s} (move to trash)...\n", .{service_id}); } // Send remove command client.sendRemoveJupyter(service_id, api_key_hash, purge) catch |err| { - colors.printError("Failed to send remove command: {}\n", .{err}); + std.debug.print("Failed to send remove command: {}\n", .{err}); return; }; // Receive response const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); // Parse response packet const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); switch (packet.packet_type) { .success => { - colors.printSuccess("Service removed successfully.\n", .{}); + std.debug.print("Service removed successfully.\n", .{}); }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - colors.printError("Failed to remove service: {s}\n", .{error_msg}); + std.debug.print("Failed to remove service: {s}\n", .{error_msg}); if (packet.error_details) |details| { - colors.printError("Details: {s}\n", .{details}); + std.debug.print("Details: {s}\n", .{details}); } else if (packet.error_message) |msg| { - colors.printError("Details: {s}\n", .{msg}); + std.debug.print("Details: {s}\n", .{msg}); } }, else => { - colors.printError("Unexpected response type\n", .{}); + std.debug.print("Unexpected response type\n", .{}); }, } } @@ -539,7 +538,7 @@ fn listServices(allocator: std.mem.Allocator) !void { // Connect to WebSocket var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -550,27 +549,27 @@ fn listServices(allocator: std.mem.Allocator) !void { // Send list command client.sendListJupyter(api_key_hash) catch |err| { - colors.printError("Failed to send list command: {}\n", .{err}); + std.debug.print("Failed to send list command: {}\n", .{err}); return; }; // Receive response const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); // Parse response packet const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); switch (packet.packet_type) { .data => { - colors.printInfo("Jupyter Services:\n", .{}); + std.debug.print("Jupyter Services:\n", .{}); if (packet.data_payload) |payload| { const parsed = std.json.parseFromSlice(std.json.Value, allocator, payload, .{}) catch { std.debug.print("{s}\n", .{payload}); @@ -594,12 +593,12 @@ fn listServices(allocator: std.mem.Allocator) !void { const services = services_opt.?; if (services.items.len == 0) { - colors.printInfo("No running services.\n", .{}); + std.debug.print("No running services.\n", .{}); return; } - colors.printInfo("NAME STATUS URL WORKSPACE\n", .{}); - colors.printInfo("---- ------ --- ---------\n", .{}); + std.debug.print("NAME\t\t\t\t\t\t\t\t\tSTATUS\t\tURL\t\t\t\t\t\t\t\t\t\t\tWORKSPACE\n", .{}); + std.debug.print("---- ------ --- ---------\n", .{}); for (services.items) |item| { if (item != .object) continue; @@ -628,22 +627,22 @@ fn listServices(allocator: std.mem.Allocator) !void { }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - colors.printError("Failed to list services: {s}\n", .{error_msg}); + std.debug.print("Failed to list services: {s}\n", .{error_msg}); if (packet.error_details) |details| { - colors.printError("Details: {s}\n", .{details}); + std.debug.print("Details: {s}\n", .{details}); } else if (packet.error_message) |msg| { - colors.printError("Details: {s}\n", .{msg}); + std.debug.print("Details: {s}\n", .{msg}); } }, else => { - colors.printError("Unexpected response type\n", .{}); + std.debug.print("Unexpected response type\n", .{}); }, } } fn workspaceCommands(args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter workspace \n", .{}); + std.debug.print("Usage: ml jupyter workspace \n", .{}); return; } @@ -651,7 +650,7 @@ fn workspaceCommands(args: []const []const u8) !void { if (std.mem.eql(u8, subcommand, "create")) { if (args.len < 2) { - colors.printError("Usage: ml jupyter workspace create --path \n", .{}); + std.debug.print("Usage: ml jupyter workspace create --path \n", .{}); return; } @@ -669,25 +668,25 @@ fn workspaceCommands(args: []const []const u8) !void { // Security validation if (!validateWorkspacePath(path)) { - colors.printError("Invalid workspace path: {s}\n", .{path}); - colors.printError("Path must be relative and cannot contain '..' for security reasons.\n", .{}); + std.debug.print("Invalid workspace path: {s}\n", .{path}); + std.debug.print("Path must be relative and cannot contain '..' for security reasons.\n", .{}); return; } - colors.printInfo("Creating workspace: {s}\n", .{path}); - colors.printInfo("Security: Path validated against security policies\n", .{}); - colors.printSuccess("Workspace created!\n", .{}); - colors.printInfo("Note: Workspace is isolated and has restricted access.\n", .{}); + std.debug.print("Creating workspace: {s}\n", .{path}); + std.debug.print("Security: Path validated against security policies\n", .{}); + std.debug.print("Workspace created!\n", .{}); + std.debug.print("Note: Workspace is isolated and has restricted access.\n", .{}); } else if (std.mem.eql(u8, subcommand, "list")) { - colors.printInfo("Workspaces:\n", .{}); - colors.printInfo("Name Path Status\n", .{}); - colors.printInfo("---- ---- ------\n", .{}); - colors.printInfo("default ./workspace active\n", .{}); - colors.printInfo("ml_project ./ml_project inactive\n", .{}); - colors.printInfo("Security: All workspaces are sandboxed and isolated.\n", .{}); + std.debug.print("Workspaces:\n", .{}); + std.debug.print("Name Path Status\n", .{}); + std.debug.print("---- ---- ------\n", .{}); + std.debug.print("default ./workspace active\n", .{}); + std.debug.print("ml_project ./ml_project inactive\n", .{}); + std.debug.print("Security: All workspaces are sandboxed and isolated.\n", .{}); } else if (std.mem.eql(u8, subcommand, "delete")) { if (args.len < 2) { - colors.printError("Usage: ml jupyter workspace delete --path \n", .{}); + std.debug.print("Usage: ml jupyter workspace delete --path \n", .{}); return; } @@ -705,47 +704,47 @@ fn workspaceCommands(args: []const []const u8) !void { // Security validation if (!validateWorkspacePath(path)) { - colors.printError("Invalid workspace path: {s}\n", .{path}); - colors.printError("Path must be relative and cannot contain '..' for security reasons.\n", .{}); + std.debug.print("Invalid workspace path: {s}\n", .{path}); + std.debug.print("Path must be relative and cannot contain '..' for security reasons.\n", .{}); return; } - colors.printInfo("Deleting workspace: {s}\n", .{path}); - colors.printInfo("Security: All data will be permanently removed.\n", .{}); - colors.printSuccess("Workspace deleted!\n", .{}); + std.debug.print("Deleting workspace: {s}\n", .{path}); + std.debug.print("Security: All data will be permanently removed.\n", .{}); + std.debug.print("Workspace deleted!\n", .{}); } else { - colors.printError("Invalid workspace command: {s}\n", .{subcommand}); + std.debug.print("Invalid workspace command: {s}\n", .{subcommand}); } } fn experimentCommands(args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter experiment \n", .{}); + std.debug.print("Usage: ml jupyter experiment \n", .{}); return; } const subcommand = args[0]; if (std.mem.eql(u8, subcommand, "link")) { - colors.printInfo("Linking workspace with experiment...\n", .{}); - colors.printSuccess("Workspace linked with experiment successfully!\n", .{}); + std.debug.print("Linking workspace with experiment...\n", .{}); + std.debug.print("Workspace linked with experiment successfully!\n", .{}); } else if (std.mem.eql(u8, subcommand, "queue")) { - colors.printInfo("Queuing experiment from workspace...\n", .{}); - colors.printSuccess("Experiment queued successfully!\n", .{}); + std.debug.print("Queuing experiment from workspace...\n", .{}); + std.debug.print("Experiment queued successfully!\n", .{}); } else if (std.mem.eql(u8, subcommand, "sync")) { - colors.printInfo("Syncing workspace with experiment data...\n", .{}); - colors.printSuccess("Sync completed!\n", .{}); + std.debug.print("Syncing workspace with experiment data...\n", .{}); + std.debug.print("Sync completed!\n", .{}); } else if (std.mem.eql(u8, subcommand, "status")) { - colors.printInfo("Experiment status for workspace: ./workspace\n", .{}); - colors.printInfo("Linked experiment: exp_123\n", .{}); + std.debug.print("Experiment status for workspace: ./workspace\n", .{}); + std.debug.print("Linked experiment: exp_123\n", .{}); } else { - colors.printError("Invalid experiment command: {s}\n", .{subcommand}); + std.debug.print("Invalid experiment command: {s}\n", .{subcommand}); } } fn packageCommands(args: []const []const u8) !void { if (args.len < 1) { - colors.printError("Usage: ml jupyter package \n", .{}); + std.debug.print("Usage: ml jupyter package \n", .{}); return; } @@ -753,7 +752,7 @@ fn packageCommands(args: []const []const u8) !void { if (std.mem.eql(u8, subcommand, "list")) { if (args.len < 2) { - colors.printError("Usage: ml jupyter package list \n", .{}); + std.debug.print("Usage: ml jupyter package list \n", .{}); return; } @@ -764,7 +763,7 @@ fn packageCommands(args: []const []const u8) !void { service_name = args[1]; } if (service_name.len == 0) { - colors.printError("Service name is required\n", .{}); + std.debug.print("Service name is required\n", .{}); return; } @@ -779,7 +778,7 @@ fn packageCommands(args: []const []const u8) !void { defer allocator.free(url); var client = ws.Client.connect(allocator, url, config.api_key) catch |err| { - colors.printError("Failed to connect to server: {}\n", .{err}); + std.debug.print("Failed to connect to server: {}\n", .{err}); return; }; defer client.close(); @@ -788,25 +787,25 @@ fn packageCommands(args: []const []const u8) !void { defer allocator.free(api_key_hash); client.sendListJupyterPackages(service_name, api_key_hash) catch |err| { - colors.printError("Failed to send list packages command: {}\n", .{err}); + std.debug.print("Failed to send list packages command: {}\n", .{err}); return; }; const response = client.receiveMessage(allocator) catch |err| { - colors.printError("Failed to receive response: {}\n", .{err}); + std.debug.print("Failed to receive response: {}\n", .{err}); return; }; defer allocator.free(response); const packet = protocol.ResponsePacket.deserialize(response, allocator) catch |err| { - colors.printError("Failed to parse response: {}\n", .{err}); + std.debug.print("Failed to parse response: {}\n", .{err}); return; }; defer packet.deinit(allocator); switch (packet.packet_type) { .data => { - colors.printInfo("Installed packages for {s}:\n", .{service_name}); + std.debug.print("Installed packages for {s}:\n", .{service_name}); if (packet.data_payload) |payload| { const parsed = std.json.parseFromSlice(std.json.Value, allocator, payload, .{}) catch { std.debug.print("{s}\n", .{payload}); @@ -821,12 +820,12 @@ fn packageCommands(args: []const []const u8) !void { const pkgs = parsed.value.array; if (pkgs.items.len == 0) { - colors.printInfo("No packages found.\n", .{}); + std.debug.print("No packages found.\n", .{}); return; } - colors.printInfo("NAME VERSION SOURCE\n", .{}); - colors.printInfo("---- ------- ------\n", .{}); + std.debug.print("NAME VERSION SOURCE\n", .{}); + std.debug.print("---- ------- ------\n", .{}); for (pkgs.items) |item| { if (item != .object) continue; @@ -851,19 +850,19 @@ fn packageCommands(args: []const []const u8) !void { }, .error_packet => { const error_msg = protocol.ResponsePacket.getErrorMessage(packet.error_code.?); - colors.printError("Failed to list packages: {s}\n", .{error_msg}); + std.debug.print("Failed to list packages: {s}\n", .{error_msg}); if (packet.error_details) |details| { - colors.printError("Details: {s}\n", .{details}); + std.debug.print("Details: {s}\n", .{details}); } else if (packet.error_message) |msg| { - colors.printError("Details: {s}\n", .{msg}); + std.debug.print("Details: {s}\n", .{msg}); } }, else => { - colors.printError("Unexpected response type\n", .{}); + std.debug.print("Unexpected response type\n", .{}); }, } } else { - colors.printError("Invalid package command: {s}\n", .{subcommand}); + std.debug.print("Invalid package command: {s}\n", .{subcommand}); } } @@ -871,7 +870,7 @@ fn launchJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: b _ = allocator; _ = args; _ = json; - core.output.errorMsg("jupyter.launch", "Not implemented"); + std.debug.print("Not implemented\n", .{}); return error.NotImplemented; } @@ -879,7 +878,7 @@ fn terminateJupyter(allocator: std.mem.Allocator, args: []const []const u8, json _ = allocator; _ = args; _ = json; - core.output.errorMsg("jupyter.terminate", "Not implemented"); + std.debug.print("Not implemented\n", .{}); return error.NotImplemented; } @@ -887,20 +886,20 @@ fn saveJupyter(allocator: std.mem.Allocator, args: []const []const u8, json: boo _ = allocator; _ = args; _ = json; - core.output.errorMsg("jupyter.save", "Not implemented"); + std.debug.print("Not implemented\n", .{}); return error.NotImplemented; } fn installJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { _ = allocator; _ = args; - core.output.errorMsg("jupyter.install", "Not implemented"); + std.debug.print("Not implemented\n", .{}); return error.NotImplemented; } fn uninstallJupyter(allocator: std.mem.Allocator, args: []const []const u8) !void { _ = allocator; _ = args; - core.output.errorMsg("jupyter.uninstall", "Not implemented"); + std.debug.print("Not implemented\n", .{}); return error.NotImplemented; } diff --git a/cli/src/commands/log.zig b/cli/src/commands/log.zig index 8eec012..7f1538b 100644 --- a/cli/src/commands/log.zig +++ b/cli/src/commands/log.zig @@ -1,7 +1,6 @@ const std = @import("std"); const config = @import("../config.zig"); const core = @import("../core.zig"); -const colors = @import("../utils/colors.zig"); const manifest_lib = @import("../manifest.zig"); const mode = @import("../mode.zig"); const ws = @import("../net/ws/client.zig"); @@ -13,11 +12,12 @@ const crypto = @import("../utils/crypto.zig"); /// ml logs # Fetch logs from local file or server /// ml logs --follow # Stream logs from server pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { - var flags = core.flags.CommonFlags{}; + var flags: core.flags.CommonFlags = .{}; + var command_args = try core.flags.parseCommon(allocator, args, &flags); defer command_args.deinit(allocator); - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); if (flags.help) { return printUsage(); @@ -148,7 +148,7 @@ fn streamServerLogs(allocator: std.mem.Allocator, target: []const u8, cfg: confi var client = try ws.Client.connect(allocator, ws_url, cfg.api_key); defer client.close(); - colors.printInfo("Streaming logs for: {s}\n", .{target}); + std.debug.print("Streaming logs for: {s}\n", .{target}); try client.sendStreamLogs(target, api_key_hash); @@ -171,7 +171,7 @@ fn streamServerLogs(allocator: std.mem.Allocator, target: []const u8, cfg: confi }, .error_packet => { const err_msg = packet.error_message orelse "Stream error"; - colors.printError("Error: {s}\n", .{err_msg}); + core.output.err(err_msg); return error.ServerError; }, else => {}, @@ -183,10 +183,10 @@ fn printUsage() !void { std.debug.print("Usage: ml logs [options]\n\n", .{}); std.debug.print("Fetch or stream run logs.\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --follow, -f Stream logs from server (online mode)\n", .{}); - std.debug.print(" --help, -h Show this help message\n", .{}); - std.debug.print(" --json Output structured JSON\n\n", .{}); + std.debug.print("\t--follow, -f\tStream logs from server (online mode)\n", .{}); + std.debug.print("\t--help, -h\tShow this help message\n", .{}); + std.debug.print("\t--json\t\tOutput structured JSON\n\n", .{}); std.debug.print("Examples:\n", .{}); - std.debug.print(" ml logs abc123 # Fetch logs (local or server)\n", .{}); - std.debug.print(" ml logs abc123 --follow # Stream logs from server\n", .{}); + std.debug.print("\tml logs abc123\t\t# Fetch logs (local or server)\n", .{}); + std.debug.print("\tml logs abc123 --follow\t# Stream logs from server\n", .{}); } diff --git a/cli/src/commands/prune.zig b/cli/src/commands/prune.zig index f647f14..8c9eeb4 100644 --- a/cli/src/commands/prune.zig +++ b/cli/src/commands/prune.zig @@ -26,7 +26,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } } - core.output.init(if (flags.flags.json) .flags.json else .text); + core.output.setMode(if (flags.flags.json) .flags.json else .text); if (keep_count == null and older_than_days == null) { core.output.usage("prune", "ml prune --keep | --older-than "); @@ -95,13 +95,13 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (flags.json) { std.debug.print("{\"ok\":true}\n", .{}); } else { - logging.success("✓ Prune operation completed successfully\n", .{}); + logging.success("Prune operation completed successfully\n", .{}); } } else { if (flags.json) { std.debug.print("{\"ok\":false,\"error_code\":{d}}\n", .{response[0]}); } else { - logging.err("✗ Prune operation failed: error code {d}\n", .{response[0]}); + logging.err("[FAIL] Prune operation failed: error code {d}\n", .{response[0]}); } return error.PruneFailed; } @@ -109,7 +109,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (flags.json) { std.debug.print("{\"ok\":true,\"note\":\"no_response\"}\n", .{}); } else { - logging.success("✓ Prune request sent (no response received)\n", .{}); + logging.success("Prune request sent (no response received)\n", .{}); } } } @@ -117,8 +117,8 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { fn printUsage() void { logging.info("Usage: ml prune [options]\n\n", .{}); logging.info("Options:\n", .{}); - logging.info(" --keep Keep N most recent experiments\n", .{}); - logging.info(" --older-than Remove experiments older than N days\n", .{}); - logging.info(" --flags.json Output machine-readable JSON\n", .{}); - logging.info(" --help, -h Show this help message\n", .{}); + logging.info("\t--keep \t\tKeep N most recent experiments\n", .{}); + logging.info("\t--older-than \tRemove experiments older than N days\n", .{}); + logging.info("\t--json\t\t\tOutput machine-readable JSON\n", .{}); + logging.info("\t--help, -h\t\tShow this help message\n", .{}); } diff --git a/cli/src/commands/queue.zig b/cli/src/commands/queue.zig index ff03943..460df2d 100644 --- a/cli/src/commands/queue.zig +++ b/cli/src/commands/queue.zig @@ -1,7 +1,6 @@ const std = @import("std"); const Config = @import("../config.zig").Config; const ws = @import("../net/ws/client.zig"); -const colors = @import("../utils/colors.zig"); const history = @import("../utils/history.zig"); const crypto = @import("../utils/crypto.zig"); const protocol = @import("../net/protocol.zig"); @@ -128,7 +127,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { // If --rerun is specified, handle re-queueing if (rerun_id) |id| { if (mode.isOffline(mode_result.mode)) { - colors.printError("ml queue --rerun requires server connection\n", .{}); + std.debug.print("ml queue --rerun requires server connection\n", .{}); return error.RequiresServer; } return try handleRerun(allocator, id, args, config); @@ -136,7 +135,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { // Regular queue - requires server if (mode.isOffline(mode_result.mode)) { - colors.printError("ml queue requires server connection (use 'ml run' for local execution)\n", .{}); + std.debug.print("ml queue requires server connection (use 'ml run' for local execution)\n", .{}); return error.RequiresServer; } @@ -147,7 +146,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { fn executeQueue(allocator: std.mem.Allocator, args: []const []const u8, config: Config) !void { // Support batch operations - multiple job names var job_names = std.ArrayList([]const u8).initCapacity(allocator, 10) catch |err| { - colors.printError("Failed to allocate job list: {}\n", .{err}); + std.debug.print("Failed to allocate job list: {}\n", .{err}); return err; }; defer job_names.deinit(allocator); @@ -218,21 +217,21 @@ fn executeQueue(allocator: std.mem.Allocator, args: []const []const u8, config: const commit_in = pre[i + 1]; const commit_hex = resolveCommitHexOrPrefix(allocator, config.worker_base, commit_in) catch |err| { if (err == error.FileNotFound) { - colors.printError("No commit matches prefix: {s}\n", .{commit_in}); + std.debug.print("No commit matches prefix: {s}\n", .{commit_in}); return error.InvalidArgs; } - colors.printError("Invalid commit id\n", .{}); + std.debug.print("Invalid commit id\n", .{}); return error.InvalidArgs; }; defer allocator.free(commit_hex); const commit_bytes = crypto.decodeHex(allocator, commit_hex) catch { - colors.printError("Invalid commit id: must be hex\n", .{}); + std.debug.print("Invalid commit id: must be hex\n", .{}); return error.InvalidArgs; }; if (commit_bytes.len != 20) { allocator.free(commit_bytes); - colors.printError("Invalid commit id: expected 20 bytes\n", .{}); + std.debug.print("Invalid commit id: expected 20 bytes\n", .{}); return error.InvalidArgs; } commit_id_override = commit_bytes; @@ -332,14 +331,14 @@ fn executeQueue(allocator: std.mem.Allocator, args: []const []const u8, config: } else { // This is a job name job_names.append(allocator, arg) catch |err| { - colors.printError("Failed to append job: {}\n", .{err}); + std.debug.print("Failed to append job: {}\n", .{err}); return err; }; } } if (job_names.items.len == 0) { - colors.printError("No job names specified\n", .{}); + std.debug.print("No job names specified\n", .{}); return error.InvalidArgs; } @@ -361,29 +360,24 @@ fn executeQueue(allocator: std.mem.Allocator, args: []const []const u8, config: return; } - colors.printInfo("Queueing {d} job(s)...\n", .{job_names.items.len}); + std.debug.print("Queueing {d} job(s)...\n", .{job_names.items.len}); // Generate tracking JSON if needed (simplified for now) - var tracking_json: []const u8 = ""; - if (has_tracking) { - tracking_json = "{}"; // Placeholder for tracking JSON - } + const tracking_json: []const u8 = ""; // Process each job var success_count: usize = 0; var failed_jobs = std.ArrayList([]const u8).initCapacity(allocator, 10) catch |err| { - colors.printError("Failed to allocate failed jobs list: {}\n", .{err}); + std.debug.print("Failed to allocate failed jobs list: {}\n", .{err}); return err; }; defer failed_jobs.deinit(allocator); - defer if (commit_id_override) |cid| allocator.free(cid); - const args_str: []const u8 = if (args_override) |a| a else args_joined; const note_str: []const u8 = if (note_override) |n| n else ""; for (job_names.items, 0..) |job_name, index| { - colors.printInfo("Processing job {d}/{d}: {s}\n", .{ index + 1, job_names.items.len, job_name }); + std.debug.print("Processing job {d}/{d}: {s}\n", .{ index + 1, job_names.items.len, job_name }); queueSingleJob( allocator, @@ -398,31 +392,31 @@ fn executeQueue(allocator: std.mem.Allocator, args: []const []const u8, config: note_str, print_next_steps, ) catch |err| { - colors.printError("Failed to queue job '{s}': {}\n", .{ job_name, err }); + std.debug.print("Failed to queue job '{s}': {}\n", .{ job_name, err }); failed_jobs.append(allocator, job_name) catch |append_err| { - colors.printError("Failed to track failed job: {}\n", .{append_err}); + std.debug.print("Failed to track failed job: {}\n", .{append_err}); }; continue; }; - colors.printSuccess("Successfully queued job '{s}'\n", .{job_name}); + std.debug.print("Successfully queued job '{s}'\n", .{job_name}); success_count += 1; } // Show summary - colors.printInfo("Batch queuing complete.\n", .{}); - colors.printSuccess("Successfully queued: {d} job(s)\n", .{success_count}); + std.debug.print("Batch queuing complete.\n", .{}); + std.debug.print("Successfully queued: {d} job(s)\n", .{success_count}); if (failed_jobs.items.len > 0) { - colors.printError("Failed to queue: {d} job(s)\n", .{failed_jobs.items.len}); + std.debug.print("Failed to queue: {d} job(s)\n", .{failed_jobs.items.len}); for (failed_jobs.items) |failed_job| { - colors.printError(" - {s}\n", .{failed_job}); + std.debug.print(" - {s}\n", .{failed_job}); } } if (!options.json and success_count > 0 and job_names.items.len > 1) { - colors.printInfo("\nNext steps:\n", .{}); - colors.printInfo(" ml status --watch\n", .{}); + std.debug.print("\nNext steps:\n", .{}); + std.debug.print(" ml status --watch\n", .{}); } } @@ -448,9 +442,9 @@ fn handleRerun(allocator: std.mem.Allocator, run_id: []const u8, args: []const [ // Parse response (simplified) if (std.mem.indexOf(u8, message, "success") != null) { - colors.printSuccess("✓ Re-queued run {s}\n", .{run_id[0..8]}); + std.debug.print("Re-queued run {s}\n", .{run_id[0..8]}); } else { - colors.printError("Failed to re-queue: {s}\n", .{message}); + std.debug.print("Failed to re-queue: {s}\n", .{message}); return error.RerunFailed; } } @@ -508,7 +502,7 @@ fn queueSingleJob( } } - colors.printInfo("Queueing job '{s}' with commit {s}...\n", .{ job_name, commit_hex }); + std.debug.print("Queueing job '{s}' with commit {s}...\n", .{ job_name, commit_hex }); // Connect to WebSocket and send queue message const ws_url = try config.getWebSocketUrl(allocator); @@ -518,11 +512,11 @@ fn queueSingleJob( defer client.close(); if ((snapshot_id != null) != (snapshot_sha256 != null)) { - colors.printError("Both --snapshot-id and --snapshot-sha256 must be set\n", .{}); + std.debug.print("Both --snapshot-id and --snapshot-sha256 must be set\n", .{}); return error.InvalidArgs; } if (snapshot_id != null and tracking_json.len > 0) { - colors.printError("Snapshot queueing is not supported with tracking yet\n", .{}); + std.debug.print("Snapshot queueing is not supported with tracking yet\n", .{}); return error.InvalidArgs; } @@ -633,7 +627,7 @@ fn queueSingleJob( if (message.len > 0 and message[0] == '{') { try handleDuplicateResponse(allocator, message, job_name, commit_hex, options); } else { - colors.printInfo("Server response: {s}\n", .{message}); + std.debug.print("Server response: {s}\n", .{message}); } return; }; @@ -642,97 +636,85 @@ fn queueSingleJob( switch (packet.packet_type) { .success => { history.record(allocator, job_name, commit_hex) catch |err| { - colors.printWarning("Warning: failed to record job in history ({})", .{err}); + std.debug.print("Warning: failed to record job in history ({})\n", .{err}); }; if (options.json) { std.debug.print("{{\"success\":true,\"job_name\":\"{s}\",\"commit_id\":\"{s}\",\"status\":\"queued\"}}\n", .{ job_name, commit_hex }); } else { - colors.printSuccess("✓ Job queued successfully: {s}\n", .{job_name}); + std.debug.print("Job queued: {s}\n", .{job_name}); if (print_next_steps) { const next_steps = try formatNextSteps(allocator, job_name, commit_hex); defer allocator.free(next_steps); - colors.printInfo("\n{s}", .{next_steps}); + std.debug.print("{s}\n", .{next_steps}); } } }, - .data => { - if (packet.data_payload) |payload| { - try handleDuplicateResponse(allocator, payload, job_name, commit_hex, options); - } - }, .error_packet => { const err_msg = packet.error_message orelse "Unknown error"; if (options.json) { std.debug.print("{{\"success\":false,\"error\":\"{s}\"}}\n", .{err_msg}); } else { - colors.printError("Error: {s}\n", .{err_msg}); + std.debug.print("Error: {s}\n", .{err_msg}); } return error.ServerError; }, else => { try client.handleResponsePacket(packet, "Job queue"); history.record(allocator, job_name, commit_hex) catch |err| { - colors.printWarning("Warning: failed to record job in history ({})", .{err}); + std.debug.print("Warning: failed to record job in history ({})\n", .{err}); }; if (print_next_steps) { const next_steps = try formatNextSteps(allocator, job_name, commit_hex); defer allocator.free(next_steps); - colors.printInfo("\n{s}", .{next_steps}); + std.debug.print("{s}\n", .{next_steps}); } }, } } fn printUsage() !void { - colors.printInfo("Usage: ml queue [job-name ...] [options]\n", .{}); - colors.printInfo(" ml queue --rerun # Re-queue a completed run\n", .{}); - colors.printInfo("\nBasic Options:\n", .{}); - colors.printInfo(" --commit Specify commit ID\n", .{}); - colors.printInfo(" --priority Set priority (0-255, default: 5)\n", .{}); - colors.printInfo(" --help, -h Show this help message\n", .{}); - colors.printInfo(" --cpu CPU cores requested (default: 2)\n", .{}); - colors.printInfo(" --memory Memory in GB (default: 8)\n", .{}); - colors.printInfo(" --gpu GPU count (default: 0)\n", .{}); - colors.printInfo(" --gpu-memory GPU memory budget (default: auto)\n", .{}); - colors.printInfo(" --args Extra runner args (sent to worker as task.Args)\n", .{}); - colors.printInfo(" --note Human notes (stored in run manifest as metadata.note)\n", .{}); - colors.printInfo(" -- Extra runner args (alternative to --args)\n", .{}); - colors.printInfo("\nResearch Narrative:\n", .{}); - colors.printInfo(" --hypothesis Research hypothesis being tested\n", .{}); - colors.printInfo(" --context Background context for this experiment\n", .{}); - colors.printInfo(" --intent What you're trying to accomplish\n", .{}); - colors.printInfo(" --expected-outcome What you expect to happen\n", .{}); - colors.printInfo(" --experiment-group Group related experiments\n", .{}); - colors.printInfo(" --tags Comma-separated tags (e.g., ablation,batch-size)\n", .{}); - colors.printInfo("\nSpecial Modes:\n", .{}); - colors.printInfo(" --rerun Re-queue a completed local run to server\n", .{}); - colors.printInfo(" --dry-run Show what would be queued\n", .{}); - colors.printInfo(" --validate Validate experiment without queuing\n", .{}); - colors.printInfo(" --explain Explain what will happen\n", .{}); - colors.printInfo(" --json Output structured JSON\n", .{}); - colors.printInfo(" --force Queue even if duplicate exists\n", .{}); - colors.printInfo("\nTracking:\n", .{}); - colors.printInfo(" --mlflow Enable MLflow (sidecar)\n", .{}); - colors.printInfo(" --mlflow-uri Enable MLflow (remote)\n", .{}); - colors.printInfo(" --tensorboard Enable TensorBoard\n", .{}); - colors.printInfo(" --wandb-key Enable Wandb with API key\n", .{}); - colors.printInfo(" --wandb-project Set Wandb project\n", .{}); - colors.printInfo(" --wandb-entity Set Wandb entity\n", .{}); - - colors.printInfo("\nSandboxing:\n", .{}); - colors.printInfo(" --network Network mode: none, bridge, slirp4netns\n", .{}); - colors.printInfo(" --read-only Mount root filesystem as read-only\n", .{}); - colors.printInfo(" --secret Inject secret as env var (can repeat)\n", .{}); - - colors.printInfo("\nExamples:\n", .{}); - colors.printInfo(" ml queue my_job # Queue a job\n", .{}); - colors.printInfo(" ml queue my_job --dry-run # Preview submission\n", .{}); - colors.printInfo(" ml queue my_job --validate # Validate locally\n", .{}); - colors.printInfo(" ml queue --rerun abc123 # Re-queue completed run\n", .{}); - colors.printInfo(" ml status --watch # Watch queue + prewarm\n", .{}); - colors.printInfo("\nResearch Examples:\n", .{}); - colors.printInfo(" ml queue train.py --hypothesis 'LR scaling improves convergence' \n", .{}); - colors.printInfo(" --context 'Following paper XYZ' --tags ablation,lr-scaling\n", .{}); + std.debug.print("Usage: ml queue [options] [job_name2 ...]\n\n", .{}); + std.debug.print("Options:\n", .{}); + std.debug.print("\t--priority <1-10>\tJob priority (default: 5)\n", .{}); + std.debug.print("\t--commit \t\tSpecific commit to run\n", .{}); + std.debug.print("\t--snapshot-id \tSnapshot ID to use\n", .{}); + std.debug.print("\t--snapshot-sha256 \tSnapshot SHA256 to use\n", .{}); + std.debug.print("\t--dry-run\t\tShow what would be queued\n", .{}); + std.debug.print("\t--explain \tReason for running\n", .{}); + std.debug.print("\t--json\t\t\tOutput machine-readable JSON\n", .{}); + std.debug.print("\t--help, -h\t\tShow this help message\n", .{}); + std.debug.print("\t--context \tBackground context for this experiment\n", .{}); + std.debug.print("\t--intent \t\tWhat you're trying to accomplish\n", .{}); + std.debug.print("\t--expected-outcome \tWhat you expect to happen\n", .{}); + std.debug.print("\t--experiment-group \tGroup related experiments\n", .{}); + std.debug.print("\t--tags \t\tComma-separated tags (e.g., ablation,batch-size)\n", .{}); + std.debug.print("\nSpecial Modes:\n", .{}); + std.debug.print("\t--rerun \tRe-queue a completed local run to server\n", .{}); + std.debug.print("\t--dry-run\t\tShow what would be queued\n", .{}); + std.debug.print("\t--validate\t\tValidate experiment without queuing\n", .{}); + std.debug.print("\t--explain\t\tExplain what will happen\n", .{}); + std.debug.print("\t--json\t\t\tOutput structured JSON\n", .{}); + std.debug.print("\t--force\t\t\tQueue even if duplicate exists\n", .{}); + std.debug.print("\nTracking:\n", .{}); + std.debug.print("\t--mlflow\t\tEnable MLflow (sidecar)\n", .{}); + std.debug.print("\t--mlflow-uri \tEnable MLflow (remote)\n", .{}); + std.debug.print("\t--tensorboard\t\tEnable TensorBoard\n", .{}); + std.debug.print("\t--wandb-key \tEnable Wandb with API key\n", .{}); + std.debug.print("\t--wandb-project \tSet Wandb project\n", .{}); + std.debug.print("\t--wandb-entity \tSet Wandb entity\n", .{}); + std.debug.print("\nSandboxing:\n", .{}); + std.debug.print("\t--network \tNetwork mode: none, bridge, slirp4netns\n", .{}); + std.debug.print("\t--read-only\t\tMount root filesystem as read-only\n", .{}); + std.debug.print("\t--secret \t\tInject secret as env var (can repeat)\n", .{}); + std.debug.print("\nExamples:\n", .{}); + std.debug.print("\tml queue my_job\t\t\t # Queue a job\n", .{}); + std.debug.print("\tml queue my_job --dry-run\t # Preview submission\n", .{}); + std.debug.print("\tml queue my_job --validate\t # Validate locally\n", .{}); + std.debug.print("\tml queue --rerun abc123\t # Re-queue completed run\n", .{}); + std.debug.print("\tml status --watch\t\t # Watch queue + prewarm\n", .{}); + std.debug.print("\nResearch Examples:\n", .{}); + std.debug.print("\tml queue train.py --hypothesis 'LR scaling improves convergence'\n", .{}); + std.debug.print("\t\t--context 'Following paper XYZ' --tags ablation,lr-scaling\n", .{}); } pub fn formatNextSteps(allocator: std.mem.Allocator, job_name: []const u8, commit_hex: []const u8) ![]u8 { @@ -741,9 +723,9 @@ pub fn formatNextSteps(allocator: std.mem.Allocator, job_name: []const u8, commi const writer = out.writer(allocator); try writer.writeAll("Next steps:\n"); - try writer.writeAll(" ml status --watch\n"); - try writer.print(" ml cancel {s}\n", .{job_name}); - try writer.print(" ml validate {s}\n", .{commit_hex}); + try writer.writeAll("\tml status --watch\n"); + try writer.print("\tml cancel {s}\n", .{job_name}); + try writer.print("\tml validate {s}\n", .{commit_hex}); return out.toOwnedSlice(allocator); } @@ -783,40 +765,40 @@ fn explainJob( } return; } else { - colors.printInfo("Job Explanation:\n", .{}); - colors.printInfo(" Job Name: {s}\n", .{job_name}); - colors.printInfo(" Commit ID: {s}\n", .{commit_display}); - colors.printInfo(" Priority: {d}\n", .{priority}); - colors.printInfo(" Resources Requested:\n", .{}); - colors.printInfo(" CPU: {d} cores\n", .{options.cpu}); - colors.printInfo(" Memory: {d} GB\n", .{options.memory}); - colors.printInfo(" GPU: {d} device(s)\n", .{options.gpu}); - colors.printInfo(" GPU Memory: {s}\n", .{options.gpu_memory orelse "auto"}); + std.debug.print("Job Explanation:\n", .{}); + std.debug.print("\tJob Name: {s}\n", .{job_name}); + std.debug.print("\tCommit ID: {s}\n", .{commit_display}); + std.debug.print("\tPriority: {d}\n", .{priority}); + std.debug.print("\tResources Requested:\n", .{}); + std.debug.print("\t\tCPU: {d} cores\n", .{options.cpu}); + std.debug.print("\t\tMemory: {d} GB\n", .{options.memory}); + std.debug.print("\t\tGPU: {d} device(s)\n", .{options.gpu}); + std.debug.print("\t\tGPU Memory: {s}\n", .{options.gpu_memory orelse "auto"}); // Display narrative if provided if (narrative_json != null) { - colors.printInfo("\n Research Narrative:\n", .{}); + std.debug.print("\n\tResearch Narrative:\n", .{}); if (options.hypothesis) |h| { - colors.printInfo(" Hypothesis: {s}\n", .{h}); + std.debug.print("\t\tHypothesis: {s}\n", .{h}); } if (options.context) |c| { - colors.printInfo(" Context: {s}\n", .{c}); + std.debug.print("\t\tContext: {s}\n", .{c}); } if (options.intent) |i| { - colors.printInfo(" Intent: {s}\n", .{i}); + std.debug.print("\t\tIntent: {s}\n", .{i}); } if (options.expected_outcome) |eo| { - colors.printInfo(" Expected Outcome: {s}\n", .{eo}); + std.debug.print("\t\tExpected Outcome: {s}\n", .{eo}); } if (options.experiment_group) |eg| { - colors.printInfo(" Experiment Group: {s}\n", .{eg}); + std.debug.print("\t\tExperiment Group: {s}\n", .{eg}); } if (options.tags) |t| { - colors.printInfo(" Tags: {s}\n", .{t}); + std.debug.print("\t\tTags: {s}\n", .{t}); } } - colors.printInfo("\n Action: Job would be queued for execution\n", .{}); + std.debug.print("\n Action: Job would be queued for execution\n", .{}); } } @@ -855,20 +837,20 @@ fn validateJob( try stdout_file.writeAll(formatted); return; } else { - colors.printInfo("Validation Results:\n", .{}); - colors.printInfo(" Job Name: {s}\n", .{job_name}); - colors.printInfo(" Commit ID: {s}\n", .{commit_display}); + std.debug.print("Validation Results:\n", .{}); + std.debug.print("\tJob Name: {s}\n", .{job_name}); + std.debug.print("\tCommit ID: {s}\n", .{commit_display}); - colors.printInfo(" Required Files:\n", .{}); - const train_status = if (train_script_exists) "✓" else "✗"; - const req_status = if (requirements_exists) "✓" else "✗"; - colors.printInfo(" train.py {s}\n", .{train_status}); - colors.printInfo(" requirements.txt {s}\n", .{req_status}); + std.debug.print("\tRequired Files:\n", .{}); + const train_status = if (train_script_exists) "yes" else "no"; + const req_status = if (requirements_exists) "yes" else "no"; + std.debug.print("\t\ttrain.py {s}\n", .{train_status}); + std.debug.print("\t\trequirements.txt {s}\n", .{req_status}); if (overall_valid) { - colors.printSuccess(" ✓ Validation passed - job is ready to queue\n", .{}); + std.debug.print("\tValidation passed - job is ready to queue\n", .{}); } else { - colors.printError(" ✗ Validation failed - missing required files\n", .{}); + std.debug.print("\tValidation failed - missing required files\n", .{}); } } } @@ -908,42 +890,42 @@ fn dryRunJob( } return; } else { - colors.printInfo("Dry Run - Job Queue Preview:\n", .{}); - colors.printInfo(" Job Name: {s}\n", .{job_name}); - colors.printInfo(" Commit ID: {s}\n", .{commit_display}); - colors.printInfo(" Priority: {d}\n", .{priority}); - colors.printInfo(" Resources Requested:\n", .{}); - colors.printInfo(" CPU: {d} cores\n", .{options.cpu}); - colors.printInfo(" Memory: {d} GB\n", .{options.memory}); - colors.printInfo(" GPU: {d} device(s)\n", .{options.gpu}); - colors.printInfo(" GPU Memory: {s}\n", .{options.gpu_memory orelse "auto"}); + std.debug.print("Dry Run - Job Queue Preview:\n", .{}); + std.debug.print("\tJob Name: {s}\n", .{job_name}); + std.debug.print("\tCommit ID: {s}\n", .{commit_display}); + std.debug.print("\tPriority: {d}\n", .{priority}); + std.debug.print("\tResources Requested:\n", .{}); + std.debug.print("\t\tCPU: {d} cores\n", .{options.cpu}); + std.debug.print("\t\tMemory: {d} GB\n", .{options.memory}); + std.debug.print("\t\tGPU: {d} device(s)\n", .{options.gpu}); + std.debug.print("\t\tGPU Memory: {s}\n", .{options.gpu_memory orelse "auto"}); // Display narrative if provided if (narrative_json != null) { - colors.printInfo("\n Research Narrative:\n", .{}); + std.debug.print("\n\tResearch Narrative:\n", .{}); if (options.hypothesis) |h| { - colors.printInfo(" Hypothesis: {s}\n", .{h}); + std.debug.print("\t\tHypothesis: {s}\n", .{h}); } if (options.context) |c| { - colors.printInfo(" Context: {s}\n", .{c}); + std.debug.print("\t\tContext: {s}\n", .{c}); } if (options.intent) |i| { - colors.printInfo(" Intent: {s}\n", .{i}); + std.debug.print("\t\tIntent: {s}\n", .{i}); } if (options.expected_outcome) |eo| { - colors.printInfo(" Expected Outcome: {s}\n", .{eo}); + std.debug.print("\t\tExpected Outcome: {s}\n", .{eo}); } if (options.experiment_group) |eg| { - colors.printInfo(" Experiment Group: {s}\n", .{eg}); + std.debug.print("\t\tExperiment Group: {s}\n", .{eg}); } if (options.tags) |t| { - colors.printInfo(" Tags: {s}\n", .{t}); + std.debug.print("\t\tTags: {s}\n", .{t}); } } - colors.printInfo("\n Action: Would queue job\n", .{}); - colors.printInfo(" Estimated queue time: 2-5 minutes\n", .{}); - colors.printSuccess(" ✓ Dry run completed - no job was actually queued\n", .{}); + std.debug.print("\n\tAction: Would queue job\n", .{}); + std.debug.print("\tEstimated queue time: 2-5 minutes\n", .{}); + std.debug.print("\tDry run completed - no job was actually queued\n", .{}); } } @@ -994,7 +976,7 @@ fn handleDuplicateResponse( if (options.json) { std.debug.print("{s}\n", .{payload}); } else { - colors.printInfo("Server response: {s}\n", .{payload}); + std.debug.print("Server response: {s}\n", .{payload}); } return; }; @@ -1006,7 +988,7 @@ fn handleDuplicateResponse( if (options.json) { std.debug.print("{s}\n", .{payload}); } else { - colors.printSuccess("✓ Job queued: {s}\n", .{job_name}); + std.debug.print("Job queued: {s}\n", .{job_name}); } return; } @@ -1022,11 +1004,11 @@ fn handleDuplicateResponse( if (options.json) { std.debug.print("{{\"success\":true,\"duplicate\":true,\"existing_id\":\"{s}\",\"status\":\"{s}\",\"queued_by\":\"{s}\",\"minutes_ago\":{d},\"suggested_action\":\"watch\"}}\n", .{ existing_id, status, queued_by, minutes_ago }); } else { - colors.printInfo("\n→ Identical job already in progress: {s}\n", .{existing_id[0..8]}); - colors.printInfo(" Queued by {s}, {d} minutes ago\n", .{ queued_by, minutes_ago }); - colors.printInfo(" Status: {s}\n", .{status}); - colors.printInfo("\n Watch: ml watch {s}\n", .{existing_id[0..8]}); - colors.printInfo(" Rerun: ml queue {s} --commit {s} --force\n", .{ job_name, commit_hex }); + std.debug.print("\nIdentical job already in progress: {s}\n", .{existing_id[0..8]}); + std.debug.print("\tQueued by {s}, {d} minutes ago\n", .{ queued_by, minutes_ago }); + std.debug.print("\tStatus: {s}\n", .{status}); + std.debug.print("\n\tWatch: ml watch {s}\n", .{existing_id[0..8]}); + std.debug.print("\tRerun: ml queue {s} --commit {s} --force\n", .{ job_name, commit_hex }); } } else if (std.mem.eql(u8, status, "completed")) { const duration_sec = root.get("duration_seconds").?.integer; @@ -1034,23 +1016,23 @@ fn handleDuplicateResponse( if (options.json) { std.debug.print("{{\"success\":true,\"duplicate\":true,\"existing_id\":\"{s}\",\"status\":\"completed\",\"queued_by\":\"{s}\",\"duration_minutes\":{d},\"suggested_action\":\"show\"}}\n", .{ existing_id, queued_by, duration_min }); } else { - colors.printInfo("\n→ Identical job already completed: {s}\n", .{existing_id[0..8]}); - colors.printInfo(" Queued by {s}\n", .{queued_by}); + std.debug.print("\nIdentical job already completed: {s}\n", .{existing_id[0..8]}); + std.debug.print(" Queued by {s}\n", .{queued_by}); const metrics = root.get("metrics"); if (metrics) |m| { if (m == .object) { - colors.printInfo("\n Results:\n", .{}); + std.debug.print("\n Results:\n", .{}); if (m.object.get("accuracy")) |v| { - if (v == .float) colors.printInfo(" accuracy: {d:.3}\n", .{v.float}); + if (v == .float) std.debug.print(" accuracy: {d:.3}\n", .{v.float}); } if (m.object.get("loss")) |v| { - if (v == .float) colors.printInfo(" loss: {d:.3}\n", .{v.float}); + if (v == .float) std.debug.print(" loss: {d:.3}\n", .{v.float}); } } } - colors.printInfo(" duration: {d}m\n", .{duration_min}); - colors.printInfo("\n Inspect: ml experiment show {s}\n", .{existing_id[0..8]}); - colors.printInfo(" Rerun: ml queue {s} --commit {s} --force\n", .{ job_name, commit_hex }); + std.debug.print("\t\tduration: {d}m\n", .{duration_min}); + std.debug.print("\n\tInspect: ml experiment show {s}\n", .{existing_id[0..8]}); + std.debug.print("\tRerun: ml queue {s} --commit {s} --force\n", .{ job_name, commit_hex }); } } else if (std.mem.eql(u8, status, "failed")) { const error_reason = root.get("error_reason").?.string; @@ -1069,85 +1051,85 @@ fn handleDuplicateResponse( std.debug.print("{{\"success\":true,\"duplicate\":true,\"existing_id\":\"{s}\",\"status\":\"failed\",\"failure_class\":\"{s}\",\"exit_code\":{d},\"signal\":\"{s}\",\"error_reason\":\"{s}\",\"retry_count\":{d},\"retry_cap\":{d},\"auto_retryable\":{},\"requires_fix\":{},\"suggested_action\":\"{s}\"}}\n", .{ existing_id, failure_class, exit_code, signal, error_reason, retry_count, retry_cap, auto_retryable, requires_fix, suggested_action }); } else { // Print rich failure information based on FailureClass - colors.printWarning("\n→ FAILED {s} {s} failure\n", .{ existing_id[0..8], failure_class }); + std.debug.print("\nFAILED {s} {s} failure\n", .{ existing_id[0..8], failure_class }); if (signal.len > 0) { - colors.printInfo(" Signal: {s} (exit code: {d})\n", .{ signal, exit_code }); + std.debug.print("\tSignal: {s} (exit code: {d})\n", .{ signal, exit_code }); } else if (exit_code != 0) { - colors.printInfo(" Exit code: {d}\n", .{exit_code}); + std.debug.print("\tExit code: {d}\n", .{exit_code}); } // Show log tail if available if (log_tail.len > 0) { // Truncate long log tails const display_tail = if (log_tail.len > 160) log_tail[0..160] else log_tail; - colors.printInfo(" Log: {s}...\n", .{display_tail}); + std.debug.print("\tLog: {s}...\n", .{display_tail}); } // Show retry history if (retry_count > 0) { if (auto_retryable and retry_count < retry_cap) { - colors.printInfo(" Retried: {d}/{d} — auto-retry in progress\n", .{ retry_count, retry_cap }); + std.debug.print("\tRetried: {d}/{d} — auto-retry in progress\n", .{ retry_count, retry_cap }); } else { - colors.printInfo(" Retried: {d}/{d}\n", .{ retry_count, retry_cap }); + std.debug.print("\tRetried: {d}/{d}\n", .{ retry_count, retry_cap }); } } // Class-specific guidance per design spec if (std.mem.eql(u8, failure_class, "infrastructure")) { - colors.printInfo("\n Infrastructure failure (node died, preempted).\n", .{}); + std.debug.print("\n\tInfrastructure failure (node died, preempted).\n", .{}); if (auto_retryable and retry_count < retry_cap) { - colors.printSuccess(" → Auto-retrying transparently (attempt {d}/{d})\n", .{ retry_count + 1, retry_cap }); + std.debug.print("\t-> Auto-retrying transparently (attempt {d}/{d})\n", .{ retry_count + 1, retry_cap }); } else if (retry_count >= retry_cap) { - colors.printError(" → Retry cap reached. Requires manual intervention.\n", .{}); - colors.printInfo(" Resubmit: ml requeue {s}\n", .{existing_id[0..8]}); + std.debug.print("\t-> Retry cap reached. Requires manual intervention.\n", .{}); + std.debug.print("\tResubmit: ml requeue {s}\n", .{existing_id[0..8]}); } - colors.printInfo(" Logs: ml logs {s}\n", .{existing_id[0..8]}); + std.debug.print("\tLogs: ml logs {s}\n", .{existing_id[0..8]}); } else if (std.mem.eql(u8, failure_class, "code")) { // CRITICAL RULE: code failures never auto-retry - colors.printError("\n Code failure — auto-retry is blocked.\n", .{}); - colors.printWarning(" You must fix the code before resubmitting.\n", .{}); - colors.printInfo(" View logs: ml logs {s}\n", .{existing_id[0..8]}); - colors.printInfo("\n After fix:\n", .{}); - colors.printInfo(" Requeue with same config:\n", .{}); - colors.printInfo(" ml requeue {s}\n", .{existing_id[0..8]}); - colors.printInfo(" Or with more resources:\n", .{}); - colors.printInfo(" ml requeue {s} --gpu-memory 16\n", .{existing_id[0..8]}); + std.debug.print("\n\tCode failure — auto-retry is blocked.\n", .{}); + std.debug.print("\tYou must fix the code before resubmitting.\n", .{}); + std.debug.print("\t\tView logs: ml logs {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tAfter fix:\n", .{}); + std.debug.print("\t\tRequeue with same config:\n", .{}); + std.debug.print("\t\t\tml requeue {s}\n", .{existing_id[0..8]}); + std.debug.print("\t\tOr with more resources:\n", .{}); + std.debug.print("\t\t\tml requeue {s} --gpu-memory 16\n", .{existing_id[0..8]}); } else if (std.mem.eql(u8, failure_class, "data")) { // Data failures never auto-retry - colors.printError("\n Data failure — verification/checksum issue.\n", .{}); - colors.printWarning(" Auto-retry will fail again with same data.\n", .{}); - colors.printInfo("\n Check:\n", .{}); - colors.printInfo(" Dataset availability: ml dataset verify {s}\n", .{existing_id[0..8]}); - colors.printInfo(" View logs: ml logs {s}\n", .{existing_id[0..8]}); - colors.printInfo("\n After data issue resolved:\n", .{}); - colors.printInfo(" ml requeue {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tData failure — verification/checksum issue.\n", .{}); + std.debug.print("\tAuto-retry will fail again with same data.\n", .{}); + std.debug.print("\n\tCheck:\n", .{}); + std.debug.print("\t\tDataset availability: ml dataset verify {s}\n", .{existing_id[0..8]}); + std.debug.print("\t\tView logs: ml logs {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tAfter data issue resolved:\n", .{}); + std.debug.print("\t\t\tml requeue {s}\n", .{existing_id[0..8]}); } else if (std.mem.eql(u8, failure_class, "resource")) { - colors.printError("\n Resource failure — OOM or disk full.\n", .{}); + std.debug.print("\n\tResource failure — OOM or disk full.\n", .{}); if (retry_count == 0 and auto_retryable) { - colors.printInfo(" → Will retry once with backoff (30s delay)\n", .{}); + std.debug.print("\t-> Will retry once with backoff (30s delay)\n", .{}); } else if (retry_count >= 1) { - colors.printWarning(" → Retried once, failed again with same error.\n", .{}); - colors.printInfo("\n Suggestion: resubmit with more resources:\n", .{}); - colors.printInfo(" ml requeue {s} --gpu-memory 16\n", .{existing_id[0..8]}); - colors.printInfo(" ml requeue {s} --memory 32 --cpu 8\n", .{existing_id[0..8]}); + std.debug.print("\t-> Retried once, failed again with same error.\n", .{}); + std.debug.print("\n\tSuggestion: resubmit with more resources:\n", .{}); + std.debug.print("\t\tml requeue {s} --gpu-memory 16\n", .{existing_id[0..8]}); + std.debug.print("\t\tml requeue {s} --memory 32 --cpu 8\n", .{existing_id[0..8]}); } - colors.printInfo("\n Check capacity: ml status\n", .{}); - colors.printInfo(" Logs: ml logs {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tCheck capacity: ml status\n", .{}); + std.debug.print("\tLogs: ml logs {s}\n", .{existing_id[0..8]}); } else { // Unknown failures - colors.printWarning("\n Unknown failure — classification unclear.\n", .{}); - colors.printInfo("\n Review full logs and decide:\n", .{}); - colors.printInfo(" ml logs {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tUnknown failure — classification unclear.\n", .{}); + std.debug.print("\n\tReview full logs and decide:\n", .{}); + std.debug.print("\t\tml logs {s}\n", .{existing_id[0..8]}); if (auto_retryable) { - colors.printInfo("\n Or retry:\n", .{}); - colors.printInfo(" ml requeue {s}\n", .{existing_id[0..8]}); + std.debug.print("\n\tOr retry:\n", .{}); + std.debug.print("\t\tml requeue {s}\n", .{existing_id[0..8]}); } } // Always show the suggestion if available if (suggestion.len > 0) { - colors.printInfo("\n {s}\n", .{suggestion}); + std.debug.print("\n\t{s}\n", .{suggestion}); } } } diff --git a/cli/src/commands/run.zig b/cli/src/commands/run.zig index 7716ca3..59edd0e 100644 --- a/cli/src/commands/run.zig +++ b/cli/src/commands/run.zig @@ -1,7 +1,6 @@ const std = @import("std"); const db = @import("../db.zig"); const manifest_lib = @import("../manifest.zig"); -const colors = @import("../utils/colors.zig"); const core = @import("../core.zig"); const config = @import("../config.zig"); @@ -36,7 +35,7 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { var command_args = try core.flags.parseCommon(allocator, args, &flags); defer command_args.deinit(allocator); - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); if (flags.help) { return printUsage(); @@ -165,9 +164,9 @@ pub fn execute(allocator: std.mem.Allocator, args: []const []const u8) !void { exit_code, }); } else { - colors.printSuccess("✓ Run {s} complete ({s})\n", .{ run_id[0..8], status }); + std.debug.print("[OK] Run {s} complete ({s})\n", .{ run_id[0..8], status }); if (cfg.sync_uri.len > 0) { - colors.printInfo("↑ queued for sync\n", .{}); + std.debug.print("-> queued for sync\n", .{}); } } } @@ -413,13 +412,13 @@ fn parseAndLogMetric( fn printUsage() !void { std.debug.print("Usage: ml run [options] [args...]\n", .{}); - std.debug.print(" ml run -- [args...]\n\n", .{}); + std.debug.print("\t\t\tml run -- [args...]\n\n", .{}); std.debug.print("Execute a run locally with experiment tracking.\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --help, -h Show this help message\n", .{}); - std.debug.print(" --json Output structured JSON\n\n", .{}); + std.debug.print("\t--help, -h\tShow this help message\n", .{}); + std.debug.print("\t--json\t\tOutput structured JSON\n\n", .{}); std.debug.print("Examples:\n", .{}); - std.debug.print(" ml run # Use entrypoint from config\n", .{}); - std.debug.print(" ml run --lr 0.001 # Append args to entrypoint\n", .{}); - std.debug.print(" ml run -- python train.py # Run explicit command\n", .{}); + std.debug.print("\tml run\t\t\t# Use entrypoint from config\n", .{}); + std.debug.print("\tml run --lr 0.001\t\t# Append args to entrypoint\n", .{}); + std.debug.print("\tml run -- python train.py\t# Run explicit command\n", .{}); } diff --git a/cli/src/commands/status.zig b/cli/src/commands/status.zig index 01bb89f..1283ac0 100644 --- a/cli/src/commands/status.zig +++ b/cli/src/commands/status.zig @@ -36,7 +36,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } } - core.output.init(if (options.json) .json else .text); + core.output.setMode(if (options.json) .json else .text); const config = try Config.load(allocator); defer { @@ -79,17 +79,17 @@ fn runSingleStatus(allocator: std.mem.Allocator, config: Config, user_context: a } fn runWatchMode(allocator: std.mem.Allocator, config: Config, user_context: auth.UserContext, options: StatusOptions) !void { - core.output.info("Starting watch mode (interval: {d}s). Press Ctrl+C to stop.\n", .{options.watch_interval}); + std.debug.print("Starting watch mode (interval: {d}s). Press Ctrl+C to stop.\n", .{options.watch_interval}); while (true) { if (!options.json) { - core.output.info("\n=== FetchML Status - {s} ===", .{user_context.name}); + std.debug.print("\n=== FetchML Status - {s} ===", .{user_context.name}); } try runSingleStatus(allocator, config, user_context, options); if (!options.json) { - colors.printInfo("Next update in {d} seconds...\n", .{options.watch_interval}); + std.debug.print("Next update in {d} seconds...\n", .{options.watch_interval}); } std.Thread.sleep(options.watch_interval * std.time.ns_per_s); @@ -98,7 +98,7 @@ fn runWatchMode(allocator: std.mem.Allocator, config: Config, user_context: auth fn runTuiMode(allocator: std.mem.Allocator, config: Config, args: []const []const u8) !void { if (config.isLocalMode()) { - core.output.errorMsg("status", "TUI mode requires server mode. Use 'ml status' without --tui for local mode."); + core.output.err("TUI mode requires server mode. Use 'ml status' without --tui for local mode."); return error.ServerOnlyFeature; } @@ -140,12 +140,12 @@ fn runTuiMode(allocator: std.mem.Allocator, config: Config, args: []const []cons } 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(" --tui Launch TUI monitor via SSH\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", .{}); + std.debug.print("Usage: ml status [options]\n", .{}); + std.debug.print("\nOptions:\n", .{}); + std.debug.print("\t--json\t\t\tOutput structured JSON\n", .{}); + std.debug.print("\t--watch\t\t\tWatch mode - continuously update status\n", .{}); + std.debug.print("\t--tui\t\t\tLaunch TUI monitor via SSH\n", .{}); + std.debug.print("\t--limit \tLimit number of results shown\n", .{}); + std.debug.print("\t--watch-interval=\tSet watch interval in seconds (default: 5)\n", .{}); + std.debug.print("\t--help\t\t\tShow this help message\n", .{}); } diff --git a/cli/src/commands/sync.zig b/cli/src/commands/sync.zig index 79119af..20e01a5 100644 --- a/cli/src/commands/sync.zig +++ b/cli/src/commands/sync.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const colors = @import("../utils/colors.zig"); const config = @import("../config.zig"); const db = @import("../db.zig"); const ws = @import("../net/ws/client.zig"); @@ -22,7 +21,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); const cfg = try config.Config.load(allocator); defer { @@ -32,7 +31,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { const mode_result = try mode.detect(allocator, cfg); if (mode.isOffline(mode_result.mode)) { - colors.printError("ml sync requires server connection\n", .{}); + std.debug.print("ml sync requires server connection\n", .{}); return error.RequiresServer; } @@ -56,7 +55,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (try db.DB.step(stmt)) { try runs_to_sync.append(allocator, try RunInfo.fromStmt(stmt, allocator)); } else { - colors.printWarning("Run {s} already synced or not found\n", .{run_id}); + std.debug.print("Run {s} already synced or not found\n", .{run_id}); return; } } else { @@ -69,7 +68,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } if (runs_to_sync.items.len == 0) { - if (!flags.json) colors.printSuccess("All runs already synced!\n", .{}); + if (!flags.json) std.debug.print("All runs already synced!\n", .{}); return; } @@ -84,9 +83,9 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var success_count: usize = 0; for (runs_to_sync.items) |run_info| { - if (!flags.json) colors.printInfo("Syncing run {s}...\n", .{run_info.run_id[0..8]}); + if (!flags.json) std.debug.print("Syncing run {s}...\n", .{run_info.run_id[0..8]}); syncRun(allocator, &database, &client, run_info, api_key_hash) catch |err| { - if (!flags.json) colors.printError("Failed to sync run {s}: {}\n", .{ run_info.run_id[0..8], err }); + if (!flags.json) std.debug.print("Failed to sync run {s}: {}\n", .{ run_info.run_id[0..8], err }); continue; }; const update_sql = "UPDATE ml_runs SET synced = 1 WHERE run_id = ?;"; @@ -102,7 +101,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (flags.json) { std.debug.print("{{\"success\":true,\"synced\":{d},\"total\":{d}}}\n", .{ success_count, runs_to_sync.items.len }); } else { - colors.printSuccess("Synced {d}/{d} runs\n", .{ success_count, runs_to_sync.items.len }); + std.debug.print("Synced {d}/{d} runs\n", .{ success_count, runs_to_sync.items.len }); } } @@ -251,11 +250,11 @@ fn printUsage() void { std.debug.print("Usage: ml sync [run_id] [options]\n\n", .{}); std.debug.print("Push local experiment runs to the server.\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --json Output structured JSON\n", .{}); - std.debug.print(" --help, -h Show this help message\n\n", .{}); + std.debug.print("\t--json\t\tOutput structured JSON\n", .{}); + std.debug.print("\t--help, -h\tShow this help message\n\n", .{}); std.debug.print("Examples:\n", .{}); - std.debug.print(" ml sync # Sync all unsynced runs\n", .{}); - std.debug.print(" ml sync abc123 # Sync specific run\n", .{}); + std.debug.print("\tml sync\t\t\t# Sync all unsynced runs\n", .{}); + std.debug.print("\tml sync abc123\t\t# Sync specific run\n", .{}); } /// Find the git root directory by walking up from the given path diff --git a/cli/src/commands/validate.zig b/cli/src/commands/validate.zig index f846296..8b92d03 100644 --- a/cli/src/commands/validate.zig +++ b/cli/src/commands/validate.zig @@ -3,7 +3,6 @@ const testing = std.testing; const Config = @import("../config.zig").Config; const ws = @import("../net/ws/client.zig"); const protocol = @import("../net/protocol.zig"); -const colors = @import("../utils/colors.zig"); const crypto = @import("../utils/crypto.zig"); const io = @import("../utils/io.zig"); const core = @import("../core.zig"); @@ -32,14 +31,14 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } else if (std.mem.startsWith(u8, arg, "--help")) { return printUsage(); } else if (std.mem.startsWith(u8, arg, "--")) { - core.output.errorMsg("validate", "Unknown option"); + core.output.err("Unknown option"); return error.InvalidArgs; } else { commit_hex = arg; } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); const config = try Config.load(allocator); defer { @@ -62,10 +61,10 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { try client.sendValidateRequestTask(api_key_hash, tid); } else { if (commit_hex == null) { - core.output.errorMsg("validate", "No commit hash specified"); + core.output.err("No commit hash specified"); return printUsage(); } else if (commit_hex.?.len != 40) { - colors.printError("validate requires a 40-char commit id (or --task )\n", .{}); + std.debug.print("validate requires a 40-char commit id (or --task )\n", .{}); try printUsage(); return error.InvalidArgs; } @@ -80,12 +79,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { defer allocator.free(msg); const packet = protocol.ResponsePacket.deserialize(msg, allocator) catch { - if (flags.json) { - var out = io.stdoutWriter(); - try out.print("{s}\n", .{msg}); - } else { - std.debug.print("{s}\n", .{msg}); - } + std.debug.print("{s}\n", .{msg}); return error.InvalidPacket; }; defer packet.deinit(allocator); @@ -96,166 +90,96 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } if (packet.packet_type != .data or packet.data_payload == null) { - colors.printError("unexpected response for validate\n", .{}); + std.debug.print("unexpected response for validate\n", .{}); return error.InvalidPacket; } const payload = packet.data_payload.?; - if (flags.json) { - var out = io.stdoutWriter(); - try out.print("{s}\n", .{payload}); - } else { - const parsed = try std.json.parseFromSlice(std.json.Value, allocator, payload, .{}); - defer parsed.deinit(); - - const root = parsed.value.object; - const ok = try printHumanReport(root, flags.verbose); - if (!ok) return error.ValidationFailed; - } -} - -fn printHumanReport(root: std.json.ObjectMap, verbose: bool) !bool { - const ok_val = root.get("ok") orelse return error.InvalidPacket; - if (ok_val != .bool) return error.InvalidPacket; - const ok = ok_val.bool; - - if (root.get("commit_id")) |cid| { - if (cid != .null) { - std.debug.print("commit_id: {s}\n", .{cid.string}); - } - } - if (root.get("task_id")) |tid| { - if (tid != .null) { - std.debug.print("task_id: {s}\n", .{tid.string}); - } - } - - if (ok) { - std.debug.print("validate: OK\n", .{}); - } else { - std.debug.print("validate: FAILED\n", .{}); - } - - if (root.get("errors")) |errs| { - if (errs == .array and errs.array.items.len > 0) { - std.debug.print("errors:\n", .{}); - for (errs.array.items) |e| { - if (e == .string) { - std.debug.print("- {s}\n", .{e.string}); - } - } - } - } - - if (root.get("warnings")) |warns| { - if (warns == .array and warns.array.items.len > 0) { - std.debug.print("warnings:\n", .{}); - for (warns.array.items) |w| { - if (w == .string) { - std.debug.print("- {s}\n", .{w.string}); - } - } - } - } - - if (root.get("checks")) |checks_val| { - if (checks_val == .object) { - if (verbose) { - std.debug.print("checks:\n", .{}); - } else { - std.debug.print("failed_checks:\n", .{}); - } - - var it = checks_val.object.iterator(); - var any_failed: bool = false; - while (it.next()) |entry| { - const name = entry.key_ptr.*; - const check_val = entry.value_ptr.*; - if (check_val != .object) continue; - - const check_obj = check_val.object; - var check_ok: bool = false; - if (check_obj.get("ok")) |cok| { - if (cok == .bool) check_ok = cok.bool; - } - - if (!check_ok) any_failed = true; - if (!verbose and check_ok) continue; - - if (check_ok) { - std.debug.print("- {s}: OK\n", .{name}); - } else { - std.debug.print("- {s}: FAILED\n", .{name}); - } - - if (verbose or !check_ok) { - if (check_obj.get("expected")) |exp| { - if (exp != .null) { - std.debug.print(" expected: {s}\n", .{exp.string}); - } - } - if (check_obj.get("actual")) |act| { - if (act != .null) { - std.debug.print(" actual: {s}\n", .{act.string}); - } - } - if (check_obj.get("details")) |det| { - if (det != .null) { - std.debug.print(" details: {s}\n", .{det.string}); - } - } - } - } - - if (!verbose and !any_failed) { - std.debug.print("- none\n", .{}); - } - } - } - - return ok; -} - -fn printUsage() !void { - colors.printInfo("Usage:\n", .{}); - std.debug.print(" ml validate [--json] [--verbose]\n", .{}); - std.debug.print(" ml validate --task [--json] [--verbose]\n", .{}); -} - -test "validate human report formatting" { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - defer _ = gpa.deinit(); - - const payload = - \\{ - \\ "ok": false, - \\ "commit_id": "abc", - \\ "task_id": "t1", - \\ "checks": { - \\ "a": {"ok": true}, - \\ "b": {"ok": false, "expected": "x", "actual": "y", "details": "d"} - \\ }, - \\ "errors": ["e1"], - \\ "warnings": ["w1"], - \\ "ts": "now" - \\} - ; - const parsed = try std.json.parseFromSlice(std.json.Value, allocator, payload, .{}); defer parsed.deinit(); - var buf = std.ArrayList(u8).empty; - defer buf.deinit(allocator); + if (flags.json) { + try io.stdoutWriteJson(parsed.value); + } else { + const root = parsed.value.object; + const ok_val = root.get("ok") orelse return error.InvalidPacket; + if (ok_val != .bool) return error.InvalidPacket; + _ = ok_val.bool; - _ = try printHumanReport(buf.writer(), parsed.value.object, false); - try testing.expect(std.mem.indexOf(u8, buf.items, "failed_checks") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- b: FAILED") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "expected: x") != null); + if (root.get("commit_id")) |cid| { + if (cid != .null) { + std.debug.print("commit_id: {s}\n", .{cid.string}); + } + } + if (root.get("task_id")) |tid| { + if (tid != .null) { + std.debug.print("task_id: {s}\n", .{tid.string}); + } + } - buf.clearRetainingCapacity(); - _ = try printHumanReport(buf.writer(), parsed.value.object, true); - try testing.expect(std.mem.indexOf(u8, buf.items, "checks") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- a: OK") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- b: FAILED") != null); + if (root.get("checks")) |checks_val| { + if (checks_val == .object) { + if (flags.verbose) { + std.debug.print("checks:\n", .{}); + } else { + std.debug.print("failed_checks:\n", .{}); + } + + var it = checks_val.object.iterator(); + var any_failed: bool = false; + while (it.next()) |entry| { + const name = entry.key_ptr.*; + const check_val = entry.value_ptr.*; + if (check_val != .object) continue; + + const check_obj = check_val.object; + var check_ok: bool = false; + if (check_obj.get("ok")) |cok| { + if (cok == .bool) check_ok = cok.bool; + } + + if (!check_ok) any_failed = true; + if (!flags.verbose and check_ok) continue; + + if (check_ok) { + std.debug.print("- {s}: OK\n", .{name}); + } else { + std.debug.print("- {s}: FAILED\n", .{name}); + } + + if (flags.verbose or !check_ok) { + if (check_obj.get("expected")) |exp| { + if (exp != .null) { + std.debug.print(" expected: {s}\n", .{exp.string}); + } + } + if (check_obj.get("actual")) |act| { + if (act != .null) { + std.debug.print(" actual: {s}\n", .{act.string}); + } + } + if (check_obj.get("details")) |det| { + if (det != .null) { + std.debug.print(" details: {s}\n", .{det.string}); + } + } + } + } + + if (!flags.verbose and !any_failed) { + std.debug.print("- none\n", .{}); + } + } + } + + return; + } + + return; +} + +fn printUsage() !void { + std.debug.print("Usage:\n", .{}); + std.debug.print("\tml validate [--json] [--verbose]\n", .{}); + std.debug.print("\tml validate --task [--json] [--verbose]\n", .{}); } diff --git a/cli/src/commands/watch.zig b/cli/src/commands/watch.zig index f6aee9e..352b796 100644 --- a/cli/src/commands/watch.zig +++ b/cli/src/commands/watch.zig @@ -5,7 +5,6 @@ const rsync = @import("../utils/rsync_embedded.zig"); const ws = @import("../net/ws/client.zig"); const core = @import("../core.zig"); const mode = @import("../mode.zig"); -const colors = @import("../utils/colors.zig"); pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { var flags = core.flags.CommonFlags{}; @@ -27,7 +26,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { } } - core.output.init(if (flags.json) .json else .text); + core.output.setMode(if (flags.json) .json else .text); const cfg = try config.Config.load(allocator); defer { @@ -39,7 +38,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { if (should_sync) { const mode_result = try mode.detect(allocator, cfg); if (mode.isOffline(mode_result.mode)) { - colors.printError("ml watch --sync requires server connection\n", .{}); + std.debug.print("ml watch --sync requires server connection\n", .{}); return error.RequiresServer; } } @@ -48,11 +47,11 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { std.debug.print("{{\"ok\":true,\"action\":\"watch\",\"sync\":{s}}}\n", .{if (should_sync) "true" else "false"}); } else { if (should_sync) { - colors.printInfo("Watching for changes with auto-sync every {d}s...\n", .{sync_interval}); + std.debug.print("Watching for changes with auto-sync every {d}s...\n", .{sync_interval}); } else { - colors.printInfo("Watching directory for changes...\n", .{}); + std.debug.print("Watching directory for changes...\n", .{}); } - colors.printInfo("Press Ctrl+C to stop\n", .{}); + std.debug.print("Press Ctrl+C to stop\n", .{}); } // Watch loop @@ -65,7 +64,7 @@ pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void { const sync_cmd = @import("sync.zig"); sync_cmd.run(allocator, &[_][]const u8{"--json"}) catch |err| { if (!flags.json) { - colors.printError("Auto-sync failed: {}\n", .{err}); + std.debug.print("Auto-sync failed: {}\n", .{err}); } }; last_synced = now; @@ -109,7 +108,7 @@ fn syncAndQueue(allocator: std.mem.Allocator, path: []const u8, job_name: ?[]con defer allocator.free(response); if (response.len > 0 and response[0] == 0x00) { - std.debug.print("✓ Job queued successfully: {s}\n", .{actual_job_name}); + std.debug.print("Job queued successfully: {s}\n", .{actual_job_name}); } } @@ -120,7 +119,7 @@ fn printUsage() void { std.debug.print("Usage: ml watch [options]\n\n", .{}); std.debug.print("Watch for changes and optionally auto-sync.\n\n", .{}); std.debug.print("Options:\n", .{}); - std.debug.print(" --sync Auto-sync runs to server every 30s\n", .{}); - std.debug.print(" --json Output structured JSON\n", .{}); - std.debug.print(" --help, -h Show this help message\n", .{}); + std.debug.print("\t--sync\t\tAuto-sync runs to server every 30s\n", .{}); + std.debug.print("\t--json\t\tOutput structured JSON\n", .{}); + std.debug.print("\t--help, -h\tShow this help message\n", .{}); }