# GNO CLI Specification

**Version:** 0.1.0
**Last Updated:** 2025-12-30

This document specifies the command-line interface for GNO, a local knowledge indexing and retrieval system.

## Global Conventions

### Exit Codes

| Code | Name        | Description                                                                                |
| ---- | ----------- | ------------------------------------------------------------------------------------------ |
| 0    | SUCCESS     | Command completed successfully                                                             |
| 1    | VALIDATION  | Validation or usage error (bad args, missing required params)                              |
| 2    | RUNTIME     | Runtime failure (IO, DB, conversion, model, network)                                       |
| 3    | NOT_RUNNING | `--status`/`--stop` found no live matching process                                         |
| 4    | BUSY        | Write-lease contention on `index` / `update` / `embed`; a lost `remember --supersede` race |

### Global Flags

All commands accept these flags:

| Flag              | Type    | Description                                              |
| ----------------- | ------- | -------------------------------------------------------- |
| `--index <name>`  | string  | Use alternate index DB name (default: "default")         |
| `--config <path>` | string  | Override config file path                                |
| `--no-color`      | boolean | Disable colored output                                   |
| `--verbose`       | boolean | Enable verbose logging to stderr                         |
| `--yes`           | boolean | Non-interactive mode: accept safe defaults, never prompt |
| `--quiet`         | boolean | Suppress non-essential output                            |
| `--offline`       | boolean | Offline mode: use cached models only                     |
| `--no-pager`      | boolean | Disable automatic paging of long output                  |
| `--skill`         | boolean | Output SKILL.md for agent discovery and exit             |

### Output Format Flags

Commands that produce structured output support these format flags:

| Flag      | Description                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------------- |
| `--json`  | JSON output (array or object depending on command)                                                             |
| `--files` | Line protocol: `#docid,<score>,gno://collection/path` (`?index=<name>` may be present for non-default indexes) |
| `--csv`   | Comma-separated values with header row                                                                         |
| `--md`    | Markdown formatted output                                                                                      |
| `--xml`   | XML formatted output                                                                                           |

Default output is human-readable terminal format.

Index names are filesystem identifiers: 1–64 UTF-16 code units drawn from
Unicode letters, marks, numbers, internal ASCII spaces, `.`, `_`, or `-`. The
first character must be a letter or number; the last cannot be a space or `.`;
`..` is forbidden. Absolute paths, path separators, controls, and
platform-invalid punctuation are validation errors (exit 1). NFC/case-folded
equivalents have one logical identity and database selection. The canonical
identity is limited to 242 UTF-8 bytes so `index-<identity>.sqlite` stays within
the portable 255-byte filename-component limit. The same contract applies to
indexed `gno://` references. New indexes use the canonical filename. One
pre-existing legacy filename for that identity remains addressable; multiple
equivalent files fail closed as ambiguous.

### Output Format Support Matrix

| Command            | --json | --files | --csv | --md | --xml | Default  |
| ------------------ | ------ | ------- | ----- | ---- | ----- | -------- |
| status             | yes    | no      | no    | yes  | no    | terminal |
| peek               | yes    | no      | no    | no   | no    | terminal |
| init               | no     | no      | no    | no   | no    | terminal |
| setup              | yes    | no      | no    | no   | no    | terminal |
| profile check      | yes    | no      | no    | no   | no    | terminal |
| profile show       | yes    | no      | no    | no   | no    | terminal |
| profile diff       | yes    | no      | no    | no   | no    | terminal |
| profile apply      | yes    | no      | no    | no   | no    | terminal |
| collection add     | no     | no      | no    | no   | no    | terminal |
| collection list    | yes    | no      | no    | yes  | no    | terminal |
| collection remove  | no     | no      | no    | no   | no    | terminal |
| collection rename  | no     | no      | no    | no   | no    | terminal |
| update             | yes    | no      | no    | no   | no    | terminal |
| index              | yes    | no      | no    | no   | no    | terminal |
| embed              | no     | no      | no    | no   | no    | terminal |
| search             | yes    | yes     | yes   | yes  | yes   | terminal |
| vsearch            | yes    | yes     | yes   | yes  | yes   | terminal |
| query              | yes    | yes     | yes   | yes  | yes   | terminal |
| bench              | yes    | no      | no    | no   | no    | terminal |
| ask                | yes    | no      | no    | yes  | no    | terminal |
| capture            | yes    | no      | no    | no   | no    | terminal |
| remember           | yes    | no      | no    | no   | no    | terminal |
| recall             | yes    | no      | no    | no   | no    | terminal |
| get                | yes    | no      | no    | yes  | no    | terminal |
| multi-get          | yes    | yes     | no    | yes  | no    | terminal |
| ls                 | yes    | yes     | no    | yes  | no    | terminal |
| daemon             | yes¹   | no      | no    | no   | no    | terminal |
| context add        | no     | no      | no    | no   | no    | terminal |
| context list       | yes    | no      | no    | yes  | no    | terminal |
| context check      | yes    | no      | no    | yes  | no    | terminal |
| context build      | yes    | no      | no    | yes  | no    | Markdown |
| context verify     | yes    | no      | no    | yes  | no    | Markdown |
| context watch      | yes    | no      | no    | no   | no    | terminal |
| context watches    | yes    | no      | no    | no   | no    | terminal |
| context unwatch    | yes    | no      | no    | no   | no    | terminal |
| context reverify   | yes    | no      | no    | no   | no    | terminal |
| context rm         | no     | no      | no    | no   | no    | terminal |
| models list        | yes    | no      | no    | yes  | no    | terminal |
| models pull        | no     | no      | no    | no   | no    | terminal |
| models clear       | no     | no      | no    | no   | no    | terminal |
| models path        | yes    | no      | no    | no   | no    | terminal |
| publish export     | yes    | no      | no    | no   | no    | terminal |
| cleanup            | no     | no      | no    | no   | no    | terminal |
| doctor             | yes    | no      | no    | yes  | no    | terminal |
| mcp                | no     | no      | no    | no   | no    | stdio    |
| mcp install        | yes    | no      | no    | no   | no    | terminal |
| mcp uninstall      | yes    | no      | no    | no   | no    | terminal |
| mcp status         | yes    | no      | no    | no   | no    | terminal |
| skill install      | yes    | no      | no    | no   | no    | terminal |
| skill uninstall    | yes    | no      | no    | no   | no    | terminal |
| skill show         | no     | no      | no    | no   | no    | terminal |
| skill paths        | yes    | no      | no    | no   | no    | terminal |
| agents install     | yes    | no      | no    | no   | no    | terminal |
| agents update      | yes    | no      | no    | no   | no    | terminal |
| agents verify      | yes    | no      | no    | no   | no    | terminal |
| agents uninstall   | yes    | no      | no    | no   | no    | terminal |
| tags list          | yes    | no      | no    | yes  | no    | terminal |
| tags add           | yes    | no      | no    | no   | no    | terminal |
| tags rm            | yes    | no      | no    | no   | no    | terminal |
| links list         | yes    | no      | no    | yes  | no    | terminal |
| backlinks          | yes    | no      | no    | yes  | no    | terminal |
| similar            | yes    | no      | no    | yes  | no    | terminal |
| graph              | yes    | no      | no    | no   | no    | terminal |
| graph query        | yes    | no      | no    | no   | no    | terminal |
| serve              | yes¹   | no      | no    | no   | no    | terminal |
| completion         | no     | no      | no    | no   | no    | terminal |
| completion install | yes    | no      | no    | no   | no    | terminal |

¹ `--json` applies only to `--status` on `gno serve` and `gno daemon` (see [process-status schema](./output-schemas/process-status.schema.json)).

---

## Commands

### gno profile

Inspect a repository-owned `.gno/index.yml` retrieval profile without changing
the user config, index database, model cache, or tracked files.

**Synopsis:**

```bash
gno profile check [path] [--json]
gno profile show [path] [--json]
gno profile diff [path] [--json]
gno profile apply [path] [--json]
```

With no path, discovery starts at the canonical current directory and walks
upward. Each directory is checked before its parent, so the nearest nested
profile wins and any ancestor profile is reported as shadowed; profiles are
never merged. Discovery stops after checking the first Git root (`.git` may be a
directory or a worktree file), before crossing a filesystem device boundary,
or at the filesystem root. This gives a nested repository precedence over its
parent repository and lets a monorepo subtree intentionally shadow the root
profile.

An explicit directory path is an exact profile-root override: only
`<path>/.gno/index.yml` is considered. An explicit
`<path>/.gno/index.yml` file selects that exact profile. Explicit overrides do
not fall back to ancestors. The profile file and its `.gno` path must resolve
inside the selected canonical profile root; symlink escapes fail closed.
Remote callers cannot enable discovery or cause filesystem probes.

`check` validates discovery, schema, referenced paths, local preset aliases,
and, with global `--offline`, exact cache availability without downloading.
`show` returns the same receipt plus normalized portable desired state. `diff`
compares that desired state with the selected user config. It reports stale
same-name/path mappings and explicit repair/removal choices but never applies
either choice. A missing user config is treated as empty desired-state input;
an unreadable or invalid config is an actionable validation diagnostic.

The three read-only JSON forms use
[`project-profile-command@1.0`](./output-schemas/project-profile-command.schema.json).
Receipts contain no absolute profile, config, database, cache, or model paths,
no timestamps, and no model URIs. Diagnostics and changes are canonically
ordered, so identical local state produces byte-identical JSON.

`apply` rebuilds the shared diff against config reloaded inside a cross-process
runtime lock, then creates or updates only resources declared by the profile
through a resumable operation. It can initialize a missing user config. Omitted collections,
collection-scoped contexts, and content-type rules remain untouched; stale
same-path collections are reported as skipped and retained. A stale same-name
collection root is repaired in place without deleting its collection or index
identity. Apply also updates a timestamp-free `projectProfileBindings` record
in the local user config with the canonical absolute profile path, profile
fingerprint, and projected collection. The public `profile_binding` resource
receipt identifies only the collection and never exposes that local path.
Model preset aliases resolve to collection-local model overrides.
Apply synchronizes the config projection but does not index documents; changed
collections appear in `pendingIndexing`.
`affinityDefaults` remains profile-scoped and is compiled at trusted local
retrieval time; apply does not overwrite the user config `projectAffinity`
default. Its apply resource is therefore `project_affinity/profile/skipped`,
while the nearest valid profile can still supply request-local affinity.

The deterministic JSON result uses
[`project-profile-apply@1.0`](./output-schemas/project-profile-apply.schema.json).
Its created/reused/updated/skipped resource receipt is also atomically saved as
`project-profiles/apply-receipt.json` under the user data directory. The config,
index, receipt, and lock paths must all remain outside the selected profile
root. The tracked `.gno/index.yml` is never written. Interrupted or concurrent
applies resume from fresh config state and converge idempotently.

**Exit Codes:**

- `0`: profile found and valid (`diff` may still report changes)
- `1`: profile missing, ambiguous discovery could not be resolved safely, or
  profile/config validation failed, including runtime-path overlap
- `2`: unexpected local I/O, lock, receipt, or index-store failure

`gno setup` calls the same local `check` composition before its folder
transaction. Inspection is read-only and non-fatal. Plain setup prints bounded
preview/apply guidance but preserves the existing setup result contract.
Explicit `--apply-profile` runs the same lock-safe apply path before lexical
setup and emits `setup-profile-result@1.0`.

### gno status

Display index status and health information.

Embedding backlog follows the last verified partition for the selected model
when exact-input storage is authoritative, counting pending document/chunk
owners. Per-collection chunk totals remain deduplicated by canonical chunk;
embedded counts require matching current inputs for every active owner within
that collection. Status reads persisted identity and coverage without loading
models. Legacy storage remains the fallback before variant authority; ambiguous
older partition selection is conservative until a normal embed records it.

**Synopsis:**

```bash
gno status [--json|--md]
```

**Output (JSON):**

```json
{
  "indexName": "default",
  "configPath": "/path/to/config",
  "dbPath": "/path/to/index.sqlite",
  "collections": [
    {
      "name": "work",
      "path": "/path",
      "documentCount": 100,
      "chunkCount": 500,
      "embeddedCount": 500
    }
  ],
  "totalDocuments": 100,
  "totalChunks": 500,
  "embeddingBacklog": 0,
  "lastUpdated": "2025-12-23T10:00:00Z",
  "healthy": true,
  "activation": {
    "schemaVersion": "1.0",
    "usable": true,
    "healthy": true,
    "collections": [
      {
        "collection": "work",
        "ready": true,
        "generatedAt": "2025-12-23T10:00:00Z",
        "stages": {
          "index": {
            "status": "passed",
            "startedAt": "2025-12-23T10:00:00Z",
            "completedAt": "2025-12-23T10:00:00Z",
            "latencyMs": 3
          },
          "lexical": {
            "status": "passed",
            "startedAt": "2025-12-23T10:00:00Z",
            "completedAt": "2025-12-23T10:00:00Z",
            "latencyMs": 2
          },
          "semantic": {
            "status": "pending",
            "startedAt": null,
            "completedAt": null,
            "latencyMs": null,
            "code": "semantic_not_checked"
          },
          "connector": {
            "status": "skipped",
            "startedAt": null,
            "completedAt": null,
            "latencyMs": null,
            "code": "connector_not_requested"
          }
        },
        "semanticAvailability": {
          "status": "pending",
          "code": "semantic_not_checked",
          "command": "gno status"
        },
        "remediation": null
      }
    ],
    "connectors": [],
    "connectorProjection": {
      "total": 0,
      "projected": 0,
      "truncated": false
    }
  }
}
```

`activation.usable` means at least one configured collection passed its local
lexical proof. `activation.healthy` means every configured collection passed.
Semantic and connector stages remain independent; passive status never starts a
model runtime or connector process. `gno status` still exits 0 when activation
is unhealthy so scripts can inspect the structured state.

JSON output also includes `resident` using
`gno://schemas/resident-status@1.0`. Direct `gno status` is intentionally
truthful about its lifecycle: `mode:"direct-cli"`, `resident:false`, no
listener, and zero resident counters. It does not imply attachment to a live
`serve` or `daemon`.

Local activation fingerprints use active-document identifiers and source/mirror
hashes plus schema, tokenizer, and owned FTS synchronization metadata. Passive
status never selects or compares stored markdown or FTS bodies. On a receipt
miss, lexical proof reads at most 64 document prefixes of 32,768 characters and
tries at most 64 corpus-derived terms. `index_out_of_sync` fails before probing
when any active document lacks a current owned FTS row. Migration 013 compares
legacy FTS bodies once before backfilling that marker; after migration, direct
out-of-band FTS body mutations remain outside the owned-writer contract.

Passive callers report `semantic_not_checked` when vector runtime availability
is unknown. `vector_unavailable` is reserved for a resident runtime that has
positively reported vector search unavailable.

`connectorProjection.total` counts every configured collection and connector
target pair before projection bounds. `projected` equals `connectors.length`,
and `truncated` is true exactly when `total > projected`. No result is claimed
for omitted pairs, and human-readable health output must not report connector
proof as healthy while the projection is truncated.

**Exit Codes:**

- 0: Success
- 2: DB not initialized or inaccessible

---

### gno peek

Cheap read-only metadata snapshot for external integrations (status bars,
launchers, desktop plugins). One invocation; no model, embedding, or vector
initialization. Uninitialized is a successful, reportable state.

**Synopsis:**

```bash
gno peek [--json]
```

**Output (JSON):**

Bare payload (no `ok` envelope). Schema
[`peek@1.0`](./output-schemas/peek.schema.json).

```json
{
  "schemaVersion": "peek@1.0",
  "gnoVersion": "0.42.0",
  "generatedAt": "2026-08-29T09:00:05Z",
  "initialized": true,
  "indexName": "default",
  "counts": { "documents": 1234, "collections": 5 },
  "backlog": { "pending": 0, "failed": 0 },
  "lastIndexedAt": "2026-08-29T09:00:00Z",
  "recent": [
    {
      "docid": "#abc123",
      "uri": "gno://notes/inbox.md",
      "title": "Inbox",
      "collection": "notes",
      "absPath": "/home/user/notes/inbox.md",
      "modifiedAt": "2026-08-29T08:55:00Z"
    }
  ],
  "serve": { "running": true, "url": "http://localhost:3000" }
}
```

**Field semantics:**

- `initialized:false` → `counts`, `backlog`, and `lastIndexedAt` are `null`,
  `recent` is `[]`, exit 0.
- `title` is nullable (consumers fall back to the URI tail).
- `lastIndexedAt` is nullable on an initialized-but-never-indexed store.
- `recent` is bounded (max 10), sorted by `modifiedAt` descending.
- `docid` is the store document id as-is (leading `#` plus hex).
- `backlog.pending` is the chunk embedding backlog (`status.embeddingBacklog`).
- `backlog.failed` is recent ingest/index errors (`recentErrors`).
- `serve` when not running: `{ "running": false, "url": null }`. Liveness is
  pid-file based (`process.kill(pid, 0)`); a stale pid reports not running.
  Never an HTTP probe.
- Any subquery failure is atomic: `RUNTIME` envelope, never a half-filled
  payload.

**Exit Codes:**

- `0`: Success, including uninitialized
- `1`: Validation error (unsupported format)
- `2`: Locked or failed database read, or any partial subquery failure

---

### gno init

Initialize GNO configuration and index database. Safe to run repeatedly (idempotent).

**Synopsis:**

```bash
gno init [<path>] [--name <name>] [--pattern <glob>] [--include <csv-ext>] [--exclude <csv>] [--update <cmd>] [--tokenizer <type>] [--language <code>] [--yes]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<path>` | string | Optional root directory to add as a collection |

**Options:**

| Option        | Type    | Default             | Description                                               |
| ------------- | ------- | ------------------- | --------------------------------------------------------- |
| `--name`      | string  | dirname             | Collection name (required if path given)                  |
| `--pattern`   | glob    | `**/*`              | File matching pattern                                     |
| `--include`   | csv     | -                   | Extension allowlist (e.g., `.md,.pdf`)                    |
| `--exclude`   | csv     | `.git,node_modules` | Exclude patterns                                          |
| `--update`    | string  | -                   | Shell command to run before indexing                      |
| `--tokenizer` | string  | unicode61           | FTS tokenizer: unicode61, porter, trigram                 |
| `--language`  | string  | -                   | BCP-47 language hint for collection (e.g., en, de, zh-CN) |
| `--yes`       | boolean | false               | Skip prompts, accept defaults                             |

**Behavior:**

1. Creates config directory and `index.yml` if missing
2. Creates data directory and `index-<name>.sqlite` if missing
3. Runs migrations on DB
4. If `<path>` provided, adds collection (like `collection add`)
5. Prints resolved paths and next steps

**Exit Codes:**

- 0: Success (or already initialized)
- 1: Invalid arguments
- 2: Cannot create directories or DB

**Examples:**

```bash
# Initialize with defaults
gno init

# Initialize with a collection
gno init ~/notes --name notes --pattern "**/*.md"

# Non-interactive initialization
gno init ~/work/docs --name work --yes

# Initialize with porter stemmer (English-optimized)
gno init --tokenizer porter

# Initialize with language hint for German docs
gno init ~/docs/german --name german --language de
```

**Config Shape (`index.yml`):**

