cortexdb-cli 0.4.2__tar.gz → 0.5.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.
Files changed (38) hide show
  1. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/.gitignore +7 -0
  2. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/PKG-INFO +2 -2
  3. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/__init__.py +1 -1
  4. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/audit.py +78 -78
  5. cortexdb_cli-0.5.0/cortexdb_cli/commands/code.py +246 -0
  6. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/forget.py +132 -132
  7. cortexdb_cli-0.5.0/cortexdb_cli/commands/onboard.py +721 -0
  8. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/main.py +13 -0
  9. cortexdb_cli-0.5.0/cortexdb_cli/onboarding.py +497 -0
  10. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/pyproject.toml +1 -1
  11. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/README.md +0 -0
  12. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/client.py +0 -0
  13. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/__init__.py +0 -0
  14. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/admin.py +0 -0
  15. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/answer.py +0 -0
  16. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/auth.py +0 -0
  17. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/beliefs.py +0 -0
  18. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/claims.py +0 -0
  19. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/compose.py +0 -0
  20. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/config_cmd.py +0 -0
  21. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/conflicts.py +0 -0
  22. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/entities.py +0 -0
  23. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/episodes.py +0 -0
  24. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/experience.py +0 -0
  25. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/export_cmd.py +0 -0
  26. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/facts.py +0 -0
  27. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/import_cmd.py +0 -0
  28. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/init.py +0 -0
  29. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/interactive.py +0 -0
  30. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/policy.py +0 -0
  31. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/recall.py +0 -0
  32. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/scopes.py +0 -0
  33. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/search.py +0 -0
  34. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/understanding.py +0 -0
  35. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/config.py +0 -0
  36. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/errors.py +0 -0
  37. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/output.py +0 -0
  38. {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/state.py +0 -0
@@ -88,7 +88,14 @@ benchmarks/data_*/
88
88
  benchmarks/**/fast_results/
89
89
  data_*/
90
90
  *_data/
91
+ *_data_20*/
91
92
  cortexdb_data*/
92
93
  server_results/
93
94
  *.log
94
95
  docker_build.log
96
+
97
+ # local gate/bench data stores
98
+ gate*_data_*/
99
+
100
+ # Code-plane benchmark local run outputs (official results are committed deliberately)
101
+ benchmarks/codeplane/runs/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cortexdb-cli
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Command-line interface for CortexDB — the long-term memory layer for AI.
5
5
  Project-URL: Homepage, https://cortexdb.ai
6
6
  Project-URL: Documentation, https://cortexdb.ai/docs
@@ -1,3 +1,3 @@
1
1
  """CortexDB CLI — command-line interface for the v1 memory API."""
2
2
 
3
- __version__ = "0.4.2"
3
+ __version__ = "0.5.0"
@@ -1,78 +1,78 @@
1
- """cortexdb audit — GET /v1/audit/chain + POST /v1/audit/chain/verify."""
2
-
3
- from __future__ import annotations
4
-
5
- import click
6
-
7
- from cortexdb_cli.client import get_client
8
- from cortexdb_cli.errors import handle_errors
9
- from cortexdb_cli.output import console, escape, is_json_mode, print_json, spinner
10
-
11
-
12
- @click.group("audit")
13
- def audit_group() -> None:
14
- """Audit log integrity — hash-chain status and verification."""
15
-
16
-
17
- @audit_group.command("chain")
18
- @click.option("--actor", help="Restrict chain stats to one actor's rows.")
19
- @click.pass_context
20
- @handle_errors
21
- def audit_chain(ctx: click.Context, actor: str | None) -> None:
22
- """Show the audit hash-chain head + length (GET /v1/audit/chain).
23
-
24
- Integrity metadata only — hashes and counts, never row content.
25
- """
26
- cfg = ctx.obj
27
- params: dict[str, str] = {}
28
- if actor:
29
- params["actor"] = actor
30
- with get_client(cfg) as client:
31
- resp = client.get("/v1/audit/chain", params=params or None)
32
- resp.raise_for_status()
33
- data = resp.json()
34
-
35
- if is_json_mode(cfg):
36
- print_json(data)
37
- return
38
- console.print(f"Chain length: [bold]{data.get('length', 0)}[/bold] row(s)")
39
- console.print(f"Head hash: [dim]{escape(str(data.get('head_hash') or '(empty)'))}[/dim]")
40
- legacy = data.get("legacy_rows", 0)
41
- if legacy:
42
- console.print(f"[dim]{legacy} legacy row(s) predate the chain.[/dim]")
43
-
44
-
45
- @audit_group.command("verify")
46
- @click.pass_context
47
- @handle_errors
48
- def audit_verify(ctx: click.Context) -> None:
49
- """Walk the whole audit chain, recomputing every row hash
50
- (POST /v1/audit/chain/verify)."""
51
- cfg = ctx.obj
52
- with get_client(cfg) as client:
53
- if not is_json_mode(cfg):
54
- with spinner("Verifying chain..."):
55
- resp = client.post("/v1/audit/chain/verify", json={})
56
- else:
57
- resp = client.post("/v1/audit/chain/verify", json={})
58
- resp.raise_for_status()
59
- data = resp.json()
60
-
61
- if is_json_mode(cfg):
62
- print_json(data)
63
- return
64
- if data.get("valid"):
65
- console.print(
66
- f"[success]Chain valid[/success] — {data.get('length', 0)} row(s), "
67
- f"head [dim]{escape(str(data.get('head_hash') or '(empty)'))}[/dim]"
68
- )
69
- else:
70
- console.print(
71
- f"[error]Chain BROKEN[/error] at row index "
72
- f"[bold]{data.get('first_broken_index')}[/bold] "
73
- f"of {data.get('length', 0)}."
74
- )
75
- raise SystemExit(1)
76
- legacy = data.get("legacy_rows", 0)
77
- if legacy:
78
- console.print(f"[dim]{legacy} legacy row(s) predate the chain.[/dim]")
1
+ """cortexdb audit — GET /v1/audit/chain + POST /v1/audit/chain/verify."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from cortexdb_cli.client import get_client
8
+ from cortexdb_cli.errors import handle_errors
9
+ from cortexdb_cli.output import console, escape, is_json_mode, print_json, spinner
10
+
11
+
12
+ @click.group("audit")
13
+ def audit_group() -> None:
14
+ """Audit log integrity — hash-chain status and verification."""
15
+
16
+
17
+ @audit_group.command("chain")
18
+ @click.option("--actor", help="Restrict chain stats to one actor's rows.")
19
+ @click.pass_context
20
+ @handle_errors
21
+ def audit_chain(ctx: click.Context, actor: str | None) -> None:
22
+ """Show the audit hash-chain head + length (GET /v1/audit/chain).
23
+
24
+ Integrity metadata only — hashes and counts, never row content.
25
+ """
26
+ cfg = ctx.obj
27
+ params: dict[str, str] = {}
28
+ if actor:
29
+ params["actor"] = actor
30
+ with get_client(cfg) as client:
31
+ resp = client.get("/v1/audit/chain", params=params or None)
32
+ resp.raise_for_status()
33
+ data = resp.json()
34
+
35
+ if is_json_mode(cfg):
36
+ print_json(data)
37
+ return
38
+ console.print(f"Chain length: [bold]{data.get('length', 0)}[/bold] row(s)")
39
+ console.print(f"Head hash: [dim]{escape(str(data.get('head_hash') or '(empty)'))}[/dim]")
40
+ legacy = data.get("legacy_rows", 0)
41
+ if legacy:
42
+ console.print(f"[dim]{legacy} legacy row(s) predate the chain.[/dim]")
43
+
44
+
45
+ @audit_group.command("verify")
46
+ @click.pass_context
47
+ @handle_errors
48
+ def audit_verify(ctx: click.Context) -> None:
49
+ """Walk the whole audit chain, recomputing every row hash
50
+ (POST /v1/audit/chain/verify)."""
51
+ cfg = ctx.obj
52
+ with get_client(cfg) as client:
53
+ if not is_json_mode(cfg):
54
+ with spinner("Verifying chain..."):
55
+ resp = client.post("/v1/audit/chain/verify", json={})
56
+ else:
57
+ resp = client.post("/v1/audit/chain/verify", json={})
58
+ resp.raise_for_status()
59
+ data = resp.json()
60
+
61
+ if is_json_mode(cfg):
62
+ print_json(data)
63
+ return
64
+ if data.get("valid"):
65
+ console.print(
66
+ f"[success]Chain valid[/success] — {data.get('length', 0)} row(s), "
67
+ f"head [dim]{escape(str(data.get('head_hash') or '(empty)'))}[/dim]"
68
+ )
69
+ else:
70
+ console.print(
71
+ f"[error]Chain BROKEN[/error] at row index "
72
+ f"[bold]{data.get('first_broken_index')}[/bold] "
73
+ f"of {data.get('length', 0)}."
74
+ )
75
+ raise SystemExit(1)
76
+ legacy = data.get("legacy_rows", 0)
77
+ if legacy:
78
+ console.print(f"[dim]{legacy} legacy row(s) predate the chain.[/dim]")
@@ -0,0 +1,246 @@
1
+ """cortexdb code — the Code Intelligence Plane (plan T-904).
2
+
3
+ Thin wrappers over ``/v1/code/*``: register repositories, inspect status,
4
+ import compiler-tier (SCIP) indexes and coverage, export/import the
5
+ hash-verified index bundle, pin snapshots, run GC, and print the earned
6
+ capability matrix. Requires the "Code memory" feature bundle on the
7
+ server (`CORTEX_CODE_PLANE=1`); with it off every command reports the
8
+ 503 the server returns.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import pathlib
15
+ import sys
16
+
17
+ import click
18
+
19
+ from cortexdb_cli.client import get_client
20
+ from cortexdb_cli.errors import handle_errors
21
+ from cortexdb_cli.output import is_json_mode, spinner
22
+
23
+
24
+ @click.group("code")
25
+ def code_group() -> None:
26
+ """Code memory — repositories, imports, bundles, GC."""
27
+
28
+
29
+ def _echo(cfg, payload) -> None:
30
+ if is_json_mode(cfg):
31
+ click.echo(json.dumps(payload, indent=2))
32
+ else:
33
+ click.echo(json.dumps(payload, indent=2))
34
+
35
+
36
+ @code_group.command("add")
37
+ @click.argument("name")
38
+ @click.argument("path", type=click.Path(exists=True, file_okay=False))
39
+ @click.pass_context
40
+ @handle_errors
41
+ def code_add(ctx: click.Context, name: str, path: str) -> None:
42
+ """Register + index a local repository (POST /v1/code/repos).
43
+
44
+ NAME is the repo handle used by every other verb; PATH is the local
45
+ checkout. Indexing runs synchronously and reports the fact counts.
46
+ """
47
+ cfg = ctx.obj
48
+ absolute = str(pathlib.Path(path).resolve())
49
+ with get_client(cfg) as client:
50
+ with spinner(f"Indexing {name}..."):
51
+ resp = client.post(
52
+ "/v1/code/repos", json={"name": name, "path": absolute}
53
+ )
54
+ resp.raise_for_status()
55
+ _echo(cfg, resp.json())
56
+
57
+
58
+ @code_group.command("status")
59
+ @click.argument("name", required=False)
60
+ @click.pass_context
61
+ @handle_errors
62
+ def code_status(ctx: click.Context, name: str | None) -> None:
63
+ """Repo status (GET /v1/code/repos[/NAME]) — snapshot, stats, scope."""
64
+ cfg = ctx.obj
65
+ with get_client(cfg) as client:
66
+ url = f"/v1/code/repos/{name}" if name else "/v1/code/repos"
67
+ resp = client.get(url)
68
+ resp.raise_for_status()
69
+ _echo(cfg, resp.json())
70
+
71
+
72
+ @code_group.command("import-scip")
73
+ @click.argument("name")
74
+ @click.argument("index_file", type=click.Path(exists=True, dir_okay=False))
75
+ @click.pass_context
76
+ @handle_errors
77
+ def code_import_scip(ctx: click.Context, name: str, index_file: str) -> None:
78
+ """Import a compiler-tier .scip index (POST /v1/code/import/scip).
79
+
80
+ Produce one with scip-typescript / scip-go / rust-analyzer etc.;
81
+ documents are validated per-file against the repo snapshot and the
82
+ response lists exactly what was accepted and what was rejected why.
83
+ """
84
+ cfg = ctx.obj
85
+ payload = pathlib.Path(index_file).read_bytes()
86
+ with get_client(cfg) as client:
87
+ with spinner("Importing SCIP index..."):
88
+ resp = client.post(
89
+ f"/v1/code/import/scip?repo={name}",
90
+ content=payload,
91
+ headers={"Content-Type": "application/octet-stream"},
92
+ )
93
+ resp.raise_for_status()
94
+ _echo(cfg, resp.json())
95
+
96
+
97
+ @code_group.command("import-coverage")
98
+ @click.argument("name")
99
+ @click.argument("lcov_file", type=click.Path(exists=True, dir_okay=False))
100
+ @click.pass_context
101
+ @handle_errors
102
+ def code_import_coverage(ctx: click.Context, name: str, lcov_file: str) -> None:
103
+ """Import an lcov coverage run (POST /v1/code/import/coverage) —
104
+ upgrades tests.for_change rows from static to observed."""
105
+ cfg = ctx.obj
106
+ lcov = pathlib.Path(lcov_file).read_text(encoding="utf-8", errors="replace")
107
+ with get_client(cfg) as client:
108
+ resp = client.post(
109
+ "/v1/code/import/coverage", json={"repo": name, "lcov": lcov}
110
+ )
111
+ resp.raise_for_status()
112
+ _echo(cfg, resp.json())
113
+
114
+
115
+ @code_group.command("export-index")
116
+ @click.argument("name")
117
+ @click.argument("out_file", type=click.Path(dir_okay=False))
118
+ @click.pass_context
119
+ @handle_errors
120
+ def code_export_index(ctx: click.Context, name: str, out_file: str) -> None:
121
+ """Download the hash-verified index bundle (GET /v1/code/export-index)."""
122
+ cfg = ctx.obj
123
+ with get_client(cfg) as client:
124
+ with spinner("Exporting index..."):
125
+ resp = client.get(f"/v1/code/export-index?repo={name}")
126
+ resp.raise_for_status()
127
+ pathlib.Path(out_file).write_bytes(resp.content)
128
+ click.echo(f"Wrote {len(resp.content)} bytes to {out_file}")
129
+
130
+
131
+ @code_group.command("import-index")
132
+ @click.argument("name")
133
+ @click.argument("bundle_file", type=click.Path(exists=True, dir_okay=False))
134
+ @click.pass_context
135
+ @handle_errors
136
+ def code_import_index(ctx: click.Context, name: str, bundle_file: str) -> None:
137
+ """Restore a bundle (POST /v1/code/import-index). Every hash is
138
+ verified before any write; a tampered bundle is rejected whole."""
139
+ cfg = ctx.obj
140
+ payload = pathlib.Path(bundle_file).read_bytes()
141
+ with get_client(cfg) as client:
142
+ with spinner("Verifying and importing bundle..."):
143
+ resp = client.post(
144
+ f"/v1/code/import-index?repo={name}",
145
+ content=payload,
146
+ headers={"Content-Type": "application/octet-stream"},
147
+ )
148
+ resp.raise_for_status()
149
+ _echo(cfg, resp.json())
150
+
151
+
152
+ @code_group.command("pin")
153
+ @click.argument("name")
154
+ @click.argument("manifest_hash")
155
+ @click.option("--label", default="", help="Human label for the pin")
156
+ @click.option("--remove", is_flag=True, help="Remove the pin instead")
157
+ @click.pass_context
158
+ @handle_errors
159
+ def code_pin(
160
+ ctx: click.Context, name: str, manifest_hash: str, label: str, remove: bool
161
+ ) -> None:
162
+ """Pin (or unpin) a snapshot manifest so GC always retains it."""
163
+ cfg = ctx.obj
164
+ body = {"repo": name, "manifest_hash": manifest_hash, "label": label}
165
+ with get_client(cfg) as client:
166
+ if remove:
167
+ resp = client.request("DELETE", "/v1/code/pins", json=body)
168
+ else:
169
+ resp = client.post("/v1/code/pins", json=body)
170
+ resp.raise_for_status()
171
+ _echo(cfg, resp.json())
172
+
173
+
174
+ @code_group.command("gc")
175
+ @click.argument("name")
176
+ @click.pass_context
177
+ @handle_errors
178
+ def code_gc(ctx: click.Context, name: str) -> None:
179
+ """Run one GC cycle (POST /v1/code/gc) — two-phase mark/delete;
180
+ the report lists every destructive step."""
181
+ cfg = ctx.obj
182
+ with get_client(cfg) as client:
183
+ with spinner("Collecting..."):
184
+ resp = client.post("/v1/code/gc", json={"repo": name})
185
+ resp.raise_for_status()
186
+ _echo(cfg, resp.json())
187
+
188
+
189
+ @code_group.command("matrix")
190
+ @click.pass_context
191
+ @handle_errors
192
+ def code_matrix(ctx: click.Context) -> None:
193
+ """Print the earned language-capability matrix (GET /v1/code/matrix).
194
+
195
+ Every column is earned by tests, never asserted — parse-only
196
+ languages say so.
197
+ """
198
+ cfg = ctx.obj
199
+ with get_client(cfg) as client:
200
+ resp = client.get("/v1/code/matrix")
201
+ resp.raise_for_status()
202
+ body = resp.json()
203
+ if is_json_mode(cfg):
204
+ click.echo(json.dumps(body, indent=2))
205
+ return
206
+ languages = body.get("languages", [])
207
+ if not isinstance(languages, list):
208
+ click.echo(json.dumps(body, indent=2))
209
+ return
210
+ for row in languages:
211
+ if not isinstance(row, dict):
212
+ continue
213
+ name = row.get("language", "?")
214
+ earned = [
215
+ key
216
+ for key in (
217
+ "parse",
218
+ "definitions",
219
+ "imports",
220
+ "calls",
221
+ "inheritance",
222
+ "references",
223
+ "compiler_precise",
224
+ )
225
+ if row.get(key)
226
+ ]
227
+ click.echo(f"{name}: {', '.join(earned) or 'none'}")
228
+
229
+
230
+ @code_group.command("watch")
231
+ @click.argument("name")
232
+ @click.pass_context
233
+ @handle_errors
234
+ def code_watch(ctx: click.Context, name: str) -> None:
235
+ """Watcher status for a repo. Server-side watch wiring reports
236
+ through repo status; this surfaces the same data."""
237
+ cfg = ctx.obj
238
+ with get_client(cfg) as client:
239
+ resp = client.get(f"/v1/code/repos/{name}")
240
+ resp.raise_for_status()
241
+ body = resp.json()
242
+ _echo(cfg, {"repo": name, "snapshot": body.get("snapshot"), "stats": body.get("stats")})
243
+
244
+
245
+ if __name__ == "__main__": # pragma: no cover
246
+ sys.exit(0)