agent-brain-cli 10.0.5__tar.gz → 10.0.7__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.
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/PKG-INFO +2 -2
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/__init__.py +1 -1
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/diagnostics.py +212 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/pyproject.toml +2 -2
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/README.md +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/cli.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/client/__init__.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/client/api_client.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/__init__.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/cache.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/config.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/doctor.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/folders.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/index.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/init.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/inject.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/install_agent.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/jobs.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/list_cmd.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/query.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/reset.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/start.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/status.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/stop.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/types.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/commands/uninstall.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/config.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/config_migrate.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/config_schema.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/migration.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/__init__.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/claude_converter.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/codex_converter.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/converter_base.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/gemini_converter.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/opencode_converter.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/parser.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/skill_runtime_converter.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/tool_maps.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/types.py +0 -0
- {agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/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.
|
|
3
|
+
Version: 10.0.7
|
|
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.
|
|
18
|
+
Requires-Dist: agent-brain-rag (>=10.0.7,<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)
|
|
@@ -371,6 +371,103 @@ def _check_optional_dep(provider: str, module_name: str, extra: str) -> CheckRes
|
|
|
371
371
|
)
|
|
372
372
|
|
|
373
373
|
|
|
374
|
+
def _graph_index_dir(state_dir: Path) -> Path:
|
|
375
|
+
"""Return the conventional graph_index location under a state dir."""
|
|
376
|
+
return state_dir / "data" / "graph_index"
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _read_graphrag_block(state_dir: Path) -> dict[str, Any] | None:
|
|
380
|
+
"""Read the ``graphrag`` block from the project's config.yaml, if any.
|
|
381
|
+
|
|
382
|
+
The CLI's Pydantic ``AgentBrainConfig`` doesn't model graphrag (it's a
|
|
383
|
+
server concern), so this peeks directly at the YAML. Returns None when
|
|
384
|
+
no graphrag block is present.
|
|
385
|
+
"""
|
|
386
|
+
import yaml # local import to avoid a hard dep at module load
|
|
387
|
+
|
|
388
|
+
for name in ("config.yaml", "agent-brain.yaml", "config.yml"):
|
|
389
|
+
path = state_dir / name
|
|
390
|
+
if not path.exists():
|
|
391
|
+
continue
|
|
392
|
+
try:
|
|
393
|
+
data = yaml.safe_load(path.read_text()) or {}
|
|
394
|
+
except (OSError, yaml.YAMLError):
|
|
395
|
+
continue
|
|
396
|
+
block = data.get("graphrag")
|
|
397
|
+
if isinstance(block, dict):
|
|
398
|
+
return block
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _check_graph_store_health(state_dir: Path) -> CheckResult | None:
|
|
403
|
+
"""Verify the Kuzu DB opens cleanly when GraphRAG is enabled (Issue #166).
|
|
404
|
+
|
|
405
|
+
Returns None when GraphRAG is disabled or the store backend isn't Kuzu —
|
|
406
|
+
those cases have no graph health to report. Otherwise opens the Kuzu DB
|
|
407
|
+
in process briefly. A successful open is OK. An IndexError / RuntimeError
|
|
408
|
+
(the typical kill-mid-write signature) is FAIL with a fix hint pointing
|
|
409
|
+
at ``--fix`` or manual recovery.
|
|
410
|
+
"""
|
|
411
|
+
block = _read_graphrag_block(state_dir)
|
|
412
|
+
if not block or not block.get("enabled"):
|
|
413
|
+
return None
|
|
414
|
+
store_type = str(block.get("store_type") or "simple").lower()
|
|
415
|
+
if store_type != "kuzu":
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
graph_dir = _graph_index_dir(state_dir)
|
|
419
|
+
kuzu_db_path = graph_dir / "kuzu_db"
|
|
420
|
+
if not kuzu_db_path.exists():
|
|
421
|
+
# Nothing on disk yet — nothing to be corrupted.
|
|
422
|
+
return CheckResult(
|
|
423
|
+
"graph_store_health",
|
|
424
|
+
SEVERITY_OK,
|
|
425
|
+
f"No Kuzu DB on disk yet at {kuzu_db_path} (will be created on "
|
|
426
|
+
"first indexing job).",
|
|
427
|
+
details={"path": str(kuzu_db_path)},
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
import kuzu
|
|
432
|
+
except ImportError:
|
|
433
|
+
# The langextract optional-dep check already covers missing extras.
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
kuzu.Database(str(kuzu_db_path))
|
|
438
|
+
except (IndexError, RuntimeError) as exc:
|
|
439
|
+
return CheckResult(
|
|
440
|
+
"graph_store_health",
|
|
441
|
+
SEVERITY_FAIL,
|
|
442
|
+
f"Kuzu DB at {kuzu_db_path} appears corrupted: {exc}. This "
|
|
443
|
+
"typically happens when the server was killed mid-indexing.",
|
|
444
|
+
fix=(
|
|
445
|
+
"Run `agent-brain doctor --fix` (server must be stopped) to "
|
|
446
|
+
"quarantine the corrupted file and restore from the latest "
|
|
447
|
+
"snapshot, or manually `rm` the file (loses all triplets)."
|
|
448
|
+
),
|
|
449
|
+
details={"path": str(kuzu_db_path), "error": str(exc)},
|
|
450
|
+
)
|
|
451
|
+
except Exception as exc: # noqa: BLE001 — anything unexpected
|
|
452
|
+
return CheckResult(
|
|
453
|
+
"graph_store_health",
|
|
454
|
+
SEVERITY_WARN,
|
|
455
|
+
f"Could not check Kuzu DB at {kuzu_db_path}: {exc}",
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
return CheckResult(
|
|
459
|
+
"graph_store_health",
|
|
460
|
+
SEVERITY_OK,
|
|
461
|
+
f"Kuzu DB at {kuzu_db_path} opens cleanly.",
|
|
462
|
+
details={"path": str(kuzu_db_path)},
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _server_is_running(state_dir: Path) -> bool:
|
|
467
|
+
"""Best-effort check for an active server lock under state_dir."""
|
|
468
|
+
return (state_dir / "server.lock").exists() or (state_dir / "lock").exists()
|
|
469
|
+
|
|
470
|
+
|
|
374
471
|
def _check_gitignore(project_root: Path) -> CheckResult:
|
|
375
472
|
gi = project_root / ".gitignore"
|
|
376
473
|
if not gi.exists():
|
|
@@ -436,6 +533,11 @@ def run_doctor(server_url_override: str | None = None) -> DoctorReport:
|
|
|
436
533
|
_check_optional_dep("graphrag (langextract)", "langextract", "graphrag")
|
|
437
534
|
)
|
|
438
535
|
|
|
536
|
+
# Issue #166 — verify Kuzu DB opens cleanly.
|
|
537
|
+
graph_check = _check_graph_store_health(state_dir)
|
|
538
|
+
if graph_check is not None:
|
|
539
|
+
checks.append(graph_check)
|
|
540
|
+
|
|
439
541
|
checks.append(_check_gitignore(project_root))
|
|
440
542
|
|
|
441
543
|
checks.append(_check_server(server_url, runtime_file))
|
|
@@ -490,9 +592,119 @@ def apply_safe_fixes(report: DoctorReport) -> list[str]:
|
|
|
490
592
|
+ "\n"
|
|
491
593
|
)
|
|
492
594
|
actions.append(f"Created {cfg_json}.")
|
|
595
|
+
elif check.name == "graph_store_health" and check.status == SEVERITY_FAIL:
|
|
596
|
+
# Issue #166 — recover a corrupted Kuzu DB offline.
|
|
597
|
+
if _server_is_running(state_dir):
|
|
598
|
+
actions.append(
|
|
599
|
+
"Skipped Kuzu recovery: server appears to be running. "
|
|
600
|
+
"Stop it with `agent-brain stop` first, then re-run "
|
|
601
|
+
"`agent-brain doctor --fix`."
|
|
602
|
+
)
|
|
603
|
+
continue
|
|
604
|
+
graph_dir = _graph_index_dir(state_dir)
|
|
605
|
+
kuzu_db = graph_dir / "kuzu_db"
|
|
606
|
+
kuzu_wal = graph_dir / "kuzu_db.wal"
|
|
607
|
+
stamp = _utc_stamp()
|
|
608
|
+
for src in (kuzu_db, kuzu_wal):
|
|
609
|
+
if src.exists():
|
|
610
|
+
dest = src.with_name(f"{src.name}.corrupted-{stamp}")
|
|
611
|
+
src.rename(dest)
|
|
612
|
+
actions.append(
|
|
613
|
+
f"Quarantined {src.name} → {dest.name} "
|
|
614
|
+
"(forensic preservation)."
|
|
615
|
+
)
|
|
616
|
+
# Restore from latest snapshot, if one exists.
|
|
617
|
+
restored = _replay_latest_snapshot(graph_dir)
|
|
618
|
+
if restored is not None:
|
|
619
|
+
snapshot_name, count = restored
|
|
620
|
+
actions.append(
|
|
621
|
+
f"Restored {count} triplets from snapshot "
|
|
622
|
+
f"{snapshot_name} into a fresh Kuzu DB."
|
|
623
|
+
)
|
|
624
|
+
else:
|
|
625
|
+
actions.append(
|
|
626
|
+
"No snapshot available to restore; graph index will "
|
|
627
|
+
"start empty. Re-run `agent-brain index <folder>` to "
|
|
628
|
+
"rebuild."
|
|
629
|
+
)
|
|
493
630
|
return actions
|
|
494
631
|
|
|
495
632
|
|
|
633
|
+
def _utc_stamp() -> str:
|
|
634
|
+
"""Filesystem-safe UTC timestamp suffix shared with the server's
|
|
635
|
+
quarantine logic (graph_store._corrupted_sibling)."""
|
|
636
|
+
from datetime import datetime, timezone
|
|
637
|
+
|
|
638
|
+
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _replay_latest_snapshot(graph_dir: Path) -> tuple[str, int] | None:
|
|
642
|
+
"""Replay triplets from the newest valid snapshot into a fresh Kuzu DB.
|
|
643
|
+
|
|
644
|
+
Returns ``(snapshot_filename, triplet_count)`` on success, or ``None`` if
|
|
645
|
+
there's no usable snapshot. Best-effort: any failure becomes a None
|
|
646
|
+
return (the caller will report it). The CLI doesn't import the server
|
|
647
|
+
code path because the CLI may be installed without the server package;
|
|
648
|
+
instead it reads snapshot JSON directly using a minimal schema.
|
|
649
|
+
"""
|
|
650
|
+
snap_dir = graph_dir / "snapshots"
|
|
651
|
+
if not snap_dir.is_dir():
|
|
652
|
+
return None
|
|
653
|
+
try:
|
|
654
|
+
import kuzu
|
|
655
|
+
from llama_index.core.graph_stores.types import EntityNode, Relation
|
|
656
|
+
from llama_index.graph_stores.kuzu import KuzuPropertyGraphStore
|
|
657
|
+
except ImportError:
|
|
658
|
+
return None
|
|
659
|
+
|
|
660
|
+
candidates = sorted(
|
|
661
|
+
(p for p in snap_dir.iterdir() if p.is_file() and p.suffix == ".json"),
|
|
662
|
+
key=lambda p: (p.stat().st_mtime, p.name),
|
|
663
|
+
reverse=True,
|
|
664
|
+
)
|
|
665
|
+
for snap in candidates:
|
|
666
|
+
try:
|
|
667
|
+
payload = json.loads(snap.read_text())
|
|
668
|
+
if payload.get("schema_version") != 1:
|
|
669
|
+
continue
|
|
670
|
+
triplets = payload.get("triplets") or []
|
|
671
|
+
except (OSError, json.JSONDecodeError):
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
try:
|
|
675
|
+
db = kuzu.Database(str(graph_dir / "kuzu_db"))
|
|
676
|
+
store = KuzuPropertyGraphStore(db, use_vector_index=False)
|
|
677
|
+
for t in triplets:
|
|
678
|
+
subj = EntityNode(
|
|
679
|
+
name=t["subject"],
|
|
680
|
+
label=t.get("subject_type") or "Entity",
|
|
681
|
+
)
|
|
682
|
+
obj = EntityNode(
|
|
683
|
+
name=t["object"],
|
|
684
|
+
label=t.get("object_type") or "Entity",
|
|
685
|
+
)
|
|
686
|
+
store.upsert_nodes([subj, obj])
|
|
687
|
+
store.upsert_relations(
|
|
688
|
+
[
|
|
689
|
+
Relation(
|
|
690
|
+
label=t["predicate"],
|
|
691
|
+
source_id=subj.id,
|
|
692
|
+
target_id=obj.id,
|
|
693
|
+
properties=(
|
|
694
|
+
{"source_chunk_id": t["source_chunk_id"]}
|
|
695
|
+
if t.get("source_chunk_id")
|
|
696
|
+
else {}
|
|
697
|
+
),
|
|
698
|
+
)
|
|
699
|
+
]
|
|
700
|
+
)
|
|
701
|
+
return snap.name, len(triplets)
|
|
702
|
+
except Exception: # noqa: BLE001
|
|
703
|
+
# If replay against this snapshot failed, try the next-older.
|
|
704
|
+
continue
|
|
705
|
+
return None
|
|
706
|
+
|
|
707
|
+
|
|
496
708
|
def doctor_hint_message(project_root: Path | None = None) -> str:
|
|
497
709
|
"""Suggest the doctor command — and call out the most likely setup issue.
|
|
498
710
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "agent-brain-cli"
|
|
3
|
-
version = "10.0.
|
|
3
|
+
version = "10.0.7"
|
|
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,7 +27,7 @@ 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.0.
|
|
30
|
+
agent-brain-rag = "^10.0.7"
|
|
31
31
|
|
|
32
32
|
[tool.poetry.group.dev.dependencies]
|
|
33
33
|
pytest = "^8.3.0"
|
|
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
|
{agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/claude_converter.py
RENAMED
|
File without changes
|
{agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/codex_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/gemini_converter.py
RENAMED
|
File without changes
|
{agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/opencode_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_brain_cli-10.0.5 → agent_brain_cli-10.0.7}/agent_brain_cli/runtime/skill_runtime_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|