praisonai-code 0.0.38__tar.gz → 0.0.40__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 (302) hide show
  1. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/PKG-INFO +1 -1
  2. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/__init__.py +1 -1
  3. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/command.py +69 -2
  4. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/session_checkpoints.py +118 -1
  5. praisonai_code-0.0.40/praisonai_code/cli/interactive/command_registry.py +396 -0
  6. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/repl.py +50 -1
  7. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/PKG-INFO +1 -1
  8. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/SOURCES.txt +1 -0
  9. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/pyproject.toml +1 -1
  10. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/README.md +0 -0
  11. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/__main__.py +0 -0
  12. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_bot_bridge.py +0 -0
  13. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_framework_availability.py +0 -0
  14. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_logging.py +0 -0
  15. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_registry.py +0 -0
  16. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_safe_loader.py +0 -0
  17. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_version.py +0 -0
  18. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/_wrapper_bridge.py +0 -0
  19. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/__init__.py +0 -0
  20. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/_forward_shim.py +0 -0
  21. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/_paths.py +0 -0
  22. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/_warnings.py +0 -0
  23. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/_wrapper_reexport.py +0 -0
  24. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/app.py +0 -0
  25. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/approval_backend.py +0 -0
  26. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/branding.py +0 -0
  27. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/__init__.py +0 -0
  28. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/acp.py +0 -0
  29. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/agent.py +0 -0
  30. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/agents.py +0 -0
  31. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/attach.py +0 -0
  32. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/auth.py +0 -0
  33. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/benchmark.py +0 -0
  34. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/browser.py +0 -0
  35. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/call.py +0 -0
  36. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/chat.py +0 -0
  37. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/checkpoint.py +0 -0
  38. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/code.py +0 -0
  39. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/commit.py +0 -0
  40. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/completion.py +0 -0
  41. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/config.py +0 -0
  42. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/daemon.py +0 -0
  43. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/debug.py +0 -0
  44. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/deploy.py +0 -0
  45. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/diag.py +0 -0
  46. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/doctor.py +0 -0
  47. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/endpoints.py +0 -0
  48. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/environment.py +0 -0
  49. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/eval.py +0 -0
  50. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/github.py +0 -0
  51. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/hooks.py +0 -0
  52. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/init.py +0 -0
  53. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/loop.py +0 -0
  54. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/lsp.py +0 -0
  55. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/memory.py +0 -0
  56. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/models.py +0 -0
  57. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/obs.py +0 -0
  58. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/package.py +0 -0
  59. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/paths.py +0 -0
  60. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/permissions.py +0 -0
  61. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/plugins.py +0 -0
  62. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/port.py +0 -0
  63. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/publish.py +0 -0
  64. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/registry.py +0 -0
  65. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/research.py +0 -0
  66. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/retrieval.py +0 -0
  67. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/rules.py +0 -0
  68. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/run.py +0 -0
  69. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/sandbox.py +0 -0
  70. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/serve.py +0 -0
  71. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/session.py +0 -0
  72. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/setup.py +0 -0
  73. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/skills.py +0 -0
  74. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/templates.py +0 -0
  75. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/test.py +0 -0
  76. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/todo.py +0 -0
  77. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/tools.py +0 -0
  78. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/traces.py +0 -0
  79. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/tracker.py +0 -0
  80. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/ui.py +0 -0
  81. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/up.py +0 -0
  82. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/version.py +0 -0
  83. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/commands/workflow.py +0 -0
  84. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/config_loader.py +0 -0
  85. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/__init__.py +0 -0
  86. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/config.schema.json +0 -0
  87. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/credentials.py +0 -0
  88. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/loader.py +0 -0
  89. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/model_resolver.py +0 -0
  90. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/oauth.py +0 -0
  91. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/paths.py +0 -0
  92. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/resolver.py +0 -0
  93. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/configuration/schema.py +0 -0
  94. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/execution/__init__.py +0 -0
  95. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/execution/core.py +0 -0
  96. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/execution/profiler.py +0 -0
  97. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/execution/request.py +0 -0
  98. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/execution/result.py +0 -0
  99. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/fallback_schema.py +0 -0
  100. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/__init__.py +0 -0
  101. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/_approval_bridge.py +0 -0
  102. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/_endpoint_registry.py +0 -0
  103. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/_search_registry.py +0 -0
  104. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/action_orchestrator.py +0 -0
  105. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/agents.py +0 -0
  106. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/at_mentions.py +0 -0
  107. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/audit_cli.py +0 -0
  108. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/auto_memory.py +0 -0
  109. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/auto_mode.py +0 -0
  110. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/autonomy_mode.py +0 -0
  111. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/base.py +0 -0
  112. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/benchmark.py +0 -0
  113. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/capabilities.py +0 -0
  114. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/checkpoints.py +0 -0
  115. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/cli_profiler.py +0 -0
  116. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/code_intelligence.py +0 -0
  117. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/compaction.py +0 -0
  118. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/compare.py +0 -0
  119. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/config_hierarchy.py +0 -0
  120. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/context_manager.py +0 -0
  121. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/cost_tracker.py +0 -0
  122. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/csv_test_runner.py +0 -0
  123. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/custom_definitions.py +0 -0
  124. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/debug.py +0 -0
  125. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/diag.py +0 -0
  126. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/display_jsonl.py +0 -0
  127. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/__init__.py +0 -0
  128. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/__init__.py +0 -0
  129. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/_wrapper_checks.py +0 -0
  130. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/acp_checks.py +0 -0
  131. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/bot_checks.py +0 -0
  132. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/config_checks.py +0 -0
  133. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/db_checks.py +0 -0
  134. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/env_checks.py +0 -0
  135. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/gateway_checks.py +0 -0
  136. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/lsp_checks.py +0 -0
  137. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/mcp_checks.py +0 -0
  138. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/memory_checks.py +0 -0
  139. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/network_checks.py +0 -0
  140. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/obs_checks.py +0 -0
  141. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/packaging_checks.py +0 -0
  142. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/performance_checks.py +0 -0
  143. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/permissions_checks.py +0 -0
  144. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/runtime_checks.py +0 -0
  145. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +0 -0
  146. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/selftest_checks.py +0 -0
  147. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/serve_checks.py +0 -0
  148. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/skills_checks.py +0 -0
  149. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/checks/tools_checks.py +0 -0
  150. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/engine.py +0 -0
  151. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/formatters.py +0 -0
  152. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/handler.py +0 -0
  153. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/models.py +0 -0
  154. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/doctor/registry.py +0 -0
  155. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/endpoints.py +0 -0
  156. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/examples.py +0 -0
  157. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/external_agents.py +0 -0
  158. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/fast_context.py +0 -0
  159. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/file_history.py +0 -0
  160. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/flow_display.py +0 -0
  161. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/git_attribution.py +0 -0
  162. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/git_integration.py +0 -0
  163. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/guardrail.py +0 -0
  164. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/handoff.py +0 -0
  165. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/hooks.py +0 -0
  166. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/hybrid_workflow.py +0 -0
  167. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/image.py +0 -0
  168. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/interactive_core_headless.py +0 -0
  169. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/interactive_runtime.py +0 -0
  170. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/interactive_test_harness.py +0 -0
  171. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/interactive_tools.py +0 -0
  172. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/interactive_tui.py +0 -0
  173. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/job_workflow.py +0 -0
  174. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/jobs.py +0 -0
  175. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/knowledge.py +0 -0
  176. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/knowledge_cli.py +0 -0
  177. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/lite.py +0 -0
  178. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/logs.py +0 -0
  179. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/lsp_cli.py +0 -0
  180. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/lsp_diagnostics.py +0 -0
  181. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/mcp.py +0 -0
  182. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/message_queue.py +0 -0
  183. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/metrics.py +0 -0
  184. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/migrate.py +0 -0
  185. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/migration_flow.py +0 -0
  186. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/migration_spec.py +0 -0
  187. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/n8n.py +0 -0
  188. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/observability.py +0 -0
  189. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/output_modes.py +0 -0
  190. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/output_style.py +0 -0
  191. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/package.py +0 -0
  192. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/performance.py +0 -0
  193. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/profiler/__init__.py +0 -0
  194. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/profiler/core.py +0 -0
  195. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/profiler/optimizations.py +0 -0
  196. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/profiler/suite.py +0 -0
  197. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/__init__.py +0 -0
  198. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/manager.py +0 -0
  199. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/models.py +0 -0
  200. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/persistence.py +0 -0
  201. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/scheduler.py +0 -0
  202. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/queue/worker.py +0 -0
  203. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/recipe_creator.py +0 -0
  204. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/recipe_prompts.py +0 -0
  205. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/repo_map.py +0 -0
  206. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/router.py +0 -0
  207. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/safe_shell.py +0 -0
  208. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/sandbox_executor.py +0 -0
  209. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/sdk_knowledge.py +0 -0
  210. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/session.py +0 -0
  211. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/setup/__init__.py +0 -0
  212. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/setup/handler.py +0 -0
  213. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/setup/templates.py +0 -0
  214. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/skills.py +0 -0
  215. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/slash_commands.py +0 -0
  216. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/telemetry.py +0 -0
  217. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/thinking.py +0 -0
  218. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/todo.py +0 -0
  219. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tools.py +0 -0
  220. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/__init__.py +0 -0
  221. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/cli.py +0 -0
  222. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/config.py +0 -0
  223. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/debug.py +0 -0
  224. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/events.py +0 -0
  225. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/mock_provider.py +0 -0
  226. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/orchestrator.py +0 -0
  227. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/__init__.py +0 -0
  228. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/help.py +0 -0
  229. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/main.py +0 -0
  230. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/queue.py +0 -0
  231. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/session.py +0 -0
  232. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/screens/settings.py +0 -0
  233. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/session_store.py +0 -0
  234. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/__init__.py +0 -0
  235. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/chat.py +0 -0
  236. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/command_popup.py +0 -0
  237. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/composer.py +0 -0
  238. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/file_popup.py +0 -0
  239. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/queue_panel.py +0 -0
  240. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/status.py +0 -0
  241. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/tui/widgets/tool_panel.py +0 -0
  242. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/wizard.py +0 -0
  243. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/workflow.py +0 -0
  244. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/features/yaml_utils.py +0 -0
  245. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/__init__.py +0 -0
  246. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/async_tui.py +0 -0
  247. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/config.py +0 -0
  248. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/core.py +0 -0
  249. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/events.py +0 -0
  250. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/frontends/__init__.py +0 -0
  251. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/frontends/rich_frontend.py +0 -0
  252. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/frontends/textual_frontend.py +0 -0
  253. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/praison_io.py +0 -0
  254. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/split_tui.py +0 -0
  255. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/interactive/tui_app.py +0 -0
  256. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/legacy/__init__.py +0 -0
  257. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/legacy/env_security.py +0 -0
  258. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/legacy/praison_ai.py +0 -0
  259. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/legacy/prompt_dispatch.py +0 -0
  260. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/main.py +0 -0
  261. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/output/__init__.py +0 -0
  262. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/output/console.py +0 -0
  263. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/output/event_bridge.py +0 -0
  264. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/schedule_cli.py +0 -0
  265. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/schema_provider.py +0 -0
  266. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/session/__init__.py +0 -0
  267. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/session/resume.py +0 -0
  268. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/session/unified.py +0 -0
  269. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/state/__init__.py +0 -0
  270. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/state/identifiers.py +0 -0
  271. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/state/project_sessions.py +0 -0
  272. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/state/sessions.py +0 -0
  273. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/__init__.py +0 -0
  274. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/config.py +0 -0
  275. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/events.py +0 -0
  276. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/mg_backend.py +0 -0
  277. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/plain.py +0 -0
  278. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/ui/rich_backend.py +0 -0
  279. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/unified_schema.py +0 -0
  280. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/utils/env_utils.py +0 -0
  281. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/utils/project.py +0 -0
  282. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli/utils/stdin.py +0 -0
  283. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli_backends/__init__.py +0 -0
  284. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli_backends/claude.py +0 -0
  285. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/cli_backends/registry.py +0 -0
  286. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/llm/__init__.py +0 -0
  287. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/llm/catalogue.py +0 -0
  288. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/llm/config.py +0 -0
  289. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/llm/credentials.py +0 -0
  290. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/llm/env.py +0 -0
  291. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/runtime/__init__.py +0 -0
  292. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/runtime/__main__.py +0 -0
  293. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/runtime/client.py +0 -0
  294. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/runtime/descriptor.py +0 -0
  295. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/runtime/server.py +0 -0
  296. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/tool_registry.py +0 -0
  297. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code/tool_resolver.py +0 -0
  298. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/dependency_links.txt +0 -0
  299. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/entry_points.txt +0 -0
  300. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/requires.txt +0 -0
  301. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/praisonai_code.egg-info/top_level.txt +0 -0
  302. {praisonai_code-0.0.38 → praisonai_code-0.0.40}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: praisonai-code
