praisonai-code 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.
Files changed (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,940 @@
1
+ """
2
+ Skills CLI Feature Handler
3
+
4
+ Provides CLI commands for managing Agent Skills:
5
+ - list: List available skills
6
+ - validate: Validate a skill directory
7
+ - create: Create a new skill from template
8
+ - prompt: Generate prompt XML for skills
9
+ """
10
+
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Optional, List
14
+
15
+
16
+ class SkillsHandler:
17
+ """Handler for skills CLI commands."""
18
+
19
+ def __init__(self, verbose: bool = True):
20
+ """Initialize the skills handler.
21
+
22
+ Args:
23
+ verbose: Whether to print verbose output
24
+ """
25
+ self.verbose = verbose
26
+
27
+ def list_skills(
28
+ self,
29
+ skill_dirs: Optional[List[str]] = None,
30
+ include_defaults: bool = True
31
+ ) -> List[dict]:
32
+ """List all available skills.
33
+
34
+ Args:
35
+ skill_dirs: Optional list of directories to scan
36
+ include_defaults: Whether to include default skill directories
37
+
38
+ Returns:
39
+ List of skill info dictionaries
40
+ """
41
+ from praisonaiagents.skills import discover_skills
42
+
43
+ skills = discover_skills(skill_dirs, include_defaults)
44
+
45
+ result = []
46
+ for skill in skills:
47
+ info = {
48
+ "name": skill.name,
49
+ "description": skill.description,
50
+ "path": str(skill.path) if skill.path else None,
51
+ "license": skill.license,
52
+ }
53
+ result.append(info)
54
+
55
+ if self.verbose:
56
+ print(f" {skill.name}: {skill.description[:60]}...")
57
+
58
+ if self.verbose and not result:
59
+ print("No skills found.")
60
+
61
+ return result
62
+
63
+ def validate_skill(self, skill_path: str) -> dict:
64
+ """Validate a skill directory.
65
+
66
+ Args:
67
+ skill_path: Path to the skill directory
68
+
69
+ Returns:
70
+ Validation result dictionary with 'valid' and 'errors' keys
71
+ """
72
+ from praisonaiagents.skills import validate
73
+
74
+ path = Path(skill_path).expanduser().resolve()
75
+ errors = validate(path)
76
+
77
+ result = {
78
+ "valid": len(errors) == 0,
79
+ "path": str(path),
80
+ "errors": errors
81
+ }
82
+
83
+ if self.verbose:
84
+ if result["valid"]:
85
+ print(f"✓ Skill at {path} is valid")
86
+ else:
87
+ print(f"✗ Skill at {path} has errors:")
88
+ for error in errors:
89
+ print(f" - {error}")
90
+
91
+ return result
92
+
93
+ def create_skill(
94
+ self,
95
+ name: str,
96
+ description: str = "A custom skill",
97
+ output_dir: Optional[str] = None,
98
+ author: Optional[str] = None,
99
+ license: Optional[str] = None,
100
+ compatibility: Optional[str] = None,
101
+ template: bool = False,
102
+ use_ai: bool = True,
103
+ generate_script: bool = False
104
+ ) -> str:
105
+ """Create a new skill from template or AI generation.
106
+
107
+ Args:
108
+ name: Skill name (kebab-case)
109
+ description: Skill description (also used as prompt for AI)
110
+ output_dir: Directory to create skill in (default: current dir)
111
+ author: Author name for metadata
112
+ license: License type (default: Apache-2.0)
113
+ compatibility: Compatibility information
114
+ template: If True, use template only (no AI)
115
+ use_ai: If True, try to use AI to generate content
116
+ generate_script: If True, generate scripts/skill.py
117
+
118
+ Returns:
119
+ Path to created skill directory
120
+ """
121
+ # Validate name format
122
+ import re
123
+ if not re.match(r'^[a-z][a-z0-9-]*[a-z0-9]$', name) and len(name) > 1:
124
+ if not re.match(r'^[a-z]$', name):
125
+ raise ValueError(
126
+ f"Invalid skill name '{name}'. "
127
+ "Must be lowercase, use hyphens, and not start/end with hyphen."
128
+ )
129
+
130
+ base_dir = Path(output_dir or os.getcwd())
131
+ skill_dir = base_dir / name
132
+
133
+ if skill_dir.exists():
134
+ raise ValueError(f"Directory already exists: {skill_dir}")
135
+
136
+ # Create directory structure
137
+ skill_dir.mkdir(parents=True)
138
+ (skill_dir / "scripts").mkdir()
139
+ (skill_dir / "references").mkdir()
140
+ (skill_dir / "assets").mkdir()
141
+
142
+ # Set defaults
143
+ author = author or "user"
144
+ license = license or "Apache-2.0"
145
+ compatibility = compatibility or "Works with PraisonAI Agents"
146
+
147
+ # Try AI generation if requested and not in template mode
148
+ ai_content = None
149
+ if use_ai and not template:
150
+ ai_content = self._generate_skill_content_with_ai(name, description)
151
+
152
+ if ai_content and ai_content.get("skill_md"):
153
+ # Use AI-generated content
154
+ skill_md_content = ai_content["skill_md"]
155
+ if self.verbose:
156
+ print("✓ Generated SKILL.md with AI")
157
+ else:
158
+ # Use template
159
+ skill_md_content = self._generate_template_content(
160
+ name, description, author, license, compatibility,
161
+ include_script=generate_script
162
+ )
163
+ if self.verbose and use_ai and not template:
164
+ print("⚠ No API key found, using template")
165
+
166
+ (skill_dir / "SKILL.md").write_text(skill_md_content)
167
+
168
+ # Generate scripts/skill.py if requested or AI provided it
169
+ if generate_script or (ai_content and ai_content.get("skill_py")):
170
+ script_content = (ai_content or {}).get("skill_py") or self._generate_template_script(name, description)
171
+ # Strip any remaining code blocks from script content
172
+ script_content = self._strip_code_blocks(script_content)
173
+ (skill_dir / "scripts" / "skill.py").write_text(script_content)
174
+ if self.verbose:
175
+ print(" - scripts/skill.py")
176
+ else:
177
+ (skill_dir / "scripts" / ".gitkeep").write_text("")
178
+
179
+ # Create placeholder files
180
+ (skill_dir / "references" / ".gitkeep").write_text("")
181
+ (skill_dir / "assets" / ".gitkeep").write_text("")
182
+
183
+ if self.verbose:
184
+ print(f"✓ Created skill at {skill_dir}")
185
+ print(" - SKILL.md")
186
+ print(" - scripts/")
187
+ print(" - references/")
188
+ print(" - assets/")
189
+
190
+ return str(skill_dir)
191
+
192
+ def _generate_template_content(
193
+ self,
194
+ name: str,
195
+ description: str,
196
+ author: str,
197
+ license: str,
198
+ compatibility: str,
199
+ include_script: bool = False
200
+ ) -> str:
201
+ """Generate template SKILL.md content."""
202
+ title = name.replace('-', ' ').title()
203
+ func_name = name.replace('-', '_')
204
+
205
+ script_section = ""
206
+ if include_script:
207
+ script_section = f"""
208
+ ## Script Usage
209
+
210
+ This skill includes a Python script at `scripts/skill.py` that provides the core functionality.
211
+
212
+ ### Running the Script
213
+
214
+ ```bash
215
+ python scripts/skill.py <input>
216
+ ```
217
+
218
+ ### Using as a Module
219
+
220
+ ```python
221
+ from scripts.skill import {func_name}
222
+
223
+ result = {func_name}(input_data)
224
+ print(result)
225
+ ```
226
+ """
227
+
228
+ return f"""---
229
+ name: {name}
230
+ description: {description}
231
+ license: {license}
232
+ compatibility: {compatibility}
233
+ metadata:
234
+ author: {author}
235
+ version: "1.0"
236
+ ---
237
+
238
+ # {title}
239
+
240
+ ## Overview
241
+
242
+ {description}
243
+ {script_section}
244
+ ## Usage
245
+
246
+ Describe how to use this skill.
247
+
248
+ ## Instructions
249
+
250
+ 1. Step one
251
+ 2. Step two
252
+ 3. Step three
253
+ """
254
+
255
+ def _generate_template_script(self, name: str, description: str) -> str:
256
+ """Generate description-relevant scripts/skill.py content."""
257
+ func_name = name.replace('-', '_')
258
+
259
+ # Analyze description to generate relevant code
260
+ desc_lower = description.lower()
261
+
262
+ # CSV/Data analysis patterns
263
+ if any(kw in desc_lower for kw in ['csv', 'spreadsheet', 'data analysis', 'analyze data', 'tabular']):
264
+ return self._generate_csv_script(name, description, func_name)
265
+
266
+ # PDF patterns
267
+ if any(kw in desc_lower for kw in ['pdf', 'document', 'extract text']):
268
+ return self._generate_pdf_script(name, description, func_name)
269
+
270
+ # Web/API patterns
271
+ if any(kw in desc_lower for kw in ['api', 'http', 'request', 'web', 'fetch', 'url']):
272
+ return self._generate_api_script(name, description, func_name)
273
+
274
+ # File processing patterns
275
+ if any(kw in desc_lower for kw in ['file', 'read', 'write', 'process', 'parse']):
276
+ return self._generate_file_script(name, description, func_name)
277
+
278
+ # Image patterns
279
+ if any(kw in desc_lower for kw in ['image', 'photo', 'picture', 'resize', 'convert']):
280
+ return self._generate_image_script(name, description, func_name)
281
+
282
+ # JSON/YAML patterns
283
+ if any(kw in desc_lower for kw in ['json', 'yaml', 'config', 'configuration']):
284
+ return self._generate_json_script(name, description, func_name)
285
+
286
+ # Text processing patterns
287
+ if any(kw in desc_lower for kw in ['text', 'string', 'regex', 'search', 'replace', 'format']):
288
+ return self._generate_text_script(name, description, func_name)
289
+
290
+ # Default generic script
291
+ return self._generate_generic_script(name, description, func_name)
292
+
293
+ def _generate_csv_script(self, name: str, description: str, func_name: str) -> str:
294
+ """Generate CSV analysis script."""
295
+ return f'''"""
296
+ {name} - {description}
297
+
298
+ This script provides functionality for the {name} skill.
299
+ """
300
+ import sys
301
+ import json
302
+ import pandas as pd
303
+
304
+
305
+ def json_serializer(obj):
306
+ """Handle numpy types for JSON serialization."""
307
+ if hasattr(obj, 'item'):
308
+ return obj.item()
309
+ elif hasattr(obj, 'tolist'):
310
+ return obj.tolist()
311
+ return str(obj)
312
+
313
+
314
+ def {func_name}(file_path: str) -> dict:
315
+ """
316
+ Analyze CSV file and return statistics.
317
+
318
+ Args:
319
+ file_path: Path to the CSV file to analyze
320
+
321
+ Returns:
322
+ Dictionary with analysis results
323
+ """
324
+ df = pd.read_csv(file_path)
325
+
326
+ result = {{
327
+ "rows": int(len(df)),
328
+ "columns": int(len(df.columns)),
329
+ "column_names": list(df.columns),
330
+ "dtypes": {{col: str(dtype) for col, dtype in df.dtypes.items()}},
331
+ "missing_values": {{k: int(v) for k, v in df.isnull().sum().to_dict().items()}},
332
+ "numeric_summary": {{}}
333
+ }}
334
+
335
+ numeric_cols = df.select_dtypes(include=['number']).columns
336
+ for col in numeric_cols:
337
+ result["numeric_summary"][col] = {{
338
+ "mean": float(df[col].mean()),
339
+ "std": float(df[col].std()) if len(df) > 1 else 0.0,
340
+ "min": float(df[col].min()),
341
+ "max": float(df[col].max())
342
+ }}
343
+
344
+ return result
345
+
346
+
347
+ def main():
348
+ """Main entry point for the skill."""
349
+ if len(sys.argv) > 1:
350
+ result = {func_name}(sys.argv[1])
351
+ print(json.dumps(result, indent=2, default=json_serializer))
352
+ else:
353
+ print("Usage: python skill.py <csv_file>")
354
+
355
+
356
+ if __name__ == "__main__":
357
+ main()
358
+ '''
359
+
360
+ def _generate_pdf_script(self, name: str, description: str, func_name: str) -> str:
361
+ """Generate PDF processing script."""
362
+ return f'''"""\n{name} - {description}\n\nThis script provides PDF processing functionality.\n"""\n\ndef {func_name}(file_path: str) -> dict:\n """\n Process PDF file and extract content.\n \n Args:\n file_path: Path to the PDF file\n \n Returns:\n Dictionary with extracted content\n """\n from pypdf import PdfReader\n \n reader = PdfReader(file_path)\n \n result = {{\n "pages": len(reader.pages),\n "metadata": {{\n "title": reader.metadata.title if reader.metadata else None,\n "author": reader.metadata.author if reader.metadata else None\n }},\n "text": []\n }}\n \n for i, page in enumerate(reader.pages):\n result["text"].append({{\n "page": i + 1,\n "content": page.extract_text()\n }})\n \n return result\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n import json\n \n if len(sys.argv) > 1:\n result = {func_name}(sys.argv[1])\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <pdf_file>")\n\n\nif __name__ == "__main__":\n main()\n'''
363
+
364
+ def _generate_api_script(self, name: str, description: str, func_name: str) -> str:
365
+ """Generate API/HTTP request script."""
366
+ return f'''"""\n{name} - {description}\n\nThis script provides API request functionality.\n"""\nimport requests\n\ndef {func_name}(url: str, method: str = "GET", data: dict = None) -> dict:\n """\n Make HTTP request to API endpoint.\n \n Args:\n url: API endpoint URL\n method: HTTP method (GET, POST, PUT, DELETE)\n data: Optional request body data\n \n Returns:\n Dictionary with response data\n """\n headers = {{"Content-Type": "application/json"}}\n \n if method.upper() == "GET":\n response = requests.get(url, headers=headers)\n elif method.upper() == "POST":\n response = requests.post(url, json=data, headers=headers)\n elif method.upper() == "PUT":\n response = requests.put(url, json=data, headers=headers)\n elif method.upper() == "DELETE":\n response = requests.delete(url, headers=headers)\n else:\n raise ValueError(f"Unsupported method: {{method}}")\n \n return {{\n "status_code": response.status_code,\n "headers": dict(response.headers),\n "data": response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text\n }}\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n import json\n \n if len(sys.argv) > 1:\n result = {func_name}(sys.argv[1])\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <url>")\n\n\nif __name__ == "__main__":\n main()\n'''
367
+
368
+ def _generate_file_script(self, name: str, description: str, func_name: str) -> str:
369
+ """Generate file processing script."""
370
+ return f'''"""\n{name} - {description}\n\nThis script provides file processing functionality.\n"""\nfrom pathlib import Path\n\ndef {func_name}(file_path: str) -> dict:\n """\n Process file and return information.\n \n Args:\n file_path: Path to the file to process\n \n Returns:\n Dictionary with file information and content\n """\n path = Path(file_path)\n \n if not path.exists():\n raise FileNotFoundError(f"File not found: {{file_path}}")\n \n stat = path.stat()\n \n result = {{\n "name": path.name,\n "extension": path.suffix,\n "size_bytes": stat.st_size,\n "is_file": path.is_file(),\n "is_dir": path.is_dir()\n }}\n \n if path.is_file() and stat.st_size < 1024 * 1024:\n try:\n result["content"] = path.read_text()\n result["lines"] = len(result["content"].splitlines())\n except UnicodeDecodeError:\n result["content"] = "<binary file>"\n \n return result\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n import json\n \n if len(sys.argv) > 1:\n result = {func_name}(sys.argv[1])\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <file_path>")\n\n\nif __name__ == "__main__":\n main()\n'''
371
+
372
+ def _generate_image_script(self, name: str, description: str, func_name: str) -> str:
373
+ """Generate image processing script."""
374
+ return f'''"""\n{name} - {description}\n\nThis script provides image processing functionality.\n"""\nfrom PIL import Image\n\ndef {func_name}(file_path: str, output_path: str = None, resize: tuple = None) -> dict:\n """\n Process image file.\n \n Args:\n file_path: Path to the image file\n output_path: Optional output path for processed image\n resize: Optional tuple (width, height) to resize\n \n Returns:\n Dictionary with image information\n """\n img = Image.open(file_path)\n \n result = {{\n "format": img.format,\n "mode": img.mode,\n "size": img.size,\n "width": img.width,\n "height": img.height\n }}\n \n if resize:\n img = img.resize(resize)\n result["resized_to"] = resize\n \n if output_path:\n img.save(output_path)\n result["saved_to"] = output_path\n \n return result\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n import json\n \n if len(sys.argv) > 1:\n result = {func_name}(sys.argv[1])\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <image_file>")\n\n\nif __name__ == "__main__":\n main()\n'''
375
+
376
+ def _generate_json_script(self, name: str, description: str, func_name: str) -> str:
377
+ """Generate JSON/YAML processing script."""
378
+ return f'''"""\n{name} - {description}\n\nThis script provides JSON/YAML processing functionality.\n"""\nimport json\nfrom pathlib import Path\n\ndef {func_name}(file_path: str) -> dict:\n """\n Process JSON or YAML file.\n \n Args:\n file_path: Path to the JSON/YAML file\n \n Returns:\n Dictionary with parsed content and metadata\n """\n path = Path(file_path)\n content = path.read_text()\n \n if path.suffix in [".yaml", ".yml"]:\n import yaml\n data = yaml.safe_load(content)\n else:\n data = json.loads(content)\n \n result = {{\n "file": file_path,\n "format": "yaml" if path.suffix in [".yaml", ".yml"] else "json",\n "keys": list(data.keys()) if isinstance(data, dict) else None,\n "length": len(data) if isinstance(data, (list, dict)) else None,\n "data": data\n }}\n \n return result\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n \n if len(sys.argv) > 1:\n result = {func_name}(sys.argv[1])\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <json_or_yaml_file>")\n\n\nif __name__ == "__main__":\n main()\n'''
379
+
380
+ def _generate_text_script(self, name: str, description: str, func_name: str) -> str:
381
+ """Generate text processing script."""
382
+ return f'''"""\n{name} - {description}\n\nThis script provides text processing functionality.\n"""\nimport re\n\ndef {func_name}(text: str, pattern: str = None, replacement: str = None) -> dict:\n """\n Process text with optional regex operations.\n \n Args:\n text: Input text to process\n pattern: Optional regex pattern to search\n replacement: Optional replacement string\n \n Returns:\n Dictionary with processing results\n """\n result = {{\n "original_length": len(text),\n "lines": len(text.splitlines()),\n "words": len(text.split()),\n "characters": len(text.replace(" ", ""))\n }}\n \n if pattern:\n matches = re.findall(pattern, text)\n result["matches"] = matches\n result["match_count"] = len(matches)\n \n if replacement is not None:\n result["replaced_text"] = re.sub(pattern, replacement, text)\n \n return result\n\n\ndef main():\n """Main entry point for the skill."""\n import sys\n import json\n \n if len(sys.argv) > 1:\n with open(sys.argv[1]) as f:\n text = f.read()\n result = {func_name}(text)\n print(json.dumps(result, indent=2))\n else:\n print("Usage: python skill.py <text_file>")\n\n\nif __name__ == "__main__":\n main()\n'''
383
+
384
+ def _generate_generic_script(self, name: str, description: str, func_name: str) -> str:
385
+ """Generate generic script template."""
386
+ return f'''"""
387
+ {name} - {description}
388
+
389
+ This script provides functionality for the {name} skill.
390
+ """
391
+
392
+ def {func_name}(input_data: str) -> str:
393
+ """
394
+ Process input data for {name}.
395
+
396
+ Args:
397
+ input_data: The input to process
398
+
399
+ Returns:
400
+ Processed result
401
+ """
402
+ result = f"Processed: {{input_data}}"
403
+ return result
404
+
405
+
406
+ def main():
407
+ """Main entry point for the skill."""
408
+ import sys
409
+ if len(sys.argv) > 1:
410
+ result = {func_name}(sys.argv[1])
411
+ print(result)
412
+ else:
413
+ print("Usage: python skill.py <input>")
414
+
415
+
416
+ if __name__ == "__main__":
417
+ main()
418
+ '''
419
+
420
+ def _generate_skill_content_with_ai(
421
+ self,
422
+ name: str,
423
+ description: str
424
+ ) -> Optional[dict]:
425
+ """Generate SKILL.md and optional skill.py using AI.
426
+
427
+ Args:
428
+ name: Skill name
429
+ description: Skill description/prompt
430
+
431
+ Returns:
432
+ Dict with 'skill_md' and optionally 'skill_py', or None if no API key
433
+ """
434
+ # Check for API keys
435
+ api_key = (
436
+ os.environ.get("OPENAI_API_KEY") or
437
+ os.environ.get("ANTHROPIC_API_KEY") or
438
+ os.environ.get("GOOGLE_API_KEY") or
439
+ os.environ.get("GEMINI_API_KEY")
440
+ )
441
+
442
+ if not api_key:
443
+ return None
444
+
445
+ try:
446
+ from praisonaiagents import Agent
447
+
448
+ # Create prompt for AI to generate skill content
449
+ prompt = f"""Create a comprehensive SKILL.md file for an Agent Skill with the following details:
450
+
451
+ Name: {name}
452
+ Description: {description}
453
+
454
+ The SKILL.md must follow this exact format:
455
+
456
+ ```markdown
457
+ ---
458
+ name: {name}
459
+ description: {description}
460
+ license: Apache-2.0
461
+ compatibility: Works with PraisonAI Agents, Claude Code, and other Agent Skills compatible tools
462
+ metadata:
463
+ author: user
464
+ version: "1.0"
465
+ ---
466
+
467
+ # [Title]
468
+
469
+ ## Overview
470
+ [Detailed overview of what this skill does]
471
+
472
+ ## When to Use
473
+ [Describe when this skill should be activated]
474
+
475
+ ## Instructions
476
+ [Step-by-step instructions for the agent]
477
+
478
+ ## Examples
479
+ [Provide concrete examples]
480
+
481
+ ## Best Practices
482
+ [List best practices]
483
+ ```
484
+
485
+ Generate detailed, practical content based on the description. Make it comprehensive but concise.
486
+ The skill should be immediately useful for an AI agent.
487
+
488
+ Also, if this skill requires any Python code to function, provide a skill.py that implements the core functionality.
489
+ The script MUST follow this pattern:
490
+ 1. Have a main function that accepts file path as command line argument (sys.argv[1])
491
+ 2. Print JSON output using json.dumps() with a custom default handler for numpy types
492
+ 3. Include usage message if no arguments provided
493
+ 4. Convert numpy types to native Python types (int, float) before JSON serialization
494
+
495
+ Example script pattern:
496
+ ```python
497
+ import sys
498
+ import json
499
+
500
+ def json_serializer(obj):
501
+ \"\"\"Handle numpy types for JSON serialization.\"\"\"
502
+ if hasattr(obj, 'item'):
503
+ return obj.item()
504
+ elif hasattr(obj, 'tolist'):
505
+ return obj.tolist()
506
+ return str(obj)
507
+
508
+ def process_file(file_path: str) -> dict:
509
+ # Core logic here
510
+ return {{"result": "data"}}
511
+
512
+ def main():
513
+ if len(sys.argv) > 1:
514
+ result = process_file(sys.argv[1])
515
+ print(json.dumps(result, indent=2, default=json_serializer))
516
+ else:
517
+ print("Usage: python skill.py <file_path>")
518
+
519
+ if __name__ == "__main__":
520
+ main()
521
+ ```
522
+
523
+ Format your response as:
524
+ ---SKILL.MD---
525
+ [content]
526
+ ---SKILL.PY---
527
+ [content or "NONE" if no script needed]
528
+ """
529
+
530
+ agent = Agent(
531
+ name="SkillGenerator",
532
+ role="Skill Content Generator",
533
+ goal="Generate high-quality Agent Skill content",
534
+ instructions="You are an expert at creating Agent Skills. Generate comprehensive, practical skill content.",
535
+ llm=os.environ.get("OPENAI_MODEL_NAME", "gpt-4o-mini"), output="minimal"
536
+ )
537
+
538
+ result = agent.start(prompt)
539
+
540
+ # Parse the response
541
+ if result and "---SKILL.MD---" in result:
542
+ parts = result.split("---SKILL.MD---")
543
+ if len(parts) > 1:
544
+ skill_md_part = parts[1]
545
+ skill_py = None
546
+
547
+ if "---SKILL.PY---" in skill_md_part:
548
+ md_parts = skill_md_part.split("---SKILL.PY---")
549
+ skill_md = self._strip_code_blocks(md_parts[0].strip())
550
+ skill_py_content = md_parts[1].strip() if len(md_parts) > 1 else None
551
+ if skill_py_content and skill_py_content.upper() != "NONE":
552
+ skill_py = self._strip_code_blocks(skill_py_content)
553
+ else:
554
+ skill_md = self._strip_code_blocks(skill_md_part.strip())
555
+
556
+ return {"skill_md": skill_md, "skill_py": skill_py}
557
+
558
+ return None
559
+
560
+ except Exception as e:
561
+ if self.verbose:
562
+ print(f"⚠ AI generation failed: {e}")
563
+ return None
564
+
565
+ def _strip_code_blocks(self, content: str) -> str:
566
+ """Strip markdown code block wrappers from content.
567
+
568
+ Args:
569
+ content: Content that may be wrapped in ```markdown or ```python blocks
570
+
571
+ Returns:
572
+ Content with code block wrappers removed
573
+ """
574
+ import re
575
+ # Match ```language at start and ``` at end
576
+ pattern = r'^```(?:markdown|python|json|yaml|md)?\s*\n?(.*?)\n?```\s*$'
577
+ match = re.match(pattern, content, re.DOTALL)
578
+ if match:
579
+ return match.group(1).strip()
580
+ return content
581
+
582
+ def upload_skill(
583
+ self,
584
+ skill_path: str,
585
+ display_title: Optional[str] = None
586
+ ) -> Optional[str]:
587
+ """Upload a skill to Anthropic Skills API.
588
+
589
+ Args:
590
+ skill_path: Path to the skill directory
591
+ display_title: Optional display title for the skill
592
+
593
+ Returns:
594
+ Skill ID if successful, None otherwise
595
+ """
596
+ path = Path(skill_path).expanduser().resolve()
597
+
598
+ if not path.exists():
599
+ raise ValueError(f"Skill path does not exist: {path}")
600
+
601
+ skill_md_path = path / "SKILL.md"
602
+ if not skill_md_path.exists():
603
+ raise ValueError(f"SKILL.md not found in {path}")
604
+
605
+ # Check for Anthropic API key
606
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
607
+ if not api_key:
608
+ print("Error: ANTHROPIC_API_KEY environment variable is required")
609
+ return None
610
+
611
+ try:
612
+ import anthropic
613
+
614
+ client = anthropic.Anthropic(api_key=api_key)
615
+
616
+ # Read skill name from SKILL.md
617
+ skill_md_content = skill_md_path.read_text()
618
+ skill_name = path.name
619
+
620
+ # Extract name from frontmatter if possible
621
+ if "---" in skill_md_content:
622
+ import re
623
+ match = re.search(r'^name:\s*(.+)$', skill_md_content, re.MULTILINE)
624
+ if match:
625
+ skill_name = match.group(1).strip()
626
+
627
+ title = display_title or skill_name.replace('-', ' ').title()
628
+
629
+ if self.verbose:
630
+ print(f"Uploading skill '{skill_name}' to Anthropic...")
631
+
632
+ # Create skill using Anthropic API
633
+ with open(skill_md_path, 'rb') as f:
634
+ response = client.beta.skills.create(
635
+ display_title=title,
636
+ files=[(f"{skill_name}/SKILL.md", f, "text/markdown")],
637
+ betas=["skills-2025-10-02"]
638
+ )
639
+
640
+ if self.verbose:
641
+ print("✓ Skill uploaded successfully!")
642
+ print(f" ID: {response.id}")
643
+ print(f" Title: {response.display_title}")
644
+
645
+ return response.id
646
+
647
+ except ImportError:
648
+ print("Error: anthropic package is required. Install with: pip install anthropic")
649
+ return None
650
+ except Exception as e:
651
+ if self.verbose:
652
+ print(f"❌ Upload failed: {e}")
653
+ return None
654
+
655
+ def generate_prompt(
656
+ self,
657
+ skill_dirs: Optional[List[str]] = None,
658
+ include_defaults: bool = True
659
+ ) -> str:
660
+ """Generate prompt XML for available skills.
661
+
662
+ Args:
663
+ skill_dirs: Optional list of directories to scan
664
+ include_defaults: Whether to include default skill directories
665
+
666
+ Returns:
667
+ XML string with <available_skills> block
668
+ """
669
+ from praisonaiagents.skills import SkillManager
670
+
671
+ manager = SkillManager()
672
+
673
+ if skill_dirs:
674
+ manager.discover(skill_dirs, include_defaults=include_defaults)
675
+ elif include_defaults:
676
+ manager.discover(include_defaults=True)
677
+
678
+ prompt = manager.to_prompt()
679
+
680
+ if self.verbose:
681
+ print(prompt)
682
+
683
+ return prompt
684
+
685
+ def handle_bundle(
686
+ self,
687
+ bundle_command: Optional[str],
688
+ name: Optional[str] = None,
689
+ skill_dirs: Optional[List[str]] = None,
690
+ ) -> int:
691
+ """List or show skill bundles (named, reusable sets of skills).
692
+
693
+ Args:
694
+ bundle_command: 'list' or 'show'
695
+ name: Bundle name (required for 'show')
696
+ skill_dirs: Optional directories to scan
697
+
698
+ Returns:
699
+ Exit code (0 success, non-zero on error)
700
+ """
701
+ from praisonaiagents.skills import discover_bundles
702
+ from praisonaiagents.skills.bundles import strip_bundle_marker
703
+
704
+ bundles = discover_bundles(skill_dirs, include_defaults=True)
705
+
706
+ if bundle_command in (None, "list"):
707
+ if not bundles:
708
+ print("No skill bundles found.")
709
+ return 0
710
+ print(f"Skill Bundles ({len(bundles)} found):")
711
+ for b in bundles:
712
+ members = ", ".join(b.skills) if b.skills else "-"
713
+ desc = f" - {b.description}" if b.description else ""
714
+ print(f" {b.name}: [{members}]{desc}")
715
+ return 0
716
+
717
+ if bundle_command == "show":
718
+ if not name:
719
+ print("Error: bundle name is required for 'show'")
720
+ return 1
721
+ target = strip_bundle_marker(name)
722
+ match = next((b for b in bundles if b.name == target), None)
723
+ if match is None:
724
+ print(f"Bundle not found: {name}")
725
+ return 1
726
+ print(f"{match.name}")
727
+ if match.description:
728
+ print(f" {match.description}")
729
+ if match.instruction:
730
+ print(f"\nInstruction:\n {match.instruction}")
731
+ print("\nSkills:")
732
+ if match.skills:
733
+ for s in match.skills:
734
+ print(f" - {s}")
735
+ else:
736
+ print(" (none)")
737
+ return 0
738
+
739
+ print(f"Unknown bundle command: {bundle_command}")
740
+ return 1
741
+
742
+
743
+ def handle_skills_command(args) -> int:
744
+ """Handle skills subcommand from CLI.
745
+
746
+ Args:
747
+ args: Parsed command line arguments
748
+
749
+ Returns:
750
+ Exit code (0 for success, non-zero for error)
751
+ """
752
+ handler = SkillsHandler(verbose=True)
753
+
754
+ try:
755
+ if args.skills_command == "list":
756
+ dirs = args.dirs if hasattr(args, 'dirs') and args.dirs else None
757
+ handler.list_skills(dirs, include_defaults=True)
758
+
759
+ elif args.skills_command == "validate":
760
+ if not hasattr(args, 'path') or not args.path:
761
+ print("Error: --path is required for validate command")
762
+ return 1
763
+ result = handler.validate_skill(args.path)
764
+ return 0 if result["valid"] else 1
765
+
766
+ elif args.skills_command == "create":
767
+ if not hasattr(args, 'name') or not args.name:
768
+ print("Error: --name is required for create command")
769
+ return 1
770
+
771
+ # Get all optional arguments
772
+ description = getattr(args, 'description', None) or "A custom skill"
773
+ output_dir = getattr(args, 'output_dir', None) or getattr(args, 'output', None)
774
+ author = getattr(args, 'author', None)
775
+ license_type = getattr(args, 'license', None)
776
+ compatibility = getattr(args, 'compatibility', None)
777
+ template = getattr(args, 'template', False)
778
+ generate_script = getattr(args, 'script', False)
779
+
780
+ handler.create_skill(
781
+ name=args.name,
782
+ description=description,
783
+ output_dir=output_dir,
784
+ author=author,
785
+ license=license_type,
786
+ compatibility=compatibility,
787
+ template=template,
788
+ use_ai=not template,
789
+ generate_script=generate_script
790
+ )
791
+
792
+ elif args.skills_command == "prompt":
793
+ dirs = args.dirs if hasattr(args, 'dirs') and args.dirs else None
794
+ handler.generate_prompt(dirs, include_defaults=True)
795
+
796
+ elif args.skills_command == "bundle":
797
+ dirs = args.dirs if hasattr(args, 'dirs') and args.dirs else None
798
+ return handler.handle_bundle(
799
+ getattr(args, 'bundle_command', None),
800
+ getattr(args, 'name', None),
801
+ dirs,
802
+ )
803
+
804
+ elif args.skills_command == "upload":
805
+ if not hasattr(args, 'path') or not args.path:
806
+ print("Error: --path is required for upload command")
807
+ return 1
808
+ title = getattr(args, 'title', None)
809
+ handler.upload_skill(args.path, title)
810
+
811
+ else:
812
+ print(f"Unknown skills command: {args.skills_command}")
813
+ return 1
814
+
815
+ except Exception as e:
816
+ print(f"Error: {e}")
817
+ return 1
818
+
819
+ return 0
820
+
821
+
822
+ def add_skills_parser(subparsers) -> None:
823
+ """Add skills subcommand to argument parser.
824
+
825
+ Args:
826
+ subparsers: Subparsers object from argparse
827
+ """
828
+ # subparsers is already the skills subparsers object
829
+ # We just need to add the subcommands to it
830
+
831
+ # list command
832
+ list_parser = subparsers.add_parser(
833
+ 'list',
834
+ help='List available skills'
835
+ )
836
+ list_parser.add_argument(
837
+ '--dirs',
838
+ nargs='+',
839
+ help='Directories to scan for skills'
840
+ )
841
+
842
+ # validate command
843
+ validate_parser = subparsers.add_parser(
844
+ 'validate',
845
+ help='Validate a skill directory'
846
+ )
847
+ validate_parser.add_argument(
848
+ '--path',
849
+ required=True,
850
+ help='Path to skill directory'
851
+ )
852
+
853
+ # create command
854
+ create_parser = subparsers.add_parser(
855
+ 'create',
856
+ help='Create a new skill (uses AI by default, --template for template only)'
857
+ )
858
+ create_parser.add_argument(
859
+ '--name',
860
+ required=True,
861
+ help='Skill name (kebab-case, e.g., my-skill)'
862
+ )
863
+ create_parser.add_argument(
864
+ '--description',
865
+ default='A custom skill',
866
+ help='Skill description (used as prompt for AI generation)'
867
+ )
868
+ create_parser.add_argument(
869
+ '--output-dir', '--output',
870
+ dest='output_dir',
871
+ help='Output directory (default: current directory)'
872
+ )
873
+ create_parser.add_argument(
874
+ '--author',
875
+ help='Author name for skill metadata'
876
+ )
877
+ create_parser.add_argument(
878
+ '--license',
879
+ help='License type (default: Apache-2.0)'
880
+ )
881
+ create_parser.add_argument(
882
+ '--compatibility',
883
+ help='Compatibility information'
884
+ )
885
+ create_parser.add_argument(
886
+ '--template',
887
+ action='store_true',
888
+ help='Use template only, skip AI generation'
889
+ )
890
+ create_parser.add_argument(
891
+ '--script',
892
+ action='store_true',
893
+ help='Generate scripts/skill.py with template code'
894
+ )
895
+
896
+ # prompt command
897
+ prompt_parser = subparsers.add_parser(
898
+ 'prompt',
899
+ help='Generate prompt XML for skills'
900
+ )
901
+ prompt_parser.add_argument(
902
+ '--dirs',
903
+ nargs='+',
904
+ help='Directories to scan for skills'
905
+ )
906
+
907
+ # upload command - upload skill to Anthropic
908
+ upload_parser = subparsers.add_parser(
909
+ 'upload',
910
+ help='Upload skill to Anthropic Skills API'
911
+ )
912
+ upload_parser.add_argument(
913
+ '--path',
914
+ required=True,
915
+ help='Path to skill directory'
916
+ )
917
+ upload_parser.add_argument(
918
+ '--title',
919
+ help='Display title for the skill'
920
+ )
921
+
922
+ # bundle command - inspect named sets of skills
923
+ bundle_parser = subparsers.add_parser(
924
+ 'bundle',
925
+ help='Inspect skill bundles (named, reusable sets of skills)'
926
+ )
927
+ bundle_sub = bundle_parser.add_subparsers(
928
+ dest='bundle_command', help='Bundle commands'
929
+ )
930
+ bundle_list_parser = bundle_sub.add_parser('list', help='List skill bundles')
931
+ bundle_list_parser.add_argument(
932
+ '--dirs', nargs='+', help='Directories to scan for bundles'
933
+ )
934
+ bundle_show_parser = bundle_sub.add_parser(
935
+ 'show', help='Show the members of a skill bundle'
936
+ )
937
+ bundle_show_parser.add_argument('name', help='Bundle name')
938
+ bundle_show_parser.add_argument(
939
+ '--dirs', nargs='+', help='Directories to scan for bundles'
940
+ )