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,830 @@
1
+ """
2
+ Replay command group for PraisonAI CLI.
3
+
4
+ Provides context replay functionality:
5
+ - replay list: List available traces
6
+ - replay context <session_id>: Interactive replay of a trace
7
+ """
8
+
9
+ from typing import Optional
10
+
11
+ import typer
12
+
13
+ app = typer.Typer(help="Context replay for debugging agent execution")
14
+
15
+
16
+ @app.command("list")
17
+ def replay_list(
18
+ limit: int = typer.Option(
19
+ 20,
20
+ "--limit",
21
+ "-n",
22
+ help="Maximum number of traces to show",
23
+ ),
24
+ json_output: bool = typer.Option(
25
+ False,
26
+ "--json",
27
+ "-j",
28
+ help="Output as JSON",
29
+ ),
30
+ ):
31
+ """List available context traces."""
32
+ from ..output.console import get_output_controller
33
+
34
+ output = get_output_controller()
35
+
36
+ try:
37
+ from praisonai.replay import list_traces
38
+ traces = list_traces(limit=limit)
39
+ except ImportError:
40
+ output.print_error("Replay module not available")
41
+ raise typer.Exit(1)
42
+
43
+ if json_output or output.is_json_mode:
44
+ output.print_json({
45
+ "traces": [t.to_dict() for t in traces]
46
+ })
47
+ return
48
+
49
+ if not traces:
50
+ output.print_info("No traces found. Run agents with trace=True to create traces.")
51
+ return
52
+
53
+ # Format as table
54
+ headers = ["Session ID", "Events", "Size", "Modified"]
55
+ rows = []
56
+
57
+ for trace in traces:
58
+ modified = trace.modified_at.strftime("%Y-%m-%d %H:%M")
59
+ size_kb = trace.size_bytes / 1024
60
+ size_str = f"{size_kb:.1f} KB" if size_kb >= 1 else f"{trace.size_bytes} B"
61
+
62
+ rows.append([
63
+ trace.session_id[:40] + "..." if len(trace.session_id) > 40 else trace.session_id,
64
+ str(trace.event_count),
65
+ size_str,
66
+ modified,
67
+ ])
68
+
69
+ output.print_table(headers, rows, title="Available Context Traces")
70
+ output.print("\nUse 'praisonai replay context <session_id>' to replay a trace.")
71
+
72
+
73
+ @app.command("context")
74
+ def replay_context(
75
+ session_id: str = typer.Argument(
76
+ ...,
77
+ help="Session ID or path to trace file",
78
+ ),
79
+ start: int = typer.Option(
80
+ 0,
81
+ "--start",
82
+ "-s",
83
+ help="Start from event number (1-based)",
84
+ ),
85
+ no_rich: bool = typer.Option(
86
+ False,
87
+ "--no-rich",
88
+ help="Disable Rich formatting",
89
+ ),
90
+ dump: bool = typer.Option(
91
+ False,
92
+ "--dump",
93
+ "-d",
94
+ help="Dump all events non-interactively (for scripting)",
95
+ ),
96
+ json_output: bool = typer.Option(
97
+ False,
98
+ "--json",
99
+ "-j",
100
+ help="Output as JSON (implies --dump)",
101
+ ),
102
+ full: bool = typer.Option(
103
+ False,
104
+ "--full",
105
+ "-f",
106
+ help="Show full context content (messages, responses) without truncation",
107
+ ),
108
+ stats: bool = typer.Option(
109
+ False,
110
+ "--stats",
111
+ help="Show session statistics (tokens, costs, duplicates)",
112
+ ),
113
+ cost: bool = typer.Option(
114
+ False,
115
+ "--cost",
116
+ help="Show cost breakdown only",
117
+ ),
118
+ ):
119
+ """Interactive replay of a context trace.
120
+
121
+ Use --dump for non-interactive output (useful for scripting/verification).
122
+ Use --json for machine-readable output.
123
+ Use --full to show complete context content at each stage.
124
+ Use --stats to show session statistics (tokens, costs, duplicates).
125
+ Use --cost to show cost breakdown only.
126
+ """
127
+ from ..output.console import get_output_controller
128
+
129
+ output = get_output_controller()
130
+
131
+ try:
132
+ from praisonai.replay import ContextTraceReader, ReplayPlayer
133
+ from praisonai.replay.analyzer import TraceAnalyzer, format_stats_output, format_cost_output
134
+ except ImportError:
135
+ output.print_error("Replay module not available")
136
+ raise typer.Exit(1)
137
+
138
+ # Load trace
139
+ reader = ContextTraceReader(session_id)
140
+
141
+ if not reader.exists:
142
+ output.print_error(f"Trace not found: {session_id}")
143
+ output.print_info("Use 'praisonai replay list' to see available traces.")
144
+ raise typer.Exit(1)
145
+
146
+ events = reader.get_all()
147
+ if not events:
148
+ output.print_error(f"No events in trace: {session_id}")
149
+ raise typer.Exit(1)
150
+
151
+ # Stats mode - show statistics only
152
+ if stats:
153
+ analyzer = TraceAnalyzer(events)
154
+ result = analyzer.analyze()
155
+ if json_output:
156
+ output.print_json(result.to_dict())
157
+ else:
158
+ output.print(format_stats_output(result))
159
+ return
160
+
161
+ # Cost mode - show cost breakdown only
162
+ if cost:
163
+ analyzer = TraceAnalyzer(events)
164
+ result = analyzer.analyze()
165
+ if json_output:
166
+ output.print_json({"cost": result.to_dict()["cost_stats"]})
167
+ else:
168
+ output.print(format_cost_output(result))
169
+ return
170
+
171
+ # JSON output implies dump mode
172
+ if json_output:
173
+ dump = True
174
+
175
+ # Non-interactive dump mode
176
+ if dump:
177
+ _dump_events(output, events, session_id, json_output, full)
178
+ return
179
+
180
+ output.print_success(f"Loaded {len(events)} events from: {session_id}")
181
+
182
+ # Create and run player with full mode support
183
+ player = ReplayPlayer(reader, use_rich=not no_rich, full_mode=full)
184
+
185
+ # Set start position if specified
186
+ if start > 0:
187
+ if player.goto(start - 1): # Convert to 0-based
188
+ output.print(f"Starting from event {start}")
189
+ else:
190
+ output.print_warning(f"Invalid start position. Valid range: 1-{len(events)}")
191
+
192
+ player.run()
193
+
194
+
195
+ def _dump_events(output, events, session_id: str, json_output: bool, full: bool = False):
196
+ """Dump all events non-interactively.
197
+
198
+ Args:
199
+ output: Output controller
200
+ events: List of trace events
201
+ session_id: Session identifier
202
+ json_output: Whether to output as JSON
203
+ full: Whether to show full content without truncation
204
+ """
205
+ # Import analyzer for stats
206
+ from praisonai.replay.analyzer import TraceAnalyzer
207
+
208
+ if json_output or output.is_json_mode:
209
+ # Include analysis in JSON output
210
+ analyzer = TraceAnalyzer(events)
211
+ result = analyzer.analyze()
212
+ output.print_json({
213
+ "session_id": session_id,
214
+ "total_events": len(events),
215
+ "analysis": result.to_dict(),
216
+ "events": [e.to_dict() if hasattr(e, 'to_dict') else e for e in events],
217
+ })
218
+ return
219
+
220
+ # Text output - formatted for readability
221
+ output.print(f"\n{'='*60}")
222
+ output.print(f" CONTEXT REPLAY: {session_id}")
223
+ output.print(f" Total Events: {len(events)}")
224
+ output.print(f"{'='*60}\n")
225
+
226
+ # Track running totals
227
+ running_prompt_tokens = 0
228
+ running_completion_tokens = 0
229
+ running_cost = 0.0
230
+
231
+ for i, event in enumerate(events):
232
+ # Get event details
233
+ if hasattr(event, 'event_type'):
234
+ et = event.event_type
235
+ if hasattr(et, 'value'):
236
+ et = et.value
237
+ else:
238
+ et = event.get('event_type', 'unknown')
239
+
240
+ agent_name = getattr(event, 'agent_name', None) or (event.get('agent_name') if isinstance(event, dict) else None)
241
+ seq = getattr(event, 'sequence_num', i) or (event.get('sequence_num', i) if isinstance(event, dict) else i)
242
+ branch_id = getattr(event, 'branch_id', None) or (event.get('branch_id') if isinstance(event, dict) else None)
243
+
244
+ # Get token/cost info
245
+ prompt_tokens = getattr(event, 'prompt_tokens', 0) or (event.get('prompt_tokens', 0) if isinstance(event, dict) else 0)
246
+ completion_tokens = getattr(event, 'completion_tokens', 0) or (event.get('completion_tokens', 0) if isinstance(event, dict) else 0)
247
+ cost_usd = getattr(event, 'cost_usd', 0) or (event.get('cost_usd', 0) if isinstance(event, dict) else 0)
248
+
249
+ # Update running totals
250
+ running_prompt_tokens += prompt_tokens
251
+ running_completion_tokens += completion_tokens
252
+ running_cost += cost_usd
253
+
254
+ # Format event header
255
+ header = f"[{seq + 1:3d}] {et.upper()}"
256
+ if agent_name:
257
+ header += f" ({agent_name})"
258
+ if branch_id:
259
+ header += f" [branch: {branch_id}]"
260
+
261
+ output.print(f"\n{'-'*50}")
262
+ output.print(header)
263
+
264
+ # Show token/cost info for LLM events
265
+ if prompt_tokens or completion_tokens:
266
+ output.print(f" prompt_tokens: {prompt_tokens:,}")
267
+ output.print(f" completion_tokens: {completion_tokens:,}")
268
+ if cost_usd:
269
+ output.print(f" cost: ${cost_usd:.6f}")
270
+
271
+ # Show event-specific data
272
+ data = getattr(event, 'data', None) or (event.get('data') if isinstance(event, dict) else None)
273
+ if data:
274
+ for key, value in data.items():
275
+ if value is not None:
276
+ value_str = str(value)
277
+ # Full mode: show complete content; otherwise truncate
278
+ if not full and len(value_str) > 100:
279
+ value_str = value_str[:100] + "..."
280
+
281
+ # For messages array in full mode, format nicely
282
+ if full and key == "messages" and isinstance(value, list):
283
+ output.print(f" {key}:")
284
+ for msg_idx, msg in enumerate(value):
285
+ role = msg.get("role", "unknown") if isinstance(msg, dict) else "unknown"
286
+ content = msg.get("content", "") if isinstance(msg, dict) else str(msg)
287
+ output.print(f" [{msg_idx + 1}] {role}:")
288
+ # Indent content lines
289
+ for line in str(content).split('\n')[:20]: # Limit lines in full mode
290
+ output.print(f" {line}")
291
+ if len(str(content).split('\n')) > 20:
292
+ output.print(f" ... ({len(str(content).split(chr(10)))} lines total)")
293
+ else:
294
+ output.print(f" {key}: {value_str}")
295
+
296
+ # Show token info if available (legacy field)
297
+ tokens_used = getattr(event, 'tokens_used', 0) or (event.get('tokens_used', 0) if isinstance(event, dict) else 0)
298
+ if tokens_used and not prompt_tokens and not completion_tokens:
299
+ output.print(f" tokens_used: {tokens_used}")
300
+
301
+ # Show summary with running totals
302
+ output.print(f"\n{'='*60}")
303
+ output.print(f" SESSION SUMMARY")
304
+ output.print(f"{'='*60}")
305
+ output.print(f" Total Events: {len(events)}")
306
+ output.print(f" Prompt Tokens: {running_prompt_tokens:,}")
307
+ output.print(f" Completion Tokens: {running_completion_tokens:,}")
308
+ output.print(f" Total Tokens: {running_prompt_tokens + running_completion_tokens:,}")
309
+ output.print(f" Total Cost: ${running_cost:.6f}")
310
+
311
+ # Show duplicate analysis if in full mode
312
+ if full:
313
+ analyzer = TraceAnalyzer(events)
314
+ result = analyzer.analyze()
315
+ if result.duplicates:
316
+ output.print(f"\n DUPLICATE CONTENT DETECTED:")
317
+ output.print(f" Duplicates Found: {result.duplicate_count}")
318
+ output.print(f" Wasted Tokens: {result.total_wasted_tokens:,}")
319
+ for dup in result.duplicates[:3]:
320
+ output.print(f" - Hash {dup.content_hash[:12]}... ({dup.duplicate_count}x, ~{dup.wasted_tokens:,} wasted)")
321
+ else:
322
+ output.print(f"\n CONTEXT EFFICIENCY: No duplicates detected ✓")
323
+
324
+ output.print(f"{'='*60}\n")
325
+
326
+
327
+ @app.command("show")
328
+ def replay_show(
329
+ session_id: str = typer.Argument(
330
+ ...,
331
+ help="Session ID or path to trace file",
332
+ ),
333
+ event_num: int = typer.Option(
334
+ None,
335
+ "--event",
336
+ "-e",
337
+ help="Show specific event number (1-based)",
338
+ ),
339
+ agent: Optional[str] = typer.Option(
340
+ None,
341
+ "--agent",
342
+ "-a",
343
+ help="Filter by agent name",
344
+ ),
345
+ event_type: Optional[str] = typer.Option(
346
+ None,
347
+ "--type",
348
+ "-t",
349
+ help="Filter by event type",
350
+ ),
351
+ json_output: bool = typer.Option(
352
+ False,
353
+ "--json",
354
+ "-j",
355
+ help="Output as JSON",
356
+ ),
357
+ ):
358
+ """Show trace events without interactive mode."""
359
+ from ..output.console import get_output_controller
360
+
361
+ output = get_output_controller()
362
+
363
+ try:
364
+ from praisonai.replay import ContextTraceReader
365
+ except ImportError:
366
+ output.print_error("Replay module not available")
367
+ raise typer.Exit(1)
368
+
369
+ reader = ContextTraceReader(session_id)
370
+
371
+ if not reader.exists:
372
+ output.print_error(f"Trace not found: {session_id}")
373
+ raise typer.Exit(1)
374
+
375
+ # Get events with filters
376
+ if event_num is not None:
377
+ events = [reader[event_num - 1]] if 0 < event_num <= len(reader) else []
378
+ elif agent:
379
+ events = reader.get_by_agent(agent)
380
+ elif event_type:
381
+ events = reader.get_by_type(event_type)
382
+ else:
383
+ events = reader.get_all()
384
+
385
+ if json_output or output.is_json_mode:
386
+ output.print_json({
387
+ "session_id": session_id,
388
+ "total_events": len(reader),
389
+ "filtered_events": len(events),
390
+ "events": [e.to_dict() if hasattr(e, 'to_dict') else e for e in events],
391
+ })
392
+ return
393
+
394
+ if not events:
395
+ output.print_info("No events match the filter criteria.")
396
+ return
397
+
398
+ output.print(f"Session: {session_id}")
399
+ output.print(f"Showing {len(events)} of {len(reader)} events\n")
400
+
401
+ for i, event in enumerate(events):
402
+ # Get event details
403
+ if hasattr(event, 'event_type'):
404
+ et = event.event_type
405
+ if hasattr(et, 'value'):
406
+ et = et.value
407
+ else:
408
+ et = event.get('event_type', 'unknown')
409
+
410
+ agent_name = getattr(event, 'agent_name', None) or (event.get('agent_name') if isinstance(event, dict) else None)
411
+ seq = getattr(event, 'sequence_num', i) or (event.get('sequence_num', i) if isinstance(event, dict) else i)
412
+
413
+ line = f"[{seq + 1}] {et.upper()}"
414
+ if agent_name:
415
+ line += f" ({agent_name})"
416
+
417
+ output.print(line)
418
+
419
+
420
+ @app.command("delete")
421
+ def replay_delete(
422
+ session_id: str = typer.Argument(
423
+ ...,
424
+ help="Session ID to delete",
425
+ ),
426
+ force: bool = typer.Option(
427
+ False,
428
+ "--force",
429
+ "-f",
430
+ help="Skip confirmation",
431
+ ),
432
+ ):
433
+ """Delete a context trace."""
434
+ from ..output.console import get_output_controller
435
+
436
+ output = get_output_controller()
437
+
438
+ try:
439
+ from praisonai.replay import ContextTraceReader
440
+ except ImportError:
441
+ output.print_error("Replay module not available")
442
+ raise typer.Exit(1)
443
+
444
+ reader = ContextTraceReader(session_id)
445
+
446
+ if not reader.exists:
447
+ output.print_error(f"Trace not found: {session_id}")
448
+ raise typer.Exit(1)
449
+
450
+ if not force:
451
+ confirm = typer.confirm(f"Delete trace '{session_id}' ({len(reader)} events)?")
452
+ if not confirm:
453
+ output.print("Cancelled.")
454
+ raise typer.Exit(0)
455
+
456
+ try:
457
+ reader.path.unlink()
458
+ output.print_success(f"Deleted trace: {session_id}")
459
+ except Exception as e:
460
+ output.print_error(f"Failed to delete: {e}")
461
+ raise typer.Exit(1)
462
+
463
+
464
+ @app.command("flow")
465
+ def replay_flow(
466
+ session_id: str = typer.Argument(
467
+ ...,
468
+ help="Session ID or path to trace file",
469
+ ),
470
+ format: str = typer.Option(
471
+ "ascii",
472
+ "--format",
473
+ "-f",
474
+ help="Output format: ascii, json",
475
+ ),
476
+ ):
477
+ """Visualize agent flow from a context trace."""
478
+ from ..output.console import get_output_controller
479
+
480
+ output = get_output_controller()
481
+
482
+ try:
483
+ from praisonai.replay import ContextTraceReader
484
+ except ImportError:
485
+ output.print_error("Replay module not available")
486
+ raise typer.Exit(1)
487
+
488
+ reader = ContextTraceReader(session_id)
489
+
490
+ if not reader.exists:
491
+ output.print_error(f"Trace not found: {session_id}")
492
+ raise typer.Exit(1)
493
+
494
+ events = reader.get_all()
495
+ if not events:
496
+ output.print_error(f"No events in trace: {session_id}")
497
+ raise typer.Exit(1)
498
+
499
+ # Build flow structure from events
500
+ flow_data = _build_flow_from_events(events)
501
+
502
+ if format == "json":
503
+ output.print_json(flow_data)
504
+ return
505
+
506
+ # ASCII visualization
507
+ _print_ascii_flow(output, flow_data, session_id)
508
+
509
+
510
+ def _build_flow_from_events(events) -> dict:
511
+ """Build flow structure from context events."""
512
+ agents = {}
513
+ handoffs = []
514
+
515
+ for event in events:
516
+ event_type = event.event_type
517
+ if hasattr(event_type, 'value'):
518
+ event_type = event_type.value
519
+
520
+ agent_name = event.agent_name
521
+
522
+ if event_type == "agent_start" and agent_name:
523
+ if agent_name not in agents:
524
+ agents[agent_name] = {
525
+ "name": agent_name,
526
+ "start_time": event.timestamp,
527
+ "end_time": None,
528
+ "messages": 0,
529
+ "tool_calls": [],
530
+ "llm_calls": 0,
531
+ }
532
+
533
+ elif event_type == "agent_end" and agent_name:
534
+ if agent_name in agents:
535
+ agents[agent_name]["end_time"] = event.timestamp
536
+
537
+ elif event_type == "agent_handoff":
538
+ handoffs.append({
539
+ "from": event.data.get("from_agent"),
540
+ "to": event.data.get("to_agent"),
541
+ "reason": event.data.get("reason"),
542
+ "timestamp": event.timestamp,
543
+ })
544
+
545
+ elif event_type == "message_added" and agent_name:
546
+ if agent_name in agents:
547
+ agents[agent_name]["messages"] += 1
548
+
549
+ elif event_type == "tool_call_start" and agent_name:
550
+ tool_name = event.data.get("tool_name", "unknown")
551
+ if agent_name in agents:
552
+ agents[agent_name]["tool_calls"].append(tool_name)
553
+
554
+ elif event_type == "llm_request" and agent_name:
555
+ if agent_name in agents:
556
+ agents[agent_name]["llm_calls"] += 1
557
+
558
+ # Determine agent order based on start times
559
+ agent_order = sorted(
560
+ agents.keys(),
561
+ key=lambda a: agents[a].get("start_time", 0) or 0
562
+ )
563
+
564
+ return {
565
+ "session_id": events[0].session_id if events else "",
566
+ "agents": agents,
567
+ "agent_order": agent_order,
568
+ "handoffs": handoffs,
569
+ "total_events": len(events),
570
+ }
571
+
572
+
573
+ def _print_ascii_flow(output, flow_data: dict, session_id: str):
574
+ """Print ASCII flow visualization."""
575
+ agents = flow_data["agents"]
576
+ agent_order = flow_data["agent_order"]
577
+ handoffs = flow_data["handoffs"]
578
+
579
+ output.print(f"\n{'='*60}")
580
+ output.print(f" AGENT FLOW: {session_id}")
581
+ output.print(f"{'='*60}\n")
582
+
583
+ if not agent_order:
584
+ output.print(" No agents found in trace.")
585
+ return
586
+
587
+ # Print agent boxes with connections
588
+ for i, agent_name in enumerate(agent_order):
589
+ agent = agents[agent_name]
590
+
591
+ # Agent box
592
+ box_width = 40
593
+ output.print(f" ┌{'─' * box_width}┐")
594
+ output.print(f" │ {'Agent: ' + agent_name:<{box_width-2}} │")
595
+ output.print(f" │{' ' * box_width}│")
596
+ output.print(f" │ Messages: {agent['messages']:<{box_width-15}} │")
597
+ output.print(f" │ LLM Calls: {agent['llm_calls']:<{box_width-16}} │")
598
+
599
+ if agent['tool_calls']:
600
+ tools_str = ", ".join(agent['tool_calls'][:3])
601
+ if len(agent['tool_calls']) > 3:
602
+ tools_str += f" (+{len(agent['tool_calls'])-3} more)"
603
+ output.print(f" │ Tools: {tools_str:<{box_width-13}} │")
604
+
605
+ output.print(f" └{'─' * box_width}┘")
606
+
607
+ # Connection to next agent
608
+ if i < len(agent_order) - 1:
609
+ next_agent = agent_order[i + 1]
610
+
611
+ # Find handoff reason
612
+ handoff_reason = None
613
+ for h in handoffs:
614
+ if h["from"] == agent_name and h["to"] == next_agent:
615
+ handoff_reason = h.get("reason")
616
+ break
617
+
618
+ output.print(" │")
619
+ output.print(" ▼")
620
+ if handoff_reason:
621
+ reason_short = handoff_reason[:35] + "..." if len(handoff_reason) > 35 else handoff_reason
622
+ output.print(f" [{reason_short}]")
623
+ output.print("")
624
+
625
+ # Summary
626
+ output.print("\n" + "─"*60)
627
+ output.print(" Summary:")
628
+ output.print(f" Agents: {len(agent_order)}")
629
+ output.print(f" Handoffs: {len(handoffs)}")
630
+ output.print(f" Total Events: {flow_data['total_events']}")
631
+ output.print("─"*60 + "\n")
632
+
633
+
634
+ @app.command("dashboard")
635
+ def replay_dashboard(
636
+ session_id: Optional[str] = typer.Argument(
637
+ None,
638
+ help="Session ID (optional, shows all recent if not specified)",
639
+ ),
640
+ limit: int = typer.Option(
641
+ 5,
642
+ "--limit",
643
+ "-n",
644
+ help="Number of recent sessions to analyze",
645
+ ),
646
+ json_output: bool = typer.Option(
647
+ False,
648
+ "--json",
649
+ "-j",
650
+ help="Output as JSON",
651
+ ),
652
+ ):
653
+ """Show context analytics dashboard with usage patterns.
654
+
655
+ Displays:
656
+ - Token usage by agent (bar chart)
657
+ - Cost breakdown by tool
658
+ - Duplicate content patterns
659
+ - Optimization recommendations
660
+ """
661
+ from ..output.console import get_output_controller
662
+
663
+ output = get_output_controller()
664
+
665
+ try:
666
+ from praisonai.replay import list_traces, ContextTraceReader
667
+ from praisonai.replay.analyzer import TraceAnalyzer
668
+ except ImportError:
669
+ output.print_error("Replay module not available")
670
+ raise typer.Exit(1)
671
+
672
+ # Get traces to analyze
673
+ if session_id:
674
+ traces_to_analyze = [session_id]
675
+ else:
676
+ traces = list_traces(limit=limit)
677
+ if not traces:
678
+ output.print_info("No traces found. Run agents with trace=True to create traces.")
679
+ return
680
+ traces_to_analyze = [t.session_id for t in traces]
681
+
682
+ # Aggregate stats across sessions
683
+ all_stats = []
684
+ total_tokens = 0
685
+ total_cost = 0.0
686
+ total_duplicates = 0
687
+ total_wasted = 0
688
+ agent_tokens = {}
689
+ tool_costs = {}
690
+
691
+ for sid in traces_to_analyze:
692
+ reader = ContextTraceReader(sid)
693
+ if not reader.exists:
694
+ continue
695
+
696
+ events = reader.get_all()
697
+ if not events:
698
+ continue
699
+
700
+ analyzer = TraceAnalyzer(events)
701
+ result = analyzer.analyze()
702
+ all_stats.append(result)
703
+
704
+ # Aggregate using correct attribute paths
705
+ total_tokens += result.token_stats.total_tokens
706
+ total_cost += result.cost_stats.total_cost
707
+ total_duplicates += result.duplicate_count
708
+ total_wasted += result.total_wasted_tokens
709
+
710
+ # By agent - tokens is a dict with {prompt, completion, total}
711
+ for agent, tokens in result.token_stats.by_agent.items():
712
+ agent_total = tokens.get("total", 0) if isinstance(tokens, dict) else tokens
713
+ agent_tokens[agent] = agent_tokens.get(agent, 0) + agent_total
714
+
715
+ # By tool - cost is a dict with {count, cost}
716
+ for tool, cost_info in result.cost_stats.by_tool.items():
717
+ tool_cost = cost_info.get("cost", 0.0) if isinstance(cost_info, dict) else cost_info
718
+ tool_costs[tool] = tool_costs.get(tool, 0.0) + tool_cost
719
+
720
+ if json_output:
721
+ output.print_json({
722
+ "sessions_analyzed": len(all_stats),
723
+ "total_tokens": total_tokens,
724
+ "total_cost": total_cost,
725
+ "total_duplicates": total_duplicates,
726
+ "total_wasted_tokens": total_wasted,
727
+ "tokens_by_agent": agent_tokens,
728
+ "cost_by_tool": tool_costs,
729
+ })
730
+ return
731
+
732
+ # Display dashboard
733
+ output.print("\n" + "="*70)
734
+ output.print(" CONTEXT ANALYTICS DASHBOARD")
735
+ output.print("="*70)
736
+ output.print(f"\n Sessions Analyzed: {len(all_stats)}")
737
+ output.print(f" Total Tokens: {total_tokens:,}")
738
+ output.print(f" Total Cost: ${total_cost:.4f}")
739
+
740
+ # Token usage by agent (bar chart)
741
+ if agent_tokens:
742
+ output.print("\n TOKEN USAGE BY AGENT:")
743
+ output.print(" " + "-"*50)
744
+ max_tokens = max(agent_tokens.values()) if agent_tokens else 1
745
+ for agent, tokens in sorted(agent_tokens.items(), key=lambda x: -x[1]):
746
+ bar_len = int((tokens / max_tokens) * 30)
747
+ bar = "█" * bar_len
748
+ pct = (tokens / total_tokens * 100) if total_tokens > 0 else 0
749
+ output.print(f" {agent[:20]:<20} {bar:<30} {tokens:>10,} ({pct:.1f}%)")
750
+
751
+ # Cost by tool
752
+ if tool_costs:
753
+ output.print("\n COST BY TOOL:")
754
+ output.print(" " + "-"*50)
755
+ for tool, cost in sorted(tool_costs.items(), key=lambda x: -x[1]):
756
+ output.print(f" {tool:<25} ${cost:.4f}")
757
+
758
+ # Context efficiency
759
+ output.print("\n CONTEXT EFFICIENCY:")
760
+ output.print(" " + "-"*50)
761
+ if total_duplicates > 0:
762
+ waste_pct = (total_wasted / total_tokens * 100) if total_tokens > 0 else 0
763
+ output.print(f" ⚠️ Duplicates Found: {total_duplicates}")
764
+ output.print(f" ⚠️ Wasted Tokens: {total_wasted:,} ({waste_pct:.1f}%)")
765
+ output.print("\n RECOMMENDATIONS:")
766
+ output.print(" • Enable session-level deduplication in workflow")
767
+ output.print(" • Consider using context: {strategy: smart} in YAML")
768
+ output.print(" • Review tool outputs for redundant content")
769
+ else:
770
+ output.print(" ✅ Context is optimized! No duplicates detected.")
771
+
772
+ output.print("\n" + "="*70 + "\n")
773
+
774
+
775
+ @app.command("cleanup")
776
+ def replay_cleanup(
777
+ max_age_days: int = typer.Option(
778
+ 7,
779
+ "--max-age",
780
+ help="Delete traces older than N days",
781
+ ),
782
+ max_size_mb: int = typer.Option(
783
+ 100,
784
+ "--max-size",
785
+ help="Delete oldest traces if total size exceeds N MB",
786
+ ),
787
+ dry_run: bool = typer.Option(
788
+ False,
789
+ "--dry-run",
790
+ help="Show what would be deleted without deleting",
791
+ ),
792
+ ):
793
+ """Clean up old context traces."""
794
+ from ..output.console import get_output_controller
795
+
796
+ output = get_output_controller()
797
+
798
+ try:
799
+ from praisonai.replay.storage import cleanup_old_traces, list_traces
800
+ except ImportError:
801
+ output.print_error("Replay module not available")
802
+ raise typer.Exit(1)
803
+
804
+ if dry_run:
805
+ from datetime import datetime
806
+ traces = list_traces(limit=10000)
807
+ now = datetime.now()
808
+
809
+ to_delete = []
810
+ for trace in traces:
811
+ age_days = (now - trace.modified_at).days
812
+ if age_days > max_age_days:
813
+ to_delete.append(trace)
814
+
815
+ if to_delete:
816
+ output.print(f"Would delete {len(to_delete)} traces older than {max_age_days} days:")
817
+ for t in to_delete[:10]:
818
+ output.print(f" - {t.session_id}")
819
+ if len(to_delete) > 10:
820
+ output.print(f" ... and {len(to_delete) - 10} more")
821
+ else:
822
+ output.print("No traces would be deleted.")
823
+ return
824
+
825
+ deleted = cleanup_old_traces(max_age_days=max_age_days, max_size_mb=max_size_mb)
826
+
827
+ if deleted > 0:
828
+ output.print_success(f"Deleted {deleted} old traces.")
829
+ else:
830
+ output.print_info("No traces needed cleanup.")