```yaml
version: "1.0"
ftsTokenizer: snowball english
editorUriTemplate: "vscode://file/{path}:{line}:{col}"
collections:
  - name: notes
    path: /Users/you/notes
    pattern: "**/*"
    include: []
    exclude: [.git, node_modules]
    updateCmd: git pull
    languageHint: en
    # Source content availability (distinct from egressPolicy):
    # any (default) = legacy reads; local = opt-in no-materialization guard
    # (macOS File Provider). Unsupported platforms fail closed under local.
    sourceAvailability: any
    models:
      embed: file:/models/embed.gguf
contexts:
  - scopeType: global
    scopeKey: /
    text: Shared retrieval context
models:
  activePreset: slim-tuned
contentTypes:
  - id: person
    prefixes: [people/, contacts/]
    preset: person
    graphHints: [mentions, works_at]
    searchBoost: 1.15
  - id: meeting
    prefixes: [meetings/]
    preset: meeting
    temporal: true
```

`contentTypes` is optional and defaults to `[]`. It is schema-lite and opt-in:
Zod validates `id`, `prefixes`, `preset`, `graphHints`, `searchBoost`, and
`temporal`, while `preset` remains a permissive string. Post-parse normalization
warns and drops unknown preset references, dedupes exact duplicate prefixes,
retains overlapping prefixes, and sorts rules longest-prefix-first. `searchBoost`
defaults to neutral `1`, accepts `0.5..2`, and maps one canonical configured
type to a bounded `-0.05..+0.05` ranking contribution. A frontmatter type ID
wins over longest-prefix matching; boosts never stack, cannot create
candidates, never widen retrieval or defer `minScore`, and share the final
`±0.08` auxiliary cap with project affinity. Hybrid applies the composed score
to normalized fusion before rerank blending; rerank order and lexical top-hit
protection remain authoritative.
`graphHints` is active: ordered hints type
projected wiki/markdown edges and surface in graph traversal/diagnose metadata.

Optional root `chunking` sets one policy per index. `maxTokens` defaults to 800
and accepts integers from 10 through 2251799813685247; the chunker estimates
four characters per token. `overlapPercent` defaults to 0.15 and accepts a
fraction from 0 through 0.5. Missing fields inherit defaults. Invalid values
fail configuration validation before index mutation.

Omitted or explicitly default settings preserve existing chunks and embeddings
on upgrade. A policy change rechunks cached Markdown mirrors during the next
index/update or targeted sync, including mirrors outside the source-refresh
collection. Rechunking does not read original files or claim source freshness.
Sync-only operations leave changed chunks pending embedding; `gno index`
also embeds. A stale writer receives `CHUNKING_POLICY_CONFLICT` and must reopen
its client/runtime with the intended configuration.

Status JSON adds `chunking` with `configured` parameters, `applied` parameters
(null for empty/mixed layouts), `state` (`empty`, `legacy-default`, `current`,
`pending`, or `mixed`), `pendingDocuments`, and `pendingMirrors`. These counts
cover active cached content, independently of source errors and embedding
backlog. Sync receipts optionally add `rechunkedMirrors` when cached layouts
were updated; existing file counters keep their source-refresh meaning.

`collections[].sourceAvailability` is optional; omitted means `any`. Exact
values: `any` | `local`. There is no separate public knob beyond these two
modes and no claim that availability is egress policy. `any` preserves
historical source reads. `local` is opt-in and establishes a platform-aware
no-materialization content-read boundary (currently macOS File Provider via
process-scoped `IOPOL_TYPE_VFS_MATERIALIZE_DATALESS_FILES` plus hierarchical
memoized per-directory availability classification and a content-boundary
recheck shared by full/targeted/watch ingestion, sniff/hash/conversion, and
record import). Unsupported platforms/filesystems and policy-setup failure fail
closed with distinct codes (`SOURCE_AVAILABILITY_UNSUPPORTED`,
`SOURCE_AVAILABILITY_POLICY_FAILED`, `SOURCE_AVAILABILITY_UNKNOWN`).
Cloud-placeholder refusal (`EDEADLK`) surfaces as a skipped file with
`CLOUD_PLACEHOLDER` / `CLOUD_PARTIAL`, not a conversion error. Local mode also
refuses descent into dataless or availability-unknown directories
(`DATALESS_DIRECTORY` skip, or the fail-closed codes above) and preserves
previously indexed descendants under those unproven prefixes rather than
marking them inactive. Eligible files have no availability `errorCode`.
Evidence-qualified scope: Google Drive, iCloud Drive, and OneDrive only for the
tested OS/provider configuration; OneDrive only for both validated immediate
SharePoint library roots. No Windows/Linux guarantee; metadata/provider
bookkeeping may occur; GNO does not pin/evict/download as product behavior.
Source availability is distinct from `egressPolicy` (where derived content may
travel).

The fixture-scoped post-implementation performance contract is recorded by
`benchmark-local`: at least 2 warmups and 9 retained samples per lane, raw
samples plus median/p95/min/max/stddev, explicit contamination, and separate
discovery/traversal, availability metadata, sniff/read/hash, conversion, and
embedding phases. The controlled 5,000-file all-local production comparison
used the same corpus and interleaved pre-implementation/current walkers; it
passed the current-`any` ≤3% gate at -1.1280% and the hierarchical-`local` ≤10%
gate at +1.1841%. It is not a provider-latency guarantee.

---

### gno setup

Add or reuse one folder collection, build its lexical index, and prove a real
corpus-derived retrieval before reporting success. Semantic indexing is handed
to one standalone background worker and never delays lexical success.

**Synopsis:**

```bash
gno setup <folder> [-n|--name <name>] [--exclude <pattern>]...
  [--authorize-secret-risk] [--connector <id>]...
  [--apply-profile] [--no-semantic] [--json]
```

**Options:**

| Option                    | Type       | Default       | Description                                                                  |
| ------------------------- | ---------- | ------------- | ---------------------------------------------------------------------------- |
| `-n, --name <name>`       | string     | dirname       | Requested collection name; exact-root reruns reuse the configured collection |
| `--exclude <pattern>`     | repeatable | core defaults | One literal exclusion per occurrence; never CSV                              |
| `--authorize-secret-risk` | boolean    | false         | Explicitly authorize likely credentials, private keys, or env files          |
| `--connector <id>`        | repeatable | none          | Install or reuse and verify one supported connector after lexical proof      |
| `--apply-profile`         | boolean    | false         | Apply a valid discovered project profile before lexical setup                |
| `--no-semantic`           | boolean    | false         | Prove lexical retrieval but record semantic work as skipped                  |
| `--json`                  | boolean    | false         | Emit one closed setup result object                                          |

Omitting `--exclude` preserves the core create defaults or the filters already
configured for an exact-root rerun. An empty occurrence is invalid. Global
`--yes` accepts safe defaults only; it never authorizes secret risk. Only an
interactive terminal may ask one default-No question after the lexical core
returns `secret_risk`. JSON, `--yes`, non-TTY, decline, and EOF fail closed.

The command bootstraps missing config/data/database state without adding the
folder through `init`, then runs the folder setup transaction. Success requires
a completed `FolderSetupReceipt@1.0`, `activation.ready=true`, and a non-empty
exact `activation.evidence.resultUri`. Terminal stage progress uses stderr;
`--quiet` suppresses progress but not the final result. JSON writes exactly one
canonical result to stdout on both success and domain failure, with no progress.

Before the folder transaction, setup performs the same local read-only
`gno profile check` composition from the supplied folder. Missing and invalid
profiles remain optional and never block ordinary setup. Terminal output shows
a valid profile fingerprint plus `profile diff`/`--apply-profile` guidance, or
an invalid-profile diagnostic, before setup mutates local state.

`--apply-profile` is explicit. When the check is valid, setup runs the existing
cross-process lock-safe, create/update-only `profile apply` path first, then
uses the applied profile collection's canonical root, name, and filters for
lexical setup. A nested folder invocation therefore indexes the declared
profile root rather than creating a duplicate subdirectory collection. The
profile never deletes omitted resources or overwrites the user-level
`projectAffinity` default.

Once a valid profile has been discovered for an explicit `--apply-profile`
request, apply is a fail-closed prerequisite. A validation failure returns exit
1; an apply I/O/lock/store failure, thrown apply transport, missing result, or
malformed success receipt returns exit 2. In every case setup aborts before its
folder transaction or connector work. A late apply failure may have persisted
resumable create/update-only profile state, but cannot trigger ordinary setup
reconciliation. The outer result is `failed`, the nested lexical error code is
`profile_apply_failed`, and `profile.apply` retains a returned apply result or
is `null` when apply produced no result.

Missing or invalid profile inspection remains the optional fallback described
below. A thrown inspection transport/runtime failure for an explicit
`--apply-profile` request instead returns exit 2 with
`profile_inspection_failed` before apply or setup mutation. Because no trusted
profile check result exists, this failure uses the unchanged
`setup-command-result@1.0` shape rather than fabricating `profile.check`.

For a valid profile, `--apply-profile` is mutually exclusive with explicit
`--name` and `--exclude` values. A conflict returns exit 1 with
`profile_option_conflict` before config, store, or index mutation. Profile
include/exclude values use validated Bun-glob semantics; plain exclusion
components retain component matching. Brace alternatives are rejected; express
their branches as separate include/exclude entries. Discovery permission/I/O
failures return exit 2, while missing, disabled, invalid, and unsafe profiles
return exit 1.

Opt-in JSON uses
[`setup-profile-result@1.0`](./output-schemas/setup-profile-result.schema.json)
and includes the closed `profile.check`, nullable `profile.apply`, unchanged
setup result, and connectors. A missing/invalid profile keeps setup usable and
returns `completed_with_actions` with `profile.apply: null`. Without
`--apply-profile`, the existing setup/activation JSON bytes remain unchanged.

After lexical proof, the command records one private atomic
`setup-semantic@1.0` receipt per canonical index/folder and starts one detached,
collection-scoped Bun worker. A matching live worker is reused; a dead worker is
replaced; and a live worker with an older identity remains authoritative until
it exits so its completion receipt cannot be stranded. A later setup rerun may
then schedule the new identity. The worker uses the normal model
download/offline policy, records `completed` only when embedding and vector
synchronization finish without errors, and exits. Spawn, download, partial
embedding, and vector-sync failures retain an exact foreground
`gno ... embed <collection>` remediation and never change proven lexical exit 0.
Direct setup never contacts a resident, MCP, or Web runtime.

`--connector` is explicit and repeatable; omission performs no connector
inspection, installation, or verification. Supported IDs are
`claude-code-skill`, `claude-desktop-mcp`, `cursor-mcp`, `codex-skill`,
`opencode-skill`, `openclaw-skill`, and `hermes-skill`. Exact duplicates dedupe
in first-seen order. Unknown IDs fail before setup side effects.
Any connector-mode JSON invocation emits `setup-activation-result@1.0`.
Argument or lexical failure uses outer `status: failed`, nests the unchanged
failed setup result, and keeps `connectors: []`; it retains the original setup
exit code and performs no connector action.

After proven lexical success and after the lexical store closes, the direct CLI
opens a new standalone store for connector composition. Missing targets use the
existing read-only installer; existing entries are reused without overwrite.
Malformed or unreadable entries are preserved. MCP targets run the shipped
bounded read-only activation smoke. Skill targets install but return
`skipped/target_runtime_unverifiable` because they expose no safe runtime hook.
Connector failures or skips do not roll back lexical success: the command exits
0 with `completed_with_actions` and bounded per-target remediation. No raw child
output or client config path is serialized. Reruns delegate passed-receipt reuse
and recoverable retry to the shipped activation verifier; setup introduces no
second connector fingerprint or cache.

**Structured schemas:**

- [`setup-command-result@1.0`](./output-schemas/setup-command-result.schema.json)
- [`setup-activation-result@1.0`](./output-schemas/setup-activation-result.schema.json)
- [`setup-profile-result@1.0`](./output-schemas/setup-profile-result.schema.json)
- [`setup-semantic@1.0`](./output-schemas/setup-semantic-receipt.schema.json)
- [`FolderSetupReceipt@1.0`](./output-schemas/setup-receipt.schema.json)

**Exit Codes:**

- 0: Lexical setup completed with a real exact result URI; semantic state may
  be scheduled, running, pending, completed, or skipped; requested connector
  actions may require bounded follow-up
- 1: Invalid/safe-input rejection, collection/filter/index disagreement,
  secret-risk refusal, or declined confirmation
- 2: Config, receipt, IO, store, indexing, proof, or internal invariant failure

---

### gno collection add

Add a new collection to the index.

**Synopsis:**

```bash
gno collection add <path> --name <name> [--pattern <glob>] [--include <csv-ext>] [--exclude <csv>] [--update <cmd>] [--embed-model <uri>] [--language <code>]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<path>` | string | Absolute path to collection root directory |

**Options:**

| Option          | Type   | Default                                    | Description                                          |
| --------------- | ------ | ------------------------------------------ | ---------------------------------------------------- |
| `--name`        | string | required                                   | Unique collection identifier                         |
| `--pattern`     | glob   | `**/*`                                     | File matching glob pattern                           |
| `--include`     | csv    | -                                          | Extension allowlist                                  |
| `--exclude`     | csv    | `.git,node_modules,.venv,.idea,dist,build` | Exclude patterns                                     |
| `--update`      | string | -                                          | Shell command to run before indexing                 |
| `--embed-model` | string | -                                          | Initial collection-specific embedding model override |
| `--language`    | string | -                                          | BCP-47 language hint (e.g., en, de, zh-CN)           |

**Exit Codes:**

- 0: Success
- 1: Missing required args, invalid path, duplicate name, or invalid language hint
- 2: Config write failure

**Examples:**

```bash
gno collection add ~/notes --name notes --pattern "**/*.md"
gno collection add ~/work/docs --name work --pattern "**/*.{md,pdf,docx}"
```

---

### gno collection list

List all configured collections.

**Synopsis:**

```bash
gno collection list [--json|--md]
```

---

### gno collection clear-embeddings

Clear embeddings for one collection.

**Synopsis:**

```bash
gno collection clear-embeddings <name> [--all] [--json]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<name>` | string | Collection name |

**Options:**

| Option   | Type    | Default | Description                                                                  |
| -------- | ------- | ------- | ---------------------------------------------------------------------------- |
| `--all`  | boolean | false   | Remove all embeddings for the collection (default only removes stale models) |
| `--json` | boolean | false   | JSON output                                                                  |

**Behavior:**

- default mode is `stale`
- `stale` removes embeddings for models that are not the current embed model for that collection
- `all` removes every embedding for that collection and requires a new `gno embed --collection <name>` run
- embeddings shared by active documents in other collections are retained

---

### gno collection policy

Inspect, change, or explain one collection-owned egress boundary.

```bash
gno collection policy get <name>
gno collection policy set <name> <local_only|lan|remote> [--confirm-relaxation <current-revision>]
gno collection policy check --action <action> --destination <zone> --content-class <class> [-c <name>...] [--authenticated] [--authorized] [--partial] [--explain-egress]
```

`get` returns the configured value, effective value, provenance source, and a
durable monotonic revision plus a diagnostic version fingerprint. Tightening is
immediate. Relaxation requires `--confirm-relaxation` with the exact current
numeric revision returned by `get`; stale, replayed, cross-collection, and
cross-target confirmation fails without changing config or the index
projection.
`check` and `--explain-egress` perform no action. They return the same
content-free decision, lineage, partial disclosure, audit metadata, and
remediation contract.

### gno audit

Read-only, offline knowledge-integrity audits. This command is distinct from
`gno egress-audit`: it examines workspace links, declared provenance
requirements, and source/index freshness without persisting findings or
modifying notes, configuration, index rows, graph edges, or daemon state.

```bash
gno audit [links|provenance|freshness|all] [--collection <name>...] \
  [--path <prefix>...] [--tag <tag>...] [--max-findings <n>] \
  [--max-age-days <days>] [--orphan-root <uri>...] \
  [--orphan-ignore-prefix <prefix>...] [--no-progress] \
  [--json] [--output <path>]
```

The default category is `all`; `--max-findings` defaults to 100 and is bounded
to 1–1000. Truncation limits returned findings but preserves exact totals.
`--output` writes only the requested report artifact with local file
permissions. Human output renders the same report represented by
`audit-report.schema.json`.
When attached to a terminal, human mode reports snapshot/rule progress on
stderr; `--no-progress`, `--quiet`, and JSON mode suppress it. `Ctrl-C` returns
the bounded report gathered so far with partial status and exit 5. Freshness
age is reported only when `--max-age-days` supplies an explicit policy.

Rule statuses are `pass`, `fail`, `skip`, `unavailable`, and `inconclusive`.
Report statuses are `complete`, `partial`, `changed_during_audit`, and `failed`.
A skipped, unavailable, inconclusive, or changing check is never rendered as
healthy.

**Exit Codes:**

- `0` — complete and clean
- `1` — invalid input
- `2` — runtime failure
- `4` — complete report with findings
- `5` — partial, inconclusive, unavailable, or changed-during-audit evidence

The JSON contract is versioned as `gno://schemas/audit-report@1.0`. Finding IDs
are stable SHA-256 identities derived from rule, normalized subject/location,
and load-bearing evidence. Wall-clock timing and traversal order do not affect
identity. A source/index fingerprint change causes one bounded retry and then
`changed_during_audit`; it can never produce a clean exit.

### gno egress-audit

Manage local content-free policy decision receipts:

```bash
gno egress-audit list [--limit <n>] [--cursor <cursor>]
gno egress-audit show <audit-id>
gno egress-audit status
gno egress-audit delete <audit-id>
gno egress-audit purge
```

Pages are newest-first. Delete/purge report truthful SQLite physical cleanup
status. These local inspection and deletion commands never require a relaxed
collection policy.

---

### gno embed

Generate embeddings for indexed chunks.

**Synopsis:**

```bash
gno embed [collection] [--collection <name>] [--force] [--model <uri>] [--batch-size <n>] [--dry-run] [--yes] [--json]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `[collection]` | string | Optional collection name shortcut |

**Behavior note:**

- `[collection]` and `--collection <name>` are aliases
- if provided, embedding work is scoped to that collection only

````

**Output (JSON):**

```json
[
  {
    "name": "notes",
    "path": "/home/user/notes",
    "pattern": "**/*.md",
    "include": null,
    "exclude": [".git", "node_modules"],
    "updateCmd": null
  }
]
````

**Exit Codes:**

- 0: Success

---

### gno collection remove

Remove a collection from the index.

**Synopsis:**

```bash
gno collection remove <name>
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<name>` | string | Collection name to remove |

**Behavior:**

- Removes collection from config
- Marks documents as inactive (does not delete DB rows until `cleanup`)

**Exit Codes:**

- 0: Success
- 1: Collection not found

---

### gno collection rename

Rename a collection.

**Synopsis:**

```bash
gno collection rename <old> <new>
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<old>` | string | Current collection name |
| `<new>` | string | New collection name |

**Exit Codes:**

- 0: Success
- 1: Old name not found or new name already exists

---

### gno update

Sync files from disk into the index (ingestion without embedding).

**Synopsis:**

```bash
gno update [--git-pull] [--json] [--lock-wait <duration>] [--no-wait]
```

**Options:**
| Option | Type | Description |
|--------|------|-------------|
| `--git-pull` | boolean | Run `git pull` in git repositories before scanning |
| `--json` | boolean | Emit the complete deterministic sync result on stdout |
| `--lock-wait <duration>` | duration | How long to wait for the index write lease (default: `120s`). Accepts `120`, `120s`, or `2m`. |
| `--no-wait` | boolean | Do not wait; fail immediately if another writer holds the lease |

