agent-brain-cli 9.4.1__tar.gz → 9.6.0__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 (40) hide show
  1. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/PKG-INFO +2 -2
  2. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/__init__.py +1 -1
  3. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/config.py +205 -3
  4. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/types.py +3 -1
  5. agent_brain_cli-9.6.0/agent_brain_cli/config_migrate.py +198 -0
  6. agent_brain_cli-9.6.0/agent_brain_cli/config_schema.py +490 -0
  7. agent_brain_cli-9.6.0/agent_brain_cli/runtime/opencode_converter.py +234 -0
  8. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/parser.py +3 -0
  9. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/tool_maps.py +19 -3
  10. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/types.py +3 -0
  11. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/pyproject.toml +2 -2
  12. agent_brain_cli-9.4.1/agent_brain_cli/runtime/opencode_converter.py +0 -146
  13. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/README.md +0 -0
  14. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/cli.py +0 -0
  15. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/client/__init__.py +0 -0
  16. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/client/api_client.py +0 -0
  17. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/__init__.py +0 -0
  18. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/cache.py +0 -0
  19. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/folders.py +0 -0
  20. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/index.py +0 -0
  21. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/init.py +0 -0
  22. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/inject.py +0 -0
  23. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/install_agent.py +0 -0
  24. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/jobs.py +0 -0
  25. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/list_cmd.py +0 -0
  26. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/query.py +0 -0
  27. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/reset.py +0 -0
  28. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/start.py +0 -0
  29. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/status.py +0 -0
  30. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/stop.py +0 -0
  31. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/commands/uninstall.py +0 -0
  32. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/config.py +0 -0
  33. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/migration.py +0 -0
  34. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/__init__.py +0 -0
  35. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/claude_converter.py +0 -0
  36. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/codex_converter.py +0 -0
  37. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/converter_base.py +0 -0
  38. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/gemini_converter.py +0 -0
  39. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/agent_brain_cli/runtime/skill_runtime_converter.py +0 -0
  40. {agent_brain_cli-9.4.1 → agent_brain_cli-9.6.0}/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: 9.4.1
