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.
@@ -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,2 @@
1
+ [console_scripts]
2
+ forge = forge.cli:app
@@ -0,0 +1,4 @@
1
+ typer>=0.9.0
2
+ rich>=13.0.0
3
+ httpx>=0.25.0
4
+ pyyaml>=6.0
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Run the CLI via python -m forge."""
2
+ from forge.cli import app
3
+
4
+ if __name__ == "__main__":
5
+ app()
@@ -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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+