Add execution_mode enum (local/remote/auto) to config for persistent control over command execution behavior. Removes --local/--remote flags from commands to simplify user workflow - no need to check server connection status manually. Changes: - config.zig: Add ExecutionMode enum, execution_mode field, parsing/serialization - mode.zig: Update detect() to check execution_mode == .local - init.zig: Add --mode flag (local/remote/auto) for setting during init - info.zig: Use config execution_mode, removed --local/--remote flags - run.zig: Use config execution_mode, removed --local/--remote flags - exec/mod.zig: Use config execution_mode, removed --local/--remote flags Priority order for determining execution mode: 1. Config setting (execution_mode: local/remote/auto) 2. Auto-detect only if config is 'auto' Users set mode once during init: ml init --mode=local # Always use local ml init --mode=remote # Always use remote ml init --mode=auto # Auto-detect (default)
127 lines
5 KiB
Zig
127 lines
5 KiB
Zig
const std = @import("std");
|
|
const Config = @import("../config.zig").Config;
|
|
const db = @import("../db.zig");
|
|
const core = @import("../core.zig");
|
|
|
|
pub fn run(allocator: std.mem.Allocator, args: []const []const u8) !void {
|
|
var flags = core.flags.CommonFlags{};
|
|
var remaining = try core.flags.parseCommon(allocator, args, &flags);
|
|
defer remaining.deinit(allocator);
|
|
|
|
core.output.setMode(if (flags.json) .json else .text);
|
|
|
|
// Handle help flag early
|
|
if (flags.help) {
|
|
return printUsage();
|
|
}
|
|
|
|
// Parse CLI-specific overrides and flags
|
|
const cli_tracking_uri = core.flags.parseKVFlag(remaining.items, "tracking-uri");
|
|
const cli_artifact_path = core.flags.parseKVFlag(remaining.items, "artifact-path");
|
|
const cli_sync_uri = core.flags.parseKVFlag(remaining.items, "sync-uri");
|
|
const force_local = core.flags.parseBoolFlag(remaining.items, "local");
|
|
const cli_mode = core.flags.parseKVFlag(remaining.items, "mode");
|
|
|
|
var cfg = try Config.loadWithOverrides(allocator, cli_tracking_uri, cli_artifact_path, cli_sync_uri);
|
|
defer cfg.deinit(allocator);
|
|
|
|
// Apply mode preference from --mode flag
|
|
if (cli_mode) |mode| {
|
|
if (std.mem.eql(u8, mode, "local")) {
|
|
cfg.execution_mode = .local;
|
|
} else if (std.mem.eql(u8, mode, "remote")) {
|
|
cfg.execution_mode = .remote;
|
|
} else {
|
|
cfg.execution_mode = .auto;
|
|
}
|
|
}
|
|
|
|
// Print resolved config
|
|
std.debug.print("Resolved config:\n", .{});
|
|
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("\t(default)\n", .{});
|
|
} else {
|
|
std.debug.print("\n", .{});
|
|
}
|
|
|
|
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("\t(default)\n", .{});
|
|
} else {
|
|
std.debug.print("\n", .{});
|
|
}
|
|
std.debug.print("\tsync_uri = {s}\n", .{if (cfg.sync_uri.len > 0) cfg.sync_uri else "(not set)"});
|
|
std.debug.print("\texecution_mode = {s}\n", .{switch (cfg.execution_mode) {
|
|
.local => "local",
|
|
.remote => "remote",
|
|
.auto => "auto",
|
|
}});
|
|
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("\tLocal tracking DB will be created automatically if server becomes unavailable.\n", .{});
|
|
|
|
if (cfg.sync_uri.len > 0) {
|
|
std.debug.print("\tServer: {s}:{d}\n", .{ cfg.worker_host, cfg.worker_port });
|
|
}
|
|
return;
|
|
}
|
|
|
|
// --local path: create config + DB now
|
|
std.debug.print("(local mode explicitly requested)\n\n", .{});
|
|
|
|
// Get DB path from tracking URI
|
|
const db_path = try cfg.getDBPath(allocator);
|
|
defer allocator.free(db_path);
|
|
|
|
// Check if DB already exists
|
|
const db_exists = blk: {
|
|
std.fs.accessAbsolute(db_path, .{}) catch |err| {
|
|
if (err == error.FileNotFound) break :blk false;
|
|
};
|
|
break :blk true;
|
|
};
|
|
|
|
if (db_exists) {
|
|
std.debug.print("Database already exists: {s}\n", .{db_path});
|
|
} else {
|
|
// Create parent directories if needed
|
|
if (std.fs.path.dirname(db_path)) |dir| {
|
|
std.fs.makeDirAbsolute(dir) catch |err| {
|
|
if (err != error.PathAlreadyExists) {
|
|
std.log.err("Failed to create directory {s}: {}", .{ dir, err });
|
|
return error.MkdirFailed;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Initialize database (creates schema)
|
|
var database = try db.DB.init(allocator, db_path);
|
|
defer database.close();
|
|
defer database.checkpointOnExit();
|
|
|
|
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("\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("\t--local\t\t\tCreate local database now (default: config only)\n", .{});
|
|
std.debug.print("\t--mode MODE\t\tDefault execution mode: local, remote, or auto (default: auto)\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", .{});
|
|
}
|