3
- Version: 0.0.38
3
+ Version: 0.0.40
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.38"
15
+ __version__ = "0.0.40"
16
16
 
17
17
  __all__ = ["__version__"]
@@ -16,10 +16,24 @@ app = typer.Typer(help="Manage custom commands")
16
16
  @app.command(name="list")
17
17
  def list_commands(
18
18
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed information"),
19
+ all_sources: bool = typer.Option(
20
+ False,
21
+ "--all",
22
+ "-a",
23
+ help="List the full interactive command namespace (built-ins, custom, skills, MCP)",
24
+ ),
19
25
  ):
20
- """List all discovered custom commands."""
26
+ """List all discovered custom commands.
27
+
28
+ By default only custom ``.praisonai/commands/*.md`` commands are shown.
29
+ Pass ``--all`` to list the same unified namespace the interactive session
30
+ resolves ``/name`` from, so listing and invocation cannot drift.
31
+ """
21
32
  output = get_output_controller()
22
-
33
+
34
+ if all_sources:
35
+ return _list_registry(output)
36
+
23
37
  try:
24
38
  from praisonai_code.cli.features.custom_definitions import CustomDefinitionsDiscovery
25
39
 
@@ -109,6 +123,59 @@ def show(
109
123
  syntax = Syntax(preview_text, "markdown", theme="monokai", line_numbers=False)
110
124
  console.print(Panel(syntax, title=f"Preview with args: '{sample_args}'", border_style="yellow"))
111
125
 
126
+ except Exception as e:
127
+ output.print_error(str(e))
128
+ raise typer.Exit(1)
129
+
130
+
131
+ def _list_registry(output) -> None:
132
+ """List the full unified command namespace via ``CommandRegistry``.
133
+
134
+ This is the same registry every interactive surface drives ``/name``
135
+ resolution and ``/help`` from, so what is listed here is exactly what can
136
+ be invoked interactively.
137
+ """
138
+ try:
139
+ from praisonai_code.cli.interactive.command_registry import (
140
+ create_default_registry,
141
+ )
142
+
143
+ try:
144
+ from praisonai_code.cli.interactive.repl import DEFAULT_COMMANDS as builtins
145
+ except Exception: # pragma: no cover - defensive
146
+ builtins = None
147
+
148
+ registry = create_default_registry(
149
+ builtins,
150
+ include_custom=True,
151
+ include_skills=True,
152
+ )
153
+ commands = registry.list_commands()
154
+
155
+ if not commands:
156
+ output.print_info("No commands found.")
157
+ return
158
+
159
+ from rich.console import Console
160
+ from rich.table import Table
161
+
162
+ console = Console()
163
+ table = Table(title="Commands", show_header=True)
164
+ table.add_column("Name", style="cyan")
165
+ table.add_column("Kind", style="magenta")
166
+ table.add_column("Source", style="yellow")
167
+ table.add_column("Description", style="green")
168
+
169
+ for cmd in commands:
170
+ table.add_row(
171
+ cmd.name,
172
+ cmd.kind.value,
173
+ cmd.source,
174
+ cmd.description or "No description",
175
+ )
176
+
177
+ console.print(table)
178
+
112
179
  except Exception as e:
113
180
  output.print_error(str(e))
114
181
  raise typer.Exit(1)
@@ -33,6 +33,11 @@ class _Turn:
33
33
  checkpoint_id: str
34
34
  short_id: str
35
35
  message: str
36
+ # Number of conversation messages captured at turn start. Used to rewind
37
+ # the conversation store to the same boundary as the file checkpoint so
38
+ # files and history stay coherent after an undo. ``None`` when no
39
+ # conversation store is wired in (legacy / file-only sessions).
40
+ message_count: Optional[int] = None
36
41
 
37
42
 
38
43
  @dataclass
@@ -54,6 +59,12 @@ class SessionCheckpointManager:
54
59
  enabled: bool = False
55
60
  verbose: bool = False
56
61
  storage_dir: Optional[str] = None
62
+ # Optional conversation store + session id. When both are set, a revert
63
+ # rewinds the conversation history to the same turn boundary as the file
64
+ # checkpoint, keeping files and chat_history coherent. Left unset for
65
+ # file-only / legacy sessions, which keeps existing behaviour unchanged.
66
+ session_store: Optional[object] = None
67
+ session_id: Optional[str] = None
57
68
  _handler: Optional[object] = field(default=None, repr=False)
58
69
  _turns: List[_Turn] = field(default_factory=list)
59
70
  _turn_counter: int = 0
@@ -65,6 +76,8 @@ class SessionCheckpointManager:
65
76
  workspace_dir: Optional[str] = None,
66
77
  config: Optional[dict] = None,
67
78
  verbose: bool = False,
79
+ session_store: Optional[object] = None,
80
+ session_id: Optional[str] = None,
68
81
  ) -> "SessionCheckpointManager":
