agent-brain-cli 10.1.2__tar.gz → 10.2.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.
Files changed (42) hide show
  1. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/PKG-INFO +3 -3
  2. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/__init__.py +1 -1
  3. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/client/api_client.py +17 -5
  4. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/client/transport.py +10 -4
  5. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/index.py +0 -7
  6. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/init.py +31 -2
  7. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/inject.py +11 -7
  8. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/start.py +6 -0
  9. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/config.py +52 -0
  10. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/pyproject.toml +3 -3
  11. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/README.md +0 -0
  12. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/cli.py +0 -0
  13. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/client/__init__.py +0 -0
  14. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/__init__.py +0 -0
  15. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/cache.py +0 -0
  16. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/config.py +0 -0
  17. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/doctor.py +0 -0
  18. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/folders.py +0 -0
  19. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/install_agent.py +0 -0
  20. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/jobs.py +0 -0
  21. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/list_cmd.py +0 -0
  22. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/query.py +0 -0
  23. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/reset.py +0 -0
  24. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/status.py +0 -0
  25. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/stop.py +0 -0
  26. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/types.py +0 -0
  27. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/commands/uninstall.py +0 -0
  28. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/config_migrate.py +0 -0
  29. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/config_schema.py +0 -0
  30. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/diagnostics.py +0 -0
  31. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/migration.py +0 -0
  32. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/__init__.py +0 -0
  33. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/claude_converter.py +0 -0
  34. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/codex_converter.py +0 -0
  35. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/converter_base.py +0 -0
  36. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/gemini_converter.py +0 -0
  37. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/opencode_converter.py +0 -0
  38. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/parser.py +0 -0
  39. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/skill_runtime_converter.py +0 -0
  40. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/tool_maps.py +0 -0
  41. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/runtime/types.py +0 -0
  42. {agent_brain_cli-10.1.2 → agent_brain_cli-10.2.1}/agent_brain_cli/xdg_paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: agent-brain-cli
3
- Version: 10.1.2
3
+ Version: 10.2.1
4
4
  Summary: Agent Brain CLI - Command-line interface for managing AI agent memory and knowledge retrieval
5
5
  Home-page: https://github.com/SpillwaveSolutions/agent-brain
6
6
  License: MIT
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
- Requires-Dist: agent-brain-rag (>=10.1.0,<11.0.0)
19
- Requires-Dist: agent-brain-uds (>=10.1.0,<11.0.0)
18
+ Requires-Dist: agent-brain-rag (>=10.2.1,<11.0.0)
19
+ Requires-Dist: agent-brain-uds (>=10.2.1,<11.0.0)
20
20
  Requires-Dist: click (>=8.1.0,<9.0.0)
21
21
  Requires-Dist: httpx (>=0.28.0,<0.29.0)
22
22
  Requires-Dist: pydantic (>=2.10.0,<3.0.0)
@@ -1,3 +1,3 @@
1
1
  """Doc-Serve CLI - Command-line interface for managing Doc-Serve server."""
2
2
 
3
- __version__ = "10.1.2"
3
+ __version__ = "10.2.1"
@@ -159,6 +159,7 @@ class DocServeClient:
159
159
  self,
160
160
  base_url: str = "http://127.0.0.1:8000",
161
161
  timeout: float = 30.0,
162
+ api_key: str | None = None,
162
163
  ):
163
164
  """
164
165
  Initialize the client.
@@ -166,13 +167,21 @@ class DocServeClient:
166
167
  Args:
167
168
  base_url: Server base URL.
168
169
  timeout: Request timeout in seconds.
170
+ api_key: Optional X-API-Key value (Issue #179). When supplied,
171
+ every outbound request carries the header. ``None`` means
172
+ no auth (legacy single-user loopback dev path).
169
173
  """
170
174
  self.base_url = base_url.rstrip("/")
171
175
  self.timeout = timeout
172
- self._client = httpx.Client(timeout=timeout)
176
+ headers = {"X-API-Key": api_key} if api_key else None
177
+ self._client = httpx.Client(timeout=timeout, headers=headers)
173
178
 
174
179
  @classmethod
