agent-brain-cli 9.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-9.0.0 → agent_brain_cli-9.2.0}/PKG-INFO +2 -2
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/__init__.py +1 -1
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/install_agent.py +147 -57
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/__init__.py +8 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/codex_converter.py +215 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/parser.py +66 -0
- agent_brain_cli-9.2.0/agent_brain_cli/runtime/skill_runtime_converter.py +234 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/types.py +22 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/pyproject.toml +2 -2
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/README.md +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/cli.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/client/__init__.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/client/api_client.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/__init__.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/cache.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/config.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/folders.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/index.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/init.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/inject.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/jobs.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/list_cmd.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/query.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/reset.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/start.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/status.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/stop.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/types.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/commands/uninstall.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/config.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/migration.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/claude_converter.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/converter_base.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/gemini_converter.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/opencode_converter.py +0 -0
- {agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/tool_maps.py +0 -0
- {agent_brain_cli-9.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: 9.
|
|
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 (>=9.
|
|
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)
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from rich.panel import Panel
|
|
9
10
|
|
|
10
11
|
from agent_brain_cli.runtime.claude_converter import ClaudeConverter
|
|
12
|
+
from agent_brain_cli.runtime.codex_converter import CodexConverter
|
|
11
13
|
from agent_brain_cli.runtime.gemini_converter import GeminiConverter
|
|
12
14
|
from agent_brain_cli.runtime.opencode_converter import OpenCodeConverter
|
|
13
15
|
from agent_brain_cli.runtime.parser import parse_plugin_dir
|
|
16
|
+
from agent_brain_cli.runtime.skill_runtime_converter import SkillRuntimeConverter
|
|
14
17
|
from agent_brain_cli.runtime.types import Scope
|
|
15
18
|
|
|
16
19
|
console = Console()
|
|
@@ -29,12 +32,29 @@ INSTALL_DIRS: dict[str, dict[str, str]] = {
|
|
|
29
32
|
"project": ".gemini/plugins/agent-brain",
|
|
30
33
|
"global": "~/.config/gemini/plugins/agent-brain",
|
|
31
34
|
},
|
|
35
|
+
"codex": {
|
|
36
|
+
"project": ".codex/skills/agent-brain",
|
|
37
|
+
"global": "~/.codex/skills/agent-brain",
|
|
38
|
+
},
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
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] = {
|
|
35
53
|
"claude": ClaudeConverter,
|
|
36
54
|
"opencode": OpenCodeConverter,
|
|
37
55
|
"gemini": GeminiConverter,
|
|
56
|
+
"skill-runtime": SkillRuntimeConverter,
|
|
57
|
+
"codex": CodexConverter,
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
|
|
@@ -58,9 +78,14 @@ def _find_plugin_dir() -> Path | None:
|
|
|
58
78
|
|
|
59
79
|
|
|
60
80
|
def _resolve_target_dir(
|
|
61
|
-
runtime: str,
|
|
81
|
+
runtime: str,
|
|
82
|
+
scope: str,
|
|
83
|
+
project_root: Path | None = None,
|
|
84
|
+
custom_dir: str | None = None,
|
|
62
85
|
) -> Path:
|
|
63
86
|
"""Resolve the target installation directory."""
|
|
87
|
+
if custom_dir:
|
|
88
|
+
return Path(custom_dir).expanduser().resolve()
|
|
64
89
|
dir_template = INSTALL_DIRS[runtime][scope]
|
|
65
90
|
if scope == "global":
|
|
66
91
|
return Path(dir_template).expanduser()
|
|
@@ -69,12 +94,15 @@ def _resolve_target_dir(
|
|
|
69
94
|
return project_root / dir_template
|
|
70
95
|
|
|
71
96
|
|
|
97
|
+
RUNTIME_CHOICES = ["claude", "opencode", "gemini", "skill-runtime", "codex"]
|
|
98
|
+
|
|
99
|
+
|
|
72
100
|
@click.command("install-agent")
|
|
73
101
|
@click.option(
|
|
74
102
|
"--agent",
|
|
75
103
|
"-a",
|
|
76
104
|
required=True,
|
|
77
|
-
type=click.Choice(
|
|
105
|
+
type=click.Choice(RUNTIME_CHOICES),
|
|
78
106
|
help="Target runtime to install for",
|
|
79
107
|
)
|
|
80
108
|
@click.option(
|
|
@@ -95,6 +123,12 @@ def _resolve_target_dir(
|
|
|
95
123
|
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
96
124
|
help="Custom canonical plugin source directory",
|
|
97
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
|
+
)
|
|
98
132
|
@click.option(
|
|
99
133
|
"--dry-run",
|
|
100
134
|
is_flag=True,
|
|
@@ -116,6 +150,7 @@ def install_agent_command(
|
|
|
116
150
|
agent: str,
|
|
117
151
|
scope: str,
|
|
118
152
|
plugin_dir: str | None,
|
|
153
|
+
target_dir_option: str | None,
|
|
119
154
|
dry_run: bool,
|
|
120
155
|
json_output: bool,
|
|
121
156
|
path: str | None,
|
|
@@ -130,9 +165,22 @@ def install_agent_command(
|
|
|
130
165
|
agent-brain install-agent --agent claude --project
|
|
131
166
|
agent-brain install-agent --agent opencode --global
|
|
132
167
|
agent-brain install-agent --agent gemini --dry-run
|
|
133
|
-
agent-brain install-agent --agent
|
|
168
|
+
agent-brain install-agent --agent skill-runtime --dir ./my-skills
|
|
169
|
+
agent-brain install-agent --agent codex
|
|
134
170
|
"""
|
|
135
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
|
+
|
|
136
184
|
# Resolve plugin source directory
|
|
137
185
|
source: Path
|
|
138
186
|
if plugin_dir:
|
|
@@ -158,12 +206,14 @@ def install_agent_command(
|
|
|
158
206
|
console.print(
|
|
159
207
|
f"[dim]Parsed {len(bundle.commands)} commands, "
|
|
160
208
|
f"{len(bundle.agents)} agents, "
|
|
161
|
-
f"{len(bundle.skills)} skills
|
|
209
|
+
f"{len(bundle.skills)} skills, "
|
|
210
|
+
f"{len(bundle.templates)} templates, "
|
|
211
|
+
f"{len(bundle.scripts)} scripts[/]"
|
|
162
212
|
)
|
|
163
213
|
|
|
164
214
|
# Resolve target directory
|
|
165
215
|
project_root = Path(path) if path else None
|
|
166
|
-
target = _resolve_target_dir(agent, scope, project_root)
|
|
216
|
+
target = _resolve_target_dir(agent, scope, project_root, target_dir_option)
|
|
167
217
|
|
|
168
218
|
# Create converter
|
|
169
219
|
converter_cls = CONVERTERS[agent]
|
|
@@ -171,62 +221,36 @@ def install_agent_command(
|
|
|
171
221
|
scope_enum = Scope.GLOBAL if scope == "global" else Scope.PROJECT
|
|
172
222
|
|
|
173
223
|
if dry_run:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if json_output:
|
|
184
|
-
click.echo(
|
|
185
|
-
json.dumps(
|
|
186
|
-
{
|
|
187
|
-
"dry_run": True,
|
|
188
|
-
"agent": agent,
|
|
189
|
-
"scope": scope,
|
|
190
|
-
"target_dir": str(target),
|
|
191
|
-
"files": [str(f) for f in planned],
|
|
192
|
-
"file_count": len(planned),
|
|
193
|
-
},
|
|
194
|
-
indent=2,
|
|
195
|
-
)
|
|
196
|
-
)
|
|
197
|
-
else:
|
|
198
|
-
console.print(
|
|
199
|
-
Panel(
|
|
200
|
-
f"[yellow]Dry run[/] — no files written\n\n"
|
|
201
|
-
f"[bold]Runtime:[/] {agent}\n"
|
|
202
|
-
f"[bold]Scope:[/] {scope}\n"
|
|
203
|
-
f"[bold]Target:[/] {target}\n"
|
|
204
|
-
f"[bold]Files:[/] {len(planned)}",
|
|
205
|
-
title="Install Preview",
|
|
206
|
-
border_style="yellow",
|
|
207
|
-
)
|
|
208
|
-
)
|
|
209
|
-
for f in planned:
|
|
210
|
-
console.print(f" [dim]{f}[/]")
|
|
224
|
+
_handle_dry_run(
|
|
225
|
+
converter,
|
|
226
|
+
bundle,
|
|
227
|
+
target,
|
|
228
|
+
scope_enum,
|
|
229
|
+
agent,
|
|
230
|
+
scope,
|
|
231
|
+
json_output,
|
|
232
|
+
)
|
|
211
233
|
return
|
|
212
234
|
|
|
213
235
|
# Actually install
|
|
214
|
-
|
|
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)
|
|
215
243
|
|
|
216
244
|
if json_output:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
},
|
|
227
|
-
indent=2,
|
|
228
|
-
)
|
|
229
|
-
)
|
|
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))
|
|
230
254
|
else:
|
|
231
255
|
console.print(
|
|
232
256
|
Panel(
|
|
@@ -248,3 +272,69 @@ def install_agent_command(
|
|
|
248
272
|
else:
|
|
249
273
|
console.print(f"[red]Error:[/] {exc}")
|
|
250
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}[/]")
|
|
@@ -6,7 +6,9 @@ from agent_brain_cli.runtime.parser import (
|
|
|
6
6
|
parse_command,
|
|
7
7
|
parse_frontmatter,
|
|
8
8
|
parse_plugin_dir,
|
|
9
|
+
parse_scripts,
|
|
9
10
|
parse_skill,
|
|
11
|
+
parse_templates,
|
|
10
12
|
)
|
|
11
13
|
from agent_brain_cli.runtime.tool_maps import (
|
|
12
14
|
CLAUDE_TOOLS,
|
|
@@ -21,7 +23,9 @@ from agent_brain_cli.runtime.types import (
|
|
|
21
23
|
PluginCommand,
|
|
22
24
|
PluginManifest,
|
|
23
25
|
PluginParameter,
|
|
26
|
+
PluginScript,
|
|
24
27
|
PluginSkill,
|
|
28
|
+
PluginTemplate,
|
|
25
29
|
RuntimeType,
|
|
26
30
|
Scope,
|
|
27
31
|
TriggerPattern,
|
|
@@ -36,7 +40,9 @@ __all__ = [
|
|
|
36
40
|
"PluginCommand",
|
|
37
41
|
"PluginManifest",
|
|
38
42
|
"PluginParameter",
|
|
43
|
+
"PluginScript",
|
|
39
44
|
"PluginSkill",
|
|
45
|
+
"PluginTemplate",
|
|
40
46
|
"RuntimeConverter",
|
|
41
47
|
"RuntimeType",
|
|
42
48
|
"Scope",
|
|
@@ -47,5 +53,7 @@ __all__ = [
|
|
|
47
53
|
"parse_command",
|
|
48
54
|
"parse_frontmatter",
|
|
49
55
|
"parse_plugin_dir",
|
|
56
|
+
"parse_scripts",
|
|
50
57
|
"parse_skill",
|
|
58
|
+
"parse_templates",
|
|
51
59
|
]
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Codex runtime converter.
|
|
2
|
+
|
|
3
|
+
Codex is a named preset built on the generic skill-runtime converter.
|
|
4
|
+
It installs to `.codex/skills/agent-brain/` and generates an AGENTS.md
|
|
5
|
+
file at the project root with Agent Brain guidance.
|
|
6
|
+
|
|
7
|
+
Key differences from base SkillRuntimeConverter:
|
|
8
|
+
- Default install dir: .codex/skills/agent-brain/
|
|
9
|
+
- AGENTS.md generated at project root (idempotent via HTML comment markers)
|
|
10
|
+
- Skills include invocation guidance headers
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from agent_brain_cli.runtime.skill_runtime_converter import (
|
|
17
|
+
SkillRuntimeConverter,
|
|
18
|
+
)
|
|
19
|
+
from agent_brain_cli.runtime.types import (
|
|
20
|
+
PluginAgent,
|
|
21
|
+
PluginBundle,
|
|
22
|
+
PluginCommand,
|
|
23
|
+
PluginSkill,
|
|
24
|
+
RuntimeType,
|
|
25
|
+
Scope,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
# HTML comment markers for idempotent AGENTS.md updates
|
|
31
|
+
AGENTS_MD_START = "<!-- agent-brain:start -->"
|
|
32
|
+
AGENTS_MD_END = "<!-- agent-brain:end -->"
|
|
33
|
+
|
|
34
|
+
AGENTS_MD_SECTION = """\
|
|
35
|
+
{start_marker}
|
|
36
|
+
|
|
37
|
+
## Agent Brain
|
|
38
|
+
|
|
39
|
+
Agent Brain provides semantic search over your codebase and documentation.
|
|
40
|
+
|
|
41
|
+
### Available Skills
|
|
42
|
+
|
|
43
|
+
| Skill | Description |
|
|
44
|
+
|-------|-------------|
|
|
45
|
+
{skill_table}
|
|
46
|
+
|
|
47
|
+
### Usage
|
|
48
|
+
|
|
49
|
+
Ask your AI assistant to search documentation or code:
|
|
50
|
+
|
|
51
|
+
- "Search for authentication patterns in my codebase"
|
|
52
|
+
- "Find documentation about the API endpoints"
|
|
53
|
+
- "Look up how error handling works"
|
|
54
|
+
|
|
55
|
+
### Setup
|
|
56
|
+
|
|
57
|
+
Run `agent-brain start` to start the Agent Brain server, then use
|
|
58
|
+
`agent-brain index ./src` to index your source code.
|
|
59
|
+
|
|
60
|
+
{end_marker}"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CodexConverter:
|
|
64
|
+
"""Converter for Codex runtime (skill-runtime preset).
|
|
65
|
+
|
|
66
|
+
Delegates skill-directory creation to SkillRuntimeConverter and
|
|
67
|
+
adds Codex-specific AGENTS.md generation.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self._base = SkillRuntimeConverter()
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def runtime_type(self) -> RuntimeType:
|
|
75
|
+
return RuntimeType.CODEX
|
|
76
|
+
|
|
77
|
+
def convert_command(self, command: PluginCommand) -> str:
|
|
78
|
+
"""Convert command with Codex invocation guidance header."""
|
|
79
|
+
base_content = self._base.convert_command(command)
|
|
80
|
+
return _add_codex_header(base_content, command.name)
|
|
81
|
+
|
|
82
|
+
def convert_agent(self, agent: PluginAgent) -> str:
|
|
83
|
+
"""Convert agent with Codex invocation guidance header."""
|
|
84
|
+
base_content = self._base.convert_agent(agent)
|
|
85
|
+
return _add_codex_header(base_content, agent.name)
|
|
86
|
+
|
|
87
|
+
def convert_skill(self, skill: PluginSkill) -> str:
|
|
88
|
+
"""Convert skill with Codex invocation guidance header."""
|
|
89
|
+
return self._base.convert_skill(skill)
|
|
90
|
+
|
|
91
|
+
def install(
|
|
92
|
+
self,
|
|
93
|
+
bundle: PluginBundle,
|
|
94
|
+
target_dir: Path,
|
|
95
|
+
scope: Scope,
|
|
96
|
+
project_root: Path | None = None,
|
|
97
|
+
) -> list[Path]:
|
|
98
|
+
"""Install Codex skills and generate AGENTS.md.
|
|
99
|
+
|
|
100
|
+
Delegates skill-directory creation to the base converter,
|
|
101
|
+
then generates/updates AGENTS.md at the project root.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
bundle: Parsed plugin bundle.
|
|
105
|
+
target_dir: Where to write skill directories.
|
|
106
|
+
scope: Project-level or global installation.
|
|
107
|
+
project_root: Project root for AGENTS.md. If None,
|
|
108
|
+
derived from target_dir for project scope.
|
|
109
|
+
"""
|
|
110
|
+
# Install skill directories via base converter
|
|
111
|
+
created = self._base.install(bundle, target_dir, scope)
|
|
112
|
+
|
|
113
|
+
# Re-write command and agent SKILL.md files with Codex headers
|
|
114
|
+
for cmd in bundle.commands:
|
|
115
|
+
from agent_brain_cli.runtime.skill_runtime_converter import (
|
|
116
|
+
_skill_dir_name,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
skill_name = _skill_dir_name(cmd.name)
|
|
120
|
+
skill_file = target_dir / skill_name / "SKILL.md"
|
|
121
|
+
if skill_file.exists():
|
|
122
|
+
skill_file.write_text(self.convert_command(cmd), encoding="utf-8")
|
|
123
|
+
|
|
124
|
+
for agent in bundle.agents:
|
|
125
|
+
from agent_brain_cli.runtime.skill_runtime_converter import (
|
|
126
|
+
_skill_dir_name,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
skill_name = _skill_dir_name(agent.name)
|
|
130
|
+
skill_file = target_dir / skill_name / "SKILL.md"
|
|
131
|
+
if skill_file.exists():
|
|
132
|
+
skill_file.write_text(self.convert_agent(agent), encoding="utf-8")
|
|
133
|
+
|
|
134
|
+
# Generate AGENTS.md at project root
|
|
135
|
+
if project_root is None:
|
|
136
|
+
if scope == Scope.PROJECT:
|
|
137
|
+
# .codex/skills/agent-brain → project root (3 levels up)
|
|
138
|
+
project_root = target_dir.parent.parent.parent
|
|
139
|
+
else:
|
|
140
|
+
project_root = Path.cwd()
|
|
141
|
+
|
|
142
|
+
agents_md_path = project_root / "AGENTS.md"
|
|
143
|
+
try:
|
|
144
|
+
agents_md_files = _update_agents_md(agents_md_path, bundle)
|
|
145
|
+
created.extend(agents_md_files)
|
|
146
|
+
except OSError as exc:
|
|
147
|
+
logger.warning("Could not write AGENTS.md: %s", exc)
|
|
148
|
+
|
|
149
|
+
return created
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _add_codex_header(content: str, name: str) -> str:
|
|
153
|
+
"""Add a Codex invocation guidance header after the frontmatter.
|
|
154
|
+
|
|
155
|
+
Inserts a brief note about how to invoke this skill in Codex.
|
|
156
|
+
"""
|
|
157
|
+
# Split on the closing --- of frontmatter
|
|
158
|
+
parts = content.split("---\n", 2)
|
|
159
|
+
if len(parts) < 3:
|
|
160
|
+
return content
|
|
161
|
+
|
|
162
|
+
header = (
|
|
163
|
+
f"> **Codex Skill:** `{name}`\n"
|
|
164
|
+
f"> Invoke by asking about {name} or referencing it directly.\n\n"
|
|
165
|
+
)
|
|
166
|
+
return f"---\n{parts[1]}---\n{header}{parts[2]}"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _update_agents_md(agents_md_path: Path, bundle: PluginBundle) -> list[Path]:
|
|
170
|
+
"""Generate or update AGENTS.md with Agent Brain section.
|
|
171
|
+
|
|
172
|
+
Uses HTML comment markers for idempotent updates — running this
|
|
173
|
+
multiple times will replace the existing section rather than
|
|
174
|
+
duplicating it.
|
|
175
|
+
|
|
176
|
+
Returns list of created/updated paths.
|
|
177
|
+
"""
|
|
178
|
+
# Build skill table
|
|
179
|
+
skill_rows: list[str] = []
|
|
180
|
+
for cmd in bundle.commands:
|
|
181
|
+
skill_rows.append(f"| {cmd.name} | {cmd.description} |")
|
|
182
|
+
for agent in bundle.agents:
|
|
183
|
+
skill_rows.append(f"| {agent.name} | {agent.description} |")
|
|
184
|
+
for skill in bundle.skills:
|
|
185
|
+
skill_rows.append(f"| {skill.name} | {skill.description} |")
|
|
186
|
+
|
|
187
|
+
skill_table = "\n".join(skill_rows) if skill_rows else "| (none) | - |"
|
|
188
|
+
|
|
189
|
+
section = AGENTS_MD_SECTION.format(
|
|
190
|
+
start_marker=AGENTS_MD_START,
|
|
191
|
+
end_marker=AGENTS_MD_END,
|
|
192
|
+
skill_table=skill_table,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if agents_md_path.exists():
|
|
196
|
+
existing = agents_md_path.read_text(encoding="utf-8")
|
|
197
|
+
if AGENTS_MD_START in existing and AGENTS_MD_END in existing:
|
|
198
|
+
# Replace existing section
|
|
199
|
+
start_idx = existing.index(AGENTS_MD_START)
|
|
200
|
+
end_idx = existing.index(AGENTS_MD_END) + len(AGENTS_MD_END)
|
|
201
|
+
updated = existing[:start_idx] + section + existing[end_idx:]
|
|
202
|
+
agents_md_path.write_text(updated, encoding="utf-8")
|
|
203
|
+
else:
|
|
204
|
+
# Append section
|
|
205
|
+
agents_md_path.write_text(
|
|
206
|
+
existing.rstrip() + "\n\n" + section + "\n",
|
|
207
|
+
encoding="utf-8",
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
# Create new file
|
|
211
|
+
content = f"# AGENTS.md\n\n{section}\n"
|
|
212
|
+
agents_md_path.parent.mkdir(parents=True, exist_ok=True)
|
|
213
|
+
agents_md_path.write_text(content, encoding="utf-8")
|
|
214
|
+
|
|
215
|
+
return [agents_md_path]
|
|
@@ -13,7 +13,9 @@ from agent_brain_cli.runtime.types import (
|
|
|
13
13
|
PluginCommand,
|
|
14
14
|
PluginManifest,
|
|
15
15
|
PluginParameter,
|
|
16
|
+
PluginScript,
|
|
16
17
|
PluginSkill,
|
|
18
|
+
PluginTemplate,
|
|
17
19
|
TriggerPattern,
|
|
18
20
|
)
|
|
19
21
|
|
|
@@ -195,6 +197,62 @@ def parse_manifest(path: Path) -> PluginManifest:
|
|
|
195
197
|
)
|
|
196
198
|
|
|
197
199
|
|
|
200
|
+
def parse_templates(templates_dir: Path) -> list[PluginTemplate]:
|
|
201
|
+
"""Parse template files from the templates/ directory.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
templates_dir: Path to the templates directory.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
List of parsed PluginTemplate objects.
|
|
208
|
+
"""
|
|
209
|
+
templates: list[PluginTemplate] = []
|
|
210
|
+
if not templates_dir.is_dir():
|
|
211
|
+
return templates
|
|
212
|
+
for tpl_file in sorted(templates_dir.iterdir()):
|
|
213
|
+
if tpl_file.is_file():
|
|
214
|
+
try:
|
|
215
|
+
content = tpl_file.read_text(encoding="utf-8")
|
|
216
|
+
templates.append(
|
|
217
|
+
PluginTemplate(
|
|
218
|
+
name=tpl_file.name,
|
|
219
|
+
content=content,
|
|
220
|
+
source_path=str(tpl_file),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
except OSError as exc:
|
|
224
|
+
logger.warning("Failed to read template %s: %s", tpl_file, exc)
|
|
225
|
+
return templates
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def parse_scripts(scripts_dir: Path) -> list[PluginScript]:
|
|
229
|
+
"""Parse script files from the scripts/ directory.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
scripts_dir: Path to the scripts directory.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
List of parsed PluginScript objects.
|
|
236
|
+
"""
|
|
237
|
+
scripts: list[PluginScript] = []
|
|
238
|
+
if not scripts_dir.is_dir():
|
|
239
|
+
return scripts
|
|
240
|
+
for script_file in sorted(scripts_dir.iterdir()):
|
|
241
|
+
if script_file.is_file():
|
|
242
|
+
try:
|
|
243
|
+
content = script_file.read_text(encoding="utf-8")
|
|
244
|
+
scripts.append(
|
|
245
|
+
PluginScript(
|
|
246
|
+
name=script_file.name,
|
|
247
|
+
content=content,
|
|
248
|
+
source_path=str(script_file),
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
except OSError as exc:
|
|
252
|
+
logger.warning("Failed to read script %s: %s", script_file, exc)
|
|
253
|
+
return scripts
|
|
254
|
+
|
|
255
|
+
|
|
198
256
|
def parse_plugin_dir(plugin_dir: Path) -> PluginBundle:
|
|
199
257
|
"""Parse an entire plugin directory into a PluginBundle.
|
|
200
258
|
|
|
@@ -255,10 +313,18 @@ def parse_plugin_dir(plugin_dir: Path) -> PluginBundle:
|
|
|
255
313
|
except (ValueError, OSError) as exc:
|
|
256
314
|
logger.warning("Failed to parse skill %s: %s", skill_file, exc)
|
|
257
315
|
|
|
316
|
+
# Parse templates
|
|
317
|
+
templates = parse_templates(plugin_dir / "templates")
|
|
318
|
+
|
|
319
|
+
# Parse scripts
|
|
320
|
+
scripts = parse_scripts(plugin_dir / "scripts")
|
|
321
|
+
|
|
258
322
|
return PluginBundle(
|
|
259
323
|
commands=commands,
|
|
260
324
|
agents=agents,
|
|
261
325
|
skills=skills,
|
|
326
|
+
templates=templates,
|
|
327
|
+
scripts=scripts,
|
|
262
328
|
manifest=manifest,
|
|
263
329
|
source_dir=str(plugin_dir),
|
|
264
330
|
)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Generic skill-runtime converter.
|
|
2
|
+
|
|
3
|
+
Transforms all plugin artifacts (commands, agents, skills, templates, scripts)
|
|
4
|
+
into flat skill directories. Each artifact becomes a directory with a SKILL.md
|
|
5
|
+
file. This converter supports any runtime that uses skill directories (Codex,
|
|
6
|
+
Qwen, Cursor, etc.) via the --dir option.
|
|
7
|
+
|
|
8
|
+
Transformation rules:
|
|
9
|
+
command agent-brain-init.md → <dir>/agent-brain-init/SKILL.md
|
|
10
|
+
agent research-assistant.md → <dir>/agent-brain-research/SKILL.md
|
|
11
|
+
skill using-agent-brain/ → <dir>/agent-brain-using/SKILL.md + references/
|
|
12
|
+
templates/* → <dir>/agent-brain-setup/assets/
|
|
13
|
+
scripts/* → <dir>/agent-brain-verify/scripts/
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import shutil
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
from agent_brain_cli.runtime.types import (
|
|
23
|
+
PluginAgent,
|
|
24
|
+
PluginBundle,
|
|
25
|
+
PluginCommand,
|
|
26
|
+
PluginSkill,
|
|
27
|
+
RuntimeType,
|
|
28
|
+
Scope,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
LEGACY_PATH = ".claude/agent-brain"
|
|
34
|
+
NEW_PATH = ".agent-brain"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _replace_paths(text: str) -> str:
|
|
38
|
+
"""Replace legacy state dir paths with new runtime-neutral paths."""
|
|
39
|
+
return text.replace(LEGACY_PATH, NEW_PATH)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _build_skill_md(frontmatter: dict, body: str) -> str: # type: ignore[type-arg]
|
|
43
|
+
"""Build a SKILL.md file from frontmatter and body."""
|
|
44
|
+
yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)
|
|
45
|
+
return f"---\n{yaml_str}---\n{body}\n"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _skill_dir_name(name: str, prefix: str = "agent-brain-") -> str:
|
|
49
|
+
"""Derive a skill directory name from an artifact name.
|
|
50
|
+
|
|
51
|
+
Ensures the name starts with the agent-brain- prefix for namespacing.
|
|
52
|
+
"""
|
|
53
|
+
if name.startswith(prefix):
|
|
54
|
+
return name
|
|
55
|
+
return f"{prefix}{name}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SkillRuntimeConverter:
|
|
59
|
+
"""Converter that flattens all plugin artifacts into skill directories.
|
|
60
|
+
|
|
61
|
+
Commands become skills with their body as instructions.
|
|
62
|
+
Agents become orchestration skills referencing dependent skills.
|
|
63
|
+
Existing skills are copied with references intact.
|
|
64
|
+
Templates and scripts become asset skill directories.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def runtime_type(self) -> RuntimeType:
|
|
69
|
+
return RuntimeType.SKILL_RUNTIME
|
|
70
|
+
|
|
71
|
+
def convert_command(self, command: PluginCommand) -> str:
|
|
72
|
+
"""Convert a command into a SKILL.md file.
|
|
73
|
+
|
|
74
|
+
The command body becomes the skill instructions, with
|
|
75
|
+
allowed-tools set to common tools needed for CLI commands.
|
|
76
|
+
"""
|
|
77
|
+
fm: dict[str, object] = {
|
|
78
|
+
"name": command.name,
|
|
79
|
+
"description": command.description,
|
|
80
|
+
"allowed-tools": ["Bash", "Read", "Write"],
|
|
81
|
+
}
|
|
82
|
+
if command.parameters:
|
|
83
|
+
params_text = "\n\n## Parameters\n\n"
|
|
84
|
+
for p in command.parameters:
|
|
85
|
+
req = " (required)" if p.required else ""
|
|
86
|
+
default = f" [default: {p.default}]" if p.default else ""
|
|
87
|
+
params_text += f"- **{p.name}**: {p.description}{req}{default}\n"
|
|
88
|
+
body = _replace_paths(command.body) + params_text
|
|
89
|
+
else:
|
|
90
|
+
body = _replace_paths(command.body)
|
|
91
|
+
return _build_skill_md(fm, body)
|
|
92
|
+
|
|
93
|
+
def convert_agent(self, agent: PluginAgent) -> str:
|
|
94
|
+
"""Convert an agent into an orchestration SKILL.md file.
|
|
95
|
+
|
|
96
|
+
The agent body becomes skill instructions with a note that
|
|
97
|
+
this is an orchestration skill.
|
|
98
|
+
"""
|
|
99
|
+
fm: dict[str, object] = {
|
|
100
|
+
"name": agent.name,
|
|
101
|
+
"description": agent.description,
|
|
102
|
+
"allowed-tools": ["Bash", "Read", "Write", "Grep", "Glob"],
|
|
103
|
+
}
|
|
104
|
+
header = "<!-- Orchestration skill converted from agent -->\n\n"
|
|
105
|
+
if agent.skills:
|
|
106
|
+
header += "## Related Skills\n\n"
|
|
107
|
+
for skill_name in agent.skills:
|
|
108
|
+
header += f"- {skill_name}\n"
|
|
109
|
+
header += "\n"
|
|
110
|
+
body = header + _replace_paths(agent.body)
|
|
111
|
+
return _build_skill_md(fm, body)
|
|
112
|
+
|
|
113
|
+
def convert_skill(self, skill: PluginSkill) -> str:
|
|
114
|
+
"""Convert a skill, preserving its existing format."""
|
|
115
|
+
fm: dict[str, object] = {
|
|
116
|
+
"name": skill.name,
|
|
117
|
+
"description": skill.description,
|
|
118
|
+
"allowed-tools": skill.allowed_tools,
|
|
119
|
+
}
|
|
120
|
+
if skill.license:
|
|
121
|
+
fm["license"] = skill.license
|
|
122
|
+
if skill.metadata:
|
|
123
|
+
fm["metadata"] = skill.metadata
|
|
124
|
+
return _build_skill_md(fm, _replace_paths(skill.body))
|
|
125
|
+
|
|
126
|
+
def install(
|
|
127
|
+
self,
|
|
128
|
+
bundle: PluginBundle,
|
|
129
|
+
target_dir: Path,
|
|
130
|
+
scope: Scope,
|
|
131
|
+
) -> list[Path]:
|
|
132
|
+
"""Install all plugin artifacts as flat skill directories.
|
|
133
|
+
|
|
134
|
+
Each artifact gets its own directory under target_dir with a
|
|
135
|
+
SKILL.md file. References, templates, and scripts are included
|
|
136
|
+
as assets.
|
|
137
|
+
"""
|
|
138
|
+
created: list[Path] = []
|
|
139
|
+
|
|
140
|
+
# Commands → skill directories
|
|
141
|
+
for cmd in bundle.commands:
|
|
142
|
+
skill_name = _skill_dir_name(cmd.name)
|
|
143
|
+
skill_dir = target_dir / skill_name
|
|
144
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
skill_file = skill_dir / "SKILL.md"
|
|
146
|
+
skill_file.write_text(self.convert_command(cmd), encoding="utf-8")
|
|
147
|
+
created.append(skill_file)
|
|
148
|
+
|
|
149
|
+
# Agents → orchestration skill directories
|
|
150
|
+
for agent in bundle.agents:
|
|
151
|
+
skill_name = _skill_dir_name(agent.name)
|
|
152
|
+
skill_dir = target_dir / skill_name
|
|
153
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
154
|
+
skill_file = skill_dir / "SKILL.md"
|
|
155
|
+
skill_file.write_text(self.convert_agent(agent), encoding="utf-8")
|
|
156
|
+
created.append(skill_file)
|
|
157
|
+
|
|
158
|
+
# Skills → skill directories (with references)
|
|
159
|
+
for skill in bundle.skills:
|
|
160
|
+
skill_name = _skill_dir_name(skill.name)
|
|
161
|
+
skill_dir = target_dir / skill_name
|
|
162
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
163
|
+
skill_file = skill_dir / "SKILL.md"
|
|
164
|
+
skill_file.write_text(self.convert_skill(skill), encoding="utf-8")
|
|
165
|
+
created.append(skill_file)
|
|
166
|
+
|
|
167
|
+
# Copy references if they exist
|
|
168
|
+
if skill.source_path:
|
|
169
|
+
refs_src = Path(skill.source_path).parent / "references"
|
|
170
|
+
if refs_src.is_dir():
|
|
171
|
+
refs_dest = skill_dir / "references"
|
|
172
|
+
if refs_dest.exists():
|
|
173
|
+
shutil.rmtree(refs_dest)
|
|
174
|
+
shutil.copytree(refs_src, refs_dest)
|
|
175
|
+
for ref in refs_dest.rglob("*"):
|
|
176
|
+
if ref.is_file():
|
|
177
|
+
created.append(ref)
|
|
178
|
+
|
|
179
|
+
# Templates → agent-brain-setup/assets/
|
|
180
|
+
if bundle.templates:
|
|
181
|
+
setup_dir = target_dir / "agent-brain-setup"
|
|
182
|
+
assets_dir = setup_dir / "assets"
|
|
183
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
|
184
|
+
# Create a setup SKILL.md if it doesn't exist
|
|
185
|
+
setup_skill = setup_dir / "SKILL.md"
|
|
186
|
+
if not setup_skill.exists():
|
|
187
|
+
fm: dict[str, object] = {
|
|
188
|
+
"name": "agent-brain-setup",
|
|
189
|
+
"description": "Agent Brain setup templates and configuration",
|
|
190
|
+
"allowed-tools": ["Bash", "Read", "Write"],
|
|
191
|
+
}
|
|
192
|
+
body = (
|
|
193
|
+
"This skill contains setup templates for Agent Brain.\n\n"
|
|
194
|
+
"## Assets\n\n"
|
|
195
|
+
)
|
|
196
|
+
for tpl in bundle.templates:
|
|
197
|
+
body += f"- `assets/{tpl.name}`\n"
|
|
198
|
+
setup_skill.write_text(_build_skill_md(fm, body), encoding="utf-8")
|
|
199
|
+
created.append(setup_skill)
|
|
200
|
+
|
|
201
|
+
for tpl in bundle.templates:
|
|
202
|
+
tpl_file = assets_dir / tpl.name
|
|
203
|
+
tpl_file.write_text(tpl.content, encoding="utf-8")
|
|
204
|
+
created.append(tpl_file)
|
|
205
|
+
|
|
206
|
+
# Scripts → agent-brain-verify/scripts/
|
|
207
|
+
if bundle.scripts:
|
|
208
|
+
verify_dir = target_dir / "agent-brain-verify"
|
|
209
|
+
scripts_dir = verify_dir / "scripts"
|
|
210
|
+
scripts_dir.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
# Create a verify SKILL.md if it doesn't exist
|
|
212
|
+
verify_skill = verify_dir / "SKILL.md"
|
|
213
|
+
if not verify_skill.exists():
|
|
214
|
+
fm_v: dict[str, object] = {
|
|
215
|
+
"name": "agent-brain-verify",
|
|
216
|
+
"description": "Agent Brain verification and health check scripts",
|
|
217
|
+
"allowed-tools": ["Bash", "Read"],
|
|
218
|
+
}
|
|
219
|
+
body_v = (
|
|
220
|
+
"This skill contains verification scripts for "
|
|
221
|
+
"Agent Brain.\n\n"
|
|
222
|
+
"## Scripts\n\n"
|
|
223
|
+
)
|
|
224
|
+
for script in bundle.scripts:
|
|
225
|
+
body_v += f"- `scripts/{script.name}`\n"
|
|
226
|
+
verify_skill.write_text(_build_skill_md(fm_v, body_v), encoding="utf-8")
|
|
227
|
+
created.append(verify_skill)
|
|
228
|
+
|
|
229
|
+
for script in bundle.scripts:
|
|
230
|
+
script_file = scripts_dir / script.name
|
|
231
|
+
script_file.write_text(script.content, encoding="utf-8")
|
|
232
|
+
created.append(script_file)
|
|
233
|
+
|
|
234
|
+
return created
|
|
@@ -10,6 +10,8 @@ class RuntimeType(str, Enum):
|
|
|
10
10
|
CLAUDE = "claude"
|
|
11
11
|
OPENCODE = "opencode"
|
|
12
12
|
GEMINI = "gemini"
|
|
13
|
+
SKILL_RUNTIME = "skill-runtime"
|
|
14
|
+
CODEX = "codex"
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class Scope(str, Enum):
|
|
@@ -75,6 +77,24 @@ class PluginSkill:
|
|
|
75
77
|
references: list[str] = field(default_factory=list)
|
|
76
78
|
|
|
77
79
|
|
|
80
|
+
@dataclass
|
|
81
|
+
class PluginTemplate:
|
|
82
|
+
"""A template file from the plugin templates/ directory."""
|
|
83
|
+
|
|
84
|
+
name: str
|
|
85
|
+
content: str
|
|
86
|
+
source_path: str = ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class PluginScript:
|
|
91
|
+
"""A script file from the plugin scripts/ directory."""
|
|
92
|
+
|
|
93
|
+
name: str
|
|
94
|
+
content: str
|
|
95
|
+
source_path: str = ""
|
|
96
|
+
|
|
97
|
+
|
|
78
98
|
@dataclass
|
|
79
99
|
class PluginManifest:
|
|
80
100
|
"""Parsed plugin.json manifest."""
|
|
@@ -96,5 +116,7 @@ class PluginBundle:
|
|
|
96
116
|
commands: list[PluginCommand] = field(default_factory=list)
|
|
97
117
|
agents: list[PluginAgent] = field(default_factory=list)
|
|
98
118
|
skills: list[PluginSkill] = field(default_factory=list)
|
|
119
|
+
templates: list[PluginTemplate] = field(default_factory=list)
|
|
120
|
+
scripts: list[PluginScript] = field(default_factory=list)
|
|
99
121
|
manifest: PluginManifest = field(default_factory=PluginManifest)
|
|
100
122
|
source_dir: str = ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "agent-brain-cli"
|
|
3
|
-
version = "9.
|
|
3
|
+
version = "9.2.0"
|
|
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 = "^9.
|
|
30
|
+
agent-brain-rag = "^9.2.0"
|
|
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
|
{agent_brain_cli-9.0.0 → agent_brain_cli-9.2.0}/agent_brain_cli/runtime/opencode_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|