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,650 @@
1
+ """
2
+ Slash Commands System for PraisonAI CLI.
3
+
4
+ Inspired by Aider's command system and Gemini CLI's slash commands.
5
+ Provides interactive commands like /help, /cost, /plan, /model, /clear, etc.
6
+
7
+ Architecture:
8
+ - SlashCommand: Base class for all commands
9
+ - SlashCommandRegistry: Manages command registration and lookup
10
+ - SlashCommandParser: Parses user input for slash commands
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Callable, Dict, List, Optional
15
+ from enum import Enum
16
+ import logging
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class CommandKind(Enum):
22
+ """Type of slash command."""
23
+ BUILT_IN = "built_in"
24
+ CUSTOM = "custom"
25
+ MCP = "mcp"
26
+
27
+
28
+ @dataclass
29
+ class SlashCommand:
30
+ """
31
+ Represents a slash command.
32
+
33
+ Attributes:
34
+ name: Primary command name (e.g., "help", "cost")
35
+ description: Short description for help text
36
+ action: Callable that executes the command
37
+ alt_names: Alternative names/aliases
38
+ sub_commands: Nested sub-commands
39
+ kind: Type of command (built-in, custom, MCP)
40
+ auto_execute: Whether to execute without confirmation
41
+ """
42
+ name: str
43
+ description: str
44
+ action: Optional[Callable[["CommandContext", str], Any]] = None
45
+ alt_names: List[str] = field(default_factory=list)
46
+ sub_commands: List["SlashCommand"] = field(default_factory=list)
47
+ kind: CommandKind = CommandKind.BUILT_IN
48
+ auto_execute: bool = True
49
+
50
+ def __post_init__(self):
51
+ """Validate command after initialization."""
52
+ if not self.name:
53
+ raise ValueError("Command name cannot be empty")
54
+ if not self.name.isalnum() and "_" not in self.name and "-" not in self.name:
55
+ raise ValueError(f"Invalid command name: {self.name}")
56
+
57
+
58
+ @dataclass
59
+ class CommandContext:
60
+ """
61
+ Context passed to command actions.
62
+
63
+ Contains all services and state needed by commands.
64
+ """
65
+ # Core services
66
+ agent: Any = None # PraisonAI Agent instance
67
+ config: Dict[str, Any] = field(default_factory=dict)
68
+
69
+ # Session state
70
+ session_id: Optional[str] = None
71
+ session_start_time: Optional[float] = None
72
+
73
+ # Metrics
74
+ total_tokens: int = 0
75
+ total_cost: float = 0.0
76
+ prompt_count: int = 0
77
+
78
+ # UI callbacks
79
+ print_fn: Callable[[str], None] = print
80
+ input_fn: Callable[[str], str] = input
81
+
82
+ # Command invocation info
83
+ raw_input: str = ""
84
+ command_name: str = ""
85
+ args: str = ""
86
+
87
+
88
+ @dataclass
89
+ class ParsedSlashCommand:
90
+ """Result of parsing a slash command."""
91
+ command: Optional[SlashCommand] = None
92
+ args: str = ""
93
+ canonical_path: List[str] = field(default_factory=list)
94
+ error: Optional[str] = None
95
+
96
+
97
+ class SlashCommandRegistry:
98
+ """
99
+ Registry for slash commands.
100
+
101
+ Manages command registration, lookup, and help generation.
102
+ """
103
+
104
+ def __init__(self):
105
+ self._commands: Dict[str, SlashCommand] = {}
106
+ self._aliases: Dict[str, str] = {} # alias -> primary name
107
+
108
+ def register(self, command: SlashCommand) -> None:
109
+ """Register a slash command."""
110
+ if command.name in self._commands:
111
+ logger.warning(f"Overwriting existing command: {command.name}")
112
+
113
+ self._commands[command.name] = command
114
+
115
+ # Register aliases
116
+ for alias in command.alt_names:
117
+ if alias in self._aliases:
118
+ logger.warning(f"Alias '{alias}' already registered")
119
+ self._aliases[alias] = command.name
120
+
121
+ def unregister(self, name: str) -> bool:
122
+ """Unregister a command by name."""
123
+ if name in self._commands:
124
+ cmd = self._commands.pop(name)
125
+ # Remove aliases
126
+ for alias in cmd.alt_names:
127
+ self._aliases.pop(alias, None)
128
+ return True
129
+ return False
130
+
131
+ def get(self, name: str) -> Optional[SlashCommand]:
132
+ """Get a command by name or alias."""
133
+ # Check primary name first
134
+ if name in self._commands:
135
+ return self._commands[name]
136
+ # Check aliases
137
+ if name in self._aliases:
138
+ return self._commands.get(self._aliases[name])
139
+ return None
140
+
141
+ def get_all(self) -> List[SlashCommand]:
142
+ """Get all registered commands."""
143
+ return list(self._commands.values())
144
+
145
+ def get_names(self) -> List[str]:
146
+ """Get all command names (including aliases)."""
147
+ names = list(self._commands.keys())
148
+ names.extend(self._aliases.keys())
149
+ return sorted(set(names))
150
+
151
+
152
+ class SlashCommandParser:
153
+ """
154
+ Parser for slash commands.
155
+
156
+ Handles parsing of user input into command and arguments.
157
+ """
158
+
159
+ def __init__(self, registry: SlashCommandRegistry):
160
+ self.registry = registry
161
+
162
+ def is_slash_command(self, query: str) -> bool:
163
+ """Check if input is a slash command."""
164
+ query = query.strip()
165
+ if not query.startswith('/'):
166
+ return False
167
+ # Exclude code comments
168
+ if query.startswith('//') or query.startswith('/*'):
169
+ return False
170
+ return True
171
+
172
+ def parse(self, query: str) -> ParsedSlashCommand:
173
+ """
174
+ Parse a slash command string.
175
+
176
+ Args:
177
+ query: Raw input string (e.g., "/help model" or "/cost")
178
+
179
+ Returns:
180
+ ParsedSlashCommand with resolved command and arguments
181
+ """
182
+ if not self.is_slash_command(query):
183
+ return ParsedSlashCommand(error="Not a slash command")
184
+
185
+ trimmed = query.strip()
186
+ parts = trimmed[1:].strip().split() # Remove leading /
187
+
188
+ if not parts:
189
+ return ParsedSlashCommand(error="Empty command")
190
+
191
+ command_path = parts
192
+ canonical_path: List[str] = []
193
+ current_commands = self.registry.get_all()
194
+ command_to_execute: Optional[SlashCommand] = None
195
+ path_index = 0
196
+
197
+ for part in command_path:
198
+ # First pass: exact match on primary name
199
+ found_command = None
200
+ for cmd in current_commands:
201
+ if cmd.name == part:
202
+ found_command = cmd
203
+ break
204
+
205
+ # Second pass: check aliases
206
+ if not found_command:
207
+ for cmd in current_commands:
208
+ if part in cmd.alt_names:
209
+ found_command = cmd
210
+ break
211
+
212
+ if found_command:
213
+ command_to_execute = found_command
214
+ canonical_path.append(found_command.name)
215
+ path_index += 1
216
+ if found_command.sub_commands:
217
+ current_commands = found_command.sub_commands
218
+ else:
219
+ break
220
+ else:
221
+ break
222
+
223
+ args = ' '.join(parts[path_index:])
224
+
225
+ return ParsedSlashCommand(
226
+ command=command_to_execute,
227
+ args=args,
228
+ canonical_path=canonical_path
229
+ )
230
+
231
+
232
+ # ============================================================================
233
+ # Built-in Commands
234
+ # ============================================================================
235
+
236
+ def cmd_help(context: CommandContext, args: str) -> Dict[str, Any]:
237
+ """Show help for commands."""
238
+ from rich.console import Console
239
+ from rich.table import Table
240
+
241
+ console = Console()
242
+
243
+ if args:
244
+ # Help for specific command
245
+ registry = context.config.get("command_registry")
246
+ if registry:
247
+ cmd = registry.get(args)
248
+ if cmd:
249
+ console.print(f"\n[bold cyan]/{cmd.name}[/bold cyan]")
250
+ console.print(f" {cmd.description}")
251
+ if cmd.alt_names:
252
+ console.print(f" Aliases: {', '.join(cmd.alt_names)}")
253
+ if cmd.sub_commands:
254
+ console.print("\n Sub-commands:")
255
+ for sub in cmd.sub_commands:
256
+ console.print(f" /{cmd.name} {sub.name} - {sub.description}")
257
+ return {"type": "help", "command": cmd.name}
258
+ else:
259
+ console.print(f"[red]Unknown command: {args}[/red]")
260
+ return {"type": "error", "message": f"Unknown command: {args}"}
261
+
262
+ # General help
263
+ table = Table(title="Available Commands", show_header=True)
264
+ table.add_column("Command", style="cyan")
265
+ table.add_column("Description")
266
+
267
+ registry = context.config.get("command_registry")
268
+ if registry:
269
+ for cmd in registry.get_all():
270
+ aliases = f" ({', '.join(cmd.alt_names)})" if cmd.alt_names else ""
271
+ table.add_row(f"/{cmd.name}{aliases}", cmd.description)
272
+
273
+ console.print(table)
274
+ return {"type": "help"}
275
+
276
+
277
+ def cmd_cost(context: CommandContext, args: str) -> Dict[str, Any]:
278
+ """Show session cost and token usage."""
279
+ from rich.console import Console
280
+ from rich.panel import Panel
281
+
282
+ console = Console()
283
+
284
+ cost_info = f"""
285
+ [bold]Session Statistics[/bold]
286
+
287
+ Total Tokens: {context.total_tokens:,}
288
+ Total Cost: ${context.total_cost:.4f}
289
+ Prompts: {context.prompt_count}
290
+ """
291
+
292
+ if context.session_start_time:
293
+ import time
294
+ duration = time.time() - context.session_start_time
295
+ minutes = int(duration // 60)
296
+ seconds = int(duration % 60)
297
+ cost_info += f"Duration: {minutes}m {seconds}s\n"
298
+
299
+ console.print(Panel(cost_info, title="💰 Cost Tracker", border_style="green"))
300
+
301
+ return {
302
+ "type": "stats",
303
+ "tokens": context.total_tokens,
304
+ "cost": context.total_cost,
305
+ "prompts": context.prompt_count
306
+ }
307
+
308
+
309
+ def cmd_clear(context: CommandContext, args: str) -> Dict[str, Any]:
310
+ """Clear the conversation history."""
311
+ from rich.console import Console
312
+ console = Console()
313
+
314
+ # Reset agent conversation if available
315
+ if context.agent and hasattr(context.agent, 'clear_history'):
316
+ context.agent.clear_history()
317
+
318
+ console.print("[green]✓ Conversation cleared[/green]")
319
+ return {"type": "clear"}
320
+
321
+
322
+ def cmd_model(context: CommandContext, args: str) -> Dict[str, Any]:
323
+ """Show or change the current model."""
324
+ from rich.console import Console
325
+ console = Console()
326
+
327
+ if args:
328
+ # Change model
329
+ if context.agent and hasattr(context.agent, 'set_model'):
330
+ context.agent.set_model(args)
331
+ console.print(f"[green]✓ Model changed to: {args}[/green]")
332
+ return {"type": "model_change", "model": args}
333
+ else:
334
+ console.print(f"[yellow]Model change requested: {args}[/yellow]")
335
+ return {"type": "model_change", "model": args}
336
+ else:
337
+ # Show current model
338
+ current_model = "unknown"
339
+ if context.agent and hasattr(context.agent, 'llm'):
340
+ current_model = getattr(context.agent.llm, 'model', 'unknown')
341
+ console.print(f"[cyan]Current model: {current_model}[/cyan]")
342
+ return {"type": "model_info", "model": current_model}
343
+
344
+
345
+ def cmd_tokens(context: CommandContext, args: str) -> Dict[str, Any]:
346
+ """Show token usage breakdown."""
347
+ from rich.console import Console
348
+ from rich.table import Table
349
+
350
+ console = Console()
351
+
352
+ table = Table(title="Token Usage", show_header=True)
353
+ table.add_column("Metric", style="cyan")
354
+ table.add_column("Value", justify="right")
355
+
356
+ table.add_row("Total Tokens", f"{context.total_tokens:,}")
357
+ table.add_row("Prompts Sent", str(context.prompt_count))
358
+ if context.prompt_count > 0:
359
+ avg = context.total_tokens / context.prompt_count
360
+ table.add_row("Avg Tokens/Prompt", f"{avg:.0f}")
361
+
362
+ console.print(table)
363
+ return {"type": "tokens", "total": context.total_tokens}
364
+
365
+
366
+ def cmd_plan(context: CommandContext, args: str) -> Dict[str, Any]:
367
+ """Show or create an execution plan."""
368
+ from rich.console import Console
369
+ console = Console()
370
+
371
+ if args:
372
+ console.print(f"[cyan]Creating plan for: {args}[/cyan]")
373
+ # Trigger planning mode
374
+ return {"type": "submit_prompt", "content": f"Create a detailed plan for: {args}"}
375
+ else:
376
+ console.print("[yellow]Usage: /plan <task description>[/yellow]")
377
+ return {"type": "help", "command": "plan"}
378
+
379
+
380
+ def cmd_undo(context: CommandContext, args: str) -> Dict[str, Any]:
381
+ """Undo the last change."""
382
+ from rich.console import Console
383
+ console = Console()
384
+
385
+ # Check for git integration
386
+ try:
387
+ import subprocess
388
+ result = subprocess.run(
389
+ ["git", "diff", "--stat", "HEAD~1"],
390
+ capture_output=True,
391
+ text=True,
392
+ cwd="."
393
+ )
394
+ if result.returncode == 0:
395
+ console.print("[yellow]Last commit changes:[/yellow]")
396
+ console.print(result.stdout)
397
+ console.print("\n[cyan]Run 'git reset --soft HEAD~1' to undo[/cyan]")
398
+ else:
399
+ console.print("[red]No git history available[/red]")
400
+ except Exception as e:
401
+ console.print(f"[red]Error: {e}[/red]")
402
+
403
+ return {"type": "undo"}
404
+
405
+
406
+ def cmd_diff(context: CommandContext, args: str) -> Dict[str, Any]:
407
+ """Show git diff of changes."""
408
+ from rich.console import Console
409
+ from rich.syntax import Syntax
410
+
411
+ console = Console()
412
+
413
+ try:
414
+ import subprocess
415
+ result = subprocess.run(
416
+ ["git", "diff"],
417
+ capture_output=True,
418
+ text=True,
419
+ cwd="."
420
+ )
421
+ if result.stdout:
422
+ syntax = Syntax(result.stdout, "diff", theme="monokai")
423
+ console.print(syntax)
424
+ else:
425
+ console.print("[green]No uncommitted changes[/green]")
426
+ except Exception as e:
427
+ console.print(f"[red]Error: {e}[/red]")
428
+
429
+ return {"type": "diff"}
430
+
431
+
432
+ def cmd_commit(context: CommandContext, args: str) -> Dict[str, Any]:
433
+ """Commit changes with AI-generated message."""
434
+ from rich.console import Console
435
+ console = Console()
436
+
437
+ console.print("[cyan]Generating commit message...[/cyan]")
438
+
439
+ # This will trigger the commit command handler
440
+ return {"type": "commit", "message": args if args else None}
441
+
442
+
443
+ def cmd_exit(context: CommandContext, args: str) -> Dict[str, Any]:
444
+ """Exit the CLI."""
445
+ from rich.console import Console
446
+ console = Console()
447
+
448
+ console.print("[yellow]Goodbye![/yellow]")
449
+ return {"type": "exit"}
450
+
451
+
452
+ def cmd_settings(context: CommandContext, args: str) -> Dict[str, Any]:
453
+ """Show current settings."""
454
+ from rich.console import Console
455
+ from rich.table import Table
456
+
457
+ console = Console()
458
+
459
+ table = Table(title="Current Settings", show_header=True)
460
+ table.add_column("Setting", style="cyan")
461
+ table.add_column("Value")
462
+
463
+ for key, value in context.config.items():
464
+ if key != "command_registry": # Skip internal objects
465
+ table.add_row(key, str(value))
466
+
467
+ console.print(table)
468
+ return {"type": "settings"}
469
+
470
+
471
+ def cmd_map(context: CommandContext, args: str) -> Dict[str, Any]:
472
+ """Show repository map."""
473
+ from rich.console import Console
474
+ console = Console()
475
+
476
+ console.print("[cyan]Generating repository map...[/cyan]")
477
+ # This will be implemented with RepoMap feature
478
+ return {"type": "map"}
479
+
480
+
481
+ # ============================================================================
482
+ # Registry Setup
483
+ # ============================================================================
484
+
485
+ def create_default_registry() -> SlashCommandRegistry:
486
+ """Create registry with default built-in commands."""
487
+ registry = SlashCommandRegistry()
488
+
489
+ # Core commands
490
+ registry.register(SlashCommand(
491
+ name="help",
492
+ description="Show help for commands",
493
+ action=cmd_help,
494
+ alt_names=["h", "?"]
495
+ ))
496
+
497
+ registry.register(SlashCommand(
498
+ name="cost",
499
+ description="Show session cost and token usage",
500
+ action=cmd_cost,
501
+ alt_names=["usage", "stats"]
502
+ ))
503
+
504
+ registry.register(SlashCommand(
505
+ name="clear",
506
+ description="Clear conversation history",
507
+ action=cmd_clear,
508
+ alt_names=["reset"]
509
+ ))
510
+
511
+ registry.register(SlashCommand(
512
+ name="model",
513
+ description="Show or change the current model",
514
+ action=cmd_model,
515
+ alt_names=["m"]
516
+ ))
517
+
518
+ registry.register(SlashCommand(
519
+ name="tokens",
520
+ description="Show token usage breakdown",
521
+ action=cmd_tokens
522
+ ))
523
+
524
+ registry.register(SlashCommand(
525
+ name="plan",
526
+ description="Create an execution plan for a task",
527
+ action=cmd_plan
528
+ ))
529
+
530
+ registry.register(SlashCommand(
531
+ name="undo",
532
+ description="Undo the last change",
533
+ action=cmd_undo
534
+ ))
535
+
536
+ registry.register(SlashCommand(
537
+ name="diff",
538
+ description="Show git diff of changes",
539
+ action=cmd_diff
540
+ ))
541
+
542
+ registry.register(SlashCommand(
543
+ name="commit",
544
+ description="Commit changes with AI-generated message",
545
+ action=cmd_commit
546
+ ))
547
+
548
+ registry.register(SlashCommand(
549
+ name="exit",
550
+ description="Exit the CLI",
551
+ action=cmd_exit,
552
+ alt_names=["quit", "q"]
553
+ ))
554
+
555
+ registry.register(SlashCommand(
556
+ name="settings",
557
+ description="Show current settings",
558
+ action=cmd_settings
559
+ ))
560
+
561
+ registry.register(SlashCommand(
562
+ name="map",
563
+ description="Show repository map",
564
+ action=cmd_map,
565
+ alt_names=["repo"]
566
+ ))
567
+
568
+ return registry
569
+
570
+
571
+ # ============================================================================
572
+ # Handler for CLI Integration
573
+ # ============================================================================
574
+
575
+ class SlashCommandHandler:
576
+ """
577
+ Handler for integrating slash commands with PraisonAI CLI.
578
+ """
579
+
580
+ def __init__(self, verbose: bool = False):
581
+ self.verbose = verbose
582
+ self.registry = create_default_registry()
583
+ self.parser = SlashCommandParser(self.registry)
584
+ self._context: Optional[CommandContext] = None
585
+
586
+ def set_context(self, context: CommandContext) -> None:
587
+ """Set the command context."""
588
+ self._context = context
589
+ context.config["command_registry"] = self.registry
590
+
591
+ def is_command(self, query: str) -> bool:
592
+ """Check if input is a slash command."""
593
+ return self.parser.is_slash_command(query)
594
+
595
+ def execute(self, query: str) -> Optional[Dict[str, Any]]:
596
+ """
597
+ Execute a slash command.
598
+
599
+ Returns:
600
+ Command result dict or None if not a command
601
+ """
602
+ if not self.is_command(query):
603
+ return None
604
+
605
+ parsed = self.parser.parse(query)
606
+
607
+ if parsed.error:
608
+ if self.verbose:
609
+ print(f"[red]Error: {parsed.error}[/red]")
610
+ return {"type": "error", "message": parsed.error}
611
+
612
+ if not parsed.command:
613
+ return {"type": "error", "message": "Unknown command"}
614
+
615
+ if not parsed.command.action:
616
+ return {"type": "error", "message": f"Command '{parsed.command.name}' has no action"}
617
+
618
+ # Create context if not set
619
+ if not self._context:
620
+ self._context = CommandContext()
621
+ self._context.config["command_registry"] = self.registry
622
+
623
+ self._context.raw_input = query
624
+ self._context.command_name = parsed.command.name
625
+ self._context.args = parsed.args
626
+
627
+ try:
628
+ result = parsed.command.action(self._context, parsed.args)
629
+ return result
630
+ except Exception as e:
631
+ logger.error(f"Error executing command: {e}")
632
+ return {"type": "error", "message": str(e)}
633
+
634
+ def register_command(self, command: SlashCommand) -> None:
635
+ """Register a custom command."""
636
+ self.registry.register(command)
637
+
638
+ def get_completions(self, partial: str) -> List[str]:
639
+ """Get command completions for partial input."""
640
+ if not partial.startswith('/'):
641
+ return []
642
+
643
+ partial_cmd = partial[1:].lower()
644
+ completions = []
645
+
646
+ for name in self.registry.get_names():
647
+ if name.lower().startswith(partial_cmd):
648
+ completions.append(f"/{name}")
649
+
650
+ return sorted(completions)