175
- def from_httpx(cls, client: httpx.Client) -> "DocServeClient":
180
+ def from_httpx(
181
+ cls,
182
+ client: httpx.Client,
183
+ api_key: str | None = None,
184
+ ) -> "DocServeClient":
176
185
  """Build a DocServeClient that uses a pre-constructed httpx.Client.
177
186
 
178
187
  Used by the transport selector to inject a UDS-backed client
@@ -183,6 +192,9 @@ class DocServeClient:
183
192
  Args:
184
193
  client: An already-configured ``httpx.Client``. The wrapper
185
194
  takes ownership and will close it on ``__exit__``.
195
+ api_key: Optional X-API-Key to merge into the client's
196
+ default headers (Issue #179). Caller may also have set
197
+ the header on ``client`` directly; both paths work.
186
198
 
187
199
  Returns:
188
200
  A DocServeClient backed by ``client``.
@@ -191,6 +203,8 @@ class DocServeClient:
191
203
  instance.base_url = "" # inner client carries the real base_url
192
204
  timeout = client.timeout
193
205
  instance.timeout = timeout.read or 30.0
206
+ if api_key:
207
+ client.headers["X-API-Key"] = api_key
194
208
  instance._client = client
195
209
  return instance
196
210
 
@@ -370,7 +384,6 @@ class DocServeClient:
370
384
  include_types: list[str] | None = None,
371
385
  generate_summaries: bool = False,
372
386
  force: bool = False,
373
- allow_external: bool = False,
374
387
  injector_script: str | None = None,
375
388
  folder_metadata_file: str | None = None,
376
389
  dry_run: bool = False,
@@ -393,7 +406,6 @@ class DocServeClient:
393
406
  include_types: File type preset names (e.g., ["python", "docs"]).
394
407
  generate_summaries: Generate LLM summaries for code chunks.
395
408
  force: Bypass deduplication and force a new job.
396
- allow_external: Allow paths outside the project directory.
397
409
  injector_script: Path to Python script exporting process_chunk().
398
410
  folder_metadata_file: Path to JSON file with static metadata.
399
411
  dry_run: Validate injector against sample chunks without indexing.
@@ -433,7 +445,7 @@ class DocServeClient:
433
445
  "POST",
434
446
  "/index/",
435
447
  json=body,
436
- params={"force": force, "allow_external": allow_external},
448
+ params={"force": force},
437
449
  )
438
450
 
439
451
  return IndexResponse(
@@ -17,7 +17,7 @@ from pathlib import Path
17
17
 
18
18
  import click
19
19
 
20
- from ..config import resolve_transport
20
+ from ..config import resolve_api_key, resolve_transport
21
21
  from .api_client import DocServeClient
22
22
 
23
23
 
@@ -41,14 +41,20 @@ def open_client(ctx: click.Context, *, timeout: float = 30.0) -> DocServeClient:
41
41
  base_url_override=obj.get("base_url_override"),
42
42
  socket_path_override=obj.get("socket_path_override"),
43
43
  )
44
+ # Issue #179: resolve the API key alongside the transport so the same
45
+ # CLI invocation works against an authed and an unauthed server.
46
+ api_key = resolve_api_key()
44
47
  if obj.get("debug_transport"):
45
- click.echo(f"[debug-transport] {transport} -> {target}", err=True)
48
+ auth_marker = "with X-API-Key" if api_key else "no auth"
49
+ click.echo(
50
+ f"[debug-transport] {transport} -> {target} ({auth_marker})", err=True
51
+ )
46
52
 
47
53
  if transport == "http":
48
- return DocServeClient(base_url=target, timeout=timeout)
54
+ return DocServeClient(base_url=target, timeout=timeout, api_key=api_key)
49
55
 
50
56
  # UDS: import lazily so HTTP-only invocations don't pay the cost.
51
57
  from agent_brain_uds import make_client
52
58
 
53
59
  inner = make_client(socket_path=Path(target), timeout=timeout)
54
- return DocServeClient.from_httpx(inner)
60
+ return DocServeClient.from_httpx(inner, api_key=api_key)
@@ -79,11 +79,6 @@ console = Console()
79
79
  is_flag=True,
80
80
  help="Force re-indexing even if embedding provider has changed",
81
81
  )
82
- @click.option(
83
- "--allow-external",
84
- is_flag=True,
85
- help="Allow indexing paths outside the project directory",
86
- )
87
82
  @click.option("--json", "json_output", is_flag=True, help="Output as JSON")
88
83
  @click.pass_context
89
84
  def index_command(
@@ -101,7 +96,6 @@ def index_command(
101
96
  exclude_patterns: str | None,
102
97
  generate_summaries: bool,
103
98
  force: bool,
104
- allow_external: bool,
105
99
  json_output: bool,
106
100
  ) -> None:
107
101
  """Index documents from a folder.
