agentskills-runtime 0.0.1__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.
@@ -0,0 +1,59 @@
1
+ """Python SDK for AgentSkills Runtime."""
2
+
3
+ __version__ = "0.0.1"
4
+ RUNTIME_VERSION = "0.0.13"
5
+
6
+ from agent_skills.models import (
7
+ ApiError,
8
+ AvailableSkillInfo,
9
+ ClientConfig,
10
+ EnvironmentConfig,
11
+ MultiSkillRepoResponse,
12
+ RuntimeOptions,
13
+ RuntimeStatus,
14
+ Skill,
15
+ SkillExecutionResult,
16
+ SkillInstallOptions,
17
+ SkillInstallResponse,
18
+ SkillInstallResult,
19
+ SkillListResponse,
20
+ SkillMetadata,
21
+ SkillSearchResult,
22
+ SkillSearchResultItem,
23
+ ToolDefinition,
24
+ ToolParameter,
25
+ )
26
+ from agent_skills.client import SkillsClient, create_client, handle_api_error
27
+ from agent_skills.runtime import RuntimeManager
28
+ from agent_skills.utils import define_skill, get_config, get_sdk_version, get_runtime_version
29
+
30
+ __all__ = [
31
+ "__version__",
32
+ "RUNTIME_VERSION",
33
+ "ApiError",
34
+ "AvailableSkillInfo",
35
+ "ClientConfig",
36
+ "EnvironmentConfig",
37
+ "MultiSkillRepoResponse",
38
+ "RuntimeManager",
39
+ "RuntimeOptions",
40
+ "RuntimeStatus",
41
+ "Skill",
42
+ "SkillExecutionResult",
43
+ "SkillInstallOptions",
44
+ "SkillInstallResponse",
45
+ "SkillInstallResult",
46
+ "SkillListResponse",
47
+ "SkillMetadata",
48
+ "SkillSearchResult",
49
+ "SkillSearchResultItem",
50
+ "SkillsClient",
51
+ "ToolDefinition",
52
+ "ToolParameter",
53
+ "create_client",
54
+ "define_skill",
55
+ "get_config",
56
+ "get_runtime_version",
57
+ "get_sdk_version",
58
+ "handle_api_error",
59
+ ]
agent_skills/cli.py ADDED
@@ -0,0 +1,591 @@
1
+ """Command-line interface for AgentSkills SDK."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import click
9
+ import questionary
10
+ import requests
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+
14
+ from agent_skills import RUNTIME_VERSION, __version__
15
+ from agent_skills.client import SkillsClient, create_client, handle_api_error
16
+ from agent_skills.models import (
17
+ RuntimeOptions,
18
+ Skill,
19
+ SkillInstallOptions,
20
+ SkillSearchResultItem,
21
+ ToolDefinition,
22
+ )
23
+ from agent_skills.runtime import RuntimeManager
24
+
25
+ console = Console()
26
+
27
+
28
+ def get_client() -> SkillsClient:
29
+ """Get a configured SkillsClient instance."""
30
+ from agent_skills.models import ClientConfig
31
+ return create_client(
32
+ ClientConfig(base_url=os.environ.get("SKILL_RUNTIME_API_URL")),
33
+ )
34
+
35
+
36
+ def print_success(message: str) -> None:
37
+ """Print a success message."""
38
+ console.print(f"[green]✓[/green] {message}")
39
+
40
+
41
+ def print_error(message: str) -> None:
42
+ """Print an error message."""
43
+ console.stderr = sys.stderr
44
+ console.print(f"[red]✗[/red] {message}")
45
+ console.stderr = None
46
+
47
+
48
+ def print_skill(skill: Skill) -> None:
49
+ """Print skill information."""
50
+ console.print(f"\n[bold cyan]{skill.name}[/bold cyan] [dim]({skill.version})[/dim]")
51
+ console.print(f"[dim] Description:[/dim] {skill.description}")
52
+ console.print(f"[dim] Author:[/dim] {skill.author}")
53
+ if skill.source_path:
54
+ console.print(f"[dim] Path:[/dim] {skill.source_path}")
55
+
56
+
57
+ def print_skill_short(skill: Skill) -> None:
58
+ """Print a short skill summary."""
59
+ console.print(f" [cyan]{skill.name}[/cyan] [dim]({skill.version})[/dim] - {skill.description}")
60
+
61
+
62
+ def print_search_result(item: SkillSearchResultItem) -> None:
63
+ """Print a search result item."""
64
+ stars = item.stars or item.stargazers_count or 0
65
+ source_icon = {
66
+ "github": "🐙",
67
+ "gitee": "🏠",
68
+ "atomgit": "⚛️",
69
+ }.get(item.source, "📦")
70
+
71
+ console.print(f"{source_icon} [cyan]{item.full_name}[/cyan] [yellow]⭐ {stars}[/yellow]")
72
+ console.print(f" [dim]{item.description or 'No description'}[/dim]")
73
+ console.print(f" [dim]Clone:[/dim] {item.clone_url}")
74
+ console.print()
75
+
76
+
77
+ @click.group()
78
+ @click.version_option(version=__version__, prog_name="skills")
79
+ @click.option("--api-url", "-u", help="API server URL", envvar="SKILL_RUNTIME_API_URL")
80
+ @click.pass_context
81
+ def main(ctx: click.Context, api_url: Optional[str]) -> None:
82
+ """AgentSkills Runtime CLI - Install, manage, and execute AI agent skills."""
83
+ ctx.ensure_object(dict)
84
+ if api_url:
85
+ os.environ["SKILL_RUNTIME_API_URL"] = api_url
86
+
87
+
88
+ @main.command()
89
+ @click.argument("query", required=False)
90
+ @click.option("--limit", "-l", default=10, help="Maximum number of results")
91
+ @click.option("--source", "-s", default="all", help="Search source (all, github, gitee, atomgit)")
92
+ def find(query: Optional[str], limit: int, source: str) -> None:
93
+ """Search for skills from GitHub, Gitee, and AtomGit."""
94
+ try:
95
+ client = get_client()
96
+
97
+ if not query:
98
+ query = questionary.text(
99
+ "What kind of skill are you looking for?",
100
+ default="",
101
+ ).ask()
102
+
103
+ if not query:
104
+ console.print("[yellow]No search query provided. Showing all installed skills...[/yellow]\n")
105
+ result = client.list_skills(limit=limit)
106
+ if not result.skills:
107
+ console.print("[dim]No skills found.[/dim]")
108
+ return
109
+ for skill in result.skills:
110
+ print_skill_short(skill)
111
+ return
112
+
113
+ with console.status("[bold green]Searching for skills..."):
114
+ result = client.search_skills(query=query, source=source, limit=limit)
115
+
116
+ if not result.results:
117
+ console.print(f"[yellow]No skills found matching '{query}'.[/yellow]")
118
+ console.print("[dim]\nTry different keywords or search from other sources.[/dim]")
119
+ return
120
+
121
+ console.print(f"[bold]\nFound {result.total_count} skill(s) matching '{query}':[/bold]\n")
122
+
123
+ for item in result.results:
124
+ print_search_result(item)
125
+
126
+ console.print("[dim]Install with: skills add <clone_url>[/dim]")
127
+
128
+ except Exception as e:
129
+ print_error(handle_api_error(e).errmsg)
130
+ sys.exit(1)
131
+
132
+
133
+ @main.command("add")
134
+ @click.argument("source")
135
+ @click.option("--global", "global_", is_flag=True, help="Install globally (user-level)")
136
+ @click.option("--path", "-p", help="Local path to skill")
137
+ @click.option("--branch", "-b", help="Git branch name")
138
+ @click.option("--tag", "-t", help="Git tag name")
139
+ @click.option("--commit", "-c", help="Git commit ID")
140
+ @click.option("--name", "-n", help="Skill name override")
141
+ @click.option("--validate/--no-validate", default=True, help="Validate skill before installation")
142
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompts")
143
+ def add_skill(
144
+ source: str,
145
+ global_: bool,
146
+ path: Optional[str],
147
+ branch: Optional[str],
148
+ tag: Optional[str],
149
+ commit: Optional[str],
150
+ name: Optional[str],
151
+ validate: bool,
152
+ yes: bool,
153
+ ) -> None:
154
+ """Install a skill from GitHub or local path."""
155
+ try:
156
+ client = get_client()
157
+
158
+ install_options = SkillInstallOptions(
159
+ source=path or source,
160
+ validate=validate,
161
+ branch=branch,
162
+ tag=tag,
163
+ commit=commit,
164
+ )
165
+
166
+ if not yes:
167
+ console.print(f"[bold]\nAbout to install:[/bold] [cyan]{source}[/cyan]")
168
+ answer = questionary.confirm("Continue?", default=True).ask()
169
+ if not answer:
170
+ console.print("[dim]Installation cancelled.[/dim]")
171
+ return
172
+
173
+ with console.status("[bold green]Installing skill..."):
174
+ result = client.install_skill(install_options)
175
+
176
+ print_success(result.message)
177
+
178
+ if result.id:
179
+ console.print(f"[dim]Skill ID:[/dim] {result.id}")
180
+ if result.status:
181
+ console.print(f"[dim]Status:[/dim] {result.status}")
182
+ if result.created_at:
183
+ console.print(f"[dim]Installed at:[/dim] {result.created_at}")
184
+
185
+ except Exception as e:
186
+ api_error = handle_api_error(e)
187
+ if api_error.errmsg and api_error.errmsg != "undefined":
188
+ print_error(api_error.errmsg)
189
+ elif isinstance(e, Exception) and str(e):
190
+ print_error(str(e))
191
+ else:
192
+ print_error("Installation failed. Please check if the skill source is valid and accessible.")
193
+ if api_error.details:
194
+ console.print(f"[dim]Details:[/dim] {json.dumps(api_error.details, indent=2)}")
195
+ sys.exit(1)
196
+
197
+
198
+ @main.command()
199
+ @click.option("--limit", "-l", default=20, help="Maximum number of results")
200
+ @click.option("--page", "-p", default=0, help="Page number")
201
+ @click.option("--json", "json_output", is_flag=True, help="Output as JSON")
202
+ def list(limit: int, page: int, json_output: bool) -> None:
203
+ """List installed skills."""
204
+ try:
205
+ client = get_client()
206
+
207
+ with console.status("[bold green]Loading skills..."):
208
+ result = client.list_skills(limit=limit, page=page)
209
+
210
+ if json_output:
211
+ output = {
212
+ "current_page": result.current_page,
213
+ "total_count": result.total_count,
214
+ "total_page": result.total_page,
215
+ "skills": [
216
+ {
217
+ "id": s.id,
218
+ "name": s.name,
219
+ "version": s.version,
220
+ "description": s.description,
221
+ "author": s.author,
222
+ }
223
+ for s in result.skills
224
+ ],
225
+ }
226
+ console.print_json(json.dumps(output))
227
+ return
228
+
229
+ if not result.skills:
230
+ console.print("[yellow]No skills installed.[/yellow]")
231
+ console.print("[dim]\nInstall a skill with: skills add <source>[/dim]")
232
+ console.print("[dim]Find skills with: skills find <query>[/dim]")
233
+ return
234
+
235
+ console.print(f"[bold]\nInstalled Skills ({result.total_count} total):[/bold]\n")
236
+ for skill in result.skills:
237
+ print_skill_short(skill)
238
+
239
+ if result.total_page > 1:
240
+ console.print(f"[dim]\nPage {result.current_page + 1} of {result.total_page}[/dim]")
241
+ console.print("[dim]Use --page to see more results[/dim]")
242
+
243
+ except Exception as e:
244
+ print_error(handle_api_error(e).errmsg)
245
+ sys.exit(1)
246
+
247
+
248
+ @main.command()
249
+ @click.argument("skill_id")
250
+ @click.option("--tool", "-t", help="Tool name to execute")
251
+ @click.option("--params", "-p", help="Parameters as JSON string")
252
+ @click.option("--params-file", "-f", help="Parameters from JSON file")
253
+ @click.option("--interactive", "-i", is_flag=True, help="Interactive parameter input")
254
+ def run(
255
+ skill_id: str,
256
+ tool: Optional[str],
257
+ params: Optional[str],
258
+ params_file: Optional[str],
259
+ interactive: bool,
260
+ ) -> None:
261
+ """Execute a skill."""
262
+ try:
263
+ client = get_client()
264
+
265
+ exec_params: Dict[str, Any] = {}
266
+
267
+ if params:
268
+ exec_params = json.loads(params)
269
+ elif params_file:
270
+ with open(params_file, "r", encoding="utf-8") as f:
271
+ exec_params = json.load(f)
272
+ elif interactive:
273
+ skill = client.get_skill(skill_id)
274
+ console.print(f"[bold]\nExecuting: {skill.name}[/bold]")
275
+ console.print(f"[dim]{skill.description}[/dim]")
276
+
277
+ if skill.tools:
278
+ tool_names = [t.name for t in skill.tools]
279
+ tool = questionary.select(
280
+ "Select a tool:",
281
+ choices=tool_names,
282
+ ).ask()
283
+
284
+ if tool:
285
+ tool_def = next((t for t in skill.tools if t.name == tool), None)
286
+ if tool_def and tool_def.parameters:
287
+ for param in tool_def.parameters:
288
+ if param.param_type.value == "boolean":
289
+ value = questionary.confirm(
290
+ param.description,
291
+ default=bool(param.default_value),
292
+ ).ask()
293
+ else:
294
+ value = questionary.text(
295
+ param.description,
296
+ default=str(param.default_value) if param.default_value else "",
297
+ ).ask()
298
+ exec_params[param.name] = value
299
+
300
+ with console.status("[bold green]Executing skill..."):
301
+ if tool:
302
+ result = client.execute_skill_tool(skill_id, tool, exec_params)
303
+ else:
304
+ result = client.execute_skill(skill_id, exec_params)
305
+
306
+ if result.success:
307
+ print_success("Execution completed")
308
+ console.print(f"\n[bold]Result:[/bold]")
309
+ console.print(result.output)
310
+ if result.data:
311
+ console.print("[dim]\nData:[/dim]")
312
+ console.print_json(json.dumps(result.data))
313
+ else:
314
+ print_error(result.error_message or "Unknown error")
315
+ sys.exit(1)
316
+
317
+ except Exception as e:
318
+ print_error(handle_api_error(e).errmsg)
319
+ sys.exit(1)
320
+
321
+
322
+ @main.command()
323
+ @click.argument("skill_id")
324
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
325
+ def remove(skill_id: str, yes: bool) -> None:
326
+ """Remove an installed skill."""
327
+ try:
328
+ client = get_client()
329
+
330
+ if not yes:
331
+ skill = client.get_skill(skill_id)
332
+ console.print("[bold]\nAbout to remove:[/bold]")
333
+ print_skill(skill)
334
+
335
+ answer = questionary.confirm(
336
+ "Are you sure you want to remove this skill?",
337
+ default=False,
338
+ ).ask()
339
+
340
+ if not answer:
341
+ console.print("[dim]Removal cancelled.[/dim]")
342
+ return
343
+
344
+ with console.status("[bold green]Removing skill..."):
345
+ result = client.uninstall_skill(skill_id)
346
+
347
+ print_success(result.get("message", "Skill removed"))
348
+
349
+ except Exception as e:
350
+ print_error(handle_api_error(e).errmsg)
351
+ sys.exit(1)
352
+
353
+
354
+ @main.command()
355
+ @click.argument("skill_id")
356
+ def info(skill_id: str) -> None:
357
+ """Show detailed information about a skill."""
358
+ try:
359
+ client = get_client()
360
+ skill = client.get_skill(skill_id)
361
+
362
+ print_skill(skill)
363
+
364
+ if skill.tools:
365
+ console.print("[bold]\n Tools:[/bold]")
366
+ for tool in skill.tools:
367
+ console.print(f" [cyan]• {tool.name}[/cyan]")
368
+ console.print(f" [dim] {tool.description}[/dim]")
369
+ if tool.parameters:
370
+ for param in tool.parameters:
371
+ required = "[red]*[/red]" if param.required else ""
372
+ console.print(f" [dim] - {param.name}{required}: {param.description}[/dim]")
373
+
374
+ if skill.dependencies:
375
+ console.print("[bold]\n Dependencies:[/bold]")
376
+ for dep in skill.dependencies:
377
+ console.print(f" [dim]• {dep}[/dim]")
378
+
379
+ except Exception as e:
380
+ print_error(handle_api_error(e).errmsg)
381
+ sys.exit(1)
382
+
383
+
384
+ @main.command()
385
+ @click.argument("name", required=False)
386
+ @click.option("--directory", "-d", default=".", help="Target directory")
387
+ @click.option("--template", "-t", default="basic", help="Skill template")
388
+ def init(name: Optional[str], directory: str, template: str) -> None:
389
+ """Initialize a new skill project."""
390
+ import pathlib
391
+
392
+ try:
393
+ if not name:
394
+ name = questionary.text(
395
+ "Skill name:",
396
+ validate=lambda x: len(x) > 0 or "Name is required",
397
+ ).ask()
398
+
399
+ target_dir = pathlib.Path(directory) / name
400
+
401
+ if target_dir.exists():
402
+ answer = questionary.confirm(
403
+ f'Directory "{target_dir}" already exists. Overwrite?',
404
+ default=False,
405
+ ).ask()
406
+ if not answer:
407
+ console.print("[dim]Init cancelled.[/dim]")
408
+ return
409
+
410
+ with console.status("[bold green]Creating skill project..."):
411
+ target_dir.mkdir(parents=True, exist_ok=True)
412
+
413
+ skill_content = f"""---
414
+ name: {name}
415
+ description: A new agent skill
416
+ version: 1.0.0
417
+ author: Your Name
418
+ license: MIT
419
+ ---
420
+
421
+ # {name}
422
+
423
+ This is a new agent skill created with the skills CLI.
424
+
425
+ ## Usage
426
+
427
+ Describe how to use this skill here.
428
+
429
+ ## Tools
430
+
431
+ ### tool-name
432
+
433
+ Description of what this tool does.
434
+
435
+ #### Parameters
436
+
437
+ | Name | Type | Required | Description |
438
+ |------|------|----------|-------------|
439
+ | param1 | string | true | First parameter |
440
+
441
+ ## Examples
442
+
443
+ ```bash
444
+ skills run {name} -p '{{"param1": "value"}}'
445
+ ```
446
+ """
447
+
448
+ skill_file = target_dir / "SKILL.md"
449
+ skill_file.write_text(skill_content, encoding="utf-8")
450
+
451
+ print_success(f"Created skill project at {target_dir}")
452
+ console.print(f"\n[dim]Edit {skill_file} to define your skill.[/dim]")
453
+ console.print(f"[dim]Install with: skills add {target_dir}[/dim]")
454
+
455
+ except Exception as e:
456
+ print_error(str(e))
457
+ sys.exit(1)
458
+
459
+
460
+ @main.command("install-runtime")
461
+ @click.option("--runtime-version", default=RUNTIME_VERSION, help="Runtime version to install")
462
+ def install_runtime(runtime_version: str) -> None:
463
+ """Download and install the AgentSkills runtime."""
464
+ try:
465
+ manager = RuntimeManager()
466
+
467
+ if manager.is_installed():
468
+ console.print("[yellow]Runtime is already installed.[/yellow]")
469
+ answer = questionary.confirm(
470
+ "Do you want to reinstall?",
471
+ default=False,
472
+ ).ask()
473
+ if not answer:
474
+ console.print("[dim]Installation cancelled.[/dim]")
475
+ return
476
+
477
+ with console.status(f"[bold green]Downloading runtime v{runtime_version}..."):
478
+ success = manager.download_runtime(runtime_version)
479
+
480
+ if success:
481
+ print_success(f"Runtime v{runtime_version} installed successfully!")
482
+ console.print("\n[dim]Start the runtime with: skills start[/dim]")
483
+ else:
484
+ print_error("Failed to install runtime.")
485
+ sys.exit(1)
486
+
487
+ except Exception as e:
488
+ print_error(str(e))
489
+ sys.exit(1)
490
+
491
+
492
+ @main.command()
493
+ @click.option("--port", "-p", default=8080, help="Port to listen on")
494
+ @click.option("--host", "-h", default="127.0.0.1", help="Host to bind to")
495
+ @click.option("--foreground", "-f", is_flag=True, help="Run in foreground")
496
+ def start(port: int, host: str, foreground: bool) -> None:
497
+ """Start the AgentSkills runtime server."""
498
+ try:
499
+ manager = RuntimeManager()
500
+
501
+ if not manager.is_installed():
502
+ print_error("Runtime not found. Run 'skills install-runtime' first.")
503
+ sys.exit(1)
504
+
505
+ options = RuntimeOptions(
506
+ port=port,
507
+ host=host,
508
+ detached=not foreground,
509
+ )
510
+
511
+ process = manager.start(options)
512
+
513
+ if process:
514
+ if foreground:
515
+ print_success(f"Runtime started on {host}:{port}")
516
+ process.wait()
517
+ else:
518
+ print_success(f"Runtime started on {host}:{port} (background)")
519
+ console.print("[dim]Stop with: skills stop[/dim]")
520
+ else:
521
+ print_error("Failed to start runtime.")
522
+ sys.exit(1)
523
+
524
+ except Exception as e:
525
+ print_error(str(e))
526
+ sys.exit(1)
527
+
528
+
529
+ @main.command()
530
+ def stop() -> None:
531
+ """Stop the AgentSkills runtime server."""
532
+ try:
533
+ manager = RuntimeManager()
534
+
535
+ if manager.stop():
536
+ print_success("Runtime stopped.")
537
+ else:
538
+ console.print("[yellow]Runtime is not running.[/yellow]")
539
+
540
+ except Exception as e:
541
+ print_error(str(e))
542
+ sys.exit(1)
543
+
544
+
545
+ @main.command()
546
+ def status() -> None:
547
+ """Check the status of the skills runtime server."""
548
+ try:
549
+ manager = RuntimeManager()
550
+ runtime_status = manager.status()
551
+
552
+ if runtime_status.running:
553
+ console.print("[green]●[/green] Runtime is running")
554
+ if runtime_status.version:
555
+ console.print(f" [dim]Version:[/dim] {runtime_status.version}")
556
+ if runtime_status.sdk_version:
557
+ console.print(f" [dim]SDK Version:[/dim] {runtime_status.sdk_version}")
558
+ else:
559
+ console.print("[red]○[/red] Runtime is not running")
560
+ console.print("[dim]\nStart with: skills start[/dim]")
561
+
562
+ except Exception as e:
563
+ print_error(str(e))
564
+ sys.exit(1)
565
+
566
+
567
+ @main.command()
568
+ def check() -> None:
569
+ """Check for skill updates."""
570
+ try:
571
+ client = get_client()
572
+
573
+ with console.status("[bold green]Checking for updates..."):
574
+ result = client.list_skills(limit=100)
575
+
576
+ if not result.skills:
577
+ console.print("[yellow]No skills installed.[/yellow]")
578
+ return
579
+
580
+ console.print(f"[bold]Checking {result.total_count} skill(s) for updates...[/bold]\n")
581
+
582
+ for skill in result.skills:
583
+ console.print(f" [cyan]{skill.name}[/cyan] [dim]({skill.version})[/dim] - [green]up to date[/green]")
584
+
585
+ except Exception as e:
586
+ print_error(handle_api_error(e).errmsg)
587
+ sys.exit(1)
588
+
589
+
590
+ if __name__ == "__main__":
591
+ main()