**Concurrency:** One writer at a time on the shared index database. `update` waits up to `--lock-wait` for the lease (the same `.mcp-write.lock` MCP write tools use); `index`, `embed`, `cleanup`, `vec sync`, `vec rebuild`, `collection clear-embeddings`, `tags add`, and `tags rm` follow the same contract; `capture` takes the same lock internally, and single-row writes such as `collection policy set` are absorbed by `busy_timeout`. `--no-wait` opts out. Reads (`search`, `query`, `get`) never take the lease. External serialising wrappers are no longer required for CLI-vs-CLI and CLI-vs-MCP overlap. Residual window: a resident (`gno serve`/`gno daemon`) watch or embed flush writes without the lease; those short transactions are absorbed by the raised `busy_timeout` and the SQLITE_BUSY retry, and a deferred chunk is reported as contention, never as an embedding failure.

**Behavior:**

1. For each collection, enumerate files matching patterns
2. Hash files, detect MIME types
3. Convert to Markdown mirror
4. Chunk content for indexing
5. Update FTS index
6. Mark missing files as inactive

**JSON output:** The top-level value is the complete sync result. Each entry in
`collections[].files[]` may contain a `recordImport` receipt conforming to
[`record-import@1.0`](./output-schemas/record-import.schema.json). Receipt items
are sorted deterministically and capped at 1,000; `itemsTruncated` reports
omitted actions. Partial snapshots expose `warnings[]` even when no individual
record failure was reported. Human progress and diagnostics remain on stderr.

**Exit Codes:**

- 0: Success (conversion warnings do not affect exit code)
- 2: DB failure or critical IO error
- 4: Write lease busy (contention after `--lock-wait`, or immediately with `--no-wait`)

---

### gno index

Build or update the index end-to-end (update + embed).

**Synopsis:**

```bash
gno index [collection] [--no-embed] [--models-pull] [--git-pull] [--json] [--yes] [--lock-wait <duration>] [--no-wait]
```

**Options:**
| Option | Type | Description |
|--------|------|-------------|
| `--collection` | string | Scope to single collection |
| `--no-embed` | boolean | Run ingestion only, skip embedding |
| `--models-pull` | boolean | Download models if missing (prompts unless `--yes`) |
| `--git-pull` | boolean | Run `git pull` in git repositories |
| `--json` | boolean | Emit the complete deterministic sync and embedding result on stdout |
| `--yes` | boolean | Accept defaults, no prompts |
| `--lock-wait <duration>` | duration | How long to wait for the index write lease (default: `120s`). Accepts `120`, `120s`, or `2m`. |
| `--no-wait` | boolean | Do not wait; fail immediately if another writer holds the lease |

**Behavior:**

- Runs two separable stages: `lexical` (`update`) then `embed`
- With `--no-embed`, runs the `lexical` stage only (embed stage `skipped`)
- Waits by default for the shared write lease; `--no-wait` fails immediately with exit 4

**Staged, resumable contract:** each stage persists its own progress inside
the index database (documents and chunks for `lexical`; vectors per batch for
`embed`) plus a per-stage lifecycle marker (`schema_meta.index_stage_state`).
A stage that fails never invalidates a completed earlier stage: the lexical
index stays searchable when embedding fails or the process dies. A process
killed mid-stage (SIGKILL, native crash, power loss) emits nothing; the next
`gno index` or `gno embed` run reads the marker, reports the interrupted stage
in its resume preamble (stderr in human mode, `resumedFrom` in JSON), and
continues from persisted progress - unchanged files are skipped and already
stored chunks are never re-embedded. A `gno index --no-embed` run that
surfaces an interrupted embed stage settles that marker (the embed progress
itself stays on disk), so later runs do not repeat the preamble.

**JSON output:** conforms to
[`index-receipt@1.0`](./output-schemas/index-receipt.schema.json):
`{ success, error?, stages: { lexical, embed }, resumedFrom, syncResult?, embedSkipped, embedResult? }`.
Each stage reports `state` (`completed` | `failed` | `skipped` |
`interrupted`) with counts (`filesProcessed`/`filesAdded`/`filesUpdated`/
`filesErrored`/`filesSkipped`/`durationMs` for lexical;
`embedded`/`errors`/`contentionErrors`/`durationMs` for embed), a `reason`
when skipped, and an `error` when failed. The embed stage is `completed` only
when every attempted chunk was stored: any `errors > 0` or a vector-sync error
makes it `failed`. `resumedFrom` is `null` on a clean start or
`{ stage, state: "interrupted", startedAt, pid, collection? }`.
`syncResult.collections[].files[].recordImport`, when present, conforms to
[`record-import@1.0`](./output-schemas/record-import.schema.json) with the same
deterministic ordering, bounds, truncation disclosure, and partial-snapshot
warnings as `gno update --json`. Human progress and diagnostics remain on
stderr. A failed stage still emits the partial receipt on stdout (with
`success: false` and `error`) before the non-zero exit. On lease timeout,
stdout is one object `{ success: false, error, contention }` and the process
exits 4.

**Exit Codes:**

- 0: Every attempted stage completed
- 1: Invalid collection name or invalid `--lock-wait`
- 2: DB or model failure, or any stage `failed` (including chunk-level embed
  failures; the partial receipt is still emitted)
- 4: Write lease busy (contention, not corruption), or chunks deferred by
  index contention after an otherwise completed embed stage

**Examples:**

```bash
# Full index build
gno index

# Update single collection without embedding
gno index --collection notes --no-embed

# CI/scripted usage
gno index --models-pull --yes

# Debug embedding errors
gno index --verbose
```

**Verbose Mode:**

With `--verbose`, embedding errors during the embed phase are logged to stderr (see `gno embed`).

---

### gno embed

Generate embeddings for chunks without vectors.

On CPU-only machines, implementations may use multiple embedding contexts
internally to improve throughput, and may fall back to fewer contexts if
memory pressure prevents creating the full pool.

**Synopsis:**

```bash
gno embed [--force] [--model <uri>] [--batch-size <n>] [--dry-run] [--yes] [--json] [--lock-wait <duration>] [--no-wait]
```

**Options:**

| Option                   | Type     | Default | Description                                                                 |
| ------------------------ | -------- | ------- | --------------------------------------------------------------------------- |
| `--force`                | boolean  | false   | Re-embed all chunks (ignore existing vectors)                               |
| `--model`                | string   | config  | Override embedding model URI                                                |
| `--batch-size`           | integer  | 32      | Chunks per batch                                                            |
| `--dry-run`              | boolean  | false   | Show what would be embedded without doing it                                |
| `--yes`, `-y`            | boolean  | false   | Skip confirmation prompts                                                   |
| `--json`                 | boolean  | false   | Output result as JSON                                                       |
| `--lock-wait <duration>` | duration | `120s`  | How long to wait for the index write lease. Accepts `120`, `120s`, or `2m`. |
| `--no-wait`              | boolean  | false   | Do not wait; fail immediately if another writer holds the lease             |

Waits by default for the same write lease as `index` / `update` / MCP writers. `--no-wait` opts out.

**Exit Codes:**

- 0: Success
- 1: User cancelled or invalid `--lock-wait`
- 2: Model not available or embedding failure
- 4: Write lease busy (contention, not corruption)

**JSON Output:**

```json
{
  "embedded": 1234,
  "errors": 0,
  "duration": 45.2,
  "model": "hf:BAAI/bge-m3-gguf/bge-m3-q8_0.gguf",
  "searchAvailable": true
}
```

**Verbose Mode:**

With `--verbose`, embedding errors are logged to stderr:

```
[embed] Batch failed: <error message>
[embed] Count mismatch: got X, expected Y
[embed] Store failed: <error message>
```

---

### Retrieval trace receipts

When local retrieval tracing is enabled, successful `search`, `vsearch`,
`query`, `ask`, `get`, and `context build` commands write one
`Trace: <traceId>` receipt line to stderr after the normal result. Stdout and
all JSON/Markdown/file payload schemas remain byte-for-byte unchanged.
Retrieval-only commands leave the trace open for explicit evidence follow-up.
Pass that receipt back to `gno get --trace-id <traceId>` to record the exact
opened line range against the same query. Disabled tracing performs no trace
ID, fingerprint, or receipt work and writes no receipt line.

Trace management remains available for already-stored receipts after recording
is disabled:

```text
gno trace list [-n <limit>] [--cursor <cursor>] [--json|--md]
gno trace show <trace-id> [--detail-limit <limit>] [--json|--md]
gno trace label <trace-id> --label <relevant|irrelevant|missing-expected> --target <ref>
  [--target-kind <document|chunk|span>] [--from-line <line> --to-line <line>]
  [--source-hash <sha256>] [--docid <docid>] [--idempotency-key <key>] [--json|--md]
gno trace export <trace-id...> [--format <agentic-receipt|qrels>] [--output <path>] [--json]
gno trace replay <qrels-export-id> --candidate <bm25|vector|hybrid>
  [-n <limit>] [--candidate-limit <limit>] [--no-expand] [--no-rerank] [--json|--md]
gno trace delete <trace-id> [--json|--md]
gno --yes trace purge [--json|--md]
```

`list` is newest-first, cursor-paginated, and never returns raw replay queries
or goals. `show` returns one bounded detail receipt with exact per-section
totals and truncation flags. A relevant or irrelevant label must resolve to
recorded evidence; `missing-expected` accepts only a content-free `gno://` URI,
docid, or immutable source hash. Labels are append-only and retry-safe.

`export` accepts one or more immutable terminal traces and sorts and
deduplicates their IDs. The default deterministic `agentic-receipt` artifact
preserves the complete stored receipt. `--format qrels` requires replay-mode
receipts with an exact query, strict filters, ranked evidence, and at least one
explicit relevant or missing-expected judgment. It exports only hashes,
coordinates, ranks, capabilities, fallbacks, and explicit outcomes—never
source or mirror text. Both formats reject open or missing traces.
`completed`, `partial`, `failed`, and `cancelled` remain distinct; no terminal
state implies negative relevance.

`replay` verifies the saved qrels aggregate manifest and reruns only the named
candidate against the current local index. It compares final and planner ranks,
coverage, explicit open/cite/pin outcomes, capability fallbacks, fingerprints,
and unchanged/stale/missing source state. The result is
`improved|unchanged|regressed|unreplayable` with a human promotion
recommendation and `applied: false`; replay never changes configuration,
boosts, prompts, models, traces, or user files. Missing or cascaded manifest
links and changed source hashes fail closed instead of becoming an empty
successful run.
Without `--output`, JSON is the complete `retrieval-trace-export` receipt.
`--output` writes only the canonical artifact atomically and intentionally
emits no stdout. Full purge requires the global `--yes` flag and reports
whether SQLite/WAL physical cleanup completed, remained busy, or failed.

Structured outputs validate against
`retrieval-trace-{list,show,judgment,export,qrels,replay,delete,purge}.schema.json`;
file-only `trace export --output` is the documented exception.

---

### gno search

BM25 keyword search over indexed documents.

**Synopsis:**

```bash
gno search [query] [--query-file <path>] [-n <num>] [--min-score <num>] [-c <collection>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--tags-all <tags>] [--tags-any <tags>] [--full] [--line-numbers] [--lang <bcp47>] [--json|--files|--csv|--md|--xml]
```

**Arguments:**

| Arg       | Type   | Description                                                          |
| --------- | ------ | -------------------------------------------------------------------- |
| `[query]` | string | Search query. Optional when `--query-file` is set. Do not pass both. |

**Options:**

| Option                  | Type     | Default                   | Description                                                                                                          |
| ----------------------- | -------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `--query-file`          | string   | none                      | Read the query from a file; `-` reads stdin so the query never appears on argv. Do not also pass a positional query. |
| `-n`                    | integer  | 5 (20 for --json/--files) | Max results                                                                                                          |
| `--min-score`           | number   | 0                         | Minimum score threshold                                                                                              |
| `-c, --collection`      | string   | all                       | Filter to collection                                                                                                 |
| `--since`               | string   | none                      | Modified-at lower bound (ISO date/time or relative token)                                                            |
| `--until`               | string   | none                      | Modified-at upper bound (ISO date/time or relative token)                                                            |
| `--category`            | string   | none                      | Filter to docs with matching category/content type (comma-separated)                                                 |
| `--author`              | string   | none                      | Filter to docs where author contains value (case-insensitive)                                                        |
| `--intent`              | string   | none                      | Disambiguating context for ambiguous queries; steers snippets without being searched directly                        |
| `--exclude`             | string   | none                      | Hard-prune docs containing any comma-separated term in title/path/body                                               |
| `--tags-all`            | string   | none                      | Filter to docs with ALL tags (comma-separated)                                                                       |
| `--tags-any`            | string   | none                      | Filter to docs with ANY tag (comma-separated)                                                                        |
| `--project-root`        | string[] | cwd                       | Trusted project root; repeatable, replaces default cwd/repository affinity                                           |
| `--no-project-affinity` | boolean  | false                     | Disable project-aware soft ranking; invalid with `--project-root`                                                    |
| `--full`                | boolean  | false                     | Include full mirror content instead of snippet                                                                       |
| `--line-numbers`        | boolean  | false                     | Include line numbers in output                                                                                       |
| `--lang`                | string   | auto                      | Language filter/hint (BCP-47)                                                                                        |

**Scoring:**

Scores are normalized per query to a 0-1 range using min-max scaling:

- `1.0` = best match among returned results
- `0.0` = worst match among returned results

Important notes:

- Scores are **relative within a single query's result set**, not comparable across different queries
- `--min-score` filters based on this normalized score (e.g., `--min-score 0.5` keeps top half)
- Raw SQLite FTS5 BM25 scores vary with corpus size; normalization ensures consistent UX
- When all results have equal raw scores, they all receive `1.0`
- Queries with explicit recency intent (`latest`, `newest`, `recent`) are ordered newest-first using canonical frontmatter date when present, falling back to source modified time.

**Lexical query semantics:**

- plain terms use prefix matching
- quoted phrases are supported
- negation is supported only when at least one positive term exists
- hyphenated compounds such as `real-time`, `gpt-4`, and `DEC-0054` are handled intentionally
- malformed lexical syntax returns exit code `1`

**Output (JSON):**
See [Output Schemas](./output-schemas/search-results.schema.json)

`results[].source.absPath` is the documented source-path field. It is included
by default when the collection root and `source.relPath` can be joined; search
has no `--source` flag. When `absPath` is absent (unresolvable collection path,
missing relPath, or a hit without a filesystem file), consumers display the URI
tail and must disable file-open for that row — do not call `gno get` just to
recover a path.

Every structured search result may include `context`, the matching
user-configured guidance joined in deterministic global, collection, then
broad-to-specific path-prefix order. The field is absent when no scope matches;
`uri` and `docid` remain the exact source identity. The same contract applies to
`vsearch`, `query`, and the `results` array returned by `ask`.

Logical documents created from file/export adapters additionally include a
bounded `record` object with their opaque record key, exact export locator,
people/dates, thread/event/session identity, attachment inventory, and anchors.
`source.relPath` names the real export container; `uri` and `docid` address the
individual virtual document. The closed `record.adapter` identity contains the
adapter ID, version, and configuration fingerprint. `get` and `multi-get`
preserve the same fields.

File/export adapter iteration has a 60-second deadline in addition to the byte,
record, metadata, and total-character caps. A partial import that retains valid
siblings exits successfully but is never silent: terminal output includes the
file, bounded warning count, and snapshot state; `--verbose` adds stable failure
codes, bounded source locators, retryability, and redacted messages.
`gno update --json`, `gno index --json`, and internal and SDK-facing sync
results include a `recordImport` receipt conforming to
[`record-import@1.0`](./output-schemas/record-import.schema.json), with adapter
identity, configuration fingerprint, snapshot authority, cap state, bytes read,
per-action identity/provenance/attachment receipts, truncation disclosure,
warnings, and bounded failures. `.gno/records/` is a reserved virtual
namespace and is never walked as physical collection content.

**Exit Codes:**

- 0: Success (including zero results)
- 1: Invalid query or options
- 2: DB failure

**Examples:**

```bash
gno search "termination clause"
gno search "deploy staging" -n 10 --collection work
gno search "contract" --json | jq '.[] | .uri'
```

---

Keyword retrieval applies collection/path scope, caller allowlists, tags, modified-date bounds, category and author filters, managed-memory visibility, and whole-document exclusions before its ranked candidate limit. Higher-ranked ineligible documents cannot consume that window; fewer eligible matches still produce a short result. Existing BM25 weights, query syntax, recency/project-affinity reranking and minimum-score behavior are unchanged.

### gno vsearch

Vector semantic search over indexed documents.

**Synopsis:**

```bash
gno vsearch <query> [-n <num>] [--min-score <num>] [-c <collection>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--tags-all <tags>] [--tags-any <tags>] [--full] [--line-numbers] [--lang <bcp47>] [--json|--files|--csv|--md|--xml]
```

**Options:** Same as `gno search` (including temporal/category/author, tag, and
project-affinity controls).

**Embedding ownership:** After an exact-input partition activates, vector hits
resolve only to eligible current document owners of that formatted input.
Same-body documents with different title-derived inputs retain separate vector
ranks. Collection, path, tag, memory, metadata and language eligibility apply
before the nearest-neighbor limit. Public document IDs, URIs and mirror hashes
keep their existing meaning.

Incomplete shadow backfill retains the legacy retrieval path. Initial promotion
requires complete active coverage and an atomic mutation-epoch check. Once
promoted, later mutations never permit fallback to unproven legacy vectors:
stale owners await embedding; missing runtime identity, an unactivated selected
partition or an unavailable variant index reports semantic failure. Hybrid query
may continue lexically with fallback diagnostics. Run `gno embed` to complete or
repair coverage; no historical vector is treated as proof of its formatted input.

**Scoring:**

Vector similarity scores are normalized to a 0-1 range:

- `1.0` = identical/most similar
- `0.0` = least similar (within result set)

Cosine distance (0=identical, 2=opposite) is converted: `score = 1 - (distance / 2)`

**Exit Codes:**

- 0: Success
- 1: Invalid options
- 2: Vectors not available (suggests `gno index` or `gno embed`)

---

### gno query

Hybrid search combining BM25 and vector retrieval with optional expansion and reranking.

**Synopsis:**

```bash
gno query [query...] [--query-file <path>] [-n <num>] [--min-score <num>] [-c <collection>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [-C <num>] [--tags-all <tags>] [--tags-any <tags>] [--full] [--line-numbers] [--lang <bcp47>] [--no-expand] [--no-rerank] [--graph] [--no-graph] [--query-mode <mode:text>]... [--explain] [--json|--files|--csv|--md|--xml]
gno query diagnose <query> --target <doc> [-n <num>] [--min-score <num>] [-c <collection>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [-C <num>] [--tags-all <tags>] [--tags-any <tags>] [--lang <bcp47>] [--no-expand] [--no-rerank] [--graph] [--no-graph] [--json]
```

The positional query is optional when `--query-file` is set. Do not pass both. `--query-file` is invalid on `query diagnose`.

**Options:** Same as `gno search`, plus:

