agentic-coop-db 0.3.1__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.
@@ -0,0 +1,55 @@
1
+ # Build artifacts
2
+ /bin/
3
+ /dist/
4
+ /build/
5
+ *.exe
6
+ *.test
7
+ *.out
8
+ coverage.*
9
+
10
+ # Go workspace
11
+ go.work
12
+ go.work.sum
13
+ vendor/
14
+
15
+ # Python
16
+ __pycache__/
17
+ *.pyc
18
+ *.pyo
19
+ *.egg-info/
20
+ *.egg
21
+ .pytest_cache/
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+ .tox/
25
+ .venv/
26
+ venv/
27
+ .coverage
28
+ htmlcov/
29
+
30
+ # Editor / OS
31
+ .DS_Store
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # Local secrets / state (never commit these)
38
+ .env
39
+ .env.local
40
+ *.pem
41
+ *.key
42
+ *.crt
43
+ secrets/
44
+
45
+ # Local docker volumes
46
+ deploy/data/
47
+ deploy/postgres-data/
48
+
49
+ # Agentic Coop DB local state (CLI config via platformdirs)
50
+ .config/agentcoopdb/
51
+ queue.db
52
+ queue.db-journal
53
+
54
+ # Test containers reaper state
55
+ .testcontainers/
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-coop-db
3
+ Version: 0.3.1
4
+ Summary: Auth gateway client for shared PostgreSQL — sends parameterised SQL over HTTPS.
5
+ Project-URL: Homepage, https://github.com/fheinfling/agentic-coop-db
6
+ Project-URL: Source, https://github.com/fheinfling/agentic-coop-db
7
+ Project-URL: Issues, https://github.com/fheinfling/agentic-coop-db/issues
8
+ Author: Franz Heinfling
9
+ License: Apache-2.0
10
+ Keywords: auth,gateway,pgvector,postgres,postgresql,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: platformdirs>=4.2
23
+ Requires-Dist: requests>=2.32
24
+ Requires-Dist: typer>=0.12
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.10; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: ruff>=0.5; extra == 'dev'
30
+ Requires-Dist: types-requests; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # agentcoopdb (Python client)
34
+
35
+ A thin Python client for [Agentic Coop DB](https://github.com/fheinfling/agentic-coop-db), the
36
+ auth gateway for shared PostgreSQL.
37
+
38
+ ```python
39
+ from agentcoopdb import connect
40
+
41
+ db = connect("https://db.example.com", api_key="acd_live_...")
42
+ db.execute(
43
+ "INSERT INTO notes(id, body) VALUES ($1, $2)",
44
+ ["b9c3...", "hi"],
45
+ )
46
+ rows = db.select("SELECT * FROM notes WHERE owner = $1", ["alice"])
47
+ ```
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install agentic-coop-db
53
+ ```
54
+
55
+ Or install directly from GitHub (the Python client lives in a subdirectory):
56
+
57
+ ```bash
58
+ pip install "agentic-coop-db @ git+https://github.com/fheinfling/agentic-coop-db.git#subdirectory=clients/python"
59
+ ```
60
+
61
+ ## CLI
62
+
63
+ ```bash
64
+ agentic-coop-db init # interactive onboarding wizard
65
+ agentic-coop-db me
66
+ agentic-coop-db sql "SELECT 1"
67
+ agentic-coop-db queue flush
68
+ agentic-coop-db doctor
69
+ ```
70
+
71
+ See the [main repo](https://github.com/fheinfling/agentic-coop-db) for the full docs.
@@ -0,0 +1,39 @@
1
+ # agentcoopdb (Python client)
2
+
3
+ A thin Python client for [Agentic Coop DB](https://github.com/fheinfling/agentic-coop-db), the
4
+ auth gateway for shared PostgreSQL.
5
+
6
+ ```python
7
+ from agentcoopdb import connect
8
+
9
+ db = connect("https://db.example.com", api_key="acd_live_...")
10
+ db.execute(
11
+ "INSERT INTO notes(id, body) VALUES ($1, $2)",
12
+ ["b9c3...", "hi"],
13
+ )
14
+ rows = db.select("SELECT * FROM notes WHERE owner = $1", ["alice"])
15
+ ```
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install agentic-coop-db
21
+ ```
22
+
23
+ Or install directly from GitHub (the Python client lives in a subdirectory):
24
+
25
+ ```bash
26
+ pip install "agentic-coop-db @ git+https://github.com/fheinfling/agentic-coop-db.git#subdirectory=clients/python"
27
+ ```
28
+
29
+ ## CLI
30
+
31
+ ```bash
32
+ agentic-coop-db init # interactive onboarding wizard
33
+ agentic-coop-db me
34
+ agentic-coop-db sql "SELECT 1"
35
+ agentic-coop-db queue flush
36
+ agentic-coop-db doctor
37
+ ```
38
+
39
+ See the [main repo](https://github.com/fheinfling/agentic-coop-db) for the full docs.
@@ -0,0 +1,82 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "agentic-coop-db"
7
+ version = "0.3.1"
8
+ description = "Auth gateway client for shared PostgreSQL — sends parameterised SQL over HTTPS."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [
13
+ { name = "Franz Heinfling" }
14
+ ]
15
+ keywords = ["postgres", "postgresql", "auth", "gateway", "pgvector", "sdk"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: Apache Software License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Database",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+ dependencies = [
29
+ "requests>=2.32",
30
+ "typer>=0.12",
31
+ "platformdirs>=4.2",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ dev = [
36
+ "pytest>=8",
37
+ "pytest-asyncio>=0.23",
38
+ "ruff>=0.5",
39
+ "mypy>=1.10",
40
+ "types-requests",
41
+ ]
42
+
43
+ [project.scripts]
44
+ agentic-coop-db = "agentcoopdb.cli.main:app"
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/fheinfling/agentic-coop-db"
48
+ Source = "https://github.com/fheinfling/agentic-coop-db"
49
+ Issues = "https://github.com/fheinfling/agentic-coop-db/issues"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/agentcoopdb"]
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = ["src", "README.md", "tests"]
56
+
57
+ [tool.ruff]
58
+ line-length = 100
59
+ target-version = "py311"
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I", "B", "UP", "SIM"]
63
+ ignore = ["E501"]
64
+
65
+ # typer.Option(...) / typer.Argument(...) MUST appear in argument
66
+ # defaults — that's how Typer (and FastAPI) declare option metadata.
67
+ # B008 ("Do not perform function call in argument defaults") is a
68
+ # known false positive for these libraries; suppress only on the CLI
69
+ # entrypoint module.
70
+ [tool.ruff.lint.per-file-ignores]
71
+ "src/agentcoopdb/cli/main.py" = ["B008"]
72
+
73
+ [tool.mypy]
74
+ python_version = "3.11"
75
+ strict = true
76
+ warn_unused_ignores = true
77
+ disallow_untyped_defs = true
78
+ no_implicit_optional = true
79
+
80
+ [tool.pytest.ini_options]
81
+ testpaths = ["tests"]
82
+ addopts = "-ra"
@@ -0,0 +1,44 @@
1
+ """agentcoopdb — Python client for the Agentic Coop DB auth gateway.
2
+
3
+ This package is intentionally tiny. The public surface is:
4
+
5
+ from agentcoopdb import connect
6
+ db = connect("https://db.example.com", api_key="acd_live_...")
7
+ db.execute(sql, params)
8
+ db.select(sql, params)
9
+ db.transaction()
10
+ db.vector_upsert(...)
11
+ db.vector_search(...)
12
+ db.rotate_key()
13
+ db.health()
14
+ db.me()
15
+
16
+ Plus error classes (errors.py) and the offline retry queue (queue.py).
17
+ """
18
+
19
+ from agentcoopdb.client import AgentCoopDBClient, connect
20
+ from agentcoopdb.errors import (
21
+ AgentCoopDBError,
22
+ AuthError,
23
+ IdempotencyConflict,
24
+ NetworkError,
25
+ QueueFullError,
26
+ RateLimited,
27
+ ServerError,
28
+ ValidationError,
29
+ )
30
+
31
+ __all__ = [
32
+ "connect",
33
+ "AgentCoopDBClient",
34
+ "AgentCoopDBError",
35
+ "AuthError",
36
+ "ValidationError",
37
+ "IdempotencyConflict",
38
+ "RateLimited",
39
+ "ServerError",
40
+ "NetworkError",
41
+ "QueueFullError",
42
+ ]
43
+
44
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """agentcoopdb CLI package.
2
+
3
+ The entry point is `agentcoopdb.cli.main:app`. Each subcommand lives in its
4
+ own module and is registered in main.py.
5
+ """
@@ -0,0 +1,66 @@
1
+ """Local user config for the CLI.
2
+
3
+ The CLI persists a tiny TOML file under the platform config directory
4
+ (e.g. ~/.config/agentcoopdb/config.toml on Linux, ~/Library/Application
5
+ Support/agentcoopdb/config.toml on macOS) so that subsequent commands don't
6
+ need --url / --api-key flags. The wizard (`agentic-coop-db init`) writes
7
+ this file; `agentic-coop-db me` and friends read it.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import contextlib
13
+ import os
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ import platformdirs
19
+
20
+ CONFIG_FILENAME = "config.toml"
21
+
22
+
23
+ def config_dir() -> Path:
24
+ return Path(platformdirs.user_config_dir("agentcoopdb", appauthor=False, ensure_exists=True))
25
+
26
+
27
+ def config_path() -> Path:
28
+ return config_dir() / CONFIG_FILENAME
29
+
30
+
31
+ @dataclass
32
+ class CLIConfig:
33
+ base_url: str
34
+ api_key: str
35
+
36
+ def write(self) -> Path:
37
+ path = config_path()
38
+ path.write_text(
39
+ f'base_url = "{self.base_url}"\napi_key = "{self.api_key}"\n',
40
+ encoding="utf-8",
41
+ )
42
+ # Best-effort chmod — on Windows this raises and we don't care.
43
+ with contextlib.suppress(OSError):
44
+ os.chmod(path, 0o600)
45
+ return path
46
+
47
+
48
+ def load() -> CLIConfig | None:
49
+ path = config_path()
50
+ if not path.exists():
51
+ return None
52
+ text = path.read_text(encoding="utf-8")
53
+ data: dict[str, Any] = {}
54
+ for line in text.splitlines():
55
+ line = line.strip()
56
+ if not line or line.startswith("#"):
57
+ continue
58
+ if "=" not in line:
59
+ continue
60
+ k, v = line.split("=", 1)
61
+ k = k.strip()
62
+ v = v.strip().strip('"')
63
+ data[k] = v
64
+ if "base_url" not in data or "api_key" not in data:
65
+ return None
66
+ return CLIConfig(base_url=data["base_url"], api_key=data["api_key"])
@@ -0,0 +1,119 @@
1
+ """`agentic-coop-db doctor` — verify the local install end to end.
2
+
3
+ Checks (in order):
4
+
5
+ 1. Config file exists and is parseable
6
+ 2. /healthz reachable
7
+ 3. /readyz returns 200 (DB + migrations OK)
8
+ 4. /v1/me succeeds (auth works)
9
+ 5. SELECT 1 round trip
10
+ 6. Idempotency replay probe — same key returns cached response
11
+ 7. pgvector available (CREATE EXTENSION query)
12
+ 8. Local queue path writable
13
+ 9. Server vs SDK version skew
14
+
15
+ Exits 0 on success, non-zero on the first failure.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import uuid
21
+ from pathlib import Path
22
+ from typing import NoReturn
23
+
24
+ import typer
25
+
26
+ from agentcoopdb import __version__ as sdk_version
27
+ from agentcoopdb import connect
28
+ from agentcoopdb.cli.config import config_dir
29
+ from agentcoopdb.cli.config import load as load_config
30
+ from agentcoopdb.errors import AgentCoopDBError
31
+ from agentcoopdb.queue import Queue
32
+
33
+
34
+ def doctor() -> None:
35
+ cfg = load_config()
36
+ if cfg is None:
37
+ _fail("config not found — run `agentic-coop-db init` first")
38
+
39
+ _ok("config", f"loaded from {Path(config_dir()) / 'config.toml'}")
40
+
41
+ db = connect(cfg.base_url, api_key=cfg.api_key, timeout=5.0)
42
+
43
+ try:
44
+ h = db.health()
45
+ _ok("/healthz", str(h.get("status", "?")))
46
+ except AgentCoopDBError as e:
47
+ _fail(f"/healthz failed: {e}")
48
+
49
+ try:
50
+ rz = db._get("/readyz")
51
+ _ok("/readyz", str(rz.get("status", "?")))
52
+ except AgentCoopDBError as e:
53
+ _fail(f"/readyz failed (DB or migrations unhealthy): {e}")
54
+
55
+ try:
56
+ info = db.me()
57
+ _ok("/v1/me", f"workspace={info.get('workspace_id')} role={info.get('role')}")
58
+ except AgentCoopDBError as e:
59
+ _fail(f"/v1/me failed: {e}")
60
+
61
+ try:
62
+ res = db.execute("SELECT 1")
63
+ if res.command != "SELECT" or res.rows_affected != 1:
64
+ _fail(f"SELECT 1 returned unexpected result: {res}")
65
+ _ok("SELECT 1", "round trip ok")
66
+ except AgentCoopDBError as e:
67
+ _fail(f"SELECT 1 failed: {e}")
68
+
69
+ # Idempotency replay probe — uses a temp uuid as the key and verifies
70
+ # the server's X-Idempotent-Replayed header to confirm a cached replay.
71
+ key = str(uuid.uuid4())
72
+ try:
73
+ db.execute("SELECT pg_sleep(0)", idempotency_key=key)
74
+ r2 = db.execute("SELECT pg_sleep(0)", idempotency_key=key)
75
+ if r2.replayed:
76
+ _ok("idempotency", "replay returned cached response (X-Idempotent-Replayed confirmed)")
77
+ else:
78
+ _fail("idempotency replay succeeded but X-Idempotent-Replayed header missing — caching may be broken")
79
+ except AgentCoopDBError as e:
80
+ _fail(f"idempotency probe failed: {e}")
81
+
82
+ try:
83
+ rows = db.select("SELECT extname FROM pg_extension WHERE extname = 'vector'")
84
+ if rows:
85
+ _ok("pgvector", "extension installed")
86
+ else:
87
+ _warn("pgvector", "extension not installed (run migration 0005)")
88
+ except AgentCoopDBError as e:
89
+ _warn("pgvector", f"could not query pg_extension: {e}")
90
+
91
+ try:
92
+ Queue(config_dir() / "queue.db").depth()
93
+ _ok("queue", "local sqlite path writable")
94
+ except Exception as e:
95
+ _warn("queue", f"could not open local queue: {e}")
96
+
97
+ server_version = info.get("server", {}).get("version", "unknown")
98
+ if server_version != sdk_version:
99
+ _warn(
100
+ "version skew",
101
+ f"sdk={sdk_version} server={server_version} — keep them in lockstep when possible",
102
+ )
103
+ else:
104
+ _ok("version", f"sdk={sdk_version} server={server_version}")
105
+
106
+ typer.echo("\nall checks passed")
107
+
108
+
109
+ def _ok(name: str, detail: str = "") -> None:
110
+ typer.echo(f" [OK] {name}: {detail}")
111
+
112
+
113
+ def _warn(name: str, detail: str) -> None:
114
+ typer.echo(f" [WARN] {name}: {detail}")
115
+
116
+
117
+ def _fail(msg: str) -> NoReturn:
118
+ typer.echo(f" [FAIL] {msg}", err=True)
119
+ raise typer.Exit(code=1)
@@ -0,0 +1,177 @@
1
+ """`agentic-coop-db init` — interactive onboarding wizard.
2
+
3
+ Goal: get a brand-new user from zero to a working CRUD round trip in
4
+ under five minutes without reading any docs beyond the README.
5
+
6
+ Decision tree:
7
+
8
+ 1. Detect docker. If missing, link to install instructions.
9
+ 2. Ask which profile they want: local / pi-lite / cloud / "I have a server".
10
+ 3. For self-hosted profiles: run `make up-<profile>`, wait for /readyz,
11
+ mint an admin API key via scripts/gen-key.sh, capture it once.
12
+ 4. For "existing server": prompt for URL + key, call /v1/me to validate.
13
+ 5. Write ~/.agentic-coop-db/config.toml.
14
+ 6. Run `agentic-coop-db doctor` and print a Python + curl snippet.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import shutil
20
+ import subprocess
21
+ import sys
22
+ import time
23
+ from pathlib import Path
24
+
25
+ import typer
26
+
27
+ from agentcoopdb import connect
28
+ from agentcoopdb.cli import config as cli_config
29
+ from agentcoopdb.errors import AgentCoopDBError
30
+
31
+
32
+ def init() -> None:
33
+ """Run the wizard."""
34
+ typer.echo(_BANNER)
35
+
36
+ if not _have("docker"):
37
+ typer.echo(
38
+ "Docker is not on PATH. Install it from https://docs.docker.com/engine/install/ "
39
+ "and re-run `agentic-coop-db init`."
40
+ )
41
+ raise typer.Exit(code=2)
42
+
43
+ profile = typer.prompt(
44
+ "Which profile? [local / pi-lite / cloud / existing]",
45
+ default="local",
46
+ ).strip().lower()
47
+
48
+ if profile == "existing":
49
+ base_url = typer.prompt("Server base URL (e.g. https://db.example.com)").strip()
50
+ api_key = typer.prompt("API key (will not be echoed)", hide_input=True).strip()
51
+ _validate_and_save(base_url, api_key)
52
+ return
53
+
54
+ repo_root = _find_repo_root()
55
+ if repo_root is None:
56
+ typer.echo(
57
+ "Could not find an Agentic Coop DB checkout in the current or parent directories. "
58
+ "Either run this from inside the repo, or pick `existing` and point at a remote server."
59
+ )
60
+ raise typer.Exit(code=2)
61
+
62
+ target = {
63
+ "local": "up-local",
64
+ "pi-lite": "up-pi",
65
+ "cloud": "up-cloud",
66
+ }.get(profile)
67
+ if target is None:
68
+ typer.echo(f"unknown profile: {profile}")
69
+ raise typer.Exit(code=2)
70
+
71
+ typer.echo(f"\n→ make {target}")
72
+ res = subprocess.run(["make", target], cwd=repo_root)
73
+ if res.returncode != 0:
74
+ typer.echo("`make` failed — fix the error above and re-run `agentic-coop-db init`")
75
+ raise typer.Exit(code=res.returncode)
76
+
77
+ base_url = "http://localhost:8080"
78
+ typer.echo(f"\n→ waiting for {base_url}/readyz ...")
79
+ if not _wait_ready(base_url, timeout=120):
80
+ typer.echo("the server did not become ready within 120s — check `make logs`")
81
+ raise typer.Exit(code=1)
82
+
83
+ typer.echo("→ minting an admin API key via scripts/gen-key.sh ...")
84
+ key_proc = subprocess.run(
85
+ ["./scripts/gen-key.sh", "default", "dbadmin"],
86
+ cwd=repo_root,
87
+ capture_output=True,
88
+ text=True,
89
+ )
90
+ if key_proc.returncode != 0:
91
+ typer.echo(key_proc.stderr)
92
+ raise typer.Exit(code=key_proc.returncode)
93
+ api_key = _extract_token(key_proc.stdout)
94
+ if not api_key:
95
+ typer.echo("could not parse the minted key from gen-key.sh output")
96
+ typer.echo(key_proc.stdout)
97
+ raise typer.Exit(code=1)
98
+
99
+ _validate_and_save(base_url, api_key)
100
+
101
+
102
+ # ---- helpers -----------------------------------------------------------------
103
+
104
+ def _validate_and_save(base_url: str, api_key: str) -> None:
105
+ db = connect(base_url, api_key=api_key, timeout=10.0)
106
+ try:
107
+ info = db.me()
108
+ except AgentCoopDBError as e:
109
+ typer.echo(f"could not authenticate against {base_url}: {e}")
110
+ raise typer.Exit(code=1) from e
111
+
112
+ cfg = cli_config.CLIConfig(base_url=base_url, api_key=api_key)
113
+ path = cfg.write()
114
+ typer.echo(f"\nconfig saved to {path}")
115
+ typer.echo(f"workspace: {info.get('workspace_id', '?')}")
116
+ typer.echo(f"role: {info.get('role', '?')}")
117
+ typer.echo(f"server: {info.get('server', {}).get('version', '?')}")
118
+ typer.echo("\nyou are ready. try this:\n")
119
+ typer.echo(f" curl -H 'Authorization: Bearer {api_key}' {base_url}/v1/me")
120
+ typer.echo(" agentic-coop-db sql 'SELECT 1'")
121
+ typer.echo("\nor in Python:\n")
122
+ typer.echo(" from agentcoopdb import connect")
123
+ typer.echo(f" db = connect('{base_url}', api_key='acd_...')")
124
+ typer.echo(" db.execute('SELECT 1')")
125
+
126
+
127
+ def _have(cmd: str) -> bool:
128
+ return shutil.which(cmd) is not None
129
+
130
+
131
+ def _find_repo_root() -> Path | None:
132
+ here = Path.cwd().resolve()
133
+ for candidate in [here, *here.parents]:
134
+ if (candidate / "Makefile").exists() and (candidate / "deploy").is_dir():
135
+ return candidate
136
+ return None
137
+
138
+
139
+ def _wait_ready(base_url: str, timeout: int) -> bool:
140
+ import requests
141
+
142
+ deadline = time.time() + timeout
143
+ while time.time() < deadline:
144
+ try:
145
+ r = requests.get(base_url + "/readyz", timeout=2.0)
146
+ if r.status_code == 200:
147
+ return True
148
+ except requests.RequestException:
149
+ pass
150
+ time.sleep(1)
151
+ return False
152
+
153
+
154
+ def _extract_token(stdout: str) -> str | None:
155
+ for line in stdout.splitlines():
156
+ line = line.strip()
157
+ if line.startswith("acd_"):
158
+ return line
159
+ return None
160
+
161
+
162
+ _BANNER = """\
163
+ agentic-coop-db init — interactive onboarding wizard
164
+
165
+ This wizard will:
166
+ 1. Verify Docker is up
167
+ 2. Start the stack with your chosen profile
168
+ 3. Mint an admin API key
169
+ 4. Save your config (run `agentic-coop-db doctor` to see the path)
170
+ 5. Print a Python + curl snippet you can copy/paste
171
+
172
+ Press Ctrl-C at any time to abort.
173
+ """
174
+
175
+ if __name__ == "__main__":
176
+ init()
177
+ sys.exit(0)