agent-brain-cli 8.0.0__tar.gz → 9.2.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.
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/PKG-INFO +2 -2
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/__init__.py +1 -1
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/cli.py +2 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/__init__.py +2 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/config.py +8 -5
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/init.py +8 -4
- agent_brain_cli-9.2.0/agent_brain_cli/commands/install_agent.py +340 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/start.py +5 -2
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/stop.py +5 -2
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/uninstall.py +1 -1
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/config.py +27 -15
- agent_brain_cli-9.2.0/agent_brain_cli/migration.py +117 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/__init__.py +59 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/claude_converter.py +155 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/codex_converter.py +215 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/converter_base.py +73 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/gemini_converter.py +119 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/opencode_converter.py +146 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/parser.py +330 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/skill_runtime_converter.py +234 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/tool_maps.py +82 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/types.py +122 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/pyproject.toml +2 -2
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/README.md +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/client/__init__.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/client/api_client.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/cache.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/folders.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/index.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/inject.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/jobs.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/list_cmd.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/query.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/reset.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/status.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/types.py +0 -0
- {agent_brain_cli-8.0.0 → agent_brain_cli-9.2.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:
|
|
3
|
+
Version: 9.2.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 (>=
|
|
18
|
+
Requires-Dist: agent-brain-rag (>=9.2.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)
|
|
@@ -14,6 +14,7 @@ from .commands import (
|
|
|
14
14
|
index_command,
|
|
15
15
|
init_command,
|
|
16
16
|
inject_command,
|
|
17
|
+
install_agent_command,
|
|
17
18
|
jobs_command,
|
|
18
19
|
list_command,
|
|
19
20
|
query_command,
|
|
@@ -101,6 +102,7 @@ cli.add_command(folders_group, name="folders")
|
|
|
101
102
|
cli.add_command(types_group, name="types")
|
|
102
103
|
cli.add_command(cache_group, name="cache")
|
|
103
104
|
cli.add_command(uninstall_command, name="uninstall")
|
|
105
|
+
cli.add_command(install_agent_command, name="install-agent")
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
if __name__ == "__main__":
|
|
@@ -6,6 +6,7 @@ from .folders import folders_group
|
|
|
6
6
|
from .index import index_command
|
|
7
7
|
from .init import init_command
|
|
8
8
|
from .inject import inject_command
|
|
9
|
+
from .install_agent import install_agent_command
|
|
9
10
|
from .jobs import jobs_command
|
|
10
11
|
from .list_cmd import list_command
|
|
11
12
|
from .query import query_command
|
|
@@ -23,6 +24,7 @@ __all__ = [
|
|
|
23
24
|
"index_command",
|
|
24
25
|
"inject_command",
|
|
25
26
|
"init_command",
|
|
27
|
+
"install_agent_command",
|
|
26
28
|
"jobs_command",
|
|
27
29
|
"list_command",
|
|
28
30
|
"query_command",
|
|
@@ -22,7 +22,7 @@ def _find_config_file() -> Path | None:
|
|
|
22
22
|
1. AGENT_BRAIN_CONFIG environment variable
|
|
23
23
|
2. State directory config.yaml (if AGENT_BRAIN_STATE_DIR set)
|
|
24
24
|
3. Current directory config.yaml
|
|
25
|
-
4. Walk up from CWD
|
|
25
|
+
4. Walk up from CWD: .agent-brain/config.yaml (or legacy path)
|
|
26
26
|
5. XDG config ~/.config/agent-brain/config.yaml (preferred)
|
|
27
27
|
6. Legacy ~/.agent-brain/config.yaml (deprecated, prints warning)
|
|
28
28
|
|
|
@@ -48,13 +48,16 @@ def _find_config_file() -> Path | None:
|
|
|
48
48
|
if cwd_config.exists():
|
|
49
49
|
return cwd_config
|
|
50
50
|
|
|
51
|
-
# 4. Walk up from CWD
|
|
51
|
+
# 4. Walk up from CWD looking for .agent-brain/ or legacy .claude/agent-brain/
|
|
52
52
|
current = Path.cwd()
|
|
53
53
|
root = Path(current.anchor)
|
|
54
54
|
while current != root:
|
|
55
|
-
|
|
56
|
-
if
|
|
57
|
-
return
|
|
55
|
+
new_config = current / ".agent-brain" / "config.yaml"
|
|
56
|
+
if new_config.exists():
|
|
57
|
+
return new_config
|
|
58
|
+
legacy_config = current / ".claude" / "agent-brain" / "config.yaml"
|
|
59
|
+
if legacy_config.exists():
|
|
60
|
+
return legacy_config
|
|
58
61
|
current = current.parent
|
|
59
62
|
|
|
60
63
|
# 5. XDG config (checked before legacy per XDG standard)
|
|
@@ -7,6 +7,7 @@ import click
|
|
|
7
7
|
from rich.console import Console
|
|
8
8
|
from rich.panel import Panel
|
|
9
9
|
|
|
10
|
+
from agent_brain_cli.migration import migrate_state_dir
|
|
10
11
|
from agent_brain_cli.xdg_paths import migrate_legacy_paths
|
|
11
12
|
|
|
12
13
|
console = Console()
|
|
@@ -33,7 +34,7 @@ DEFAULT_CONFIG = {
|
|
|
33
34
|
],
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
STATE_DIR_NAME = ".
|
|
37
|
+
STATE_DIR_NAME = ".agent-brain"
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def resolve_project_root(start_path: Path | None = None) -> Path:
|
|
@@ -72,6 +73,8 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
|
|
|
72
73
|
# Walk up looking for markers
|
|
73
74
|
current = start
|
|
74
75
|
while current != current.parent:
|
|
76
|
+
if (current / ".agent-brain").is_dir():
|
|
77
|
+
return current
|
|
75
78
|
if (current / ".claude").is_dir():
|
|
76
79
|
return current
|
|
77
80
|
if (current / "pyproject.toml").is_file():
|
|
@@ -110,7 +113,7 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
|
|
|
110
113
|
"--state-dir",
|
|
111
114
|
"-s",
|
|
112
115
|
type=click.Path(file_okay=False, resolve_path=True),
|
|
113
|
-
help="Custom state directory for index data (default: .
|
|
116
|
+
help="Custom state directory for index data (default: .agent-brain)",
|
|
114
117
|
)
|
|
115
118
|
def init_command(
|
|
116
119
|
path: str | None,
|
|
@@ -122,7 +125,7 @@ def init_command(
|
|
|
122
125
|
) -> None:
|
|
123
126
|
"""Initialize a new Agent Brain project.
|
|
124
127
|
|
|
125
|
-
Creates the .
|
|
128
|
+
Creates the .agent-brain/ directory structure and writes
|
|
126
129
|
a default config.json file.
|
|
127
130
|
|
|
128
131
|
\b
|
|
@@ -147,7 +150,8 @@ def init_command(
|
|
|
147
150
|
if state_dir:
|
|
148
151
|
resolved_state_dir = Path(state_dir).resolve()
|
|
149
152
|
else:
|
|
150
|
-
|
|
153
|
+
# Auto-migrate from legacy .claude/agent-brain if needed
|
|
154
|
+
resolved_state_dir = migrate_state_dir(project_root)
|
|
151
155
|
config_path = resolved_state_dir / "config.json"
|
|
152
156
|
|
|
153
157
|
# Check for existing configuration
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Install-agent command for installing runtime-specific plugin files."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
|
|
11
|
+
from agent_brain_cli.runtime.claude_converter import ClaudeConverter
|
|
12
|
+
from agent_brain_cli.runtime.codex_converter import CodexConverter
|
|
13
|
+
from agent_brain_cli.runtime.gemini_converter import GeminiConverter
|
|
14
|
+
from agent_brain_cli.runtime.opencode_converter import OpenCodeConverter
|
|
15
|
+
from agent_brain_cli.runtime.parser import parse_plugin_dir
|
|
16
|
+
from agent_brain_cli.runtime.skill_runtime_converter import SkillRuntimeConverter
|
|
17
|
+
from agent_brain_cli.runtime.types import Scope
|
|
18
|
+
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
# Default install directories per runtime and scope
|
|
22
|
+
INSTALL_DIRS: dict[str, dict[str, str]] = {
|
|
23
|
+
"claude": {
|
|
24
|
+
"project": ".claude/plugins/agent-brain",
|
|
25
|
+
"global": "~/.claude/plugins/agent-brain",
|
|
26
|
+
},
|
|
27
|
+
"opencode": {
|
|
28
|
+
"project": ".opencode/plugins/agent-brain",
|
|
29
|
+
"global": "~/.config/opencode/plugins/agent-brain",
|
|
30
|
+
},
|
|
31
|
+
"gemini": {
|
|
32
|
+
"project": ".gemini/plugins/agent-brain",
|
|
33
|
+
"global": "~/.config/gemini/plugins/agent-brain",
|
|
34
|
+
},
|
|
35
|
+
"codex": {
|
|
36
|
+
"project": ".codex/skills/agent-brain",
|
|
37
|
+
"global": "~/.codex/skills/agent-brain",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Runtimes that require --dir (no default directory)
|
|
42
|
+
DIR_REQUIRED_RUNTIMES = {"skill-runtime"}
|
|
43
|
+
|
|
44
|
+
ConverterType = type[
|
|
45
|
+
ClaudeConverter
|
|
46
|
+
| OpenCodeConverter
|
|
47
|
+
| GeminiConverter
|
|
48
|
+
| SkillRuntimeConverter
|
|
49
|
+
| CodexConverter
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
CONVERTERS: dict[str, ConverterType] = {
|
|
53
|
+
"claude": ClaudeConverter,
|
|
54
|
+
"opencode": OpenCodeConverter,
|
|
55
|
+
"gemini": GeminiConverter,
|
|
56
|
+
"skill-runtime": SkillRuntimeConverter,
|
|
57
|
+
"codex": CodexConverter,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _find_plugin_dir() -> Path | None:
|
|
62
|
+
"""Find the canonical plugin directory.
|
|
63
|
+
|
|
64
|
+
Searches for agent-brain-plugin in common locations.
|
|
65
|
+
"""
|
|
66
|
+
# Check relative to this package (development layout)
|
|
67
|
+
pkg_dir = Path(__file__).parent.parent.parent.parent
|
|
68
|
+
candidate = pkg_dir / "agent-brain-plugin"
|
|
69
|
+
if candidate.is_dir() and (candidate / "commands").is_dir():
|
|
70
|
+
return candidate
|
|
71
|
+
|
|
72
|
+
# Check installed location
|
|
73
|
+
installed = Path.home() / ".claude" / "plugins" / "agent-brain"
|
|
74
|
+
if installed.is_dir() and (installed / "commands").is_dir():
|
|
75
|
+
return installed
|
|
76
|
+
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _resolve_target_dir(
|
|
81
|
+
runtime: str,
|
|
82
|
+
scope: str,
|
|
83
|
+
project_root: Path | None = None,
|
|
84
|
+
custom_dir: str | None = None,
|
|
85
|
+
) -> Path:
|
|
86
|
+
"""Resolve the target installation directory."""
|
|
87
|
+
if custom_dir:
|
|
88
|
+
return Path(custom_dir).expanduser().resolve()
|
|
89
|
+
dir_template = INSTALL_DIRS[runtime][scope]
|
|
90
|
+
if scope == "global":
|
|
91
|
+
return Path(dir_template).expanduser()
|
|
92
|
+
if project_root is None:
|
|
93
|
+
project_root = Path.cwd()
|
|
94
|
+
return project_root / dir_template
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
RUNTIME_CHOICES = ["claude", "opencode", "gemini", "skill-runtime", "codex"]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@click.command("install-agent")
|
|
101
|
+
@click.option(
|
|
102
|
+
"--agent",
|
|
103
|
+
"-a",
|
|
104
|
+
required=True,
|
|
105
|
+
type=click.Choice(RUNTIME_CHOICES),
|
|
106
|
+
help="Target runtime to install for",
|
|
107
|
+
)
|
|
108
|
+
@click.option(
|
|
109
|
+
"--project",
|
|
110
|
+
"scope",
|
|
111
|
+
flag_value="project",
|
|
112
|
+
default=True,
|
|
113
|
+
help="Install to project directory (default)",
|
|
114
|
+
)
|
|
115
|
+
@click.option(
|
|
116
|
+
"--global",
|
|
117
|
+
"scope",
|
|
118
|
+
flag_value="global",
|
|
119
|
+
help="Install to user-level directory",
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"--plugin-dir",
|
|
123
|
+
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
124
|
+
help="Custom canonical plugin source directory",
|
|
125
|
+
)
|
|
126
|
+
@click.option(
|
|
127
|
+
"--dir",
|
|
128
|
+
"target_dir_option",
|
|
129
|
+
type=click.Path(resolve_path=True),
|
|
130
|
+
help="Target skill directory (required for skill-runtime)",
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--dry-run",
|
|
134
|
+
is_flag=True,
|
|
135
|
+
help="List files that would be created without writing",
|
|
136
|
+
)
|
|
137
|
+
@click.option(
|
|
138
|
+
"--json",
|
|
139
|
+
"json_output",
|
|
140
|
+
is_flag=True,
|
|
141
|
+
help="Output as JSON",
|
|
142
|
+
)
|
|
143
|
+
@click.option(
|
|
144
|
+
"--path",
|
|
145
|
+
"-p",
|
|
146
|
+
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
147
|
+
help="Project path for --project scope (default: cwd)",
|
|
148
|
+
)
|
|
149
|
+
def install_agent_command(
|
|
150
|
+
agent: str,
|
|
151
|
+
scope: str,
|
|
152
|
+
plugin_dir: str | None,
|
|
153
|
+
target_dir_option: str | None,
|
|
154
|
+
dry_run: bool,
|
|
155
|
+
json_output: bool,
|
|
156
|
+
path: str | None,
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Install Agent Brain plugin for a specific runtime.
|
|
159
|
+
|
|
160
|
+
Converts the canonical plugin format into the target runtime's
|
|
161
|
+
native format and installs it.
|
|
162
|
+
|
|
163
|
+
\b
|
|
164
|
+
Examples:
|
|
165
|
+
agent-brain install-agent --agent claude --project
|
|
166
|
+
agent-brain install-agent --agent opencode --global
|
|
167
|
+
agent-brain install-agent --agent gemini --dry-run
|
|
168
|
+
agent-brain install-agent --agent skill-runtime --dir ./my-skills
|
|
169
|
+
agent-brain install-agent --agent codex
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
# Validate --dir requirement for skill-runtime
|
|
173
|
+
if agent in DIR_REQUIRED_RUNTIMES and not target_dir_option:
|
|
174
|
+
msg = (
|
|
175
|
+
f"--dir is required for --agent {agent}. "
|
|
176
|
+
"Specify the target skill directory."
|
|
177
|
+
)
|
|
178
|
+
if json_output:
|
|
179
|
+
click.echo(json.dumps({"error": msg}))
|
|
180
|
+
else:
|
|
181
|
+
console.print(f"[red]Error:[/] {msg}")
|
|
182
|
+
raise SystemExit(1)
|
|
183
|
+
|
|
184
|
+
# Resolve plugin source directory
|
|
185
|
+
source: Path
|
|
186
|
+
if plugin_dir:
|
|
187
|
+
source = Path(plugin_dir)
|
|
188
|
+
else:
|
|
189
|
+
found = _find_plugin_dir()
|
|
190
|
+
if found is None:
|
|
191
|
+
msg = (
|
|
192
|
+
"Could not find canonical plugin directory. "
|
|
193
|
+
"Use --plugin-dir to specify location."
|
|
194
|
+
)
|
|
195
|
+
if json_output:
|
|
196
|
+
click.echo(json.dumps({"error": msg}))
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"[red]Error:[/] {msg}")
|
|
199
|
+
raise SystemExit(1)
|
|
200
|
+
source = found
|
|
201
|
+
|
|
202
|
+
# Parse the plugin
|
|
203
|
+
bundle = parse_plugin_dir(source)
|
|
204
|
+
|
|
205
|
+
if not json_output and not dry_run:
|
|
206
|
+
console.print(
|
|
207
|
+
f"[dim]Parsed {len(bundle.commands)} commands, "
|
|
208
|
+
f"{len(bundle.agents)} agents, "
|
|
209
|
+
f"{len(bundle.skills)} skills, "
|
|
210
|
+
f"{len(bundle.templates)} templates, "
|
|
211
|
+
f"{len(bundle.scripts)} scripts[/]"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Resolve target directory
|
|
215
|
+
project_root = Path(path) if path else None
|
|
216
|
+
target = _resolve_target_dir(agent, scope, project_root, target_dir_option)
|
|
217
|
+
|
|
218
|
+
# Create converter
|
|
219
|
+
converter_cls = CONVERTERS[agent]
|
|
220
|
+
converter = converter_cls()
|
|
221
|
+
scope_enum = Scope.GLOBAL if scope == "global" else Scope.PROJECT
|
|
222
|
+
|
|
223
|
+
if dry_run:
|
|
224
|
+
_handle_dry_run(
|
|
225
|
+
converter,
|
|
226
|
+
bundle,
|
|
227
|
+
target,
|
|
228
|
+
scope_enum,
|
|
229
|
+
agent,
|
|
230
|
+
scope,
|
|
231
|
+
json_output,
|
|
232
|
+
)
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
# Actually install
|
|
236
|
+
if isinstance(converter, CodexConverter):
|
|
237
|
+
codex_root = Path(path) if path else Path.cwd()
|
|
238
|
+
files = converter.install(
|
|
239
|
+
bundle, target, scope_enum, project_root=codex_root
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
files = converter.install(bundle, target, scope_enum)
|
|
243
|
+
|
|
244
|
+
if json_output:
|
|
245
|
+
result: dict[str, Any] = {
|
|
246
|
+
"status": "installed",
|
|
247
|
+
"agent": agent,
|
|
248
|
+
"scope": scope,
|
|
249
|
+
"target_dir": str(target),
|
|
250
|
+
"files_created": len(files),
|
|
251
|
+
"source_dir": str(source),
|
|
252
|
+
}
|
|
253
|
+
click.echo(json.dumps(result, indent=2))
|
|
254
|
+
else:
|
|
255
|
+
console.print(
|
|
256
|
+
Panel(
|
|
257
|
+
f"[green]Plugin installed successfully![/]\n\n"
|
|
258
|
+
f"[bold]Runtime:[/] {agent}\n"
|
|
259
|
+
f"[bold]Scope:[/] {scope}\n"
|
|
260
|
+
f"[bold]Target:[/] {target}\n"
|
|
261
|
+
f"[bold]Files:[/] {len(files)}",
|
|
262
|
+
title="Agent Brain Installed",
|
|
263
|
+
border_style="green",
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
except SystemExit:
|
|
268
|
+
raise
|
|
269
|
+
except Exception as exc:
|
|
270
|
+
if json_output:
|
|
271
|
+
click.echo(json.dumps({"error": str(exc)}))
|
|
272
|
+
else:
|
|
273
|
+
console.print(f"[red]Error:[/] {exc}")
|
|
274
|
+
raise SystemExit(1) from exc
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _handle_dry_run(
|
|
278
|
+
converter: (
|
|
279
|
+
ClaudeConverter
|
|
280
|
+
| OpenCodeConverter
|
|
281
|
+
| GeminiConverter
|
|
282
|
+
| SkillRuntimeConverter
|
|
283
|
+
| CodexConverter
|
|
284
|
+
),
|
|
285
|
+
bundle: Any,
|
|
286
|
+
target: Path,
|
|
287
|
+
scope_enum: Scope,
|
|
288
|
+
agent: str,
|
|
289
|
+
scope: str,
|
|
290
|
+
json_output: bool,
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Handle dry-run mode: simulate install in temp dir."""
|
|
293
|
+
import tempfile
|
|
294
|
+
|
|
295
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
296
|
+
tmp_target = Path(tmp)
|
|
297
|
+
# For Codex, pass tmp as project_root so AGENTS.md lands in tmpdir
|
|
298
|
+
if isinstance(converter, CodexConverter):
|
|
299
|
+
files = converter.install(
|
|
300
|
+
bundle, tmp_target, scope_enum, project_root=Path(tmp)
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
files = converter.install(bundle, tmp_target, scope_enum)
|
|
304
|
+
# Remap paths to real target
|
|
305
|
+
planned: list[Path] = []
|
|
306
|
+
for f in files:
|
|
307
|
+
try:
|
|
308
|
+
planned.append(target / f.relative_to(tmp_target))
|
|
309
|
+
except ValueError:
|
|
310
|
+
# AGENTS.md may be at project_root, not under target
|
|
311
|
+
planned.append(f)
|
|
312
|
+
|
|
313
|
+
if json_output:
|
|
314
|
+
click.echo(
|
|
315
|
+
json.dumps(
|
|
316
|
+
{
|
|
317
|
+
"dry_run": True,
|
|
318
|
+
"agent": agent,
|
|
319
|
+
"scope": scope,
|
|
320
|
+
"target_dir": str(target),
|
|
321
|
+
"files": [str(f) for f in planned],
|
|
322
|
+
"file_count": len(planned),
|
|
323
|
+
},
|
|
324
|
+
indent=2,
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
console.print(
|
|
329
|
+
Panel(
|
|
330
|
+
f"[yellow]Dry run[/] — no files written\n\n"
|
|
331
|
+
f"[bold]Runtime:[/] {agent}\n"
|
|
332
|
+
f"[bold]Scope:[/] {scope}\n"
|
|
333
|
+
f"[bold]Target:[/] {target}\n"
|
|
334
|
+
f"[bold]Files:[/] {len(planned)}",
|
|
335
|
+
title="Install Preview",
|
|
336
|
+
border_style="yellow",
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
for f in planned:
|
|
340
|
+
console.print(f" [dim]{f}[/]")
|
|
@@ -15,11 +15,12 @@ import click
|
|
|
15
15
|
from rich.console import Console
|
|
16
16
|
from rich.panel import Panel
|
|
17
17
|
|
|
18
|
+
from agent_brain_cli.migration import resolve_state_dir_with_fallback
|
|
18
19
|
from agent_brain_cli.xdg_paths import get_xdg_state_dir, migrate_legacy_paths
|
|
19
20
|
|
|
20
21
|
console = Console()
|
|
21
22
|
|
|
22
|
-
STATE_DIR_NAME = ".
|
|
23
|
+
STATE_DIR_NAME = ".agent-brain"
|
|
23
24
|
LOCK_FILE = "agent-brain.lock"
|
|
24
25
|
PID_FILE = "agent-brain.pid"
|
|
25
26
|
RUNTIME_FILE = "runtime.json"
|
|
@@ -46,6 +47,8 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
|
|
|
46
47
|
# Walk up looking for markers
|
|
47
48
|
current = start
|
|
48
49
|
while current != current.parent:
|
|
50
|
+
if (current / ".agent-brain").is_dir():
|
|
51
|
+
return current
|
|
49
52
|
if (current / ".claude").is_dir():
|
|
50
53
|
return current
|
|
51
54
|
if (current / "pyproject.toml").is_file():
|
|
@@ -234,7 +237,7 @@ def start_command(
|
|
|
234
237
|
else:
|
|
235
238
|
project_root = resolve_project_root()
|
|
236
239
|
|
|
237
|
-
state_dir = project_root
|
|
240
|
+
state_dir = resolve_state_dir_with_fallback(project_root)
|
|
238
241
|
|
|
239
242
|
# Check if initialized
|
|
240
243
|
if not state_dir.exists():
|
|
@@ -12,11 +12,12 @@ from urllib.request import Request, urlopen
|
|
|
12
12
|
import click
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
|
|
15
|
+
from agent_brain_cli.migration import resolve_state_dir_with_fallback
|
|
15
16
|
from agent_brain_cli.xdg_paths import get_registry_path
|
|
16
17
|
|
|
17
18
|
console = Console()
|
|
18
19
|
|
|
19
|
-
STATE_DIR_NAME = ".
|
|
20
|
+
STATE_DIR_NAME = ".agent-brain"
|
|
20
21
|
LOCK_FILE = "agent-brain.lock"
|
|
21
22
|
PID_FILE = "agent-brain.pid"
|
|
22
23
|
RUNTIME_FILE = "runtime.json"
|
|
@@ -43,6 +44,8 @@ def resolve_project_root(start_path: Path | None = None) -> Path:
|
|
|
43
44
|
# Walk up looking for markers
|
|
44
45
|
current = start
|
|
45
46
|
while current != current.parent:
|
|
47
|
+
if (current / ".agent-brain").is_dir():
|
|
48
|
+
return current
|
|
46
49
|
if (current / ".claude").is_dir():
|
|
47
50
|
return current
|
|
48
51
|
if (current / "pyproject.toml").is_file():
|
|
@@ -181,7 +184,7 @@ def stop_command(
|
|
|
181
184
|
else:
|
|
182
185
|
project_root = resolve_project_root()
|
|
183
186
|
|
|
184
|
-
state_dir = project_root
|
|
187
|
+
state_dir = resolve_state_dir_with_fallback(project_root)
|
|
185
188
|
|
|
186
189
|
# Check if state directory exists
|
|
187
190
|
if not state_dir.exists():
|
|
@@ -102,7 +102,7 @@ def uninstall_command(yes: bool, json_output: bool) -> None:
|
|
|
102
102
|
- ~/.local/state/agent-brain/ (XDG state directory)
|
|
103
103
|
- ~/.agent-brain/ (legacy directory, if present)
|
|
104
104
|
|
|
105
|
-
Does NOT remove project-level .
|
|
105
|
+
Does NOT remove project-level .agent-brain/ directories.
|
|
106
106
|
|
|
107
107
|
\b
|
|
108
108
|
Examples:
|
|
@@ -18,7 +18,8 @@ from agent_brain_cli.xdg_paths import get_xdg_config_dir
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
# Default state directory name within project root
|
|
21
|
-
STATE_DIR_NAME = ".
|
|
21
|
+
STATE_DIR_NAME = ".agent-brain"
|
|
22
|
+
LEGACY_STATE_DIR_NAME = ".claude/agent-brain"
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class ServerConfig(BaseModel):
|
|
@@ -47,7 +48,7 @@ class ProjectConfig(BaseModel):
|
|
|
47
48
|
|
|
48
49
|
state_dir: str | None = Field(
|
|
49
50
|
default=None,
|
|
50
|
-
description="Custom state directory path (default: .
|
|
51
|
+
description="Custom state directory path (default: .agent-brain)",
|
|
51
52
|
)
|
|
52
53
|
project_root: str | None = Field(
|
|
53
54
|
default=None,
|
|
@@ -120,7 +121,7 @@ def _find_config_file(start_path: Path | None = None) -> Path | None:
|
|
|
120
121
|
Search order:
|
|
121
122
|
1. AGENT_BRAIN_CONFIG environment variable
|
|
122
123
|
2. Current directory: agent-brain.yaml or config.yaml
|
|
123
|
-
3. Project .
|
|
124
|
+
3. Project .agent-brain/config.yaml (or legacy .claude/agent-brain/)
|
|
124
125
|
4. User home: ~/.agent-brain/config.yaml
|
|
125
126
|
5. User home: ~/.config/agent-brain/config.yaml (XDG)
|
|
126
127
|
|
|
@@ -148,14 +149,18 @@ def _find_config_file(start_path: Path | None = None) -> Path | None:
|
|
|
148
149
|
logger.debug(f"Using config from current directory: {cwd_config}")
|
|
149
150
|
return cwd_config
|
|
150
151
|
|
|
151
|
-
# 3. Project .claude/agent-brain
|
|
152
|
-
# Walk up looking for
|
|
152
|
+
# 3. Project .agent-brain directory (or legacy .claude/agent-brain)
|
|
153
|
+
# Walk up looking for state directory
|
|
153
154
|
current = start
|
|
154
155
|
while current != current.parent:
|
|
155
|
-
|
|
156
|
-
if
|
|
157
|
-
logger.debug(f"Using config from project: {
|
|
158
|
-
return
|
|
156
|
+
new_config = current / ".agent-brain" / "config.yaml"
|
|
157
|
+
if new_config.exists():
|
|
158
|
+
logger.debug(f"Using config from project: {new_config}")
|
|
159
|
+
return new_config
|
|
160
|
+
legacy_config = current / ".claude" / "agent-brain" / "config.yaml"
|
|
161
|
+
if legacy_config.exists():
|
|
162
|
+
logger.debug(f"Using config from project: {legacy_config}")
|
|
163
|
+
return legacy_config
|
|
159
164
|
current = current.parent
|
|
160
165
|
|
|
161
166
|
# 4. XDG config (checked before legacy per XDG standard)
|
|
@@ -281,6 +286,8 @@ def _find_project_root(start_path: Path | None = None) -> Path:
|
|
|
281
286
|
# Walk up looking for markers
|
|
282
287
|
current = start
|
|
283
288
|
while current != current.parent:
|
|
289
|
+
if (current / ".agent-brain").is_dir():
|
|
290
|
+
return current
|
|
284
291
|
if (current / ".claude").is_dir():
|
|
285
292
|
return current
|
|
286
293
|
if (current / "pyproject.toml").is_file():
|
|
@@ -297,10 +304,10 @@ def get_state_dir(
|
|
|
297
304
|
"""Get the resolved state directory path.
|
|
298
305
|
|
|
299
306
|
Resolution order:
|
|
300
|
-
1. Detect project root and check for .claude/agent-brain/
|
|
307
|
+
1. Detect project root and check for .agent-brain/ (or legacy .claude/agent-brain/)
|
|
301
308
|
2. config.project.state_dir from config file
|
|
302
309
|
3. AGENT_BRAIN_STATE_DIR environment variable (explicit override)
|
|
303
|
-
4. Default: {project_root}/.
|
|
310
|
+
4. Default: {project_root}/.agent-brain
|
|
304
311
|
|
|
305
312
|
Args:
|
|
306
313
|
config: Optional pre-loaded config.
|
|
@@ -313,9 +320,14 @@ def get_state_dir(
|
|
|
313
320
|
if project_root is None:
|
|
314
321
|
project_root = _find_project_root()
|
|
315
322
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
# Check new path first, then legacy
|
|
324
|
+
new_state_dir = project_root / STATE_DIR_NAME
|
|
325
|
+
if new_state_dir.exists() and (new_state_dir / "config.json").exists():
|
|
326
|
+
return new_state_dir
|
|
327
|
+
|
|
328
|
+
legacy_state_dir = project_root / LEGACY_STATE_DIR_NAME
|
|
329
|
+
if legacy_state_dir.exists() and (legacy_state_dir / "config.json").exists():
|
|
330
|
+
return legacy_state_dir
|
|
319
331
|
|
|
320
332
|
# 2. Check config file setting
|
|
321
333
|
if config is None:
|
|
@@ -329,7 +341,7 @@ def get_state_dir(
|
|
|
329
341
|
if env_state_dir:
|
|
330
342
|
return Path(env_state_dir).expanduser().resolve()
|
|
331
343
|
|
|
332
|
-
# 4. Default: project_root/.
|
|
344
|
+
# 4. Default: project_root/.agent-brain
|
|
333
345
|
return project_root / STATE_DIR_NAME
|
|
334
346
|
|
|
335
347
|
|