69
82
  """
70
83
  Build a manager from a resolved ``checkpoints`` config section.
@@ -90,6 +103,8 @@ class SessionCheckpointManager:
90
103
  enabled=enabled,
91
104
  verbose=verbose,
92
105
  storage_dir=storage_dir,
106
+ session_store=session_store,
107
+ session_id=session_id,
93
108
  )
94
109
 
95
110
  def _get_handler(self):
@@ -121,6 +136,68 @@ class SessionCheckpointManager:
121
136
  def turns(self) -> List[_Turn]:
122
137
  return list(self._turns)
123
138
 
139
+ def _conversation_message_count(self) -> Optional[int]:
140
+ """
141
+ Best-effort count of messages currently in the conversation store.
142
+
143
+ Returns ``None`` when no conversation store is wired in or the count
144
+ cannot be determined, so callers can skip conversation bookkeeping and
145
+ preserve the previous file-only behaviour.
146
+ """
147
+ store = self.session_store
148
+ if store is None or not self.session_id:
149
+ return None
150
+ try:
151
+ getter = getattr(store, "get_messages", None)
152
+ if callable(getter):
153
+ messages = getter(self.session_id)
154
+ if messages is not None:
155
+ return len(messages)
156
+ session = getattr(store, "get_extended_session", None)
157
+ if callable(session):
158
+ data = session(self.session_id)
159
+ msgs = getattr(data, "messages", None)
160
+ if msgs is not None:
161
+ return len(msgs)
162
+ except Exception:
163
+ pass
164
+ return None
165
+
166
+ def _rewind_conversation(self, message_count: Optional[int]) -> None:
167
+ """
168
+ Rewind the conversation store to ``message_count`` messages.
169
+
170
+ Uses the core ``revert_to_message`` primitive so that undoing a turn
171
+ drops the assistant/tool messages produced by the reverted turn(s),
172
+ keeping ``chat_history`` consistent with the restored files. Best-effort
173
+ and non-raising: a conversation-revert failure must not corrupt the
174
+ already-restored workspace.
175
+ """
176
+ store = self.session_store
177
+ if store is None or not self.session_id or message_count is None:
178
+ return
179
+ try:
180
+ if message_count <= 0:
181
+ # Keep nothing. The core ``revert_to_message`` contract keeps
182
+ # ``messages[:index + 1]`` and rejects out-of-range indices, so
183
+ # there is no index that clears to empty (index 0 would leave a
184
+ # stale first message). Clear explicitly where the store exposes
185
+ # a primitive; otherwise no-op safely rather than leaving a ghost
186
+ # message behind.
187
+ for name in ("clear_messages", "clear_history", "clear"):
188
+ clear = getattr(store, name, None)
189
+ if callable(clear):
190
+ clear(self.session_id)
191
+ return
192
+ return
193
+ revert = getattr(store, "revert_to_message", None)
194
+ if callable(revert):
195
+ # revert_to_message keeps messages[:index + 1]; to keep
196
+ # ``message_count`` messages we target the last kept index.
197
+ revert(self.session_id, message_count - 1)
198
+ except Exception:
199
+ pass
200
+
124
201
  def checkpoint_turn(self, message: str) -> Optional[str]:
125
202
  """
