xmemcli 0.0.11__tar.gz → 0.0.12__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.
@@ -1,3 +1,11 @@
1
+ Metadata-Version: 2.5
2
+ Name: xmemcli
3
+ Version: 0.0.12
4
+ Summary: Command-line client for the xmemory HTTP API: PKCE auth, instances, read/write, schema and XMD tools.
5
+ Keywords: api,cli,llm,memory,xmemory
6
+ Requires-Python: <4,>=3.12
7
+ Description-Content-Type: text/markdown
8
+
1
9
  # `xmemcli` — xmemory command-line client
2
10
 
3
11
  Single-command CLI for reading from, writing to, and managing xmemory instances over the HTTP API at `$XMEM_API_URL` (default `https://api.xmemory.ai`), using an API key from the Console UI or `xmemcli auth login`.
@@ -89,7 +97,7 @@ xmemcli version
89
97
  xmemcli auth login
90
98
  ```
91
99
 
92
- Version: **0.0.11**.
100
+ Version: **0.0.12**.
93
101
 
94
102
 
95
103
  ---
@@ -153,6 +161,8 @@ A credential that already resolves (via `/whoami`) to the requested address at t
153
161
 
154
162
  Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking up from `--rc-dir` / CWD, ceiling `$HOME`.
155
163
 
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
+
156
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.
157
167
 
158
168
  ## Identity — `/whoami`
@@ -183,7 +193,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
183
193
 
184
194
  # `xmemcli` — capabilities reference
185
195
 
186
- **Version:** `0.0.11` (see `xmemcli version`)
196
+ **Version:** `0.0.12` (see `xmemcli version`)
187
197
 
188
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.
189
199
 
@@ -505,7 +515,9 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
505
515
 
506
516
  **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.
507
517
 
508
- See also [`ACCOUNTS_AUTH.md`](../ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
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
+
520
+ See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
509
521
 
510
522
  ---
511
523
 
@@ -594,6 +606,8 @@ These apply **before** any subcommand (`xmemcli help options`).
594
606
 
595
607
  **Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
596
608
 
609
+ **`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings. 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.
610
+
597
611
  **`context` is the one exception, and it is a security boundary.** Every other command walks up
598
612
  from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
599
613
  that directory and ran the command in it. `context` has neither — a session-start hook runs it
@@ -1,11 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: xmemcli
3
- Version: 0.0.11
4
- Summary: Command-line client for the xmemory HTTP API: PKCE auth, instances, read/write, schema and XMD tools.
5
- Keywords: api,cli,llm,memory,xmemory
6
- Requires-Python: <4,>=3.12
7
- Description-Content-Type: text/markdown
8
-
9
1
  # `xmemcli` — xmemory command-line client
10
2
 
11
3
  Single-command CLI for reading from, writing to, and managing xmemory instances over the HTTP API at `$XMEM_API_URL` (default `https://api.xmemory.ai`), using an API key from the Console UI or `xmemcli auth login`.
@@ -97,7 +89,7 @@ xmemcli version
97
89
  xmemcli auth login
