xmemcli 1.0.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.0.0 → xmemcli-1.3.0}/PKG-INFO +159 -24
- {xmemcli-1.0.0 → xmemcli-1.3.0}/README.md +158 -23
- {xmemcli-1.0.0 → xmemcli-1.3.0}/pyproject.toml +1 -1
- {xmemcli-1.0.0 → xmemcli-1.3.0}/uv.lock +1 -1
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/__init__.py +1 -1
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_email_login.py +4 -9
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_help.py +23 -6
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_instance_schema.py +127 -10
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_login.py +92 -30
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_mcp_bridge.py +134 -30
- xmemcli-1.3.0/xmemcli/_org_cluster.py +134 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_output.py +31 -7
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_pretty.py +245 -17
- xmemcli-1.3.0/xmemcli/_rc.py +411 -0
- xmemcli-1.3.0/xmemcli/_trace.py +649 -0
- xmemcli-1.3.0/xmemcli/_version.py +60 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_xmem_bridge.py +9 -2
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/cli.py +1072 -117
- xmemcli-1.0.0/xmemcli/_org_cluster.py +0 -70
- xmemcli-1.0.0/xmemcli/_rc.py +0 -208
- xmemcli-1.0.0/xmemcli/_version.py +0 -4
- {xmemcli-1.0.0 → xmemcli-1.3.0}/.gitignore +0 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_binding.py +0 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_binding_cmd.py +0 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_context_cmd.py +0 -0
- {xmemcli-1.0.0 → xmemcli-1.3.0}/xmemcli/_envelope.py +0 -0
- {xmemcli-1.0.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
|
|
@@ -22,15 +22,20 @@ xmemcli read <query>
|
|
|
22
22
|
xmemcli write <text>
|
|
23
23
|
xmemcli write-status <write_id> [<id> …]
|
|
24
24
|
xmemcli quota
|
|
25
|
+
xmemcli instance templates
|
|
26
|
+
xmemcli instance create --name NAME (--template ID | --schema-file PATH | --schema-stdin)
|
|
25
27
|
xmemcli binding list [--autoload]
|
|
26
28
|
xmemcli binding add <instance_id> [--tier autoload|available|off] [--scope project|user]
|
|
27
29
|
xmemcli binding remove <instance_id> [--scope project|user]
|
|
28
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]
|
|
29
34
|
```
|
|
30
35
|
|
|
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`.
|
|
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).
|
|
32
37
|
|
|
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.
|
|
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.
|
|
34
39
|
|
|
35
40
|
`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
41
|
|
|
@@ -46,6 +51,8 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
46
51
|
| `xmemcli read --read-mode single` (default) | `mode: "single-answer"` |
|
|
47
52
|
| `xmemcli read --read-mode raw` | `mode: "raw-tables"` |
|
|
48
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 |
|
|
49
56
|
|
|
50
57
|
`--read-mode` selects the **server-side answer shape**; `--json` and the TTY rule govern stdout format.
|
|
51
58
|
|
|
@@ -53,6 +60,30 @@ Two local files, same discovery rule, different jobs: `.xmemrc.json` holds the c
|
|
|
53
60
|
|
|
54
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.
|
|
55
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
|
+
|
|
56
87
|
## Output personality — JSON vs pretty
|
|
57
88
|
|
|
58
89
|
- **Non-TTY stdout** or **`--json`**: exactly one JSON document.
|
|
@@ -97,7 +128,7 @@ xmemcli version
|
|
|
97
128
|
xmemcli auth login
|
|
98
129
|
```
|
|
99
130
|
|
|
100
|
-
Version: **1.
|
|
131
|
+
Version: **1.3.0**.
|
|
101
132
|
|
|
102
133
|
|
|
103
134
|
---
|
|
@@ -151,7 +182,7 @@ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never hand
|
|
|
151
182
|
4. With the session, the CLI runs the Console onboarding's core sequence: `GET /api/check-user`; when the address has no account, `GET /api/suggest-slug` (server-derived org name, `--org-name` overrides; ignored for an existing account) and `POST /api/public-signup` — **note that creating a new organisation requires the deployment to allow public registration, which the hosted service currently does not**: an existing account gets a key, a new address gets the server's "organisation creation is not available" refusal (the Console's admin-fallback signup arm is not implemented here); then `GET /api/accounts/users/by-email`, `GET /api/accounts/api-roles` (the seeded default role, or the org's only role — an ambiguous set is refused, never guessed), and `POST /api/accounts/api-keys` with **no cluster list** — the key attaches to every cluster the org has at mint time. Clusters created later are attached by a server-side backfill that repairs only keys already linked to every other cluster in the org; an org with no clusters at mint time gets a key attached to nothing. The key is named **`CLI email login (<hostname>, <timestamp>)`** so every mint stays individually identifiable and revocable on the Console's API Keys page.
|
|
152
183
|
5. The key is written to `.xmemrc.json` (0600), stdout carries the prefix only, and the session cookie dies with the process.
|
|
153
184
|
|
|
154
|
-
The wait is bounded by `--timeout` — 600 seconds for this path by default, which is also a **ceiling**: nothing stays claimable past the server's pending window, so larger values are clamped. The emailed one-click link and six-digit code themselves expire at about five minutes; the wait continues past that because an approval given in time stays claimable, and the terminal says when the emailed artifacts have lapsed. The Console origin must be `https` (or a loopback host, for local development). The Console must also match the API origin the key will be used against: with a non-default `--api-url` the CLI only infers a Console where the layout determines one — a single-origin `/xmem` base names its `/console` sibling on the same host, and that sibling beats `$XMEM_CONSOLE_URL` on the same call — and the local-dev convention pairs `:8000` with `:8002` — otherwise it refuses to guess (pass `--console-url
|
|
185
|
+
The wait is bounded by `--timeout` — 600 seconds for this path by default, which is also a **ceiling**: nothing stays claimable past the server's pending window, so larger values are clamped. The emailed one-click link and six-digit code themselves expire at about five minutes; the wait continues past that because an approval given in time stays claimable, and the terminal says when the emailed artifacts have lapsed. The Console origin must be `https` (or a loopback host, for local development). The Console must also match the API origin the key will be used against: with a non-default `--api-url` the CLI only infers a Console where the layout determines one — a single-origin `/xmem` base names its `/console` sibling on the same host, and that sibling beats `$XMEM_CONSOLE_URL` on the same call — and the local-dev convention pairs `:8000` with `:8002` — otherwise it refuses to guess (pass `--console-url`; `$XMEM_CONSOLE_URL` only when `--api-url` was not given). Because that inference exists, a single-origin deployment's rendered sign-in command names the API alone; every other layout still renders the `--api-url`/`--console-url` pair, since naming one without the other is worse than naming neither (`api/agent_setup_renderer.py`), and an explicitly named Console that provably mismatches the API origin (mixed loopback-ness, or a default-production half paired with a non-default one) is refused rather than trusted.
|
|
155
186
|
|
|
156
187
|
When the server offers no matching code on the request reply (cross-device hand-off disabled, no public Console origin configured, or a transient degradation), there is nothing for a headless starter to claim — the emailed link then signs in the *approving* browser — so the CLI falls back to the emailed **six-digit code typed into the terminal** (`POST /api/kingdom-signin-verify`), still a single human action. That fallback needs an interactive stdin; a non-interactive run cancels the attempt it just started (the mode is only known from the server's reply, after the email is on its way) and refuses with instructions — the typed code dies with that cancel, but on such a server the one-click link has nothing durable to revoke and stays redeemable in a browser until it expires (about five minutes), which the terminal states plainly.
|
|
157
188
|
|
|
@@ -163,12 +194,16 @@ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking
|
|
|
163
194
|
|
|
164
195
|
Those three keys are required and must be strings. Anything else in the file is **ignored, not rejected** (since 0.0.12): the rc is the only durable credential store, so a file this CLI cannot load is a CLI that cannot run any command at all — which would make adding a field to the file a breaking change far wider than the feature that added it.
|
|
165
196
|
|
|
166
|
-
|
|
197
|
+
Since 1.1.0 the file may also carry **`mcp_url`** — the MCP resource the API reported for itself when the key was issued, written only when the server names one. It is what lets `xmemcli mcp` reach an on-premise MCP host: that host is in no table the client was built with, and the rc is the one anchor a cloned `.mcp.json` does not control. A blank or non-string value is read as absent. It outranks the single-origin sibling inference below — a deployment naming itself beats a shape we derived — and both beat the built-in default. The mount path is part of the destination: `https://host/mcp` authorises that mount, not the sibling services beside it — and the instance id is canonicalised as a UUID before it is appended, so nothing following the mount can walk back out of it. Every lookup that reads `mcp_url` refuses redirects rather than merely dropping the credential across them, because what it learns is a host the key is later sent to. Every mutation of the file — write, in-place update, and logout's delete — is serialised under an exclusive lock on its containing directory, and an update is additionally bound to the identity *and* origin it was decided from. What protects an in-place update is that comparison, which is portable; the lock narrows what remains — the moment between comparing and replacing — where the platform offers one. A failed record is a warning, a failed de-authorisation fails the command. The enrichment write after login is conditional on the same comparison, so logging out or signing in again while it is in flight is not undone by it.
|
|
198
|
+
|
|
199
|
+
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. A base URL's path is preserved, never stripped — on a single-origin deployment the API lives at `https://host/xmem`, and that mount is part of every request the CLI builds; query, fragment, and userinfo on a base URL refuse normalisation rather than being stripped, so sibling inference cannot run on a shape the caller did not supply. Console resolution for login: `--console-url` → Console pinned by a positional (or mistaken `--api-url`) single-origin URL → `$XMEM_CONSOLE_URL` when `--api-url` was *not* given → sibling inference from the call's `--api-url` when it applies → sibling inference from `$XMEM_API_URL` → default. An explicit `--api-url` suppresses `$XMEM_CONSOLE_URL` for that invocation (a five-host `--api-url` without `--console-url` is a usage error, not a fall-through to the env Console). A named API origin from `$XMEM_API_URL` is held to the same infer-or-refuse rule, so an env-only non-default API without a Console cannot mint at the hosted Console and record that key against the foreign API; an inferred sibling that is remote HTTP is refused under the same HTTPS-or-loopback rule as a positional login URL. The same resolved Console is passed to the post-login `/whoami` that records `mcp_url`, so a leftover `$XMEM_CONSOLE_URL` cannot steer that lookup either. When `--api-url` is present, `$XMEM_API_URL` is not consulted for Console inference. Because a mounted base cannot also serve the `/api`-prefixed spelling of a route, the compatibility candidate in `whoami_urls` / `token_redeem_urls` (`{base}/api/whoami`, `{base}/api/cli/token` — a root-served deployment's second form) is offered only for a path-less base. The MCP bridge resolves its endpoint as `--mcp-url` → `$XMEM_MCP_URL` → the `/mcp` sibling of the effective API base (`--api-url` → `$XMEM_API_URL` → rc → default) → default, and its foreign-origin guard accepts the `/xmem` ↔ `/mcp` sibling pair as one deployment.
|
|
167
200
|
|
|
168
201
|
## Identity — `/whoami`
|
|
169
202
|
|
|
170
203
|
`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.
|
|
171
204
|
|
|
205
|
+
The `AuthApiKey` body also carries **`mcp_url`**, this deployment's own MCP origin (`null` where it serves none). The field is always present, `null` included: an absent key means the server predates it, which is a different answer and is treated as one. `auth login` reads it from there — on both paths, since the email flow never calls `POST /cli/token` — and records it in the rc. The lookup is best-effort: any failure simply leaves the key out of the file, and the transport falls back to its table of shipped API↔MCP pairs.
|
|
206
|
+
|
|
172
207
|
## Security model
|
|
173
208
|
|
|
174
209
|
| Threat | Mitigation |
|
|
@@ -193,7 +228,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
193
228
|
|
|
194
229
|
# `xmemcli` — capabilities reference
|
|
195
230
|
|
|
196
|
-
**Version:** `1.
|
|
231
|
+
**Version:** `1.3.0` (see `xmemcli version`)
|
|
197
232
|
|
|
198
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.
|
|
199
234
|
|
|
@@ -213,7 +248,7 @@ Every command below has dedicated help — see [Help](#help).
|
|
|
213
248
|
|---------|-------------|
|
|
214
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. |
|
|
215
250
|
| **`write-status`** `<write_id>` … | Poll async writes until terminal or `--timeout` (default 60s). Accepts multiple ids. |
|
|
216
|
-
| **`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`). |
|
|
217
252
|
|
|
218
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)).
|
|
219
254
|
|
|
@@ -226,7 +261,7 @@ Permission-filtered view of what this API key can see.
|
|
|
226
261
|
| Command | Description |
|
|
227
262
|
|---------|-------------|
|
|
228
263
|
| **`org list clusters`** | Clusters visible to this credential |
|
|
229
|
-
| **`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. |
|
|
230
265
|
| **`org list keys`** | API keys visible to this credential (metadata only; plaintext never returned) |
|
|
231
266
|
|
|
232
267
|
---
|
|
@@ -236,7 +271,8 @@ Permission-filtered view of what this API key can see.
|
|
|
236
271
|
| Command | Description |
|
|
237
272
|
|---------|-------------|
|
|
238
273
|
| **`instance get`** `<instance_id>` | Fetch one instance |
|
|
239
|
-
| **`instance
|
|
274
|
+
| **`instance templates`** | List the ready-made templates a create can name — id, label, one-line description, who each is for, example writes and reads, and the object-type names. No schema crosses the wire; the server holds the shape. |
|
|
275
|
+
| **`instance create`** | Create from a template or an XMD/JSON schema. Required `--name`; then exactly one of `--template <id>`, `--schema-file` or `--schema-stdin`. `--template` sends only the id: no schema is composed, generated or repaired, so the shape you get is exactly the one you picked. |
|
|
240
276
|
| **`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. |
|
|
241
277
|
| **`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. |
|
|
242
278
|
|
|
@@ -270,6 +306,10 @@ XMD (xmemory Data) is the typed schema format for instances. These commands mirr
|
|
|
270
306
|
#### Typical workflow
|
|
271
307
|
|
|
272
308
|
```text
|
|
309
|
+
# 0. Or skip 1-2 entirely: pick a ready-made shape
|
|
310
|
+
xmemcli instance templates
|
|
311
|
+
xmemcli instance create --name demo --template personal_crm
|
|
312
|
+
|
|
273
313
|
# 1. Synthesize or enhance
|
|
274
314
|
xmemcli xmd generate "people and their pets" -o schema.yml
|
|
275
315
|
xmemcli xmd enhance schema.yml "add a Pet.breed field" -o next.yml --plan-output plan.json
|
|
@@ -408,27 +448,102 @@ so a typo fails loudly rather than being ignored.
|
|
|
408
448
|
|
|
409
449
|
---
|
|
410
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
|
+
|
|
411
492
|
### Auth & meta
|
|
412
493
|
|
|
413
494
|
| Command | Description |
|
|
414
495
|
|---------|-------------|
|
|
415
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). |
|
|
416
497
|
| **`auth logout`** | Remove active `.xmemrc.json` |
|
|
417
|
-
| **`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). |
|
|
418
499
|
| **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
|
|
419
500
|
| **`version`** | CLI release version (no network) |
|
|
420
|
-
| **`status`** | Local health: version + whether `.xmemrc.json` exists (no network) |
|
|
501
|
+
| **`status`** | Local health: version + whether `.xmemrc.json` exists, and the `mcp_url` it declares when it declares one (no network) |
|
|
421
502
|
|
|
422
503
|
**`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
|
|
423
504
|
JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
|
|
424
|
-
CLI's credential attached, and writes the answer back.
|
|
425
|
-
`claude mcp add <name> -- xmemcli mcp <instance-id>` (
|
|
426
|
-
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.
|
|
427
516
|
|
|
428
517
|
It exists because an MCP client can reference an environment variable in an
|
|
429
518
|
`Authorization` header but cannot read `.xmemrc.json`. Reading the credential per
|
|
430
|
-
connection means
|
|
431
|
-
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.
|
|
522
|
+
|
|
523
|
+
**Where a file-held key may be sent.** The MCP origin is configured separately from the API
|
|
524
|
+
origin, and a project `.mcp.json` arrives by `git clone` — it controls that server's `args` and
|
|
525
|
+
`env`, so it is not allowed to vouch for itself. A key read from `.xmemrc.json` therefore reaches
|
|
526
|
+
only the MCP origin that same file records in `mcp_url`, the origin paired with its `api_url` in
|
|
527
|
+
this client's built-in table of shipped deployments, or a loopback host when both ends are on this
|
|
528
|
+
machine. Anywhere else needs a key named on the call with `--api-key`.
|
|
529
|
+
|
|
530
|
+
A recorded origin says *where*, never *how*: a remote one must be `https`, so a hand-written
|
|
531
|
+
`http://` entry is refused rather than sending the key in clear text. Loopback stays exempt — those
|
|
532
|
+
bytes never leave the machine.
|
|
533
|
+
|
|
534
|
+
The **mount path is part of the destination**, not decoration. A deployment serving every route
|
|
535
|
+
group on one hostname puts MCP at `https://host/mcp`, the REST API at `https://host/xmem` and the
|
|
536
|
+
OAuth server at `https://host/oauth2`; an rc recording the first authorises the first only.
|
|
537
|
+
|
|
538
|
+
`auth login` fills in `mcp_url` from what the API reports about itself, which is what makes an
|
|
539
|
+
on-premise deployment work: its MCP host is in no table this client was built with, and every
|
|
540
|
+
attempt to *derive* one host from the other has been wrong in a new way. If the transport refuses
|
|
541
|
+
an origin you expect it to accept, run `auth login` against that deployment again — the rc predates
|
|
542
|
+
the field, or that server does not report one. That works even when the stored key is still good:
|
|
543
|
+
the `--email` path records the origin it learns while checking, and reports it as `mcp_url` on the
|
|
544
|
+
`already_authorized` payload. A deployment that has stopped serving MCP answers `null`, and the
|
|
545
|
+
recorded origin is dropped rather than left authorised — and if that removal cannot be written,
|
|
546
|
+
the command fails rather than reporting success over a host that is still authorised.
|
|
432
547
|
|
|
433
548
|
Two consequences worth knowing:
|
|
434
549
|
|
|
@@ -468,6 +583,7 @@ Quota **increases** are Console-only — see [What `xmemcli` does not do](#what-
|
|
|
468
583
|
| **5** | Timeout |
|
|
469
584
|
| **6** | Quota exhausted (API quota-exceeded response; `stage: "quota"`) |
|
|
470
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 |
|
|
471
587
|
| **130** | Interrupted (Ctrl+C) |
|
|
472
588
|
|
|
473
589
|
### HTTP status → exit code and JSON `stage`
|
|
@@ -502,6 +618,7 @@ condition, and the hook author is the one reading stderr.
|
|
|
502
618
|
| **403** | **3** | `permission` | **Bad authorization** — key is valid but this key/role cannot perform the operation |
|
|
503
619
|
| **Other 4xx/5xx** | **3** | `request` | Request failed for another reason (404, 422, 5xx, …) |
|
|
504
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 |
|
|
505
622
|
|
|
506
623
|
#### How to interpret failures
|
|
507
624
|
|
|
@@ -509,21 +626,31 @@ condition, and the hook author is the one reading stderr.
|
|
|
509
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.
|
|
510
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.
|
|
511
628
|
|
|
512
|
-
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.
|
|
630
|
+
|
|
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`.
|
|
513
632
|
|
|
514
|
-
|
|
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.
|
|
515
634
|
|
|
516
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.
|
|
517
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
|
+
|
|
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.)
|
|
640
|
+
|
|
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.
|
|
642
|
+
|
|
518
643
|
**Compatibility change in 0.0.12:** an `.xmemrc.json` carrying a key this release does not recognise is now **accepted**, and the unknown key ignored; `email`, `api_url`, and `api_key` are still required and still have to be strings. Until 0.0.12 the key set was closed, which made adding a field to the file a breaking change for **every** command rather than for the one that would read it — a CLI that cannot load the rc cannot resolve a credential for anything. Nothing writes an extra key yet; this release exists so that a later one can.
|
|
519
644
|
|
|
520
645
|
**Compatibility change in 0.0.13 (single-origin deployments):** an API base URL's path is now **preserved**, never stripped — `--api-url https://host/xmem` builds requests under `/xmem` instead of reducing to the bare origin. Where the base's path is exactly `/xmem`, the CLI infers its single-origin siblings on the same host: `auth login` finds the Console at `/console` without `--console-url`, and `xmemcli mcp` reaches `/mcp` without `--mcp-url` (explicit `--console-url` / `--mcp-url` and `$XMEM_CONSOLE_URL` / `$XMEM_MCP_URL` still win when inference does not apply; on a call that names `--api-url …/xmem`, sibling inference beats `$XMEM_CONSOLE_URL`; the MCP origin guard accepts the `/xmem` ↔ `/mcp` pair as one deployment). Bare origins and every other path shape behave exactly as before. See `cli/`.
|
|
521
646
|
|
|
522
|
-
Two consequences worth knowing. Connect instructions from the API assume **1.0.0** or newer everywhere.
|
|
647
|
+
Two consequences worth knowing. Connect instructions from the API assume **1.0.0** or newer everywhere, and **1.1.1** on a single-origin deployment — earlier releases let `$XMEM_CONSOLE_URL` steer sign-in and token redemption to a foreign Console. On a single-origin deployment they render `auth login --api-url https://host/xmem` with **no** `--console-url`. The inference is keyed on the conventional mounts (`/xmem`, `/console`, `/mcp`). A deployment that mounts its services under other prefixes is not inferable: name `--console-url` and `--mcp-url` explicitly there, which is what its own rendered commands do.
|
|
648
|
+
|
|
649
|
+
**Console instance URLs (1.0.0+):** `xmemcli auth login https://host/console/instance/<uuid>` infers the API base (`/xmem`) and Console (`/console`) from that page URL on a single-origin host — no extra flags or env vars. The Connect tab paste prompt uses this form. When no ``.xmemrc.json`` exists yet, the CLI stages the inferred ``api_url`` into a placeholder rc as soon as login starts; an existing credential file is never rewritten mid-login. Remote plain HTTP is refused (HTTPS or loopback only) — the same rule as the plain URLs below.
|
|
523
650
|
|
|
524
|
-
**
|
|
651
|
+
**Plain single-origin login URLs (1.1.1+):** `xmemcli auth login https://host/console` or `…/xmem` (trailing slash optional) infer the same sibling pair with no instance id — useful when signing in before you have an instance page open. Remote plain HTTP is refused (HTTPS or loopback only). An existing `.xmemrc.json` credential is never rebound mid-login; only a missing rc gets a placeholder `api_url`.
|
|
525
652
|
|
|
526
|
-
**Compatibility change in 1.0.0:** console instance page URLs for login (above), immediate ``api_url`` staging in ``.xmemrc.json``, and **1.0.0** as the minimum version named by connect setup instructions.
|
|
653
|
+
**Compatibility change in 1.0.0:** console instance page URLs for login (above), immediate ``api_url`` staging in ``.xmemrc.json``, and **1.0.0** as the minimum version named by connect setup instructions. **Revised in 1.1.1:** staging writes only a placeholder when no rc exists (an existing credential file is left alone until login succeeds); single-origin connect instructions name **1.1.1** as their minimum — earlier releases let `$XMEM_CONSOLE_URL` steer the sign-in Console; and HTTPS-or-loopback now applies to every single-origin login URL, including instance pages, not only the new `/console` and `/xmem` forms. A mistaken `--api-url http://host/xmem` is refused the same way.
|
|
527
654
|
|
|
528
655
|
See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
|
|
529
656
|
|
|
@@ -570,6 +697,9 @@ xmemcli help xmd validate
|
|
|
570
697
|
xmemcli help xmd validate-yaml
|
|
571
698
|
xmemcli help xmd validate-json
|
|
572
699
|
xmemcli help xmd convert
|
|
700
|
+
xmemcli help trace
|
|
701
|
+
xmemcli help trace ls
|
|
702
|
+
xmemcli help trace get
|
|
573
703
|
xmemcli help auth login
|
|
574
704
|
xmemcli help quota
|
|
575
705
|
xmemcli help options
|
|
@@ -587,7 +717,7 @@ Nested topics work at any level that exists in the parser (`help org`, `help org
|
|
|
587
717
|
| **Routine API key creation** | Post-onboarding: API Keys UI (CLI opens that flow via `/xmemcli`). |
|
|
588
718
|
| **Billing & plans** | Console only |
|
|
589
719
|
| **Users, roles, org admin** | Console only |
|
|
590
|
-
| **Instance chat
|
|
720
|
+
| **Instance chat** | Console only (trace inspection is `xmemcli trace …`, see [Trace](#trace--the-consoles-operation-traces)) |
|
|
591
721
|
| **Cluster create/delete** | CLI lists clusters; provisioning in Console |
|
|
592
722
|
| **`read --extraction deep`** | Server has no read-depth knob today |
|
|
593
723
|
| **Raw REST proxy** | Curated response fields only |
|
|
@@ -609,12 +739,13 @@ These apply **before** any subcommand (`xmemcli help options`).
|
|
|
609
739
|
|
|
610
740
|
| `--rc-dir` | `XMEM_RC_DIR` | Where `auth login` writes `.xmemrc.json`; reads walk **up** toward `$HOME` |
|
|
611
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 |
|
|
612
743
|
| `--json` | — | Force one JSON document on stdout |
|
|
613
744
|
| `--verbose` | — | Add `trace_id` / `console_url` when present (never SQL, diff plans, or internals) |
|
|
614
745
|
|
|
615
746
|
**Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
|
|
616
747
|
|
|
617
|
-
**`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings
|
|
748
|
+
**`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings, plus an optional `mcp_url` recorded by `auth login`. Keys beyond those are ignored rather than rejected (see the 0.0.12 note above), so a file written by a newer CLI still loads here.
|
|
618
749
|
|
|
619
750
|
**`context` is the one exception, and it is a security boundary.** Every other command walks up
|
|
620
751
|
from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
|
|
@@ -628,6 +759,10 @@ and each delivery mechanism turned out to be a fresh bypass.
|
|
|
628
759
|
|
|
629
760
|
**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.
|
|
630
761
|
|
|
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.
|
|
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
|
+
|
|
631
766
|
**Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
|
|
632
767
|
|
|
633
768
|
**Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.
|