cortexdb-cli 0.5.1__tar.gz → 0.5.3__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.5.1 → cortexdb_cli-0.5.3}/PKG-INFO +20 -4
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/README.md +106 -90
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/__init__.py +1 -1
- cortexdb_cli-0.5.3/cortexdb_cli/commands/init.py +230 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/pyproject.toml +1 -1
- cortexdb_cli-0.5.1/cortexdb_cli/commands/init.py +0 -129
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/.gitignore +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/client.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/__init__.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/admin.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/answer.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/audit.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/auth.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/beliefs.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/claims.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/code.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/compose.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/config_cmd.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/conflicts.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/entities.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/episodes.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/experience.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/export_cmd.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/facts.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/forget.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/import_cmd.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/interactive.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/onboard.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/policy.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/recall.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/scopes.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/search.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/understanding.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/commands/wire.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/config.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/errors.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/harness_packs_data.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/main.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/onboarding.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/output.py +0 -0
- {cortexdb_cli-0.5.1 → cortexdb_cli-0.5.3}/cortexdb_cli/state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cortexdb-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
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
|
|
@@ -62,8 +62,22 @@ cortexdb recall "Q3 revenue?"
|
|
|
62
62
|
cortexdb
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
The signup token has a 7-day TTL on the free tier.
|
|
66
|
-
|
|
65
|
+
The signup token has a 7-day TTL on the free tier.
|
|
66
|
+
|
|
67
|
+
**Re-running `cortexdb init` does not refresh it.** `init` reuses an existing
|
|
68
|
+
identity and changes nothing; against an expired one it refuses and exits 3.
|
|
69
|
+
That is deliberate: a new identity is a new *brain*, and memories written
|
|
70
|
+
under the old actor stay where they are, unreachable from the new config.
|
|
71
|
+
|
|
72
|
+
When a token expires you have two safe options:
|
|
73
|
+
|
|
74
|
+
- `cortexdb init --api-key <token> --actor <actor>` — keep the same brain
|
|
75
|
+
with a token you already hold (e.g. from your IdP).
|
|
76
|
+
- `cortexdb init --force` — deliberately start a NEW brain. The previous
|
|
77
|
+
identity is backed up to `state.json.replaced` first.
|
|
78
|
+
|
|
79
|
+
For a second brain alongside the first, isolate instead of replacing:
|
|
80
|
+
`cortexdb --config-dir <dir> init`, or set `CORTEXDB_HOME`.
|
|
67
81
|
|
|
68
82
|
## Commands
|
|
69
83
|
|
|
@@ -112,7 +126,9 @@ Force JSON mode any time with `--json`.
|
|
|
112
126
|
- The v1 API requires both `Authorization: Bearer <PASETO>` and `X-Cortex-Actor`.
|
|
113
127
|
The CLI stamps both on every request — you never need to pass them by hand.
|
|
114
128
|
- 401s show the specific error code (`TOKEN_EXPIRED`, `actor_mismatch`, …)
|
|
115
|
-
and the remediation
|
|
129
|
+
and the remediation. Note that `TOKEN_EXPIRED` is **not** fixed by re-running
|
|
130
|
+
`cortexdb init` — see the identity note above; that would mint a different
|
|
131
|
+
brain rather than restore access to yours.
|
|
116
132
|
- 403s cite the missing capability and the policy tier that denied. If a
|
|
117
133
|
recall returns `diagnostics.read denied`, pass `--diagnostics none` —
|
|
118
134
|
free-tier tokens don't carry the `diagnostics.read` capability.
|
|
@@ -1,90 +1,106 @@
|
|
|
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.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cortexdb
|
|
43
|
-
|
|
44
|
-
cortexdb
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
cortexdb
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
34
|
+
|
|
35
|
+
**Re-running `cortexdb init` does not refresh it.** `init` reuses an existing
|
|
36
|
+
identity and changes nothing; against an expired one it refuses and exits 3.
|
|
37
|
+
That is deliberate: a new identity is a new *brain*, and memories written
|
|
38
|
+
under the old actor stay where they are, unreachable from the new config.
|
|
39
|
+
|
|
40
|
+
When a token expires you have two safe options:
|
|
41
|
+
|
|
42
|
+
- `cortexdb init --api-key <token> --actor <actor>` — keep the same brain
|
|
43
|
+
with a token you already hold (e.g. from your IdP).
|
|
44
|
+
- `cortexdb init --force` — deliberately start a NEW brain. The previous
|
|
45
|
+
identity is backed up to `state.json.replaced` first.
|
|
46
|
+
|
|
47
|
+
For a second brain alongside the first, isolate instead of replacing:
|
|
48
|
+
`cortexdb --config-dir <dir> init`, or set `CORTEXDB_HOME`.
|
|
49
|
+
|
|
50
|
+
## Commands
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
cortexdb init anonymous signup; persist token + actor
|
|
54
|
+
cortexdb remember "..." store a memory (POST /v1/experience)
|
|
55
|
+
cortexdb recall "..." recall a stratified context pack
|
|
56
|
+
cortexdb search "..." granular event-level search
|
|
57
|
+
cortexdb forget --memory-id ... delete with audit
|
|
58
|
+
cortexdb episodes {list,get,delete} episode CRUD
|
|
59
|
+
cortexdb entities {list,get,link} entity rollups over the Facts layer
|
|
60
|
+
cortexdb admin {health,usage,layers} diagnostics
|
|
61
|
+
cortexdb import data.json bulk-load (JSON / JSONL / CSV / TXT)
|
|
62
|
+
cortexdb export -o backup.json export memories
|
|
63
|
+
cortexdb config {show,set} view / edit ~/.cortexdb/config.toml
|
|
64
|
+
cortexdb interactive REPL
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Three layers, in precedence order:
|
|
70
|
+
|
|
71
|
+
1. **CLI flags** — `--api-key`, `--endpoint`, `--actor`, `--scope`, `--profile`
|
|
72
|
+
2. **Environment** — `CORTEXDB_API_KEY`, `CORTEXDB_URL`, `CORTEXDB_ACTOR`, `CORTEXDB_SCOPE`
|
|
73
|
+
3. **Persisted state**:
|
|
74
|
+
- `~/.cortexdb/config.toml` — endpoint, profile names (human-editable)
|
|
75
|
+
- `~/.cortexdb/state.json` — token, actor, scope, expiry (managed by `init`, 0600 on POSIX)
|
|
76
|
+
|
|
77
|
+
The state.json format matches the cortexdb-mcp 0.3.0 server's, so you can
|
|
78
|
+
copy state across to use both tools from the same anonymous identity.
|
|
79
|
+
|
|
80
|
+
## Pipe-friendly
|
|
81
|
+
|
|
82
|
+
Auto-detects when stdout isn't a TTY and emits JSON:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cortexdb recall "Q3 revenue" | jq .context_block
|
|
86
|
+
cortexdb entities list | jq '.[].subject'
|
|
87
|
+
echo "remember this" | cortexdb remember -
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Force JSON mode any time with `--json`.
|
|
91
|
+
|
|
92
|
+
## Auth notes
|
|
93
|
+
|
|
94
|
+
- The v1 API requires both `Authorization: Bearer <PASETO>` and `X-Cortex-Actor`.
|
|
95
|
+
The CLI stamps both on every request — you never need to pass them by hand.
|
|
96
|
+
- 401s show the specific error code (`TOKEN_EXPIRED`, `actor_mismatch`, …)
|
|
97
|
+
and the remediation. Note that `TOKEN_EXPIRED` is **not** fixed by re-running
|
|
98
|
+
`cortexdb init` — see the identity note above; that would mint a different
|
|
99
|
+
brain rather than restore access to yours.
|
|
100
|
+
- 403s cite the missing capability and the policy tier that denied. If a
|
|
101
|
+
recall returns `diagnostics.read denied`, pass `--diagnostics none` —
|
|
102
|
+
free-tier tokens don't carry the `diagnostics.read` capability.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
Apache-2.0
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""cortexdb init - anonymous signup + config persistence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
from cortexdb_cli.client import DEFAULT_ENDPOINT, signup
|
|
11
|
+
from cortexdb_cli.config import config_path, resolve_config_dir, save_config
|
|
12
|
+
from cortexdb_cli.output import console, err_console
|
|
13
|
+
from cortexdb_cli.state import load_state, save_state, state_path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _inherit_group_flag(
|
|
17
|
+
ctx: click.Context,
|
|
18
|
+
name: str,
|
|
19
|
+
own_value: str | None,
|
|
20
|
+
) -> str | None:
|
|
21
|
+
"""Fall back to a flag given at the GROUP level (`cortexdb --endpoint ...
|
|
22
|
+
init`) when init's own flag wasn't supplied.
|
|
23
|
+
|
|
24
|
+
Users reasonably write global flags before the subcommand; Click parses
|
|
25
|
+
them into the group's params and, before 0.4.1, init silently ignored
|
|
26
|
+
them - `cortexdb --endpoint X --api-key Y init` performed an anonymous
|
|
27
|
+
signup against the DEFAULT endpoint. Only the parent's RAW params are
|
|
28
|
+
consulted (never merged config), so a stale config.toml can't leak into
|
|
29
|
+
a fresh init. If both levels are set and disagree, init's own flag wins
|
|
30
|
+
with a warning.
|
|
31
|
+
"""
|
|
32
|
+
parent = ctx.parent
|
|
33
|
+
group_value = parent.params.get(name) if parent is not None else None
|
|
34
|
+
if own_value is not None and group_value is not None and own_value != group_value:
|
|
35
|
+
console.print(
|
|
36
|
+
f"[warning]--{name.replace('_', '-')} given both before and after "
|
|
37
|
+
f"`init` with different values; using {own_value!r}.[/warning]"
|
|
38
|
+
)
|
|
39
|
+
return own_value
|
|
40
|
+
return own_value if own_value is not None else group_value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _existing_identity() -> tuple[dict, str] | None:
|
|
44
|
+
"""Return `(state, status)` for a usable identity already on disk.
|
|
45
|
+
|
|
46
|
+
KAN-145/156: `init` used to mint and overwrite unconditionally. Running
|
|
47
|
+
it twice in one directory replaced a working identity with a brand-new
|
|
48
|
+
anonymous one - and because the actor and scope change, everything
|
|
49
|
+
written under the old identity becomes unreachable from this machine.
|
|
50
|
+
Nothing warned, and nothing had to fail for it to happen: the second
|
|
51
|
+
`init` looked exactly as successful as the first.
|
|
52
|
+
|
|
53
|
+
`status` is "valid" or "expired"; both are treated as "do not silently
|
|
54
|
+
replace this", because an expired token still names the brain the user's
|
|
55
|
+
data is in.
|
|
56
|
+
"""
|
|
57
|
+
state = load_state()
|
|
58
|
+
if not state.get("token"):
|
|
59
|
+
return None
|
|
60
|
+
expires = state.get("expires_at")
|
|
61
|
+
status = "valid"
|
|
62
|
+
if expires:
|
|
63
|
+
try:
|
|
64
|
+
when = datetime.fromisoformat(str(expires).replace("Z", "+00:00"))
|
|
65
|
+
if when.tzinfo is None:
|
|
66
|
+
when = when.replace(tzinfo=timezone.utc)
|
|
67
|
+
if when <= datetime.now(timezone.utc):
|
|
68
|
+
status = "expired"
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
# An unparseable expiry is not evidence of expiry. Prefer the
|
|
71
|
+
# conservative reading: something is here, do not clobber it.
|
|
72
|
+
status = "valid"
|
|
73
|
+
return state, status
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _report_existing(state: dict, status: str) -> None:
|
|
77
|
+
console.print(f" [success]Existing identity reused[/success] ({status})")
|
|
78
|
+
console.print(f" [success]Actor:[/success] {state.get('user_id', '?')}")
|
|
79
|
+
console.print(f" [success]Default scope:[/success] {state.get('scope', '?')}")
|
|
80
|
+
if state.get("expires_at"):
|
|
81
|
+
console.print(f" [dim]Token expires: {state['expires_at']}[/dim]")
|
|
82
|
+
console.print(f"")
|
|
83
|
+
console.print(f"[dim]State directory: {resolve_config_dir()}[/dim]")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@click.command("init")
|
|
87
|
+
@click.option(
|
|
88
|
+
"--endpoint",
|
|
89
|
+
default=None,
|
|
90
|
+
help=f"CortexDB v1 API base URL. [default: {DEFAULT_ENDPOINT}]",
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--api-key",
|
|
94
|
+
default=None,
|
|
95
|
+
help="Existing PASETO bearer token. If set, skips anonymous signup.",
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--actor",
|
|
99
|
+
default=None,
|
|
100
|
+
help="X-Cortex-Actor value. Required when --api-key is set.",
|
|
101
|
+
)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--scope",
|
|
104
|
+
default=None,
|
|
105
|
+
help="Default scope path. Defaults to the actor.",
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"--force",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
help="Replace an existing identity. Backs the old one up first. "
|
|
111
|
+
"A new identity is a NEW brain: memories written under the old actor "
|
|
112
|
+
"stay where they are and are no longer reachable from this config.",
|
|
113
|
+
)
|
|
114
|
+
@click.pass_context
|
|
115
|
+
def init_cmd(
|
|
116
|
+
ctx: click.Context,
|
|
117
|
+
endpoint: str | None,
|
|
118
|
+
api_key: str | None,
|
|
119
|
+
actor: str | None,
|
|
120
|
+
scope: str | None,
|
|
121
|
+
force: bool,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Set up CortexDB CLI.
|
|
124
|
+
|
|
125
|
+
\b
|
|
126
|
+
Without flags: anonymous signup (free tier, 7-day token TTL).
|
|
127
|
+
With --api-key: register an existing token + actor (e.g. from your IdP).
|
|
128
|
+
"""
|
|
129
|
+
# Global-flag inheritance: `cortexdb --endpoint X init` == `cortexdb init --endpoint X`.
|
|
130
|
+
endpoint = _inherit_group_flag(ctx, "endpoint", endpoint) or DEFAULT_ENDPOINT
|
|
131
|
+
api_key = _inherit_group_flag(ctx, "api_key", api_key)
|
|
132
|
+
actor = _inherit_group_flag(ctx, "actor", actor)
|
|
133
|
+
scope = _inherit_group_flag(ctx, "scope", scope)
|
|
134
|
+
|
|
135
|
+
console.print("\n[header]CortexDB CLI setup[/header]\n")
|
|
136
|
+
|
|
137
|
+
# Guard BEFORE any signup call or state write. `init` is read-only when a
|
|
138
|
+
# usable identity already exists: reuse and report it, never replace it
|
|
139
|
+
# silently. Reminting is a data-reachability event, not a retry.
|
|
140
|
+
existing = _existing_identity()
|
|
141
|
+
if existing and not force:
|
|
142
|
+
state, status = existing
|
|
143
|
+
if status == "valid":
|
|
144
|
+
_report_existing(state, status)
|
|
145
|
+
console.print(
|
|
146
|
+
"\n[dim]Nothing changed. Use --force to replace this identity, "
|
|
147
|
+
"or --config-dir / CORTEXDB_HOME for a separate one.[/dim]\n"
|
|
148
|
+
)
|
|
149
|
+
return
|
|
150
|
+
# Expired: still refuse. The token names the brain the user's data is
|
|
151
|
+
# in, so a silent remint strands it. Say so and make them choose.
|
|
152
|
+
err_console.print(
|
|
153
|
+
f"[error]The stored token for {state.get('user_id', '?')} has expired "
|
|
154
|
+
f"({state.get('expires_at')}).[/error]"
|
|
155
|
+
)
|
|
156
|
+
err_console.print(
|
|
157
|
+
"Refusing to mint a new identity automatically: a new identity is a "
|
|
158
|
+
"NEW brain, and memories written under the old actor would no longer "
|
|
159
|
+
"be reachable from this config."
|
|
160
|
+
)
|
|
161
|
+
err_console.print(
|
|
162
|
+
" To deliberately start a new brain: cortexdb init --force"
|
|
163
|
+
)
|
|
164
|
+
err_console.print(
|
|
165
|
+
" To use a token you already hold: "
|
|
166
|
+
"cortexdb init --api-key <token> --actor <actor>"
|
|
167
|
+
)
|
|
168
|
+
raise SystemExit(3)
|
|
169
|
+
|
|
170
|
+
if existing and force:
|
|
171
|
+
backup = state_path().with_suffix(".json.replaced")
|
|
172
|
+
try:
|
|
173
|
+
backup.write_text(
|
|
174
|
+
state_path().read_text(encoding="utf-8"), encoding="utf-8", newline="\n"
|
|
175
|
+
)
|
|
176
|
+
console.print(
|
|
177
|
+
f"[warning]Replacing the existing identity; "
|
|
178
|
+
f"previous state saved to {backup}[/warning]"
|
|
179
|
+
)
|
|
180
|
+
except OSError as exc:
|
|
181
|
+
console.print(
|
|
182
|
+
f"[warning]Could not back up existing state ({exc}); continuing[/warning]"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if api_key:
|
|
186
|
+
if not actor:
|
|
187
|
+
raise click.UsageError("--actor is required when --api-key is supplied.")
|
|
188
|
+
# Pure config write - no signup call.
|
|
189
|
+
save_config("default", {"endpoint": endpoint})
|
|
190
|
+
save_state(
|
|
191
|
+
{
|
|
192
|
+
"token": api_key,
|
|
193
|
+
"user_id": actor,
|
|
194
|
+
"scope": scope or actor,
|
|
195
|
+
"source": "explicit",
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
console.print(f"[success]Saved token + actor to {state_path()}[/success]")
|
|
199
|
+
console.print(f"[dim]Endpoint stored in {config_path()}[/dim]\n")
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
# Anonymous signup path.
|
|
203
|
+
console.print(f" Endpoint: [bold]{endpoint}[/bold]")
|
|
204
|
+
console.print(" No email, no card - free tier, 7-day token TTL.\n")
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
resp = signup(endpoint)
|
|
208
|
+
except httpx.HTTPError as exc:
|
|
209
|
+
console.print(f"[error]Signup failed: {exc}[/error]")
|
|
210
|
+
raise SystemExit(2) from exc
|
|
211
|
+
|
|
212
|
+
state = {
|
|
213
|
+
"token": resp["token"],
|
|
214
|
+
"user_id": resp["user_id"],
|
|
215
|
+
"scope": resp.get("scope") or resp["user_id"],
|
|
216
|
+
"expires_at": resp.get("expires_at"),
|
|
217
|
+
"tier": resp.get("tier", "free"),
|
|
218
|
+
"source": "signup",
|
|
219
|
+
}
|
|
220
|
+
save_state(state)
|
|
221
|
+
save_config("default", {"endpoint": endpoint})
|
|
222
|
+
|
|
223
|
+
console.print(f" [success]Token minted[/success] (expires {resp.get('expires_at', '?')})")
|
|
224
|
+
console.print(f" [success]Actor:[/success] {resp['user_id']}")
|
|
225
|
+
console.print(f" [success]Default scope:[/success] {state['scope']}")
|
|
226
|
+
console.print(f"\n[dim]State written to {state_path()}[/dim]")
|
|
227
|
+
console.print(f"[dim]Endpoint stored in {config_path()}[/dim]\n")
|
|
228
|
+
console.print("Try it:\n")
|
|
229
|
+
console.print(' [bold]cortexdb remember "Q3 revenue: $2.4M"[/bold]')
|
|
230
|
+
console.print(' [bold]cortexdb recall "Q3 revenue?"[/bold]\n')
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""cortexdb init - anonymous signup + config persistence."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
import httpx
|
|
7
|
-
|
|
8
|
-
from cortexdb_cli.client import DEFAULT_ENDPOINT, signup
|
|
9
|
-
from cortexdb_cli.config import config_path, save_config
|
|
10
|
-
from cortexdb_cli.output import console
|
|
11
|
-
from cortexdb_cli.state import save_state, state_path
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _inherit_group_flag(
|
|
15
|
-
ctx: click.Context,
|
|
16
|
-
name: str,
|
|
17
|
-
own_value: str | None,
|
|
18
|
-
) -> str | None:
|
|
19
|
-
"""Fall back to a flag given at the GROUP level (`cortexdb --endpoint ...
|
|
20
|
-
init`) when init's own flag wasn't supplied.
|
|
21
|
-
|
|
22
|
-
Users reasonably write global flags before the subcommand; Click parses
|
|
23
|
-
them into the group's params and, before 0.4.1, init silently ignored
|
|
24
|
-
them - `cortexdb --endpoint X --api-key Y init` performed an anonymous
|
|
25
|
-
signup against the DEFAULT endpoint. Only the parent's RAW params are
|
|
26
|
-
consulted (never merged config), so a stale config.toml can't leak into
|
|
27
|
-
a fresh init. If both levels are set and disagree, init's own flag wins
|
|
28
|
-
with a warning.
|
|
29
|
-
"""
|
|
30
|
-
parent = ctx.parent
|
|
31
|
-
group_value = parent.params.get(name) if parent is not None else None
|
|
32
|
-
if own_value is not None and group_value is not None and own_value != group_value:
|
|
33
|
-
console.print(
|
|
34
|
-
f"[warning]--{name.replace('_', '-')} given both before and after "
|
|
35
|
-
f"`init` with different values; using {own_value!r}.[/warning]"
|
|
36
|
-
)
|
|
37
|
-
return own_value
|
|
38
|
-
return own_value if own_value is not None else group_value
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@click.command("init")
|
|
42
|
-
@click.option(
|
|
43
|
-
"--endpoint",
|
|
44
|
-
default=None,
|
|
45
|
-
help=f"CortexDB v1 API base URL. [default: {DEFAULT_ENDPOINT}]",
|
|
46
|
-
)
|
|
47
|
-
@click.option(
|
|
48
|
-
"--api-key",
|
|
49
|
-
default=None,
|
|
50
|
-
help="Existing PASETO bearer token. If set, skips anonymous signup.",
|
|
51
|
-
)
|
|
52
|
-
@click.option(
|
|
53
|
-
"--actor",
|
|
54
|
-
default=None,
|
|
55
|
-
help="X-Cortex-Actor value. Required when --api-key is set.",
|
|
56
|
-
)
|
|
57
|
-
@click.option(
|
|
58
|
-
"--scope",
|
|
59
|
-
default=None,
|
|
60
|
-
help="Default scope path. Defaults to the actor.",
|
|
61
|
-
)
|
|
62
|
-
@click.pass_context
|
|
63
|
-
def init_cmd(
|
|
64
|
-
ctx: click.Context,
|
|
65
|
-
endpoint: str | None,
|
|
66
|
-
api_key: str | None,
|
|
67
|
-
actor: str | None,
|
|
68
|
-
scope: str | None,
|
|
69
|
-
) -> None:
|
|
70
|
-
"""Set up CortexDB CLI.
|
|
71
|
-
|
|
72
|
-
\b
|
|
73
|
-
Without flags: anonymous signup (free tier, 7-day token TTL).
|
|
74
|
-
With --api-key: register an existing token + actor (e.g. from your IdP).
|
|
75
|
-
"""
|
|
76
|
-
# Global-flag inheritance: `cortexdb --endpoint X init` == `cortexdb init --endpoint X`.
|
|
77
|
-
endpoint = _inherit_group_flag(ctx, "endpoint", endpoint) or DEFAULT_ENDPOINT
|
|
78
|
-
api_key = _inherit_group_flag(ctx, "api_key", api_key)
|
|
79
|
-
actor = _inherit_group_flag(ctx, "actor", actor)
|
|
80
|
-
scope = _inherit_group_flag(ctx, "scope", scope)
|
|
81
|
-
|
|
82
|
-
console.print("\n[header]CortexDB CLI setup[/header]\n")
|
|
83
|
-
|
|
84
|
-
if api_key:
|
|
85
|
-
if not actor:
|
|
86
|
-
raise click.UsageError("--actor is required when --api-key is supplied.")
|
|
87
|
-
# Pure config write - no signup call.
|
|
88
|
-
save_config("default", {"endpoint": endpoint})
|
|
89
|
-
save_state(
|
|
90
|
-
{
|
|
91
|
-
"token": api_key,
|
|
92
|
-
"user_id": actor,
|
|
93
|
-
"scope": scope or actor,
|
|
94
|
-
"source": "explicit",
|
|
95
|
-
}
|
|
96
|
-
)
|
|
97
|
-
console.print(f"[success]Saved token + actor to {state_path()}[/success]")
|
|
98
|
-
console.print(f"[dim]Endpoint stored in {config_path()}[/dim]\n")
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
# Anonymous signup path.
|
|
102
|
-
console.print(f" Endpoint: [bold]{endpoint}[/bold]")
|
|
103
|
-
console.print(" No email, no card - free tier, 7-day token TTL.\n")
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
resp = signup(endpoint)
|
|
107
|
-
except httpx.HTTPError as exc:
|
|
108
|
-
console.print(f"[error]Signup failed: {exc}[/error]")
|
|
109
|
-
raise SystemExit(2) from exc
|
|
110
|
-
|
|
111
|
-
state = {
|
|
112
|
-
"token": resp["token"],
|
|
113
|
-
"user_id": resp["user_id"],
|
|
114
|
-
"scope": resp.get("scope") or resp["user_id"],
|
|
115
|
-
"expires_at": resp.get("expires_at"),
|
|
116
|
-
"tier": resp.get("tier", "free"),
|
|
117
|
-
"source": "signup",
|
|
118
|
-
}
|
|
119
|
-
save_state(state)
|
|
120
|
-
save_config("default", {"endpoint": endpoint})
|
|
121
|
-
|
|
122
|
-
console.print(f" [success]Token minted[/success] (expires {resp.get('expires_at', '?')})")
|
|
123
|
-
console.print(f" [success]Actor:[/success] {resp['user_id']}")
|
|
124
|
-
console.print(f" [success]Default scope:[/success] {state['scope']}")
|
|
125
|
-
console.print(f"\n[dim]State written to {state_path()}[/dim]")
|
|
126
|
-
console.print(f"[dim]Endpoint stored in {config_path()}[/dim]\n")
|
|
127
|
-
console.print("Try it:\n")
|
|
128
|
-
console.print(' [bold]cortexdb remember "Q3 revenue: $2.4M"[/bold]')
|
|
129
|
-
console.print(' [bold]cortexdb recall "Q3 revenue?"[/bold]\n')
|
|
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
|
|
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
|
|
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
|