126
203
  Save a checkpoint for the upcoming turn and record it on the timeline.
@@ -144,6 +221,10 @@ class SessionCheckpointManager:
144
221
  result = await service.save(label, allow_empty=True)
145
222
  return result
146
223
 
224
+ # Capture the conversation boundary *before* the turn runs so a
225
+ # later revert rewinds history to exactly this point.
226
+ message_count = self._conversation_message_count()
227
+
147
228
  result = self._run(_save())
148
229
  if result and getattr(result, "success", False):
149
230
  cp = result.checkpoint
@@ -153,6 +234,7 @@ class SessionCheckpointManager:
153
234
  checkpoint_id=cp.id,
154
235
  short_id=cp.short_id,
155
236
  message=label,
237
+ message_count=message_count,
156
238
  )
157
239
  )
158
240
  self._initialized = True
@@ -181,6 +263,11 @@ class SessionCheckpointManager:
181
263
  handler = self._get_handler()
182
264
  ok = self._run(handler.restore(target.checkpoint_id))
183
265
  if ok:
266
+ # Files are restored; now rewind the conversation history to the
267
+ # same turn boundary so the agent's chat_history no longer
268
+ # references the edits that were just undone. Best-effort: a
269
+ # conversation-revert failure leaves the restored files intact.
270
+ self._rewind_conversation(target.message_count)
184
271
  # The workspace now matches `target`'s checkpoint. Drop the
