Skip to content

Nyia Keeper CLI Reference

Complete reference for all CLI flags and their interactions.

Quick Reference Matrix

Flag Category Requires Conflicts Description
-w, --work-branch <name> Branch - - Switch to specific work branch
--create Branch --work-branch - Create branch if missing
--base-branch <name> Branch - - Source branch for new branch
--build-custom-image Build - - Build with user overlays
--base-image <image> Build --build-custom-image --flavor Override base image for overlay build (dev only)
--no-cache Build --build* or --build-custom-image - Force rebuild without Docker cache
--flavor <name> Image - --image* Use flavor image
--image <tag> Image - --flavor* Use specific image
--list-images Image - - List available images
--list-flavors Image - - List available flavors
--agent <name> Agent - - Select agent persona for session
--list-agents Agent - - List available agent personas
--rag RAG Ollama - Enable codebase search
--rag-verbose RAG --rag - Debug RAG indexing
--rag-model <name> RAG --rag - Override embedding model
--login Auth - - Authenticate assistant
--force Auth --login - Bypass auth checks
--set-api-key Auth - - Set API key (OpenCode)
--profile <name> Auth - - Use a named profile (separate account; auth-only keeps your content)
--status Config - - Show current configuration
--setup Config - - Interactive setup
--path <dir> Config - - Work on different project
--shell System - - Interactive bash shell
--mcp-auth Auth - - OAuth an MCP server (OpenCode, Linux only)
--check-requirements System - - Verify system requirements
--disable-exclusions System - - Disable mount exclusions
--hide Config - - Keep Nyia's ignore rules in .git/info/exclude for this launch (hide_footprint=local)
--skip-checks System - - Skip startup checks
--verbose, -v Output - - Verbose output
--help, -h Output - - Show help

*--image takes precedence over --flavor when both specified.


Aliases & short flags

Every plural command/flag also accepts a singular form, and the common flags have short forms (kubectl-style, additive — the plural/long name stays canonical in help and docs).

Command aliases (singular = same command): nyia plan = nyia plans · nyia exclusion = nyia exclusions · nyia completion = nyia completions.

Flag aliases & short forms (on the assistant launchers, e.g. nyia-claude):

Canonical Singular alias Short
--image — -i
--flavor — -f
--agent — -a
--status — -s
--login — -L
--version — -V
--list-images --list-image -li
--list-flavors --list-flavor -lf
--list-agents --list-agent -la
--list-skills --list-skill -ls
--disable-exclusions --disable-exclusion —

-l alone is reserved as the list-prefix; parsing is literal token-matching (no getopts bundling), so -li is a single token, never -l -i.

Flag Categories

Branch Management

Control how Nyia Keeper creates and manages Git branches for your work.

Flag Short Description
--work-branch <name> -w Switch to a specific work branch
--create Create the work branch if it doesn't exist (requires --work-branch)
--base-branch <name> Specify which branch to create new branches from

Default behavior: Works on current branch. Protected branches (main, master, + configured) trigger an interactive prompt.

Config: Set NYIA_AUTO_BRANCH=true for old timestamped branch behavior. Set NYIA_PROTECTED_BRANCHES to add protected branches.

See also: BRANCH_MANAGEMENT.md for detailed workflows.

Custom Image Building

Flag Description
--build-custom-image Build with user overlay Dockerfiles
--base-image <image> Override base image for overlay build (dev only). Mutually exclusive with --flavor
--no-cache Force rebuild without Docker cache (requires --build* or --build-custom-image)

*--build and --base-image are dev-only and not available in runtime distribution.

--base-image: Override which image the overlay builds on top of. Useful for testing overlays against locally-built flavor images:

# Build overlay on a local flavor image:
nyia-claude --build-custom-image --base-image nyiakeeper/claude-python:dev-feature

Overlay Dockerfiles must follow this pattern:

ARG BASE_IMAGE
FROM ${BASE_IMAGE}

