march-cli 0.1.21 → 0.1.23

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 (244) hide show
  1. package/README.md +88 -0
  2. package/README.zh.md +88 -0
  3. package/bin/march.mjs +13 -13
  4. package/package.json +43 -43
  5. package/src/agent/command-exec-tool.mjs +172 -168
  6. package/src/agent/context-stats-tool.mjs +57 -57
  7. package/src/agent/editing/diff-apply.mjs +28 -28
  8. package/src/agent/editing/diff-format.mjs +57 -57
  9. package/src/agent/editing/lsp-report.mjs +69 -69
  10. package/src/agent/file-edit-tool.mjs +262 -262
  11. package/src/agent/file-tools/read-file-tool.mjs +112 -112
  12. package/src/agent/file-tools/read-image-tool.mjs +76 -76
  13. package/src/agent/model-payload-dumper.mjs +208 -208
  14. package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
  15. package/src/agent/provider/payload-messages.mjs +138 -138
  16. package/src/agent/runner/codex-large-context-guard.mjs +87 -87
  17. package/src/agent/runner/codex-transport-compression.mjs +180 -180
  18. package/src/agent/runner/codex-transport-debug.mjs +113 -113
  19. package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
  20. package/src/agent/runner/fast-model.mjs +36 -36
  21. package/src/agent/runner/runner-cleanup.mjs +12 -12
  22. package/src/agent/runner/runner-init.mjs +15 -15
  23. package/src/agent/runner/runner-session-state.mjs +40 -40
  24. package/src/agent/runner/runner-utils.mjs +24 -24
  25. package/src/agent/runner.mjs +299 -299
  26. package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
  27. package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
  28. package/src/agent/runtime/remote-runner-client.mjs +73 -73
  29. package/src/agent/runtime/remote-ui-client.mjs +20 -20
  30. package/src/agent/runtime/runner-ipc-target.mjs +125 -125
  31. package/src/agent/runtime/runner-process-client.mjs +47 -47
  32. package/src/agent/runtime/runner-process-entry.mjs +11 -11
  33. package/src/agent/runtime/runner-process-factory.mjs +108 -108
  34. package/src/agent/runtime/runner-runtime-host.mjs +79 -79
  35. package/src/agent/runtime/runtime-factory.mjs +42 -42
  36. package/src/agent/runtime/runtime-host.mjs +34 -34
  37. package/src/agent/runtime/ui-event-bridge.mjs +95 -95
  38. package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
  39. package/src/agent/screen-tools/screen-tool.mjs +49 -49
  40. package/src/agent/screen-tools/windows-screen.mjs +133 -133
  41. package/src/agent/session/session-auto-name.mjs +41 -41
  42. package/src/agent/session/session-binding.mjs +12 -12
  43. package/src/agent/session/session-options.mjs +47 -47
  44. package/src/agent/tool-names.mjs +1 -1
  45. package/src/agent/tool-result.mjs +3 -3
  46. package/src/agent/tool-summary.mjs +112 -112
  47. package/src/agent/tools.mjs +58 -58
  48. package/src/agent/turn/turn-events.mjs +111 -111
  49. package/src/agent/turn/turn-logging.mjs +30 -30
  50. package/src/agent/turn/turn-runner.mjs +196 -196
  51. package/src/agent/vision-capability.mjs +14 -14
  52. package/src/auth/login-command.mjs +90 -90
  53. package/src/auth/storage.mjs +34 -34
  54. package/src/cli/args.mjs +79 -79
  55. package/src/cli/commands/copy-command.mjs +87 -87
  56. package/src/cli/commands/export-command.mjs +206 -206
  57. package/src/cli/commands/extensions-command.mjs +53 -53
  58. package/src/cli/commands/help-command.mjs +7 -7
  59. package/src/cli/commands/model-command.mjs +141 -141
  60. package/src/cli/commands/paste-image-command.mjs +43 -43
  61. package/src/cli/commands/provider-command.mjs +59 -59
  62. package/src/cli/commands/status-command.mjs +194 -194
  63. package/src/cli/commands/thinking-command.mjs +87 -87
  64. package/src/cli/fallback-ui.mjs +156 -156
  65. package/src/cli/input/attachment-tokens.mjs +20 -20
  66. package/src/cli/input/autocomplete.mjs +74 -106
  67. package/src/cli/input/external-editor.mjs +39 -39
  68. package/src/cli/input/file-search/index.mjs +160 -0
  69. package/src/cli/input/history-store.mjs +35 -35
  70. package/src/cli/input/image-clipboard.mjs +55 -55
  71. package/src/cli/input/keybinding-dispatch.mjs +76 -76
  72. package/src/cli/input/keybindings.mjs +96 -96
  73. package/src/cli/input/mode-state.mjs +43 -43
  74. package/src/cli/input/prompt-templates.mjs +84 -84
  75. package/src/cli/input/select-with-keyboard.mjs +86 -86
  76. package/src/cli/permissions.mjs +103 -103
  77. package/src/cli/repl-commands.mjs +86 -86
  78. package/src/cli/repl-loop.mjs +183 -183
  79. package/src/cli/selector-list.mjs +21 -21
  80. package/src/cli/session/pi-session-switch-command.mjs +41 -41
  81. package/src/cli/session/session-command.mjs +23 -23
  82. package/src/cli/session/session-list-command.mjs +68 -68
  83. package/src/cli/session/session-name-command.mjs +26 -26
  84. package/src/cli/session/session-source-command.mjs +89 -89
  85. package/src/cli/session/session-switch-command.mjs +1 -1
  86. package/src/cli/shell/shell-command.mjs +55 -55
  87. package/src/cli/shell/shell-drawer-controls.mjs +33 -33
  88. package/src/cli/shell/shell-drawer.mjs +192 -192
  89. package/src/cli/shell/shell-split-layout.mjs +70 -70
  90. package/src/cli/slash-commands.mjs +192 -192
  91. package/src/cli/startup/create-runtime-runner.mjs +61 -61
  92. package/src/cli/startup/runtime-close.mjs +23 -23
  93. package/src/cli/startup/startup-banner.mjs +71 -71
  94. package/src/cli/startup/startup-session.mjs +51 -51
  95. package/src/cli/status-line-updater.mjs +75 -75
  96. package/src/cli/tool-output.mjs +9 -9
  97. package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
  98. package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
  99. package/src/cli/tui/input/mouse-tracking.mjs +20 -20
  100. package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
  101. package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
  102. package/src/cli/tui/markdown-renderer.mjs +285 -285
  103. package/src/cli/tui/output/scroll-state.mjs +79 -79
  104. package/src/cli/tui/output/text-line-renderer.mjs +50 -50
  105. package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
  106. package/src/cli/tui/output/visible-lines.mjs +8 -8
  107. package/src/cli/tui/output-buffer.mjs +293 -293
  108. package/src/cli/tui/permission-request-ui.mjs +18 -18
  109. package/src/cli/tui/recall-rendering.mjs +25 -25
  110. package/src/cli/tui/render/render-scheduler.mjs +26 -26
  111. package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
  112. package/src/cli/tui/select/editor-select-list.mjs +111 -111
  113. package/src/cli/tui/selection-screen.mjs +269 -269
  114. package/src/cli/tui/status/retry-status.mjs +72 -72
  115. package/src/cli/tui/status/spinner-status.mjs +42 -42
  116. package/src/cli/tui/status/status-bar.mjs +225 -225
  117. package/src/cli/tui/syntax/highlighting.mjs +260 -260
  118. package/src/cli/tui/syntax/languages.mjs +91 -91
  119. package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
  120. package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
  121. package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
  122. package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
  123. package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
  124. package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
  125. package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
  126. package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
  127. package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
  128. package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
  129. package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
  130. package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
  131. package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
  132. package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
  133. package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
  134. package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
  135. package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
  136. package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
  137. package/src/cli/tui/tool-rendering.mjs +87 -87
  138. package/src/cli/tui/tui-diff-rendering.mjs +157 -157
  139. package/src/cli/tui/tui-handlers.mjs +111 -111
  140. package/src/cli/tui/tui-input-controller.mjs +61 -61
  141. package/src/cli/tui/ui-theme.mjs +157 -157
  142. package/src/cli/ui.mjs +297 -297
  143. package/src/config/config-json.mjs +84 -84
  144. package/src/config/dotenv.mjs +20 -20
  145. package/src/config/features.mjs +75 -75
  146. package/src/config/loader.mjs +143 -143
  147. package/src/config/settings-command.mjs +97 -97
  148. package/src/context/engine.mjs +198 -198
  149. package/src/context/injections.mjs +26 -26
  150. package/src/context/profiles.mjs +39 -39
  151. package/src/context/project-context.mjs +20 -20
  152. package/src/context/session-status.mjs +17 -17
  153. package/src/context/shell-layers.mjs +23 -23
  154. package/src/context/system-core/base.md +61 -53
  155. package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
  156. package/src/context/system-core/prompts/default.md +3 -3
  157. package/src/context/system-core.mjs +35 -35
  158. package/src/debug/logger.mjs +141 -141
  159. package/src/debug/model-context-dumper.mjs +52 -52
  160. package/src/extensions/discovery.mjs +40 -40
  161. package/src/extensions/lifecycle-adapter.mjs +210 -210
  162. package/src/extensions/lifecycle-manifest.mjs +69 -69
  163. package/src/image-gen/index.mjs +7 -7
  164. package/src/image-gen/provider.mjs +231 -231
  165. package/src/image-gen/tool.mjs +84 -84
  166. package/src/lsp/client.mjs +257 -257
  167. package/src/lsp/diagnostic-store.mjs +42 -42
  168. package/src/lsp/diagnostics-format.mjs +72 -72
  169. package/src/lsp/managed-node-server.mjs +99 -99
  170. package/src/lsp/path-match.mjs +10 -10
  171. package/src/lsp/server-definitions.mjs +188 -188
  172. package/src/lsp/servers.mjs +165 -165
  173. package/src/lsp/service.mjs +110 -110
  174. package/src/lsp/status-message.mjs +9 -9
  175. package/src/lsp/typescript-project-resolver.mjs +186 -186
  176. package/src/main.mjs +299 -299
  177. package/src/mcp/client.mjs +195 -195
  178. package/src/mcp/config.mjs +130 -130
  179. package/src/mcp/index.mjs +48 -48
  180. package/src/mcp/tools.mjs +98 -98
  181. package/src/memory/markdown/markdown-delete.mjs +23 -23
  182. package/src/memory/markdown/markdown-format.mjs +128 -128
  183. package/src/memory/markdown/markdown-recall.mjs +28 -28
  184. package/src/memory/markdown/ripgrep.mjs +16 -16
  185. package/src/memory/markdown/sqlite-index.mjs +87 -87
  186. package/src/memory/markdown-store.mjs +286 -286
  187. package/src/memory/markdown-tools.mjs +103 -103
  188. package/src/network/environment.mjs +131 -131
  189. package/src/notification/desktop-notifier.mjs +262 -262
  190. package/src/platform/open-file.mjs +28 -28
  191. package/src/platform/spawn-command.mjs +27 -27
  192. package/src/provider/accept-command.mjs +89 -89
  193. package/src/provider/command.mjs +21 -21
  194. package/src/provider/config-command.mjs +129 -129
  195. package/src/provider/custom-provider.mjs +113 -113
  196. package/src/provider/hosted-tools.mjs +111 -111
  197. package/src/provider/presets.mjs +72 -72
  198. package/src/provider/share-command.mjs +79 -79
  199. package/src/provider/share-payload.mjs +52 -52
  200. package/src/session/attachment-display.mjs +16 -16
  201. package/src/session/attachment-references.mjs +65 -65
  202. package/src/session/attachments.mjs +140 -140
  203. package/src/session/persist.mjs +1 -1
  204. package/src/session/pi-manager.mjs +34 -34
  205. package/src/session/session-utils.mjs +16 -16
  206. package/src/session/sidecar-sync.mjs +19 -19
  207. package/src/session/sidecar.mjs +69 -69
  208. package/src/session/transcript.mjs +83 -83
  209. package/src/session/tree.mjs +42 -42
  210. package/src/shell/cli-runtime.mjs +11 -11
  211. package/src/shell/hints.mjs +12 -12
  212. package/src/shell/node-pty-adapter.mjs +81 -81
  213. package/src/shell/runtime-state.mjs +126 -126
  214. package/src/shell/runtime.mjs +252 -252
  215. package/src/shell/screen-buffer.mjs +136 -136
  216. package/src/shell/tool-read.mjs +74 -74
  217. package/src/shell/tools.mjs +299 -299
  218. package/src/supergrok/actions/image-generate.mjs +60 -60
  219. package/src/supergrok/actions/search.mjs +78 -78
  220. package/src/supergrok/auth.mjs +36 -36
  221. package/src/supergrok/constants.mjs +18 -18
  222. package/src/supergrok/oauth-provider.mjs +278 -278
  223. package/src/supergrok/provider.mjs +35 -35
  224. package/src/supergrok/response.mjs +76 -76
  225. package/src/supergrok/tool.mjs +61 -61
  226. package/src/text/ansi.mjs +3 -3
  227. package/src/web/config-command.mjs +43 -43
  228. package/src/web/fetch.mjs +78 -78
  229. package/src/web/presets.mjs +16 -16
  230. package/src/web/search.mjs +83 -83
  231. package/src/web/tools.mjs +107 -107
  232. package/src/memory/database.mjs +0 -219
  233. package/src/memory/glossary.mjs +0 -124
  234. package/src/memory/graph/graph-cascades.mjs +0 -109
  235. package/src/memory/graph/graph-diagnostics.mjs +0 -73
  236. package/src/memory/graph/graph-path-removal.mjs +0 -50
  237. package/src/memory/graph/graph-path-utils.mjs +0 -17
  238. package/src/memory/graph/graph-primitives.mjs +0 -103
  239. package/src/memory/graph/graph-read.mjs +0 -159
  240. package/src/memory/graph.mjs +0 -282
  241. package/src/memory/search.mjs +0 -142
  242. package/src/memory/snapshot.mjs +0 -86
  243. package/src/memory/system-views.mjs +0 -120
  244. package/src/memory/tools.mjs +0 -282
