PraisonAI 3.0.0__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 (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,856 @@
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"),
536
+ verbose=False
537
+ )
538
+
539
+ result = agent.start(prompt)
540
+
541
+ # Parse the response
542
+ if result and "---SKILL.MD---" in result:
543
+ parts = result.split("---SKILL.MD---")
544
+ if len(parts) > 1:
545
+ skill_md_part = parts[1]
546
+ skill_py = None
547
+
548
+ if "---SKILL.PY---" in skill_md_part:
549
+ md_parts = skill_md_part.split("---SKILL.PY---")
550
+ skill_md = self._strip_code_blocks(md_parts[0].strip())
551
+ skill_py_content = md_parts[1].strip() if len(md_parts) > 1 else None
552
+ if skill_py_content and skill_py_content.upper() != "NONE":
553
+ skill_py = self._strip_code_blocks(skill_py_content)
554
+ else:
555
+ skill_md = self._strip_code_blocks(skill_md_part.strip())
556
+
557
+ return {"skill_md": skill_md, "skill_py": skill_py}
558
+
559
+ return None
560
+
561
+ except Exception as e:
562
+ if self.verbose:
563
+ print(f"⚠ AI generation failed: {e}")
564
+ return None
565
+
566
+ def _strip_code_blocks(self, content: str) -> str:
567
+ """Strip markdown code block wrappers from content.
568
+
569
+ Args:
570
+ content: Content that may be wrapped in ```markdown or ```python blocks
571
+
572
+ Returns:
573
+ Content with code block wrappers removed
574
+ """
575
+ import re
576
+ # Match ```language at start and ``` at end
577
+ pattern = r'^```(?:markdown|python|json|yaml|md)?\s*\n?(.*?)\n?```\s*$'
578
+ match = re.match(pattern, content, re.DOTALL)
579
+ if match:
580
+ return match.group(1).strip()
581
+ return content
582
+
583
+ def upload_skill(
584
+ self,
585
+ skill_path: str,
586
+ display_title: Optional[str] = None
587
+ ) -> Optional[str]:
588
+ """Upload a skill to Anthropic Skills API.
589
+
590
+ Args:
591
+ skill_path: Path to the skill directory
592
+ display_title: Optional display title for the skill
593
+
594
+ Returns:
595
+ Skill ID if successful, None otherwise
596
+ """
597
+ path = Path(skill_path).expanduser().resolve()
598
+
599
+ if not path.exists():
600
+ raise ValueError(f"Skill path does not exist: {path}")
601
+
602
+ skill_md_path = path / "SKILL.md"
603
+ if not skill_md_path.exists():
604
+ raise ValueError(f"SKILL.md not found in {path}")
605
+
606
+ # Check for Anthropic API key
607
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
608
+ if not api_key:
609
+ print("Error: ANTHROPIC_API_KEY environment variable is required")
610
+ return None
611
+
612
+ try:
613
+ import anthropic
614
+
615
+ client = anthropic.Anthropic(api_key=api_key)
616
+
617
+ # Read skill name from SKILL.md
618
+ skill_md_content = skill_md_path.read_text()
619
+ skill_name = path.name
620
+
621
+ # Extract name from frontmatter if possible
622
+ if "---" in skill_md_content:
623
+ import re
624
+ match = re.search(r'^name:\s*(.+)$', skill_md_content, re.MULTILINE)
625
+ if match:
626
+ skill_name = match.group(1).strip()
627
+
628
+ title = display_title or skill_name.replace('-', ' ').title()
629
+
630
+ if self.verbose:
631
+ print(f"Uploading skill '{skill_name}' to Anthropic...")
632
+
633
+ # Create skill using Anthropic API
634
+ with open(skill_md_path, 'rb') as f:
635
+ response = client.beta.skills.create(
636
+ display_title=title,
637
+ files=[(f"{skill_name}/SKILL.md", f, "text/markdown")],
638
+ betas=["skills-2025-10-02"]
639
+ )
640
+
641
+ if self.verbose:
642
+ print("✓ Skill uploaded successfully!")
643
+ print(f" ID: {response.id}")
644
+ print(f" Title: {response.display_title}")
645
+
646
+ return response.id
647
+
648
+ except ImportError:
649
+ print("Error: anthropic package is required. Install with: pip install anthropic")
650
+ return None
651
+ except Exception as e:
652
+ if self.verbose:
653
+ print(f"❌ Upload failed: {e}")
654
+ return None
655
+
656
+ def generate_prompt(
657
+ self,
658
+ skill_dirs: Optional[List[str]] = None,
659
+ include_defaults: bool = True
660
+ ) -> str:
661
+ """Generate prompt XML for available skills.
662
+
663
+ Args:
664
+ skill_dirs: Optional list of directories to scan
665
+ include_defaults: Whether to include default skill directories
666
+
667
+ Returns:
668
+ XML string with <available_skills> block
669
+ """
670
+ from praisonaiagents.skills import SkillManager
671
+
672
+ manager = SkillManager()
673
+
674
+ if skill_dirs:
675
+ manager.discover(skill_dirs, include_defaults=include_defaults)
676
+ elif include_defaults:
677
+ manager.discover(include_defaults=True)
678
+
679
+ prompt = manager.to_prompt()
680
+
681
+ if self.verbose:
682
+ print(prompt)
683
+
684
+ return prompt
685
+
686
+
687
+ def handle_skills_command(args) -> int:
688
+ """Handle skills subcommand from CLI.
689
+
690
+ Args:
691
+ args: Parsed command line arguments
692
+
693
+ Returns:
694
+ Exit code (0 for success, non-zero for error)
695
+ """
696
+ handler = SkillsHandler(verbose=True)
697
+
698
+ try:
699
+ if args.skills_command == "list":
700
+ dirs = args.dirs if hasattr(args, 'dirs') and args.dirs else None
701
+ handler.list_skills(dirs, include_defaults=True)
702
+
703
+ elif args.skills_command == "validate":
704
+ if not hasattr(args, 'path') or not args.path:
705
+ print("Error: --path is required for validate command")
706
+ return 1
707
+ result = handler.validate_skill(args.path)
708
+ return 0 if result["valid"] else 1
709
+
710
+ elif args.skills_command == "create":
711
+ if not hasattr(args, 'name') or not args.name:
712
+ print("Error: --name is required for create command")
713
+ return 1
714
+
715
+ # Get all optional arguments
716
+ description = getattr(args, 'description', None) or "A custom skill"
717
+ output_dir = getattr(args, 'output_dir', None) or getattr(args, 'output', None)
718
+ author = getattr(args, 'author', None)
719
+ license_type = getattr(args, 'license', None)
720
+ compatibility = getattr(args, 'compatibility', None)
721
+ template = getattr(args, 'template', False)
722
+ generate_script = getattr(args, 'script', False)
723
+
724
+ handler.create_skill(
725
+ name=args.name,
726
+ description=description,
727
+ output_dir=output_dir,
728
+ author=author,
729
+ license=license_type,
730
+ compatibility=compatibility,
731
+ template=template,
732
+ use_ai=not template,
733
+ generate_script=generate_script
734
+ )
735
+
736
+ elif args.skills_command == "prompt":
737
+ dirs = args.dirs if hasattr(args, 'dirs') and args.dirs else None
738
+ handler.generate_prompt(dirs, include_defaults=True)
739
+
740
+ elif args.skills_command == "upload":
741
+ if not hasattr(args, 'path') or not args.path:
742
+ print("Error: --path is required for upload command")
743
+ return 1
744
+ title = getattr(args, 'title', None)
745
+ handler.upload_skill(args.path, title)
746
+
747
+ else:
748
+ print(f"Unknown skills command: {args.skills_command}")
749
+ return 1
750
+
751
+ except Exception as e:
752
+ print(f"Error: {e}")
753
+ return 1
754
+
755
+ return 0
756
+
757
+
758
+ def add_skills_parser(subparsers) -> None:
759
+ """Add skills subcommand to argument parser.
760
+
761
+ Args:
762
+ subparsers: Subparsers object from argparse
763
+ """
764
+ # subparsers is already the skills subparsers object
765
+ # We just need to add the subcommands to it
766
+
767
+ # list command
768
+ list_parser = subparsers.add_parser(
769
+ 'list',
770
+ help='List available skills'
771
+ )
772
+ list_parser.add_argument(
773
+ '--dirs',
774
+ nargs='+',
775
+ help='Directories to scan for skills'
776
+ )
777
+
778
+ # validate command
779
+ validate_parser = subparsers.add_parser(
780
+ 'validate',
781
+ help='Validate a skill directory'
782
+ )
783
+ validate_parser.add_argument(
784
+ '--path',
785
+ required=True,
786
+ help='Path to skill directory'
787
+ )
788
+
789
+ # create command
790
+ create_parser = subparsers.add_parser(
791
+ 'create',
792
+ help='Create a new skill (uses AI by default, --template for template only)'
793
+ )
794
+ create_parser.add_argument(
795
+ '--name',
796
+ required=True,
797
+ help='Skill name (kebab-case, e.g., my-skill)'
798
+ )
799
+ create_parser.add_argument(
800
+ '--description',
801
+ default='A custom skill',
802
+ help='Skill description (used as prompt for AI generation)'
803
+ )
804
+ create_parser.add_argument(
805
+ '--output-dir', '--output',
806
+ dest='output_dir',
807
+ help='Output directory (default: current directory)'
808
+ )
809
+ create_parser.add_argument(
810
+ '--author',
811
+ help='Author name for skill metadata'
812
+ )
813
+ create_parser.add_argument(
814
+ '--license',
815
+ help='License type (default: Apache-2.0)'
816
+ )
817
+ create_parser.add_argument(
818
+ '--compatibility',
819
+ help='Compatibility information'
820
+ )
821
+ create_parser.add_argument(
822
+ '--template',
823
+ action='store_true',
824
+ help='Use template only, skip AI generation'
825
+ )
826
+ create_parser.add_argument(
827
+ '--script',
828
+ action='store_true',
829
+ help='Generate scripts/skill.py with template code'
830
+ )
831
+
832
+ # prompt command
833
+ prompt_parser = subparsers.add_parser(
834
+ 'prompt',
835
+ help='Generate prompt XML for skills'
836
+ )
837
+ prompt_parser.add_argument(
838
+ '--dirs',
839
+ nargs='+',
840
+ help='Directories to scan for skills'
841
+ )
842
+
843
+ # upload command - upload skill to Anthropic
844
+ upload_parser = subparsers.add_parser(
845
+ 'upload',
846
+ help='Upload skill to Anthropic Skills API'
847
+ )
848
+ upload_parser.add_argument(
849
+ '--path',
850
+ required=True,
851
+ help='Path to skill directory'
852
+ )
853
+ upload_parser.add_argument(
854
+ '--title',
855
+ help='Display title for the skill'
856
+ )