/** * CLI Entry Point * * Orchestration function (Rule 10.1) - routes commands to handlers */ import { log as uiLog } from '@celilo/cli-display'; import { COMMANDS, type CommandDef, EXIT_BLOCKED, resolveRemote, runRemoteClient, } from '@celilo/core'; import { CLIServerRequestSchema, parseJsonWithValidation } from '../validation/schemas'; import { handleApiAuthorizedKeys, handleApiGrant, handleApiKeyNew, handleApiList, handleApiRevoke, } from './commands/api'; import { handleAptUpgrade } from './commands/apt-upgrade'; import { handleCapabilityInfo } from './commands/capability-info'; import { handleCapabilityList } from './commands/capability-list'; import { handleCommands } from './commands/commands-json'; import { handleCompletion } from './commands/completion'; import { handleConsoleGet, handleConsoleStatus } from './commands/console'; import { handleDnsRegistrations } from './commands/dns'; import { handleEventsAck, handleEventsDrain, handleEventsEmit, handleEventsFail, handleEventsInstallDaemon, handleEventsListFailed, handleEventsListPending, handleEventsListSubscribers, handleEventsListUnanswered, handleEventsRepair, handleEventsReply, handleEventsRespond, handleEventsRestartDaemon, handleEventsResyncSubscriptions, handleEventsRun, handleEventsRunHook, handleEventsShowDaemon, handleEventsStatus, handleEventsTail, handleEventsUninstallDaemon, } from './commands/events'; import { handleFirewallInterfaceList } from './commands/firewall-interface-list'; import { handleHookRun } from './commands/hook-run'; import { handleIpamIpEdit, handleIpamIpListReservations, handleIpamIpReserve, handleIpamIpUnreserve, handleIpamListAllocations, handleIpamShow, handleIpamVmidListReservations, handleIpamVmidReserve, handleIpamVmidUnreserve, } from './commands/ipam'; import { handleMachineAdd } from './commands/machine-add'; import { handleMachineEarmark } from './commands/machine-earmark'; import { handleMachineList } from './commands/machine-list'; import { handleMachineRemove } from './commands/machine-remove'; import { handleMachineStatus } from './commands/machine-status'; import { moduleAudit } from './commands/module-audit'; import { handleModuleBuild } from './commands/module-build'; import { handleModuleChangeset } from './commands/module-changeset'; import { handleModuleCheck } from './commands/module-check'; import { handleModuleConfigGet, handleModuleConfigSet, handleModuleConfigUnset, } from './commands/module-config'; import { handleModuleDeploy } from './commands/module-deploy'; import { handleModuleGenerate } from './commands/module-generate'; import { handleModuleHealth } from './commands/module-health'; import { handleModuleImport } from './commands/module-import'; import { handleModuleJail } from './commands/module-jail'; import { handleModuleJournal } from './commands/module-journal'; import { handleModuleList } from './commands/module-list'; import { handleModuleLogs } from './commands/module-logs'; import { handleModuleOperations } from './commands/module-operations'; import { handleModulePause, handleModuleUnpause } from './commands/module-pause'; import { handleModulePublish } from './commands/module-publish'; import { handleModuleRemove } from './commands/module-remove'; import { handleModuleSearch } from './commands/module-search'; import { handleModuleShowConfig, handleModuleShowZone } from './commands/module-show'; import { handleModuleStatus } from './commands/module-status'; import { handleModuleTerraformUnlock } from './commands/module-terraform-unlock'; import { handleModuleTypesCheck, handleModuleTypesGenerate } from './commands/module-types'; import { handleModuleUpdate } from './commands/module-update'; import { handleModuleUpgrade } from './commands/module-upgrade'; import { moduleVerify } from './commands/module-verify'; import { handleModuleVersion } from './commands/module-version'; import { handleModuleWhere } from './commands/module-where'; import { handlePackage } from './commands/package'; import { handleProxmoxInstanceList } from './commands/proxmox-instance-list'; import { handleProxmoxInstanceResize } from './commands/proxmox-instance-resize'; import { handleProxmoxNodeList } from './commands/proxmox-node-list'; import { handleSecretList } from './commands/secret-list'; import { handleSecretSet } from './commands/secret-set'; import { handleServiceAddDigitalOcean } from './commands/service-add-digitalocean'; import { handleServiceAddProxmox } from './commands/service-add-proxmox'; import { handleServiceConfigGet } from './commands/service-config-get'; import { handleServiceConfigSet } from './commands/service-config-set'; import { handleServiceList } from './commands/service-list'; import { handleServiceReconfigure } from './commands/service-reconfigure'; import { handleServiceRemove } from './commands/service-remove'; import { handleServiceSetCredentials } from './commands/service-set-credentials'; import { handleServiceVerify } from './commands/service-verify'; import { handleStatus } from './commands/status'; import { handleSubscribersAdd } from './commands/subscribers-add'; import { handleSubscribersInstallDaemon, handleSubscribersUninstallDaemon, } from './commands/subscribers-install-daemon'; import { handleSubscribersList } from './commands/subscribers-list'; import { handleSubscribersRemove } from './commands/subscribers-remove'; import { handleSubscribersServe } from './commands/subscribers-serve'; import { handleSubscribersStatus } from './commands/subscribers-status'; import { handleSubscribersTest } from './commands/subscribers-test'; import { handleSystemApplyConfig } from './commands/system-apply-config'; import { handleSystemAudit } from './commands/system-audit'; import { handleSystemConfigGet, handleSystemConfigSet } from './commands/system-config'; import { handleSystemDiscoverNetwork } from './commands/system-discover-network'; import { handleSystemDoctor } from './commands/system-doctor'; import { handleSystemEnsureFleetKey } from './commands/system-ensure-fleet-key'; import { handleSystemInit } from './commands/system-init'; import { handleSystemMigrate } from './commands/system-migrate'; import { handleSystemSecretGet } from './commands/system-secret-get'; import { handleSystemSecretSet } from './commands/system-secret-set'; import { handleSystemUpdate } from './commands/system-update'; import { handleSystemVaultPassword } from './commands/system-vault-password'; import { getCompletions } from './completion'; import { parseArguments, validateFlags } from './parser'; import type { CommandResult } from './types'; /** * Look up a command definition from the registry and validate flags. * Walks the full subcommand chain (e.g., ipam -> ip -> reserve) to find * the leaf command where flags are defined. * Returns an error CommandResult if unknown flags are found, or null if valid. */ function checkFlags( command: string, subcommand: string | undefined, flags: Record, args: string[] = [], ): CommandResult | null { // Skip validation for help requests if (flags.help || flags.h) return null; const topDef = COMMANDS.find((c) => c.name === command); if (!topDef) return null; let commandDef: CommandDef | undefined = subcommand ? topDef.subcommands?.find((s) => s.name === subcommand) : topDef; if (!commandDef) return null; // Walk deeper into nested subcommands using args // e.g., for "ipam ip reserve", subcommand='ip' and args=['reserve', ...] // We need to find the 'reserve' sub-subcommand to get its flags for (const arg of args) { const deeper: CommandDef | undefined = commandDef?.subcommands?.find((s) => s.name === arg); if (!deeper) break; commandDef = deeper; } if (!commandDef) return null; const error = validateFlags(flags, commandDef); if (error) { return { success: false, error }; } return null; } /** * Display CLI version. Reads the npm package version from this * package's manifest at runtime — single source of truth, so * `bun publish` bumping the version automatically updates what * `celilo --version` reports. */ function displayVersion(): CommandResult { const pkg = require('../../package.json') as { version: string }; return { success: true, message: `celilo ${pkg.version}` }; } /** * Display general help message */ function displayHelp(): CommandResult { const helpText = ` Celilo - Home Lab Orchestration System Usage: celilo [subcommand] [args...] [options] Commands: status Show system and module status audit Top-level alias for 'system audit' alerts View alerts raised by monitors monitor Manage what celilo watches (health checks on a schedule) person Manage people celilo can reach route Manage how each person is reached (transport + address) escalation-policy Manage who gets paged, and in what order events SQLite event-bus operations (status, tail, run dispatcher, etc.) capability View registered module capabilities dns View DNS bookkeeping (registrations ledger) package Create distributable .netapp packages from module source module Manage modules (import, list, configure, build, generate) service Manage container services (Proxmox, Digital Ocean) storage Manage backup storage destinations backup Create and manage backups restore Restore a celilo-mgmt backup from a local file (fresh-bootstrap path) firewall Inspect a firewall's interfaces (classification, on demand) machine Manage machine pool (bring-your-own-hardware) system Manage system configuration apt-upgrade Upgrade the deb-installed celilo packages + apply migrations ipam Manage IP address and VMID allocations and reservations proxmox Proxmox cluster introspection (proxmox node list) publish Publish workspace packages to npm and modules to celilo.computer registry Administer the module registry (append-safe publish-token management) token Manage contributor identity tokens (idp-issued per-user publish tokens) subscribers Manage build-bus subscribers (cross-machine publish-event delivery) api Manage remote-API access (principals, grants, authorized_keys) completion Generate shell completion scripts (bash/zsh) commands Print the CLI command registry as JSON (drives @celilo/mcp) console Narrow read-only projections for the web console help, --help, -h Show this help message Run any command on a remote celilo-mgr over SSH: celilo --remote (or set CELILO_REMOTE=) For command-specific help: celilo package --help celilo module --help celilo secret --help celilo service --help celilo machine --help celilo system --help celilo ipam --help Enable tab completion: # Bash celilo completion bash >> ~/.bashrc && source ~/.bashrc # Zsh celilo completion zsh >> ~/.zshrc && source ~/.zshrc Examples: celilo package ./modules/homebridge celilo module import ./modules/homebridge celilo module list celilo module build caddy celilo module secret set homebridge api_key mykey123 celilo system config set dns.primary 192.168.0.1 celilo system vault-password `; return { success: true, message: helpText.trim(), }; } /** * Display package command help */ function displayPackageHelp(): CommandResult { const helpText = ` Celilo - Module Packaging Usage: celilo package [options] Description: Creates a distributable .netapp package from a module source directory. The package includes checksums and a signature for integrity verification. Options: --output Output path for the package (default: /.netapp) Examples: celilo package ./modules/homebridge celilo package ./my-module --output /tmp/test.netapp celilo package ../custom-module Related Commands: celilo module import Import a module (registry name or local path) celilo module verify Verify package integrity `; return { success: true, message: helpText.trim(), }; } /** * Display capability command help */ function displayEventsHelp(): CommandResult { const helpText = ` Celilo - SQLite Event Bus Usage: celilo events [args...] Subcommands: status Print bus health as JSON tail [--type T] [--limit N] Recent events as JSON list-subscribers List persistent bus subscribers resync-subscriptions Rebuild subscribers from deployed modules' manifests (after a restore/migration) list-pending [--subscriber] List pending deliveries list-failed [--subscriber] List failed/abandoned deliveries with a true total list-unanswered List interview questions nobody has answered yet drain [--concurrency N] Process pending deliveries once and return run [--poll-ms N] Run the long-running dispatcher (foreground) emit [] Emit an event (operator/test path) reply Answer one pending interview query by id (config/secret/ensure) ack Mark a running delivery succeeded fail --error MSG Mark a running delivery failed repair Crash-recovery sweep without starting the dispatcher resume Alias for repair (acknowledges halt-on-recovery) respond Run the terminal responder; answer deploy prompts from another shell install-daemon [--system] Write a systemd/launchd unit for the dispatcher (--system: management-plane scope) uninstall-daemon [--system] Remove the installed supervisor unit restart-daemon [--system] Restart the dispatcher and verify the new process is on current code show-daemon [--system] Print the currently installed unit file Description: The event bus is a SQLite-backed pub/sub layer for celilo modules. Modules declare \`subscriptions:\` in their manifests; \`celilo module deploy\` emits lifecycle events that subscribers react to. See infra/openspec/specs/event-bus/spec.md for the full design. Examples: celilo events run # foreground dispatcher celilo events status # is anything stuck? celilo events tail --type deploy.completed.lunacycle # filter by type celilo events emit deploy.completed.lunacycle '{}' # operator-fired event celilo events tail --type 'config.required.*' # see pending deploy questions celilo events reply 42 '"example.net"' # answer query #42 (config) `; return { success: true, message: helpText.trim() }; } function displaySubscribersHelp(): CommandResult { const helpText = ` Celilo - Build-Bus Subscribers Usage: celilo subscribers [args...] [options] Subcommands: list Show registered subscribers (truncated secret fingerprints) add --secret Add a subscriber (replaces any with the same URL) [--name