praisonai-code 0.0.53__tar.gz → 0.0.55__tar.gz

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 (318) hide show
  1. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/PKG-INFO +1 -1
  2. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/__init__.py +1 -1
  3. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/app.py +46 -30
  4. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/__init__.py +4 -0
  5. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/run.py +276 -82
  6. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/session.py +12 -3
  7. praisonai_code-0.0.55/praisonai_code/cli/commands/usage.py +235 -0
  8. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/loader.py +13 -6
  9. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/model_resolver.py +25 -1
  10. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/paths.py +72 -8
  11. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/code_intelligence.py +92 -28
  12. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/interactive_tools.py +80 -17
  13. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/setup/handler.py +8 -2
  14. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/tool_panel.py +2 -1
  15. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/events.py +62 -0
  16. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/frontends/rich_frontend.py +30 -1
  17. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/frontends/textual_frontend.py +31 -6
  18. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/legacy/praison_ai.py +10 -10
  19. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/state/project_sessions.py +33 -32
  20. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/state/session_resolver.py +7 -16
  21. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli_backends/__init__.py +22 -4
  22. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/llm/__init__.py +5 -0
  23. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/llm/credentials.py +14 -0
  24. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/llm/env.py +17 -0
  25. praisonai_code-0.0.55/praisonai_code/llm/local_detect.py +160 -0
  26. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/PKG-INFO +1 -1
  27. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/SOURCES.txt +2 -0
  28. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/pyproject.toml +1 -1
  29. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/README.md +0 -0
  30. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/__main__.py +0 -0
  31. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_bot_bridge.py +0 -0
  32. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_browser_bridge.py +0 -0
  33. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_framework_availability.py +0 -0
  34. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_logging.py +0 -0
  35. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_mcp_bridge.py +0 -0
  36. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_registry.py +0 -0
  37. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_safe_loader.py +0 -0
  38. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_train_bridge.py +0 -0
  39. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_version.py +0 -0
  40. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/_wrapper_bridge.py +0 -0
  41. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/__init__.py +0 -0
  42. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/_forward_shim.py +0 -0
  43. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/_paths.py +0 -0
  44. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/_warnings.py +0 -0
  45. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/_wrapper_reexport.py +0 -0
  46. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/approval_backend.py +0 -0
  47. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/branding.py +0 -0
  48. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/acp.py +0 -0
  49. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/agent.py +0 -0
  50. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/agents.py +0 -0
  51. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/attach.py +0 -0
  52. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/auth.py +0 -0
  53. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/benchmark.py +0 -0
  54. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/browser_tool.py +0 -0
  55. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/call.py +0 -0
  56. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/chat.py +0 -0
  57. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/checkpoint.py +0 -0
  58. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/code.py +0 -0
  59. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/command.py +0 -0
  60. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/commit.py +0 -0
  61. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/completion.py +0 -0
  62. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/config.py +0 -0
  63. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/daemon.py +0 -0
  64. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/debug.py +0 -0
  65. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/deploy.py +0 -0
  66. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/diag.py +0 -0
  67. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/doctor.py +0 -0
  68. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/endpoints.py +0 -0
  69. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/environment.py +0 -0
  70. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/eval.py +0 -0
  71. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/github.py +0 -0
  72. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/hooks.py +0 -0
  73. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/init.py +0 -0
  74. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/loop.py +0 -0
  75. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/lsp.py +0 -0
  76. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/memory.py +0 -0
  77. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/models.py +0 -0
  78. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/obs.py +0 -0
  79. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/package.py +0 -0
  80. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/paths.py +0 -0
  81. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/permissions.py +0 -0
  82. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/plugins.py +0 -0
  83. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/port.py +0 -0
  84. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/publish.py +0 -0
  85. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/registry.py +0 -0
  86. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/research.py +0 -0
  87. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/retrieval.py +0 -0
  88. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/rules.py +0 -0
  89. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/sandbox.py +0 -0
  90. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/serve.py +0 -0
  91. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/setup.py +0 -0
  92. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/skills.py +0 -0
  93. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/templates.py +0 -0
  94. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/test.py +0 -0
  95. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/todo.py +0 -0
  96. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/tools.py +0 -0
  97. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/traces.py +0 -0
  98. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/tracker.py +0 -0
  99. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/ui.py +0 -0
  100. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/uninstall.py +0 -0
  101. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/up.py +0 -0
  102. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/upgrade.py +0 -0
  103. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/version.py +0 -0
  104. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/commands/workflow.py +0 -0
  105. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/config_loader.py +0 -0
  106. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/__init__.py +0 -0
  107. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/config.schema.json +0 -0
  108. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/credentials.py +0 -0
  109. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/oauth.py +0 -0
  110. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/resolver.py +0 -0
  111. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/configuration/schema.py +0 -0
  112. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/execution/__init__.py +0 -0
  113. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/execution/core.py +0 -0
  114. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/execution/profiler.py +0 -0
  115. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/execution/request.py +0 -0
  116. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/execution/result.py +0 -0
  117. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/fallback_schema.py +0 -0
  118. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/__init__.py +0 -0
  119. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/_approval_bridge.py +0 -0
  120. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/_endpoint_registry.py +0 -0
  121. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/_search_registry.py +0 -0
  122. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/action_orchestrator.py +0 -0
  123. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/agents.py +0 -0
  124. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/at_mentions.py +0 -0
  125. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/audit_cli.py +0 -0
  126. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/auto_memory.py +0 -0
  127. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/auto_mode.py +0 -0
  128. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/autonomy_mode.py +0 -0
  129. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/base.py +0 -0
  130. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/benchmark.py +0 -0
  131. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/capabilities.py +0 -0
  132. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/checkpoints.py +0 -0
  133. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/cli_profiler.py +0 -0
  134. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/compaction.py +0 -0
  135. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/compare.py +0 -0
  136. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/config_hierarchy.py +0 -0
  137. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/context_manager.py +0 -0
  138. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/cost_tracker.py +0 -0
  139. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/csv_test_runner.py +0 -0
  140. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/custom_definitions.py +0 -0
  141. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/debug.py +0 -0
  142. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/diag.py +0 -0
  143. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/display_jsonl.py +0 -0
  144. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/__init__.py +0 -0
  145. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/__init__.py +0 -0
  146. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/_wrapper_checks.py +0 -0
  147. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/acp_checks.py +0 -0
  148. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/bot_checks.py +0 -0
  149. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/config_checks.py +0 -0
  150. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/db_checks.py +0 -0
  151. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/env_checks.py +0 -0
  152. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/gateway_checks.py +0 -0
  153. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/lsp_checks.py +0 -0
  154. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/mcp_checks.py +0 -0
  155. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/memory_checks.py +0 -0
  156. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/network_checks.py +0 -0
  157. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/obs_checks.py +0 -0
  158. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/packaging_checks.py +0 -0
  159. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/performance_checks.py +0 -0
  160. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/permissions_checks.py +0 -0
  161. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/runtime_checks.py +0 -0
  162. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +0 -0
  163. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/selftest_checks.py +0 -0
  164. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/serve_checks.py +0 -0
  165. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/skills_checks.py +0 -0
  166. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/checks/tools_checks.py +0 -0
  167. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/engine.py +0 -0
  168. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/formatters.py +0 -0
  169. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/handler.py +0 -0
  170. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/models.py +0 -0
  171. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/doctor/registry.py +0 -0
  172. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/endpoints.py +0 -0
  173. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/examples.py +0 -0
  174. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/external_agents.py +0 -0
  175. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/fast_context.py +0 -0
  176. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/file_history.py +0 -0
  177. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/flow_display.py +0 -0
  178. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/git_attribution.py +0 -0
  179. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/git_integration.py +0 -0
  180. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/guardrail.py +0 -0
  181. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/handoff.py +0 -0
  182. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/hooks.py +0 -0
  183. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/hybrid_workflow.py +0 -0
  184. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/image.py +0 -0
  185. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/interactive_core_headless.py +0 -0
  186. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/interactive_runtime.py +0 -0
  187. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/interactive_test_harness.py +0 -0
  188. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/interactive_tui.py +0 -0
  189. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/job_workflow.py +0 -0
  190. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/jobs.py +0 -0
  191. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/knowledge.py +0 -0
  192. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/knowledge_cli.py +0 -0
  193. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/lite.py +0 -0
  194. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/logs.py +0 -0
  195. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/lsp_cli.py +0 -0
  196. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/lsp_diagnostics.py +0 -0
  197. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/mcp.py +0 -0
  198. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/message_queue.py +0 -0
  199. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/metrics.py +0 -0
  200. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/migrate.py +0 -0
  201. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/migration_flow.py +0 -0
  202. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/migration_spec.py +0 -0
  203. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/n8n.py +0 -0
  204. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/observability.py +0 -0
  205. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/output_modes.py +0 -0
  206. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/output_style.py +0 -0
  207. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/package.py +0 -0
  208. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/performance.py +0 -0
  209. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/profiler/__init__.py +0 -0
  210. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/profiler/core.py +0 -0
  211. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/profiler/optimizations.py +0 -0
  212. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/profiler/suite.py +0 -0
  213. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/__init__.py +0 -0
  214. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/manager.py +0 -0
  215. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/models.py +0 -0
  216. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/persistence.py +0 -0
  217. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/scheduler.py +0 -0
  218. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/queue/worker.py +0 -0
  219. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/recipe_creator.py +0 -0
  220. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/recipe_prompts.py +0 -0
  221. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/registry.py +0 -0
  222. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/repo_map.py +0 -0
  223. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/router.py +0 -0
  224. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/safe_shell.py +0 -0
  225. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/sandbox_executor.py +0 -0
  226. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/sdk_knowledge.py +0 -0
  227. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/self_manage.py +0 -0
  228. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/session.py +0 -0
  229. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/session_checkpoints.py +0 -0
  230. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/setup/__init__.py +0 -0
  231. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/setup/templates.py +0 -0
  232. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/skills.py +0 -0
  233. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/slash_commands.py +0 -0
  234. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/telemetry.py +0 -0
  235. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/thinking.py +0 -0
  236. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/todo.py +0 -0
  237. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tools.py +0 -0
  238. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/__init__.py +0 -0
  239. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/cli.py +0 -0
  240. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/config.py +0 -0
  241. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/debug.py +0 -0
  242. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/events.py +0 -0
  243. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/mock_provider.py +0 -0
  244. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/orchestrator.py +0 -0
  245. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/__init__.py +0 -0
  246. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/help.py +0 -0
  247. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/main.py +0 -0
  248. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/queue.py +0 -0
  249. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/session.py +0 -0
  250. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/screens/settings.py +0 -0
  251. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/session_store.py +0 -0
  252. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/__init__.py +0 -0
  253. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/chat.py +0 -0
  254. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/command_popup.py +0 -0
  255. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/composer.py +0 -0
  256. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/file_popup.py +0 -0
  257. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/queue_panel.py +0 -0
  258. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/tui/widgets/status.py +0 -0
  259. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/wizard.py +0 -0
  260. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/workflow.py +0 -0
  261. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/features/yaml_utils.py +0 -0
  262. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/help_categories.py +0 -0
  263. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/__init__.py +0 -0
  264. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/async_tui.py +0 -0
  265. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/command_registry.py +0 -0
  266. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/config.py +0 -0
  267. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/core.py +0 -0
  268. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/frontends/__init__.py +0 -0
  269. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/praison_io.py +0 -0
  270. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/repl.py +0 -0
  271. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/split_tui.py +0 -0
  272. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/interactive/tui_app.py +0 -0
  273. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/legacy/__init__.py +0 -0
  274. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/legacy/env_security.py +0 -0
  275. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/legacy/prompt_dispatch.py +0 -0
  276. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/main.py +0 -0
  277. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/output/__init__.py +0 -0
  278. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/output/console.py +0 -0
  279. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/output/event_bridge.py +0 -0
  280. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/schedule_cli.py +0 -0
  281. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/schema_provider.py +0 -0
  282. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/session/__init__.py +0 -0
  283. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/session/resume.py +0 -0
  284. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/session/unified.py +0 -0
  285. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/state/__init__.py +0 -0
  286. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/state/identifiers.py +0 -0
  287. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/state/sessions.py +0 -0
  288. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/__init__.py +0 -0
  289. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/config.py +0 -0
  290. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/events.py +0 -0
  291. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/mg_backend.py +0 -0
  292. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/plain.py +0 -0
  293. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/ui/rich_backend.py +0 -0
  294. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/unified_schema.py +0 -0
  295. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/utils/env_utils.py +0 -0
  296. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/utils/project.py +0 -0
  297. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli/utils/stdin.py +0 -0
  298. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli_backends/claude.py +0 -0
  299. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/cli_backends/registry.py +0 -0
  300. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/data/__init__.py +0 -0
  301. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/data/fixtures/interactive/multi_agent.csv +0 -0
  302. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/data/fixtures/interactive/refactor.csv +0 -0
  303. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/data/fixtures/interactive/smoke.csv +0 -0
  304. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/data/fixtures/interactive/tools.csv +0 -0
  305. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/llm/catalogue.py +0 -0
  306. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/llm/config.py +0 -0
  307. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/runtime/__init__.py +0 -0
  308. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/runtime/__main__.py +0 -0
  309. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/runtime/client.py +0 -0
  310. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/runtime/descriptor.py +0 -0
  311. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/runtime/server.py +0 -0
  312. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/tool_registry.py +0 -0
  313. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code/tool_resolver.py +0 -0
  314. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/dependency_links.txt +0 -0
  315. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/entry_points.txt +0 -0
  316. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/requires.txt +0 -0
  317. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/praisonai_code.egg-info/top_level.txt +0 -0
  318. {praisonai_code-0.0.53 → praisonai_code-0.0.55}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonai-code
