cortexdb-cli 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cortexdb_cli-0.2.0/.gitignore +71 -0
- cortexdb_cli-0.2.0/PKG-INFO +122 -0
- cortexdb_cli-0.2.0/README.md +90 -0
- cortexdb_cli-0.2.0/cortexdb_cli/__init__.py +3 -0
- cortexdb_cli-0.2.0/cortexdb_cli/client.py +92 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/__init__.py +1 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/admin.py +83 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/answer.py +98 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/auth.py +128 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/beliefs.py +121 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/config_cmd.py +50 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/entities.py +174 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/episodes.py +131 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/experience.py +238 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/export_cmd.py +65 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/facts.py +171 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/forget.py +86 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/import_cmd.py +198 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/init.py +95 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/interactive.py +254 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/policy.py +99 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/recall.py +93 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/scopes.py +98 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/search.py +64 -0
- cortexdb_cli-0.2.0/cortexdb_cli/commands/understanding.py +141 -0
- cortexdb_cli-0.2.0/cortexdb_cli/config.py +113 -0
- cortexdb_cli-0.2.0/cortexdb_cli/errors.py +96 -0
- cortexdb_cli-0.2.0/cortexdb_cli/main.py +140 -0
- cortexdb_cli-0.2.0/cortexdb_cli/output.py +399 -0
- cortexdb_cli-0.2.0/cortexdb_cli/state.py +53 -0
- cortexdb_cli-0.2.0/pyproject.toml +56 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Rust
|
|
2
|
+
/target
|
|
3
|
+
**/*.rs.bk
|
|
4
|
+
|
|
5
|
+
# Environment / secrets
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env*.local
|
|
9
|
+
*.pem
|
|
10
|
+
*.key
|
|
11
|
+
.npmrc
|
|
12
|
+
|
|
13
|
+
# SQLite database
|
|
14
|
+
*.sqlite
|
|
15
|
+
*.sqlite-wal
|
|
16
|
+
*.sqlite-shm
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
Thumbs.db
|
|
21
|
+
desktop.ini
|
|
22
|
+
|
|
23
|
+
# IDE
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
*.swp
|
|
27
|
+
*.swo
|
|
28
|
+
|
|
29
|
+
# Data directories
|
|
30
|
+
cortexdb_data*/
|
|
31
|
+
/data/
|
|
32
|
+
# Per-bench tenant stores (RocksDB + Tantivy + HNSW state; regeneratable per run)
|
|
33
|
+
/data_*/
|
|
34
|
+
# Experimental per-branch stores (not tracked on this branch but left gitignored
|
|
35
|
+
# so checkout from other branches doesn't surface them in git status)
|
|
36
|
+
/event_memory_store/
|
|
37
|
+
/llm_cache/
|
|
38
|
+
|
|
39
|
+
# Benchmark inputs and per-run outputs (kept local, regenerated each run)
|
|
40
|
+
benchmarks/longmemeval/data/
|
|
41
|
+
benchmarks/longmemeval/server_results/
|
|
42
|
+
benchmarks/longmemeval/fast_results/
|
|
43
|
+
benchmarks/longmemeval/micro_results/
|
|
44
|
+
benchmarks/longmemeval/server_logs/
|
|
45
|
+
benchmarks/longmemeval/*.log
|
|
46
|
+
benchmarks/locomo/locomo_results*.json
|
|
47
|
+
benchmarks/locomo/server_results/
|
|
48
|
+
benchmarks/locomo/*.log
|
|
49
|
+
/answer_out.json
|
|
50
|
+
|
|
51
|
+
# Local Claude Code state
|
|
52
|
+
.claude/
|
|
53
|
+
.tmp/
|
|
54
|
+
|
|
55
|
+
# Python
|
|
56
|
+
__pycache__/
|
|
57
|
+
*.pyc
|
|
58
|
+
.venv/
|
|
59
|
+
venv/
|
|
60
|
+
|
|
61
|
+
# Node
|
|
62
|
+
node_modules/
|
|
63
|
+
dist/
|
|
64
|
+
.next/
|
|
65
|
+
|
|
66
|
+
# Egg info
|
|
67
|
+
*.egg-info/
|
|
68
|
+
|
|
69
|
+
# Scratch/debug text files at root
|
|
70
|
+
/*.txt
|
|
71
|
+
/*.log
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cortexdb-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Command-line interface for CortexDB — the long-term memory layer for AI.
|
|
5
|
+
Project-URL: Homepage, https://cortexdb.ai
|
|
6
|
+
Project-URL: Documentation, https://cortexdb.ai/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/cortexdb/cortexdb
|
|
8
|
+
Project-URL: Issues, https://github.com/cortexdb/cortexdb/issues
|
|
9
|
+
Author-email: CortexDB Team <team@cortexdb.ai>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Keywords: agents,ai,cli,cortexdb,llm,memory
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: click>=8.1
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: prompt-toolkit>=3.0
|
|
27
|
+
Requires-Dist: rich-click>=1.7
|
|
28
|
+
Requires-Dist: rich>=13.0
|
|
29
|
+
Requires-Dist: tomli-w>=1.0
|
|
30
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# cortexdb-cli
|
|
34
|
+
|
|
35
|
+
Command-line interface for [CortexDB](https://cortexdb.ai) — the long-term memory layer for AI systems.
|
|
36
|
+
|
|
37
|
+
Wraps the v1 HTTP API in a small, fast Click + Rich CLI. Anonymous signup
|
|
38
|
+
in one command, no email or card; works against the public SaaS or any
|
|
39
|
+
self-hosted CortexDB deployment.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install cortexdb-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires Python 3.10+.
|
|
48
|
+
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 1. One-time setup — anonymous signup, writes ~/.cortexdb/state.json
|
|
53
|
+
cortexdb init
|
|
54
|
+
|
|
55
|
+
# 2. Store a memory
|
|
56
|
+
cortexdb remember "Q3 revenue: $2.4M. We're on track for $10M ARR by year-end."
|
|
57
|
+
|
|
58
|
+
# 3. Recall it
|
|
59
|
+
cortexdb recall "Q3 revenue?"
|
|
60
|
+
|
|
61
|
+
# 4. Or just type — drops into a REPL
|
|
62
|
+
cortexdb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The signup token has a 7-day TTL on the free tier. Re-run `cortexdb init`
|
|
66
|
+
to refresh, or pass `--api-key` + `--actor` to use a token from your IdP.
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
cortexdb init anonymous signup; persist token + actor
|
|
72
|
+
cortexdb remember "..." store a memory (POST /v1/experience)
|
|
73
|
+
cortexdb recall "..." recall a stratified context pack
|
|
74
|
+
cortexdb search "..." granular event-level search
|
|
75
|
+
cortexdb forget --memory-id ... delete with audit
|
|
76
|
+
cortexdb episodes {list,get,delete} episode CRUD
|
|
77
|
+
cortexdb entities {list,get,link} entity rollups over the Facts layer
|
|
78
|
+
cortexdb admin {health,usage,layers} diagnostics
|
|
79
|
+
cortexdb import data.json bulk-load (JSON / JSONL / CSV / TXT)
|
|
80
|
+
cortexdb export -o backup.json export memories
|
|
81
|
+
cortexdb config {show,set} view / edit ~/.cortexdb/config.toml
|
|
82
|
+
cortexdb interactive REPL
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Three layers, in precedence order:
|
|
88
|
+
|
|
89
|
+
1. **CLI flags** — `--api-key`, `--endpoint`, `--actor`, `--scope`, `--profile`
|
|
90
|
+
2. **Environment** — `CORTEXDB_API_KEY`, `CORTEXDB_URL`, `CORTEXDB_ACTOR`, `CORTEXDB_SCOPE`
|
|
91
|
+
3. **Persisted state**:
|
|
92
|
+
- `~/.cortexdb/config.toml` — endpoint, profile names (human-editable)
|
|
93
|
+
- `~/.cortexdb/state.json` — token, actor, scope, expiry (managed by `init`, 0600 on POSIX)
|
|
94
|
+
|
|
95
|
+
The state.json format matches the cortexdb-mcp 0.3.0 server's, so you can
|
|
96
|
+
copy state across to use both tools from the same anonymous identity.
|
|
97
|
+
|
|
98
|
+
## Pipe-friendly
|
|
99
|
+
|
|
100
|
+
Auto-detects when stdout isn't a TTY and emits JSON:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
cortexdb recall "Q3 revenue" | jq .context_block
|
|
104
|
+
cortexdb entities list | jq '.[].subject'
|
|
105
|
+
echo "remember this" | cortexdb remember -
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Force JSON mode any time with `--json`.
|
|
109
|
+
|
|
110
|
+
## Auth notes
|
|
111
|
+
|
|
112
|
+
- The v1 API requires both `Authorization: Bearer <PASETO>` and `X-Cortex-Actor`.
|
|
113
|
+
The CLI stamps both on every request — you never need to pass them by hand.
|
|
114
|
+
- 401s show the specific error code (`TOKEN_EXPIRED`, `actor_mismatch`, …)
|
|
115
|
+
and the remediation: usually `cortexdb init`.
|
|
116
|
+
- 403s cite the missing capability and the policy tier that denied. If a
|
|
117
|
+
recall returns `diagnostics.read denied`, pass `--diagnostics none` —
|
|
118
|
+
free-tier tokens don't carry the `diagnostics.read` capability.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
Apache-2.0
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# cortexdb-cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [CortexDB](https://cortexdb.ai) — the long-term memory layer for AI systems.
|
|
4
|
+
|
|
5
|
+
Wraps the v1 HTTP API in a small, fast Click + Rich CLI. Anonymous signup
|
|
6
|
+
in one command, no email or card; works against the public SaaS or any
|
|
7
|
+
self-hosted CortexDB deployment.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install cortexdb-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.10+.
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 1. One-time setup — anonymous signup, writes ~/.cortexdb/state.json
|
|
21
|
+
cortexdb init
|
|
22
|
+
|
|
23
|
+
# 2. Store a memory
|
|
24
|
+
cortexdb remember "Q3 revenue: $2.4M. We're on track for $10M ARR by year-end."
|
|
25
|
+
|
|
26
|
+
# 3. Recall it
|
|
27
|
+
cortexdb recall "Q3 revenue?"
|
|
28
|
+
|
|
29
|
+
# 4. Or just type — drops into a REPL
|
|
30
|
+
cortexdb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The signup token has a 7-day TTL on the free tier. Re-run `cortexdb init`
|
|
34
|
+
to refresh, or pass `--api-key` + `--actor` to use a token from your IdP.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
cortexdb init anonymous signup; persist token + actor
|
|
40
|
+
cortexdb remember "..." store a memory (POST /v1/experience)
|
|
41
|
+
cortexdb recall "..." recall a stratified context pack
|
|
42
|
+
cortexdb search "..." granular event-level search
|
|
43
|
+
cortexdb forget --memory-id ... delete with audit
|
|
44
|
+
cortexdb episodes {list,get,delete} episode CRUD
|
|
45
|
+
cortexdb entities {list,get,link} entity rollups over the Facts layer
|
|
46
|
+
cortexdb admin {health,usage,layers} diagnostics
|
|
47
|
+
cortexdb import data.json bulk-load (JSON / JSONL / CSV / TXT)
|
|
48
|
+
cortexdb export -o backup.json export memories
|
|
49
|
+
cortexdb config {show,set} view / edit ~/.cortexdb/config.toml
|
|
50
|
+
cortexdb interactive REPL
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Three layers, in precedence order:
|
|
56
|
+
|
|
57
|
+
1. **CLI flags** — `--api-key`, `--endpoint`, `--actor`, `--scope`, `--profile`
|
|
58
|
+
2. **Environment** — `CORTEXDB_API_KEY`, `CORTEXDB_URL`, `CORTEXDB_ACTOR`, `CORTEXDB_SCOPE`
|
|
59
|
+
3. **Persisted state**:
|
|
60
|
+
- `~/.cortexdb/config.toml` — endpoint, profile names (human-editable)
|
|
61
|
+
- `~/.cortexdb/state.json` — token, actor, scope, expiry (managed by `init`, 0600 on POSIX)
|
|
62
|
+
|
|
63
|
+
The state.json format matches the cortexdb-mcp 0.3.0 server's, so you can
|
|
64
|
+
copy state across to use both tools from the same anonymous identity.
|
|
65
|
+
|
|
66
|
+
## Pipe-friendly
|
|
67
|
+
|
|
68
|
+
Auto-detects when stdout isn't a TTY and emits JSON:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cortexdb recall "Q3 revenue" | jq .context_block
|
|
72
|
+
cortexdb entities list | jq '.[].subject'
|
|
73
|
+
echo "remember this" | cortexdb remember -
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Force JSON mode any time with `--json`.
|
|
77
|
+
|
|
78
|
+
## Auth notes
|
|
79
|
+
|
|
80
|
+
- The v1 API requires both `Authorization: Bearer <PASETO>` and `X-Cortex-Actor`.
|
|
81
|
+
The CLI stamps both on every request — you never need to pass them by hand.
|
|
82
|
+
- 401s show the specific error code (`TOKEN_EXPIRED`, `actor_mismatch`, …)
|
|
83
|
+
and the remediation: usually `cortexdb init`.
|
|
84
|
+
- 403s cite the missing capability and the policy tier that denied. If a
|
|
85
|
+
recall returns `diagnostics.read denied`, pass `--diagnostics none` —
|
|
86
|
+
free-tier tokens don't carry the `diagnostics.read` capability.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
Apache-2.0
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""HTTP client for the CortexDB v1 API.
|
|
2
|
+
|
|
3
|
+
Thin wrapper around httpx that reads credentials + endpoint from the CLI
|
|
4
|
+
context and stamps every request with both `Authorization: Bearer …` and
|
|
5
|
+
`X-Cortex-Actor: …`. The actor header is mandatory on v1 — missing it
|
|
6
|
+
returns 401 ACTOR_MISMATCH no matter how valid the token is.
|
|
7
|
+
|
|
8
|
+
We deliberately don't depend on the cortexdbai SDK: keeping the CLI a
|
|
9
|
+
straight HTTP client makes the install fast and lets us pin httpx
|
|
10
|
+
independently from the SDK's release cadence.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from contextlib import contextmanager
|
|
16
|
+
from typing import Any, Generator
|
|
17
|
+
|
|
18
|
+
import httpx
|
|
19
|
+
|
|
20
|
+
from cortexdb_cli import __version__
|
|
21
|
+
|
|
22
|
+
DEFAULT_ENDPOINT = "https://api-v1.cortexdb.ai"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CortexClient:
|
|
26
|
+
"""Synchronous HTTP client for the CortexDB v1 API."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
endpoint: str,
|
|
31
|
+
api_key: str,
|
|
32
|
+
actor: str = "",
|
|
33
|
+
timeout: float = 30.0,
|
|
34
|
+
) -> None:
|
|
35
|
+
self.endpoint = endpoint.rstrip("/")
|
|
36
|
+
self.api_key = api_key
|
|
37
|
+
self.actor = actor
|
|
38
|
+
headers: dict[str, str] = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"User-Agent": f"cortexdb-cli/{__version__}",
|
|
41
|
+
}
|
|
42
|
+
if api_key:
|
|
43
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
44
|
+
if actor:
|
|
45
|
+
headers["X-Cortex-Actor"] = actor
|
|
46
|
+
self._http = httpx.Client(
|
|
47
|
+
base_url=self.endpoint,
|
|
48
|
+
headers=headers,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def close(self) -> None:
|
|
53
|
+
self._http.close()
|
|
54
|
+
|
|
55
|
+
def get(self, path: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
|
56
|
+
return self._http.get(path, params=params)
|
|
57
|
+
|
|
58
|
+
def post(
|
|
59
|
+
self,
|
|
60
|
+
path: str,
|
|
61
|
+
json: dict[str, Any] | None = None,
|
|
62
|
+
params: dict[str, Any] | None = None,
|
|
63
|
+
) -> httpx.Response:
|
|
64
|
+
return self._http.post(path, json=json, params=params)
|
|
65
|
+
|
|
66
|
+
def delete(self, path: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
|
67
|
+
return self._http.delete(path, params=params)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def signup(endpoint: str = DEFAULT_ENDPOINT, timeout: float = 30.0) -> dict[str, Any]:
|
|
71
|
+
"""Call POST /v1/auth/signup and return {token, user_id, scope, expires_at, tier}.
|
|
72
|
+
|
|
73
|
+
The endpoint is public — no auth required. Free tier; 7-day TTL.
|
|
74
|
+
"""
|
|
75
|
+
with httpx.Client(timeout=timeout) as http:
|
|
76
|
+
resp = http.post(f"{endpoint.rstrip('/')}/v1/auth/signup", json={})
|
|
77
|
+
resp.raise_for_status()
|
|
78
|
+
return resp.json()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@contextmanager
|
|
82
|
+
def get_client(ctx: dict[str, Any]) -> Generator[CortexClient, None, None]:
|
|
83
|
+
"""Create a CortexClient from CLI context."""
|
|
84
|
+
client = CortexClient(
|
|
85
|
+
endpoint=ctx.get("endpoint", DEFAULT_ENDPOINT),
|
|
86
|
+
api_key=ctx.get("api_key", ""),
|
|
87
|
+
actor=ctx.get("actor", ""),
|
|
88
|
+
)
|
|
89
|
+
try:
|
|
90
|
+
yield client
|
|
91
|
+
finally:
|
|
92
|
+
client.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CortexDB CLI command modules."""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""cortexdb admin — health, usage, layer stats."""
|
|
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 (
|
|
10
|
+
is_json_mode,
|
|
11
|
+
print_health,
|
|
12
|
+
print_ontology,
|
|
13
|
+
print_usage,
|
|
14
|
+
spinner,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group("admin")
|
|
19
|
+
def admin_group() -> None:
|
|
20
|
+
"""Diagnostics — reachability, identity, rate-limit, layer status."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@admin_group.command("health")
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@handle_errors
|
|
26
|
+
def admin_health(ctx: click.Context) -> None:
|
|
27
|
+
"""Auth + reachability check (GET /v1/auth/whoami)."""
|
|
28
|
+
cfg = ctx.obj
|
|
29
|
+
with get_client(cfg) as client:
|
|
30
|
+
resp = client.get("/v1/auth/whoami")
|
|
31
|
+
resp.raise_for_status()
|
|
32
|
+
body = resp.json()
|
|
33
|
+
# Normalise into the shape print_health expects.
|
|
34
|
+
body.setdefault("status", "ok")
|
|
35
|
+
print_health(body, cfg)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@admin_group.command("usage")
|
|
39
|
+
@click.pass_context
|
|
40
|
+
@handle_errors
|
|
41
|
+
def admin_usage(ctx: click.Context) -> None:
|
|
42
|
+
"""Tier, capabilities, rate-limit headroom, token expiry."""
|
|
43
|
+
cfg = ctx.obj
|
|
44
|
+
with get_client(cfg) as client:
|
|
45
|
+
if not is_json_mode(cfg):
|
|
46
|
+
with spinner("Loading usage..."):
|
|
47
|
+
resp = client.get("/v1/auth/whoami")
|
|
48
|
+
else:
|
|
49
|
+
resp = client.get("/v1/auth/whoami")
|
|
50
|
+
resp.raise_for_status()
|
|
51
|
+
body = resp.json()
|
|
52
|
+
# Fold rate-limit + expiry response headers into the body so the
|
|
53
|
+
# output formatter can render them in one block.
|
|
54
|
+
for k in (
|
|
55
|
+
"X-RateLimit-Limit",
|
|
56
|
+
"X-RateLimit-Reset",
|
|
57
|
+
"X-RateLimit-Remaining",
|
|
58
|
+
"X-Cortex-Token-Expires-In",
|
|
59
|
+
"X-Cortex-Token-Expires-At",
|
|
60
|
+
):
|
|
61
|
+
if k in resp.headers:
|
|
62
|
+
body[k] = resp.headers[k]
|
|
63
|
+
print_usage(body, cfg)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@admin_group.command("layers")
|
|
67
|
+
@click.pass_context
|
|
68
|
+
@handle_errors
|
|
69
|
+
def admin_layers(ctx: click.Context) -> None:
|
|
70
|
+
"""Per-layer counts and synthesis lag (GET /v1/admin/layers/stats).
|
|
71
|
+
|
|
72
|
+
Stability: experimental — the endpoint is gated by the
|
|
73
|
+
`admin.layers.stats` capability and may not be present on the free tier.
|
|
74
|
+
"""
|
|
75
|
+
cfg = ctx.obj
|
|
76
|
+
with get_client(cfg) as client:
|
|
77
|
+
if not is_json_mode(cfg):
|
|
78
|
+
with spinner("Loading layer stats..."):
|
|
79
|
+
resp = client.get("/v1/admin/layers/stats")
|
|
80
|
+
else:
|
|
81
|
+
resp = client.get("/v1/admin/layers/stats")
|
|
82
|
+
resp.raise_for_status()
|
|
83
|
+
print_ontology(resp.json(), cfg)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""cortexdb answer — POST /v1/answer (recall + LLM answer)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from cortexdb_cli.client import get_client
|
|
10
|
+
from cortexdb_cli.errors import handle_errors
|
|
11
|
+
from cortexdb_cli.output import console, is_json_mode, print_json, spinner
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command("answer")
|
|
15
|
+
@click.argument("question")
|
|
16
|
+
@click.option("--scope", "-S", help="Scope path (defaults to your default scope)")
|
|
17
|
+
@click.option(
|
|
18
|
+
"--view",
|
|
19
|
+
type=click.Choice(["raw", "granular", "structured", "holistic", "descend"]),
|
|
20
|
+
default="holistic",
|
|
21
|
+
show_default=True,
|
|
22
|
+
)
|
|
23
|
+
@click.option("--temporal", help='Natural-language time window, e.g. "last 30 days"')
|
|
24
|
+
@click.option("--answer-model", help="Override the answer model (paid tier only).")
|
|
25
|
+
@click.option(
|
|
26
|
+
"--diagnostics",
|
|
27
|
+
type=click.Choice(["none", "summary", "full"]),
|
|
28
|
+
default="none",
|
|
29
|
+
show_default=True,
|
|
30
|
+
)
|
|
31
|
+
@click.pass_context
|
|
32
|
+
@handle_errors
|
|
33
|
+
def answer_cmd(
|
|
34
|
+
ctx: click.Context,
|
|
35
|
+
question: str,
|
|
36
|
+
scope: str | None,
|
|
37
|
+
view: str,
|
|
38
|
+
temporal: str | None,
|
|
39
|
+
answer_model: str | None,
|
|
40
|
+
diagnostics: str,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Ask a question — server recalls context and an LLM answers it.
|
|
43
|
+
|
|
44
|
+
Counts against the tenant's LLM quota. Requires the `llm.invoke`
|
|
45
|
+
capability (granted by default on every tier including Free).
|
|
46
|
+
|
|
47
|
+
cortexdb answer "Did Acme renew?" --temporal "last 30 days"
|
|
48
|
+
"""
|
|
49
|
+
cfg = ctx.obj
|
|
50
|
+
target_scope = scope or cfg.get("scope") or cfg.get("actor")
|
|
51
|
+
if not target_scope:
|
|
52
|
+
raise click.UsageError(
|
|
53
|
+
"No scope configured. Run `cortexdb init` first, or pass --scope explicitly."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
body: dict[str, Any] = {
|
|
57
|
+
"scope": target_scope,
|
|
58
|
+
"question": question,
|
|
59
|
+
"view": view,
|
|
60
|
+
"diagnostics": diagnostics,
|
|
61
|
+
}
|
|
62
|
+
if temporal:
|
|
63
|
+
body["temporal"] = {"natural": temporal}
|
|
64
|
+
if answer_model:
|
|
65
|
+
body["answer_model"] = answer_model
|
|
66
|
+
|
|
67
|
+
with get_client(cfg) as client:
|
|
68
|
+
if not is_json_mode(cfg):
|
|
69
|
+
with spinner("Answering..."):
|
|
70
|
+
resp = client.post("/v1/answer", json=body)
|
|
71
|
+
else:
|
|
72
|
+
resp = client.post("/v1/answer", json=body)
|
|
73
|
+
resp.raise_for_status()
|
|
74
|
+
data = resp.json()
|
|
75
|
+
|
|
76
|
+
if is_json_mode(cfg):
|
|
77
|
+
print_json(data)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
from rich.markup import escape
|
|
81
|
+
from rich.panel import Panel
|
|
82
|
+
|
|
83
|
+
text = data.get("answer") or data.get("text") or "(no answer returned)"
|
|
84
|
+
cites = data.get("citations") or []
|
|
85
|
+
|
|
86
|
+
console.print(
|
|
87
|
+
Panel(
|
|
88
|
+
escape(str(text)),
|
|
89
|
+
title="[header]Answer[/header]",
|
|
90
|
+
border_style="cyan",
|
|
91
|
+
padding=(1, 2),
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
if cites:
|
|
95
|
+
console.print(f"\n[dim]{len(cites)} citation(s):[/dim]")
|
|
96
|
+
for c in cites[:10]:
|
|
97
|
+
eid = c.get("event_id") or c.get("id") or ""
|
|
98
|
+
console.print(f" [dim]· {escape(eid)}[/dim]")
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""cortexdb auth — /v1/auth/{whoami,signup,tokens,revoke}."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from cortexdb_cli.client import DEFAULT_ENDPOINT, get_client, signup as signup_call
|
|
8
|
+
from cortexdb_cli.config import config_path, save_config
|
|
9
|
+
from cortexdb_cli.errors import handle_errors
|
|
10
|
+
from cortexdb_cli.output import console, is_json_mode, print_json, print_health, spinner
|
|
11
|
+
from cortexdb_cli.state import save_state, state_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group("auth")
|
|
15
|
+
def auth_group() -> None:
|
|
16
|
+
"""Identity, token mint, revoke, anonymous signup."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@auth_group.command("whoami")
|
|
20
|
+
@click.pass_context
|
|
21
|
+
@handle_errors
|
|
22
|
+
def auth_whoami(ctx: click.Context) -> None:
|
|
23
|
+
"""GET /v1/auth/whoami — caller, tenant, effective capabilities."""
|
|
24
|
+
cfg = ctx.obj
|
|
25
|
+
with get_client(cfg) as client:
|
|
26
|
+
resp = client.get("/v1/auth/whoami")
|
|
27
|
+
resp.raise_for_status()
|
|
28
|
+
body = resp.json()
|
|
29
|
+
body.setdefault("status", "ok")
|
|
30
|
+
print_health(body, cfg)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@auth_group.command("signup")
|
|
34
|
+
@click.option(
|
|
35
|
+
"--endpoint",
|
|
36
|
+
default=DEFAULT_ENDPOINT,
|
|
37
|
+
show_default=True,
|
|
38
|
+
help="CortexDB v1 API base URL.",
|
|
39
|
+
)
|
|
40
|
+
@click.option("--save", "save_state_flag", is_flag=True, help="Persist to ~/.cortexdb/state.json")
|
|
41
|
+
@click.pass_context
|
|
42
|
+
@handle_errors
|
|
43
|
+
def auth_signup(ctx: click.Context, endpoint: str, save_state_flag: bool) -> None:
|
|
44
|
+
"""POST /v1/auth/signup — anonymous free-tier token.
|
|
45
|
+
|
|
46
|
+
Without --save, prints the response and exits — useful when you want
|
|
47
|
+
to feed the token into a CI env var. With --save, behaves like
|
|
48
|
+
`cortexdb init` and writes ~/.cortexdb/state.json.
|
|
49
|
+
"""
|
|
50
|
+
resp = signup_call(endpoint)
|
|
51
|
+
if save_state_flag:
|
|
52
|
+
save_state(
|
|
53
|
+
{
|
|
54
|
+
"token": resp["token"],
|
|
55
|
+
"user_id": resp["user_id"],
|
|
56
|
+
"scope": resp.get("scope") or resp["user_id"],
|
|
57
|
+
"expires_at": resp.get("expires_at"),
|
|
58
|
+
"tier": resp.get("tier", "free"),
|
|
59
|
+
"source": "signup",
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
save_config("default", {"endpoint": endpoint})
|
|
63
|
+
if is_json_mode(ctx.obj):
|
|
64
|
+
print_json(resp)
|
|
65
|
+
else:
|
|
66
|
+
console.print(
|
|
67
|
+
f"[success]Saved state[/success] to {state_path()}\n"
|
|
68
|
+
f" Actor: {resp['user_id']}\n"
|
|
69
|
+
f" Scope: {resp.get('scope') or resp['user_id']}\n"
|
|
70
|
+
f" Expires: {resp.get('expires_at', '?')}"
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
print_json(resp)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@auth_group.command("tokens")
|
|
77
|
+
@click.option("--subject", required=True, help="`sub` claim on the minted token, e.g. user:alice")
|
|
78
|
+
@click.option(
|
|
79
|
+
"--ttl",
|
|
80
|
+
"ttl_seconds",
|
|
81
|
+
type=int,
|
|
82
|
+
default=3600,
|
|
83
|
+
show_default=True,
|
|
84
|
+
help="Token lifetime in seconds (server enforces a tenant ceiling).",
|
|
85
|
+
)
|
|
86
|
+
@click.option("--scope", "scopes", multiple=True, help="`scopes` claim — repeat for multiple.")
|
|
87
|
+
@click.pass_context
|
|
88
|
+
@handle_errors
|
|
89
|
+
def auth_tokens(
|
|
90
|
+
ctx: click.Context, subject: str, ttl_seconds: int, scopes: tuple[str, ...]
|
|
91
|
+
) -> None:
|
|
92
|
+
"""POST /v1/auth/tokens — mint a service-account token.
|
|
93
|
+
|
|
94
|
+
Requires the `auth.mint` capability. Free-tier anonymous tokens do not
|
|
95
|
+
have it — you must hold an IdP-issued token (or a token previously
|
|
96
|
+
minted by an account that does).
|
|
97
|
+
|
|
98
|
+
cortexdb auth tokens --subject user:alice --ttl 3600 --scope org:acme/user:alice
|
|
99
|
+
"""
|
|
100
|
+
cfg = ctx.obj
|
|
101
|
+
body: dict[str, object] = {"subject": subject, "ttl_seconds": ttl_seconds}
|
|
102
|
+
if scopes:
|
|
103
|
+
body["scopes"] = list(scopes)
|
|
104
|
+
with get_client(cfg) as client:
|
|
105
|
+
if not is_json_mode(cfg):
|
|
106
|
+
with spinner("Minting..."):
|
|
107
|
+
resp = client.post("/v1/auth/tokens", json=body)
|
|
108
|
+
else:
|
|
109
|
+
resp = client.post("/v1/auth/tokens", json=body)
|
|
110
|
+
resp.raise_for_status()
|
|
111
|
+
print_json(resp.json())
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@auth_group.command("revoke")
|
|
115
|
+
@click.option("--jti", required=True, help="Token jti claim to add to the revocation list.")
|
|
116
|
+
@click.option("--reason", required=True, help="Audit reason (e.g. 'token leaked').")
|
|
117
|
+
@click.pass_context
|
|
118
|
+
@handle_errors
|
|
119
|
+
def auth_revoke(ctx: click.Context, jti: str, reason: str) -> None:
|
|
120
|
+
"""POST /v1/auth/revoke — invalidate a token by jti."""
|
|
121
|
+
cfg = ctx.obj
|
|
122
|
+
with get_client(cfg) as client:
|
|
123
|
+
resp = client.post("/v1/auth/revoke", json={"jti": jti, "reason": reason})
|
|
124
|
+
resp.raise_for_status()
|
|
125
|
+
if is_json_mode(cfg):
|
|
126
|
+
print_json({"revoked": jti})
|
|
127
|
+
else:
|
|
128
|
+
console.print(f"[success]Revoked[/success] jti={jti}")
|