PraisonAI 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. praisonai/__init__.py +54 -0
  2. praisonai/__main__.py +15 -0
  3. praisonai/acp/__init__.py +54 -0
  4. praisonai/acp/config.py +159 -0
  5. praisonai/acp/server.py +587 -0
  6. praisonai/acp/session.py +219 -0
  7. praisonai/adapters/__init__.py +50 -0
  8. praisonai/adapters/readers.py +395 -0
  9. praisonai/adapters/rerankers.py +315 -0
  10. praisonai/adapters/retrievers.py +394 -0
  11. praisonai/adapters/vector_stores.py +409 -0
  12. praisonai/agent_scheduler.py +337 -0
  13. praisonai/agents_generator.py +903 -0
  14. praisonai/api/call.py +292 -0
  15. praisonai/auto.py +1197 -0
  16. praisonai/capabilities/__init__.py +275 -0
  17. praisonai/capabilities/a2a.py +140 -0
  18. praisonai/capabilities/assistants.py +283 -0
  19. praisonai/capabilities/audio.py +320 -0
  20. praisonai/capabilities/batches.py +469 -0
  21. praisonai/capabilities/completions.py +336 -0
  22. praisonai/capabilities/container_files.py +155 -0
  23. praisonai/capabilities/containers.py +93 -0
  24. praisonai/capabilities/embeddings.py +158 -0
  25. praisonai/capabilities/files.py +467 -0
  26. praisonai/capabilities/fine_tuning.py +293 -0
  27. praisonai/capabilities/guardrails.py +182 -0
  28. praisonai/capabilities/images.py +330 -0
  29. praisonai/capabilities/mcp.py +190 -0
  30. praisonai/capabilities/messages.py +270 -0
  31. praisonai/capabilities/moderations.py +154 -0
  32. praisonai/capabilities/ocr.py +217 -0
  33. praisonai/capabilities/passthrough.py +204 -0
  34. praisonai/capabilities/rag.py +207 -0
  35. praisonai/capabilities/realtime.py +160 -0
  36. praisonai/capabilities/rerank.py +165 -0
  37. praisonai/capabilities/responses.py +266 -0
  38. praisonai/capabilities/search.py +109 -0
  39. praisonai/capabilities/skills.py +133 -0
  40. praisonai/capabilities/vector_store_files.py +334 -0
  41. praisonai/capabilities/vector_stores.py +304 -0
  42. praisonai/capabilities/videos.py +141 -0
  43. praisonai/chainlit_ui.py +304 -0
  44. praisonai/chat/__init__.py +106 -0
  45. praisonai/chat/app.py +125 -0
  46. praisonai/cli/__init__.py +26 -0
  47. praisonai/cli/app.py +213 -0
  48. praisonai/cli/commands/__init__.py +75 -0
  49. praisonai/cli/commands/acp.py +70 -0
  50. praisonai/cli/commands/completion.py +333 -0
  51. praisonai/cli/commands/config.py +166 -0
  52. praisonai/cli/commands/debug.py +142 -0
  53. praisonai/cli/commands/diag.py +55 -0
  54. praisonai/cli/commands/doctor.py +166 -0
  55. praisonai/cli/commands/environment.py +179 -0
  56. praisonai/cli/commands/lsp.py +112 -0
  57. praisonai/cli/commands/mcp.py +210 -0
  58. praisonai/cli/commands/profile.py +457 -0
  59. praisonai/cli/commands/run.py +228 -0
  60. praisonai/cli/commands/schedule.py +150 -0
  61. praisonai/cli/commands/serve.py +97 -0
  62. praisonai/cli/commands/session.py +212 -0
  63. praisonai/cli/commands/traces.py +145 -0
  64. praisonai/cli/commands/version.py +101 -0
  65. praisonai/cli/configuration/__init__.py +18 -0
  66. praisonai/cli/configuration/loader.py +353 -0
  67. praisonai/cli/configuration/paths.py +114 -0
  68. praisonai/cli/configuration/schema.py +164 -0
  69. praisonai/cli/features/__init__.py +268 -0
  70. praisonai/cli/features/acp.py +236 -0
  71. praisonai/cli/features/action_orchestrator.py +546 -0
  72. praisonai/cli/features/agent_scheduler.py +773 -0
  73. praisonai/cli/features/agent_tools.py +474 -0
  74. praisonai/cli/features/agents.py +375 -0
  75. praisonai/cli/features/at_mentions.py +471 -0
  76. praisonai/cli/features/auto_memory.py +182 -0
  77. praisonai/cli/features/autonomy_mode.py +490 -0
  78. praisonai/cli/features/background.py +356 -0
  79. praisonai/cli/features/base.py +168 -0
  80. praisonai/cli/features/capabilities.py +1326 -0
  81. praisonai/cli/features/checkpoints.py +338 -0
  82. praisonai/cli/features/code_intelligence.py +652 -0
  83. praisonai/cli/features/compaction.py +294 -0
  84. praisonai/cli/features/compare.py +534 -0
  85. praisonai/cli/features/cost_tracker.py +514 -0
  86. praisonai/cli/features/debug.py +810 -0
  87. praisonai/cli/features/deploy.py +517 -0
  88. praisonai/cli/features/diag.py +289 -0
  89. praisonai/cli/features/doctor/__init__.py +63 -0
  90. praisonai/cli/features/doctor/checks/__init__.py +24 -0
  91. praisonai/cli/features/doctor/checks/acp_checks.py +240 -0
  92. praisonai/cli/features/doctor/checks/config_checks.py +366 -0
  93. praisonai/cli/features/doctor/checks/db_checks.py +366 -0
  94. praisonai/cli/features/doctor/checks/env_checks.py +543 -0
  95. praisonai/cli/features/doctor/checks/lsp_checks.py +199 -0
  96. praisonai/cli/features/doctor/checks/mcp_checks.py +349 -0
  97. praisonai/cli/features/doctor/checks/memory_checks.py +268 -0
  98. praisonai/cli/features/doctor/checks/network_checks.py +251 -0
  99. praisonai/cli/features/doctor/checks/obs_checks.py +328 -0
  100. praisonai/cli/features/doctor/checks/performance_checks.py +235 -0
  101. praisonai/cli/features/doctor/checks/permissions_checks.py +259 -0
  102. praisonai/cli/features/doctor/checks/selftest_checks.py +322 -0
  103. praisonai/cli/features/doctor/checks/serve_checks.py +426 -0
  104. praisonai/cli/features/doctor/checks/skills_checks.py +231 -0
  105. praisonai/cli/features/doctor/checks/tools_checks.py +371 -0
  106. praisonai/cli/features/doctor/engine.py +266 -0
  107. praisonai/cli/features/doctor/formatters.py +310 -0
  108. praisonai/cli/features/doctor/handler.py +397 -0
  109. praisonai/cli/features/doctor/models.py +264 -0
  110. praisonai/cli/features/doctor/registry.py +239 -0
  111. praisonai/cli/features/endpoints.py +1019 -0
  112. praisonai/cli/features/eval.py +560 -0
  113. praisonai/cli/features/external_agents.py +231 -0
  114. praisonai/cli/features/fast_context.py +410 -0
  115. praisonai/cli/features/flow_display.py +566 -0
  116. praisonai/cli/features/git_integration.py +651 -0
  117. praisonai/cli/features/guardrail.py +171 -0
  118. praisonai/cli/features/handoff.py +185 -0
  119. praisonai/cli/features/hooks.py +583 -0
  120. praisonai/cli/features/image.py +384 -0
  121. praisonai/cli/features/interactive_runtime.py +585 -0
  122. praisonai/cli/features/interactive_tools.py +380 -0
  123. praisonai/cli/features/interactive_tui.py +603 -0
  124. praisonai/cli/features/jobs.py +632 -0
  125. praisonai/cli/features/knowledge.py +531 -0
  126. praisonai/cli/features/lite.py +244 -0
  127. praisonai/cli/features/lsp_cli.py +225 -0
  128. praisonai/cli/features/mcp.py +169 -0
  129. praisonai/cli/features/message_queue.py +587 -0
  130. praisonai/cli/features/metrics.py +211 -0
  131. praisonai/cli/features/n8n.py +673 -0
  132. praisonai/cli/features/observability.py +293 -0
  133. praisonai/cli/features/ollama.py +361 -0
  134. praisonai/cli/features/output_style.py +273 -0
  135. praisonai/cli/features/package.py +631 -0
  136. praisonai/cli/features/performance.py +308 -0
  137. praisonai/cli/features/persistence.py +636 -0
  138. praisonai/cli/features/profile.py +226 -0
  139. praisonai/cli/features/profiler/__init__.py +81 -0
  140. praisonai/cli/features/profiler/core.py +558 -0
  141. praisonai/cli/features/profiler/optimizations.py +652 -0
  142. praisonai/cli/features/profiler/suite.py +386 -0
  143. praisonai/cli/features/profiling.py +350 -0
  144. praisonai/cli/features/queue/__init__.py +73 -0
  145. praisonai/cli/features/queue/manager.py +395 -0
  146. praisonai/cli/features/queue/models.py +286 -0
  147. praisonai/cli/features/queue/persistence.py +564 -0
  148. praisonai/cli/features/queue/scheduler.py +484 -0
  149. praisonai/cli/features/queue/worker.py +372 -0
  150. praisonai/cli/features/recipe.py +1723 -0
  151. praisonai/cli/features/recipes.py +449 -0
  152. praisonai/cli/features/registry.py +229 -0
  153. praisonai/cli/features/repo_map.py +860 -0
  154. praisonai/cli/features/router.py +466 -0
  155. praisonai/cli/features/sandbox_executor.py +515 -0
  156. praisonai/cli/features/serve.py +829 -0
  157. praisonai/cli/features/session.py +222 -0
  158. praisonai/cli/features/skills.py +856 -0
  159. praisonai/cli/features/slash_commands.py +650 -0
  160. praisonai/cli/features/telemetry.py +179 -0
  161. praisonai/cli/features/templates.py +1384 -0
  162. praisonai/cli/features/thinking.py +305 -0
  163. praisonai/cli/features/todo.py +334 -0
  164. praisonai/cli/features/tools.py +680 -0
  165. praisonai/cli/features/tui/__init__.py +83 -0
  166. praisonai/cli/features/tui/app.py +580 -0
  167. praisonai/cli/features/tui/cli.py +566 -0
  168. praisonai/cli/features/tui/debug.py +511 -0
  169. praisonai/cli/features/tui/events.py +99 -0
  170. praisonai/cli/features/tui/mock_provider.py +328 -0
  171. praisonai/cli/features/tui/orchestrator.py +652 -0
  172. praisonai/cli/features/tui/screens/__init__.py +50 -0
  173. praisonai/cli/features/tui/screens/main.py +245 -0
  174. praisonai/cli/features/tui/screens/queue.py +174 -0
  175. praisonai/cli/features/tui/screens/session.py +124 -0
  176. praisonai/cli/features/tui/screens/settings.py +148 -0
  177. praisonai/cli/features/tui/widgets/__init__.py +56 -0
  178. praisonai/cli/features/tui/widgets/chat.py +261 -0
  179. praisonai/cli/features/tui/widgets/composer.py +224 -0
  180. praisonai/cli/features/tui/widgets/queue_panel.py +200 -0
  181. praisonai/cli/features/tui/widgets/status.py +167 -0
  182. praisonai/cli/features/tui/widgets/tool_panel.py +248 -0
  183. praisonai/cli/features/workflow.py +720 -0
  184. praisonai/cli/legacy.py +236 -0
  185. praisonai/cli/main.py +5559 -0
  186. praisonai/cli/schedule_cli.py +54 -0
  187. praisonai/cli/state/__init__.py +31 -0
  188. praisonai/cli/state/identifiers.py +161 -0
  189. praisonai/cli/state/sessions.py +313 -0
  190. praisonai/code/__init__.py +93 -0
  191. praisonai/code/agent_tools.py +344 -0
  192. praisonai/code/diff/__init__.py +21 -0
  193. praisonai/code/diff/diff_strategy.py +432 -0
  194. praisonai/code/tools/__init__.py +27 -0
  195. praisonai/code/tools/apply_diff.py +221 -0
  196. praisonai/code/tools/execute_command.py +275 -0
  197. praisonai/code/tools/list_files.py +274 -0
  198. praisonai/code/tools/read_file.py +206 -0
  199. praisonai/code/tools/search_replace.py +248 -0
  200. praisonai/code/tools/write_file.py +217 -0
  201. praisonai/code/utils/__init__.py +46 -0
  202. praisonai/code/utils/file_utils.py +307 -0
  203. praisonai/code/utils/ignore_utils.py +308 -0
  204. praisonai/code/utils/text_utils.py +276 -0
  205. praisonai/db/__init__.py +64 -0
  206. praisonai/db/adapter.py +531 -0
  207. praisonai/deploy/__init__.py +62 -0
  208. praisonai/deploy/api.py +231 -0
  209. praisonai/deploy/docker.py +454 -0
  210. praisonai/deploy/doctor.py +367 -0
  211. praisonai/deploy/main.py +327 -0
  212. praisonai/deploy/models.py +179 -0
  213. praisonai/deploy/providers/__init__.py +33 -0
  214. praisonai/deploy/providers/aws.py +331 -0
  215. praisonai/deploy/providers/azure.py +358 -0
  216. praisonai/deploy/providers/base.py +101 -0
  217. praisonai/deploy/providers/gcp.py +314 -0
  218. praisonai/deploy/schema.py +208 -0
  219. praisonai/deploy.py +185 -0
  220. praisonai/endpoints/__init__.py +53 -0
  221. praisonai/endpoints/a2u_server.py +410 -0
  222. praisonai/endpoints/discovery.py +165 -0
  223. praisonai/endpoints/providers/__init__.py +28 -0
  224. praisonai/endpoints/providers/a2a.py +253 -0
  225. praisonai/endpoints/providers/a2u.py +208 -0
  226. praisonai/endpoints/providers/agents_api.py +171 -0
  227. praisonai/endpoints/providers/base.py +231 -0
  228. praisonai/endpoints/providers/mcp.py +263 -0
  229. praisonai/endpoints/providers/recipe.py +206 -0
  230. praisonai/endpoints/providers/tools_mcp.py +150 -0
  231. praisonai/endpoints/registry.py +131 -0
  232. praisonai/endpoints/server.py +161 -0
  233. praisonai/inbuilt_tools/__init__.py +24 -0
  234. praisonai/inbuilt_tools/autogen_tools.py +117 -0
  235. praisonai/inc/__init__.py +2 -0
  236. praisonai/inc/config.py +96 -0
  237. praisonai/inc/models.py +155 -0
  238. praisonai/integrations/__init__.py +56 -0
  239. praisonai/integrations/base.py +303 -0
  240. praisonai/integrations/claude_code.py +270 -0
  241. praisonai/integrations/codex_cli.py +255 -0
  242. praisonai/integrations/cursor_cli.py +195 -0
  243. praisonai/integrations/gemini_cli.py +222 -0
  244. praisonai/jobs/__init__.py +67 -0
  245. praisonai/jobs/executor.py +425 -0
  246. praisonai/jobs/models.py +230 -0
  247. praisonai/jobs/router.py +314 -0
  248. praisonai/jobs/server.py +186 -0
  249. praisonai/jobs/store.py +203 -0
  250. praisonai/llm/__init__.py +66 -0
  251. praisonai/llm/registry.py +382 -0
  252. praisonai/mcp_server/__init__.py +152 -0
  253. praisonai/mcp_server/adapters/__init__.py +74 -0
  254. praisonai/mcp_server/adapters/agents.py +128 -0
  255. praisonai/mcp_server/adapters/capabilities.py +168 -0
  256. praisonai/mcp_server/adapters/cli_tools.py +568 -0
  257. praisonai/mcp_server/adapters/extended_capabilities.py +462 -0
  258. praisonai/mcp_server/adapters/knowledge.py +93 -0
  259. praisonai/mcp_server/adapters/memory.py +104 -0
  260. praisonai/mcp_server/adapters/prompts.py +306 -0
  261. praisonai/mcp_server/adapters/resources.py +124 -0
  262. praisonai/mcp_server/adapters/tools_bridge.py +280 -0
  263. praisonai/mcp_server/auth/__init__.py +48 -0
  264. praisonai/mcp_server/auth/api_key.py +291 -0
  265. praisonai/mcp_server/auth/oauth.py +460 -0
  266. praisonai/mcp_server/auth/oidc.py +289 -0
  267. praisonai/mcp_server/auth/scopes.py +260 -0
  268. praisonai/mcp_server/cli.py +852 -0
  269. praisonai/mcp_server/elicitation.py +445 -0
  270. praisonai/mcp_server/icons.py +302 -0
  271. praisonai/mcp_server/recipe_adapter.py +573 -0
  272. praisonai/mcp_server/recipe_cli.py +824 -0
  273. praisonai/mcp_server/registry.py +703 -0
  274. praisonai/mcp_server/sampling.py +422 -0
  275. praisonai/mcp_server/server.py +490 -0
  276. praisonai/mcp_server/tasks.py +443 -0
  277. praisonai/mcp_server/transports/__init__.py +18 -0
  278. praisonai/mcp_server/transports/http_stream.py +376 -0
  279. praisonai/mcp_server/transports/stdio.py +132 -0
  280. praisonai/persistence/__init__.py +84 -0
  281. praisonai/persistence/config.py +238 -0
  282. praisonai/persistence/conversation/__init__.py +25 -0
  283. praisonai/persistence/conversation/async_mysql.py +427 -0
  284. praisonai/persistence/conversation/async_postgres.py +410 -0
  285. praisonai/persistence/conversation/async_sqlite.py +371 -0
  286. praisonai/persistence/conversation/base.py +151 -0
  287. praisonai/persistence/conversation/json_store.py +250 -0
  288. praisonai/persistence/conversation/mysql.py +387 -0
  289. praisonai/persistence/conversation/postgres.py +401 -0
  290. praisonai/persistence/conversation/singlestore.py +240 -0
  291. praisonai/persistence/conversation/sqlite.py +341 -0
  292. praisonai/persistence/conversation/supabase.py +203 -0
  293. praisonai/persistence/conversation/surrealdb.py +287 -0
  294. praisonai/persistence/factory.py +301 -0
  295. praisonai/persistence/hooks/__init__.py +18 -0
  296. praisonai/persistence/hooks/agent_hooks.py +297 -0
  297. praisonai/persistence/knowledge/__init__.py +26 -0
  298. praisonai/persistence/knowledge/base.py +144 -0
  299. praisonai/persistence/knowledge/cassandra.py +232 -0
  300. praisonai/persistence/knowledge/chroma.py +295 -0
  301. praisonai/persistence/knowledge/clickhouse.py +242 -0
  302. praisonai/persistence/knowledge/cosmosdb_vector.py +438 -0
  303. praisonai/persistence/knowledge/couchbase.py +286 -0
  304. praisonai/persistence/knowledge/lancedb.py +216 -0
  305. praisonai/persistence/knowledge/langchain_adapter.py +291 -0
  306. praisonai/persistence/knowledge/lightrag_adapter.py +212 -0
  307. praisonai/persistence/knowledge/llamaindex_adapter.py +256 -0
  308. praisonai/persistence/knowledge/milvus.py +277 -0
  309. praisonai/persistence/knowledge/mongodb_vector.py +306 -0
  310. praisonai/persistence/knowledge/pgvector.py +335 -0
  311. praisonai/persistence/knowledge/pinecone.py +253 -0
  312. praisonai/persistence/knowledge/qdrant.py +301 -0
  313. praisonai/persistence/knowledge/redis_vector.py +291 -0
  314. praisonai/persistence/knowledge/singlestore_vector.py +299 -0
  315. praisonai/persistence/knowledge/surrealdb_vector.py +309 -0
  316. praisonai/persistence/knowledge/upstash_vector.py +266 -0
  317. praisonai/persistence/knowledge/weaviate.py +223 -0
  318. praisonai/persistence/migrations/__init__.py +10 -0
  319. praisonai/persistence/migrations/manager.py +251 -0
  320. praisonai/persistence/orchestrator.py +406 -0
  321. praisonai/persistence/state/__init__.py +21 -0
  322. praisonai/persistence/state/async_mongodb.py +200 -0
  323. praisonai/persistence/state/base.py +107 -0
  324. praisonai/persistence/state/dynamodb.py +226 -0
  325. praisonai/persistence/state/firestore.py +175 -0
  326. praisonai/persistence/state/gcs.py +155 -0
  327. praisonai/persistence/state/memory.py +245 -0
  328. praisonai/persistence/state/mongodb.py +158 -0
  329. praisonai/persistence/state/redis.py +190 -0
  330. praisonai/persistence/state/upstash.py +144 -0
  331. praisonai/persistence/tests/__init__.py +3 -0
  332. praisonai/persistence/tests/test_all_backends.py +633 -0
  333. praisonai/profiler.py +1214 -0
  334. praisonai/recipe/__init__.py +134 -0
  335. praisonai/recipe/bridge.py +278 -0
  336. praisonai/recipe/core.py +893 -0
  337. praisonai/recipe/exceptions.py +54 -0
  338. praisonai/recipe/history.py +402 -0
  339. praisonai/recipe/models.py +266 -0
  340. praisonai/recipe/operations.py +440 -0
  341. praisonai/recipe/policy.py +422 -0
  342. praisonai/recipe/registry.py +849 -0
  343. praisonai/recipe/runtime.py +214 -0
  344. praisonai/recipe/security.py +711 -0
  345. praisonai/recipe/serve.py +859 -0
  346. praisonai/recipe/server.py +613 -0
  347. praisonai/scheduler/__init__.py +45 -0
  348. praisonai/scheduler/agent_scheduler.py +552 -0
  349. praisonai/scheduler/base.py +124 -0
  350. praisonai/scheduler/daemon_manager.py +225 -0
  351. praisonai/scheduler/state_manager.py +155 -0
  352. praisonai/scheduler/yaml_loader.py +193 -0
  353. praisonai/scheduler.py +194 -0
  354. praisonai/setup/__init__.py +1 -0
  355. praisonai/setup/build.py +21 -0
  356. praisonai/setup/post_install.py +23 -0
  357. praisonai/setup/setup_conda_env.py +25 -0
  358. praisonai/setup.py +16 -0
  359. praisonai/templates/__init__.py +116 -0
  360. praisonai/templates/cache.py +364 -0
  361. praisonai/templates/dependency_checker.py +358 -0
  362. praisonai/templates/discovery.py +391 -0
  363. praisonai/templates/loader.py +564 -0
  364. praisonai/templates/registry.py +511 -0
  365. praisonai/templates/resolver.py +206 -0
  366. praisonai/templates/security.py +327 -0
  367. praisonai/templates/tool_override.py +498 -0
  368. praisonai/templates/tools_doctor.py +256 -0
  369. praisonai/test.py +105 -0
  370. praisonai/train.py +562 -0
  371. praisonai/train_vision.py +306 -0
  372. praisonai/ui/agents.py +824 -0
  373. praisonai/ui/callbacks.py +57 -0
  374. praisonai/ui/chainlit_compat.py +246 -0
  375. praisonai/ui/chat.py +532 -0
  376. praisonai/ui/code.py +717 -0
  377. praisonai/ui/colab.py +474 -0
  378. praisonai/ui/colab_chainlit.py +81 -0
  379. praisonai/ui/components/aicoder.py +284 -0
  380. praisonai/ui/context.py +283 -0
  381. praisonai/ui/database_config.py +56 -0
  382. praisonai/ui/db.py +294 -0
  383. praisonai/ui/realtime.py +488 -0
  384. praisonai/ui/realtimeclient/__init__.py +756 -0
  385. praisonai/ui/realtimeclient/tools.py +242 -0
  386. praisonai/ui/sql_alchemy.py +710 -0
  387. praisonai/upload_vision.py +140 -0
  388. praisonai/version.py +1 -0
  389. praisonai-3.0.0.dist-info/METADATA +3493 -0
  390. praisonai-3.0.0.dist-info/RECORD +393 -0
  391. praisonai-3.0.0.dist-info/WHEEL +5 -0
  392. praisonai-3.0.0.dist-info/entry_points.txt +4 -0
  393. praisonai-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,511 @@
