praisonai-code 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. praisonai_code/__init__.py +17 -0
  2. praisonai_code/cli/__init__.py +12 -0
  3. praisonai_code/cli/_forward_shim.py +10 -0
  4. praisonai_code/cli/_paths.py +88 -0
  5. praisonai_code/cli/_warnings.py +58 -0
  6. praisonai_code/cli/app.py +757 -0
  7. praisonai_code/cli/approval_backend.py +272 -0
  8. praisonai_code/cli/branding.py +94 -0
  9. praisonai_code/cli/commands/__init__.py +114 -0
  10. praisonai_code/cli/commands/acp.py +80 -0
  11. praisonai_code/cli/commands/agent.py +116 -0
  12. praisonai_code/cli/commands/agents.py +80 -0
  13. praisonai_code/cli/commands/app.py +139 -0
  14. praisonai_code/cli/commands/attach.py +95 -0
  15. praisonai_code/cli/commands/audit.py +102 -0
  16. praisonai_code/cli/commands/auth.py +508 -0
  17. praisonai_code/cli/commands/batch.py +848 -0
  18. praisonai_code/cli/commands/benchmark.py +286 -0
  19. praisonai_code/cli/commands/browser.py +299 -0
  20. praisonai_code/cli/commands/call.py +45 -0
  21. praisonai_code/cli/commands/chat.py +332 -0
  22. praisonai_code/cli/commands/checkpoint.py +170 -0
  23. praisonai_code/cli/commands/code.py +276 -0
  24. praisonai_code/cli/commands/command.py +114 -0
  25. praisonai_code/cli/commands/commit.py +47 -0
  26. praisonai_code/cli/commands/completion.py +333 -0
  27. praisonai_code/cli/commands/config.py +681 -0
  28. praisonai_code/cli/commands/context.py +414 -0
  29. praisonai_code/cli/commands/daemon.py +203 -0
  30. praisonai_code/cli/commands/debug.py +142 -0
  31. praisonai_code/cli/commands/deploy.py +71 -0
  32. praisonai_code/cli/commands/diag.py +55 -0
  33. praisonai_code/cli/commands/docs.py +1575 -0
  34. praisonai_code/cli/commands/doctor.py +332 -0
  35. praisonai_code/cli/commands/endpoints.py +51 -0
  36. praisonai_code/cli/commands/environment.py +179 -0
  37. praisonai_code/cli/commands/eval.py +131 -0
  38. praisonai_code/cli/commands/examples.py +953 -0
  39. praisonai_code/cli/commands/flow.py +436 -0
  40. praisonai_code/cli/commands/github.py +752 -0
  41. praisonai_code/cli/commands/hooks.py +74 -0
  42. praisonai_code/cli/commands/init.py +174 -0
  43. praisonai_code/cli/commands/knowledge.py +440 -0
  44. praisonai_code/cli/commands/langextract.py +120 -0
  45. praisonai_code/cli/commands/langfuse.py +984 -0
  46. praisonai_code/cli/commands/loop.py +211 -0
  47. praisonai_code/cli/commands/lsp.py +112 -0
  48. praisonai_code/cli/commands/managed.py +659 -0
  49. praisonai_code/cli/commands/mcp.py +763 -0
  50. praisonai_code/cli/commands/memory.py +298 -0
  51. praisonai_code/cli/commands/models.py +264 -0
  52. praisonai_code/cli/commands/n8n.py +326 -0
  53. praisonai_code/cli/commands/obs.py +19 -0
  54. praisonai_code/cli/commands/package.py +76 -0
  55. praisonai_code/cli/commands/paths.py +106 -0
  56. praisonai_code/cli/commands/permissions.py +272 -0
  57. praisonai_code/cli/commands/plugins.py +609 -0
  58. praisonai_code/cli/commands/port.py +530 -0
  59. praisonai_code/cli/commands/profile.py +466 -0
  60. praisonai_code/cli/commands/publish.py +193 -0
  61. praisonai_code/cli/commands/rag.py +913 -0
  62. praisonai_code/cli/commands/realtime.py +52 -0
  63. praisonai_code/cli/commands/recipe.py +684 -0
  64. praisonai_code/cli/commands/registry.py +59 -0
  65. praisonai_code/cli/commands/replay.py +830 -0
  66. praisonai_code/cli/commands/research.py +49 -0
  67. praisonai_code/cli/commands/retrieval.py +377 -0
  68. praisonai_code/cli/commands/rules.py +71 -0
  69. praisonai_code/cli/commands/run.py +1573 -0
  70. praisonai_code/cli/commands/sandbox.py +371 -0
  71. praisonai_code/cli/commands/schedule.py +529 -0
  72. praisonai_code/cli/commands/serve.py +690 -0
  73. praisonai_code/cli/commands/session.py +450 -0
  74. praisonai_code/cli/commands/setup.py +174 -0
  75. praisonai_code/cli/commands/skills.py +545 -0
  76. praisonai_code/cli/commands/standardise.py +711 -0
  77. praisonai_code/cli/commands/templates.py +54 -0
  78. praisonai_code/cli/commands/test.py +558 -0
  79. praisonai_code/cli/commands/todo.py +74 -0
  80. praisonai_code/cli/commands/tools.py +205 -0
  81. praisonai_code/cli/commands/traces.py +145 -0
  82. praisonai_code/cli/commands/tracker.py +852 -0
  83. praisonai_code/cli/commands/train.py +613 -0
  84. praisonai_code/cli/commands/ui.py +172 -0
  85. praisonai_code/cli/commands/up.py +354 -0
  86. praisonai_code/cli/commands/validate.py +291 -0
  87. praisonai_code/cli/commands/version.py +101 -0
  88. praisonai_code/cli/commands/workflow.py +97 -0
  89. praisonai_code/cli/config_loader.py +437 -0
  90. praisonai_code/cli/configuration/__init__.py +27 -0
  91. praisonai_code/cli/configuration/config.schema.json +57 -0
  92. praisonai_code/cli/configuration/credentials.py +446 -0
  93. praisonai_code/cli/configuration/loader.py +364 -0
  94. praisonai_code/cli/configuration/model_resolver.py +161 -0
  95. praisonai_code/cli/configuration/oauth.py +389 -0
  96. praisonai_code/cli/configuration/paths.py +224 -0
  97. praisonai_code/cli/configuration/resolver.py +687 -0
  98. praisonai_code/cli/configuration/schema.py +317 -0
  99. praisonai_code/cli/execution/__init__.py +99 -0
  100. praisonai_code/cli/execution/core.py +208 -0
  101. praisonai_code/cli/execution/profiler.py +898 -0
  102. praisonai_code/cli/execution/request.py +85 -0
  103. praisonai_code/cli/execution/result.py +74 -0
  104. praisonai_code/cli/fallback_schema.py +416 -0
  105. praisonai_code/cli/features/__init__.py +278 -0
  106. praisonai_code/cli/features/_endpoint_registry.py +64 -0
  107. praisonai_code/cli/features/_search_registry.py +43 -0
  108. praisonai_code/cli/features/acp.py +236 -0
  109. praisonai_code/cli/features/action_orchestrator.py +576 -0
  110. praisonai_code/cli/features/agent_scheduler.py +773 -0
  111. praisonai_code/cli/features/agent_tools.py +603 -0
  112. praisonai_code/cli/features/agents.py +397 -0
  113. praisonai_code/cli/features/at_mentions.py +471 -0
  114. praisonai_code/cli/features/audit_cli.py +270 -0
  115. praisonai_code/cli/features/auto_memory.py +182 -0
  116. praisonai_code/cli/features/auto_mode.py +552 -0
  117. praisonai_code/cli/features/autonomy_mode.py +546 -0
  118. praisonai_code/cli/features/background.py +356 -0
  119. praisonai_code/cli/features/base.py +168 -0
  120. praisonai_code/cli/features/benchmark.py +1462 -0
  121. praisonai_code/cli/features/capabilities.py +1326 -0
  122. praisonai_code/cli/features/checkpoints.py +345 -0
  123. praisonai_code/cli/features/cli_profiler.py +335 -0
  124. praisonai_code/cli/features/code_intelligence.py +666 -0
  125. praisonai_code/cli/features/compaction.py +294 -0
  126. praisonai_code/cli/features/compare.py +534 -0
  127. praisonai_code/cli/features/config_hierarchy.py +366 -0
  128. praisonai_code/cli/features/context_manager.py +597 -0
  129. praisonai_code/cli/features/cost_tracker.py +514 -0
  130. praisonai_code/cli/features/csv_test_runner.py +736 -0
  131. praisonai_code/cli/features/custom_definitions.py +790 -0
  132. praisonai_code/cli/features/debug.py +810 -0
  133. praisonai_code/cli/features/deploy.py +605 -0
  134. praisonai_code/cli/features/diag.py +289 -0
  135. praisonai_code/cli/features/display_jsonl.py +173 -0
  136. praisonai_code/cli/features/doctor/__init__.py +63 -0
  137. praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
  138. praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
  139. praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
  140. praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
  141. praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
  142. praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
  143. praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
  144. praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
  145. praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
  146. praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
  147. praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
  148. praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
  149. praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
  150. praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
  151. praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
  152. praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
  153. praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
  154. praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
  155. praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
  156. praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
  157. praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
  158. praisonai_code/cli/features/doctor/engine.py +266 -0
  159. praisonai_code/cli/features/doctor/formatters.py +377 -0
  160. praisonai_code/cli/features/doctor/handler.py +564 -0
  161. praisonai_code/cli/features/doctor/models.py +276 -0
  162. praisonai_code/cli/features/doctor/registry.py +239 -0
  163. praisonai_code/cli/features/endpoints.py +1016 -0
  164. praisonai_code/cli/features/eval.py +559 -0
  165. praisonai_code/cli/features/examples.py +707 -0
  166. praisonai_code/cli/features/external_agents.py +231 -0
  167. praisonai_code/cli/features/fast_context.py +410 -0
  168. praisonai_code/cli/features/file_history.py +320 -0
  169. praisonai_code/cli/features/flow_display.py +566 -0
  170. praisonai_code/cli/features/git_attribution.py +159 -0
  171. praisonai_code/cli/features/git_integration.py +651 -0
  172. praisonai_code/cli/features/guardrail.py +171 -0
  173. praisonai_code/cli/features/handoff.py +252 -0
  174. praisonai_code/cli/features/hooks.py +583 -0
  175. praisonai_code/cli/features/hybrid_workflow.py +391 -0
  176. praisonai_code/cli/features/image.py +384 -0
  177. praisonai_code/cli/features/interactive_core_headless.py +450 -0
  178. praisonai_code/cli/features/interactive_runtime.py +600 -0
  179. praisonai_code/cli/features/interactive_test_harness.py +537 -0
  180. praisonai_code/cli/features/interactive_tools.py +428 -0
  181. praisonai_code/cli/features/interactive_tui.py +603 -0
  182. praisonai_code/cli/features/job_workflow.py +906 -0
  183. praisonai_code/cli/features/jobs.py +632 -0
  184. praisonai_code/cli/features/knowledge.py +531 -0
  185. praisonai_code/cli/features/knowledge_cli.py +438 -0
  186. praisonai_code/cli/features/lite.py +244 -0
  187. praisonai_code/cli/features/logs.py +200 -0
  188. praisonai_code/cli/features/lsp_cli.py +225 -0
  189. praisonai_code/cli/features/lsp_diagnostics.py +185 -0
  190. praisonai_code/cli/features/mcp.py +344 -0
  191. praisonai_code/cli/features/message_queue.py +587 -0
  192. praisonai_code/cli/features/metrics.py +210 -0
  193. praisonai_code/cli/features/migrate.py +1329 -0
  194. praisonai_code/cli/features/migration_flow.py +463 -0
  195. praisonai_code/cli/features/migration_spec.py +276 -0
  196. praisonai_code/cli/features/n8n.py +703 -0
  197. praisonai_code/cli/features/observability.py +293 -0
  198. praisonai_code/cli/features/ollama.py +361 -0
  199. praisonai_code/cli/features/output_modes.py +155 -0
  200. praisonai_code/cli/features/output_style.py +273 -0
  201. praisonai_code/cli/features/package.py +631 -0
  202. praisonai_code/cli/features/performance.py +308 -0
  203. praisonai_code/cli/features/persistence.py +636 -0
  204. praisonai_code/cli/features/profiler/__init__.py +81 -0
  205. praisonai_code/cli/features/profiler/core.py +558 -0
  206. praisonai_code/cli/features/profiler/optimizations.py +652 -0
  207. praisonai_code/cli/features/profiler/suite.py +386 -0
  208. praisonai_code/cli/features/queue/__init__.py +73 -0
  209. praisonai_code/cli/features/queue/manager.py +435 -0
  210. praisonai_code/cli/features/queue/models.py +289 -0
  211. praisonai_code/cli/features/queue/persistence.py +564 -0
  212. praisonai_code/cli/features/queue/scheduler.py +529 -0
  213. praisonai_code/cli/features/queue/worker.py +400 -0
  214. praisonai_code/cli/features/recipe.py +2187 -0
  215. praisonai_code/cli/features/recipe_creator.py +996 -0
  216. praisonai_code/cli/features/recipe_optimizer.py +1364 -0
  217. praisonai_code/cli/features/recipe_prompts.py +226 -0
  218. praisonai_code/cli/features/registry.py +229 -0
  219. praisonai_code/cli/features/repo_map.py +860 -0
  220. praisonai_code/cli/features/router.py +466 -0
  221. praisonai_code/cli/features/safe_shell.py +427 -0
  222. praisonai_code/cli/features/sandbox_cli.py +283 -0
  223. praisonai_code/cli/features/sandbox_executor.py +536 -0
  224. praisonai_code/cli/features/sdk_knowledge.py +500 -0
  225. praisonai_code/cli/features/session.py +222 -0
  226. praisonai_code/cli/features/session_checkpoints.py +208 -0
  227. praisonai_code/cli/features/setup/__init__.py +9 -0
  228. praisonai_code/cli/features/setup/handler.py +355 -0
  229. praisonai_code/cli/features/setup/templates.py +62 -0
  230. praisonai_code/cli/features/skills.py +940 -0
  231. praisonai_code/cli/features/slash_commands.py +692 -0
  232. praisonai_code/cli/features/telemetry.py +179 -0
  233. praisonai_code/cli/features/templates.py +1390 -0
  234. praisonai_code/cli/features/thinking.py +343 -0
  235. praisonai_code/cli/features/todo.py +334 -0
  236. praisonai_code/cli/features/tools.py +680 -0
  237. praisonai_code/cli/features/tui/__init__.py +83 -0
  238. praisonai_code/cli/features/tui/app.py +871 -0
  239. praisonai_code/cli/features/tui/cli.py +580 -0
  240. praisonai_code/cli/features/tui/config.py +150 -0
  241. praisonai_code/cli/features/tui/debug.py +526 -0
  242. praisonai_code/cli/features/tui/events.py +99 -0
  243. praisonai_code/cli/features/tui/mock_provider.py +328 -0
  244. praisonai_code/cli/features/tui/orchestrator.py +652 -0
  245. praisonai_code/cli/features/tui/screens/__init__.py +50 -0
  246. praisonai_code/cli/features/tui/screens/help.py +157 -0
  247. praisonai_code/cli/features/tui/screens/main.py +568 -0
  248. praisonai_code/cli/features/tui/screens/queue.py +174 -0
  249. praisonai_code/cli/features/tui/screens/session.py +124 -0
  250. praisonai_code/cli/features/tui/screens/settings.py +148 -0
  251. praisonai_code/cli/features/tui/session_store.py +198 -0
  252. praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
  253. praisonai_code/cli/features/tui/widgets/chat.py +263 -0
  254. praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
  255. praisonai_code/cli/features/tui/widgets/composer.py +292 -0
  256. praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
  257. praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
  258. praisonai_code/cli/features/tui/widgets/status.py +181 -0
  259. praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
  260. praisonai_code/cli/features/wizard.py +289 -0
  261. praisonai_code/cli/features/workflow.py +802 -0
  262. praisonai_code/cli/features/yaml_utils.py +321 -0
  263. praisonai_code/cli/interactive/__init__.py +48 -0
  264. praisonai_code/cli/interactive/async_tui.py +1218 -0
  265. praisonai_code/cli/interactive/config.py +139 -0
  266. praisonai_code/cli/interactive/core.py +618 -0
  267. praisonai_code/cli/interactive/events.py +131 -0
  268. praisonai_code/cli/interactive/frontends/__init__.py +31 -0
  269. praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
  270. praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
  271. praisonai_code/cli/interactive/praison_io.py +502 -0
  272. praisonai_code/cli/interactive/repl.py +297 -0
  273. praisonai_code/cli/interactive/split_tui.py +456 -0
  274. praisonai_code/cli/interactive/tui_app.py +457 -0
  275. praisonai_code/cli/langfuse_client.py +360 -0
  276. praisonai_code/cli/main.py +7421 -0
  277. praisonai_code/cli/output/__init__.py +25 -0
  278. praisonai_code/cli/output/console.py +456 -0
  279. praisonai_code/cli/output/event_bridge.py +191 -0
  280. praisonai_code/cli/schedule_cli.py +54 -0
  281. praisonai_code/cli/schema_provider.py +23 -0
  282. praisonai_code/cli/session/__init__.py +16 -0
  283. praisonai_code/cli/session/resume.py +148 -0
  284. praisonai_code/cli/session/unified.py +548 -0
  285. praisonai_code/cli/state/__init__.py +31 -0
  286. praisonai_code/cli/state/identifiers.py +161 -0
  287. praisonai_code/cli/state/project_sessions.py +383 -0
  288. praisonai_code/cli/state/sessions.py +390 -0
  289. praisonai_code/cli/ui/__init__.py +160 -0
  290. praisonai_code/cli/ui/config.py +46 -0
  291. praisonai_code/cli/ui/events.py +61 -0
  292. praisonai_code/cli/ui/mg_backend.py +342 -0
  293. praisonai_code/cli/ui/plain.py +133 -0
  294. praisonai_code/cli/ui/rich_backend.py +162 -0
  295. praisonai_code/cli/unified_schema.py +655 -0
  296. praisonai_code/cli/utils/env_utils.py +126 -0
  297. praisonai_code/cli/utils/project.py +131 -0
  298. praisonai_code/cli_backends/__init__.py +73 -0
  299. praisonai_code/cli_backends/claude.py +373 -0
  300. praisonai_code/cli_backends/registry.py +113 -0
  301. praisonai_code/runtime/__init__.py +36 -0
  302. praisonai_code/runtime/__main__.py +81 -0
  303. praisonai_code/runtime/client.py +131 -0
  304. praisonai_code/runtime/descriptor.py +209 -0
  305. praisonai_code/runtime/server.py +356 -0
  306. praisonai_code-0.0.1.dist-info/METADATA +80 -0
  307. praisonai_code-0.0.1.dist-info/RECORD +309 -0
  308. praisonai_code-0.0.1.dist-info/WHEEL +5 -0
  309. praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ """
2
+ Event types and dataclasses for InteractiveCore.
3
+
4
+ These events are emitted by InteractiveCore and consumed by frontends
5
+ (Rich REPL, Textual TUI, etc.) for rendering.
6
+ """
7
+
8
+ import time
9
+ import uuid
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Dict, Optional
13
+
14
+
15
+ class InteractiveEventType(Enum):
16
+ """Types of events emitted by InteractiveCore."""
17
+
18
+ # Message lifecycle
19
+ MESSAGE_START = "message.start"
20
+ MESSAGE_CHUNK = "message.chunk"
21
+ MESSAGE_END = "message.end"
22
+
23
+ # Tool execution
24
+ TOOL_START = "tool.start"
25
+ TOOL_END = "tool.end"
26
+
27
+ # Stage transitions (PLAN → EXPLORE → BUILD → REVIEW)
28
+ STAGE_CHANGE = "stage.change"
29
+
30
+ # Approval/permission flow
31
+ APPROVAL_ASKED = "approval.asked"
32
+ APPROVAL_ANSWERED = "approval.answered"
33
+
34
+ # Session lifecycle
35
+ SESSION_CREATED = "session.created"
36
+ SESSION_RESUMED = "session.resumed"
37
+
38
+ # Status
39
+ ERROR = "error"
40
+ IDLE = "idle"
41
+
42
+
43
+ class ApprovalDecision(Enum):
44
+ """Possible decisions for approval requests."""
45
+
46
+ ONCE = "once" # Allow this one time
47
+ ALWAYS = "always" # Always allow this pattern (persistent)
48
+ ALWAYS_SESSION = "always_session" # Always allow for this session only
49
+ REJECT = "reject" # Reject this action
50
+
51
+
52
+ @dataclass
53
+ class InteractiveEvent:
54
+ """Event emitted by InteractiveCore."""
55
+
56
+ type: InteractiveEventType
57
+ data: Dict[str, Any] = field(default_factory=dict)
58
+ timestamp: float = field(default_factory=time.time)
59
+ source: Optional[str] = None
60
+
61
+ def to_dict(self) -> Dict[str, Any]:
62
+ """Convert event to dictionary."""
63
+ return {
64
+ "type": self.type.value,
65
+ "data": self.data,
66
+ "timestamp": self.timestamp,
67
+ "source": self.source,
68
+ }
69
+
70
+
71
+ @dataclass
72
+ class ApprovalRequest:
73
+ """Request for user approval before executing an action."""
74
+
75
+ action_type: str # e.g., "file_write", "shell_command", "file_read"
76
+ description: str # Human-readable description
77
+ tool_name: str # Name of the tool requesting approval
78
+ parameters: Dict[str, Any] # Tool parameters
79
+ request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
80
+
81
+ def to_dict(self) -> Dict[str, Any]:
82
+ """Convert to dictionary."""
83
+ return {
84
+ "request_id": self.request_id,
85
+ "action_type": self.action_type,
86
+ "description": self.description,
87
+ "tool_name": self.tool_name,
88
+ "parameters": self.parameters,
89
+ }
90
+
91
+ def matches_pattern(self, pattern: str) -> bool:
92
+ """Check if this request matches an approval pattern.
93
+
94
+ Pattern format: "action_type:path_pattern"
95
+ Examples:
96
+ - "file_read:*" matches all file reads
97
+ - "file_write:/tmp/*" matches writes to /tmp/
98
+ - "shell_command:ls*" matches ls commands
99
+ """
100
+ if ":" not in pattern:
101
+ return self.action_type == pattern
102
+
103
+ action_pattern, path_pattern = pattern.split(":", 1)
104
+
105
+ if action_pattern != "*" and action_pattern != self.action_type:
106
+ return False
107
+
108
+ if path_pattern == "*":
109
+ return True
110
+
111
+ # Simple glob matching
112
+ import fnmatch
113
+ path = self.parameters.get("path", self.parameters.get("command", ""))
114
+ return fnmatch.fnmatch(str(path), path_pattern)
115
+
116
+
117
+ @dataclass
118
+ class ApprovalResponse:
119
+ """Response to an approval request."""
120
+
121
+ request_id: str
122
+ decision: ApprovalDecision
123
+ remember_pattern: Optional[str] = None # Pattern to remember for ALWAYS decisions
124
+
125
+ def to_dict(self) -> Dict[str, Any]:
126
+ """Convert to dictionary."""
127
+ return {
128
+ "request_id": self.request_id,
129
+ "decision": self.decision.value,
130
+ "remember_pattern": self.remember_pattern,
131
+ }
@@ -0,0 +1,31 @@
1
+ """
2
+ Frontend implementations for InteractiveCore.
3
+
4
+ Each frontend subscribes to InteractiveCore events and renders them
5
+ using its specific UI framework.
6
+ """
7
+
8
+ __all__ = [
9
+ "RichFrontend",
10
+ "TextualFrontend",
11
+ ]
12
+
13
+ _lazy_cache = {}
14
+
15
+
16
+ def __getattr__(name: str):
17
+ """Lazy load frontends."""
18
+ if name in _lazy_cache:
19
+ return _lazy_cache[name]
20
+
21
+ if name == "RichFrontend":
22
+ from .rich_frontend import RichFrontend
23
+ _lazy_cache[name] = RichFrontend
24
+ return RichFrontend
25
+
26
+ if name == "TextualFrontend":
27
+ from .textual_frontend import TextualFrontend
28
+ _lazy_cache[name] = TextualFrontend
29
+ return TextualFrontend
30
+
31
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,462 @@
1
+ """
2
+ Rich/prompt_toolkit frontend for InteractiveCore.
3
+
4
+ This frontend provides a REPL-style interface using Rich for rendering
5
+ and prompt_toolkit for input handling.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ from typing import Optional
11
+
12
+ from ..core import InteractiveCore
13
+ from ..config import InteractiveConfig
14
+ from ..events import (
15
+ InteractiveEvent,
16
+ InteractiveEventType,
17
+ ApprovalRequest,
18
+ ApprovalResponse,
19
+ ApprovalDecision,
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class RichFrontend:
26
+ """
27
+ Rich/prompt_toolkit frontend for interactive mode.
28
+
29
+ This is used by:
30
+ - `praisonai run --interactive`
31
+ - `praisonai chat`
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ core: Optional[InteractiveCore] = None,
37
+ config: Optional[InteractiveConfig] = None,
38
+ ui_config: Optional['UIConfig'] = None,
39
+ ):
40
+ """Initialize the Rich frontend.
41
+
42
+ Args:
43
+ core: InteractiveCore instance. If None, creates one.
44
+ config: Configuration. Used if core is None.
45
+ ui_config: UI backend configuration for rendering options.
46
+ """
47
+ self.core = core or InteractiveCore(config=config)
48
+ self._ui_config = ui_config
49
+ self._running = False
50
+ self._current_response = ""
51
+ self._pending_approval: Optional[ApprovalRequest] = None
52
+ self._approval_event = asyncio.Event()
53
+ self._approval_response: Optional[ApprovalResponse] = None
54
+
55
+ # Subscribe to events
56
+ self.core.subscribe(self._handle_event)
57
+
58
+ def _handle_event(self, event: InteractiveEvent) -> None:
59
+ """Handle events from InteractiveCore."""
60
+ if event.type == InteractiveEventType.MESSAGE_START:
61
+ self._on_message_start(event)
62
+ elif event.type == InteractiveEventType.MESSAGE_CHUNK:
63
+ self._on_message_chunk(event)
64
+ elif event.type == InteractiveEventType.MESSAGE_END:
65
+ self._on_message_end(event)
66
+ elif event.type == InteractiveEventType.TOOL_START:
67
+ self._on_tool_start(event)
68
+ elif event.type == InteractiveEventType.TOOL_END:
69
+ self._on_tool_end(event)
70
+ elif event.type == InteractiveEventType.APPROVAL_ASKED:
71
+ self._on_approval_asked(event)
72
+ elif event.type == InteractiveEventType.ERROR:
73
+ self._on_error(event)
74
+ elif event.type == InteractiveEventType.SESSION_CREATED:
75
+ self._on_session_created(event)
76
+ elif event.type == InteractiveEventType.SESSION_RESUMED:
77
+ self._on_session_resumed(event)
78
+
79
+ def _on_message_start(self, event: InteractiveEvent) -> None:
80
+ """Handle message start."""
81
+ try:
82
+ from rich.console import Console
83
+ console = Console()
84
+ console.print("\n[bold cyan]Assistant:[/bold cyan]", end=" ")
85
+ except ImportError:
86
+ print("\nAssistant:", end=" ")
87
+
88
+ def _on_message_chunk(self, event: InteractiveEvent) -> None:
89
+ """Handle message chunk (streaming)."""
90
+ chunk = event.data.get("chunk", "")
91
+ self._current_response += chunk
92
+ print(chunk, end="", flush=True)
93
+
94
+ def _on_message_end(self, event: InteractiveEvent) -> None:
95
+ """Handle message end."""
96
+ response = event.data.get("response", "")
97
+ if response and not self._current_response:
98
+ # Full response (non-streaming)
99
+ print(response)
100
+ else:
101
+ print() # Newline after streaming
102
+ self._current_response = ""
103
+
104
+ def _on_tool_start(self, event: InteractiveEvent) -> None:
105
+ """Handle tool execution start."""
106
+ tool_name = event.data.get("tool_name", "unknown")
107
+ try:
108
+ from rich.console import Console
109
+ console = Console()
110
+ console.print(f"[dim]⚙ Running {tool_name}...[/dim]")
111
+ except ImportError:
112
+ print(f"⚙ Running {tool_name}...")
113
+
114
+ def _on_tool_end(self, event: InteractiveEvent) -> None:
115
+ """Handle tool execution end."""
116
+ tool_name = event.data.get("tool_name", "unknown")
117
+ success = event.data.get("success", True)
118
+
119
+ try:
120
+ from rich.console import Console
121
+ console = Console()
122
+ if success:
123
+ console.print(f"[dim]✓ {tool_name} completed[/dim]")
124
+ else:
125
+ console.print(f"[red]✗ {tool_name} failed[/red]")
126
+ except ImportError:
127
+ status = "✓" if success else "✗"
128
+ print(f"{status} {tool_name} {'completed' if success else 'failed'}")
129
+
130
+ def _on_approval_asked(self, event: InteractiveEvent) -> None:
131
+ """Handle approval request."""
132
+ self._pending_approval = ApprovalRequest(**event.data)
133
+
134
+ def _on_error(self, event: InteractiveEvent) -> None:
135
+ """Handle error."""
136
+ error = event.data.get("error", "Unknown error")
137
+ try:
138
+ from rich.console import Console
139
+ console = Console()
140
+ console.print(f"[bold red]Error:[/bold red] {error}")
141
+ except ImportError:
142
+ print(f"Error: {error}")
143
+
144
+ def _on_session_created(self, event: InteractiveEvent) -> None:
145
+ """Handle session created."""
146
+ session_id = event.data.get("session_id", "")
147
+ try:
148
+ from rich.console import Console
149
+ console = Console()
150
+ console.print(f"[dim]Session created: {session_id[:8]}...[/dim]")
151
+ except ImportError:
152
+ print(f"Session created: {session_id[:8]}...")
153
+
154
+ def _on_session_resumed(self, event: InteractiveEvent) -> None:
155
+ """Handle session resumed."""
156
+ session_id = event.data.get("session_id", "")
157
+ try:
158
+ from rich.console import Console
159
+ console = Console()
160
+ console.print(f"[dim]Session resumed: {session_id[:8]}...[/dim]")
161
+ except ImportError:
162
+ print(f"Session resumed: {session_id[:8]}...")
163
+
164
+ def _prompt_approval(self, request: ApprovalRequest) -> ApprovalResponse:
165
+ """Prompt user for approval decision."""
166
+ try:
167
+ from rich.console import Console
168
+ from rich.panel import Panel
169
+ console = Console()
170
+
171
+ console.print(Panel(
172
+ f"[bold]{request.description}[/bold]\n\n"
173
+ f"Tool: {request.tool_name}\n"
174
+ f"Action: {request.action_type}",
175
+ title="[yellow]Approval Required[/yellow]",
176
+ border_style="yellow"
177
+ ))
178
+
179
+ console.print("[1] Allow once")
180
+ console.print("[2] Always allow this pattern")
181
+ console.print("[3] Always allow for this session")
182
+ console.print("[4] Reject")
183
+
184
+ except ImportError:
185
+ print(f"\n=== Approval Required ===")
186
+ print(f"Description: {request.description}")
187
+ print(f"Tool: {request.tool_name}")
188
+ print(f"Action: {request.action_type}")
189
+ print("[1] Allow once")
190
+ print("[2] Always allow this pattern")
191
+ print("[3] Always allow for this session")
192
+ print("[4] Reject")
193
+
194
+ while True:
195
+ try:
196
+ choice = input("\nChoice [1-4]: ").strip()
197
+
198
+ if choice == "1":
199
+ return ApprovalResponse(
200
+ request_id=request.request_id,
201
+ decision=ApprovalDecision.ONCE
202
+ )
203
+ elif choice == "2":
204
+ pattern = f"{request.action_type}:*"
205
+ return ApprovalResponse(
206
+ request_id=request.request_id,
207
+ decision=ApprovalDecision.ALWAYS,
208
+ remember_pattern=pattern
209
+ )
210
+ elif choice == "3":
211
+ pattern = f"{request.action_type}:*"
212
+ return ApprovalResponse(
213
+ request_id=request.request_id,
214
+ decision=ApprovalDecision.ALWAYS_SESSION,
215
+ remember_pattern=pattern
216
+ )
217
+ elif choice == "4":
218
+ return ApprovalResponse(
219
+ request_id=request.request_id,
220
+ decision=ApprovalDecision.REJECT
221
+ )
222
+ else:
223
+ print("Invalid choice. Please enter 1-4.")
224
+ except (EOFError, KeyboardInterrupt):
225
+ return ApprovalResponse(
226
+ request_id=request.request_id,
227
+ decision=ApprovalDecision.REJECT
228
+ )
229
+
230
+ async def run(self) -> None:
231
+ """Run the interactive REPL loop."""
232
+ self._running = True
233
+
234
+ try:
235
+ from rich.console import Console
236
+ console = Console()
237
+ console.print("[bold green]PraisonAI Interactive Mode[/bold green]")
238
+ console.print("[dim]Type /help for commands, /exit to quit[/dim]\n")
239
+ except ImportError:
240
+ print("PraisonAI Interactive Mode")
241
+ print("Type /help for commands, /exit to quit\n")
242
+
243
+ # Handle --continue flag
244
+ if self.core.config.continue_session:
245
+ session_id = self.core.continue_session()
246
+ if not session_id:
247
+ try:
248
+ from rich.console import Console
249
+ Console().print("[yellow]No previous session found. Starting new session.[/yellow]")
250
+ except ImportError:
251
+ print("No previous session found. Starting new session.")
252
+
253
+ # Try to use prompt_toolkit for better input
254
+ try:
255
+ from prompt_toolkit import PromptSession
256
+ from prompt_toolkit.history import FileHistory
257
+ from pathlib import Path
258
+
259
+ history_file = Path.home() / ".praison" / "history"
260
+ history_file.parent.mkdir(parents=True, exist_ok=True)
261
+
262
+ session = PromptSession(history=FileHistory(str(history_file)))
263
+ get_input = lambda: session.prompt("You: ")
264
+ except ImportError:
265
+ get_input = lambda: input("You: ")
266
+
267
+ while self._running:
268
+ try:
269
+ user_input = get_input().strip()
270
+
271
+ if not user_input:
272
+ continue
273
+
274
+ # Handle slash commands
275
+ if user_input.startswith("/"):
276
+ if await self._handle_command(user_input):
277
+ continue
278
+
279
+ # Execute prompt
280
+ await self.core.prompt(user_input)
281
+
282
+ except (EOFError, KeyboardInterrupt):
283
+ print("\nGoodbye!")
284
+ break
285
+ except Exception as e:
286
+ logger.exception("Error in REPL loop")
287
+ try:
288
+ from rich.console import Console
289
+ Console().print(f"[red]Error: {e}[/red]")
290
+ except ImportError:
291
+ print(f"Error: {e}")
292
+
293
+ async def _handle_command(self, command: str) -> bool:
294
+ """Handle slash commands. Returns True if command was handled."""
295
+ cmd = command.lower().split()[0]
296
+ args = command.split()[1:] if len(command.split()) > 1 else []
297
+
298
+ try:
299
+ from rich.console import Console
300
+ console = Console()
301
+ except ImportError:
302
+ console = None
303
+
304
+ if cmd in ("/exit", "/quit", "/q"):
305
+ self._running = False
306
+ return True
307
+
308
+ elif cmd == "/help":
309
+ help_text = """
310
+ Available commands:
311
+ /help - Show this help
312
+ /exit, /quit - Exit interactive mode
313
+ /clear - Clear conversation history
314
+ /session - Show current session info
315
+ /sessions - List all sessions
316
+ /continue - Continue last session
317
+ /export <file> - Export session to file
318
+ /import <file> - Import session from file
319
+ /model <name> - Switch model
320
+ /cost - Show token/cost usage
321
+ """
322
+ if console:
323
+ console.print(help_text)
324
+ else:
325
+ print(help_text)
326
+ return True
327
+
328
+ elif cmd == "/clear":
329
+ # Create new session
330
+ self.core.create_session()
331
+ if console:
332
+ console.print("[dim]Conversation cleared.[/dim]")
333
+ else:
334
+ print("Conversation cleared.")
335
+ return True
336
+
337
+ elif cmd == "/session":
338
+ session_id = self.core.current_session_id
339
+ if session_id:
340
+ if console:
341
+ console.print(f"[dim]Current session: {session_id}[/dim]")
342
+ else:
343
+ print(f"Current session: {session_id}")
344
+ else:
345
+ if console:
346
+ console.print("[dim]No active session[/dim]")
347
+ else:
348
+ print("No active session")
349
+ return True
350
+
351
+ elif cmd == "/sessions":
352
+ sessions = self.core.session_store.list_sessions()
353
+ if sessions:
354
+ if console:
355
+ console.print("[bold]Sessions:[/bold]")
356
+ for s in sessions[:10]:
357
+ sid = s.get("session_id", "")[:8]
358
+ title = s.get("title", "Untitled")
359
+ console.print(f" {sid}... - {title}")
360
+ else:
361
+ print("Sessions:")
362
+ for s in sessions[:10]:
363
+ print(f" {s.get('session_id', '')[:8]}... - {s.get('title', 'Untitled')}")
364
+ else:
365
+ if console:
366
+ console.print("[dim]No sessions found[/dim]")
367
+ else:
368
+ print("No sessions found")
369
+ return True
370
+
371
+ elif cmd == "/continue":
372
+ session_id = self.core.continue_session()
373
+ if session_id:
374
+ if console:
375
+ console.print(f"[dim]Continued session: {session_id[:8]}...[/dim]")
376
+ else:
377
+ print(f"Continued session: {session_id[:8]}...")
378
+ else:
379
+ if console:
380
+ console.print("[yellow]No previous session found[/yellow]")
381
+ else:
382
+ print("No previous session found")
383
+ return True
384
+
385
+ elif cmd == "/export":
386
+ if not args:
387
+ if console:
388
+ console.print("[red]Usage: /export <filename>[/red]")
389
+ else:
390
+ print("Usage: /export <filename>")
391
+ return True
392
+
393
+ session_id = self.core.current_session_id
394
+ if session_id:
395
+ try:
396
+ self.core.export_session_to_file(session_id, args[0])
397
+ if console:
398
+ console.print(f"[green]Session exported to {args[0]}[/green]")
399
+ else:
400
+ print(f"Session exported to {args[0]}")
401
+ except Exception as e:
402
+ if console:
403
+ console.print(f"[red]Export failed: {e}[/red]")
404
+ else:
405
+ print(f"Export failed: {e}")
406
+ else:
407
+ if console:
408
+ console.print("[yellow]No active session to export[/yellow]")
409
+ else:
410
+ print("No active session to export")
411
+ return True
412
+
413
+ elif cmd == "/import":
414
+ if not args:
415
+ if console:
416
+ console.print("[red]Usage: /import <filename>[/red]")
417
+ else:
418
+ print("Usage: /import <filename>")
419
+ return True
420
+
421
+ try:
422
+ session_id = self.core.import_session_from_file(args[0])
423
+ self.core.resume_session(session_id)
424
+ if console:
425
+ console.print(f"[green]Session imported: {session_id[:8]}...[/green]")
426
+ else:
427
+ print(f"Session imported: {session_id[:8]}...")
428
+ except Exception as e:
429
+ if console:
430
+ console.print(f"[red]Import failed: {e}[/red]")
431
+ else:
432
+ print(f"Import failed: {e}")
433
+ return True
434
+
435
+ elif cmd == "/model":
436
+ if args:
437
+ self.core.config.model = args[0]
438
+ if console:
439
+ console.print(f"[dim]Model set to: {args[0]}[/dim]")
440
+ else:
441
+ print(f"Model set to: {args[0]}")
442
+ else:
443
+ model = self.core.config.model or "default"
444
+ if console:
445
+ console.print(f"[dim]Current model: {model}[/dim]")
446
+ else:
447
+ print(f"Current model: {model}")
448
+ return True
449
+
450
+ elif cmd == "/cost":
451
+ # TODO: Integrate with cost tracker
452
+ if console:
453
+ console.print("[dim]Cost tracking not yet implemented in unified core[/dim]")
454
+ else:
455
+ print("Cost tracking not yet implemented in unified core")
456
+ return True
457
+
458
+ return False # Command not recognized
459
+
460
+ def stop(self) -> None:
461
+ """Stop the REPL loop."""
462
+ self._running = False