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,651 @@
1
+ """
2
+ Git Integration System for PraisonAI CLI.
3
+
4
+ Inspired by Aider's Git integration for auto-commits and change tracking.
5
+ Provides seamless Git operations with AI-generated commit messages.
6
+
7
+ Architecture:
8
+ - GitManager: Core Git operations using subprocess
9
+ - CommitGenerator: AI-powered commit message generation
10
+ - DiffViewer: Rich diff display
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import List, Optional
15
+ from pathlib import Path
16
+ import subprocess
17
+ import logging
18
+ import re
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ # ============================================================================
24
+ # Data Classes
25
+ # ============================================================================
26
+
27
+ @dataclass
28
+ class GitStatus:
29
+ """Git repository status."""
30
+ branch: str = ""
31
+ is_clean: bool = True
32
+ staged_files: List[str] = field(default_factory=list)
33
+ modified_files: List[str] = field(default_factory=list)
34
+ untracked_files: List[str] = field(default_factory=list)
35
+ ahead: int = 0
36
+ behind: int = 0
37
+
38
+ @property
39
+ def has_changes(self) -> bool:
40
+ """Check if there are any changes."""
41
+ return bool(self.staged_files or self.modified_files or self.untracked_files)
42
+
43
+
44
+ @dataclass
45
+ class GitCommit:
46
+ """Git commit information."""
47
+ hash: str
48
+ short_hash: str
49
+ message: str
50
+ author: str
51
+ date: str
52
+ files_changed: int = 0
53
+ insertions: int = 0
54
+ deletions: int = 0
55
+
56
+
57
+ @dataclass
58
+ class GitDiff:
59
+ """Git diff information."""
60
+ file_path: str
61
+ additions: int = 0
62
+ deletions: int = 0
63
+ content: str = ""
64
+ is_new: bool = False
65
+ is_deleted: bool = False
66
+
67
+
68
+ # ============================================================================
69
+ # Git Manager
70
+ # ============================================================================
71
+
72
+ class GitManager:
73
+ """
74
+ Manages Git operations using subprocess.
75
+
76
+ Uses subprocess instead of GitPython for lighter dependencies.
77
+ """
78
+
79
+ def __init__(self, repo_path: Optional[str] = None, verbose: bool = False):
80
+ self.repo_path = Path(repo_path) if repo_path else Path.cwd()
81
+ self.verbose = verbose
82
+ self._git_available = self._check_git()
83
+
84
+ def _check_git(self) -> bool:
85
+ """Check if git is available."""
86
+ try:
87
+ result = subprocess.run(
88
+ ["git", "--version"],
89
+ capture_output=True,
90
+ text=True,
91
+ cwd=self.repo_path
92
+ )
93
+ return result.returncode == 0
94
+ except FileNotFoundError:
95
+ return False
96
+
97
+ def _run_git(self, *args: str, check: bool = True) -> subprocess.CompletedProcess:
98
+ """Run a git command."""
99
+ cmd = ["git"] + list(args)
100
+ if self.verbose:
101
+ logger.debug(f"Running: {' '.join(cmd)}")
102
+
103
+ result = subprocess.run(
104
+ cmd,
105
+ capture_output=True,
106
+ text=True,
107
+ cwd=self.repo_path
108
+ )
109
+
110
+ if check and result.returncode != 0:
111
+ logger.debug(f"Git error: {result.stderr}")
112
+
113
+ return result
114
+
115
+ @property
116
+ def is_repo(self) -> bool:
117
+ """Check if current directory is a git repository."""
118
+ if not self._git_available:
119
+ return False
120
+ result = self._run_git("rev-parse", "--git-dir", check=False)
121
+ return result.returncode == 0
122
+
123
+ def get_status(self) -> GitStatus:
124
+ """Get repository status."""
125
+ status = GitStatus()
126
+
127
+ if not self.is_repo:
128
+ return status
129
+
130
+ # Get branch
131
+ result = self._run_git("branch", "--show-current", check=False)
132
+ if result.returncode == 0:
133
+ status.branch = result.stdout.strip()
134
+
135
+ # Get status
136
+ result = self._run_git("status", "--porcelain", check=False)
137
+ if result.returncode == 0:
138
+ for line in result.stdout.strip().split("\n"):
139
+ if not line:
140
+ continue
141
+
142
+ status_code = line[:2]
143
+ file_path = line[3:]
144
+
145
+ if status_code[0] in "MADRCU":
146
+ status.staged_files.append(file_path)
147
+ if status_code[1] in "MD":
148
+ status.modified_files.append(file_path)
149
+ if status_code == "??":
150
+ status.untracked_files.append(file_path)
151
+
152
+ status.is_clean = not status.has_changes
153
+
154
+ # Get ahead/behind
155
+ result = self._run_git("rev-list", "--left-right", "--count", "HEAD...@{u}", check=False)
156
+ if result.returncode == 0:
157
+ parts = result.stdout.strip().split()
158
+ if len(parts) == 2:
159
+ status.ahead = int(parts[0])
160
+ status.behind = int(parts[1])
161
+
162
+ return status
163
+
164
+ def get_diff(self, staged: bool = False, file_path: Optional[str] = None) -> List[GitDiff]:
165
+ """Get diff information."""
166
+ diffs = []
167
+
168
+ if not self.is_repo:
169
+ return diffs
170
+
171
+ args = ["diff"]
172
+ if staged:
173
+ args.append("--staged")
174
+ args.append("--numstat")
175
+
176
+ if file_path:
177
+ args.append("--")
178
+ args.append(file_path)
179
+
180
+ result = self._run_git(*args, check=False)
181
+ if result.returncode != 0:
182
+ return diffs
183
+
184
+ for line in result.stdout.strip().split("\n"):
185
+ if not line:
186
+ continue
187
+
188
+ parts = line.split("\t")
189
+ if len(parts) >= 3:
190
+ additions = int(parts[0]) if parts[0] != "-" else 0
191
+ deletions = int(parts[1]) if parts[1] != "-" else 0
192
+ path = parts[2]
193
+
194
+ diffs.append(GitDiff(
195
+ file_path=path,
196
+ additions=additions,
197
+ deletions=deletions
198
+ ))
199
+
200
+ return diffs
201
+
202
+ def get_diff_content(self, staged: bool = False, file_path: Optional[str] = None) -> str:
203
+ """Get full diff content."""
204
+ if not self.is_repo:
205
+ return ""
206
+
207
+ args = ["diff"]
208
+ if staged:
209
+ args.append("--staged")
210
+
211
+ if file_path:
212
+ args.append("--")
213
+ args.append(file_path)
214
+
215
+ result = self._run_git(*args, check=False)
216
+ return result.stdout if result.returncode == 0 else ""
217
+
218
+ def stage_files(self, files: Optional[List[str]] = None) -> bool:
219
+ """Stage files for commit."""
220
+ if not self.is_repo:
221
+ return False
222
+
223
+ if files:
224
+ result = self._run_git("add", *files, check=False)
225
+ else:
226
+ result = self._run_git("add", "-A", check=False)
227
+
228
+ return result.returncode == 0
229
+
230
+ def commit(self, message: str, allow_empty: bool = False) -> Optional[GitCommit]:
231
+ """Create a commit."""
232
+ if not self.is_repo:
233
+ return None
234
+
235
+ args = ["commit", "-m", message]
236
+ if allow_empty:
237
+ args.append("--allow-empty")
238
+
239
+ result = self._run_git(*args, check=False)
240
+
241
+ if result.returncode != 0:
242
+ logger.debug(f"Commit failed: {result.stderr}")
243
+ return None
244
+
245
+ # Get commit info
246
+ return self.get_last_commit()
247
+
248
+ def get_last_commit(self) -> Optional[GitCommit]:
249
+ """Get the last commit."""
250
+ if not self.is_repo:
251
+ return None
252
+
253
+ result = self._run_git(
254
+ "log", "-1",
255
+ "--format=%H|%h|%s|%an|%ai",
256
+ check=False
257
+ )
258
+
259
+ if result.returncode != 0:
260
+ return None
261
+
262
+ parts = result.stdout.strip().split("|")
263
+ if len(parts) >= 5:
264
+ return GitCommit(
265
+ hash=parts[0],
266
+ short_hash=parts[1],
267
+ message=parts[2],
268
+ author=parts[3],
269
+ date=parts[4]
270
+ )
271
+
272
+ return None
273
+
274
+ def get_log(self, count: int = 10) -> List[GitCommit]:
275
+ """Get commit log."""
276
+ commits = []
277
+
278
+ if not self.is_repo:
279
+ return commits
280
+
281
+ result = self._run_git(
282
+ "log", f"-{count}",
283
+ "--format=%H|%h|%s|%an|%ai",
284
+ check=False
285
+ )
286
+
287
+ if result.returncode != 0:
288
+ return commits
289
+
290
+ for line in result.stdout.strip().split("\n"):
291
+ if not line:
292
+ continue
293
+
294
+ parts = line.split("|")
295
+ if len(parts) >= 5:
296
+ commits.append(GitCommit(
297
+ hash=parts[0],
298
+ short_hash=parts[1],
299
+ message=parts[2],
300
+ author=parts[3],
301
+ date=parts[4]
302
+ ))
303
+
304
+ return commits
305
+
306
+ def undo_last_commit(self, soft: bool = True) -> bool:
307
+ """Undo the last commit."""
308
+ if not self.is_repo:
309
+ return False
310
+
311
+ reset_type = "--soft" if soft else "--hard"
312
+ result = self._run_git("reset", reset_type, "HEAD~1", check=False)
313
+
314
+ return result.returncode == 0
315
+
316
+ def create_branch(self, name: str, checkout: bool = True) -> bool:
317
+ """Create a new branch."""
318
+ if not self.is_repo:
319
+ return False
320
+
321
+ if checkout:
322
+ result = self._run_git("checkout", "-b", name, check=False)
323
+ else:
324
+ result = self._run_git("branch", name, check=False)
325
+
326
+ return result.returncode == 0
327
+
328
+ def checkout_branch(self, name: str) -> bool:
329
+ """Checkout a branch."""
330
+ if not self.is_repo:
331
+ return False
332
+
333
+ result = self._run_git("checkout", name, check=False)
334
+ return result.returncode == 0
335
+
336
+ def get_branches(self) -> List[str]:
337
+ """Get list of branches."""
338
+ if not self.is_repo:
339
+ return []
340
+
341
+ result = self._run_git("branch", "--list", check=False)
342
+ if result.returncode != 0:
343
+ return []
344
+
345
+ branches = []
346
+ for line in result.stdout.strip().split("\n"):
347
+ if line:
348
+ # Remove * and whitespace
349
+ branch = line.strip().lstrip("* ")
350
+ branches.append(branch)
351
+
352
+ return branches
353
+
354
+ def stash(self, message: Optional[str] = None) -> bool:
355
+ """Stash changes."""
356
+ if not self.is_repo:
357
+ return False
358
+
359
+ args = ["stash", "push"]
360
+ if message:
361
+ args.extend(["-m", message])
362
+
363
+ result = self._run_git(*args, check=False)
364
+ return result.returncode == 0
365
+
366
+ def stash_pop(self) -> bool:
367
+ """Pop stashed changes."""
368
+ if not self.is_repo:
369
+ return False
370
+
371
+ result = self._run_git("stash", "pop", check=False)
372
+ return result.returncode == 0
373
+
374
+
375
+ # ============================================================================
376
+ # Commit Message Generator
377
+ # ============================================================================
378
+
379
+ class CommitMessageGenerator:
380
+ """
381
+ Generates commit messages using AI or templates.
382
+ """
383
+
384
+ def __init__(self, use_ai: bool = True):
385
+ self.use_ai = use_ai
386
+
387
+ def generate(
388
+ self,
389
+ diff_content: str,
390
+ context: Optional[str] = None,
391
+ style: str = "conventional"
392
+ ) -> str:
393
+ """
394
+ Generate a commit message.
395
+
396
+ Args:
397
+ diff_content: The diff to describe
398
+ context: Optional context about the changes
399
+ style: Message style (conventional, simple, detailed)
400
+
401
+ Returns:
402
+ Generated commit message
403
+ """
404
+ if not diff_content:
405
+ return "Empty commit"
406
+
407
+ if self.use_ai:
408
+ return self._generate_with_ai(diff_content, context, style)
409
+ else:
410
+ return self._generate_from_diff(diff_content, style)
411
+
412
+ def _generate_with_ai(
413
+ self,
414
+ diff_content: str,
415
+ context: Optional[str],
416
+ style: str
417
+ ) -> str:
418
+ """Generate commit message using AI."""
419
+ # This would integrate with the agent system
420
+ # For now, return a template-based message
421
+ return self._generate_from_diff(diff_content, style)
422
+
423
+ def _generate_from_diff(self, diff_content: str, style: str) -> str:
424
+ """Generate commit message from diff analysis."""
425
+ # Analyze diff
426
+ files_changed = set()
427
+ additions = 0
428
+ deletions = 0
429
+
430
+ for line in diff_content.split("\n"):
431
+ if line.startswith("diff --git"):
432
+ match = re.search(r"b/(.+)$", line)
433
+ if match:
434
+ files_changed.add(match.group(1))
435
+ elif line.startswith("+") and not line.startswith("+++"):
436
+ additions += 1
437
+ elif line.startswith("-") and not line.startswith("---"):
438
+ deletions += 1
439
+
440
+ # Determine change type
441
+ if additions > deletions * 2:
442
+ change_type = "feat"
443
+ action = "Add"
444
+ elif deletions > additions * 2:
445
+ change_type = "refactor"
446
+ action = "Remove"
447
+ else:
448
+ change_type = "fix"
449
+ action = "Update"
450
+
451
+ # Build message
452
+ if len(files_changed) == 1:
453
+ file_name = list(files_changed)[0]
454
+ scope = Path(file_name).stem
455
+ message = f"{change_type}({scope}): {action} {file_name}"
456
+ elif len(files_changed) <= 3:
457
+ files_str = ", ".join(sorted(files_changed))
458
+ message = f"{change_type}: {action} {files_str}"
459
+ else:
460
+ message = f"{change_type}: {action} {len(files_changed)} files"
461
+
462
+ if style == "detailed":
463
+ message += f"\n\n+{additions} -{deletions} lines"
464
+
465
+ return message
466
+
467
+
468
+ # ============================================================================
469
+ # Diff Viewer
470
+ # ============================================================================
471
+
472
+ class DiffViewer:
473
+ """
474
+ Rich-based diff viewer.
475
+ """
476
+
477
+ def __init__(self):
478
+ self._console = None
479
+
480
+ @property
481
+ def console(self):
482
+ """Lazy load Rich console."""
483
+ if self._console is None:
484
+ try:
485
+ from rich.console import Console
486
+ self._console = Console()
487
+ except ImportError:
488
+ self._console = None
489
+ return self._console
490
+
491
+ def display_diff(self, diff_content: str, title: str = "Changes") -> None:
492
+ """Display diff with syntax highlighting."""
493
+ if not self.console:
494
+ print(diff_content)
495
+ return
496
+
497
+ from rich.panel import Panel
498
+ from rich.syntax import Syntax
499
+
500
+ syntax = Syntax(diff_content, "diff", theme="monokai", line_numbers=True)
501
+ self.console.print(Panel(syntax, title=title, border_style="blue"))
502
+
503
+ def display_status(self, status: GitStatus) -> None:
504
+ """Display git status."""
505
+ if not self.console:
506
+ print(f"Branch: {status.branch}")
507
+ print(f"Staged: {len(status.staged_files)}")
508
+ print(f"Modified: {len(status.modified_files)}")
509
+ print(f"Untracked: {len(status.untracked_files)}")
510
+ return
511
+
512
+ from rich.panel import Panel
513
+ from rich.table import Table
514
+
515
+ table = Table(show_header=True, header_style="bold cyan")
516
+ table.add_column("Category")
517
+ table.add_column("Files", justify="right")
518
+
519
+ table.add_row("Branch", status.branch or "detached")
520
+ table.add_row("Staged", str(len(status.staged_files)))
521
+ table.add_row("Modified", str(len(status.modified_files)))
522
+ table.add_row("Untracked", str(len(status.untracked_files)))
523
+
524
+ if status.ahead or status.behind:
525
+ table.add_row("Ahead/Behind", f"+{status.ahead} / -{status.behind}")
526
+
527
+ self.console.print(Panel(table, title="📊 Git Status", border_style="green"))
528
+
529
+ def display_log(self, commits: List[GitCommit]) -> None:
530
+ """Display commit log."""
531
+ if not self.console:
532
+ for commit in commits:
533
+ print(f"{commit.short_hash} {commit.message}")
534
+ return
535
+
536
+ from rich.table import Table
537
+
538
+ table = Table(show_header=True, header_style="bold cyan")
539
+ table.add_column("Hash")
540
+ table.add_column("Message")
541
+ table.add_column("Author")
542
+ table.add_column("Date")
543
+
544
+ for commit in commits:
545
+ table.add_row(
546
+ commit.short_hash,
547
+ commit.message[:50] + "..." if len(commit.message) > 50 else commit.message,
548
+ commit.author,
549
+ commit.date[:10]
550
+ )
551
+
552
+ self.console.print(table)
553
+
554
+
555
+ # ============================================================================
556
+ # CLI Integration Handler
557
+ # ============================================================================
558
+
559
+ class GitIntegrationHandler:
560
+ """
561
+ Handler for integrating Git with PraisonAI CLI.
562
+ """
563
+
564
+ def __init__(self, verbose: bool = False):
565
+ self.verbose = verbose
566
+ self._git: Optional[GitManager] = None
567
+ self._viewer: Optional[DiffViewer] = None
568
+ self._generator: Optional[CommitMessageGenerator] = None
569
+
570
+ @property
571
+ def feature_name(self) -> str:
572
+ return "git_integration"
573
+
574
+ def initialize(self, repo_path: Optional[str] = None) -> GitManager:
575
+ """Initialize Git manager."""
576
+ self._git = GitManager(repo_path=repo_path, verbose=self.verbose)
577
+ self._viewer = DiffViewer()
578
+ self._generator = CommitMessageGenerator()
579
+
580
+ if self.verbose and self._git.is_repo:
581
+ from rich import print as rprint
582
+ status = self._git.get_status()
583
+ rprint(f"[cyan]Git initialized on branch: {status.branch}[/cyan]")
584
+
585
+ return self._git
586
+
587
+ def get_git(self) -> Optional[GitManager]:
588
+ """Get the Git manager."""
589
+ return self._git
590
+
591
+ def show_status(self) -> GitStatus:
592
+ """Show git status."""
593
+ if not self._git:
594
+ self._git = self.initialize()
595
+
596
+ status = self._git.get_status()
597
+ if self._viewer:
598
+ self._viewer.display_status(status)
599
+
600
+ return status
601
+
602
+ def show_diff(self, staged: bool = False) -> str:
603
+ """Show diff."""
604
+ if not self._git:
605
+ self._git = self.initialize()
606
+
607
+ diff_content = self._git.get_diff_content(staged=staged)
608
+ if self._viewer and diff_content:
609
+ self._viewer.display_diff(diff_content)
610
+
611
+ return diff_content
612
+
613
+ def commit(
614
+ self,
615
+ message: Optional[str] = None,
616
+ auto_stage: bool = True
617
+ ) -> Optional[GitCommit]:
618
+ """Create a commit."""
619
+ if not self._git:
620
+ self._git = self.initialize()
621
+
622
+ if auto_stage:
623
+ self._git.stage_files()
624
+
625
+ if not message:
626
+ # Generate message from diff
627
+ diff_content = self._git.get_diff_content(staged=True)
628
+ if self._generator:
629
+ message = self._generator.generate(diff_content)
630
+ else:
631
+ message = "Update files"
632
+
633
+ return self._git.commit(message)
634
+
635
+ def undo(self, soft: bool = True) -> bool:
636
+ """Undo last commit."""
637
+ if not self._git:
638
+ self._git = self.initialize()
639
+
640
+ return self._git.undo_last_commit(soft=soft)
641
+
642
+ def show_log(self, count: int = 10) -> List[GitCommit]:
643
+ """Show commit log."""
644
+ if not self._git:
645
+ self._git = self.initialize()
646
+
647
+ commits = self._git.get_log(count=count)
648
+ if self._viewer:
649
+ self._viewer.display_log(commits)
650
+
651
+ return commits