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,310 @@
1
+ """
2
+ Output formatters for the Doctor CLI module.
3
+
4
+ Provides text and JSON formatting with secret redaction.
5
+ """
6
+
7
+ import json
8
+ import re
9
+ import sys
10
+ from typing import Any, Dict, List, Optional, TextIO
11
+ from .models import CheckResult, CheckStatus, DoctorReport, ReportSummary
12
+
13
+
14
+ # Patterns for detecting secrets
15
+ SECRET_PATTERNS = [
16
+ re.compile(r'(sk-[a-zA-Z0-9]{20,})', re.IGNORECASE), # OpenAI
17
+ re.compile(r'(sk-ant-[a-zA-Z0-9-]{20,})', re.IGNORECASE), # Anthropic
18
+ re.compile(r'(AIza[a-zA-Z0-9_-]{35})', re.IGNORECASE), # Google
19
+ re.compile(r'(tvly-[a-zA-Z0-9]{20,})', re.IGNORECASE), # Tavily
20
+ re.compile(r'(xai-[a-zA-Z0-9]{20,})', re.IGNORECASE), # xAI
21
+ re.compile(r'([a-zA-Z0-9_-]*api[_-]?key[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
22
+ re.compile(r'([a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
23
+ re.compile(r'([a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*=\s*["\']?[a-zA-Z0-9_-]{16,})', re.IGNORECASE),
24
+ re.compile(r'(password\s*=\s*["\']?[^\s"\']{8,})', re.IGNORECASE),
25
+ ]
26
+
27
+ # Known API key environment variable names
28
+ API_KEY_ENV_VARS = [
29
+ "OPENAI_API_KEY",
30
+ "ANTHROPIC_API_KEY",
31
+ "GOOGLE_API_KEY",
32
+ "GEMINI_API_KEY",
33
+ "TAVILY_API_KEY",
34
+ "EXA_API_KEY",
35
+ "COHERE_API_KEY",
36
+ "HUGGINGFACE_API_KEY",
37
+ "HF_TOKEN",
38
+ "LANGFUSE_PUBLIC_KEY",
39
+ "LANGFUSE_SECRET_KEY",
40
+ "LANGSMITH_API_KEY",
41
+ "AGENTOPS_API_KEY",
42
+ "WANDB_API_KEY",
43
+ "DATADOG_API_KEY",
44
+ "BRAINTRUST_API_KEY",
45
+ "YDC_API_KEY",
46
+ ]
47
+
48
+
49
+ def redact_secrets(text: str, show_prefix_suffix: bool = False) -> str:
50
+ """
51
+ Redact secrets from text.
52
+
53
+ Args:
54
+ text: Text to redact
55
+ show_prefix_suffix: If True, show first 4 and last 4 chars
56
+
57
+ Returns:
58
+ Text with secrets redacted
59
+ """
60
+ if not text:
61
+ return text
62
+
63
+ result = text
64
+
65
+ for pattern in SECRET_PATTERNS:
66
+ def replace_match(match):
67
+ secret = match.group(1)
68
+ if show_prefix_suffix and len(secret) > 12:
69
+ return f"{secret[:4]}...{secret[-4:]}"
70
+ return "***REDACTED***"
71
+ result = pattern.sub(replace_match, result)
72
+
73
+ return result
74
+
75
+
76
+ def redact_dict(data: Dict[str, Any], show_prefix_suffix: bool = False) -> Dict[str, Any]:
77
+ """
78
+ Recursively redact secrets from a dictionary.
79
+
80
+ Args:
81
+ data: Dictionary to redact
82
+ show_prefix_suffix: If True, show first 4 and last 4 chars
83
+
84
+ Returns:
85
+ Dictionary with secrets redacted
86
+ """
87
+ result = {}
88
+ for key, value in data.items():
89
+ if isinstance(value, str):
90
+ # Check if key suggests it's a secret
91
+ key_lower = key.lower()
92
+ if any(s in key_lower for s in ["key", "token", "secret", "password", "credential"]):
93
+ if show_prefix_suffix and len(value) > 12:
94
+ result[key] = f"{value[:4]}...{value[-4:]}"
95
+ else:
96
+ result[key] = "***REDACTED***"
97
+ else:
98
+ result[key] = redact_secrets(value, show_prefix_suffix)
99
+ elif isinstance(value, dict):
100
+ result[key] = redact_dict(value, show_prefix_suffix)
101
+ elif isinstance(value, list):
102
+ result[key] = [
103
+ redact_dict(v, show_prefix_suffix) if isinstance(v, dict)
104
+ else redact_secrets(v, show_prefix_suffix) if isinstance(v, str)
105
+ else v
106
+ for v in value
107
+ ]
108
+ else:
109
+ result[key] = value
110
+ return result
111
+
112
+
113
+ class BaseFormatter:
114
+ """Base class for output formatters."""
115
+
116
+ def __init__(
117
+ self,
118
+ no_color: bool = False,
119
+ quiet: bool = False,
120
+ redact: bool = True,
121
+ show_prefix_suffix: bool = False,
122
+ ):
123
+ self.no_color = no_color or not sys.stdout.isatty()
124
+ self.quiet = quiet
125
+ self.redact = redact
126
+ self.show_prefix_suffix = show_prefix_suffix
127
+
128
+ def format_report(self, report: DoctorReport) -> str:
129
+ """Format a complete report."""
130
+ raise NotImplementedError
131
+
132
+ def format_result(self, result: CheckResult) -> str:
133
+ """Format a single check result."""
134
+ raise NotImplementedError
135
+
136
+ def write(self, report: DoctorReport, output: Optional[TextIO] = None) -> None:
137
+ """Write formatted report to output."""
138
+ output = output or sys.stdout
139
+ output.write(self.format_report(report))
140
+ if not self.format_report(report).endswith("\n"):
141
+ output.write("\n")
142
+
143
+
144
+ class TextFormatter(BaseFormatter):
145
+ """Text formatter with optional color support."""
146
+
147
+ # ANSI color codes
148
+ COLORS = {
149
+ "reset": "\033[0m",
150
+ "bold": "\033[1m",
151
+ "dim": "\033[2m",
152
+ "red": "\033[31m",
153
+ "green": "\033[32m",
154
+ "yellow": "\033[33m",
155
+ "blue": "\033[34m",
156
+ "cyan": "\033[36m",
157
+ "white": "\033[37m",
158
+ }
159
+
160
+ STATUS_COLORS = {
161
+ CheckStatus.PASS: "green",
162
+ CheckStatus.WARN: "yellow",
163
+ CheckStatus.FAIL: "red",
164
+ CheckStatus.SKIP: "dim",
165
+ CheckStatus.ERROR: "red",
166
+ }
167
+
168
+ STATUS_SYMBOLS = {
169
+ CheckStatus.PASS: "✓",
170
+ CheckStatus.WARN: "⚠",
171
+ CheckStatus.FAIL: "✗",
172
+ CheckStatus.SKIP: "○",
173
+ CheckStatus.ERROR: "✗",
174
+ }
175
+
176
+ def _color(self, text: str, color: str) -> str:
177
+ """Apply color to text if colors are enabled."""
178
+ if self.no_color:
179
+ return text
180
+ return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"
181
+
182
+ def _status_symbol(self, status: CheckStatus) -> str:
183
+ """Get colored status symbol."""
184
+ symbol = self.STATUS_SYMBOLS.get(status, "?")
185
+ color = self.STATUS_COLORS.get(status, "white")
186
+ return self._color(symbol, color)
187
+
188
+ def format_result(self, result: CheckResult) -> str:
189
+ """Format a single check result."""
190
+ symbol = self._status_symbol(result.status)
191
+ message = result.message
192
+ if self.redact:
193
+ message = redact_secrets(message, self.show_prefix_suffix)
194
+
195
+ line = f"{symbol} {result.title}: {message}"
196
+
197
+ if result.details and not self.quiet:
198
+ details = result.details
199
+ if self.redact:
200
+ details = redact_secrets(details, self.show_prefix_suffix)
201
+ line += f"\n {self._color(details, 'dim')}"
202
+
203
+ if result.remediation and result.status in (CheckStatus.FAIL, CheckStatus.ERROR):
204
+ line += f"\n {self._color('Fix:', 'yellow')} {result.remediation}"
205
+
206
+ return line
207
+
208
+ def format_summary(self, summary: ReportSummary, strict: bool = False) -> str:
209
+ """Format the summary section."""
210
+ parts = []
211
+
212
+ if summary.passed > 0:
213
+ parts.append(self._color(f"{summary.passed} passed", "green"))
214
+ if summary.warnings > 0:
215
+ color = "red" if strict else "yellow"
216
+ parts.append(self._color(f"{summary.warnings} warnings", color))
217
+ if summary.failed > 0:
218
+ parts.append(self._color(f"{summary.failed} failed", "red"))
219
+ if summary.skipped > 0:
220
+ parts.append(self._color(f"{summary.skipped} skipped", "dim"))
221
+ if summary.errors > 0:
222
+ parts.append(self._color(f"{summary.errors} errors", "red"))
223
+
224
+ return f"{summary.total} checks: " + ", ".join(parts)
225
+
226
+ def format_report(self, report: DoctorReport) -> str:
227
+ """Format a complete report."""
228
+ lines = []
229
+
230
+ # Header
231
+ if not self.quiet:
232
+ header = f"PraisonAI Doctor v{report.version}"
233
+ lines.append(self._color(header, "bold"))
234
+ lines.append("━" * 70)
235
+
236
+ # Results
237
+ for result in report.results:
238
+ lines.append(self.format_result(result))
239
+
240
+ # Summary
241
+ if not self.quiet:
242
+ lines.append("━" * 70)
243
+ lines.append(self.format_summary(report.summary))
244
+
245
+ # Duration
246
+ if not self.quiet and report.duration_ms > 0:
247
+ lines.append(self._color(f"Completed in {report.duration_ms:.0f}ms", "dim"))
248
+
249
+ return "\n".join(lines)
250
+
251
+
252
+ class JsonFormatter(BaseFormatter):
253
+ """JSON formatter with deterministic output."""
254
+
255
+ def format_result(self, result: CheckResult) -> str:
256
+ """Format a single check result as JSON."""
257
+ data = result.to_dict()
258
+ if self.redact:
259
+ data = redact_dict(data, self.show_prefix_suffix)
260
+ return json.dumps(data, indent=2, sort_keys=True)
261
+
262
+ def format_report(self, report: DoctorReport) -> str:
263
+ """Format a complete report as JSON."""
264
+ data = report.to_dict()
265
+ if self.redact:
266
+ data = redact_dict(data, self.show_prefix_suffix)
267
+
268
+ # Ensure deterministic ordering
269
+ return json.dumps(data, indent=2, sort_keys=True)
270
+
271
+ def write(self, report: DoctorReport, output: Optional[TextIO] = None) -> None:
272
+ """Write formatted report to output."""
273
+ output = output or sys.stdout
274
+ output.write(self.format_report(report))
275
+ output.write("\n")
276
+
277
+
278
+ def get_formatter(
279
+ format_type: str = "text",
280
+ no_color: bool = False,
281
+ quiet: bool = False,
282
+ redact: bool = True,
283
+ show_prefix_suffix: bool = False,
284
+ ) -> BaseFormatter:
285
+ """
286
+ Get a formatter instance.
287
+
288
+ Args:
289
+ format_type: "text" or "json"
290
+ no_color: Disable ANSI colors
291
+ quiet: Minimal output
292
+ redact: Redact secrets
293
+ show_prefix_suffix: Show partial secrets
294
+
295
+ Returns:
296
+ Formatter instance
297
+ """
298
+ if format_type == "json":
299
+ return JsonFormatter(
300
+ no_color=True, # JSON never has colors
301
+ quiet=quiet,
302
+ redact=redact,
303
+ show_prefix_suffix=show_prefix_suffix,
304
+ )
305
+ return TextFormatter(
306
+ no_color=no_color,
307
+ quiet=quiet,
308
+ redact=redact,
309
+ show_prefix_suffix=show_prefix_suffix,
310
+ )
@@ -0,0 +1,397 @@
1
+ """
2
+ Doctor CLI handler for PraisonAI.
3
+
4
+ Provides the main DoctorHandler class that integrates with the CLI.
5
+ """
6
+
7
+ import argparse
8
+ import sys
9
+ from typing import List, Optional
10
+
11
+ from ..base import CommandHandler
12
+ from .models import CheckCategory, DoctorConfig, DoctorReport
13
+ from .engine import DoctorEngine
14
+ from .registry import get_registry
15
+ from .formatters import get_formatter
16
+
17
+
18
+ class DoctorHandler(CommandHandler):
19
+ """
20
+ Handler for the 'praisonai doctor' command.
21
+
22
+ Provides comprehensive health checks and diagnostics.
23
+ """
24
+
25
+ @property
26
+ def feature_name(self) -> str:
27
+ return "doctor"
28
+
29
+ def get_actions(self) -> List[str]:
30
+ return [
31
+ "env",
32
+ "config",
33
+ "tools",
34
+ "db",
35
+ "mcp",
36
+ "obs",
37
+ "skills",
38
+ "memory",
39
+ "permissions",
40
+ "network",
41
+ "performance",
42
+ "ci",
43
+ "selftest",
44
+ ]
45
+
46
+ def _register_checks(self) -> None:
47
+ """Register all doctor checks."""
48
+ from .checks import register_all_checks
49
+ register_all_checks()
50
+
51
+ def _parse_args(self, args: List[str]) -> argparse.Namespace:
52
+ """Parse doctor command arguments."""
53
+ parser = argparse.ArgumentParser(
54
+ prog="praisonai doctor",
55
+ description="PraisonAI health checks and diagnostics",
56
+ )
57
+
58
+ # Subcommand
59
+ parser.add_argument(
60
+ "subcommand",
61
+ nargs="?",
62
+ choices=self.get_actions() + [None],
63
+ help="Subcommand to run (default: run all fast checks)",
64
+ )
65
+
66
+ # Global flags
67
+ parser.add_argument(
68
+ "--json",
69
+ action="store_true",
70
+ help="Output in JSON format",
71
+ )
72
+ parser.add_argument(
73
+ "--format",
74
+ choices=["text", "json"],
75
+ default="text",
76
+ help="Output format (default: text)",
77
+ )
78
+ parser.add_argument(
79
+ "--output", "-o",
80
+ type=str,
81
+ help="Write report to file",
82
+ )
83
+ parser.add_argument(
84
+ "--deep",
85
+ action="store_true",
86
+ help="Enable deeper probes (DB connects, network checks)",
87
+ )
88
+ parser.add_argument(
89
+ "--timeout",
90
+ type=float,
91
+ default=10.0,
92
+ help="Per-check timeout in seconds (default: 10)",
93
+ )
94
+ parser.add_argument(
95
+ "--strict",
96
+ action="store_true",
97
+ help="Treat warnings as failures",
98
+ )
99
+ parser.add_argument(
100
+ "--quiet", "-q",
101
+ action="store_true",
102
+ help="Minimal output",
103
+ )
104
+ parser.add_argument(
105
+ "--no-color",
106
+ action="store_true",
107
+ help="Disable ANSI colors",
108
+ )
109
+ parser.add_argument(
110
+ "--only",
111
+ type=str,
112
+ help="Only run these check IDs (comma-separated)",
113
+ )
114
+ parser.add_argument(
115
+ "--skip",
116
+ type=str,
117
+ help="Skip these check IDs (comma-separated)",
118
+ )
119
+ parser.add_argument(
120
+ "--list-checks",
121
+ action="store_true",
122
+ help="List available check IDs",
123
+ )
124
+ parser.add_argument(
125
+ "--version",
126
+ action="store_true",
127
+ help="Show doctor module version",
128
+ )
129
+
130
+ # Subcommand-specific flags
131
+ parser.add_argument(
132
+ "--show-keys",
133
+ action="store_true",
134
+ help="Show masked API key values (env subcommand)",
135
+ )
136
+ parser.add_argument(
137
+ "--require",
138
+ type=str,
139
+ help="Require these env vars (comma-separated)",
140
+ )
141
+ parser.add_argument(
142
+ "--file", "-f",
143
+ type=str,
144
+ help="Config file to validate (config subcommand)",
145
+ )
146
+ parser.add_argument(
147
+ "--schema",
148
+ action="store_true",
149
+ help="Print expected schema (config subcommand)",
150
+ )
151
+ parser.add_argument(
152
+ "--dsn",
153
+ type=str,
154
+ help="Database DSN (db subcommand)",
155
+ )
156
+ parser.add_argument(
157
+ "--provider",
158
+ type=str,
159
+ help="Provider name (db/obs subcommand)",
160
+ )
161
+ parser.add_argument(
162
+ "--read-only",
163
+ action="store_true",
164
+ default=True,
165
+ help="Read-only mode for DB checks (default: true)",
166
+ )
167
+ parser.add_argument(
168
+ "--name",
169
+ type=str,
170
+ help="Name filter (mcp/tools subcommand)",
171
+ )
172
+ parser.add_argument(
173
+ "--category",
174
+ type=str,
175
+ help="Category filter (tools subcommand)",
176
+ )
177
+ parser.add_argument(
178
+ "--all",
179
+ action="store_true",
180
+ dest="all_checks",
181
+ help="Show all items (tools subcommand)",
182
+ )
183
+ parser.add_argument(
184
+ "--missing-only",
185
+ action="store_true",
186
+ help="Show only missing items (tools subcommand)",
187
+ )
188
+ parser.add_argument(
189
+ "--list-tools",
190
+ action="store_true",
191
+ help="List MCP tools (mcp subcommand)",
192
+ )
193
+ parser.add_argument(
194
+ "--path",
195
+ type=str,
196
+ help="Path to check (skills subcommand)",
197
+ )
198
+ parser.add_argument(
199
+ "--all-installed",
200
+ action="store_true",
201
+ help="Check all installed skills",
202
+ )
203
+ parser.add_argument(
204
+ "--budget-ms",
205
+ type=int,
206
+ help="Import time budget in ms (performance subcommand)",
207
+ )
208
+ parser.add_argument(
209
+ "--top",
210
+ type=int,
211
+ default=10,
212
+ dest="top_n",
213
+ help="Number of top items to show (default: 10)",
214
+ )
215
+ parser.add_argument(
216
+ "--fail-fast",
217
+ action="store_true",
218
+ help="Stop on first failure (ci subcommand)",
219
+ )
220
+ parser.add_argument(
221
+ "--mock",
222
+ action="store_true",
223
+ default=True,
224
+ help="Use mock mode (selftest, default: true)",
225
+ )
226
+ parser.add_argument(
227
+ "--live",
228
+ action="store_true",
229
+ help="Use live API calls (selftest)",
230
+ )
231
+ parser.add_argument(
232
+ "--model",
233
+ type=str,
234
+ help="Model to use for selftest",
235
+ )
236
+ parser.add_argument(
237
+ "--save-report",
238
+ action="store_true",
239
+ help="Save selftest report",
240
+ )
241
+
242
+ return parser.parse_args(args)
243
+
244
+ def _build_config(self, args: argparse.Namespace) -> DoctorConfig:
245
+ """Build DoctorConfig from parsed arguments."""
246
+ return DoctorConfig(
247
+ deep=args.deep,
248
+ timeout=args.timeout if args.deep else min(args.timeout, 10.0),
249
+ strict=args.strict,
250
+ quiet=args.quiet,
251
+ no_color=args.no_color,
252
+ format="json" if args.json else args.format,
253
+ output_path=args.output,
254
+ only=args.only.split(",") if args.only else [],
255
+ skip=args.skip.split(",") if args.skip else [],
256
+ show_keys=args.show_keys,
257
+ require_keys=args.require.split(",") if args.require else [],
258
+ config_file=args.file,
259
+ dsn=args.dsn,
260
+ provider=args.provider,
261
+ read_only=args.read_only,
262
+ mock=args.mock and not args.live,
263
+ live=args.live,
264
+ model=args.model,
265
+ budget_ms=args.budget_ms,
266
+ top_n=args.top_n,
267
+ fail_fast=args.fail_fast,
268
+ list_tools=args.list_tools,
269
+ all_checks=args.all_checks,
270
+ missing_only=args.missing_only,
271
+ name=args.name,
272
+ category=args.category,
273
+ path=args.path,
274
+ save_report=args.save_report,
275
+ )
276
+
277
+ def _get_categories_for_subcommand(self, subcommand: Optional[str]) -> Optional[List[CheckCategory]]:
278
+ """Get check categories for a subcommand."""
279
+ category_map = {
280
+ "env": [CheckCategory.ENVIRONMENT],
281
+ "config": [CheckCategory.CONFIG],
282
+ "tools": [CheckCategory.TOOLS],
283
+ "db": [CheckCategory.DATABASE],
284
+ "mcp": [CheckCategory.MCP],
285
+ "obs": [CheckCategory.OBSERVABILITY],
286
+ "skills": [CheckCategory.SKILLS],
287
+ "memory": [CheckCategory.MEMORY],
288
+ "permissions": [CheckCategory.PERMISSIONS],
289
+ "network": [CheckCategory.NETWORK],
290
+ "performance": [CheckCategory.PERFORMANCE],
291
+ "selftest": [CheckCategory.SELFTEST],
292
+ "ci": None, # CI runs all checks
293
+ }
294
+ return category_map.get(subcommand)
295
+
296
+ def execute(self, action: str, action_args: List[str], **kwargs) -> int:
297
+ """
298
+ Execute the doctor command.
299
+
300
+ Args:
301
+ action: The subcommand or empty string
302
+ action_args: Additional arguments
303
+
304
+ Returns:
305
+ Exit code (0=pass, 1=fail, 2=error)
306
+ """
307
+ # Combine action and action_args for parsing
308
+ all_args = [action] + action_args if action and action != "help" else action_args
309
+
310
+ try:
311
+ args = self._parse_args(all_args)
312
+ except SystemExit:
313
+ return 0 # Help was shown
314
+
315
+ # Handle special flags
316
+ if args.version:
317
+ from . import __version__
318
+ print(f"PraisonAI Doctor v{__version__}")
319
+ return 0
320
+
321
+ # Register all checks
322
+ self._register_checks()
323
+
324
+ if args.list_checks:
325
+ registry = get_registry()
326
+ print(registry.list_checks_text())
327
+ return 0
328
+
329
+ # Build config
330
+ config = self._build_config(args)
331
+
332
+ # Handle CI mode
333
+ if args.subcommand == "ci":
334
+ config.format = "json"
335
+ config.no_color = True
336
+ config.quiet = True
337
+
338
+ # Get categories for subcommand
339
+ categories = self._get_categories_for_subcommand(args.subcommand)
340
+
341
+ # Run doctor
342
+ engine = DoctorEngine(config)
343
+ report = engine.run(categories=categories)
344
+
345
+ # Format output
346
+ formatter = get_formatter(
347
+ format_type=config.format,
348
+ no_color=config.no_color,
349
+ quiet=config.quiet,
350
+ show_prefix_suffix=config.show_keys,
351
+ )
352
+
353
+ # Write output
354
+ if config.output_path:
355
+ with open(config.output_path, "w") as f:
356
+ formatter.write(report, f)
357
+ if not config.quiet:
358
+ print(f"Report written to: {config.output_path}")
359
+ else:
360
+ formatter.write(report, sys.stdout)
361
+
362
+ return report.exit_code
363
+
364
+ def run(self, args: List[str]) -> int:
365
+ """
366
+ Run the doctor command from CLI.
367
+
368
+ Args:
369
+ args: Command line arguments after 'doctor'
370
+
371
+ Returns:
372
+ Exit code
373
+ """
374
+ if not args:
375
+ return self.execute("", [])
376
+
377
+ action = args[0] if args else ""
378
+ action_args = args[1:] if len(args) > 1 else []
379
+
380
+ return self.execute(action, action_args)
381
+
382
+
383
+ def run_doctor(args: Optional[List[str]] = None) -> int:
384
+ """
385
+ Convenience function to run doctor from CLI.
386
+
387
+ Args:
388
+ args: Command line arguments (default: sys.argv[1:])
389
+
390
+ Returns:
391
+ Exit code
392
+ """
393
+ if args is None:
394
+ args = sys.argv[1:]
395
+
396
+ handler = DoctorHandler()
397
+ return handler.run(args)