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.
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/.gitignore +7 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/PKG-INFO +2 -2
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/__init__.py +1 -1
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/audit.py +78 -78
- cortexdb_cli-0.5.0/cortexdb_cli/commands/code.py +246 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/forget.py +132 -132
- cortexdb_cli-0.5.0/cortexdb_cli/commands/onboard.py +721 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/main.py +13 -0
- cortexdb_cli-0.5.0/cortexdb_cli/onboarding.py +497 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/pyproject.toml +1 -1
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/README.md +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/client.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/__init__.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/admin.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/answer.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/auth.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/beliefs.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/claims.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/compose.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/config_cmd.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/conflicts.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/entities.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/episodes.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/experience.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/export_cmd.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/facts.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/import_cmd.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/init.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/interactive.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/policy.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/recall.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/scopes.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/search.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/commands/understanding.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/config.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/errors.py +0 -0
- {cortexdb_cli-0.4.2 → cortexdb_cli-0.5.0}/cortexdb_cli/output.py +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: cortexdb-cli
|
|
3
|
-
Version: 0.
|
|
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,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)
|