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,231 @@
1
+ """
2
+ External Agents Handler for CLI.
3
+
4
+ Provides integration with external AI coding CLI tools:
5
+ - Claude Code CLI
6
+ - Gemini CLI
7
+ - OpenAI Codex CLI
8
+ - Cursor CLI
9
+
10
+ Usage:
11
+ praisonai "Refactor the code" --external-agent claude
12
+ praisonai "Analyze codebase" --external-agent gemini
13
+ praisonai "Fix bugs" --external-agent codex
14
+ praisonai "Add tests" --external-agent cursor
15
+ """
16
+
17
+ from typing import Dict, Any, List, Optional, Tuple
18
+
19
+ from .base import FlagHandler
20
+
21
+
22
+ class ExternalAgentsHandler(FlagHandler):
23
+ """
24
+ Handler for --external-agent flag.
25
+
26
+ Integrates external AI coding CLI tools as agent tools.
27
+
28
+ Example:
29
+ praisonai "Refactor the auth module" --external-agent claude
30
+ praisonai "Analyze this codebase" --external-agent gemini
31
+ """
32
+
33
+ # Mapping of integration names to their module paths
34
+ INTEGRATIONS = {
35
+ "claude": "claude_code.ClaudeCodeIntegration",
36
+ "gemini": "gemini_cli.GeminiCLIIntegration",
37
+ "codex": "codex_cli.CodexCLIIntegration",
38
+ "cursor": "cursor_cli.CursorCLIIntegration",
39
+ }
40
+
41
+ def __init__(self, verbose: bool = False):
42
+ """Initialize the handler."""
43
+ super().__init__(verbose)
44
+ self._integration_cache: Dict[str, Any] = {}
45
+
46
+ @property
47
+ def feature_name(self) -> str:
48
+ """Return the feature name."""
49
+ return "external_agents"
50
+
51
+ @property
52
+ def flag_name(self) -> str:
53
+ """Return the flag name."""
54
+ return "external-agent"
55
+
56
+ @property
57
+ def flag_help(self) -> str:
58
+ """Return the flag help text."""
59
+ return "External AI CLI tool to use (claude, gemini, codex, cursor)"
60
+
61
+ def check_dependencies(self) -> Tuple[bool, str]:
62
+ """Check if integrations module is available."""
63
+ try:
64
+ import importlib.util
65
+ if importlib.util.find_spec("praisonai.integrations") is not None:
66
+ return True, ""
67
+ return False, "Integrations module not available"
68
+ except ImportError:
69
+ return False, "Integrations module not available"
70
+
71
+ def get_integration(self, name: str, **options):
72
+ """
73
+ Get an integration instance by name.
74
+
75
+ Args:
76
+ name: Integration name (claude, gemini, codex, cursor)
77
+ **options: Options to pass to the integration
78
+
79
+ Returns:
80
+ Integration instance
81
+
82
+ Raises:
83
+ ValueError: If integration name is invalid
84
+ """
85
+ if name not in self.INTEGRATIONS:
86
+ raise ValueError(f"Unknown integration: {name}. Available: {list(self.INTEGRATIONS.keys())}")
87
+
88
+ # Check cache first
89
+ cache_key = f"{name}_{hash(frozenset(options.items()))}"
90
+ if cache_key in self._integration_cache:
91
+ return self._integration_cache[cache_key]
92
+
93
+ # Lazy import the integration
94
+ module_path = self.INTEGRATIONS[name]
95
+ module_name, class_name = module_path.rsplit(".", 1)
96
+
97
+ import importlib
98
+ module = importlib.import_module(f"praisonai.integrations.{module_name}")
99
+ integration_class = getattr(module, class_name)
100
+
101
+ # Create instance with options
102
+ integration = integration_class(**options)
103
+
104
+ # Cache it
105
+ self._integration_cache[cache_key] = integration
106
+
107
+ return integration
108
+
109
+ def list_integrations(self) -> List[str]:
110
+ """
111
+ List all available integration names.
112
+
113
+ Returns:
114
+ List of integration names
115
+ """
116
+ return list(self.INTEGRATIONS.keys())
117
+
118
+ def check_availability(self) -> Dict[str, bool]:
119
+ """
120
+ Check availability of all integrations.
121
+
122
+ Returns:
123
+ Dict mapping integration name to availability
124
+ """
125
+ availability = {}
126
+ for name in self.INTEGRATIONS:
127
+ try:
128
+ integration = self.get_integration(name)
129
+ availability[name] = integration.is_available
130
+ except Exception:
131
+ availability[name] = False
132
+ return availability
133
+
134
+ def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
135
+ """
136
+ Apply external agent configuration to agent config.
137
+
138
+ Args:
139
+ config: Agent configuration dictionary
140
+ flag_value: Integration name or dict with name and options
141
+
142
+ Returns:
143
+ Modified configuration with external agent tool
144
+ """
145
+ if not flag_value:
146
+ return config
147
+
148
+ # Handle both string and dict formats
149
+ if isinstance(flag_value, str):
150
+ name = flag_value
151
+ options = {}
152
+ elif isinstance(flag_value, dict):
153
+ name = flag_value.get("name", "")
154
+ options = {k: v for k, v in flag_value.items() if k != "name"}
155
+ else:
156
+ return config
157
+
158
+ try:
159
+ integration = self.get_integration(name, **options)
160
+
161
+ if integration.is_available:
162
+ # Add tool to existing tools
163
+ existing_tools = config.get("tools", [])
164
+ if isinstance(existing_tools, list):
165
+ existing_tools.append(integration.as_tool())
166
+ else:
167
+ existing_tools = [integration.as_tool()]
168
+ config["tools"] = existing_tools
169
+
170
+ self.print_status(f"🔌 External agent connected: {name}", "success")
171
+ else:
172
+ self.print_status(f"⚠️ External agent not available: {name}", "warning")
173
+
174
+ except ValueError as e:
175
+ self.print_status(str(e), "error")
176
+ except Exception as e:
177
+ self.print_status(f"Failed to connect external agent: {e}", "error")
178
+
179
+ return config
180
+
181
+ def execute(
182
+ self,
183
+ integration_name: str = None,
184
+ **options
185
+ ) -> Optional[Any]:
186
+ """
187
+ Execute external agent setup.
188
+
189
+ Args:
190
+ integration_name: Name of the integration
191
+ **options: Options for the integration
192
+
193
+ Returns:
194
+ Integration instance or None
195
+ """
196
+ if not integration_name:
197
+ # List available integrations
198
+ self.print_status("\n🔧 Available External Agents:", "info")
199
+ self.print_status("-" * 40, "info")
200
+
201
+ availability = self.check_availability()
202
+ for name, available in availability.items():
203
+ status = "✅ Available" if available else "❌ Not installed"
204
+ self.print_status(f" {name}: {status}", "info")
205
+
206
+ return None
207
+
208
+ try:
209
+ integration = self.get_integration(integration_name, **options)
210
+
211
+ if integration.is_available:
212
+ self.print_status(f"🔌 External agent ready: {integration_name}", "success")
213
+ return integration
214
+ else:
215
+ self.print_status(f"⚠️ External agent not installed: {integration_name}", "warning")
216
+ self.print_status(f" Install with: {self._get_install_instructions(integration_name)}", "info")
217
+ return None
218
+
219
+ except ValueError as e:
220
+ self.print_status(str(e), "error")
221
+ return None
222
+
223
+ def _get_install_instructions(self, name: str) -> str:
224
+ """Get installation instructions for an integration."""
225
+ instructions = {
226
+ "claude": "npm install -g @anthropic-ai/claude-code",
227
+ "gemini": "npm install -g @anthropic-ai/gemini-cli",
228
+ "codex": "npm install -g @openai/codex",
229
+ "cursor": "Download from cursor.com",
230
+ }
231
+ return instructions.get(name, "See documentation")
@@ -0,0 +1,410 @@
1
+ """
2
+ Fast Context Handler for CLI.
3
+
4
+ Provides codebase search capability using FastContext.
5
+ Usage: praisonai "Find authentication code" --fast-context ./src
6
+
7
+ Fast Context Strategy:
8
+ 1. Extract search keywords from natural language query using LLM
9
+ 2. Provide folder tree context to help understand codebase structure
10
+ 3. Use both file name search AND content search (ripgrep-style)
11
+ 4. Return relevant code snippets for context
12
+ """
13
+
14
+ import os
15
+ import logging
16
+ from typing import Any, Dict, Tuple, List
17
+ from .base import FlagHandler
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class FastContextHandler(FlagHandler):
23
+ """
24
+ Handler for --fast-context flag.
25
+
26
+ Searches codebase for relevant context before agent execution.
27
+
28
+ Example:
29
+ praisonai "Find authentication code" --fast-context ./src
30
+ praisonai "Explain the database schema" --fast-context /path/to/project
31
+ """
32
+
33
+ @property
34
+ def feature_name(self) -> str:
35
+ return "fast_context"
36
+
37
+ @property
38
+ def flag_name(self) -> str:
39
+ return "fast-context"
40
+
41
+ @property
42
+ def flag_help(self) -> str:
43
+ return "Path to search for relevant code context"
44
+
45
+ def check_dependencies(self) -> Tuple[bool, str]:
46
+ """Check if FastContext is available."""
47
+ try:
48
+ import importlib.util
49
+ if importlib.util.find_spec("praisonaiagents") is not None:
50
+ return True, ""
51
+ return False, "praisonaiagents not installed"
52
+ except ImportError:
53
+ return False, "praisonaiagents not installed. Install with: pip install praisonaiagents"
54
+
55
+ def validate_search_path(self, path: str) -> Tuple[bool, str]:
56
+ """
57
+ Validate that the search path exists.
58
+
59
+ Args:
60
+ path: Path to search directory
61
+
62
+ Returns:
63
+ Tuple of (is_valid, error_message)
64
+ """
65
+ if not path:
66
+ return False, "No search path provided"
67
+
68
+ if not os.path.isdir(path):
69
+ return False, f"Directory not found: {path}"
70
+
71
+ return True, ""
72
+
73
+ def _count_files_quick(self, path: str, max_count: int = 1000) -> int:
74
+ """Quickly count files in directory, stopping at max_count."""
75
+ count = 0
76
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build'}
77
+
78
+ for root, dirs, files in os.walk(path):
79
+ dirs[:] = [d for d in dirs if d not in ignore_dirs and not d.startswith('.')]
80
+ count += len(files)
81
+ if count >= max_count:
82
+ return count
83
+ return count
84
+
85
+ def _get_folder_tree(self, path: str, max_depth: int = 2) -> str:
86
+ """
87
+ Get a compact folder tree structure for context.
88
+
89
+ Optimized for large directories:
90
+ - Limits depth to max_depth (default 2)
91
+ - Shows max 10 files per directory
92
+ - Truncates total output to 50 lines
93
+ - Skips common non-code directories
94
+ """
95
+ # Quick check for very large directories
96
+ file_count = self._count_files_quick(path, 500)
97
+ if file_count >= 500:
98
+ # For large directories, reduce depth and show summary
99
+ max_depth = 1
100
+ logger.debug(f"Large directory detected ({file_count}+ files), reducing tree depth")
101
+
102
+ tree_lines = []
103
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.cache', 'dist', 'build', 'coverage', '.pytest_cache', '.mypy_cache', 'eggs', '*.egg-info'}
104
+ ignore_exts = {'.pyc', '.pyo', '.so', '.o', '.a', '.dylib', '.class', '.jar'}
105
+ total_files = 0
106
+ max_total_files = 100 # Stop after seeing this many files
107
+
108
+ def walk_tree(current_path, prefix="", depth=0):
109
+ nonlocal total_files
110
+ if depth > max_depth or total_files >= max_total_files:
111
+ return
112
+
113
+ try:
114
+ entries = sorted(os.listdir(current_path))
115
+ except PermissionError:
116
+ return
117
+
118
+ dirs = []
119
+ files = []
120
+ for entry in entries:
121
+ full_path = os.path.join(current_path, entry)
122
+ if os.path.isdir(full_path):
123
+ if entry not in ignore_dirs and not entry.startswith('.'):
124
+ dirs.append(entry)
125
+ else:
126
+ ext = os.path.splitext(entry)[1]
127
+ if ext not in ignore_exts and not entry.startswith('.'):
128
+ files.append(entry)
129
+
130
+ # Limit directories shown at each level
131
+ dirs_to_show = dirs[:15]
132
+ for i, d in enumerate(dirs_to_show):
133
+ is_last = (i == len(dirs_to_show) - 1) and not files
134
+ tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{d}/")
135
+ walk_tree(os.path.join(current_path, d),
136
+ prefix + (' ' if is_last else '│ '), depth + 1)
137
+ if len(dirs) > 15:
138
+ tree_lines.append(f"{prefix} ... and {len(dirs) - 15} more directories")
139
+
140
+ # Limit files shown
141
+ files_to_show = files[:10]
142
+ total_files += len(files)
143
+ for i, f in enumerate(files_to_show):
144
+ is_last = i == len(files_to_show) - 1
145
+ tree_lines.append(f"{prefix}{'└── ' if is_last else '├── '}{f}")
146
+ if len(files) > 10:
147
+ tree_lines.append(f"{prefix} ... and {len(files) - 10} more files")
148
+
149
+ tree_lines.append(os.path.basename(path) + "/")
150
+ walk_tree(path)
151
+ return "\n".join(tree_lines[:50]) # Limit total lines
152
+
153
+ def _extract_keywords(self, query: str, folder_tree: str) -> List[str]:
154
+ """Extract search keywords from natural language query using LLM."""
155
+ try:
156
+ from praisonaiagents import Agent
157
+
158
+ extractor = Agent(
159
+ name="KeywordExtractor",
160
+ role="Search Query Analyzer",
161
+ goal="Extract file search keywords from natural language",
162
+ backstory="Expert at understanding code search queries",
163
+ llm="gpt-4o-mini",
164
+ verbose=False
165
+ )
166
+
167
+ prompt = f"""Given this folder structure:
168
+ {folder_tree}
169
+
170
+ And this search query: "{query}"
171
+
172
+ Extract 1-5 specific keywords/patterns to search for in file names and content.
173
+ Return ONLY the keywords, one per line, no explanations.
174
+ Focus on: file names, function names, class names, variable names that might match.
175
+ If the query mentions a folder name that exists, include files from that folder."""
176
+
177
+ response = extractor.chat(prompt, stream=False)
178
+ keywords = [k.strip() for k in str(response).strip().split('\n') if k.strip()]
179
+ logger.debug(f"Extracted keywords: {keywords}")
180
+ return keywords[:5] # Limit to 5 keywords
181
+ except Exception as e:
182
+ logger.debug(f"Keyword extraction failed: {e}")
183
+ # Fallback: extract words from query
184
+ return [w for w in query.split() if len(w) > 2][:3]
185
+
186
+ def _search_files_by_name(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
187
+ """Search for files matching keywords in their names."""
188
+ matches = []
189
+ path = os.path.abspath(path)
190
+
191
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
192
+
193
+ for root, dirs, files in os.walk(path):
194
+ dirs[:] = [d for d in dirs if d not in ignore_dirs]
195
+
196
+ for filename in files:
197
+ filepath = os.path.join(root, filename)
198
+ rel_path = os.path.relpath(filepath, path)
199
+
200
+ # Check if any keyword matches filename or path
201
+ for kw in keywords:
202
+ kw_lower = kw.lower()
203
+ if kw_lower in filename.lower() or kw_lower in rel_path.lower():
204
+ matches.append({
205
+ 'file': rel_path,
206
+ 'lines': [(1, 50)], # First 50 lines
207
+ 'relevance': 1.0,
208
+ 'match_type': 'filename'
209
+ })
210
+ break
211
+
212
+ return matches[:20] # Limit results
213
+
214
+ def _search_files_by_content(self, path: str, keywords: List[str]) -> List[Dict[str, Any]]:
215
+ """Search for files containing keywords in content."""
216
+ matches = []
217
+ path = os.path.abspath(path)
218
+
219
+ ignore_dirs = {'.git', '__pycache__', 'node_modules', '.venv', 'venv'}
220
+ code_exts = {'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rs', '.rb', '.sh', '.yaml', '.yml', '.json', '.md', '.txt'}
221
+
222
+ for root, dirs, files in os.walk(path):
223
+ dirs[:] = [d for d in dirs if d not in ignore_dirs]
224
+
225
+ for filename in files:
226
+ ext = os.path.splitext(filename)[1]
227
+ if ext not in code_exts:
228
+ continue
229
+
230
+ filepath = os.path.join(root, filename)
231
+ rel_path = os.path.relpath(filepath, path)
232
+
233
+ try:
234
+ with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
235
+ content = f.read(50000) # Read first 50KB
236
+
237
+ content_lower = content.lower()
238
+ for kw in keywords:
239
+ if kw.lower() in content_lower:
240
+ # Find line numbers with matches
241
+ lines = content.split('\n')
242
+ match_lines = []
243
+ for i, line in enumerate(lines[:200], 1): # Check first 200 lines
244
+ if kw.lower() in line.lower():
245
+ match_lines.append((max(1, i-2), min(len(lines), i+2)))
246
+
247
+ if match_lines:
248
+ matches.append({
249
+ 'file': rel_path,
250
+ 'lines': match_lines[:5], # Limit line ranges
251
+ 'relevance': 0.8,
252
+ 'match_type': 'content'
253
+ })
254
+ break
255
+ except Exception:
256
+ continue
257
+
258
+ return matches[:15] # Limit results
259
+
260
+ def search_context(self, query: str, path: str, **kwargs) -> List[Dict[str, Any]]:
261
+ """
262
+ Search for relevant context in the codebase.
263
+
264
+ Uses a smart multi-step approach:
265
+ 1. Get folder tree structure
266
+ 2. Use LLM to extract search keywords from query
267
+ 3. Search by filename AND content
268
+ 4. Return combined results
269
+
270
+ Args:
271
+ query: Natural language search query
272
+ path: Path to search
273
+ **kwargs: Additional search options
274
+ - use_llm_keywords: Use LLM to extract keywords (default: True)
275
+
276
+ Returns:
277
+ List of matching results
278
+ """
279
+ available, msg = self.check_dependencies()
280
+ if not available:
281
+ self.print_status(msg, "error")
282
+ return []
283
+
284
+ valid, msg = self.validate_search_path(path)
285
+ if not valid:
286
+ self.print_status(msg, "error")
287
+ return []
288
+
289
+ try:
290
+ use_llm_keywords = kwargs.get('use_llm_keywords', True)
291
+
292
+ # Step 1: Get folder tree for context
293
+ logger.debug(f"Getting folder tree for: {path}")
294
+ folder_tree = self._get_folder_tree(path)
295
+ logger.debug(f"Folder tree:\n{folder_tree}")
296
+
297
+ # Step 2: Extract keywords
298
+ if use_llm_keywords:
299
+ logger.debug(f"Extracting keywords from query: {query}")
300
+ keywords = self._extract_keywords(query, folder_tree)
301
+ else:
302
+ keywords = [w for w in query.split() if len(w) > 2][:3]
303
+
304
+ logger.debug(f"Search keywords: {keywords}")
305
+
306
+ if not keywords:
307
+ self.print_status("Could not extract search keywords", "warning")
308
+ return []
309
+
310
+ # Step 3: Search by filename
311
+ logger.debug("Searching by filename...")
312
+ filename_matches = self._search_files_by_name(path, keywords)
313
+ logger.debug(f"Found {len(filename_matches)} filename matches")
314
+
315
+ # Step 4: Search by content
316
+ logger.debug("Searching by content...")
317
+ content_matches = self._search_files_by_content(path, keywords)
318
+ logger.debug(f"Found {len(content_matches)} content matches")
319
+
320
+ # Combine and deduplicate
321
+ seen_files = set()
322
+ matches = []
323
+
324
+ for m in filename_matches + content_matches:
325
+ if m['file'] not in seen_files:
326
+ seen_files.add(m['file'])
327
+ matches.append(m)
328
+
329
+ self.print_status(f"🔍 Found {len(matches)} relevant files", "success")
330
+ return matches
331
+
332
+ except Exception as e:
333
+ logger.error(f"FastContext search error: {e}")
334
+ self.log(f"FastContext search error: {e}", "error")
335
+ return []
336
+
337
+ def format_context_for_prompt(self, matches: List[Dict[str, Any]], max_chars: int = 10000) -> str:
338
+ """
339
+ Format search results as context for the prompt.
340
+
341
+ Args:
342
+ matches: List of search results
343
+ max_chars: Maximum characters to include
344
+
345
+ Returns:
346
+ Formatted context string
347
+ """
348
+ if not matches:
349
+ return ""
350
+
351
+ context_parts = ["## Relevant Code Context\n"]
352
+ total_chars = 0
353
+
354
+ for match in matches:
355
+ file_path = match.get('file', '')
356
+ lines = match.get('lines', [])
357
+
358
+ if not file_path or not os.path.exists(file_path):
359
+ continue
360
+
361
+ try:
362
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
363
+ content = f.readlines()
364
+
365
+ for start, end in lines:
366
+ snippet = ''.join(content[max(0, start-1):end])
367
+
368
+ if total_chars + len(snippet) > max_chars:
369
+ break
370
+
371
+ context_parts.append(f"\n### {file_path} (lines {start}-{end})\n```\n{snippet}```\n")
372
+ total_chars += len(snippet)
373
+
374
+ except Exception:
375
+ continue
376
+
377
+ return '\n'.join(context_parts)
378
+
379
+ def apply_to_agent_config(self, config: Dict[str, Any], flag_value: Any) -> Dict[str, Any]:
380
+ """
381
+ Apply fast context configuration.
382
+
383
+ Args:
384
+ config: Agent configuration dictionary
385
+ flag_value: Search path
386
+
387
+ Returns:
388
+ Modified configuration
389
+ """
390
+ if flag_value:
391
+ config['fast_context'] = True
392
+ config['fast_context_path'] = flag_value
393
+ return config
394
+
395
+ def execute(self, query: str = None, path: str = None, **kwargs) -> str:
396
+ """
397
+ Execute fast context search and return formatted context.
398
+
399
+ Args:
400
+ query: Search query
401
+ path: Search path
402
+
403
+ Returns:
404
+ Formatted context string
405
+ """
406
+ if not query or not path:
407
+ return ""
408
+
409
+ matches = self.search_context(query, path, **kwargs)
410
+ return self.format_context_for_prompt(matches)