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,391 @@
1
+ """
2
+ Template Discovery
3
+
4
+ Discovers templates from multiple directories with precedence support.
5
+ Supports custom user directories, built-in templates, and package templates.
6
+ Includes caching to avoid repeated filesystem scans.
7
+ """
8
+
9
+ import os
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional, Tuple
13
+ from dataclasses import dataclass, field
14
+
15
+
16
+ @dataclass
17
+ class DiscoveredTemplate:
18
+ """Information about a discovered template."""
19
+ name: str
20
+ path: Path
21
+ source: str # 'custom', 'builtin', 'package'
22
+ priority: int # Lower = higher priority
23
+
24
+ # Optional metadata (loaded lazily)
25
+ description: Optional[str] = None
26
+ version: Optional[str] = None
27
+ author: Optional[str] = None
28
+
29
+
30
+ @dataclass
31
+ class _CacheEntry:
32
+ """Cache entry for template discovery results."""
33
+ templates: Dict[str, 'DiscoveredTemplate']
34
+ timestamp: float
35
+ mtimes: Dict[str, float] = field(default_factory=dict) # path -> mtime for invalidation
36
+
37
+
38
+ class TemplateDiscovery:
39
+ """
40
+ Discovers templates from multiple directories with precedence.
41
+
42
+ Search order (highest to lowest priority):
43
+ 1. User custom directory (~/.praison/templates)
44
+ 2. XDG config directory (~/.config/praison/templates)
45
+ 3. Project local directory (./.praison/templates)
46
+ 4. Built-in package templates (agent_recipes)
47
+
48
+ Templates in higher priority directories override those in lower priority.
49
+
50
+ Caching:
51
+ - Results are cached to avoid repeated filesystem scans
52
+ - Cache is invalidated when directory mtimes change
53
+ - Use refresh=True to force a fresh scan
54
+ - Default TTL is 300 seconds (5 minutes)
55
+ """
56
+
57
+ # Default search paths (in priority order)
58
+ DEFAULT_SEARCH_PATHS = [
59
+ ("~/.praison/templates", "custom", 1),
60
+ ("~/.config/praison/templates", "custom", 2),
61
+ ("./.praison/templates", "project", 3),
62
+ ]
63
+
64
+ TEMPLATE_FILE = "TEMPLATE.yaml"
65
+
66
+ # Cache TTL in seconds (5 minutes default)
67
+ CACHE_TTL = 300
68
+
69
+ # Class-level cache shared across instances with same search paths
70
+ _cache: Dict[str, _CacheEntry] = {}
71
+
72
+ def __init__(
73
+ self,
74
+ custom_dirs: Optional[List[str]] = None,
75
+ include_package: bool = True,
76
+ include_defaults: bool = True,
77
+ cache_ttl: Optional[int] = None
78
+ ):
79
+ """
80
+ Initialize template discovery.
81
+
82
+ Args:
83
+ custom_dirs: Additional custom directories to search (highest priority)
84
+ include_package: Whether to include package templates (agent_recipes)
85
+ include_defaults: Whether to include default search paths
86
+ cache_ttl: Cache time-to-live in seconds (default: 300)
87
+ """
88
+ self.search_paths: List[Tuple[Path, str, int]] = []
89
+
90
+ # Add custom directories first (highest priority)
91
+ if custom_dirs:
92
+ for i, dir_path in enumerate(custom_dirs):
93
+ expanded = Path(os.path.expanduser(dir_path))
94
+ self.search_paths.append((expanded, "custom", i))
95
+
96
+ # Add default search paths
97
+ if include_defaults:
98
+ base_priority = len(custom_dirs) if custom_dirs else 0
99
+ for path, source, priority in self.DEFAULT_SEARCH_PATHS:
100
+ expanded = Path(os.path.expanduser(path))
101
+ self.search_paths.append((expanded, source, base_priority + priority))
102
+
103
+ self.include_package = include_package
104
+ self._package_path: Optional[Path] = None
105
+ self._cache_ttl = cache_ttl if cache_ttl is not None else self.CACHE_TTL
106
+
107
+ # Generate cache key based on search paths configuration
108
+ self._cache_key = self._generate_cache_key()
109
+
110
+ def _generate_cache_key(self) -> str:
111
+ """Generate a unique cache key based on search paths."""
112
+ paths_str = "|".join(
113
+ f"{p}:{s}:{pr}" for p, s, pr in self.search_paths
114
+ )
115
+ return f"{paths_str}|pkg={self.include_package}"
116
+
117
+ def _get_dir_mtime(self, path: Path) -> float:
118
+ """Get directory modification time, or 0 if not accessible."""
119
+ try:
120
+ if path.exists():
121
+ return path.stat().st_mtime
122
+ except (OSError, PermissionError):
123
+ pass
124
+ return 0.0
125
+
126
+ def _is_cache_valid(self, entry: _CacheEntry) -> bool:
127
+ """Check if cache entry is still valid."""
128
+ # Check TTL
129
+ if time.time() - entry.timestamp > self._cache_ttl:
130
+ return False
131
+
132
+ # Check if any directory mtimes have changed
133
+ for path_str, cached_mtime in entry.mtimes.items():
134
+ current_mtime = self._get_dir_mtime(Path(path_str))
135
+ if current_mtime != cached_mtime:
136
+ return False
137
+
138
+ return True
139
+
140
+ @property
141
+ def package_templates_path(self) -> Optional[Path]:
142
+ """Get path to package templates (lazy loaded)."""
143
+ if self._package_path is None and self.include_package:
144
+ try:
145
+ import importlib.resources
146
+ try:
147
+ # Python 3.9+
148
+ ref = importlib.resources.files("agent_recipes") / "templates"
149
+ self._package_path = Path(str(ref))
150
+ except (TypeError, AttributeError):
151
+ # Fallback for older Python
152
+ import agent_recipes
153
+ self._package_path = Path(agent_recipes.__file__).parent / "templates"
154
+ except ImportError:
155
+ pass
156
+ return self._package_path
157
+
158
+ def discover_all(self, refresh: bool = False) -> Dict[str, DiscoveredTemplate]:
159
+ """
160
+ Discover all templates from all search paths.
161
+
162
+ Results are cached to avoid repeated filesystem scans.
163
+ Cache is invalidated when directory mtimes change or TTL expires.
164
+
165
+ Args:
166
+ refresh: If True, bypass cache and force a fresh scan
167
+
168
+ Returns:
169
+ Dict mapping template name to DiscoveredTemplate.
170
+ When duplicates exist, higher priority wins.
171
+ """
172
+ # Check cache first (unless refresh requested)
173
+ if not refresh and self._cache_key in self._cache:
174
+ entry = self._cache[self._cache_key]
175
+ if self._is_cache_valid(entry):
176
+ return entry.templates.copy()
177
+
178
+ templates: Dict[str, DiscoveredTemplate] = {}
179
+ mtimes: Dict[str, float] = {}
180
+
181
+ # Scan search paths in reverse priority order (lowest first)
182
+ # so that higher priority overwrites lower
183
+ all_paths = list(self.search_paths)
184
+
185
+ # Add package templates with lowest priority
186
+ if self.include_package and self.package_templates_path:
187
+ max_priority = max((p[2] for p in all_paths), default=0) + 1
188
+ all_paths.append((self.package_templates_path, "package", max_priority))
189
+
190
+ # Sort by priority descending (lowest priority first)
191
+ all_paths.sort(key=lambda x: x[2], reverse=True)
192
+
193
+ for dir_path, source, priority in all_paths:
194
+ # Record mtime for cache invalidation
195
+ mtimes[str(dir_path)] = self._get_dir_mtime(dir_path)
196
+
197
+ if dir_path.exists() and dir_path.is_dir():
198
+ discovered = self._scan_directory(dir_path, source, priority)
199
+ # Higher priority overwrites lower
200
+ templates.update(discovered)
201
+
202
+ # Store in cache
203
+ self._cache[self._cache_key] = _CacheEntry(
204
+ templates=templates.copy(),
205
+ timestamp=time.time(),
206
+ mtimes=mtimes
207
+ )
208
+
209
+ return templates
210
+
211
+ def _scan_directory(
212
+ self,
213
+ directory: Path,
214
+ source: str,
215
+ priority: int
216
+ ) -> Dict[str, DiscoveredTemplate]:
217
+ """
218
+ Scan a directory for templates (shallow scan).
219
+
220
+ Only looks at direct subdirectories containing TEMPLATE.yaml.
221
+ Does NOT recursively scan nested directories.
222
+
223
+ Args:
224
+ directory: Directory to scan
225
+ source: Source identifier ('custom', 'builtin', 'package')
226
+ priority: Priority level
227
+
228
+ Returns:
229
+ Dict mapping template name to DiscoveredTemplate
230
+ """
231
+ templates = {}
232
+
233
+ try:
234
+ for item in directory.iterdir():
235
+ if item.is_dir():
236
+ template_file = item / self.TEMPLATE_FILE
237
+ workflow_file = item / "workflow.yaml"
238
+
239
+ # A valid template has TEMPLATE.yaml or workflow.yaml
240
+ if template_file.exists() or workflow_file.exists():
241
+ template = DiscoveredTemplate(
242
+ name=item.name,
243
+ path=item,
244
+ source=source,
245
+ priority=priority
246
+ )
247
+
248
+ # Load basic metadata if TEMPLATE.yaml exists
249
+ if template_file.exists():
250
+ self._load_template_metadata(template, template_file)
251
+
252
+ templates[item.name] = template
253
+ except PermissionError:
254
+ pass # Skip directories we can't read
255
+
256
+ return templates
257
+
258
+ def _load_template_metadata(
259
+ self,
260
+ template: DiscoveredTemplate,
261
+ template_file: Path
262
+ ) -> None:
263
+ """Load basic metadata from TEMPLATE.yaml."""
264
+ try:
265
+ import yaml
266
+ with open(template_file) as f:
267
+ data = yaml.safe_load(f) or {}
268
+
269
+ template.description = data.get("description", "")
270
+ template.version = data.get("version", "1.0.0")
271
+ template.author = data.get("author")
272
+ except Exception:
273
+ pass # Metadata loading is optional
274
+
275
+ def find_template(self, name: str, refresh: bool = False) -> Optional[DiscoveredTemplate]:
276
+ """
277
+ Find a template by name.
278
+
279
+ Searches all directories in priority order and returns the
280
+ highest priority match.
281
+
282
+ Args:
283
+ name: Template name to find
284
+ refresh: If True, bypass cache and force a fresh scan
285
+
286
+ Returns:
287
+ DiscoveredTemplate if found, None otherwise
288
+ """
289
+ all_templates = self.discover_all(refresh=refresh)
290
+ return all_templates.get(name)
291
+
292
+ def resolve_template_path(self, name: str) -> Optional[Path]:
293
+ """
294
+ Resolve a template name to its path.
295
+
296
+ Args:
297
+ name: Template name
298
+
299
+ Returns:
300
+ Path to template directory, or None if not found
301
+ """
302
+ template = self.find_template(name)
303
+ return template.path if template else None
304
+
305
+ def list_templates(
306
+ self,
307
+ source_filter: Optional[str] = None,
308
+ refresh: bool = False
309
+ ) -> List[DiscoveredTemplate]:
310
+ """
311
+ List all discovered templates.
312
+
313
+ Args:
314
+ source_filter: Optional filter by source ('custom', 'package', etc.)
315
+ refresh: If True, bypass cache and force a fresh scan
316
+
317
+ Returns:
318
+ List of DiscoveredTemplate objects
319
+ """
320
+ templates = self.discover_all(refresh=refresh)
321
+
322
+ if source_filter:
323
+ return [t for t in templates.values() if t.source == source_filter]
324
+
325
+ return list(templates.values())
326
+
327
+ def clear_cache(self) -> None:
328
+ """Clear the template discovery cache."""
329
+ if self._cache_key in self._cache:
330
+ del self._cache[self._cache_key]
331
+
332
+ @classmethod
333
+ def clear_all_caches(cls) -> None:
334
+ """Clear all template discovery caches."""
335
+ cls._cache.clear()
336
+
337
+ def get_search_paths(self) -> List[Tuple[str, str, bool]]:
338
+ """
339
+ Get all search paths with their status.
340
+
341
+ Returns:
342
+ List of (path, source, exists) tuples
343
+ """
344
+ paths = []
345
+
346
+ for dir_path, source, _ in self.search_paths:
347
+ paths.append((str(dir_path), source, dir_path.exists()))
348
+
349
+ if self.include_package and self.package_templates_path:
350
+ paths.append((
351
+ str(self.package_templates_path),
352
+ "package",
353
+ self.package_templates_path.exists()
354
+ ))
355
+
356
+ return paths
357
+
358
+
359
+ # Convenience functions
360
+ def discover_templates(
361
+ custom_dirs: Optional[List[str]] = None
362
+ ) -> Dict[str, DiscoveredTemplate]:
363
+ """
364
+ Discover all templates from default and custom directories.
365
+
366
+ Args:
367
+ custom_dirs: Additional custom directories to search
368
+
369
+ Returns:
370
+ Dict mapping template name to DiscoveredTemplate
371
+ """
372
+ discovery = TemplateDiscovery(custom_dirs=custom_dirs)
373
+ return discovery.discover_all()
374
+
375
+
376
+ def find_template_path(
377
+ name: str,
378
+ custom_dirs: Optional[List[str]] = None
379
+ ) -> Optional[Path]:
380
+ """
381
+ Find a template by name and return its path.
382
+
383
+ Args:
384
+ name: Template name
385
+ custom_dirs: Additional custom directories to search
386
+
387
+ Returns:
388
+ Path to template directory, or None if not found
389
+ """
390
+ discovery = TemplateDiscovery(custom_dirs=custom_dirs)
391
+ return discovery.resolve_template_path(name)