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.
- praisonai/__init__.py +54 -0
- praisonai/__main__.py +15 -0
- praisonai/acp/__init__.py +54 -0
- praisonai/acp/config.py +159 -0
- praisonai/acp/server.py +587 -0
- praisonai/acp/session.py +219 -0
- praisonai/adapters/__init__.py +50 -0
- praisonai/adapters/readers.py +395 -0
- praisonai/adapters/rerankers.py +315 -0
- praisonai/adapters/retrievers.py +394 -0
- praisonai/adapters/vector_stores.py +409 -0
- praisonai/agent_scheduler.py +337 -0
- praisonai/agents_generator.py +903 -0
- praisonai/api/call.py +292 -0
- praisonai/auto.py +1197 -0
- praisonai/capabilities/__init__.py +275 -0
- praisonai/capabilities/a2a.py +140 -0
- praisonai/capabilities/assistants.py +283 -0
- praisonai/capabilities/audio.py +320 -0
- praisonai/capabilities/batches.py +469 -0
- praisonai/capabilities/completions.py +336 -0
- praisonai/capabilities/container_files.py +155 -0
- praisonai/capabilities/containers.py +93 -0
- praisonai/capabilities/embeddings.py +158 -0
- praisonai/capabilities/files.py +467 -0
- praisonai/capabilities/fine_tuning.py +293 -0
- praisonai/capabilities/guardrails.py +182 -0
- praisonai/capabilities/images.py +330 -0
- praisonai/capabilities/mcp.py +190 -0
- praisonai/capabilities/messages.py +270 -0
- praisonai/capabilities/moderations.py +154 -0
- praisonai/capabilities/ocr.py +217 -0
- praisonai/capabilities/passthrough.py +204 -0
- praisonai/capabilities/rag.py +207 -0
- praisonai/capabilities/realtime.py +160 -0
- praisonai/capabilities/rerank.py +165 -0
- praisonai/capabilities/responses.py +266 -0
- praisonai/capabilities/search.py +109 -0
- praisonai/capabilities/skills.py +133 -0
- praisonai/capabilities/vector_store_files.py +334 -0
- praisonai/capabilities/vector_stores.py +304 -0
- praisonai/capabilities/videos.py +141 -0
- praisonai/chainlit_ui.py +304 -0
- praisonai/chat/__init__.py +106 -0
- praisonai/chat/app.py +125 -0
- praisonai/cli/__init__.py +26 -0
- praisonai/cli/app.py +213 -0
- praisonai/cli/commands/__init__.py +75 -0
- praisonai/cli/commands/acp.py +70 -0
- praisonai/cli/commands/completion.py +333 -0
- praisonai/cli/commands/config.py +166 -0
- praisonai/cli/commands/debug.py +142 -0
- praisonai/cli/commands/diag.py +55 -0
- praisonai/cli/commands/doctor.py +166 -0
- praisonai/cli/commands/environment.py +179 -0
- praisonai/cli/commands/lsp.py +112 -0
- praisonai/cli/commands/mcp.py +210 -0
- praisonai/cli/commands/profile.py +457 -0
- praisonai/cli/commands/run.py +228 -0
- praisonai/cli/commands/schedule.py +150 -0
- praisonai/cli/commands/serve.py +97 -0
- praisonai/cli/commands/session.py +212 -0
- praisonai/cli/commands/traces.py +145 -0
- praisonai/cli/commands/version.py +101 -0
- praisonai/cli/configuration/__init__.py +18 -0
- praisonai/cli/configuration/loader.py +353 -0
- praisonai/cli/configuration/paths.py +114 -0
- praisonai/cli/configuration/schema.py +164 -0
- praisonai/cli/features/__init__.py +268 -0
- praisonai/cli/features/acp.py +236 -0
- praisonai/cli/features/action_orchestrator.py +546 -0
- praisonai/cli/features/agent_scheduler.py +773 -0
- praisonai/cli/features/agent_tools.py +474 -0
- praisonai/cli/features/agents.py +375 -0
- praisonai/cli/features/at_mentions.py +471 -0
- praisonai/cli/features/auto_memory.py +182 -0
- praisonai/cli/features/autonomy_mode.py +490 -0
- praisonai/cli/features/background.py +356 -0
- praisonai/cli/features/base.py +168 -0
- praisonai/cli/features/capabilities.py +1326 -0
- praisonai/cli/features/checkpoints.py +338 -0
- praisonai/cli/features/code_intelligence.py +652 -0
- praisonai/cli/features/compaction.py +294 -0
- praisonai/cli/features/compare.py +534 -0
- praisonai/cli/features/cost_tracker.py +514 -0
- praisonai/cli/features/debug.py +810 -0
- praisonai/cli/features/deploy.py +517 -0
- praisonai/cli/features/diag.py +289 -0
- praisonai/cli/features/doctor/__init__.py +63 -0
- praisonai/cli/features/doctor/checks/__init__.py +24 -0
- praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
- praisonai/cli/features/doctor/checks/config_checks.py +366 -0
- praisonai/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai/cli/features/doctor/checks/env_checks.py +543 -0
- praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
- praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
- praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
- praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai/cli/features/doctor/engine.py +266 -0
- praisonai/cli/features/doctor/formatters.py +310 -0
- praisonai/cli/features/doctor/handler.py +397 -0
- praisonai/cli/features/doctor/models.py +264 -0
- praisonai/cli/features/doctor/registry.py +239 -0
- praisonai/cli/features/endpoints.py +1019 -0
- praisonai/cli/features/eval.py +560 -0
- praisonai/cli/features/external_agents.py +231 -0
- praisonai/cli/features/fast_context.py +410 -0
- praisonai/cli/features/flow_display.py +566 -0
- praisonai/cli/features/git_integration.py +651 -0
- praisonai/cli/features/guardrail.py +171 -0
- praisonai/cli/features/handoff.py +185 -0
- praisonai/cli/features/hooks.py +583 -0
- praisonai/cli/features/image.py +384 -0
- praisonai/cli/features/interactive_runtime.py +585 -0
- praisonai/cli/features/interactive_tools.py +380 -0
- praisonai/cli/features/interactive_tui.py +603 -0
- praisonai/cli/features/jobs.py +632 -0
- praisonai/cli/features/knowledge.py +531 -0
- praisonai/cli/features/lite.py +244 -0
- praisonai/cli/features/lsp_cli.py +225 -0
- praisonai/cli/features/mcp.py +169 -0
- praisonai/cli/features/message_queue.py +587 -0
- praisonai/cli/features/metrics.py +211 -0
- praisonai/cli/features/n8n.py +673 -0
- praisonai/cli/features/observability.py +293 -0
- praisonai/cli/features/ollama.py +361 -0
- praisonai/cli/features/output_style.py +273 -0
- praisonai/cli/features/package.py +631 -0
- praisonai/cli/features/performance.py +308 -0
- praisonai/cli/features/persistence.py +636 -0
- praisonai/cli/features/profile.py +226 -0
- praisonai/cli/features/profiler/__init__.py +81 -0
- praisonai/cli/features/profiler/core.py +558 -0
- praisonai/cli/features/profiler/optimizations.py +652 -0
- praisonai/cli/features/profiler/suite.py +386 -0
- praisonai/cli/features/profiling.py +350 -0
- praisonai/cli/features/queue/__init__.py +73 -0
- praisonai/cli/features/queue/manager.py +395 -0
- praisonai/cli/features/queue/models.py +286 -0
- praisonai/cli/features/queue/persistence.py +564 -0
- praisonai/cli/features/queue/scheduler.py +484 -0
- praisonai/cli/features/queue/worker.py +372 -0
- praisonai/cli/features/recipe.py +1723 -0
- praisonai/cli/features/recipes.py +449 -0
- praisonai/cli/features/registry.py +229 -0
- praisonai/cli/features/repo_map.py +860 -0
- praisonai/cli/features/router.py +466 -0
- praisonai/cli/features/sandbox_executor.py +515 -0
- praisonai/cli/features/serve.py +829 -0
- praisonai/cli/features/session.py +222 -0
- praisonai/cli/features/skills.py +856 -0
- praisonai/cli/features/slash_commands.py +650 -0
- praisonai/cli/features/telemetry.py +179 -0
- praisonai/cli/features/templates.py +1384 -0
- praisonai/cli/features/thinking.py +305 -0
- praisonai/cli/features/todo.py +334 -0
- praisonai/cli/features/tools.py +680 -0
- praisonai/cli/features/tui/__init__.py +83 -0
- praisonai/cli/features/tui/app.py +580 -0
- praisonai/cli/features/tui/cli.py +566 -0
- praisonai/cli/features/tui/debug.py +511 -0
- praisonai/cli/features/tui/events.py +99 -0
- praisonai/cli/features/tui/mock_provider.py +328 -0
- praisonai/cli/features/tui/orchestrator.py +652 -0
- praisonai/cli/features/tui/screens/__init__.py +50 -0
- praisonai/cli/features/tui/screens/main.py +245 -0
- praisonai/cli/features/tui/screens/queue.py +174 -0
- praisonai/cli/features/tui/screens/session.py +124 -0
- praisonai/cli/features/tui/screens/settings.py +148 -0
- praisonai/cli/features/tui/widgets/__init__.py +56 -0
- praisonai/cli/features/tui/widgets/chat.py +261 -0
- praisonai/cli/features/tui/widgets/composer.py +224 -0
- praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
- praisonai/cli/features/tui/widgets/status.py +167 -0
- praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
- praisonai/cli/features/workflow.py +720 -0
- praisonai/cli/legacy.py +236 -0
- praisonai/cli/main.py +5559 -0
- praisonai/cli/schedule_cli.py +54 -0
- praisonai/cli/state/__init__.py +31 -0
- praisonai/cli/state/identifiers.py +161 -0
- praisonai/cli/state/sessions.py +313 -0
- praisonai/code/__init__.py +93 -0
- praisonai/code/agent_tools.py +344 -0
- praisonai/code/diff/__init__.py +21 -0
- praisonai/code/diff/diff_strategy.py +432 -0
- praisonai/code/tools/__init__.py +27 -0
- praisonai/code/tools/apply_diff.py +221 -0
- praisonai/code/tools/execute_command.py +275 -0
- praisonai/code/tools/list_files.py +274 -0
- praisonai/code/tools/read_file.py +206 -0
- praisonai/code/tools/search_replace.py +248 -0
- praisonai/code/tools/write_file.py +217 -0
- praisonai/code/utils/__init__.py +46 -0
- praisonai/code/utils/file_utils.py +307 -0
- praisonai/code/utils/ignore_utils.py +308 -0
- praisonai/code/utils/text_utils.py +276 -0
- praisonai/db/__init__.py +64 -0
- praisonai/db/adapter.py +531 -0
- praisonai/deploy/__init__.py +62 -0
- praisonai/deploy/api.py +231 -0
- praisonai/deploy/docker.py +454 -0
- praisonai/deploy/doctor.py +367 -0
- praisonai/deploy/main.py +327 -0
- praisonai/deploy/models.py +179 -0
- praisonai/deploy/providers/__init__.py +33 -0
- praisonai/deploy/providers/aws.py +331 -0
- praisonai/deploy/providers/azure.py +358 -0
- praisonai/deploy/providers/base.py +101 -0
- praisonai/deploy/providers/gcp.py +314 -0
- praisonai/deploy/schema.py +208 -0
- praisonai/deploy.py +185 -0
- praisonai/endpoints/__init__.py +53 -0
- praisonai/endpoints/a2u_server.py +410 -0
- praisonai/endpoints/discovery.py +165 -0
- praisonai/endpoints/providers/__init__.py +28 -0
- praisonai/endpoints/providers/a2a.py +253 -0
- praisonai/endpoints/providers/a2u.py +208 -0
- praisonai/endpoints/providers/agents_api.py +171 -0
- praisonai/endpoints/providers/base.py +231 -0
- praisonai/endpoints/providers/mcp.py +263 -0
- praisonai/endpoints/providers/recipe.py +206 -0
- praisonai/endpoints/providers/tools_mcp.py +150 -0
- praisonai/endpoints/registry.py +131 -0
- praisonai/endpoints/server.py +161 -0
- praisonai/inbuilt_tools/__init__.py +24 -0
- praisonai/inbuilt_tools/autogen_tools.py +117 -0
- praisonai/inc/__init__.py +2 -0
- praisonai/inc/config.py +96 -0
- praisonai/inc/models.py +155 -0
- praisonai/integrations/__init__.py +56 -0
- praisonai/integrations/base.py +303 -0
- praisonai/integrations/claude_code.py +270 -0
- praisonai/integrations/codex_cli.py +255 -0
- praisonai/integrations/cursor_cli.py +195 -0
- praisonai/integrations/gemini_cli.py +222 -0
- praisonai/jobs/__init__.py +67 -0
- praisonai/jobs/executor.py +425 -0
- praisonai/jobs/models.py +230 -0
- praisonai/jobs/router.py +314 -0
- praisonai/jobs/server.py +186 -0
- praisonai/jobs/store.py +203 -0
- praisonai/llm/__init__.py +66 -0
- praisonai/llm/registry.py +382 -0
- praisonai/mcp_server/__init__.py +152 -0
- praisonai/mcp_server/adapters/__init__.py +74 -0
- praisonai/mcp_server/adapters/agents.py +128 -0
- praisonai/mcp_server/adapters/capabilities.py +168 -0
- praisonai/mcp_server/adapters/cli_tools.py +568 -0
- praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
- praisonai/mcp_server/adapters/knowledge.py +93 -0
- praisonai/mcp_server/adapters/memory.py +104 -0
- praisonai/mcp_server/adapters/prompts.py +306 -0
- praisonai/mcp_server/adapters/resources.py +124 -0
- praisonai/mcp_server/adapters/tools_bridge.py +280 -0
- praisonai/mcp_server/auth/__init__.py +48 -0
- praisonai/mcp_server/auth/api_key.py +291 -0
- praisonai/mcp_server/auth/oauth.py +460 -0
- praisonai/mcp_server/auth/oidc.py +289 -0
- praisonai/mcp_server/auth/scopes.py +260 -0
- praisonai/mcp_server/cli.py +852 -0
- praisonai/mcp_server/elicitation.py +445 -0
- praisonai/mcp_server/icons.py +302 -0
- praisonai/mcp_server/recipe_adapter.py +573 -0
- praisonai/mcp_server/recipe_cli.py +824 -0
- praisonai/mcp_server/registry.py +703 -0
- praisonai/mcp_server/sampling.py +422 -0
- praisonai/mcp_server/server.py +490 -0
- praisonai/mcp_server/tasks.py +443 -0
- praisonai/mcp_server/transports/__init__.py +18 -0
- praisonai/mcp_server/transports/http_stream.py +376 -0
- praisonai/mcp_server/transports/stdio.py +132 -0
- praisonai/persistence/__init__.py +84 -0
- praisonai/persistence/config.py +238 -0
- praisonai/persistence/conversation/__init__.py +25 -0
- praisonai/persistence/conversation/async_mysql.py +427 -0
- praisonai/persistence/conversation/async_postgres.py +410 -0
- praisonai/persistence/conversation/async_sqlite.py +371 -0
- praisonai/persistence/conversation/base.py +151 -0
- praisonai/persistence/conversation/json_store.py +250 -0
- praisonai/persistence/conversation/mysql.py +387 -0
- praisonai/persistence/conversation/postgres.py +401 -0
- praisonai/persistence/conversation/singlestore.py +240 -0
- praisonai/persistence/conversation/sqlite.py +341 -0
- praisonai/persistence/conversation/supabase.py +203 -0
- praisonai/persistence/conversation/surrealdb.py +287 -0
- praisonai/persistence/factory.py +301 -0
- praisonai/persistence/hooks/__init__.py +18 -0
- praisonai/persistence/hooks/agent_hooks.py +297 -0
- praisonai/persistence/knowledge/__init__.py +26 -0
- praisonai/persistence/knowledge/base.py +144 -0
- praisonai/persistence/knowledge/cassandra.py +232 -0
- praisonai/persistence/knowledge/chroma.py +295 -0
- praisonai/persistence/knowledge/clickhouse.py +242 -0
- praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
- praisonai/persistence/knowledge/couchbase.py +286 -0
- praisonai/persistence/knowledge/lancedb.py +216 -0
- praisonai/persistence/knowledge/langchain_adapter.py +291 -0
- praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
- praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
- praisonai/persistence/knowledge/milvus.py +277 -0
- praisonai/persistence/knowledge/mongodb_vector.py +306 -0
- praisonai/persistence/knowledge/pgvector.py +335 -0
- praisonai/persistence/knowledge/pinecone.py +253 -0
- praisonai/persistence/knowledge/qdrant.py +301 -0
- praisonai/persistence/knowledge/redis_vector.py +291 -0
- praisonai/persistence/knowledge/singlestore_vector.py +299 -0
- praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
- praisonai/persistence/knowledge/upstash_vector.py +266 -0
- praisonai/persistence/knowledge/weaviate.py +223 -0
- praisonai/persistence/migrations/__init__.py +10 -0
- praisonai/persistence/migrations/manager.py +251 -0
- praisonai/persistence/orchestrator.py +406 -0
- praisonai/persistence/state/__init__.py +21 -0
- praisonai/persistence/state/async_mongodb.py +200 -0
- praisonai/persistence/state/base.py +107 -0
- praisonai/persistence/state/dynamodb.py +226 -0
- praisonai/persistence/state/firestore.py +175 -0
- praisonai/persistence/state/gcs.py +155 -0
- praisonai/persistence/state/memory.py +245 -0
- praisonai/persistence/state/mongodb.py +158 -0
- praisonai/persistence/state/redis.py +190 -0
- praisonai/persistence/state/upstash.py +144 -0
- praisonai/persistence/tests/__init__.py +3 -0
- praisonai/persistence/tests/test_all_backends.py +633 -0
- praisonai/profiler.py +1214 -0
- praisonai/recipe/__init__.py +134 -0
- praisonai/recipe/bridge.py +278 -0
- praisonai/recipe/core.py +893 -0
- praisonai/recipe/exceptions.py +54 -0
- praisonai/recipe/history.py +402 -0
- praisonai/recipe/models.py +266 -0
- praisonai/recipe/operations.py +440 -0
- praisonai/recipe/policy.py +422 -0
- praisonai/recipe/registry.py +849 -0
- praisonai/recipe/runtime.py +214 -0
- praisonai/recipe/security.py +711 -0
- praisonai/recipe/serve.py +859 -0
- praisonai/recipe/server.py +613 -0
- praisonai/scheduler/__init__.py +45 -0
- praisonai/scheduler/agent_scheduler.py +552 -0
- praisonai/scheduler/base.py +124 -0
- praisonai/scheduler/daemon_manager.py +225 -0
- praisonai/scheduler/state_manager.py +155 -0
- praisonai/scheduler/yaml_loader.py +193 -0
- praisonai/scheduler.py +194 -0
- praisonai/setup/__init__.py +1 -0
- praisonai/setup/build.py +21 -0
- praisonai/setup/post_install.py +23 -0
- praisonai/setup/setup_conda_env.py +25 -0
- praisonai/setup.py +16 -0
- praisonai/templates/__init__.py +116 -0
- praisonai/templates/cache.py +364 -0
- praisonai/templates/dependency_checker.py +358 -0
- praisonai/templates/discovery.py +391 -0
- praisonai/templates/loader.py +564 -0
- praisonai/templates/registry.py +511 -0
- praisonai/templates/resolver.py +206 -0
- praisonai/templates/security.py +327 -0
- praisonai/templates/tool_override.py +498 -0
- praisonai/templates/tools_doctor.py +256 -0
- praisonai/test.py +105 -0
- praisonai/train.py +562 -0
- praisonai/train_vision.py +306 -0
- praisonai/ui/agents.py +824 -0
- praisonai/ui/callbacks.py +57 -0
- praisonai/ui/chainlit_compat.py +246 -0
- praisonai/ui/chat.py +532 -0
- praisonai/ui/code.py +717 -0
- praisonai/ui/colab.py +474 -0
- praisonai/ui/colab_chainlit.py +81 -0
- praisonai/ui/components/aicoder.py +284 -0
- praisonai/ui/context.py +283 -0
- praisonai/ui/database_config.py +56 -0
- praisonai/ui/db.py +294 -0
- praisonai/ui/realtime.py +488 -0
- praisonai/ui/realtimeclient/__init__.py +756 -0
- praisonai/ui/realtimeclient/tools.py +242 -0
- praisonai/ui/sql_alchemy.py +710 -0
- praisonai/upload_vision.py +140 -0
- praisonai/version.py +1 -0
- praisonai-3.0.0.dist-info/METADATA +3493 -0
- praisonai-3.0.0.dist-info/RECORD +393 -0
- praisonai-3.0.0.dist-info/WHEEL +5 -0
- praisonai-3.0.0.dist-info/entry_points.txt +4 -0
- 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
|
+
)
|