fix(cli): CLI structure, manifest, and asset fixes

- Fix commands.zig imports (logs.zig → log.zig, remove missing modules)
- Fix manifest.writeManifest to accept allocator param
- Add db.Stmt type alias for sqlite3_stmt
- Fix rsync placeholder to be valid shell script (#!/bin/sh)
This commit is contained in:
Jeremie Fraeys 2026-02-21 17:59:20 -05:00
parent 382c67edfc
commit b1c9bc97fc
No known key found for this signature in database
4 changed files with 68 additions and 40 deletions

View file

@ -1 +1,2 @@
dummy #!/bin/sh
exec /usr/bin/rsync "$@"

View file

@ -8,13 +8,10 @@ pub const find = @import("commands/find.zig");
pub const info = @import("commands/info.zig"); pub const info = @import("commands/info.zig");
pub const init = @import("commands/init.zig"); pub const init = @import("commands/init.zig");
pub const jupyter = @import("commands/jupyter.zig"); pub const jupyter = @import("commands/jupyter.zig");
pub const logs = @import("commands/logs.zig"); pub const logs = @import("commands/log.zig");
pub const monitor = @import("commands/monitor.zig");
pub const narrative = @import("commands/narrative.zig");
pub const outcome = @import("commands/outcome.zig");
pub const prune = @import("commands/prune.zig"); pub const prune = @import("commands/prune.zig");
pub const queue = @import("commands/queue.zig"); pub const queue = @import("commands/queue.zig");
pub const requeue = @import("commands/requeue.zig"); pub const run = @import("commands/run.zig");
pub const status = @import("commands/status.zig"); pub const status = @import("commands/status.zig");
pub const sync = @import("commands/sync.zig"); pub const sync = @import("commands/sync.zig");
pub const validate = @import("commands/validate.zig"); pub const validate = @import("commands/validate.zig");

View file

@ -1,10 +1,13 @@
const std = @import("std"); const std = @import("std");
// SQLite C bindings // SQLite C bindings
const c = @cImport({ pub const c = @cImport({
@cInclude("sqlite3.h"); @cInclude("sqlite3.h");
}); });
// Public type alias for prepared statement
pub const Stmt = ?*c.sqlite3_stmt;
// SQLITE_TRANSIENT constant - use C wrapper to avoid Zig 0.15 C translation issue // SQLITE_TRANSIENT constant - use C wrapper to avoid Zig 0.15 C translation issue
extern fn fetchml_sqlite_transient() c.sqlite3_destructor_type; extern fn fetchml_sqlite_transient() c.sqlite3_destructor_type;
fn sqliteTransient() c.sqlite3_destructor_type { fn sqliteTransient() c.sqlite3_destructor_type {

View file

@ -59,82 +59,109 @@ pub const RunManifest = struct {
}; };
/// Write manifest to JSON file /// Write manifest to JSON file
pub fn writeManifest(manifest: RunManifest, path: []const u8) !void { pub fn writeManifest(manifest: RunManifest, path: []const u8, allocator: std.mem.Allocator) !void {
var file = try std.fs.cwd().createFile(path, .{}); var file = try std.fs.cwd().createFile(path, .{});
defer file.close(); defer file.close();
const writer = file.writer();
// Write JSON manually to avoid std.json complexity with hash maps // Write JSON manually to avoid std.json complexity with hash maps
try writer.writeAll("{\n"); try file.writeAll("{\n");
try writer.print(" \"run_id\": \"{s}\",\n", .{manifest.run_id}); const line1 = try std.fmt.allocPrint(allocator, " \"run_id\": \"{s}\",\n", .{manifest.run_id});
try writer.print(" \"experiment\": \"{s}\",\n", .{manifest.experiment}); defer allocator.free(line1);
try writer.print(" \"command\": \"{s}\",\n", .{manifest.command}); try file.writeAll(line1);
const line2 = try std.fmt.allocPrint(allocator, " \"experiment\": \"{s}\",\n", .{manifest.experiment});
defer allocator.free(line2);
try file.writeAll(line2);
const line3 = try std.fmt.allocPrint(allocator, " \"command\": \"{s}\",\n", .{manifest.command});
defer allocator.free(line3);
try file.writeAll(line3);
// Args array // Args array
try writer.writeAll(" \"args\": ["); try file.writeAll(" \"args\": [");
for (manifest.args, 0..) |arg, i| { for (manifest.args, 0..) |arg, i| {
if (i > 0) try writer.writeAll(", "); if (i > 0) try file.writeAll(", ");
try writer.print("\"{s}\"", .{arg}); const arg_str = try std.fmt.allocPrint(allocator, "\"{s}\"", .{arg});
defer allocator.free(arg_str);
try file.writeAll(arg_str);
} }
try writer.writeAll("],\n"); try file.writeAll("],\n");
// Commit ID (optional) // Commit ID (optional)
if (manifest.commit_id) |cid| { if (manifest.commit_id) |cid| {
try writer.print(" \"commit_id\": \"{s}\",\n", .{cid}); const cid_str = try std.fmt.allocPrint(allocator, " \"commit_id\": \"{s}\",\n", .{cid});
defer allocator.free(cid_str);
try file.writeAll(cid_str);
} else { } else {
try writer.writeAll(" \"commit_id\": null,\n"); try file.writeAll(" \"commit_id\": null,\n");
} }
try writer.print(" \"started_at\": \"{s}\",\n", .{manifest.started_at}); const started_str = try std.fmt.allocPrint(allocator, " \"started_at\": \"{s}\",\n", .{manifest.started_at});
defer allocator.free(started_str);
try file.writeAll(started_str);
// Ended at (optional) // Ended at (optional)
if (manifest.ended_at) |ended| { if (manifest.ended_at) |ended| {
try writer.print(" \"ended_at\": \"{s}\",\n", .{ended}); const ended_str = try std.fmt.allocPrint(allocator, " \"ended_at\": \"{s}\",\n", .{ended});
defer allocator.free(ended_str);
try file.writeAll(ended_str);
} else { } else {
try writer.writeAll(" \"ended_at\": null,\n"); try file.writeAll(" \"ended_at\": null,\n");
} }
try writer.print(" \"status\": \"{s}\",\n", .{manifest.status}); const status_str = try std.fmt.allocPrint(allocator, " \"status\": \"{s}\",\n", .{manifest.status});
defer allocator.free(status_str);
try file.writeAll(status_str);
// Exit code (optional) // Exit code (optional)
if (manifest.exit_code) |code| { if (manifest.exit_code) |code| {
try writer.print(" \"exit_code\": {d},\n", .{code}); const exit_str = try std.fmt.allocPrint(allocator, " \"exit_code\": {d},\n", .{code});
defer allocator.free(exit_str);
try file.writeAll(exit_str);
} else { } else {
try writer.writeAll(" \"exit_code\": null,\n"); try file.writeAll(" \"exit_code\": null,\n");
} }
// Params object // Params object
try writer.writeAll(" \"params\": {"); try file.writeAll(" \"params\": {");
var params_first = true; var params_first = true;
var params_iter = manifest.params.iterator(); var params_iter = manifest.params.iterator();
while (params_iter.next()) |entry| { while (params_iter.next()) |entry| {
if (!params_first) try writer.writeAll(", "); if (!params_first) try file.writeAll(", ");
params_first = false; params_first = false;
try writer.print("\"{s}\": \"{s}\"", .{ entry.key_ptr.*, entry.value_ptr.* }); const param_str = try std.fmt.allocPrint(allocator, "\"{s}\": \"{s}\"", .{ entry.key_ptr.*, entry.value_ptr.* });
defer allocator.free(param_str);
try file.writeAll(param_str);
} }
try writer.writeAll("},\n"); try file.writeAll("},\n");
// Metrics summary (optional) // Metrics summary (optional)
if (manifest.metrics_summary) |summary| { if (manifest.metrics_summary) |summary| {
try writer.writeAll(" \"metrics_summary\": {"); try file.writeAll(" \"metrics_summary\": {");
var summary_first = true; var summary_first = true;
var summary_iter = summary.iterator(); var summary_iter = summary.iterator();
while (summary_iter.next()) |entry| { while (summary_iter.next()) |entry| {
if (!summary_first) try writer.writeAll(", "); if (!summary_first) try file.writeAll(", ");
summary_first = false; summary_first = false;
try writer.print("\"{s}\": {d:.4}", .{ entry.key_ptr.*, entry.value_ptr.* }); const metric_str = try std.fmt.allocPrint(allocator, "\"{s}\": {d:.4}", .{ entry.key_ptr.*, entry.value_ptr.* });
defer allocator.free(metric_str);
try file.writeAll(metric_str);
} }
try writer.writeAll("},\n"); try file.writeAll("},\n");
} else { } else {
try writer.writeAll(" \"metrics_summary\": null,\n"); try file.writeAll(" \"metrics_summary\": null,\n");
} }
try writer.print(" \"artifact_path\": \"{s}\",\n", .{manifest.artifact_path}); const artifact_str = try std.fmt.allocPrint(allocator, " \"artifact_path\": \"{s}\",\n", .{manifest.artifact_path});
try writer.print(" \"synced\": {}", .{manifest.synced}); defer allocator.free(artifact_str);
try file.writeAll(artifact_str);
try writer.writeAll("\n}\n"); const synced_str = try std.fmt.allocPrint(allocator, " \"synced\": {}", .{manifest.synced});
defer allocator.free(synced_str);
try file.writeAll(synced_str);
try file.writeAll("\n}\n");
} }
/// Read manifest from JSON file /// Read manifest from JSON file
@ -265,7 +292,7 @@ pub fn updateManifestStatus(path: []const u8, status: []const u8, exit_code: ?i3
manifest.ended_at = try allocator.dupe(u8, timestamp); manifest.ended_at = try allocator.dupe(u8, timestamp);
try writeManifest(manifest, path); try writeManifest(manifest, path, allocator);
} }
/// Mark manifest as synced /// Mark manifest as synced
@ -274,7 +301,7 @@ pub fn markManifestSynced(path: []const u8, allocator: std.mem.Allocator) !void
defer manifest.deinit(allocator); defer manifest.deinit(allocator);
manifest.synced = true; manifest.synced = true;
try writeManifest(manifest, path); try writeManifest(manifest, path, allocator);
} }
/// Build manifest path from experiment and run_id /// Build manifest path from experiment and run_id