USER root
RUN apt-get update && apt-get install -y your-packages && rm -rf /var/lib/apt/lists/*
USER node
RUN pip install --no-cache-dir your-python-packages

See USER_GUIDE_FLAVORS_OVERLAYS.md for full overlay documentation.

Image Selection

Choose which Docker image to run.

Flag Description
--flavor <name> Use a pre-built flavor image (e.g., python, node)
--image <tag> Use a specific image tag
--list-images List all available local images
--list-flavors List all available flavors

Precedence: --image > --flavor > default

Available flavors: - python - pytest, black, mypy, ruff, isort, ipython - php - PHP 8.3, Composer, PHPUnit, PHPStan - node - Node.js 22, yarn, pnpm, typescript, biome, vitest, vite, storybook, cypress, Expo (via npx expo), eas-cli - php-react - PHP 8.2 + React fullstack - rust-tauri - Rust, Cargo, Tauri v2, clippy, rustfmt, Node.js 22

See also: USER_GUIDE_FLAVORS_OVERLAYS.md for flavor details.

Agent Personas

Select or list agent personas for the session. See assistant-agents-matrix.md for per-assistant capabilities.

Flag Description
--agent <name> Select agent persona (Claude, OpenCode, Vibe: direct mapping; Codex: guidance-only)
--list-agents List available agent personas (host-side discovery, no container needed)

Scope precedence: --agent (session) > project-local agents > global agents > assistant default.

Agent name rules: lowercase letters, numbers, and hyphens only. Max 64 characters.

# List available agents
nyia-claude --list-agents

# Use a specific agent
nyia-claude --agent reviewer
nyia-vibe --agent code-reviewer
nyia-opencode --agent my-custom-agent

Semantic code search using local embeddings. Requires Ollama.

Flag Description
--rag Enable RAG codebase search (the MCP server is given to the assistant for this session only: no .mcp.json, no project or global config file is written)
--rag-verbose Enable verbose debug logging for RAG indexing
--rag-model <name> Override embedding model (default: nomic-embed-text)

Requirements: Ollama must be installed and running locally.

Authentication

Manage assistant authentication.

Flag Description
--login Authenticate with the assistant's service
--force Bypass authentication checks (use with --login)
--set-api-key Set API key for team plan users (OpenCode)
--mcp-auth [name] Authenticate an OAuth-protected remote MCP server (OpenCode; native Linux only — see below)
--profile <name> Use a named profile: a separate account. Auth-only (the default) keeps your global skills/agents/rules; a persona has its own. See Profiles.

OAuth MCP servers (--mcp-auth) — native Linux only

A remote MCP server ("type": "remote") usually authenticates with OAuth, which needs a browser. The assistant runs in a container with no browser, so the flow works like this:

nyia-opencode --mcp-auth               # or: --mcp-auth <server-name>
# OpenCode prints an authorization URL — open it in your own browser.
# Credentials are then stored and reused; you do not re-authenticate every run.

No URL printed? OpenCode prints one only when the server needs a login. If credentials are already stored, it asks Re-authenticate? (answer Yes) or refreshes them silently and prints none. To force a fresh login: nyia-opencode --shell, then opencode mcp logout <server-name>, exit, and run nyia-opencode --mcp-auth <server-name> again. The server name starts with a letter or digit and may hold only letters, digits, ., _ and - (at most 64 characters). Before launching OpenCode, --mcp-auth checks your project's config files and your global one (the path it prints is your profile's when you use --profile).

Add a server first. The easiest way is to let OpenCode write the file for you:

nyia-opencode --shell
opencode mcp add my-server --url https://example.com/mcp
opencode mcp auth list                 # per-server status

That writes your global config (~/.config/nyiakeeper/opencode/opencode.json on the host, which Nyia mounts as OpenCode's config dir), so the server is available in every project. To scope it to one project instead, put it in that project's own opencode.json — and .gitignore it if it is personal rather than something the team should get.

The interactive opencode mcp add also offers Current project: OpenCode then writes the project's .opencode/opencode.json itself. Nyia no longer keeps anything there (Plan 393): the codebase-search server is handed to OpenCode for the session only (OPENCODE_CONFIG_CONTENT). A launch removes an old Nyia codebase-search entry from that file when it has exactly Nyia's shape, and keeps your servers.

⚠️ If you write the file by hand, mind OpenCode's own hint. When nothing is configured, OpenCode prints an example to add — but it shows a fragment:

// NOT a complete file — pasting this alone is invalid JSON
"mcp": {
  "my-server": { "type": "remote", "url": "https://example.com/mcp" }
}

Pasted into an empty file that has no enclosing braces, and OpenCode silently ignores a config it cannot parse — so you get the same "No OAuth-capable MCP servers configured" message with no hint that your file was rejected. A complete file:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "my-server": { "type": "remote", "url": "https://example.com/mcp", "enabled": true }
  }
}

Check it parses before launching: python3 -m json.tool opencode.json.

Sharing MCP or model config with a team? Use the project's own opencode.json, committed to the repository — that is OpenCode's native mechanism and everyone who clones gets it. Nyia's team_dir deliberately carries skills, agents and prompts only, never config: a shared source must not be able to silently add an MCP server, redirect a provider's baseURL, or change permissions. See THREAT_MODEL.md for the equivalent note about repository-supplied config — the same review applies, which is the point of putting it somewhere visible.

What the MCP server must support. OpenCode mints its own OAuth client at auth time via RFC 7591 dynamic client registration, so an authorization server that does not advertise a registration_endpoint fails with "Incompatible auth server: does not support dynamic client registration". DCR is optional in the MCP spec, so a perfectly compliant server can hit this. It is not a dead end — give OpenCode a pre-registered client instead and the DCR path is never taken:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "my-server": {
      "type": "remote",
      "url": "https://example.com/mcp",
      "oauth": { "clientId": "{env:MY_MCP_CLIENT_ID}" }
    }
  }
}

Try any value first — you may not need anything from the vendor. Plenty of MCP servers never validate the client id, because the server is itself the OAuth client to an upstream identity provider and merely brokers the flow. In that design "clientId": "opencode" is enough, and the server's own registered client is what talks to the real IdP. This is not theoretical — it is the path verified end to end against a real third-party OAuth MCP server, including the token exchange, with an arbitrary client id and no vendor involvement. Check before asking anyone for credentials — it is two requests against public discovery metadata:

curl -s https://example.com/.well-known/oauth-protected-resource        # which AS serves this resource
curl -s https://<as-host>/.well-known/oauth-authorization-server        # does it list registration_endpoint?
# then: does /authorize accept an arbitrary client id?
curl -s -o /dev/null -D - -G https://<as-host>/authorize \
  --data-urlencode response_type=code --data-urlencode client_id=probe \
  --data-urlencode 'redirect_uri=http://127.0.0.1:19876/mcp/oauth/callback' \
  --data-urlencode code_challenge_method=S256 --data-urlencode code_challenge=<43-char-base64url>

A 302 means the id was accepted and you can stop there. An error means the server keeps a client allow-list, and then ask the vendor for a client_id (plus clientSecret only if the client is confidential — if the metadata says "token_endpoint_auth_methods_supported": ["none"], there is no secret to issue) with the redirect URI http://127.0.0.1:19876/mcp/oauth/callback registered, authorization_code + refresh_token grants and PKCE S256. That is a far smaller ask than implementing DCR. callbackPort and redirectUri change those defaults if they insist on others.

⚠️ "oauth": {} is not the same as omitting it and not the same as false: an empty object leaves auto-detection on with no client id, so DCR is still attempted. Use "oauth": false to turn OAuth off outright — which is what you want if the server authenticates with a static token instead:

{ "mcp": { "my-server": { "type": "remote", "url": "https://example.com/mcp",
    "oauth": false, "headers": { "Authorization": "Bearer {env:MY_API_KEY}" } } } }

Diagnose either path with opencode mcp debug <name> inside the box: it reports the status code, the WWW-Authenticate header, and whether a client id was found.

What OpenCode's last line actually means. These were measured against a local mock authorization server, so each one maps to a definite cause rather than a guess:

OpenCode ends with What happened What to do
Incompatible auth server: does not support dynamic client registration The server advertises no registration_endpoint and your config names no client id, so there is no client to authorize as. Add "oauth": {"clientId": "…"} — see the pre-registered-client path above.
unknown client (or another phrase from the server) The authorization server refused your client_id. The text is the server's own error_description. Ask the vendor to register the id, or use the one they issued.
Operation timed out after 30000ms Authorization succeeded — including the token exchange — but the follow-up connection to the MCP server did not complete within 30s. Look at the MCP endpoint itself, not at OAuth.
Unexpected status: needs_auth The browser did come back, and then connecting to the MCP endpoint raised an OAuth/401 error. In OpenCode this status is set on the connection path, not by the callback handler — so state validated and the token exchange did not throw. The credential exists and the resource server will not accept it. Run opencode mcp debug <name> in the box: it prints the endpoint's status code and the first 500 bytes of its response body, which is the only thing that says why. Then suspect the token's audience/resource, its scope, or your account's entitlement on the server — not the flow and not the callback.
It sits on Waiting for authorization… and never stops Nothing reached the callback. There is no timeout on this wait — it will hold indefinitely. The browser never completed, or it is not on the machine running Docker. Ctrl-C and retry.

The last two rows are the ones worth internalising: because the wait never times out, a run that ends by itself proves the callback was delivered. That rules out the whole class of "my browser could not reach the container" causes, which is otherwise the first thing to suspect.

Find out what the endpoint really is — do not assume a path. Some servers expose MCP at a path (/mcp), others at the origin itself; /mcp on a root-mounted server is a 404. Two curls settle it in seconds, and they need no credentials:

curl -s -o /dev/null -D - -X GET https://<host>/        # and again with /mcp
  • 405 Method Not Allowed with Allow: POST, DELETE → that is the MCP endpoint. Streamable HTTP speaks POST, so a plain GET is supposed to be rejected.
  • 404 → wrong path.

Then POST an initialize to the URL you picked and read the 401:

curl -s -D - -X POST https://<host>/ -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"diag","version":"0"}}}'

A conformant OAuth-protected MCP server answers that 401 with a WWW-Authenticate header carrying resource_metadata (RFC 9728) — that is how a client is supposed to find the authorization server. If the header is absent, the server is not advertising it, and a client can only get there by guessing the origin's /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server. Check those directly:

curl -s https://<host>/.well-known/oauth-protected-resource
curl -s https://<host>/.well-known/oauth-authorization-server

Read registration_endpoint and token_endpoint_auth_methods_supported from the second one: no registration_endpoint means dynamic registration is impossible and you must configure a clientId; ["none"] means a public client with no secret. If the metadata says none but /token answers {"error":"invalid_client"}, those two statements contradict each other and that contradiction is the thing to take to whoever runs the server.

Confirm it persisted — the check that actually matters. Authenticating proves the flow works; it does not prove the credential survives. Run opencode mcp auth list in a fresh session (exit, then launch again) and look for the tick rather than re-reading the output of the session that just authenticated. Persistence comes from Nyia pointing OpenCode's XDG data tree at the mounted global config dir, so a credential obtained on one machine stays on that machine — authenticate once per machine, not once per session.

Why native Linux only. OpenCode binds its OAuth callback to 127.0.0.1:19876 inside the container. On native Linux Nyia uses --network host, so that loopback is your machine's and the browser reaches it. On Docker Desktop (macOS / WSL2) there is no host networking, and Docker's port forwarding reaches the container's bridge address rather than its loopback — so the browser cannot deliver the authorization code. MCP OAuth also has no device-code flow, so the workaround used for codex --login does not apply. --mcp-auth therefore refuses on those platforms with an explanation.

This is a scope decision, not a technical dead end: a port relay would solve it. If you need macOS or Windows support, please open an issue and it can be built.

Ask the server's operator for these three things, before you start

Most of the pain in authenticating a remote MCP server is discovering that something exists which nobody mentioned. If the authorization server has no registration_endpoint (check its /.well-known/oauth-authorization-server), dynamic registration is impossible and a client ID must be issued to you. Nothing in the flow announces that; you simply get an auth failure. So ask for:

  1. The MCP endpoint URL. It may be a path (/mcp) or the origin itself — verify with curl (see above); guessing produces a 404 or a confusing 401.
  2. The OAuth client ID, and a client secret if the client is confidential. If the metadata says "token_endpoint_auth_methods_supported": ["none"], there is no secret to issue.
  3. The redirect URI(s) they must register — these differ per assistant, so a client ID that works in one CLI can fail in another:
Assistant Where the client ID goes Redirect URI it will use
OpenCode (--mcp-auth) "oauth": {"clientId": …, "clientSecret": …} in opencode.json http://127.0.0.1:19876/mcp/oauth/callback — fixed
Claude Code claude mcp add --transport http <name> <url> --client-id <id> [--client-secret] http://localhost:<port>/callback — port is random unless you pin it with --callback-port
Codex codex mcp add <name> --url <url> --oauth-client-id <id> http://127.0.0.1:<port>/callback/<token> — both the port and the path segment are random per run

Codex's randomised path is worth flagging when you ask: it cannot be pre-registered, so it only works against an authorization server that allows loopback redirects loosely (as RFC 8252 §7.3 recommends for native apps) rather than by exact string match. OpenCode's is a single fixed string, and Claude Code's becomes one as soon as you pass --callback-port.

Using Claude Code or Codex instead of OpenCode

--mcp-auth is implemented for OpenCode only — each CLI has its own flow, and each needs its own verification before Nyia claims it works. The other two are reachable through the debug shell:

nyia-claude --shell      # then: claude mcp add --transport http <name> <url> --client-id <id>
                         #       claude mcp login <name> --no-browser
nyia-codex  --shell      # then: codex mcp add <name> --url <url> --oauth-client-id <id>

Two behaviours found by measurement rather than documentation:

  • Claude Code's --no-browser prints the URL and takes the redirect URL pasted back. Nothing has to reach the container's loopback, so this path does not depend on --network host and is not subject to the platform limit described above. It does need a real terminal — it refuses with "stdin isn't a terminal" otherwise.
  • codex mcp add --oauth-client-id starts the OAuth flow immediately, during add, not at login. It prints the URL when it cannot open a browser, but it waits for the callback, so it does need the loopback reachable.

Not available under restrict-local. --mcp-auth launches its container through the same path as --shell, which is refused when network_egress_policy=restrict-local because that path skips the entrypoint that installs the egress firewall. The refusal is deliberate — permitting it would run an unfirewalled container in a project that asked for restriction — but the message mentions the debug shell, so it can read oddly if you only typed --mcp-auth. Authenticate with the policy off for that project, then turn it back on; the stored credential persists.

What the server can see — read this before authenticating one. A remote MCP server is a third party in your session. When the assistant calls one of its tools, the call carries whatever the model puts in the arguments — which in practice can include file contents, paths and excerpts of your conversation. Nyia cannot scope that: it does not sit between the assistant and the server, and it has no way to know what a given tool call will contain. The credential --mcp-auth stores is a standing grant — it persists across runs by design, and revoking it is done at the server, not here.

So authenticating a remote MCP server is a trust decision about its operator, not just a connection. Prefer servers you or your organisation run. If you want the exposure bounded, add the server to a single project's config rather than your global one, so it is not present in every session. This is distinct from the planted-config risk in THREAT_MODEL.md — that one is about a repository adding a server you never chose; this one is about a server you chose on purpose.

Profiles (nyia profile)

Multiple accounts per assistant, and optional per-profile content. See Profiles for the full guide.

Command Description
nyia profile list List profiles, the active one, and each named profile's mode (auth-only / persona)
nyia profile create <name> Register an auth-only profile (inherits your global content)
nyia profile create <name> --persona [--from tech\|non-tech\|empty] Create an isolated persona with its own content, seeded once
nyia config global auth_profile=<name> Make a profile the default for every command (global scope only)

Plan tracking (nyia plans / nyia todo)

Nyia keeps execution plans and a generated todo inventory under .nyiakeeper/plans/; the built-in skills (/nyia-make-a-plan, /nyia-implement-plan, /nyia-plan-review, /nyia-code-review) read and write them. The command is nyia plans (plural).

nyia runs on the host only — it is never inside the assistant container. The skills work with the files directly (a plan's Status: line, decisions.md entries, the read-only todo.md); the host regenerates todo.md automatically at every launch and after every session. The file formats are the contract: PLAN_FILE_CONTRACT.md. Decisions are recorded by appending an entry to a plan's decisions.md in that format (the former nyia plans decision add command was removed in Plan 337). Built-in skills are Nyia-owned and refreshed on every upgrade — customize through your own skills, a team directory or an overlay, never by editing a shipped copy.

Command Description
nyia plans migrate [--dry-run\|--yes] Migrate flat plans/NNN-*.md to per-plan directories, or finalize a migration that stopped early (a full backup is made first). Reviews whose plan number has no body are routed to the nearest exact plan or left flat and named in plans/migration-notes.md — they never block
nyia plans status Show the detected plan layout (empty | new | legacy | mixed), whether the migration is finalized (.layout-v2), and any directories under plans/ that are not plan directories (nested trees, empty numbered dirs)
nyia plans todo [--write] The generated plan inventory (alias of nyia todo)
nyia plans status-backfill [--yes] Write a canonical Status: into plans that lack one (dry-run by default)
nyia plans decisions [N] [--by X] [--since] Show recorded decisions for plan N (or all plans); a malformed hand-edited entry is named as a warning, the rest still renders
nyia todo [--write] Show — or regenerate with --write — the generated plan inventory (also regenerated automatically at launch and after each session; a hand-written todo.md is never overwritten)

Configuration

View and manage configuration.

Flag Description
--status Show current configuration and overlay status
--setup Run interactive setup wizard
--path <dir> Work on a different project directory

System Operations

System-level operations.

Flag Description
--shell Open interactive bash shell in container
--check-requirements Verify Docker and system requirements
--disable-exclusions Disable mount exclusions (mount everything)
--hide Hide Nyia's footprint for this launch: ignore rules go to .git/info/exclude, not .gitignore (see Configuration)
--skip-checks Skip startup requirement checks

Output Control

Flag Description
--verbose, -v Enable verbose output
--help, -h Show help message

Flag Interactions

Required Combinations

If you use... You must also use... Reason
--create --work-branch --create specifies what to create
--no-cache --build* or --build-custom-image Cache bypass applies to builds
--base-image --build-custom-image Base image override applies to custom builds
--rag-verbose --rag Verbose mode for RAG
--rag-model --rag Model selection for RAG
--force --login Force applies to login

Precedence Rules

Flags Winner Behavior
--image + --flavor --image Custom image overrides flavor
--work-branch + --base-branch Both apply Creates/switches work branch from base

Mutually Exclusive

Flag A Flag B Reason
--base-image --flavor Base image override replaces flavor selection

Compatible Combinations

# Work on current branch (default — no flags needed)
nyia-claude

# Work branch from specific base
nyia-claude --work-branch feature/x --create --base-branch develop

# RAG with custom model
nyia-claude --rag --rag-model nomic-embed-text --verbose

# Flavor with prompt
nyia-claude --flavor python

Examples by Use Case

Starting a New Feature

# Create named work branch from main
nyia-claude --work-branch feature/auth --create --base-branch main

# Or just work on current branch (default)
nyia-claude

Resuming Previous Work

# Switch to existing branch
nyia-claude --work-branch feature/auth

Python Development

# Use Python flavor
nyia-claude --flavor python

# Interactive mode with flavor
nyia-claude --flavor python

Building Custom Images

# Build with custom overlays
nyia-claude --build-custom-image

# Force rebuild without Docker cache
nyia-claude --build-custom-image --no-cache

# Then use your custom image
nyia-claude --image nyiakeeper/claude-custom

The custom image records the base image it was built on. A --flavor custom or --flavor <base>-custom launch warns when that base has changed since (for example after nyia update) and prints the rebuild command; the launch still starts. After nyia update, rebuild your custom image: nyia-claude --build-custom-image (add --flavor <base> for <base>-custom). --image <name> launches are not checked.

# Enable RAG for semantic search
nyia-claude --rag

# Debug RAG indexing
nyia-claude --rag --rag-verbose

Working on Different Project

# Work on project in different directory
nyia-claude --path /path/to/other/project

Error Messages

--create requires --work-branch

Error: --create requires --work-branch

The --create flag explicitly creates a branch if it doesn't exist.
Usage: nyia-assistant --work-branch feature/my-branch --create

Fix: Add --work-branch <name> before --create.

Branch does not exist

Branch 'feature/x' does not exist locally or on remote

Fix: Either: - Use --create to create it: --work-branch feature/x --create - Check spelling and use an existing branch

Cannot use protected branch

Cannot use protected branch as work branch: 'main'

Fix: Use a feature branch name like feature/my-work instead.


Update Management

Manage Nyia Keeper installation updates and rollbacks.

Subcommands

Subcommand Description
nyia update status Show installed version, channel, NYIAKEEPER_HOME, and last check time
nyia update list Show available channels (latest, alpha, beta) and recent releases
nyia update check Manual check for updates, shows release notes if available
nyia update install [target] Install update by channel name (alpha, beta, latest) or version tag
nyia update rollback Rollback to previous version (same as nyia rollback)
nyia update help Show update subcommand help

Channel Management

Nyia Keeper supports three update channels:

  • beta - Pre-release builds (default channel)
  • latest - Stable releases (resolves only once a stable release exists)
  • alpha - Deprecated & frozen (pinned at v0.1.0-alpha.103 as a bridge; no new alpha builds)

Switch channels with:

nyia update install beta      # Switch to beta channel (default)
nyia update install latest    # Switch to latest (stable) channel

Beta availability (fail-closed by absence): beta is a first-class channel name, but the channel manifest carries no beta entry until the first beta release is cut. Until then, nyia update install beta prints a clear "beta is not available yet" message and exits non-zero — it never silently falls back to the stable (latest) channel. nyia update list shows beta with an availability marker.

Backward Compatibility

Previous syntax continues to work:

Old Syntax Equivalent New Syntax
nyia update nyia update install
nyia update v0.1.0-alpha.50 nyia update install v0.1.0-alpha.50
nyia update --list nyia update list
nyia rollback nyia update rollback

Examples

# Check current status
nyia update status

# Check if an update is available
nyia update check

# List all available versions
nyia update list

# Install latest for current channel
nyia update install

# Install specific version
nyia update install v0.1.0-alpha.55

# Rollback after a bad update
nyia update rollback

Shell Auto-Completion

Enable tab-completion for nyia and all nyia-* assistant commands.

Setup

# Bash — add to ~/.bashrc
eval "$(nyia completions bash)"

# Zsh — add to ~/.zshrc
eval "$(nyia completions zsh)"

What Completes

Context Completions
nyia <TAB> config, exclusions, profile, git-history, plans, todo, update, list, status, clean, completions, rollback, logo, help
nyia config <TAB> view, list, dump, get, project, global, help
nyia exclusions <TAB> list, test, status, patterns, lockdown, help
nyia exclusions list <TAB> --recreate, --deep, --help
nyia update <TAB> status, list, check, install, rollback, help
nyia completions <TAB> bash, zsh
nyia-claude <TAB> All runtime flags (--status, --login, --shell, --rag, etc.)

All nyia-* assistant commands (nyia-claude, nyia-codex, nyia-opencode, nyia-vibe) share the same flag completions.

Subcommand

Command Description
nyia completions bash Output Bash completion script to stdout
nyia completions zsh Output Zsh completion script to stdout
nyia completions Show usage and setup instructions

Setup instructions are printed to stderr only when the terminal is interactive, so eval usage in shell RC files stays silent.


Which versions can I install?

nyia update list shows the versions that are installable: your channel's current version plus the newest 4 releases of that channel, and every stable release, with the current channel pointers. (The channel's own version is always kept, so it is shown even when it is older than those 4.) Older releases still exist on GitHub but their container images are pruned, so they are hidden — nyia update install <version> refuses a version whose images are gone rather than installing a dist that cannot launch. That check applies to releases published with per-version image tags (v0.1.0-beta.10 onwards); for older releases it cannot tell, so it installs and warns.

Switching a version switches the image too. Since v0.1.0-beta.9 each release publishes per-version image tags, and the launcher runs the image belonging to your installed version — so going back to a retained version gives you that version's container, not just its host scripts. When two releases share the same image (a dist-only release rebuilds nothing), it is one image carrying several tags: Docker fetches a manifest and no layers, so the switch is near-instant. Installs older than v0.1.0-beta.9 predate per-version tags and keep following the channel image. If a version's image is no longer published, the launcher warns once and falls back to the channel image rather than refusing to start; an image you chose explicitly (--image, NYIA_IMAGE_TAG) is never silently replaced.

If a release turns out to be bad, the maintainer moves the channel back to the previous retained version. Your images follow on the next launch, and nyia update offers you the matching dist ("the channel was rolled back to …"). nyia update rollback is different: it restores your own previous install.