agentnet-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. agentnet_cli/__init__.py +6 -0
  2. agentnet_cli/agents/README.md +55 -0
  3. agentnet_cli/agents/__init__.py +0 -0
  4. agentnet_cli/agents/base.py +37 -0
  5. agentnet_cli/agents/claude.py +114 -0
  6. agentnet_cli/agents/codex.py +78 -0
  7. agentnet_cli/agents/copilot.py +89 -0
  8. agentnet_cli/agents/cursor.py +90 -0
  9. agentnet_cli/agents/hermes.py +171 -0
  10. agentnet_cli/agents/openclaw.py +54 -0
  11. agentnet_cli/agents/registry.py +28 -0
  12. agentnet_cli/agents/shims.py +9 -0
  13. agentnet_cli/agents/vscode.py +119 -0
  14. agentnet_cli/commands/__init__.py +0 -0
  15. agentnet_cli/commands/agent.py +32 -0
  16. agentnet_cli/commands/discover.py +34 -0
  17. agentnet_cli/commands/session.py +35 -0
  18. agentnet_cli/commands/wallet.py +48 -0
  19. agentnet_cli/config.py +68 -0
  20. agentnet_cli/connect.py +73 -0
  21. agentnet_cli/detect.py +23 -0
  22. agentnet_cli/disconnect.py +60 -0
  23. agentnet_cli/hermes_plugin/__init__.py +36 -0
  24. agentnet_cli/hermes_plugin/handlers.py +69 -0
  25. agentnet_cli/hermes_plugin/plugin.yaml +17 -0
  26. agentnet_cli/hermes_plugin/schemas.py +182 -0
  27. agentnet_cli/hermes_plugin/skills/agentnet/SKILL.md +46 -0
  28. agentnet_cli/main.py +263 -0
  29. agentnet_cli/manifest.py +58 -0
  30. agentnet_cli/marketplace.py +39 -0
  31. agentnet_cli/mcp/README.md +64 -0
  32. agentnet_cli/mcp/__init__.py +0 -0
  33. agentnet_cli/mcp/server.py +244 -0
  34. agentnet_cli/mcp/tools.py +67 -0
  35. agentnet_cli/paths.py +91 -0
  36. agentnet_cli/platform/README.md +79 -0
  37. agentnet_cli/platform/__init__.py +0 -0
  38. agentnet_cli/platform/client.py +134 -0
  39. agentnet_cli/register.py +146 -0
  40. agentnet_cli/shims/README.md +48 -0
  41. agentnet_cli/shims/SKILL.md +225 -0
  42. agentnet_cli/shims/codex/skill.md +8 -0
  43. agentnet_cli/shims/copilot/agentnet.agent.md +14 -0
  44. agentnet_cli/shims/cursor/agent.md +9 -0
  45. agentnet_cli/shims/cursor/agentnet.mdc +8 -0
  46. agentnet_cli/shims/shared/context.md +62 -0
  47. agentnet_cli/shims/vscode/instructions.md +3 -0
  48. agentnet_cli/status.py +65 -0
  49. agentnet_cli/updater.py +123 -0
  50. agentnet_cli-0.1.0.dist-info/METADATA +253 -0
  51. agentnet_cli-0.1.0.dist-info/RECORD +53 -0
  52. agentnet_cli-0.1.0.dist-info/WHEEL +4 -0
  53. agentnet_cli-0.1.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import os
