march-cli 0.1.21 → 0.1.22

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 (242) hide show
  1. package/bin/march.mjs +13 -13
  2. package/package.json +43 -43
  3. package/src/agent/command-exec-tool.mjs +172 -168
  4. package/src/agent/context-stats-tool.mjs +57 -57
  5. package/src/agent/editing/diff-apply.mjs +28 -28
  6. package/src/agent/editing/diff-format.mjs +57 -57
  7. package/src/agent/editing/lsp-report.mjs +69 -69
  8. package/src/agent/file-edit-tool.mjs +262 -262
  9. package/src/agent/file-tools/read-file-tool.mjs +112 -112
  10. package/src/agent/file-tools/read-image-tool.mjs +76 -76
  11. package/src/agent/model-payload-dumper.mjs +208 -208
  12. package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
  13. package/src/agent/provider/payload-messages.mjs +138 -138
  14. package/src/agent/runner/codex-large-context-guard.mjs +87 -87
  15. package/src/agent/runner/codex-transport-compression.mjs +180 -180
  16. package/src/agent/runner/codex-transport-debug.mjs +113 -113
  17. package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
  18. package/src/agent/runner/fast-model.mjs +36 -36
  19. package/src/agent/runner/runner-cleanup.mjs +12 -12
  20. package/src/agent/runner/runner-init.mjs +15 -15
  21. package/src/agent/runner/runner-session-state.mjs +40 -40
  22. package/src/agent/runner/runner-utils.mjs +24 -24
  23. package/src/agent/runner.mjs +299 -299
  24. package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
  25. package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
  26. package/src/agent/runtime/remote-runner-client.mjs +73 -73
  27. package/src/agent/runtime/remote-ui-client.mjs +20 -20
  28. package/src/agent/runtime/runner-ipc-target.mjs +125 -125
  29. package/src/agent/runtime/runner-process-client.mjs +47 -47
  30. package/src/agent/runtime/runner-process-entry.mjs +11 -11
  31. package/src/agent/runtime/runner-process-factory.mjs +108 -108
  32. package/src/agent/runtime/runner-runtime-host.mjs +79 -79
  33. package/src/agent/runtime/runtime-factory.mjs +42 -42
  34. package/src/agent/runtime/runtime-host.mjs +34 -34
  35. package/src/agent/runtime/ui-event-bridge.mjs +95 -95
  36. package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
  37. package/src/agent/screen-tools/screen-tool.mjs +49 -49
  38. package/src/agent/screen-tools/windows-screen.mjs +133 -133
  39. package/src/agent/session/session-auto-name.mjs +41 -41
  40. package/src/agent/session/session-binding.mjs +12 -12
  41. package/src/agent/session/session-options.mjs +47 -47
  42. package/src/agent/tool-names.mjs +1 -1
  43. package/src/agent/tool-result.mjs +3 -3
  44. package/src/agent/tool-summary.mjs +112 -112
  45. package/src/agent/tools.mjs +58 -58
  46. package/src/agent/turn/turn-events.mjs +111 -111
  47. package/src/agent/turn/turn-logging.mjs +30 -30
  48. package/src/agent/turn/turn-runner.mjs +196 -196
  49. package/src/agent/vision-capability.mjs +14 -14
  50. package/src/auth/login-command.mjs +90 -90
  51. package/src/auth/storage.mjs +34 -34
  52. package/src/cli/args.mjs +79 -79
  53. package/src/cli/commands/copy-command.mjs +87 -87
  54. package/src/cli/commands/export-command.mjs +206 -206
  55. package/src/cli/commands/extensions-command.mjs +53 -53
  56. package/src/cli/commands/help-command.mjs +7 -7
  57. package/src/cli/commands/model-command.mjs +141 -141
  58. package/src/cli/commands/paste-image-command.mjs +43 -43
  59. package/src/cli/commands/provider-command.mjs +59 -59
  60. package/src/cli/commands/status-command.mjs +194 -194
  61. package/src/cli/commands/thinking-command.mjs +87 -87
  62. package/src/cli/fallback-ui.mjs +156 -156
  63. package/src/cli/input/attachment-tokens.mjs +20 -20
  64. package/src/cli/input/autocomplete.mjs +74 -106
  65. package/src/cli/input/external-editor.mjs +39 -39
  66. package/src/cli/input/file-search/index.mjs +160 -0
  67. package/src/cli/input/history-store.mjs +35 -35
  68. package/src/cli/input/image-clipboard.mjs +55 -55
  69. package/src/cli/input/keybinding-dispatch.mjs +76 -76
  70. package/src/cli/input/keybindings.mjs +96 -96
  71. package/src/cli/input/mode-state.mjs +43 -43
  72. package/src/cli/input/prompt-templates.mjs +84 -84
  73. package/src/cli/input/select-with-keyboard.mjs +86 -86
  74. package/src/cli/permissions.mjs +103 -103
  75. package/src/cli/repl-commands.mjs +86 -86
  76. package/src/cli/repl-loop.mjs +183 -183
  77. package/src/cli/selector-list.mjs +21 -21
  78. package/src/cli/session/pi-session-switch-command.mjs +41 -41
  79. package/src/cli/session/session-command.mjs +23 -23
  80. package/src/cli/session/session-list-command.mjs +68 -68
  81. package/src/cli/session/session-name-command.mjs +26 -26
  82. package/src/cli/session/session-source-command.mjs +89 -89
  83. package/src/cli/session/session-switch-command.mjs +1 -1
  84. package/src/cli/shell/shell-command.mjs +55 -55
  85. package/src/cli/shell/shell-drawer-controls.mjs +33 -33
  86. package/src/cli/shell/shell-drawer.mjs +192 -192
  87. package/src/cli/shell/shell-split-layout.mjs +70 -70
  88. package/src/cli/slash-commands.mjs +192 -192
  89. package/src/cli/startup/create-runtime-runner.mjs +61 -61
  90. package/src/cli/startup/runtime-close.mjs +23 -23
  91. package/src/cli/startup/startup-banner.mjs +71 -71
  92. package/src/cli/startup/startup-session.mjs +51 -51
  93. package/src/cli/status-line-updater.mjs +75 -75
  94. package/src/cli/tool-output.mjs +9 -9
  95. package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
  96. package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
  97. package/src/cli/tui/input/mouse-tracking.mjs +20 -20
  98. package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
  99. package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
  100. package/src/cli/tui/markdown-renderer.mjs +285 -285
  101. package/src/cli/tui/output/scroll-state.mjs +79 -79
  102. package/src/cli/tui/output/text-line-renderer.mjs +50 -50
  103. package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
  104. package/src/cli/tui/output/visible-lines.mjs +8 -8
  105. package/src/cli/tui/output-buffer.mjs +293 -293
  106. package/src/cli/tui/permission-request-ui.mjs +18 -18
  107. package/src/cli/tui/recall-rendering.mjs +25 -25
  108. package/src/cli/tui/render/render-scheduler.mjs +26 -26
  109. package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
  110. package/src/cli/tui/select/editor-select-list.mjs +111 -111
  111. package/src/cli/tui/selection-screen.mjs +269 -269
  112. package/src/cli/tui/status/retry-status.mjs +72 -72
  113. package/src/cli/tui/status/spinner-status.mjs +42 -42
  114. package/src/cli/tui/status/status-bar.mjs +225 -225
  115. package/src/cli/tui/syntax/highlighting.mjs +260 -260
  116. package/src/cli/tui/syntax/languages.mjs +91 -91
  117. package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
  118. package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
  119. package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
  120. package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
  121. package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
  122. package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
  123. package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
  124. package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
  125. package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
  126. package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
  127. package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
  128. package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
  129. package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
  130. package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
  131. package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
  132. package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
  133. package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
  134. package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
  135. package/src/cli/tui/tool-rendering.mjs +87 -87
  136. package/src/cli/tui/tui-diff-rendering.mjs +157 -157
  137. package/src/cli/tui/tui-handlers.mjs +111 -111
  138. package/src/cli/tui/tui-input-controller.mjs +61 -61
  139. package/src/cli/tui/ui-theme.mjs +157 -157
  140. package/src/cli/ui.mjs +297 -297
  141. package/src/config/config-json.mjs +84 -84
  142. package/src/config/dotenv.mjs +20 -20
  143. package/src/config/features.mjs +75 -75
  144. package/src/config/loader.mjs +143 -143
  145. package/src/config/settings-command.mjs +97 -97
  146. package/src/context/engine.mjs +198 -198
  147. package/src/context/injections.mjs +26 -26
  148. package/src/context/profiles.mjs +39 -39
  149. package/src/context/project-context.mjs +20 -20
  150. package/src/context/session-status.mjs +17 -17
  151. package/src/context/shell-layers.mjs +23 -23
  152. package/src/context/system-core/base.md +65 -65
  153. package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
  154. package/src/context/system-core/prompts/default.md +3 -3
  155. package/src/context/system-core.mjs +35 -35
  156. package/src/debug/logger.mjs +141 -141
  157. package/src/debug/model-context-dumper.mjs +52 -52
  158. package/src/extensions/discovery.mjs +40 -40
  159. package/src/extensions/lifecycle-adapter.mjs +210 -210
  160. package/src/extensions/lifecycle-manifest.mjs +69 -69
  161. package/src/image-gen/index.mjs +7 -7
  162. package/src/image-gen/provider.mjs +231 -231
  163. package/src/image-gen/tool.mjs +84 -84
  164. package/src/lsp/client.mjs +257 -257
  165. package/src/lsp/diagnostic-store.mjs +42 -42
  166. package/src/lsp/diagnostics-format.mjs +72 -72
  167. package/src/lsp/managed-node-server.mjs +99 -99
  168. package/src/lsp/path-match.mjs +10 -10
  169. package/src/lsp/server-definitions.mjs +188 -188
  170. package/src/lsp/servers.mjs +165 -165
  171. package/src/lsp/service.mjs +110 -110
  172. package/src/lsp/status-message.mjs +9 -9
  173. package/src/lsp/typescript-project-resolver.mjs +186 -186
  174. package/src/main.mjs +299 -299
  175. package/src/mcp/client.mjs +195 -195
  176. package/src/mcp/config.mjs +130 -130
  177. package/src/mcp/index.mjs +48 -48
  178. package/src/mcp/tools.mjs +98 -98
  179. package/src/memory/database.mjs +219 -219
  180. package/src/memory/glossary.mjs +124 -124
  181. package/src/memory/graph/graph-cascades.mjs +109 -109
  182. package/src/memory/graph/graph-diagnostics.mjs +73 -73
  183. package/src/memory/graph/graph-path-removal.mjs +50 -50
  184. package/src/memory/graph/graph-path-utils.mjs +17 -17
  185. package/src/memory/graph/graph-primitives.mjs +103 -103
  186. package/src/memory/graph/graph-read.mjs +159 -159
  187. package/src/memory/graph.mjs +282 -282
  188. package/src/memory/markdown/markdown-delete.mjs +23 -23
  189. package/src/memory/markdown/markdown-format.mjs +128 -128
  190. package/src/memory/markdown/markdown-recall.mjs +28 -28
  191. package/src/memory/markdown/ripgrep.mjs +16 -16
  192. package/src/memory/markdown/sqlite-index.mjs +87 -87
  193. package/src/memory/markdown-store.mjs +286 -286
  194. package/src/memory/markdown-tools.mjs +103 -103
  195. package/src/memory/search.mjs +142 -142
  196. package/src/memory/snapshot.mjs +86 -86
  197. package/src/memory/system-views.mjs +120 -120
  198. package/src/memory/tools.mjs +282 -282
  199. package/src/network/environment.mjs +131 -131
  200. package/src/notification/desktop-notifier.mjs +262 -262
  201. package/src/platform/open-file.mjs +28 -28
  202. package/src/platform/spawn-command.mjs +27 -27
  203. package/src/provider/accept-command.mjs +89 -89
  204. package/src/provider/command.mjs +21 -21
  205. package/src/provider/config-command.mjs +129 -129
  206. package/src/provider/custom-provider.mjs +113 -113
  207. package/src/provider/hosted-tools.mjs +111 -111
  208. package/src/provider/presets.mjs +72 -72
  209. package/src/provider/share-command.mjs +79 -79
  210. package/src/provider/share-payload.mjs +52 -52
  211. package/src/session/attachment-display.mjs +16 -16
  212. package/src/session/attachment-references.mjs +65 -65
  213. package/src/session/attachments.mjs +140 -140
  214. package/src/session/persist.mjs +1 -1
  215. package/src/session/pi-manager.mjs +34 -34
  216. package/src/session/session-utils.mjs +16 -16
  217. package/src/session/sidecar-sync.mjs +19 -19
  218. package/src/session/sidecar.mjs +69 -69
  219. package/src/session/transcript.mjs +83 -83
  220. package/src/session/tree.mjs +42 -42
  221. package/src/shell/cli-runtime.mjs +11 -11
  222. package/src/shell/hints.mjs +12 -12
  223. package/src/shell/node-pty-adapter.mjs +81 -81
  224. package/src/shell/runtime-state.mjs +126 -126
  225. package/src/shell/runtime.mjs +252 -252
  226. package/src/shell/screen-buffer.mjs +136 -136
  227. package/src/shell/tool-read.mjs +74 -74
  228. package/src/shell/tools.mjs +299 -299
  229. package/src/supergrok/actions/image-generate.mjs +60 -60
  230. package/src/supergrok/actions/search.mjs +78 -78
  231. package/src/supergrok/auth.mjs +36 -36
  232. package/src/supergrok/constants.mjs +18 -18
  233. package/src/supergrok/oauth-provider.mjs +278 -278
  234. package/src/supergrok/provider.mjs +35 -35
  235. package/src/supergrok/response.mjs +76 -76
  236. package/src/supergrok/tool.mjs +61 -61
  237. package/src/text/ansi.mjs +3 -3
  238. package/src/web/config-command.mjs +43 -43
  239. package/src/web/fetch.mjs +78 -78
  240. package/src/web/presets.mjs +16 -16
  241. package/src/web/search.mjs +83 -83
  242. package/src/web/tools.mjs +107 -107
