agent-brain-cli 10.0.4__tar.gz → 10.0.6__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.0.4 → agent_brain_cli-10.0.6}/PKG-INFO +2 -2
  2. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/__init__.py +1 -1
  3. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/doctor.py +38 -2
  4. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/jobs.py +31 -7
  5. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/config.py +23 -10
  6. agent_brain_cli-10.0.6/agent_brain_cli/diagnostics.py +737 -0
  7. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/pyproject.toml +2 -2
  8. agent_brain_cli-10.0.4/agent_brain_cli/diagnostics.py +0 -384
  9. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/README.md +0 -0
  10. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/cli.py +0 -0
  11. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/client/__init__.py +0 -0
  12. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/client/api_client.py +0 -0
  13. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/__init__.py +0 -0
  14. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/cache.py +0 -0
  15. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/config.py +0 -0
  16. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/folders.py +0 -0
  17. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/index.py +0 -0
  18. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/init.py +0 -0
  19. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/inject.py +0 -0
  20. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/install_agent.py +0 -0
  21. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/list_cmd.py +0 -0
  22. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/query.py +0 -0
  23. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/reset.py +0 -0
  24. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/start.py +0 -0
  25. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/status.py +0 -0
  26. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/stop.py +0 -0
  27. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/types.py +0 -0
  28. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/commands/uninstall.py +0 -0
  29. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/config_migrate.py +0 -0
  30. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/config_schema.py +0 -0
  31. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/migration.py +0 -0
  32. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/__init__.py +0 -0
  33. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/claude_converter.py +0 -0
  34. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/codex_converter.py +0 -0
  35. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/converter_base.py +0 -0
  36. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/gemini_converter.py +0 -0
  37. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/opencode_converter.py +0 -0
  38. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/parser.py +0 -0
  39. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/skill_runtime_converter.py +0 -0
  40. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/tool_maps.py +0 -0
  41. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/agent_brain_cli/runtime/types.py +0 -0
  42. {agent_brain_cli-10.0.4 → agent_brain_cli-10.0.6}/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.0.4
3
+ Version: 10.0.6
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,7 +15,7 @@ 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.0.4,<11.0.0)
18
+ Requires-Dist: agent-brain-rag (>=10.0.6,<11.0.0)
19
19
  Requires-Dist: click (>=8.1.0,<9.0.0)
20
20
  Requires-Dist: httpx (>=0.28.0,<0.29.0)
21
21
  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.0.4"
3
+ __version__ = "10.0.6"
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
6
+
5
7
  import click
6
8
  from rich.console import Console
7
9
  from rich.panel import Panel
@@ -11,6 +13,7 @@ from agent_brain_cli.diagnostics import (
11
13
  SEVERITY_FAIL,
12
14
  SEVERITY_OK,
13
15
  SEVERITY_WARN,
16
+ apply_safe_fixes,
14
17
  report_to_json,
15
18
  run_doctor,
16
19
  )
@@ -33,18 +36,40 @@ _STATUS_STYLE = {
33
36
  help="Server URL to probe (default: resolved from runtime.json or config).",
34
37
  )
35
38
  @click.option("--json", "json_output", is_flag=True, help="Emit machine-readable JSON.")
36
- def doctor_command(url: str | None, json_output: bool) -> None:
39
+ @click.option(
40
+ "--fix",
41
+ "apply_fixes",
42
+ is_flag=True,
43
+ help=(
44
+ "Apply safe, idempotent, offline fixes (add .agent-brain/ to .gitignore, "
45
+ "create state dir + stub config.json). Will not touch API keys, network, "
46
+ "or user code. Re-runs the report after fixing."
47
+ ),
48
+ )
49
+ def doctor_command(url: str | None, json_output: bool, apply_fixes: bool) -> None:
37
50
  """Diagnose your Agent Brain setup.
38
51
 
39
52
  Inspects Python version, project init state, provider config, required
40
53
  API keys, optional dependencies, .gitignore hygiene, and whether the
41
54
  server is reachable. Exits non-zero on any critical failure so it can
42
55
  be used in scripts (``agent-brain doctor || agent-brain init``).
56
+
57
+ Pass ``--fix`` to auto-apply the safe subset of remediations and re-run.
43
58
  """
44
59
  report = run_doctor(server_url_override=url)
45
60
 
61
+ fix_actions: list[str] = []
62
+ if apply_fixes:
63
+ fix_actions = apply_safe_fixes(report)
64
+ if fix_actions:
65
+ # Re-run so the printed report reflects the fixed state.
66
+ report = run_doctor(server_url_override=url)
67
+
46
68
  if json_output:
47
- click.echo(report_to_json(report))
69
+ payload = json.loads(report_to_json(report))
70
+ if apply_fixes:
71
+ payload["applied_fixes"] = fix_actions
72
+ click.echo(json.dumps(payload, indent=2))
48
73
  raise SystemExit(report.exit_code)
49
74
 
50
75
  header_color = "green" if report.exit_code == 0 else "red"
@@ -75,6 +100,17 @@ def doctor_command(url: str | None, json_output: bool) -> None:
75
100
 
76
101
  console.print(table)
77
102
 
103
+ if apply_fixes:
104
+ if fix_actions:
105
+ console.print("\n[cyan]Applied safe fixes:[/]")
106
+ for action in fix_actions:
107
+ console.print(f" • {action}")
108
+ else:
109
+ console.print(
110
+ "\n[dim]No safe fixes applied (nothing actionable, or all "
111
+ "checks already passing).[/]"
112
+ )
113
+
78
114
  if report.exit_code != 0:
79
115
  console.print(
80
116
  "\n[red]Doctor reported critical issues.[/] "
@@ -25,13 +25,32 @@ def _format_timestamp(ts: str | None) -> str:
25
25
  return ts
26
26
 
27
27
 
28
- def _format_progress(progress: float | None, total: int | None) -> str:
29
- """Format progress for display."""
28
+ def _format_progress(
29
+ progress: float | int | dict[str, Any] | None,
30
+ total: int | None,
31
+ ) -> str:
32
+ """Format progress for display.
33
+
34
+ Copes with both the legacy float shape and the structured ``JobProgress``
35
+ dict the server emits today (#150). Falls back to a string coercion for
36
+ unknown types so the CLI never raises.
37
+ """
30
38
  if progress is None:
31
39
  return "-"
32
- if total:
33
- return f"{progress:.1f}% ({total} files)"
34
- return f"{progress:.1f}%"
40
+ if isinstance(progress, dict):
41
+ pct = progress.get("percent_complete")
42
+ if isinstance(pct, (int, float)):
43
+ files_total = progress.get("files_total") or 0
44
+ files_done = progress.get("files_processed") or 0
45
+ if files_total:
46
+ return f"{pct:.1f}% ({files_done}/{files_total} files)"
47
+ return f"{pct:.1f}%"
48
+ return ", ".join(f"{k}={v}" for k, v in progress.items())
49
+ if isinstance(progress, (int, float)):
50
+ if total:
51
+ return f"{progress:.1f}% ({total} files)"
52
+ return f"{progress:.1f}%"
53
+ return str(progress)
35
54
 
36
55
 
37
56
  def _get_status_style(status: str) -> str:
@@ -121,12 +140,17 @@ def _create_job_detail_panel(job: dict[str, Any]) -> Panel:
121
140
  if (progress := job.get("progress_percent", job.get("progress"))) is not None:
122
141
  total = job.get("total_files", 0)
123
142
  processed = job.get("processed_files", 0)
124
- if total:
143
+ # #150: server emits structured JobProgress dicts in addition to floats.
144
+ # Defer formatting to _format_progress so list-view and detail-view
145
+ # never diverge on type handling.
146
+ if isinstance(progress, (int, float)) and total:
125
147
  lines.append(
126
148
  f"[bold]Progress:[/] {progress:.1f}% ({processed}/{total} files)"
127
149
  )
128
150
  else:
129
- lines.append(f"[bold]Progress:[/] {progress:.1f}%")
151
+ lines.append(
152
+ f"[bold]Progress:[/] {_format_progress(progress, total or None)}"
153
+ )
130
154
 
131
155
  if enqueued := job.get("enqueued_at", job.get("created_at")):
132
156
  lines.append(f"[bold]Enqueued:[/] {_format_timestamp(enqueued)}")
@@ -254,6 +254,20 @@ def load_config(start_path: Path | None = None) -> AgentBrainConfig:
254
254
  def resolve_project_root(start_path: Path | None = None) -> Path:
255
255
  """Find the project root by looking for markers.
256
256
 
257
+ Thin wrapper around :func:`resolve_project_root_with_strategy` that drops
258
+ the strategy label for callers that only need the path.
259
+ """
260
+ return resolve_project_root_with_strategy(start_path)[0]
261
+
262
+
263
+ def resolve_project_root_with_strategy(
264
+ start_path: Path | None = None,
265
+ ) -> tuple[Path, str]:
266
+ """Find the project root and report *which* rule matched.
267
+
268
+ Used by ``agent-brain doctor`` to explain why a given directory was
269
+ selected (issue #146).
270
+
257
271
  Resolution order (first match wins):
258
272
  1. Walk up from ``start_path`` looking for ``.agent-brain/`` — this lets a
259
273
  sub-project inside a mono-repo keep its own state dir and not get
@@ -263,11 +277,10 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
263
277
  4. Walk up looking for ``.claude/`` or ``pyproject.toml``.
264
278
  5. Fall back to ``start_path``.
265
279
 
266
- Args:
267
- start_path: Starting directory. Defaults to cwd.
268
-
269
280
  Returns:
270
- Project root path.
281
+ ``(root, strategy)`` where ``strategy`` is one of
282
+ ``"agent_brain_dir"``, ``"legacy_claude_dir"``, ``"git_root"``,
283
+ ``"claude_dir"``, ``"pyproject"``, ``"cwd_fallback"``.
271
284
  """
272
285
  import subprocess
273
286
 
@@ -277,9 +290,9 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
277
290
  current = start
278
291
  while current != current.parent:
279
292
  if (current / STATE_DIR_NAME).is_dir():
280
- return current
293
+ return current, "agent_brain_dir"
281
294
  if (current / LEGACY_STATE_DIR_NAME).is_dir():
282
- return current
295
+ return current, "legacy_claude_dir"
283
296
  current = current.parent
284
297
 
285
298
  # 3. Git root next — useful when this is the first time the user runs init.
@@ -292,7 +305,7 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
292
305
  cwd=str(start),
293
306
  )
294
307
  if result.returncode == 0:
295
- return Path(result.stdout.strip()).resolve()
308
+ return Path(result.stdout.strip()).resolve(), "git_root"
296
309
  except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
297
310
  pass
298
311
 
@@ -300,12 +313,12 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
300
313
  current = start
301
314
  while current != current.parent:
302
315
  if (current / ".claude").is_dir():
303
- return current
316
+ return current, "claude_dir"
304
317
  if (current / "pyproject.toml").is_file():
305
- return current
318
+ return current, "pyproject"
306
319
  current = current.parent
307
320
 
308
- return start
321
+ return start, "cwd_fallback"
309
322
 
310
323
 
311
324
  # Backwards-compatible alias for any external callers.