3
- Version: 0.0.53
3
+ Version: 0.0.55
4
4
  Summary: Agentic terminal CLI for PraisonAI — the terminal-native agent (run, chat, code, runtime, cli backends) extracted from the praisonai wrapper.
5
5
  Author: Mervin Praison
6
6
  License: MIT
@@ -12,6 +12,6 @@ the remaining terminal-agent modules, with PEP 562 shims left behind at the
12
12
  old ``praisonai.*`` paths for backward compatibility.
13
13
  """
14
14
 
15
- __version__ = "0.0.53"
15
+ __version__ = "0.0.55"
16
16
 
17
17
  __all__ = ["__version__"]
@@ -147,6 +147,7 @@ _LAZY_COMMANDS: Dict[str, Tuple[str, str, str]] = {
147
147
  "env": (".commands.environment", "app", "Environment and diagnostics"),
148
148
  "auth": (".commands.auth", "app", "Credential management"),
149
149
  "session": (".commands.session", "app", "Session management"),
150
+ "usage": (".commands.usage", "app", "Local token/cost usage reporting"),
150
151
  "completion": (".commands.completion", "app", "Shell completion scripts"),
151
152
  "version": (".commands.version", "app", "Version information"),
152
153
  "upgrade": (".commands.upgrade", "app", "Update the managed PraisonAI CLI install"),
@@ -852,6 +853,7 @@ def main_callback(
852
853
  if ctx.invoked_subcommand is None:
853
854
  # Check for credentials before starting TUI
854
855
  from praisonai_code.llm.credentials import (
856
+ detect_local_endpoint,
855
857
  inject_credentials_into_env,
856
858
  is_configured,
857
859
  )
@@ -859,43 +861,57 @@ def main_callback(
859
861
 
860
862
  inject_credentials_into_env()
861
863
  if not is_configured(): # Check for any configured credentials
864
+ # Keyless local-first: if a local OpenAI-compatible endpoint is
865
+ # reachable (e.g. Ollama), use it as the zero-config default so the
866
+ # first run works before any auth. Detection is timeout-bounded.
867
+ local = detect_local_endpoint()
868
+ if local is not None:
869
+ typer.echo(
870
+ f"No cloud key found; using local model {local.model}. "
871
+ "Run `praisonai setup` to add a hosted provider.",
872
+ err=True,
873
+ )
874
+ # Fall through to the TUI; no gate.
862
875
  # In non-interactive mode, just show error
863
- if not sys.stdin.isatty() or quiet:
876
+ elif not sys.stdin.isatty() or quiet:
864
877
  typer.echo(
865
- "Error: No API key configured. Run: praisonai setup",
878
+ "Error: No API key configured. Run: praisonai setup\n"
879
+ "(a running local endpoint such as Ollama would be used "
880
+ "automatically)",
866
881
  err=True
867
882
  )
868
883
  raise typer.Exit(1)
869
884
 
870
- # In interactive mode, offer to run setup
871
- typer.echo("No API key configured.")
872
- run_setup = typer.confirm("Would you like to run the setup wizard now?")
873
-
874
- if run_setup:
875
- # Import and run setup
876
- from .commands.setup import _run_setup
877
- exit_code = _run_setup(
878
- non_interactive=False,
879
- provider=None,
880
- api_key=None,
881
- model=None
882
- )
883
- if exit_code != 0:
884
- typer.echo("Setup failed. Exiting.", err=True)
885
- raise typer.Exit(exit_code)
886
-
887
- # Re-check credentials after setup
888
- inject_credentials_into_env()
889
- if not is_configured():
890
- typer.echo("Setup completed but credentials still not detected.", err=True)
891
- raise typer.Exit(1)
892
-
893
- # After successful setup, continue to TUI
894
- typer.echo("\nSetup complete! Starting interactive mode...\n")
885
+ # In interactive mode, offer to run setup (non-blocking suggestion)
895
886
  else:
896
- typer.echo("\nTo configure credentials later, run: praisonai setup")
897
- typer.echo("or set environment variables like OPENAI_API_KEY")
898
- raise typer.Exit(0)
887
+ typer.echo("No API key configured.")
888
+ run_setup = typer.confirm("Would you like to run the setup wizard now?")
889
+
890
+ if run_setup:
891
+ # Import and run setup
892
+ from .commands.setup import _run_setup
893
+ exit_code = _run_setup(
894
+ non_interactive=False,
895
+ provider=None,
896
+ api_key=None,
897
+ model=None
898
+ )
899
+ if exit_code != 0:
900
+ typer.echo("Setup failed. Exiting.", err=True)
901
+ raise typer.Exit(exit_code)
902
+
903
+ # Re-check credentials after setup
904
+ inject_credentials_into_env()
905
+ if not is_configured():
906
+ typer.echo("Setup completed but credentials still not detected.", err=True)
907
+ raise typer.Exit(1)
908
+
909
+ # After successful setup, continue to TUI
910
+ typer.echo("\nSetup complete! Starting interactive mode...\n")
911
+ else:
912
+ typer.echo("\nTo configure credentials later, run: praisonai setup")
913
+ typer.echo("or set environment variables like OPENAI_API_KEY")
914
+ raise typer.Exit(0)
899
915
 
900
916
  from .interactive.async_tui import AsyncTUI, AsyncTUIConfig
901
917
 
@@ -15,6 +15,7 @@ __all__ = [
15
15
  'traces_app',
16
16
  'env_app',
17
17
  'session_app',
18
+ 'usage_app',
18
19
  'schedule_app',
19
20
  'serve_app',
20
21
  'completion_app',
@@ -54,6 +55,9 @@ def __getattr__(name: str):
54
55
  elif name == 'session_app':
55
56
  from .session import app as session_app
56
57
  return session_app
58
+ elif name == 'usage_app':
59
+ from .usage import app as usage_app
60
+ return usage_app
57
61
  elif name == 'schedule_app':
58
62
  from .schedule import app as schedule_app
59
63
  return schedule_app
@@ -4,6 +4,7 @@ Run command group for PraisonAI CLI.
4
4
  Provides agent execution commands.
5
5
  """
