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.
- agentnet_cli/__init__.py +6 -0
- agentnet_cli/agents/README.md +55 -0
- agentnet_cli/agents/__init__.py +0 -0
- agentnet_cli/agents/base.py +37 -0
- agentnet_cli/agents/claude.py +114 -0
- agentnet_cli/agents/codex.py +78 -0
- agentnet_cli/agents/copilot.py +89 -0
- agentnet_cli/agents/cursor.py +90 -0
- agentnet_cli/agents/hermes.py +171 -0
- agentnet_cli/agents/openclaw.py +54 -0
- agentnet_cli/agents/registry.py +28 -0
- agentnet_cli/agents/shims.py +9 -0
- agentnet_cli/agents/vscode.py +119 -0
- agentnet_cli/commands/__init__.py +0 -0
- agentnet_cli/commands/agent.py +32 -0
- agentnet_cli/commands/discover.py +34 -0
- agentnet_cli/commands/session.py +35 -0
- agentnet_cli/commands/wallet.py +48 -0
- agentnet_cli/config.py +68 -0
- agentnet_cli/connect.py +73 -0
- agentnet_cli/detect.py +23 -0
- agentnet_cli/disconnect.py +60 -0
- agentnet_cli/hermes_plugin/__init__.py +36 -0
- agentnet_cli/hermes_plugin/handlers.py +69 -0
- agentnet_cli/hermes_plugin/plugin.yaml +17 -0
- agentnet_cli/hermes_plugin/schemas.py +182 -0
- agentnet_cli/hermes_plugin/skills/agentnet/SKILL.md +46 -0
- agentnet_cli/main.py +263 -0
- agentnet_cli/manifest.py +58 -0
- agentnet_cli/marketplace.py +39 -0
- agentnet_cli/mcp/README.md +64 -0
- agentnet_cli/mcp/__init__.py +0 -0
- agentnet_cli/mcp/server.py +244 -0
- agentnet_cli/mcp/tools.py +67 -0
- agentnet_cli/paths.py +91 -0
- agentnet_cli/platform/README.md +79 -0
- agentnet_cli/platform/__init__.py +0 -0
- agentnet_cli/platform/client.py +134 -0
- agentnet_cli/register.py +146 -0
- agentnet_cli/shims/README.md +48 -0
- agentnet_cli/shims/SKILL.md +225 -0
- agentnet_cli/shims/codex/skill.md +8 -0
- agentnet_cli/shims/copilot/agentnet.agent.md +14 -0
- agentnet_cli/shims/cursor/agent.md +9 -0
- agentnet_cli/shims/cursor/agentnet.mdc +8 -0
- agentnet_cli/shims/shared/context.md +62 -0
- agentnet_cli/shims/vscode/instructions.md +3 -0
- agentnet_cli/status.py +65 -0
- agentnet_cli/updater.py +123 -0
- agentnet_cli-0.1.0.dist-info/METADATA +253 -0
- agentnet_cli-0.1.0.dist-info/RECORD +53 -0
- agentnet_cli-0.1.0.dist-info/WHEEL +4 -0
- 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
|
agentnet_cli/connect.py
ADDED
|
@@ -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)
|