**Additional Options:**
| Option | Type | Description |
|--------|------|-------------|
| `--query-file` | string | Same as `gno search`: read the query from a file (`-` is stdin). Invalid with a positional query and invalid on `query diagnose`. |
| `--no-expand` | boolean | Disable query expansion |
| `--no-rerank` | boolean | Disable cross-encoder reranking |
| `--graph` | boolean | Explicitly enable the default bounded one-hop graph neighbor expansion |
| `--no-graph` | boolean | Disable graph neighbor expansion |
| `--intent` | string | Disambiguating context for ambiguous queries; steers expansion, rerank chunk/snippet choice, and disables strong-signal bypass without being searched directly |
| `--exclude` | string | Hard-prune docs containing any comma-separated term in title/path/body |
| `-C, --candidate-limit` | integer | Max candidates passed to reranking (default 20) |
| `--query-mode` | string[] | Structured mode entry (`term:<text>`, `intent:<text>`, `hyde:<text>`). Repeatable. |
| `--explain` | boolean | Print retrieval explanation to stderr |
| `--target` | ref | Required for `query diagnose`; target document to diagnose |

`query diagnose` accepts the same `--project-root` and
`--no-project-affinity` controls as `query`. It rejects `--query-file`.

**Compatibility / Migration:**

- Legacy query invocations remain valid (`gno query "<text>"`, `--fast`, `--thorough`, `--no-expand`, `--no-rerank`).
- `--fast` skips query expansion, graph expansion, and reranking.
- `--intent` is orthogonal to `--query-mode`: intent steers scoring/prompting, while query modes inject caller-provided retrieval expansions.
- `--query-mode` is optional and additive to the command surface.
- If one or more `--query-mode` entries are provided, generated expansion is bypassed and provided entries are used as retrieval intents.
- By default, balanced and thorough `gno query` retrieval adds a capped one-hop wiki/markdown-neighbor candidate set after BM25/vector retrieval. Use `--no-graph` to disable it. Query-time expansion resolves only links touching the top seeds; semantic similarity remains the vector stage's responsibility. Explicit links are weighted above inferred or ambiguous link matches.

**Diagnose Output:**

`gno query diagnose` wraps the shared `diagnoseQueryTarget()` core and emits
`query-diagnose.schema.json` for `--json`. No trusted affinity input preserves
the exact closed `schemaVersion: "1.0"` payload and omits `affinity`; the legacy
contract remains in `query-diagnose-v1.schema.json`. A resolved trusted CLI cwd
or explicit project root emits `schemaVersion: "1.1"` and requires closed,
redacted `affinity` metadata, including unmatched state. The payload resolves
the target first, reports `target.status`
(`not_found|inactive|no_indexed_content|filtered_out|diagnosed`), and only runs
stage tracing for `diagnosed` targets. Stages report
`present`, `rank`, `score`, `survived`, `dropReason`, `status`, and
`sourceCount` across BM25, vector, fusion, graph, and rerank. BM25-only mode
marks vector/rerank skipped when unavailable or disabled, but fusion remains
active with `sourceCount: 1`.

**Explain Output (stderr):**

```
[explain] expansion: enabled (3 lexical, 2 semantic variants)
[explain] bm25: 45 candidates
[explain] vector: 38 candidates
[explain] graph: seeds=5, candidates=4/20, explicit=3, inferred=1, ambiguous=0, similarity=0
[explain] fusion: RRF k=60, 52 unique candidates
[explain] rerank: top 20 reranked
[explain] result 1: score=0.92 (bm25=0.85, vec=0.78, rerank=0.95)
```

**Exit Codes:**

- 0: Success (degrades gracefully if vectors unavailable)
- 1: Invalid options
- 2: DB or model failure

---

### gno bench

Run retrieval quality benchmarks against an already indexed GNO corpus.

**Synopsis:**

```bash
gno bench <fixture.json> [-c <collection>] [-k <num>] [--mode <name>]... [-C <num>] [--json]
```

**Fixture schema:** [`spec/bench-fixture.schema.json`](./bench-fixture.schema.json)

**JSON output schema:** [`spec/output-schemas/bench-result.schema.json`](./output-schemas/bench-result.schema.json)

**Options:**

| Option                  | Type     | Description                                                                                     |
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `-c, --collection`      | string   | Override fixture/query collection                                                               |
| `-k, --top-k`           | integer  | Override top-k cutoff used for Precision@K, Recall@K, F1@K, MRR, and nDCG@K                     |
| `--mode`                | string[] | Override fixture modes. Repeatable: `bm25`, `vector`, `hybrid`, `fast`, `no-rerank`, `thorough` |
| `-C, --candidate-limit` | integer  | Override candidate limit for hybrid/rerank modes                                                |
| `--json`                | boolean  | Emit structured benchmark result                                                                |

Fixtures support:

- `version: 1`
- optional `metadata`, `collection`, `topK`, `candidateLimit`
- `modes` as aliases or objects with `type`, `noExpand`, `noRerank`, `candidateLimit`, `limit`, and `queryModes`
- `queries[]` with `id`, `query`, expected documents/URIs, optional `collection`, optional `topK`, optional `queryModes`, and optional graded `judgments`

Metrics reported per mode and per query:

- `precisionAtK`
- `recallAtK`
- `f1AtK`
- `mrr`
- `ndcgAtK`
- latency summaries (`p50Ms`, `p95Ms`, `meanMs`)

**Exit Codes:**

- 0: Fixture loaded and benchmark ran
- 1: Invalid fixture, mode, or options
- 2: Runtime failure

---

### gno ask

Human-friendly query with citations-first output and optional grounded answer.

**Synopsis:**

```bash
gno ask <query> [-n <num>] [-c <collection>] [--lang <bcp47>] [--since <date>] [--until <date>] [--category <values>] [--author <text>] [--intent <text>] [--exclude <values>] [--query-mode <mode:text>]... [-C <num>] [--answer|--verify] [--no-answer] [--max-answer-tokens <n>] [--context-budget-tokens <n>] [--context-budget-bytes <n>] [--min-score <score>] [--graph|--no-graph] [--no-expand] [--no-rerank] [--explain] [--show-sources] [--json|--md]
```

**Options:**

| Option                    | Type     | Default | Description                                                                        |
| ------------------------- | -------- | ------- | ---------------------------------------------------------------------------------- |
| `--answer`                | boolean  | false   | Generate short grounded answer                                                     |
| `--verify`                | boolean  | false   | Generate from a closed Context Capsule; verify every claim or abstain              |
| `--no-answer`             | boolean  | false   | Force retrieval-only output                                                        |
| `--max-answer-tokens`     | integer  | config  | Cap answer generation tokens                                                       |
| `--context-budget-tokens` | integer  | 12000   | Global token budget for verified Context evidence                                  |
| `--context-budget-bytes`  | integer  | none    | Optional global byte budget for verified Context evidence                          |
| `--min-score`             | number   | none    | Minimum retrieval score from 0 through 1                                           |
| `--graph`                 | boolean  | true    | Include bounded graph expansion in verified Context retrieval                      |
| `--no-graph`              | boolean  | false   | Disable graph expansion                                                            |
| `--since`                 | string   | none    | Modified-at lower bound (ISO date/time or relative token)                          |
| `--until`                 | string   | none    | Modified-at upper bound (ISO date/time or relative token)                          |
| `--category`              | string   | none    | Filter to docs with matching category/content type (comma-separated)               |
| `--author`                | string   | none    | Filter to docs where author contains value (case-insensitive)                      |
| `--intent`                | string   | none    | Disambiguating context for ambiguous questions without searching on that text      |
| `--exclude`               | string   | none    | Hard-prune docs containing any comma-separated term in title/path/body             |
| `--query-mode`            | string[] | none    | Structured mode entry (`term:<text>`, `intent:<text>`, `hyde:<text>`). Repeatable. |
| `-C, --candidate-limit`   | integer  | 20      | Max candidates passed to reranking                                                 |
| `--no-expand`             | boolean  | false   | Disable query expansion                                                            |
| `--no-rerank`             | boolean  | false   | Disable cross-encoder reranking                                                    |
| `--explain`               | boolean  | false   | Include retrieval scoring details; prints to stderr outside structured output      |
| `--show-sources`          | boolean  | false   | Show all retrieved sources (not just cited)                                        |
| `--project-root`          | string[] | cwd     | Trusted project root; repeatable, replaces default cwd/repository affinity         |
| `--no-project-affinity`   | boolean  | false   | Disable project-aware soft ranking; invalid with `--project-root`                  |

**Output (JSON):**
See [Output Schemas](./output-schemas/ask.schema.json)

Notes:

- `meta.answerContext` is optional explain payload for answer source selection.
- `meta.explain` is present only with `--explain`. Its optional
  `contentTypeBoost` result component contains the raw/base score, configured
  factor, capped and combined contributions, final score, rule source, and full
  ranking-rules fingerprint. Verified Ask attaches this as a non-canonical
  sidecar; Capsule identity and bytes do not change.
- `--verify` implies answer generation and cannot be combined with
  `--no-answer`. The JSON result adds the closed Capsule, freshness receipt,
  four-state per-claim verdicts (`supported`, `contradicted`, `insufficient`,
  `uncertain`), exact evidence IDs and line spans, coverage, gaps, semantic
  verifier state, and explicit abstention. Support below 100% never returns the
  draft answer.
- Terminal and Markdown verified output preserve the same verdicts, exact
  support/conflict spans, coverage, gaps, abstention, and capability
  degradation. With `--show-sources`, both formats list every retained Capsule
  evidence span with its exact URI and line range. JSON remains the canonical
  machine contract.
- Verification classifies support only against the closed Capsule and its
  freshness receipt. It does not guarantee corpus completeness or source truth.
  An unavailable, incapable, failed, or malformed semantic verifier cannot mark
  claims supported; unresolved substantive claims remain uncertain and force
  abstention.
- Verified retrieval records the normalized request and requested/attempted
  capability states in its Capsule. The active `--index` value is host-owned
  and used for both compilation and freshness verification.
- Strategy: adaptive coverage (relevance + query/facet coverage), not fixed top-N.
- Each result preserves optional configured `context`. Answer generation places
  that trusted configuration in a separate prompt role from untrusted retrieved
  document content.

**Exit Codes:**

- 0: Success
- 1: Invalid options
- 2: DB or model failure

**Examples:**

```bash
gno ask "how do we deploy to staging"
gno ask "termination clause" --collection work --answer
gno ask "who owns launch?" --verify --show-sources
```

---

### gno capture

Capture a note into an editable collection with structured provenance.

**Synopsis:**

```bash
gno capture [content...] [--stdin|--file <path>] [--collection <name>] [--title <title>] [--path <relPath>] [--folder <relPath>] [--preset <id>] [--tags <tags>] [--collision-policy <policy>] [--source-kind <kind>] [--source-url <url>] [--source-title <title>] [--source-author <author>] [--source-date <date>] [--source-id <id>] [--json]
```

**Content Sources:**

- Inline argument, `--stdin`, and `--file` are mutually exclusive.
- Content is required unless `--preset` can scaffold a non-empty note.
- `--preset` accepts: `blank`, `project-note`, `research-note`,
  `decision-note`, `prompt-pattern`, `source-summary`, `idea-original`,
  `person`, `company-project`, `meeting`.
- `--json` wins over global `--quiet`; quiet prints only the created/opened URI.

**Provenance:**

Capture writes structured `source:` frontmatter and returns the shared
[`capture-receipt`](./output-schemas/capture-receipt.schema.json). `--source-date`
maps to `source.observedAt`; `--source-id` maps to `source.externalId`.

Browser-clip receipts MAY extend `source` with `canonicalUrl`, `site`,
`publishedAt`, and the closed
[`browser-clip`](./output-schemas/browser-clip.schema.json) provenance object.
The provenance preserves exact selection text separately from canonical final
Markdown and carries extraction/final hashes, deterministic identity and
preview digests, normalized dates/URLs, browser metadata, server capture time,
and bounded warnings. Existing capture inputs and receipts remain compatible.
Browser-clip URLs use a closed, credential-free HTTP(S) subset: ASCII DNS or
punycode hosts (or IPv4), optional ports from 1 through 65535, and ASCII
RFC 3986 path/query/fragment characters with well-formed percent escapes. Raw
Unicode hosts and IPv6 literals are rejected; browser clients should submit
their serialized IDNA/punycode URL. Free-form clip strings reject C0/C1 control
characters except tab, LF, and CR.
For browser provenance, `open_existing` MUST open only when the stored
`clipIdentity` matches; missing or different provenance MUST return
`collisionPolicyResult: "conflict"` without writing. `create_with_suffix`
creates a distinct note.

**Path and Collision Rules:**

- Explicit `--path` wins.
- Without `--path`, `--folder`/`--title` produce a safe markdown filename.
- Without path, folder, or title, GNO writes to
  `inbox/YYYY-MM-DD/capture-<body-hash>.md` using UTC capture time.
- Default collision policy is `open_existing` for generated hash paths and
  `error` for explicit/title/folder paths.
- Collision checks include indexed documents and disk-only files.
- Content must be text; NUL or binary-like control bytes are rejected.
- Capture writes use exclusive create semantics so a file that appears after
  planning is not replaced.

**Examples:**

```bash
gno capture "thought to remember"
gno capture --stdin --collection notes --preset source-summary --tags inbox,gno
gno capture --file ./clip.md --source-url https://example.com --source-kind web --json
gno capture "meeting note" --quiet
```

---

### gno remember

Store one fact in a memory-managed collection. Thin adapter over the core
memory service (`src/core/memory.ts`): the CLI never touches the store or the
write lease directly.

**Synopsis:**

```bash
gno remember <text> --scope <scope> [--scope <scope>...] [--collection <name>] [--decision add|supersede | --add | --supersede <uri>] [--predecessor <uri>] [--predecessor-hash <hash>] [--receipt <path>] [--derived-from <uri>...] [--source <text>] [--caller <id>] [--session <id>] [--json]
```

**Scope and collection (fail-closed):**

- `--scope` is required and repeatable (1..8 scopes, normalized: trim,
  lowercase, NFC, dedupe). A missing scope exits `VALIDATION` with a message
  naming `--scope`. There is no implicit global scope.
- `--collection` names a collection with `memoryManaged: true`. It may be
  omitted only when exactly one memory-managed collection is configured. A
  collection without the flag exits `VALIDATION` (`MEMORY_COLLECTION_UNMANAGED`).

**Identity:**

- `--caller` defaults to `$GNO_MEMORY_CALLER`, then `cli:<os user>`.
- `--session` defaults to `$GNO_MEMORY_SESSION`, then `ppid:<parent pid>`.
- Both are recorded in the fact frontmatter and bound into recall receipts.

**Decision:**

- No decision flag: candidate proposal only. Same-scope current facts are
  matched (BM25 pool of 16; cosine >= 0.83 when an embedding model is cached,
  else token Jaccard >= 0.5); nothing is written, `outcome: "candidates"`.
- `--decision add` / `--add`: write a new fact file. An exact duplicate
  (same content hash) returns the existing record idempotently
  (`outcome: "existing"`).
