xmemcli 0.0.4__tar.gz → 0.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
xmemcli-0.0.8/PKG-INFO ADDED
@@ -0,0 +1,594 @@
1
+ Metadata-Version: 2.4
2
+ Name: xmemcli
3
+ Version: 0.0.8
4
+ Summary: Command-line client for the xmemory HTTP API: PKCE auth, instances, read/write, schema and XMD tools.
5
+ Keywords: api,cli,llm,memory,xmemory
6
+ Requires-Python: <4,>=3.12
7
+ Description-Content-Type: text/markdown
8
+
9
+ # `xmemcli` — xmemory command-line client
10
+
11
+ Single-command CLI for reading from, writing to, and managing xmemory instances over the HTTP API at `$XMEM_API_URL` (default `https://api.xmemory.ai`), using an API key from the Console UI or `xmemcli auth login`.
12
+
13
+ Full command reference is included below.
14
+
15
+ ## Usage
16
+
17
+ ```text
18
+ xmemcli auth login [--no-browser] [--timeout S] [--console-url URL]
19
+ xmemcli auth logout
20
+ xmemcli auth status
21
+ xmemcli read <query>
22
+ xmemcli write <text>
23
+ xmemcli write-status <write_id> [<id> …]
24
+ xmemcli quota
25
+ xmemcli binding list [--autoload]
26
+ xmemcli binding add <instance_id> [--tier autoload|available|off] [--scope project|user]
27
+ xmemcli binding remove <instance_id> [--scope project|user]
28
+ xmemcli context [--text] [--max-tokens N] [--no-live-state]
29
+ ```
30
+
31
+ Global flags / env: `--api-key` / `$XMEM_API_KEY`, `--instance-id` / `$XMEM_INSTANCE_ID`, `--api-url` / `$XMEM_API_URL`, `--rc-dir` / `$XMEM_RC_DIR`, `--binding-dir` / `$XMEM_BINDING_DIR`, `--json`, `--verbose`. See `xmemcli help options`.
32
+
33
+ Credential resolution: `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` (walk up from `--rc-dir` toward `$HOME`). Origin: `--api-url` → `$XMEM_API_URL` → rc `api_url` → default.
34
+
35
+ `context` is the exception: it runs unattended from a session-start hook, with no person and no chosen directory behind it, so it resolves the credential from `$HOME` **only** — a project `.xmemrc.json` is used only when `--api-key` / `$XMEM_API_KEY` or `--rc-dir` / `$XMEM_RC_DIR` names it explicitly. That stops a cloned repository shipping the key and origin its own context is fetched with.
36
+
37
+ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the credential (`0600`, never committed, nearest wins); `.xmemory.json` holds the instance binding (`0644`, *meant* to be committed at project scope, every scope merged). `binding` commands are local-only — no network, no credential.
38
+
39
+ ## Read / write modes
40
+
41
+ | CLI | API field |
42
+ |-----|-----------|
43
+ | `xmemcli write --extraction fast` (default) | `extraction_logic: "fast"` |
44
+ | `xmemcli write --extraction deep` | `extraction_logic: "deep"` |
45
+ | `xmemcli write --no-wait` | async write; poll with `write-status` |
46
+ | `xmemcli read --read-mode single` (default) | `mode: "single-answer"` |
47
+ | `xmemcli read --read-mode raw` | `mode: "raw-tables"` |
48
+ | `xmemcli read --read-mode xresponse` | `mode: "xresponse"` |
49
+
50
+ `--read-mode` selects the **server-side answer shape**; `--json` and the TTY rule govern stdout format.
51
+
52
+ ## Curated output
53
+
54
+ The CLI does not echo raw API payloads. Server internals (`sql`, `diff_plan`, extraction trees) are never emitted. Read responses keep `pending_suggestions` (schema-evolution hint). `--verbose` adds `trace_id` and `console_url` when present.
55
+
56
+ ## Output personality — JSON vs pretty
57
+
58
+ - **Non-TTY stdout** or **`--json`**: exactly one JSON document.
59
+ - **Interactive TTY** without `--json`: short one-line summaries per command.
60
+
61
+ Scripts can rely on `xmemcli … | jq` without `--json`.
62
+
63
+ ## Example session
64
+
65
+ ```bash
66
+ export XMEM_API_KEY="xmem_..."
67
+ export XMEM_INSTANCE_ID="<uuid>"
68
+
69
+ xmemcli write "John likes orange"
70
+ xmemcli read "what colour does John like?"
71
+
72
+ ID1=$(xmemcli write --no-wait "Ivy likes amber" | jq -r .write_id)
73
+ ID2=$(xmemcli write --no-wait "Lev likes indigo" | jq -r .write_id)
74
+ xmemcli write-status "$ID1" "$ID2"
75
+ ```
76
+
77
+ ## Install
78
+
79
+ Requires **Python 3.12+**. Stdlib only — no runtime dependencies.
80
+
81
+ Preferred:
82
+
83
+ ```bash
84
+ uv tool install xmemcli
85
+ ```
86
+
87
+ When uv is not available:
88
+
89
+ ```bash
90
+ pip install xmemcli
91
+ ```
92
+
93
+ Then:
94
+
95
+ ```bash
96
+ xmemcli version
97
+ xmemcli auth login
98
+ ```
99
+
100
+ Version: **0.0.8**.
101
+
102
+
103
+ ---
104
+
105
+ # `xmemcli` auth flow — PKCE handoff and credentials
106
+
107
+ The CLI is **human-governed for key acquisition**: an AI-assisted IDE may run reads, writes, and inventory, but **acquiring an API key** stays under human control in the browser. A key must **never enter the AI transcript** — it is handed to the CLI out-of-band via `xmemcli auth login` or environment / `.xmemrc.json`.
108
+
109
+ **Model:** the CLI sends the human to **`/xmemcli/<challenge>`** on the Console, listens on **`127.0.0.1:9636/callback`**, and the Console redirects a signed short-lived **code** (not the key) to that callback.
110
+
111
+ > **Scope.** Shipped in this package: `.xmemrc.json` discovery, PKCE `auth login` / `logout` / `status`, `/whoami` consumption, and `quota`. Server endpoints: `POST /cli/handoff`, `POST /cli/token`.
112
+
113
+ ## Acquiring a key
114
+
115
+ The human either:
116
+
117
+ - **Provides a key they already hold** (`$XMEM_API_KEY`, `.xmemrc.json`, or paste at login), or
118
+ - **Creates a key** in the Console and clicks **"Use this key in the CLI"** (when the page was opened via `/xmemcli/<challenge>`).
119
+
120
+ During **onboarding**, the Console may auto-handoff an onboarding key while a short-lived cookie bridge is active.
121
+
122
+ ### Why a code, not the key
123
+
124
+ Plaintext keys in URLs leak to history and proxies. Handoff uses **PKCE + signed code + localhost callback**:
125
+
126
+ 1. CLI mints **`verifier`**, derives **`challenge`** = `base64url(SHA256(verifier))`, opens **`/xmemcli/<challenge>`**.
127
+ 2. Human clicks **"Use this key in the CLI"** → Console **`POST`s the key** to `/cli/handoff` bound to the challenge → receives a short-lived **`code`**.
128
+ 3. Browser navigates to **`http://127.0.0.1:9636/callback?code=…&state=<challenge>`** — key never in the URL.
129
+ 4. CLI **`POST /cli/token`** with `{ code, verifier }` → receives key → writes `0600` `.xmemrc.json`.
130
+
131
+ ### Sequence
132
+
133
+ ```
134
+ AI-assisted IDE ── xmemcli auth login ──► CLI: PKCE verifier + localhost listener
135
+ Human ── opens Console URL ──► creates key → "Use this key in the CLI"
136
+ CLI ◄── localhost redirect ── redeems code + verifier → writes .xmemrc.json
137
+ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never handles plaintext key
138
+ ```
139
+
140
+ ## Credential store (`.xmemrc.json`)
141
+
142
+ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking up from `--rc-dir` / CWD, ceiling `$HOME`.
143
+
144
+ Resolution order: `--api-key` → `$XMEM_API_KEY` → `.xmemrc.json`. Origin: `--api-url` → `$XMEM_API_URL` → rc `api_url` → default — **except** when the key is explicit (`--api-key` / `$XMEM_API_KEY`): the rc's `api_url` is skipped and the origin falls back to the built-in default unless you also set `--api-url` / `$XMEM_API_URL`. The localhost-key-at-prod guard applies when the key comes from the rc and an explicit `--api-url` / `$XMEM_API_URL` mismatches that rc origin.
145
+
146
+ ## Identity — `/whoami`
147
+
148
+ `xmemcli auth status` calls **`GET /whoami`** with the active bearer key and reports `{authenticated, email, key_prefix}`. Exit `0` when the key resolves; auth error when it does not.
149
+
150
+ ## Security model
151
+
152
+ | Threat | Mitigation |
153
+ | --- | --- |
154
+ | Key in AI transcript | localhost callback → `0600` store; login stdout shows `key_prefix` only |
155
+ | Key in URL | URL carries code only; key over TLS on handoff/token |
156
+ | Captured code replay | PKCE verifier never leaves CLI; code is TTL-bounded |
157
+ | CSRF on callback | state = PKCE challenge; redeem requires verifier |
158
+ | Wrong Console page | CLI button only on `/xmemcli/<challenge>` |
159
+
160
+ Fixed port **9636** ("xmem" on phone keypad) allows SSH/devcontainer forwarding; security rests on PKCE, not port secrecy.
161
+
162
+ ## Quota (observe vs escalate)
163
+
164
+ **`xmemcli quota`** observes usage freely. **Raising** limits requires human approval in the Console — the CLI must not self-grant quota increases.
165
+
166
+ ---
167
+
168
+ # `xmemcli` — capabilities reference
169
+
170
+ **Version:** `0.0.8` (see `xmemcli version`)
171
+
172
+ `xmemcli` is the command-line client for the **xmemory HTTP API**. It is **human-governed for key acquisition**; an AI-assisted IDE may run reads, writes, and inventory once credentials exist. Humans acquire API keys and change quota limits in the **Console UI**.
173
+
174
+ ```bash
175
+ xmemcli … # installed CLI
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Commands
181
+
182
+ Every command below has dedicated help — see [Help](#help).
183
+
184
+ ### Data
185
+
186
+ | Command | Description |
187
+ |---------|-------------|
188
+ | **`write`** `<text>` | Sync write (`POST …/write`). `--extraction fast\|deep` (default `fast`). `--no-wait` queues async write, returns `write_id`. |
189
+ | **`write-status`** `<write_id>` … | Poll async writes until terminal or `--timeout` (default 60s). Accepts multiple ids. |
190
+ | **`read`** `<query>` | Read from active instance. `--read-mode single\|raw\|xresponse`, `--scope TYPE:ID` (repeatable), `--scope-relations`. |
191
+
192
+ Requires **`--instance-id`** or **`$XMEM_INSTANCE_ID`** for read/write (or auto-pick when exactly one instance is visible — see [Global flags](#global-flags--environment)).
193
+
194
+ ---
195
+
196
+ ### Org inventory
197
+
198
+ Permission-filtered view of what this API key can see.
199
+
200
+ | Command | Description |
201
+ |---------|-------------|
202
+ | **`org list clusters`** | Clusters visible to this credential |
203
+ | **`org list instances`** | Instances visible to this credential |
204
+ | **`org list keys`** | API keys visible to this credential (metadata only; plaintext never returned) |
205
+
206
+ ---
207
+
208
+ ### Instances
209
+
210
+ | Command | Description |
211
+ |---------|-------------|
212
+ | **`instance get`** `<instance_id>` | Fetch one instance |
213
+ | **`instance create`** | Create from XMD/JSON schema. Required `--name`; schema via `--schema-file` or `--schema-stdin`. |
214
+ | **`instance instructions`** `[instance_id]` `[text]` `[--clear]` | Read, set or clear the instance's standing instructions to an agent — the owner's own words, carried to every agent that connects it. No text and no `--clear` reads and changes nothing. Setting reads the current value first and writes with the epoch it read, so an edit composed against instructions someone has since replaced is refused rather than applied (the console form and the instance chat write the same field). Clearing is `--clear`; an empty string is refused, because that is what a shell substitutes for an unset variable. The id may be pinned with `--instance-id` or `$XMEM_INSTANCE_ID` and the text given alone — a lone positional that is not an id is read as the text. |
215
+ | **`instance setup`** `[instance_id]` `[--format agent\|project]` | How to connect this instance to an agent, ordered for where it is likely to be used. Prints the same steps the `get_setup_instructions` MCP tool gives an agent, so a person and the agent beside them read the same thing. `--format project` additionally prints the shared setup files to commit; each teammate still approves the install and signs in once. Carries no credential. |
216
+
217
+ ---
218
+
219
+ ### Schema & XMD
220
+
221
+ XMD (xmemory Data) is the typed schema format for instances. These commands mirror the Console schema workflow (read/update/dry-run/migrations, LLM generate/enhance, and validation).
222
+
223
+ #### Command map
224
+
225
+ | User task | CLI command | Notes |
226
+ |----------|-------------|-------|
227
+ | Read live schema | **`schema get`** `[instance_id]` | Live schema for one instance. `-o` writes YAML. |
228
+ | Apply migration | **`schema update`** `[instance_id]` | `--schema-file`, optional `--migration-plan`, `--confirm-destructive`. |
229
+ | Preview migration | **`schema dry-run`** `[instance_id]` | Same flags as `schema update`; returns planned DDL without applying. |
230
+ | List migrations | **`schema migrations list`** `[instance_id]` | Newest first. `--limit`, `--before-id`, `--include-yaml`. |
231
+ | Fetch one migration | **`schema migrations get`** `[instance_id]` `<migration_id>` | Optional positional instance id; else `--instance-id` / `$XMEM_INSTANCE_ID`. `--include-yaml`. |
232
+ | Review suggestions | **`schema suggestions review`** `[instance_id]` | Suggestion engine: consolidated proposal from read-traffic gaps. |
233
+ | Decide suggestions | **`schema suggestions decide`** `[instance_id]` | `--proposal-version` (required) + `--accept`/`--reject`/`--defer`/`--file`. |
234
+ | Apply suggestions | **`schema suggestions apply`** `[instance_id]` | `--proposal-version` plus mandatory `--confirm-destructive`; preview removals in Console first. |
235
+ | Enhance bound schema | **`xmd enhance --from-instance`** `<description>` | Uses live schema; requires instance id. |
236
+ | Synthesize schema | **`xmd generate`** `<description>` | Cluster-scoped LLM synthesis. `-o` writes YAML. |
237
+ | Enhance from file | **`xmd enhance`** `[schema_file]` `<description>` | From a file, or `--from-instance` for bound enhance. `-o`, `--plan-output`. |
238
+ | Create instance | **`instance create`** | `--name`, `--schema-file` or `--schema-stdin`. |
239
+ | Validate schema | **`xmd validate`** / **`validate-yaml`** / **`validate-json`** | Cluster-scoped server validation. |
240
+ | Convert JSON Schema | **`xmd convert`** | JSON Schema → XMD YAML via the API (`validate_json_schema`). |
241
+
242
+ **Not exposed in CLI** (Console-only today): the suggestions before/after YAML **preview**.
243
+
244
+ #### Typical workflow
245
+
246
+ ```text
247
+ # 1. Synthesize or enhance
248
+ xmemcli xmd generate "people and their pets" -o schema.yml
249
+ xmemcli xmd enhance schema.yml "add a Pet.breed field" -o next.yml --plan-output plan.json
250
+
251
+ # 2. Validate before create or update
252
+ xmemcli xmd validate next.yml
253
+
254
+ # 3. Create instance OR migrate existing
255
+ xmemcli instance create --name demo --schema-file schema.yml
256
+ xmemcli schema dry-run INST_ID --schema-file next.yml --migration-plan plan.json
257
+ xmemcli schema update INST_ID --schema-file next.yml --migration-plan plan.json --confirm-destructive
258
+
259
+ # 4. Inspect history
260
+ xmemcli schema migrations list INST_ID
261
+ xmemcli schema migrations get INST_ID MIGRATION_UUID
262
+
263
+ # 5. Suggestion engine (from real read traffic) — confirm with the user before decide/apply
264
+ xmemcli schema suggestions review INST_ID
265
+ xmemcli schema suggestions decide INST_ID --proposal-version PV --accept FP1 --accept FP2
266
+ xmemcli schema suggestions apply INST_ID --proposal-version NEXT_PV --confirm-destructive
267
+ ```
268
+
269
+ #### Commands
270
+
271
+ | Command | Description |
272
+ |---------|-------------|
273
+ | **`schema get`** `[instance_id]` | Live XMD schema. Optional `-o` file. |
274
+ | **`schema update`** `[instance_id]` | Replace schema from `--schema-file`. Optional `--schema-type`, `--migration-plan`, `--confirm-destructive`. |
275
+ | **`schema dry-run`** `[instance_id]` | Preview migration (same schema/plan flags as update). |
276
+ | **`schema migrations list`** `[instance_id]` | Applied migrations, newest first. `--limit` (default 50), `--before-id`, `--include-yaml`. |
277
+ | **`schema migrations get`** `[instance_id]` `<migration_id>` | One record. Optional positional instance id; else global instance flags. `--include-yaml`. |
278
+ | **`schema suggestions review`** `[instance_id]` | Consolidated proposal from read-traffic gaps. Returns `proposal_version` + items. |
279
+ | **`schema suggestions decide`** `[instance_id]` | Record accept/reject/defer. Requires `--proposal-version` plus fingerprints or `--file`. |
280
+ | **`schema suggestions apply`** `[instance_id]` | Apply accepted items as one migration. Requires `--proposal-version` and `--confirm-destructive`. |
281
+ | **`xmd generate`** `<description>` | LLM-synthesize XMD. Optional `-o`. Requires cluster resolution. |
282
+ | **`xmd enhance`** `[schema_file]` `<description>` | LLM-augment schema from file, or **`--from-instance`** for live schema. `-o`, `--plan-output`. |
283
+ | **`xmd validate`** `<schema_file>` | Validate; YAML vs JSON inferred from `.json` extension. |
284
+ | **`xmd validate-yaml`** `<schema_file>` | Force YAML validation endpoint. |
285
+ | **`xmd validate-json`** `<schema_file>` | Force JSON Schema validation endpoint. |
286
+ | **`xmd convert`** `<json_schema_file>` | JSON Schema → XMD YAML via the API (`validate_json_schema`). Optional `-o`, `--root-object`. |
287
+
288
+ Instance id for `schema *` commands: positional `[instance_id]`, else **`--instance-id`** / **`$XMEM_INSTANCE_ID`**.
289
+
290
+ ---
291
+
292
+ ### Binding (local only — no network, no credential)
293
+
294
+ Which instances an agent working in **this directory** should know about, and how eagerly to
295
+ engage each one. Stored in `.xmemory.json`, discovered by walking **up** from `--binding-dir` /
296
+ `$XMEM_BINDING_DIR` (or the cwd), ceilinged at `$HOME` — the same rule `.xmemrc.json` has always
297
+ used, and **no git anywhere**. From a start outside `$HOME` the walk checks that directory and
298
+ stops, rather than climbing into a shared parent like `/work` or `/tmp` where another user's file
299
+ could be planted. A session-start hook passes the project root explicitly, so it never depends on
300
+ the walk at all.
301
+ `~/.xmemory.json` is consulted as the outermost scope whenever `$HOME` is the user's own rather than part of the tree being read. Where the session root *is* the home directory — a dotfiles repository, a devcontainer or an archive unpacked as `$HOME` — that file is treated as project scope like any other file inside the tree.
302
+
303
+ Unlike the rc — where the nearest file simply wins — every scope is **merged**, git-config style:
304
+ `~/.xmemory.json` supplies personal instances, a repository's committed `.xmemory.json` adds the
305
+ team's, and the nearer file wins **field by field** on any instance both mention.
306
+
307
+ | Command | Description |
308
+ |---------|-------------|
309
+ | **`binding list`** | Merged view for this directory. `--autoload` lists only what a session-start hook would pull. |
310
+ | **`binding add <id>`** | Bind an instance, or update the binding it already has. `--name`, `--purpose`, `--tier`, `--engage` (repeatable), `--scope`. |
311
+ | **`binding remove <id>`** | Unbind from **one** scope. To silence an instance inherited from a wider scope, bind it here with `--tier off` instead. |
312
+ | **`context`** | Session-start pack for the `autoload`-tiered bindings here. `--text` prints the pack alone, `--max-tokens`, `--no-live-state`, `--timeout`. The only one of these that needs a credential. |
313
+
314
+ **Tiers:** `autoload` (pull its context every session) · `available` (default; engage on demand) ·
315
+ `off` (bound but dormant).
316
+
317
+ **Scopes:** `--scope project` (default) writes the committable file — at an existing nearer
318
+ binding, else the git work-tree root **when git can say and the reader would actually look
319
+ there**, else the cwd. git only ever suggests: no git, or a directory that is not a checkout, is
320
+ an ordinary state and never blocks a write. The one refusal left is when the target would resolve
321
+ onto the home directory, which would write the user-scope file while calling it a project one.
322
+ `--scope user` always writes `~/.xmemory.json`.
323
+
324
+ `context` makes no request when nothing is tiered `autoload`, and resolves no credential
325
+ either — a hook running on every session start must not error in a project that was never
326
+ going to fetch anything. A server without the endpoint is treated as nothing to inject, so a
327
+ client ahead of its deployment degrades quietly instead of failing every session, as does a
328
+ binding this build cannot parse.
329
+
330
+ **`--timeout` bounds the whole command.** Not the request — *everything*: binding discovery,
331
+ the credential preflight, the git provenance lookup, name resolution, TLS, response headers and
332
+ body. It is a wall-clock limit, because it is the only bound a session-start hook has (POSIX
333
+ `sh` has no portable `timeout`) and a bound with an exception in it is not one. Expiry degrades
334
+ like every other failure: exit **0**, empty pack, one `warning:` line naming the budget.
335
+
336
+ `--text` prints the pack and nothing else, for a caller that will **wrap** it. Do not point a
337
+ hook at it directly: a hook's stdout is parsed as its control document on some events, so a
338
+ pack that happened to be valid JSON would be read as control fields rather than injected. A
339
+ companion editor-integration plugin wraps this output in the documented hook envelope its own
340
+ client expects; it ships from a separate repository and is not part of this one.
341
+
342
+ **No secrets.** An instance id is not a credential, so a *project* `.xmemory.json` is written
343
+ `0644` and is *meant* to be committed — the opposite of `.xmemrc.json` (`0600`, never
344
+ committed). `~/.xmemory.json` is written `0600`: it stays on one machine, and on a shared host
345
+ its instance names and purposes are nobody else's business.
346
+
347
+ **Scope precedence has one exception: your own entries are a ceiling.** A nearer scope normally
348
+ wins field by field, but a project file arrives by `git clone` and is the less trusted of the
349
+ two. So for any instance `~/.xmemory.json` lists, a committed file may make it **less** eager,
350
+ never more:
351
+
352
+ - **Tier.** Ranked `autoload` > `available` > `off`. A project may take your `autoload` down to
353
+ `available` or `off`; it can never raise `off` to `available`, or `available` to `autoload`.
354
+ **An omitted `tier` in your file means `available` and binds as one** — so `binding add --scope
355
+ user <id>` with no `--tier` caps that instance at `available` everywhere. If you want a
356
+ project's `autoload` to stand, say `--tier autoload` in your own file: your entry states the
357
+ most eager you are willing to be, not merely that you know the id.
358
+ - **`engage`.** Replaced by a nearer scope for every other instance, but not for one of yours —
359
+ otherwise a committed file could rewrite the cues attached to your own instance, which is a
360
+ quieter version of raising its tier.
361
+
362
+ Everything else (`name`, `purpose`) still merges nearest-wins, and a project may always add
363
+ detail to an instance you listed.
364
+
365
+ **File format** (v1). `id` is required; everything else is optional. Unknown keys are rejected,
366
+ so a typo fails loudly rather than being ignored.
367
+
368
+ ```json
369
+ {
370
+ "version": 1,
371
+ "instances": [
372
+ {
373
+ "id": "9c421cd4-77ab-4a51-a7e3-b8fd5d09d394",
374
+ "name": "Team Knowledge",
375
+ "purpose": "shared dev conventions",
376
+ "tier": "autoload",
377
+ "engage": ["a convention is learned or corrected"]
378
+ }
379
+ ]
380
+ }
381
+ ```
382
+
383
+ ---
384
+
385
+ ### Auth & meta
386
+
387
+ | Command | Description |
388
+ |---------|-------------|
389
+ | **`auth login`** | PKCE via Console `/xmemcli/<challenge>` → `.xmemrc.json`. `--no-browser`, `--timeout`, `--console-url`. |
390
+ | **`auth logout`** | Remove active `.xmemrc.json` |
391
+ | **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix. |
392
+ | **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
393
+ | **`version`** | CLI release version (no network) |
394
+ | **`status`** | Local health: version + whether `.xmemrc.json` exists (no network) |
395
+
396
+ **`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
397
+ JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
398
+ CLI's credential attached, and writes the answer back. Register it with
399
+ `claude mcp add <name> -- xmemcli mcp <instance-id>` (or the equivalent for another
400
+ client).
401
+
402
+ It exists because an MCP client can reference an environment variable in an
403
+ `Authorization` header but cannot read `.xmemrc.json`. Reading the credential per
404
+ connection means nothing is captured when the server is registered, so a configuration
405
+ written today still works in a session opened next week with an empty environment.
406
+
407
+ Two consequences worth knowing:
408
+
409
+ - **stdout is the protocol.** Alone among these commands it emits no JSON document and
410
+ honours neither `--json` nor the exit-code table above; its exit code is a process
411
+ status. Diagnostics go to stderr.
412
+ - **Signed out is reported, not fatal.** Every request is answered with a JSON-RPC error
413
+ naming `auth login`, because a client that is told only "the server failed to start"
414
+ gives the reader nothing to act on. Exit code and stage do not apply.
415
+
416
+ **Console `/xmemcli` routing:**
417
+
418
+ - **Onboarding** → silent onboarding-key handoff to the CLI (no overlay, no button click).
419
+ - **Post-onboarding** → API Keys create modal → “Use this key in the CLI”.
420
+
421
+ ---
422
+
423
+ ### Quota (read-only)
424
+
425
+ | Command | Description |
426
+ |---------|-------------|
427
+ | **`quota`** | Usage / limit / window start for the key's cluster (derived from instance → cluster). |
428
+
429
+ - **Default:** exit `0` when quota is readable; stderr **warning** when usage is high.
430
+ - **`--fail-if-under THRESHOLD`** — exit **7** when **remaining** quota is below that share of the limit (`0.1` or `10%`). Distinct from exit **6** (API quota exhausted). See `xmemcli help quota`.
431
+
432
+ Quota **increases** are Console-only — see [What `xmemcli` does not do](#what-xmemcli-does-not-do).
433
+
434
+ ### Exit codes
435
+
436
+ | Code | Meaning |
437
+ |------|---------|
438
+ | **0** | Success |
439
+ | **2** | Usage error (bad flags, unknown `help` topic, missing required args) |
440
+ | **3** | HTTP / request failure — see [HTTP status mapping](#http-status--exit-code-and-json-stage) (includes permission denied, other API errors, and unwritable `-o` paths) |
441
+ | **4** | Auth error — invalid or missing API key (`401`; `stage: "auth"`) |
442
+ | **5** | Timeout |
443
+ | **6** | Quota exhausted (API quota-exceeded response; `stage: "quota"`) |
444
+ | **7** | Quota headroom below `--fail-if-under` |
445
+ | **130** | Interrupted (Ctrl+C) |
446
+
447
+ ### HTTP status → exit code and JSON `stage`
448
+
449
+ `xmemcli` mirrors the API's **401 vs 403** distinction on the process exit code and on the machine-readable `stage` field. This is the single contract for **every** command that performs an HTTP call (read/write, `org list *`, schema/XMD, quota, `auth status`, …) — with one deliberate exception, below.
450
+
451
+ **`context` is exempt, by design** — from the release that introduces `context`, which is the
452
+ first one where this carve-out means anything. It is the one command nobody invokes: a
453
+ session-start hook runs it unattended, and a non-zero exit there costs the user their session
454
+ rather than their context. So **every** HTTP outcome — 401 and 403 included — exits **0** with an
455
+ empty context and an explanatory `warnings[]` entry. Use `auth status` to tell a credential
456
+ problem apart from an empty binding; `context` will not.
457
+
458
+ The exemption goes further than HTTP status. Any runtime failure inside `context` — a malformed
459
+ binding, an unreadable response shape, a timeout, a bug nobody anticipated — exits **0** with an
460
+ empty pack and a `warning:` line naming the cause. This is enforced in code rather than by
461
+ enumerating failure modes, so an unforeseen one degrades like the rest.
462
+
463
+ **And it is exempt from the one-JSON-document rule on stdout.** Every other command emits a JSON
464
+ error document in machine mode, including on usage errors. A piped, non-`--json` `context` whose
465
+ arguments fail to parse writes **zero bytes to stdout** and puts its reason on stderr instead,
466
+ because a hook discards stdout at exit **2** — so a JSON error document there is a session start
467
+ failing with no stated cause anywhere the user can see it. Usage and parse errors are the only
468
+ paths that still exit **2**; they are a mistake in the hook's own configuration, not a runtime
469
+ condition, and the hook author is the one reading stderr.
470
+
471
+ #### Quick reference
472
+
473
+ | API HTTP status | Shell exit code | JSON `stage` | Meaning |
474
+ |-----------------|-----------------|--------------|---------|
475
+ | **401** | **4** | `auth` | **Bad credential** — missing, invalid, expired, or inactive API key |
476
+ | **403** | **3** | `permission` | **Bad authorization** — key is valid but this key/role cannot perform the operation |
477
+ | **Other 4xx/5xx** | **3** | `request` | Request failed for another reason (404, 422, 5xx, …) |
478
+ | **402** (quota exhausted) | **6** | `quota` | Quota window exhausted — human must extend quota in Console |
479
+
480
+ #### How to interpret failures
481
+
482
+ 1. **Exit 4 / `stage: "auth"` / HTTP 401** → fix the key: login again, rotate the key, check `$XMEM_API_KEY` / `.xmemrc.json`, confirm `--api-url` points at the environment that issued the key.
483
+ 2. **Exit 3 / `stage: "permission"` / HTTP 403** → the key works, but the operation needs a broader API role (for example `data.write`, `instance.generate_schema`, `cluster.get_own`). Do **not** treat this as a wrong password.
484
+ 3. **Exit 3 / `stage: "request"`** → other HTTP failure (instance not found, validation error, server error). Read the error message and HTTP status in the JSON body.
485
+
486
+ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …) while still using exit **3** unless the row above applies. The `schema suggestions` commands add their own stages: `stale_proposal` (re-run review for a fresh `proposal_version`), `invalid_decision`, `dependency` (accept the dangling dependencies first), `not_initialised` (submit a write first), and `apply`; `review` reports an `evolution_in_progress` condition as a successful status with exit 0.
487
+
488
+ **Envelope errors** (HTTP **200** with non-empty `errors[]`) are not HTTP 401/403; they typically exit **3** with `stage: "request"`.
489
+
490
+ **Breaking change in 0.0.5:** HTTP **403** previously exited **4** (`auth`); it now exits **3** (`permission`). Scripts that treated exit **4** as “any auth-layer failure” must check `stage` or handle exit **3** for permission denials.
491
+
492
+ See also [`ACCOUNTS_AUTH.md`](../ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
493
+
494
+ ---
495
+
496
+ ## Help
497
+
498
+ `xmemcli` supports root `-h` / `--help` (same overview as bare `xmemcli`) and the **`help`** subcommand for nested topics.
499
+
500
+ | Invocation | What you get |
501
+ |------------|--------------|
502
+ | **`xmemcli`** | Full command overview (grouped list of every capability) |
503
+ | **`xmemcli help`** | Same as bare `xmemcli` |
504
+ | **`xmemcli help options`** | Global flags (`--api-url`, `--api-key`, `--json`, …) |
505
+ | **`xmemcli help <topic>`** | Usage, options, and subcommands for that topic |
506
+ | **`xmemcli <cmd> … -h`** / **`--help`** | Same as `help <topic>` for that command path |
507
+
508
+ Unknown help topics exit **2** (same as other usage errors).
509
+
510
+ **Every command in this document has help.** Examples:
511
+
512
+ ```text
513
+ xmemcli help write
514
+ xmemcli help write-status
515
+ xmemcli help read
516
+ xmemcli help org
517
+ xmemcli help org list instances
518
+ xmemcli help instance create
519
+ xmemcli help schema
520
+ xmemcli help schema get
521
+ xmemcli help schema update
522
+ xmemcli help schema dry-run
523
+ xmemcli help schema migrations
524
+ xmemcli help schema migrations list
525
+ xmemcli help schema migrations get
526
+ xmemcli help schema suggestions
527
+ xmemcli help schema suggestions review
528
+ xmemcli help schema suggestions decide
529
+ xmemcli help schema suggestions apply
530
+ xmemcli help xmd
531
+ xmemcli help xmd generate
532
+ xmemcli help xmd enhance
533
+ xmemcli help xmd validate
534
+ xmemcli help xmd validate-yaml
535
+ xmemcli help xmd validate-json
536
+ xmemcli help xmd convert
537
+ xmemcli help auth login
538
+ xmemcli help quota
539
+ xmemcli help options
540
+ ```
541
+
542
+ Nested topics work at any level that exists in the parser (`help org`, `help org list`, `help org list keys`, …). Unknown topics print a short error and suggest `xmemcli` or `help options`.
543
+
544
+ ---
545
+
546
+ ## What `xmemcli` does **not** do
547
+
548
+ | Area | Use the Console instead |
549
+ |------|-------------------------|
550
+ | **Quota increases** | No `quota request-increase`. Observe with `xmemcli quota`; **raise limits in Billing / Quotas UX**. |
551
+ | **Routine API key creation** | Post-onboarding: API Keys UI (CLI opens that flow via `/xmemcli`). |
552
+ | **Billing & plans** | Console only |
553
+ | **Users, roles, org admin** | Console only |
554
+ | **Instance chat / trace inspection** | Console only |
555
+ | **Cluster create/delete** | CLI lists clusters; provisioning in Console |
556
+ | **`read --extraction deep`** | Server has no read-depth knob today |
557
+ | **Raw REST proxy** | Curated response fields only |
558
+ | **Instance deletion** | **Human operators only** — a hidden `instance delete` exists for interactive TTY use (type `yes` to confirm). It is omitted from help and must not be used by agents or automation. Console UI remains the primary human path. |
559
+
560
+ On HTTP **402** (quota exhausted), stop and ask a human to extend quota in the Console.
561
+
562
+ ---
563
+
564
+ ## Global flags & environment
565
+
566
+ These apply **before** any subcommand (`xmemcli help options`).
567
+
568
+ | Flag | Env var | Purpose |
569
+ |------|---------|---------|
570
+ | `--api-url` | `XMEM_API_URL` | API origin (default `https://api.xmemory.ai`) |
571
+ | `--api-key` | `XMEM_API_KEY` | Bearer API key |
572
+ | `--instance-id` | `XMEM_INSTANCE_ID` | Default instance for read/write and cluster derivation |
573
+ | `--rc-dir` | `XMEM_RC_DIR` | Where `auth login` writes `.xmemrc.json`; reads walk **up** toward `$HOME` |
574
+ | `--binding-dir` | `XMEM_BINDING_DIR` | Where `binding` commands look for `.xmemory.json`; reads walk **up** toward `$HOME` and **merge** every scope |
575
+ | `--json` | — | Force one JSON document on stdout |
576
+ | `--verbose` | — | Add `trace_id` / `console_url` when present (never SQL, diff plans, or internals) |
577
+
578
+ **Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
579
+
580
+ **`context` is the one exception, and it is a security boundary.** Every other command walks up
581
+ from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
582
+ that directory and ran the command in it. `context` has neither — a session-start hook runs it
583
+ unattended, in whatever tree the editor happened to open — so it resolves the credential from
584
+ **`$HOME` only**, and consults a project `.xmemrc.json` only when the caller named that source
585
+ explicitly with `--api-key` / `$XMEM_API_KEY` or `--rc-dir` / `$XMEM_RC_DIR`. Without the pin, a
586
+ cloned repository could ship a key *and* an origin and have both used automatically. This is a
587
+ rule rather than a detector: earlier versions tried to *detect* a checkout-supplied credential
588
+ and each delivery mechanism turned out to be a fresh bypass.
589
+
590
+ **Origin resolution:** `--api-url` → `$XMEM_API_URL` → rc `api_url` → default — **with one escape hatch**: when the key comes from `--api-key` / `$XMEM_API_KEY` (not from the rc), the rc's `api_url` is **not** consulted and the origin falls back to the built-in default (`https://api.xmemory.ai`) unless you also set `--api-url` / `$XMEM_API_URL`. That keeps an explicitly supplied key from being silently paired with an unrelated origin in a project `.xmemrc.json`. If `$XMEM_API_KEY` is set without `$XMEM_API_URL`, the CLI warns once per invocation.
591
+
592
+ **Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
593
+
594
+ **Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.