agentforge-cli 0.1.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.
- agentforge_cli-0.1.0/PKG-INFO +53 -0
- agentforge_cli-0.1.0/README.md +42 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/PKG-INFO +53 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/SOURCES.txt +17 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/dependency_links.txt +1 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/entry_points.txt +2 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/requires.txt +4 -0
- agentforge_cli-0.1.0/agentforge_cli.egg-info/top_level.txt +1 -0
- agentforge_cli-0.1.0/forge/__init__.py +1 -0
- agentforge_cli-0.1.0/forge/__main__.py +5 -0
- agentforge_cli-0.1.0/forge/cli.py +58 -0
- agentforge_cli-0.1.0/forge/commands/__init__.py +1 -0
- agentforge_cli-0.1.0/forge/commands/init_cmd.py +44 -0
- agentforge_cli-0.1.0/forge/commands/inspect_cmd.py +65 -0
- agentforge_cli-0.1.0/forge/commands/install_cmd.py +28 -0
- agentforge_cli-0.1.0/forge/commands/publish_cmd.py +47 -0
- agentforge_cli-0.1.0/forge/commands/search_cmd.py +38 -0
- agentforge_cli-0.1.0/pyproject.toml +23 -0
- agentforge_cli-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentforge-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Registry and trust layer for AI agent skills
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.9.0
|
|
8
|
+
Requires-Dist: rich>=13.0.0
|
|
9
|
+
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
|
|
12
|
+
# agentforge (CLI: forge)
|
|
13
|
+
|
|
14
|
+
Registry and trust layer for AI agent skills. This package provides the **forge** command so you can create, inspect, publish, and install skills from the terminal.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install agentforge-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
From any directory:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
forge init # scaffold a skill (skill.yaml + example code)
|
|
28
|
+
forge inspect # show permissions and risk for the skill here
|
|
29
|
+
forge publish # publish to the registry
|
|
30
|
+
forge search # list skills
|
|
31
|
+
forge install <slug> # record an install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Registry URL (what `-r` is for)**
|
|
35
|
+
The **registry** is the backend API that stores and serves skills. When you deploy the AgentForge backend (e.g. on Render or Railway), you get a URL like `https://agentforge-api.onrender.com`. That URL is your **registry URL**.
|
|
36
|
+
|
|
37
|
+
- **`forge publish`** sends your skill to that API so it appears on the website and in `forge search`.
|
|
38
|
+
- **`forge search`** and **`forge install`** read from that same API.
|
|
39
|
+
|
|
40
|
+
If you don’t pass `-r`, the CLI defaults to `http://localhost:8000` (your own API running on your machine). For the **public** registry, pass its URL:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Example: your backend is at https://agentforge-api.onrender.com
|
|
44
|
+
forge publish -r https://agentforge-api.onrender.com
|
|
45
|
+
forge search -r https://agentforge-api.onrender.com
|
|
46
|
+
forge install send-email -r https://agentforge-api.onrender.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
So `https://your-api.example.com` in the docs is a **placeholder** for whatever your real backend URL is (e.g. from Render or Railway).
|
|
50
|
+
|
|
51
|
+
## Links
|
|
52
|
+
|
|
53
|
+
- [AgentForge](https://github.com/vince13/AgentForge) — repo and docs
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# agentforge (CLI: forge)
|
|
2
|
+
|
|
3
|
+
Registry and trust layer for AI agent skills. This package provides the **forge** command so you can create, inspect, publish, and install skills from the terminal.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install agentforge-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
From any directory:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
forge init # scaffold a skill (skill.yaml + example code)
|
|
17
|
+
forge inspect # show permissions and risk for the skill here
|
|
18
|
+
forge publish # publish to the registry
|
|
19
|
+
forge search # list skills
|
|
20
|
+
forge install <slug> # record an install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Registry URL (what `-r` is for)**
|
|
24
|
+
The **registry** is the backend API that stores and serves skills. When you deploy the AgentForge backend (e.g. on Render or Railway), you get a URL like `https://agentforge-api.onrender.com`. That URL is your **registry URL**.
|
|
25
|
+
|
|
26
|
+
- **`forge publish`** sends your skill to that API so it appears on the website and in `forge search`.
|
|
27
|
+
- **`forge search`** and **`forge install`** read from that same API.
|
|
28
|
+
|
|
29
|
+
If you don’t pass `-r`, the CLI defaults to `http://localhost:8000` (your own API running on your machine). For the **public** registry, pass its URL:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Example: your backend is at https://agentforge-api.onrender.com
|
|
33
|
+
forge publish -r https://agentforge-api.onrender.com
|
|
34
|
+
forge search -r https://agentforge-api.onrender.com
|
|
35
|
+
forge install send-email -r https://agentforge-api.onrender.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
So `https://your-api.example.com` in the docs is a **placeholder** for whatever your real backend URL is (e.g. from Render or Railway).
|
|
39
|
+
|
|
40
|
+
## Links
|
|
41
|
+
|
|
42
|
+
- [AgentForge](https://github.com/vince13/AgentForge) — repo and docs
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentforge-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Registry and trust layer for AI agent skills
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.9.0
|
|
8
|
+
Requires-Dist: rich>=13.0.0
|
|
9
|
+
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
|
|
12
|
+
# agentforge (CLI: forge)
|
|
13
|
+
|
|
14
|
+
Registry and trust layer for AI agent skills. This package provides the **forge** command so you can create, inspect, publish, and install skills from the terminal.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install agentforge-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use
|
|
23
|
+
|
|
24
|
+
From any directory:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
forge init # scaffold a skill (skill.yaml + example code)
|
|
28
|
+
forge inspect # show permissions and risk for the skill here
|
|
29
|
+
forge publish # publish to the registry
|
|
30
|
+
forge search # list skills
|
|
31
|
+
forge install <slug> # record an install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Registry URL (what `-r` is for)**
|
|
35
|
+
The **registry** is the backend API that stores and serves skills. When you deploy the AgentForge backend (e.g. on Render or Railway), you get a URL like `https://agentforge-api.onrender.com`. That URL is your **registry URL**.
|
|
36
|
+
|
|
37
|
+
- **`forge publish`** sends your skill to that API so it appears on the website and in `forge search`.
|
|
38
|
+
- **`forge search`** and **`forge install`** read from that same API.
|
|
39
|
+
|
|
40
|
+
If you don’t pass `-r`, the CLI defaults to `http://localhost:8000` (your own API running on your machine). For the **public** registry, pass its URL:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Example: your backend is at https://agentforge-api.onrender.com
|
|
44
|
+
forge publish -r https://agentforge-api.onrender.com
|
|
45
|
+
forge search -r https://agentforge-api.onrender.com
|
|
46
|
+
forge install send-email -r https://agentforge-api.onrender.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
So `https://your-api.example.com` in the docs is a **placeholder** for whatever your real backend URL is (e.g. from Render or Railway).
|
|
50
|
+
|
|
51
|
+
## Links
|
|
52
|
+
|
|
53
|
+
- [AgentForge](https://github.com/vince13/AgentForge) — repo and docs
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
agentforge_cli.egg-info/PKG-INFO
|
|
4
|
+
agentforge_cli.egg-info/SOURCES.txt
|
|
5
|
+
agentforge_cli.egg-info/dependency_links.txt
|
|
6
|
+
agentforge_cli.egg-info/entry_points.txt
|
|
7
|
+
agentforge_cli.egg-info/requires.txt
|
|
8
|
+
agentforge_cli.egg-info/top_level.txt
|
|
9
|
+
forge/__init__.py
|
|
10
|
+
forge/__main__.py
|
|
11
|
+
forge/cli.py
|
|
12
|
+
forge/commands/__init__.py
|
|
13
|
+
forge/commands/init_cmd.py
|
|
14
|
+
forge/commands/inspect_cmd.py
|
|
15
|
+
forge/commands/install_cmd.py
|
|
16
|
+
forge/commands/publish_cmd.py
|
|
17
|
+
forge/commands/search_cmd.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
forge
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
from forge.commands import init_cmd, inspect_cmd, publish_cmd, search_cmd, install_cmd
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
name="forge",
|
|
8
|
+
help="Registry and trust layer for AI agent skills.",
|
|
9
|
+
add_completion=False,
|
|
10
|
+
)
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
def init(
|
|
16
|
+
path: str = typer.Argument(".", help="Directory to scaffold (default: current)"),
|
|
17
|
+
):
|
|
18
|
+
"""Scaffold a skill.yaml and example code."""
|
|
19
|
+
init_cmd.run(path, console=console)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command()
|
|
23
|
+
def inspect(
|
|
24
|
+
path: str = typer.Argument(".", help="Directory containing skill.yaml"),
|
|
25
|
+
):
|
|
26
|
+
"""Show permissions and risk score for the skill in this directory."""
|
|
27
|
+
inspect_cmd.run(path, console=console)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
def publish(
|
|
32
|
+
path: str = typer.Argument(".", help="Directory containing skill.yaml"),
|
|
33
|
+
registry: str = typer.Option("http://localhost:8000", "--registry", "-r", help="Registry API URL"),
|
|
34
|
+
):
|
|
35
|
+
"""Publish the skill to the registry."""
|
|
36
|
+
publish_cmd.run(path, registry=registry, console=console)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def search(
|
|
41
|
+
query: str = typer.Argument("", help="Search query (optional)"),
|
|
42
|
+
registry: str = typer.Option("http://localhost:8000", "--registry", "-r", help="Registry API URL"),
|
|
43
|
+
):
|
|
44
|
+
"""List or search skills in the registry."""
|
|
45
|
+
search_cmd.run(query, registry=registry, console=console)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@app.command()
|
|
49
|
+
def install(
|
|
50
|
+
slug: str = typer.Argument(..., help="Skill slug to install (e.g. send-email)"),
|
|
51
|
+
registry: str = typer.Option("http://localhost:8000", "--registry", "-r", help="Registry API URL"),
|
|
52
|
+
):
|
|
53
|
+
"""Record an install for a skill (increments install count)."""
|
|
54
|
+
install_cmd.run(slug, registry=registry, console=console)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import init_cmd, inspect_cmd, publish_cmd, search_cmd, install_cmd
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
EXAMPLE_MANIFEST = """name: send_email
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
description: Send transactional emails
|
|
10
|
+
author: vincent
|
|
11
|
+
entrypoint: send_email.py
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
- network:outbound
|
|
15
|
+
- secrets:read
|
|
16
|
+
- filesystem:write:/tmp
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
EXAMPLE_ENTRYPOINT = '''"""Example skill entrypoint."""
|
|
20
|
+
# Your skill logic here.
|
|
21
|
+
def run():
|
|
22
|
+
print("Hello from send_email skill")
|
|
23
|
+
'''
|
|
24
|
+
|
|
25
|
+
def run(path: str, console: Console) -> None:
|
|
26
|
+
base = Path(path).resolve()
|
|
27
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
manifest_path = base / "skill.yaml"
|
|
29
|
+
if manifest_path.exists():
|
|
30
|
+
console.print("[yellow]skill.yaml already exists, skipping.[/yellow]")
|
|
31
|
+
else:
|
|
32
|
+
manifest_path.write_text(EXAMPLE_MANIFEST, encoding="utf-8")
|
|
33
|
+
console.print(f"[green]Created {manifest_path}[/green]")
|
|
34
|
+
entrypoint_name = "send_email.py"
|
|
35
|
+
entrypath = base / entrypoint_name
|
|
36
|
+
if not entrypath.exists():
|
|
37
|
+
entrypath.write_text(EXAMPLE_ENTRYPOINT, encoding="utf-8")
|
|
38
|
+
console.print(f"[green]Created {entrypath}[/green]")
|
|
39
|
+
console.print(Panel(
|
|
40
|
+
"Run [bold]forge inspect[/bold] to see permissions and risk.\n"
|
|
41
|
+
"Run [bold]forge publish[/bold] to publish to the registry.",
|
|
42
|
+
title="Next steps",
|
|
43
|
+
border_style="blue",
|
|
44
|
+
))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
# Inline risk logic (mirrors backend)
|
|
9
|
+
PERMISSION_RISK = {
|
|
10
|
+
"network": "MEDIUM", "network:outbound": "MEDIUM", "network:inbound": "MEDIUM",
|
|
11
|
+
"filesystem": "MEDIUM", "filesystem:read": "LOW", "filesystem:write": "MEDIUM",
|
|
12
|
+
"secrets": "HIGH", "secrets:read": "HIGH", "secrets:write": "HIGH",
|
|
13
|
+
"payments": "HIGH", "payments:read": "HIGH", "payments:write": "HIGH",
|
|
14
|
+
}
|
|
15
|
+
RISK_ORDER = {"LOW": 0, "MEDIUM": 1, "HIGH": 2}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def permission_to_risk(permission: str) -> str:
|
|
19
|
+
perm = permission.strip().lower()
|
|
20
|
+
if perm in PERMISSION_RISK:
|
|
21
|
+
return PERMISSION_RISK[perm]
|
|
22
|
+
if perm.startswith("network"):
|
|
23
|
+
return "MEDIUM"
|
|
24
|
+
if perm.startswith("filesystem"):
|
|
25
|
+
return "MEDIUM"
|
|
26
|
+
if perm.startswith("secrets") or perm.startswith("payments"):
|
|
27
|
+
return "HIGH"
|
|
28
|
+
return "LOW"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run(path: str, console: Console) -> None:
|
|
32
|
+
base = Path(path).resolve()
|
|
33
|
+
manifest_path = base / "skill.yaml"
|
|
34
|
+
if not manifest_path.exists():
|
|
35
|
+
console.print("[red]skill.yaml not found. Run 'forge init' first.[/red]")
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
|
38
|
+
perms = data.get("permissions") or []
|
|
39
|
+
if isinstance(perms, str):
|
|
40
|
+
perms = [p.strip() for p in perms.split(",") if p.strip()]
|
|
41
|
+
explained = []
|
|
42
|
+
max_level = "LOW"
|
|
43
|
+
for p in perms:
|
|
44
|
+
r = permission_to_risk(p)
|
|
45
|
+
explained.append((p, r))
|
|
46
|
+
if RISK_ORDER.get(r, 0) > RISK_ORDER.get(max_level, 0):
|
|
47
|
+
max_level = r
|
|
48
|
+
name = data.get("name", "unnamed")
|
|
49
|
+
version = data.get("version", "0.1.0")
|
|
50
|
+
console.print(Panel(f"[bold]{name}[/bold] v{version}", title="Skill", border_style="blue"))
|
|
51
|
+
console.print(f" [dim]Description:[/dim] {data.get('description') or '(none)'}")
|
|
52
|
+
console.print(f" [dim]Author:[/dim] {data.get('author') or '(none)'}")
|
|
53
|
+
console.print(f" [dim]Entrypoint:[/dim] {data.get('entrypoint') or '(none)'}")
|
|
54
|
+
risk_style = {"LOW": "green", "MEDIUM": "yellow", "HIGH": "red"}.get(max_level, "white")
|
|
55
|
+
console.print(f" [dim]Overall risk:[/dim] [{risk_style}]{max_level}[/{risk_style}]")
|
|
56
|
+
if explained:
|
|
57
|
+
table = Table(title="Permissions")
|
|
58
|
+
table.add_column("Permission", style="cyan")
|
|
59
|
+
table.add_column("Risk", style="magenta")
|
|
60
|
+
for p, r in explained:
|
|
61
|
+
rs = {"LOW": "green", "MEDIUM": "yellow", "HIGH": "red"}.get(r, "white")
|
|
62
|
+
table.add_row(p, f"[{rs}]{r}[/{rs}]")
|
|
63
|
+
console.print(table)
|
|
64
|
+
else:
|
|
65
|
+
console.print("[dim]No permissions declared.[/dim]")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.panel import Panel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run(slug: str, registry: str, console: Console) -> None:
|
|
8
|
+
url = f"{registry.rstrip('/')}/skills/{slug}/install"
|
|
9
|
+
try:
|
|
10
|
+
with httpx.Client(timeout=10.0) as client:
|
|
11
|
+
r = client.post(url)
|
|
12
|
+
r.raise_for_status()
|
|
13
|
+
out = r.json()
|
|
14
|
+
except httpx.ConnectError:
|
|
15
|
+
console.print(f"[red]Could not reach registry at {registry}. Is the API running?[/red]")
|
|
16
|
+
raise SystemExit(1)
|
|
17
|
+
except httpx.HTTPStatusError as e:
|
|
18
|
+
if e.response.status_code == 404:
|
|
19
|
+
console.print(f"[red]Skill '{slug}' not found.[/red]")
|
|
20
|
+
else:
|
|
21
|
+
console.print(f"[red]Install failed: {e.response.status_code}[/red]")
|
|
22
|
+
raise SystemExit(1)
|
|
23
|
+
console.print(Panel(
|
|
24
|
+
f"Installed [bold]{out.get('name')}[/bold] ([cyan]{out.get('slug')}[/cyan])\n"
|
|
25
|
+
f"Install count: {out.get('installs', 0)}",
|
|
26
|
+
title="Installed",
|
|
27
|
+
border_style="green",
|
|
28
|
+
))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import yaml
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run(path: str, registry: str, console: Console) -> None:
|
|
10
|
+
base = Path(path).resolve()
|
|
11
|
+
manifest_path = base / "skill.yaml"
|
|
12
|
+
if not manifest_path.exists():
|
|
13
|
+
console.print("[red]skill.yaml not found. Run 'forge init' first.[/red]")
|
|
14
|
+
raise SystemExit(1)
|
|
15
|
+
data = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) or {}
|
|
16
|
+
perms = data.get("permissions") or []
|
|
17
|
+
if isinstance(perms, str):
|
|
18
|
+
perms = [p.strip() for p in perms.split(",") if p.strip()]
|
|
19
|
+
payload = {
|
|
20
|
+
"name": data.get("name", "unnamed"),
|
|
21
|
+
"version": data.get("version", "0.1.0"),
|
|
22
|
+
"description": data.get("description"),
|
|
23
|
+
"author": data.get("author"),
|
|
24
|
+
"entrypoint": data.get("entrypoint"),
|
|
25
|
+
"permissions": perms,
|
|
26
|
+
"manifest": data,
|
|
27
|
+
}
|
|
28
|
+
url = f"{registry.rstrip('/')}/skills/publish"
|
|
29
|
+
try:
|
|
30
|
+
with httpx.Client(timeout=10.0) as client:
|
|
31
|
+
r = client.post(url, json=payload)
|
|
32
|
+
r.raise_for_status()
|
|
33
|
+
out = r.json()
|
|
34
|
+
except httpx.ConnectError:
|
|
35
|
+
console.print(f"[red]Could not reach registry at {registry}. Is the API running?[/red]")
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
except httpx.HTTPStatusError as e:
|
|
38
|
+
console.print(f"[red]Publish failed: {e.response.status_code} {e.response.text}[/red]")
|
|
39
|
+
raise SystemExit(1)
|
|
40
|
+
slug = out.get("slug", "?")
|
|
41
|
+
console.print(Panel(
|
|
42
|
+
f"Published [bold]{out.get('name')}[/bold] as [cyan]{slug}[/cyan]\n"
|
|
43
|
+
f"Risk: [bold]{out.get('risk_score', 'LOW')}[/bold]\n"
|
|
44
|
+
f"View: {registry.rstrip('/')}/skills/{slug}",
|
|
45
|
+
title="Published",
|
|
46
|
+
border_style="green",
|
|
47
|
+
))
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run(query: str, registry: str, console: Console) -> None:
|
|
8
|
+
url = f"{registry.rstrip('/')}/skills"
|
|
9
|
+
try:
|
|
10
|
+
with httpx.Client(timeout=10.0) as client:
|
|
11
|
+
r = client.get(url)
|
|
12
|
+
r.raise_for_status()
|
|
13
|
+
skills = r.json()
|
|
14
|
+
except httpx.ConnectError:
|
|
15
|
+
console.print(f"[red]Could not reach registry at {registry}. Is the API running?[/red]")
|
|
16
|
+
raise SystemExit(1)
|
|
17
|
+
except httpx.HTTPStatusError as e:
|
|
18
|
+
console.print(f"[red]Search failed: {e.response.status_code}[/red]")
|
|
19
|
+
raise SystemExit(1)
|
|
20
|
+
if query:
|
|
21
|
+
q = query.lower()
|
|
22
|
+
skills = [s for s in skills if q in (s.get("name") or "").lower() or q in (s.get("slug") or "").lower() or q in (s.get("description") or "").lower()]
|
|
23
|
+
if not skills:
|
|
24
|
+
console.print("[dim]No skills found.[/dim]")
|
|
25
|
+
return
|
|
26
|
+
table = Table(title="Skills")
|
|
27
|
+
table.add_column("Slug", style="cyan")
|
|
28
|
+
table.add_column("Name", style="green")
|
|
29
|
+
table.add_column("Risk", style="yellow")
|
|
30
|
+
table.add_column("Installs")
|
|
31
|
+
for s in skills:
|
|
32
|
+
table.add_row(
|
|
33
|
+
s.get("slug", "?"),
|
|
34
|
+
s.get("name", "?"),
|
|
35
|
+
s.get("risk_score", "LOW"),
|
|
36
|
+
str(s.get("installs", 0)),
|
|
37
|
+
)
|
|
38
|
+
console.print(table)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentforge-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Registry and trust layer for AI agent skills"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typer>=0.9.0",
|
|
13
|
+
"rich>=13.0.0",
|
|
14
|
+
"httpx>=0.25.0",
|
|
15
|
+
"pyyaml>=6.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
forge = "forge.cli:app"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["."]
|
|
23
|
+
include = ["forge*"]
|