agent-brain-cli 10.4.0__tar.gz → 10.5.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-10.4.0 → agent_brain_cli-10.5.0}/PKG-INFO +2 -1
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/__init__.py +1 -1
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/install_agent.py +158 -32
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/__init__.py +0 -2
- agent_brain_cli-10.5.0/agent_brain_cli/runtime/mcp_registration.py +316 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/tool_maps.py +1 -16
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/types.py +0 -1
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/pyproject.toml +2 -1
- agent_brain_cli-10.4.0/agent_brain_cli/runtime/gemini_converter.py +0 -119
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/README.md +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/__main__.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/cli.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/client/__init__.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/client/api_client.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/client/protocol.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/client/transport.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/__init__.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/cache.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/config.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/doctor.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/folders.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/graph.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/index.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/init.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/inject.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/jobs.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/list_cmd.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/mcp.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/prompt.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/query.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/reset.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/resources.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/start.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/status.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/stop.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/types.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/commands/uninstall.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/config.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/config_migrate.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/config_schema.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/diagnostics.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/mcp_runtime.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/migration.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/claude_converter.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/codex_converter.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/converter_base.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/opencode_converter.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/parser.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/skill_runtime_converter.py +0 -0
- {agent_brain_cli-10.4.0 → agent_brain_cli-10.5.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: 10.
|
|
3
|
+
Version: 10.5.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
|
|
@@ -23,6 +23,7 @@ Requires-Dist: psutil (>=6.0,<7.0)
|
|
|
23
23
|
Requires-Dist: pydantic (>=2.10.0,<3.0.0)
|
|
24
24
|
Requires-Dist: pyyaml (>=6.0.0,<7.0.0)
|
|
25
25
|
Requires-Dist: rich (>=13.9.0,<14.0.0)
|
|
26
|
+
Requires-Dist: tomlkit (>=0.13.0,<0.14.0)
|
|
26
27
|
Project-URL: Documentation, https://github.com/SpillwaveSolutions/agent-brain/wiki
|
|
27
28
|
Project-URL: Repository, https://github.com/SpillwaveSolutions/agent-brain
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Install-agent command for installing runtime-specific plugin files."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Callable
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
@@ -10,7 +12,12 @@ from rich.panel import Panel
|
|
|
10
12
|
|
|
11
13
|
from agent_brain_cli.runtime.claude_converter import ClaudeConverter
|
|
12
14
|
from agent_brain_cli.runtime.codex_converter import CodexConverter
|
|
13
|
-
from agent_brain_cli.runtime.
|
|
15
|
+
from agent_brain_cli.runtime.mcp_registration import (
|
|
16
|
+
McpRegistrationResult,
|
|
17
|
+
register_claude_mcp,
|
|
18
|
+
register_codex_mcp,
|
|
19
|
+
register_opencode_mcp,
|
|
20
|
+
)
|
|
14
21
|
from agent_brain_cli.runtime.opencode_converter import OpenCodeConverter
|
|
15
22
|
from agent_brain_cli.runtime.parser import parse_plugin_dir
|
|
16
23
|
from agent_brain_cli.runtime.skill_runtime_converter import SkillRuntimeConverter
|
|
@@ -28,10 +35,6 @@ INSTALL_DIRS: dict[str, dict[str, str]] = {
|
|
|
28
35
|
"project": ".opencode/plugins/agent-brain",
|
|
29
36
|
"global": "~/.config/opencode/plugins/agent-brain",
|
|
30
37
|
},
|
|
31
|
-
"gemini": {
|
|
32
|
-
"project": ".gemini/plugins/agent-brain",
|
|
33
|
-
"global": "~/.config/gemini/plugins/agent-brain",
|
|
34
|
-
},
|
|
35
38
|
"codex": {
|
|
36
39
|
"project": ".codex/skills/agent-brain",
|
|
37
40
|
"global": "~/.codex/skills/agent-brain",
|
|
@@ -42,17 +45,12 @@ INSTALL_DIRS: dict[str, dict[str, str]] = {
|
|
|
42
45
|
DIR_REQUIRED_RUNTIMES = {"skill-runtime"}
|
|
43
46
|
|
|
44
47
|
ConverterType = type[
|
|
45
|
-
ClaudeConverter
|
|
46
|
-
| OpenCodeConverter
|
|
47
|
-
| GeminiConverter
|
|
48
|
-
| SkillRuntimeConverter
|
|
49
|
-
| CodexConverter
|
|
48
|
+
ClaudeConverter | OpenCodeConverter | SkillRuntimeConverter | CodexConverter
|
|
50
49
|
]
|
|
51
50
|
|
|
52
51
|
CONVERTERS: dict[str, ConverterType] = {
|
|
53
52
|
"claude": ClaudeConverter,
|
|
54
53
|
"opencode": OpenCodeConverter,
|
|
55
|
-
"gemini": GeminiConverter,
|
|
56
54
|
"skill-runtime": SkillRuntimeConverter,
|
|
57
55
|
"codex": CodexConverter,
|
|
58
56
|
}
|
|
@@ -94,7 +92,89 @@ def _resolve_target_dir(
|
|
|
94
92
|
return project_root / dir_template
|
|
95
93
|
|
|
96
94
|
|
|
97
|
-
RUNTIME_CHOICES = ["claude", "opencode", "
|
|
95
|
+
RUNTIME_CHOICES = ["claude", "opencode", "skill-runtime", "codex"]
|
|
96
|
+
|
|
97
|
+
# Runtimes for which we can auto-register the MCP server today, mapped to the
|
|
98
|
+
# writer that knows that runtime's config schema. All writers share the
|
|
99
|
+
# ``(config_path, state_dir, *, backend, auth, dry_run)`` signature.
|
|
100
|
+
MCP_REGISTRARS: dict[str, Callable[..., McpRegistrationResult]] = {
|
|
101
|
+
"claude": register_claude_mcp,
|
|
102
|
+
"opencode": register_opencode_mcp,
|
|
103
|
+
"codex": register_codex_mcp,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _codex_config_path() -> Path:
|
|
108
|
+
"""Return Codex's MCP config file, honoring ``$CODEX_HOME``."""
|
|
109
|
+
codex_home = os.environ.get("CODEX_HOME")
|
|
110
|
+
base = Path(codex_home) if codex_home else Path.home() / ".codex"
|
|
111
|
+
return base / "config.toml"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _resolve_mcp_paths(
|
|
115
|
+
agent: str, scope: str, project_root: Path | None
|
|
116
|
+
) -> tuple[Path, Path]:
|
|
117
|
+
"""Return (config_path, state_dir) for MCP registration.
|
|
118
|
+
|
|
119
|
+
The config file follows each runtime's own discovery rules:
|
|
120
|
+
|
|
121
|
+
* **claude** — project ``.mcp.json`` at the root, global ``~/.claude.json``.
|
|
122
|
+
* **opencode** — project ``opencode.json`` at the root (highest-precedence
|
|
123
|
+
project config), global ``~/.config/opencode/opencode.json``.
|
|
124
|
+
* **codex** — always ``$CODEX_HOME/config.toml`` (default
|
|
125
|
+
``~/.codex/config.toml``); Codex has no project-level MCP config, so the
|
|
126
|
+
file is shared and the project's ``.agent-brain`` is pinned via the entry.
|
|
127
|
+
"""
|
|
128
|
+
root = project_root if project_root is not None else Path.cwd()
|
|
129
|
+
if agent == "opencode":
|
|
130
|
+
if scope == "global":
|
|
131
|
+
config = Path.home() / ".config" / "opencode" / "opencode.json"
|
|
132
|
+
return config, Path.home() / ".agent-brain"
|
|
133
|
+
return root / "opencode.json", root / ".agent-brain"
|
|
134
|
+
if agent == "codex":
|
|
135
|
+
state_dir = (
|
|
136
|
+
Path.home() / ".agent-brain" if scope == "global" else root / ".agent-brain"
|
|
137
|
+
)
|
|
138
|
+
return _codex_config_path(), state_dir
|
|
139
|
+
# claude (and any future mcpServers-style runtime)
|
|
140
|
+
if scope == "global":
|
|
141
|
+
return Path.home() / ".claude.json", Path.home() / ".agent-brain"
|
|
142
|
+
return root / ".mcp.json", root / ".agent-brain"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _register_mcp(
|
|
146
|
+
agent: str,
|
|
147
|
+
scope: str,
|
|
148
|
+
project_root: Path | None,
|
|
149
|
+
mcp_auth: str,
|
|
150
|
+
mcp_backend: str,
|
|
151
|
+
dry_run: bool,
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
"""Register the MCP server for supported runtimes; report what happened."""
|
|
154
|
+
registrar = MCP_REGISTRARS.get(agent)
|
|
155
|
+
if registrar is None:
|
|
156
|
+
return {
|
|
157
|
+
"skipped": True,
|
|
158
|
+
"reason": (
|
|
159
|
+
f"MCP auto-registration is currently supported only for "
|
|
160
|
+
f"{', '.join(sorted(MCP_REGISTRARS))}; configure "
|
|
161
|
+
f"{agent} manually (see the configuring-agent-brain skill)."
|
|
162
|
+
),
|
|
163
|
+
}
|
|
164
|
+
config_path, state_dir = _resolve_mcp_paths(agent, scope, project_root)
|
|
165
|
+
result = registrar(
|
|
166
|
+
config_path,
|
|
167
|
+
state_dir,
|
|
168
|
+
backend=mcp_backend,
|
|
169
|
+
auth=mcp_auth,
|
|
170
|
+
dry_run=dry_run,
|
|
171
|
+
)
|
|
172
|
+
return {
|
|
173
|
+
"skipped": False,
|
|
174
|
+
"action": result.action,
|
|
175
|
+
"path": str(result.path),
|
|
176
|
+
"server_name": result.server_name,
|
|
177
|
+
}
|
|
98
178
|
|
|
99
179
|
|
|
100
180
|
@click.command("install-agent")
|
|
@@ -146,6 +226,23 @@ RUNTIME_CHOICES = ["claude", "opencode", "gemini", "skill-runtime", "codex"]
|
|
|
146
226
|
type=click.Path(exists=True, file_okay=False, resolve_path=True),
|
|
147
227
|
help="Project path for --project scope (default: cwd)",
|
|
148
228
|
)
|
|
229
|
+
@click.option(
|
|
230
|
+
"--with-mcp",
|
|
231
|
+
is_flag=True,
|
|
232
|
+
help="Also register the agent-brain MCP server (Claude Code, OpenCode, Codex)",
|
|
233
|
+
)
|
|
234
|
+
@click.option(
|
|
235
|
+
"--mcp-auth",
|
|
236
|
+
type=click.Choice(["none", "oauth"]),
|
|
237
|
+
default="none",
|
|
238
|
+
help="MCP client auth mode written into the registration (default: none)",
|
|
239
|
+
)
|
|
240
|
+
@click.option(
|
|
241
|
+
"--mcp-backend",
|
|
242
|
+
type=click.Choice(["auto", "uds", "http"]),
|
|
243
|
+
default="auto",
|
|
244
|
+
help="How the MCP server reaches agent-brain-serve (default: auto)",
|
|
245
|
+
)
|
|
149
246
|
def install_agent_command(
|
|
150
247
|
agent: str,
|
|
151
248
|
scope: str,
|
|
@@ -154,6 +251,9 @@ def install_agent_command(
|
|
|
154
251
|
dry_run: bool,
|
|
155
252
|
json_output: bool,
|
|
156
253
|
path: str | None,
|
|
254
|
+
with_mcp: bool,
|
|
255
|
+
mcp_auth: str,
|
|
256
|
+
mcp_backend: str,
|
|
157
257
|
) -> None:
|
|
158
258
|
"""Install Agent Brain plugin for a specific runtime.
|
|
159
259
|
|
|
@@ -164,7 +264,7 @@ def install_agent_command(
|
|
|
164
264
|
Examples:
|
|
165
265
|
agent-brain install-agent --agent claude --project
|
|
166
266
|
agent-brain install-agent --agent opencode --global
|
|
167
|
-
agent-brain install-agent --agent
|
|
267
|
+
agent-brain install-agent --agent claude --dry-run
|
|
168
268
|
agent-brain install-agent --agent skill-runtime --dir ./my-skills
|
|
169
269
|
agent-brain install-agent --agent codex
|
|
170
270
|
"""
|
|
@@ -220,6 +320,12 @@ def install_agent_command(
|
|
|
220
320
|
converter = converter_cls()
|
|
221
321
|
scope_enum = Scope.GLOBAL if scope == "global" else Scope.PROJECT
|
|
222
322
|
|
|
323
|
+
mcp_summary: dict[str, Any] | None = None
|
|
324
|
+
if with_mcp:
|
|
325
|
+
mcp_summary = _register_mcp(
|
|
326
|
+
agent, scope, project_root, mcp_auth, mcp_backend, dry_run
|
|
327
|
+
)
|
|
328
|
+
|
|
223
329
|
if dry_run:
|
|
224
330
|
_handle_dry_run(
|
|
225
331
|
converter,
|
|
@@ -229,6 +335,7 @@ def install_agent_command(
|
|
|
229
335
|
agent,
|
|
230
336
|
scope,
|
|
231
337
|
json_output,
|
|
338
|
+
mcp_summary,
|
|
232
339
|
)
|
|
233
340
|
return
|
|
234
341
|
|
|
@@ -250,6 +357,8 @@ def install_agent_command(
|
|
|
250
357
|
"files_created": len(files),
|
|
251
358
|
"source_dir": str(source),
|
|
252
359
|
}
|
|
360
|
+
if mcp_summary is not None:
|
|
361
|
+
result["mcp_registration"] = mcp_summary
|
|
253
362
|
click.echo(json.dumps(result, indent=2))
|
|
254
363
|
else:
|
|
255
364
|
console.print(
|
|
@@ -263,6 +372,7 @@ def install_agent_command(
|
|
|
263
372
|
border_style="green",
|
|
264
373
|
)
|
|
265
374
|
)
|
|
375
|
+
_print_mcp_summary(mcp_summary)
|
|
266
376
|
|
|
267
377
|
except SystemExit:
|
|
268
378
|
raise
|
|
@@ -276,11 +386,7 @@ def install_agent_command(
|
|
|
276
386
|
|
|
277
387
|
def _handle_dry_run(
|
|
278
388
|
converter: (
|
|
279
|
-
ClaudeConverter
|
|
280
|
-
| OpenCodeConverter
|
|
281
|
-
| GeminiConverter
|
|
282
|
-
| SkillRuntimeConverter
|
|
283
|
-
| CodexConverter
|
|
389
|
+
ClaudeConverter | OpenCodeConverter | SkillRuntimeConverter | CodexConverter
|
|
284
390
|
),
|
|
285
391
|
bundle: Any,
|
|
286
392
|
target: Path,
|
|
@@ -288,18 +394,26 @@ def _handle_dry_run(
|
|
|
288
394
|
agent: str,
|
|
289
395
|
scope: str,
|
|
290
396
|
json_output: bool,
|
|
397
|
+
mcp_summary: dict[str, Any] | None = None,
|
|
291
398
|
) -> None:
|
|
292
399
|
"""Handle dry-run mode: simulate install in temp dir."""
|
|
293
400
|
import tempfile
|
|
294
401
|
|
|
295
402
|
with tempfile.TemporaryDirectory() as tmp:
|
|
296
|
-
tmp_target = Path(tmp)
|
|
297
403
|
# For Codex, pass tmp as project_root so AGENTS.md lands in tmpdir
|
|
298
404
|
if isinstance(converter, CodexConverter):
|
|
405
|
+
tmp_target = Path(tmp)
|
|
299
406
|
files = converter.install(
|
|
300
407
|
bundle, tmp_target, scope_enum, project_root=Path(tmp)
|
|
301
408
|
)
|
|
302
409
|
else:
|
|
410
|
+
# Mirror the real target's full structure under tmp so converters
|
|
411
|
+
# that write *outside* target_dir stay inside the sandbox — e.g.
|
|
412
|
+
# OpenCode writes opencode.json at target_dir.parent.parent, which
|
|
413
|
+
# escapes to an unwritable ancestor (CI: "/") if the temp target is
|
|
414
|
+
# shallow. Rooting at tmp + the target's relative path keeps every
|
|
415
|
+
# parent the converter computes inside the throwaway dir.
|
|
416
|
+
tmp_target = Path(tmp) / target.relative_to(target.anchor)
|
|
303
417
|
files = converter.install(bundle, tmp_target, scope_enum)
|
|
304
418
|
# Remap paths to real target
|
|
305
419
|
planned: list[Path] = []
|
|
@@ -311,19 +425,17 @@ def _handle_dry_run(
|
|
|
311
425
|
planned.append(f)
|
|
312
426
|
|
|
313
427
|
if json_output:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
)
|
|
326
|
-
)
|
|
428
|
+
payload: dict[str, Any] = {
|
|
429
|
+
"dry_run": True,
|
|
430
|
+
"agent": agent,
|
|
431
|
+
"scope": scope,
|
|
432
|
+
"target_dir": str(target),
|
|
433
|
+
"files": [str(f) for f in planned],
|
|
434
|
+
"file_count": len(planned),
|
|
435
|
+
}
|
|
436
|
+
if mcp_summary is not None:
|
|
437
|
+
payload["mcp_registration"] = mcp_summary
|
|
438
|
+
click.echo(json.dumps(payload, indent=2))
|
|
327
439
|
else:
|
|
328
440
|
console.print(
|
|
329
441
|
Panel(
|
|
@@ -338,3 +450,17 @@ def _handle_dry_run(
|
|
|
338
450
|
)
|
|
339
451
|
for f in planned:
|
|
340
452
|
console.print(f" [dim]{f}[/]")
|
|
453
|
+
_print_mcp_summary(mcp_summary)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _print_mcp_summary(mcp_summary: dict[str, Any] | None) -> None:
|
|
457
|
+
"""Render the MCP registration outcome for human-readable output."""
|
|
458
|
+
if mcp_summary is None:
|
|
459
|
+
return
|
|
460
|
+
if mcp_summary.get("skipped"):
|
|
461
|
+
console.print(f"[yellow]MCP:[/] {mcp_summary['reason']}")
|
|
462
|
+
return
|
|
463
|
+
console.print(
|
|
464
|
+
f"[green]MCP server registered[/] ([bold]{mcp_summary['action']}[/]) "
|
|
465
|
+
f"→ {mcp_summary['path']}"
|
|
466
|
+
)
|
|
@@ -12,7 +12,6 @@ from agent_brain_cli.runtime.parser import (
|
|
|
12
12
|
)
|
|
13
13
|
from agent_brain_cli.runtime.tool_maps import (
|
|
14
14
|
CLAUDE_TOOLS,
|
|
15
|
-
GEMINI_TOOLS,
|
|
16
15
|
OPENCODE_TOOLS,
|
|
17
16
|
map_tool_name,
|
|
18
17
|
map_tools,
|
|
@@ -33,7 +32,6 @@ from agent_brain_cli.runtime.types import (
|
|
|
33
32
|
|
|
34
33
|
__all__ = [
|
|
35
34
|
"CLAUDE_TOOLS",
|
|
36
|
-
"GEMINI_TOOLS",
|
|
37
35
|
"OPENCODE_TOOLS",
|
|
38
36
|
"PluginAgent",
|
|
39
37
|
"PluginBundle",
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Register the Agent Brain MCP server into a runtime's config file.
|
|
2
|
+
|
|
3
|
+
Each runtime discovers MCP servers from its own JSON config:
|
|
4
|
+
|
|
5
|
+
* **Claude Code** — an ``mcpServers`` object in a project-level ``.mcp.json`` (or
|
|
6
|
+
the user-level ``~/.claude.json`` for global scope). Each entry is
|
|
7
|
+
``{command, args, env}``.
|
|
8
|
+
* **OpenCode** — an ``mcp`` object in the project-root ``opencode.json`` (or
|
|
9
|
+
``~/.config/opencode/opencode.json`` for global scope). Each entry is
|
|
10
|
+
``{type: "local", command: [...], enabled: true, environment: {...}}`` — the
|
|
11
|
+
executable and its args are fused into one ``command`` array.
|
|
12
|
+
* **Codex** — a ``[mcp_servers.<name>]`` TOML table in
|
|
13
|
+
``$CODEX_HOME/config.toml`` (default ``~/.codex/config.toml``). The entry
|
|
14
|
+
shape mirrors Claude's (``command`` string, ``args`` array, ``env`` table) but
|
|
15
|
+
the file is TOML, so it is merged with tomlkit to preserve the user's other
|
|
16
|
+
servers, top-level keys, and comments.
|
|
17
|
+
|
|
18
|
+
These writers deliberately do NOT shell out to a runtime CLI (``claude mcp add``
|
|
19
|
+
etc.): the file-based path is dependency-free, deterministic, dry-run friendly,
|
|
20
|
+
and matches the file-writing pattern the rest of ``install-agent`` already uses.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
import tomlkit
|
|
31
|
+
from tomlkit.exceptions import TOMLKitError
|
|
32
|
+
|
|
33
|
+
MCP_SERVER_NAME = "agent-brain"
|
|
34
|
+
MCP_COMMAND = "agent-brain-mcp"
|
|
35
|
+
OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _build_env(state_dir: Path, auth: str) -> dict[str, str]:
|
|
39
|
+
"""Build the environment block shared by every runtime's entry.
|
|
40
|
+
|
|
41
|
+
The state dir is resolved to an absolute path — MCP clients launch the
|
|
42
|
+
server from an unknown cwd, so a relative path would break discovery.
|
|
43
|
+
``oauth`` opts the client into the OAuth dance via ``AGENT_BRAIN_MCP_AUTH``.
|
|
44
|
+
"""
|
|
45
|
+
env: dict[str, str] = {
|
|
46
|
+
"AGENT_BRAIN_STATE_DIR": str(state_dir.expanduser().resolve())
|
|
47
|
+
}
|
|
48
|
+
if auth == "oauth":
|
|
49
|
+
env["AGENT_BRAIN_MCP_AUTH"] = "oauth"
|
|
50
|
+
return env
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_mcp_server_entry(
|
|
54
|
+
state_dir: Path,
|
|
55
|
+
backend: str = "auto",
|
|
56
|
+
auth: str = "none",
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
"""Build the Claude Code ``mcpServers`` entry for the Agent Brain MCP server.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
state_dir: The ``.agent-brain`` state directory the MCP server should use.
|
|
62
|
+
backend: How the MCP server reaches ``agent-brain-serve``
|
|
63
|
+
(``auto`` | ``uds`` | ``http``).
|
|
64
|
+
auth: ``none`` (default) or ``oauth``.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A dict suitable for ``mcpServers[<name>]``.
|
|
68
|
+
"""
|
|
69
|
+
return {
|
|
70
|
+
"command": MCP_COMMAND,
|
|
71
|
+
"args": ["--backend", backend],
|
|
72
|
+
"env": _build_env(state_dir, auth),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_opencode_mcp_entry(
|
|
77
|
+
state_dir: Path,
|
|
78
|
+
backend: str = "auto",
|
|
79
|
+
auth: str = "none",
|
|
80
|
+
) -> dict[str, Any]:
|
|
81
|
+
"""Build the OpenCode ``mcp`` entry for the Agent Brain MCP server.
|
|
82
|
+
|
|
83
|
+
OpenCode fuses the executable and its args into one ``command`` array, uses
|
|
84
|
+
``environment`` (not ``env``), and marks local servers with
|
|
85
|
+
``type: "local"`` and ``enabled: true``.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
state_dir: The ``.agent-brain`` state directory the MCP server should use.
|
|
89
|
+
backend: How the MCP server reaches ``agent-brain-serve``
|
|
90
|
+
(``auto`` | ``uds`` | ``http``).
|
|
91
|
+
auth: ``none`` (default) or ``oauth``.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A dict suitable for ``mcp[<name>]`` in ``opencode.json``.
|
|
95
|
+
"""
|
|
96
|
+
return {
|
|
97
|
+
"type": "local",
|
|
98
|
+
"command": [MCP_COMMAND, "--backend", backend],
|
|
99
|
+
"enabled": True,
|
|
100
|
+
"environment": _build_env(state_dir, auth),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class McpRegistrationResult:
|
|
106
|
+
"""Outcome of an MCP registration attempt."""
|
|
107
|
+
|
|
108
|
+
path: Path
|
|
109
|
+
action: str # "created" | "updated" | "unchanged"
|
|
110
|
+
server_name: str
|
|
111
|
+
entry: dict[str, Any]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _merge_json_mcp(
|
|
115
|
+
config_path: Path,
|
|
116
|
+
*,
|
|
117
|
+
servers_key: str,
|
|
118
|
+
entry: dict[str, Any],
|
|
119
|
+
top_level_defaults: dict[str, Any] | None = None,
|
|
120
|
+
dry_run: bool = False,
|
|
121
|
+
) -> McpRegistrationResult:
|
|
122
|
+
"""Merge an MCP entry into a JSON config under ``servers_key``.
|
|
123
|
+
|
|
124
|
+
Shared skeleton for every JSON-config runtime: read (fail closed on corrupt
|
|
125
|
+
JSON), compare the existing ``agent-brain`` entry, classify the action, and
|
|
126
|
+
write only when something changed. Other servers and top-level keys are
|
|
127
|
+
preserved.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
config_path: Path to the runtime's JSON config file.
|
|
131
|
+
servers_key: Top-level key holding the server map
|
|
132
|
+
(``mcpServers`` for Claude, ``mcp`` for OpenCode).
|
|
133
|
+
entry: The fully-built entry to store under ``[servers_key][name]``.
|
|
134
|
+
top_level_defaults: Keys applied via ``setdefault`` when writing
|
|
135
|
+
(e.g. OpenCode's ``$schema``); only touched on create/update.
|
|
136
|
+
dry_run: When True, compute the action but write nothing.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
An :class:`McpRegistrationResult` describing what (would have) changed.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: If an existing config file is not valid JSON.
|
|
143
|
+
"""
|
|
144
|
+
data: dict[str, Any]
|
|
145
|
+
file_existed = config_path.exists()
|
|
146
|
+
if file_existed:
|
|
147
|
+
try:
|
|
148
|
+
data = json.loads(config_path.read_text())
|
|
149
|
+
except json.JSONDecodeError as exc:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"Existing {config_path.name} is not valid JSON: {exc}. "
|
|
152
|
+
"Fix or remove it, then re-run."
|
|
153
|
+
) from exc
|
|
154
|
+
else:
|
|
155
|
+
data = {}
|
|
156
|
+
|
|
157
|
+
servers = data.setdefault(servers_key, {})
|
|
158
|
+
existing = servers.get(MCP_SERVER_NAME)
|
|
159
|
+
|
|
160
|
+
if existing == entry:
|
|
161
|
+
action = "unchanged"
|
|
162
|
+
elif not file_existed:
|
|
163
|
+
action = "created"
|
|
164
|
+
else:
|
|
165
|
+
action = "updated"
|
|
166
|
+
|
|
167
|
+
if action != "unchanged" and not dry_run:
|
|
168
|
+
for key, value in (top_level_defaults or {}).items():
|
|
169
|
+
data.setdefault(key, value)
|
|
170
|
+
servers[MCP_SERVER_NAME] = entry
|
|
171
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
172
|
+
config_path.write_text(json.dumps(data, indent=2) + "\n")
|
|
173
|
+
|
|
174
|
+
return McpRegistrationResult(
|
|
175
|
+
path=config_path,
|
|
176
|
+
action=action,
|
|
177
|
+
server_name=MCP_SERVER_NAME,
|
|
178
|
+
entry=entry,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def register_claude_mcp(
|
|
183
|
+
config_path: Path,
|
|
184
|
+
state_dir: Path,
|
|
185
|
+
*,
|
|
186
|
+
backend: str = "auto",
|
|
187
|
+
auth: str = "none",
|
|
188
|
+
dry_run: bool = False,
|
|
189
|
+
) -> McpRegistrationResult:
|
|
190
|
+
"""Merge the Agent Brain MCP entry into a Claude Code config file.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
config_path: Path to ``.mcp.json`` (project) or ``~/.claude.json`` (global).
|
|
194
|
+
state_dir: The ``.agent-brain`` state directory for the server.
|
|
195
|
+
backend: MCP backend selector passed to the server.
|
|
196
|
+
auth: ``none`` or ``oauth``.
|
|
197
|
+
dry_run: When True, compute the action but write nothing.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
An :class:`McpRegistrationResult` describing what (would have) changed.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
ValueError: If an existing config file is not valid JSON.
|
|
204
|
+
"""
|
|
205
|
+
return _merge_json_mcp(
|
|
206
|
+
config_path,
|
|
207
|
+
servers_key="mcpServers",
|
|
208
|
+
entry=build_mcp_server_entry(state_dir, backend=backend, auth=auth),
|
|
209
|
+
dry_run=dry_run,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def register_opencode_mcp(
|
|
214
|
+
config_path: Path,
|
|
215
|
+
state_dir: Path,
|
|
216
|
+
*,
|
|
217
|
+
backend: str = "auto",
|
|
218
|
+
auth: str = "none",
|
|
219
|
+
dry_run: bool = False,
|
|
220
|
+
) -> McpRegistrationResult:
|
|
221
|
+
"""Merge the Agent Brain MCP entry into an OpenCode config file.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
config_path: Path to the project-root ``opencode.json`` (project) or
|
|
225
|
+
``~/.config/opencode/opencode.json`` (global).
|
|
226
|
+
state_dir: The ``.agent-brain`` state directory for the server.
|
|
227
|
+
backend: MCP backend selector passed to the server.
|
|
228
|
+
auth: ``none`` or ``oauth``.
|
|
229
|
+
dry_run: When True, compute the action but write nothing.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
An :class:`McpRegistrationResult` describing what (would have) changed.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
ValueError: If an existing config file is not valid JSON.
|
|
236
|
+
"""
|
|
237
|
+
return _merge_json_mcp(
|
|
238
|
+
config_path,
|
|
239
|
+
servers_key="mcp",
|
|
240
|
+
entry=build_opencode_mcp_entry(state_dir, backend=backend, auth=auth),
|
|
241
|
+
top_level_defaults={"$schema": OPENCODE_SCHEMA_URL},
|
|
242
|
+
dry_run=dry_run,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def register_codex_mcp(
|
|
247
|
+
config_path: Path,
|
|
248
|
+
state_dir: Path,
|
|
249
|
+
*,
|
|
250
|
+
backend: str = "auto",
|
|
251
|
+
auth: str = "none",
|
|
252
|
+
dry_run: bool = False,
|
|
253
|
+
) -> McpRegistrationResult:
|
|
254
|
+
"""Merge the Agent Brain MCP entry into a Codex ``config.toml`` file.
|
|
255
|
+
|
|
256
|
+
Codex stores MCP servers as ``[mcp_servers.<name>]`` TOML tables. The entry
|
|
257
|
+
fields match Claude's (``command``/``args``/``env``), so the value is built
|
|
258
|
+
with :func:`build_mcp_server_entry`; only the on-disk format differs. tomlkit
|
|
259
|
+
parses and re-emits the document so the user's other servers, top-level keys,
|
|
260
|
+
and comments are preserved.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
config_path: Path to ``$CODEX_HOME/config.toml`` (default
|
|
264
|
+
``~/.codex/config.toml``). Codex has no project-level MCP config, so
|
|
265
|
+
both install scopes target this user-level file.
|
|
266
|
+
state_dir: The ``.agent-brain`` state directory for the server.
|
|
267
|
+
backend: MCP backend selector passed to the server.
|
|
268
|
+
auth: ``none`` or ``oauth``.
|
|
269
|
+
dry_run: When True, compute the action but write nothing.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
An :class:`McpRegistrationResult` describing what (would have) changed.
|
|
273
|
+
|
|
274
|
+
Raises:
|
|
275
|
+
ValueError: If an existing config file is not valid TOML.
|
|
276
|
+
"""
|
|
277
|
+
entry = build_mcp_server_entry(state_dir, backend=backend, auth=auth)
|
|
278
|
+
|
|
279
|
+
file_existed = config_path.exists()
|
|
280
|
+
if file_existed:
|
|
281
|
+
try:
|
|
282
|
+
doc = tomlkit.parse(config_path.read_text())
|
|
283
|
+
except TOMLKitError as exc:
|
|
284
|
+
raise ValueError(
|
|
285
|
+
f"Existing {config_path.name} is not valid TOML: {exc}. "
|
|
286
|
+
"Fix or remove it, then re-run."
|
|
287
|
+
) from exc
|
|
288
|
+
else:
|
|
289
|
+
doc = tomlkit.document()
|
|
290
|
+
|
|
291
|
+
servers = doc.get("mcp_servers")
|
|
292
|
+
if servers is None:
|
|
293
|
+
servers = tomlkit.table()
|
|
294
|
+
doc["mcp_servers"] = servers
|
|
295
|
+
|
|
296
|
+
existing = servers.get(MCP_SERVER_NAME)
|
|
297
|
+
existing_plain = existing.unwrap() if existing is not None else None
|
|
298
|
+
|
|
299
|
+
if existing_plain == entry:
|
|
300
|
+
action = "unchanged"
|
|
301
|
+
elif not file_existed:
|
|
302
|
+
action = "created"
|
|
303
|
+
else:
|
|
304
|
+
action = "updated"
|
|
305
|
+
|
|
306
|
+
if action != "unchanged" and not dry_run:
|
|
307
|
+
servers[MCP_SERVER_NAME] = entry
|
|
308
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
config_path.write_text(tomlkit.dumps(doc))
|
|
310
|
+
|
|
311
|
+
return McpRegistrationResult(
|
|
312
|
+
path=config_path,
|
|
313
|
+
action=action,
|
|
314
|
+
server_name=MCP_SERVER_NAME,
|
|
315
|
+
entry=entry,
|
|
316
|
+
)
|
|
@@ -36,25 +36,10 @@ OPENCODE_TOOLS: dict[str, str] = {
|
|
|
36
36
|
"TodoWrite": "todowrite",
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
# Gemini CLI — different tool name convention
|
|
40
|
-
GEMINI_TOOLS: dict[str, str] = {
|
|
41
|
-
"Bash": "run_shell_command",
|
|
42
|
-
"Read": "read_file",
|
|
43
|
-
"Write": "write_file",
|
|
44
|
-
"Edit": "replace",
|
|
45
|
-
"Glob": "glob",
|
|
46
|
-
"Grep": "grep",
|
|
47
|
-
"Agent": "agent",
|
|
48
|
-
"WebFetch": "web_fetch",
|
|
49
|
-
"WebSearch": "web_search",
|
|
50
|
-
"NotebookEdit": "notebook_edit",
|
|
51
|
-
}
|
|
52
|
-
|
|
53
39
|
# Mapping from RuntimeType to tool map
|
|
54
40
|
TOOL_MAPS: dict[str, dict[str, str]] = {
|
|
55
41
|
"claude": CLAUDE_TOOLS,
|
|
56
42
|
"opencode": OPENCODE_TOOLS,
|
|
57
|
-
"gemini": GEMINI_TOOLS,
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
|
|
@@ -67,7 +52,7 @@ def map_tool_name(tool: str, runtime: str) -> str:
|
|
|
67
52
|
|
|
68
53
|
Args:
|
|
69
54
|
tool: Canonical tool name (e.g., "Bash", "Write(.agent-brain/**)").
|
|
70
|
-
runtime: Target runtime ("claude", "opencode"
|
|
55
|
+
runtime: Target runtime ("claude", "opencode").
|
|
71
56
|
|
|
72
57
|
Returns:
|
|
73
58
|
Mapped tool name, or the lowercased base name if no mapping exists.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "agent-brain-cli"
|
|
3
|
-
version = "10.
|
|
3
|
+
version = "10.5.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"
|
|
@@ -26,6 +26,7 @@ click = "^8.1.0"
|
|
|
26
26
|
httpx = "^0.28.0"
|
|
27
27
|
rich = "^13.9.0"
|
|
28
28
|
pyyaml = "^6.0.0"
|
|
29
|
+
tomlkit = "^0.13.0"
|
|
29
30
|
pydantic = "^2.10.0"
|
|
30
31
|
agent-brain-rag = "^10.4.0"
|
|
31
32
|
agent-brain-uds = "^10.3.0"
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"""Gemini CLI runtime converter.
|
|
2
|
-
|
|
3
|
-
Gemini uses different tool names and doesn't support some fields:
|
|
4
|
-
- Tool names: Read→read_file, Write→write_file, Edit→replace, Bash→run_shell_command
|
|
5
|
-
- No `color` field support
|
|
6
|
-
- Skills use `tools` list with Gemini-mapped names
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
import yaml
|
|
13
|
-
|
|
14
|
-
from agent_brain_cli.runtime.tool_maps import map_tools
|
|
15
|
-
from agent_brain_cli.runtime.types import (
|
|
16
|
-
PluginAgent,
|
|
17
|
-
PluginBundle,
|
|
18
|
-
PluginCommand,
|
|
19
|
-
PluginSkill,
|
|
20
|
-
RuntimeType,
|
|
21
|
-
Scope,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
logger = logging.getLogger(__name__)
|
|
25
|
-
|
|
26
|
-
LEGACY_PATH = ".claude/agent-brain"
|
|
27
|
-
NEW_PATH = ".agent-brain"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _replace_paths(text: str) -> str:
|
|
31
|
-
return text.replace(LEGACY_PATH, NEW_PATH)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _rebuild_file(frontmatter: dict, body: str) -> str: # type: ignore[type-arg]
|
|
35
|
-
yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)
|
|
36
|
-
return f"---\n{yaml_str}---\n{body}\n"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class GeminiConverter:
|
|
40
|
-
"""Converter for Gemini CLI runtime."""
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def runtime_type(self) -> RuntimeType:
|
|
44
|
-
return RuntimeType.GEMINI
|
|
45
|
-
|
|
46
|
-
def convert_command(self, command: PluginCommand) -> str:
|
|
47
|
-
fm: dict[str, object] = {
|
|
48
|
-
"name": command.name,
|
|
49
|
-
"description": command.description,
|
|
50
|
-
"parameters": [
|
|
51
|
-
{
|
|
52
|
-
"name": p.name,
|
|
53
|
-
"description": p.description,
|
|
54
|
-
"required": p.required,
|
|
55
|
-
**({"default": p.default} if p.default else {}),
|
|
56
|
-
}
|
|
57
|
-
for p in command.parameters
|
|
58
|
-
],
|
|
59
|
-
"skills": command.skills,
|
|
60
|
-
}
|
|
61
|
-
return _rebuild_file(fm, _replace_paths(command.body))
|
|
62
|
-
|
|
63
|
-
def convert_agent(self, agent: PluginAgent) -> str:
|
|
64
|
-
fm: dict[str, object] = {
|
|
65
|
-
"name": agent.name,
|
|
66
|
-
"description": agent.description,
|
|
67
|
-
"triggers": [
|
|
68
|
-
{"pattern": t.pattern, "type": t.type} for t in agent.triggers
|
|
69
|
-
],
|
|
70
|
-
"skills": agent.skills,
|
|
71
|
-
}
|
|
72
|
-
return _rebuild_file(fm, _replace_paths(agent.body))
|
|
73
|
-
|
|
74
|
-
def convert_skill(self, skill: PluginSkill) -> str:
|
|
75
|
-
"""Convert skill with Gemini-mapped tool names, no color field."""
|
|
76
|
-
mapped_tools = map_tools(skill.allowed_tools, "gemini")
|
|
77
|
-
# Filter out unsupported metadata fields
|
|
78
|
-
clean_metadata = {k: v for k, v in skill.metadata.items() if k != "color"}
|
|
79
|
-
fm: dict[str, object] = {
|
|
80
|
-
"name": skill.name,
|
|
81
|
-
"description": skill.description,
|
|
82
|
-
"license": skill.license,
|
|
83
|
-
"allowed-tools": mapped_tools,
|
|
84
|
-
"metadata": clean_metadata,
|
|
85
|
-
}
|
|
86
|
-
return _rebuild_file(fm, _replace_paths(skill.body))
|
|
87
|
-
|
|
88
|
-
def install(
|
|
89
|
-
self,
|
|
90
|
-
bundle: PluginBundle,
|
|
91
|
-
target_dir: Path,
|
|
92
|
-
scope: Scope,
|
|
93
|
-
) -> list[Path]:
|
|
94
|
-
"""Install Gemini CLI plugin files."""
|
|
95
|
-
created: list[Path] = []
|
|
96
|
-
|
|
97
|
-
cmds_dir = target_dir / "commands"
|
|
98
|
-
cmds_dir.mkdir(parents=True, exist_ok=True)
|
|
99
|
-
for cmd in bundle.commands:
|
|
100
|
-
out = cmds_dir / f"{cmd.name}.md"
|
|
101
|
-
out.write_text(self.convert_command(cmd), encoding="utf-8")
|
|
102
|
-
created.append(out)
|
|
103
|
-
|
|
104
|
-
agents_dir = target_dir / "agents"
|
|
105
|
-
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
106
|
-
for agent in bundle.agents:
|
|
107
|
-
out = agents_dir / f"{agent.name}.md"
|
|
108
|
-
out.write_text(self.convert_agent(agent), encoding="utf-8")
|
|
109
|
-
created.append(out)
|
|
110
|
-
|
|
111
|
-
skills_dir = target_dir / "skills"
|
|
112
|
-
for skill in bundle.skills:
|
|
113
|
-
skill_out = skills_dir / skill.name
|
|
114
|
-
skill_out.mkdir(parents=True, exist_ok=True)
|
|
115
|
-
skill_file = skill_out / "SKILL.md"
|
|
116
|
-
skill_file.write_text(self.convert_skill(skill), encoding="utf-8")
|
|
117
|
-
created.append(skill_file)
|
|
118
|
-
|
|
119
|
-
return created
|
|
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
|
|
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.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/claude_converter.py
RENAMED
|
File without changes
|
{agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/codex_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/opencode_converter.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_brain_cli-10.4.0 → agent_brain_cli-10.5.0}/agent_brain_cli/runtime/skill_runtime_converter.py
RENAMED
|
File without changes
|
|
File without changes
|