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
@@ -1,252 +1,252 @@
1
- import { randomUUID } from "node:crypto";
2
- import { createTerminalScreenBuffer } from "./screen-buffer.mjs";
3
- import {
4
- appendOutput,
5
- closeShellForReplacement,
6
- findShellIdByName,
7
- isPromptNoise,
8
- markExited,
9
- markFailed,
10
- normalizeSize,
11
- publicShell,
12
- requireShell,
13
- touch,
14
- uniqueName,
15
- } from "./runtime-state.mjs";
16
-
17
- export { stripAnsi } from "./runtime-state.mjs";
18
-
19
- export function createShellRuntime({
20
- createPty,
21
- now = () => new Date(),
22
- maxScrollbackLines = 200,
23
- idFactory = () => randomUUID().slice(0, 8),
24
- defaultCommand = process.platform === "win32" ? "powershell.exe" : process.env.SHELL || "sh",
25
- defaultArgs = process.platform === "win32" ? ["-NoLogo", "-NoProfile"] : [],
26
- defaultCols = 120,
27
- defaultRows = 24,
28
- createScreenBuffer = createTerminalScreenBuffer,
29
- } = {}) {
30
- if (typeof createPty !== "function") {
31
- throw new Error("createPty is required");
32
- }
33
-
34
- const shells = new Map();
35
-
36
- function spawnShell({
37
- name,
38
- command,
39
- args = [],
40
- cwd = process.cwd(),
41
- env = process.env,
42
- cols = defaultCols,
43
- rows = defaultRows,
44
- nameConflict = "suffix",
45
- } = {}) {
46
- const resolvedCommand = command || defaultCommand;
47
- const resolvedArgs = command ? args : (args.length ? args : defaultArgs);
48
- const baseName = String(name || resolvedCommand || "shell").trim() || "shell";
49
- const existing = shells.get(findShellIdByName(shells, baseName));
50
- if (existing && nameConflict === "reuse") {
51
- return publicShell(existing);
52
- }
53
- if (existing && nameConflict === "replace") {
54
- closeShellForReplacement(shells, existing);
55
- }
56
- const size = normalizeSize({ cols, rows, fallbackCols: defaultCols, fallbackRows: defaultRows });
57
- const id = idFactory();
58
- const shell = {
59
- id,
60
- name: uniqueName(baseName, shells),
61
- command: resolvedCommand,
62
- args: [...resolvedArgs],
63
- cwd,
64
- status: "starting",
65
- exitCode: null,
66
- signal: null,
67
- error: null,
68
- cols: size.cols,
69
- rows: size.rows,
70
- createdAt: now().toISOString(),
71
- updatedAt: now().toISOString(),
72
- rawChunks: [],
73
- plainLines: [],
74
- screen: createScreenBuffer({ cols: size.cols, rows: size.rows }),
75
- pty: null,
76
- };
77
- shells.set(id, shell);
78
-
79
- try {
80
- shell.pty = createPty({
81
- command: resolvedCommand,
82
- args: shell.args,
83
- cwd,
84
- env,
85
- cols: shell.cols,
86
- rows: shell.rows,
87
- onData: (chunk) => appendOutput(shell, chunk, maxScrollbackLines),
88
- onExit: (event = {}) => markExited(shell, event),
89
- onError: (error) => markFailed(shell, error),
90
- });
91
- shell.status = "running";
92
- touch(shell, now);
93
- } catch (error) {
94
- markFailed(shell, error);
95
- }
96
-
97
- return publicShell(shell);
98
- }
99
-
100
- function sendShell(id, text) {
101
- const shell = requireShell(shells, id);
102
- if (shell.status !== "running") {
103
- return { ok: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
104
- }
105
- shell.pty.write(String(text ?? ""));
106
- touch(shell, now);
107
- return { ok: true, shell: publicShell(shell) };
108
- }
109
-
110
- function resizeShell(id, { cols, rows } = {}) {
111
- const shell = requireShell(shells, id);
112
- const size = normalizeSize({ cols, rows, fallbackCols: shell.cols, fallbackRows: shell.rows });
113
- if (shell.cols === size.cols && shell.rows === size.rows) {
114
- return { ok: true, changed: false, shell: publicShell(shell) };
115
- }
116
- if (shell.status !== "running" && shell.status !== "starting") {
117
- return { ok: false, changed: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
118
- }
119
- if (typeof shell.pty?.resize !== "function") {
120
- return { ok: false, changed: false, error: `shell ${id} does not support resize`, shell: publicShell(shell) };
121
- }
122
- try {
123
- shell.pty.resize(size.cols, size.rows);
124
- } catch (error) {
125
- shell.status = "failed";
126
- shell.error = `resize failed: ${error?.message ?? String(error)}`;
127
- touch(shell, now);
128
- return { ok: false, changed: true, error: shell.error, shell: publicShell(shell) };
129
- }
130
- shell.cols = size.cols;
131
- shell.rows = size.rows;
132
- shell.screen?.resize?.(shell.cols, shell.rows);
133
- touch(shell, now);
134
- return { ok: true, changed: true, shell: publicShell(shell) };
135
- }
136
-
137
- function killShell(id) {
138
- const shell = requireShell(shells, id);
139
- if (shell.status !== "running" && shell.status !== "starting") {
140
- return { ok: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
141
- }
142
- shell.status = "killed";
143
- touch(shell, now);
144
- try {
145
- shell.pty?.kill?.();
146
- } catch (error) {
147
- shell.status = "failed";
148
- shell.error = `kill failed: ${error?.message ?? String(error)}`;
149
- touch(shell, now);
150
- return { ok: false, error: shell.error, shell: publicShell(shell) };
151
- }
152
- return { ok: true, shell: publicShell(shell) };
153
- }
154
-
155
- function listShells() {
156
- return [...shells.values()].map(publicShell);
157
- }
158
-
159
- function getShell(id) {
160
- const shell = shells.get(id);
161
- return shell ? publicShell(shell) : null;
162
- }
163
-
164
- function searchShell(id, pattern, { source = "auto", includePrompts = false } = {}) {
165
- const shell = requireShell(shells, id);
166
- const needle = String(pattern ?? "");
167
- const screenLines = shell.screen?.snapshot?.().plain?.split("\n") ?? [];
168
- const scrollbackLines = shell.plainLines;
169
- const lines = source === "screen" ? screenLines : source === "scrollback" ? scrollbackLines : screenLines;
170
- let matches = findLineMatches(lines, needle, shell, includePrompts);
171
- let resolvedSource = source === "auto" ? "screen" : source;
172
- if (source === "auto" && matches.length === 0) {
173
- matches = findLineMatches(scrollbackLines, needle, shell, includePrompts);
174
- resolvedSource = "scrollback";
175
- }
176
- return { shell: publicShell(shell), matches, source: resolvedSource };
177
- }
178
-
179
- function findLineMatches(lines, needle, shell, includePrompts) {
180
- return lines
181
- .map((line, index) => ({ index, line }))
182
- .filter(({ line }) => includePrompts || !isPromptNoise(line, shell))
183
- .filter(({ line }) => line.includes(needle));
184
- }
185
-
186
- function snapshotShell(id) {
187
- const shell = requireShell(shells, id);
188
- return {
189
- shell: publicShell(shell),
190
- ansi: shell.rawChunks.join(""),
191
- plain: shell.plainLines.join("\n"),
192
- screen: shell.screen?.snapshot?.() ?? null,
193
- };
194
- }
195
-
196
- function snapshotShellScreen(id) {
197
- const shell = requireShell(shells, id);
198
- return {
199
- shell: publicShell(shell),
200
- screen: shell.screen?.snapshot?.() ?? null,
201
- };
202
- }
203
-
204
- function clearShell(id) {
205
- const shell = requireShell(shells, id);
206
- shell.rawChunks = [];
207
- shell.plainLines = [];
208
- shell.screen?.dispose?.();
209
- shell.screen = createScreenBuffer({ cols: shell.cols, rows: shell.rows });
210
- touch(shell, now);
211
- return { ok: true, shell: publicShell(shell) };
212
- }
213
-
214
- function killAll() {
215
- const results = [];
216
- for (const shell of shells.values()) {
217
- if (shell.status === "running" || shell.status === "starting") {
218
- results.push(killShell(shell.id));
219
- }
220
- }
221
- return results;
222
- }
223
-
224
- function dispose() {
225
- const results = killAll();
226
- for (const shell of shells.values()) {
227
- try {
228
- shell.pty?.dispose?.();
229
- shell.screen?.dispose?.();
230
- } catch (error) {
231
- shell.error = `dispose failed: ${error?.message ?? String(error)}`;
232
- touch(shell, now);
233
- }
234
- }
235
- return results;
236
- }
237
-
238
- return {
239
- spawnShell,
240
- sendShell,
241
- resizeShell,
242
- killShell,
243
- killAll,
244
- listShells,
245
- getShell,
246
- searchShell,
247
- snapshotShell,
248
- snapshotShellScreen,
249
- clearShell,
250
- dispose,
251
- };
252
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { createTerminalScreenBuffer } from "./screen-buffer.mjs";
3
+ import {
4
+ appendOutput,
5
+ closeShellForReplacement,
6
+ findShellIdByName,
7
+ isPromptNoise,
8
+ markExited,
9
+ markFailed,
10
+ normalizeSize,
11
+ publicShell,
12
+ requireShell,
13
+ touch,
14
+ uniqueName,
15
+ } from "./runtime-state.mjs";
16
+
17
+ export { stripAnsi } from "./runtime-state.mjs";
18
+
19
+ export function createShellRuntime({
20
+ createPty,
21
+ now = () => new Date(),
22
+ maxScrollbackLines = 200,
23
+ idFactory = () => randomUUID().slice(0, 8),
24
+ defaultCommand = process.platform === "win32" ? "powershell.exe" : process.env.SHELL || "sh",
25
+ defaultArgs = process.platform === "win32" ? ["-NoLogo", "-NoProfile"] : [],
26
+ defaultCols = 120,
27
+ defaultRows = 24,
28
+ createScreenBuffer = createTerminalScreenBuffer,
29
+ } = {}) {
30
+ if (typeof createPty !== "function") {
31
+ throw new Error("createPty is required");
32
+ }
33
+
34
+ const shells = new Map();
35
+
36
+ function spawnShell({
37
+ name,
38
+ command,
39
+ args = [],
40
+ cwd = process.cwd(),
41
+ env = process.env,
42
+ cols = defaultCols,
43
+ rows = defaultRows,
44
+ nameConflict = "suffix",
45
+ } = {}) {
46
+ const resolvedCommand = command || defaultCommand;
47
+ const resolvedArgs = command ? args : (args.length ? args : defaultArgs);
48
+ const baseName = String(name || resolvedCommand || "shell").trim() || "shell";
49
+ const existing = shells.get(findShellIdByName(shells, baseName));
50
+ if (existing && nameConflict === "reuse") {
51
+ return publicShell(existing);
52
+ }
53
+ if (existing && nameConflict === "replace") {
54
+ closeShellForReplacement(shells, existing);
55
+ }
56
+ const size = normalizeSize({ cols, rows, fallbackCols: defaultCols, fallbackRows: defaultRows });
57
+ const id = idFactory();
58
+ const shell = {
59
+ id,
60
+ name: uniqueName(baseName, shells),
61
+ command: resolvedCommand,
62
+ args: [...resolvedArgs],
63
+ cwd,
64
+ status: "starting",
65
+ exitCode: null,
66
+ signal: null,
67
+ error: null,
68
+ cols: size.cols,
69
+ rows: size.rows,
70
+ createdAt: now().toISOString(),
71
+ updatedAt: now().toISOString(),
72
+ rawChunks: [],
73
+ plainLines: [],
74
+ screen: createScreenBuffer({ cols: size.cols, rows: size.rows }),
75
+ pty: null,
76
+ };
77
+ shells.set(id, shell);
78
+
79
+ try {
80
+ shell.pty = createPty({
81
+ command: resolvedCommand,
82
+ args: shell.args,
83
+ cwd,
84
+ env,
85
+ cols: shell.cols,
86
+ rows: shell.rows,
87
+ onData: (chunk) => appendOutput(shell, chunk, maxScrollbackLines),
88
+ onExit: (event = {}) => markExited(shell, event),
89
+ onError: (error) => markFailed(shell, error),
90
+ });
91
+ shell.status = "running";
92
+ touch(shell, now);
93
+ } catch (error) {
94
+ markFailed(shell, error);
95
+ }
96
+
97
+ return publicShell(shell);
98
+ }
99
+
100
+ function sendShell(id, text) {
101
+ const shell = requireShell(shells, id);
102
+ if (shell.status !== "running") {
103
+ return { ok: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
104
+ }
105
+ shell.pty.write(String(text ?? ""));
106
+ touch(shell, now);
107
+ return { ok: true, shell: publicShell(shell) };
108
+ }
109
+
110
+ function resizeShell(id, { cols, rows } = {}) {
111
+ const shell = requireShell(shells, id);
112
+ const size = normalizeSize({ cols, rows, fallbackCols: shell.cols, fallbackRows: shell.rows });
113
+ if (shell.cols === size.cols && shell.rows === size.rows) {
114
+ return { ok: true, changed: false, shell: publicShell(shell) };
115
+ }
116
+ if (shell.status !== "running" && shell.status !== "starting") {
117
+ return { ok: false, changed: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
118
+ }
119
+ if (typeof shell.pty?.resize !== "function") {
120
+ return { ok: false, changed: false, error: `shell ${id} does not support resize`, shell: publicShell(shell) };
121
+ }
122
+ try {
123
+ shell.pty.resize(size.cols, size.rows);
124
+ } catch (error) {
125
+ shell.status = "failed";
126
+ shell.error = `resize failed: ${error?.message ?? String(error)}`;
127
+ touch(shell, now);
128
+ return { ok: false, changed: true, error: shell.error, shell: publicShell(shell) };
129
+ }
130
+ shell.cols = size.cols;
131
+ shell.rows = size.rows;
132
+ shell.screen?.resize?.(shell.cols, shell.rows);
133
+ touch(shell, now);
134
+ return { ok: true, changed: true, shell: publicShell(shell) };
135
+ }
136
+
137
+ function killShell(id) {
138
+ const shell = requireShell(shells, id);
139
+ if (shell.status !== "running" && shell.status !== "starting") {
140
+ return { ok: false, error: `shell ${id} is ${shell.status}`, shell: publicShell(shell) };
141
+ }
142
+ shell.status = "killed";
143
+ touch(shell, now);
144
+ try {
145
+ shell.pty?.kill?.();
146
+ } catch (error) {
147
+ shell.status = "failed";
148
+ shell.error = `kill failed: ${error?.message ?? String(error)}`;
149
+ touch(shell, now);
150
+ return { ok: false, error: shell.error, shell: publicShell(shell) };
151
+ }
152
+ return { ok: true, shell: publicShell(shell) };
153
+ }
154
+
155
+ function listShells() {
156
+ return [...shells.values()].map(publicShell);
157
+ }
158
+
159
+ function getShell(id) {
160
+ const shell = shells.get(id);
161
+ return shell ? publicShell(shell) : null;
162
+ }
163
+
164
+ function searchShell(id, pattern, { source = "auto", includePrompts = false } = {}) {
165
+ const shell = requireShell(shells, id);
166
+ const needle = String(pattern ?? "");
167
+ const screenLines = shell.screen?.snapshot?.().plain?.split("\n") ?? [];
168
+ const scrollbackLines = shell.plainLines;
169
+ const lines = source === "screen" ? screenLines : source === "scrollback" ? scrollbackLines : screenLines;
170
+ let matches = findLineMatches(lines, needle, shell, includePrompts);
171
+ let resolvedSource = source === "auto" ? "screen" : source;
172
+ if (source === "auto" && matches.length === 0) {
173
+ matches = findLineMatches(scrollbackLines, needle, shell, includePrompts);
174
+ resolvedSource = "scrollback";
175
+ }
176
+ return { shell: publicShell(shell), matches, source: resolvedSource };
177
+ }
178
+
179
+ function findLineMatches(lines, needle, shell, includePrompts) {
180
+ return lines
181
+ .map((line, index) => ({ index, line }))
182
+ .filter(({ line }) => includePrompts || !isPromptNoise(line, shell))
183
+ .filter(({ line }) => line.includes(needle));
184
+ }
185
+
186
+ function snapshotShell(id) {
187
+ const shell = requireShell(shells, id);
188
+ return {
189
+ shell: publicShell(shell),
190
+ ansi: shell.rawChunks.join(""),
191
+ plain: shell.plainLines.join("\n"),
192
+ screen: shell.screen?.snapshot?.() ?? null,
193
+ };
194
+ }
195
+
196
+ function snapshotShellScreen(id) {
197
+ const shell = requireShell(shells, id);
198
+ return {
199
+ shell: publicShell(shell),
200
+ screen: shell.screen?.snapshot?.() ?? null,
201
+ };
202
+ }
203
+
204
+ function clearShell(id) {
205
+ const shell = requireShell(shells, id);
206
+ shell.rawChunks = [];
207
+ shell.plainLines = [];
208
+ shell.screen?.dispose?.();
209
+ shell.screen = createScreenBuffer({ cols: shell.cols, rows: shell.rows });
210
+ touch(shell, now);
211
+ return { ok: true, shell: publicShell(shell) };
212
+ }
213
+
214
+ function killAll() {
215
+ const results = [];
216
+ for (const shell of shells.values()) {
217
+ if (shell.status === "running" || shell.status === "starting") {
218
+ results.push(killShell(shell.id));
219
+ }
220
+ }
221
+ return results;
222
+ }
223
+
224
+ function dispose() {
225
+ const results = killAll();
226
+ for (const shell of shells.values()) {
227
+ try {
228
+ shell.pty?.dispose?.();
229
+ shell.screen?.dispose?.();
230
+ } catch (error) {
231
+ shell.error = `dispose failed: ${error?.message ?? String(error)}`;
232
+ touch(shell, now);
233
+ }
234
+ }
235
+ return results;
236
+ }
237
+
238
+ return {
239
+ spawnShell,
240
+ sendShell,
241
+ resizeShell,
242
+ killShell,
243
+ killAll,
244
+ listShells,
245
+ getShell,
246
+ searchShell,
247
+ snapshotShell,
248
+ snapshotShellScreen,
249
+ clearShell,
250
+ dispose,
251
+ };
252
+ }