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,456 @@
1
+ """
2
+ PraisonAI Split-Pane TUI Application.
3
+
4
+ Full-screen terminal UI with:
5
+ - Fixed bottom input pane (always visible)
6
+ - Scrollable top output pane
7
+ - Direct response (no streaming - faster)
8
+ - Activity indicators showing agent status
9
+
10
+ Uses prompt_toolkit Application for true split-pane layout.
11
+ """
12
+
13
+ import shutil
14
+ import threading
15
+ import time
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import List, Optional
19
+ from datetime import datetime
20
+
21
+ # ============================================================================
22
+ # Branding - Import from unified source
23
+ # ============================================================================
24
+
25
+ from praisonai.cli.branding import get_logo, get_version
26
+
27
+
28
+ # ============================================================================
29
+ # Configuration
30
+ # ============================================================================
31
+
32
+ @dataclass
33
+ class SplitTUIConfig:
34
+ """Configuration for the Split TUI application."""
35
+ model: str = "gpt-4o-mini"
36
+ show_logo: bool = True
37
+ show_status_bar: bool = True
38
+ session_id: Optional[str] = None
39
+ workspace: Optional[str] = None
40
+ history_file: Optional[str] = None
41
+
42
+
43
+ # ============================================================================
44
+ # Message Types
45
+ # ============================================================================
46
+
47
+ @dataclass
48
+ class ChatMessage:
49
+ """A chat message."""
50
+ role: str # "user", "assistant", "system", "status"
51
+ content: str
52
+ timestamp: datetime = field(default_factory=datetime.now)
53
+
54
+
55
+ # ============================================================================
56
+ # Split TUI Application
57
+ # ============================================================================
58
+
59
+ class SplitTUI:
60
+ """
61
+ Split-pane TUI with fixed bottom input and scrollable top output.
62
+
63
+ Key features:
64
+ - Input box ALWAYS visible at bottom
65
+ - Output scrolls above independently
66
+ - Direct response (no streaming) for speed
67
+ - Activity indicators during processing
68
+ """
69
+
70
+ def __init__(self, config: Optional[SplitTUIConfig] = None):
71
+ self.config = config or SplitTUIConfig()
72
+ self.messages: List[ChatMessage] = []
73
+ self._running = False
74
+ self._agent = None
75
+ self._processing = False
76
+ self._status_text = ""
77
+
78
+ # Terminal size
79
+ self.term_width, self.term_height = shutil.get_terminal_size((80, 24))
80
+
81
+ # Session ID
82
+ import uuid
83
+ self.session_id = self.config.session_id or str(uuid.uuid4())[:8]
84
+
85
+ def _get_agent(self):
86
+ """Lazy-load the agent with verbose=False for no streaming."""
87
+ if self._agent is None:
88
+ try:
89
+ from praisonaiagents import Agent
90
+
91
+ self._agent = Agent(
92
+ name="Praison",
93
+ role="AI Assistant",
94
+ goal="Help the user with their requests",
95
+ instructions="You are Praison AI, a helpful assistant. Be concise and helpful.",
96
+ llm=self.config.model,
97
+ )
98
+ except ImportError as e:
99
+ raise RuntimeError(f"Failed to import praisonaiagents: {e}")
100
+
101
+ return self._agent
102
+
103
+ def _format_output(self) -> str:
104
+ """Format all messages for the output pane."""
105
+ lines = []
106
+
107
+ # Logo
108
+ if self.config.show_logo and not self.messages:
109
+ logo = get_logo(self.term_width)
110
+ lines.append(logo.strip())
111
+ lines.append("")
112
+ lines.append(f" v{get_version()} · Model: {self.config.model}")
113
+ lines.append("")
114
+ lines.append(" Type your message and press Enter. Use /help for commands.")
115
+ lines.append("")
116
+
117
+ # Messages
118
+ for msg in self.messages:
119
+ if msg.role == "user":
120
+ lines.append(f"› {msg.content}")
121
+ lines.append("")
122
+ elif msg.role == "assistant":
123
+ lines.append(f"● {msg.content}")
124
+ lines.append("")
125
+ elif msg.role == "system":
126
+ lines.append(f" {msg.content}")
127
+ lines.append("")
128
+ elif msg.role == "status":
129
+ lines.append(f" ⏳ {msg.content}")
130
+
131
+ # Processing indicator
132
+ if self._processing:
133
+ lines.append(f" ⏳ {self._status_text}")
134
+
135
+ return "\n".join(lines)
136
+
137
+ def _handle_command(self, command: str) -> bool:
138
+ """Handle slash commands. Returns True if handled."""
139
+ parts = command.split(maxsplit=1)
140
+ cmd = parts[0].lower().lstrip("/")
141
+ args = parts[1] if len(parts) > 1 else ""
142
+
143
+ if cmd in ("exit", "quit", "q"):
144
+ self._running = False
145
+ return True
146
+
147
+ elif cmd == "help":
148
+ help_text = """Available Commands:
149
+ /help Show this help
150
+ /exit, /quit Exit Praison AI
151
+ /clear Clear conversation
152
+ /model [name] Show or change model
153
+ /new Start new conversation"""
154
+ self.messages.append(ChatMessage(role="system", content=help_text))
155
+ return True
156
+
157
+ elif cmd == "clear":
158
+ self.messages.clear()
159
+ return True
160
+
161
+ elif cmd == "new":
162
+ self.messages.clear()
163
+ import uuid
164
+ self.session_id = str(uuid.uuid4())[:8]
165
+ self.messages.append(ChatMessage(role="system", content=f"New session: {self.session_id}"))
166
+ return True
167
+
168
+ elif cmd == "model":
169
+ if args:
170
+ self.config.model = args
171
+ self._agent = None
172
+ self.messages.append(ChatMessage(role="system", content=f"Model changed to: {args}"))
173
+ else:
174
+ self.messages.append(ChatMessage(role="system", content=f"Current model: {self.config.model}"))
175
+ return True
176
+
177
+ else:
178
+ self.messages.append(ChatMessage(role="system", content=f"Unknown command: /{cmd}. Use /help for available commands."))
179
+ return True
180
+
181
+ def _execute_prompt(self, prompt: str) -> Optional[str]:
182
+ """Execute a prompt and return the response (direct, no streaming)."""
183
+ import sys
184
+ import io
185
+
186
+ try:
187
+ agent = self._get_agent()
188
+
189
+ # Suppress agent's Rich output by redirecting stdout/stderr
190
+ old_stdout = sys.stdout
191
+ old_stderr = sys.stderr
192
+ sys.stdout = io.StringIO()
193
+ sys.stderr = io.StringIO()
194
+
195
+ try:
196
+ response = agent.start(prompt)
197
+ finally:
198
+ # Restore stdout/stderr
199
+ sys.stdout = old_stdout
200
+ sys.stderr = old_stderr
201
+
202
+ return str(response) if response else None
203
+ except Exception as e:
204
+ return f"Error: {e}"
205
+
206
+ def _run_with_status(self, prompt: str) -> Optional[str]:
207
+ """Run prompt with status updates."""
208
+ self._processing = True
209
+ self._status_text = "Praison AI is thinking..."
210
+
211
+ # Execute in background
212
+ result = [None]
213
+ error = [None]
214
+
215
+ def execute():
216
+ try:
217
+ result[0] = self._execute_prompt(prompt)
218
+ except Exception as e:
219
+ error[0] = str(e)
220
+
221
+ thread = threading.Thread(target=execute)
222
+ thread.start()
223
+
224
+ # Update status while waiting
225
+ start_time = time.time()
226
+ spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
227
+ idx = 0
228
+
229
+ while thread.is_alive():
230
+ elapsed = time.time() - start_time
231
+ self._status_text = f"{spinner[idx]} Praison AI is thinking... ({elapsed:.1f}s)"
232
+ idx = (idx + 1) % len(spinner)
233
+ time.sleep(0.1)
234
+
235
+ thread.join()
236
+ self._processing = False
237
+ self._status_text = ""
238
+
239
+ if error[0]:
240
+ return f"Error: {error[0]}"
241
+ return result[0]
242
+
243
+ def run(self) -> None:
244
+ """Run the split-pane TUI."""
245
+ self._running = True
246
+
247
+ # Try to use prompt_toolkit for better experience
248
+ try:
249
+ self._run_with_prompt_toolkit()
250
+ except ImportError:
251
+ # Fallback to simple mode
252
+ self._run_simple()
253
+
254
+ def _run_simple(self) -> None:
255
+ """Simple fallback mode without prompt_toolkit."""
256
+ # Print welcome
257
+ print(self._format_output())
258
+
259
+ while self._running:
260
+ try:
261
+ # Status bar
262
+ if self.config.show_status_bar:
263
+ status = f" ? for help | {self.config.model} | Session: {self.session_id} "
264
+ print(f"\033[90m{status}\033[0m")
265
+
266
+ # Get input
267
+ user_input = input("› ").strip()
268
+
269
+ if not user_input:
270
+ continue
271
+
272
+ # Handle commands
273
+ if user_input.startswith("/"):
274
+ self._handle_command(user_input)
275
+ # Print system messages
276
+ for msg in self.messages:
277
+ if msg.role == "system":
278
+ print(f" {msg.content}")
279
+ self.messages = [m for m in self.messages if m.role != "system"]
280
+ continue
281
+
282
+ # Add user message
283
+ self.messages.append(ChatMessage(role="user", content=user_input))
284
+
285
+ # Execute with status
286
+ print(" ⏳ Praison AI is thinking...")
287
+ response = self._execute_prompt(user_input)
288
+
289
+ if response:
290
+ self.messages.append(ChatMessage(role="assistant", content=response))
291
+ print(f"\n● {response}\n")
292
+
293
+ except KeyboardInterrupt:
294
+ print("\n Use /exit to quit")
295
+ except EOFError:
296
+ self._running = False
297
+
298
+ print("\n Goodbye from Praison AI!")
299
+
300
+ def _run_with_prompt_toolkit(self) -> None:
301
+ """Run with prompt_toolkit for split-pane layout."""
302
+ from prompt_toolkit import Application
303
+ from prompt_toolkit.buffer import Buffer
304
+ from prompt_toolkit.layout.containers import HSplit, VSplit, Window
305
+ from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
306
+ from prompt_toolkit.layout.layout import Layout
307
+ from prompt_toolkit.key_binding import KeyBindings
308
+ from prompt_toolkit.styles import Style
309
+ from prompt_toolkit.history import FileHistory
310
+
311
+ # Setup history
312
+ history_file = self.config.history_file or str(Path.home() / ".praison" / "history")
313
+ Path(history_file).parent.mkdir(parents=True, exist_ok=True)
314
+
315
+ # Output buffer (read-only, for displaying conversation)
316
+ output_text = [self._format_output()]
317
+
318
+ def get_output_text():
319
+ return output_text[0]
320
+
321
+ # Input buffer
322
+ input_buffer = Buffer(
323
+ multiline=False,
324
+ history=FileHistory(history_file),
325
+ )
326
+
327
+ # Key bindings
328
+ kb = KeyBindings()
329
+
330
+ @kb.add("enter")
331
+ def handle_enter(event):
332
+ user_input = input_buffer.text.strip()
333
+ if not user_input:
334
+ return
335
+
336
+ input_buffer.reset()
337
+
338
+ # Handle commands
339
+ if user_input.startswith("/"):
340
+ self._handle_command(user_input)
341
+ output_text[0] = self._format_output()
342
+ return
343
+
344
+ # Add user message
345
+ self.messages.append(ChatMessage(role="user", content=user_input))
346
+
347
+ # Show processing status
348
+ self.messages.append(ChatMessage(role="status", content="Praison AI is thinking..."))
349
+ output_text[0] = self._format_output()
350
+
351
+ # Execute (this blocks but that's OK for direct response)
352
+ response = self._execute_prompt(user_input)
353
+
354
+ # Remove status message
355
+ self.messages = [m for m in self.messages if m.role != "status"]
356
+
357
+ if response:
358
+ self.messages.append(ChatMessage(role="assistant", content=response))
359
+
360
+ output_text[0] = self._format_output()
361
+
362
+ @kb.add("c-c")
363
+ def handle_ctrl_c(event):
364
+ input_buffer.reset()
365
+
366
+ @kb.add("c-d")
367
+ def handle_ctrl_d(event):
368
+ if not input_buffer.text:
369
+ self._running = False
370
+ event.app.exit()
371
+
372
+ @kb.add("c-q")
373
+ def handle_ctrl_q(event):
374
+ self._running = False
375
+ event.app.exit()
376
+
377
+ # Style
378
+ style = Style.from_dict({
379
+ "output-area": "bg:#0d0d0d",
380
+ "input-area": "bg:#1a1a1a",
381
+ "status-bar": "bg:#333333 #888888",
382
+ "prompt": "#00aaff bold",
383
+ })
384
+
385
+ # Status bar text
386
+ def get_status_bar():
387
+ left = "? for help"
388
+ center = self.config.model
389
+ right = f"Session: {self.session_id}"
390
+ return f" {left} | {center} | {right} "
391
+
392
+ # Layout: Output (top, scrollable) + Status bar + Input (bottom, fixed)
393
+ layout = Layout(
394
+ HSplit([
395
+ # Output area (takes remaining space)
396
+ Window(
397
+ content=FormattedTextControl(get_output_text),
398
+ wrap_lines=True,
399
+ style="class:output-area",
400
+ ),
401
+ # Status bar (1 line)
402
+ Window(
403
+ content=FormattedTextControl(get_status_bar),
404
+ height=1,
405
+ style="class:status-bar",
406
+ ),
407
+ # Input area (fixed at bottom, 3 lines for visibility)
408
+ HSplit([
409
+ Window(height=1), # Spacer
410
+ VSplit([
411
+ Window(
412
+ content=FormattedTextControl([("class:prompt", "› ")]),
413
+ width=2,
414
+ ),
415
+ Window(
416
+ content=BufferControl(buffer=input_buffer),
417
+ wrap_lines=True,
418
+ ),
419
+ ]),
420
+ Window(height=1), # Spacer
421
+ ], height=3, style="class:input-area"),
422
+ ])
423
+ )
424
+
425
+ # Create application
426
+ app = Application(
427
+ layout=layout,
428
+ key_bindings=kb,
429
+ style=style,
430
+ full_screen=True,
431
+ mouse_support=True,
432
+ )
433
+
434
+ # Run
435
+ app.run()
436
+
437
+ print("\n Goodbye from Praison AI!")
438
+
439
+ def run_single(self, prompt: str) -> Optional[str]:
440
+ """Run a single prompt (non-interactive)."""
441
+ return self._execute_prompt(prompt)
442
+
443
+
444
+ def start_split_tui(
445
+ model: str = "gpt-4o-mini",
446
+ show_logo: bool = True,
447
+ **kwargs
448
+ ) -> None:
449
+ """Start the split-pane TUI application."""
450
+ config = SplitTUIConfig(
451
+ model=model,
452
+ show_logo=show_logo,
453
+ )
454
+
455
+ tui = SplitTUI(config=config)
456
+ tui.run()