agentpowers 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.
Files changed (58) hide show
  1. agentpowers-0.1.0/.gitignore +28 -0
  2. agentpowers-0.1.0/LICENSE +21 -0
  3. agentpowers-0.1.0/PKG-INFO +61 -0
  4. agentpowers-0.1.0/README.md +31 -0
  5. agentpowers-0.1.0/agentpowers_cli/__init__.py +0 -0
  6. agentpowers-0.1.0/agentpowers_cli/banner.py +56 -0
  7. agentpowers-0.1.0/agentpowers_cli/commands/__init__.py +0 -0
  8. agentpowers-0.1.0/agentpowers_cli/commands/auth.py +274 -0
  9. agentpowers-0.1.0/agentpowers_cli/commands/claim.py +69 -0
  10. agentpowers-0.1.0/agentpowers_cli/commands/detail.py +388 -0
  11. agentpowers-0.1.0/agentpowers_cli/commands/install.py +392 -0
  12. agentpowers-0.1.0/agentpowers_cli/commands/profile.py +103 -0
  13. agentpowers-0.1.0/agentpowers_cli/commands/publish.py +239 -0
  14. agentpowers-0.1.0/agentpowers_cli/commands/republish.py +76 -0
  15. agentpowers-0.1.0/agentpowers_cli/commands/scan.py +364 -0
  16. agentpowers-0.1.0/agentpowers_cli/commands/search.py +253 -0
  17. agentpowers-0.1.0/agentpowers_cli/commands/status.py +134 -0
  18. agentpowers-0.1.0/agentpowers_cli/commands/uninstall.py +96 -0
  19. agentpowers-0.1.0/agentpowers_cli/commands/unpublish.py +85 -0
  20. agentpowers-0.1.0/agentpowers_cli/commands/update.py +553 -0
  21. agentpowers-0.1.0/agentpowers_cli/commands/verify.py +50 -0
  22. agentpowers-0.1.0/agentpowers_cli/main.py +46 -0
  23. agentpowers-0.1.0/agentpowers_cli/services/__init__.py +0 -0
  24. agentpowers-0.1.0/agentpowers_cli/services/api_client.py +221 -0
  25. agentpowers-0.1.0/agentpowers_cli/services/auth_store.py +70 -0
  26. agentpowers-0.1.0/agentpowers_cli/services/config.py +14 -0
  27. agentpowers-0.1.0/agentpowers_cli/services/content_hasher.py +83 -0
  28. agentpowers-0.1.0/agentpowers_cli/services/external_installer.py +207 -0
  29. agentpowers-0.1.0/agentpowers_cli/services/installer.py +183 -0
  30. agentpowers-0.1.0/agentpowers_cli/services/packager.py +122 -0
  31. agentpowers-0.1.0/agentpowers_cli/services/pin_manager.py +89 -0
  32. agentpowers-0.1.0/pyproject.toml +59 -0
  33. agentpowers-0.1.0/skills/find-skills/SKILL.md +85 -0
  34. agentpowers-0.1.0/tests/__init__.py +0 -0
  35. agentpowers-0.1.0/tests/conftest.py +17 -0
  36. agentpowers-0.1.0/tests/test_api_client.py +180 -0
  37. agentpowers-0.1.0/tests/test_auth_commands.py +402 -0
  38. agentpowers-0.1.0/tests/test_auth_store.py +131 -0
  39. agentpowers-0.1.0/tests/test_banner.py +81 -0
  40. agentpowers-0.1.0/tests/test_claim.py +223 -0
  41. agentpowers-0.1.0/tests/test_content_hasher.py +85 -0
  42. agentpowers-0.1.0/tests/test_detail_command.py +468 -0
  43. agentpowers-0.1.0/tests/test_external_install.py +384 -0
  44. agentpowers-0.1.0/tests/test_install_command.py +623 -0
  45. agentpowers-0.1.0/tests/test_installer_zipslip.py +111 -0
  46. agentpowers-0.1.0/tests/test_pin_manager.py +104 -0
  47. agentpowers-0.1.0/tests/test_profile.py +104 -0
  48. agentpowers-0.1.0/tests/test_publish_command.py +207 -0
  49. agentpowers-0.1.0/tests/test_publish_display_name_required.py +166 -0
  50. agentpowers-0.1.0/tests/test_publish_update.py +566 -0
  51. agentpowers-0.1.0/tests/test_republish_command.py +147 -0
  52. agentpowers-0.1.0/tests/test_scan_command.py +234 -0
  53. agentpowers-0.1.0/tests/test_search_command.py +584 -0
  54. agentpowers-0.1.0/tests/test_status_command.py +163 -0
  55. agentpowers-0.1.0/tests/test_uninstall_command.py +156 -0
  56. agentpowers-0.1.0/tests/test_unpublish_command.py +147 -0
  57. agentpowers-0.1.0/tests/test_update_command.py +1588 -0
  58. agentpowers-0.1.0/tests/test_verify.py +94 -0
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+
9
+ # Environment
10
+ .env
11
+ *.db
12
+
13
+ # IDE
14
+ .vscode/
15
+ .idea/
16
+
17
+ # OS
18
+ .DS_Store
19
+
20
+ # Testing
21
+ .pytest_cache/
22
+ .coverage
23
+ htmlcov/
24
+
25
+ # Ruff
26
+ .ruff_cache/
27
+ .gstack/
28
+ .claude/skills/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AgentPowers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentpowers
3
+ Version: 0.1.0
4
+ Summary: AgentPowers CLI — discover, install, and publish marketplace skills
5
+ Project-URL: Homepage, https://agentpowers.ai
6
+ Project-URL: Repository, https://github.com/AgentPowers-AI/agentpowers-app
7
+ Project-URL: Documentation, https://docs.agentpowers.ai
8
+ Project-URL: Bug Tracker, https://github.com/AgentPowers-AI/agentpowers-app/issues
9
+ Author: Nate Ritter
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,claude,cli,marketplace,skills
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: httpx>=0.28.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: typer>=0.15.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.9.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # AgentPowers CLI
32
+
33
+ The `ap` command-line tool for the AgentPowers marketplace.
34
+
35
+ ## Status
36
+
37
+ **Phase 1E: Complete** -- 73 passing tests. All core commands implemented including `ap claim`.
38
+
39
+ ## Commands
40
+
41
+ - `ap login` -- Open browser for Clerk auth, store JWT locally
42
+ - `ap logout` -- Remove stored credentials
43
+ - `ap whoami` -- Show current user info
44
+ - `ap search <query>` -- Search the marketplace (Rich table output)
45
+ - `ap install <slug> [--code XXXX]` -- Install a skill or agent (with optional license code)
46
+ - `ap publish [--price N] [--dir .] [--category dev]` -- Package and publish a skill or agent
47
+ - `ap claim <slug>` -- Claim ownership of a ClawHub-imported skill
48
+
49
+ ## Development
50
+
51
+ ```bash
52
+ cd agentpowers-cli
53
+ uv venv .venv && source .venv/bin/activate
54
+ uv pip install -e ".[dev]"
55
+ ap --help
56
+ pytest tests/ -v # Run tests
57
+ ```
58
+
59
+ ## Auth
60
+
61
+ Credentials stored at `~/.agentpowers/auth.json` (permissions: 600). Shared with the Claude Code plugin's MCP server.
@@ -0,0 +1,31 @@
1
+ # AgentPowers CLI
2
+
3
+ The `ap` command-line tool for the AgentPowers marketplace.
4
+
5
+ ## Status
6
+
7
+ **Phase 1E: Complete** -- 73 passing tests. All core commands implemented including `ap claim`.
8
+
9
+ ## Commands
10
+
11
+ - `ap login` -- Open browser for Clerk auth, store JWT locally
12
+ - `ap logout` -- Remove stored credentials
13
+ - `ap whoami` -- Show current user info
14
+ - `ap search <query>` -- Search the marketplace (Rich table output)
15
+ - `ap install <slug> [--code XXXX]` -- Install a skill or agent (with optional license code)
16
+ - `ap publish [--price N] [--dir .] [--category dev]` -- Package and publish a skill or agent
17
+ - `ap claim <slug>` -- Claim ownership of a ClawHub-imported skill
18
+
19
+ ## Development
20
+
21
+ ```bash
22
+ cd agentpowers-cli
23
+ uv venv .venv && source .venv/bin/activate
24
+ uv pip install -e ".[dev]"
25
+ ap --help
26
+ pytest tests/ -v # Run tests
27
+ ```
28
+
29
+ ## Auth
30
+
31
+ Credentials stored at `~/.agentpowers/auth.json` (permissions: 600). Shared with the Claude Code plugin's MCP server.
File without changes
@@ -0,0 +1,56 @@
1
+ """AgentPowers CLI banner — brand mark and version display."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import version as pkg_version
6
+
7
+ from rich.console import Console
8
+
9
+ # Brand teal: #5fbab8
10
+ TEAL = "#5fbab8"
11
+
12
+
13
+ def _get_version() -> str:
14
+ """Return the installed CLI version, falling back to 0.1.0."""
15
+ try:
16
+ return pkg_version("agentpowers")
17
+ except Exception:
18
+ return "0.1.0"
19
+
20
+
21
+ def print_banner(console: Console | None = None) -> None:
22
+ """Print the AgentPowers brand banner to the console.
23
+
24
+ Three geometric teal shapes forming a stylized 'A' mark.
25
+
26
+ Args:
27
+ console: Rich Console instance. Creates one if not provided.
28
+ """
29
+ if console is None:
30
+ console = Console()
31
+
32
+ ver = _get_version()
33
+
34
+ console.print()
35
+ console.print(f" [{TEAL}] ████[/{TEAL}]")
36
+ console.print(
37
+ f" [{TEAL}] ████[/{TEAL}]"
38
+ f" [bold]AgentPowers[/bold] [dim]v{ver}[/dim]"
39
+ )
40
+ console.print(
41
+ f" [{TEAL}] ████[/{TEAL}]"
42
+ f" [dim]Premium Claude skills & agents[/dim]"
43
+ )
44
+ console.print(
45
+ f" [{TEAL}] █[/{TEAL}]"
46
+ f" [{TEAL}]████[/{TEAL}]"
47
+ f" [dim]agentpowers.ai[/dim]"
48
+ )
49
+ console.print(
50
+ f" [{TEAL}]███[/{TEAL}]"
51
+ f" [{TEAL}]████[/{TEAL}]"
52
+ )
53
+ console.print(f" [{TEAL}] ██████[/{TEAL}]")
54
+ console.print(f" [{TEAL}] ████[/{TEAL}]")
55
+ console.print(f" [{TEAL}] ██[/{TEAL}]")
56
+ console.print()
File without changes
@@ -0,0 +1,274 @@
1
+ """Authentication commands for the AgentPowers CLI.
2
+
3
+ Provides login (browser-based OAuth via Clerk), logout, and whoami commands.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import socket
9
+ import threading
10
+ import webbrowser
11
+ from http.server import BaseHTTPRequestHandler, HTTPServer
12
+ from typing import Any
13
+ from urllib.parse import parse_qs, urlparse
14
+
15
+ import typer
16
+ from rich.console import Console
17
+
18
+ from agentpowers_cli.banner import print_banner
19
+ from agentpowers_cli.services.api_client import APIClient, APIError
20
+ from agentpowers_cli.services.auth_store import delete_token, save_token
21
+ from agentpowers_cli.services.config import (
22
+ AUTH_CALLBACK_HOST,
23
+ CLERK_FRONTEND_URL,
24
+ SITE_URL,
25
+ )
26
+
27
+ console = Console()
28
+
29
+ _PAGE_STYLE = """\
30
+ <style>
31
+ * { margin: 0; padding: 0; box-sizing: border-box; }
32
+ body {
33
+ font-family: 'DM Sans', system-ui, -apple-system, sans-serif;
34
+ background: #0a0f1a;
35
+ color: #e2e8f0;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ min-height: 100vh;
40
+ }
41
+ .card {
42
+ text-align: center;
43
+ padding: 3rem 2.5rem;
44
+ max-width: 420px;
45
+ border: 1px solid rgba(56, 189, 184, 0.2);
46
+ border-radius: 16px;
47
+ background: rgba(15, 23, 42, 0.8);
48
+ box-shadow: 0 0 40px rgba(56, 189, 184, 0.08);
49
+ }
50
+ .icon {
51
+ width: 56px; height: 56px; margin: 0 auto 1.5rem;
52
+ border-radius: 50%;
53
+ display: flex; align-items: center; justify-content: center;
54
+ }
55
+ .icon-success { background: rgba(56, 189, 184, 0.15); }
56
+ .icon-error { background: rgba(239, 68, 68, 0.15); }
57
+ .icon svg { width: 28px; height: 28px; }
58
+ h1 {
59
+ font-family: 'Space Grotesk', 'DM Sans', system-ui, sans-serif;
60
+ font-size: 1.5rem;
61
+ font-weight: 600;
62
+ margin-bottom: 0.75rem;
63
+ }
64
+ p {
65
+ font-size: 0.95rem;
66
+ color: #94a3b8;
67
+ line-height: 1.5;
68
+ }
69
+ .brand {
70
+ margin-top: 2rem;
71
+ font-size: 0.8rem;
72
+ color: #475569;
73
+ letter-spacing: 0.05em;
74
+ }
75
+ .brand span { color: #38bdb8; }
76
+ </style>
77
+ """
78
+
79
+ _SUCCESS_HTML = (
80
+ "<!DOCTYPE html>"
81
+ "<html lang='en'><head><meta charset='utf-8'>"
82
+ "<meta name='viewport' content='width=device-width,initial-scale=1'>"
83
+ "<title>Logged in to AgentPowers!</title>"
84
+ f"{_PAGE_STYLE}</head><body>"
85
+ "<div class='card'>"
86
+ "<div class='icon icon-success'>"
87
+ "<svg viewBox='0 0 24 24' fill='none' stroke='#38bdb8' stroke-width='2.5'"
88
+ " stroke-linecap='round' stroke-linejoin='round'>"
89
+ "<polyline points='20 6 9 17 4 12'/></svg></div>"
90
+ "<h1>Logged in to AgentPowers!</h1>"
91
+ "<p>You can close this tab and return to the terminal.</p>"
92
+ "<div class='brand'><span>agent</span>powers</div>"
93
+ "</div></body></html>"
94
+ )
95
+
96
+ _ERROR_HTML = (
97
+ "<!DOCTYPE html>"
98
+ "<html lang='en'><head><meta charset='utf-8'>"
99
+ "<meta name='viewport' content='width=device-width,initial-scale=1'>"
100
+ "<title>Login Error</title>"
101
+ f"{_PAGE_STYLE}</head><body>"
102
+ "<div class='card'>"
103
+ "<div class='icon icon-error'>"
104
+ "<svg viewBox='0 0 24 24' fill='none' stroke='#ef4444' stroke-width='2.5'"
105
+ " stroke-linecap='round' stroke-linejoin='round'>"
106
+ "<line x1='18' y1='6' x2='6' y2='18'/>"
107
+ "<line x1='6' y1='6' x2='18' y2='18'/></svg></div>"
108
+ "<h1>Login Failed</h1>"
109
+ "<p>Missing token. Please try logging in again from the terminal.</p>"
110
+ "<div class='brand'><span>agent</span>powers</div>"
111
+ "</div></body></html>"
112
+ )
113
+
114
+
115
+ def get_api_client() -> APIClient:
116
+ """Create and return an APIClient instance.
117
+
118
+ Returns:
119
+ A configured APIClient.
120
+ """
121
+ return APIClient()
122
+
123
+
124
+ def _find_free_port() -> int:
125
+ """Find an available TCP port on localhost.
126
+
127
+ Returns:
128
+ An available port number.
129
+ """
130
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
131
+ sock.bind(("", 0))
132
+ return sock.getsockname()[1]
133
+
134
+
135
+ def open_browser(url: str) -> None:
136
+ """Open a URL in the user's default browser.
137
+
138
+ Args:
139
+ url: The URL to open.
140
+ """
141
+ webbrowser.open(url)
142
+
143
+
144
+ class _CallbackHandler(BaseHTTPRequestHandler):
145
+ """HTTP request handler for the OAuth callback.
146
+
147
+ Expects a GET request to /callback?token=XXX and stores the
148
+ token on the server instance.
149
+ """
150
+
151
+ def do_GET(self) -> None:
152
+ """Handle the GET callback from Clerk auth redirect."""
153
+ parsed = urlparse(self.path)
154
+ if parsed.path != "/callback":
155
+ self.send_response(404)
156
+ self.end_headers()
157
+ return
158
+
159
+ params = parse_qs(parsed.query)
160
+ token_values = params.get("token")
161
+
162
+ if token_values:
163
+ server: Any = self.server
164
+ server.received_token = token_values[0]
165
+ self.send_response(200)
166
+ self.send_header("Content-Type", "text/html; charset=utf-8")
167
+ self.end_headers()
168
+ self.wfile.write(_SUCCESS_HTML.encode())
169
+ else:
170
+ self.send_response(400)
171
+ self.send_header("Content-Type", "text/html; charset=utf-8")
172
+ self.end_headers()
173
+ self.wfile.write(_ERROR_HTML.encode())
174
+
175
+ def log_message(self, format: str, *args: Any) -> None:
176
+ """Suppress default HTTP server logging."""
177
+
178
+
179
+ def start_auth_server(port: int, timeout: int = 120) -> str | None:
180
+ """Start a temporary HTTP server and wait for the auth callback.
181
+
182
+ Blocks until a token is received or the timeout expires.
183
+
184
+ Args:
185
+ port: The port to listen on.
186
+ timeout: Maximum seconds to wait for the callback.
187
+
188
+ Returns:
189
+ The received JWT token, or None if the timeout expired.
190
+ """
191
+ server = HTTPServer((AUTH_CALLBACK_HOST, port), _CallbackHandler)
192
+ server.received_token = None # type: ignore[attr-defined]
193
+ server.timeout = timeout
194
+
195
+ # Run in a thread so we can enforce the overall timeout
196
+ thread = threading.Thread(target=server.handle_request, daemon=True)
197
+ thread.start()
198
+ thread.join(timeout=timeout)
199
+
200
+ server.server_close()
201
+ return server.received_token # type: ignore[attr-defined]
202
+
203
+
204
+ def login() -> None:
205
+ """Authenticate with AgentPowers via browser-based Clerk login.
206
+
207
+ Opens the default browser to the Clerk sign-in page. A temporary
208
+ local HTTP server receives the callback with the JWT token. The
209
+ short-lived JWT is exchanged for a long-lived CLI token.
210
+ """
211
+ port = _find_free_port()
212
+ callback_url = f"http://{AUTH_CALLBACK_HOST}:{port}/callback"
213
+ bridge_url = f"{SITE_URL}/auth/cli-callback?redirect_to={callback_url}"
214
+ auth_url = f"{CLERK_FRONTEND_URL}/sign-in?redirect_url={bridge_url}"
215
+
216
+ console.print(f"Opening browser to sign in...\n [dim]{auth_url}[/dim]")
217
+ open_browser(auth_url)
218
+
219
+ console.print(f"Waiting for authentication (listening on port {port})...")
220
+ clerk_jwt = start_auth_server(port)
221
+
222
+ if clerk_jwt is None:
223
+ console.print("[red]Login timed out.[/red] Please try again.")
224
+ raise typer.Exit(code=1)
225
+
226
+ # Exchange short-lived Clerk JWT for long-lived CLI token
227
+ try:
228
+ client = get_api_client()
229
+ # Temporarily save the Clerk JWT so the client can use it
230
+ save_token(clerk_jwt)
231
+ result = client.post("/v1/auth/cli-token", auth=True)
232
+ cli_token = result["token"]
233
+ save_token(cli_token)
234
+ console.print("[green]Logged in successfully![/green]")
235
+ except (APIError, Exception):
236
+ # Fallback: save the raw JWT (will expire, but login succeeded)
237
+ save_token(clerk_jwt)
238
+ console.print(
239
+ "[green]Logged in successfully![/green] "
240
+ "[dim](session token — may expire sooner)[/dim]"
241
+ )
242
+
243
+
244
+ def logout() -> None:
245
+ """Log out by revoking server-side token and removing local credentials."""
246
+ try:
247
+ client = get_api_client()
248
+ client.post("/v1/auth/revoke-cli-token", auth=True)
249
+ except (APIError, Exception):
250
+ pass # Best-effort revocation; always delete locally
251
+ delete_token()
252
+ console.print("Logged out.")
253
+
254
+
255
+ def whoami() -> None:
256
+ """Display the currently authenticated user's information."""
257
+ print_banner(console)
258
+
259
+ client = get_api_client()
260
+ try:
261
+ user = client.get("/v1/auth/me", auth=True)
262
+ except APIError as exc:
263
+ if exc.status_code == 401:
264
+ console.print("[red]Not logged in.[/red] Run `ap login` first.")
265
+ else:
266
+ console.print(f"[red]Error:[/red] {exc}")
267
+ raise typer.Exit(code=1)
268
+
269
+ email = user.get("email", "unknown")
270
+ name = user.get("name", "")
271
+
272
+ console.print(f"[bold]Email:[/bold] {email}")
273
+ if name:
274
+ console.print(f"[bold]Name:[/bold] {name}")
@@ -0,0 +1,69 @@
1
+ """Claim command — submit ownership claim for a ClawHub skill."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+ from rich.console import Console
7
+
8
+ from agentpowers_cli.services.api_client import APIClient, APIError
9
+
10
+ console = Console()
11
+
12
+
13
+ def get_api_client() -> APIClient:
14
+ """Create and return an APIClient instance.
15
+
16
+ Returns:
17
+ A configured APIClient.
18
+ """
19
+ return APIClient()
20
+
21
+
22
+ def claim(
23
+ slug: str = typer.Argument(help="Slug of the ClawHub skill to claim"),
24
+ ) -> None:
25
+ """Claim ownership of a ClawHub-imported skill.
26
+
27
+ Submits a POST request to /v1/skills/{slug}/claim.
28
+ The API verifies GitHub identity and returns an approval status,
29
+ sandbox (manual review), or rejection with a verification score.
30
+
31
+ Args:
32
+ slug: The URL slug identifying the ClawHub skill to claim.
33
+ """
34
+ client = get_api_client()
35
+
36
+ try:
37
+ result = client.post(
38
+ f"/v1/skills/{slug}/claim",
39
+ auth=True,
40
+ )
41
+ except APIError as exc:
42
+ if exc.status_code == 401:
43
+ console.print("[red]Not logged in.[/red] Run `ap login` first.")
44
+ elif exc.status_code == 404:
45
+ console.print(f"[red]Skill '{slug}' not found.[/red]")
46
+ elif exc.status_code == 409:
47
+ console.print(
48
+ f"[yellow]Skill '{slug}' is already claimed.[/yellow]",
49
+ )
50
+ else:
51
+ console.print(f"[red]Error:[/red] {exc}")
52
+ raise typer.Exit(code=1)
53
+
54
+ status = result.get("status", "unknown")
55
+ score = result.get("verification_score", 0)
56
+ message = result.get("message", "")
57
+
58
+ if status == "approved":
59
+ console.print(f"[green]Approved![/green] {message}")
60
+ console.print(f" Score: {score}")
61
+ elif status == "sandbox":
62
+ console.print(f"[yellow]Under review.[/yellow] {message}")
63
+ console.print(f" Score: {score}")
64
+ console.print(" An admin will review your claim shortly.")
65
+ elif status == "rejected":
66
+ console.print(f"[red]Rejected.[/red] {message}")
67
+ console.print(f" Score: {score}")
68
+ else:
69
+ console.print(f"Status: {status} — {message}")