1
+ """
2
+ Template Registry
3
+
4
+ Handles fetching templates from remote sources (GitHub, HTTP).
5
+ All network operations are lazy and only performed when explicitly requested.
6
+ """
7
+
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import tempfile
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional
14
+ from dataclasses import dataclass, field
15
+
16
+ from .resolver import ResolvedTemplate, TemplateResolver, TemplateSource
17
+ from .cache import TemplateCache, CachedTemplate
18
+
19
+
20
+ @dataclass
21
+ class TemplateInfo:
22
+ """Information about a template."""
23
+ name: str
24
+ description: str
25
+ version: str = "1.0.0"
26
+ author: Optional[str] = None
27
+ tags: List[str] = field(default_factory=list)
28
+ requires: Dict[str, Any] = field(default_factory=dict)
29
+ source: Optional[str] = None
30
+ path: Optional[str] = None
31
+ sha256: Optional[str] = None
32
+
33
+
34
+ class TemplateRegistry:
35
+ """
36
+ Registry for discovering and fetching templates.
37
+
38
+ Supports:
39
+ - GitHub repositories (via contents API or raw.githubusercontent.com)
40
+ - HTTP URLs
41
+ - Local package installations
42
+ """
43
+
44
+ GITHUB_API_BASE = "https://api.github.com"
45
+ GITHUB_RAW_BASE = "https://raw.githubusercontent.com"
46
+
47
+ # Default recipes repository
48
+ DEFAULT_OWNER = "MervinPraison"
49
+ DEFAULT_REPO = "agent-recipes"
50
+
51
+ def __init__(
52
+ self,
53
+ cache: Optional[TemplateCache] = None,
54
+ github_token: Optional[str] = None,
55
+ offline: bool = False
56
+ ):
57
+ """
58
+ Initialize the registry.
59
+
60
+ Args:
61
+ cache: Template cache instance
62
+ github_token: Optional GitHub token for API requests
63
+ offline: If True, only use cached templates
64
+ """
65
+ self.cache = cache or TemplateCache()
66
+ self.github_token = github_token or os.environ.get("GITHUB_TOKEN")
67
+ self.offline = offline
68
+ self._http_client = None
69
+
70
+ @property
71
+ def http_client(self):
72
+ """Lazy-load HTTP client."""
73
+ if self._http_client is None:
74
+ try:
75
+ import httpx
76
+ self._http_client = httpx.Client(timeout=30.0)
77
+ except ImportError:
78
+ import urllib.request
79
+ self._http_client = "urllib"
80
+ return self._http_client
81
+
82
+ def _make_request(
83
+ self,
84
+ url: str,
85
+ headers: Optional[Dict[str, str]] = None
86
+ ) -> bytes:
87
+ """Make an HTTP request."""
88
+ all_headers = headers or {}
89
+ if self.github_token and "api.github.com" in url:
90
+ all_headers["Authorization"] = f"token {self.github_token}"
91
+ all_headers["User-Agent"] = "PraisonAI-Templates/1.0"
92
+
93
+ if self.http_client == "urllib":
94
+ import urllib.request
95
+ req = urllib.request.Request(url, headers=all_headers)
96
+ with urllib.request.urlopen(req, timeout=30) as response:
97
+ return response.read()
98
+ else:
99
+ response = self.http_client.get(url, headers=all_headers)
100
+ response.raise_for_status()
101
+ return response.content
102
+
103
+ def fetch_github_template(
104
+ self,
105
+ owner: str,
106
+ repo: str,
107
+ template_path: str,
108
+ ref: str = "main"
109
+ ) -> Path:
110
+ """
111
+ Fetch a template from GitHub.
112
+
113
+ Args:
114
+ owner: Repository owner
115
+ repo: Repository name
116
+ template_path: Path to template within repo
117
+ ref: Git ref (branch, tag, or commit)
118
+
119
+ Returns:
120
+ Path to downloaded template directory
121
+ """
122
+ # Create temp directory for download
123
+ temp_dir = Path(tempfile.mkdtemp(prefix="praison_template_"))
124
+
125
+ # Fetch template files using GitHub API
126
+ api_url = f"{self.GITHUB_API_BASE}/repos/{owner}/{repo}/contents/templates/{template_path}?ref={ref}"
127
+
128
+ try:
129
+ response = self._make_request(api_url)
130
+ contents = json.loads(response)
131
+
132
+ if isinstance(contents, list):
133
+ # Directory listing
134
+ for item in contents:
135
+ if item["type"] == "file":
136
+ file_content = self._fetch_github_file(item["download_url"])
137
+ file_path = temp_dir / item["name"]
138
+ file_path.write_bytes(file_content)
139
+ elif item["type"] == "dir":
140
+ # Recursively fetch subdirectory
141
+ subdir = temp_dir / item["name"]
142
+ subdir.mkdir(parents=True, exist_ok=True)
143
+ self._fetch_github_dir(
144
+ owner, repo, f"templates/{template_path}/{item['name']}",
145
+ ref, subdir
146
+ )
147
+ else:
148
+ # Single file
149
+ file_content = self._fetch_github_file(contents["download_url"])
150
+ file_path = temp_dir / contents["name"]
151
+ file_path.write_bytes(file_content)
152
+
153
+ except Exception:
154
+ # Fallback to raw.githubusercontent.com
155
+ raw_url = f"{self.GITHUB_RAW_BASE}/{owner}/{repo}/{ref}/templates/{template_path}"
156
+ self._fetch_raw_template(raw_url, temp_dir, template_path)
157
+
158
+ return temp_dir
159
+
160
+ def _fetch_github_file(self, url: str) -> bytes:
161
+ """Fetch a single file from GitHub."""
162
+ return self._make_request(url)
163
+
164
+ def _fetch_github_dir(
165
+ self,
166
+ owner: str,
167
+ repo: str,
168
+ path: str,
169
+ ref: str,
170
+ target_dir: Path
171
+ ) -> None:
172
+ """Recursively fetch a directory from GitHub."""
173
+ api_url = f"{self.GITHUB_API_BASE}/repos/{owner}/{repo}/contents/{path}?ref={ref}"
174
+ response = self._make_request(api_url)
175
+ contents = json.loads(response)
176
+
177
+ for item in contents:
178
+ if item["type"] == "file":
179
+ file_content = self._fetch_github_file(item["download_url"])
180
+ file_path = target_dir / item["name"]
181
+ file_path.write_bytes(file_content)
182
+ elif item["type"] == "dir":
183
+ subdir = target_dir / item["name"]
184
+ subdir.mkdir(parents=True, exist_ok=True)
185
+ self._fetch_github_dir(owner, repo, f"{path}/{item['name']}", ref, subdir)
186
+
187
+ def _fetch_raw_template(
188
+ self,
189
+ base_url: str,
190
+ target_dir: Path,
191
+ template_name: str
192
+ ) -> None:
193
+ """Fetch template using raw URLs (fallback)."""
194
+ # Try to fetch common template files
195
+ files_to_try = [
196
+ "TEMPLATE.yaml",
197
+ "workflow.yaml",
198
+ "agents.yaml",
199
+ "README.md"
200
+ ]
201
+
202
+ for filename in files_to_try:
203
+ try:
204
+ url = f"{base_url}/{filename}"
205
+ content = self._make_request(url)
206
+ (target_dir / filename).write_bytes(content)
207
+ except Exception:
208
+ continue
209
+
210
+ def fetch_http_template(self, url: str) -> Path:
211
+ """
212
+ Fetch a template from an HTTP URL.
213
+
214
+ Args:
215
+ url: URL to template (YAML file or directory index)
216
+
217
+ Returns:
218
+ Path to downloaded template
219
+ """
220
+ temp_dir = Path(tempfile.mkdtemp(prefix="praison_template_"))
221
+
222
+ content = self._make_request(url)
223
+
224
+ # Determine filename from URL
225
+ filename = url.split("/")[-1]
226
+ if not filename.endswith((".yaml", ".yml")):
227
+ filename = "TEMPLATE.yaml"
228
+
229
+ (temp_dir / filename).write_bytes(content)
230
+
231
+ return temp_dir
232
+
233
+ def get_template(
234
+ self,
235
+ uri: str,
236
+ offline: bool = False
237
+ ) -> CachedTemplate:
238
+ """
239
+ Get a template by URI, fetching if necessary.
240
+
241
+ Args:
242
+ uri: Template URI
243
+ offline: If True, only use cache
244
+
245
+ Returns:
246
+ CachedTemplate with path and metadata
247
+
248
+ Raises:
249
+ ValueError: If template not found
250
+ """
251
+ resolved = TemplateResolver.resolve(uri)
252
+ use_offline = offline or self.offline
253
+
254
+ # Check cache first
255
+ cached = self.cache.get(resolved, offline=use_offline)
256
+ if cached:
257
+ return cached
258
+
259
+ if use_offline:
260
+ raise ValueError(
261
+ f"Template not found in cache: {uri}\n"
262
+ "Run without --offline to fetch from remote."
263
+ )
264
+
265
+ # Fetch based on source
266
+ if resolved.source == TemplateSource.LOCAL:
267
+ path = Path(resolved.path)
268
+ if not path.exists():
269
+ raise ValueError(f"Local template not found: {resolved.path}")
270
+ return CachedTemplate(
271
+ path=path,
272
+ metadata=self.cache.put(resolved, path).metadata
273
+ )
274
+
275
+ elif resolved.source == TemplateSource.PACKAGE:
276
+ # Try to find in installed package
277
+ return self._get_package_template(resolved)
278
+
279
+ elif resolved.source == TemplateSource.GITHUB:
280
+ temp_dir = self.fetch_github_template(
281
+ resolved.owner,
282
+ resolved.repo,
283
+ resolved.path,
284
+ resolved.ref or "main"
285
+ )
286
+ # Calculate checksum
287
+ sha256 = self._calculate_dir_checksum(temp_dir)
288
+ return self.cache.put(resolved, temp_dir, sha256=sha256)
289
+
290
+ elif resolved.source == TemplateSource.HTTP:
291
+ temp_dir = self.fetch_http_template(resolved.url)
292
+ sha256 = self._calculate_dir_checksum(temp_dir)
293
+ return self.cache.put(resolved, temp_dir, sha256=sha256)
294
+
295
+ raise ValueError(f"Unknown template source: {resolved.source}")
296
+
297
+ def _get_package_template(self, resolved: ResolvedTemplate) -> CachedTemplate:
298
+ """Get template from installed Python package."""
299
+ parts = resolved.path.split("/")
300
+ if len(parts) < 2:
301
+ raise ValueError(f"Invalid package template path: {resolved.path}")
302
+
303
+ package_name = parts[0]
304
+ template_name = "/".join(parts[1:])
305
+
306
+ try:
307
+ # Try importlib.resources first (Python 3.9+)
308
+ import importlib.resources as pkg_resources
309
+ package = __import__(package_name)
310
+
311
+ # Navigate to templates directory
312
+ templates_path = Path(package.__file__).parent / "templates" / template_name
313
+ if templates_path.exists():
314
+ return CachedTemplate(
315
+ path=templates_path,
316
+ metadata=self.cache.put(resolved, templates_path).metadata
317
+ )
318
+ except (ImportError, AttributeError):
319
+ pass
320
+
321
+ # Try pkg_resources fallback
322
+ try:
323
+ import pkg_resources as pkg_res
324
+ template_path = pkg_res.resource_filename(
325
+ package_name,
326
+ f"templates/{template_name}"
327
+ )
328
+ if Path(template_path).exists():
329
+ return CachedTemplate(
330
+ path=Path(template_path),
331
+ metadata=self.cache.put(resolved, Path(template_path)).metadata
332
+ )
333
+ except Exception:
334
+ pass
335
+
336
+ raise ValueError(
337
+ f"Package template not found: {resolved.path}\n"
338
+ f"Make sure package '{package_name}' is installed."
339
+ )
340
+
341
+ def _calculate_dir_checksum(self, directory: Path) -> str:
342
+ """Calculate SHA256 checksum of directory contents."""
343
+ hasher = hashlib.sha256()
344
+
345
+ for file_path in sorted(directory.rglob("*")):
346
+ if file_path.is_file() and not file_path.name.startswith("."):
347
+ hasher.update(file_path.name.encode())
348
+ hasher.update(file_path.read_bytes())
349
+
350
+ return hasher.hexdigest()
351
+
352
+ def list_remote_templates(
353
+ self,
354
+ owner: str = None,
355
+ repo: str = None,
356
+ ref: str = "main"
357
+ ) -> List[TemplateInfo]:
358
+ """
359
+ List templates available in a GitHub repository.
360
+
361
+ Args:
362
+ owner: Repository owner (default: MervinPraison)
363
+ repo: Repository name (default: agent-recipes)
364
+ ref: Git ref
365
+
366
+ Returns:
367
+ List of TemplateInfo objects
368
+ """
369
+ owner = owner or self.DEFAULT_OWNER
370
+ repo = repo or self.DEFAULT_REPO
371
+
372
+ if self.offline:
373
+ return []
374
+
375
+ templates = []
376
+
377
+ try:
378
+ # Fetch templates directory listing
379
+ api_url = f"{self.GITHUB_API_BASE}/repos/{owner}/{repo}/contents/templates?ref={ref}"
380
+ response = self._make_request(api_url)
381
+ contents = json.loads(response)
382
+
383
+ for item in contents:
384
+ if item["type"] == "dir":
385
+ # Try to fetch TEMPLATE.yaml for metadata
386
+ try:
387
+ template_url = f"{self.GITHUB_RAW_BASE}/{owner}/{repo}/{ref}/templates/{item['name']}/TEMPLATE.yaml"
388
+ template_content = self._make_request(template_url)
389
+
390
+ import yaml
391
+ config = yaml.safe_load(template_content)
392
+
393
+ templates.append(TemplateInfo(
394
+ name=config.get("name", item["name"]),
395
+ description=config.get("description", ""),
396
+ version=config.get("version", "1.0.0"),
397
+ author=config.get("author"),
398
+ tags=config.get("tags", []),
399
+ requires=config.get("requires", {}),
400
+ source=f"github:{owner}/{repo}/{item['name']}",
401
+ path=item["name"]
402
+ ))
403
+ except Exception:
404
+ # Fallback to basic info
405
+ templates.append(TemplateInfo(
406
+ name=item["name"],
407
+ description="",
408
+ source=f"github:{owner}/{repo}/{item['name']}",
409
+ path=item["name"]
410
+ ))
411
+ except Exception:
412
+ # Return empty list on error
413
+ pass
414
+
415
+ return templates
416
+
417
+ def search_templates(
418
+ self,
419
+ query: str,
420
+ owner: str = None,
421
+ repo: str = None
422
+ ) -> List[TemplateInfo]:
423
+ """
424
+ Search templates by name or tags.
425
+
426
+ Args:
427
+ query: Search query
428
+ owner: Repository owner
429
+ repo: Repository name
430
+
431
+ Returns:
432
+ List of matching TemplateInfo objects
433
+ """
434
+ all_templates = self.list_remote_templates(owner, repo)
435
+ query_lower = query.lower()
436
+
437
+ return [
438
+ t for t in all_templates
439
+ if query_lower in t.name.lower() or
440
+ query_lower in t.description.lower() or
441
+ any(query_lower in tag.lower() for tag in t.tags)
442
+ ]
443
+
444
+
445
+ def list_templates(
446
+ source: str = "all",
447
+ offline: bool = False
448
+ ) -> List[TemplateInfo]:
449
+ """
450
+ List available templates.
451
+
452
+ Args:
453
+ source: 'all', 'local', 'remote', or 'cached'
454
+ offline: If True, only list cached templates
455
+
456
+ Returns:
457
+ List of TemplateInfo objects
458
+ """
459
+ registry = TemplateRegistry(offline=offline)
460
+ templates = []
461
+
462
+ if source in ("all", "cached"):
463
+ # List cached templates
464
+ cache = TemplateCache()
465
+ for path, metadata in cache.list_cached():
466
+ templates.append(TemplateInfo(
467
+ name=path.name,
468
+ description="(cached)",
469
+ version=metadata.version or "unknown",
470
+ source=str(path)
471
+ ))
472
+
473
+ if source in ("all", "remote") and not offline:
474
+ # List remote templates
475
+ remote = registry.list_remote_templates()
476
+ templates.extend(remote)
477
+
478
+ return templates
479
+
480
+
481
+ def search_templates(query: str, offline: bool = False) -> List[TemplateInfo]:
482
+ """
483
+ Search templates by query.
484
+
485
+ Args:
486
+ query: Search query
487
+ offline: If True, only search cached templates
488
+
489
+ Returns:
490
+ List of matching TemplateInfo objects
491
+ """
492
+ registry = TemplateRegistry(offline=offline)
493
+ return registry.search_templates(query)
494
+
495
+
496
+ def install_template(
497
+ uri: str,
498
+ offline: bool = False
499
+ ) -> CachedTemplate:
500
+ """
501
+ Install (fetch and cache) a template.
502
+
503
+ Args:
504
+ uri: Template URI
505
+ offline: If True, fail if not cached
506
+
507
+ Returns:
508
+ CachedTemplate with path and metadata
509
+ """
510
+ registry = TemplateRegistry(offline=offline)
511
+ return registry.get_template(uri, offline=offline)