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,450 @@
1
+ """
2
+ Session command group for PraisonAI CLI.
3
+
4
+ Provides session management:
5
+ - session list: List all sessions
6
+ - session resume: Resume a session
7
+ - session delete: Delete a session
8
+ - session export: Export a session
9
+ """
10
+
11
+ from typing import Optional
12
+
13
+ import typer
14
+
15
+ from ..output.console import get_output_controller
16
+ from ..state.sessions import get_session_manager, set_session_backend
17
+
18
+ app = typer.Typer(help="Session management")
19
+
20
+
21
+ def _create_backend(backend_type: str, storage_path: Optional[str]):
22
+ """Create storage backend from CLI options."""
23
+ try:
24
+ if backend_type == "file":
25
+ from praisonaiagents.storage import FileBackend
26
+ return FileBackend(storage_dir=storage_path or "~/.praison/sessions")
27
+ elif backend_type == "sqlite":
28
+ from praisonaiagents.storage import SQLiteBackend
29
+ db_path = storage_path or "~/.praison/sessions.db"
30
+ return SQLiteBackend(db_path=db_path)
31
+ elif backend_type.startswith("redis://"):
32
+ from praisonaiagents.storage import RedisBackend
33
+ return RedisBackend(url=backend_type, prefix="session:")
34
+ else:
35
+ return None
36
+ except Exception:
37
+ return None
38
+
39
+
40
+ @app.command("list")
41
+ def session_list(
42
+ limit: int = typer.Option(
43
+ 20,
44
+ "--limit",
45
+ "-n",
46
+ help="Maximum number of sessions to show",
47
+ ),
48
+ storage_backend: Optional[str] = typer.Option(
49
+ None,
50
+ "--storage-backend",
51
+ help="Storage backend: 'file', 'sqlite', or 'redis://url'",
52
+ ),
53
+ storage_path: Optional[str] = typer.Option(
54
+ None,
55
+ "--storage-path",
56
+ help="Path for storage backend",
57
+ ),
58
+ all_projects: bool = typer.Option(
59
+ False,
60
+ "--all",
61
+ help="Show sessions from all projects (default: current project only)",
62
+ ),
63
+ project_id: Optional[str] = typer.Option(
64
+ None,
65
+ "--project",
66
+ help="Show sessions for specific project ID",
67
+ ),
68
+ ):
69
+ """List all sessions."""
70
+ output = get_output_controller()
71
+
72
+ # Handle project-scoped session listing
73
+ if not all_projects and not storage_backend:
74
+ # Use project-scoped session store by default
75
+ from ..state.project_sessions import get_project_session_store
76
+ from ..utils.project import get_project_id, get_project_name
77
+
78
+ # List sessions for specific or current project
79
+ project_store = get_project_session_store(project_id=project_id)
80
+
81
+ sessions_data = project_store.list_sessions(limit=limit)
82
+
83
+ # Convert to expected format
84
+ class SessionInfo:
85
+ def __init__(self, data):
86
+ from datetime import datetime
87
+
88
+ self.session_id = data.get("session_id", data.get("id", ""))
89
+ self.name = data.get("agent_name", "")
90
+ self.status = data.get("status") # Use actual status from data if available
91
+ self.event_count = data.get("message_count", 0)
92
+
93
+ # Cumulative usage totals persisted per session (Issue #2421).
94
+ usage = data.get("usage")
95
+ if isinstance(usage, dict):
96
+ self.total_tokens = usage.get("total_tokens") or 0
97
+ self.cost = usage.get("cost") or 0.0
98
+ else:
99
+ self.total_tokens = data.get("total_tokens") or 0
100
+ self.cost = data.get("cost") or 0.0
101
+
102
+ # Parse updated_at string
103
+ updated_str = data.get("updated_at", "")
104
+ try:
105
+ self.updated_at = datetime.fromisoformat(updated_str.replace('Z', '+00:00')) if updated_str else datetime.now()
106
+ except ValueError:
107
+ self.updated_at = datetime.now()
108
+
109
+ def to_dict(self):
110
+ return {
111
+ "session_id": self.session_id,
112
+ "name": self.name,
113
+ "status": self.status,
114
+ "event_count": self.event_count,
115
+ "total_tokens": self.total_tokens,
116
+ "cost": self.cost,
117
+ "updated_at": self.updated_at.isoformat(),
118
+ }
119
+
120
+ sessions = [SessionInfo(data) for data in sessions_data]
121
+
122
+ # Add project info to output
123
+ if not project_id:
124
+ current_project = get_project_name()
125
+ current_id = get_project_id()
126
+ output.print_info(f"Project: {current_project} (ID: {current_id})")
127
+
128
+ else:
129
+ # Use global session manager
130
+ # Configure backend if specified
131
+ if storage_backend:
132
+ backend = _create_backend(storage_backend, storage_path)
133
+ if backend:
134
+ set_session_backend(backend)
135
+
136
+ manager = get_session_manager()
137
+ sessions = manager.list(limit=limit)
138
+
139
+ # SessionMetadata objects don't carry usage; surface persisted totals
140
+ # for global/backend listings the same way the project path does so the
141
+ # Tokens/Cost columns aren't blank when usage was recorded (Issue #2421).
142
+ try:
143
+ from ..state.project_sessions import read_session_usage
144
+
145
+ for s in sessions:
146
+ sid = getattr(s, "session_id", None) or getattr(s, "id", None)
147
+ if not sid:
148
+ continue
149
+ usage = read_session_usage(sid)
150
+ if usage.get("total_tokens") or usage.get("cost"):
151
+ try:
152
+ s.total_tokens = usage.get("total_tokens") or 0
153
+ s.cost = usage.get("cost") or 0.0
154
+ except Exception:
155
+ pass
156
+ except Exception:
157
+ pass
158
+
159
+ if output.is_json_mode:
160
+ def _session_dict(s):
161
+ d = s.to_dict()
162
+ # Ensure usage totals surface in JSON even for SessionMetadata,
163
+ # whose to_dict() omits them (Issue #2421).
164
+ if "total_tokens" not in d:
165
+ d["total_tokens"] = getattr(s, "total_tokens", 0) or 0
166
+ if "cost" not in d:
167
+ d["cost"] = getattr(s, "cost", 0.0) or 0.0
168
+ return d
169
+
170
+ output.print_json({
171
+ "sessions": [_session_dict(s) for s in sessions]
172
+ })
173
+ return
174
+
175
+ if not sessions:
176
+ output.print_info("No sessions found")
177
+ return
178
+
179
+ headers = ["ID", "Name", "Status", "Events", "Tokens", "Cost", "Updated"]
180
+ rows = []
181
+ for s in sessions:
182
+ total_tokens = getattr(s, "total_tokens", 0) or 0
183
+ cost = getattr(s, "cost", 0.0) or 0.0
184
+ rows.append([
185
+ s.session_id[:20] + "..." if len(s.session_id) > 20 else s.session_id,
186
+ s.name or "-",
187
+ s.status,
188
+ str(s.event_count),
189
+ f"{int(total_tokens):,}" if total_tokens else "-",
190
+ f"${float(cost):.4f}" if cost else "-",
191
+ s.updated_at.strftime("%Y-%m-%d %H:%M"),
192
+ ])
193
+
194
+ output.print_table(headers, rows, title="Sessions")
195
+
196
+
197
+ @app.command("resume")
198
+ def session_resume(
199
+ session_id: str = typer.Argument(..., help="Session ID to resume"),
200
+ prompt: Optional[str] = typer.Argument(
201
+ None,
202
+ help="Optional prompt to continue the session with",
203
+ ),
204
+ transcript: bool = typer.Option(
205
+ False,
206
+ "--transcript",
207
+ help="Only show the session transcript instead of restoring state",
208
+ ),
209
+ ):
210
+ """Resume a session with full conversational state restored."""
211
+ output = get_output_controller()
212
+
213
+ # Transcript-only path (opt-in for the old behaviour).
214
+ if transcript:
215
+ _print_session_transcript(session_id, output)
216
+ return
217
+
218
+ # Deterministic restoration via the shared rehydrate helper.
219
+ from ..session.resume import rehydrate_session
220
+
221
+ restored = rehydrate_session(session_id)
222
+
223
+ if not restored.found:
224
+ output.print_error(
225
+ f"Session not found: {session_id}",
226
+ remediation="Use 'praisonai session list' to see available sessions"
227
+ )
228
+ raise typer.Exit(1)
229
+
230
+ # When a prompt is supplied we hand off to `_run_prompt`, which owns all
231
+ # output for the continuation run. Emitting a restore blob here too would
232
+ # produce two top-level outputs (and break `--json` consumers), so we skip
233
+ # the standalone restore rendering in that case.
234
+ if prompt is None:
235
+ if output.is_json_mode:
236
+ output.print_json({
237
+ "session": restored.to_dict(),
238
+ "restored": True,
239
+ })
240
+ return
241
+
242
+ from ..state.project_sessions import format_usage_footer
243
+
244
+ usage_line = ""
245
+ if restored.usage and (restored.usage.get("total_tokens") or restored.usage.get("cost")):
246
+ usage_line = f"\nUsage: {format_usage_footer(restored.usage)}"
247
+
248
+ output.print_panel(
249
+ f"Session: {restored.agent_name or restored.session_id}\n"
250
+ f"Model: {restored.model or 'default'}\n"
251
+ f"Messages restored: {len(restored.chat_history)}"
252
+ f"{usage_line}",
253
+ title="Session Resumed"
254
+ )
255
+
256
+ if restored.chat_history:
257
+ output.print("\n--- Restored Conversation ---\n")
258
+ for msg in restored.chat_history[-10:]:
259
+ role = msg.get("role", "?")
260
+ content = msg.get("content", "")
261
+ output.print(f"[{role}] {content}")
262
+ return
263
+
264
+ # Continue the run with the restored state via the shared run path so
265
+ # behaviour matches `praisonai run --session <id>`.
266
+ from .run import _run_prompt
267
+
268
+ _run_prompt(
269
+ prompt=prompt,
270
+ model=restored.model,
271
+ session=session_id,
272
+ )
273
+
274
+
275
+ def _print_session_transcript(session_id: str, output) -> None:
276
+ """Print a session transcript (legacy ``--transcript`` behaviour)."""
277
+ manager = get_session_manager()
278
+ session = manager.get(session_id)
279
+
280
+ if not session:
281
+ output.print_error(
282
+ f"Session not found: {session_id}",
283
+ remediation="Use 'praisonai session list' to see available sessions"
284
+ )
285
+ raise typer.Exit(1)
286
+
287
+ events = manager.get_events(session_id)
288
+
289
+ if output.is_json_mode:
290
+ output.print_json({
291
+ "session": session.to_dict(),
292
+ "events": events,
293
+ })
294
+ return
295
+
296
+ output.print_panel(
297
+ f"Session: {session.name or session.session_id}\n"
298
+ f"Run ID: {session.run_id}\n"
299
+ f"Trace ID: {session.trace_id}\n"
300
+ f"Events: {session.event_count}\n"
301
+ f"Status: {session.status}",
302
+ title="Session Transcript"
303
+ )
304
+
305
+ if events:
306
+ output.print("\n--- Recent Events ---\n")
307
+ for event in events[-10:]:
308
+ event_type = event.get("event", "unknown")
309
+ message = event.get("message", "")
310
+ output.print(f"[{event_type}] {message}")
311
+
312
+
313
+ @app.command("delete")
314
+ def session_delete(
315
+ session_id: str = typer.Argument(..., help="Session ID to delete"),
316
+ confirm: bool = typer.Option(
317
+ False,
318
+ "--yes",
319
+ "-y",
320
+ help="Skip confirmation",
321
+ ),
322
+ ):
323
+ """Delete a session."""
324
+ output = get_output_controller()
325
+ manager = get_session_manager()
326
+
327
+ session = manager.get(session_id)
328
+
329
+ if not session:
330
+ output.print_error(f"Session not found: {session_id}")
331
+ raise typer.Exit(1)
332
+
333
+ if not confirm:
334
+ confirmed = typer.confirm(f"Delete session {session_id}?")
335
+ if not confirmed:
336
+ output.print_info("Cancelled")
337
+ raise typer.Exit(0)
338
+
339
+ deleted = manager.delete(session_id)
340
+
341
+ if output.is_json_mode:
342
+ output.print_json({"deleted": deleted, "session_id": session_id})
343
+ else:
344
+ if deleted:
345
+ output.print_success(f"Deleted session: {session_id}")
346
+ else:
347
+ output.print_error(f"Failed to delete session: {session_id}")
348
+ raise typer.Exit(1)
349
+
350
+
351
+ @app.command("export")
352
+ def session_export(
353
+ session_id: str = typer.Argument(..., help="Session ID to export"),
354
+ format: str = typer.Option(
355
+ "md",
356
+ "--format",
357
+ "-f",
358
+ help="Export format: md or json",
359
+ ),
360
+ output_file: Optional[str] = typer.Option(
361
+ None,
362
+ "--output",
363
+ "-o",
364
+ help="Output file path",
365
+ ),
366
+ ):
367
+ """Export a session."""
368
+ output = get_output_controller()
369
+ manager = get_session_manager()
370
+
371
+ content = manager.export(session_id, format=format)
372
+
373
+ if content is None:
374
+ output.print_error(f"Session not found: {session_id}")
375
+ raise typer.Exit(1)
376
+
377
+ if output_file:
378
+ with open(output_file, "w", encoding="utf-8") as f:
379
+ f.write(content)
380
+ output.print_success(f"Exported to: {output_file}")
381
+ else:
382
+ print(content)
383
+
384
+
385
+ @app.command("show")
386
+ def session_show(
387
+ session_id: str = typer.Argument(..., help="Session ID to show"),
388
+ ):
389
+ """Show session details."""
390
+ output = get_output_controller()
391
+ manager = get_session_manager()
392
+
393
+ session = manager.get(session_id)
394
+
395
+ if not session:
396
+ output.print_error(f"Session not found: {session_id}")
397
+ raise typer.Exit(1)
398
+
399
+ if output.is_json_mode:
400
+ output.print_json(session.to_dict())
401
+ return
402
+
403
+ output.print_panel(
404
+ f"Session ID: {session.session_id}\n"
405
+ f"Name: {session.name or '-'}\n"
406
+ f"Run ID: {session.run_id}\n"
407
+ f"Trace ID: {session.trace_id}\n"
408
+ f"Created: {session.created_at.isoformat()}\n"
409
+ f"Updated: {session.updated_at.isoformat()}\n"
410
+ f"Status: {session.status}\n"
411
+ f"Events: {session.event_count}\n"
412
+ f"Workspace: {session.workspace or '-'}",
413
+ title="Session Details"
414
+ )
415
+
416
+
417
+ @app.command("import")
418
+ def session_import(
419
+ input_file: str = typer.Argument(..., help="Session file to import (JSON format)"),
420
+ ):
421
+ """Import a session from a file."""
422
+ import json
423
+
424
+ output = get_output_controller()
425
+
426
+ try:
427
+ with open(input_file, "r", encoding="utf-8") as f:
428
+ data = json.load(f)
429
+ except FileNotFoundError:
430
+ output.print_error(f"File not found: {input_file}")
431
+ raise typer.Exit(1)
432
+ except json.JSONDecodeError as e:
433
+ output.print_error(f"Invalid JSON: {e}")
434
+ raise typer.Exit(1)
435
+
436
+ # Use InteractiveCore to import
437
+ try:
438
+ from praisonai.cli.interactive import InteractiveCore
439
+
440
+ core = InteractiveCore()
441
+ session_id = core.import_session(data)
442
+
443
+ if output.is_json_mode:
444
+ output.print_json({"imported": True, "session_id": session_id})
445
+ else:
446
+ output.print_success(f"Imported session: {session_id}")
447
+
448
+ except Exception as e:
449
+ output.print_error(f"Import failed: {e}")
450
+ raise typer.Exit(1)
@@ -0,0 +1,174 @@
1
+ """
2
+ Setup command group for PraisonAI CLI.
3
+
4
+ Provides interactive onboarding and configuration wizard.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import typer
12
+
13
+ from ..output.console import get_output_controller
14
+
15
+ app = typer.Typer(help="Interactive onboarding / configuration wizard")
16
+
17
+ # Default PRAISON_HOME directory
18
+ def get_praison_home() -> Path:
19
+ """Get the PraisonAI home directory."""
20
+ home = os.getenv("PRAISONAI_HOME")
21
+ if home:
22
+ return Path(home)
23
+ return Path.home() / ".praisonai"
24
+
25
+ PRAISON_HOME = get_praison_home()
26
+ ENV_FILE = PRAISON_HOME / ".env"
27
+
28
+ # Provider configurations
29
+ PROVIDERS = {
30
+ "1": ("openai", "OPENAI_API_KEY", "gpt-4o-mini"),
31
+ "2": ("anthropic", "ANTHROPIC_API_KEY", "claude-3-5-sonnet-latest"),
32
+ "3": ("google", "GEMINI_API_KEY", "gemini-2.0-flash"),
33
+ "4": ("ollama", None, "llama3.2"),
34
+ "5": ("custom", None, None),
35
+ }
36
+
37
+ PROVIDER_NAMES = {
38
+ "openai": ("OpenAI", "OPENAI_API_KEY", "gpt-4o-mini"),
39
+ "anthropic": ("Anthropic", "ANTHROPIC_API_KEY", "claude-3-5-sonnet-latest"),
40
+ "google": ("Google", "GEMINI_API_KEY", "gemini-2.0-flash"),
41
+ "ollama": ("Ollama", None, "llama3.2"),
42
+ "custom": ("Custom", None, None),
43
+ }
44
+
45
+
46
+ def _run_setup(
47
+ non_interactive: bool = False,
48
+ provider: Optional[str] = None,
49
+ api_key: Optional[str] = None,
50
+ model: Optional[str] = None,
51
+ ) -> int:
52
+ """Run the setup wizard."""
53
+ try:
54
+ from praisonai.cli.features.setup.handler import SetupHandler
55
+ handler = SetupHandler()
56
+ return handler.execute(
57
+ non_interactive=non_interactive,
58
+ provider=provider,
59
+ api_key=api_key,
60
+ model=model
61
+ )
62
+ except ImportError as e:
63
+ output = get_output_controller()
64
+ output.print_error(f"Setup module not available: {e}")
65
+ return 4
66
+ except Exception as e:
67
+ output = get_output_controller()
68
+ output.print_error(f"Setup error: {e}")
69
+ return 1
70
+
71
+
72
+ @app.callback(invoke_without_command=True)
73
+ def setup_callback(
74
+ ctx: typer.Context,
75
+ non_interactive: bool = typer.Option(False, "--non-interactive", help="Run in non-interactive mode"),
76
+ provider: Optional[str] = typer.Option(None, "--provider", help="LLM provider (openai, anthropic, google, ollama, custom)"),
77
+ api_key: Optional[str] = typer.Option(None, "--api-key", help="API key for the provider"),
78
+ model: Optional[str] = typer.Option(None, "--model", help="Default model to use"),
79
+ ):
80
+ """Run the onboarding wizard (idempotent — safe to re-run)."""
81
+ if ctx.invoked_subcommand:
82
+ return
83
+
84
+ exit_code = _run_setup(
85
+ non_interactive=non_interactive,
86
+ provider=provider,
87
+ api_key=api_key,
88
+ model=model
89
+ )
90
+ raise typer.Exit(exit_code)
91
+
92
+
93
+ @app.command("wizard")
94
+ def setup_wizard(
95
+ provider: Optional[str] = typer.Option(None, "--provider", help="LLM provider"),
96
+ api_key: Optional[str] = typer.Option(None, "--api-key", help="API key"),
97
+ model: Optional[str] = typer.Option(None, "--model", help="Default model"),
98
+ ):
99
+ """Run the interactive setup wizard."""
100
+ exit_code = _run_setup(
101
+ non_interactive=False,
102
+ provider=provider,
103
+ api_key=api_key,
104
+ model=model
105
+ )
106
+ raise typer.Exit(exit_code)
107
+
108
+
109
+ @app.command("config")
110
+ def setup_config(
111
+ show: bool = typer.Option(False, "--show", help="Show current configuration"),
112
+ edit: bool = typer.Option(False, "--edit", help="Edit configuration file"),
113
+ ):
114
+ """Manage setup configuration."""
115
+ output = get_output_controller()
116
+
117
+ if show:
118
+ if ENV_FILE.exists():
119
+ output.console.print(f"[bold]Configuration at {ENV_FILE}:[/bold]")
120
+ content = ENV_FILE.read_text()
121
+ # Don't show actual API keys for security
122
+ lines = []
123
+ for line in content.split('\n'):
124
+ if '=' in line and any(key in line for key in ['API_KEY', 'TOKEN', 'SECRET']):
125
+ key, _ = line.split('=', 1)
126
+ lines.append(f"{key}=***")
127
+ else:
128
+ lines.append(line)
129
+ output.console.print('\n'.join(lines))
130
+ else:
131
+ output.print_warning(f"No configuration found at {ENV_FILE}")
132
+ output.console.print("Run [cyan]praisonai setup[/cyan] to create one.")
133
+
134
+ if edit:
135
+ import subprocess
136
+ editor = os.getenv("EDITOR", "nano")
137
+ try:
138
+ subprocess.run([editor, str(ENV_FILE)], check=True)
139
+ except subprocess.CalledProcessError:
140
+ output.print_error(f"Failed to open editor: {editor}")
141
+ except FileNotFoundError:
142
+ output.print_error(f"Editor not found: {editor}")
143
+
144
+
145
+ @app.command("reset")
146
+ def setup_reset(
147
+ force: bool = typer.Option(False, "--force", help="Skip confirmation"),
148
+ ):
149
+ """Reset setup configuration."""
150
+ output = get_output_controller()
151
+
152
+ praison_home = get_praison_home()
153
+ env_file = praison_home / ".env"
154
+ config_file = praison_home / "config.yaml"
155
+ files_to_remove = [path for path in (env_file, config_file) if path.exists()]
156
+
157
+ if not files_to_remove:
158
+ output.print_info("No setup configuration to reset.")
159
+ return
160
+
161
+ if not force:
162
+ confirm = typer.confirm(f"Reset configuration at {praison_home}?")
163
+ if not confirm:
164
+ output.print_info("Reset cancelled.")
165
+ return
166
+
167
+ try:
168
+ for path in files_to_remove:
169
+ path.unlink()
170
+ output.print_success("Configuration reset successfully.")
171
+ output.console.print("Run [cyan]praisonai setup[/cyan] to configure again.")
172
+ except Exception as e:
173
+ output.print_error(f"Failed to reset configuration: {e}")
174
+ raise typer.Exit(1)