6
6
 
7
+ from contextlib import contextmanager
7
8
  from typing import Any, Dict, Optional, List
8
9
 
9
10
  import typer
@@ -663,6 +664,140 @@ def _try_attach_runtime(
663
664
  return True
664
665
 
665
666
 
667
+ @contextmanager
668
+ def _worktree_isolation(enabled: bool, name: str, *, keep: bool = False):
669
+ """Run the enclosed block inside an isolated git worktree/branch.
670
+
671
+ Provisions a fresh worktree via the core ``GitWorktreeAdapter`` and chdirs
672
+ into it for the duration of the run so the agent edits an isolated branch
673
+ instead of the working tree. On exit it reports the branch name and a
674
+ summary of changes (tracked *and* untracked), then tears the worktree down.
675
+ Degrades to a transparent no-op when isolation is disabled or the directory
676
+ is not a git repository (the adapter reports ``available=False``), so callers
677
+ can wrap unconditionally.
678
+
679
+ Per-run isolation: a short random token is appended to ``name`` so two
680
+ concurrent or repeated runs of the same target never resolve to the same
681
+ worktree/branch and mix each other's uncommitted changes.
682
+
683
+ Data-safety on teardown: the agent's output may be entirely *untracked* new
684
+ files, which ``git diff`` does not see. So before removing the worktree we
685
+ snapshot any change (tracked or untracked, via ``git status --porcelain``)
686
+ onto the branch with an automatic commit and *retain that branch* — only the
687
+ worktree checkout directory is pruned. A run that produced no changes is
688
+ torn down completely (worktree + branch). ``--keep`` additionally retains the
689
+ worktree checkout itself for in-place review.
690
+ """
691
+ if not enabled:
692
+ yield None
693
+ return
694
+
695
+ import os
696
+ import subprocess
697
+ import uuid
698
+
699
+ from praisonaiagents.workspace import GitWorktreeAdapter
700
+
701
+ output = get_output_controller()
702
+ adapter = GitWorktreeAdapter()
703
+ if not adapter.available:
704
+ output.print_warning(
705
+ "Not a git repository; running without worktree isolation."
706
+ )
707
+ yield None
708
+ return
709
+
710
+ def _git(*args, cwd):
711
+ try:
712
+ return subprocess.run(
713
+ ["git", *args], cwd=cwd, capture_output=True, text=True
714
+ )
715
+ except (FileNotFoundError, OSError):
716
+ return None
717
+
718
+ def _branch_of(worktree_path: str) -> str:
719
+ result = _git("rev-parse", "--abbrev-ref", "HEAD", cwd=worktree_path)
720
+ if result is not None and result.returncode == 0:
721
+ return result.stdout.strip() or "?"
722
+ return "?"
723
+
724
+ def _has_changes(worktree_path: str) -> bool:
725
+ # ``--porcelain`` reports staged, unstaged *and* untracked entries, so a
726
+ # run that only creates brand-new files (invisible to ``git diff``) is
727
+ # still recognised as having produced output.
728
+ result = _git("status", "--porcelain", cwd=worktree_path)
729
+ if result is None or result.returncode != 0:
730
+ return False
731
+ return bool((result.stdout or "").strip())
732
+
733
+ def _summary(worktree_path: str) -> str:
734
+ result = _git("status", "--short", cwd=worktree_path)
735
+ if result is None or result.returncode != 0:
736
+ return ""
737
+ return (result.stdout or "").strip()
738
+
739
+ # Make the run's worktree/branch unique per invocation so identical targets
740
+ # (same prompt/YAML) run concurrently without sharing state.
741
+ unique_name = f"{name}-{uuid.uuid4().hex[:8]}"
742
+
743
+ original_cwd = os.getcwd()
744
+ path = adapter.create(unique_name)
745
+ branch = _branch_of(path)
746
+ if not output.is_json_mode:
747
+ output.print_info(f"Isolated run on branch '{branch}' ({path})")
748
+ os.chdir(path)
749
+ try:
750
+ yield path
751
+ finally:
752
+ os.chdir(original_cwd)
753
+
754
+ summary = _summary(path)
755
+ changed = _has_changes(path)
756
+ if not output.is_json_mode:
757
+ if summary:
758
+ output.print_info(f"Changes on '{branch}':\n{summary}")
759
+ else:
760
+ output.print_info(f"No changes on '{branch}'.")
761
+
762
+ if keep:
763
+ if not output.is_json_mode:
764
+ output.print_info(
765
+ f"Worktree kept at {path} (branch '{branch}'). "
766
+ "Review/merge then remove with: git worktree remove."
767
+ )
768
+ return
769
+
770
+ if changed:
771
+ # Never destroy the agent's output. Persist every change (tracked +
772
+ # untracked) as a commit on the isolated branch and retain the
773
+ # branch; only the worktree checkout directory is pruned.
774
+ _git("add", "-A", cwd=path)
775
+ committed = _git(
776
+ "commit", "--no-verify", "-m", f"praisonai run: {name}", cwd=path
777
+ )
778
+ if committed is None or committed.returncode != 0:
779
+ # Couldn't persist the work (e.g. no git identity configured):
780
+ # keep the worktree checkout in place so output is never lost.
781
+ if not output.is_json_mode:
782
+ output.print_warning(
783
+ f"Could not commit isolated changes; worktree kept at "
784
+ f"{path} (branch '{branch}') for manual review."
785
+ )
786
+ return
787
+ # ``worktree remove`` deletes the checkout but leaves the branch
788
+ # intact for review/merge (unlike ``adapter.remove`` which also
789
+ # force-deletes the branch and would lose the committed work).
790
+ _git("worktree", "remove", "--force", path, cwd=original_cwd)
791
+ if not output.is_json_mode:
792
+ output.print_info(
793
+ f"Committed changes to branch '{branch}'. "
794
+ f"Review/merge with: git merge {branch}"
795
+ )
796
+ else:
797
+ # No output produced: safe to remove worktree and branch entirely.
798
+ adapter.remove(unique_name)
799
+
800
+
666
801
  @app.callback(invoke_without_command=True)
667
802
  def run_main(
668
803
  ctx: typer.Context,
@@ -706,6 +841,9 @@ def run_main(
706
841
  restore: Optional[str] = typer.Option(None, "--restore", help="Restore the workspace to a checkpoint id (or 'last') and exit"),
707
842
  # Warm-runtime live session: tag this run so other terminals can `attach`.
708
843
  attach: Optional[str] = typer.Option(None, "--attach", help="Run on the warm runtime under this session id so other terminals can observe it via `praisonai attach <id>`"),
844
+ # Per-run git-worktree isolation: run on a fresh branch/worktree.
845
+ worktree: bool = typer.Option(False, "--worktree", help="Run on an isolated git worktree/branch (branch-per-task); no-op when not a git repo"),
846
+ keep: bool = typer.Option(False, "--keep", help="With --worktree, keep the worktree/branch after the run for review instead of tearing it down"),
709
847
  ):
710
848
  """
711
849
  Run agents from a file or prompt.
@@ -760,6 +898,7 @@ def run_main(
760
898
  # Early credential check before any processing
761
899
  if target: # Only check if we actually have something to run
762
900
  from praisonai_code.llm.credentials import (
901
+ detect_local_endpoint,
763
902
  inject_credentials_into_env,
764
903
  is_configured,
765
904
  )
@@ -768,43 +907,62 @@ def run_main(
768
907
  # Check if credentials are configured (use model if provided, else check general)
769
908
  inject_credentials_into_env()
770
909
  if not is_configured(model):
910
+ # Keyless local-first: when no explicit model was requested and no
911
+ # cloud key is configured, prefer a reachable local endpoint (e.g.
912
+ # Ollama) so `praisonai run "..."` just works before any auth. An
913
+ # explicit --model is left to its own provider gate.
914
+ local = detect_local_endpoint() if not model else None
915
+ if local is not None:
916
+ output.print_info(
917
+ f"No cloud key found; using local model {local.model}. "
918
+ "Run `praisonai setup` to add a hosted provider."
919
+ )
920
+ # Adopt the detected local model + base URL for this run so the
921
+ # Agent reaches the local endpoint. An explicit env base_url,
922
+ # if any, is left untouched.
923
+ import os as _os_local
924
+ model = local.model
925
+ _os_local.environ.setdefault("OPENAI_BASE_URL", local.base_url)
771
926
  # In non-interactive mode, show clear error
772
- if not sys.stdin.isatty() or output.is_json_mode:
927
+ elif not sys.stdin.isatty() or output.is_json_mode:
773
928
  output.print_error(
774
929
  "No API key configured. Run: praisonai setup\n"
775
- "or set environment variables like OPENAI_API_KEY"
930
+ "or set environment variables like OPENAI_API_KEY\n"
931
+ "(a running local endpoint such as Ollama would be used "
932
+ "automatically)"
776
933
  )
777
934
  raise typer.Exit(1)
778
935
 
779
936
  # In interactive mode, offer to run setup
780
- typer.echo(f"No API key configured{f' for model {model}' if model else ''}.")
781
- run_setup = typer.confirm("Would you like to run the setup wizard now?")
782
-
783
- if run_setup:
784
- from praisonai_code.cli.commands.setup import _run_setup
785
- exit_code = _run_setup(
786
- non_interactive=False,
787
- provider=None,
788
- api_key=None,
789
- model=None
790
- )
791
- if exit_code != 0:
792
- output.print_error("Setup failed. Exiting.")
793
- raise typer.Exit(exit_code)
794
-
795
- output.print_success("Setup complete! Continuing with your run...")
796
- # Re-check after setup
797
- inject_credentials_into_env()
798
- if not is_configured(model):
799
- output.print_error("Setup completed but credentials still not detected.")
800
- raise typer.Exit(1)
801
937
  else:
802
- output.print_info(
803
- "To configure credentials:\n"
804
- " - Run: praisonai setup\n"
805
- " - Or set environment variables like OPENAI_API_KEY"
806
- )
807
- raise typer.Exit(0)
938
+ typer.echo(f"No API key configured{f' for model {model}' if model else ''}.")
939
+ run_setup = typer.confirm("Would you like to run the setup wizard now?")
940
+
941
+ if run_setup:
942
+ from praisonai_code.cli.commands.setup import _run_setup
943
+ exit_code = _run_setup(
944
+ non_interactive=False,
945
+ provider=None,
946
+ api_key=None,
947
+ model=None
948
+ )
949
+ if exit_code != 0:
950
+ output.print_error("Setup failed. Exiting.")
951
+ raise typer.Exit(exit_code)
952
+
953
+ output.print_success("Setup complete! Continuing with your run...")
954
+ # Re-check after setup
955
+ inject_credentials_into_env()
956
+ if not is_configured(model):
957
+ output.print_error("Setup completed but credentials still not detected.")
958
+ raise typer.Exit(1)
959
+ else:
960
+ output.print_info(
961
+ "To configure credentials:\n"
962
+ " - Run: praisonai setup\n"
963
+ " - Or set environment variables like OPENAI_API_KEY"
964
+ )
965
+ raise typer.Exit(0)
808
966
 
809
967
  # Resolve configuration if model not explicitly provided
810
968
  if model is None:
@@ -828,6 +986,24 @@ def run_main(
828
986
  output.print_error("Cannot use both --continue and --session together")
829
987
  raise typer.Exit(1)
830
988
 
989
+ # Worktree isolation runs the agent in a chdir'd worktree in-process; the
990
+ # warm runtime is a separate process whose cwd we can't redirect, so reject
991
+ # the combination up front rather than silently ignoring isolation.
992
+ if worktree and attach:
993
+ output.print_error("--worktree cannot be combined with --attach")
994
+ raise typer.Exit(1)
995
+ if keep and not worktree:
996
+ output.print_error("--keep requires --worktree")
997
+ raise typer.Exit(1)
998
+ # Scope isolation to the primary `run "<task>"` / `run agents.yaml` surfaces.
999
+ # Custom agent/command and profiling flows have their own execution paths;
1000
+ # keep the feature focused rather than threading a worktree through each.
1001
+ if worktree and (agent or command or profile or profile_deep):
1002
+ output.print_error(
1003
+ "--worktree is only supported for direct prompt and YAML file runs"
1004
+ )
1005
+ raise typer.Exit(1)
1006
+
831
1007
  # --attach tags a warm-runtime run so other terminals can observe it, but
832
1008
  # only the direct-prompt path forwards to the warm runtime. Reject it up
833
1009
  # front on profile/custom-agent/custom-command flows so users never
@@ -1017,58 +1193,72 @@ def run_main(
1017
1193
  )
1018
1194
  return
1019
1195
 
1020
- if is_file:
1021
- # Run from file
1022
- _run_from_file(
1023
- target,
1024
- model=model,
1025
- framework=framework,
1026
- interactive=interactive,
1027
- verbose=verbose,
1028
- stream=stream,
1029
- trace=trace,
1030
- memory=memory,
1031
- tools=tools,
1032
- max_tokens=max_tokens,
1033
- output_mode=output_mode,
1034
- continue_session=continue_session,
1035
- session=session,
1036
- fork=fork,
1037
- no_save=no_save,
1038
- )
1039
- else:
1040
- # Run as prompt
1041
- permissions_config = _parse_permissions(allow, deny, permissions, permission_default)
1042
- mcp_command, mcp_env, permissions_config, mcp_servers = _apply_config_defaults(
1043
- None, None, permissions_config
1044
- )
1045
- _run_prompt(
1046
- target,
1047
- model=model,
1048
- verbose=verbose,
1049
- stream=stream,
1050
- trace=trace,
1051
- memory=memory,
1052
- tools=tools,
1053
- toolset=toolset,
1054
- max_tokens=max_tokens,
1055
- output_mode=output_mode,
1056
- approval=approval,
1057
- approve_all_tools=approve_all_tools,
1058
- approval_timeout=approval_timeout,
1059
- no_rules=no_rules,
1060
- permissions_config=permissions_config,
1061
- mcp=mcp_command,
1062
- mcp_env=mcp_env,
1063
- mcp_servers=mcp_servers,
1064
- continue_session=continue_session,
1065
- session=session,
1066
- fork=fork,
1067
- no_save=no_save,
1068
- attach_session=attach,
1069
- thinking_budget=thinking_budget,
1070
- allow_local_tools=allow_local_tools,
1071
- )
1196
+ # Resolve a YAML file target to an absolute path *before* isolation chdirs
1197
+ # away. The config is loaded from the original checkout so an untracked or
1198
+ # git-ignored ``./agents.yaml`` (absent from the fresh worktree) still loads;
1199
+ # the agent then produces its output inside the isolated worktree.
1200
+ run_target = target
1201
+ if worktree and is_file:
1202
+ import os as _os_ff
1203
+ run_target = _os_ff.path.abspath(target)
1204
+
1205
+ # Provision a per-run git worktree/branch when --worktree is set and the cwd
1206
+ # is a git repo; otherwise this is a transparent no-op. The agent runs with
1207
+ # its cwd redirected into the isolated worktree for the duration of the run.
1208
+ with _worktree_isolation(worktree, target, keep=keep):
1209
+ if is_file:
1210
+ # Run from file
1211
+ _run_from_file(
1212
+ run_target,
1213
+ model=model,
1214
+ framework=framework,
1215
+ interactive=interactive,
1216
+ verbose=verbose,
1217
+ stream=stream,
1218
+ trace=trace,
1219
+ memory=memory,
1220
+ tools=tools,
1221
+ max_tokens=max_tokens,
1222
+ output_mode=output_mode,
1223
+ continue_session=continue_session,
1224
+ session=session,
1225
+ fork=fork,
1226
+ no_save=no_save,
1227
+ )
1228
+ else:
1229
+ # Run as prompt
1230
+ permissions_config = _parse_permissions(allow, deny, permissions, permission_default)
1231
+ mcp_command, mcp_env, permissions_config, mcp_servers = _apply_config_defaults(
1232
+ None, None, permissions_config
1233
+ )
1234
+ _run_prompt(
1235
+ target,
1236
+ model=model,
1237
+ verbose=verbose,
1238
+ stream=stream,
1239
+ trace=trace,
1240
+ memory=memory,
1241
+ tools=tools,
1242
+ toolset=toolset,
1243
+ max_tokens=max_tokens,
1244
+ output_mode=output_mode,
1245
+ approval=approval,
1246
+ approve_all_tools=approve_all_tools,
1247
+ approval_timeout=approval_timeout,
1248
+ no_rules=no_rules,
1249
+ permissions_config=permissions_config,
1250
+ mcp=mcp_command,
1251
+ mcp_env=mcp_env,
1252
+ mcp_servers=mcp_servers,
1253
+ continue_session=continue_session,
1254
+ session=session,
1255
+ fork=fork,
1256
+ no_save=no_save,
1257
+ attach_session=attach,
1258
+ thinking_budget=thinking_budget,
1259
+ allow_local_tools=allow_local_tools,
1260
+ isolated=worktree,
1261
+ )
1072
1262
 
1073
1263
 
1074
1264
  def _run_from_file(
@@ -1203,6 +1393,7 @@ def _run_prompt(
1203
1393
  attach_session: Optional[str] = None,
1204
1394
  thinking_budget: Optional[int] = None,
1205
1395
  allow_local_tools: bool = False,
1396
+ isolated: bool = False,
1206
1397
  ):
1207
1398
  """Run a direct prompt."""
1208
1399
  output = get_output_controller()
@@ -1280,7 +1471,10 @@ def _run_prompt(
1280
1471
  # approval/memory), so it stays in-process: the warm runtime reuses a
1281
1472
  # cached agent and does not carry a per-call thinking budget, so attaching
1282
1473
  # would silently drop the requested setting.
1283
- runtime_eligible = no_save and thinking_budget is None and not any([
1474
+ # Isolated (--worktree) runs must stay in-process: the warm runtime is a
1475
+ # separate process whose cwd we can't redirect into the worktree, so
1476
+ # attaching would run the task outside the isolated branch.
1477
+ runtime_eligible = no_save and thinking_budget is None and not isolated and not any([
1284
1478
  mcp, mcp_servers, tools, toolset, approval, approve_all_tools,
1285
1479
  memory, permissions_config, continue_session, session, fork,
1286
1480
  ])
@@ -19,14 +19,23 @@ app = typer.Typer(help="Session management")
19
19
 
20
20
 
21
21
  def _create_backend(backend_type: str, storage_path: Optional[str]):
22
- """Create storage backend from CLI options."""
22
+ """Create storage backend from CLI options.
23
+
24
+ Defaults anchor under the *canonical* data home (``get_sessions_dir()`` →
25
+ ``~/.praisonai/sessions``) rather than the legacy ``~/.praison`` root, so a
26
+ backend-selected store lives under the same home as the project/global
27
+ stores instead of silently splitting sessions across two home roots
28
+ (Issue #3201).
29
+ """
23
30
  try:
31
+ from praisonaiagents.paths import get_data_dir, get_sessions_dir
32
+
24
33
  if backend_type == "file":
25
34
  from praisonaiagents.storage import FileBackend
26
- return FileBackend(storage_dir=storage_path or "~/.praison/sessions")
35
+ return FileBackend(storage_dir=storage_path or str(get_sessions_dir()))
27
36
  elif backend_type == "sqlite":
28
37
  from praisonaiagents.storage import SQLiteBackend
29
- db_path = storage_path or "~/.praison/sessions.db"
38
+ db_path = storage_path or str(get_data_dir() / "sessions.db")
30
39
  return SQLiteBackend(db_path=db_path)
31
40
  elif backend_type.startswith("redis://"):
32
41
  from praisonaiagents.storage import RedisBackend