package/src/cli/ui.mjs CHANGED
@@ -1,297 +1,297 @@
1
- import { stdout } from "node:process";
2
- import { Editor, ProcessTerminal, TUI } from "@earendil-works/pi-tui";
3
- import { writeSystemClipboardAsync } from "./commands/copy-command.mjs";
4
- import { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocomplete.mjs";
5
- import { createJsonUI, createPlainUI } from "./fallback-ui.mjs";
6
- import { createKeybindingDispatcher } from "./input/keybinding-dispatch.mjs";
7
- import { OutputBuffer } from "./tui/output-buffer.mjs";
8
- import { requestToolPermission } from "./tui/permission-request-ui.mjs";
9
- import { runTuiExternalEditor } from "./tui/editor/external-editor-runner.mjs";
10
- import { createRetryStatusController } from "./tui/status/retry-status.mjs";
11
- import { createShellDrawerControls } from "./shell/shell-drawer-controls.mjs";
12
- import { ShellDrawer } from "./shell/shell-drawer.mjs";
13
- import { ShellSplitLayout } from "./shell/shell-split-layout.mjs";
14
- import { createSpinnerStatusController } from "./tui/status/spinner-status.mjs";
15
- import { showEditorSelectList } from "./tui/select/editor-select-list.mjs";
16
- import { StatusBar } from "./tui/status/status-bar.mjs";
17
- import { MainPaneLayout } from "./tui/layout/main-pane-layout.mjs";
18
- import { SafeRenderBoundary } from "./tui/layout/safe-render-boundary.mjs";
19
- import { createMouseSelectionController } from "./tui/input/mouse-selection-controller.mjs";
20
- import { ScreenSelection } from "./tui/selection-screen.mjs";
21
- import { writeEditDiff } from "./tui/tui-diff-rendering.mjs";
22
- import { createTuiInputController } from "./tui/tui-input-controller.mjs";
23
- import { writeMemoryHint } from "./tui/recall-rendering.mjs";
24
- import { writeToolEnd, writeToolStart } from "./tui/tool-rendering.mjs";
25
- import { EDITOR_THEME, brightBlack } from "./tui/ui-theme.mjs";
26
- import { createRenderScheduler } from "./tui/render/render-scheduler.mjs";
27
- import { createStreamDeltaBuffer } from "./tui/render/stream-delta-buffer.mjs";
28
- import { writeTranscriptToOutput } from "../session/transcript.mjs";
29
-
30
- export { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocomplete.mjs";
31
-
32
- export function createTuiUI({
33
- cwd = process.cwd(),
34
- keybindings,
35
- promptTemplates = [],
36
- shellRuntime = null,
37
- historyStore = null,
38
- terminal = new ProcessTerminal(),
39
- writeClipboard = writeSystemClipboardAsync,
40
- } = {}) {
41
- const tui = new TUI(terminal);
42
- const output = new OutputBuffer();
43
- const shellDrawer = new ShellDrawer({ shellRuntime });
44
- const statusBar = new StatusBar(undefined, { cwd });
45
- const editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
46
- const selection = new ScreenSelection();
47
- const mainPane = new MainPaneLayout({ output, statusBar, editor, terminal, selection });
48
- const shellSplitLayout = new ShellSplitLayout({
49
- mainChildren: [mainPane],
50
- shellPane: shellDrawer,
51
- selection,
52
- });
53
- const autocomplete = new MarchAutocompleteProvider(buildMarchCommands(promptTemplates), cwd);
54
- editor.setAutocompleteProvider(autocomplete);
55
- editor.history = historyStore?.load?.() ?? [];
56
-
57
- tui.addChild(new SafeRenderBoundary(shellSplitLayout));
58
- tui.setFocus(editor);
59
-
60
- let started = false;
61
- let mouseOn = true;
62
- let toolsExpanded = false;
63
- const activeToolBlocks = [];
64
- const renderScheduler = createRenderScheduler({ requestRender: () => tui.requestRender() });
65
- const streamDeltas = createStreamDeltaBuffer({ writeText: (delta) => output.writeMarkdown(delta), writeThinking: (delta) => output.appendThinking(delta), renderSoon: renderScheduler.renderSoon });
66
- const flushStreamDeltas = () => streamDeltas.flush({ notify: false });
67
- const requestRender = () => { flushStreamDeltas(); renderScheduler.renderNow(); };
68
- const spinnerStatus = createSpinnerStatusController({ output, requestRender });
69
- const retryStatus = createRetryStatusController({ output, requestRender, stopSpinner: spinnerStatus.stop });
70
- const shellDrawerControls = createShellDrawerControls({ shellDrawer, output, requestRender });
71
- const mouseSelectionController = createMouseSelectionController({ terminal, output, shellDrawer, shellDrawerControls, selection, writeClipboard, requestRender });
72
-
73
- let onEscapeHandler = null, onCtrlCHandler = null, onShiftTabHandler = null;
74
- let onCtrlTHandler = null, onCtrlLHandler = null, onPasteImageHandler = null, onToggleModeHandler = null;
75
- const keybindingDispatcher = createKeybindingDispatcher({
76
- keybindings,
77
- handlers: {
78
- abort: () => onEscapeHandler?.(),
79
- interrupt: () => onCtrlCHandler?.(),
80
- toggleMode: () => onToggleModeHandler?.(),
81
- cycleThinking: () => onShiftTabHandler?.(),
82
- thinkingSelector: () => onCtrlTHandler?.(),
83
- modelSelector: () => onCtrlLHandler?.(),
84
- externalEditor: () => openExternalEditor(),
85
- toggleToolOutput: () => toggleToolOutput(),
86
- toggleShellDrawer: () => shellDrawerControls.toggle(),
87
- nextShell: () => shellDrawerControls.selectNext(),
88
- shellScrollUp: () => shellDrawerControls.scroll(-1),
89
- shellScrollDown: () => shellDrawerControls.scroll(1),
90
- outputScrollUp: () => { output.scroll(-1); requestRender(); },
91
- outputScrollDown: () => { output.scroll(1); requestRender(); },
92
- pasteImage: () => onPasteImageHandler?.(),
93
- },
94
- isAutocompleteOpen: () => editor.isShowingAutocomplete(),
95
- hasOverlay: () => tui.hasOverlay(),
96
- });
97
-
98
- function ensureStarted() {
99
- if (!started) {
100
- tui.addInputListener((data) => {
101
- const mouseResult = mouseSelectionController.handleMouseInput(data, mouseOn);
102
- if (mouseResult) return mouseResult;
103
- const copyKeyResult = mouseSelectionController.handleCopyKey(data);
104
- if (copyKeyResult) return copyKeyResult;
105
- const dispatched = keybindingDispatcher.dispatch(data);
106
- if (dispatched) return dispatched;
107
- // When output is scrolled up, the next render has fewer lines.
108
- // On new input, reset scroll to tail so the editor stays at bottom.
109
- if (output.scrollOffset > 0) {
110
- output.resetScroll();
111
- requestRender();
112
- }
113
- if (shellDrawer.isInputActive()) {
114
- shellDrawer.sendInput(data);
115
- requestRender();
116
- return { consume: true };
117
- }
118
- });
119
- terminal.write("\x1b[?1049h");
120
- terminal.write("\x1b[?1002h\x1b[?1006h");
121
- tui.start();
122
- started = true;
123
- }
124
- }
125
-
126
- function openExternalEditor() {
127
- runTuiExternalEditor({ terminal, tui, editor, output, requestRender, mouseOn: () => mouseOn });
128
- }
129
-
130
- function toggleToolOutput() {
131
- toolsExpanded = !toolsExpanded;
132
- output.setToolCardsExpanded(toolsExpanded);
133
- output.writeln(brightBlack(`● tool output: ${toolsExpanded ? "expanded" : "collapsed"}`));
134
- requestRender();
135
- return toolsExpanded;
136
- }
137
-
138
- function selectList({ items, selectedIndex = 0, maxVisible = 8, ...options }) {
139
- ensureStarted();
140
- return showEditorSelectList({ tui, editor, items, selectedIndex, maxVisible, requestRender, ...options });
141
- }
142
-
143
- function retryStart({ attempt, maxAttempts, delayMs, errorMessage }) {
144
- ensureStarted();
145
- retryStatus.start({ attempt, maxAttempts, delayMs, errorMessage });
146
- }
147
-
148
- function retryEnd({ success, attempt, finalError }) {
149
- ensureStarted();
150
- retryStatus.end({ success, attempt, finalError });
151
- }
152
-
153
- const inputController = createTuiInputController({ editor, requestRender, historyStore });
154
-
155
- return {
156
- readline: (_prompt) => {
157
- ensureStarted();
158
- return inputController.readline();
159
- },
160
- write: (text) => {
161
- ensureStarted();
162
- output.write(text);
163
- requestRender();
164
- },
165
- writeln: (text) => {
166
- ensureStarted();
167
- output.writeln(text);
168
- requestRender();
169
- },
170
- thinkingStart: () => {
171
- retryStatus.stop(); output.startThinking(); requestRender();
172
- },
173
-
174
- thinkingDelta: (delta) => streamDeltas.thinking(delta),
175
-
176
- thinkingEnd: (tokens) => {
177
- flushStreamDeltas();
178
- output.endThinking(tokens);
179
- requestRender();
180
- },
181
-
182
- thinkingBlock: (tokens, content) => {
183
- retryStatus.stop(); output.addThinkingBlock(tokens, content); requestRender();
184
- },
185
-
186
- toggleLastThinking: () => false,
187
-
188
- toolStart: (name, args) => {
189
- ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); activeToolBlocks.push(writeToolStart({ output, name, args })); requestRender();
190
- },
191
-
192
- toolEnd: (name, isError, result) => {
193
- if (writeToolEnd({ output, name, isError, result, toolsExpanded, toolBlock: activeToolBlocks.pop() })) requestRender();
194
- },
195
-
196
- textDelta: (delta) => {
197
- ensureStarted(); retryStatus.stop(); spinnerStatus.stop(); streamDeltas.text(delta);
198
- },
199
- assistantReplyEnd: () => {
200
- ensureStarted();
201
- flushStreamDeltas();
202
- const changed = output.ensureNewline();
203
- if (output.sealCurrentText() || changed) requestRender();
204
- },
205
- status: (text) => {
206
- ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.setOverlayStatus([brightBlack(`● ${text}`)]); requestRender();
207
- },
208
- memoryHint: ({ hints }) => {
209
- ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.ensureNewline(); writeMemoryHint({ output, hints }); requestRender();
210
- },
211
-
212
- clearOutput: () => {
213
- ensureStarted(); flushStreamDeltas(); spinnerStatus.stop(); retryStatus.stop(); output.clear(); requestRender();
214
- },
215
- restoreTranscript: (turns) => {
216
- ensureStarted(); flushStreamDeltas(); spinnerStatus.stop(); retryStatus.stop(); output.clear(); writeTranscriptToOutput(output, turns); requestRender();
217
- },
218
-
219
- setStatusBar: (text) => {
220
- if (statusBar.setText(text)) requestRender();
221
- },
222
-
223
- turnStart: () => {
224
- ensureStarted();
225
- },
226
-
227
- turnEnd: () => {
228
- flushStreamDeltas();
229
- const changed = output.ensureNewline();
230
- if (output.sealCurrentText() || changed) requestRender();
231
- },
232
-
233
- retryStart,
234
- retryEnd,
235
-
236
- editDiff: (path, diffLines) => {
237
- ensureStarted();
238
- flushStreamDeltas();
239
- spinnerStatus.stop();
240
- writeEditDiff({ output, path, diffLines });
241
- requestRender();
242
- },
243
-
244
- toggleMouse: () => {
245
- if (mouseOn) {
246
- terminal.write("\x1b[?1002l\x1b[?1006l");
247
- mouseOn = false;
248
- return false;
249
- } else {
250
- terminal.write("\x1b[?1002h\x1b[?1006h");
251
- mouseOn = true;
252
- return true;
253
- }
254
- },
255
-
256
- requestPermission: async ({ toolName, params, category }) => {
257
- ensureStarted();
258
- spinnerStatus.stop();
259
- return requestToolPermission({ toolName, params, category, output, selectList, requestRender });
260
- },
261
-
262
- setEscapeHandler: (fn) => { onEscapeHandler = fn; },
263
- setCtrlCHandler: (fn) => { onCtrlCHandler = fn; },
264
- setShiftTabHandler: (fn) => { onShiftTabHandler = fn; },
265
- setCtrlTHandler: (fn) => { onCtrlTHandler = fn; },
266
- setCtrlLHandler: (fn) => { onCtrlLHandler = fn; },
267
- setPasteImageHandler: (fn) => { onPasteImageHandler = fn; },
268
- setToggleModeHandler: (fn) => { onToggleModeHandler = fn; },
269
-
270
- selectList,
271
- getInputText: () => inputController.getInputText(),
272
- insertTextAtCursor: (text) => inputController.insertTextAtCursor(text),
273
- insertAttachmentAtCursor: (attachment) => inputController.insertAttachmentAtCursor(attachment),
274
- openExternalEditor: () => { openExternalEditor(); },
275
- toggleToolOutput,
276
- toggleShellDrawer: () => shellDrawerControls.toggle(),
277
- requestExit: () => inputController.requestExit(),
278
- close: async () => {
279
- flushStreamDeltas();
280
- renderScheduler.clearPending();
281
- spinnerStatus.stop();
282
- retryStatus.stop();
283
- if (started) {
284
- await terminal.drainInput?.();
285
- if (mouseOn) terminal.write("\x1b[?1002l\x1b[?1006l");
286
- tui.stop();
287
- terminal.write("\x1b[?1049l");
288
- }
289
- },
290
- };
291
- }
292
-
293
- export function createUI({ json, cwd = process.cwd(), keybindings, promptTemplates = [], shellRuntime = null, historyStore = null } = {}) {
294
- if (json) return createJsonUI();
295
- if (!stdout.isTTY) return createPlainUI();
296
- return createTuiUI({ cwd, keybindings, promptTemplates, shellRuntime, historyStore });
297
- }
1
+ import { stdout } from "node:process";
2
+ import { Editor, ProcessTerminal, TUI } from "@earendil-works/pi-tui";
3
+ import { writeSystemClipboardAsync } from "./commands/copy-command.mjs";
4
+ import { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocomplete.mjs";
5
+ import { createJsonUI, createPlainUI } from "./fallback-ui.mjs";
6
+ import { createKeybindingDispatcher } from "./input/keybinding-dispatch.mjs";
7
+ import { OutputBuffer } from "./tui/output-buffer.mjs";
8
+ import { requestToolPermission } from "./tui/permission-request-ui.mjs";
9
+ import { runTuiExternalEditor } from "./tui/editor/external-editor-runner.mjs";
10
+ import { createRetryStatusController } from "./tui/status/retry-status.mjs";
11
+ import { createShellDrawerControls } from "./shell/shell-drawer-controls.mjs";
12
+ import { ShellDrawer } from "./shell/shell-drawer.mjs";
13
+ import { ShellSplitLayout } from "./shell/shell-split-layout.mjs";
14
+ import { createSpinnerStatusController } from "./tui/status/spinner-status.mjs";
15
+ import { showEditorSelectList } from "./tui/select/editor-select-list.mjs";
16
+ import { StatusBar } from "./tui/status/status-bar.mjs";
17
+ import { MainPaneLayout } from "./tui/layout/main-pane-layout.mjs";
18
+ import { SafeRenderBoundary } from "./tui/layout/safe-render-boundary.mjs";
19
+ import { createMouseSelectionController } from "./tui/input/mouse-selection-controller.mjs";
20
+ import { ScreenSelection } from "./tui/selection-screen.mjs";
21
+ import { writeEditDiff } from "./tui/tui-diff-rendering.mjs";
22
+ import { createTuiInputController } from "./tui/tui-input-controller.mjs";
23
+ import { writeMemoryHint } from "./tui/recall-rendering.mjs";
24
+ import { writeToolEnd, writeToolStart } from "./tui/tool-rendering.mjs";
25
+ import { EDITOR_THEME, brightBlack } from "./tui/ui-theme.mjs";
26
+ import { createRenderScheduler } from "./tui/render/render-scheduler.mjs";
27
+ import { createStreamDeltaBuffer } from "./tui/render/stream-delta-buffer.mjs";
28
+ import { writeTranscriptToOutput } from "../session/transcript.mjs";
29
+
30
+ export { buildMarchCommands, MarchAutocompleteProvider } from "./input/autocomplete.mjs";
31
+
32
+ export function createTuiUI({
33
+ cwd = process.cwd(),
34
+ keybindings,
35
+ promptTemplates = [],
36
+ shellRuntime = null,
37
+ historyStore = null,
38
+ terminal = new ProcessTerminal(),
39
+ writeClipboard = writeSystemClipboardAsync,
40
+ } = {}) {
41
+ const tui = new TUI(terminal);
42
+ const output = new OutputBuffer();
43
+ const shellDrawer = new ShellDrawer({ shellRuntime });
44
+ const statusBar = new StatusBar(undefined, { cwd });
45
+ const editor = new Editor(tui, EDITOR_THEME, { paddingX: 1 });
46
+ const selection = new ScreenSelection();
47
+ const mainPane = new MainPaneLayout({ output, statusBar, editor, terminal, selection });
48
+ const shellSplitLayout = new ShellSplitLayout({
49
+ mainChildren: [mainPane],
50
+ shellPane: shellDrawer,
51
+ selection,
52
+ });
53
+ const autocomplete = new MarchAutocompleteProvider(buildMarchCommands(promptTemplates), cwd);
54
+ editor.setAutocompleteProvider(autocomplete);
55
+ editor.history = historyStore?.load?.() ?? [];
56
+
57
+ tui.addChild(new SafeRenderBoundary(shellSplitLayout));
58
+ tui.setFocus(editor);
59
+
60
+ let started = false;
61
+ let mouseOn = true;
62
+ let toolsExpanded = false;
63
+ const activeToolBlocks = [];
64
+ const renderScheduler = createRenderScheduler({ requestRender: () => tui.requestRender() });
65
+ const streamDeltas = createStreamDeltaBuffer({ writeText: (delta) => output.writeMarkdown(delta), writeThinking: (delta) => output.appendThinking(delta), renderSoon: renderScheduler.renderSoon });
66
+ const flushStreamDeltas = () => streamDeltas.flush({ notify: false });
67
+ const requestRender = () => { flushStreamDeltas(); renderScheduler.renderNow(); };
68
+ const spinnerStatus = createSpinnerStatusController({ output, requestRender });
69
+ const retryStatus = createRetryStatusController({ output, requestRender, stopSpinner: spinnerStatus.stop });
70
+ const shellDrawerControls = createShellDrawerControls({ shellDrawer, output, requestRender });
71
+ const mouseSelectionController = createMouseSelectionController({ terminal, output, shellDrawer, shellDrawerControls, selection, writeClipboard, requestRender });
72
+
73
+ let onEscapeHandler = null, onCtrlCHandler = null, onShiftTabHandler = null;
74
+ let onCtrlTHandler = null, onCtrlLHandler = null, onPasteImageHandler = null, onToggleModeHandler = null;
75
+ const keybindingDispatcher = createKeybindingDispatcher({
76
+ keybindings,
77
+ handlers: {
78
+ abort: () => onEscapeHandler?.(),
79
+ interrupt: () => onCtrlCHandler?.(),
80
+ toggleMode: () => onToggleModeHandler?.(),
81
+ cycleThinking: () => onShiftTabHandler?.(),
82
+ thinkingSelector: () => onCtrlTHandler?.(),
83
+ modelSelector: () => onCtrlLHandler?.(),
84
+ externalEditor: () => openExternalEditor(),
85
+ toggleToolOutput: () => toggleToolOutput(),
86
+ toggleShellDrawer: () => shellDrawerControls.toggle(),
87
+ nextShell: () => shellDrawerControls.selectNext(),
88
+ shellScrollUp: () => shellDrawerControls.scroll(-1),
89
+ shellScrollDown: () => shellDrawerControls.scroll(1),
90
+ outputScrollUp: () => { output.scroll(-1); requestRender(); },
91
+ outputScrollDown: () => { output.scroll(1); requestRender(); },
92
+ pasteImage: () => onPasteImageHandler?.(),
93
+ },
94
+ isAutocompleteOpen: () => editor.isShowingAutocomplete(),
95
+ hasOverlay: () => tui.hasOverlay(),
96
+ });
97
+
98
+ function ensureStarted() {
99
+ if (!started) {
100
+ tui.addInputListener((data) => {
101
+ const mouseResult = mouseSelectionController.handleMouseInput(data, mouseOn);
102
+ if (mouseResult) return mouseResult;
103
+ const copyKeyResult = mouseSelectionController.handleCopyKey(data);
104
+ if (copyKeyResult) return copyKeyResult;
105
+ const dispatched = keybindingDispatcher.dispatch(data);
106
+ if (dispatched) return dispatched;
107
+ // When output is scrolled up, the next render has fewer lines.
108
+ // On new input, reset scroll to tail so the editor stays at bottom.
109
+ if (output.scrollOffset > 0) {
110
+ output.resetScroll();
111
+ requestRender();
112
+ }
113
+ if (shellDrawer.isInputActive()) {
114
+ shellDrawer.sendInput(data);
115
+ requestRender();
116
+ return { consume: true };
117
+ }
118
+ });
119
+ terminal.write("\x1b[?1049h");
120
+ terminal.write("\x1b[?1002h\x1b[?1006h");
121
+ tui.start();
122
+ started = true;
123
+ }
124
+ }
125
+
126
+ function openExternalEditor() {
127
+ runTuiExternalEditor({ terminal, tui, editor, output, requestRender, mouseOn: () => mouseOn });
128
+ }
129
+
130
+ function toggleToolOutput() {
131
+ toolsExpanded = !toolsExpanded;
132
+ output.setToolCardsExpanded(toolsExpanded);
133
+ output.writeln(brightBlack(`● tool output: ${toolsExpanded ? "expanded" : "collapsed"}`));
134
+ requestRender();
135
+ return toolsExpanded;
136
+ }
137
+
138
+ function selectList({ items, selectedIndex = 0, maxVisible = 8, ...options }) {
139
+ ensureStarted();
140
+ return showEditorSelectList({ tui, editor, items, selectedIndex, maxVisible, requestRender, ...options });
141
+ }
142
+
143
+ function retryStart({ attempt, maxAttempts, delayMs, errorMessage }) {
144
+ ensureStarted();
145
+ retryStatus.start({ attempt, maxAttempts, delayMs, errorMessage });
146
+ }
147
+
148
+ function retryEnd({ success, attempt, finalError }) {
149
+ ensureStarted();
150
+ retryStatus.end({ success, attempt, finalError });
151
+ }
152
+
153
+ const inputController = createTuiInputController({ editor, requestRender, historyStore });
154
+
155
+ return {
156
+ readline: (_prompt) => {
157
+ ensureStarted();
158
+ return inputController.readline();
159
+ },
160
+ write: (text) => {
161
+ ensureStarted();
162
+ output.write(text);
163
+ requestRender();
164
+ },
165
+ writeln: (text) => {
166
+ ensureStarted();
167
+ output.writeln(text);
168
+ requestRender();
169
+ },
170
+ thinkingStart: () => {
171
+ retryStatus.stop(); output.startThinking(); requestRender();
172
+ },
173
+
174
+ thinkingDelta: (delta) => streamDeltas.thinking(delta),
175
+
176
+ thinkingEnd: (tokens) => {
177
+ flushStreamDeltas();
178
+ output.endThinking(tokens);
179
+ requestRender();
180
+ },
181
+
182
+ thinkingBlock: (tokens, content) => {
183
+ retryStatus.stop(); output.addThinkingBlock(tokens, content); requestRender();
184
+ },
185
+
186
+ toggleLastThinking: () => false,
187
+
188
+ toolStart: (name, args) => {
189
+ ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); activeToolBlocks.push(writeToolStart({ output, name, args })); requestRender();
190
+ },
191
+
192
+ toolEnd: (name, isError, result) => {
193
+ if (writeToolEnd({ output, name, isError, result, toolsExpanded, toolBlock: activeToolBlocks.pop() })) requestRender();
194
+ },
195
+
196
+ textDelta: (delta) => {
197
+ ensureStarted(); retryStatus.stop(); spinnerStatus.stop(); streamDeltas.text(delta);
198
+ },
199
+ assistantReplyEnd: () => {
200
+ ensureStarted();
201
+ flushStreamDeltas();
202
+ const changed = output.ensureNewline();
203
+ if (output.sealCurrentText() || changed) requestRender();
204
+ },
205
+ status: (text) => {
206
+ ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.setOverlayStatus([brightBlack(`● ${text}`)]); requestRender();
207
+ },
208
+ memoryHint: ({ hints }) => {
209
+ ensureStarted(); flushStreamDeltas(); retryStatus.stop(); spinnerStatus.stop(); output.ensureNewline(); writeMemoryHint({ output, hints }); requestRender();
210
+ },
211
+
212
+ clearOutput: () => {
213
+ ensureStarted(); flushStreamDeltas(); spinnerStatus.stop(); retryStatus.stop(); output.clear(); requestRender();
214
+ },
215
+ restoreTranscript: (turns) => {
216
+ ensureStarted(); flushStreamDeltas(); spinnerStatus.stop(); retryStatus.stop(); output.clear(); writeTranscriptToOutput(output, turns); requestRender();
217
+ },
218
+
219
+ setStatusBar: (text) => {
220
+ if (statusBar.setText(text)) requestRender();
221
+ },
222
+
223
+ turnStart: () => {
224
+ ensureStarted();
225
+ },
226
+
227
+ turnEnd: () => {
228
+ flushStreamDeltas();
229
+ const changed = output.ensureNewline();
230
+ if (output.sealCurrentText() || changed) requestRender();
231
+ },
232
+
233
+ retryStart,
234
+ retryEnd,
235
+
236
+ editDiff: (path, diffLines) => {
237
+ ensureStarted();
238
+ flushStreamDeltas();
239
+ spinnerStatus.stop();
240
+ writeEditDiff({ output, path, diffLines });
241
+ requestRender();
242
+ },
243
+
244
+ toggleMouse: () => {
245
+ if (mouseOn) {
246
+ terminal.write("\x1b[?1002l\x1b[?1006l");
247
+ mouseOn = false;
248
+ return false;
249
+ } else {
250
+ terminal.write("\x1b[?1002h\x1b[?1006h");
251
+ mouseOn = true;
252
+ return true;
253
+ }
254
+ },
255
+
256
+ requestPermission: async ({ toolName, params, category }) => {
257
+ ensureStarted();
258
+ spinnerStatus.stop();
259
+ return requestToolPermission({ toolName, params, category, output, selectList, requestRender });
260
+ },
261
+
262
+ setEscapeHandler: (fn) => { onEscapeHandler = fn; },
263
+ setCtrlCHandler: (fn) => { onCtrlCHandler = fn; },
264
+ setShiftTabHandler: (fn) => { onShiftTabHandler = fn; },
265
+ setCtrlTHandler: (fn) => { onCtrlTHandler = fn; },
266
+ setCtrlLHandler: (fn) => { onCtrlLHandler = fn; },
267
+ setPasteImageHandler: (fn) => { onPasteImageHandler = fn; },
268
+ setToggleModeHandler: (fn) => { onToggleModeHandler = fn; },
269
+
270
+ selectList,
271
+ getInputText: () => inputController.getInputText(),
272
+ insertTextAtCursor: (text) => inputController.insertTextAtCursor(text),
273
+ insertAttachmentAtCursor: (attachment) => inputController.insertAttachmentAtCursor(attachment),
274
+ openExternalEditor: () => { openExternalEditor(); },
275
+ toggleToolOutput,
276
+ toggleShellDrawer: () => shellDrawerControls.toggle(),
277
+ requestExit: () => inputController.requestExit(),
278
+ close: async () => {
279
+ flushStreamDeltas();
280
+ renderScheduler.clearPending();
281
+ spinnerStatus.stop();
282
+ retryStatus.stop();
283
+ if (started) {
284
+ await terminal.drainInput?.();
285
+ if (mouseOn) terminal.write("\x1b[?1002l\x1b[?1006l");
286
+ tui.stop();
287
+ terminal.write("\x1b[?1049l");
288
+ }
289
+ },
290
+ };
291
+ }
292
+
293
+ export function createUI({ json, cwd = process.cwd(), keybindings, promptTemplates = [], shellRuntime = null, historyStore = null } = {}) {
294
+ if (json) return createJsonUI();
295
+ if (!stdout.isTTY) return createPlainUI();
296
+ return createTuiUI({ cwd, keybindings, promptTemplates, shellRuntime, historyStore });
297
+ }