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,1384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Templates CLI Feature Handler
|
|
3
|
+
|
|
4
|
+
Provides CLI commands for template management:
|
|
5
|
+
- list, search, info, install, cache clear
|
|
6
|
+
- run templates directly
|
|
7
|
+
- init projects from templates
|
|
8
|
+
- Custom templates directory support with precedence
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import shutil
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TemplatesHandler:
|
|
17
|
+
"""
|
|
18
|
+
CLI handler for template operations.
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
- list: List available templates
|
|
22
|
+
- search: Search templates by query
|
|
23
|
+
- info: Show template details
|
|
24
|
+
- install: Install a template to cache
|
|
25
|
+
- cache: Cache management (clear, list)
|
|
26
|
+
- run: Run a template directly
|
|
27
|
+
- init: Initialize a project from template
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
"""Initialize the handler with lazy-loaded dependencies."""
|
|
32
|
+
self._loader = None
|
|
33
|
+
self._registry = None
|
|
34
|
+
self._cache = None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def loader(self):
|
|
38
|
+
"""Lazy load template loader."""
|
|
39
|
+
if self._loader is None:
|
|
40
|
+
from praisonai.templates import TemplateLoader
|
|
41
|
+
self._loader = TemplateLoader()
|
|
42
|
+
return self._loader
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def registry(self):
|
|
46
|
+
"""Lazy load template registry."""
|
|
47
|
+
if self._registry is None:
|
|
48
|
+
from praisonai.templates import TemplateRegistry
|
|
49
|
+
self._registry = TemplateRegistry()
|
|
50
|
+
return self._registry
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def cache(self):
|
|
54
|
+
"""Lazy load template cache."""
|
|
55
|
+
if self._cache is None:
|
|
56
|
+
from praisonai.templates import TemplateCache
|
|
57
|
+
self._cache = TemplateCache()
|
|
58
|
+
return self._cache
|
|
59
|
+
|
|
60
|
+
def handle(self, args: List[str]) -> int:
|
|
61
|
+
"""
|
|
62
|
+
Handle templates subcommand.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
args: Command arguments
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Exit code (0 for success)
|
|
69
|
+
"""
|
|
70
|
+
if not args:
|
|
71
|
+
self._print_help()
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
command = args[0]
|
|
75
|
+
remaining = args[1:]
|
|
76
|
+
|
|
77
|
+
commands = {
|
|
78
|
+
"list": self.cmd_list,
|
|
79
|
+
"search": self.cmd_search,
|
|
80
|
+
"info": self.cmd_info,
|
|
81
|
+
"install": self.cmd_install,
|
|
82
|
+
"cache": self.cmd_cache,
|
|
83
|
+
"run": self.cmd_run,
|
|
84
|
+
"init": self.cmd_init,
|
|
85
|
+
"add": self.cmd_add,
|
|
86
|
+
"add-sources": self.cmd_add_sources,
|
|
87
|
+
"remove-sources": self.cmd_remove_sources,
|
|
88
|
+
"browse": self.cmd_browse,
|
|
89
|
+
"catalog": self.cmd_catalog,
|
|
90
|
+
"validate": self.cmd_validate,
|
|
91
|
+
"help": lambda _: self._print_help() or 0,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if command in commands:
|
|
95
|
+
return commands[command](remaining)
|
|
96
|
+
else:
|
|
97
|
+
print(f"[red]Unknown command: {command}[/red]")
|
|
98
|
+
self._print_help()
|
|
99
|
+
return 1
|
|
100
|
+
|
|
101
|
+
def _print_help(self):
|
|
102
|
+
"""Print help message."""
|
|
103
|
+
help_text = """
|
|
104
|
+
[bold cyan]PraisonAI Templates[/bold cyan]
|
|
105
|
+
|
|
106
|
+
[bold]Usage:[/bold]
|
|
107
|
+
praisonai templates <command> [options]
|
|
108
|
+
|
|
109
|
+
[bold]Commands:[/bold]
|
|
110
|
+
list List available templates
|
|
111
|
+
search <query> Search templates by name or tags
|
|
112
|
+
info <template> Show template details
|
|
113
|
+
install <uri> Install a template to cache
|
|
114
|
+
cache clear Clear the template cache
|
|
115
|
+
cache list List cached templates
|
|
116
|
+
run <template> Run a template directly
|
|
117
|
+
init <name> Initialize a project from template
|
|
118
|
+
add <source> Add template from GitHub or local path
|
|
119
|
+
add-sources <src> Add a template source to persistent config
|
|
120
|
+
remove-sources Remove a template source from config
|
|
121
|
+
browse Open template catalog in browser
|
|
122
|
+
catalog build Build catalog locally
|
|
123
|
+
catalog sync Sync catalog sources
|
|
124
|
+
validate Validate template YAML files
|
|
125
|
+
|
|
126
|
+
[bold]Options:[/bold]
|
|
127
|
+
--offline Use only cached templates (no network)
|
|
128
|
+
--source <src> Filter by source (custom, package, all)
|
|
129
|
+
--custom-dir <path> Add custom templates directory
|
|
130
|
+
--paths Show template search paths
|
|
131
|
+
|
|
132
|
+
[bold]Examples:[/bold]
|
|
133
|
+
praisonai templates list
|
|
134
|
+
praisonai templates search video
|
|
135
|
+
praisonai templates info transcript-generator
|
|
136
|
+
praisonai templates install github:MervinPraison/agent-recipes/transcript-generator
|
|
137
|
+
praisonai templates run transcript-generator ./audio.mp3
|
|
138
|
+
praisonai templates add github:user/repo/my-template
|
|
139
|
+
praisonai templates add-sources github:MervinPraison/Agent-Recipes
|
|
140
|
+
praisonai init my-project --template transcript-generator
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
from rich import print as rprint
|
|
144
|
+
rprint(help_text)
|
|
145
|
+
except ImportError:
|
|
146
|
+
print(help_text.replace("[bold cyan]", "").replace("[/bold cyan]", "")
|
|
147
|
+
.replace("[bold]", "").replace("[/bold]", "")
|
|
148
|
+
.replace("[red]", "").replace("[/red]", ""))
|
|
149
|
+
|
|
150
|
+
def cmd_list(self, args: List[str]) -> int:
|
|
151
|
+
"""List available templates from all sources including custom directories."""
|
|
152
|
+
source_filter = None
|
|
153
|
+
custom_dirs = []
|
|
154
|
+
show_paths = "--paths" in args
|
|
155
|
+
|
|
156
|
+
# Parse --source filter
|
|
157
|
+
if "--source" in args:
|
|
158
|
+
idx = args.index("--source")
|
|
159
|
+
if idx + 1 < len(args):
|
|
160
|
+
source_filter = args[idx + 1]
|
|
161
|
+
|
|
162
|
+
# Parse --custom-dir (can be specified multiple times)
|
|
163
|
+
i = 0
|
|
164
|
+
while i < len(args):
|
|
165
|
+
if args[i] == "--custom-dir" and i + 1 < len(args):
|
|
166
|
+
custom_dirs.append(args[i + 1])
|
|
167
|
+
i += 2
|
|
168
|
+
else:
|
|
169
|
+
i += 1
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
from praisonai.templates.discovery import TemplateDiscovery
|
|
173
|
+
|
|
174
|
+
discovery = TemplateDiscovery(
|
|
175
|
+
custom_dirs=custom_dirs if custom_dirs else None,
|
|
176
|
+
include_package=True,
|
|
177
|
+
include_defaults=True
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Show search paths if requested
|
|
181
|
+
if show_paths:
|
|
182
|
+
print("\nTemplate Search Paths (in priority order):")
|
|
183
|
+
for path, source, exists in discovery.get_search_paths():
|
|
184
|
+
status = "✓" if exists else "✗"
|
|
185
|
+
print(f" {status} [{source}] {path}")
|
|
186
|
+
print()
|
|
187
|
+
|
|
188
|
+
# Discover templates
|
|
189
|
+
templates = discovery.list_templates(source_filter=source_filter)
|
|
190
|
+
|
|
191
|
+
if not templates:
|
|
192
|
+
print("No templates found.")
|
|
193
|
+
if not show_paths:
|
|
194
|
+
print("Use --paths to see search locations.")
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
from rich.console import Console
|
|
199
|
+
from rich.table import Table
|
|
200
|
+
|
|
201
|
+
console = Console()
|
|
202
|
+
table = Table(title="Available Templates")
|
|
203
|
+
table.add_column("Name", style="cyan")
|
|
204
|
+
table.add_column("Version", style="green")
|
|
205
|
+
table.add_column("Description")
|
|
206
|
+
table.add_column("Source", style="dim")
|
|
207
|
+
|
|
208
|
+
for t in sorted(templates, key=lambda x: (x.priority, x.name)):
|
|
209
|
+
desc = t.description or ""
|
|
210
|
+
table.add_row(
|
|
211
|
+
t.name,
|
|
212
|
+
t.version or "1.0.0",
|
|
213
|
+
desc[:50] + "..." if len(desc) > 50 else desc,
|
|
214
|
+
t.source
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
console.print(table)
|
|
218
|
+
except ImportError:
|
|
219
|
+
print(f"{'Name':<25} {'Version':<10} {'Source':<10} {'Description':<35}")
|
|
220
|
+
print("-" * 80)
|
|
221
|
+
for t in sorted(templates, key=lambda x: (x.priority, x.name)):
|
|
222
|
+
desc = (t.description or "")[:35]
|
|
223
|
+
if len(t.description or "") > 35:
|
|
224
|
+
desc += "..."
|
|
225
|
+
print(f"{t.name:<25} {t.version or '1.0.0':<10} {t.source:<10} {desc:<35}")
|
|
226
|
+
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"Error listing templates: {e}")
|
|
231
|
+
import traceback
|
|
232
|
+
traceback.print_exc()
|
|
233
|
+
return 1
|
|
234
|
+
|
|
235
|
+
def cmd_search(self, args: List[str]) -> int:
|
|
236
|
+
"""Search templates by query."""
|
|
237
|
+
if not args or args[0].startswith("--"):
|
|
238
|
+
print("Usage: praisonai templates search <query>")
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
query = args[0]
|
|
242
|
+
offline = "--offline" in args
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
from praisonai.templates import search_templates
|
|
246
|
+
templates = search_templates(query, offline=offline)
|
|
247
|
+
|
|
248
|
+
if not templates:
|
|
249
|
+
print(f"No templates found matching '{query}'")
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
print(f"Found {len(templates)} template(s) matching '{query}':\n")
|
|
253
|
+
for t in templates:
|
|
254
|
+
print(f" • {t.name} (v{t.version})")
|
|
255
|
+
if t.description:
|
|
256
|
+
print(f" {t.description}")
|
|
257
|
+
if t.tags:
|
|
258
|
+
print(f" Tags: {', '.join(t.tags)}")
|
|
259
|
+
print()
|
|
260
|
+
|
|
261
|
+
return 0
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
print(f"Error searching templates: {e}")
|
|
265
|
+
return 1
|
|
266
|
+
|
|
267
|
+
def cmd_info(self, args: List[str]) -> int:
|
|
268
|
+
"""Show template details with custom directory support."""
|
|
269
|
+
if not args or args[0].startswith("--"):
|
|
270
|
+
print("Usage: praisonai templates info <template>")
|
|
271
|
+
return 1
|
|
272
|
+
|
|
273
|
+
name_or_uri = args[0]
|
|
274
|
+
offline = "--offline" in args
|
|
275
|
+
custom_dirs = []
|
|
276
|
+
|
|
277
|
+
# Parse --custom-dir
|
|
278
|
+
i = 0
|
|
279
|
+
while i < len(args):
|
|
280
|
+
if args[i] == "--custom-dir" and i + 1 < len(args):
|
|
281
|
+
custom_dirs.append(args[i + 1])
|
|
282
|
+
i += 2
|
|
283
|
+
else:
|
|
284
|
+
i += 1
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
# First try to find in custom/local directories
|
|
288
|
+
from praisonai.templates.discovery import TemplateDiscovery
|
|
289
|
+
|
|
290
|
+
discovery = TemplateDiscovery(
|
|
291
|
+
custom_dirs=custom_dirs if custom_dirs else None,
|
|
292
|
+
include_package=True,
|
|
293
|
+
include_defaults=True
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
discovered = discovery.find_template(name_or_uri)
|
|
297
|
+
|
|
298
|
+
if discovered:
|
|
299
|
+
# Load from discovered path
|
|
300
|
+
from praisonai.templates import load_template
|
|
301
|
+
template = load_template(str(discovered.path), offline=offline)
|
|
302
|
+
else:
|
|
303
|
+
# Fall back to URI resolution
|
|
304
|
+
from praisonai.templates import load_template
|
|
305
|
+
template = load_template(name_or_uri, offline=offline)
|
|
306
|
+
|
|
307
|
+
# Check dependency availability
|
|
308
|
+
from praisonai.templates.dependency_checker import DependencyChecker
|
|
309
|
+
checker = DependencyChecker()
|
|
310
|
+
deps = checker.check_template_dependencies(template)
|
|
311
|
+
|
|
312
|
+
# Build availability strings
|
|
313
|
+
def format_dep_list(items, key="available"):
|
|
314
|
+
result = []
|
|
315
|
+
for item in items:
|
|
316
|
+
status = "✓" if item.get(key, False) else "✗"
|
|
317
|
+
hint = ""
|
|
318
|
+
if not item.get(key, False):
|
|
319
|
+
if item.get("install_hint"):
|
|
320
|
+
hint = f" ({item['install_hint']})"
|
|
321
|
+
result.append(f"{status} {item['name']}{hint}")
|
|
322
|
+
return result
|
|
323
|
+
|
|
324
|
+
tools_status = format_dep_list(deps["tools"])
|
|
325
|
+
pkgs_status = format_dep_list(deps["packages"])
|
|
326
|
+
env_status = format_dep_list(deps["env"])
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
from rich.console import Console
|
|
330
|
+
from rich.panel import Panel
|
|
331
|
+
from rich.markdown import Markdown
|
|
332
|
+
|
|
333
|
+
console = Console()
|
|
334
|
+
|
|
335
|
+
info = f"""
|
|
336
|
+
**Name:** {template.name}
|
|
337
|
+
**Version:** {template.version}
|
|
338
|
+
**Author:** {template.author or 'Unknown'}
|
|
339
|
+
**License:** {template.license or 'Not specified'}
|
|
340
|
+
|
|
341
|
+
**Description:**
|
|
342
|
+
{template.description}
|
|
343
|
+
|
|
344
|
+
**Tags:** {', '.join(template.tags) if template.tags else 'None'}
|
|
345
|
+
|
|
346
|
+
**Skills:** {', '.join(template.skills) if template.skills else 'None'}
|
|
347
|
+
|
|
348
|
+
**Path:** {template.path}
|
|
349
|
+
"""
|
|
350
|
+
console.print(Panel(Markdown(info), title=f"Template: {template.name}"))
|
|
351
|
+
|
|
352
|
+
# Print dependency status
|
|
353
|
+
all_ok = "✓" if deps["all_satisfied"] else "✗"
|
|
354
|
+
console.print(f"\n[bold]Dependencies Status:[/bold] {all_ok}")
|
|
355
|
+
|
|
356
|
+
if tools_status:
|
|
357
|
+
console.print("\n[bold]Required Tools:[/bold]")
|
|
358
|
+
for t in tools_status:
|
|
359
|
+
color = "green" if t.startswith("✓") else "red"
|
|
360
|
+
console.print(f" [{color}]{t}[/{color}]")
|
|
361
|
+
|
|
362
|
+
if pkgs_status:
|
|
363
|
+
console.print("\n[bold]Required Packages:[/bold]")
|
|
364
|
+
for p in pkgs_status:
|
|
365
|
+
color = "green" if p.startswith("✓") else "red"
|
|
366
|
+
console.print(f" [{color}]{p}[/{color}]")
|
|
367
|
+
|
|
368
|
+
if env_status:
|
|
369
|
+
console.print("\n[bold]Required Environment:[/bold]")
|
|
370
|
+
for e in env_status:
|
|
371
|
+
color = "green" if e.startswith("✓") else "red"
|
|
372
|
+
console.print(f" [{color}]{e}[/{color}]")
|
|
373
|
+
|
|
374
|
+
# Print install hints if any missing
|
|
375
|
+
if not deps["all_satisfied"]:
|
|
376
|
+
hints = checker.get_install_hints(template)
|
|
377
|
+
if hints:
|
|
378
|
+
console.print("\n[bold yellow]To fix missing dependencies:[/bold yellow]")
|
|
379
|
+
for hint in hints:
|
|
380
|
+
console.print(f" • {hint}")
|
|
381
|
+
|
|
382
|
+
except ImportError:
|
|
383
|
+
print(f"Template: {template.name}")
|
|
384
|
+
print(f"Version: {template.version}")
|
|
385
|
+
print(f"Author: {template.author or 'Unknown'}")
|
|
386
|
+
print(f"Description: {template.description}")
|
|
387
|
+
print(f"Path: {template.path}")
|
|
388
|
+
print(f"\nDependencies: {'All satisfied' if deps['all_satisfied'] else 'Some missing'}")
|
|
389
|
+
if tools_status:
|
|
390
|
+
print("\nRequired Tools:")
|
|
391
|
+
for t in tools_status:
|
|
392
|
+
print(f" {t}")
|
|
393
|
+
if pkgs_status:
|
|
394
|
+
print("\nRequired Packages:")
|
|
395
|
+
for p in pkgs_status:
|
|
396
|
+
print(f" {p}")
|
|
397
|
+
if env_status:
|
|
398
|
+
print("\nRequired Environment:")
|
|
399
|
+
for e in env_status:
|
|
400
|
+
print(f" {e}")
|
|
401
|
+
|
|
402
|
+
return 0
|
|
403
|
+
|
|
404
|
+
except Exception as e:
|
|
405
|
+
print(f"Error loading template info: {e}")
|
|
406
|
+
return 1
|
|
407
|
+
|
|
408
|
+
def cmd_install(self, args: List[str]) -> int:
|
|
409
|
+
"""Install a template to cache."""
|
|
410
|
+
if not args or args[0].startswith("--"):
|
|
411
|
+
print("Usage: praisonai templates install <uri>")
|
|
412
|
+
return 1
|
|
413
|
+
|
|
414
|
+
uri = args[0]
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
from praisonai.templates import install_template
|
|
418
|
+
|
|
419
|
+
print(f"Installing template: {uri}")
|
|
420
|
+
cached = install_template(uri)
|
|
421
|
+
print(f"✓ Template installed to: {cached.path}")
|
|
422
|
+
|
|
423
|
+
return 0
|
|
424
|
+
|
|
425
|
+
except Exception as e:
|
|
426
|
+
print(f"Error installing template: {e}")
|
|
427
|
+
return 1
|
|
428
|
+
|
|
429
|
+
def cmd_cache(self, args: List[str]) -> int:
|
|
430
|
+
"""Cache management commands."""
|
|
431
|
+
if not args:
|
|
432
|
+
print("Usage: praisonai templates cache <clear|list|size>")
|
|
433
|
+
return 1
|
|
434
|
+
|
|
435
|
+
subcmd = args[0]
|
|
436
|
+
|
|
437
|
+
if subcmd == "clear":
|
|
438
|
+
source = None
|
|
439
|
+
if len(args) > 1:
|
|
440
|
+
source = args[1]
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
from praisonai.templates import clear_cache
|
|
444
|
+
count = clear_cache(source)
|
|
445
|
+
print(f"✓ Cleared {count} cached template(s)")
|
|
446
|
+
return 0
|
|
447
|
+
except Exception as e:
|
|
448
|
+
print(f"Error clearing cache: {e}")
|
|
449
|
+
return 1
|
|
450
|
+
|
|
451
|
+
elif subcmd == "list":
|
|
452
|
+
try:
|
|
453
|
+
cached = self.cache.list_cached()
|
|
454
|
+
if not cached:
|
|
455
|
+
print("No cached templates.")
|
|
456
|
+
return 0
|
|
457
|
+
|
|
458
|
+
print(f"Cached templates ({len(cached)}):\n")
|
|
459
|
+
for path, meta in cached:
|
|
460
|
+
status = "pinned" if meta.is_pinned else "expires in " + str(int(meta.ttl_seconds - (import_time() - meta.fetched_at))) + "s"
|
|
461
|
+
print(f" • {path.name}")
|
|
462
|
+
print(f" Path: {path}")
|
|
463
|
+
print(f" Status: {status}")
|
|
464
|
+
print()
|
|
465
|
+
|
|
466
|
+
return 0
|
|
467
|
+
except Exception as e:
|
|
468
|
+
print(f"Error listing cache: {e}")
|
|
469
|
+
return 1
|
|
470
|
+
|
|
471
|
+
elif subcmd == "size":
|
|
472
|
+
size = self.cache.get_cache_size()
|
|
473
|
+
print(f"Cache size: {size / 1024 / 1024:.2f} MB")
|
|
474
|
+
return 0
|
|
475
|
+
|
|
476
|
+
else:
|
|
477
|
+
print(f"Unknown cache command: {subcmd}")
|
|
478
|
+
return 1
|
|
479
|
+
|
|
480
|
+
def cmd_run(self, args: List[str]) -> int:
|
|
481
|
+
"""Run a template directly."""
|
|
482
|
+
if not args or args[0].startswith("--"):
|
|
483
|
+
print("Usage: praisonai templates run <template> [args...] [--strict-tools] [--offline]")
|
|
484
|
+
return 1
|
|
485
|
+
|
|
486
|
+
uri = args[0]
|
|
487
|
+
template_args = args[1:]
|
|
488
|
+
offline = "--offline" in args
|
|
489
|
+
strict_tools = "--strict-tools" in args
|
|
490
|
+
no_template_tools_py = "--no-template-tools-py" in args
|
|
491
|
+
|
|
492
|
+
# Parse --tools, --tools-dir, --tools-source overrides
|
|
493
|
+
tools_files = []
|
|
494
|
+
tools_dirs = []
|
|
495
|
+
tools_sources_override = []
|
|
496
|
+
i = 0
|
|
497
|
+
while i < len(args):
|
|
498
|
+
if args[i] == "--tools" and i + 1 < len(args):
|
|
499
|
+
tools_files.append(args[i + 1])
|
|
500
|
+
i += 2
|
|
501
|
+
elif args[i] == "--tools-dir" and i + 1 < len(args):
|
|
502
|
+
tools_dirs.append(args[i + 1])
|
|
503
|
+
i += 2
|
|
504
|
+
elif args[i] == "--tools-source" and i + 1 < len(args):
|
|
505
|
+
tools_sources_override.append(args[i + 1])
|
|
506
|
+
i += 2
|
|
507
|
+
else:
|
|
508
|
+
i += 1
|
|
509
|
+
|
|
510
|
+
try:
|
|
511
|
+
from praisonai.templates.loader import TemplateLoader
|
|
512
|
+
from praisonai.templates.discovery import TemplateDiscovery
|
|
513
|
+
|
|
514
|
+
# First try to find in custom/local directories
|
|
515
|
+
discovery = TemplateDiscovery(
|
|
516
|
+
custom_dirs=tools_dirs if tools_dirs else None,
|
|
517
|
+
include_package=True,
|
|
518
|
+
include_defaults=True
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
discovered = discovery.find_template(uri)
|
|
522
|
+
|
|
523
|
+
loader = TemplateLoader(offline=offline)
|
|
524
|
+
|
|
525
|
+
if discovered:
|
|
526
|
+
# Load from discovered path
|
|
527
|
+
template = loader.load(str(discovered.path), offline=offline)
|
|
528
|
+
else:
|
|
529
|
+
# Fall back to URI resolution
|
|
530
|
+
template = loader.load(uri, offline=offline)
|
|
531
|
+
|
|
532
|
+
# Strict mode: fail-fast on missing dependencies
|
|
533
|
+
if strict_tools:
|
|
534
|
+
from praisonai.templates.dependency_checker import DependencyChecker, StrictModeError
|
|
535
|
+
checker = DependencyChecker()
|
|
536
|
+
try:
|
|
537
|
+
checker.enforce_strict_mode(template)
|
|
538
|
+
print("✓ All dependencies satisfied (strict mode)")
|
|
539
|
+
except StrictModeError as e:
|
|
540
|
+
print(f"✗ Strict mode check failed:\n{e}")
|
|
541
|
+
return 1
|
|
542
|
+
else:
|
|
543
|
+
# Non-strict: warn but continue
|
|
544
|
+
missing = loader.check_requirements(template)
|
|
545
|
+
if missing["missing_packages"]:
|
|
546
|
+
print(f"Warning: Missing packages: {', '.join(missing['missing_packages'])}")
|
|
547
|
+
print("Install with: pip install " + " ".join(missing["missing_packages"]))
|
|
548
|
+
if missing["missing_env"]:
|
|
549
|
+
print(f"Warning: Missing environment variables: {', '.join(missing['missing_env'])}")
|
|
550
|
+
|
|
551
|
+
# Load tool overrides if specified
|
|
552
|
+
tool_registry = None
|
|
553
|
+
from praisonai.templates.tool_override import create_tool_registry_with_overrides, resolve_tools
|
|
554
|
+
|
|
555
|
+
# Get template directory for local tools.py autoload (unless disabled)
|
|
556
|
+
template_dir = None
|
|
557
|
+
if not no_template_tools_py:
|
|
558
|
+
template_dir = str(template.path) if template.path else None
|
|
559
|
+
|
|
560
|
+
# Get tools_sources from template requires + CLI overrides
|
|
561
|
+
tools_sources = []
|
|
562
|
+
if template.requires and isinstance(template.requires, dict):
|
|
563
|
+
ts = template.requires.get("tools_sources", [])
|
|
564
|
+
if ts:
|
|
565
|
+
tools_sources.extend(ts)
|
|
566
|
+
# Add CLI --tools-source overrides
|
|
567
|
+
if tools_sources_override:
|
|
568
|
+
tools_sources.extend(tools_sources_override)
|
|
569
|
+
|
|
570
|
+
# Always build registry (includes defaults + template sources)
|
|
571
|
+
tool_registry = create_tool_registry_with_overrides(
|
|
572
|
+
override_files=tools_files if tools_files else None,
|
|
573
|
+
override_dirs=tools_dirs if tools_dirs else None,
|
|
574
|
+
include_defaults=True,
|
|
575
|
+
tools_sources=tools_sources if tools_sources else None,
|
|
576
|
+
template_dir=template_dir,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if tools_files or tools_dirs:
|
|
580
|
+
print(f"✓ Loaded {len(tool_registry)} tools from overrides")
|
|
581
|
+
|
|
582
|
+
# Load and run workflow
|
|
583
|
+
workflow_config = loader.load_workflow_config(template)
|
|
584
|
+
|
|
585
|
+
# Parse template args into config
|
|
586
|
+
config = self._parse_template_args(template_args, template)
|
|
587
|
+
|
|
588
|
+
# Merge config
|
|
589
|
+
if config:
|
|
590
|
+
workflow_config = {**workflow_config, **config}
|
|
591
|
+
|
|
592
|
+
# Run workflow - determine which class to use based on config structure
|
|
593
|
+
if "agents" in workflow_config and "tasks" in workflow_config:
|
|
594
|
+
# PraisonAIAgents format (agents + tasks)
|
|
595
|
+
from praisonaiagents import Agent, Task, PraisonAIAgents
|
|
596
|
+
|
|
597
|
+
# Build agents
|
|
598
|
+
agents_config = workflow_config.get("agents", [])
|
|
599
|
+
agents = []
|
|
600
|
+
agent_map = {}
|
|
601
|
+
|
|
602
|
+
for agent_cfg in agents_config:
|
|
603
|
+
# Resolve tool names to callable tools
|
|
604
|
+
agent_tool_names = agent_cfg.get("tools", [])
|
|
605
|
+
resolved_agent_tools = resolve_tools(
|
|
606
|
+
agent_tool_names,
|
|
607
|
+
registry=tool_registry,
|
|
608
|
+
template_dir=template_dir
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
agent = Agent(
|
|
612
|
+
name=agent_cfg.get("name", "Agent"),
|
|
613
|
+
role=agent_cfg.get("role", ""),
|
|
614
|
+
goal=agent_cfg.get("goal", ""),
|
|
615
|
+
backstory=agent_cfg.get("backstory", ""),
|
|
616
|
+
tools=resolved_agent_tools,
|
|
617
|
+
llm=agent_cfg.get("llm"),
|
|
618
|
+
verbose=agent_cfg.get("verbose", True)
|
|
619
|
+
)
|
|
620
|
+
agents.append(agent)
|
|
621
|
+
agent_map[agent_cfg.get("name", "Agent")] = agent
|
|
622
|
+
|
|
623
|
+
# Build tasks
|
|
624
|
+
tasks_config = workflow_config.get("tasks", [])
|
|
625
|
+
tasks = []
|
|
626
|
+
|
|
627
|
+
for task_cfg in tasks_config:
|
|
628
|
+
agent_name = task_cfg.get("agent", "")
|
|
629
|
+
agent = agent_map.get(agent_name, agents[0] if agents else None)
|
|
630
|
+
|
|
631
|
+
task = Task(
|
|
632
|
+
name=task_cfg.get("name", "Task"),
|
|
633
|
+
description=task_cfg.get("description", ""),
|
|
634
|
+
expected_output=task_cfg.get("expected_output", ""),
|
|
635
|
+
agent=agent
|
|
636
|
+
)
|
|
637
|
+
tasks.append(task)
|
|
638
|
+
|
|
639
|
+
# Run
|
|
640
|
+
praison_agents = PraisonAIAgents(
|
|
641
|
+
agents=agents,
|
|
642
|
+
tasks=tasks,
|
|
643
|
+
process=workflow_config.get("process", "sequential"),
|
|
644
|
+
verbose=workflow_config.get("verbose", 1)
|
|
645
|
+
)
|
|
646
|
+
praison_agents.start()
|
|
647
|
+
elif "steps" in workflow_config:
|
|
648
|
+
# Workflow format (steps) - resolve tools in each step's agent
|
|
649
|
+
from praisonaiagents import Workflow
|
|
650
|
+
|
|
651
|
+
# Resolve tools for each step's agent if specified
|
|
652
|
+
steps = workflow_config.get("steps", [])
|
|
653
|
+
for step in steps:
|
|
654
|
+
if "agent" in step and isinstance(step["agent"], dict):
|
|
655
|
+
agent_cfg = step["agent"]
|
|
656
|
+
if "tools" in agent_cfg:
|
|
657
|
+
agent_cfg["tools"] = resolve_tools(
|
|
658
|
+
agent_cfg["tools"],
|
|
659
|
+
registry=tool_registry,
|
|
660
|
+
template_dir=template_dir
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
workflow = Workflow(**workflow_config)
|
|
664
|
+
workflow.run()
|
|
665
|
+
else:
|
|
666
|
+
raise ValueError("Invalid workflow config: must have 'agents'+'tasks' or 'steps'")
|
|
667
|
+
|
|
668
|
+
print(f"\n✓ Template '{template.name}' completed successfully")
|
|
669
|
+
return 0
|
|
670
|
+
|
|
671
|
+
except Exception as e:
|
|
672
|
+
print(f"Error running template: {e}")
|
|
673
|
+
import traceback
|
|
674
|
+
traceback.print_exc()
|
|
675
|
+
return 1
|
|
676
|
+
|
|
677
|
+
def cmd_init(self, args: List[str]) -> int:
|
|
678
|
+
"""Initialize a project from template."""
|
|
679
|
+
if not args or args[0].startswith("--"):
|
|
680
|
+
print("Usage: praisonai templates init <project-name> --template <template>")
|
|
681
|
+
return 1
|
|
682
|
+
|
|
683
|
+
project_name = args[0]
|
|
684
|
+
template_uri = None
|
|
685
|
+
offline = "--offline" in args
|
|
686
|
+
|
|
687
|
+
if "--template" in args:
|
|
688
|
+
idx = args.index("--template")
|
|
689
|
+
if idx + 1 < len(args):
|
|
690
|
+
template_uri = args[idx + 1]
|
|
691
|
+
|
|
692
|
+
if not template_uri:
|
|
693
|
+
print("Error: --template is required")
|
|
694
|
+
return 1
|
|
695
|
+
|
|
696
|
+
try:
|
|
697
|
+
from praisonai.templates import load_template
|
|
698
|
+
|
|
699
|
+
template = load_template(template_uri, offline=offline)
|
|
700
|
+
|
|
701
|
+
# Create project directory
|
|
702
|
+
project_dir = Path(project_name)
|
|
703
|
+
if project_dir.exists():
|
|
704
|
+
print(f"Error: Directory '{project_name}' already exists")
|
|
705
|
+
return 1
|
|
706
|
+
|
|
707
|
+
project_dir.mkdir(parents=True)
|
|
708
|
+
|
|
709
|
+
# Copy template files
|
|
710
|
+
for item in template.path.iterdir():
|
|
711
|
+
if item.name.startswith("."):
|
|
712
|
+
continue
|
|
713
|
+
if item.is_file():
|
|
714
|
+
shutil.copy2(item, project_dir / item.name)
|
|
715
|
+
elif item.is_dir():
|
|
716
|
+
shutil.copytree(item, project_dir / item.name)
|
|
717
|
+
|
|
718
|
+
print(f"✓ Created project '{project_name}' from template '{template.name}'")
|
|
719
|
+
print("Next steps:")
|
|
720
|
+
print(f" cd {project_name}")
|
|
721
|
+
print(" praisonai run workflow.yaml")
|
|
722
|
+
|
|
723
|
+
return 0
|
|
724
|
+
|
|
725
|
+
except Exception as e:
|
|
726
|
+
print(f"Error initializing project: {e}")
|
|
727
|
+
return 1
|
|
728
|
+
|
|
729
|
+
def _parse_template_args(
|
|
730
|
+
self,
|
|
731
|
+
args: List[str],
|
|
732
|
+
template
|
|
733
|
+
) -> Dict[str, Any]:
|
|
734
|
+
"""Parse template-specific arguments."""
|
|
735
|
+
config = {}
|
|
736
|
+
|
|
737
|
+
# Handle positional args based on CLI config
|
|
738
|
+
cli_config = template.cli
|
|
739
|
+
if cli_config and "args" in cli_config:
|
|
740
|
+
positional_idx = 0
|
|
741
|
+
for arg_def in cli_config["args"]:
|
|
742
|
+
if arg_def.get("positional"):
|
|
743
|
+
if positional_idx < len(args) and not args[positional_idx].startswith("--"):
|
|
744
|
+
config[arg_def["name"]] = args[positional_idx]
|
|
745
|
+
positional_idx += 1
|
|
746
|
+
|
|
747
|
+
# Handle named args
|
|
748
|
+
i = 0
|
|
749
|
+
while i < len(args):
|
|
750
|
+
if args[i].startswith("--"):
|
|
751
|
+
key = args[i][2:].replace("-", "_")
|
|
752
|
+
if i + 1 < len(args) and not args[i + 1].startswith("--"):
|
|
753
|
+
config[key] = args[i + 1]
|
|
754
|
+
i += 2
|
|
755
|
+
else:
|
|
756
|
+
config[key] = True
|
|
757
|
+
i += 1
|
|
758
|
+
else:
|
|
759
|
+
i += 1
|
|
760
|
+
|
|
761
|
+
return config
|
|
762
|
+
|
|
763
|
+
def _get_templates_config_path(self):
|
|
764
|
+
"""Get the path to the templates config file."""
|
|
765
|
+
config_dir = Path.home() / ".praison"
|
|
766
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
767
|
+
return config_dir / "templates_sources.yaml"
|
|
768
|
+
|
|
769
|
+
def _load_templates_config(self) -> Dict[str, Any]:
|
|
770
|
+
"""Load templates config from file."""
|
|
771
|
+
import yaml
|
|
772
|
+
config_path = self._get_templates_config_path()
|
|
773
|
+
if config_path.exists():
|
|
774
|
+
with open(config_path, 'r') as f:
|
|
775
|
+
return yaml.safe_load(f) or {}
|
|
776
|
+
return {"sources": []}
|
|
777
|
+
|
|
778
|
+
def _save_templates_config(self, config: Dict[str, Any]):
|
|
779
|
+
"""Save templates config to file."""
|
|
780
|
+
import yaml
|
|
781
|
+
config_path = self._get_templates_config_path()
|
|
782
|
+
with open(config_path, 'w') as f:
|
|
783
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
784
|
+
|
|
785
|
+
def cmd_add(self, args: List[str]) -> int:
|
|
786
|
+
"""
|
|
787
|
+
Add template from GitHub or local path.
|
|
788
|
+
|
|
789
|
+
Args:
|
|
790
|
+
args: [source] - github:user/repo/template or local path
|
|
791
|
+
"""
|
|
792
|
+
if not args:
|
|
793
|
+
print("[red]Usage: praisonai templates add <source>[/red]")
|
|
794
|
+
print(" source: github:user/repo/template or local path")
|
|
795
|
+
return 1
|
|
796
|
+
|
|
797
|
+
source = args[0]
|
|
798
|
+
|
|
799
|
+
# Check if it's a local directory
|
|
800
|
+
if source.startswith("./") or source.startswith("/"):
|
|
801
|
+
path = Path(source).resolve()
|
|
802
|
+
if path.exists() and path.is_dir():
|
|
803
|
+
# Check for TEMPLATE.yaml
|
|
804
|
+
template_yaml = path / "TEMPLATE.yaml"
|
|
805
|
+
if not template_yaml.exists():
|
|
806
|
+
print(f"[red]Not a valid template: {path} (missing TEMPLATE.yaml)[/red]")
|
|
807
|
+
return 1
|
|
808
|
+
|
|
809
|
+
# Copy to ~/.praison/templates/
|
|
810
|
+
templates_dir = Path.home() / ".praison" / "templates"
|
|
811
|
+
templates_dir.mkdir(parents=True, exist_ok=True)
|
|
812
|
+
dest = templates_dir / path.name
|
|
813
|
+
|
|
814
|
+
if dest.exists():
|
|
815
|
+
shutil.rmtree(dest)
|
|
816
|
+
shutil.copytree(path, dest)
|
|
817
|
+
|
|
818
|
+
print(f"\n✅ Added template: {path.name}")
|
|
819
|
+
print(f" Copied to: {dest}")
|
|
820
|
+
return 0
|
|
821
|
+
else:
|
|
822
|
+
print(f"[red]Directory not found: {source}[/red]")
|
|
823
|
+
return 1
|
|
824
|
+
|
|
825
|
+
# Check if it's a GitHub reference
|
|
826
|
+
elif source.startswith("github:"):
|
|
827
|
+
github_path = source[7:] # Remove "github:"
|
|
828
|
+
parts = github_path.split("/")
|
|
829
|
+
if len(parts) < 3:
|
|
830
|
+
print("[red]Invalid GitHub format. Use: github:user/repo/template-name[/red]")
|
|
831
|
+
return 1
|
|
832
|
+
|
|
833
|
+
user, repo = parts[0], parts[1]
|
|
834
|
+
template_path = "/".join(parts[2:])
|
|
835
|
+
|
|
836
|
+
# Download from GitHub
|
|
837
|
+
import urllib.request
|
|
838
|
+
import tempfile
|
|
839
|
+
import zipfile
|
|
840
|
+
|
|
841
|
+
try:
|
|
842
|
+
# Download repo as zip
|
|
843
|
+
zip_url = f"https://github.com/{user}/{repo}/archive/refs/heads/main.zip"
|
|
844
|
+
print(f"Downloading from: {zip_url}")
|
|
845
|
+
|
|
846
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
847
|
+
zip_path = Path(tmpdir) / "repo.zip"
|
|
848
|
+
urllib.request.urlretrieve(zip_url, zip_path)
|
|
849
|
+
|
|
850
|
+
# Extract
|
|
851
|
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
|
852
|
+
zip_ref.extractall(tmpdir)
|
|
853
|
+
|
|
854
|
+
# Find the template
|
|
855
|
+
extracted_dir = Path(tmpdir) / f"{repo}-main"
|
|
856
|
+
template_src = extracted_dir / template_path
|
|
857
|
+
|
|
858
|
+
if not template_src.exists():
|
|
859
|
+
# Try common paths
|
|
860
|
+
for alt_path in [
|
|
861
|
+
extracted_dir / "agent_recipes" / "templates" / template_path,
|
|
862
|
+
extracted_dir / "templates" / template_path,
|
|
863
|
+
]:
|
|
864
|
+
if alt_path.exists():
|
|
865
|
+
template_src = alt_path
|
|
866
|
+
break
|
|
867
|
+
|
|
868
|
+
if not template_src.exists():
|
|
869
|
+
print(f"[red]Template not found: {template_path}[/red]")
|
|
870
|
+
return 1
|
|
871
|
+
|
|
872
|
+
# Copy to ~/.praison/templates/
|
|
873
|
+
templates_dir = Path.home() / ".praison" / "templates"
|
|
874
|
+
templates_dir.mkdir(parents=True, exist_ok=True)
|
|
875
|
+
dest = templates_dir / template_src.name
|
|
876
|
+
|
|
877
|
+
if dest.exists():
|
|
878
|
+
shutil.rmtree(dest)
|
|
879
|
+
shutil.copytree(template_src, dest)
|
|
880
|
+
|
|
881
|
+
print(f"\n✅ Added template from GitHub: {user}/{repo}/{template_path}")
|
|
882
|
+
print(f" Saved to: {dest}")
|
|
883
|
+
return 0
|
|
884
|
+
|
|
885
|
+
except Exception as e:
|
|
886
|
+
print(f"[red]Failed to download from GitHub: {e}[/red]")
|
|
887
|
+
return 1
|
|
888
|
+
|
|
889
|
+
else:
|
|
890
|
+
print(f"[red]Unknown source format: {source}[/red]")
|
|
891
|
+
print("Use: github:user/repo/template or ./local-path")
|
|
892
|
+
return 1
|
|
893
|
+
|
|
894
|
+
def cmd_add_sources(self, args: List[str]) -> int:
|
|
895
|
+
"""
|
|
896
|
+
Add a template source to persistent config.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
args: [source] - github:user/repo or URL
|
|
900
|
+
"""
|
|
901
|
+
if not args:
|
|
902
|
+
print("[red]Usage: praisonai templates add-sources <source>[/red]")
|
|
903
|
+
return 1
|
|
904
|
+
|
|
905
|
+
source = args[0]
|
|
906
|
+
config = self._load_templates_config()
|
|
907
|
+
|
|
908
|
+
if "sources" not in config:
|
|
909
|
+
config["sources"] = []
|
|
910
|
+
|
|
911
|
+
if source in config["sources"]:
|
|
912
|
+
print(f"[yellow]Source '{source}' already in config[/yellow]")
|
|
913
|
+
return 0
|
|
914
|
+
|
|
915
|
+
config["sources"].append(source)
|
|
916
|
+
self._save_templates_config(config)
|
|
917
|
+
|
|
918
|
+
print(f"\n✅ Added template source: {source}")
|
|
919
|
+
print(f" Config saved to: {self._get_templates_config_path()}")
|
|
920
|
+
return 0
|
|
921
|
+
|
|
922
|
+
def cmd_remove_sources(self, args: List[str]) -> int:
|
|
923
|
+
"""
|
|
924
|
+
Remove a template source from persistent config.
|
|
925
|
+
|
|
926
|
+
Args:
|
|
927
|
+
args: [source] - source to remove
|
|
928
|
+
"""
|
|
929
|
+
if not args:
|
|
930
|
+
print("[red]Usage: praisonai templates remove-sources <source>[/red]")
|
|
931
|
+
return 1
|
|
932
|
+
|
|
933
|
+
source = args[0]
|
|
934
|
+
config = self._load_templates_config()
|
|
935
|
+
|
|
936
|
+
if "sources" not in config or source not in config["sources"]:
|
|
937
|
+
print(f"[yellow]Source '{source}' not found in config[/yellow]")
|
|
938
|
+
return 1
|
|
939
|
+
|
|
940
|
+
config["sources"].remove(source)
|
|
941
|
+
self._save_templates_config(config)
|
|
942
|
+
|
|
943
|
+
print(f"\n✅ Removed template source: {source}")
|
|
944
|
+
return 0
|
|
945
|
+
|
|
946
|
+
def cmd_browse(self, args: List[str]) -> int:
|
|
947
|
+
"""
|
|
948
|
+
Open template catalog in browser.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
args: [--local] [--url <url>] [--print]
|
|
952
|
+
"""
|
|
953
|
+
import webbrowser
|
|
954
|
+
|
|
955
|
+
# Default catalog URL
|
|
956
|
+
catalog_url = "https://mervinpraison.github.io/praisonai-template-catalog"
|
|
957
|
+
|
|
958
|
+
# Parse arguments
|
|
959
|
+
print_only = "--print" in args
|
|
960
|
+
local_mode = "--local" in args
|
|
961
|
+
|
|
962
|
+
# Custom URL
|
|
963
|
+
if "--url" in args:
|
|
964
|
+
idx = args.index("--url")
|
|
965
|
+
if idx + 1 < len(args):
|
|
966
|
+
catalog_url = args[idx + 1]
|
|
967
|
+
|
|
968
|
+
if local_mode:
|
|
969
|
+
print("Local catalog server not implemented yet.")
|
|
970
|
+
print(f"Visit the online catalog at: {catalog_url}")
|
|
971
|
+
return 0
|
|
972
|
+
|
|
973
|
+
if print_only:
|
|
974
|
+
print(catalog_url)
|
|
975
|
+
return 0
|
|
976
|
+
|
|
977
|
+
print(f"Opening template catalog: {catalog_url}")
|
|
978
|
+
try:
|
|
979
|
+
webbrowser.open(catalog_url)
|
|
980
|
+
return 0
|
|
981
|
+
except Exception as e:
|
|
982
|
+
print(f"Failed to open browser: {e}")
|
|
983
|
+
print(f"Visit: {catalog_url}")
|
|
984
|
+
return 1
|
|
985
|
+
|
|
986
|
+
def cmd_catalog(self, args: List[str]) -> int:
|
|
987
|
+
"""
|
|
988
|
+
Catalog management commands.
|
|
989
|
+
|
|
990
|
+
Subcommands:
|
|
991
|
+
build Build catalog locally
|
|
992
|
+
sync Sync catalog sources
|
|
993
|
+
"""
|
|
994
|
+
if not args:
|
|
995
|
+
print("Usage: praisonai templates catalog <build|sync> [options]")
|
|
996
|
+
print("\nSubcommands:")
|
|
997
|
+
print(" build Build catalog locally")
|
|
998
|
+
print(" sync Sync catalog sources from GitHub")
|
|
999
|
+
print("\nExamples:")
|
|
1000
|
+
print(" praisonai templates catalog build --out ./dist")
|
|
1001
|
+
print(" praisonai templates catalog sync --source agent-recipes")
|
|
1002
|
+
return 0
|
|
1003
|
+
|
|
1004
|
+
subcmd = args[0]
|
|
1005
|
+
remaining = args[1:]
|
|
1006
|
+
|
|
1007
|
+
if subcmd == "build":
|
|
1008
|
+
return self._catalog_build(remaining)
|
|
1009
|
+
elif subcmd == "sync":
|
|
1010
|
+
return self._catalog_sync(remaining)
|
|
1011
|
+
else:
|
|
1012
|
+
print(f"Unknown catalog command: {subcmd}")
|
|
1013
|
+
return 1
|
|
1014
|
+
|
|
1015
|
+
def _catalog_build(self, args: List[str]) -> int:
|
|
1016
|
+
"""Build catalog locally."""
|
|
1017
|
+
import subprocess
|
|
1018
|
+
import os
|
|
1019
|
+
|
|
1020
|
+
# Parse arguments
|
|
1021
|
+
out_dir = None
|
|
1022
|
+
source_dir = None
|
|
1023
|
+
minify = "--minify" in args
|
|
1024
|
+
|
|
1025
|
+
if "--out" in args:
|
|
1026
|
+
idx = args.index("--out")
|
|
1027
|
+
if idx + 1 < len(args):
|
|
1028
|
+
out_dir = args[idx + 1]
|
|
1029
|
+
|
|
1030
|
+
if "--source" in args:
|
|
1031
|
+
idx = args.index("--source")
|
|
1032
|
+
if idx + 1 < len(args):
|
|
1033
|
+
source_dir = args[idx + 1]
|
|
1034
|
+
|
|
1035
|
+
# Check if catalog repo is available locally
|
|
1036
|
+
catalog_repo = Path.home() / "praisonai-template-catalog"
|
|
1037
|
+
if not catalog_repo.exists():
|
|
1038
|
+
# Try to find it relative to this package
|
|
1039
|
+
possible_paths = [
|
|
1040
|
+
Path(__file__).parent.parent.parent.parent.parent.parent / "praisonai-template-catalog",
|
|
1041
|
+
Path.cwd() / "praisonai-template-catalog",
|
|
1042
|
+
]
|
|
1043
|
+
for p in possible_paths:
|
|
1044
|
+
if p.exists():
|
|
1045
|
+
catalog_repo = p
|
|
1046
|
+
break
|
|
1047
|
+
|
|
1048
|
+
if catalog_repo.exists() and (catalog_repo / "scripts" / "build-catalog.js").exists():
|
|
1049
|
+
print(f"Building catalog using: {catalog_repo}")
|
|
1050
|
+
cmd = ["node", "scripts/build-catalog.js"]
|
|
1051
|
+
if out_dir:
|
|
1052
|
+
cmd.extend(["--out", out_dir])
|
|
1053
|
+
if source_dir:
|
|
1054
|
+
cmd.extend(["--source", source_dir])
|
|
1055
|
+
if minify:
|
|
1056
|
+
cmd.append("--minify")
|
|
1057
|
+
|
|
1058
|
+
try:
|
|
1059
|
+
result = subprocess.run(cmd, cwd=str(catalog_repo), capture_output=True, text=True)
|
|
1060
|
+
print(result.stdout)
|
|
1061
|
+
if result.stderr:
|
|
1062
|
+
print(result.stderr)
|
|
1063
|
+
return result.returncode
|
|
1064
|
+
except FileNotFoundError:
|
|
1065
|
+
print("Node.js not found. Please install Node.js to build the catalog.")
|
|
1066
|
+
return 1
|
|
1067
|
+
else:
|
|
1068
|
+
# Fallback: Generate minimal catalog using Python
|
|
1069
|
+
print("Catalog repo not found locally. Generating minimal catalog...")
|
|
1070
|
+
return self._generate_minimal_catalog(out_dir, source_dir)
|
|
1071
|
+
|
|
1072
|
+
def _generate_minimal_catalog(self, out_dir: str = None, source_dir: str = None) -> int:
|
|
1073
|
+
"""Generate minimal catalog JSON using Python."""
|
|
1074
|
+
import json
|
|
1075
|
+
from datetime import datetime
|
|
1076
|
+
|
|
1077
|
+
try:
|
|
1078
|
+
import yaml
|
|
1079
|
+
except ImportError:
|
|
1080
|
+
print("PyYAML not installed. Install with: pip install pyyaml")
|
|
1081
|
+
return 1
|
|
1082
|
+
|
|
1083
|
+
# Find templates
|
|
1084
|
+
if source_dir:
|
|
1085
|
+
templates_dir = Path(source_dir)
|
|
1086
|
+
else:
|
|
1087
|
+
# Try Agent-Recipes
|
|
1088
|
+
possible_paths = [
|
|
1089
|
+
Path.home() / "Agent-Recipes" / "agent_recipes" / "templates",
|
|
1090
|
+
Path.cwd() / "Agent-Recipes" / "agent_recipes" / "templates",
|
|
1091
|
+
]
|
|
1092
|
+
templates_dir = None
|
|
1093
|
+
for p in possible_paths:
|
|
1094
|
+
if p.exists():
|
|
1095
|
+
templates_dir = p
|
|
1096
|
+
break
|
|
1097
|
+
|
|
1098
|
+
if not templates_dir:
|
|
1099
|
+
# Try package templates
|
|
1100
|
+
try:
|
|
1101
|
+
import agent_recipes
|
|
1102
|
+
templates_dir = Path(agent_recipes.__file__).parent / "templates"
|
|
1103
|
+
except ImportError:
|
|
1104
|
+
pass
|
|
1105
|
+
|
|
1106
|
+
if not templates_dir or not templates_dir.exists():
|
|
1107
|
+
print("No templates directory found.")
|
|
1108
|
+
return 1
|
|
1109
|
+
|
|
1110
|
+
print(f"Scanning templates in: {templates_dir}")
|
|
1111
|
+
|
|
1112
|
+
templates = []
|
|
1113
|
+
for entry in templates_dir.iterdir():
|
|
1114
|
+
if not entry.is_dir():
|
|
1115
|
+
continue
|
|
1116
|
+
template_yaml = entry / "TEMPLATE.yaml"
|
|
1117
|
+
if template_yaml.exists():
|
|
1118
|
+
try:
|
|
1119
|
+
with open(template_yaml) as f:
|
|
1120
|
+
data = yaml.safe_load(f)
|
|
1121
|
+
if data:
|
|
1122
|
+
templates.append({
|
|
1123
|
+
"name": data.get("name", entry.name),
|
|
1124
|
+
"version": data.get("version", "1.0.0"),
|
|
1125
|
+
"description": data.get("description", ""),
|
|
1126
|
+
"author": data.get("author", "Unknown"),
|
|
1127
|
+
"license": data.get("license", "Apache-2.0"),
|
|
1128
|
+
"tags": data.get("tags", []),
|
|
1129
|
+
"requires": data.get("requires", {}),
|
|
1130
|
+
})
|
|
1131
|
+
except Exception as e:
|
|
1132
|
+
print(f" Warning: Failed to parse {template_yaml}: {e}")
|
|
1133
|
+
|
|
1134
|
+
# Output
|
|
1135
|
+
output = {
|
|
1136
|
+
"version": "1.0.0",
|
|
1137
|
+
"generated_at": datetime.now().isoformat(),
|
|
1138
|
+
"count": len(templates),
|
|
1139
|
+
"templates": templates
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
out_path = Path(out_dir) if out_dir else Path.cwd() / "templates.json"
|
|
1143
|
+
if out_path.is_dir():
|
|
1144
|
+
out_path = out_path / "templates.json"
|
|
1145
|
+
|
|
1146
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1147
|
+
with open(out_path, "w") as f:
|
|
1148
|
+
json.dump(output, f, indent=2)
|
|
1149
|
+
|
|
1150
|
+
print(f"Generated: {out_path} ({len(templates)} templates)")
|
|
1151
|
+
return 0
|
|
1152
|
+
|
|
1153
|
+
def _catalog_sync(self, args: List[str]) -> int:
|
|
1154
|
+
"""Sync catalog sources."""
|
|
1155
|
+
import subprocess
|
|
1156
|
+
|
|
1157
|
+
# Parse arguments
|
|
1158
|
+
config_path = None
|
|
1159
|
+
source_name = None
|
|
1160
|
+
cache_dir = None
|
|
1161
|
+
|
|
1162
|
+
if "--config" in args:
|
|
1163
|
+
idx = args.index("--config")
|
|
1164
|
+
if idx + 1 < len(args):
|
|
1165
|
+
config_path = args[idx + 1]
|
|
1166
|
+
|
|
1167
|
+
if "--source" in args:
|
|
1168
|
+
idx = args.index("--source")
|
|
1169
|
+
if idx + 1 < len(args):
|
|
1170
|
+
source_name = args[idx + 1]
|
|
1171
|
+
|
|
1172
|
+
if "--cache-dir" in args:
|
|
1173
|
+
idx = args.index("--cache-dir")
|
|
1174
|
+
if idx + 1 < len(args):
|
|
1175
|
+
cache_dir = args[idx + 1]
|
|
1176
|
+
|
|
1177
|
+
# Check if catalog repo is available
|
|
1178
|
+
catalog_repo = Path.home() / "praisonai-template-catalog"
|
|
1179
|
+
if catalog_repo.exists() and (catalog_repo / "scripts" / "sync-sources.js").exists():
|
|
1180
|
+
print(f"Syncing using: {catalog_repo}")
|
|
1181
|
+
cmd = ["node", "scripts/sync-sources.js"]
|
|
1182
|
+
if config_path:
|
|
1183
|
+
cmd.extend(["--config", config_path])
|
|
1184
|
+
if source_name:
|
|
1185
|
+
cmd.extend(["--source", source_name])
|
|
1186
|
+
if cache_dir:
|
|
1187
|
+
cmd.extend(["--cache-dir", cache_dir])
|
|
1188
|
+
|
|
1189
|
+
try:
|
|
1190
|
+
result = subprocess.run(cmd, cwd=str(catalog_repo), capture_output=True, text=True)
|
|
1191
|
+
print(result.stdout)
|
|
1192
|
+
if result.stderr:
|
|
1193
|
+
print(result.stderr)
|
|
1194
|
+
return result.returncode
|
|
1195
|
+
except FileNotFoundError:
|
|
1196
|
+
print("Node.js not found. Please install Node.js.")
|
|
1197
|
+
return 1
|
|
1198
|
+
else:
|
|
1199
|
+
# Fallback: Clone Agent-Recipes
|
|
1200
|
+
print("Catalog repo not found. Cloning Agent-Recipes directly...")
|
|
1201
|
+
cache_path = Path(cache_dir) if cache_dir else Path.home() / ".praison" / "cache"
|
|
1202
|
+
cache_path.mkdir(parents=True, exist_ok=True)
|
|
1203
|
+
|
|
1204
|
+
target = cache_path / "Agent-Recipes"
|
|
1205
|
+
if target.exists():
|
|
1206
|
+
print(f"Updating: {target}")
|
|
1207
|
+
try:
|
|
1208
|
+
subprocess.run(["git", "-C", str(target), "pull", "--depth=1"], check=True)
|
|
1209
|
+
print("✓ Updated successfully")
|
|
1210
|
+
return 0
|
|
1211
|
+
except subprocess.CalledProcessError as e:
|
|
1212
|
+
print(f"Failed to update: {e}")
|
|
1213
|
+
return 1
|
|
1214
|
+
else:
|
|
1215
|
+
print(f"Cloning to: {target}")
|
|
1216
|
+
try:
|
|
1217
|
+
subprocess.run([
|
|
1218
|
+
"git", "clone", "--depth=1",
|
|
1219
|
+
"https://github.com/MervinPraison/Agent-Recipes.git",
|
|
1220
|
+
str(target)
|
|
1221
|
+
], check=True)
|
|
1222
|
+
print("✓ Cloned successfully")
|
|
1223
|
+
return 0
|
|
1224
|
+
except subprocess.CalledProcessError as e:
|
|
1225
|
+
print(f"Failed to clone: {e}")
|
|
1226
|
+
return 1
|
|
1227
|
+
|
|
1228
|
+
def cmd_validate(self, args: List[str]) -> int:
|
|
1229
|
+
"""
|
|
1230
|
+
Validate template YAML files.
|
|
1231
|
+
|
|
1232
|
+
Args:
|
|
1233
|
+
args: [--source <dir>] [--strict] [--json]
|
|
1234
|
+
"""
|
|
1235
|
+
import subprocess
|
|
1236
|
+
|
|
1237
|
+
# Parse arguments
|
|
1238
|
+
source_dir = None
|
|
1239
|
+
strict = "--strict" in args
|
|
1240
|
+
json_output = "--json" in args
|
|
1241
|
+
|
|
1242
|
+
if "--source" in args:
|
|
1243
|
+
idx = args.index("--source")
|
|
1244
|
+
if idx + 1 < len(args):
|
|
1245
|
+
source_dir = args[idx + 1]
|
|
1246
|
+
|
|
1247
|
+
# Check if catalog repo is available
|
|
1248
|
+
catalog_repo = Path.home() / "praisonai-template-catalog"
|
|
1249
|
+
if catalog_repo.exists() and (catalog_repo / "scripts" / "validate-templates.js").exists():
|
|
1250
|
+
cmd = ["node", "scripts/validate-templates.js"]
|
|
1251
|
+
if source_dir:
|
|
1252
|
+
cmd.extend(["--source", source_dir])
|
|
1253
|
+
if strict:
|
|
1254
|
+
cmd.append("--strict")
|
|
1255
|
+
if json_output:
|
|
1256
|
+
cmd.append("--json")
|
|
1257
|
+
|
|
1258
|
+
try:
|
|
1259
|
+
result = subprocess.run(cmd, cwd=str(catalog_repo), capture_output=True, text=True)
|
|
1260
|
+
print(result.stdout)
|
|
1261
|
+
if result.stderr:
|
|
1262
|
+
print(result.stderr)
|
|
1263
|
+
return result.returncode
|
|
1264
|
+
except FileNotFoundError:
|
|
1265
|
+
print("Node.js not found.")
|
|
1266
|
+
return 1
|
|
1267
|
+
else:
|
|
1268
|
+
# Fallback: Basic Python validation
|
|
1269
|
+
return self._validate_templates_python(source_dir, strict, json_output)
|
|
1270
|
+
|
|
1271
|
+
def _validate_templates_python(self, source_dir: str = None, strict: bool = False, json_output: bool = False) -> int:
|
|
1272
|
+
"""Validate templates using Python."""
|
|
1273
|
+
import json as json_module
|
|
1274
|
+
|
|
1275
|
+
try:
|
|
1276
|
+
import yaml
|
|
1277
|
+
except ImportError:
|
|
1278
|
+
print("PyYAML not installed. Install with: pip install pyyaml")
|
|
1279
|
+
return 1
|
|
1280
|
+
|
|
1281
|
+
# Find templates directory
|
|
1282
|
+
if source_dir:
|
|
1283
|
+
templates_dir = Path(source_dir)
|
|
1284
|
+
else:
|
|
1285
|
+
possible_paths = [
|
|
1286
|
+
Path.home() / "Agent-Recipes" / "agent_recipes" / "templates",
|
|
1287
|
+
Path.cwd() / "Agent-Recipes" / "agent_recipes" / "templates",
|
|
1288
|
+
]
|
|
1289
|
+
templates_dir = None
|
|
1290
|
+
for p in possible_paths:
|
|
1291
|
+
if p.exists():
|
|
1292
|
+
templates_dir = p
|
|
1293
|
+
break
|
|
1294
|
+
|
|
1295
|
+
if not templates_dir or not templates_dir.exists():
|
|
1296
|
+
print(f"Templates directory not found: {source_dir or 'default locations'}")
|
|
1297
|
+
return 1
|
|
1298
|
+
|
|
1299
|
+
print(f"Validating templates in: {templates_dir}\n")
|
|
1300
|
+
|
|
1301
|
+
results = []
|
|
1302
|
+
errors_count = 0
|
|
1303
|
+
warnings_count = 0
|
|
1304
|
+
|
|
1305
|
+
for entry in sorted(templates_dir.iterdir()):
|
|
1306
|
+
if not entry.is_dir() or entry.name.startswith("."):
|
|
1307
|
+
continue
|
|
1308
|
+
|
|
1309
|
+
template_yaml = entry / "TEMPLATE.yaml"
|
|
1310
|
+
if not template_yaml.exists():
|
|
1311
|
+
continue
|
|
1312
|
+
|
|
1313
|
+
result = {"name": entry.name, "valid": True, "errors": [], "warnings": []}
|
|
1314
|
+
|
|
1315
|
+
try:
|
|
1316
|
+
with open(template_yaml) as f:
|
|
1317
|
+
data = yaml.safe_load(f)
|
|
1318
|
+
|
|
1319
|
+
# Check required fields
|
|
1320
|
+
required = ["name", "version", "description"]
|
|
1321
|
+
for field in required:
|
|
1322
|
+
if not data.get(field):
|
|
1323
|
+
result["errors"].append(f"Missing required field: {field}")
|
|
1324
|
+
result["valid"] = False
|
|
1325
|
+
|
|
1326
|
+
# Check version format
|
|
1327
|
+
version = data.get("version", "")
|
|
1328
|
+
if version and not self._is_valid_version(version):
|
|
1329
|
+
result["warnings"].append(f"Invalid version format: {version}")
|
|
1330
|
+
|
|
1331
|
+
# Check workflow file
|
|
1332
|
+
workflow = data.get("workflow", "workflow.yaml")
|
|
1333
|
+
if isinstance(workflow, str) and not (entry / workflow).exists():
|
|
1334
|
+
if strict:
|
|
1335
|
+
result["errors"].append(f"Workflow file not found: {workflow}")
|
|
1336
|
+
result["valid"] = False
|
|
1337
|
+
else:
|
|
1338
|
+
result["warnings"].append(f"Workflow file not found: {workflow}")
|
|
1339
|
+
|
|
1340
|
+
except yaml.YAMLError as e:
|
|
1341
|
+
result["errors"].append(f"Invalid YAML: {e}")
|
|
1342
|
+
result["valid"] = False
|
|
1343
|
+
except Exception as e:
|
|
1344
|
+
result["errors"].append(f"Error: {e}")
|
|
1345
|
+
result["valid"] = False
|
|
1346
|
+
|
|
1347
|
+
results.append(result)
|
|
1348
|
+
errors_count += len(result["errors"])
|
|
1349
|
+
warnings_count += len(result["warnings"])
|
|
1350
|
+
|
|
1351
|
+
# Output
|
|
1352
|
+
if json_output:
|
|
1353
|
+
print(json_module.dumps(results, indent=2))
|
|
1354
|
+
else:
|
|
1355
|
+
for r in results:
|
|
1356
|
+
status = "✓" if r["valid"] else "✗"
|
|
1357
|
+
color = "\033[32m" if r["valid"] else "\033[31m"
|
|
1358
|
+
print(f"{color}{status}\033[0m {r['name']}")
|
|
1359
|
+
for err in r["errors"]:
|
|
1360
|
+
print(f" \033[31m✗ ERROR:\033[0m {err}")
|
|
1361
|
+
for warn in r["warnings"]:
|
|
1362
|
+
print(f" \033[33m⚠ WARNING:\033[0m {warn}")
|
|
1363
|
+
|
|
1364
|
+
print(f"\n{len(results)} templates checked, {errors_count} errors, {warnings_count} warnings")
|
|
1365
|
+
|
|
1366
|
+
return 1 if errors_count > 0 else 0
|
|
1367
|
+
|
|
1368
|
+
def _is_valid_version(self, version: str) -> bool:
|
|
1369
|
+
"""Check if version string is valid semver."""
|
|
1370
|
+
import re
|
|
1371
|
+
return bool(re.match(r'^\d+\.\d+\.\d+', version))
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def import_time():
|
|
1375
|
+
"""Get current time (helper for cache expiry display)."""
|
|
1376
|
+
import time
|
|
1377
|
+
return time.time()
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
# Convenience function for CLI integration
|
|
1381
|
+
def handle_templates_command(args: List[str]) -> int:
|
|
1382
|
+
"""Handle templates command from main CLI."""
|
|
1383
|
+
handler = TemplatesHandler()
|
|
1384
|
+
return handler.handle(args)
|