- `--decision supersede --predecessor <uri>` / `--supersede <uri>`: requires
  `--predecessor-hash <hash>` (the predecessor's `contentHash` from recall).
  Under the shared write lease the service verifies the predecessor exists,
  is current, matches the hash, and has no successor; the successor carries
  `supersedes: [<uri>]`. `--add` and `--supersede` are mutually exclusive.
- A write returns success only after the file exists and lexical sync
  completed; the fact is retrievable before the command exits.

**Context fencing:**

- `--receipt <path>` presents a recall receipt (the `recall --json` output or
  its `receipt` object). Text whose normalized hash matches a receipted span
  is rejected (`MEMORY_FENCED_REPLAY`).
- `--derived-from <uri>` declares origins; any `gno://` origin is rejected
  (`MEMORY_FENCED_DERIVED`).
- Paraphrases that carry neither a receipt nor a lineage declaration cannot
  be fenced.

**Evidence:**

- `--source <text>` records free-text evidence for the fact. It is written to
  the fact frontmatter (`memory.source`) and echoed as `record.source` /
  `source` on every fact in remember and recall results.

**Output:**

- `--json` prints the shared `RememberResult` (`outcome` of `existing` |
  `candidates` | `added` | `superseded`, with `record` / `candidates`,
  `absPath`, `sync`, and `matching`). `--json` wins over global `--quiet`;
  quiet prints the record URI (or one candidate URI per line).
- Terminal output states the outcome, URI, record id, content hash, scopes,
  `Supersedes:` when present, sync state, and the matching mode.

**Exit codes:** `VALIDATION` (1) for flag, scope, collection, identity,
predecessor, and fence errors; `BUSY` (4) when another writer holds the lease
(`MEMORY_WRITE_LEASE_BUSY`) or already superseded the predecessor
(`MEMORY_SUPERSEDE_CONFLICT`); `RUNTIME` (2) when the file was written but
lexical sync failed (`MEMORY_SYNC_FAILED`) or the successor's `supersedes`
edge did not project (`MEMORY_SUPERSEDE_PROJECTION_FAILED`). The JSON envelope carries the core code in
`details.memoryCode`.

**Examples:**

```bash
gno remember "Finn's kindergarten starts at 08:30" --scope family --add
gno remember "Prod deploys from main only" --scope project:gno --scope ops
gno remember "Prod deploys from release/*" --scope project:gno --supersede gno://memory/facts/2026/... --predecessor-hash <hash> --json
```

---

### gno recall

Recall current facts from a memory-managed collection under a budget.

**Synopsis:**

```bash
gno recall <query> --scope <scope> [--scope <scope>...] [--collection <name>] [--max-facts <n>] [--max-tokens <n>] [--caller <id>] [--session <id>] [--json]
```

**Behavior:**

- `--scope`, `--collection`, `--caller`, and `--session` follow the
  `gno remember` rules (fail-closed scope, memory-managed collection only).
- Retrieval is BM25 plus a vector leg when the configured embedding model is
  cached and vectors exist (`retrieval.mode` = `hybrid`, else `lexical` with
  `retrieval.semanticUnavailable` explaining why). Query expansion, graph
  expansion, and reranking are disabled. Scope and supersession filtering run
  inside the retrieval query; superseded facts are never returned.
- Budget: at most `--max-facts` facts (default 8) under `--max-tokens`
  (default 512). Both must be positive integers. Recall never downloads a
  model.
- Every fact carries `uri` (`gno://`), `text`, `scopes`, `caller`,
  `session`, `createdAt`, `contentHash`, `spanHash`, `supersedes`, `score`,
  and `egressLineage`; the response carries a content-free `receipt`
  (`caller`, `session`, `issuedAt`, `memoryIds`, `spanHashes`, `digest`) plus
  `budget` and `retrieval`. Derived output inherits the strictest source
  egress policy (`egressLineage`).
- Empty recall prints the self-teaching line naming `gno remember`
  (`hint` in JSON) and exits 0.

**Output:** `--json` prints the shared `RecallResult`. Terminal output lists
numbered facts with URI, text, scopes, hash, and identity, then `Budget:`,
`Retrieval:`, and `Receipt:` lines. Quiet prints one URI per line.

**Exit codes:** `VALIDATION` (1) for scope, collection, identity, and budget
errors (a bad `--max-facts` / `--max-tokens` carries
`details.memoryCode: MEMORY_BUDGET_INVALID` in the JSON envelope);
`RUNTIME` (2) on retrieval failure.

**Examples:**

```bash
gno recall "deploy branch" --scope project:gno
gno recall "kindergarten" --scope family --max-facts 3 --json > receipt.json
```

---

### gno get

Retrieve a single document by reference.

**Synopsis:**

```bash
gno get <ref> [--from <line>] [-l <lines>] [--line-numbers] [--trace-id <id>] [--source] [--json|--md]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<ref>` | string | Document reference: `gno://...`, `collection/path`, `#docid`, or `:line` suffix |

**Options:**
| Option | Type | Description |
|--------|------|-------------|
| `--from` | integer | Start at line number |
| `-l` | integer | Limit to N lines |
| `--line-numbers` | boolean | Prefix lines with numbers |
| `--trace-id` | string | Continue an open retrieval trace and record the exact returned span |
| `--source` | boolean | Include source metadata in output |

**Ref Formats:**

- `gno://work/contracts/nda.docx` - Full URI
- `work/contracts/nda.docx` - Collection-relative path
- `#a1b2c3d4` - Document ID
- `gno://work/doc.md:120` - URI with line number suffix

**Output (JSON):**
See [Output Schemas](./output-schemas/get.schema.json)

**Exit Codes:**

- 0: Success
- 1: Invalid ref format
- 2: Document not found

**Examples:**

```bash
gno get gno://work/contracts/nda.docx
gno get "#a1b2c3d4" --line-numbers
gno get work/doc.md:120 -l 50
gno get gno://work/doc.md --from 120 -l 50 --trace-id <traceId>
```

---

### gno multi-get

Retrieve multiple documents by pattern or list.

**Synopsis:**

```bash
gno multi-get <pattern-or-list> [--max-bytes <n>] [--line-numbers] [--json|--files|--md]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<pattern-or-list>` | string | Glob pattern, comma-separated refs, or docid list |

**Options:**

| Option           | Type    | Default | Description                                    |
| ---------------- | ------- | ------- | ---------------------------------------------- |
| `--max-bytes`    | integer | 10240   | Max bytes per document (truncate with warning) |
| `--line-numbers` | boolean | false   | Include line numbers                           |

**Output (JSON):**
See [Output Schemas](./output-schemas/multi-get.schema.json)

**Exit Codes:**

- 0: Success (partial results if some docs missing)
- 1: Invalid pattern
- 2: DB failure

---

### gno ls

List documents in a collection or prefix.

**Synopsis:**

```bash
gno ls [<scope>] [--json|--files|--md]
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<scope>` | string | Collection name or `gno://collection/prefix` (default: all) |

**Output (JSON):**

```json
[
  {
    "docid": "#a1b2c3d4",
    "uri": "gno://work/doc.md",
    "title": "Document Title",
    "source": { "relPath": "doc.md", "mime": "text/markdown", "ext": ".md" }
  }
]
```

**Exit Codes:**

- 0: Success
- 1: Invalid scope
- 2: DB failure

---

### gno context add

Add context metadata for a scope.

Configured text is returned as optional `context` on matching structured
retrieval results and is used as trusted guidance during grounded answer
generation. Matching scopes compose once in this order: global, collection,
then path prefixes from broadest to most specific. Context guides interpretation;
it is not searched and does not change ranking.
Multiple distinct normalized texts may share one scope. The persisted identity
is `(scopeType, canonicalScopeKey, normalizedText)`. Text normalization removes
a leading BOM, converts CRLF/CR to LF, applies NFC, and trims surrounding
whitespace; an exact normalized duplicate is rejected.

**Synopsis:**

```bash
gno context add <scope> "<text>"
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<scope>` | string | `/` (global), `collection:` prefix, or `gno://collection/prefix` |
| `<text>` | string | Context description text |

**Exit Codes:**

- 0: Success
- 1: Invalid scope format

**Examples:**

```bash
gno context add / "Corporate knowledge base"
gno context add work: "Work documents and contracts"
gno context add gno://work/contracts "Legal contracts and NDAs"
```

---

### gno context list

List all configured contexts.

**Synopsis:**

```bash
gno context list [--json|--md]
```

**Output (JSON):**

```json
[
  { "scope": "/", "text": "Corporate knowledge base" },
  { "scope": "work:", "text": "Work documents" }
]
```

---

### gno context check

Validate context configuration.

**Synopsis:**

```bash
gno context check [--json|--md]
```

**Output (JSON):**

```json
{
  "valid": true,
  "warnings": [],
  "errors": []
}
```

---

### gno context rm

Remove a context. Scope-only removal succeeds only when exactly one record
matches. If multiple texts share the scope, the caller MUST pass exact text;
ambiguous removal fails without mutation.

**Synopsis:**

```bash
gno context rm <scope> [text]
```

**Exit Codes:**

- 0: Success
- 1: Invalid/not-found scope, text not found, or ambiguous scope-only removal

---

### gno context build

Compile exact indexed evidence into a deterministic Context Capsule. The
Capsule is returned only on stdout or at an explicitly requested output path;
the command never persists Capsules implicitly. Model and download progress is
written to stderr.

**Synopsis:**

```bash
gno context build "<goal>" --budget <tokens> [--collection <name>] [--project-root <path>]... [--no-project-affinity] [--graph|--no-graph] [--fast|--thorough] [--json|--md] [--output <file>]
```

`--budget` is the global token ceiling. `--bytes` optionally sets a separate
byte ceiling; otherwise it is four times the token request. Without an active
token counter, `usedTokens` uses the conservative UTF-8 byte count and the
Capsule records `tokenizer_unavailable`. `--query`, `--uri-prefix`, tag,
category, author, language, date, and repeatable `--query-mode` filters use the
same canonical retrieval semantics as `gno query`. `--collection` is
repeatable. Tag filters are NFC-normalized, lowercased, deduplicated, and
validated before retrieval. Result and candidate limits are global across
repeated collections: the merged result pool is capped once, while candidate
work is distributed deterministically in canonical collection order.
Project affinity defaults to the trusted process cwd/repository. Repeatable
`--project-root` values replace that default, are normalized/deduplicated, and
are capped at 16. `--no-project-affinity` disables the soft signal and cannot
be combined with explicit roots.
Balanced and thorough Context retrieval request bounded graph expansion by
default. `--no-graph` disables it; `--fast` also keeps the graph stage off.

JSON is the canonical V1 payload. Markdown is a readable projection of that
same payload and hard-delimits each untrusted evidence passage. Passage,
metadata, manifest, and verification-receipt blocks use deterministic
collision-resistant Markdown fences: the fence character and width are derived
from the complete block, so indexed text cannot forge a closing boundary.
Indexed title, heading, and configured-context metadata remains JSON-escaped;
exact passage bytes remain unchanged inside the fence. Budgets, normalized
retrieval requests, capability attempts/outcomes, fingerprints, snapshots,
fallbacks, omissions, and truncation remain auditable.
An enabled retrieval trace links the request to `capsuleId` in local trace
storage and returns its random identity only on stderr. The trace identity is
never added to the canonical Capsule, its budget, or its deterministic ID.
Invalid goals,
budgets, filters, URI/index combinations, or output paths exit 1. Snapshot,
retrieval, provenance, and store failures exit 2 with no partial Capsule.
Requested collections must exist in the active configuration before retrieval.

### gno context verify

Verify a saved canonical JSON Capsule without rebuilding or mutating it.

**Synopsis:**

```bash
gno context verify <file|-> [--json|--md] [--output <file>]
```

`-` reads stdin. Verification re-resolves exact source, mirror, chunk, passage,
and index state. Without a live rank resolver, ranking is reported as
`ranking_unavailable`; stale or missing evidence is never reported as ranked.
JSON uses the canonical verification schema. Markdown projects the same receipt,
including fingerprint drift and every available current hash. Non-canonical
metadata and invalid identity/budget data fail before the store is read.
When global `--index` is omitted, the Capsule scope selects the index. An
explicit global `--index` must match the Capsule scope; mismatch fails before a
store is opened. Active-tokenizer Capsules require the matching tokenizer
fingerprint and deterministic recount callback before any store read; CLI
runtimes without that tokenizer fail with `tokenizer_unavailable` rather than
trusting saved `usedTokens`.

### gno context watch / watches / unwatch / reverify

Register an explicit canonical JSON Capsule file for local, evidence-triggered
reverification:

```bash
gno context watch <file> [--question <text>] [--label <text>] [--notify] [--json]
gno context watches [--json]
gno context unwatch <registration> [--json]
gno context reverify <registration> [--json]
```

The registration persists only the absolute file path, exact file hash,
Capsule/index identity, optional question and label, notification preference,
and evidence URI/hash references. Capsule bytes and evidence passages are never
copied into the database. The file remains caller-owned and immutable to GNO.
The Capsule's canonical index is authoritative when `watch` is invoked without
an explicit global `--index`; an explicit mismatch fails before journal or
evidence reads.

A resident `serve` or `daemon` runtime reverifies affected registrations after
watcher work has settled. Raw journal changes are coalesced, one bounded
reverification batch runs at a time, and the durable journal high-water mark
prevents duplicate work after restart. An expired journal cursor triggers a
conservative bounded pass over all registrations. Reverification uses the
canonical `context verify` receipt. Operation failures are stored separately
and never synthesized into a receipt.

`--notify` enables local metadata-only `capsule-reverified` events after the
verification record commits. Events contain registration/Capsule identity,
operation status, affected-question state, and timestamp; they contain no
question, file path, URI, passage, Capsule, or receipt bytes. `context
reverify` performs the same non-generative verification immediately.

`context reverify` exits `0` only when `operationStatus` is `completed`. A
persisted `failed` operation is still rendered: terminal output includes the
failure code and message, while `--json` writes the closed structured
reverification object to stdout. The command then exits `2`; the structured
failure must never be mistaken for a successful verification receipt.

JSON contracts are Draft-07 and closed:

- `watch`: `saved-capsule-watch.schema.json`; the initial verification is null.
- `watches`: `saved-capsule-list.schema.json`.
- `unwatch`: `saved-capsule-unwatch.schema.json`.
- `reverify`: `saved-capsule-reverification.schema.json`; a completed
  canonical receipt and a failed operation record are mutually exclusive.
- local SSE notification data: `capsule-reverified-event.schema.json`.

These registration-management surfaces are CLI-only. REST, MCP, and SDK expose
the non-persistent `context verify` operation, not watch lifecycle mutations.

---

### gno models list

List configured and available models.

**Synopsis:**

```bash
gno models list [--json|--md]
```

**Output (JSON):**

```json
{
  "activePreset": "slim",
  "presets": [
    { "id": "slim", "name": "Slim (Default, ~1GB)", "active": true },
    { "id": "balanced", "name": "Balanced (~2GB)", "active": false },
    {
      "id": "quality",
      "name": "Quality (Best Answers, ~2.5GB)",
      "active": false
    }
  ],
  "embed": {
    "uri": "hf:gpustack/bge-m3-GGUF/bge-m3-Q4_K_M.gguf",
    "cached": true
  },
  "rerank": {
    "uri": "hf:gpustack/bge-reranker-v2-m3-GGUF/bge-reranker-v2-m3-Q4_K_M.gguf",
    "cached": false
  },
  "gen": {
    "uri": "hf:unsloth/Qwen3-1.7B-GGUF/Qwen3-1.7B-Q4_K_M.gguf",
    "cached": true
  }
}
```

---

### gno models use

Switch active model preset.

**Synopsis:**

```bash
gno models use <preset>
```

**Arguments:**
| Arg | Type | Description |
|-----|------|-------------|
| `<preset>` | string | Preset ID: `slim`, `balanced`, or `quality` |

**Presets:**
| ID | Gen Model | RAM | Use Case |
|----|-----------|-----|----------|
| `slim` | Qwen3-1.7B | ~1GB | Default, fast queries |
| `balanced` | Qwen2.5-3B-Instruct | ~2GB | Slightly larger model |
| `quality` | Qwen3-4B-Instruct | ~2.5GB | Best grounded answers |

**Exit Codes:**

- 0: Success
- 1: Unknown preset

**Behavior note:**

- if the preset switch changes the active embedding model, terminal output should
  tell the user to run `gno embed`

---

### gno models pull

Download models to local cache.

**Synopsis:**

```bash
gno models pull [--all|--embed|--rerank|--gen] [--force]
```

**Options:**
| Option | Description |
|--------|-------------|
| `--all` | Pull all configured models |
| `--embed` | Pull embedding model only |
| `--rerank` | Pull reranker model only |
| `--gen` | Pull generation model only |
| `--force` | Re-download even if already cached |

**Behavior:**

- Skips models that are already cached (checksum match) unless `--force` is used
- Skips HTTP(S) rerank endpoints as external services; they are called directly
  and are never downloaded or cached, including with `--force`
- Default (no flags): pulls all models

**Exit Codes:**

- 0: Success
- 2: Download failure

---

### gno models clear

Remove cached models.

**Synopsis:**

```bash
gno models clear [--all|--embed|--rerank|--gen]
```

---

### gno models path

Print model cache directory.

**Synopsis:**

```bash
gno models path [--json]
```

**Output:**

```
/Users/user/Library/Caches/gno/models
```

---

### gno publish export

Build a reader-safe gno.sh artifact from one active collection or document.

**Synopsis:**

```bash
gno publish export <target> \
  [--out <path>] \
  [--visibility public|secret-link|invite-only|encrypted] \
  [--passphrase <value>] \
  [--slug <slug>] \
  [--title <title>] \
  [--summary <summary>] \
  [--preview] \
  [--json]
```

The CLI and `POST /api/publish/export` MUST preserve the existing `public`
default when visibility is omitted. Local Web UI export dialogs MUST instead
require an explicit mode. Both successful export results MUST report the
selected mode in `artifact.spaces[].visibility`. Invalid modes and missing required encryption input
MUST fail without an artifact. Encrypted CLI export requires `--passphrase`;
the local API requires `encryptionPassphrase`. Neither sends that input to
gno.sh. Export is a local operation, not hosted activation or deletion.

New exports MUST assign each note a random lowercase UUIDv4 persisted in
`publish-identities.json` beside the resolved config file (`--config` and
`GNO_CONFIG_DIR` apply). The private registry keys canonical collection roots
and source-relative paths, outside the disposable index. The collection root
MUST be accessible for canonical-path resolution. V1 notes emit `id`;
V2 spaces emit `noteIds` matching decrypted reader cards’ `noteId` values.
Legacy artifacts without IDs remain valid. Registry corruption or write failure
MUST fail export rather than reset IDs. Content/title edits, published route/slug
changes, and index rebuilds preserve IDs for unchanged source paths. Moving a
source file or collection root starts a new identity; no move inference or
source-Markdown mutation is performed. Registry paths MUST NOT enter artifacts.

Public V1 spaces MUST carry a `manifest` conforming to
[`publish-artifact.schema.json`](./output-schemas/publish-artifact.schema.json).
The manifest contains schema version `1.0`, a deterministic projection
revision, generated time, closed public capabilities, sorted published
documents, relative Markdown locators, SHA-256 content hashes, and
Capsule-compatible evidence identities. Manifest hashes and revisions MUST be
derived only from sanitized notes and metadata present in the published
projection. Local collection paths, document source URIs, unpublished
documents, and filtered metadata MUST NOT enter artifact bytes or revision
inputs. Reader metadata values containing embedded local path or GNO/file URI
tokens MUST be filtered. Canonical and image metadata MUST contain
uncredentialed public HTTP(S) targets; local hostnames and literal loopback,
private, or link-local addresses MUST be filtered.

When local raster attachments are bundled, the V1 artifact MAY include
`assets[]` and `requiredCapabilities: ["bundled-raster-assets@1"]`. Export
success payloads (CLI `--json` and `POST /api/publish/export`) MUST include a
deterministic `assetSummary` with asset/ref counts, raw/encoded/final UTF-8
upload bytes, dedup savings, external image count, and unresolved/unsupported
diagnostics. Exact final serialized upload bytes MUST be enforced against the
100 MiB ceiling before returning. Encrypted V2 ciphertext string bounds MUST
align to that same 100 MiB final-envelope budget and MUST NOT replace exact
final serialized measurement as the authoritative upload gate. Producer
ingress MUST require AV1-decodable AVIF payloads (structural BMFF alone is
insufficient). Asset-free exports omit `assets` /
`requiredCapabilities` and report zero asset counts.

Secret-link and invite-only V1 spaces MUST NOT contain a manifest or agent
capability field. Encrypted V2 spaces MUST contain only ciphertext parameters,
the opaque secret token, route slug, source type, encrypted visibility, and an
optional unique `noteIds` UUIDv4 roster (1–5000 IDs); no
plaintext manifest or evidence may appear outside the ciphertext. V2 builders
MUST emit a closed projection, validate payload strings as non-empty bounded
base64, require a positive safe-integer KDF iteration count, and bound the
non-blank opaque token. Caller-supplied extension fields MUST NOT enter the
artifact. Encrypted export MUST place bundled assets only inside the encrypted
client payload. The outer V2 envelope MUST contain neither plaintext note
content, asset descriptors, source references, nor raster bytes.

---

### gno cleanup

Remove orphaned content, chunks, and vectors not referenced by active documents.

**Synopsis:**

```bash
gno cleanup
```

**Exit Codes:**

- 0: Success
- 2: DB failure

---

### gno doctor

Diagnose configuration and dependencies.

**Synopsis:**

```bash
gno doctor [--json|--md]
```

**Output (JSON):**

```json
{
  "healthy": true,
  "checks": [
    {
      "name": "config",
      "status": "ok",
      "message": "Config loaded: ~/.config/gno/config.yaml"
    },
    {
      "name": "database",
      "status": "ok",
      "message": "Database found: ~/.local/share/gno/index.db"
    },
    { "name": "embed-model", "status": "ok", "message": "embed model cached" },
    {
      "name": "rerank-model",
      "status": "warn",
      "message": "rerank model not cached. Run: gno models pull --rerank"
    },
    { "name": "gen-model", "status": "ok", "message": "gen model cached" },
    {
      "name": "node-llama-cpp",
      "status": "ok",
      "message": "node-llama-cpp loaded successfully"
    },
    {
      "name": "embedding-fingerprint",
      "status": "warn",
      "message": "current abc123def456, 12 pending/stale, 3 legacy, 2 groups",
      "details": [
        "Run: gno embed",
        "If vectors still look stale, run: gno embed --force"
      ],
      "embeddingFingerprint": {
        "model": "hf:Qwen/Qwen3-Embedding-0.6B-GGUF:Qwen3-Embedding-0.6B-Q8_0.gguf",
        "currentFingerprint": "abc123def4567890",
        "pendingChunks": 12,
        "legacyChunks": 3,
        "mixedGroups": 2,
        "groups": [
          {
            "model": "hf:Qwen/Qwen3-Embedding-0.6B-GGUF:Qwen3-Embedding-0.6B-Q8_0.gguf",
            "fingerprint": "abc123def4567890",
            "count": 42,
            "current": true,
            "legacy": false
          }
        ]
      }
    },
    {
      "name": "findings-pass",
      "status": "ok",
      "message": "disabled (opt-in via findings.enabled)"
    }
  ]
}
```

The `findings-pass` check reports the daemon's scheduled findings pass from
its persisted run state: `ok` when disabled or the last run succeeded, `warn`
on `skipped_lease` / `overdue` / no recorded run, `error` on `failed` or a
misconfigured `findings` block (see [Daemon Mode](../docs/DAEMON.md#scheduled-findings-pass)).

The `embedding-fingerprint` check is additive doctor-only diagnostics. It uses
the active embed model and stored vector dimensions to report the current
freshness fingerprint, pending/stale chunks, legacy empty-fingerprint vectors,
and stored fingerprint groups. Stale, legacy, and mixed groups are warnings;
recover with `gno embed`, or `gno embed --force` if vectors still look stale.

The additive `activation` object uses the same contract as `gno status` and
`GET /api/status`. Doctor performs only the local lexical proof. It never starts
connector children or initializes/downloads models. A failed lexical proof adds
the `retrieval-activation` error check and exits 2 after writing the complete
result; no duplicate error is written to stderr. Connector failure or projection
truncation adds a warning and makes the structured doctor result non-healthy,
but preserves exit 0 when lexical proof and all other required checks pass. An
omitted target/collection pair has no inferred result.

**Exit Codes:**

- 0: All checks pass or only warnings
- 2: Critical checks failed

---

### gno mcp

Start MCP server over stdio.

**Synopsis:**

```bash
gno mcp
```

**Behavior:**

- Starts JSON-RPC 2.0 MCP server on stdin/stdout
- Keeps DB open for server lifetime
- See [MCP Specification](./mcp.md) for protocol details

**Exit Codes:**

- 0: Clean shutdown
- 2: Initialization failure

---

### gno mcp install

Install gno as an MCP server in client configurations.

**Synopsis:**

```bash
gno mcp install [--target <target>] [--scope <scope>] [--force] [--dry-run] [--enable-write] [--tool-profile <core|full>] [--json]
```

**Options:**

| Option           | Type    | Default        | Description                                                                                                                                                 |
| ---------------- | ------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--target`       | string  | claude-desktop | Target client (see table below)                                                                                                                             |
| `--scope`        | string  | target default | Scope: `user` or `project`; LibreChat defaults to project                                                                                                   |
| `--force`        | boolean | false          | Overwrite existing gno configuration                                                                                                                        |
| `--dry-run`      | boolean | false          | Show what would be done without changes                                                                                                                     |
| `--enable-write` | boolean | false          | Start the registered server with write tools enabled                                                                                                        |
| `--tool-profile` | string  | full           | Advertised tool set written into the registration: `core` (7 read tools, plus capture and remember with `--enable-write`) or `full`; omitted writes no flag |

**Targets:**

| Value            | Description                  | Project Scope      |
| ---------------- | ---------------------------- | ------------------ |
| `claude-desktop` | Claude Desktop app (default) | No                 |
| `claude-code`    | Claude Code CLI              | Yes                |
| `codex`          | OpenAI Codex CLI             | Yes                |
| `cursor`         | Cursor editor                | Yes                |
| `zed`            | Zed editor                   | No                 |
| `windsurf`       | Windsurf IDE                 | No                 |
| `opencode`       | OpenCode CLI                 | Yes                |
| `amp`            | Amp (Sourcegraph)            | No                 |
| `lmstudio`       | LM Studio                    | No                 |
| `librechat`      | LibreChat                    | Yes (project only) |

**Config Locations:**

| Target         | Scope   | macOS                                                             | Windows                                       | Linux                                         |
| -------------- | ------- | ----------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
| claude-desktop | user    | `~/Library/Application Support/Claude/claude_desktop_config.json` | `%APPDATA%\Claude\claude_desktop_config.json` | `~/.config/Claude/claude_desktop_config.json` |
| claude-code    | user    | `~/.claude.json`                                                  | `~/.claude.json`                              | `~/.claude.json`                              |
| claude-code    | project | `./.mcp.json`                                                     | `./.mcp.json`                                 | `./.mcp.json`                                 |
| codex          | user    | `~/.codex/config.toml`                                            | `~/.codex/config.toml`                        | `~/.codex/config.toml`                        |
| codex          | project | `./.codex/config.toml`                                            | `./.codex/config.toml`                        | `./.codex/config.toml`                        |
| cursor         | user    | `~/.cursor/mcp.json`                                              | `~/.cursor/mcp.json`                          | `~/.cursor/mcp.json`                          |
| cursor         | project | `./.cursor/mcp.json`                                              | `./.cursor/mcp.json`                          | `./.cursor/mcp.json`                          |
| zed            | user    | `~/.config/zed/settings.json`                                     | `%APPDATA%\Zed\settings.json`                 | `~/.config/zed/settings.json`                 |
| windsurf       | user    | `~/.codeium/windsurf/mcp_config.json`                             | `~/.codeium/windsurf/mcp_config.json`         | `~/.codeium/windsurf/mcp_config.json`         |
| opencode       | user    | `~/.config/opencode/opencode.json`                                | `~/.config/opencode/opencode.json`            | `~/.config/opencode/opencode.json`            |
| opencode       | project | `./opencode.json`                                                 | `./opencode.json`                             | `./opencode.json`                             |
| amp            | user    | `~/.config/amp/settings.json`                                     | `~/.config/amp/settings.json`                 | `~/.config/amp/settings.json`                 |
| lmstudio       | user    | `~/.lmstudio/mcp.json`                                            | `~/.lmstudio/mcp.json`                        | `~/.lmstudio/mcp.json`                        |
| librechat      | project | `./librechat.yaml`                                                | `./librechat.yaml`                            | `./librechat.yaml`                            |

**Config Formats:**

- JSONC-compatible (`mcpServers` key): Claude Desktop, Claude Code, Cursor, Windsurf, LM Studio
- Standard YAML (`mcpServers` key): LibreChat
- Codex TOML: `[mcp_servers.gno]` plus `[mcp_servers.gno.env]`
- Zed: `context_servers` key
- OpenCode: `mcp` key with array command format
- Amp: `amp.mcpServers` key

JSON/JSONC edits preserve comments, trailing commas, and unrelated layout.
OpenCode and Amp discover an existing `.jsonc` alternate instead of creating a
duplicate canonical `.json` file.

`--dry-run --json` reports the normalized command, arguments, and workspace
environment, not the target's persisted wrapper shape. Previewing replacement
of an existing `gno` entry requires `--force --dry-run --json`; no file is
written in dry-run mode.

**Behavior:**

1. Resolves the active index and validates it with the shared index-name contract
2. Resolves the active explicit, environment-selected, or default config to an absolute path
3. Builds an absolute command using the current Bun executable, `run`, and the current package's `src/index.ts`
4. Appends `--index <active> --config <absolute> mcp` (`--enable-write` follows `mcp` when requested)
5. Resolves absolute `GNO_DATA_DIR` and `GNO_CACHE_DIR` values for the active workspace
6. Reads existing config (creates if missing)
7. Adds the format-specific `gno` server entry, using `env` for standard/Codex/YAML entries and `environment` for OpenCode
8. Creates a backup before modifying
9. Writes atomically via temp file + rename

The persisted index, config, data directory, and cache directory are workspace
identity, not display metadata. They make the installed GUI client deterministic
even when it has a different `PATH` or does not inherit `GNO_*` variables. Only
the two audited absolute-path environment keys are persisted; status and
activation reject other environment keys or invalid values.

**Output (JSON):**

```json
{
  "installed": {
    "target": "claude-desktop",
    "scope": "user",
    "configPath": "~/Library/Application Support/Claude/claude_desktop_config.json",
    "action": "created",
    "serverEntry": {
      "command": "/path/to/bun",
      "args": [
        "run",
        "/path/to/@gmickel/gno/src/index.ts",
        "--index",
        "default",
        "--config",
        "/absolute/path/to/index.yml",
        "mcp"
      ],
      "env": {
        "GNO_DATA_DIR": "/absolute/path/to/data",
        "GNO_CACHE_DIR": "/absolute/path/to/cache"
      }
    }
  }
}
```

**Exit Codes:**

- 0: Success
- 1: Already configured (without --force), invalid scope for target, invalid index name
- 2: Bun not found, gno not found, IO failure

**Examples:**

```bash
# Install for Claude Desktop (default)
gno mcp install

# Install for Cursor
gno mcp install --target cursor

# Install for Zed
gno mcp install --target zed

# Install for Claude Code (project scope)
gno mcp install --target claude-code --scope project

# Force overwrite
gno mcp install --force

# Preview changes
gno mcp install --dry-run
```

---

### gno mcp uninstall

Remove gno MCP server from client configurations.

**Synopsis:**

```bash
gno mcp uninstall [--target <target>] [--scope <scope>] [--json]
```

**Options:**

| Option     | Type   | Default        | Description                          |
| ---------- | ------ | -------------- | ------------------------------------ |
| `--target` | string | claude-desktop | Target client                        |
| `--scope`  | string | target default | Scope; LibreChat defaults to project |

**Behavior:**

1. Reads existing config
2. Removes the format-specific GNO entry if present (`mcpServers.gno`,
   `context_servers.gno`, `mcp.gno`, `amp.mcpServers.gno`, or Codex's
   `[mcp_servers.gno]` plus `[mcp_servers.gno.env]` tables)
3. Creates backup before modifying
4. Removes an empty format-specific server object; Codex preserves unrelated
   TOML and comments byte-for-byte apart from necessary surrounding whitespace
5. Preserves other entries

**Output (JSON):**

```json
{
  "uninstalled": {
    "target": "claude-desktop",
    "scope": "user",
    "configPath": "~/Library/Application Support/Claude/claude_desktop_config.json",
    "action": "removed"
  }
}
```

**Exit Codes:**

- 0: Success (including if not configured)
- 1: Invalid scope for target
- 2: IO failure

---

### gno mcp status

Show MCP server installation status across all targets.

**Synopsis:**

```bash
gno mcp status [--target <target>] [--scope <scope>] [--json]
```

**Options:**

| Option     | Type   | Default | Description                 |
| ---------- | ------ | ------- | --------------------------- |
| `--target` | string | all     | Filter by target (or `all`) |
| `--scope`  | string | all     | Filter by scope (or `all`)  |

**Output (Terminal, abbreviated; unfiltered status enumerates 14 target/scope
pairs):**

```text
MCP Server Status
──────────────────────────────────────────────────

✓ Claude Desktop: configured
    Command: /path/to/bun
    Args: run /path/to/@gmickel/gno/src/index.ts --index default --config /absolute/path/to/index.yml mcp
    Config: ~/Library/Application Support/Claude/claude_desktop_config.json

✗ Claude Code: not configured
    Config: ~/.claude.json

✗ Claude Code (project): not configured
    Config: ./.mcp.json

1/14 targets configured
```

**Output (JSON):**

```json
{
  "targets": [
    {
      "target": "claude-desktop",
      "scope": "user",
      "configPath": "~/Library/Application Support/Claude/claude_desktop_config.json",
      "configured": true,
      "serverEntry": {
        "command": "/path/to/bun",
        "args": [
          "run",
          "/path/to/@gmickel/gno/src/index.ts",
          "--index",
          "default",
          "--config",
          "/absolute/path/to/index.yml",
          "mcp"
        ],
        "env": {
          "GNO_DATA_DIR": "/absolute/path/to/data",
          "GNO_CACHE_DIR": "/absolute/path/to/cache"
        }
      }
    },
    {
      "target": "claude-code",
      "scope": "user",
      "configPath": "~/.claude.json",
      "configured": false
    }
  ],
  "summary": { "configured": 1, "total": 14 }
}
```

**Exit Codes:**

- 0: Success
- 1: Invalid target or scope
- 2: IO failure

---

### gno skill install

Install GNO agent skill for Claude Code, Codex, OpenCode, OpenClaw, or Hermes.

**Synopsis:**

```bash
gno skill install [--scope <project|user>] [--target <claude|codex|opencode|openclaw|hermes|all>] [--force] [--json]
```

**Options:**

| Option     | Type    | Default | Description                                                   |
| ---------- | ------- | ------- | ------------------------------------------------------------- |
| `--scope`  | string  | project | `project` (.claude/skills/) or `user` (~/.claude/skills/)     |
| `--target` | string  | claude  | `claude`, `codex`, `opencode`, `openclaw`, `hermes`, or `all` |
| `--force`  | boolean | false   | Overwrite existing skill without prompting                    |

**Behavior:**

1. Resolves target path based on scope and target
2. If skill exists and not `--force`/`--yes`: error
3. Atomically installs skill directory (temp + rename)
4. Copies SKILL.md, reference files, and nested recipe files

**Compatibility:** As of their current releases, Grok Build (the xAI grok CLI) automatically reads Claude skill directories (`.claude/skills/` and `~/.claude/skills/`) with no extra configuration, and Cursor loads agent skills from `.claude/skills/` and `.codex/skills/` (project and user scope) in addition to its own `.cursor/skills/`. `--target claude` therefore covers Grok Build and Cursor; `--target codex` is also read by Cursor. Other skill-capable clients can copy the installed files; `gno skill paths` prints their locations. Dedicated `grok` and `cursor` installer targets are not shipped: they would duplicate the claude/codex paths those tools already read. Revisit only if users keep `~/.grok/skills` or `~/.cursor/skills` separate and ask for first-class targets.

**Output (JSON):**

```json
{
  "installed": [
    { "target": "claude", "scope": "project", "path": ".claude/skills/gno" }
  ]
}
```

**Exit Codes:**

- 0: Success
- 1: Skill already exists (without --force)
- 2: IO failure

**Examples:**

```bash
# Install to current project for Claude Code
gno skill install

# Install globally for all agents
gno skill install --scope user --target all

# Force reinstall
gno skill install --force
```

---

### gno skill uninstall

Remove GNO agent skill.

**Synopsis:**

```bash
gno skill uninstall [--scope <project|user>] [--target <claude|codex|opencode|openclaw|hermes|all>] [--json]
```

**Options:** Same as `skill install` (except `--force`).

**Safety Checks:**

- Validates path ends with `/skills/gno` before removal
- Rejects paths that don't match expected structure
- Uses atomic removal with retry for Windows compatibility

**Output (JSON):**

```json
{
  "uninstalled": [
    { "target": "claude", "scope": "project", "path": ".claude/skills/gno" }
  ]
}
```

**Exit Codes:**

- 0: Success
- 1: Skill not found
- 2: IO failure or safety check failed

---

### gno skill show

Preview skill files without installing.

**Synopsis:**

```bash
gno skill show [--file <relative-md-path>] [--all]
```

**Options:**

| Option   | Type    | Default  | Description                                                                                       |
| -------- | ------- | -------- | ------------------------------------------------------------------------------------------------- |
| `--file` | string  | SKILL.md | Relative POSIX markdown path to show, including nested paths like `recipes/brain-first-lookup.md` |
| `--all`  | boolean | false    | Show all skill markdown files with separators                                                     |

**Behavior:**

- Outputs file content to stdout
- Lists available files at end
- Recursively lists bundled markdown files under the skill asset directory
- Rejects absolute paths, `..`, backslashes, and non-markdown file paths

**Exit Codes:**

- 0: Success
- 1: Invalid file name

**Examples:**

```bash
gno skill show
gno skill show --file cli-reference.md
gno skill show --file recipes/brain-first-lookup.md
gno skill show --all
```

---

### gno skill paths

Show resolved skill installation paths.

**Synopsis:**

```bash
gno skill paths [--scope <project|user>] [--target <claude|codex|opencode|openclaw|hermes|all>] [--json]
```

**Options:** Same as `skill install`

**Output (JSON):**

```json
{
  "paths": [
    {
      "target": "claude",
      "scope": "project",
      "path": "/path/to/.claude/skills/gno",
      "exists": false
    },
    {
      "target": "claude",
      "scope": "user",
      "path": "/home/user/.claude/skills/gno",
      "exists": true
    }
  ]
}
```

**Exit Codes:**

- 0: Success

---

### gno agents install

Install the compact, versioned GNO protocol block into the global (user-scope)
instruction files of every detected harness. The block is bounded by stable
markers (`<!-- gno:agents:begin -->` / `<!-- gno:agents:end -->`); install and
update touch ONLY the owned block — content outside the markers stays
byte-identical. The block content is static (identical on every machine), so
a block is current exactly when its stamp version and hash match the installed
release.

**Synopsis:**

```bash
gno agents install [--target <claude|codex|cursor|opencode|grok|hermes|openclaw|all>] [--extra-dir <path>]... [--dry-run] [--json]
```

**Options:**

| Option        | Type    | Default | Description                                                                                     |
| ------------- | ------- | ------- | ----------------------------------------------------------------------------------------------- |
| `--target`    | string  | all     | Harness to install for; `all` = every harness detected on this machine                          |
| `--extra-dir` | string  | —       | Additional instruction dir (repeatable, for nonstandard/multi-instance layouts). Never guessed. |
| `--dry-run`   | boolean | false   | Print per-target unified diffs; write nothing                                                   |

**Harness matrix (standard documented locations):**

| Target     | Instruction file                                        | Detection root          | Notes                                                                               |
| ---------- | ------------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------- |
| `claude`   | `$CLAUDE_CONFIG_DIR/CLAUDE.md` or `~/.claude/CLAUDE.md` | `~/.claude`             | Honors `CLAUDE_CONFIG_DIR`                                                          |
| `codex`    | `$CODEX_HOME/AGENTS.md` or `~/.codex/AGENTS.md`         | `~/.codex`              | Honors `CODEX_HOME`                                                                 |
| `cursor`   | `~/AGENTS.md`                                           | `~/.cursor`             | Cursor Agent walks cwd → home for AGENTS.md                                         |
| `opencode` | `~/.config/opencode/AGENTS.md`                          | `~/.config/opencode`    |                                                                                     |
| `grok`     | — (import chain)                                        | `~/.grok`               | Reads the Claude global file; reported `covered via claude`, never double-installed |
| `hermes`   | `~/.hermes/SOUL.md`                                     | `~/.hermes`             |                                                                                     |
| `openclaw` | `~/.openclaw/workspace/AGENTS.md`                       | `~/.openclaw/workspace` |                                                                                     |

**Behavior:**

1. Detects harnesses (config dir exists); undetected targets are reported
   `not-detected` and skipped — the installer never fabricates harness dirs
   (creating the instruction FILE inside a detected dir is fine). An explicit
   detected covered target (e.g. `--target grok`) also resolves its covering
   target so the file it actually reads converges; when the explicit target is
   absent, the covering chain never activates.
2. Backup-first, atomic: an existing file is copied to
   `<file>.gno-agents.bak.<timestamp>` (same permission mode as the source),
   then the new content is written to a sibling temp file and renamed over the
   destination, so a failed write leaves the live file unchanged. A fresh
   install appends the block after one blank line (a file without a final
   newline gets that newline first).
3. Idempotent: a current block is a `current` no-op (no write, no backup).
4. Fail-closed: malformed or duplicate markers, a file that is not valid
   UTF-8, or any read/write failure produce a per-target `error` row, nothing
   is written to that file, and — for install/update — the command prints the
   complete block (`manualBlock` in JSON) so the operator can apply it by
   hand. Other targets in the same run still proceed. A leading UTF-8 BOM is
   preserved across every operation.
5. Symlink-aware: writes go through the resolved real file (an operator's
   canonical-file-linked-everywhere scheme survives); targets resolving to the
   same real file are written once (`covered via <target> (same file)`).
6. `GNO_AGENTS_HOME_OVERRIDE` overrides the home directory (testing/sandboxed
   verification); any home override suppresses harness config-dir env vars.

**Output (JSON):**

```json
{
  "command": "install",
  "blockVersion": 2,
  "dryRun": false,
  "results": [
    {
      "target": "claude",
      "label": "Claude Code",
      "path": "/home/user/.claude/CLAUDE.md",
      "action": "install",
      "detected": true,
      "backup": null
    },
    {
      "target": "grok",
      "label": "Grok Build",
      "path": "/home/user/.claude/CLAUDE.md",
      "action": "covered",
      "detected": true,
      "via": "claude",
      "detail": "covered via claude"
    }
  ]
}
```

`action` is one of `install`, `update`, `current`, `covered`, `not-detected`,
`error` (and `remove`, `absent` for uninstall). A covered row's `path` is the
file the harness actually reads (its covering target's file). With
`--dry-run`, a `diffs` array of unified diffs is included. When
any target failed on install/update, `manualBlock` carries the complete block
text to paste. Schema: `spec/output-schemas/agents-mutation.schema.json`
(shared by install, update, and uninstall).

**Exit Codes:**

- 0: Success (including no-op)
- 1: Validation failure (unknown target, missing `--extra-dir`, malformed
  markers, non-UTF-8 file)
- 2: Runtime failure — every failing target hit an I/O error (unreadable file,
  backup or write failed); the receipt still lists every target's outcome

---

### gno agents update

Refresh an installed block in place (block-version migration). Same options,
output, and exit codes as `agents install`; both verbs converge the block to
the current release — `update` exists for operator intent clarity.

```bash
gno agents update [--target <...>] [--extra-dir <path>]... [--dry-run] [--json]
```

---

### gno agents verify

Deterministic per-target verification of installed blocks. Fresh-session
behavioral canaries are an operator practice, not automated here.

**Synopsis:**

```bash
gno agents verify [--target <...>] [--extra-dir <path>]... [--json]
```

**Checks per target:**

- Exactly one marker block (`malformed` otherwise)
- Block stamp hash matches the block body (tamper detection)
- Block version and content match the installed release (`outdated` otherwise)
- The block carries no filesystem references by construction, so the spec's
  link-resolution check is vacuously satisfied
- Targets resolving to the same real file are verified once, using install's
  ownership order; the rest report `covered via <target> (same file)`

**Output (JSON):**

```json
{
  "command": "verify",
  "blockVersion": 2,
  "ok": true,
  "results": [
    {
      "target": "claude",
      "label": "Claude Code",
      "path": "/home/user/.claude/CLAUDE.md",
      "status": "ok",
      "detected": true,
      "blockVersion": 2,
      "hashOk": true
    }
  ]
}
```

`status` is one of `ok`, `outdated`, `missing`, `malformed`, `error`,
`covered`, `not-detected`. `error` means the file exists but could not be read
— a "could not check", never a content verdict. Schema:
`spec/output-schemas/agents-verify.schema.json`.

**Exit Codes:**

- 0: All verified targets ok (covered / not-detected do not fail)
- 1: One or more targets `outdated`, `missing`, or `malformed`
- 2: Every failing target is an `error` (I/O) row

---

### gno agents uninstall

Remove the block, its markers, and the blank line install added above it;
everything else stays byte-identical. Same options, output shape
(`action: remove|absent|...`), and exit codes as `agents install`.

```bash
gno agents uninstall [--target <...>] [--extra-dir <path>]... [--dry-run] [--json]
```

---

### gno tags list

List all tags with document counts.

**Synopsis:**

```bash
gno tags [list] [-c, --collection <name>] [--prefix <prefix>] [--json] [--md]
```

**Options:**

| Option             | Type   | Description               |
| ------------------ | ------ | ------------------------- |
| `-c, --collection` | string | Filter by collection name |
| `--prefix`         | string | Filter by tag prefix      |
| `--json`           | flag   | JSON output               |
| `--md`             | flag   | Markdown output           |

**Output (JSON):**

```json
{
  "tags": [
    { "tag": "javascript", "count": 15 },
    { "tag": "python", "count": 8 }
  ],
  "meta": {
    "total": 25,
    "collection": "notes",
    "prefix": "java"
  }
}
```

**Exit Codes:**

- 0: Success

---

### gno tags add

Add a tag to a document.

**Synopsis:**

```bash
gno tags add <doc> <tag> [--json]
```

**Arguments:**

- `<doc>` - Document reference (docid or URI)
- `<tag>` - Tag to add (normalized to lowercase)

**Options:**

| Option   | Type | Description |
| -------- | ---- | ----------- |
| `--json` | flag | JSON output |

**Behavior:**

- Validates tag format (lowercase alphanumeric with hyphens/dots/slashes)
- Adds tag to document in database with source='user'
- For markdown files, also updates frontmatter tags
- Idempotent: succeeds if tag already exists

**Output (JSON):**

```json
{
  "docid": "abc123",
  "tag": "javascript",
  "wroteToFile": true
}
```

**Exit Codes:**

- 0: Success
- 1: Invalid tag format or document not found

---

### gno tags rm

Remove a tag from a document.

**Synopsis:**

```bash
gno tags rm <doc> <tag> [--json]
```

**Arguments:**

- `<doc>` - Document reference (docid or URI)
- `<tag>` - Tag to remove

**Options:**

| Option   | Type | Description |
| -------- | ---- | ----------- |
| `--json` | flag | JSON output |

**Behavior:**

- Removes tag from document in database
- For markdown files with frontmatter tags, also updates the file

**Output (JSON):**

```json
{
  "docid": "abc123",
  "tag": "javascript",
  "removedFromFile": true
}
```

**Exit Codes:**

- 0: Success
- 1: Tag not found on document or document not found

---

### gno links list

List outgoing links from a document.

**Synopsis:**

```bash
gno links [list] <doc> [--type <wiki|markdown>] [--edge-type <type>] [--relation <type>] [--json] [--md]
```

**Arguments:**

| Argument | Description                              |
| -------- | ---------------------------------------- |
| `<doc>`  | Document reference (docid, URI, or path) |

**Options:**

| Flag          | Type   | Description                       |
| ------------- | ------ | --------------------------------- |
| `--type`      | string | Filter positional links by syntax |
| `--edge-type` | string | Filter semantic edges by type     |
| `--relation`  | string | Alias for `--edge-type`           |
| `--json`      | flag   | JSON output                       |
| `--md`        | flag   | Markdown output                   |

**Behavior:**

- Lists all outgoing links from the document
- Shows link type (wiki or markdown), target, display text, location
- Indicates whether each link resolves to an indexed document
- Default subcommand is `list` (can be omitted)
- `--edge-type`/`--relation` switches to semantic `doc_edges` output (`edgeType`, `relationType`, `confidence`, `edgeSource`)
- `--edge-type` and `--relation` are aliases for the same semantic edge type filter; if both are supplied they must match
- `--type` cannot be combined with `--edge-type` or `--relation`

**Output (JSON):**

Schema: `links-list.schema.json`

```json
{
  "links": [
    {
      "targetRef": "Other Note",
      "linkType": "wiki",
      "linkText": "display text",
      "startLine": 10,
      "startCol": 5,
      "resolved": true,
      "resolvedDocid": "#abc123"
    }
  ],
  "meta": {
    "docid": "#def456",
    "uri": "gno://notes/source.md",
    "totalLinks": 1,
    "resolvedCount": 1
  }
}
```

**Exit Codes:**

- 0: Success
- 1: Document not found or invalid options

**Examples:**

```bash
# List all links from a document
gno links gno://notes/source.md

# Filter to wiki links only
gno links list #abc123 --type wiki

# Filter semantic relationship edges
gno links gno://notes/source.md --edge-type mentions --json
gno links gno://notes/source.md --relation mentions --json

# JSON output
gno links gno://notes/note.md --json
```

---

### gno backlinks

List documents that link to a target document.

**Synopsis:**

```bash
gno backlinks <doc> [-c, --collection <name>] [--edge-type <type>] [--relation <type>] [--json] [--md]
```

**Arguments:**

| Argument | Description                              |
| -------- | ---------------------------------------- |
| `<doc>`  | Document reference (docid, URI, or path) |

**Options:**

| Flag               | Type   | Description                       |
| ------------------ | ------ | --------------------------------- |
| `-c, --collection` | string | Filter by collection              |
| `--edge-type`      | string | Filter semantic backlinks by type |
| `--relation`       | string | Alias for `--edge-type`           |
| `--json`           | flag   | JSON output                       |
| `--md`             | flag   | Markdown output                   |

**Behavior:**

- Lists all documents that link TO the specified document
- Shows source document info, link location, and link text
- Supports both wiki and markdown link resolution
- `--edge-type`/`--relation` switches to semantic `doc_edges` backlinks (`edgeType`, `relationType`, `confidence`, `edgeSource`) while preserving `--collection`

**Output (JSON):**

Schema: `backlinks.schema.json`

```json
{
  "backlinks": [
    {
      "sourceDocid": "#abc123",
      "sourceUri": "gno://notes/source.md",
      "sourceTitle": "Source Note",
      "linkText": "link to target",
      "startLine": 15,
      "startCol": 3
    }
  ],
  "meta": {
    "docid": "#def456",
    "uri": "gno://notes/target.md",
    "totalBacklinks": 1
  }
}
```

**Exit Codes:**

- 0: Success
- 1: Document not found

**Examples:**

```bash
# List backlinks to a document
gno backlinks gno://notes/target.md

# Filter by collection
gno backlinks #abc123 --collection notes

# Filter semantic backlinks
gno backlinks gno://notes/target.md --relation related_to --json

# JSON output
gno backlinks gno://docs/api.md --json
```

---

### gno similar

Find semantically similar documents using vector embeddings.

**Synopsis:**

```bash
gno similar <doc> [-n, --limit <num>] [--threshold <num>] [--cross-collection] [--json] [--md]
```

**Arguments:**

| Argument | Description                              |
| -------- | ---------------------------------------- |
| `<doc>`  | Document reference (docid, URI, or path) |

**Options:**

| Flag                 | Type   | Default | Description                    |
| -------------------- | ------ | ------- | ------------------------------ |
| `-n, --limit`        | number | 5       | Maximum results                |
| `--threshold`        | number | 0.7     | Minimum similarity score (0-1) |
| `--cross-collection` | flag   | false   | Search across all collections  |
| `--json`             | flag   |         | JSON output                    |
| `--md`               | flag   |         | Markdown output                |

**Behavior:**

- Finds documents semantically similar to the source document
- Requires embeddings to be generated (`gno embed`)
- Uses average document embedding for comparison
- By default, limits results to same collection

**Output (JSON):**

Schema: `similar.schema.json`

```json
{
  "similar": [
    {
      "docid": "#abc123",
      "uri": "gno://notes/related.md",
      "title": "Related Note",
      "score": 0.85,
      "collection": "notes",
      "relPath": "related.md"
    }
  ],
  "meta": {
    "docid": "#def456",
    "totalResults": 1,
    "limit": 5,
    "threshold": 0.7,
    "crossCollection": false
  }
}
```

**Exit Codes:**

- 0: Success
- 1: Document not found or no embeddings
- 2: Vector search unavailable

**Examples:**

```bash
# Find similar documents
gno similar gno://notes/note.md

# Increase limit and lower threshold
gno similar #abc123 --limit 10 --threshold 0.5

# Search across all collections
gno similar gno://docs/api.md --cross-collection --json
```

---

### gno graph

Generate knowledge graph of document links.

**Synopsis:**

```bash
gno graph [-c, --collection <name>] [--limit <num>] [--edge-limit <num>] [--include-similar] [--threshold <num>] [--include-isolated] [--similar-top-k <num>] [--json]
```

**Options:**

| Flag                 | Type   | Default | Description                    |
| -------------------- | ------ | ------- | ------------------------------ |
| `-c, --collection`   | string | all     | Filter to single collection    |
| `--limit`            | number | 2000    | Maximum nodes to return        |
| `--edge-limit`       | number | 10000   | Maximum edges to return        |
| `--include-similar`  | flag   | false   | Include similarity edges       |
| `--threshold`        | number | 0.7     | Similarity threshold (0-1)     |
| `--include-isolated` | flag   | false   | Include isolated nodes         |
| `--similar-top-k`    | number | 5       | Similar docs per node (max 20) |
| `--json`             | flag   |         | JSON output                    |

**Behavior:**

- Returns nodes (documents) and links (edges) as graph data
- Includes a graph report with hubs, bridge candidates, isolated documents, unresolved links, and edge-type counts
- Edges include wiki links, markdown links, and optionally similarity edges
- Node degree reflects total unique connections (in + out)
- When collection is filtered, degree may reflect links outside the filter
- Truncates results if node/edge limits are exceeded

**Output (JSON):**

Schema: `graph.schema.json`

```json
{
  "nodes": [
    {
      "id": "#abc123",
      "uri": "gno://notes/note.md",
      "title": "My Note",
      "collection": "notes",
      "relPath": "note.md",
      "degree": 5
    }
  ],
  "links": [
    {
      "source": "#abc123",
      "target": "#def456",
      "type": "wiki",
      "weight": 1,
      "confidence": "explicit",
      "audit": { "resolution": "exact-title", "matchCount": 1 }
    }
  ],
  "report": {
    "hubs": [
      {
        "id": "#abc123",
        "uri": "gno://notes/note.md",
        "title": "My Note",
        "collection": "notes",
        "relPath": "note.md",
        "degree": 5
      }
    ],
    "bridgeCandidates": [],
    "isolated": { "total": 2, "examples": [] },
    "unresolvedLinks": {
      "total": 5,
      "byType": { "wiki": 4, "markdown": 1 }
    },
    "edgeTypes": { "wiki": 280, "markdown": 40, "similar": 0 },
    "edgeConfidence": {
      "explicit": 300,
      "inferred": 18,
      "ambiguous": 2,
      "similarity": 0
    },
    "audit": { "inferredEdges": 18, "ambiguousEdges": 2, "similarityEdges": 0 }
  },
  "meta": {
    "collection": null,
    "nodeLimit": 2000,
    "edgeLimit": 10000,
    "totalNodes": 150,
    "totalEdges": 320,
    "totalEdgesUnresolved": 5,
    "returnedNodes": 150,
    "returnedEdges": 320,
    "truncated": false,
    "linkedOnly": true,
    "includedSimilar": false,
    "similarAvailable": true,
    "similarTopK": 5,
    "similarTruncatedByComputeBudget": false,
    "warnings": []
  }
}
```

### gno graph query

Run a bounded typed-edge traversal from one document. This command uses the
typed `doc_edges` projection (`relations:`, graph-hinted links, and backfilled
wiki/markdown links) and is scoped to a resolved root rather than the global
graph export.

**Synopsis:**

```bash
gno graph query <doc> [--direction <both|out|in>] [--edge-type <type>] [--max-depth <n>] [--max-nodes <n>] [--frontier-limit <n>] [--visited-limit <n>] [--json]
```

**Options:**

| Flag               | Type   | Default | Description                          |
| ------------------ | ------ | ------- | ------------------------------------ |
| `--direction`      | enum   | both    | Traverse outgoing, incoming, or both |
| `--edge-type`      | string | all     | Filter to one typed edge/relation    |
| `--max-depth`      | number | 2       | Maximum traversal depth              |
| `--max-nodes`      | number | 100     | Maximum returned nodes               |
| `--frontier-limit` | number | 100     | Max frontier width per depth         |
| `--visited-limit`  | number | 500     | Max visited rows during traversal    |
| `--json`           | flag   |         | JSON output                          |

**Behavior:**

- Resolves `<doc>` using the shared core ref parser (`#docid`, `gno://...`, or `collection/path`)
- Traverses typed edges with cycle safety and deterministic ordering
- Enforces hard depth, frontier, and visited-row caps; sets `meta.truncated` with warnings when caps trip
- Includes per-node `graphHints` from the node's configured content type

Schema: `graph-query.schema.json`

**Global Graph Edge Types:**

- `wiki`: Wiki link (`[[Target]]`)
- `markdown`: Markdown link (`[text](path.md)`)
- `similar`: Semantic similarity (requires `--include-similar` flag)

`gno graph query --edge-type` filters the typed `doc_edges.edge_type` values
derived from frontmatter relations, content-type graph hints, and backfilled
wiki/markdown projections (for example `mentions`, `references`, or
`related`), not the global graph export edge-type enum above.

### gno changes

List retained, metadata-only document lifecycle changes.

```bash
gno changes [--since <ISO-8601|cursor>] [--collection <name>] [--limit <n>] [--json]
gno changes --follow --jsonl [--cursor <cursor>] [--collection <name>]
```

- `--since` accepts an ISO-8601 time or an opaque cursor returned by an earlier
  call. Cursors are monotonic, stable, and must not be parsed by callers.
- `--limit` defaults to 100 and is bounded to 1-1000.
- JSON output uses `changes.schema.json`. It includes opaque per-change IDs,
  old/new identity and hash snapshots, normalized structural deltas, pagination,
  cursor-expiry, and retention-truncation disclosure.
- The journal never returns source bodies.

**Follow mode (`--follow --jsonl`)** streams journal events as they land and is
the durable automation input for consumers that resume across restarts.

- `--follow` and `--jsonl` are one mode and must be given together; `--cursor`
  requires both. The mode excludes `--since`, `--limit`, and `--json` (exit 1).
  `--collection` filters the stream.
- Wire contract: one JSON object per stdout line, validated by
  `changes-follow-event.schema.json`. An event line is
  `{"event": <change>, "postCursor": "<cursor>"}` where `event` is one
  `changes.schema.json` change and `postCursor` is the journal cursor after
  that event was applied. Lines are emitted in journal order.
- Checkpoint rule: a consumer persists `postCursor` after it has durably
  handled the line and restarts with `--cursor <postCursor>`; nothing at or
  before that cursor is replayed. Delivery is at-least-once: a line the
  consumer received but did not checkpoint is redelivered on resume, so
  handlers must be idempotent by `event.id` (each event id is unique and
  equals its own `postCursor`).
- Start position: without `--cursor` the stream starts at the journal's
  current `latestCursor` (tail semantics, no backfill). `--cursor` must be an
  opaque cursor from an earlier response (exit 1 when malformed or ahead of
  the journal).
- Quiet periods emit nothing. There is no keepalive line in v1; consumers
  detect liveness from the process, not the stream.
- Cursor expiry: when the resume cursor falls below the retention floor the
  stream writes exactly one terminal line,
  `{"error": "cursor_expired", "earliestCursor": "<cursor>", "latestCursor": "<cursor>"}`,
  then exits 2 with nothing on stderr. `earliestCursor` is the journal's
  documented resume floor (`gno changes --json` reports the same value);
  `latestCursor` is the current tail. The consumer chooses whether to backfill
  from `earliestCursor` or resume from `latestCursor` and accept the gap;
  resuming from `latestCursor` skips every retained event.
- Signals: SIGINT or SIGTERM ends the stream after the line in progress and
  exits 0. No partial line is ever written.
- The reader never takes the write lease; `gno index`, `gno update`, capture,
  and the daemon are never blocked by a follower.

### gno diff

Show the latest retained structural delta for one document, or select an exact
retained journal entry by opaque ID.

```bash
gno diff <doc> [--change <id>] [--json]
```

JSON output uses `document-diff.schema.json`. `content.status` is always
`not_retained`; GNO does not reconstruct old bodies. `history.status` is
`partial` when `structureDelta.truncated` discloses unavailable prior
structure. Expired/purged IDs return `status: "expired"` without inventing
history.

### gno impact

Find active documents that depend on one document through inbound typed,
wiki-link, or Markdown-link edges.

```bash
gno impact <doc> [--max-depth <n>] [--max-nodes <n>] [--max-edges <n>] [--frontier-limit <n>] [--visited-limit <n>] [--json]
```

The traversal is cycle-safe and enforces depth, node, edge, frontier, and
visited-row caps. Every impacted document includes one deterministic
dependency-to-root evidence path. JSON output uses `impact.schema.json`.

**Exit Codes:**

- 0: Success
- 1: No documents indexed

**Examples:**

```bash
# Full graph
gno graph

# Filter by collection
gno graph --collection notes

# Include similarity edges
gno graph --include-similar --threshold 0.6

# JSON output with limits
gno graph --limit 500 --edge-limit 2000 --json
```

---

### gno serve

Start web UI server for visual search and browse.

Both resident commands read the optional root `gateway` config. CLI gateway
flags override the corresponding scalar/list values for that invocation:

```yaml
gateway:
  host: 127.0.0.1
  tokenFile: ~/.config/gno/mcp-token
  allowedHosts: [127.0.0.1:3000, localhost:3000]
  allowedOrigins: [http://127.0.0.1:3000, http://localhost:3000]
  enableWrite: false
  limits:
    maxBodyBytes: 1048576
    maxRequestsPerMinute: 120
    maxConcurrentRequests: 64
    maxQueuedRequests: 16
    maxSessions: 32
    sessionIdleTimeoutMs: 300000
```

The token file is generated only when a path is explicitly configured. A
wildcard/non-loopback `host` requires a token file plus non-empty exact Host and
Origin allowlists; startup otherwise exits 2 without opening a listener.
`gno serve` additionally rejects non-loopback hosts because its Web UI and REST
API share the listener; use `gno daemon` for authenticated non-loopback MCP.

**Synopsis:**

```bash
gno serve [--port <num>] [--dev] [gateway-options] [--detach] [--pid-file <path>] [--log-file <path>]
gno serve --status [--json]
gno serve --stop
```

Default `gno serve` (no `--dev`) is the production WebUI bundle, even when
`NODE_ENV` is unset. Production serve loads the committed SPA snapshot
(`assets/spa-production.json.gz`) so first listen does not wait on `Bun.build`.
Refresh the snapshot with `bun scripts/build-spa-production.ts`. `--dev` is the
operator switch for the development bundle and HMR (`serve:dev` / `bun --hot`
remain the hot-reload path). `--dev` does not apply to `--status` or `--stop`
(those paths do not boot the UI). A detached child inherits the same
production default unless the parent was started with `--dev`.

**Options:**

| Option                 | Type    | Default                  | Description                                                      |
| ---------------------- | ------- | ------------------------ | ---------------------------------------------------------------- |
| `-p, --port`           | number  | 3000                     | Port to listen on                                                |
| `--dev`                | boolean | false                    | Serve the development bundle with HMR (default is production)    |
| `--detach`             | boolean | false                    | Self-spawn a detached child; parent prints `{pid,url}` and exits |
| `--pid-file <path>`    | string  | `{data}/serve.pid`       | Override pid-file location (JSON metadata, absolute path)        |
| `--log-file <path>`    | string  | `{data}/serve.log`       | Override log-file location (append mode)                         |
| `--status`             | boolean | false                    | Read pid-file, check liveness, print status (JSON with `--json`) |
| `--stop`               | boolean | false                    | Graceful SIGTERM with 12s timeout → SIGKILL fallback             |
| `--host <address>`     | string  | `127.0.0.1`              | Loopback listen address (Web/REST remains local-only)            |
| `--mcp-token-file`     | string  | config                   | Restrictive bearer-token file                                    |
| `--mcp-allowed-host`   | string  | config/loopback defaults | Exact Host value; repeatable                                     |
| `--mcp-allowed-origin` | string  | config/loopback defaults | Exact Origin; repeatable                                         |
| `--mcp-enable-write`   | boolean | false                    | Separately authorize HTTP MCP mutation tools                     |

`--detach`, `--status`, and `--stop` are mutually exclusive. Passing more than one produces a `VALIDATION` error (exit 1).

Default paths live under `resolveDirs().data` (honours `GNO_DATA_DIR`). Only one
resident owner (`serve` or `daemon`) may use a `GNO_DATA_DIR`; any second start
is blocked.

**Behavior:**

- Opens database once at startup (not per-request)
- Closes the HTTP server, background runtime, and database on SIGINT/SIGTERM
  before the CLI exits; the CLI bootstrap does not race the command's handler
- Sets CSP header: `default-src 'self'; script-src 'self'`
- Health check at `/api/health` returns `{ok:true}`
- Safe lifecycle status at `/api/resident/status` and the `resident` member of
  `/api/status` derive from the same `resident-status@1.0` snapshot
- Mounts stateful Streamable HTTP MCP at `/mcp` only after the fail-closed
  actual-peer, Host, Origin, bearer, body, rate, request, queue, and session
  boundary initializes
- On the loopback `gno serve` listener only, mounts the browser-clipper pairing,
  preview, and capture routes. These require exact extension/same-origin
  policies and dedicated origin-bound capture grants; MCP bearer tokens,
  `GNO_API_TOKEN`, and `gateway.enableWrite` do not authorize them. Clipper
  routes are structurally absent from non-loopback listeners. Capture
  idempotency persists exact content-free plan hashes and reconciles an
  interrupted atomic write only at its original path; changed plans fail
  closed without a duplicate write.
- Serves the standalone `/clipper/pair` approval page. Its exact pair-ID
  fragment is synchronously validated and scrubbed before normal workspace
  state initializes; the user enters the eight-digit code manually. The page
  obtains same-origin CSRF state, never receives the extension grant, and does
  not persist pairing material.
- Browser-clipper HTTP write receipts add `schemaVersion: "1.0"` to the closed
  shared capture receipt. HTTP 409 can therefore be either a valid provenance
  conflict receipt or a closed clipper error; clients parse the body contract
  before classifying the outcome.
- Browser clip response/status closure is exact: HTTP 200 only for
  `opened_existing`; HTTP 202 only for `created`, `created_with_suffix`, or
  `overwritten`; HTTP 409 for either a valid `conflict` receipt or the matching
  closed `clipper-error@1.0`. Unknown versions, fields, codes, non-JSON bodies,
  and status/body mismatches fail as invalid responses. `CLIPPER_OFFLINE`,
  `CLIPPER_INVALID_RESPONSE`, and `CLIPPER_CLIENT` are client-only
  classifications.
- Browser clip provenance contains exactly `extractionHash`, `finalBodyHash`,
  `clipIdentity`, and `previewDigest`; it does not contain `sourceHash`.
- The Chromium service worker stores the loopback origin, plaintext bounded
  grant, and at most one `{payload, previewDigest, idempotencyKey}` in protected
  local extension storage. Unfinished pairing state uses extension session
  storage. Page extraction and the approval page receive neither grant nor
  pending-write state. Recovery reuses the same logical write or fails closed
  without changing destination.
- On `--detach`: forks a detached child with stdio redirected to `--log-file`, writes pid-file JSON (`{pid, port, cmd:"serve", version, started_at}`), prints `{pid, url}` on stdout, exits 0
- On `--status`: output matches the [process-status schema](./output-schemas/process-status.schema.json). Liveness via `process.kill(pid, 0)`; stale pid-files (ESRCH) are reported as `running:false`. Live status best-effort reads the same redacted `resident-status@1.0` snapshot from the recorded listener.
- On `--stop`: sends SIGTERM, polls every 100ms for up to 12s, falls back to SIGKILL, polls 2s more, unlinks pid-file if the process cleaned up after itself
- **Windows**: `--detach` is unsupported and returns a `VALIDATION` error pointing to WSL. `--status` / `--stop` / `--pid-file` / `--log-file` remain parseable but have nothing to manage.

**Exit Codes:**

- 0: Server stopped gracefully, `--detach` succeeded, `--stop` completed, or `--status` found a live process
- 1: Validation error (mutex violation, bad flag combination, Windows `--detach`)
- 2: Server failed to start (DB error, port in use, spawn failure)
- 3: `--status` or `--stop` found no live matching process (`NOT_RUNNING`)

**Examples:**

```bash
gno serve
gno serve --port 8080
gno serve --dev

# Backgrounding
gno serve --detach
gno serve --status
gno serve --status --json
gno serve --stop

# Custom paths
gno serve --detach --pid-file /tmp/gno-serve.pid --log-file /tmp/gno-serve.log

# Mutually exclusive — errors with VALIDATION
gno serve --detach --stop
```

---

### gno daemon

Start a headless long-running watcher process for continuous indexing.

**Synopsis:**

```bash
gno daemon [--port <num>] [--no-sync-on-start] [gateway-options] [--detach] [--pid-file <path>] [--log-file <path>]
gno daemon --status [--json]
gno daemon --stop
```

**Options:**

| Option                 | Type    | Default                  | Description                                                      |
| ---------------------- | ------- | ------------------------ | ---------------------------------------------------------------- |
| `--no-sync-on-start`   | boolean | false                    | Skip initial sync; only watch future file changes                |
| `-p, --port <num>`     | number  | 3000                     | Headless HTTP MCP gateway port                                   |
| `--detach`             | boolean | false                    | Self-spawn a detached child; parent prints `{pid}` and exits     |
| `--pid-file <path>`    | string  | `{data}/daemon.pid`      | Override pid-file location (JSON metadata, absolute path)        |
| `--log-file <path>`    | string  | `{data}/daemon.log`      | Override log-file location (append mode)                         |
| `--status`             | boolean | false                    | Read pid-file, check liveness, print status (JSON with `--json`) |
| `--stop`               | boolean | false                    | Graceful SIGTERM with 12s timeout → SIGKILL fallback             |
| `--host <address>`     | string  | `127.0.0.1`              | HTTP listen address                                              |
| `--mcp-token-file`     | string  | config                   | Restrictive bearer-token file                                    |
| `--mcp-allowed-host`   | string  | config/loopback defaults | Exact Host value; repeatable                                     |
| `--mcp-allowed-origin` | string  | config/loopback defaults | Exact Origin; repeatable                                         |
| `--mcp-enable-write`   | boolean | false                    | Separately authorize HTTP MCP mutation tools                     |

`--detach`, `--status`, and `--stop` are mutually exclusive. Passing more than one produces a `VALIDATION` error (exit 1).

Default paths live under `resolveDirs().data` (honours `GNO_DATA_DIR`). Only one
resident owner (`serve` or `daemon`) may use a `GNO_DATA_DIR`; any second start
is blocked.

**Behavior:**

- Opens DB once at startup
- Loads config and requires at least one configured collection
- Starts the same watcher + embed scheduler used by `gno serve`
- Exact contained eligible paths always use targeted content-hash sync.
  Ambiguous temp/directory/missing-name/recursive-delete events use bounded,
  failure-safe filesystem/index reconciliation with durable retry.
- Bounded overflow or unavailable native anchored handles escalates to full
  collection sync. This does not claim universal network/removable filesystem
  watcher behavior.
- Runs an initial sync by default
- Triggers embedding after initial sync completes
- With `findings.enabled`, runs the scheduled findings pass on its cadence:
  a read-only audit of every collection except the findings one, written as
  deterministic Markdown records into `findings.collection`. The audit runs
  without the write lease; only the record write takes it (no wait; a busy
  lease is recorded as `skipped_lease`). Every attempt persists to
  `{data}/index-<name>.findings-run.json`, surfaced by `--status` (`findings`)
  and the `findings-pass` doctor check. See [Daemon Mode](../docs/DAEMON.md#scheduled-findings-pass)
- Runs in the foreground until `SIGINT` / `SIGTERM`
- Starts a headless `/mcp` Streamable HTTP listener; it does not serve the Web UI
- Exposes the same safe REST lifecycle snapshot at `/api/resident/status`;
  resident-aware app status at `/api/status` is loopback-only because it
  includes local index and configuration details. Non-loopback resident status
  intersects exact Host/Origin and bearer authentication with the current
  peer zone and all participating collection policies before returning metadata
- On `--detach`: forks a detached child with stdio redirected to `--log-file`, writes pid-file JSON including the MCP gateway `port`, prints `{pid}` on stdout, exits 0
- On `--status`: output matches the [process-status schema](./output-schemas/process-status.schema.json), including the MCP gateway port and a best-effort copy of the live redacted resident snapshot
- On `--stop`: SIGTERM → 12s poll → SIGKILL → 2s poll; the daemon's own signal handler unlinks the pid-file, `--stop` unlinks as fallback
- **Windows**: `--detach` is unsupported and returns a `VALIDATION` error pointing to WSL.

**Packaged conformance:** `bun run test:package` installs the generated npm
tarball and exercises the shipped binary. It covers concurrent HTTP MCP clients,
stdio parity, resident reuse, redacted lifecycle schemas, boundary rejection,
bearer rotation and session binding, daemon-only authenticated non-loopback
binding, and detached restart/shutdown. Windows artifact jobs provide the final
platform-specific detach and interrupt-exit sweep.

**Exit Codes:**

- 0: Daemon stopped gracefully, `--detach` succeeded, `--stop` completed, or `--status` found a live process
- 1: Validation error (mutex violation, bad flag combination, Windows `--detach`)
- 2: Startup/runtime failure
- 3: `--status` or `--stop` found no live matching process (`NOT_RUNNING`)

**Examples:**

```bash
gno daemon
gno daemon --no-sync-on-start

# Backgrounding
gno daemon --detach
gno daemon --status
gno daemon --status --json
gno daemon --stop

# Custom paths
gno daemon --detach --log-file /tmp/gno-daemon.log

# Mutually exclusive — errors with VALIDATION
gno daemon --status --stop
```

---

### gno completion

Output or install shell completion scripts.

**Synopsis:**

```bash
gno completion <shell>
gno completion install [--shell <shell>] [--json]
```

**Subcommands:**

| Subcommand | Description                                |
| ---------- | ------------------------------------------ |
| `<shell>`  | Output completion script (bash, zsh, fish) |
| `install`  | Auto-install completion to shell config    |

**Options (install):**

| Flag          | Type   | Description                                     |
| ------------- | ------ | ----------------------------------------------- |
| `-s, --shell` | string | Shell to install for (auto-detected if omitted) |
| `--json`      | flag   | JSON output                                     |

**Supported Shells:**

- `bash` - Appends to ~/.bashrc or ~/.bash_profile (macOS)
- `zsh` - Appends to ~/.zshrc
- `fish` - Creates ~/.config/fish/completions/gno.fish

**Completion Features:**

- Static: Commands, subcommands, flags (always available)
- Dynamic: Collection names for `--collection` flag (when DB available)

**Examples:**

```bash
# Output bash completion script
gno completion bash >> ~/.bashrc

# Auto-install for detected shell
gno completion install

# Install for specific shell
gno completion install --shell zsh
```

**Exit Codes:**

- 0: Success
- 1: Unsupported shell

---

## Error Output

Errors are written to stderr. With `--json` flag, errors are also returned as:

```json
{
  "error": {
    "code": "VALIDATION",
    "message": "Missing required argument: query",
    "details": {}
  }
}
```

Error codes match exit codes: `VALIDATION` (exit 1), `RUNTIME` (exit 2), `NOT_RUNNING` (exit 3), `BUSY` (exit 4).

Write-lease contention on `index` / `update` / `embed` does not use the generic envelope. Text mode writes the dedicated "index is busy" message to stderr; `--json` writes `{ success: false, error, contention }` to stdout. Both exit 4. `gno audit` also uses exit 4 for findings.

**`NOT_RUNNING` is not an error envelope.**

**`NOT_RUNNING` is not an error envelope.** `gno serve|daemon --status --json` returns a `process-status`-shaped payload on stdout with exit 3 when no live matching process is found (it reports observable state, not failure). `--stop` exits 3 silently when there is nothing to stop and does not accept `--json`. The error envelope above is reserved for `VALIDATION` and `RUNTIME` failures where the command could not produce its structured output at all.

---

## Environment Variables

| Variable                   | Description                                                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `GNO_CONFIG_DIR`           | Override config directory                                                                                                  |
| `GNO_DATA_DIR`             | Override data directory (DB location)                                                                                      |
| `GNO_CACHE_DIR`            | Override cache directory (models)                                                                                          |
| `NO_COLOR`                 | Disable colored output (standard)                                                                                          |
| `PAGER`                    | Pager for long output (default: less -R on Unix, built-in on Windows)                                                      |
| `GNO_SKILLS_HOME_OVERRIDE` | Override home dir for skill user scope (testing)                                                                           |
| `GNO_MEMORY_CALLER`        | Default `--caller` identity for `gno remember` / `gno recall`                                                              |
| `GNO_MEMORY_SESSION`       | Default `--session` identity for `gno remember` / `gno recall`                                                             |
| `CLAUDE_SKILLS_DIR`        | Override Claude skills directory                                                                                           |
| `CODEX_SKILLS_DIR`         | Override Codex skills directory                                                                                            |
| `CLAUDE_CONFIG_DIR`        | Claude Code config dir; `gno agents` resolves Claude's instruction file under it (suppressed by an explicit home override) |
| `CODEX_HOME`               | Codex config dir; same rule as `CLAUDE_CONFIG_DIR`                                                                         |

---

## Workspace file refactors (non-CLI)

Reference-safe note rename and same-collection move use a transport-neutral
preview/apply contract defined in `src/core/file-refactors.ts` and frozen as:

- `gno://schemas/file-refactor-preview@1.0`
- `gno://schemas/file-refactor-apply-result@1.0`

Surfaces that adapt the contract are REST/Web UI, SDK, and write-gated MCP
(`gno_rename_note`, `gno_move_note`). This specification intentionally does
**not** add a CLI command family or generic action bus for refactors.
Duplicate-note and create-folder keep their existing shipped semantics and do
not retarget inbound references.

The contract separates durable filesystem commit/rollback from post-commit
index convergence. A successful filesystem refactor is never rolled back solely
because reindexing is temporarily unavailable
(`applied_with_sync_pending`).

## See Also

- [MCP Specification](./mcp.md)
- [Output Schemas](./output-schemas/)
- [PRD](../docs/prd.md)

### Typed metadata predicates

Retrieval commands accept `--filter '<JSON>'`. The same `filter` object is
accepted by SDK, MCP and REST retrieval inputs. Metadata comes from the nested
YAML `gno.metadata` map; ordinary frontmatter keys remain unchanged.

The bounded recursive contract is:

- `{op:"and"|"or", predicates: Predicate[]}` (nonempty).
- `{op:"not", predicate: Predicate}`.
- `{op:"eq"|"ne", key:string, value:string|number|boolean}`.
- `{op:"gt"|"gte"|"lt"|"lte", key:string, value:number}`.
- `{op:"in"|"nin"|"all", key:string, values:Scalar[]}` (nonempty, homogeneous).
- `{op:"exists", key:string, value:boolean}`.

Comparison is type-strict and text is exact/case-sensitive. Positive comparisons,
`ne` and `nin` require a present field. Logical `not` negates the complete child,
so missing fields satisfy `not(eq(...))`. `eq/ne` require scalar fields; `in/nin`
accept scalar or array fields; `all` requires an array. Invalid metadata and
documents awaiting ingestion repair are ineligible for every typed predicate,
including negation. Filtered queries report incomplete coverage until repaired.
Limits: 64 metadata keys, 128-character keys, 4096-character strings, 128 array
members, 64 KiB serialized metadata/filter, 128 predicate nodes, depth 8.
Keys `__proto__`, `prototype`, and `constructor` are reserved.

For executable examples and the missing-field truth table, see
[Typed metadata filters](../docs/TYPED-METADATA.md). `ne` additionally requires
the field's scalar type to match the operand; `nin` matches a present scalar or
array with no exact typed member match. Ordering never coerces strings.