@@ -149,7 +143,6 @@ def index_command(
149
143
  exclude_patterns=exclude_patterns_list,
150
144
  generate_summaries=generate_summaries,
151
145
  force=force,
152
- allow_external=allow_external,
153
146
  )
154
147
 
155
148
  if json_output:
@@ -1,6 +1,7 @@
1
1
  """Init command for initializing an Agent Brain project."""
2
2
 
3
3
  import json
4
+ import secrets
4
5
  from pathlib import Path
5
6
 
6
7
  import click
@@ -69,6 +70,14 @@ STATE_DIR_NAME = ".agent-brain"
69
70
  type=click.Path(file_okay=False, resolve_path=True),
70
71
  help="Custom state directory for index data (default: .agent-brain)",
71
72
  )
73
+ @click.option(
74
+ "--no-api-key",
75
+ is_flag=True,
76
+ help=(
77
+ "Skip auto-generating an API key (Issue #179). Use for single-user "
78
+ "loopback dev when no auth is desired. Server still starts without auth."
79
+ ),
80
+ )
72
81
  def init_command(
73
82
  path: str | None,
74
83
  host: str,
@@ -76,6 +85,7 @@ def init_command(
76
85
  force: bool,
77
86
  json_output: bool,
78
87
  state_dir: str | None,
88
+ no_api_key: bool,
79
89
  ) -> None:
80
90
  """Initialize a new Agent Brain project.
81
91
 
@@ -145,8 +155,21 @@ def init_command(
145
155
  config["port"] = port
146
156
  config["auto_port"] = False
147
157
 
148
- # Write configuration
158
+ # Issue #179: auto-generate an API key so the server boots with auth
159
+ # by default. Stored in config.json (project-local); the `start`
160
+ # command exports it as AGENT_BRAIN_API_KEY for the server
161
+ # subprocess, and `resolve_api_key` reads it for the CLI side. Opt
162
+ # out with --no-api-key for single-user loopback workflows.
163
+ if not no_api_key:
164
+ config["api_key"] = secrets.token_urlsafe(32)
165
+
166
+ # Write configuration with mode 0o600 since it may carry a secret.
149
167
  config_path.write_text(json.dumps(config, indent=2))
168
+ try:
169
+ config_path.chmod(0o600)
170
+ except OSError:
171
+ # Best-effort; filesystems without POSIX modes (FAT) still work.
172
+ pass
150
173
 
151
174
  if json_output:
152
175
  click.echo(
@@ -162,12 +185,18 @@ def init_command(
162
185
  )
163
186
  )
164
187
  else:
188
+ api_key_note = (
189
+ f"[bold]API Key:[/] generated ({config_path.name}, mode 0o600)"
190
+ if not no_api_key
191
+ else "[bold]API Key:[/] [yellow]disabled[/] (--no-api-key)"
192
+ )
165
193
  console.print(
166
194
  Panel(
167
195
  f"[green]Project initialized successfully![/]\n\n"
168
196
  f"[bold]Project Root:[/] {project_root}\n"
169
197
  f"[bold]State Directory:[/] {resolved_state_dir}\n"
170
- f"[bold]Configuration:[/] {config_path}",
198
+ f"[bold]Configuration:[/] {config_path}\n"
199
+ f"{api_key_note}",
171
200
  title="Agent Brain Initialized",
172
201
  border_style="green",
173
202
  )
@@ -98,11 +98,6 @@ console = Console()
98
98
  is_flag=True,
99
99
  help="Force re-indexing even if embedding provider has changed",
100
100
  )
101
- @click.option(
102
- "--allow-external",
103
- is_flag=True,
104
- help="Allow indexing paths outside the project directory",
105
- )
106
101
  @click.option("--json", "json_output", is_flag=True, help="Output as JSON")
107
102
  @click.pass_context
108
103
  def inject_command(
@@ -123,7 +118,6 @@ def inject_command(
123
118
  exclude_patterns: str | None,
124
119
  generate_summaries: bool,
125
120
  force: bool,
126
- allow_external: bool,
127
121
  json_output: bool,
128
122
  ) -> None:
129
123
  """Index documents from a folder with content injection.
@@ -199,7 +193,6 @@ def inject_command(
199
193
  exclude_patterns=exclude_patterns_list,
200
194
  generate_summaries=generate_summaries,
201
195
  force=force,
202
- allow_external=allow_external,
203
196
  injector_script=resolved_script,
204
197
  folder_metadata_file=resolved_metadata,
205
198
  dry_run=dry_run,
@@ -272,4 +265,15 @@ def inject_command(
272
265
  "\n[dim]A conflict occurred. "
273
266
  "Check 'agent-brain jobs' for queue status.[/]"
274
267
  )
268
+ elif e.status_code == 403:
269
+ # Injector allowlist rejected the script (issue #181).
270
+ console.print(
271
+ "\n[dim]Injector scripts must be allowlisted in "
272
+ "[bold].agent-brain/config.yaml[/] before they can run on "
273
+ "the server. Add an entry under [bold]injector_scripts:[/] "
274
+ "with the script's path and the sha256 of its contents "
275
+ "(run [bold]sha256sum <script>[/] to get the hash). "
276
+ "See docs/USER_GUIDE.md 'Content Injection' for the full "
277
+ "schema.[/]"
278
+ )
275
279
  raise SystemExit(1) from e
@@ -368,6 +368,12 @@ def start_command(
368
368
  env = os.environ.copy()
369
369
  env["AGENT_BRAIN_PROJECT_ROOT"] = str(project_root)
370
370
  env["AGENT_BRAIN_STATE_DIR"] = str(state_dir)
371
+ # Issue #179: propagate the project-local API key from config.json
372
+ # into the server subprocess. Existing env value wins so operators
373
+ # can override the file-stored key without re-running init.
374
+ config_api_key = config.get("api_key")
375
+ if config_api_key and not env.get("AGENT_BRAIN_API_KEY"):
376
+ env["AGENT_BRAIN_API_KEY"] = str(config_api_key)
371
377
  if strict:
372
378
  env["AGENT_BRAIN_STRICT_MODE"] = "true"
373
379
  if enable_uds:
@@ -414,6 +414,58 @@ def get_server_url(config: AgentBrainConfig | None = None) -> str:
414
414
  return config.server.url
415
415
 
416
416
 
417
+ def resolve_api_key(state_dir: Path | None = None) -> str | None:
418
+ """Resolve the X-API-Key value the CLI should send (Issue #179).
419
+
420
+ Precedence (first non-empty wins):
421
+ 1. ``AGENT_BRAIN_API_KEY`` environment variable
422
+ 2. ``runtime.json::api_key`` for the resolved state directory
423
+ (set by the running server)
424
+ 3. ``config.json::api_key`` for the resolved state directory
425
+ (set by ``agent-brain init``, used when the server hasn't
426
+ started yet)
427
+
428
+ Returns ``None`` when no source provides a value, which is the
429
+ correct behavior for a server running in default no-auth loopback
430
+ mode — the client sends no header and the server's no-op dependency
431
+ accepts the request.
432
+
433
+ Args:
434
+ state_dir: Optional state directory to read from. Defaults to
435
+ ``get_state_dir()`` so callers in CLI commands don't need
436
+ to thread the path through.
437
+
438
+ Returns:
439
+ The resolved API key or ``None``.
440
+ """
441
+ import json
442
+
443
+ env_key = os.getenv("AGENT_BRAIN_API_KEY")
444
+ if env_key:
445
+ return env_key
446
+
447
+ if state_dir is None:
448
+ try:
449
+ state_dir = get_state_dir()
450
+ except Exception:
451
+ return None
452
+
453
+ for filename in ("runtime.json", "config.json"):
454
+ candidate = state_dir / filename
455
+ if not candidate.exists():
456
+ continue
457
+ try:
458
+ with open(candidate) as f:
459
+ payload = json.load(f)
460
+ except (json.JSONDecodeError, OSError):
461
+ continue
462
+ api_key = payload.get("api_key")
463
+ if api_key:
464
+ return str(api_key)
465
+
466
+ return None
467
+
468
+
417
469
  def resolve_transport(
418
470
  *,
419
471
  transport_hint: str | None = None,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "agent-brain-cli"
3
- version = "10.1.2"
3
+ version = "10.2.1"
4
4
  description = "Agent Brain CLI - Command-line interface for managing AI agent memory and knowledge retrieval"
5
5
  authors = ["Spillwave Solutions"]
6
6
  readme = "README.md"
@@ -27,8 +27,8 @@ httpx = "^0.28.0"
27
27
  rich = "^13.9.0"
28
28
  pyyaml = "^6.0.0"
29
29
  pydantic = "^2.10.0"
30
- agent-brain-rag = "^10.1.0"
31
- agent-brain-uds = "^10.1.0"
30
+ agent-brain-rag = "^10.2.1"
31
+ agent-brain-uds = "^10.2.1"
32
32
 
33
33
  [tool.poetry.group.dev.dependencies]
34
34
  pytest = "^8.3.0"