98
90
  ```
99
91
 
100
- Version: **0.0.11**.
92
+ Version: **0.0.12**.
101
93
 
102
94
 
103
95
  ---
@@ -161,6 +153,8 @@ A credential that already resolves (via `/whoami`) to the requested address at t
161
153
 
162
154
  Project-local **`.xmemrc.json`**: `{email, api_url, api_key}`. Found by walking up from `--rc-dir` / CWD, ceiling `$HOME`.
163
155
 
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
+
164
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.
165
159
 
166
160
  ## Identity — `/whoami`
@@ -191,7 +185,7 @@ Each login binds its own port and names it in the login URL, so concurrent login
191
185
 
192
186
  # `xmemcli` — capabilities reference
193
187
 
194
- **Version:** `0.0.11` (see `xmemcli version`)
188
+ **Version:** `0.0.12` (see `xmemcli version`)
195
189
 
196
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.
197
191
 
@@ -513,7 +507,9 @@ Some commands attach a more specific `stage` (`validate`, `usage`, `limit`, …)
513
507
 
514
508
  **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.
515
509
 
516
- See also [`ACCOUNTS_AUTH.md`](../ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
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
+
512
+ See also [`ACCOUNTS_AUTH.md`](../docs/auth/ACCOUNTS_AUTH.md) for the server-side 401/403/404 rules.
517
513
 
518
514
  ---
519
515
 
@@ -602,6 +598,8 @@ These apply **before** any subcommand (`xmemcli help options`).
602
598
 
603
599
  **Credential resolution:** `--api-key` → `$XMEM_API_KEY` → nearest `.xmemrc.json` → *Not logged in — run `xmemcli auth login` first.*
604
600
 
601
+ **`.xmemrc.json` shape:** `{email, api_url, api_key}`, all three required and all three strings. 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.
602
+
605
603
  **`context` is the one exception, and it is a security boundary.** Every other command walks up
606
604
  from the current directory for the nearest `.xmemrc.json`, which is deliberate: a person chose
607
605
  that directory and ran the command in it. `context` has neither — a session-start hook runs it
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xmemcli"
3
- version = "0.0.11"
3
+ version = "0.0.12"
4
4
  description = "Command-line client for the xmemory HTTP API: PKCE auth, instances, read/write, schema and XMD tools."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<4"
@@ -4,5 +4,5 @@ requires-python = ">=3.12, <4"
4
4
 
5
5
  [[package]]
6
6
  name = "xmemcli"
7
- version = "0.0.11"
7
+ version = "0.0.12"
8
8
  source = { editable = "." }
@@ -1,3 +1,3 @@
1
1
  """Standalone xmemcli package (``xmemcli`` console script)."""
2
2
 
3
- __version__ = "0.0.11"
3
+ __version__ = "0.0.12"
@@ -84,9 +84,12 @@ class OutputKind(StrEnum):
84
84
  #: payload: ``write_id``; the caller must poll.
85
85
  WRITE_ASYNC = "write_async"
86
86
 
87
- #: Terminal result of an ``async_write_status`` poll. Curated payload:
88
- #: ``write_id`` + ``write_status`` in {``completed``, ``failed``,
89
- #: ``not_found``} (+ ``error_detail`` / ``timed_out`` when applicable).
87
+ #: Result of an ``async_write_status`` poll. Curated payload: ``write_id`` + ``write_status``
88
+ #: — any ``WriteQueueStatus`` value, not only the terminal ones, plus the CLI's own
89
+ #: ``"error"`` / ``"unknown"`` fallbacks — whichever of ``submitted_at`` / ``started_at`` /
90
+ #: ``completed_at`` is set, ``restart_count`` once non-zero (``--verbose`` keeps a zero count and
91
+ #: drops only the null an unknown ``write_id`` reports), and ``error_detail`` / ``error``
92
+ #: / ``http_status`` / ``timed_out`` when applicable.
90
93
  WRITE_STATUS = "write_status"
91
94
 
92
95
  #: Read in ``mode=single-answer``. Curated payload: ``answer`` (falls
@@ -78,7 +78,7 @@ def _render_write_async(payload: Any, stream: IO[str]) -> None:
78
78
 
79
79
 
80
80
  def _render_write_status(payload: Any, stream: IO[str]) -> None:
81
- """Terminal poll result — short status line, no body."""
81
+ """Poll result — short status line, no body; the timing fields stay JSON-only."""
82
82
  if not isinstance(payload, dict):
83
83
  _default_handler(payload, stream)
84
84
  return
@@ -5,9 +5,11 @@ The CLI resolves its API key and API origin from a **project-local**
5
5
  (or the current directory). The search does **not** stop at the repository root
6
6
  and is **ceilinged at ``$HOME``** — ``~/.xmemrc.json`` is the last fallback.
7
7
 
8
- The file is stdlib JSON, strongly typed ``{email, api_url, api_key}`` (one
9
- identity per file; extras / missing / non-string keys are rejected). The ``0600``
10
- atomic writer is used by ``xmem auth login``; see ``cli/AUTH-FLOW.md``.
8
+ The file is stdlib JSON, one identity per file: ``{email, api_url, api_key}``.
9
+ Those three are required and must be strings; a key this release does not know is
10
+ ignored rather than rejected, so a later release can record something new in the
11
+ file without every older CLI refusing to run. The ``0600`` atomic writer is used
12
+ by ``xmem auth login``; see ``cli/AUTH-FLOW.md``.
11
13
 
12
14
  Resolution precedence (applied by ``cli/xmem.py``): ``--api-key`` → ``$XMEM_API_KEY`` → ``.xmemrc.json``; and
13
15
  ``--api-url`` → ``$XMEM_API_URL`` → the rc's ``api_url`` → the built-in default — **with one escape hatch**: when the
@@ -26,9 +28,10 @@ from typing import NamedTuple
26
28
  RC_FILENAME = ".xmemrc.json"
27
29
  ENV_RC_DIR = "XMEM_RC_DIR"
28
30
 
29
- # The exact, complete set of keys an `.xmemrc.json` must contain — no more, no
30
- # fewer. Keeping it closed means a typo'd or extended file fails loudly instead
31
- # of silently ignoring a field.
31
+ # The keys an `.xmemrc.json` must contain. Required, not exhaustive: anything else in the file is ignored.
32
+ # The set was closed until 0.0.12, on the grounds that a typo'd or extended file should fail loudly.
33
+ # It made adding a field a breaking change for every command rather than for the one that would read it,
34
+ # because a CLI that cannot load the rc cannot resolve a credential for anything.
32
35
  _RC_KEYS = ("email", "api_url", "api_key")
33
36
 
34
37
 
@@ -90,27 +93,22 @@ def find_rc(start_dir: Path) -> Path | None:
90
93
 
91
94
 
92
95
  def load_rc(path: Path) -> RcRecord:
93
- """Parse and validate an ``.xmemrc.json``; raise ``RcError`` if malformed."""
96
+ """Parse and validate an ``.xmemrc.json``; raise ``RcError`` if malformed.
97
+
98
+ "Required keys present", not "exactly these keys": an unrecognised key is ignored
99
+ whatever its type. Type-checking a field nothing here reads is how a later release's
100
+ richer value becomes this release's hard failure — having no opinion about it is the
101
+ whole point of ignoring it.
102
+ """
94
103
  try:
95
104
  raw = json.loads(path.read_text(encoding="utf-8"))
96
105
  except (OSError, json.JSONDecodeError) as exc:
97
106
  raise RcError(f"{path}: could not read JSON ({exc})") from exc
98
107
  if not isinstance(raw, dict):
99
108
  raise RcError(f"{path}: expected a JSON object, got {type(raw).__name__}")
100
- keys = set(raw.keys())
101
- expected = set(_RC_KEYS)
102
- if keys != expected:
103
- missing = sorted(expected - keys)
104
- extra = sorted(keys - expected)
105
- detail = ", ".join(
106
- part
107
- for part in (
108
- f"missing {missing}" if missing else "",
109
- f"unexpected {extra}" if extra else "",
110
- )
111
- if part
112
- )
113
- raise RcError(f"{path}: must contain exactly {list(_RC_KEYS)} ({detail})")
109
+ missing = sorted(set(_RC_KEYS) - set(raw.keys()))
110
+ if missing:
111
+ raise RcError(f"{path}: must contain {list(_RC_KEYS)} (missing {missing})")
114
112
  for key in _RC_KEYS:
115
113
  if not isinstance(raw[key], str):
116
114
  raise RcError(f"{path}: '{key}' must be a string")
@@ -1,4 +1,4 @@
1
1
  """Release metadata for the xmemcli CLI (copied into standalone builds)."""
2
2
 
3
3
  CLI_NAME = "xmemcli"
4
- __version__ = "0.0.11"
4
+ __version__ = "0.0.12"
@@ -30,13 +30,16 @@ Curated output
30
30
  The CLI does NOT echo the raw API payload. Every subcommand reduces the
31
31
  server response to the fields the user actually asked for (see the
32
32
  ``_curate_*`` helpers): a read returns the answer / rows, a write returns
33
- its ``write_id``, a status poll returns ``write_id`` + ``write_status``.
33
+ its ``write_id``, a status poll returns ``write_id`` + ``write_status``
34
+ plus whichever timing fields the server set (and the restart count once
35
+ it is non-zero).
34
36
  Server internals — ``sql``, ``diff_plan``, the raw ``cleaned_objects``
35
37
  tree — are never emitted, in either output mode. ``pending_suggestions``
36
38
  (a count of schema-evolution proposals waiting for review) is kept on
37
39
  read responses so agents know when to run ``schema suggestions review``.
38
40
  ``--verbose`` adds *extra* (still non-internal) context: ``trace_id`` and
39
- ``console_url``, when the server provided them.
41
+ ``console_url``, when the server provided them, and keeps a zero
42
+ ``restart_count`` a status poll would otherwise curate away.
40
43
 
41
44
  Sync vs async writes
42
45
  --------------------
@@ -749,10 +752,26 @@ def _curate_write_status(item: dict[str, Any], verbose: bool) -> dict[str, Any]:
749
752
  "write_status": item.get("write_status", "unknown"),
750
753
  }
751
754
  # Carry the failure / bookkeeping fields only when they say something.
752
- for key in ("error_detail", "completed_at", "error", "http_status", "timed_out"):
755
+ for key in (
756
+ "error_detail",
757
+ "submitted_at",
758
+ "started_at",
759
+ "completed_at",
760
+ "error",
761
+ "http_status",
762
+ "timed_out",
763
+ ):
753
764
  value = item.get(key)
754
765
  if value is not None:
755
766
  curated[key] = value
767
+ # Never-restarted is the norm, so a zero count is noise on the default output; show it there only when
768
+ # the write was restarted. Under --verbose the 0 is kept and only the null is dropped: the server sends
769
+ # null exactly for an unknown write_id, and printing that as 0 would let a caller aggregating polls count
770
+ # a row that does not exist as a write that ran clean.
771
+ restart_count = item.get("restart_count")
772
+ show_restart_count = restart_count is not None if verbose else bool(restart_count)
773
+ if show_restart_count:
774
+ curated["restart_count"] = restart_count
756
775
  return _add_verbose_extras(curated, item, verbose)
757
776
 
758
777
 
@@ -1122,6 +1141,9 @@ def cmd_write_status(args: argparse.Namespace) -> tuple[CliOutput, int]:
1122
1141
  }
1123
1142
  last_status = item.get("write_status", "unknown")
1124
1143
  last_body = item
1144
+ # Mirrors the terminal tail of WriteQueueStatus (completed, failed, not_found). Hand-written
1145
+ # rather than derived: this CLI is stdlib-only by design and imports nothing from the server
1146
+ # packages, so the enum is out of reach here.
1125
1147
  if last_status in ("completed", "failed", "not_found"):
1126
1148
  return item
1127
1149
  if time.monotonic() >= deadline:
@@ -1444,6 +1466,7 @@ def _cmd_auth_login_email(args: argparse.Namespace) -> tuple[CliOutput, int]:
1444
1466
  ),
1445
1467
  EXIT_HTTP_ERROR,
1446
1468
  )
1469
+
1447
1470
  explicit_api = _explicit_api_url(args)
1448
1471
  rc_origin = rc.api_url.strip() if rc is not None and rc.api_url.strip() else None
1449
1472
  api_origin = explicit_api or rc_origin or DEFAULT_API_URL
@@ -1641,7 +1664,7 @@ def _cmd_auth_login_email(args: argparse.Namespace) -> tuple[CliOutput, int]:
1641
1664
  detail = getattr(exc, "strerror", None) or type(exc).__name__
1642
1665
  return (
1643
1666
  make_error(
1644
- f"could not write {RC_FILENAME} ({detail}). The minted key \"{result.key_name}\" "
1667
+ f'could not write {RC_FILENAME} ({detail}). The minted key "{result.key_name}" '
1645
1668
  f"(prefix {result.key_prefix}…) is active — revoke it in the Console if it should not stay.",
1646
1669
  stage="login",
1647
1670
  ),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes