Files
paseo/packages/cli/src/cli.ts
Mohamed Boudra ffe76a7e57 Make workspace, agent, and schedule automation consistent (#2186)
* feat(workspaces): align agent and schedule automation

Make workspace identity the shared placement boundary for MCP and CLI, while caller identity determines parentage. Keep heartbeats minimal and cron-based without changing legacy rolling interval semantics.

* test(cli): expect canonical schedule cadence

* fix(automation): preserve workspace and schedule compatibility

* fix(automation): preserve compatibility edges

* fix(workspaces): align MCP lifecycle resolution

* fix(workspaces): preserve creation intent

* fix(workspaces): preserve branch and schedule identity
2026-07-18 14:04:48 +02:00

198 lines
7.6 KiB
TypeScript

import { Command, Option } from "commander";
import { createAgentCommand } from "./commands/agent/index.js";
import { createDaemonCommand } from "./commands/daemon/index.js";
import { createChatCommand } from "./commands/chat/index.js";
import { createLoopCommand } from "./commands/loop/index.js";
import { createPermitCommand } from "./commands/permit/index.js";
import { createProviderCommand } from "./commands/provider/index.js";
import { createScheduleCommand } from "./commands/schedule/index.js";
import { createSpeechCommand } from "./commands/speech/index.js";
import { createTerminalCommand } from "./commands/terminal/index.js";
import { createWorktreeCommand } from "./commands/worktree/index.js";
import { createWorkspaceCommand } from "./commands/workspace/index.js";
import { createHeartbeatCommand } from "./commands/heartbeat/index.js";
import { createHubCommand } from "./commands/hub/index.js";
import { createHooksCommand } from "./commands/hooks.js";
import { startCommand as daemonStartCommand } from "./commands/daemon/start.js";
import { runStatusCommand as runDaemonStatusCommand } from "./commands/daemon/status.js";
import { runRestartCommand as runDaemonRestartCommand } from "./commands/daemon/restart.js";
import { addLsOptions, runLsCommand } from "./commands/agent/ls.js";
import { addRunOptions, runRunCommand } from "./commands/agent/run.js";
import { addLogsOptions, runLogsCommand } from "./commands/agent/logs.js";
import { addDeleteOptions, runDeleteCommand } from "./commands/agent/delete.js";
import { addStopOptions, runStopCommand } from "./commands/agent/stop.js";
import { addSendOptions, runSendCommand } from "./commands/agent/send.js";
import { addInspectOptions, runInspectCommand } from "./commands/agent/inspect.js";
import { addWaitOptions, runWaitCommand } from "./commands/agent/wait.js";
import { addArchiveOptions, runArchiveCommand } from "./commands/agent/archive.js";
import { addAttachOptions, runAttachCommand } from "./commands/agent/attach.js";
import { addImportOptions, runImportCommand } from "./commands/agent/import.js";
import { withOutput } from "./output/index.js";
import { runCloneCommand } from "./commands/clone.js";
import { onboardCommand } from "./commands/onboard.js";
import {
addDaemonHostOption,
addJsonAndDaemonHostOptions,
addJsonOption,
} from "./utils/command-options.js";
import { resolveCliVersion } from "./version.js";
const VERSION = resolveCliVersion();
function resolveHostnamesOption(hostnames: unknown, allowedHosts: unknown): string | undefined {
if (typeof hostnames === "string") return hostnames;
if (typeof allowedHosts === "string") return allowedHosts;
return undefined;
}
export function createCli(): Command {
const program = new Command();
program
.name("paseo")
.description("Paseo CLI - control your AI coding agents from the command line")
.version(VERSION, "-v, --version", "output the version number")
// Global output options
.option("-o, --format <format>", "output format: table, json, yaml", "table")
.option("--json", "output in JSON format (alias for --format json)")
.option("-q, --quiet", "minimal output (IDs only)")
.option("--no-headers", "omit table headers")
.option("--no-color", "disable colored output");
// Primary agent commands (top-level)
addJsonAndDaemonHostOptions(addLsOptions(program.command("ls"))).action(withOutput(runLsCommand));
addJsonAndDaemonHostOptions(addRunOptions(program.command("run"))).action(
withOutput(runRunCommand),
);
addJsonAndDaemonHostOptions(addImportOptions(program.command("import"))).action(
withOutput(runImportCommand),
);
addJsonAndDaemonHostOptions(
program
.command("clone")
.description("Clone a GitHub repo and register it as a Paseo workspace")
.argument("<repo>", "GitHub repo in owner/repo format or a full git remote URL")
.requiredOption("--dir <path>", "Parent directory to clone into (for example: ~/workspace)"),
)
.addOption(
new Option("--protocol <protocol>", "Protocol for owner/repo shorthand repositories").choices(
["https", "ssh"],
),
)
.action(withOutput(runCloneCommand));
addDaemonHostOption(addAttachOptions(program.command("attach"))).action(runAttachCommand);
addDaemonHostOption(addLogsOptions(program.command("logs"))).action(runLogsCommand);
addJsonAndDaemonHostOptions(addStopOptions(program.command("stop"))).action(
withOutput(runStopCommand),
);
addJsonAndDaemonHostOptions(addDeleteOptions(program.command("delete"))).action(
withOutput(runDeleteCommand),
);
addJsonAndDaemonHostOptions(addSendOptions(program.command("send"))).action(
withOutput(runSendCommand),
);
addJsonAndDaemonHostOptions(addInspectOptions(program.command("inspect"))).action(
withOutput(runInspectCommand),
);
addJsonAndDaemonHostOptions(addWaitOptions(program.command("wait"))).action(
withOutput(runWaitCommand),
);
addJsonAndDaemonHostOptions(addArchiveOptions(program.command("archive"))).action(
withOutput(runArchiveCommand),
);
// Top-level local daemon shortcuts
program.addCommand(onboardCommand());
program.addCommand(daemonStartCommand());
program.addCommand(createHooksCommand());
addJsonOption(
program
.command("status")
.description('Show local daemon status (alias for "paseo daemon status")'),
)
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
.action(withOutput(runDaemonStatusCommand));
addJsonOption(
program
.command("restart")
.description('Restart local daemon (alias for "paseo daemon restart")'),
)
.option("--home <path>", "Paseo home directory (default: ~/.paseo)")
.option("--timeout <seconds>", "Wait timeout before force step (default: 15)")
.option("--force", "Send SIGKILL if graceful stop times out")
.option(
"--listen <listen>",
"Listen target for restarted daemon (host:port, port, or unix socket)",
)
.option("--port <port>", "Port for restarted daemon listen target")
.option("--no-relay", "Disable relay on restarted daemon")
.option("--no-mcp", "Disable Agent MCP on restarted daemon")
.option(
"--hostnames <hosts>",
'Daemon hostnames (comma-separated, e.g. "myhost,.example.com" or "true" for any)',
)
.addOption(new Option("--allowed-hosts <hosts>").hideHelp())
.action(
withOutput((...args) => {
const [options, command] = args.slice(-2) as [(typeof args)[number], Command];
return runDaemonRestartCommand(
{
...options,
hostnames: resolveHostnamesOption(options.hostnames, options.allowedHosts),
},
command,
);
}),
);
// Advanced agent commands (less common operations)
program.addCommand(createAgentCommand());
// Daemon commands
program.addCommand(createDaemonCommand());
program.addCommand(createHubCommand());
// Chat commands
program.addCommand(createChatCommand());
// Terminal commands
program.addCommand(createTerminalCommand());
// Loop commands
program.addCommand(createLoopCommand());
// Schedule commands
program.addCommand(createScheduleCommand());
program.addCommand(createHeartbeatCommand());
// Permission commands
program.addCommand(createPermitCommand());
// Provider commands
program.addCommand(createProviderCommand());
// Speech model commands
program.addCommand(createSpeechCommand());
// Workspace commands
program.addCommand(createWorkspaceCommand());
// COMPAT(worktreeCli): legacy command alias added before workspace was the product unit.
// Added in v0.2.0; remove after 2027-01-17.
program.addCommand(createWorktreeCommand(), { hidden: true });
return program;
}