@@ -1,156 +1,156 @@
1
- import { stdout } from "node:process";
2
- import { extractToolOutput } from "./tool-output.mjs";
3
- import { formatMemoryHintLines } from "./tui/recall-rendering.mjs";
4
- import { formatToolStartLine } from "./tui/tool-rendering.mjs";
5
- import { brightBlack, dim, red, green, yellow } from "./tui/ui-theme.mjs";
6
-
7
- export function createJsonUI() {
8
- let thinkingBuf = "";
9
- return {
10
- readline: () => Promise.resolve(""),
11
- write: () => {},
12
- writeln: (text) => {
13
- stdout.write(text + "\n");
14
- },
15
- thinkingStart: () => { thinkingBuf = ""; },
16
- thinkingDelta: (delta) => { thinkingBuf += delta; },
17
- thinkingEnd: (tokens) => {
18
- stdout.write(JSON.stringify({ type: "thinking", tokens, content: thinkingBuf }) + "\n");
19
- thinkingBuf = "";
20
- },
21
- thinkingBlock: (tokens, content) => {
22
- stdout.write(JSON.stringify({ type: "thinking", tokens, content }) + "\n");
23
- },
24
- toggleLastThinking: () => {},
25
- toolStart: (name, args) => {
26
- stdout.write(JSON.stringify({ type: "tool_start", name, args }) + "\n");
27
- },
28
- toolEnd: (name, isError, result) => {
29
- stdout.write(JSON.stringify({ type: "tool_end", name, isError, output: extractToolOutput(result) }) + "\n");
30
- },
31
- textDelta: (delta) => {
32
- stdout.write(delta);
33
- },
34
- status: () => {},
35
- memoryHint: () => {},
36
- clearOutput: () => {},
37
- restoreTranscript: () => {},
38
- setStatusBar: () => {},
39
- turnStart: () => {},
40
- assistantReplyEnd: () => {},
41
- turnEnd: () => {},
42
- retryStart: (event) => {
43
- stdout.write(JSON.stringify({ type: "retry_start", ...event }) + "\n");
44
- },
45
- retryEnd: (event) => {
46
- stdout.write(JSON.stringify({ type: "retry_end", ...event }) + "\n");
47
- },
48
- editDiff: (path, diffLines) => {
49
- stdout.write(JSON.stringify({ type: "edit_diff", path, diff: diffLines }) + "\n");
50
- },
51
- requestPermission: async () => true,
52
- setEscapeHandler: () => {},
53
- setCtrlCHandler: () => {},
54
- setShiftTabHandler: () => {},
55
- setCtrlTHandler: () => {},
56
- setCtrlLHandler: () => {},
57
- setPasteImageHandler: () => {},
58
- getInputText: () => "",
59
- insertTextAtCursor: () => {},
60
- openExternalEditor: () => {},
61
- toggleMouse: () => false,
62
- toggleToolOutput: () => false,
63
- requestExit: () => {},
64
- close: () => {},
65
- };
66
- }
67
-
68
- export function createPlainUI() {
69
- let thinkingBuf = "";
70
- let needsNewline = false;
71
- const writeText = (text) => {
72
- stdout.write(text);
73
- needsNewline = text.length > 0 && !text.endsWith("\n");
74
- };
75
- const ensureNewline = () => {
76
- if (needsNewline) stdout.write("\n");
77
- needsNewline = false;
78
- };
79
- return {
80
- readline: (_prompt) => Promise.resolve(""),
81
- write: writeText,
82
- writeln: (text) => { stdout.write(text + "\n"); needsNewline = false; },
83
- thinkingStart: () => { thinkingBuf = ""; },
84
- thinkingDelta: (delta) => { thinkingBuf += delta; },
85
- thinkingEnd: (tokens) => {
86
- stdout.write(`\n${brightBlack(`--- thinking (${tokens} tokens) ---`)}\n`);
87
- stdout.write(`${brightBlack(thinkingBuf)}\n`);
88
- stdout.write(`${brightBlack("--- end thinking ---")}\n\n`);
89
- thinkingBuf = "";
90
- needsNewline = false;
91
- },
92
- thinkingBlock: (tokens, content) => {
93
- stdout.write(`\n${brightBlack(`--- thinking (${tokens} tokens) ---`)}\n`);
94
- stdout.write(`${brightBlack(content)}\n`);
95
- stdout.write(`${brightBlack("--- end thinking ---")}\n\n`);
96
- needsNewline = false;
97
- },
98
- toggleLastThinking: () => {},
99
- toolStart: (name, args) => {
100
- stdout.write(`${dim(` ${formatToolStartLine(name, args)}`)}\n`);
101
- },
102
- toolEnd: (name, isError, result) => {
103
- const out = extractToolOutput(result);
104
- if (isError) {
105
- stdout.write(`${red(` ◆ ${name} failed`)}\n`);
106
- if (out) stdout.write(`${red(` ${out.slice(0, 200)}`)}\n`);
107
- } else if (out) {
108
- stdout.write(`${dim(` ${out.split("\n")[0].slice(0, 200)}`)}\n`);
109
- }
110
- needsNewline = false;
111
- },
112
- textDelta: writeText,
113
- status: (text) => { ensureNewline(); stdout.write(`${brightBlack(`● ${text}`)}\n`); },
114
- memoryHint: ({ hints }) => {
115
- ensureNewline();
116
- for (const line of formatMemoryHintLines(hints)) stdout.write(`${brightBlack(line)}\n`);
117
- },
118
- clearOutput: () => {},
119
- restoreTranscript: () => {},
120
- setStatusBar: () => {},
121
- turnStart: () => {},
122
- assistantReplyEnd: ensureNewline,
123
- turnEnd: ensureNewline,
124
- retryStart: ({ attempt, maxAttempts, delayMs, errorMessage }) => {
125
- ensureNewline();
126
- stdout.write(`${yellow(`● retrying (${attempt}/${maxAttempts}) in ${Math.ceil(delayMs / 1000)}s: ${errorMessage || "Unknown error"}`)}\n`);
127
- },
128
- retryEnd: ({ success, attempt, finalError }) => {
129
- ensureNewline();
130
- const status = success ? "recovered" : "stopped";
131
- stdout.write(`${brightBlack(`● retry ${status} after ${attempt} attempt${attempt === 1 ? "" : "s"}${finalError ? `: ${finalError}` : ""}`)}\n`);
132
- },
133
- editDiff: (path, diffLines) => {
134
- stdout.write(`\n${dim(` ± ${path}`)}\n`);
135
- for (const d of diffLines) {
136
- if (d.type === "del") stdout.write(`${red(` - ${d.text}`)}\n`);
137
- else if (d.type === "add") stdout.write(`${green(` + ${d.text}`)}\n`);
138
- else stdout.write(`${dim(` ${d.text}`)}\n`);
139
- }
140
- },
141
- requestPermission: async () => true,
142
- setEscapeHandler: () => {},
143
- setCtrlCHandler: () => {},
144
- setShiftTabHandler: () => {},
145
- setCtrlTHandler: () => {},
146
- setCtrlLHandler: () => {},
147
- setPasteImageHandler: () => {},
148
- getInputText: () => "",
149
- insertTextAtCursor: () => {},
150
- openExternalEditor: () => {},
151
- toggleMouse: () => false,
152
- toggleToolOutput: () => false,
153
- requestExit: () => {},
154
- close: () => {},
155
- };
156
- }
1
+ import { stdout } from "node:process";
2
+ import { extractToolOutput } from "./tool-output.mjs";
3
+ import { formatMemoryHintLines } from "./tui/recall-rendering.mjs";
4
+ import { formatToolStartLine } from "./tui/tool-rendering.mjs";
5
+ import { brightBlack, dim, red, green, yellow } from "./tui/ui-theme.mjs";
6
+
7
+ export function createJsonUI() {
8
+ let thinkingBuf = "";
9
+ return {
10
+ readline: () => Promise.resolve(""),
11
+ write: () => {},
12
+ writeln: (text) => {
13
+ stdout.write(text + "\n");
14
+ },
15
+ thinkingStart: () => { thinkingBuf = ""; },
16
+ thinkingDelta: (delta) => { thinkingBuf += delta; },
17
+ thinkingEnd: (tokens) => {
18
+ stdout.write(JSON.stringify({ type: "thinking", tokens, content: thinkingBuf }) + "\n");
19
+ thinkingBuf = "";
20
+ },
21
+ thinkingBlock: (tokens, content) => {
22
+ stdout.write(JSON.stringify({ type: "thinking", tokens, content }) + "\n");
23
+ },
24
+ toggleLastThinking: () => {},
25
+ toolStart: (name, args) => {
26
+ stdout.write(JSON.stringify({ type: "tool_start", name, args }) + "\n");
27
+ },
28
+ toolEnd: (name, isError, result) => {
29
+ stdout.write(JSON.stringify({ type: "tool_end", name, isError, output: extractToolOutput(result) }) + "\n");
30
+ },
31
+ textDelta: (delta) => {
32
+ stdout.write(delta);
33
+ },
34
+ status: () => {},
35
+ memoryHint: () => {},
36
+ clearOutput: () => {},
37
+ restoreTranscript: () => {},
38
+ setStatusBar: () => {},
39
+ turnStart: () => {},
40
+ assistantReplyEnd: () => {},
41
+ turnEnd: () => {},
42
+ retryStart: (event) => {
43
+ stdout.write(JSON.stringify({ type: "retry_start", ...event }) + "\n");
44
+ },
45
+ retryEnd: (event) => {
46
+ stdout.write(JSON.stringify({ type: "retry_end", ...event }) + "\n");
47
+ },
48
+ editDiff: (path, diffLines) => {
49
+ stdout.write(JSON.stringify({ type: "edit_diff", path, diff: diffLines }) + "\n");
50
+ },
51
+ requestPermission: async () => true,
52
+ setEscapeHandler: () => {},
53
+ setCtrlCHandler: () => {},
54
+ setShiftTabHandler: () => {},
55
+ setCtrlTHandler: () => {},
56
+ setCtrlLHandler: () => {},
57
+ setPasteImageHandler: () => {},
58
+ getInputText: () => "",
59
+ insertTextAtCursor: () => {},
60
+ openExternalEditor: () => {},
61
+ toggleMouse: () => false,
62
+ toggleToolOutput: () => false,
63
+ requestExit: () => {},
64
+ close: () => {},
65
+ };
66
+ }
67
+
68
+ export function createPlainUI() {
69
+ let thinkingBuf = "";
70
+ let needsNewline = false;
71
+ const writeText = (text) => {
72
+ stdout.write(text);
73
+ needsNewline = text.length > 0 && !text.endsWith("\n");
74
+ };
75
+ const ensureNewline = () => {
76
+ if (needsNewline) stdout.write("\n");
77
+ needsNewline = false;
78
+ };
79
+ return {
80
+ readline: (_prompt) => Promise.resolve(""),
81
+ write: writeText,
82
+ writeln: (text) => { stdout.write(text + "\n"); needsNewline = false; },
83
+ thinkingStart: () => { thinkingBuf = ""; },
84
+ thinkingDelta: (delta) => { thinkingBuf += delta; },
85
+ thinkingEnd: (tokens) => {
86
+ stdout.write(`\n${brightBlack(`--- thinking (${tokens} tokens) ---`)}\n`);
87
+ stdout.write(`${brightBlack(thinkingBuf)}\n`);
88
+ stdout.write(`${brightBlack("--- end thinking ---")}\n\n`);
89
+ thinkingBuf = "";
90
+ needsNewline = false;
91
+ },
92
+ thinkingBlock: (tokens, content) => {
93
+ stdout.write(`\n${brightBlack(`--- thinking (${tokens} tokens) ---`)}\n`);
94
+ stdout.write(`${brightBlack(content)}\n`);
95
+ stdout.write(`${brightBlack("--- end thinking ---")}\n\n`);
96
+ needsNewline = false;
97
+ },
98
+ toggleLastThinking: () => {},
99
+ toolStart: (name, args) => {
100
+ stdout.write(`${dim(` ${formatToolStartLine(name, args)}`)}\n`);
101
+ },
102
+ toolEnd: (name, isError, result) => {
103
+ const out = extractToolOutput(result);
104
+ if (isError) {
105
+ stdout.write(`${red(` ◆ ${name} failed`)}\n`);
106
+ if (out) stdout.write(`${red(` ${out.slice(0, 200)}`)}\n`);
107
+ } else if (out) {
108
+ stdout.write(`${dim(` ${out.split("\n")[0].slice(0, 200)}`)}\n`);
109
+ }
110
+ needsNewline = false;
111
+ },
112
+ textDelta: writeText,
113
+ status: (text) => { ensureNewline(); stdout.write(`${brightBlack(`● ${text}`)}\n`); },
114
+ memoryHint: ({ hints }) => {
115
+ ensureNewline();
116
+ for (const line of formatMemoryHintLines(hints)) stdout.write(`${brightBlack(line)}\n`);
117
+ },
118
+ clearOutput: () => {},
119
+ restoreTranscript: () => {},
120
+ setStatusBar: () => {},
121
+ turnStart: () => {},
122
+ assistantReplyEnd: ensureNewline,
123
+ turnEnd: ensureNewline,
124
+ retryStart: ({ attempt, maxAttempts, delayMs, errorMessage }) => {
125
+ ensureNewline();
126
+ stdout.write(`${yellow(`● retrying (${attempt}/${maxAttempts}) in ${Math.ceil(delayMs / 1000)}s: ${errorMessage || "Unknown error"}`)}\n`);
127
+ },
128
+ retryEnd: ({ success, attempt, finalError }) => {
129
+ ensureNewline();
130
+ const status = success ? "recovered" : "stopped";
131
+ stdout.write(`${brightBlack(`● retry ${status} after ${attempt} attempt${attempt === 1 ? "" : "s"}${finalError ? `: ${finalError}` : ""}`)}\n`);
132
+ },
133
+ editDiff: (path, diffLines) => {
134
+ stdout.write(`\n${dim(` ± ${path}`)}\n`);
135
+ for (const d of diffLines) {
136
+ if (d.type === "del") stdout.write(`${red(` - ${d.text}`)}\n`);
137
+ else if (d.type === "add") stdout.write(`${green(` + ${d.text}`)}\n`);
138
+ else stdout.write(`${dim(` ${d.text}`)}\n`);
139
+ }
140
+ },
141
+ requestPermission: async () => true,
142
+ setEscapeHandler: () => {},
143
+ setCtrlCHandler: () => {},
144
+ setShiftTabHandler: () => {},
145
+ setCtrlTHandler: () => {},
146
+ setCtrlLHandler: () => {},
147
+ setPasteImageHandler: () => {},
148
+ getInputText: () => "",
149
+ insertTextAtCursor: () => {},
150
+ openExternalEditor: () => {},
151
+ toggleMouse: () => false,
152
+ toggleToolOutput: () => false,
153
+ requestExit: () => {},
154
+ close: () => {},
155
+ };
156
+ }
@@ -1,20 +1,20 @@
1
- export function resolveAttachmentTokens(text, tokens) {
2
- let resolved = text;
3
- for (const [token, marker] of tokens) {
4
- resolved = resolved.split(token).join(marker);
5
- }
6
- return resolved;
7
- }
8
-
9
- export function uniqueAttachmentToken(label, tokens) {
10
- if (!tokens.has(label)) return label;
11
- for (let i = 2; ; i++) {
12
- const candidate = label.replace(/\]$/, ` ${i}]`);
13
- if (!tokens.has(candidate)) return candidate;
14
- }
15
- }
16
-
17
- export function withLeadingSpace(currentText, text) {
18
- if (!String(currentText || "").trim()) return text;
19
- return ` ${text}`;
20
- }
1
+ export function resolveAttachmentTokens(text, tokens) {
2
+ let resolved = text;
3
+ for (const [token, marker] of tokens) {
4
+ resolved = resolved.split(token).join(marker);
5
+ }
6
+ return resolved;
7
+ }
8
+
9
+ export function uniqueAttachmentToken(label, tokens) {
10
+ if (!tokens.has(label)) return label;
11
+ for (let i = 2; ; i++) {
12
+ const candidate = label.replace(/\]$/, ` ${i}]`);
13
+ if (!tokens.has(candidate)) return candidate;
14
+ }
15
+ }
16
+
17
+ export function withLeadingSpace(currentText, text) {
18
+ if (!String(currentText || "").trim()) return text;
19
+ return ` ${text}`;
20
+ }
@@ -1,106 +1,74 @@
1
- import { join } from "node:path";
2
- import { readdirSync } from "node:fs";
3
- import { CombinedAutocompleteProvider } from "@earendil-works/pi-tui";
4
-
5
- const MARCH_COMMANDS = [
6
- { name: "new", description: "Start a new pi session" },
7
- { name: "exit", description: "Exit March" },
8
- { name: "quit", description: "Exit March" },
9
- { name: "help", description: "Show available commands" },
10
- { name: "model", description: "Open model selector" },
11
- { name: "models", description: "List available models" },
12
- { name: "session", description: "Open previous session selector" },
13
- { name: "save", description: "Show auto-save status" },
14
- { name: "name", description: "Show or set session name" },
15
- { name: "copy", description: "Copy last assistant response to clipboard" },
16
- { name: "thinking", description: "Open thinking selector" },
17
- { name: "thinking list", description: "List available thinking levels" },
18
- { name: "mouse", description: "Toggle mouse wheel and TUI selection copy" },
19
- { name: "hotkeys", description: "Show keyboard shortcuts and input prefixes" },
20
- { name: "templates", description: "List project prompt templates" },
21
- { name: "export jsonl", description: "Export current session turns as JSONL" },
22
- { name: "export html", description: "Export current session turns as HTML" },
23
- { name: "export gist html", description: "Share current session HTML as a private GitHub Gist" },
24
- { name: "export gist jsonl", description: "Share current session JSONL as a private GitHub Gist" },
25
- { name: "settings", description: "Show or edit global/project settings" },
26
- { name: "shell", description: "List shells or inspect shell output" },
27
- { name: "shell spawn", description: "Start a default PTY shell" },
28
- ];
29
-
30
- export function buildMarchCommands(promptTemplates = []) {
31
- const templateCommands = promptTemplates
32
- .map((template) => ({
33
- name: typeof template === "string" ? template : template.name,
34
- description: "Expand prompt template",
35
- }))
36
- .filter((command) => command.name && !command.name.startsWith("/"));
37
- return [...MARCH_COMMANDS, ...templateCommands];
38
- }
39
-
40
- export class MarchAutocompleteProvider {
41
- constructor(commands, cwd) {
42
- this.base = new CombinedAutocompleteProvider(commands, cwd);
43
- this.cwd = cwd;
44
- }
45
-
46
- async getSuggestions(lines, cursorLine, cursorCol, options) {
47
- const suggestions = await this.base.getSuggestions(lines, cursorLine, cursorCol, options);
48
- if (suggestions) return suggestions;
49
- return this.getAtFileSuggestions(lines, cursorLine, cursorCol);
50
- }
51
-
52
- applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
53
- return this.base.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
54
- }
55
-
56
- shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
57
- return this.base.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
58
- }
59
-
60
- getAtFileSuggestions(lines, cursorLine, cursorCol) {
61
- const currentLine = lines[cursorLine] || "";
62
- const textBeforeCursor = currentLine.slice(0, cursorCol);
63
- const match = textBeforeCursor.match(/(?:^|[\s([{])(@[^\s]*)$/);
64
- if (!match) return null;
65
-
66
- const prefix = match[1];
67
- const query = prefix.slice(1);
68
- const displayDotSlash = query.startsWith("./");
69
- const normalizedQuery = query.replace(/^[.][/\\]/, "").replace(/\\/g, "/").toLowerCase();
70
- const items = [];
71
- const skip = new Set([".git", "node_modules"]);
72
-
73
- const walk = (dir, relative = "", depth = 0) => {
74
- if (items.length >= 50 || depth > 5) return;
75
- let entries;
76
- try {
77
- entries = readdirSync(dir, { withFileTypes: true });
78
- } catch {
79
- return;
80
- }
81
- for (const entry of entries) {
82
- if (items.length >= 50) return;
83
- if (skip.has(entry.name)) continue;
84
- const rel = relative ? `${relative}/${entry.name}` : entry.name;
85
- const isDirectory = entry.isDirectory();
86
- const relForMatch = rel.toLowerCase();
87
- if (!normalizedQuery || relForMatch.includes(normalizedQuery)) {
88
- const displayPath = `${displayDotSlash ? "./" : ""}${rel}${isDirectory ? "/" : ""}`;
89
- items.push({
90
- value: `@${displayPath}`,
91
- label: `${entry.name}${isDirectory ? "/" : ""}`,
92
- description: displayPath,
93
- });
94
- }
95
- if (isDirectory) {
96
- walk(join(dir, entry.name), rel, depth + 1);
97
- }
98
- }
99
- };
100
-
101
- walk(this.cwd);
102
- if (items.length === 0) return null;
103
- items.sort((a, b) => a.description.localeCompare(b.description));
104
- return { items, prefix };
105
- }
106
- }
1
+ import { CombinedAutocompleteProvider } from "@earendil-works/pi-tui";
2
+ import { FileSearchIndex } from "./file-search/index.mjs";
3
+
4
+ const MARCH_COMMANDS = [
5
+ { name: "new", description: "Start a new pi session" },
6
+ { name: "exit", description: "Exit March" },
7
+ { name: "quit", description: "Exit March" },
8
+ { name: "help", description: "Show available commands" },
9
+ { name: "model", description: "Open model selector" },
10
+ { name: "models", description: "List available models" },
11
+ { name: "session", description: "Open previous session selector" },
12
+ { name: "save", description: "Show auto-save status" },
13
+ { name: "name", description: "Show or set session name" },
14
+ { name: "copy", description: "Copy last assistant response to clipboard" },
15
+ { name: "thinking", description: "Open thinking selector" },
16
+ { name: "thinking list", description: "List available thinking levels" },
17
+ { name: "mouse", description: "Toggle mouse wheel and TUI selection copy" },
18
+ { name: "hotkeys", description: "Show keyboard shortcuts and input prefixes" },
19
+ { name: "templates", description: "List project prompt templates" },
20
+ { name: "export jsonl", description: "Export current session turns as JSONL" },
21
+ { name: "export html", description: "Export current session turns as HTML" },
22
+ { name: "export gist html", description: "Share current session HTML as a private GitHub Gist" },
23
+ { name: "export gist jsonl", description: "Share current session JSONL as a private GitHub Gist" },
24
+ { name: "settings", description: "Show or edit global/project settings" },
25
+ { name: "shell", description: "List shells or inspect shell output" },
26
+ { name: "shell spawn", description: "Start a default PTY shell" },
27
+ ];
28
+
29
+ export function buildMarchCommands(promptTemplates = []) {
30
+ const templateCommands = promptTemplates
31
+ .map((template) => ({
32
+ name: typeof template === "string" ? template : template.name,
33
+ description: "Expand prompt template",
34
+ }))
35
+ .filter((command) => command.name && !command.name.startsWith("/"));
36
+ return [...MARCH_COMMANDS, ...templateCommands];
37
+ }
38
+
39
+ export class MarchAutocompleteProvider {
40
+ constructor(commands, cwd) {
41
+ this.base = new CombinedAutocompleteProvider(commands, cwd);
42
+ this.fileSearchIndex = new FileSearchIndex(cwd);
43
+ }
44
+
45
+ async getSuggestions(lines, cursorLine, cursorCol, options) {
46
+ const suggestions = await this.base.getSuggestions(lines, cursorLine, cursorCol, options);
47
+ if (suggestions) return suggestions;
48
+ return this.getAtFileSuggestions(lines, cursorLine, cursorCol);
49
+ }
50
+
51
+ applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
52
+ return this.base.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
53
+ }
54
+
55
+ shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
56
+ return this.base.shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
57
+ }
58
+
59
+ async getAtFileSuggestions(lines, cursorLine, cursorCol) {
60
+ const currentLine = lines[cursorLine] || "";
61
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
62
+ const match = textBeforeCursor.match(/(?:^|[\s([{])(@[^\s]*)$/);
63
+ if (!match) return null;
64
+
65
+ const prefix = match[1];
66
+ const query = prefix.slice(1);
67
+ const items = await this.fileSearchIndex.search(query, {
68
+ limit: 50,
69
+ displayDotSlash: query.startsWith("./"),
70
+ });
71
+ if (items.length === 0) return null;
72
+ return { items, prefix };
73
+ }
74
+ }
@@ -1,39 +1,39 @@
1
- import { spawnSync } from "node:child_process";
2
- import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- export function openTextInExternalEditor({
7
- text = "",
8
- editorCommand = getExternalEditorCommand(),
9
- now = () => Date.now(),
10
- spawn = spawnSync,
11
- tempDir = tmpdir(),
12
- } = {}) {
13
- if (!editorCommand) {
14
- return { ok: false, error: "No editor configured. Set $VISUAL or $EDITOR." };
15
- }
16
-
17
- const tmpFile = join(tempDir, `march-editor-${now()}.md`);
18
- try {
19
- writeFileSync(tmpFile, String(text ?? ""), "utf8");
20
- const [bin, ...args] = editorCommand.split(" ");
21
- const result = spawn(bin, [...args, tmpFile], {
22
- stdio: "inherit",
23
- shell: process.platform === "win32",
24
- });
25
- if (result.status !== 0) {
26
- return { ok: false, error: `Editor exited with status ${result.status ?? "unknown"}` };
27
- }
28
- return {
29
- ok: true,
30
- text: readFileSync(tmpFile, "utf8").replace(/\n$/, ""),
31
- };
32
- } finally {
33
- try { unlinkSync(tmpFile); } catch {}
34
- }
35
- }
36
-
37
- export function getExternalEditorCommand(env = process.env) {
38
- return env.VISUAL || env.EDITOR || "";
39
- }
1
+ import { spawnSync } from "node:child_process";
2
+ import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ export function openTextInExternalEditor({
7
+ text = "",
8
+ editorCommand = getExternalEditorCommand(),
9
+ now = () => Date.now(),
10
+ spawn = spawnSync,
11
+ tempDir = tmpdir(),
12
+ } = {}) {
13
+ if (!editorCommand) {
14
+ return { ok: false, error: "No editor configured. Set $VISUAL or $EDITOR." };
15
+ }
16
+
17
+ const tmpFile = join(tempDir, `march-editor-${now()}.md`);
18
+ try {
19
+ writeFileSync(tmpFile, String(text ?? ""), "utf8");
20
+ const [bin, ...args] = editorCommand.split(" ");
21
+ const result = spawn(bin, [...args, tmpFile], {
22
+ stdio: "inherit",
23
+ shell: process.platform === "win32",
24
+ });
25
+ if (result.status !== 0) {
26
+ return { ok: false, error: `Editor exited with status ${result.status ?? "unknown"}` };
27
+ }
28
+ return {
29
+ ok: true,
30
+ text: readFileSync(tmpFile, "utf8").replace(/\n$/, ""),
31
+ };
32
+ } finally {
33
+ try { unlinkSync(tmpFile); } catch {}
34
+ }
35
+ }
36
+
37
+ export function getExternalEditorCommand(env = process.env) {
38
+ return env.VISUAL || env.EDITOR || "";
39
+ }