185
272
  # restored turn *and* every turn above it from the timeline so
186
273
  # the next /undo walks further back instead of restoring the
@@ -192,8 +279,29 @@ class SessionCheckpointManager:
192
279
  pass
193
280
  return None
194
281
 
282
+ def dropped_message_count(self, n: int = 1) -> Optional[int]:
283
+ """
284
+ How many conversation messages ``revert(n)`` would drop.
285
+
286
+ Returns ``None`` when no conversation store is wired in or the boundary
287
+ was not captured, so callers can present a file-only preview unchanged.
288
+ """
289
+ if not self.enabled or not self._turns:
290
+ return None
291
+ if n < 1:
292
+ n = 1
293
+ if n > len(self._turns):
294
+ return None
295
+ target = self._turns[-n]
296
+ if target.message_count is None:
297
+ return None
298
+ current = self._conversation_message_count()
299
+ if current is None:
300
+ return None
301
+ return max(0, current - target.message_count)
302
+
195
303
  def preview(self, n: int = 1) -> None:
196
- """Print the diff that ``revert(n)`` would undo (best-effort)."""
304
+ """Print the diff and conversation rollback that ``revert(n)`` would undo."""
197
305
  if not self.enabled or not self._turns:
198
306
  return
199
307
  if n < 1:
@@ -206,3 +314,12 @@ class SessionCheckpointManager:
206
314
  self._run(handler.diff(target.checkpoint_id, None))
207
315
  except Exception:
208
316
  pass
