const std = @import("std"); const Io = std.Io; const net = std.Io.net; const fmt = @import("format.zig"); const mode_cmd = @import("mode.zig"); const schedule_cmd = @import("schedule.zig"); const status_cmd = @import("status.zig"); const config_cmd = @import("config.zig"); const pomodoro_cmd = @import("pomodoro.zig"); const media_cmd = @import("media.zig"); const routine_cmd = @import("routine.zig"); const sync_cmd = @import("sync.zig"); const task_cmd = @import("tasks.zig"); const plan_cmd = @import("plan.zig"); const habit_cmd = @import("habits.zig"); const playlist_cmd = @import("playlist.zig"); const daemon = @import("../daemon/daemon.zig"); const Config = @import("../config/loader.zig").Config; pub fn run(io: Io, allocator: std.mem.Allocator, args: []const []const u8, cfg: Config) !void { if (args.len < 2) { try printUsage(io); return; } const command = args[1]; if (std.mem.eql(u8, command, "mode")) { try mode_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "schedule")) { try schedule_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "status")) { try status_cmd.run(io, allocator, cfg); } else if (std.mem.eql(u8, command, "config")) { try config_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "pomodoro")) { try pomodoro_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "media")) { try media_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "routine")) { try routine_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "sync")) { try sync_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "task")) { try task_cmd.run(io, allocator, args[2..]); } else if (std.mem.eql(u8, command, "plan")) { try plan_cmd.run(io, allocator, args[2..], cfg); } else if (std.mem.eql(u8, command, "habit")) { try habit_cmd.run(allocator, io, args[2..]); } else if (std.mem.eql(u8, command, "playlist")) { try playlist_cmd.run(allocator, io, args[2..]); } else if (std.mem.eql(u8, command, "process")) { try processCmd(io, allocator, args[2..]); } else if (std.mem.eql(u8, command, "iptables")) { try iptablesCmd(io, allocator, args[2..]); } else if (std.mem.eql(u8, command, "daemon")) { if (args.len > 2 and std.mem.eql(u8, args[2], "stop")) { const pid_content = std.Io.Dir.cwd().readFileAlloc(io, "/tmp/focusd.pid", allocator, .limited(32)) catch { try fmt.err(io, "no daemon running (no PID file)"); return; }; defer allocator.free(pid_content); const pid_str = std.mem.trim(u8, pid_content, "\n\r "); const pid = std.fmt.parseInt(i32, pid_str, 10) catch { try fmt.err(io, "invalid PID in file"); return; }; std.posix.kill(pid, std.posix.SIG.TERM) catch { try fmt.err(io, "failed to stop daemon"); return; }; try fmt.success(io, "daemon stopped"); } else { try daemon.runDaemon(allocator, io); } } else if (std.mem.eql(u8, command, "help") or std.mem.eql(u8, command, "--help")) { try printUsage(io); } else if (std.mem.eql(u8, command, "version") or std.mem.eql(u8, command, "--version")) { try Io.File.stdout().writeStreamingAll(io, "focusd 0.1.0\n"); } else { // Show condensed command list (like zig) const stdout = Io.File.stdout(); try stdout.writeStreamingAll(io, "\n"); try fmt.boldText(stdout, io, "Commands:\n"); try stdout.writeStreamingAll(io, "\n"); try fmt.dimText(stdout, io, " mode Manage focus modes\n"); try fmt.dimText(stdout, io, " schedule Manage time-based schedule rules\n"); try fmt.dimText(stdout, io, " task Manage task inventory\n"); try fmt.dimText(stdout, io, " habit Track daily habits with streaks\n"); try fmt.dimText(stdout, io, " playlist Focus music playback\n"); try fmt.dimText(stdout, io, " pomodoro Pomodoro timer sessions\n"); try fmt.dimText(stdout, io, " media Weekly media curation\n"); try fmt.dimText(stdout, io, " routine Cal Newport daily routines\n"); try fmt.dimText(stdout, io, " plan Weekly planning & reports\n"); try fmt.dimText(stdout, io, " sync Sync with external services\n"); try fmt.dimText(stdout, io, " status Daemon status overview\n"); try fmt.dimText(stdout, io, " config View/set configuration\n"); try fmt.dimText(stdout, io, " process Process monitor control\n"); try fmt.dimText(stdout, io, " iptables Iptables blocking control\n"); try fmt.dimText(stdout, io, " daemon Run the background daemon\n"); try fmt.dimText(stdout, io, " help Print this help and exit\n"); try fmt.dimText(stdout, io, " version Print version number and exit\n"); try stdout.writeStreamingAll(io, "\n"); // Suggest closest match (like git) const closest = findClosestCommand(command); if (closest) |suggestion| { var hint_buf: [128]u8 = undefined; const hint = std.fmt.bufPrint(&hint_buf, "'{s}' is similar to '{s}'", .{ suggestion, command }) catch ""; try fmt.dimText(stdout, io, hint); try stdout.writeStreamingAll(io, "\n\n"); } // Error at the bottom (like zig) try fmt.err(io, "unknown command: "); try fmt.boldText(stdout, io, command); try stdout.writeStreamingAll(io, "\n"); } } fn findClosestCommand(input: []const u8) ?[]const u8 { const commands = [_][]const u8{ "mode", "schedule", "task", "habit", "playlist", "pomodoro", "media", "routine", "plan", "sync", "status", "config", "process", "iptables", "daemon", "help", "version", }; var best_match: ?[]const u8 = null; var best_distance: usize = 3; // Max distance to consider for (commands) |cmd| { const distance = levenshteinDistance(input, cmd); if (distance < best_distance) { best_distance = distance; best_match = cmd; } } return best_match; } fn levenshteinDistance(a: []const u8, b: []const u8) usize { const a_len = a.len; const b_len = b.len; // Quick checks if (a_len == 0) return b_len; if (b_len == 0) return a_len; if (a_len == b_len and std.mem.eql(u8, a, b)) return 0; // Simple distance for short strings var distance: usize = 0; var i: usize = 0; var j: usize = 0; while (i < a_len and j < b_len) { if (a[i] == b[j]) { i += 1; j += 1; } else { distance += 1; if (i + 1 < a_len and j + 1 < b_len) { // Check if substitution or insertion/deletion is better if (a[i + 1] == b[j]) { i += 1; } else if (a[i] == b[j + 1]) { j += 1; } else { i += 1; j += 1; } } else if (i + 1 < a_len) { i += 1; } else { j += 1; } } } distance += (a_len - i) + (b_len - j); return distance; } fn processCmd(io: Io, allocator: std.mem.Allocator, args: []const []const u8) !void { const client = @import("../ipc/client.zig"); const stdout = Io.File.stdout(); if (args.len == 0) { try fmt.boldText(stdout, io, "focusd process"); try stdout.writeStreamingAll(io, " — process monitor control\n\n"); try fmt.boldText(stdout, io, "COMMANDS\n"); try stdout.writeStreamingAll(io, " status Show process monitor status\n"); try stdout.writeStreamingAll(io, " scan Scan and kill blocked processes\n"); return; } const subcommand = args[0]; const c = client.Client.init(allocator, io); if (std.mem.eql(u8, subcommand, "status")) { const raw = c.sendRaw("process status") catch { try fmt.err(io, "could not connect to daemon"); try fmt.warn(io, "start with: sudo focusd daemon"); return; }; defer allocator.free(raw); try stdout.writeStreamingAll(io, raw); try stdout.writeStreamingAll(io, "\n"); } else if (std.mem.eql(u8, subcommand, "scan")) { const raw = c.sendRaw("process scan") catch { try fmt.err(io, "could not connect to daemon"); return; }; defer allocator.free(raw); try stdout.writeStreamingAll(io, raw); try stdout.writeStreamingAll(io, "\n"); } else { try fmt.err(io, "unknown process command"); } } fn iptablesCmd(io: Io, allocator: std.mem.Allocator, args: []const []const u8) !void { const client = @import("../ipc/client.zig"); const stdout = Io.File.stdout(); if (args.len == 0) { try fmt.boldText(stdout, io, "focusd iptables"); try stdout.writeStreamingAll(io, " — iptables blocking control\n\n"); try fmt.boldText(stdout, io, "COMMANDS\n"); try stdout.writeStreamingAll(io, " status Show iptables blocking status\n"); try stdout.writeStreamingAll(io, " cleanup Remove blocking chain\n"); return; } const subcommand = args[0]; const c = client.Client.init(allocator, io); if (std.mem.eql(u8, subcommand, "status")) { const raw = c.sendRaw("iptables status") catch { try fmt.err(io, "could not connect to daemon"); return; }; defer allocator.free(raw); try stdout.writeStreamingAll(io, raw); try stdout.writeStreamingAll(io, "\n"); } else if (std.mem.eql(u8, subcommand, "cleanup")) { const response = c.sendCommand("iptables cleanup") catch { try fmt.err(io, "could not connect to daemon"); return; }; switch (response) { .ok => try fmt.success(io, "iptables chain cleaned up"), else => try fmt.err(io, "could not cleanup iptables"), } } else { try fmt.err(io, "unknown iptables command"); } } fn printUsage(io: Io) !void { const w = Io.File.stdout(); // Title try fmt.boldText(w, io, "focusd"); try fmt.dimText(w, io, " — Deep Focus Media Control System\n\n"); // Commands section try fmt.boldText(w, io, "COMMANDS\n"); try fmt.coloredText(w, io, " mode", fmt.Color.bold); try fmt.dimText(w, io, " Manage focus modes\n"); try fmt.coloredText(w, io, " schedule", fmt.Color.bold); try fmt.dimText(w, io, " Manage time-based schedule rules\n"); try fmt.coloredText(w, io, " task", fmt.Color.bold); try fmt.dimText(w, io, " Manage task inventory\n"); try fmt.coloredText(w, io, " pomodoro", fmt.Color.bold); try fmt.dimText(w, io, " Pomodoro timer sessions\n"); try fmt.coloredText(w, io, " media", fmt.Color.bold); try fmt.dimText(w, io, " Weekly media curation\n"); try fmt.coloredText(w, io, " routine", fmt.Color.bold); try fmt.dimText(w, io, " Cal Newport daily routines\n"); try fmt.coloredText(w, io, " plan", fmt.Color.bold); try fmt.dimText(w, io, " Weekly planning & reports\n"); try fmt.coloredText(w, io, " sync", fmt.Color.bold); try fmt.dimText(w, io, " Sync with external services\n"); try fmt.coloredText(w, io, " status", fmt.Color.bold); try fmt.dimText(w, io, " Daemon status overview\n"); try fmt.coloredText(w, io, " config", fmt.Color.bold); try fmt.dimText(w, io, " View/set configuration\n"); try fmt.coloredText(w, io, " process", fmt.Color.bold); try fmt.dimText(w, io, " Process monitor control\n"); try fmt.coloredText(w, io, " iptables", fmt.Color.bold); try fmt.dimText(w, io, " Iptables blocking control\n"); try fmt.coloredText(w, io, " daemon", fmt.Color.bold); try fmt.dimText(w, io, " Run the background daemon\n"); try fmt.coloredText(w, io, " help", fmt.Color.bold); try fmt.dimText(w, io, " Show this help message\n"); try fmt.coloredText(w, io, " version", fmt.Color.bold); try fmt.dimText(w, io, " Show version\n"); // Subcommands try fmt.writeSection(w, io, "MODE SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " mode ", "Enter mode (work, reading, gaming, learning, writing, offline)", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " mode exit", "Exit current mode", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " mode list", "List available modes", fmt.Color.cyan); try fmt.writeSection(w, io, "SCHEDULE SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " schedule list", "Show schedule rules", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " schedule add", "Add schedule entry", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " schedule remove", "Remove schedule entry", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " schedule clear", "Clear all rules", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " schedule export", "Export to Waybar YAML", fmt.Color.cyan); try fmt.writeSection(w, io, "TASK SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " task add", "Add new task", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " task list", "Show all tasks", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " task done ", "Mark task completed", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " task status ", "Update task status", fmt.Color.cyan); try fmt.writeSection(w, io, "MEDIA SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " media list", "Show weekly media plan", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " media add", "Add media to plan", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " media done ", "Mark media consumed", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " media plan", "Friday review guide", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " media newsletters", "Fetch from Gmail", fmt.Color.cyan); try fmt.writeSection(w, io, "ROUTINE SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " routine morning", "Morning routine (05:00)", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " routine shutdown", "Shutdown routine (17:00)", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " routine friday", "Friday weekly review", fmt.Color.cyan); try fmt.writeSection(w, io, "POMODORO SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " pomodoro start", "Start work session (25 min)", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " pomodoro stop", "Stop current session", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " pomodoro pause", "Pause/resume session", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " pomodoro break", "Start break manually", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " pomodoro config", "Configure durations", fmt.Color.cyan); try fmt.writeSection(w, io, "PLAN SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " plan slots", "Available time slots", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " plan week", "Weekly planning view", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " plan review", "Friday review checklist", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " plan book", "Current book from plan", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " plan report", "Generate weekly report", fmt.Color.cyan); try fmt.writeSection(w, io, "SYNC SUBCOMMANDS"); try fmt.writeStatusLine(w, io, " sync push", "Push to blocks.adjarnor.dev", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " sync pull", "Pull from blocks.adjarnor.dev", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " sync calendar", "Show Google Calendar events", fmt.Color.cyan); try fmt.writeStatusLine(w, io, " sync calendar-update", "Write schedule to Calendar", fmt.Color.cyan); // Modes try fmt.writeSection(w, io, "MODES"); try fmt.writeStatusLine(w, io, " work", "Focused professional work", fmt.Color.green); try fmt.writeStatusLine(w, io, " reading", "Deep reading and learning", fmt.Color.green); try fmt.writeStatusLine(w, io, " gaming", "Gaming time", fmt.Color.green); try fmt.writeStatusLine(w, io, " learning", "Active learning sessions", fmt.Color.green); try fmt.writeStatusLine(w, io, " writing", "Creative writing", fmt.Color.green); try fmt.writeStatusLine(w, io, " routine", "Follow daily routine", fmt.Color.green); try fmt.writeStatusLine(w, io, " offline", "Complete disconnection", fmt.Color.green); // Examples try fmt.writeSection(w, io, "EXAMPLES"); try w.writeStreamingAll(io, " "); try fmt.dimText(w, io, "# Enter work mode"); try w.writeStreamingAll(io, "\n"); try w.writeStreamingAll(io, " focusd mode work --deep\n\n"); try w.writeStreamingAll(io, " "); try fmt.dimText(w, io, "# Add schedule rule"); try w.writeStreamingAll(io, "\n"); try w.writeStreamingAll(io, " focusd schedule add 09:00 17:00 work --deep\n\n"); try w.writeStreamingAll(io, " "); try fmt.dimText(w, io, "# Start pomodoro"); try w.writeStreamingAll(io, "\n"); try w.writeStreamingAll(io, " focusd pomodoro start\n\n"); try w.writeStreamingAll(io, " "); try fmt.dimText(w, io, "# Add task"); try w.writeStreamingAll(io, "\n"); try w.writeStreamingAll(io, " focusd task add \"Write thesis chapter\" high\n\n"); try w.writeStreamingAll(io, " "); try fmt.dimText(w, io, "# Export to Waybar"); try w.writeStreamingAll(io, "\n"); try w.writeStreamingAll(io, " focusd schedule export\n\n"); }