4
+ import stat
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from ..paths import AgentName, agent_config_root, agentnet_home
8
+ from .base import AgentConnector, ConnectionResult, DetectionResult
9
+
10
+
11
+ class OpenClawConnector(AgentConnector):
12
+ def detect(self) -> DetectionResult:
13
+ root = agent_config_root(AgentName.OPENCLAW)
14
+ if not root.exists():
15
+ return DetectionResult(agent_name=AgentName.OPENCLAW, detected=False)
16
+ if (root / "openclaw.json").exists():
17
+ return DetectionResult(agent_name=AgentName.OPENCLAW, detected=True, config_root=root)
18
+ return DetectionResult(agent_name=AgentName.OPENCLAW, detected=False)
19
+
20
+ def connect(self, platform_config: dict[str, Any]) -> ConnectionResult:
21
+ root = agent_config_root(AgentName.OPENCLAW)
22
+ config_path = root / "openclaw.json"
23
+ data: dict[str, Any] = {}
24
+ if config_path.exists():
25
+ data = json.loads(config_path.read_text())
26
+ backup = agentnet_home() / "backups" / "openclaw" / "openclaw.json.bak"
27
+ backup.parent.mkdir(parents=True, exist_ok=True)
28
+ backup.write_bytes(config_path.read_bytes())
29
+ plugins = data.setdefault("plugins", {})
30
+ plugins["agentnet-gateway"] = {
31
+ "enabled": True,
32
+ "config": {
33
+ "platformUrl": platform_config.get("platform_url", ""),
34
+ "platformToken": platform_config.get("api_token", ""),
35
+ },
36
+ }
37
+ config_path.write_text(json.dumps(data, indent=2) + "\n")
38
+ if os.name != "nt":
39
+ config_path.chmod(stat.S_IRUSR | stat.S_IWUSR)
40
+ return ConnectionResult(
41
+ success=True,
42
+ mcp_entry={"scope": "user", "file": str(config_path), "server_name": "agentnet-gateway"},
43
+ )
44
+
45
+ def disconnect(self, connection_manifest: dict[str, Any]) -> bool:
46
+ mcp_info = connection_manifest.get("mcp_registered", {})
47
+ mcp_file = mcp_info.get("file")
48
+ if mcp_file:
49
+ config_path = Path(mcp_file)
50
+ if config_path.exists():
51
+ data = json.loads(config_path.read_text())
52
+ data.get("plugins", {}).pop("agentnet-gateway", None)
53
+ config_path.write_text(json.dumps(data, indent=2) + "\n")
54
+ return True
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+ from ..paths import AgentName
3
+ from .base import AgentConnector
4
+ from .claude import ClaudeConnector
5
+ from .cursor import CursorConnector
6
+ from .copilot import CopilotConnector
7
+ from .vscode import VSCodeConnector
8
+ from .codex import CodexConnector
9
+ from .hermes import HermesConnector
10
+ from .openclaw import OpenClawConnector
11
+
12
+ _CONNECTORS: dict[AgentName, type[AgentConnector]] = {
13
+ AgentName.CLAUDE: ClaudeConnector,
14
+ AgentName.CURSOR: CursorConnector,
15
+ AgentName.COPILOT: CopilotConnector,
16
+ AgentName.VSCODE: VSCodeConnector,
17
+ AgentName.CODEX: CodexConnector,
18
+ AgentName.HERMES: HermesConnector,
19
+ AgentName.OPENCLAW: OpenClawConnector,
20
+ }
21
+
22
+
23
+ def get_connector(agent: AgentName) -> AgentConnector:
24
+ return _CONNECTORS[agent]()
25
+
26
+
27
+ def all_connectors() -> dict[AgentName, AgentConnector]:
28
+ return {name: cls() for name, cls in _CONNECTORS.items()}
@@ -0,0 +1,9 @@
1
+ from pathlib import Path
2
+
3
+ _SHIMS_DIR = Path(__file__).resolve().parent.parent / "shims"
4
+
5
+
6
+ def load_shim(relative_path: str) -> str:
7
+ context = (_SHIMS_DIR / "shared" / "context.md").read_text()
8
+ template = (_SHIMS_DIR / relative_path).read_text()
9
+ return template.replace("{{CONTEXT}}", context)
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import platform
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from ..paths import AgentName, agent_config_root, agentnet_home
11
+ from .base import AgentConnector, ConnectionResult, DetectionResult
12
+ from .shims import load_shim
13
+
14
+
15
+ def _vscode_user_dirs() -> list[Path]:
16
+ system = platform.system()
17
+ home = Path.home()
18
+ candidates: list[Path] = []
19
+
20
+ if system == "Darwin":
21
+ base = home / "Library" / "Application Support"
22
+ for variant in ["Code", "Code - Insiders"]:
23
+ candidates.append(base / variant / "User")
24
+ elif system == "Linux":
25
+ config = Path(os.environ.get("XDG_CONFIG_HOME", home / ".config"))
26
+ for variant in ["Code", "Code - Insiders"]:
27
+ candidates.append(config / variant / "User")
28
+ elif system == "Windows":
29
+ appdata = Path(os.environ.get("APPDATA", home / "AppData" / "Roaming"))
30
+ for variant in ["Code", "Code - Insiders"]:
31
+ candidates.append(appdata / variant / "User")
32
+
33
+ return [p for p in candidates if p.exists()]
34
+
35
+
36
+ class VSCodeConnector(AgentConnector):
37
+ def detect(self) -> DetectionResult:
38
+ dot_vscode = agent_config_root(AgentName.VSCODE)
39
+ has_extensions = dot_vscode.exists() and (dot_vscode / "extensions").exists()
40
+ user_dirs = _vscode_user_dirs()
41
+ if has_extensions or user_dirs:
42
+ config_root = user_dirs[0] if user_dirs else dot_vscode
43
+ return DetectionResult(
44
+ agent_name=AgentName.VSCODE,
45
+ detected=True,
46
+ config_root=config_root,
47
+ )
48
+ return DetectionResult(agent_name=AgentName.VSCODE, detected=False)
49
+
50
+ def connect(self, platform_config: dict[str, Any]) -> ConnectionResult:
51
+ files_created: list[Path] = []
52
+ files_modified: list[tuple[Path, Path]] = []
53
+ mcp_entry_info: dict[str, Any] = {}
54
+ mcp_config = self._build_mcp_entry()
55
+
56
+ vscode_files: list[str] = []
57
+ for user_dir in _vscode_user_dirs():
58
+ mcp_path = user_dir / "mcp.json"
59
+ backup = agentnet_home() / "backups" / "vscode" / mcp_path.parent.parent.name / "mcp.json.bak"
60
+ backup.parent.mkdir(parents=True, exist_ok=True)
61
+ if mcp_path.exists():
62
+ backup.write_bytes(mcp_path.read_bytes())
63
+ files_modified.append((mcp_path, backup))
64
+ self._merge_mcp(mcp_path, mcp_config)
65
+ vscode_files.append(str(mcp_path))
66
+
67
+ mcp_entry_info["vscode_files"] = vscode_files
68
+
69
+ user_dirs = _vscode_user_dirs()
70
+ instructions_dir = user_dirs[0] if user_dirs else agent_config_root(AgentName.VSCODE)
71
+ instructions_path = instructions_dir / ".github" / "copilot-instructions.md"
72
+ instructions_path.parent.mkdir(parents=True, exist_ok=True)
73
+ instructions_path.write_text(load_shim("vscode/instructions.md"))
74
+ files_created.append(instructions_path)
75
+
76
+ return ConnectionResult(
77
+ success=True,
78
+ files_created=files_created,
79
+ files_modified=files_modified,
80
+ mcp_entry=mcp_entry_info,
81
+ )
82
+
83
+ def disconnect(self, connection_manifest: dict[str, Any]) -> bool:
84
+ for path_str in connection_manifest.get("files_created", []):
85
+ p = Path(path_str)
86
+ if p.exists():
87
+ p.unlink()
88
+
89
+ mcp_info = connection_manifest.get("mcp_registered", {})
90
+ for vsc_path_str in mcp_info.get("vscode_files", []):
91
+ vsc_path = Path(vsc_path_str)
92
+ if vsc_path.exists():
93
+ data = json.loads(vsc_path.read_text())
94
+ data.get("servers", {}).pop("agentnet", None)
95
+ vsc_path.write_text(json.dumps(data, indent=2) + "\n")
96
+ return True
97
+
98
+ def _build_mcp_entry(self) -> dict[str, Any]:
99
+ agentnet_bin = shutil.which("agentnet")
100
+ if agentnet_bin:
101
+ return {
102
+ "type": "stdio",
103
+ "command": agentnet_bin,
104
+ "args": ["mcp-serve"],
105
+ }
106
+ return {
107
+ "type": "stdio",
108
+ "command": "uvx",
109
+ "args": ["agentnet-cli", "mcp-serve"],
110
+ }
111
+
112
+ def _merge_mcp(self, mcp_path: Path, entry: dict[str, Any]) -> None:
113
+ data: dict[str, Any] = {}
114
+ if mcp_path.exists():
115
+ data = json.loads(mcp_path.read_text())
116
+ data.setdefault("servers", {})
117
+ data["servers"]["agentnet"] = entry
118
+ mcp_path.parent.mkdir(parents=True, exist_ok=True)
119
+ mcp_path.write_text(json.dumps(data, indent=2) + "\n")
File without changes
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ..marketplace import die, get_client, output
6
+ from ..platform.client import PlatformError
7
+
8
+
9
+ def agent(
10
+ agent_id: str = typer.Argument(help="Agent ID from discovery results"),
11
+ ) -> None:
12
+ """Get full details about an agent — skills, pricing, trust score."""
13
+ client = get_client()
14
+ try:
15
+ result = client.get_agent(agent_id=agent_id)
16
+ output(result)
17
+ except PlatformError as e:
18
+ die(str(e))
19
+
20
+
21
+ def hire(
22
+ agent_id: str = typer.Argument(help="Agent to hire"),
23
+ task: str = typer.Option(..., "--task", "-t", help="Task description"),
24
+ budget: float = typer.Option(0, "--budget", "-b", help="Max budget in USD"),
25
+ ) -> None:
26
+ """Hire an agent to do a task. Returns result or session_id for follow-up."""
27
+ client = get_client()
28
+ try:
29
+ result = client.use_agent(agent_id=agent_id, task=task, max_amount=budget)
30
+ output(result)
31
+ except PlatformError as e:
32
+ die(str(e))
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ..marketplace import die, get_client, output
6
+ from ..platform.client import PlatformError
7
+
8
+
9
+ def discover(
10
+ query: str = typer.Argument(help="What to search for"),
11
+ category: str | None = typer.Option(None, help="Filter by category"),
12
+ limit: int = typer.Option(20, "--limit", "-l", help="Max results"),
13
+ max_price: int | None = typer.Option(None, "--max-price", help="Max price in USD"),
14
+ ) -> None:
15
+ """Search the Agent-net marketplace for products and services."""
16
+ client = get_client()
17
+ try:
18
+ result = client.discover(query=query, category=category, max_results=limit, max_price=max_price)
19
+ output(result)
20
+ except PlatformError as e:
21
+ die(str(e))
22
+
23
+
24
+ def agents(
25
+ query: str = typer.Argument(help="Agent name or capability to search for"),
26
+ limit: int = typer.Option(20, "--limit", "-l", help="Max results"),
27
+ ) -> None:
28
+ """Search for AI agents by name or capability."""
29
+ client = get_client()
30
+ try:
31
+ result = client.discover_agents(query=query, limit=limit)
32
+ output(result)
33
+ except PlatformError as e:
34
+ die(str(e))
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ..marketplace import die, get_client, output
6
+ from ..platform.client import PlatformError
7
+
8
+ session_app = typer.Typer(help="Manage multi-turn agent sessions.")
9
+
10
+
11
+ @session_app.command(name="continue")
12
+ def continue_session(
13
+ session_id: str = typer.Argument(help="Session ID from a previous hire"),
14
+ message: str = typer.Option(..., "--message", "-m", help="Follow-up message"),
15
+ ) -> None:
16
+ """Send a follow-up message in a multi-turn session."""
17
+ client = get_client()
18
+ try:
19
+ result = client.continue_session(session_id=session_id, message=message)
20
+ output(result)
21
+ except PlatformError as e:
22
+ die(str(e))
23
+
24
+
25
+ @session_app.command()
26
+ def settle(
27
+ session_id: str = typer.Argument(help="Session ID to settle"),
28
+ ) -> None:
29
+ """Confirm satisfaction and release payment for a session."""
30
+ client = get_client()
31
+ try:
32
+ result = client.settle_session(session_id=session_id)
33
+ output(result)
34
+ except PlatformError as e:
35
+ die(str(e))
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ..marketplace import die, get_agent_id, get_client, output
6
+ from ..platform.client import PlatformError
7
+
8
+ wallet_app = typer.Typer(help="Manage your Agent-net wallet.")
9
+
10
+
11
+ @wallet_app.command()
12
+ def balance() -> None:
13
+ """Check your current wallet balance."""
14
+ client = get_client()
15
+ aid = get_agent_id()
16
+ try:
17
+ result = client.wallet_balance(agent_id=aid)
18
+ output(result)
19
+ except PlatformError as e:
20
+ die(str(e))
21
+
22
+
23
+ @wallet_app.command()
24
+ def history(
25
+ limit: int = typer.Option(50, "--limit", "-l", help="Number of transactions to show"),
26
+ ) -> None:
27
+ """View recent wallet transactions."""
28
+ client = get_client()
29
+ aid = get_agent_id()
30
+ try:
31
+ result = client.wallet_history(agent_id=aid, limit=limit)
32
+ output(result)
33
+ except PlatformError as e:
34
+ die(str(e))
35
+
36
+
37
+ @wallet_app.command()
38
+ def topup(
39
+ amount: float = typer.Argument(help="Amount to add in USD"),
40
+ ) -> None:
41
+ """Add funds to your wallet."""
42
+ client = get_client()
43
+ aid = get_agent_id()
44
+ try:
45
+ result = client.wallet_topup(agent_id=aid, amount=amount)
46
+ output(result)
47
+ except PlatformError as e:
48
+ die(str(e))
agentnet_cli/config.py ADDED
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import stat
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from .paths import agentnet_home
11
+
12
+
13
+ def _atomic_write(path: Path, content: str, *, restricted: bool = False) -> None:
14
+ """Write content atomically via temp file + rename."""
15
+ path.parent.mkdir(parents=True, exist_ok=True)
16
+ tmp = path.with_suffix(".tmp")
17
+ try:
18
+ tmp.write_text(content)
19
+ if restricted and os.name != "nt":
20
+ tmp.chmod(stat.S_IRUSR | stat.S_IWUSR)
21
+ tmp.replace(path)
22
+ except BaseException:
23
+ tmp.unlink(missing_ok=True)
24
+ raise
25
+
26
+
27
+ def _config_path() -> Path:
28
+ return agentnet_home() / "config.json"
29
+
30
+
31
+ def load_config() -> dict[str, Any] | None:
32
+ path = _config_path()
33
+ if not path.exists():
34
+ return None
35
+ try:
36
+ return json.loads(path.read_text())
37
+ except json.JSONDecodeError:
38
+ print(f"Warning: {path} is corrupted, ignoring", file=sys.stderr)
39
+ return None
40
+
41
+
42
+ def save_config(data: dict[str, Any]) -> None:
43
+ path = _config_path()
44
+ _atomic_write(path, json.dumps(data, indent=2) + "\n", restricted=True)
45
+
46
+
47
+ def load_agent_paths() -> dict[str, str]:
48
+ config = load_config()
49
+ if not config:
50
+ return {}
51
+ return config.get("agent_paths", {})
52
+
53
+
54
+ def save_agent_path(agent_name: str, binary_path: str) -> None:
55
+ config = load_config() or {}
56
+ paths = config.setdefault("agent_paths", {})
57
+ paths[agent_name] = binary_path
58
+ save_config(config)
59
+
60
+
61
+ def remove_agent_path(agent_name: str) -> bool:
62
+ config = load_config() or {}
63
+ paths = config.get("agent_paths", {})
64
+ if agent_name not in paths:
65
+ return False
66
+ del paths[agent_name]
67
+ save_config(config)
68
+ return True
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.console import Console
4
+
5
+ from .agents.registry import get_connector
6
+ from .config import load_config
7
+ from .detect import detect_all
8
+ from .manifest import record_connection
9
+ from .paths import AgentName, agent_display_name
10
+
11
+ console = Console()
12
+
13
+
14
+ def connect_command(agent_name: str | None = None, connect_all: bool = False) -> None:
15
+ config = load_config()
16
+ if not config or not config.get("api_token"):
17
+ console.print()
18
+ console.print(" [red]Not registered.[/red] Run [bold]agentnet register[/bold] first.")
19
+ console.print()
20
+ raise SystemExit(1)
21
+
22
+ if connect_all:
23
+ targets = [r.agent_name for r in detect_all() if r.detected and not r.already_connected]
24
+ if not targets:
25
+ console.print("\n [dim]All detected agents are already connected.[/dim]\n")
26
+ return
27
+ elif agent_name:
28
+ try:
29
+ AgentName(agent_name)
30
+ targets = [agent_name]
31
+ except ValueError:
32
+ console.print(f"\n [red]Error:[/red] Unknown agent [bold]{agent_name}[/bold]")
33
+ console.print(
34
+ " [dim]Available: claude, cursor, copilot, vscode, codex, hermes, openclaw[/dim]\n"
35
+ )
36
+ raise SystemExit(1)
37
+ else:
38
+ console.print("\n [red]Error:[/red] Specify an agent name or use [bold]--all[/bold]")
39
+ console.print(" [dim]Example: agentnet connect claude[/dim]\n")
40
+ raise SystemExit(1)
41
+
42
+ console.print()
43
+ succeeded = 0
44
+ for name in targets:
45
+ display = agent_display_name(AgentName(name))
46
+ connector = get_connector(AgentName(name))
47
+ detection = connector.detect()
48
+ if not detection.detected:
49
+ console.print(f" [yellow]![/yellow] {display} not detected on this system, skipping")
50
+ continue
51
+
52
+ console.print(f" Connecting {display}...")
53
+ result = connector.connect(config)
54
+ if result.success:
55
+ record_connection(
56
+ name,
57
+ files_created=result.files_created,
58
+ files_modified=result.files_modified,
59
+ mcp_entry=result.mcp_entry,
60
+ )
61
+ file_count = len(result.files_created)
62
+ mcp_info = " + MCP server registered" if result.mcp_entry else ""
63
+ console.print(
64
+ f" [green]✓[/green] {display} connected ({file_count} file{'s' if file_count != 1 else ''} created{mcp_info})"
65
+ )
66
+ succeeded += 1
67
+ else:
68
+ console.print(f" [red]✗[/red] {display} failed: {', '.join(result.errors)}")
69
+
70
+ if succeeded:
71
+ console.print(f"\n [green]{succeeded} agent{'s' if succeeded != 1 else ''} connected.[/green]")
72
+ console.print(" [dim]Your agents can now discover and transact on Agent-net.[/dim]")
73
+ console.print()
agentnet_cli/detect.py ADDED
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from .agents.base import DetectionResult
4
+ from .agents.registry import all_connectors
5
+ from .config import load_agent_paths
6
+ from .manifest import load_manifest
7
+ from .paths import AgentName, find_agent_binary
8
+
9
+
10
+ def detect_all() -> list[DetectionResult]:
11
+ manifest = load_manifest()
12
+ connected = set(manifest.get("connections", {}).keys())
13
+ custom_paths = load_agent_paths()
14
+ results: list[DetectionResult] = []
15
+ for name, connector in all_connectors().items():
16
+ result = connector.detect()
17
+ result.already_connected = name in connected
18
+ binary = find_agent_binary(AgentName(name), custom_paths)
19
+ if binary:
20
+ result.binary_path = binary
21
+ result.binary_found = True
22
+ results.append(result)
23
+ return results
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from rich.console import Console
4
+
5
+ from .agents.registry import get_connector
6
+ from .manifest import load_manifest, remove_connection
7
+ from .paths import AgentName, agent_display_name
8
+
9
+ console = Console()
10
+
11
+
12
+ def disconnect_command(agent_name: str | None = None, disconnect_all: bool = False) -> None:
13
+ manifest = load_manifest()
14
+ connections = manifest.get("connections", {})
15
+
16
+ if disconnect_all:
17
+ targets = list(connections.keys())
18
+ if not targets:
19
+ console.print("\n [dim]No agents are currently connected.[/dim]\n")
20
+ return
21
+ elif agent_name:
22
+ if agent_name not in connections:
23
+ try:
24
+ display = agent_display_name(AgentName(agent_name))
25
+ except ValueError:
26
+ display = agent_name
27
+ console.print(f"\n [yellow]![/yellow] {display} is not connected.\n")
28
+ return
29
+ targets = [agent_name]
30
+ else:
31
+ console.print("\n [red]Error:[/red] Specify an agent name or use [bold]--all[/bold]")
32
+ console.print(" [dim]Example: agentnet disconnect claude[/dim]\n")
33
+ raise SystemExit(1)
34
+
35
+ console.print()
36
+ succeeded = 0
37
+ for name in targets:
38
+ try:
39
+ agent = AgentName(name)
40
+ display = agent_display_name(agent)
41
+ except ValueError:
42
+ console.print(f" [yellow]![/yellow] Unknown agent {name}, skipping")
43
+ continue
44
+
45
+ connector = get_connector(agent)
46
+ entry = connections[name]
47
+ console.print(f" Disconnecting {display}...")
48
+ ok = connector.disconnect(entry)
49
+ if ok:
50
+ remove_connection(name)
51
+ console.print(f" [green]✓[/green] {display} disconnected")
52
+ succeeded += 1
53
+ else:
54
+ console.print(f" [red]✗[/red] {display} — failed to disconnect cleanly")
55
+
56
+ if succeeded:
57
+ console.print(
58
+ f"\n [green]{succeeded} agent{'s' if succeeded != 1 else ''} disconnected.[/green]"
59
+ )
60
+ console.print()
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from . import handlers, schemas
6
+
7
+ _PLUGIN_DIR = Path(__file__).resolve().parent
8
+
9
+ _HANDLER_MAP = {
10
+ "agentnet_discover": handlers.agentnet_discover,
11
+ "agentnet_discover_agents": handlers.agentnet_discover_agents,
12
+ "agentnet_get_agent": handlers.agentnet_get_agent,
13
+ "agentnet_use_agent": handlers.agentnet_use_agent,
14
+ "agentnet_continue_session": handlers.agentnet_continue_session,
15
+ "agentnet_settle_session": handlers.agentnet_settle_session,
16
+ "agentnet_wallet": handlers.agentnet_wallet,
17
+ "agentnet_wallet_topup": handlers.agentnet_wallet_topup,
18
+ }
19
+
20
+
21
+ def register(ctx):
22
+ for schema in schemas.SCHEMAS:
23
+ name = schema["name"]
24
+ ctx.register_tool(
25
+ name=name,
26
+ toolset="agentnet",
27
+ schema=schema,
28
+ handler=_HANDLER_MAP[name],
29
+ )
30
+
31
+ skills_dir = _PLUGIN_DIR / "skills"
32
+ if skills_dir.is_dir():
33
+ for child in sorted(skills_dir.iterdir()):
34
+ skill_md = child / "SKILL.md"
35
+ if child.is_dir() and skill_md.exists():
36
+ ctx.register_skill(child.name, skill_md)