317
+ try:
318
+ dropped = self.dropped_message_count(n)
319
+ if dropped:
320
+ print(
321
+ f"This will also drop {dropped} conversation "
322
+ f"message(s) to keep history in sync with the files."
323
+ )
324
+ except Exception:
325
+ pass
@@ -0,0 +1,396 @@
1
+ """
2
+ Unified command registry for interactive slash commands.
3
+
4
+ This module aggregates invocable commands from several pluggable sources into
5
+ one namespace so every interactive surface (async TUI, REPL, and future
6
+ frontends) resolves ``/name``, ``/help``, and autocomplete from the same place.
7
+
8
+ Sources (later sources override earlier ones on name collision):
9
+
10
+ 1. Built-in commands (registered by each interactive surface).
11
+ 2. User/project custom commands discovered from ``.praisonai/commands/*.md``
12
+ via :class:`CustomDefinitionsDiscovery` — the exact same commands that
13
+ ``praisonai run --command <name>`` executes.
14
+ 3. Skills (``praisonaiagents.skills``) — optional, discovered lazily.
15
+ 4. MCP server prompts — optional, discovered lazily.
16
+ 5. Third-party sources registered via the ``praisonai.commands`` entry-point
17
+ group, mirroring the existing ``praisonai.tool_sources`` extensibility.
18
+
19
+ The registry is intentionally lightweight: it only *describes* commands.
20
+ Execution of custom commands reuses the shared ``interpolate_command_template``
21
+ path so interactive and CLI invocation are byte-for-byte identical.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from dataclasses import dataclass, field
28
+ from enum import Enum
29
+ from typing import Any, Callable, Dict, List, Optional
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class CommandKind(str, Enum):
35
+ """Origin of a command, used for help grouping and behaviour."""
36
+
37
+ BUILTIN = "builtin"
38
+ CUSTOM = "custom"
39
+ SKILL = "skill"
40
+ MCP = "mcp"
41
+ PLUGIN = "plugin"
42
+
43
+
44
+ @dataclass
45
+ class Command:
46
+ """A single invocable slash command descriptor.
47
+
48
+ Attributes:
49
+ name: Primary invocation name (without the leading ``/``).
50
+ description: Short one-line help text.
51
+ kind: Where the command came from.
52
+ source: Free-form origin label (``user``/``project``/``builtin``/...).
53
+ handler: Optional callable that performs the command. Built-in surfaces
54
+ usually leave this ``None`` and dispatch via their own branches;
55
+ custom/skill/mcp commands carry a handler that returns the prompt
56
+ text (or performs the action) when invoked.
57
+ aliases: Alternative names that resolve to this command.
58
+ """
59
+
60
+ name: str
61
+ description: str = ""
62
+ kind: CommandKind = CommandKind.BUILTIN
63
+ source: str = "builtin"
64
+ handler: Optional[Callable[[str], Any]] = None
65
+ aliases: List[str] = field(default_factory=list)
66
+
67
+
68
+ class CommandSourceProtocol:
69
+ """Structural protocol for a command source.
70
+
71
+ A source yields :class:`Command` descriptors. Any object exposing a
72
+ ``discover() -> List[Command]`` method is a valid source; using a plain
73
+ base class (rather than ``typing.Protocol``) keeps runtime ``isinstance``
74
+ checks cheap and avoids importing ``typing_extensions`` on older Pythons.
75
+ """
76
+
77
+ def discover(self) -> List[Command]: # pragma: no cover - interface
78
+ raise NotImplementedError
79
+
80
+
81
+ class BuiltinCommandSource:
82
+ """Wraps a static mapping of built-in commands into a source.
83
+
84
+ Interactive surfaces pass their existing hard-coded ``{name: description}``
85
+ help mapping here so the built-ins become first-class registry entries with
86
+ no behaviour change (the surface still dispatches them via its own
87
+ branches; the registry only powers help/autocomplete/collision handling).
88
+ """
89
+
90
+ def __init__(self, commands: Dict[str, str]):
91
+ self._commands = dict(commands)
92
+
93
+ def discover(self) -> List[Command]:
94
+ return [
95
+ Command(
96
+ name=name,
97
+ description=description,
98
+ kind=CommandKind.BUILTIN,
99
+ source="builtin",
100
+ )
101
+ for name, description in self._commands.items()
102
+ ]
103
+
104
+
105
+ class CustomCommandSource:
106
+ """Source backed by ``.praisonai/commands/*.md`` custom command files.
107
+
108
+ Reuses :class:`CustomDefinitionsDiscovery` (the same discovery that powers
109
+ ``praisonai run --command``) so interactive ``/name`` invocation stays in
110
+ lock-step with the CLI. Each command's handler interpolates the template
111
+ via the shared ``interpolate_command_template`` helper.
112
+ """
113
+
114
+ def discover(self) -> List[Command]:
115
+ try:
116
+ from ..features.custom_definitions import (
117
+ CustomDefinitionsDiscovery,
118
+ interpolate_command_template,
119
+ )
120
+ except ImportError: # pragma: no cover - defensive
121
+ return []
122
+
123
+ try:
124
+ discovery = CustomDefinitionsDiscovery()
125
+ discovery.discover()
126
+ except Exception as exc: # pragma: no cover - defensive
127
+ logger.warning("Custom command discovery failed: %s", exc)
128
+ return []
129
+
130
+ commands: List[Command] = []
131
+ for cmd in discovery.list_commands():
132
+ def make_handler(cmd_name: str) -> Callable[[str], Optional[str]]:
133
+ def handler(args: str) -> Optional[str]:
134
+ return interpolate_command_template(cmd_name, args)
135
+
136
+ return handler
137
+
138
+ commands.append(
139
+ Command(
140
+ name=cmd.name,
141
+ description=cmd.description
142
+ or f"Custom command from {cmd.source}",
143
+ kind=CommandKind.CUSTOM,
144
+ source=cmd.source,
145
+ handler=make_handler(cmd.name),
146
+ )
147
+ )
148
+ return commands
149
+
150
+
151
+ class SkillCommandSource:
152
+ """Optional source exposing discovered skills as invocable commands.
153
+
154
+ Skills live in the core SDK (``praisonaiagents.skills``); this source only
155
+ *consumes* them. It fails silently (returns ``[]``) when the skills module
156
+ or any project skills are unavailable, so it never adds import cost or
157
+ breaks surfaces that do not use skills.
158
+ """
159
+
160
+ def discover(self) -> List[Command]:
161
+ try:
162
+ from praisonaiagents.skills.discovery import discover_skills
163
+ except Exception:
164
+ return []
165
+
166
+ try:
167
+ skills = discover_skills()
168
+ except Exception as exc: # pragma: no cover - defensive
169
+ logger.debug("Skill discovery unavailable: %s", exc)
170
+ return []
171
+
172
+ commands: List[Command] = []
173
+ for skill in skills or []:
174
+ name = getattr(skill, "name", None)
175
+ if not name:
176
+ continue
177
+ commands.append(
178
+ Command(
179
+ name=str(name),
180
+ description=getattr(skill, "description", "") or "Skill",
181
+ kind=CommandKind.SKILL,
182
+ source="skill",
183
+ )
184
+ )
185
+ return commands
186
+
187
+
188
+ class MCPPromptCommandSource:
189
+ """Optional source exposing MCP server prompts as invocable commands.
190
+
191
+ MCP is a core protocol; this source consumes prompts exposed by configured
192
+ MCP servers. It is best-effort and lazy: any failure yields ``[]`` so the
193
+ registry degrades gracefully when MCP is not configured or installed.
194
+ """
195
+
196
+ def __init__(self, prompts: Optional[List[Any]] = None):
197
+ self._prompts = prompts
198
+
199
+ def discover(self) -> List[Command]:
200
+ prompts = self._prompts
201
+ if prompts is None:
202
+ return []
203
+
204
+ commands: List[Command] = []
205
+ for prompt in prompts:
206
+ name = getattr(prompt, "name", None) or (
207
+ prompt.get("name") if isinstance(prompt, dict) else None
208
+ )
209
+ if not name:
210
+ continue
211
+ description = getattr(prompt, "description", None) or (
212
+ prompt.get("description") if isinstance(prompt, dict) else ""
213
+ )
214
+ commands.append(
215
+ Command(
216
+ name=str(name),
217
+ description=description or "MCP prompt",
218
+ kind=CommandKind.MCP,
219
+ source="mcp",
220
+ )
221
+ )
222
+ return commands
223
+
224
+
225
+ # Entry-point group third parties use to contribute command sources, mirroring
226
+ # the existing ``praisonai.tool_sources`` extensibility for tools.
227
+ ENTRY_POINT_GROUP = "praisonai.commands"
228
+
229
+
230
+ def _discover_entry_point_sources() -> List[Any]:
231
+ """Load command sources registered via the ``praisonai.commands`` group.
232
+
233
+ Each entry point may resolve to either a source instance or a
234
+ zero-argument callable/class returning a source. Failures are logged and
235
+ skipped so a broken third-party plugin never takes down the registry.
236
+ """
237
+ sources: List[Any] = []
238
+ try:
239
+ from importlib.metadata import entry_points
240
+ except Exception: # pragma: no cover - very old Pythons
241
+ return sources
242
+
243
+ try:
244
+ eps = entry_points()
245
+ # Python 3.10+ returns a SelectableGroups object.
246
+ if hasattr(eps, "select"):
247
+ group = eps.select(group=ENTRY_POINT_GROUP)
248
+ else: # pragma: no cover - legacy mapping API
249
+ group = eps.get(ENTRY_POINT_GROUP, [])
250
+ except Exception as exc: # pragma: no cover - defensive
251
+ logger.debug("Entry-point command discovery failed: %s", exc)
252
+ return sources
253
+
254
+ for ep in group:
255
+ try:
256
+ obj = ep.load()
257
+ source = obj() if callable(obj) else obj
258
+ if hasattr(source, "discover"):
259
+ sources.append(source)
260
+ except Exception as exc: # pragma: no cover - defensive
261
+ logger.warning("Failed to load command source %r: %s", ep, exc)
262
+ return sources
263
+
264
+
265
+ class CommandRegistry:
266
+ """Aggregates command sources into a single ``/name`` namespace.
267
+
268
+ Later sources override earlier ones on name collision, matching the
269
+ user/project override semantics of :class:`CustomDefinitionsDiscovery`.
270
+ """
271
+
272
+ def __init__(
273
+ self,
274
+ sources: Optional[List[Any]] = None,
275
+ *,
276
+ discover_entry_points: bool = True,
277
+ ):
278
+ self._sources: List[Any] = list(sources or [])
279
+ self._discover_entry_points = discover_entry_points
280
+ self._commands: Dict[str, Command] = {}
281
+ self._aliases: Dict[str, str] = {}
282
+ self._discovered = False
283
+
284
+ def add_source(self, source: Any) -> None:
285
+ """Append a command source (invalidates the discovery cache)."""
286
+ self._sources.append(source)
287
+ self._discovered = False
288
+
289
+ def discover(self, force: bool = False) -> Dict[str, Command]:
290
+ """Resolve all sources into ``{name: Command}`` (cached)."""
291
+ if self._discovered and not force:
292
+ return self._commands
293
+
294
+ self._commands.clear()
295
+ self._aliases.clear()
296
+
297
+ sources = list(self._sources)
298
+ if self._discover_entry_points:
299
+ sources.extend(_discover_entry_point_sources())
300
+
301
+ for source in sources:
302
+ try:
303
+ for command in source.discover():
304
+ existing = self._commands.get(command.name)
305
+ # Built-in commands are dispatched by the interactive
306
+ # surface's own branches, which fire *before* the registry
307
+ # is consulted. A later (custom/skill/mcp) source that
308
+ # reuses a built-in name would therefore be registered but
309
+ # never actually execute. Keep the built-in so listing and
310
+ # runtime behaviour stay consistent, and warn about the
311
+ # shadowed definition rather than silently dropping it.
312
+ if (
313
+ existing is not None
314
+ and existing.kind is CommandKind.BUILTIN
315
+ and command.kind is not CommandKind.BUILTIN
316
+ ):
317
+ logger.warning(
318
+ "Command /%s from %s is shadowed by a built-in "
319
+ "command and will not be invoked interactively.",
320
+ command.name,
321
+ command.source,
322
+ )
323
+ continue
324
+ self._commands[command.name] = command
325
+ for alias in command.aliases:
326
+ self._aliases[alias] = command.name
327
+ except Exception as exc: # pragma: no cover - defensive
328
+ logger.warning("Command source %r failed: %s", source, exc)
329
+
330
+ self._discovered = True
331
+ return self._commands
332
+
333
+ def get(self, name: str) -> Optional[Command]:
334
+ """Look up a command by name or alias."""
335
+ self.discover()
336
+ if name in self._commands:
337
+ return self._commands[name]
338
+ primary = self._aliases.get(name)
339
+ if primary:
340
+ return self._commands.get(primary)
341
+ return None
342
+
343
+ def has(self, name: str) -> bool:
344
+ """Return True if ``name`` resolves to a command."""
345
+ return self.get(name) is not None
346
+
347
+ def list_commands(self) -> List[Command]:
348
+ """Return all commands sorted by name."""
349
+ self.discover()
350
+ return sorted(self._commands.values(), key=lambda c: c.name)
351
+
352
+ def names(self) -> List[str]:
353
+ """Return all command names and aliases, sorted."""
354
+ self.discover()
355
+ return sorted(set(self._commands) | set(self._aliases))
356
+
357
+ def completions(self, partial: str) -> List[str]:
358
+ """Return ``/name`` completions for a partial ``/`` input."""
359
+ if not partial.startswith("/"):
360
+ return []
361
+ stub = partial[1:].lower()
362
+ return sorted(
363
+ f"/{name}" for name in self.names() if name.lower().startswith(stub)
364
+ )
365
+
366
+
367
+ def create_default_registry(
368
+ builtins: Optional[Dict[str, str]] = None,
369
+ *,
370
+ include_custom: bool = True,
371
+ include_skills: bool = False,
372
+ include_mcp: bool = False,
373
+ mcp_prompts: Optional[List[Any]] = None,
374
+ discover_entry_points: bool = True,
375
+ ) -> CommandRegistry:
376
+ """Build a registry wired with the standard sources.
377
+
378
+ Args:
379
+ builtins: Optional ``{name: description}`` mapping of a surface's
380
+ built-in commands, registered first (lowest precedence).
381
+ include_custom: Register the ``.praisonai/commands/*.md`` source.
382
+ include_skills: Register the (optional) skills source.
383
+ include_mcp: Register the (optional) MCP prompt source.
384
+ mcp_prompts: Pre-resolved MCP prompts to expose (avoids a live probe).
385
+ discover_entry_points: Scan the ``praisonai.commands`` entry-point group.
386
+ """
387
+ sources: List[Any] = []
388
+ if builtins:
389
+ sources.append(BuiltinCommandSource(builtins))
390
+ if include_custom:
391
+ sources.append(CustomCommandSource())
392
+ if include_skills:
393
+ sources.append(SkillCommandSource())
394
+ if include_mcp:
395
+ sources.append(MCPPromptCommandSource(mcp_prompts))
396
+ return CommandRegistry(sources, discover_entry_points=discover_entry_points)