Reference
CLI reference
Complete reference for the nauro CLI, a Typer app whose entry point is packages/nauro/src/nauro/cli/main.py. The CLI has two layers: hand-written commands for setup, connection, project and registry management, sync, and inspection; and auto-generated commands that mirror the MCP read and write tools one-for-one.
Overview: how the CLI is structured
There are 11 MCP tools total (8 read, 3 write). The local stdio server registers 10 of them: 7 read plus 3 write, because list_projects is remote-only and is also excluded from the CLI autogen allowlist.
Each hand-written command's flags are taken verbatim from its typer.Option and typer.Argument definitions; each autogen command's flags are derived from the tool's JSON input_schema: a required string or integer becomes a positional argument, everything else becomes an option, project_id becomes --project, and cwd is dropped.
- The CLI is a Typer application named
nauro, defined inpackages/nauro/src/nauro/cli/main.py. The app help string reads: "Set your project's doctrine once; every connected AI agent inherits it." A second line appends: "Run 'nauro telemetry --help' to manage anonymous usage telemetry." - A global
--version/-Vflag printsnauro <version>and exits via the eager callback_version_callback. The root callback also runsconsent.maybe_prompt()(telemetry consent) on each invocation. no_args_is_help=True: runningnaurowith no command prints help._register_commands()registers the hand-written surface: top-level commandsinit,adopt,attach,link,note,sync,log,import,serve,render-plugin(hidden), andstatus; plus the Typer sub-appsprojects,questions,hook,setup,config,validate,auth, andtelemetry.- After the hand-written commands,
register_autogen_commands(app)adds one command per allowlisted MCP tool, andinstrument_app(app)wraps the app for telemetry. - Two registered commands are not user-facing features:
render-pluginis registered withhidden=True, and the entirehooksub-app is invoked by an AI agent's hook runner, not by a human at a prompt.
nauro init nauro adopt nauro attach nauro link
nauro note nauro sync nauro log nauro import
nauro serve nauro status
nauro projects … nauro questions … nauro hook … nauro setup …
nauro config … nauro validate … nauro auth … nauro telemetry …
# auto-generated from MCP tools (see "MCP tool to CLI mapping"):
nauro get-context nauro get-raw-file nauro list-decisions
nauro get-decision nauro diff-since-last-session
nauro search-decisions nauro check-decision
nauro propose-decision nauro flag-question nauro update-state
$ nauro --version
# -> nauro <version>
The version string format beyond nauro <version> is dynamic: it is read from nauro.__version__ at runtime, so the current version number depends on the installed build.
Source: packages/nauro/src/nauro/cli/main.py (app, main callback, _register_commands, register_autogen_commands).
Setup and connect
nauro init [NAME] registers a new project and scaffolds its store. NAME defaults to demo-project. Flags: --add-repo PATH (repeatable; associate existing repo paths with the project, defaults to cwd), --demo (seed a sample project with pre-written decisions), --cloud (create a cloud-scoped project via the remote MCP server's POST /projects), --force (overwrite the cwd's .nauro/config.json only).
--demo and --cloud are mutually exclusive and rejected at command entry with typer.BadParameter. Local init writes .nauro/config.json (commit it) and mints a ULID, with no network calls. --add-repo against a cloud-mode project is rejected; use nauro attach instead.
nauro adopt bootstraps an existing repo in one shot: detect the repo root, register a v2 project (local mode), scaffold the store, wire MCP, and materialize skills across Claude Code, Cursor, and Codex. Flags: --name NAME (default: repo dir basename), --repo PATH (default: cwd), --print-prompt (print the canonical /nauro-adopt skill body and exit; mutually exclusive with all other flags), --no-setup-and-skills, --with-subagents (install @nauro-planner, @nauro-executor, @nauro-reviewer, @nauro-tech-lead into ~/.claude/agents/), --force-overwrite, and --with-skills (install opt-in skills alongside the always-installed /nauro-adopt).
adopt requires a git repo and refuses with a "run git init first" message otherwise. On an already-adopted repo (where a .nauro/config.json exists) it exits 1 unless re-run with --with-subagents, --with-skills, or --force-overwrite, in which case it routes to the materialize step without re-registering.
The always-installed skill is /nauro-adopt; the opt-in skills added by --with-skills are nauro-ship-task, nauro-handoff, and nauro-context. nauro-handoff and nauro-context compose only existing MCP tools, with no subagent dependency.
Wiring the MCP server into each agent
nauro setup claude-coderegisters the MCP server in each repo's project-scope.mcp.jsonundermcpServers["nauro"]with the entry{"command": "<nauro>", "args": ["serve", "--stdio"]}. Flags:--project NAME,--remove, and--with-hooks(wire the advisory UserPromptSubmit hook into each repo's.claude/settings.json).nauro setup cursorwrites<repo>/.cursor/mcp.jsonfor each repo (samemcpServers["nauro"]entry shape). Flags:--project,--remove.nauro setup codexwrites the user-global~/.codex/config.tomlunder[mcp_servers.nauro](same command and args entry). Flag:--remove.nauro setup allconfigures Claude Code, Cursor, and Codex in one call. Flags:--project,--remove,--with-subagents,--force-overwrite,--with-skills,--with-hooks.setup_apphas noinvoke_without_commandcallback, so runningnauro setupwith no subcommand prints help. (This is unlikenauro projects, which lists on a bare invocation.)- The local-install MCP server name is
nauro(lowercase) across all three surfaces. The remote cloud connector must be named exactlyNauro(capitalized) so the bundled subagents resolve its tools;nauro adopt --with-subagentssurfaces this requirement.
Auth and cloud projects
nauro auth loginruns Auth0 Authorization Code with PKCE via a localhost redirect (port 18457, redirect URIhttp://localhost:18457/callback) and stores tokens (sub,sanitized_sub,user_id,access_token,refresh_token) in~/.nauro/config.json.nauro auth statusshows auth state and exits 1 if not authenticated;nauro auth logoutclears stored credentials. Default API URL:https://mcp.nauro.ai. Auth0 audience and MCP connector URL:https://mcp.nauro.ai/mcp(enter exactly, no trailing slash). The login success message instructs Codex users to addmcp_oauth_callback_port = 8765to~/.codex/config.tomlfor the remote connector.nauro attach PROJECT_IDis the cloud equivalent ofinit --add-repo: it associates the current repo with an existing cloud project, verifying membership againstGET /projectsfirst. Flag:--repo PATH(default cwd). PROJECT_ID is a ULID argument.nauro link --cloudpromotes the current repo's local-only project to cloud: it callscreate_project(name)to mint a cloud project_id, re-keys the local store and registry entry (rename_project_id_v2), rewrites the repo config to cloud mode, and does a best-effort initial push. It requires auth (runnauro auth loginfirst) and a local-mode project. The promotion is one-way; there is no inverse unlink.nauro linkwith no target errors and suggestsnauro link --cloud.
The Codex remote-connector callback port 8765 is confirmed in both the login message and the README. The README also states a minimum Codex version for the remote connector, given there as prose guidance.
$ nauro init my-project
$ nauro setup claude-code # or: nauro setup all
$ nauro init --demo
$ nauro check-decision "Add a WebSocket endpoint for live task updates"
$ nauro adopt --with-subagents
# then restart your agent and invoke /nauro-adopt
$ nauro auth login
$ nauro link --cloud # one-time: promote the local project to cloud
$ nauro sync
Source: init.py; adopt.py; setup.py (claude_code, cursor, codex, all_, _configure_json_mcp, _configure_codex, SUBAGENTS_CONNECTOR_NAME_NOTICE); auth.py (login, status, logout; DEFAULT_API_URL, DEFAULT_AUTH0_AUDIENCE, REDIRECT_PORT, REDIRECT_URI); attach.py; link.py; README (Quickstart, Connect, Remote connector).
Decisions and context (read)
The read-side MCP tools are exposed as CLI commands by cli/autogen.py. Each prints the tool's JSON envelope (json.dumps(..., indent=2)) to stdout. They resolve the project from the cwd's .nauro/config.json, or via --project NAME.
nauro check-decision PROPOSED_APPROACH [--context TEXT] [--project NAME]runs thecheck_decisionkernel against the local store and returns related decisions (via Tier 1 plus Tier 2 BM25 retrieval) along with a deterministic assessment string. It does not judge conflicts. PROPOSED_APPROACH is a positional argument (required string).nauro get-context [--level L0|L1|L2] [--project]returns project context. Default level L0 is concise (project summary, current state, top open questions, and the last 10 active decisions with titles and dates); L1 (working set) adds full decision bodies for recent decisions; L2 is the full dump of everything in the store.nauro search-decisions QUERY [--limit 10] [--include-superseded/--no-include-superseded] [--project]BM25-ranks decisions against titles and rationale and returns active decisions by default. QUERY is a positional argument (required, non-empty). Default--limitis 10;include_supersededdefaults to False.nauro list-decisions [--limit 20] [--include-superseded/--no-include-superseded] [--project]browses decision history. Default--limitis 20;include_supersededdefaults to False.nauro get-decision NUMBER [--mode header|full] [--project]returns one decision by number. Default modefullreturns complete markdown;headerreturns the triage frontmatter (status, supersession, date, type, confidence) plus the title and a short lede from the rationale. NUMBER is a positional integer argument.nauro diff-since-last-session [--days N] [--project]diffs the two most recent session-scoped snapshots when--daysis omitted, or finds the nearest snapshot to N days ago and diffs against the latest when--daysis provided.daysis an optional integer with no default.nauro get-raw-file PATH [--project]returns the raw markdown of any store file. Valid paths includeproject.md,state.md,stack.md,open-questions.md, anddecisions/042-some-decision.md. PATH is a positional argument (required string).
Note: list_projects is the 8th read tool but is remote-only, so it is not in the CLI autogen allowlist and there is no nauro list-projects MCP command. The unrelated nauro projects list lists the local registry, not cloud projects.
$ nauro check-decision "Adopt Redis for the read cache"
$ nauro search-decisions "authentication" --limit 5
$ nauro get-decision 42 --mode header
$ nauro get-context --level L1
Source: cli/autogen.py (AUTOGEN_ALLOWLIST, _schema_to_typer_params, _make_command); nauro_core/mcp_tools.py (GET_CONTEXT, GET_RAW_FILE, LIST_DECISIONS, GET_DECISION, DIFF_SINCE_LAST_SESSION, SEARCH_DECISIONS, CHECK_DECISION input_schema); nauro/mcp/tools.py (tool_* adapters).
Decisions and questions (write)
Three write tools are exposed as CLI commands via autogen: propose-decision, flag-question, and update-state. They run the same validation pipeline as the MCP tools (dispatching to the same tool_<name> adapter) and emit the JSON envelope.
nauro propose-decision TITLE RATIONALE [options] records a decision. TITLE and RATIONALE are positional arguments (both required strings). Options derived from the input_schema:
--operation add|update|supersede(defaultadd)--affected-decision-id ID(required when operation isupdateorsupersede)--rejected JSON(a JSON array of{alternative, reason}objects)--confidence high|medium|low(defaultmedium)--decision-type {architecture, library_choice, pattern, refactor, api_design, infrastructure, data_model}(no default)--reversibility easy|moderate|hard(no default)--files-affected PATH(repeat the flag per path)--resolves-questions ID(repeatable)--project NAME
--rejected (a list[dict] schema property) accepts three input forms via cli/_json_input.parse_json_list_of_dicts: an inline JSON literal, @file.json (read a file), or - (read stdin). Malformed input (bad JSON, non-array, non-object elements, a missing file, or empty stdin) raises typer.BadParameter, which is rendered to stderr with exit code 2; the adapter is never invoked.
--files-affected and --resolves-questions are list[str] schema properties, so Typer aggregates the repeated flags natively (repeat the flag once per value).
nauro flag-question [--question TEXT] [--context TEXT] [--targets ID] [--resolved-by ID] [--project] flags an open question or, with --resolved-by set, stamps the --targets entries as resolved against that decision id. Pass exactly one of --question or --resolved-by. --targets is a repeatable list; --resolved-by takes a decision id.
nauro update-state DELTA [--project] records a progress delta and triggers a snapshot. It checks for contradictions with recent state and returns a warning, but still applies the delta. DELTA is a positional argument (required string).
There are also two simpler hand-written write commands rooted at nauro note TEXT: it records a decision by default, or a question if TEXT ends with ? (and --decision is not set) or if --question / -q is passed.
Options: --rationale / -r, --confidence / -c (default medium), --decision / -d (force decision), --question / -q (force question), and --project. On each write it regenerates AGENTS.md in associated repos (warn-then-regen).
$ nauro propose-decision "Adopt Redis" "In-memory cache for hot read paths" \
--files-affected src/cache.py --files-affected src/api.py \
--rejected '[{"alternative": "Memcached", "reason": "Less feature-rich"}]'
$ nauro propose-decision "Adopt Redis" "..." --rejected @rejected.json
$ echo '[{"alternative":"Memcached","reason":"Less feature-rich"}]' | \
nauro propose-decision "Adopt Redis" "..." --rejected -
$ nauro note "Switched to Postgres for the audit log" -r "Need transactional writes"
$ nauro note "Should we shard the events table?" # trailing '?' -> question
Source: cli/autogen.py (_optional_param array handling, json_array_names, enum_arg_names); cli/_json_input.py (parse_json_list_of_dicts); nauro_core/mcp_tools.py (PROPOSE_DECISION, FLAG_QUESTION, UPDATE_STATE); cli/commands/note.py; README (MCP tools section, propose-decision example).
Project, registry and sync
nauro projects(ornauro projects list) prints every local v2 registry entry: project_id, name, mode, and repo paths (Repo: (none)when a project has no repos). This is the local registry, distinct from the remote-onlylist_projectsMCP tool.projects_apphasinvoke_without_command=True, so a barenauro projectslists.nauro projects rm PROJECT_ID [--yes]removes a single registry entry but leaves the on-disk store under~/.nauro/projects/<id>/intact (recoverable). It prompts for confirmation (typer.confirm(..., abort=True)) unless--yes, and exits 1 if no project has that id. This is the documented recovery path whennauro initrefuses to mint a second entry for an already-claimed repo. PROJECT_ID is a ULID argument.nauro sync [--message/-m TEXT] [--project NAME] [--status]captures a snapshot and regenerates AGENTS.md in each associated repo. With cloud sync configured it pulls from the server first (a git-style pull-then-push:GET /sync/manifest, thenPOST /sync/presign, then S3 GETs) and then pushes.--statusshows sync state instead (server URL, files tracked, last successful sync, pending local changes, conflict backups). The trigger defaults to"manual sync"when no message is given. It does not touch project state (state_current.md); use theupdate_statetool for that.nauro log [--limit/-n 10] [--full] [--all] [--decisions] [--project]lists recent snapshots (version, timestamp, trigger).--decisionsswitches to a decision list (id, status, version, title);--allincludes superseded decisions in that list;--fullprints complete snapshot content. Default--limitis 10.nauro status [--project]shows a capability table (Sync active or inactive, MCP active, AGENTS.md active) plus local (and, when cloud and authed, remote) decision counts and last-sync time. Sync is "active" only when an auth token exists and the project is v2 cloud-mode.nauro importmigrates external context. Flags (fromimport_cmd):--memory-bank PATH(a Cline / Roo Code Memory Bank.context/directory),--adr PATH(Architecture Decision Records, NNN- or NNNN-title.md files), and--project NAME. There is no--dry-runon import. With neither--memory-banknor--adr, it errors and exits 1. Each import path captures a snapshot afterward.nauro questions migrate [--project] [--dry-run]mints sequential Q### ids for legacy[YYYY-MM-DD HH:MM UTC]question entries in open-questions.md.--dry-runprints the rename map and writes nothing; on a real run it also regenerates AGENTS.md.nauro servestarts the local MCP server over stdio (the sole supported local transport; the former local HTTP transport was retired). It carries a hidden--stdioflag defaulting to True that is a no-op kept for backward compatibility with installed client configs that spawnnauro serve --stdio. There is no--projectflag; the server resolves project context from the cwd's.nauro/config.json.
$ nauro projects # list registered projects (id, name, mode, repos)
$ nauro projects rm 01J… --yes # drop a registry entry; store left intact
$ nauro sync -m "after auth refactor"
$ nauro log --decisions --all
$ nauro status
$ nauro import --memory-bank ./.context
$ nauro import --adr ./docs/adr
Source: cli/commands/projects.py (projects_app, list_projects, remove_project); sync.py (sync, _show_status, _pull_via_presign); log.py; status.py; import_cmd.py (import_cmd, _import_memory_bank, _import_adrs); questions.py (migrate); serve.py.
Configuration and telemetry
nauro configis a read-only and cleanup surface (there is no genericset; values are written by feature commands likeauth loginandtelemetry enable/disable). Subcommands:nauro config get KEY(exits 1 if not set),nauro config list(all top-level config values), andnauro config unset KEY(exits 1 if not set).config listandconfig getmask only values whose KEY contains "key" (case-insensitive) and is longer than 8 chars, showingfirst4...last4via_mask. Non-"key" secrets (for example tokens stored underauth) are not masked by this function, and non-string values are printed as-is.nauro telemetrymanages anonymous usage telemetry (telemetry_apphasno_args_is_help=True). Subcommands:status(showsanonymous_id,enabled (config),enabled (effective),consent_version,consented_at, and anyNAURO_TELEMETRYenv override line),enable(opt in; recordsconsent_versionplusconsented_at),disable(opt out; preservesanonymous_id), andreset(rotateanonymous_idto a fresh UUID4; preserves consent state).- The
NAURO_TELEMETRY=0environment variable always overrides config at read time and is reported bynauro telemetry status(the override line notes "(disables telemetry)" when set to "0"). nauro validate status [--project]shows validation diagnostics: project name, total decisions, active decisions, and the search backend line: "Search: BM25 (bm25s + PyStemmer, built on-the-fly per query)".
$ nauro telemetry status
$ nauro telemetry disable
$ nauro config list
$ nauro validate status
The masking rule covered above (_mask) was read from source; the full set of config keys that actually appear in ~/.nauro/config.json, and therefore which ones get masked versus printed in cleartext, is determined by the store-config layer.
Source: cli/commands/config.py (config_get, config_list, config_unset, _mask); telemetry.py (status, enable, disable, reset); validate.py (status).
MCP tool to CLI mapping (the 11 tools)
- The single source of truth for MCP tools is
nauro_core.mcp_tools.ALL_TOOLS: 11 tools, 8 read plus 3 write. Read:get_context,get_raw_file,list_decisions,get_decision,diff_since_last_session,search_decisions,check_decision,list_projects. Write:propose_decision,flag_question,update_state. - The local stdio server registers 10 of the 11;
list_projectsis remote-only. The README states this explicitly: "11 tools total (8 read, 3 write). The local stdio server registers 10 (7 read, 3 write);list_projectsis remote-only." - The CLI autogen layer (
cli/autogen.pyAUTOGEN_ALLOWLIST) exposes exactly 10 tools as commands;list_projectsis excluded by name, since local installs auto-resolve to a single project and need no discovery entry point. The allowlist is verified againstALL_TOOLSat import time and fails loudly on a typo. - Tool name to CLI command name is snake_case to kebab-case (
_command_namereplaces_with-):get_contextbecomesnauro get-context,propose_decisionbecomesnauro propose-decision,diff_since_last_sessionbecomesnauro diff-since-last-session, and so on. - Schema-to-flag rules (
cli/autogen.py): a requiredstringorintegerproperty becomes a positional argument; every other property becomes an option--kebab-name; abooleanbecomes--flag/--no-flag; astringwith anenumbecomes a native Choice (a bad value exits 2);array of stringbecomes a repeatable--flag;array of objectbecomes a single JSON-valued--flag(literal, @file, or -).project_idis dropped and replaced by--project NAME;cwdis dropped (implicit). A--json/--no-jsonflag (default True) exists as a parity no-op, since JSON is the only output mode. - Each CLI command dispatches to the matching
tool_<name>adapter innauro/mcp/tools.py(for exampletool_propose_decision), the same adapter the local stdio server uses, so the CLI and MCP stay in lockstep. Enum members are converted back to their wire.valuebefore dispatch. - Read tools are annotated
readOnlyHint=True,openWorldHint=False,idempotentHint=True; write tools arereadOnlyHint=False,destructiveHint=False,openWorldHint=False,idempotentHint=False. Annotations come from_READ_ANNOTATIONSand_WRITE_ANNOTATIONS(every tool is closed-world; writes are additive, so nothing is deleted).
| MCP tool | CLI command | Kind |
|---|---|---|
get_context | nauro get-context | read |
get_raw_file | nauro get-raw-file | read |
list_decisions | nauro list-decisions | read |
get_decision | nauro get-decision | read |
diff_since_last_session | nauro diff-since-last-session | read |
search_decisions | nauro search-decisions | read |
check_decision | nauro check-decision | read |
list_projects | (no CLI command) | read, remote-only |
propose_decision | nauro propose-decision | write |
flag_question | nauro flag-question | write |
update_state | nauro update-state | write |
AUTOGEN_ALLOWLIST: frozenset[str] = frozenset(
{
"get_context",
"get_raw_file",
"list_decisions",
"get_decision",
"diff_since_last_session",
"search_decisions",
"check_decision",
"update_state",
"flag_question",
"propose_decision",
}
)
Source: nauro_core/mcp_tools.py (ALL_TOOLS, _READ_ANNOTATIONS, _WRITE_ANNOTATIONS, LIST_PROJECTS comment); cli/autogen.py (AUTOGEN_ALLOWLIST, _DROPPED_PROPERTIES, _command_name, _schema_to_typer_params, _make_command, register_autogen_commands); nauro/mcp/tools.py (tool_* adapters); README (MCP tools section).
Hidden and agent-only commands
Two commands exist in code but are not user-facing, so you would not type them at a prompt.
render-pluginis registered withhidden=Trueand is a cross-repo CI helper: it materializes the canonicalnauro-*subagent bodies into<dir>/agents/so a separate plugin repo can commit byte-identical copies, and--checkbyte-verifies the committed copies against the live render (the plugin repo's CI gate). It is not a user-facing feature.- The
hooksub-app (nauro hook user-prompt-submit) is invoked by Claude Code's hook runner each turn, not by a human. It reads the hook payload JSON from stdin, resolves the project from the payload'scwd, runscheck_decision, and prints ahookSpecificOutput.additionalContextadvisory block when a decision clears the BM25 relevance floor (RELEVANCE_FLOOR = 8.0,MAX_INJECTED = 3,PREVIEW_CHARS = 120). It is advisory by construction: it never blocks a turn, never writes, swallows all failures, and always exits 0. This is the mechanism behindsetup --with-hooks, not a command users type.
Source: cli/main.py (render-plugin hidden=True); cli/commands/render_plugin.py; hook.py (user_prompt_submit, RELEVANCE_FLOOR, MAX_INJECTED, PREVIEW_CHARS).