import { Command } from "commander"; import { onboard } from "./commands/onboard.js"; import { doctor } from "./commands/doctor.js"; import { envCommand } from "./commands/env.js"; import { configure } from "./commands/configure.js"; import { addAllowedHostname } from "./commands/allowed-hostname.js"; import { heartbeatRun } from "./commands/heartbeat-run.js"; import { runCommand } from "./commands/run.js"; import { bootstrapCeoInvite } from "./commands/auth-bootstrap-ceo.js"; import { dbBackupCommand } from "./commands/db-backup.js"; import { registerContextCommands } from "./commands/client/context.js"; import { registerCompanyCommands } from "./commands/client/company.js"; import { registerIssueCommands } from "./commands/client/issue.js"; import { registerAgentCommands } from "./commands/client/agent.js"; import { registerApprovalCommands } from "./commands/client/approval.js"; import { registerActivityCommands } from "./commands/client/activity.js"; import { registerDashboardCommands } from "./commands/client/dashboard.js"; import { applyDataDirOverride, type DataDirOptionLike } from "./config/data-dir.js"; const program = new Command(); const DATA_DIR_OPTION_HELP = "Paperclip data directory root (isolates state from ~/.paperclip)"; program .name("paperclipai") .description("Paperclip CLI — setup, diagnose, and configure your instance") .version("0.2.7"); program.hook("preAction", (_thisCommand, actionCommand) => { const options = actionCommand.optsWithGlobals() as DataDirOptionLike; const optionNames = new Set(actionCommand.options.map((option) => option.attributeName())); applyDataDirOverride(options, { hasConfigOption: optionNames.has("config"), hasContextOption: optionNames.has("context"), }); }); program .command("onboard") .description("Interactive first-run setup wizard") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("-y, --yes", "Accept defaults (quickstart + start immediately)", false) .option("--run", "Start Paperclip immediately after saving config", false) .action(onboard); program .command("doctor") .description("Run diagnostic checks on your Paperclip setup") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("--repair", "Attempt to repair issues automatically") .alias("--fix") .option("-y, --yes", "Skip repair confirmation prompts") .action(async (opts) => { await doctor(opts); }); program .command("env") .description("Print environment variables for deployment") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .action(envCommand); program .command("configure") .description("Update configuration sections") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("-s, --section
", "Section to configure (llm, database, logging, server, storage, secrets)") .action(configure); program .command("db:backup") .description("Create a one-off database backup using current config") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("--dir ", "Backup output directory (overrides config)") .option("--retention-days ", "Retention window used for pruning", (value) => Number(value)) .option("--filename-prefix ", "Backup filename prefix", "paperclip") .option("--json", "Print backup metadata as JSON") .action(async (opts) => { await dbBackupCommand(opts); }); program .command("allowed-hostname") .description("Allow a hostname for authenticated/private mode access") .argument("", "Hostname to allow (for example dotta-macbook-pro)") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .action(addAllowedHostname); program .command("run") .description("Bootstrap local setup (onboard + doctor) and run Paperclip") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("-i, --instance ", "Local instance id (default: default)") .option("--repair", "Attempt automatic repairs during doctor", true) .option("--no-repair", "Disable automatic repairs during doctor") .action(runCommand); const heartbeat = program.command("heartbeat").description("Heartbeat utilities"); heartbeat .command("run") .description("Run one agent heartbeat and stream live logs") .requiredOption("-a, --agent-id ", "Agent ID to invoke") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("--context ", "Path to CLI context file") .option("--profile ", "CLI context profile name") .option("--api-base ", "Base URL for the Paperclip server API") .option("--api-key ", "Bearer token for agent-authenticated calls") .option( "--source ", "Invocation source (timer | assignment | on_demand | automation)", "on_demand", ) .option("--trigger ", "Trigger detail (manual | ping | callback | system)", "manual") .option("--timeout-ms ", "Max time to wait before giving up", "0") .option("--json", "Output raw JSON where applicable") .option("--debug", "Show raw adapter stdout/stderr JSON chunks") .action(heartbeatRun); registerContextCommands(program); registerCompanyCommands(program); registerIssueCommands(program); registerAgentCommands(program); registerApprovalCommands(program); registerActivityCommands(program); registerDashboardCommands(program); const auth = program.command("auth").description("Authentication and bootstrap utilities"); auth .command("bootstrap-ceo") .description("Create a one-time bootstrap invite URL for first instance admin") .option("-c, --config ", "Path to config file") .option("-d, --data-dir ", DATA_DIR_OPTION_HELP) .option("--force", "Create new invite even if admin already exists", false) .option("--expires-hours ", "Invite expiration window in hours", (value) => Number(value)) .option("--base-url ", "Public base URL used to print invite link") .action(bootstrapCeoInvite); program.parseAsync().catch((err) => { console.error(err instanceof Error ? err.message : String(err)); process.exit(1); });