praisonai-code 0.0.1__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 (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,345 @@
1
+ """
2
+ Checkpoints CLI Feature for PraisonAI.
3
+
4
+ Provides CLI commands for managing file-level checkpoints.
5
+
6
+ Commands:
7
+ - praisonai checkpoint save <message> # Save a checkpoint
8
+ - praisonai checkpoint restore <id> # Restore to a checkpoint
9
+ - praisonai checkpoint list # List all checkpoints
10
+ - praisonai checkpoint diff [from] [to] # Show diff between checkpoints
11
+ - praisonai checkpoint delete # Delete all checkpoints
12
+ """
13
+
14
+ import os
15
+ import asyncio
16
+ from typing import Optional, List
17
+ from dataclasses import dataclass
18
+
19
+
20
+ class CheckpointsHandler:
21
+ """
22
+ Handler for checkpoints CLI commands.
23
+
24
+ Provides functionality to:
25
+ - Save checkpoints before file modifications
26
+ - Restore workspace to previous checkpoints
27
+ - View diffs between checkpoints
28
+ - List and manage checkpoints
29
+ """
30
+
31
+ def __init__(self, workspace_dir: Optional[str] = None, verbose: bool = False,
32
+ storage_dir: Optional[str] = None):
33
+ self.workspace_dir = workspace_dir or os.getcwd()
34
+ self.verbose = verbose
35
+ self.storage_dir = storage_dir
36
+ self._service = None
37
+
38
+ @property
39
+ def feature_name(self) -> str:
40
+ return "checkpoints"
41
+
42
+ async def _get_service(self):
43
+ """Lazy load the checkpoint service."""
44
+ if self._service is None:
45
+ from praisonaiagents.checkpoints import CheckpointService
46
+ kwargs = {"workspace_dir": self.workspace_dir}
47
+ if self.storage_dir:
48
+ kwargs["storage_dir"] = self.storage_dir
49
+ self._service = CheckpointService(**kwargs)
50
+ await self._service.initialize()
51
+ return self._service
52
+
53
+ async def save(self, message: str, allow_empty: bool = False, quiet: bool = False) -> bool:
54
+ """
55
+ Save a checkpoint.
56
+
57
+ Args:
58
+ message: Checkpoint message
59
+ allow_empty: Allow checkpoint even if no changes
60
+ quiet: Suppress success/error console output (for auto-checkpointing
61
+ in machine-readable run modes)
62
+
63
+ Returns:
64
+ True if successful
65
+ """
66
+ service = await self._get_service()
67
+ result = await service.save(message, allow_empty=allow_empty)
68
+
69
+ if result.success:
70
+ if not quiet:
71
+ self._print_success(f"Checkpoint saved: {result.checkpoint.short_id}")
72
+ self._print_info(f"Message: {result.checkpoint.message}")
73
+ return True
74
+ else:
75
+ if not quiet:
76
+ self._print_error(f"Failed to save checkpoint: {result.error}")
77
+ return False
78
+
79
+ async def restore(self, checkpoint_id: str) -> bool:
80
+ """
81
+ Restore to a checkpoint.
82
+
83
+ Args:
84
+ checkpoint_id: Checkpoint ID to restore
85
+
86
+ Returns:
87
+ True if successful
88
+ """
89
+ service = await self._get_service()
90
+ result = await service.restore(checkpoint_id)
91
+
92
+ if result.success:
93
+ self._print_success(f"Restored to checkpoint: {result.checkpoint.short_id}")
94
+ return True
95
+ else:
96
+ self._print_error(f"Failed to restore: {result.error}")
97
+ return False
98
+
99
+ async def list_checkpoints(self, limit: int = 20) -> List[dict]:
100
+ """
101
+ List all checkpoints.
102
+
103
+ Args:
104
+ limit: Maximum number to show
105
+
106
+ Returns:
107
+ List of checkpoint dictionaries
108
+ """
109
+ service = await self._get_service()
110
+ checkpoints = await service.list_checkpoints(limit=limit)
111
+
112
+ if not checkpoints:
113
+ self._print_info("No checkpoints found")
114
+ return []
115
+
116
+ self._print_checkpoints_table(checkpoints)
117
+ return [cp.to_dict() for cp in checkpoints]
118
+
119
+ def _print_checkpoints_table(self, checkpoints):
120
+ """Print checkpoints in table format."""
121
+ try:
122
+ from rich.console import Console
123
+ from rich.table import Table
124
+
125
+ console = Console()
126
+ table = Table(title="Checkpoints")
127
+ table.add_column("ID", style="cyan")
128
+ table.add_column("Message", style="green")
129
+ table.add_column("Timestamp", style="yellow")
130
+
131
+ for cp in checkpoints:
132
+ table.add_row(
133
+ cp.short_id,
134
+ cp.message[:50] + "..." if len(cp.message) > 50 else cp.message,
135
+ cp.timestamp.strftime("%Y-%m-%d %H:%M:%S")
136
+ )
137
+
138
+ console.print(table)
139
+ except ImportError:
140
+ print("Checkpoints:")
141
+ for cp in checkpoints:
142
+ print(f" {cp.short_id} - {cp.message} ({cp.timestamp})")
143
+
144
+ async def diff(
145
+ self,
146
+ from_id: Optional[str] = None,
147
+ to_id: Optional[str] = None
148
+ ) -> dict:
149
+ """
150
+ Show diff between checkpoints.
151
+
152
+ Args:
153
+ from_id: Starting checkpoint (default: previous)
154
+ to_id: Ending checkpoint (default: current)
155
+
156
+ Returns:
157
+ Diff dictionary
158
+ """
159
+ service = await self._get_service()
160
+ diff = await service.diff(from_id, to_id)
161
+
162
+ self._print_diff(diff)
163
+ return diff.to_dict()
164
+
165
+ def _print_diff(self, diff):
166
+ """Print diff in a readable format."""
167
+ try:
168
+ from rich.console import Console
169
+ from rich.table import Table
170
+
171
+ console = Console()
172
+
173
+ if not diff.files:
174
+ console.print("[yellow]No changes[/yellow]")
175
+ return
176
+
177
+ console.print(f"\n[bold]Changes from {diff.from_checkpoint} to {diff.to_checkpoint or 'working directory'}[/bold]\n")
178
+
179
+ table = Table()
180
+ table.add_column("Status", style="cyan")
181
+ table.add_column("File", style="white")
182
+ table.add_column("+", style="green")
183
+ table.add_column("-", style="red")
184
+
185
+ for f in diff.files:
186
+ status_color = {
187
+ "added": "green",
188
+ "modified": "yellow",
189
+ "deleted": "red"
190
+ }.get(f.status, "white")
191
+
192
+ table.add_row(
193
+ f"[{status_color}]{f.status}[/{status_color}]",
194
+ f.path,
195
+ str(f.additions),
196
+ str(f.deletions)
197
+ )
198
+
199
+ console.print(table)
200
+ console.print(f"\n[green]+{diff.total_additions}[/green] [red]-{diff.total_deletions}[/red]")
201
+ except ImportError:
202
+ print(f"Changes from {diff.from_checkpoint} to {diff.to_checkpoint or 'working directory'}:")
203
+ for f in diff.files:
204
+ print(f" {f.status}: {f.path} (+{f.additions}/-{f.deletions})")
205
+
206
+ async def delete_all(self) -> bool:
207
+ """
208
+ Delete all checkpoints.
209
+
210
+ Returns:
211
+ True if successful
212
+ """
213
+ service = await self._get_service()
214
+ result = await service.delete_all()
215
+
216
+ if result:
217
+ self._print_success("All checkpoints deleted")
218
+ return True
219
+ else:
220
+ self._print_error("Failed to delete checkpoints")
221
+ return False
222
+
223
+ def _print_success(self, message: str):
224
+ """Print success message."""
225
+ try:
226
+ from rich.console import Console
227
+ Console().print(f"[green]✓[/green] {message}")
228
+ except ImportError:
229
+ print(f"✓ {message}")
230
+
231
+ def _print_error(self, message: str):
232
+ """Print error message."""
233
+ try:
234
+ from rich.console import Console
235
+ Console().print(f"[red]✗[/red] {message}")
236
+ except ImportError:
237
+ print(f"✗ {message}")
238
+
239
+ def _print_info(self, message: str):
240
+ """Print info message."""
241
+ try:
242
+ from rich.console import Console
243
+ Console().print(f"[dim]{message}[/dim]")
244
+ except ImportError:
245
+ print(message)
246
+
247
+
248
+ def handle_checkpoint_command(args: List[str], workspace_dir: Optional[str] = None, verbose: bool = False):
249
+ """
250
+ Handle checkpoint CLI commands.
251
+
252
+ Usage:
253
+ praisonai checkpoint save <message> # Save a checkpoint
254
+ praisonai checkpoint restore <id> # Restore to a checkpoint
255
+ praisonai checkpoint list [--limit N] # List checkpoints
256
+ praisonai checkpoint diff [from] [to] # Show diff
257
+ praisonai checkpoint delete # Delete all checkpoints
258
+ """
259
+ handler = CheckpointsHandler(workspace_dir=workspace_dir, verbose=verbose)
260
+
261
+ if not args:
262
+ print("Usage: praisonai checkpoint <command> [options]")
263
+ print("\nCommands:")
264
+ print(" save <message> Save a checkpoint with message")
265
+ print(" restore <id> Restore to a checkpoint")
266
+ print(" list [--limit N] List checkpoints (default: 20)")
267
+ print(" diff [from] [to] Show diff between checkpoints")
268
+ print(" delete Delete all checkpoints")
269
+ return
270
+
271
+ command = args[0]
272
+
273
+ if command == "save":
274
+ if len(args) < 2:
275
+ print("Usage: praisonai checkpoint save <message>")
276
+ return
277
+
278
+ message = " ".join(args[1:])
279
+ allow_empty = "--allow-empty" in args
280
+ if allow_empty:
281
+ message = message.replace("--allow-empty", "").strip()
282
+
283
+ asyncio.run(handler.save(message, allow_empty=allow_empty))
284
+
285
+ elif command == "restore":
286
+ if len(args) < 2:
287
+ print("Usage: praisonai checkpoint restore <checkpoint_id>")
288
+ return
289
+
290
+ asyncio.run(handler.restore(args[1]))
291
+
292
+ elif command == "list":
293
+ limit = 20
294
+ if "--limit" in args:
295
+ idx = args.index("--limit")
296
+ if idx + 1 < len(args):
297
+ try:
298
+ limit = int(args[idx + 1])
299
+ except ValueError:
300
+ pass
301
+
302
+ asyncio.run(handler.list_checkpoints(limit=limit))
303
+
304
+ elif command == "diff":
305
+ from_id = args[1] if len(args) > 1 else None
306
+ to_id = args[2] if len(args) > 2 else None
307
+
308
+ asyncio.run(handler.diff(from_id, to_id))
309
+
310
+ elif command == "delete":
311
+ # Confirm deletion
312
+ try:
313
+ from rich.console import Console
314
+ from rich.prompt import Confirm
315
+
316
+ console = Console()
317
+ if Confirm.ask("[yellow]Delete all checkpoints?[/yellow]"):
318
+ asyncio.run(handler.delete_all())
319
+ else:
320
+ console.print("[dim]Cancelled[/dim]")
321
+ except ImportError:
322
+ response = input("Delete all checkpoints? [y/N] ")
323
+ if response.lower() == 'y':
324
+ asyncio.run(handler.delete_all())
325
+ else:
326
+ print("Cancelled")
327
+
328
+ elif command == "help" or command == "--help":
329
+ print("Checkpoint CLI Commands:")
330
+ print("\n praisonai checkpoint save <message>")
331
+ print(" Save a checkpoint with the given message")
332
+ print(" Options: --allow-empty Allow checkpoint with no changes")
333
+ print("\n praisonai checkpoint restore <id>")
334
+ print(" Restore workspace to a specific checkpoint")
335
+ print("\n praisonai checkpoint list [--limit N]")
336
+ print(" List all checkpoints (default limit: 20)")
337
+ print("\n praisonai checkpoint diff [from_id] [to_id]")
338
+ print(" Show diff between checkpoints")
339
+ print(" If no IDs given, shows diff from last checkpoint to current")
340
+ print("\n praisonai checkpoint delete")
341
+ print(" Delete all checkpoints for this workspace")
342
+
343
+ else:
344
+ print(f"Unknown command: {command}")
345
+ print("Use 'praisonai checkpoint help' for available commands")
@@ -0,0 +1,335 @@
1
+ """
2
+ CLI Profiler - Unified profiling for terminal-native CLI commands.
3
+
4
+ Provides --profile and --profile-deep flags for eligible commands:
5
+ - praisonai "prompt"
6
+ - praisonai chat
7
+ - praisonai chat "prompt"
8
+ - praisonai code
9
+ - praisonai code "prompt"
10
+ - praisonai run agents.yaml
11
+
12
+ NOT supported for:
13
+ - praisonai ui ... (browser-based)
14
+ - praisonai tui (Textual TUI)
15
+ - praisonai call (long-running server)
16
+ """
17
+
18
+ import time
19
+ from dataclasses import dataclass
20
+ from typing import Any, Dict, Optional
21
+
22
+ import typer
23
+
24
+
25
+ # Commands that support profiling
26
+ PROFILING_ELIGIBLE_COMMANDS = frozenset({
27
+ "chat",
28
+ "code",
29
+ "run",
30
+ # Direct prompt is handled separately in main.py
31
+ })
32
+
33
+ # Commands that explicitly reject profiling
34
+ PROFILING_INELIGIBLE_COMMANDS = frozenset({
35
+ "ui",
36
+ "tui",
37
+ "call",
38
+ "realtime",
39
+ "serve",
40
+ "schedule",
41
+ "acp",
42
+ "mcp",
43
+ "lsp",
44
+ })
45
+
46
+
47
+ @dataclass
48
+ class CLIProfileConfig:
49
+ """Configuration for CLI profiling."""
50
+ enabled: bool = False
51
+ deep: bool = False
52
+ output_format: str = "text" # text or json
53
+ save_path: Optional[str] = None
54
+
55
+ @classmethod
56
+ def from_flags(cls, profile: bool = False, profile_deep: bool = False) -> 'CLIProfileConfig':
57
+ """Create config from CLI flags."""
58
+ return cls(
59
+ enabled=profile or profile_deep,
60
+ deep=profile_deep,
61
+ )
62
+
63
+
64
+ @dataclass
65
+ class CLIProfileResult:
66
+ """Result from CLI profiling."""
67
+ import_time_ms: float = 0.0
68
+ agent_init_time_ms: float = 0.0
69
+ execution_time_ms: float = 0.0
70
+ total_time_ms: float = 0.0
71
+ response: str = ""
72
+
73
+ # Deep profiling data (only if deep=True)
74
+ function_stats: Optional[list] = None
75
+ call_graph: Optional[dict] = None
76
+
77
+ def to_dict(self) -> Dict[str, Any]:
78
+ """Convert to dictionary."""
79
+ result = {
80
+ "import_time_ms": self.import_time_ms,
81
+ "agent_init_time_ms": self.agent_init_time_ms,
82
+ "execution_time_ms": self.execution_time_ms,
83
+ "total_time_ms": self.total_time_ms,
84
+ }
85
+ if self.function_stats:
86
+ result["function_stats"] = self.function_stats
87
+ if self.call_graph:
88
+ result["call_graph"] = self.call_graph
89
+ return result
90
+
91
+
92
+ def print_profile_report(result: CLIProfileResult, config: CLIProfileConfig):
93
+ """Print profiling report to console."""
94
+ try:
95
+ from rich.console import Console
96
+ from rich.table import Table
97
+ from rich.panel import Panel
98
+ console = Console()
99
+ except ImportError:
100
+ # Fallback to simple output
101
+ print("\n─── Profiling ───")
102
+ print(f"Import: {result.import_time_ms:.1f}ms")
103
+ print(f"Agent setup: {result.agent_init_time_ms:.1f}ms")
104
+ print(f"Execution: {result.execution_time_ms:.1f}ms")
105
+ print(f"Total: {result.total_time_ms:.1f}ms")
106
+ return
107
+
108
+ # Create timing table
109
+ table = Table(show_header=False, box=None, padding=(0, 1))
110
+ table.add_column("Phase", style="cyan")
111
+ table.add_column("Time", style="green", justify="right")
112
+
113
+ table.add_row("Import", f"{result.import_time_ms:.1f}ms")
114
+ table.add_row("Agent setup", f"{result.agent_init_time_ms:.1f}ms")
115
+ table.add_row("Execution", f"{result.execution_time_ms:.1f}ms")
116
+ table.add_row("─" * 12, "─" * 10)
117
+ table.add_row("Total", f"{result.total_time_ms:.1f}ms")
118
+
119
+ console.print("\n")
120
+ console.print(Panel(table, title="[bold]Profiling[/bold]", border_style="dim"))
121
+
122
+ # Deep profiling output
123
+ if config.deep and result.function_stats:
124
+ console.print("\n[bold yellow]⚠️ Deep profiling enabled - overhead included in measurements[/bold yellow]")
125
+
126
+ func_table = Table(title="Top Functions by Cumulative Time")
127
+ func_table.add_column("Function", style="cyan")
128
+ func_table.add_column("Calls", justify="right")
129
+ func_table.add_column("Cumul (ms)", justify="right", style="green")
130
+
131
+ for func in result.function_stats[:15]:
132
+ name = func.get("name", "")[:40]
133
+ calls = func.get("calls", 0)
134
+ cumtime = func.get("cumulative_time_ms", 0)
135
+ func_table.add_row(name, str(calls), f"{cumtime:.2f}")
136
+
137
+ console.print(func_table)
138
+
139
+
140
+ def reject_profiling_for_command(command_name: str):
141
+ """Raise error if profiling is attempted on ineligible command."""
142
+ if command_name in PROFILING_INELIGIBLE_COMMANDS:
143
+ raise typer.BadParameter(
144
+ f"Profiling is not supported for '{command_name}' command.\n"
145
+ f"Profiling is only available for terminal-native execution commands:\n"
146
+ f" praisonai \"prompt\" --profile\n"
147
+ f" praisonai chat \"prompt\" --profile\n"
148
+ f" praisonai code \"prompt\" --profile\n"
149
+ f" praisonai run agents.yaml --profile"
150
+ )
151
+
152
+
153
+ class CLIProfiler:
154
+ """
155
+ CLI Profiler for wrapping command execution.
156
+
157
+ Usage:
158
+ profiler = CLIProfiler(config)
159
+ with profiler.profile():
160
+ # execution code
161
+ profiler.print_report()
162
+ """
163
+
164
+ def __init__(self, config: CLIProfileConfig):
165
+ self.config = config
166
+ self._result = CLIProfileResult()
167
+ self._start_time: float = 0.0
168
+ self._cprofile = None
169
+
170
+ # Phase timestamps
171
+ self._import_start: float = 0.0
172
+ self._import_end: float = 0.0
173
+ self._init_start: float = 0.0
174
+ self._init_end: float = 0.0
175
+ self._exec_start: float = 0.0
176
+ self._exec_end: float = 0.0
177
+
178
+ def start(self):
179
+ """Start profiling."""
180
+ if not self.config.enabled:
181
+ return
182
+
183
+ self._start_time = time.perf_counter()
184
+
185
+ if self.config.deep:
186
+ import cProfile
187
+ self._cprofile = cProfile.Profile()
188
+ self._cprofile.enable()
189
+
190
+ def stop(self):
191
+ """Stop profiling and compute results."""
192
+ if not self.config.enabled:
193
+ return
194
+
195
+ end_time = time.perf_counter()
196
+
197
+ if self._cprofile:
198
+ self._cprofile.disable()
199
+ self._extract_cprofile_stats()
200
+
201
+ self._result.total_time_ms = (end_time - self._start_time) * 1000
202
+
203
+ def mark_import_start(self):
204
+ """Mark start of import phase."""
205
+ if self.config.enabled:
206
+ self._import_start = time.perf_counter()
207
+
208
+ def mark_import_end(self):
209
+ """Mark end of import phase."""
210
+ if self.config.enabled:
211
+ self._import_end = time.perf_counter()
212
+ self._result.import_time_ms = (self._import_end - self._import_start) * 1000
213
+
214
+ def mark_init_start(self):
215
+ """Mark start of agent initialization."""
216
+ if self.config.enabled:
217
+ self._init_start = time.perf_counter()
218
+
219
+ def mark_init_end(self):
220
+ """Mark end of agent initialization."""
221
+ if self.config.enabled:
222
+ self._init_end = time.perf_counter()
223
+ self._result.agent_init_time_ms = (self._init_end - self._init_start) * 1000
224
+
225
+ def mark_exec_start(self):
226
+ """Mark start of execution."""
227
+ if self.config.enabled:
228
+ self._exec_start = time.perf_counter()
229
+
230
+ def mark_exec_end(self):
231
+ """Mark end of execution."""
232
+ if self.config.enabled:
233
+ self._exec_end = time.perf_counter()
234
+ self._result.execution_time_ms = (self._exec_end - self._exec_start) * 1000
235
+
236
+ def set_response(self, response: str):
237
+ """Set the response for the profile result."""
238
+ self._result.response = response
239
+
240
+ def _extract_cprofile_stats(self):
241
+ """Extract statistics from cProfile."""
242
+ if not self._cprofile:
243
+ return
244
+
245
+ import pstats
246
+
247
+ stats = pstats.Stats(self._cprofile)
248
+ stats.sort_stats("cumulative")
249
+
250
+ function_stats = []
251
+ for (filename, line, name), (cc, nc, tt, ct, callers) in stats.stats.items():
252
+ if len(function_stats) >= 100:
253
+ break
254
+ function_stats.append({
255
+ "name": name,
256
+ "file": filename,
257
+ "line": line,
258
+ "calls": nc,
259
+ "total_time_ms": tt * 1000,
260
+ "cumulative_time_ms": ct * 1000,
261
+ })
262
+
263
+ function_stats.sort(key=lambda x: x["cumulative_time_ms"], reverse=True)
264
+ self._result.function_stats = function_stats[:30]
265
+
266
+ def get_result(self) -> CLIProfileResult:
267
+ """Get the profiling result."""
268
+ return self._result
269
+
270
+ def print_report(self):
271
+ """Print the profiling report."""
272
+ if self.config.enabled:
273
+ print_profile_report(self._result, self.config)
274
+
275
+
276
+ def run_with_profiling(
277
+ prompt: str,
278
+ config: CLIProfileConfig,
279
+ model: Optional[str] = None,
280
+ verbose: bool = False,
281
+ tools: Optional[str] = None,
282
+ ) -> tuple:
283
+ """
284
+ Run a prompt with profiling enabled.
285
+
286
+ Returns:
287
+ Tuple of (response, CLIProfileResult)
288
+ """
289
+ profiler = CLIProfiler(config)
290
+ profiler.start()
291
+
292
+ # Import phase
293
+ profiler.mark_import_start()
294
+ try:
295
+ from praisonaiagents import Agent
296
+ except ImportError:
297
+ raise ImportError("praisonaiagents not installed. Install with: pip install praisonaiagents")
298
+ profiler.mark_import_end()
299
+
300
+ # Agent initialization phase
301
+ profiler.mark_init_start()
302
+ agent_config = {
303
+ "name": "ProfiledAgent",
304
+ "role": "Assistant",
305
+ "goal": "Complete the task",
306
+ "verbose": verbose,
307
+ }
308
+ if model:
309
+ agent_config["llm"] = model
310
+
311
+ agent = Agent(**agent_config)
312
+ profiler.mark_init_end()
313
+
314
+ # Execution phase
315
+ profiler.mark_exec_start()
316
+ response = agent.start(prompt)
317
+ profiler.mark_exec_end()
318
+
319
+ profiler.stop()
320
+ profiler.set_response(str(response) if response else "")
321
+
322
+ return response, profiler.get_result()
323
+
324
+
325
+ # Export all
326
+ __all__ = [
327
+ "CLIProfileConfig",
328
+ "CLIProfileResult",
329
+ "CLIProfiler",
330
+ "PROFILING_ELIGIBLE_COMMANDS",
331
+ "PROFILING_INELIGIBLE_COMMANDS",
332
+ "print_profile_report",
333
+ "reject_profiling_for_command",
334
+ "run_with_profiling",
335
+ ]