xmemcli 0.0.13__tar.gz → 1.2.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-0.0.13 → xmemcli-1.2.0}/PKG-INFO +59 -11
- {xmemcli-0.0.13 → xmemcli-1.2.0}/README.md +58 -10
- {xmemcli-0.0.13 → xmemcli-1.2.0}/pyproject.toml +1 -1
- {xmemcli-0.0.13 → xmemcli-1.2.0}/uv.lock +1 -1
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/__init__.py +1 -1
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_help.py +15 -6
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_instance_schema.py +126 -10
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_login.py +128 -11
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_mcp_bridge.py +141 -28
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_output.py +8 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_pretty.py +46 -1
- xmemcli-1.2.0/xmemcli/_rc.py +411 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_version.py +1 -1
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/cli.py +673 -66
- xmemcli-0.0.13/xmemcli/_rc.py +0 -190
- {xmemcli-0.0.13 → xmemcli-1.2.0}/.gitignore +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_binding.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_binding_cmd.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_context_cmd.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_email_login.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_envelope.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_org_cluster.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_text.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.2.0}/xmemcli/_xmem_bridge.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xmemcli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.2.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,6 +22,8 @@ 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]
|
|
@@ -30,7 +32,7 @@ xmemcli context [--text] [--max-tokens N] [--no-live-state]
|
|
|
30
32
|
|
|
31
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`.
|
|
32
34
|
|
|
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.
|
|
35
|
+
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
36
|
|
|
35
37
|
`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
38
|
|
|
@@ -97,7 +99,7 @@ xmemcli version
|
|
|
97
99
|
xmemcli auth login
|
|
98
100
|
```
|
|
99
101
|
|
|
100
|
-
Version: **
|
|
102
|
+
Version: **1.2.0**.
|
|
101
103
|
|
|
102
104
|
|
|
103
105
|
---
|
|
@@ -151,7 +153,7 @@ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never hand
|
|
|
151
153
|
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
154
|
5. The key is written to `.xmemrc.json` (0600), stdout carries the prefix only, and the session cookie dies with the process.
|
|
153
155
|
|
|
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 the local-dev convention pairs `:8000` with `:8002` — otherwise it refuses to guess (pass `--console-url`
|
|
156
|
+
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
157
|
|
|
156
158
|
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
159
|
|
|
@@ -163,12 +165,16 @@ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking
|
|
|
163
165
|
|
|
164
166
|
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
167
|
|
|
166
|
-
|
|
168
|
+
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.
|
|
169
|
+
|
|
170
|
+
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
171
|
|
|
168
172
|
## Identity — `/whoami`
|
|
169
173
|
|
|
170
174
|
`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
175
|
|
|
176
|
+
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.
|
|
177
|
+
|
|
172
178
|
## Security model
|
|
173
179
|
|
|
174
180
|
| Threat | Mitigation |
|
|
@@ -193,7 +199,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
193
199
|
|
|
194
200
|
# `xmemcli` — capabilities reference
|
|
195
201
|
|
|
196
|
-
**Version:** `
|
|
202
|
+
**Version:** `1.2.0` (see `xmemcli version`)
|
|
197
203
|
|
|
198
204
|
`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
205
|
|
|
@@ -236,7 +242,8 @@ Permission-filtered view of what this API key can see.
|
|
|
236
242
|
| Command | Description |
|
|
237
243
|
|---------|-------------|
|
|
238
244
|
| **`instance get`** `<instance_id>` | Fetch one instance |
|
|
239
|
-
| **`instance
|
|
245
|
+
| **`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. |
|
|
246
|
+
| **`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
247
|
| **`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
248
|
| **`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
249
|
|
|
@@ -270,6 +277,10 @@ XMD (xmemory Data) is the typed schema format for instances. These commands mirr
|
|
|
270
277
|
#### Typical workflow
|
|
271
278
|
|
|
272
279
|
```text
|
|
280
|
+
# 0. Or skip 1-2 entirely: pick a ready-made shape
|
|
281
|
+
xmemcli instance templates
|
|
282
|
+
xmemcli instance create --name demo --template personal_crm
|
|
283
|
+
|
|
273
284
|
# 1. Synthesize or enhance
|
|
274
285
|
xmemcli xmd generate "people and their pets" -o schema.yml
|
|
275
286
|
xmemcli xmd enhance schema.yml "add a Pet.breed field" -o next.yml --plan-output plan.json
|
|
@@ -417,7 +428,7 @@ so a typo fails loudly rather than being ignored.
|
|
|
417
428
|
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix. |
|
|
418
429
|
| **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
|
|
419
430
|
| **`version`** | CLI release version (no network) |
|
|
420
|
-
| **`status`** | Local health: version + whether `.xmemrc.json` exists (no network) |
|
|
431
|
+
| **`status`** | Local health: version + whether `.xmemrc.json` exists, and the `mcp_url` it declares when it declares one (no network) |
|
|
421
432
|
|
|
422
433
|
**`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
|
|
423
434
|
JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
|
|
@@ -430,6 +441,31 @@ It exists because an MCP client can reference an environment variable in an
|
|
|
430
441
|
connection means nothing is captured when the server is registered, so a configuration
|
|
431
442
|
written today still works in a session opened next week with an empty environment.
|
|
432
443
|
|
|
444
|
+
**Where a file-held key may be sent.** The MCP origin is configured separately from the API
|
|
445
|
+
origin, and a project `.mcp.json` arrives by `git clone` — it controls that server's `args` and
|
|
446
|
+
`env`, so it is not allowed to vouch for itself. A key read from `.xmemrc.json` therefore reaches
|
|
447
|
+
only the MCP origin that same file records in `mcp_url`, the origin paired with its `api_url` in
|
|
448
|
+
this client's built-in table of shipped deployments, or a loopback host when both ends are on this
|
|
449
|
+
machine. Anywhere else needs a key named on the call with `--api-key`.
|
|
450
|
+
|
|
451
|
+
A recorded origin says *where*, never *how*: a remote one must be `https`, so a hand-written
|
|
452
|
+
`http://` entry is refused rather than sending the key in clear text. Loopback stays exempt — those
|
|
453
|
+
bytes never leave the machine.
|
|
454
|
+
|
|
455
|
+
The **mount path is part of the destination**, not decoration. A deployment serving every route
|
|
456
|
+
group on one hostname puts MCP at `https://host/mcp`, the REST API at `https://host/xmem` and the
|
|
457
|
+
OAuth server at `https://host/oauth2`; an rc recording the first authorises the first only.
|
|
458
|
+
|
|
459
|
+
`auth login` fills in `mcp_url` from what the API reports about itself, which is what makes an
|
|
460
|
+
on-premise deployment work: its MCP host is in no table this client was built with, and every
|
|
461
|
+
attempt to *derive* one host from the other has been wrong in a new way. If the transport refuses
|
|
462
|
+
an origin you expect it to accept, run `auth login` against that deployment again — the rc predates
|
|
463
|
+
the field, or that server does not report one. That works even when the stored key is still good:
|
|
464
|
+
the `--email` path records the origin it learns while checking, and reports it as `mcp_url` on the
|
|
465
|
+
`already_authorized` payload. A deployment that has stopped serving MCP answers `null`, and the
|
|
466
|
+
recorded origin is dropped rather than left authorised — and if that removal cannot be written,
|
|
467
|
+
the command fails rather than reporting success over a host that is still authorised.
|
|
468
|
+
|
|
433
469
|
Two consequences worth knowing:
|
|
434
470
|
|
|
435
471
|
- **stdout is the protocol.** Alone among these commands it emits no JSON document and
|
|
@@ -515,11 +551,21 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
|
|
|
515
551
|
|
|
516
552
|
**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
553
|
|
|
554
|
+
**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
|
+
|
|
556
|
+
**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.
|
|
557
|
+
|
|
518
558
|
**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
559
|
|
|
520
|
-
**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
|
|
560
|
+
**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/`.
|
|
561
|
+
|
|
562
|
+
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.
|
|
563
|
+
|
|
564
|
+
**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.
|
|
521
565
|
|
|
522
|
-
|
|
566
|
+
**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`.
|
|
567
|
+
|
|
568
|
+
**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.
|
|
523
569
|
|
|
524
570
|
See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
|
|
525
571
|
|
|
@@ -610,7 +656,7 @@ These apply **before** any subcommand (`xmemcli help options`).
|
|
|
610
656
|
|
|
611
657
|
**Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
|
|
612
658
|
|
|
613
|
-
**`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings
|
|
659
|
+
**`.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.
|
|
614
660
|
|
|
615
661
|
**`context` is the one exception, and it is a security boundary.** Every other command walks up
|
|
616
662
|
from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
|
|
@@ -624,6 +670,8 @@ and each delivery mechanism turned out to be a fresh bypass.
|
|
|
624
670
|
|
|
625
671
|
**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.
|
|
626
672
|
|
|
673
|
+
**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
|
+
|
|
627
675
|
**Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
|
|
628
676
|
|
|
629
677
|
**Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.
|
|
@@ -14,6 +14,8 @@ xmemcli read <query>
|
|
|
14
14
|
xmemcli write <text>
|
|
15
15
|
xmemcli write-status <write_id> [<id> …]
|
|
16
16
|
xmemcli quota
|
|
17
|
+
xmemcli instance templates
|
|
18
|
+
xmemcli instance create --name NAME (--template ID | --schema-file PATH | --schema-stdin)
|
|
17
19
|
xmemcli binding list [--autoload]
|
|
18
20
|
xmemcli binding add <instance_id> [--tier autoload|available|off] [--scope project|user]
|
|
19
21
|
xmemcli binding remove <instance_id> [--scope project|user]
|
|
@@ -22,7 +24,7 @@ xmemcli context [--text] [--max-tokens N] [--no-live-state]
|
|
|
22
24
|
|
|
23
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`.
|
|
24
26
|
|
|
25
|
-
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.
|
|
27
|
+
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.
|
|
26
28
|
|
|
27
29
|
`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.
|
|
28
30
|
|
|
@@ -89,7 +91,7 @@ xmemcli version
|
|
|
89
91
|
xmemcli auth login
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
Version: **
|
|
94
|
+
Version: **1.2.0**.
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
---
|
|
@@ -143,7 +145,7 @@ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never hand
|
|
|
143
145
|
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.
|
|
144
146
|
5. The key is written to `.xmemrc.json` (0600), stdout carries the prefix only, and the session cookie dies with the process.
|
|
145
147
|
|
|
146
|
-
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 the local-dev convention pairs `:8000` with `:8002` — otherwise it refuses to guess (pass `--console-url`
|
|
148
|
+
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.
|
|
147
149
|
|
|
148
150
|
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.
|
|
149
151
|
|
|
@@ -155,12 +157,16 @@ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking
|
|
|
155
157
|
|
|
156
158
|
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.
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
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.
|
|
159
163
|
|
|
160
164
|
## Identity — `/whoami`
|
|
161
165
|
|
|
162
166
|
`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.
|
|
163
167
|
|
|
168
|
+
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.
|
|
169
|
+
|
|
164
170
|
## Security model
|
|
165
171
|
|
|
166
172
|
| Threat | Mitigation |
|
|
@@ -185,7 +191,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
185
191
|
|
|
186
192
|
# `xmemcli` — capabilities reference
|
|
187
193
|
|
|
188
|
-
**Version:** `
|
|
194
|
+
**Version:** `1.2.0` (see `xmemcli version`)
|
|
189
195
|
|
|
190
196
|
`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.
|
|
191
197
|
|
|
@@ -228,7 +234,8 @@ Permission-filtered view of what this API key can see.
|
|
|
228
234
|
| Command | Description |
|
|
229
235
|
|---------|-------------|
|
|
230
236
|
| **`instance get`** `<instance_id>` | Fetch one instance |
|
|
231
|
-
| **`instance
|
|
237
|
+
| **`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. |
|
|
238
|
+
| **`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. |
|
|
232
239
|
| **`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. |
|
|
233
240
|
| **`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. |
|
|
234
241
|
|
|
@@ -262,6 +269,10 @@ XMD (xmemory Data) is the typed schema format for instances. These commands mirr
|
|
|
262
269
|
#### Typical workflow
|
|
263
270
|
|
|
264
271
|
```text
|
|
272
|
+
# 0. Or skip 1-2 entirely: pick a ready-made shape
|
|
273
|
+
xmemcli instance templates
|
|
274
|
+
xmemcli instance create --name demo --template personal_crm
|
|
275
|
+
|
|
265
276
|
# 1. Synthesize or enhance
|
|
266
277
|
xmemcli xmd generate "people and their pets" -o schema.yml
|
|
267
278
|
xmemcli xmd enhance schema.yml "add a Pet.breed field" -o next.yml --plan-output plan.json
|
|
@@ -409,7 +420,7 @@ so a typo fails loudly rather than being ignored.
|
|
|
409
420
|
| **`auth status`** | Call `/whoami`. No key → exit 0; bad key → auth error; good key → email + prefix. |
|
|
410
421
|
| **`mcp <instance-id>`** | Serve one instance to an MCP client over stdio, using this CLI's credential. Not for humans — see below. |
|
|
411
422
|
| **`version`** | CLI release version (no network) |
|
|
412
|
-
| **`status`** | Local health: version + whether `.xmemrc.json` exists (no network) |
|
|
423
|
+
| **`status`** | Local health: version + whether `.xmemrc.json` exists, and the `mcp_url` it declares when it declares one (no network) |
|
|
413
424
|
|
|
414
425
|
**`mcp` is a transport, not a command you run.** An MCP client starts it and speaks
|
|
415
426
|
JSON-RPC to it over stdio; it forwards each frame to `<mcp-url>/instance/<id>` with this
|
|
@@ -422,6 +433,31 @@ It exists because an MCP client can reference an environment variable in an
|
|
|
422
433
|
connection means nothing is captured when the server is registered, so a configuration
|
|
423
434
|
written today still works in a session opened next week with an empty environment.
|
|
424
435
|
|
|
436
|
+
**Where a file-held key may be sent.** The MCP origin is configured separately from the API
|
|
437
|
+
origin, and a project `.mcp.json` arrives by `git clone` — it controls that server's `args` and
|
|
438
|
+
`env`, so it is not allowed to vouch for itself. A key read from `.xmemrc.json` therefore reaches
|
|
439
|
+
only the MCP origin that same file records in `mcp_url`, the origin paired with its `api_url` in
|
|
440
|
+
this client's built-in table of shipped deployments, or a loopback host when both ends are on this
|
|
441
|
+
machine. Anywhere else needs a key named on the call with `--api-key`.
|
|
442
|
+
|
|
443
|
+
A recorded origin says *where*, never *how*: a remote one must be `https`, so a hand-written
|
|
444
|
+
`http://` entry is refused rather than sending the key in clear text. Loopback stays exempt — those
|
|
445
|
+
bytes never leave the machine.
|
|
446
|
+
|
|
447
|
+
The **mount path is part of the destination**, not decoration. A deployment serving every route
|
|
448
|
+
group on one hostname puts MCP at `https://host/mcp`, the REST API at `https://host/xmem` and the
|
|
449
|
+
OAuth server at `https://host/oauth2`; an rc recording the first authorises the first only.
|
|
450
|
+
|
|
451
|
+
`auth login` fills in `mcp_url` from what the API reports about itself, which is what makes an
|
|
452
|
+
on-premise deployment work: its MCP host is in no table this client was built with, and every
|
|
453
|
+
attempt to *derive* one host from the other has been wrong in a new way. If the transport refuses
|
|
454
|
+
an origin you expect it to accept, run `auth login` against that deployment again — the rc predates
|
|
455
|
+
the field, or that server does not report one. That works even when the stored key is still good:
|
|
456
|
+
the `--email` path records the origin it learns while checking, and reports it as `mcp_url` on the
|
|
457
|
+
`already_authorized` payload. A deployment that has stopped serving MCP answers `null`, and the
|
|
458
|
+
recorded origin is dropped rather than left authorised — and if that removal cannot be written,
|
|
459
|
+
the command fails rather than reporting success over a host that is still authorised.
|
|
460
|
+
|
|
425
461
|
Two consequences worth knowing:
|
|
426
462
|
|
|
427
463
|
- **stdout is the protocol.** Alone among these commands it emits no JSON document and
|
|
@@ -507,11 +543,21 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
|
|
|
507
543
|
|
|
508
544
|
**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.
|
|
509
545
|
|
|
546
|
+
**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
|
+
|
|
548
|
+
**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.
|
|
549
|
+
|
|
510
550
|
**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.
|
|
511
551
|
|
|
512
|
-
**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
|
|
552
|
+
**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/`.
|
|
553
|
+
|
|
554
|
+
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.
|
|
555
|
+
|
|
556
|
+
**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.
|
|
513
557
|
|
|
514
|
-
|
|
558
|
+
**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`.
|
|
559
|
+
|
|
560
|
+
**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.
|
|
515
561
|
|
|
516
562
|
See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
|
|
517
563
|
|
|
@@ -602,7 +648,7 @@ These apply **before** any subcommand (`xmemcli help options`).
|
|
|
602
648
|
|
|
603
649
|
**Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
|
|
604
650
|
|
|
605
|
-
**`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings
|
|
651
|
+
**`.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.
|
|
606
652
|
|
|
607
653
|
**`context` is the one exception, and it is a security boundary.** Every other command walks up
|
|
608
654
|
from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
|
|
@@ -616,6 +662,8 @@ and each delivery mechanism turned out to be a fresh bypass.
|
|
|
616
662
|
|
|
617
663
|
**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.
|
|
618
664
|
|
|
665
|
+
**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
|
+
|
|
619
667
|
**Instance resolution (read/write):** `--instance-id` → `$XMEM_INSTANCE_ID` → if exactly **one** instance is visible, auto-select with a **stderr warning** → else error.
|
|
620
668
|
|
|
621
669
|
**Output:** Pipes and non-TTY stdout → JSON. Interactive TTY → one-line summaries unless `--json`.
|
|
@@ -121,7 +121,8 @@ _OVERVIEW_SECTIONS: tuple[tuple[str, tuple[tuple[str, str], ...]], ...] = (
|
|
|
121
121
|
"Instances",
|
|
122
122
|
(
|
|
123
123
|
("instance get", "Get a single instance by id"),
|
|
124
|
-
("instance
|
|
124
|
+
("instance templates", "List the ready-made templates a create can name"),
|
|
125
|
+
("instance create", "Create a new instance from a template or an XMD/JSON schema"),
|
|
125
126
|
("instance setup", "How to connect an instance to an agent"),
|
|
126
127
|
("instance instructions", "Read, set or clear an instance's standing instructions to an agent"),
|
|
127
128
|
),
|
|
@@ -280,8 +281,9 @@ _GLOBAL_OPTION_LINES: dict[str, tuple[str, str]] = {
|
|
|
280
281
|
),
|
|
281
282
|
"mcp_url": (
|
|
282
283
|
"MCP origin that `mcp` connects to.",
|
|
283
|
-
"Default $XMEM_MCP_URL
|
|
284
|
-
"its own deployment, so a
|
|
284
|
+
"Default $XMEM_MCP_URL, then the mcp_url recorded in .xmemrc.json, then "
|
|
285
|
+
"https://mcp.xmemory.ai; a key from .xmemrc.json only travels to its own deployment, so a "
|
|
286
|
+
"foreign origin needs another `auth login` there or --api-key.",
|
|
285
287
|
),
|
|
286
288
|
"json": (
|
|
287
289
|
"Force JSON output — one JSON document on stdout.",
|
|
@@ -411,11 +413,18 @@ def _command_usage(parser: argparse.ArgumentParser, path: tuple[str, ...]) -> st
|
|
|
411
413
|
if action.dest in _INTERNAL_DESTS or action.option_strings:
|
|
412
414
|
continue
|
|
413
415
|
if action.metavar:
|
|
414
|
-
|
|
416
|
+
token = f"<{action.metavar}>"
|
|
415
417
|
elif action.nargs in (argparse.ONE_OR_MORE, "+"):
|
|
416
|
-
|
|
418
|
+
token = "<…>"
|
|
417
419
|
else:
|
|
418
|
-
|
|
420
|
+
token = f"<{action.dest}>"
|
|
421
|
+
# ``nargs='?'`` is argparse-optional, not always product-optional: `mcp` uses it
|
|
422
|
+
# so `cmd_mcp` can print its own usage, but the instance id is required. Bracket
|
|
423
|
+
# only dests that are genuinely optional (`connect_url`, `instance_id_arg`).
|
|
424
|
+
if action.nargs in (argparse.OPTIONAL, "?") and action.dest != "instance_id":
|
|
425
|
+
usage_parts.append(f"[{token}]")
|
|
426
|
+
else:
|
|
427
|
+
usage_parts.append(token)
|
|
419
428
|
if sub is not None and not any(a.dest not in _INTERNAL_DESTS and not a.option_strings for a in parser._actions):
|
|
420
429
|
usage_suffix = " <command> [options]"
|
|
421
430
|
else:
|