xmemcli 0.0.13__tar.gz → 1.0.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.0.0}/PKG-INFO +11 -7
- {xmemcli-0.0.13 → xmemcli-1.0.0}/README.md +10 -6
- {xmemcli-0.0.13 → xmemcli-1.0.0}/pyproject.toml +1 -1
- {xmemcli-0.0.13 → xmemcli-1.0.0}/uv.lock +1 -1
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/__init__.py +1 -1
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_login.py +68 -10
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_mcp_bridge.py +32 -9
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_rc.py +18 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_version.py +1 -1
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/cli.py +95 -12
- {xmemcli-0.0.13 → xmemcli-1.0.0}/.gitignore +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_binding.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_binding_cmd.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_context_cmd.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_email_login.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_envelope.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_help.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_instance_schema.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_org_cluster.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_output.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_pretty.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_text.py +0 -0
- {xmemcli-0.0.13 → xmemcli-1.0.0}/xmemcli/_xmem_bridge.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xmemcli
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 1.0.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
|
|
@@ -97,7 +97,7 @@ xmemcli version
|
|
|
97
97
|
xmemcli auth login
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Version: **0.0
|
|
100
|
+
Version: **1.0.0**.
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
---
|
|
@@ -151,7 +151,7 @@ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never hand
|
|
|
151
151
|
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
152
|
5. The key is written to `.xmemrc.json` (0600), stdout carries the prefix only, and the session cookie dies with the process.
|
|
153
153
|
|
|
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` or set `$XMEM_CONSOLE_URL`). 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.
|
|
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` or set `$XMEM_CONSOLE_URL` when inference does not apply). 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
155
|
|
|
156
156
|
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
157
|
|
|
@@ -163,7 +163,7 @@ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking
|
|
|
163
163
|
|
|
164
164
|
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
165
|
|
|
166
|
-
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. 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
|
|
166
|
+
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` → sibling inference from the call's `--api-url` when it applies → `$XMEM_CONSOLE_URL` → sibling inference from `$XMEM_API_URL` → default — 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
167
|
|
|
168
168
|
## Identity — `/whoami`
|
|
169
169
|
|
|
@@ -193,7 +193,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
193
193
|
|
|
194
194
|
# `xmemcli` — capabilities reference
|
|
195
195
|
|
|
196
|
-
**Version:** `0.0
|
|
196
|
+
**Version:** `1.0.0` (see `xmemcli version`)
|
|
197
197
|
|
|
198
198
|
`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
199
|
|
|
@@ -517,9 +517,13 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
|
|
|
517
517
|
|
|
518
518
|
**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
519
|
|
|
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
|
|
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 `--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
521
|
|
|
522
|
-
Two consequences worth knowing.
|
|
522
|
+
Two consequences worth knowing. Connect instructions from the API assume **1.0.0** or newer everywhere. On a single-origin deployment they also rely on path inference — they render `auth login --api-url https://host/xmem` with **no** `--console-url` — and an older CLI given that command signs in at the hosted Console instead. 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.
|
|
523
|
+
|
|
524
|
+
**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, and the CLI writes the inferred ``api_url`` into ``.xmemrc.json`` as soon as login starts.
|
|
525
|
+
|
|
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.
|
|
523
527
|
|
|
524
528
|
See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
|
|
525
529
|
|
|
@@ -89,7 +89,7 @@ xmemcli version
|
|
|
89
89
|
xmemcli auth login
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Version: **0.0
|
|
92
|
+
Version: **1.0.0**.
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
---
|
|
@@ -143,7 +143,7 @@ AI-assisted IDE ── xmemcli read/write ──► CLI resolves key; never hand
|
|
|
143
143
|
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
144
|
5. The key is written to `.xmemrc.json` (0600), stdout carries the prefix only, and the session cookie dies with the process.
|
|
145
145
|
|
|
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` or set `$XMEM_CONSOLE_URL`). 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.
|
|
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 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` or set `$XMEM_CONSOLE_URL` when inference does not apply). 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
147
|
|
|
148
148
|
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
149
|
|
|
@@ -155,7 +155,7 @@ Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking
|
|
|
155
155
|
|
|
156
156
|
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
157
|
|
|
158
|
-
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. 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
|
|
158
|
+
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` → sibling inference from the call's `--api-url` when it applies → `$XMEM_CONSOLE_URL` → sibling inference from `$XMEM_API_URL` → default — 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
159
|
|
|
160
160
|
## Identity — `/whoami`
|
|
161
161
|
|
|
@@ -185,7 +185,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
|
|
|
185
185
|
|
|
186
186
|
# `xmemcli` — capabilities reference
|
|
187
187
|
|
|
188
|
-
**Version:** `0.0
|
|
188
|
+
**Version:** `1.0.0` (see `xmemcli version`)
|
|
189
189
|
|
|
190
190
|
`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
191
|
|
|
@@ -509,9 +509,13 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
|
|
|
509
509
|
|
|
510
510
|
**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
511
|
|
|
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
|
|
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 `--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/`.
|
|
513
513
|
|
|
514
|
-
Two consequences worth knowing.
|
|
514
|
+
Two consequences worth knowing. Connect instructions from the API assume **1.0.0** or newer everywhere. On a single-origin deployment they also rely on path inference — they render `auth login --api-url https://host/xmem` with **no** `--console-url` — and an older CLI given that command signs in at the hosted Console instead. 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.
|
|
515
|
+
|
|
516
|
+
**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, and the CLI writes the inferred ``api_url`` into ``.xmemrc.json`` as soon as login starts.
|
|
517
|
+
|
|
518
|
+
**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.
|
|
515
519
|
|
|
516
520
|
See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
|
|
517
521
|
|
|
@@ -22,6 +22,7 @@ import errno
|
|
|
22
22
|
import hashlib
|
|
23
23
|
import json
|
|
24
24
|
import os
|
|
25
|
+
import re
|
|
25
26
|
import secrets
|
|
26
27
|
import select
|
|
27
28
|
import sys
|
|
@@ -84,6 +85,55 @@ SINGLE_ORIGIN_API_PATH = "/xmem"
|
|
|
84
85
|
SINGLE_ORIGIN_CONSOLE_PATH = "/console"
|
|
85
86
|
SINGLE_ORIGIN_MCP_PATH = "/mcp"
|
|
86
87
|
|
|
88
|
+
_CONSOLE_INSTANCE_PATH_PREFIX = f"{SINGLE_ORIGIN_CONSOLE_PATH}/instance/"
|
|
89
|
+
_INSTANCE_ID_RE = re.compile(
|
|
90
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
91
|
+
re.IGNORECASE,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ConsoleInstanceLink(NamedTuple):
|
|
96
|
+
"""A Console instance detail page on a single-origin deployment."""
|
|
97
|
+
|
|
98
|
+
console_url: str
|
|
99
|
+
api_url: str
|
|
100
|
+
instance_id: str
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_console_instance_url(url: str) -> ConsoleInstanceLink | None:
|
|
104
|
+
"""Derive login targets from ``…/console/instance/<uuid>`` on one hostname.
|
|
105
|
+
|
|
106
|
+
Query and fragment are ignored. Only the conventional single-origin mount
|
|
107
|
+
``/console/instance/<uuid>`` is recognised — a five-host Console URL must
|
|
108
|
+
still name ``--api-url`` and ``--console-url`` explicitly.
|
|
109
|
+
"""
|
|
110
|
+
raw = url.strip()
|
|
111
|
+
if not raw:
|
|
112
|
+
return None
|
|
113
|
+
try:
|
|
114
|
+
parsed = urlsplit(raw)
|
|
115
|
+
_ = parsed.port
|
|
116
|
+
except ValueError:
|
|
117
|
+
return None
|
|
118
|
+
if parsed.scheme not in ("http", "https") or not parsed.hostname:
|
|
119
|
+
return None
|
|
120
|
+
if parsed.username or parsed.password:
|
|
121
|
+
return None
|
|
122
|
+
path = parsed.path.rstrip("/")
|
|
123
|
+
if not path.startswith(_CONSOLE_INSTANCE_PATH_PREFIX):
|
|
124
|
+
return None
|
|
125
|
+
instance_id = path[len(_CONSOLE_INSTANCE_PATH_PREFIX) :]
|
|
126
|
+
if not instance_id or "/" in instance_id:
|
|
127
|
+
return None
|
|
128
|
+
if _INSTANCE_ID_RE.fullmatch(instance_id) is None:
|
|
129
|
+
return None
|
|
130
|
+
origin = f"{parsed.scheme}://{parsed.netloc}"
|
|
131
|
+
return ConsoleInstanceLink(
|
|
132
|
+
console_url=f"{origin}{SINGLE_ORIGIN_CONSOLE_PATH}",
|
|
133
|
+
api_url=f"{origin}{SINGLE_ORIGIN_API_PATH}",
|
|
134
|
+
instance_id=instance_id,
|
|
135
|
+
)
|
|
136
|
+
|
|
87
137
|
API_KEY_PREFIX = "xmem_"
|
|
88
138
|
|
|
89
139
|
#: Hostnames treated as loopback wherever the CLI relaxes an https-only rule or infers the
|
|
@@ -192,22 +242,30 @@ def console_url_from_api_url(api_url: str) -> str | None:
|
|
|
192
242
|
def console_url_for_api(*, console_url: str | None = None, api_url: str | None = None) -> str:
|
|
193
243
|
"""Resolve the Console origin for PKCE login and token/whoami proxies.
|
|
194
244
|
|
|
195
|
-
Explicit wins
|
|
196
|
-
|
|
197
|
-
``
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
Explicit ``--console-url`` wins. When the caller names an API base for this login,
|
|
246
|
+
that base alone decides whether a Console can be inferred — ``$XMEM_CONSOLE_URL`` and
|
|
247
|
+
``$XMEM_API_URL`` are not consulted on that path, so a copy-pasted
|
|
248
|
+
``--api-url https://host/xmem`` cannot be steered away by the reader's shell profile.
|
|
249
|
+
With no ``api_url``, the environment and then the default apply as before.
|
|
200
250
|
"""
|
|
201
251
|
if console_url:
|
|
202
252
|
return console_url.rstrip("/")
|
|
253
|
+
if api_url:
|
|
254
|
+
inferred = console_url_from_api_url(api_url)
|
|
255
|
+
if inferred:
|
|
256
|
+
return inferred
|
|
257
|
+
from_env = os.environ.get(_ENV_CONSOLE_URL, "").strip()
|
|
258
|
+
if from_env:
|
|
259
|
+
return from_env.rstrip("/")
|
|
260
|
+
return DEFAULT_CONSOLE_URL
|
|
203
261
|
from_env = os.environ.get(_ENV_CONSOLE_URL, "").strip()
|
|
204
262
|
if from_env:
|
|
205
263
|
return from_env.rstrip("/")
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
264
|
+
env_api = os.environ.get(_ENV_API_URL, "").strip()
|
|
265
|
+
if env_api:
|
|
266
|
+
inferred = console_url_from_api_url(env_api)
|
|
267
|
+
if inferred:
|
|
268
|
+
return inferred
|
|
211
269
|
return DEFAULT_CONSOLE_URL
|
|
212
270
|
|
|
213
271
|
|
|
@@ -29,6 +29,7 @@ import argparse
|
|
|
29
29
|
import io
|
|
30
30
|
import ipaddress
|
|
31
31
|
import json
|
|
32
|
+
import os
|
|
32
33
|
import sys
|
|
33
34
|
import urllib.parse
|
|
34
35
|
import urllib.error
|
|
@@ -556,7 +557,13 @@ def _is_single_origin_pair(api_origin: str, mcp_origin: str, m: Any) -> bool:
|
|
|
556
557
|
return _canonical_origin(expected, m) == _canonical_origin(mcp_origin, m)
|
|
557
558
|
|
|
558
559
|
|
|
559
|
-
def _refuse_foreign_origin(
|
|
560
|
+
def _refuse_foreign_origin(
|
|
561
|
+
api_origin: str | None,
|
|
562
|
+
mcp_origin: str,
|
|
563
|
+
m: Any,
|
|
564
|
+
*,
|
|
565
|
+
effective_api_base: str | None = None,
|
|
566
|
+
) -> str | None:
|
|
560
567
|
"""Why this credential may not be sent to this MCP origin, or ``None``.
|
|
561
568
|
|
|
562
569
|
The key in ``.xmemrc.json`` records the API origin it belongs to, and the CLI holds
|
|
@@ -566,6 +573,9 @@ def _refuse_foreign_origin(api_origin: str | None, mcp_origin: str, m: Any) -> s
|
|
|
566
573
|
user did not write — so the config is not allowed to vouch for itself.
|
|
567
574
|
|
|
568
575
|
``api_origin`` is ``None`` when the operator named the credential on this call.
|
|
576
|
+
``effective_api_base`` is the flag/env/rc-resolved API base used to infer MCP when
|
|
577
|
+
no explicit MCP was named — an rc that recorded the bare host while ``--api-url
|
|
578
|
+
…/xmem`` names the mount for this call still pairs with the ``/mcp`` sibling.
|
|
569
579
|
"""
|
|
570
580
|
if api_origin is None:
|
|
571
581
|
# Named on the command line or in the environment: the operator supplied this
|
|
@@ -578,6 +588,13 @@ def _refuse_foreign_origin(api_origin: str | None, mcp_origin: str, m: Any) -> s
|
|
|
578
588
|
return None
|
|
579
589
|
if _is_single_origin_pair(api_origin, mcp_origin, m):
|
|
580
590
|
return None
|
|
591
|
+
if (
|
|
592
|
+
effective_api_base
|
|
593
|
+
and effective_api_base != api_origin
|
|
594
|
+
and _is_single_origin_pair(effective_api_base, mcp_origin, m)
|
|
595
|
+
and _canonical_origin(effective_api_base, m) == api
|
|
596
|
+
):
|
|
597
|
+
return None
|
|
581
598
|
# The originals, not the canonical forms: normalising rebuilds an origin as
|
|
582
599
|
# `scheme://host:port`, which for IPv6 drops the brackets that made the address
|
|
583
600
|
# parseable — so `http://[::1]:8000` canonicalises to something no URL parser can
|
|
@@ -633,13 +650,17 @@ def cmd_mcp(args: argparse.Namespace) -> int:
|
|
|
633
650
|
)
|
|
634
651
|
_eprint(unavailable)
|
|
635
652
|
|
|
636
|
-
#
|
|
637
|
-
#
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
653
|
+
# Explicit MCP wins without parsing an API base at all — a malformed `--api-url` must
|
|
654
|
+
# not take down a valid `--mcp-url`. Otherwise the effective API base for sibling
|
|
655
|
+
# inference follows flag → env → rc precedence (`_api_url`), not the credential-bound
|
|
656
|
+
# origin alone: an rc at the bare host plus `--api-url …/xmem` must still reach `/mcp`.
|
|
657
|
+
explicit_mcp = (getattr(args, "mcp_url", None) or os.environ.get(m.ENV_MCP_URL) or "").strip()
|
|
658
|
+
if explicit_mcp:
|
|
659
|
+
mcp_origin = explicit_mcp
|
|
660
|
+
effective_api_base: str | None = None
|
|
661
|
+
else:
|
|
662
|
+
effective_api_base = m._api_url(args)
|
|
663
|
+
mcp_origin = m._mcp_url(args, api_url=effective_api_base)
|
|
643
664
|
|
|
644
665
|
# Is it a URL at all — asked first, and for every caller. The guard below is skipped
|
|
645
666
|
# when the operator names the key, and that used to skip the only look this origin
|
|
@@ -656,7 +677,9 @@ def cmd_mcp(args: argparse.Namespace) -> int:
|
|
|
656
677
|
# anywhere else needs a credential the operator named on the call. `api_origin` comes
|
|
657
678
|
# from the same read as `api_key`, so the two always describe one file.
|
|
658
679
|
if api_key is not None and unavailable is None:
|
|
659
|
-
unavailable = _refuse_foreign_origin(
|
|
680
|
+
unavailable = _refuse_foreign_origin(
|
|
681
|
+
api_origin, mcp_origin, m, effective_api_base=effective_api_base
|
|
682
|
+
)
|
|
660
683
|
if unavailable is not None:
|
|
661
684
|
_eprint(unavailable)
|
|
662
685
|
|
|
@@ -153,6 +153,24 @@ def write_rc(target_dir: Path, *, email: str, api_url: str, api_key: str) -> Pat
|
|
|
153
153
|
return path
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
def stage_login_api_url(start_dir: Path, api_url: str) -> Path:
|
|
157
|
+
"""Record ``api_url`` in the project rc before PKCE or email approval finishes.
|
|
158
|
+
|
|
159
|
+
When a rc already exists, its ``api_url`` is updated in place and the credential
|
|
160
|
+
is left untouched. Otherwise a placeholder rc is written (empty email/key) so
|
|
161
|
+
``_api_url`` names this deployment while login is still in progress.
|
|
162
|
+
"""
|
|
163
|
+
start_dir.mkdir(parents=True, exist_ok=True)
|
|
164
|
+
found = find_rc(start_dir)
|
|
165
|
+
if found is not None:
|
|
166
|
+
try:
|
|
167
|
+
record = load_rc(found)
|
|
168
|
+
except RcError:
|
|
169
|
+
return write_rc(start_dir, email="", api_url=api_url, api_key="")
|
|
170
|
+
return write_rc(found.parent, email=record.email, api_url=api_url, api_key=record.api_key)
|
|
171
|
+
return write_rc(start_dir, email="", api_url=api_url, api_key="")
|
|
172
|
+
|
|
173
|
+
|
|
156
174
|
def remove_rc(path: Path) -> bool:
|
|
157
175
|
"""Delete the ``.xmemrc.json`` at ``path``. Returns ``True`` iff it existed."""
|
|
158
176
|
try:
|
|
@@ -141,6 +141,7 @@ from ._rc import ( # noqa: E402 (sibling-script import)
|
|
|
141
141
|
load_rc,
|
|
142
142
|
remove_rc,
|
|
143
143
|
resolve_rc,
|
|
144
|
+
stage_login_api_url,
|
|
144
145
|
write_rc,
|
|
145
146
|
)
|
|
146
147
|
from ._text import oneline
|
|
@@ -653,8 +654,68 @@ def _get(url: str, api_key: str, timeout_s: float = 30.0) -> dict[str, Any]:
|
|
|
653
654
|
return _read_json_response(_build_request(url, "GET", api_key), timeout_s)
|
|
654
655
|
|
|
655
656
|
|
|
657
|
+
def _connect_url_from_login_args(args: argparse.Namespace) -> str | None:
|
|
658
|
+
"""Console instance page URL from a positional arg or a mistaken ``--api-url``."""
|
|
659
|
+
connect = getattr(args, "connect_url", None)
|
|
660
|
+
if isinstance(connect, str) and connect.strip():
|
|
661
|
+
return connect.strip()
|
|
662
|
+
flag_api = getattr(args, "api_url", None)
|
|
663
|
+
if isinstance(flag_api, str) and flag_api.strip():
|
|
664
|
+
candidate = flag_api.strip()
|
|
665
|
+
if _login.parse_console_instance_url(candidate) is not None:
|
|
666
|
+
return candidate
|
|
667
|
+
return None
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _auth_login_api_base(
|
|
671
|
+
args: argparse.Namespace,
|
|
672
|
+
*,
|
|
673
|
+
rc_origin: str | None = None,
|
|
674
|
+
) -> tuple[str, str | None, tuple[CliOutput, int] | None]:
|
|
675
|
+
"""Resolve the API base (and optional instance id) for ``auth login``."""
|
|
676
|
+
connect = _connect_url_from_login_args(args)
|
|
677
|
+
if connect:
|
|
678
|
+
link = _login.parse_console_instance_url(connect)
|
|
679
|
+
if link is None:
|
|
680
|
+
return (
|
|
681
|
+
"",
|
|
682
|
+
None,
|
|
683
|
+
(
|
|
684
|
+
make_error(
|
|
685
|
+
f"not a single-origin Console instance URL ({connect!r}); expected "
|
|
686
|
+
f"…{_login.SINGLE_ORIGIN_CONSOLE_PATH}/instance/<uuid>",
|
|
687
|
+
stage="usage",
|
|
688
|
+
),
|
|
689
|
+
EXIT_USAGE,
|
|
690
|
+
),
|
|
691
|
+
)
|
|
692
|
+
return link.api_url, link.instance_id, None
|
|
693
|
+
explicit_api = _explicit_api_url(args) or os.environ.get(ENV_API_URL, "").strip()
|
|
694
|
+
api_origin = explicit_api or rc_origin or DEFAULT_API_URL
|
|
695
|
+
base = _normalize_api_base_url(api_origin) or api_origin.rstrip("/")
|
|
696
|
+
return base, None, None
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _stage_login_api_url_if_connecting(args: argparse.Namespace, api_url: str) -> None:
|
|
700
|
+
"""When login was aimed by a Console instance page URL, persist ``api_url`` immediately."""
|
|
701
|
+
if _connect_url_from_login_args(args) is None:
|
|
702
|
+
return
|
|
703
|
+
stage_login_api_url(_rc_dir(args), api_url)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _console_url_flag_for_args(args: argparse.Namespace) -> str | None:
|
|
707
|
+
"""``--console-url`` only — not ``$XMEM_CONSOLE_URL``.
|
|
708
|
+
|
|
709
|
+
``console_url_for_api`` applies the environment after sibling inference from
|
|
710
|
+
``--api-url``; pre-merging the env here would defeat that ordering on a
|
|
711
|
+
copy-pasted single-origin login command.
|
|
712
|
+
"""
|
|
713
|
+
explicit = getattr(args, "console_url", None)
|
|
714
|
+
return explicit.strip() if isinstance(explicit, str) and explicit.strip() else None
|
|
715
|
+
|
|
716
|
+
|
|
656
717
|
def _console_url_for_args(args: argparse.Namespace) -> str | None:
|
|
657
|
-
explicit =
|
|
718
|
+
explicit = _console_url_flag_for_args(args) or os.environ.get(ENV_CONSOLE_URL)
|
|
658
719
|
return explicit.strip() if isinstance(explicit, str) and explicit.strip() else None
|
|
659
720
|
|
|
660
721
|
|
|
@@ -888,12 +949,20 @@ def _normalize_api_base_url(url: str) -> str | None:
|
|
|
888
949
|
``_normalize_api_origin`` exists for origin comparison and deliberately drops the path;
|
|
889
950
|
this form exists for building requests, where a single-origin deployment's ``/xmem``
|
|
890
951
|
mount is load-bearing — stripping it would aim every call at the Console's root instead
|
|
891
|
-
of the API. Query and
|
|
952
|
+
of the API. Query, fragment, and userinfo refuse here rather than being stripped: the
|
|
953
|
+
single-origin sibling helpers treat those components as making a URL non-inferable, and
|
|
954
|
+
normalizing them away would let inference run on a shape the caller never supplied.
|
|
892
955
|
"""
|
|
893
|
-
|
|
956
|
+
raw = url.strip()
|
|
957
|
+
if not raw:
|
|
958
|
+
return None
|
|
959
|
+
parsed = urlsplit(raw)
|
|
960
|
+
if parsed.username or parsed.password or parsed.query or parsed.fragment:
|
|
961
|
+
return None
|
|
962
|
+
origin = _normalize_api_origin(raw)
|
|
894
963
|
if origin is None:
|
|
895
964
|
return None
|
|
896
|
-
path =
|
|
965
|
+
path = parsed.path.rstrip("/")
|
|
897
966
|
return f"{origin}{path}" if path else origin
|
|
898
967
|
|
|
899
968
|
|
|
@@ -1495,11 +1564,12 @@ def _cmd_auth_login_email(args: argparse.Namespace) -> tuple[CliOutput, int]:
|
|
|
1495
1564
|
EXIT_HTTP_ERROR,
|
|
1496
1565
|
)
|
|
1497
1566
|
|
|
1498
|
-
explicit_api = _explicit_api_url(args)
|
|
1499
1567
|
rc_origin = rc.api_url.strip() if rc is not None and rc.api_url.strip() else None
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1568
|
+
base, instance_hint, usage_error = _auth_login_api_base(args, rc_origin=rc_origin)
|
|
1569
|
+
if usage_error is not None:
|
|
1570
|
+
return usage_error
|
|
1571
|
+
_stage_login_api_url_if_connecting(args, base)
|
|
1572
|
+
console, refusal = _email_console_origin(args, api_origin=base)
|
|
1503
1573
|
if refusal is not None:
|
|
1504
1574
|
return refusal
|
|
1505
1575
|
if console is None:
|
|
@@ -1711,6 +1781,8 @@ def _cmd_auth_login_email(args: argparse.Namespace) -> tuple[CliOutput, int]:
|
|
|
1711
1781
|
}
|
|
1712
1782
|
if result.org_name:
|
|
1713
1783
|
payload["org_name"] = result.org_name
|
|
1784
|
+
if instance_hint:
|
|
1785
|
+
payload["instance_id"] = instance_hint
|
|
1714
1786
|
return (CliOutput(OutputKind.LOGIN, payload), EXIT_OK)
|
|
1715
1787
|
|
|
1716
1788
|
|
|
@@ -1731,10 +1803,11 @@ def cmd_auth_login(args: argparse.Namespace) -> tuple[CliOutput, int]:
|
|
|
1731
1803
|
make_error("--org-name requires --email (it names a new account's organisation)", stage="usage"),
|
|
1732
1804
|
EXIT_USAGE,
|
|
1733
1805
|
)
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1806
|
+
base, instance_hint, usage_error = _auth_login_api_base(args)
|
|
1807
|
+
if usage_error is not None:
|
|
1808
|
+
return usage_error
|
|
1809
|
+
_stage_login_api_url_if_connecting(args, base)
|
|
1810
|
+
console = _console_url_flag_for_args(args)
|
|
1738
1811
|
verifier = _login.new_verifier()
|
|
1739
1812
|
interactive = stdout_is_tty()
|
|
1740
1813
|
offer_paste = interactive and _login.paste_from_stdin_supported()
|
|
@@ -1780,6 +1853,8 @@ def cmd_auth_login(args: argparse.Namespace) -> tuple[CliOutput, int]:
|
|
|
1780
1853
|
"key_prefix": result.key_prefix,
|
|
1781
1854
|
"rc_file": str(rc_path),
|
|
1782
1855
|
}
|
|
1856
|
+
if instance_hint:
|
|
1857
|
+
payload["instance_id"] = instance_hint
|
|
1783
1858
|
return (CliOutput(OutputKind.LOGIN, payload), EXIT_OK)
|
|
1784
1859
|
|
|
1785
1860
|
|
|
@@ -2671,6 +2746,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
2671
2746
|
"--org-name",
|
|
2672
2747
|
help="Organisation name when --email creates a new account (default: derived from the address)",
|
|
2673
2748
|
)
|
|
2749
|
+
pal.add_argument(
|
|
2750
|
+
"connect_url",
|
|
2751
|
+
nargs="?",
|
|
2752
|
+
help=(
|
|
2753
|
+
"Console instance page URL (…/console/instance/<uuid> on a single-origin host); "
|
|
2754
|
+
"the API and Console origins are inferred from it"
|
|
2755
|
+
),
|
|
2756
|
+
)
|
|
2674
2757
|
pal.set_defaults(func=cmd_auth_login)
|
|
2675
2758
|
palo = pa_sub.add_parser("logout", help="Remove the credential from the active .xmemrc.json", add_help=False)
|
|
2676
2759
|
palo.set_defaults(func=cmd_auth_logout)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|