3
+ Version: 9.6.0
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 (>=9.4.1,<10.0.0)
18
+ Requires-Dist: agent-brain-rag (>=9.6.0,<10.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__ = "9.4.1"
3
+ __version__ = "9.6.0"
@@ -12,6 +12,15 @@ import yaml
12
12
  from rich.console import Console
13
13
  from rich.table import Table
14
14
 
15
+ from agent_brain_cli.config_migrate import (
16
+ MigrationResult,
17
+ diff_config_file,
18
+ migrate_config_file,
19
+ )
20
+ from agent_brain_cli.config_schema import (
21
+ format_validation_errors,
22
+ validate_config_file,
23
+ )
15
24
  from agent_brain_cli.xdg_paths import get_xdg_config_dir
16
25
 
17
26
  console = Console()
@@ -152,9 +161,12 @@ def config_group() -> None:
152
161
 
153
162
  \b
154
163
  Commands:
155
- show - Display active configuration
156
- path - Show config file location
157
- wizard - Create/update config interactively
164
+ show - Display active configuration
165
+ path - Show config file location
166
+ wizard - Create/update config interactively
167
+ validate - Validate config against schema
168
+ migrate - Upgrade config to current schema
169
+ diff - Preview what migrate would change
158
170
  """
159
171
  pass
160
172
 
@@ -162,6 +174,17 @@ def config_group() -> None:
162
174
  @config_group.command("wizard")
163
175
  def wizard() -> None:
164
176
  """Interactive configuration wizard for Agent Brain providers."""
177
+ # Check existing config for validation issues before prompting
178
+ existing_config = _find_config_file()
179
+ if existing_config:
180
+ existing_errors = validate_config_file(existing_config)
181
+ if existing_errors:
182
+ console.print(
183
+ "\n[bold yellow]Warning:[/] Existing config has validation issues:"
184
+ )
185
+ console.print(format_validation_errors(existing_errors))
186
+ console.print()
187
+
165
188
  embed_provider: str = click.prompt(
166
189
  "Embedding provider",
167
190
  type=click.Choice(["openai", "ollama", "cohere"]),
@@ -288,6 +311,19 @@ def wizard() -> None:
288
311
 
289
312
  console.print(f"[green]Config written to {config_path}[/]")
290
313
 
314
+ # Validate the written config and warn if issues found
315
+ post_write_errors = validate_config_file(config_path)
316
+ if post_write_errors:
317
+ console.print(
318
+ "\n[bold yellow]Warning:[/] The generated config has validation issues:\n"
319
+ )
320
+ console.print(format_validation_errors(post_write_errors))
321
+ if not click.confirm("Continue with this config anyway?", default=False):
322
+ console.print(
323
+ "[red]Config wizard aborted." " Please fix the issues and try again.[/]"
324
+ )
325
+ sys.exit(1)
326
+
291
327
 
292
328
  @config_group.command("show")
293
329
  @click.option("--json", "json_output", is_flag=True, help="Output as JSON")
@@ -408,3 +444,169 @@ def config_path(json_output: bool) -> None:
408
444
  console.print(f"[green]{config_path}[/]")
409
445
  else:
410
446
  console.print("[yellow]No config file found[/]")
447
+
448
+
449
+ @config_group.command("validate")
450
+ @click.option(
451
+ "--file",
452
+ "config_file",
453
+ type=click.Path(exists=True),
454
+ default=None,
455
+ help="Path to config file (default: auto-detect)",
456
+ )
457
+ @click.option("--json", "json_output", is_flag=True, help="Output as JSON")
458
+ def validate_config(config_file: str | None, json_output: bool) -> None:
459
+ """Validate config.yaml against the Agent Brain schema.
460
+
461
+ Checks for unknown keys, invalid provider values, deprecated fields,
462
+ and type errors. Reports line numbers and fix suggestions.
463
+
464
+ \b
465
+ Examples:
466
+ agent-brain config validate
467
+ agent-brain config validate --file ./my-config.yaml
468
+ agent-brain config validate --json
469
+ """
470
+ if config_file is not None:
471
+ path: Path | None = Path(config_file)
472
+ else:
473
+ path = _find_config_file()
474
+
475
+ if path is None:
476
+ if json_output:
477
+ click.echo(
478
+ json.dumps(
479
+ {
480
+ "valid": None,
481
+ "config_file": None,
482
+ "errors": [],
483
+ "message": "No config file found",
484
+ }
485
+ )
486
+ )
487
+ else:
488
+ console.print("[yellow]No config file found. Nothing to validate.[/]")
489
+ sys.exit(0)
490
+
491
+ errors = validate_config_file(path)
492
+
493
+ if json_output:
494
+ output: dict[str, Any] = {
495
+ "valid": len(errors) == 0,
496
+ "config_file": str(path),
497
+ "errors": [
498
+ {
499
+ "field": e.field,
500
+ "message": e.message,
501
+ "line_number": e.line_number,
502
+ "suggestion": e.suggestion,
503
+ }
504
+ for e in errors
505
+ ],
506
+ }
507
+ click.echo(json.dumps(output, indent=2))
508
+ if errors:
509
+ sys.exit(1)
510
+ return
511
+
512
+ if not errors:
513
+ console.print(f"[green]Config is valid[/] ({path})")
514
+ sys.exit(0)
515
+
516
+ console.print(format_validation_errors(errors))
517
+ sys.exit(1)
518
+
519
+
520
+ @config_group.command("migrate")
521
+ @click.option(
522
+ "--file",
523
+ "config_file",
524
+ type=click.Path(exists=True),
525
+ default=None,
526
+ help="Path to config file (default: auto-detect)",
527
+ )
528
+ @click.option(
529
+ "--dry-run", is_flag=True, help="Show what would change without modifying"
530
+ )
531
+ def migrate_config_cmd(config_file: str | None, dry_run: bool) -> None:
532
+ """Migrate config.yaml to the current schema version.
533
+
534
+ Upgrades deprecated keys and restructures config sections.
535
+ Use --dry-run to preview changes without modifying the file.
536
+
537
+ \b
538
+ Examples:
539
+ agent-brain config migrate
540
+ agent-brain config migrate --dry-run
541
+ agent-brain config migrate --file ./old-config.yaml
542
+ """
543
+ if config_file is not None:
544
+ path: Path | None = Path(config_file)
545
+ else:
546
+ path = _find_config_file()
547
+
548
+ if path is None:
549
+ console.print("[yellow]No config file found.[/]")
550
+ sys.exit(0)
551
+
552
+ if dry_run:
553
+ diff = diff_config_file(path)
554
+ if diff:
555
+ console.print(diff)
556
+ else:
557
+ console.print("[green]Config is already up to date. No changes needed.[/]")
558
+ sys.exit(0)
559
+
560
+ result: MigrationResult = migrate_config_file(path)
561
+ if result.already_current:
562
+ console.print("[green]Config is already up to date[/]")
563
+ sys.exit(0)
564
+
565
+ for change in result.changes:
566
+ console.print(f" [cyan]->[/] {change}")
567
+ console.print(f"\n[green]Config migrated successfully[/] ({path})")
568
+
569
+
570
+ @config_group.command("diff")
571
+ @click.option(
572
+ "--file",
573
+ "config_file",
574
+ type=click.Path(exists=True),
575
+ default=None,
576
+ help="Path to config file (default: auto-detect)",
577
+ )
578
+ def diff_config_cmd(config_file: str | None) -> None:
579
+ """Show what 'config migrate' would change.
580
+
581
+ Displays a unified diff of the current config vs the migrated version.
582
+
583
+ \b
584
+ Examples:
585
+ agent-brain config diff
586
+ agent-brain config diff --file ./config.yaml
587
+ """
588
+ if config_file is not None:
589
+ path: Path | None = Path(config_file)
590
+ else:
591
+ path = _find_config_file()
592
+
593
+ if path is None:
594
+ console.print("[yellow]No config file found.[/]")
595
+ sys.exit(0)
596
+
597
+ diff = diff_config_file(path)
598
+ if not diff:
599
+ console.print("[green]Config is already up to date. No changes needed.[/]")
600
+ sys.exit(0)
601
+
602
+ for line in diff.splitlines():
603
+ if line.startswith("---") or line.startswith("+++"):
604
+ console.print(f"[bold]{line}[/]")
605
+ elif line.startswith("-"):
606
+ console.print(f"[red]{line}[/]")
607
+ elif line.startswith("+"):
608
+ console.print(f"[green]{line}[/]")
609
+ elif line.startswith("@@"):
610
+ console.print(f"[cyan]{line}[/]")
611
+ else:
612
+ console.print(line)
@@ -24,7 +24,8 @@ FILE_TYPE_PRESETS: dict[str, list[str]] = {
24
24
  "rust": ["*.rs"],
25
25
  "java": ["*.java"],
26
26
  "csharp": ["*.cs"],
27
- "pascal": ["*.pas", "*.pp", "*.lpr", "*.dpr"],
27
+ "pascal": ["*.pas", "*.pp", "*.lpr", "*.dpr", "*.dpk"],
28
+ "object-pascal": ["*.pas", "*.pp", "*.lpr", "*.dpr", "*.dpk"],
28
29
  "c": ["*.c", "*.h"],
29
30
  "cpp": ["*.cpp", "*.hpp", "*.cc", "*.hh"],
30
31
  "web": ["*.html", "*.css", "*.scss", "*.jsx", "*.tsx"],
@@ -49,6 +50,7 @@ FILE_TYPE_PRESETS: dict[str, list[str]] = {
49
50
  "*.pp",
50
51
  "*.lpr",
51
52
  "*.dpr",
53
+ "*.dpk",
52
54
  "*.c",
53
55
  "*.h",
54
56
  "*.cpp",
@@ -0,0 +1,198 @@
1
+ """Config migration engine for Agent Brain YAML config files.
2
+
3
+ Provides versioned migration steps that upgrade deprecated config keys to the
4
+ current schema, and a diff utility to preview changes before applying them.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import copy
10
+ import difflib
11
+ from collections.abc import Callable
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ import yaml
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # MigrationResult dataclass
20
+ # ---------------------------------------------------------------------------
21
+
22
+
23
+ @dataclass
24
+ class MigrationResult:
25
+ """Result of running migration steps against a config dict.
26
+
27
+ Attributes:
28
+ original: Deep copy of the original config dict before migration.
29
+ migrated: Config dict after all migration steps have been applied.
30
+ changes: Human-readable descriptions of each change applied.
31
+ already_current: True when no migration steps produced any changes.
32
+ """
33
+
34
+ original: dict[str, Any]
35
+ migrated: dict[str, Any]
36
+ changes: list[str]
37
+ already_current: bool
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Migration functions
42
+ # ---------------------------------------------------------------------------
43
+
44
+
45
+ def _migrate_use_llm_extraction(
46
+ config: dict[str, Any],
47
+ ) -> tuple[dict[str, Any], list[str]]:
48
+ """Migrate graphrag.use_llm_extraction -> graphrag.doc_extractor.
49
+
50
+ Phase 34 decision: `use_llm_extraction` (bool) was renamed to
51
+ `doc_extractor` (str: "langextract" | "none").
52
+
53
+ Args:
54
+ config: Config dict (will NOT be mutated; a deep copy is made).
55
+
56
+ Returns:
57
+ Tuple of (updated config dict, list of change descriptions).
58
+ """
59
+ changes: list[str] = []
60
+ config = copy.deepcopy(config)
61
+ graphrag = config.get("graphrag", {})
62
+
63
+ if "use_llm_extraction" in graphrag:
64
+ old_val = graphrag.pop("use_llm_extraction")
65
+ if old_val:
66
+ graphrag["doc_extractor"] = "langextract"
67
+ changes.append(
68
+ "graphrag.use_llm_extraction=True -> graphrag.doc_extractor=langextract"
69
+ )
70
+ else:
71
+ changes.append(
72
+ "graphrag.use_llm_extraction=False -> removed"
73
+ " (default: no doc extraction)"
74
+ )
75
+ config["graphrag"] = graphrag
76
+
77
+ return config, changes
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # MIGRATIONS list — applied in order by migrate_config
82
+ # ---------------------------------------------------------------------------
83
+
84
+ MigrationFn = Callable[[dict[str, Any]], tuple[dict[str, Any], list[str]]]
85
+
86
+ MIGRATIONS: list[MigrationFn] = [
87
+ _migrate_use_llm_extraction,
88
+ ]
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Public API
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ def migrate_config(config: dict[str, Any]) -> MigrationResult:
97
+ """Apply all migration steps to a config dict.
98
+
99
+ Does NOT read or write any files. Mutations are applied to a deep copy,
100
+ so the original dict is never modified.
101
+
102
+ Args:
103
+ config: Parsed YAML config dictionary.
104
+
105
+ Returns:
106
+ :class:`MigrationResult` describing the changes (or lack thereof).
107
+ """
108
+ original = copy.deepcopy(config)
109
+ current = copy.deepcopy(config)
110
+ all_changes: list[str] = []
111
+
112
+ for migration_fn in MIGRATIONS:
113
+ current, changes = migration_fn(current)
114
+ all_changes.extend(changes)
115
+
116
+ return MigrationResult(
117
+ original=original,
118
+ migrated=current,
119
+ changes=all_changes,
120
+ already_current=len(all_changes) == 0,
121
+ )
122
+
123
+
124
+ def migrate_config_file(path: Path) -> MigrationResult:
125
+ """Apply all migrations to a YAML config file and write results to disk.
126
+
127
+ Reads the YAML from *path*, calls :func:`migrate_config`, and if any
128
+ changes were made, writes the migrated dict back to *path* using
129
+ ``yaml.safe_dump`` with ``default_flow_style=False, sort_keys=False``.
130
+
131
+ Args:
132
+ path: Path to the YAML config file.
133
+
134
+ Returns:
135
+ :class:`MigrationResult` describing the changes (or lack thereof).
136
+
137
+ Raises:
138
+ OSError: If the file cannot be read or written.
139
+ yaml.YAMLError: If the YAML cannot be parsed.
140
+ """
141
+ yaml_text = path.read_text(encoding="utf-8")
142
+ config: dict[str, Any] = yaml.safe_load(yaml_text) or {}
143
+
144
+ result = migrate_config(config)
145
+
146
+ if not result.already_current:
147
+ with open(path, "w", encoding="utf-8") as fh:
148
+ yaml.safe_dump(
149
+ result.migrated, fh, default_flow_style=False, sort_keys=False
150
+ )
151
+
152
+ return result
153
+
154
+
155
+ def diff_config(config: dict[str, Any]) -> str:
156
+ """Return a unified diff of what :func:`migrate_config` would change.
157
+
158
+ Args:
159
+ config: Parsed YAML config dictionary.
160
+
161
+ Returns:
162
+ Unified diff string (empty if no changes needed).
163
+ """
164
+ result = migrate_config(config)
165
+ if result.already_current:
166
+ return ""
167
+
168
+ original_yaml = yaml.safe_dump(result.original, default_flow_style=False)
169
+ migrated_yaml = yaml.safe_dump(result.migrated, default_flow_style=False)
170
+
171
+ diff_lines = list(
172
+ difflib.unified_diff(
173
+ original_yaml.splitlines(keepends=True),
174
+ migrated_yaml.splitlines(keepends=True),
175
+ fromfile="config.yaml (current)",
176
+ tofile="config.yaml (migrated)",
177
+ )
178
+ )
179
+
180
+ return "".join(diff_lines)
181
+
182
+
183
+ def diff_config_file(path: Path) -> str:
184
+ """Return a unified diff of what migrating *path* would change.
185
+
186
+ Args:
187
+ path: Path to the YAML config file.
188
+
189
+ Returns:
190
+ Unified diff string (empty if no changes needed).
191
+
192
+ Raises:
193
+ OSError: If the file cannot be read.
194
+ yaml.YAMLError: If the YAML cannot be parsed.
195
+ """
196
+ yaml_text = path.read_text(encoding="utf-8")
197
+ config: dict[str, Any] = yaml.safe_load(yaml_text) or {}
198
+ return diff_config(config)