// SPDX-License-Identifier: MIT // Copyright (C) 2026 Shogo Technologies, Inc. /** * Shogo Worker CLI — entry. * * `shogo login` — save an API key to ~/.shogo/config.json * `shogo worker start [flags]` — pair this machine with Shogo Cloud * `shogo worker stop` — stop the local worker * `shogo worker status` — show running/stopped * `shogo worker logs [--follow]` — tail worker logs * `shogo config show | set ` — inspect/modify config */ import { Command } from 'commander'; import pc from 'picocolors'; import { runStart } from './commands/start.ts'; import { runStop } from './commands/stop.ts'; import { runStatus } from './commands/status.ts'; import { runLogs } from './commands/logs.ts'; import { runLogin } from './commands/login.ts'; import { runConfigShow, runConfigSet } from './commands/config.ts'; import { runRuntimeInstall, runRuntimeUpdate, runRuntimeVersion, runRuntimeWhere, } from './commands/runtime.ts'; import { runProjectPull } from './commands/project-pull.ts'; import { runProjectPush } from './commands/project-push.ts'; import { runProjectCheckout } from './commands/project-checkout.ts'; import { runDoctor } from './commands/doctor.ts'; import { runAgent } from './commands/agent.ts'; const VERSION = '0.1.0'; const program = new Command(); program .name('shogo') .description('Shogo Cloud Agent Worker — run Shogo agents on your own machine.') .version(VERSION); program .command('login') .description('Pair this machine with Shogo Cloud (browser device flow, or --api-key for CI)') .option('--api-key ', 'CI escape hatch — skip the browser flow and use a key directly') .option('--cloud-url ', 'Shogo Cloud URL (default: https://studio.shogo.ai)') .option('--name ', 'Device label shown in the dashboard (default: hostname)') .option('--workspace ', 'Pre-select a workspace on the bridge picker') .option('--no-browser', 'Do not auto-open the browser; print the URL instead') .action((flags) => handle(() => runLogin(flags))); const worker = program.command('worker').description('Manage the local worker process'); worker .command('start') .description('Start the worker and pair with Shogo Cloud') .option('--name ', 'Instance name shown in the dashboard (default: hostname)') .option('--worker-dir ', 'Working directory for the worker (default: $PWD)') .option('--api-key ', 'API key (overrides config/env)') .option('--cloud-url ', 'Shogo Cloud URL') .option('--port ', 'Local HTTP port for the embedded API') .option('--proxy ', 'HTTPS proxy (overrides HTTPS_PROXY env)') .option('--project ', 'Pin to a single project (default: multi-project on demand)') .option('--runtime-bin ', 'Override the agent-runtime binary path') .option('--debug', 'Run preflight checks before starting') .option('--foreground', 'Run in foreground (don\'t detach)') .option('--no-auto-pull', 'Disable auto-clone of project workspaces on first request') .option('--projects-dir ', 'Root directory for cloned project workspaces (default: ~/.shogo/projects)') .option('--no-git', 'Force the file-transport sync path even when git is available') .action((flags) => handle(() => runStart(flags))); worker .command('stop') .description('Stop the running worker') .action(() => handle(runStop)); worker .command('status') .description('Show worker status') .action(() => handle(runStatus)); worker .command('logs') .description('Tail worker logs from ~/.shogo/logs/worker.log') .option('-f, --follow', 'Follow the log (tail -F)') .option('--err', 'Show stderr log instead') .action((flags) => handle(() => runLogs(flags))); const runtime = program .command('runtime') .description('Install / inspect the local agent-runtime binary'); runtime .command('install') .description('Download + verify the agent-runtime tarball into ~/.shogo/runtime/') .option('--channel ', 'stable | beta | nightly (default: stable)') .option('--version ', 'install a specific version (e.g. 0.1.0)') .option('--base-url ', 'override release base URL (default: GitHub Releases)') .option('--force', 'reinstall even if the same version is already on disk') .action((flags) => handle(() => runRuntimeInstall(flags))); runtime .command('version') .description('Print the installed agent-runtime version') .action(() => handle(() => runRuntimeVersion())); runtime .command('where') .description('Print the resolved agent-runtime binary path') .action(() => handle(() => runRuntimeWhere())); runtime .command('update') .description('Update agent-runtime to the latest in its channel') .option('--channel ', 'override channel (defaults to whatever is installed)') .option('--base-url ', 'override release base URL') .action((flags) => handle(() => runRuntimeUpdate(flags))); const config = program.command('config').description('Inspect or modify ~/.shogo/config.json'); config .command('show') .description('Print current config (API key masked)') .action(() => handle(runConfigShow)); config .command('set ') .description('Set apiKey / cloudUrl / name / workerDir / port / projectsDir') .action((k: string, v: string) => handle(() => runConfigSet(k, v))); const project = program.command('project').description('Clone/sync project workspaces between Shogo Cloud and this machine'); project .command('pull ') .description('Clone a project from Shogo Cloud into ~/.shogo/projects//') .option('--into ', 'Destination directory (default: ~/.shogo/projects/)') .option('--watch', 'After pull, watch the local dir and push edits back to cloud') .option('--include ', 'Comma-separated glob patterns (e.g. "src/**,*.md")') .option('--api-key ', 'Override API key for this run') .option('--cloud-url ', 'Override Shogo Cloud URL for this run') .action((id: string, flags) => handle(() => runProjectPull(id, flags))); project .command('push ') .description('Upload local workspace edits back to Shogo Cloud') .option('--from ', 'Source directory (default: ~/.shogo/projects/)') .option('--delete-remote', 'Mirror local deletions to cloud (DESTRUCTIVE)') .option('--include ', 'Comma-separated glob patterns') .option('--api-key ', 'Override API key for this run') .option('--cloud-url ', 'Override Shogo Cloud URL for this run') .action((id: string, flags) => handle(() => runProjectPush(id, flags))); project .command('checkout ') .description('Roll the local workspace to a specific git checkpoint (SHA or named checkpoint)') .option('--at ', 'Target SHA or checkpoint name (default: remote HEAD)') .option('--unshallow', 'Fetch full history before checking out (needed for old SHAs)') .option('--into ', 'Local dir override (default: ~/.shogo/projects/)') .option('--api-key ', 'Override API key for this run') .option('--cloud-url ', 'Override Shogo Cloud URL for this run') .action((id: string, flags) => handle(() => runProjectCheckout(id, flags))); program .command('doctor') .description("Diagnose & repair a wedged local Shogo database (clears failed migrations so the app can reboot)") .option('--check', 'Detect only — never modify the database') .option('--yes', 'Repair without the confirmation prompt') .option('--db ', 'Path to shogo.db (default: the desktop app\'s local database)') .option('--bun ', 'Path to a bun binary (default: bundled/PATH bun)') .option('--no-backup', 'Skip the pre-repair database backup (discouraged)') .action((flags) => handle(() => runDoctor(flags))); program .command('chat', { isDefault: true }) .description('Start the interactive coding agent in the current directory') .argument('[prompt...]', 'Optional one-shot prompt (runs non-interactively, like -p)') .option('-p, --print ', 'Headless one-shot: run a single turn and print the result') .option('--model ', 'Model id to use for new turns') .option('--cwd ', 'Working directory to operate in (default: $PWD)') .option('--runtime-bin ', 'Override the agent-runtime binary path') .option('--no-tui', 'Disable the rich TUI; use a plain readline renderer') .option('--api-key ', 'Override API key for this run') .option('--cloud-url ', 'Override Shogo Cloud URL for this run') .action((promptParts: string[], flags) => handle(() => runAgent({ print: flags.print ?? (promptParts.length ? promptParts.join(' ') : undefined), model: flags.model, cwd: flags.cwd, runtimeBin: flags.runtimeBin, noTui: flags.tui === false, apiKey: flags.apiKey, cloudUrl: flags.cloudUrl, }), ), ); program.showHelpAfterError(pc.dim('\n(use --help for usage)')); program.parseAsync(process.argv); async function handle(fn: () => Promise | void): Promise { try { await fn(); } catch (err: any) { console.error(pc.red(`✗ ${err?.message ?? err}`)); if (process.env.SHOGO_DEBUG) console.error(err?.stack); process.exit(1); } }