xmemcli 1.2.0__tar.gz → 1.3.0__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-1.2.0 → xmemcli-1.3.0}/PKG-INFO +106 -15
- {xmemcli-1.2.0 → xmemcli-1.3.0}/README.md +105 -14
- {xmemcli-1.2.0 → xmemcli-1.3.0}/pyproject.toml +1 -1
- {xmemcli-1.2.0 → xmemcli-1.3.0}/uv.lock +1 -1
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/__init__.py +1 -1
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_email_login.py +4 -9
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_help.py +8 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_instance_schema.py +1 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_login.py +10 -7
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_mcp_bridge.py +15 -1
- xmemcli-1.3.0/xmemcli/_org_cluster.py +134 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_output.py +23 -7
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_pretty.py +199 -16
- xmemcli-1.3.0/xmemcli/_trace.py +649 -0
- xmemcli-1.3.0/xmemcli/_version.py +60 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_xmem_bridge.py +9 -2
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/cli.py +497 -66
- xmemcli-1.2.0/xmemcli/_org_cluster.py +0 -70
- xmemcli-1.2.0/xmemcli/_version.py +0 -4
- {xmemcli-1.2.0 → xmemcli-1.3.0}/.gitignore +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_binding.py +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_binding_cmd.py +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_context_cmd.py +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_envelope.py +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_rc.py +0 -0
- {xmemcli-1.2.0 → xmemcli-1.3.0}/xmemcli/_text.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xmemcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Command-line client for the xmemory HTTP API: PKCE auth, instances, read/write, schema and XMD tools.
|
|
5
5
|
Keywords: api,cli,llm,memory,xmemory
|
|
6
6
|
Requires-Python: <4,>=3.12
|
|
@@ -28,9 +28,12 @@ xmemcli binding list [--autoload]
|
|
|
28
28
|
xmemcli binding add <instance_id> [--tier autoload|available|off] [--scope project|user]
|
|
29
29
|
xmemcli binding remove <instance_id> [--scope project|user]
|
|
30
30
|
xmemcli context [--text] [--max-tokens N] [--no-live-state]
|
|
31
|
+
xmemcli trace ls [--instance ID]… [--trace-type T]… [--hours-back N] [--user-id U] [--api-key-id K] [--limit N] [--cursor TS_MS:TRACE_ID] [--counts-only]
|
|
32
|
+
xmemcli trace ls --search [--text …] [--request …] [--response …] [--object NAME]… [--field NAME]… [--status S]… [--trace-id ID]… [--session-id S] [--instance ID] [--trace-type T]… [--hours-back N]
|
|
33
|
+
xmemcli trace get <trace_id> [--trace-type write|read|create|update|evolution]
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
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`.
|
|
36
|
+
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`. `trace …` additionally take `--console-url` / `$XMEM_CONSOLE_URL` (the Console origin that serves them; inferred for the public and local pairs).
|
|
34
37
|
|
|
35
38
|
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. The `mcp` transport resolves its own origin the same way: `--mcp-url` → `$XMEM_MCP_URL` → rc `mcp_url` → default, where `mcp_url` is what the API told `auth login` about itself.
|
|
36
39
|
|
|
@@ -48,6 +51,8 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
48
51
|
| `xmemcli read --read-mode single` (default) | `mode: "single-answer"` |
|
|
49
52
|
| `xmemcli read --read-mode raw` | `mode: "raw-tables"` |
|
|
50
53
|
| `xmemcli read --read-mode xresponse` | `mode: "xresponse"` |
|
|
54
|
+
| `xmemcli read --related-types` | `include_related_types: "types"` — the response's `related_types` object is kept in the curated output |
|
|
55
|
+
| `xmemcli read --related-types-depth 2` | `include_related_types: "types"` and `related_types_depth: 2` — the catalog entries carry their `distance` and, below the last level, their own edges |
|
|
51
56
|
|
|
52
57
|
`--read-mode` selects the **server-side answer shape**; `--json` and the TTY rule govern stdout format.
|
|
53
58
|
|
|
@@ -55,6 +60,30 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
55
60
|
|
|
56
61
|
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.
|
|
57
62
|
|
|
63
|
+
**`trace …` is the one deliberate exception.** Those commands return the Console's own operation documents — the paged operations list, the Inspect search, and the per-operation detail — **verbatim**, because the Console already curates them per caller (a super admin sees LLM calls and raw token counts; everyone else does not). They are JSON on every stream and carry every internal id the Console does: `trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id`, and on detail documents `source_id`, `migration_id`, `llm_call_id`, `gateway_request_id`, …
|
|
64
|
+
|
|
65
|
+
## Trace — the Console's operation traces, from the CLI
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
xmemcli auth status # super_admin: which Console view trace will answer with
|
|
69
|
+
xmemcli org list instances # the instances this key can see — what trace ls is scoped to
|
|
70
|
+
xmemcli trace ls # every visible instance, newest first, one page
|
|
71
|
+
xmemcli trace ls --instance "$ID" --trace-type write --trace-type read --limit 50
|
|
72
|
+
xmemcli trace ls --counts-only --hours-back 168 # counts, per-instance counts and series, no rows
|
|
73
|
+
xmemcli trace ls --search --text "Ada" --instance "$ID" # Inspect search — opt-in, more expensive
|
|
74
|
+
xmemcli trace get 0001_abc --trace-type write # one operation, every internal id
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Paging is a cursor the previous page returns:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
PAGE=$(xmemcli trace ls --instance "$ID" --limit 100)
|
|
81
|
+
NEXT=$(echo "$PAGE" | jq -r '"\(.next_before_ts_ms):\(.next_before_trace_id)"')
|
|
82
|
+
xmemcli trace ls --instance "$ID" --limit 100 --cursor "$NEXT"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`--search` has no cursor — the Console's Inspect endpoint doesn't page; it returns the newest matches up to a ceiling and the CLI warns when a response sits at it. A key sees the operations of the instances it can list — never more than its owner sees in the Console; a super admin's key sees what a super admin sees. Full reference: [](#trace--the-consoles-operation-traces).
|
|
86
|
+
|
|
58
87
|
## Output personality — JSON vs pretty
|
|
59
88
|
|
|
60
89
|
- **Non-TTY stdout** or **`--json`**: exactly one JSON document.
|
|
@@ -99,7 +128,7 @@ xmemcli version
|
|
|
99
128
|
xmemcli auth login
|
|
100
129
|
```
|
|
101
130
|
|
|
102
|
-
Version: **1.
|
|
131
|
+
Version: **1.3.0**.
|
|
103
132
|
|
|
104
133
|
|
|
105
134
|
---
|
|
@@ -199,7 +228,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
199
228
|
|
|
200
229
|
# `xmemcli` — capabilities reference
|
|
201
230
|
|
|
202
|
-
**Version:** `1.
|
|
231
|
+
**Version:** `1.3.0` (see `xmemcli version`)
|
|
203
232
|
|
|
204
233
|
`xmemcli` is the command-line client for the **xmemory HTTP API**. It is **human-approved for key acquisition**: an AI-assisted IDE may run reads, writes, and inventory, and may initiate `auth login`, but a key is only issued after one explicit human approval — a click in the Console UI (PKCE handoff) or an approval of a sign-in email (`--email`). Quota limits change only in the **Console UI**. The full acquisition contract is in the auth-flow reference bundled with this package.
|
|
205
234
|
|
|
@@ -219,7 +248,7 @@ Every command below has dedicated help — see [Help](#help).
|
|
|
219
248
|
|---------|-------------|
|
|
220
249
|
| **`write`** `<text>` | Sync write (`POST …/write`). `--extraction fast\|deep` (default `fast`). `--no-wait` queues async write, returns `write_id`. `--scope TYPE:ID` (repeatable) anchors the write to existing objects: only they may be modified/deleted, new objects and links to them are allowed, anything else fails; fast extraction only. |
|
|
221
250
|
| **`write-status`** `<write_id>` … | Poll async writes until terminal or `--timeout` (default 60s). Accepts multiple ids. |
|
|
222
|
-
| **`read`** `<query>` | Read from active instance. `--read-mode single\|raw\|xresponse`, `--scope TYPE:ID` (repeatable), `--scope-relations
|
|
251
|
+
| **`read`** `<query>` | Read from active instance. `--read-mode single\|raw\|xresponse`, `--scope TYPE:ID` (repeatable), `--scope-relations`, `--related-types` (also return `related_types`: per object type the read touched, the fields it did not return and the object types a declared relation links it to; needs `instance.get_own` on the key), `--related-types-depth N` (1, 2 or 3, refused as a usage error otherwise; follow the relations N levels out, each catalog entry saying its distance; implies `--related-types`). |
|
|
223
252
|
|
|
224
253
|
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)).
|
|
225
254
|
|
|
@@ -232,7 +261,7 @@ Permission-filtered view of what this API key can see.
|
|
|
232
261
|
| Command | Description |
|
|
233
262
|
|---------|-------------|
|
|
234
263
|
| **`org list clusters`** | Clusters visible to this credential |
|
|
235
|
-
| **`org list instances`** | Instances visible to this credential |
|
|
264
|
+
| **`org list instances`** | Instances visible to this credential. A key minted with `data.read` but not `instance.get_own` (the supported shape for `trace …`) gets the ids only, with a warning saying so — the same set `trace ls` is scoped to. |
|
|
236
265
|
| **`org list keys`** | API keys visible to this credential (metadata only; plaintext never returned) |
|
|
237
266
|
|
|
238
267
|
---
|
|
@@ -419,27 +448,77 @@ so a typo fails loudly rather than being ignored.
|
|
|
419
448
|
|
|
420
449
|
---
|
|
421
450
|
|
|
451
|
+
### Trace — the Console's operation traces
|
|
452
|
+
|
|
453
|
+
What the Console shows on an instance's **Operations** tab (a paged list), on **Inspect** (a
|
|
454
|
+
search across operations), and in each operation's detail pane — fetched from the same Console
|
|
455
|
+
endpoints, with this CLI's API key, and emitted **as the Console serves them**. Nothing is curated
|
|
456
|
+
or reshaped: the Console already curates per caller, and a field the Console adds shows up here
|
|
457
|
+
the same day. These documents are for scripts and agents; on a terminal they are only indented
|
|
458
|
+
and coloured (`NO_COLOR` / `FORCE_COLOR` honoured). Flags only — there is no query language to
|
|
459
|
+
learn; the CLI composes the Console's search clauses itself.
|
|
460
|
+
|
|
461
|
+
| Command | Console view | Description |
|
|
462
|
+
|---------|--------------|-------------|
|
|
463
|
+
| **`trace ls`** | Operations tab / Home | Operations, newest first, across every instance this credential can see (nothing given — the Home view), or the `--instance ID` given (repeatable, or a comma list; default `--instance-id` / `$XMEM_INSTANCE_ID`). Returns `operations[]`, `counts`, `instance_counts`, `series`, and the paging cursor `next_before_ts_ms` + `next_before_trace_id` (with `has_more_operations`). Flags: `--trace-type T` (repeatable / comma; `write`, `read`, `create`, `update`, `evolution`), `--hours-back N` (server default 24, `0` = unbounded) or `--start-ts` / `--end-ts` (epoch seconds), `--user-id`, `--api-key-id`, `--include-console-reads`, `--bucket-ms`, `--limit N` (default 100, Console cap 500), `--cursor TS_MS:TRACE_ID` (the previous page's two halves, joined with a colon), `--counts-only` (`ops_limit=0`: what Home fetches — counters and series, no rows; refused together with `--limit` / `--cursor`, which would have nothing to page). |
|
|
464
|
+
| **`trace ls --search`** | Inspect | The Inspect search, for the filters only it can answer: `--text` (free text), `--request` / `--response` (text on either side of the operation), `--object NAME` / `--field NAME` (repeatable; all must match), `--status S` (repeatable / comma; any may match: `ok`, `error`, `stale`, `timed_out`, `internal_error`, `quota_exceeded`, `in_progress`, `schema-gap`), `--trace-id ID` (repeatable / comma), `--session-id`; plus `--instance` (at most one — the Inspect scope), `--trace-type`, `--hours-back` (a floor applied to the hits *after* the search — Inspect's own recency windows decide the candidate pool, so this narrows a result, unlike the query window it is on `trace ls`), `--user-id`, `--api-key-id`. Returns the Inspect document: `merged[]` hits with `kind`, `href` (`/write/{trace}`, `/read/{trace}`, `/create/…`, `/update/…`, `/evolution/…`), `trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id` / `api_key_name` / `api_key_prefix`, `status`, `time_taken_ms`, `match_items`. |
|
|
465
|
+
| **`trace get`** `<trace_id>` | Detail pane | One operation's full document. `--trace-type` (`operation_type` on a `trace ls` row; the first path segment of a `--search` hit's `href`) makes it one request; without it the five detail endpoints are probed in order `write, read, create, update, evolution` and the first that knows the trace answers. A miss — the Console's `{"found": false}`, or its 404 for a trace outside your scope — is `stage: "not_found"`, exit **3**. |
|
|
466
|
+
|
|
467
|
+
**`--search` is opt-in, on purpose.** The Inspect search runs text predicates across the trace store with a candidate pool of thousands and a merge — markedly more expensive than the indexed, cursor-paged `trace ls`. Passing a search-only flag (`--text`, `--request`, `--response`, `--object`, `--field`, `--status`, `--trace-id`, `--session-id`) without `--search` is a usage error (exit **2**) whose message says exactly that, so an agent reasons about whether it needs the search or whether `trace ls` narrowed by `--instance` / `--trace-type` / `--hours-back` plus `trace get` will do. Conversely `--limit`, `--cursor`, `--counts-only`, `--start-ts` / `--end-ts`, `--bucket-ms` are refused with `--search` because the Inspect endpoint cannot honour them, and `--include-console-reads` because Inspect's only spelling of that toggle is a bare phrase inside `q` itself — which is also why a free-text value containing the words "include console reads" is refused rather than sent (the Console would flip the toggle and strip the phrase from the query).
|
|
468
|
+
|
|
469
|
+
**The pagination gap — read this before scripting `--search`.** `trace ls` pages properly (the Console's Operations tab does). `trace ls --search` does **not**: the Console's Inspect endpoint has no cursor — it returns the newest 50 matches when no filter narrows it (latest-activity mode) and up to 5 000 matches per source once filtered, and stops; the Console's own Inspect view has the same cliff. So `--search` takes no `--cursor` / `--limit`, and when a response sits at one of those ceilings the CLI emits a **warning** (stderr, and folded into the JSON document's `warnings` array) saying the result was cut and how to narrow it. The warning is **best-effort** — the warning is one-sided by construction: it fires on the count the client can see, and the Console narrows *after* its per-source row caps (type/status resolution, console-read exclusion, the caller's instance allowlist), so a capped response can shrink below the ceiling — even to near-empty for a scoped key searching without an instance filter — with no client-visible signal. A missing warning is not proof of completeness — and a warning is not proof of a cut: the 5 000 figure is *per source*, while the count the warning tests is the cross-source merge (truncated only in latest-activity mode), so several uncapped sources together can trip it on a complete result. Until the Console endpoint pages (and reports truncation itself), treat **every** `--search` result as "the newest matches", not "all matches".
|
|
470
|
+
|
|
471
|
+
**Two document shapes.** `trace ls` returns the Operations-tab document; `trace ls --search` returns the Inspect document (above). They differ because the Console's two backends differ and this CLI does not reshape what the Console serves. Both carry the same identifiers per row (`trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id`, `status`), which is what `trace get` needs next.
|
|
472
|
+
|
|
473
|
+
**Every operation row carries its internal ids.** `operations[]` rows have `operation_type`, `trace_id`, `instance_id`, `timestamp`, `status`, `is_success`, `error_code`, `session_id`, `user_id`, `user_email`, `api_key_id`, `api_key_name`, `api_key_prefix`, `time_taken_ms`, `text_snippet`, `object_names`, `field_names`.
|
|
474
|
+
|
|
475
|
+
> **Three of those never resolve for an API key**, super admin or not, and this is by design rather than a gap. `user_email` is always absent: the accounts `/users` lookup is closed to keys (an agent may learn who *it* is, never enumerate the organisation's users), so rows keep `user_id` and you resolve names yourself if you need them — and a `User:<email>` search clause matches nothing; filter by `user_id`. `api_key_name` / `api_key_prefix` resolve only for the caller's **own** keys, because `GET /api-keys` scopes a key caller to its owner's keys. So a super admin's key does *not* see literally everything its owner's browser sees: it sees the same rows and the same fields, with those three enrichments empty. Everything scope-bearing — which instances, which detail fields, `llm_calls`, xuid substitution — is identical. Detail documents add what the Console's pane shows: `source_id`, `write_mode` / `read_mode`, `scope`, `extracted_objects`, `diff_plan` (sanitised unless super admin), `written_objects`, `migration_id`, `proposal_version`, `schema_version`, `stage`, `triggered_evolution {migration_id, evolution_trace_id, update_trace_id}`, and — **unrestricted callers only** (a super admin, or a browser session on a `KINGDOM_LOCAL` Console) — `llm_calls[]` (`llm_call_id`, `gateway_request_id`, `model_type`, `provider`, …), `full_error_message`, raw token counts, unsanitised xuids, `sql_queries`, `ddl_queries`, and `schema_generation`.
|
|
476
|
+
|
|
477
|
+
**A scoped key gets `[]` for `sql_queries` / `ddl_queries` / `schema_generation`, not the rows.** Those three come from `read_sql_queries`, `instance_create_ddl_queries` and `schema_generate_*`, which carry no instance ownership column — `schema_generate_*`'s trace id is client-supplied — so they cannot be scoped in SQL and are served only to callers with no instance restriction at all. This is a deliberate fail-closed choice, not a bug to work around: an empty array means "not available to this credential", never "this operation ran no SQL". `llm_calls[]` behaves the same way and always has. **Xuids are hidden for every non-super-admin**, including on writes that carry no identity map — the substitution is done by the Console, server-side, so a caller with no browser sees `#hidden` rather than a raw id.
|
|
478
|
+
|
|
479
|
+
**Who sees what — the Console decides; `auth status` tells you which view to expect.**
|
|
480
|
+
|
|
481
|
+
- A regular key sees the operations of the instances the accounts API lists for it — the same set `org list instances` prints. That is at most what its owner sees in the Console, never more.
|
|
482
|
+
- Access requires the key's role to carry **`data.read`** — the same permission the data-read endpoint requires. A role deliberately scoped away from data (`auth status` shows the codes under `permissions`) is refused with **403 → exit 3, stage `permission`**, whoever owns the key — a permission denial, not a bad credential: rotating or re-issuing the key cannot fix a role that was scoped this way on purpose. An unknown or missing key is the separate 401 → exit 4 (`auth`).
|
|
483
|
+
- A key whose owner is a **super admin** sees the super-admin Console: every organisation's instances, no instance filter, and the super-admin-only detail fields above. The accounts `is_super_admin` flag decides — the key sees the same rows and the same scope-bearing fields its owner's browser session sees, minus the three enrichments no key resolves (`user_email`, and `api_key_name` / `api_key_prefix` for other people's keys; see the note above). `auth status` reports it as `super_admin`.
|
|
484
|
+
- No new permission is involved: the Console answers a key exactly as it would answer its owner's browser session, narrowed to the key's instances. It never mints a session for a key. Filters that need to look other users up (`User:`-style email filters) are not offered: an API key can identify only itself, never list or look up users — filter by `--user-id`.
|
|
485
|
+
|
|
486
|
+
**The Console origin.** `trace …` talk to the Console, a second origin. It is `--console-url` → `$XMEM_CONSOLE_URL` → inferred: `https://console.xmemory.ai` for the default API, `:8002` for a local `:8000`. A key bound to any other API origin gets a usage error (exit **2**) rather than a guess — the credential-binding rule holds across origins.
|
|
487
|
+
|
|
488
|
+
**Parity.** These commands are the Console's data path, not a copy of it. If the Console gains a filter or a field, it is added to the same list here and is available; the MCP server exposing the same data is deferred and will follow the same rule (see the note in the MCP server source).
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
422
492
|
### Auth & meta
|
|
423
493
|
|
|
424
494
|
| Command | Description |
|
|
425
495
|
|---------|-------------|
|
|
426
496
|
| **`auth login`** | PKCE via Console `/xmemcli/<challenge>` → `.xmemrc.json`. `--no-browser`, `--timeout`, `--console-url`, `--callback-port` (pins the loopback callback port for `ssh -L`; `0` or omitted means any free port, otherwise 1024–65535; also `$XMEM_CALLBACK_PORT`, which is read globally). With `--email ADDR` (+ optional `--org-name`, used only when a new account is created; requires `--email`): headless email sign-in — one emailed approval, then the CLI mints its own org-wide key named `CLI email login (<hostname>, <timestamp>)`; details in the auth-flow reference. New-organisation creation additionally requires the deployment to allow public registration. Payload shapes: a fresh email mint carries `method: email`, `key_name`, `api_role`, and `org_created`; the browser path carries `method: pkce` (no role/org fields); a matching `--api-key` or rc credential short-circuits to `status: already_authorized` with `source`: flag / rc (`persisted: false` on the flag arm, which saves nothing); a matching `$XMEM_API_KEY` is copied into the rc and reports `status: authorized`, `source: environment`. Exit codes follow the standard HTTP contract (401 → 4 with stage `auth`, 403 → 3 with stage `permission`, other HTTP/network/server-state failures → 3). |
|
|
427
497
|
| **`auth logout`** | Remove active `.xmemrc.json` |
|
|
428
|
-
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix. |
|
|
498
|
+
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix + `super_admin` (whether the key's owner is a super admin — what decides which Console view `trace …` return) + `permissions` (the key's own capability codes; `trace …` requires `data.read` among them). |
|
|
429
499
|
| **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
|
|
430
500
|
| **`version`** | CLI release version (no network) |
|
|
431
501
|
| **`status`** | Local health: version + whether `.xmemrc.json` exists, and the `mcp_url` it declares when it declares one (no network) |
|
|
432
502
|
|
|
433
503
|
**`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
|
|
434
504
|
JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
|
|
435
|
-
CLI's credential attached, and writes the answer back.
|
|
436
|
-
`claude mcp add <name> -- xmemcli mcp <instance-id>` (
|
|
437
|
-
client)
|
|
505
|
+
CLI's credential attached, and writes the answer back. The Console renders the
|
|
506
|
+
registration as `claude mcp add <name> -- xmemcli mcp <instance-id>` (with
|
|
507
|
+
`--mcp-url <origin>` appended when the MCP origin is not the client's own default), or the
|
|
508
|
+
equivalent for another client — **replace the bare `xmemcli` with its absolute path, in
|
|
509
|
+
double quotes**. The client stores the command as written and resolves it against its own
|
|
510
|
+
`PATH` every time it starts the server, and that `PATH` need not carry the directory the
|
|
511
|
+
tool was installed into; registered bare, it fails there — Claude Code reports
|
|
512
|
+
`ENOENT: Executable not found in $PATH: "xmemcli"`. `command -v xmemcli` prints the path on
|
|
513
|
+
macOS and Linux, `(Get-Command xmemcli).Source` in PowerShell on Windows. If the bare name
|
|
514
|
+
is registered already, remove that server entry before adding it again: Claude Code refuses
|
|
515
|
+
`mcp add` for a name that already exists rather than replacing it.
|
|
438
516
|
|
|
439
517
|
It exists because an MCP client can reference an environment variable in an
|
|
440
518
|
`Authorization` header but cannot read `.xmemrc.json`. Reading the credential per
|
|
441
|
-
connection means
|
|
442
|
-
written today still works in a session opened next week with an empty
|
|
519
|
+
connection means no credential is captured when the server is registered, so a
|
|
520
|
+
configuration written today still works in a session opened next week with an empty
|
|
521
|
+
environment.
|
|
443
522
|
|
|
444
523
|
**Where a file-held key may be sent.** The MCP origin is configured separately from the API
|
|
445
524
|
origin, and a project `.mcp.json` arrives by `git clone` — it controls that server's `args` and
|
|
@@ -504,6 +583,7 @@ Quota **increases** are Console-only — see [What `xmemcli` does not do](#what-
|
|
|
504
583
|
| **5** | Timeout |
|
|
505
584
|
| **6** | Quota exhausted (API quota-exceeded response; `stage: "quota"`) |
|
|
506
585
|
| **7** | Quota headroom below `--fail-if-under` |
|
|
586
|
+
| **8** | Write still processing — the API's wait window expired while the write kept running (HTTP `200` envelope; `stage: "processing"`). Not a failure: poll `write-status`, never retry |
|
|
507
587
|
| **130** | Interrupted (Ctrl+C) |
|
|
508
588
|
|
|
509
589
|
### HTTP status → exit code and JSON `stage`
|
|
@@ -538,6 +618,7 @@ condition, and the hook author is the one reading stderr.
|
|
|
538
618
|
| **403** | **3** | `permission` | **Bad authorization** — key is valid but this key/role cannot perform the operation |
|
|
539
619
|
| **Other 4xx/5xx** | **3** | `request` | Request failed for another reason (404, 422, 5xx, …) |
|
|
540
620
|
| **402** (quota exhausted) | **6** | `quota` | Quota window exhausted — human must extend quota in Console |
|
|
621
|
+
| **any status** with `errors[0].code == WRITE_STILL_PROCESSING` | **8** | `processing` | Write is still running — the write did not fail. Matched on the **code**, whatever the status: a keepalive-flattened **200** and an unflattened **500** both exit 8, so this row overrides the 4xx/5xx row above. Poll `write-status <write_id>`; a retry starts a second write |
|
|
541
622
|
|
|
542
623
|
#### How to interpret failures
|
|
543
624
|
|
|
@@ -545,12 +626,16 @@ condition, and the hook author is the one reading stderr.
|
|
|
545
626
|
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.
|
|
546
627
|
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.
|
|
547
628
|
|
|
548
|
-
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.
|
|
629
|
+
Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, `not_found` for `trace get`, …) while still using exit **3** unless the row above applies. `write` adds `processing`, which is in the table above and exits **8** rather than **3**. 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.
|
|
549
630
|
|
|
550
|
-
**Envelope errors** (HTTP **200** with non-empty `errors[]`) are not HTTP 401/403; they typically exit **3** with `stage: "request"`.
|
|
631
|
+
**Envelope errors** (HTTP **200** with non-empty `errors[]`) are not HTTP 401/403; they typically exit **3** with `stage: "request"`. Two exceptions. `QUOTA_EXCEEDED` exits **6** with `stage: "quota"`, the same as it does on a real HTTP 402 — the keepalive flattening does not change the exit code — and this applies to every command that classifies the error, not just `write` and `read`. `WRITE_STILL_PROCESSING` exits **8** with `stage: "processing"` and is not a failure at all; unlike the quota rung this one is **`write`-only**, because its guidance is to poll `write-status`, which is meaningless for any other verb — elsewhere it falls through to **3**/`request`.
|
|
632
|
+
|
|
633
|
+
> **Known bug.** Some subcommands in the `instance`, `schema` and `xmd` groups — `instance get`, `schema get` and `xmd generate` among them — unwrap the response outside their error handling, so an envelope error never reaches the classifier and escapes as an uncaught exception: a traceback and shell exit **1**, which is not in the table above. Those report **1**, not the **6** this section describes, until that is fixed. It is not whole groups: `instance instructions`, for example, reaches the classifier and exits **6** as documented.
|
|
551
634
|
|
|
552
635
|
**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.
|
|
553
636
|
|
|
637
|
+
**New in 1.3.0:** exit **8** with `stage: "processing"` for a `write` whose server-side wait window expired while the write kept running. In `--json` mode that document also carries a `write_id` field (omitted when the server sent no handle), so a script polls with it directly instead of parsing the message. Additive — no previously documented mapping changed — but a script that reads “non-zero, and not a code I recognise” as failed-and-retryable will now retry a write that has **not** failed, starting a second one the account pays for. Branch on **8** (or on `stage`) and poll `xmemcli write-status <write_id>` until it settles.
|
|
638
|
+
|
|
554
639
|
**New in 1.2.0:** `instance templates` lists the ready-made instance shapes the service ships, with the prose to choose between them, and `instance create --template <id>` builds one from a pinned schema the server already holds. No schema is generated on that path: no description to compose, no `xmd generate` hop, and the instance comes out identical to the one the console would have made from the same template. (Creating an instance still generates its agent brief afterwards, on this path as on any other.)
|
|
555
640
|
|
|
556
641
|
**New in 1.1.0:** `auth login` records the deployment's own MCP origin in `.xmemrc.json` as `mcp_url` (omitted where the server reports none), and `mcp` both connects there by default and accepts it as the pair its key may reach. Requires 0.0.12 or newer to read such a file — see the note below.
|
|
@@ -612,6 +697,9 @@ xmemcli help xmd validate
|
|
|
612
697
|
xmemcli help xmd validate-yaml
|
|
613
698
|
xmemcli help xmd validate-json
|
|
614
699
|
xmemcli help xmd convert
|
|
700
|
+
xmemcli help trace
|
|
701
|
+
xmemcli help trace ls
|
|
702
|
+
xmemcli help trace get
|
|
615
703
|
xmemcli help auth login
|
|
616
704
|
xmemcli help quota
|
|
617
705
|
xmemcli help options
|
|
@@ -629,7 +717,7 @@ Nested topics work at any level that exists in the parser (`help org`, `help org
|
|
|
629
717
|
| **Routine API key creation** | Post-onboarding: API Keys UI (CLI opens that flow via `/xmemcli`). |
|
|
630
718
|
| **Billing & plans** | Console only |
|
|
631
719
|
| **Users, roles, org admin** | Console only |
|
|
632
|
-
| **Instance chat
|
|
720
|
+
| **Instance chat** | Console only (trace inspection is `xmemcli trace …`, see [Trace](#trace--the-consoles-operation-traces)) |
|
|
633
721
|
| **Cluster create/delete** | CLI lists clusters; provisioning in Console |
|
|
634
722
|
| **`read --extraction deep`** | Server has no read-depth knob today |
|
|
635
723
|
| **Raw REST proxy** | Curated response fields only |
|
|
@@ -651,6 +739,7 @@ These apply **before** any subcommand (`xmemcli help options`).
|
|
|
651
739
|
|
|
652
740
|
| `--rc-dir` | `XMEM_RC_DIR` | Where `auth login` writes `.xmemrc.json`; reads walk **up** toward `$HOME` |
|
|
653
741
|
| `--binding-dir` | `XMEM_BINDING_DIR` | Where `binding` commands look for `.xmemory.json`; reads walk **up** toward `$HOME` and **merge** every scope |
|
|
742
|
+
| `--console-url` (accepted by `trace …` only; `$XMEM_CONSOLE_URL` is read by `auth status` too) | `XMEM_CONSOLE_URL` | Console origin serving the trace views (inferred for the public and local API pairs; required for any other API origin). A named Console that provably does not pair with the API origin is refused; the refusal names the setting that supplied it, since only `trace` accepts the flag |
|
|
654
743
|
| `--json` | — | Force one JSON document on stdout |
|
|
655
744
|
| `--verbose` | — | Add `trace_id` / `console_url` when present (never SQL, diff plans, or internals) |
|
|
656
745
|
|
|
@@ -672,6 +761,8 @@ and each delivery mechanism turned out to be a fresh bypass.
|
|
|
672
761
|
|
|
673
762
|
**MCP origin resolution (`mcp` only):** `--mcp-url` → `$XMEM_MCP_URL` → the rc's `mcp_url` → default (`https://mcp.xmemory.ai`). The rc rung follows the same escape hatch as the API origin: a key named with `--api-key` / `$XMEM_API_KEY` never inherits an origin from a project file.
|
|
674
763
|
|
|
764
|
+
**Request identification (new in 1.3.0):** every outbound request carries `X-Xmemory-Client: xmemcli/<version> (python <version>; <system>-<machine>)`, e.g. `xmemcli/1.3.0 (python 3.12.4; Linux-x86_64)`. Each field is reduced to letters, digits, `.`, `_` and `-`, so nothing an OS or a patched interpreter reports can forge the note's own punctuation or fail the request when it is sent; a field with nothing usable left reads `unknown`. No hostname is included. The server uses this header to attribute traffic to a client. The CLI sets no `User-Agent` of its own, then or now: the installed `urllib` opener supplies its default, so the `User-Agent` the server sees is `Python-urllib/<version>` and the identity comes from the header above. Direct API requests from 1.2.0 and earlier send neither, so they record `client_name: python-urllib` -- indistinguishable from any third-party Python caller. Reading a drop in that bucket as third-party traffic leaving is therefore wrong; it is old CLIs upgrading. Three outcomes are distinct and must not be read as one figure: a recognised header, the `other` bucket a present but unrecognised header lands in, and an absent property, which means neither header named a client. Before 1.3.0 the email sign-in exchange was the only path that named itself, and it sent a bare `xmemcli/<version>` with no platform hint; it now sends the same header and the same value as every other path. What the server records for it still differs, though, and not because of the CLI: the console-facing calls (email sign-in, token redemption, whoami) reach the accounts API through the console, which rebuilds the request on its own HTTP client and forwards only Cookie, Content-Type, Authorization and Host. `X-Xmemory-Client` does not survive that hop, so those land as `console`. The attribution described above is what direct API calls record.
|
|
765
|
+
|
|
675
766
|
**Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
|
|
676
767
|
|
|
677
768
|
**Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.
|
|
@@ -20,9 +20,12 @@ xmemcli binding list [--autoload]
|
|
|
20
20
|
xmemcli binding add <instance_id> [--tier autoload|available|off] [--scope project|user]
|
|
21
21
|
xmemcli binding remove <instance_id> [--scope project|user]
|
|
22
22
|
xmemcli context [--text] [--max-tokens N] [--no-live-state]
|
|
23
|
+
xmemcli trace ls [--instance ID]… [--trace-type T]… [--hours-back N] [--user-id U] [--api-key-id K] [--limit N] [--cursor TS_MS:TRACE_ID] [--counts-only]
|
|
24
|
+
xmemcli trace ls --search [--text …] [--request …] [--response …] [--object NAME]… [--field NAME]… [--status S]… [--trace-id ID]… [--session-id S] [--instance ID] [--trace-type T]… [--hours-back N]
|
|
25
|
+
xmemcli trace get <trace_id> [--trace-type write|read|create|update|evolution]
|
|
23
26
|
```
|
|
24
27
|
|
|
25
|
-
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`.
|
|
28
|
+
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`. `trace …` additionally take `--console-url` / `$XMEM_CONSOLE_URL` (the Console origin that serves them; inferred for the public and local pairs).
|
|
26
29
|
|
|
27
30
|
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. The `mcp` transport resolves its own origin the same way: `--mcp-url` → `$XMEM_MCP_URL` → rc `mcp_url` → default, where `mcp_url` is what the API told `auth login` about itself.
|
|
28
31
|
|
|
@@ -40,6 +43,8 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
40
43
|
| `xmemcli read --read-mode single` (default) | `mode: "single-answer"` |
|
|
41
44
|
| `xmemcli read --read-mode raw` | `mode: "raw-tables"` |
|
|
42
45
|
| `xmemcli read --read-mode xresponse` | `mode: "xresponse"` |
|
|
46
|
+
| `xmemcli read --related-types` | `include_related_types: "types"` — the response's `related_types` object is kept in the curated output |
|
|
47
|
+
| `xmemcli read --related-types-depth 2` | `include_related_types: "types"` and `related_types_depth: 2` — the catalog entries carry their `distance` and, below the last level, their own edges |
|
|
43
48
|
|
|
44
49
|
`--read-mode` selects the **server-side answer shape**; `--json` and the TTY rule govern stdout format.
|
|
45
50
|
|
|
@@ -47,6 +52,30 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
47
52
|
|
|
48
53
|
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.
|
|
49
54
|
|
|
55
|
+
**`trace …` is the one deliberate exception.** Those commands return the Console's own operation documents — the paged operations list, the Inspect search, and the per-operation detail — **verbatim**, because the Console already curates them per caller (a super admin sees LLM calls and raw token counts; everyone else does not). They are JSON on every stream and carry every internal id the Console does: `trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id`, and on detail documents `source_id`, `migration_id`, `llm_call_id`, `gateway_request_id`, …
|
|
56
|
+
|
|
57
|
+
## Trace — the Console's operation traces, from the CLI
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
xmemcli auth status # super_admin: which Console view trace will answer with
|
|
61
|
+
xmemcli org list instances # the instances this key can see — what trace ls is scoped to
|
|
62
|
+
xmemcli trace ls # every visible instance, newest first, one page
|
|
63
|
+
xmemcli trace ls --instance "$ID" --trace-type write --trace-type read --limit 50
|
|
64
|
+
xmemcli trace ls --counts-only --hours-back 168 # counts, per-instance counts and series, no rows
|
|
65
|
+
xmemcli trace ls --search --text "Ada" --instance "$ID" # Inspect search — opt-in, more expensive
|
|
66
|
+
xmemcli trace get 0001_abc --trace-type write # one operation, every internal id
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Paging is a cursor the previous page returns:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
PAGE=$(xmemcli trace ls --instance "$ID" --limit 100)
|
|
73
|
+
NEXT=$(echo "$PAGE" | jq -r '"\(.next_before_ts_ms):\(.next_before_trace_id)"')
|
|
74
|
+
xmemcli trace ls --instance "$ID" --limit 100 --cursor "$NEXT"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`--search` has no cursor — the Console's Inspect endpoint doesn't page; it returns the newest matches up to a ceiling and the CLI warns when a response sits at it. A key sees the operations of the instances it can list — never more than its owner sees in the Console; a super admin's key sees what a super admin sees. Full reference: [](#trace--the-consoles-operation-traces).
|
|
78
|
+
|
|
50
79
|
## Output personality — JSON vs pretty
|
|
51
80
|
|
|
52
81
|
- **Non-TTY stdout** or **`--json`**: exactly one JSON document.
|
|
@@ -91,7 +120,7 @@ xmemcli version
|
|
|
91
120
|
xmemcli auth login
|
|
92
121
|
```
|
|
93
122
|
|
|
94
|
-
Version: **1.
|
|
123
|
+
Version: **1.3.0**.
|
|
95
124
|
|
|
96
125
|
|
|
97
126
|
---
|
|
@@ -191,7 +220,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
191
220
|
|
|
192
221
|
# `xmemcli` — capabilities reference
|
|
193
222
|
|
|
194
|
-
**Version:** `1.
|
|
223
|
+
**Version:** `1.3.0` (see `xmemcli version`)
|
|
195
224
|
|
|
196
225
|
`xmemcli` is the command-line client for the **xmemory HTTP API**. It is **human-approved for key acquisition**: an AI-assisted IDE may run reads, writes, and inventory, and may initiate `auth login`, but a key is only issued after one explicit human approval — a click in the Console UI (PKCE handoff) or an approval of a sign-in email (`--email`). Quota limits change only in the **Console UI**. The full acquisition contract is in the auth-flow reference bundled with this package.
|
|
197
226
|
|
|
@@ -211,7 +240,7 @@ Every command below has dedicated help — see [Help](#help).
|
|
|
211
240
|
|---------|-------------|
|
|
212
241
|
| **`write`** `<text>` | Sync write (`POST …/write`). `--extraction fast\|deep` (default `fast`). `--no-wait` queues async write, returns `write_id`. `--scope TYPE:ID` (repeatable) anchors the write to existing objects: only they may be modified/deleted, new objects and links to them are allowed, anything else fails; fast extraction only. |
|
|
213
242
|
| **`write-status`** `<write_id>` … | Poll async writes until terminal or `--timeout` (default 60s). Accepts multiple ids. |
|
|
214
|
-
| **`read`** `<query>` | Read from active instance. `--read-mode single\|raw\|xresponse`, `--scope TYPE:ID` (repeatable), `--scope-relations
|
|
243
|
+
| **`read`** `<query>` | Read from active instance. `--read-mode single\|raw\|xresponse`, `--scope TYPE:ID` (repeatable), `--scope-relations`, `--related-types` (also return `related_types`: per object type the read touched, the fields it did not return and the object types a declared relation links it to; needs `instance.get_own` on the key), `--related-types-depth N` (1, 2 or 3, refused as a usage error otherwise; follow the relations N levels out, each catalog entry saying its distance; implies `--related-types`). |
|
|
215
244
|
|
|
216
245
|
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)).
|
|
217
246
|
|
|
@@ -224,7 +253,7 @@ Permission-filtered view of what this API key can see.
|
|
|
224
253
|
| Command | Description |
|
|
225
254
|
|---------|-------------|
|
|
226
255
|
| **`org list clusters`** | Clusters visible to this credential |
|
|
227
|
-
| **`org list instances`** | Instances visible to this credential |
|
|
256
|
+
| **`org list instances`** | Instances visible to this credential. A key minted with `data.read` but not `instance.get_own` (the supported shape for `trace …`) gets the ids only, with a warning saying so — the same set `trace ls` is scoped to. |
|
|
228
257
|
| **`org list keys`** | API keys visible to this credential (metadata only; plaintext never returned) |
|
|
229
258
|
|
|
230
259
|
---
|
|
@@ -411,27 +440,77 @@ so a typo fails loudly rather than being ignored.
|
|
|
411
440
|
|
|
412
441
|
---
|
|
413
442
|
|
|
443
|
+
### Trace — the Console's operation traces
|
|
444
|
+
|
|
445
|
+
What the Console shows on an instance's **Operations** tab (a paged list), on **Inspect** (a
|
|
446
|
+
search across operations), and in each operation's detail pane — fetched from the same Console
|
|
447
|
+
endpoints, with this CLI's API key, and emitted **as the Console serves them**. Nothing is curated
|
|
448
|
+
or reshaped: the Console already curates per caller, and a field the Console adds shows up here
|
|
449
|
+
the same day. These documents are for scripts and agents; on a terminal they are only indented
|
|
450
|
+
and coloured (`NO_COLOR` / `FORCE_COLOR` honoured). Flags only — there is no query language to
|
|
451
|
+
learn; the CLI composes the Console's search clauses itself.
|
|
452
|
+
|
|
453
|
+
| Command | Console view | Description |
|
|
454
|
+
|---------|--------------|-------------|
|
|
455
|
+
| **`trace ls`** | Operations tab / Home | Operations, newest first, across every instance this credential can see (nothing given — the Home view), or the `--instance ID` given (repeatable, or a comma list; default `--instance-id` / `$XMEM_INSTANCE_ID`). Returns `operations[]`, `counts`, `instance_counts`, `series`, and the paging cursor `next_before_ts_ms` + `next_before_trace_id` (with `has_more_operations`). Flags: `--trace-type T` (repeatable / comma; `write`, `read`, `create`, `update`, `evolution`), `--hours-back N` (server default 24, `0` = unbounded) or `--start-ts` / `--end-ts` (epoch seconds), `--user-id`, `--api-key-id`, `--include-console-reads`, `--bucket-ms`, `--limit N` (default 100, Console cap 500), `--cursor TS_MS:TRACE_ID` (the previous page's two halves, joined with a colon), `--counts-only` (`ops_limit=0`: what Home fetches — counters and series, no rows; refused together with `--limit` / `--cursor`, which would have nothing to page). |
|
|
456
|
+
| **`trace ls --search`** | Inspect | The Inspect search, for the filters only it can answer: `--text` (free text), `--request` / `--response` (text on either side of the operation), `--object NAME` / `--field NAME` (repeatable; all must match), `--status S` (repeatable / comma; any may match: `ok`, `error`, `stale`, `timed_out`, `internal_error`, `quota_exceeded`, `in_progress`, `schema-gap`), `--trace-id ID` (repeatable / comma), `--session-id`; plus `--instance` (at most one — the Inspect scope), `--trace-type`, `--hours-back` (a floor applied to the hits *after* the search — Inspect's own recency windows decide the candidate pool, so this narrows a result, unlike the query window it is on `trace ls`), `--user-id`, `--api-key-id`. Returns the Inspect document: `merged[]` hits with `kind`, `href` (`/write/{trace}`, `/read/{trace}`, `/create/…`, `/update/…`, `/evolution/…`), `trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id` / `api_key_name` / `api_key_prefix`, `status`, `time_taken_ms`, `match_items`. |
|
|
457
|
+
| **`trace get`** `<trace_id>` | Detail pane | One operation's full document. `--trace-type` (`operation_type` on a `trace ls` row; the first path segment of a `--search` hit's `href`) makes it one request; without it the five detail endpoints are probed in order `write, read, create, update, evolution` and the first that knows the trace answers. A miss — the Console's `{"found": false}`, or its 404 for a trace outside your scope — is `stage: "not_found"`, exit **3**. |
|
|
458
|
+
|
|
459
|
+
**`--search` is opt-in, on purpose.** The Inspect search runs text predicates across the trace store with a candidate pool of thousands and a merge — markedly more expensive than the indexed, cursor-paged `trace ls`. Passing a search-only flag (`--text`, `--request`, `--response`, `--object`, `--field`, `--status`, `--trace-id`, `--session-id`) without `--search` is a usage error (exit **2**) whose message says exactly that, so an agent reasons about whether it needs the search or whether `trace ls` narrowed by `--instance` / `--trace-type` / `--hours-back` plus `trace get` will do. Conversely `--limit`, `--cursor`, `--counts-only`, `--start-ts` / `--end-ts`, `--bucket-ms` are refused with `--search` because the Inspect endpoint cannot honour them, and `--include-console-reads` because Inspect's only spelling of that toggle is a bare phrase inside `q` itself — which is also why a free-text value containing the words "include console reads" is refused rather than sent (the Console would flip the toggle and strip the phrase from the query).
|
|
460
|
+
|
|
461
|
+
**The pagination gap — read this before scripting `--search`.** `trace ls` pages properly (the Console's Operations tab does). `trace ls --search` does **not**: the Console's Inspect endpoint has no cursor — it returns the newest 50 matches when no filter narrows it (latest-activity mode) and up to 5 000 matches per source once filtered, and stops; the Console's own Inspect view has the same cliff. So `--search` takes no `--cursor` / `--limit`, and when a response sits at one of those ceilings the CLI emits a **warning** (stderr, and folded into the JSON document's `warnings` array) saying the result was cut and how to narrow it. The warning is **best-effort** — the warning is one-sided by construction: it fires on the count the client can see, and the Console narrows *after* its per-source row caps (type/status resolution, console-read exclusion, the caller's instance allowlist), so a capped response can shrink below the ceiling — even to near-empty for a scoped key searching without an instance filter — with no client-visible signal. A missing warning is not proof of completeness — and a warning is not proof of a cut: the 5 000 figure is *per source*, while the count the warning tests is the cross-source merge (truncated only in latest-activity mode), so several uncapped sources together can trip it on a complete result. Until the Console endpoint pages (and reports truncation itself), treat **every** `--search` result as "the newest matches", not "all matches".
|
|
462
|
+
|
|
463
|
+
**Two document shapes.** `trace ls` returns the Operations-tab document; `trace ls --search` returns the Inspect document (above). They differ because the Console's two backends differ and this CLI does not reshape what the Console serves. Both carry the same identifiers per row (`trace_id`, `instance_id`, `session_id`, `user_id`, `api_key_id`, `status`), which is what `trace get` needs next.
|
|
464
|
+
|
|
465
|
+
**Every operation row carries its internal ids.** `operations[]` rows have `operation_type`, `trace_id`, `instance_id`, `timestamp`, `status`, `is_success`, `error_code`, `session_id`, `user_id`, `user_email`, `api_key_id`, `api_key_name`, `api_key_prefix`, `time_taken_ms`, `text_snippet`, `object_names`, `field_names`.
|
|
466
|
+
|
|
467
|
+
> **Three of those never resolve for an API key**, super admin or not, and this is by design rather than a gap. `user_email` is always absent: the accounts `/users` lookup is closed to keys (an agent may learn who *it* is, never enumerate the organisation's users), so rows keep `user_id` and you resolve names yourself if you need them — and a `User:<email>` search clause matches nothing; filter by `user_id`. `api_key_name` / `api_key_prefix` resolve only for the caller's **own** keys, because `GET /api-keys` scopes a key caller to its owner's keys. So a super admin's key does *not* see literally everything its owner's browser sees: it sees the same rows and the same fields, with those three enrichments empty. Everything scope-bearing — which instances, which detail fields, `llm_calls`, xuid substitution — is identical. Detail documents add what the Console's pane shows: `source_id`, `write_mode` / `read_mode`, `scope`, `extracted_objects`, `diff_plan` (sanitised unless super admin), `written_objects`, `migration_id`, `proposal_version`, `schema_version`, `stage`, `triggered_evolution {migration_id, evolution_trace_id, update_trace_id}`, and — **unrestricted callers only** (a super admin, or a browser session on a `KINGDOM_LOCAL` Console) — `llm_calls[]` (`llm_call_id`, `gateway_request_id`, `model_type`, `provider`, …), `full_error_message`, raw token counts, unsanitised xuids, `sql_queries`, `ddl_queries`, and `schema_generation`.
|
|
468
|
+
|
|
469
|
+
**A scoped key gets `[]` for `sql_queries` / `ddl_queries` / `schema_generation`, not the rows.** Those three come from `read_sql_queries`, `instance_create_ddl_queries` and `schema_generate_*`, which carry no instance ownership column — `schema_generate_*`'s trace id is client-supplied — so they cannot be scoped in SQL and are served only to callers with no instance restriction at all. This is a deliberate fail-closed choice, not a bug to work around: an empty array means "not available to this credential", never "this operation ran no SQL". `llm_calls[]` behaves the same way and always has. **Xuids are hidden for every non-super-admin**, including on writes that carry no identity map — the substitution is done by the Console, server-side, so a caller with no browser sees `#hidden` rather than a raw id.
|
|
470
|
+
|
|
471
|
+
**Who sees what — the Console decides; `auth status` tells you which view to expect.**
|
|
472
|
+
|
|
473
|
+
- A regular key sees the operations of the instances the accounts API lists for it — the same set `org list instances` prints. That is at most what its owner sees in the Console, never more.
|
|
474
|
+
- Access requires the key's role to carry **`data.read`** — the same permission the data-read endpoint requires. A role deliberately scoped away from data (`auth status` shows the codes under `permissions`) is refused with **403 → exit 3, stage `permission`**, whoever owns the key — a permission denial, not a bad credential: rotating or re-issuing the key cannot fix a role that was scoped this way on purpose. An unknown or missing key is the separate 401 → exit 4 (`auth`).
|
|
475
|
+
- A key whose owner is a **super admin** sees the super-admin Console: every organisation's instances, no instance filter, and the super-admin-only detail fields above. The accounts `is_super_admin` flag decides — the key sees the same rows and the same scope-bearing fields its owner's browser session sees, minus the three enrichments no key resolves (`user_email`, and `api_key_name` / `api_key_prefix` for other people's keys; see the note above). `auth status` reports it as `super_admin`.
|
|
476
|
+
- No new permission is involved: the Console answers a key exactly as it would answer its owner's browser session, narrowed to the key's instances. It never mints a session for a key. Filters that need to look other users up (`User:`-style email filters) are not offered: an API key can identify only itself, never list or look up users — filter by `--user-id`.
|
|
477
|
+
|
|
478
|
+
**The Console origin.** `trace …` talk to the Console, a second origin. It is `--console-url` → `$XMEM_CONSOLE_URL` → inferred: `https://console.xmemory.ai` for the default API, `:8002` for a local `:8000`. A key bound to any other API origin gets a usage error (exit **2**) rather than a guess — the credential-binding rule holds across origins.
|
|
479
|
+
|
|
480
|
+
**Parity.** These commands are the Console's data path, not a copy of it. If the Console gains a filter or a field, it is added to the same list here and is available; the MCP server exposing the same data is deferred and will follow the same rule (see the note in the MCP server source).
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
414
484
|
### Auth & meta
|
|
415
485
|
|
|
416
486
|
| Command | Description |
|
|
417
487
|
|---------|-------------|
|
|
418
488
|
| **`auth login`** | PKCE via Console `/xmemcli/<challenge>` → `.xmemrc.json`. `--no-browser`, `--timeout`, `--console-url`, `--callback-port` (pins the loopback callback port for `ssh -L`; `0` or omitted means any free port, otherwise 1024–65535; also `$XMEM_CALLBACK_PORT`, which is read globally). With `--email ADDR` (+ optional `--org-name`, used only when a new account is created; requires `--email`): headless email sign-in — one emailed approval, then the CLI mints its own org-wide key named `CLI email login (<hostname>, <timestamp>)`; details in the auth-flow reference. New-organisation creation additionally requires the deployment to allow public registration. Payload shapes: a fresh email mint carries `method: email`, `key_name`, `api_role`, and `org_created`; the browser path carries `method: pkce` (no role/org fields); a matching `--api-key` or rc credential short-circuits to `status: already_authorized` with `source`: flag / rc (`persisted: false` on the flag arm, which saves nothing); a matching `$XMEM_API_KEY` is copied into the rc and reports `status: authorized`, `source: environment`. Exit codes follow the standard HTTP contract (401 → 4 with stage `auth`, 403 → 3 with stage `permission`, other HTTP/network/server-state failures → 3). |
|
|
419
489
|
| **`auth logout`** | Remove active `.xmemrc.json` |
|
|
420
|
-
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix. |
|
|
490
|
+
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix + `super_admin` (whether the key's owner is a super admin — what decides which Console view `trace …` return) + `permissions` (the key's own capability codes; `trace …` requires `data.read` among them). |
|
|
421
491
|
| **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
|
|
422
492
|
| **`version`** | CLI release version (no network) |
|
|
423
493
|
| **`status`** | Local health: version + whether `.xmemrc.json` exists, and the `mcp_url` it declares when it declares one (no network) |
|
|
424
494
|
|
|
425
495
|
**`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
|
|
426
496
|
JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
|
|
427
|
-
CLI's credential attached, and writes the answer back.
|
|
428
|
-
`claude mcp add <name> -- xmemcli mcp <instance-id>` (
|
|
429
|
-
client)
|
|
497
|
+
CLI's credential attached, and writes the answer back. The Console renders the
|
|
498
|
+
registration as `claude mcp add <name> -- xmemcli mcp <instance-id>` (with
|
|
499
|
+
`--mcp-url <origin>` appended when the MCP origin is not the client's own default), or the
|
|
500
|
+
equivalent for another client — **replace the bare `xmemcli` with its absolute path, in
|
|
501
|
+
double quotes**. The client stores the command as written and resolves it against its own
|
|
502
|
+
`PATH` every time it starts the server, and that `PATH` need not carry the directory the
|
|
503
|
+
tool was installed into; registered bare, it fails there — Claude Code reports
|
|
504
|
+
`ENOENT: Executable not found in $PATH: "xmemcli"`. `command -v xmemcli` prints the path on
|
|
505
|
+
macOS and Linux, `(Get-Command xmemcli).Source` in PowerShell on Windows. If the bare name
|
|
506
|
+
is registered already, remove that server entry before adding it again: Claude Code refuses
|
|
507
|
+
`mcp add` for a name that already exists rather than replacing it.
|
|
430
508
|
|
|
431
509
|
It exists because an MCP client can reference an environment variable in an
|
|
432
510
|
`Authorization` header but cannot read `.xmemrc.json`. Reading the credential per
|
|
433
|
-
connection means
|
|
434
|
-
written today still works in a session opened next week with an empty
|
|
511
|
+
connection means no credential is captured when the server is registered, so a
|
|
512
|
+
configuration written today still works in a session opened next week with an empty
|
|
513
|
+
environment.
|
|
435
514
|
|
|
436
515
|
**Where a file-held key may be sent.** The MCP origin is configured separately from the API
|
|
437
516
|
origin, and a project `.mcp.json` arrives by `git clone` — it controls that server's `args` and
|
|
@@ -496,6 +575,7 @@ Quota **increases** are Console-only — see [What `xmemcli` does not do](#what-
|
|
|
496
575
|
| **5** | Timeout |
|
|
497
576
|
| **6** | Quota exhausted (API quota-exceeded response; `stage: "quota"`) |
|
|
498
577
|
| **7** | Quota headroom below `--fail-if-under` |
|
|
578
|
+
| **8** | Write still processing — the API's wait window expired while the write kept running (HTTP `200` envelope; `stage: "processing"`). Not a failure: poll `write-status`, never retry |
|
|
499
579
|
| **130** | Interrupted (Ctrl+C) |
|
|
500
580
|
|
|
501
581
|
### HTTP status → exit code and JSON `stage`
|
|
@@ -530,6 +610,7 @@ condition, and the hook author is the one reading stderr.
|
|
|
530
610
|
| **403** | **3** | `permission` | **Bad authorization** — key is valid but this key/role cannot perform the operation |
|
|
531
611
|
| **Other 4xx/5xx** | **3** | `request` | Request failed for another reason (404, 422, 5xx, …) |
|
|
532
612
|
| **402** (quota exhausted) | **6** | `quota` | Quota window exhausted — human must extend quota in Console |
|
|
613
|
+
| **any status** with `errors[0].code == WRITE_STILL_PROCESSING` | **8** | `processing` | Write is still running — the write did not fail. Matched on the **code**, whatever the status: a keepalive-flattened **200** and an unflattened **500** both exit 8, so this row overrides the 4xx/5xx row above. Poll `write-status <write_id>`; a retry starts a second write |
|
|
533
614
|
|
|
534
615
|
#### How to interpret failures
|
|
535
616
|
|
|
@@ -537,12 +618,16 @@ condition, and the hook author is the one reading stderr.
|
|
|
537
618
|
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.
|
|
538
619
|
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.
|
|
539
620
|
|
|
540
|
-
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.
|
|
621
|
+
Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, `not_found` for `trace get`, …) while still using exit **3** unless the row above applies. `write` adds `processing`, which is in the table above and exits **8** rather than **3**. 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.
|
|
541
622
|
|
|
542
|
-
**Envelope errors** (HTTP **200** with non-empty `errors[]`) are not HTTP 401/403; they typically exit **3** with `stage: "request"`.
|
|
623
|
+
**Envelope errors** (HTTP **200** with non-empty `errors[]`) are not HTTP 401/403; they typically exit **3** with `stage: "request"`. Two exceptions. `QUOTA_EXCEEDED` exits **6** with `stage: "quota"`, the same as it does on a real HTTP 402 — the keepalive flattening does not change the exit code — and this applies to every command that classifies the error, not just `write` and `read`. `WRITE_STILL_PROCESSING` exits **8** with `stage: "processing"` and is not a failure at all; unlike the quota rung this one is **`write`-only**, because its guidance is to poll `write-status`, which is meaningless for any other verb — elsewhere it falls through to **3**/`request`.
|
|
624
|
+
|
|
625
|
+
> **Known bug.** Some subcommands in the `instance`, `schema` and `xmd` groups — `instance get`, `schema get` and `xmd generate` among them — unwrap the response outside their error handling, so an envelope error never reaches the classifier and escapes as an uncaught exception: a traceback and shell exit **1**, which is not in the table above. Those report **1**, not the **6** this section describes, until that is fixed. It is not whole groups: `instance instructions`, for example, reaches the classifier and exits **6** as documented.
|
|
543
626
|
|
|
544
627
|
**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.
|
|
545
628
|
|
|
629
|
+
**New in 1.3.0:** exit **8** with `stage: "processing"` for a `write` whose server-side wait window expired while the write kept running. In `--json` mode that document also carries a `write_id` field (omitted when the server sent no handle), so a script polls with it directly instead of parsing the message. Additive — no previously documented mapping changed — but a script that reads “non-zero, and not a code I recognise” as failed-and-retryable will now retry a write that has **not** failed, starting a second one the account pays for. Branch on **8** (or on `stage`) and poll `xmemcli write-status <write_id>` until it settles.
|
|
630
|
+
|
|
546
631
|
**New in 1.2.0:** `instance templates` lists the ready-made instance shapes the service ships, with the prose to choose between them, and `instance create --template <id>` builds one from a pinned schema the server already holds. No schema is generated on that path: no description to compose, no `xmd generate` hop, and the instance comes out identical to the one the console would have made from the same template. (Creating an instance still generates its agent brief afterwards, on this path as on any other.)
|
|
547
632
|
|
|
548
633
|
**New in 1.1.0:** `auth login` records the deployment's own MCP origin in `.xmemrc.json` as `mcp_url` (omitted where the server reports none), and `mcp` both connects there by default and accepts it as the pair its key may reach. Requires 0.0.12 or newer to read such a file — see the note below.
|
|
@@ -604,6 +689,9 @@ xmemcli help xmd validate
|
|
|
604
689
|
xmemcli help xmd validate-yaml
|
|
605
690
|
xmemcli help xmd validate-json
|
|
606
691
|
xmemcli help xmd convert
|
|
692
|
+
xmemcli help trace
|
|
693
|
+
xmemcli help trace ls
|
|
694
|
+
xmemcli help trace get
|
|
607
695
|
xmemcli help auth login
|
|
608
696
|
xmemcli help quota
|
|
609
697
|
xmemcli help options
|
|
@@ -621,7 +709,7 @@ Nested topics work at any level that exists in the parser (`help org`, `help org
|
|
|
621
709
|
| **Routine API key creation** | Post-onboarding: API Keys UI (CLI opens that flow via `/xmemcli`). |
|
|
622
710
|
| **Billing & plans** | Console only |
|
|
623
711
|
| **Users, roles, org admin** | Console only |
|
|
624
|
-
| **Instance chat
|
|
712
|
+
| **Instance chat** | Console only (trace inspection is `xmemcli trace …`, see [Trace](#trace--the-consoles-operation-traces)) |
|
|
625
713
|
| **Cluster create/delete** | CLI lists clusters; provisioning in Console |
|
|
626
714
|
| **`read --extraction deep`** | Server has no read-depth knob today |
|
|
627
715
|
| **Raw REST proxy** | Curated response fields only |
|
|
@@ -643,6 +731,7 @@ These apply **before** any subcommand (`xmemcli help options`).
|
|
|
643
731
|
|
|
644
732
|
| `--rc-dir` | `XMEM_RC_DIR` | Where `auth login` writes `.xmemrc.json`; reads walk **up** toward `$HOME` |
|
|
645
733
|
| `--binding-dir` | `XMEM_BINDING_DIR` | Where `binding` commands look for `.xmemory.json`; reads walk **up** toward `$HOME` and **merge** every scope |
|
|
734
|
+
| `--console-url` (accepted by `trace …` only; `$XMEM_CONSOLE_URL` is read by `auth status` too) | `XMEM_CONSOLE_URL` | Console origin serving the trace views (inferred for the public and local API pairs; required for any other API origin). A named Console that provably does not pair with the API origin is refused; the refusal names the setting that supplied it, since only `trace` accepts the flag |
|
|
646
735
|
| `--json` | — | Force one JSON document on stdout |
|
|
647
736
|
| `--verbose` | — | Add `trace_id` / `console_url` when present (never SQL, diff plans, or internals) |
|
|
648
737
|
|
|
@@ -664,6 +753,8 @@ and each delivery mechanism turned out to be a fresh bypass.
|
|
|
664
753
|
|
|
665
754
|
**MCP origin resolution (`mcp` only):** `--mcp-url` → `$XMEM_MCP_URL` → the rc's `mcp_url` → default (`https://mcp.xmemory.ai`). The rc rung follows the same escape hatch as the API origin: a key named with `--api-key` / `$XMEM_API_KEY` never inherits an origin from a project file.
|
|
666
755
|
|
|
756
|
+
**Request identification (new in 1.3.0):** every outbound request carries `X-Xmemory-Client: xmemcli/<version> (python <version>; <system>-<machine>)`, e.g. `xmemcli/1.3.0 (python 3.12.4; Linux-x86_64)`. Each field is reduced to letters, digits, `.`, `_` and `-`, so nothing an OS or a patched interpreter reports can forge the note's own punctuation or fail the request when it is sent; a field with nothing usable left reads `unknown`. No hostname is included. The server uses this header to attribute traffic to a client. The CLI sets no `User-Agent` of its own, then or now: the installed `urllib` opener supplies its default, so the `User-Agent` the server sees is `Python-urllib/<version>` and the identity comes from the header above. Direct API requests from 1.2.0 and earlier send neither, so they record `client_name: python-urllib` -- indistinguishable from any third-party Python caller. Reading a drop in that bucket as third-party traffic leaving is therefore wrong; it is old CLIs upgrading. Three outcomes are distinct and must not be read as one figure: a recognised header, the `other` bucket a present but unrecognised header lands in, and an absent property, which means neither header named a client. Before 1.3.0 the email sign-in exchange was the only path that named itself, and it sent a bare `xmemcli/<version>` with no platform hint; it now sends the same header and the same value as every other path. What the server records for it still differs, though, and not because of the CLI: the console-facing calls (email sign-in, token redemption, whoami) reach the accounts API through the console, which rebuilds the request on its own HTTP client and forwards only Cookie, Content-Type, Authorization and Host. `X-Xmemory-Client` does not survive that hop, so those land as `console`. The attribution described above is what direct API calls record.
|
|
757
|
+
|
|
667
758
|
**Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
|
|
668
759
|
|
|
669
760
|
**Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.
|
|
@@ -51,7 +51,7 @@ from ._login import (
|
|
|
51
51
|
stdin_is_interactive,
|
|
52
52
|
validate_api_key_shape,
|
|
53
53
|
)
|
|
54
|
-
from ._version import CLI_NAME,
|
|
54
|
+
from ._version import CLI_NAME, CLIENT_HEADER, client_identity
|
|
55
55
|
|
|
56
56
|
# Console-origin endpoints. The five sign-in routes authenticate by cookie proofs alone; the
|
|
57
57
|
# rest require the minted session cookie, which the opener's in-memory jar replays.
|
|
@@ -392,7 +392,7 @@ class EmailSignin:
|
|
|
392
392
|
url = f"{self.console}{path}"
|
|
393
393
|
safe_url = url.split("?", 1)[0]
|
|
394
394
|
deadline = time.monotonic() + timeout_s
|
|
395
|
-
headers = {"Accept": "application/json",
|
|
395
|
+
headers = {"Accept": "application/json", CLIENT_HEADER: client_identity()}
|
|
396
396
|
data = None
|
|
397
397
|
if body is not None:
|
|
398
398
|
headers["Content-Type"] = "application/json"
|
|
@@ -859,9 +859,7 @@ def _acquire_api_key(
|
|
|
859
859
|
raise
|
|
860
860
|
|
|
861
861
|
|
|
862
|
-
def _cancel_and_report(
|
|
863
|
-
session: EmailSignin, email: str, *, cross_device: bool, notify: Callable[[str], None]
|
|
864
|
-
) -> None:
|
|
862
|
+
def _cancel_and_report(session: EmailSignin, email: str, *, cross_device: bool, notify: Callable[[str], None]) -> None:
|
|
865
863
|
"""Cancel an aborted attempt and tell the user what that actually achieved.
|
|
866
864
|
|
|
867
865
|
Only ``revoked`` on a cross-device attempt makes the emailed link unapprovable; every other
|
|
@@ -883,10 +881,7 @@ def _cancel_and_report(
|
|
|
883
881
|
"elsewhere; check the Console if that was not you."
|
|
884
882
|
)
|
|
885
883
|
elif outcome == "nothing_to_revoke":
|
|
886
|
-
notify(
|
|
887
|
-
"Nothing durable was revoked — the emailed link may stay usable until it expires "
|
|
888
|
-
"(about five minutes)."
|
|
889
|
-
)
|
|
884
|
+
notify("Nothing durable was revoked — the emailed link may stay usable until it expires (about five minutes).")
|
|
890
885
|
else:
|
|
891
886
|
notify(
|
|
892
887
|
"The cancellation could not be confirmed — the emailed link may stay approvable until it "
|