Metadata-Version: 2.4
Name: ctxme
Version: 0.5.0
Summary: Context Platform CLI
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: click<9.0,>=8.1
Requires-Dist: keyring<25.0,>=24.3
Requires-Dist: httpx<0.29,>=0.28.1
Requires-Dist: packaging<25.0,>=23.0
Requires-Dist: platformdirs<5.0,>=4.2
Requires-Dist: pydantic<3.0,>=2.7
Requires-Dist: pydantic-settings<3.0,>=2.2
Requires-Dist: pyperclip<2.0,>=1.9
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: rich<14.0,>=13.7
Requires-Dist: typer>=0.12
Requires-Dist: typing-extensions<5.0,>=4.9
Provides-Extra: dev
Requires-Dist: pytest<9.0,>=8.3; extra == "dev"
Requires-Dist: pytest-mock<4.0,>=3.12; extra == "dev"

# Context Platform CLI

Typer-powered command-line interface for managing Context Platform projects and items.

> **Note:** Both `ctx` and `ctxme` commands are available. They are identical - use whichever you prefer. Examples below use `ctx`.

## Installation

### Homebrew (recommended)

Requires macOS 14 Sonoma or later. macOS installs a self-contained native CLI;
no separate Python or pip installation is needed. Apple Silicon is the required
release platform. Intel is best-effort and is unavailable for versions without
an Intel archive. Linuxbrew retains a best-effort Python-based installation.

```bash
brew install ctxme/cli/ctxme
```

That one command adds the tap and installs the CLI. Both `ctxme` and `ctx` are
placed on `PATH`; they are the same command.

Upgrade with:

```bash
brew upgrade ctxme
```

**Why the fully-qualified name?** Homebrew requires formulae from non-official
taps to be trusted before it will load them (`HOMEBREW_REQUIRE_TAP_TRUST`,
on by default since Homebrew 6). Naming a formula explicitly counts as granting
that trust, so `brew install ctxme/cli/ctxme` just works — and every later
`brew install ctxme` / `brew upgrade ctxme` works too, because the formula is
trusted from then on.

Tapping first is equivalent, but takes an explicit trust step, because a bare
`brew install ctxme` names no tap for Homebrew to trust:

```bash
brew tap ctxme/cli
brew trust ctxme/cli
brew install ctxme
```

Without the middle command that install fails with
`Refusing to load formula ctxme/cli/ctxme from untrusted tap ctxme/cli`.

If you installed the CLI back when the formula was named `cli`, `brew upgrade`
migrates the old keg automatically — but the same trust rule applies, since the
renamed formula is one you never installed by name. Run `brew trust ctxme/cli`
once if the upgrade reports the tap is untrusted.

### From PyPI

```bash
pip install ctxme        # or: pipx install ctxme
```

### From the repo (development)

From the repo root (uv workspace):

```bash
uv sync --all-extras --all-groups
```

`ctx` will then be available via `uv run ctx ...` (or activate `.venv` and call directly).

## Global Options

