CLI Surface (memo)
Feature Overview [edit]
memo is the command-line front door to the same engine exposed by the MCP server. src/cli.ts is published as dist/cli.js through package.json bin.memo, with a #!/usr/bin/env node shebang. It gives agents and humans a deterministic, scriptable surface: --json for automation and plain/colorized text for terminals.
Representative journeys:
- Session startup:
memo bootstrap --cwd <repo> --slug <slice>(returns the token-budgeted brief). - Recall:
memo search "database lock" --kind trap --path src/db/client.ts. - Remember:
memo upsert --kind trap --title "..." --path-patterns "src/**/*.ts" --body "...". - Operate/diagnose:
memo status,memo doctor,memo sync,memo install-hooks,memo install-skills.
Every core tool is reachable through a generic 1:1 dispatcher; CLI-only extras add operational commands that have no MCP counterpart.
Business Rules & Logic [edit]
- Command resolution:
resolveCliCommandmaps kebab aliases to MCP tool names:check-versiontocheck_version,install-skillstoinstall_skills, andprompts,session,activityall toprompt. - Argument parsing (
parseCliArgs): supports--key valueand--key=value; the first non-flag token is the command and remaining tokens are positionals;--help/-hbefore a command prints general help, after a command prints subcommand help;--version/-vprints the package version;--jsonselects machine mode. Repeated--hostvalues are merged into a CSV string bysetParsedOption. - Exit codes:
runClireturns 0 on success and 1 on failure; the entry point callsprocess.exit(exitCode)only when non-zero.memo doctorandmemo status --checkadditionally return 1 when issues are detected. - Output contract: successful human-readable output goes to stdout via
console.log; with--json,printJsonwrites sanitized JSON to stdout (JSON.stringify(..., null, 2)). Errors and validation messages go to stderr viaconsole.error, except in--jsonmode where an error object{ isError: true, error, code }is printed to stdout so scripts can parse it. - Unknown commands print
Unknown command: <cmd>to stderr followed by the general help and return 1. - Read-only commands bypass side effects:
isReadOnlyStatusCommandcoversstatus,info,state, andsetup/configinvoked with--check,--status, or--info. These are dispatched beforeensureVaultStructureand skip CLI telemetry, guaranteeing no vault mutation. --jsondisables interactive prompts (install wizard, reconcile prompt).- Node runtime gate:
assertSupportedNodeRuntime()runs before dispatch; an unsupported Node emitsUNSUPPORTED_NODE(JSON) or a stderr message and returns 1. - CLI-only extras (not MCP tools):
doctor,rank,import,sync,reconcile,canvas,reset/reset-vault,restore,backups,vault,wiki,session,activity,install-hooks,hook install,shutdown/stop,init,export-vault,serve.
Technical Architecture [edit]
Core tool mapping: TOOL_NAMES currently holds 11 names (bootstrap, search, get, upsert, append, forget, gc, promote, check_version, install_skills, prompt). When parsed.command is a tool name, the handler builds a payload from parsed.options (dropping help/json), normalizes command-specific flags (for example --max-bytes to maxBytes, --kind to kinds), and calls executeTool(command, payload). Human renderers are per-command; --json prints ToolResponse.data verbatim.
Alias map (CLI_TOOL_ALIASES):
check-version -> check_version
install-skills -> install_skills
prompts -> prompt
session -> prompt
activity -> prompt
Purpose-built CLI handlers (selected):
status|info|statetorunStatusCheck(src/status-cmd.ts), rendered byformatStatusDashboard. Flags:--check,--cwd,--vaultRoot,--json. Strictly read-only;--checkexitsresult.ok ? 0 : 1; without--checkit exits 0.doctor [productRoot]torunDoctor(src/doctor.ts). Flags:--cwd,--vaultRoot,--productRoot,--check-capture <path>,--fix,--include-tracked,--rebuild,--json. Exitsresult.healthy ? 0 : 1;--check-captureexitsresult.healthy ? 0 : 1where healthy means the path isCAPTURED. See Diagnostics.import [productRoot]toimportWorkflowTree(src/importer.ts). Flags:--from,--cwd,--vaultRoot,--json. See Import.install-hooks/install-skillsto the shared wizard and permission gates (src/hooks-install.ts,src/skills-install.ts). See Agent Adapters and MCP Tooling.hook installtoinstallPreCommitHook(src/hook.ts): writes.git/hooks/pre-commit; an existing foreign hook is copied to<hook>.spec-memo.bakfirst.servetostartMcpServer(stdio) orstartSseServer(--sse);--status/--status-portopt the stdio companion in, while--sseenables it by default and--no-statusdisables it.setup/configtorunSetup(src/setup.ts). Flags:--mode,--url,--host,--print-mcp,--write-mcp,--auth-token,--vault-root,--vaultRoot,--json. Auth tokens are never written toconfig.json.
US-36 OpenCode snippet shape: generateHostMcpSnippet emits, for OpenCode, an mcp object with type: "local", command as a string array (["memo","serve"]), and enabled: true; with a non-default vault root it appends --vaultRoot <root> to command and adds environment.SPEC_MEMO_ROOT. No args key is emitted (OpenCode McpLocalConfig rejects type: "stdio" and separate args). All other hosts (cursor, vscode, antigravity, claude, generic) keep command: "memo" plus an args array. stripStatusAutostartArgs removes --status, --no-status, and --status-port from host MCP args so per-editor-window stdio servers never race on port 3124.
CLI binary resolution follows the AGENTS.md contract: prefer memo on PATH, else node dist/cli.js (or an absolute node <path>/dist/cli.js). Global symlinks point at dist/cli.js, so npm run build does not invalidate them.
Help and version: printGeneralHelp lists Core Memory Commands, Utility Commands, and Global Options (--json, -h/--help); printCommandHelp prints per-command usage, and for core tools it derives the property list from TOOL_DEFINITIONS[...].inputSchema. memo --version / -v prints getPackageVersion() (or { "version": ... } with --json). isCliMainEntry() guards the entry point so importing cli.ts from tests does not run the CLI.
Telemetry: runCli records a cli_command telemetry event with operation, duration, success, exit code, and isJson/subcommandHelp metadata, then flushes; read-only status and read-only wiki get are excluded so pure inspection never writes telemetry state.
Error rendering pattern (representative): each handler catches, then either printJson({ isError: true, error, code: '<COMMAND>_ERROR' }) or console.error('<Command> failed: ' + msg), returning 1. Codes include DOCTOR_ERROR, IMPORT_ERROR, INSTALL_HOOKS_FAILED, INSTALL_SKILLS_FAILED, SETUP_ERROR, HOOK_ERROR, RANK_ERROR, and SSE_ERROR.
Provenance: 0005-import-and-doctor.spec.md, 0009-cli-doctor.spec.md, 0031-memo-status.spec.md, 0045-us-36.spec.md.