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)
108 lines
3.7 KiB
Zig
108 lines
3.7 KiB
Zig
const std = @import("std");
|
|
const Config = @import("config.zig").Config;
|
|
const ws = @import("net/ws/client.zig");
|
|
|
|
/// Mode represents the operating mode of the CLI
|
|
pub const Mode = enum {
|
|
/// Local/offline mode - runs execute locally, tracking to SQLite
|
|
offline,
|
|
/// Online/runner mode - jobs queue to remote server
|
|
online,
|
|
};
|
|
|
|
/// DetectionResult includes the mode and any warning messages
|
|
pub const DetectionResult = struct {
|
|
mode: Mode,
|
|
warning: ?[]const u8,
|
|
};
|
|
|
|
/// Detect mode based on configuration and environment
|
|
/// Priority order (CLI — checked on every command):
|
|
/// 1. FETCHML_LOCAL=1 env var → local (forced, skip ping)
|
|
/// 2. force_local=true in config → local (forced, skip ping)
|
|
/// 3. cfg.Host == "" → local (not configured)
|
|
/// 4. API ping within 2s timeout → runner mode
|
|
/// - timeout / refused → local (fallback, log once per session)
|
|
/// - 401/403 → local (fallback, warn once about auth)
|
|
pub fn detect(allocator: std.mem.Allocator, cfg: Config) !DetectionResult {
|
|
// Priority 1: FETCHML_LOCAL env var
|
|
if (std.posix.getenv("FETCHML_LOCAL")) |val| {
|
|
if (std.mem.eql(u8, val, "1")) {
|
|
return .{ .mode = .offline, .warning = null };
|
|
}
|
|
}
|
|
|
|
// Priority 2: execution_mode in config
|
|
if (cfg.execution_mode == .local) {
|
|
return .{ .mode = .offline, .warning = null };
|
|
}
|
|
|
|
// Priority 3: No host configured
|
|
if (cfg.worker_host.len == 0) {
|
|
return .{ .mode = .offline, .warning = null };
|
|
}
|
|
|
|
// Priority 4: API ping with 2s timeout
|
|
const ping_result = try pingServer(allocator, cfg, 2000);
|
|
return switch (ping_result) {
|
|
.success => .{ .mode = .online, .warning = null },
|
|
.timeout => .{ .mode = .offline, .warning = "Server unreachable, falling back to local mode" },
|
|
.refused => .{ .mode = .offline, .warning = "Server connection refused, falling back to local mode" },
|
|
.auth_error => .{ .mode = .offline, .warning = "Authentication failed, falling back to local mode" },
|
|
};
|
|
}
|
|
|
|
/// PingResult represents the outcome of a server ping
|
|
const PingResult = enum {
|
|
success,
|
|
timeout,
|
|
refused,
|
|
auth_error,
|
|
};
|
|
|
|
/// Ping the server with a timeout - simplified version that just tries to connect
|
|
fn pingServer(allocator: std.mem.Allocator, cfg: Config, timeout_ms: u64) !PingResult {
|
|
_ = timeout_ms; // Timeout not implemented for this simplified version
|
|
|
|
const ws_url = try cfg.getWebSocketUrl(allocator);
|
|
defer allocator.free(ws_url);
|
|
|
|
var connection = ws.Client.connect(allocator, ws_url, cfg.api_key) catch |err| {
|
|
switch (err) {
|
|
error.ConnectionTimedOut => return .timeout,
|
|
error.ConnectionRefused => return .refused,
|
|
error.AuthenticationFailed => return .auth_error,
|
|
else => return .refused,
|
|
}
|
|
};
|
|
defer connection.close();
|
|
|
|
// Try to receive any message to confirm server is responding
|
|
const response = connection.receiveMessage(allocator) catch |err| {
|
|
switch (err) {
|
|
error.ConnectionTimedOut => return .timeout,
|
|
else => return .refused,
|
|
}
|
|
};
|
|
defer allocator.free(response);
|
|
|
|
return .success;
|
|
}
|
|
|
|
/// Check if mode is online
|
|
pub fn isOnline(mode: Mode) bool {
|
|
return mode == .online;
|
|
}
|
|
|
|
/// Check if mode is offline
|
|
pub fn isOffline(mode: Mode) bool {
|
|
return mode == .offline;
|
|
}
|
|
|
|
/// Require online mode, returning error if offline
|
|
pub fn requireOnline(mode: Mode, command_name: []const u8) !void {
|
|
if (mode == .offline) {
|
|
std.log.err("{s} requires server connection", .{command_name});
|
|
return error.RequiresServer;
|
|
}
|
|
}
|