- `ctx --version` – show the CLI version and exit
- `ctx --output json <command>` (`-o json`) – emit errors as a machine-parseable JSON envelope instead of Rich-formatted text. This governs **error output only**: successful command output stays in the normal text format. To get JSON for successful output, use the per-command `--json` flag (see [JSON Output](#json-output) below).
- `ctx --install-completion <shell>` / `ctx --show-completion <shell>` – shell completion setup (see [Shell Completion](#shell-completion))

## Configuration

The CLI stores settings under the platform-specific config directory (see `platformdirs`). Override
the path in scripts/tests with the `CTXME_CONFIG_DIR` environment variable.

Key commands:

- `ctx config --default-project <project>` (`-p`) – set the default project key
- `ctx set-project <project>` – set the default project key (same effect as `config --default-project`)
- `ctx current` – show the current default project (local only, no API call)
- `ctx status` – show local config + auth status (no API call)
- `ctx auth login` – device flow sign-in (opens browser, stores API key in keychain). Without `--scopes` the issued key lands at your own permission ceiling: `admin` for an admin-role user, `write` for a standard-role user. Use `--scopes read,write,admin` to request a specific list instead; requests above your role's ceiling are denied at verification. The browser shows the exact permission before you authorize
- `ctx auth status` – show stored API key prefix + backend metadata
- `ctx auth set-key <key>` – manually persist API keys using the OS keychain
- `ctx auth logout` – remove stored API key

To point the CLI at a different backend, use the environment variables `CLI__API_BASE_URL` and
`CLI__USE_MOCK_CLIENT` (see [Environment Variables](#environment-variables)). `ctx config` also
accepts hidden `--api` and `--mode` flags, but these are **dev-only** options intentionally left
out of `--help`; they are not a supported configuration surface.

API keys are **never** written to disk; only stored through `keyring`.

For local development, you can set `CTXME_CLI__API_KEY` to override the keychain without
modifying OS credentials. This value is read from the **process environment only**
(not from `.env` files) and is visible in process listings, so use it for local
testing only. `CLI__API_KEY` remains a deprecated compatibility alias.

Both names are trimmed; whitespace-only values are absent. Equal values use
`CTXME_CLI__API_KEY`; different nonblank values stop the command until the
environment is fixed, including login and set-key. Invalid values never fall
back to Keychain. Using the legacy alias emits one deprecation notice per
process on stderr, including in JSON mode.

## Commands

All commands emit Rich-formatted tables/panels with user-friendly messaging. Most accept
`--project`/`-p` to target a project other than the default, and `--json` for machine-parseable
success output.

### Items

Item commands are available at the top level and, identically, under the `ctx items` group
(`ctx list` ≡ `ctx items list`, etc.).

- `ctx list` – list items for the default (`set-project`) or `--project` project (`--folder`/`-f` to filter by folder, `--recursive`, `--all`, `--limit`, `--sort`, `--order`, `--cursor`/`--no-limit` for pagination)
- `ctx get <item>` – download an item to the current directory (file items use original filename; `--stdout`/`--clipboard` only for text MIME types; `--json --stdout` emits one JSON object with metadata + content)
- `ctx read <item>` – retrieve text content for AI consumption (writes cache metadata by default; `--stdout` prints content only; `--json --stdout` emits one JSON object with metadata + content and skips cache writes; `--max-chars` to limit length)
- `ctx create <item> --data "<text>"` – create or update an inline item (`--title`, `--tag`, `--folder`, `--mime-type` optional; `--force` to overwrite without prompting)
- `ctx create <item> --src <path>` – upload a file as a context item
- `ctx upload <path>` – upload a file as an item (equivalent to `create --src`; `--key`/`-k` to override the derived item key)
- `ctx update <item>` – update item metadata: `--title`, `--mime-type`, `--folder`, and tag operations (`--tag` replaces all tags; `--add-tag`/`--remove-tag`/`--clear-tags` for incremental changes)
- `ctx delete <item>` – delete an item (use `--force` to skip confirmation)
- `ctx search <query>` – search for items (see [Search Modes](#search-modes) below; `--tag` to filter, `--all`/`--limit` for cross-project search)
- `ctx folders` – list folder paths and item counts (`--parent` to list direct children of a folder)
- `ctx mv <item> --folder <path>` – move an item to a folder (empty string `""` moves it to the project root)

Hidden aliases exist for backwards compatibility: `ls` (alias of `list`), `put` (alias of
`create`), `rm` (alias of `delete`), and `up` (alias of `upload`). They work but do not appear
in `--help`; prefer the canonical names.

### Projects

- `ctx projects list` – list available projects (`ctx projects ls` is a hidden alias)
- `ctx projects current` – show the current default project (local only)
- `ctx projects create <name>` – create a new project (`--key`/`-k` for an explicit project key, `--description`/`-d` optional)
- `ctx projects delete <key>` – delete a project (`--yes`/`-y` to skip the confirmation prompt when deleting the default project)

### Skills

#### Produce and publish local bundles

- `ctx skills validate PATH [--json]` validates a local skill directory without reading CLI
  credentials or contacting the backend. It reports every structured finding and the name and
  targets declared by `SKILL.md`; warnings exit 0, while errors exit nonzero. A symlink supplied
  as `PATH`, any linked or special member, and any member changed while it is inspected are
  refused.
- `ctx skills pack PATH [--archive FILE] [--overwrite] [--json]` validates and creates a
  reproducible ZIP. The default output is `<source-parent>/<validated-name>.zip`; the source
  directory's own name has no catalog meaning. The command refuses an output inside the source
  tree and an existing output unless `--overwrite` is explicit. It builds the complete archive
  in memory, then atomically replaces the destination from a temporary sibling. The result
  includes the archive path, SHA-256, byte size, members, and warnings.
- `ctx skills publish PATH [--replace-draft] [--dry-run] [--yes] [--json]` builds the same ZIP in
  memory and plans against the authenticated account catalog. It creates a missing record,
  conditionally uploads only to the observed draft state, and conditionally publishes those
  exact bytes. An identical published version is a no-op; an identical draft is reused. A
  different draft refuses unless `--replace-draft` is present, and `--yes` never implies draft
  replacement.

Publish always prints its ordered plan before mutation. Interactive runs prompt once;
non-interactive and JSON runs require `--yes`. `--dry-run` needs no consent, performs only the
authenticated catalog read, and writes no local ZIP. If the catalog changes after planning, the
command stops without retrying or applying a revised plan; rerun it to inspect and consent again.

Create, upload, and publish are separate server mutations. The command does not claim a
transaction or roll back successful earlier steps: a failed upload can leave an empty catalog
record, and a failed publish can leave the uploaded draft. Text output identifies that remaining
state. JSON output uses JSON Lines: one `phase: "plan"` object, followed by exactly one
`phase: "result"` object after mutation begins, including typed failure details and the status of
all three steps. A dry run emits only the plan.

#### Install and manage catalog skills

- `ctx skills sync NAME[@VERSION]...` – install named skills from the account catalog for Claude
  Code and/or Codex. Versions default to `@latest`; `@<number>` pins one. Options:

  - `--agent claude|codex|both` – required for `user`/`directory` scopes; `both` installs one unit per agent
  - `--scope user|directory` – `user` (default) targets `~/.claude/skills` / `~/.agents/skills`; `directory` targets `<path>/.claude/skills` / `<path>/.agents/skills` and requires `--path DIR`
  - `--dest DIR` – custom destination (`<DIR>/<name>`), mutually exclusive with `--agent`/`--scope`/`--path`; `--variant standard|claude` selects the installed variant (default `standard`, only valid with `--dest`)
  - `--mode symlink|copy` – install mode (default: `copy` on Windows, `symlink` elsewhere); symlink installs point at an immutable managed store under the data directory
  - `--yes` – skip the interactive confirmation; **required** in non-interactive or JSON mode (an install is never implied by output mode, deliberately unlike item delete's JSON-mode skip)
  - `--dry-run` – print the install plan (text or `--json`) and change nothing; needs no `--yes`
  - `--on-conflict overwrite|backup` – the explicit conflict-resolution action for unmanaged or drifted destinations. Without it conflicting installs **refuse**; `overwrite` moves the entry aside (restored automatically if the install fails), `backup` retains it at a timestamped `<name>.bak-<timestamp>` sibling. Neither `--yes` nor the confirmation prompt ever authorizes an overwrite.

  Every install verifies the downloaded archive's SHA-256 and the staged tree's stamped
  per-variant content digest, screens archive members for traversal/symlinks at install
  time, previews publisher/version/files/destination/mode/conflicts before touching
  anything, and installs atomically with automatic restoration on failure. Installed
  state is recorded in `skills-state.json` beside the CLI config. A bare
  `ctx skills sync` (no names) applies this machine's profile instead — see
  "Machine profile" below.

#### Skill collections

`ctx` and `ctxme` are equivalent. These account commands need authentication but
no registered machine. Collection selectors are quoted display names, matched
with Unicode normalization and case folding. UUID-looking selectors are names,
not IDs. Skill names are exact catalog names, without a version suffix or path.

| Command                                                   | Result                                                 |
| --------------------------------------------------------- | ------------------------------------------------------ |
| `ctxme skills collection list`                            | Names, stable IDs and member counts                    |
| `ctxme skills collection show "Development"`              | Metadata and member publication state                  |
| `ctxme skills collection create "Development"`            | New collection                                         |
| `ctxme skills collection rename "Development" "Tools"`    | Same collection and memberships under a new name       |
| `ctxme skills collection delete "Development"`            | Remove the collection and memberships only             |
| `ctxme skills collection add "Development" SKILL_NAME`    | Ensure membership, including on repeated add           |
| `ctxme skills collection remove "Development" SKILL_NAME` | Remove membership only; missing membership is an error |

Every command supports `--json`. Every mutation asks for confirmation interactively,
including create, rename and add. Use `--yes` for noninteractive or JSON mutations.
Deleting a collection or removing a member retains skills, selections and installed files.

`preview` and `apply` use only this computer's registered machine. Register with
`ctx skills machine register [--name NAME]`; use `--adopt` to deliberately bind an
existing machine. Registration is never automatic. A known catalog-origin mismatch
refuses before backend access; an unknown origin warns, including in JSON. If the
machine record was deleted, unregister the local binding and register again.

| Destination option                      | Meaning                                                                 |
| --------------------------------------- | ----------------------------------------------------------------------- |
| `--scope user\|directory\|custom`       | Defaults to `user`                                                      |
| `--install-path .agents\|.claude\|both` | Required for user/directory; forbidden for custom. `both` is one batch. |
| `--key KEY`                             | Required for directory/custom; forbidden for user                       |
| `--variant TARGET`                      | Custom only; omitted means standard                                     |
| `--mode symlink\|copy`                  | Omitted means the machine's platform default                            |

Keys resolve later through `ctx skills mapping set KEY PATH`. These commands take
no local path and do not require existing mappings or inspect installation state.
Unavailable variants produce preview warnings; installation may subsequently refuse.

```sh
ctxme skills collection preview "Development" --install-path both
ctxme skills collection apply "Development" --install-path both
ctxme skills collection apply "Development" --scope directory --install-path .agents --key project --dry-run
ctxme skills collection apply "Development" --scope custom --key tools --variant standard --mode copy --yes
ctxme skills sync --dry-run
ctxme skills sync
```

**Add current skills** creates independent desired selections, like selecting each
member manually. Apply gets its own preview, displays create/exists rows, counts,
warnings and blockers, then submits one atomic batch after consent. Existing
selections retain their pins, modes, variants and observed state; new selections
track latest. The result reports actual created/already-present rows, which can
differ from the preview after concurrent selection changes.

Preview and `apply --dry-run` are read-only, even with `--yes`, and need no write
scope. Empty collections, draft-only members and selection-cap blockers exit
nonzero with the full preview; warnings alone do not fail. Mutations need the
backend's write scope. A stale-membership or other conflict requires a fresh
preview and reapply, without automatic retry. If the apply response is lost,
the server outcome is uncertain; preview/reapply safely to check current state.

Apply installs no files. Run the two sync commands above separately. Later
membership changes require reapplication, and deleting or removing members never
removes earlier selections. **Follow collection**, remembered exclusions and
reconciliation are separate future work owned by [#813](https://github.com/mcgloneb/ai-context/issues/813).
Snapshots are never converted into follows.

#### Machine profile

A machine's **profile** is its selection set saved in the web UI or through
[skill collections](#skill-collections); there is no separate
profile entity. Server-side selections never contain absolute paths, so a `directory` or
custom-destination selection carries a path-independent **destination key** that this
machine maps locally.

- `ctx skills machine register [--name NAME] [--adopt] [--yes]` – bind this computer to a
  machine record. The name defaults to this host's name (trimmed, clipped to 128
  characters) and the platform is detected automatically. If a machine of that name
  already exists in the account, register **refuses** and names `--adopt`; `--adopt` binds
  to that existing record after a confirmation warning that this computer will apply that
  machine's selections and that two computers sharing one record contend for one selection
  set. Registration is stored in `skills-machine.json` beside the CLI config (so each
  `CTXME_CONFIG_DIR` profile has its own independent registration and mappings), together
  with the catalog origin it was made against.
- `ctx skills machine status` – the local identity, mapping count, and — when a credential
  is available and the origin matches — the live machine detail and selection count. A
  missing credential, an unreachable backend, or a deleted machine record degrades to
  local-only output with a warning, never a failure.
- `ctx skills machine unregister [--yes]` – forget the local binding only. Mappings are
  retained, nothing is uninstalled, and the server-side machine record and its selections
  are untouched.
- `ctx skills mapping set KEY PATH` / `list` / `remove KEY` – the machine-local
  `key -> absolute path` table. Keys follow the server's grammar (1-64 characters of
  lowercase letters, digits, `.`, `_`, `-`, starting with a letter or digit, no `..`);
  `PATH` is `~`-expanded, must be absolute, and is stored canonically, so two spellings of
  one directory are recognised as one destination. A path that does not exist yet is stored
  with a warning (existence is enforced per selection at sync time), re-pointing a key
  warns that content installed under the old path is neither moved nor removed, and
  removing a key uninstalls nothing. Mapping commands need neither registration nor
  authentication.
- `ctx skills sync` (no names) – apply the registered machine's profile: `--yes`,
  `--dry-run`, and `--json` only. Every other `sync` flag is a usage error here, because
  each selection carries its own agent, scope, destination key, variant, and mode.
  Preconditions refuse before anything is touched: no registration (register, or name
  skills), an unreadable `skills-machine.json`, a registration made against a different
  catalog origin, a machine record deleted server-side, or interrupted installs needing
  recovery without prior `--yes`. Dry-run never recovers, even with `--yes`. An empty profile reports
  "nothing to install" and exits 0.
  Mutating profile sync requires a write-scoped key even when no additions are pending:
  it commits reconciliation after consent to revalidate the preview before installation.
  A read-only key is refused at that commit; `--dry-run` needs only read scope.

  Application is **per selection, report-and-continue**: a selection that cannot be applied
  is refused with its reason and the exact remediation — the `ctx skills mapping set`
  command for an unmapped key, the exact named `ctx skills sync ... --on-conflict ...`
  re-invocation for a drifted or unmanaged destination — and the remaining selections still
  apply. Two selections whose keys resolve to one destination refuse each other. A rejected
  credential or a failed profile read aborts the whole run before the single confirmation,
  committing nothing. The run exits nonzero whenever any selection refused or failed —
  `--dry-run` included, so a preview never reports a broken profile as healthy — and the
  report (text and `--json`) carries every per-unit outcome.

  A record marked **held** by `ctx skills update NAME@N` or `ctx skills rollback` is
  skipped (not an error, and not a nonzero exit) whenever the selection would change its
  installed content; clear the hold with `ctx skills update NAME`/`@latest`, or pin the
  server-side selection to the held version. A held record whose selection resolves to the
  same content passes through normally. Named `ctx skills sync NAME` stays hold-blind.

  Profile application **never mirrors and never removes**: the machine's selections plus
  eligible explicitly followed collection additions are considered (no catalog listing happens), and managed installs outside the profile,
  installs deselected since the last sync, and unmanaged content are all left untouched.
  Removal remains `ctx skills prune`'s alone. Profile-driven records additionally carry
  `destination_key` and `machine_id`, recording which machine's selection last applied or
  confirmed that destination; a named sync preserves but never sets or clears them.

- `ctx skills list [NAME...]` – inspect the locally managed inventory (never the account
  catalog): per record, skill, installed version, variant, agent, scope, destination, mode,
  publisher, recorded digest (abbreviated in the table, full in `--json`), hold marker,
  history depth, and drift status computed from the observed destination — `ok`, `drifted`
  (digest mismatch, a symlink inside the installed tree, foreign or retargeted entry),
  `missing` (entry absent), or `store-missing` (a symlink entry whose recorded store tree is
  gone). Read-only and offline; `--outdated` additionally resolves each record's latest
  catalog version (origin-gated: records installed from a different API base URL/account are
  labeled `origin-mismatch` and never compared; records with unknown origin are labeled
  `origin-unknown`).

- `ctx skills update NAME[@VERSION]... | --all` – update managed installs to a pinned
  version (`@N`, which sets the hold marker; downgrades included) or latest (`NAME` /
  `@latest`, which clears it). Versions always resolve through the catalog's deterministic
  version-resolution API; each record keeps its recorded agent/scope/destination/mode/variant
  (changing those remains `sync`'s job). Same-content resolutions commit metadata only;
  content changes run the full download/verify/preview/atomic-replace chain. Named
  invocations are all-or-refuse; `--all` is per-record report-and-continue and skips held,
  drifted, missing, origin-mismatched, and unresolvable records with reasons. Drifted or
  missing destinations refuse and print the exact `ctx skills sync` re-invocation
  (conflict resolution stays `sync --on-conflict`'s single surface). An install whose stored
  origin differs from the current session refuses (`sync` is the documented cross-origin
  action); unknown origins proceed with a warning.

- `ctx skills rollback NAME[@VERSION]` – user-requested rollback to an earlier version,
  executed through the same atomic-replacement transaction and setting the hold marker.
  Bare `NAME` returns to the newest superseded version in the destination's history; `@N`
  targets any version, from history or the catalog. If a retained store generation for the
  target still digest-matches its recorded stamped digest it is used directly — offline, no
  backend call; otherwise the bundle is re-downloaded with the full verification chain
  (origin-gated like update), and a catalog version whose stamped digest differs from the
  history entry's recorded digest refuses.

- `ctx skills prune NAME... | --all` – **the only removal surface**: removes managed
  installs exclusively (destination entry, managed-state record, and retained store
  generations), local only, never contacting the server. Entries without a managed record
  are invisible to prune under every flag combination. Removal is journaled and atomic
  (a failure before the commit restores the entry exactly). Drifted installs refuse by
  default — prune never deletes drifted content in place; `--backup` preserves the observed
  entry at a timestamped `.bak-*` sibling (verbatim move for real entries and retargeted
  links, a materialized physical copy for a store-pointing managed symlink) before removing
  the record. Stale records whose entry is already gone are cleaned and reported.
  Interactive confirmation (or `--yes`) is required; JSON mode never implies consent.

- `ctx skills gc [--keep N]` – free managed-store disk space only; **prune removes
  installed skills from their destinations, gc only frees internal store space** and never
  touches destinations or `.bak-*` siblings. Superseded versions are retained per
  destination up to a fixed depth of **2** (a module constant, not configuration), enforced
  automatically after every content-changing replacement; `gc` re-runs that enforcement
  (`--keep N` overrides the depth for the invocation, `--keep 0` drops all superseded
  versions, leaving rollback to catalog re-download) and then collects **owned orphans** —
  store generations whose owner sidecar marker matches this profile's owner id
  (`skills-store-owner.json` beside the CLI config) but which no record or retained history
  entry references. Generations with no owner marker (installed before markers existed, or
  whose owner id was lost) and generations carrying another profile's marker are **reported
  only, never collected** by any invocation or flag — an unmarked generation could be
  another profile's live install sharing the same data directory. If you know the store
  serves a single profile, remove listed pre-marker leftovers manually.

### Auth

- `ctx auth login` – device-flow sign-in; the key is issued at your own permission ceiling unless you pass `--scopes` to request a specific list (`--no-open-browser` prints the verification URL instead of opening it)
- `ctx auth logout` – remove the stored API key from the keychain
- `ctx auth status` – show current authentication status
- `ctx auth set-key <key>` – store an API key securely in the OS keychain

### Usage

- `ctx usage llm` – list recent LLM invocations for your account (**requires an admin-scoped key**, which an admin-role user gets from a plain `ctx auth login`). Filters: `--project`, `--since`/`--until` (ISO-8601), `--status`, `--workload`, `--model`, `--limit`, `--cursor` (pagination, requires `--json`)

```bash
# Recent failed LLM calls for one project, as JSON
ctx usage llm --project my-project --status error --json
```

### Wiki

The `ctx wiki` group manages and queries the generated project wiki:

- `ctx wiki status` – show wiki generation state, staleness, and coverage
- `ctx wiki generate` – trigger wiki generation or refresh
- `ctx wiki list` – list wiki pages (`--page-type`, `--include-stale`, `--limit`, `--cursor`)
- `ctx wiki get <slug>` – show a wiki page's content and metadata
- `ctx wiki search <query>` – search wiki pages (`--page-type`, `--include-stale`, `--limit`)
- `ctx wiki query "<question>"` – ask the wiki for a synthesized, cited answer (`--file-back` files novel answers back as new wiki pages, visible to the whole account; `--limit` caps retrieved pages)
- `ctx wiki settings` – show or change wiki settings (`--llm-opt-in`/`--no-llm-opt-in` toggles external LLM processing for the project's wiki)
- `ctx wiki runs` – show wiki maintenance run history (`--status`, `--trigger`, `--limit`, `--cursor`)
- `ctx wiki schema get` – print the wiki schema page body (pipe to a file to edit)
- `ctx wiki schema set [file]` – replace the wiki schema from a file or stdin (ETag-aware conditional write)

```bash
# Generate the wiki, watch progress, then ask it a question
ctx wiki generate
ctx wiki status
ctx wiki query "How does authentication work?"

# Edit the wiki schema
ctx wiki schema get > schema.md
ctx wiki schema set schema.md
```

### Maintenance

- `ctx update-check` – check for newer versions of the CLI (also runs automatically at startup; see `CTXME_UPDATE_CHECK` below)

## JSON Output

There are two distinct JSON mechanisms — they are independent and do not imply each other:

- **Per-command `--json`** – switches a command's **successful** output to machine-parseable JSON
  (e.g. `ctx list --json`, `ctx read <item> --json`). Supported by most commands; see each
  command's `--help`.
- **Global `--output json`** (`-o json`) – switches **error** output to a machine-parseable JSON
  envelope. It does **not** change successful command output; `ctx --output json status` still
  prints the normal text output. Combine both when scripting: `ctx -o json get <item> --json --stdout`.

### Combined JSON + Stdout for `get` and `read`

`ctx get <item> --json --stdout` returns exactly one JSON document to stdout. It includes
item metadata plus a `content` field in the same payload.

Example shape for inline items:

```json
{
  "project_key": "core",
  "item_key": "welcome",
  "title": "welcome",
  "mime_type": "text/plain",
  "tags": [],
  "folder": null,
  "content_type": "inline",
  "content": "..."
}
```

For file items, payload also includes `file_id`, `size`, and `original_filename`.
`content` is included only for text-compatible MIME types (`text/*`, JSON/XML/YAML/JS).
Binary files still follow existing stdout guardrails (for example PDFs without `--text`):
the command fails instead of emitting partial success JSON. In global JSON mode
(`--output json`), this uses the standard JSON error envelope.

`--json --clipboard` remains invalid for `get` and `read`, and `ctx get --json --dest` remains
invalid.

`ctx read <item> --json --stdout` returns a combined, non-cache schema:

```json
{
  "content": "...",
  "chars": 123,
  "truncated": false,
  "project_key": "core",
  "item_key": "welcome",
  "title": "welcome",
  "mime_type": "text/plain",
  "tags": [],
  "folder": null
}
```

`--max-chars` truncation still applies in this mode. Combined-mode `read` intentionally omits
`path`; standalone `ctx read --json` continues to return the cache `path` schema.

## Search Modes

The `search` command intelligently determines which projects to search based on your configuration:

1. **Explicit project search** (using `--project` flag):

   ```bash
   ctx search "machine learning" --project my-research
   ```

   Searches only the specified project.

2. **Default project search** (when default project is set):

   ```bash
   ctx set-project my-main-project
   ctx search "API documentation"
   ```

   Searches only your default project.

3. **Implicit all-projects search** (no default project configured):

   ```bash
   # No default project set
   ctx search "kubernetes config"
   # Output: Searching across all projects (no default project set)
   ```

   Automatically searches all projects with an informational message.

4. **Explicit all-projects search** (using `--all` flag):
   ```bash
   ctx search "error handling" --all --limit 20
   ```
   Searches across all projects regardless of default project configuration.

**Note:** The `--project` and `--all` flags are mutually exclusive. Use one or the other, not both.

If you have no projects yet, the search command will prompt you to create one:

```bash
ctx search "test"
# Output: No projects found. Create one with: ctx projects create <name>
```

## Unified Context Items

The CLI uses a unified API that handles both inline text and file uploads:

- **Inline content**: Use `--data` to provide text content directly
- **File uploads**: Use `--src` or `ctx upload` to upload supported files
  (`.txt`, `.md`, `.pdf`, `.docx`, `.csv`, `.tsv`, `.html`, `.json`)

When listing items, you'll see:

- `content_type`: Whether the item is `inline` text or a `file`
- `processing_status`: `ready` (searchable), `processing` (being indexed), or `failed`

Example:

```bash
# Create inline item
ctx create my-notes --data "Meeting notes from today"

# Upload a file
ctx create quarterly-report --src ./report.docx

# List items with processing status
ctx list
```

### Polling for Processing Status

When uploading files, extraction and indexing happen asynchronously. You can poll for completion:

```bash
# Upload a file (returns processing status)
ctx create my-document --src document.pdf

# Use ctx list to check processing status
ctx list
```

Alternatively, rerun `ctx list` until the status shows `ready` or `failed`.

## Environment Variables

| Variable                      | Description                                                                            |
| ----------------------------- | -------------------------------------------------------------------------------------- |
| `CTXME_DEBUG`                 | Set to `1` to enable debug output (shows API URL, mode, and full tracebacks on errors) |
| `CTXME_CONFIG_DIR`            | Override the default config directory path                                             |
| `CTXME_CACHE_DIR`             | Override the default cache directory path                                              |
| `CTXME_DATA_DIR`              | Override the default data directory path (holds the managed skills store)              |
| `CTXME_UPDATE_CHECK`          | Set to `0`, `false`, or `no` to disable automatic update checks at startup             |
| `CTXME_UPDATE_CHECK_INTERVAL` | Override the update check interval in seconds (default: 86400 / 24 hours)              |
| `CLI__API_BASE_URL`           | Override the backend API URL (takes precedence over config file)                       |
| `CTXME_CLI__API_KEY`          | Override the API key (process env only; takes precedence over keychain)                |
| `CLI__API_KEY`                | Deprecated alias for `CTXME_CLI__API_KEY`; emits a notice on stderr                    |
| `CLI__DEFAULT_PROJECT`        | Override the default project (takes precedence over config file)                       |
| `CLI__USE_MOCK_CLIENT`        | Set to `true` to use the mock client                                                   |

**Example: Use `CTXME_CLI__API_KEY` for local testing**

```bash
# Local testing without touching the keychain
export CTXME_CLI__API_KEY="ctxme_a74558fa64f4a99a5666a47b4954ea99"
export CLI__API_BASE_URL="http://localhost:8000"
uv run ctx list

# Return to keychain-based auth
unset CTXME_CLI__API_KEY
```

**Example: View full configuration details**

```bash
CTXME_DEBUG=1 ctx status
# Output:
# API: https://api.example.com
# Mode: live
# Auth: Signed in
# Project: my-project (default)
```

When `ctx get` saves to a directory, it sanitizes item keys into safe filenames (no path
separators or `..`).

## Tests

```bash
uv run pytest cli/tests
```

Tests cover configuration round-trips, keyring helpers (via an in-memory backend), and representative
Typer command flows.

## Shell Completion

Enable tab completion for commands, subcommands, and flags. Works for both `ctx` and `ctxme`.

### Installation

**Bash:**

```bash
ctx --install-completion bash
# or: ctxme --install-completion bash
```

**Zsh:**

```bash
ctx --install-completion zsh
```

**Fish:**

```bash
ctx --install-completion fish
```

**PowerShell:**

```bash
ctx --install-completion powershell
```

### Usage

```bash
ctx <TAB>        # Shows: auth, config, get, list, create, ...
ctx auth <TAB>   # Shows: login, logout, status, set-key
ctx get --<TAB>  # Shows: --project, --dest, --stdout, --force, ...
```

### Troubleshooting

- Restart shell after installation
- Ensure completion script is sourced in shell config
- Check `ctx --show-completion <shell>` for manual setup

### Follow collections on this machine

`Add current skills` and `collection apply` remain independent one-time snapshots.
`Follow collection` opts into current and future published members at the chosen destinations
on the next explicit `ctxme skills sync`. Following changes configuration and installs no files.

```sh
ctxme skills collection follow "Development" --install-path both --dry-run
ctxme skills collection follow "Development" --install-path both --yes
ctxme skills collection followed --json
ctxme skills collection update-follow ASSIGNMENT_ID --mode copy --yes
ctxme skills collection unfollow ASSIGNMENT_ID --yes
ctxme skills collection exclusions --json
ctxme skills collection restore EXCLUSION_ID --yes
```

Follow uses the snapshot destination options, including directory/custom keys and custom
variants. Assignment listing shows stable IDs and destinations. Defaults updates affect future
additions only; `--reset-mode` and `--reset-variant` restore their defaults. Repeat an identical
follow safely. To change its defaults, use `update-follow` explicitly. All mutations require
confirmation or `--yes`; JSON and noninteractive mutations require `--yes`. Dry-run never mutates.

Deleting a covered selection in the web/API remembers an exclusion for that exact destination,
even if a snapshot or manual add created it. Exclusions override overlapping follows and survive
unfollow/refollow and membership removal/readdition. Snapshot and follow previews show and skip
them. Explicit restore or an explicit per-skill add clears one; local prune, missing files and
holds do not create exclusions. Unfollow, member removal and collection deletion retain existing
selections, pins, settings and installed files. Rename preserves follows.

Sync shows one plan for existing and pending selections before consent. Pending desired rows
still require consent if their local destinations are refused. Commit returns selection IDs;
installation and reporting use those IDs. A local refusal leaves the desired selection for retry.
A stale plan or failed reconciliation stops before installation. An exceeded selection cap creates
no rows, installs previewed existing eligible selections, and exits nonzero. A crash after commit
leaves desired selections for the next sync; report failure warns without rolling back installs.

Sync precondition: interrupted installs need prior consent for recovery. Without `--yes`, sync
stops before recovery with guidance to run `ctx skills sync --yes`. `--dry-run` never recovers,
including `--dry-run --yes`. Recovery itself never reconciles collections.

These commands require a backend with #813 endpoints. Release the backend and its additive
migration before distributing this CLI. #819 owns collection CRUD, membership and snapshots;
#813 owns following, exclusions and sync reconciliation.
