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.
- package/README.md +88 -0
- package/README.zh.md +88 -0
- package/bin/march.mjs +13 -13
- package/package.json +43 -43
- package/src/agent/command-exec-tool.mjs +172 -168
- package/src/agent/context-stats-tool.mjs +57 -57
- package/src/agent/editing/diff-apply.mjs +28 -28
- package/src/agent/editing/diff-format.mjs +57 -57
- package/src/agent/editing/lsp-report.mjs +69 -69
- package/src/agent/file-edit-tool.mjs +262 -262
- package/src/agent/file-tools/read-file-tool.mjs +112 -112
- package/src/agent/file-tools/read-image-tool.mjs +76 -76
- package/src/agent/model-payload-dumper.mjs +208 -208
- package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
- package/src/agent/provider/payload-messages.mjs +138 -138
- package/src/agent/runner/codex-large-context-guard.mjs +87 -87
- package/src/agent/runner/codex-transport-compression.mjs +180 -180
- package/src/agent/runner/codex-transport-debug.mjs +113 -113
- package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
- package/src/agent/runner/fast-model.mjs +36 -36
- package/src/agent/runner/runner-cleanup.mjs +12 -12
- package/src/agent/runner/runner-init.mjs +15 -15
- package/src/agent/runner/runner-session-state.mjs +40 -40
- package/src/agent/runner/runner-utils.mjs +24 -24
- package/src/agent/runner.mjs +299 -299
- package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
- package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
- package/src/agent/runtime/remote-runner-client.mjs +73 -73
- package/src/agent/runtime/remote-ui-client.mjs +20 -20
- package/src/agent/runtime/runner-ipc-target.mjs +125 -125
- package/src/agent/runtime/runner-process-client.mjs +47 -47
- package/src/agent/runtime/runner-process-entry.mjs +11 -11
- package/src/agent/runtime/runner-process-factory.mjs +108 -108
- package/src/agent/runtime/runner-runtime-host.mjs +79 -79
- package/src/agent/runtime/runtime-factory.mjs +42 -42
- package/src/agent/runtime/runtime-host.mjs +34 -34
- package/src/agent/runtime/ui-event-bridge.mjs +95 -95
- package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
- package/src/agent/screen-tools/screen-tool.mjs +49 -49
- package/src/agent/screen-tools/windows-screen.mjs +133 -133
- package/src/agent/session/session-auto-name.mjs +41 -41
- package/src/agent/session/session-binding.mjs +12 -12
- package/src/agent/session/session-options.mjs +47 -47
- package/src/agent/tool-names.mjs +1 -1
- package/src/agent/tool-result.mjs +3 -3
- package/src/agent/tool-summary.mjs +112 -112
- package/src/agent/tools.mjs +58 -58
- package/src/agent/turn/turn-events.mjs +111 -111
- package/src/agent/turn/turn-logging.mjs +30 -30
- package/src/agent/turn/turn-runner.mjs +196 -196
- package/src/agent/vision-capability.mjs +14 -14
- package/src/auth/login-command.mjs +90 -90
- package/src/auth/storage.mjs +34 -34
- package/src/cli/args.mjs +79 -79
- package/src/cli/commands/copy-command.mjs +87 -87
- package/src/cli/commands/export-command.mjs +206 -206
- package/src/cli/commands/extensions-command.mjs +53 -53
- package/src/cli/commands/help-command.mjs +7 -7
- package/src/cli/commands/model-command.mjs +141 -141
- package/src/cli/commands/paste-image-command.mjs +43 -43
- package/src/cli/commands/provider-command.mjs +59 -59
- package/src/cli/commands/status-command.mjs +194 -194
- package/src/cli/commands/thinking-command.mjs +87 -87
- package/src/cli/fallback-ui.mjs +156 -156
- package/src/cli/input/attachment-tokens.mjs +20 -20
- package/src/cli/input/autocomplete.mjs +74 -106
- package/src/cli/input/external-editor.mjs +39 -39
- package/src/cli/input/file-search/index.mjs +160 -0
- package/src/cli/input/history-store.mjs +35 -35
- package/src/cli/input/image-clipboard.mjs +55 -55
- package/src/cli/input/keybinding-dispatch.mjs +76 -76
- package/src/cli/input/keybindings.mjs +96 -96
- package/src/cli/input/mode-state.mjs +43 -43
- package/src/cli/input/prompt-templates.mjs +84 -84
- package/src/cli/input/select-with-keyboard.mjs +86 -86
- package/src/cli/permissions.mjs +103 -103
- package/src/cli/repl-commands.mjs +86 -86
- package/src/cli/repl-loop.mjs +183 -183
- package/src/cli/selector-list.mjs +21 -21
- package/src/cli/session/pi-session-switch-command.mjs +41 -41
- package/src/cli/session/session-command.mjs +23 -23
- package/src/cli/session/session-list-command.mjs +68 -68
- package/src/cli/session/session-name-command.mjs +26 -26
- package/src/cli/session/session-source-command.mjs +89 -89
- package/src/cli/session/session-switch-command.mjs +1 -1
- package/src/cli/shell/shell-command.mjs +55 -55
- package/src/cli/shell/shell-drawer-controls.mjs +33 -33
- package/src/cli/shell/shell-drawer.mjs +192 -192
- package/src/cli/shell/shell-split-layout.mjs +70 -70
- package/src/cli/slash-commands.mjs +192 -192
- package/src/cli/startup/create-runtime-runner.mjs +61 -61
- package/src/cli/startup/runtime-close.mjs +23 -23
- package/src/cli/startup/startup-banner.mjs +71 -71
- package/src/cli/startup/startup-session.mjs +51 -51
- package/src/cli/status-line-updater.mjs +75 -75
- package/src/cli/tool-output.mjs +9 -9
- package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
- package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
- package/src/cli/tui/input/mouse-tracking.mjs +20 -20
- package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
- package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
- package/src/cli/tui/markdown-renderer.mjs +285 -285
- package/src/cli/tui/output/scroll-state.mjs +79 -79
- package/src/cli/tui/output/text-line-renderer.mjs +50 -50
- package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
- package/src/cli/tui/output/visible-lines.mjs +8 -8
- package/src/cli/tui/output-buffer.mjs +293 -293
- package/src/cli/tui/permission-request-ui.mjs +18 -18
- package/src/cli/tui/recall-rendering.mjs +25 -25
- package/src/cli/tui/render/render-scheduler.mjs +26 -26
- package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
- package/src/cli/tui/select/editor-select-list.mjs +111 -111
- package/src/cli/tui/selection-screen.mjs +269 -269
- package/src/cli/tui/status/retry-status.mjs +72 -72
- package/src/cli/tui/status/spinner-status.mjs +42 -42
- package/src/cli/tui/status/status-bar.mjs +225 -225
- package/src/cli/tui/syntax/highlighting.mjs +260 -260
- package/src/cli/tui/syntax/languages.mjs +91 -91
- package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
- package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
- package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
- package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
- package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
- package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
- package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
- package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
- package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
- package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
- package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
- package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
- package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
- package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
- package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
- package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
- package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
- package/src/cli/tui/tool-rendering.mjs +87 -87
- package/src/cli/tui/tui-diff-rendering.mjs +157 -157
- package/src/cli/tui/tui-handlers.mjs +111 -111
- package/src/cli/tui/tui-input-controller.mjs +61 -61
- package/src/cli/tui/ui-theme.mjs +157 -157
- package/src/cli/ui.mjs +297 -297
- package/src/config/config-json.mjs +84 -84
- package/src/config/dotenv.mjs +20 -20
- package/src/config/features.mjs +75 -75
- package/src/config/loader.mjs +143 -143
- package/src/config/settings-command.mjs +97 -97
- package/src/context/engine.mjs +198 -198
- package/src/context/injections.mjs +26 -26
- package/src/context/profiles.mjs +39 -39
- package/src/context/project-context.mjs +20 -20
- package/src/context/session-status.mjs +17 -17
- package/src/context/shell-layers.mjs +23 -23
- package/src/context/system-core/base.md +61 -53
- package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
- package/src/context/system-core/prompts/default.md +3 -3
- package/src/context/system-core.mjs +35 -35
- package/src/debug/logger.mjs +141 -141
- package/src/debug/model-context-dumper.mjs +52 -52
- package/src/extensions/discovery.mjs +40 -40
- package/src/extensions/lifecycle-adapter.mjs +210 -210
- package/src/extensions/lifecycle-manifest.mjs +69 -69
- package/src/image-gen/index.mjs +7 -7
- package/src/image-gen/provider.mjs +231 -231
- package/src/image-gen/tool.mjs +84 -84
- package/src/lsp/client.mjs +257 -257
- package/src/lsp/diagnostic-store.mjs +42 -42
- package/src/lsp/diagnostics-format.mjs +72 -72
- package/src/lsp/managed-node-server.mjs +99 -99
- package/src/lsp/path-match.mjs +10 -10
- package/src/lsp/server-definitions.mjs +188 -188
- package/src/lsp/servers.mjs +165 -165
- package/src/lsp/service.mjs +110 -110
- package/src/lsp/status-message.mjs +9 -9
- package/src/lsp/typescript-project-resolver.mjs +186 -186
- package/src/main.mjs +299 -299
- package/src/mcp/client.mjs +195 -195
- package/src/mcp/config.mjs +130 -130
- package/src/mcp/index.mjs +48 -48
- package/src/mcp/tools.mjs +98 -98
- package/src/memory/markdown/markdown-delete.mjs +23 -23
- package/src/memory/markdown/markdown-format.mjs +128 -128
- package/src/memory/markdown/markdown-recall.mjs +28 -28
- package/src/memory/markdown/ripgrep.mjs +16 -16
- package/src/memory/markdown/sqlite-index.mjs +87 -87
- package/src/memory/markdown-store.mjs +286 -286
- package/src/memory/markdown-tools.mjs +103 -103
- package/src/network/environment.mjs +131 -131
- package/src/notification/desktop-notifier.mjs +262 -262
- package/src/platform/open-file.mjs +28 -28
- package/src/platform/spawn-command.mjs +27 -27
- package/src/provider/accept-command.mjs +89 -89
- package/src/provider/command.mjs +21 -21
- package/src/provider/config-command.mjs +129 -129
- package/src/provider/custom-provider.mjs +113 -113
- package/src/provider/hosted-tools.mjs +111 -111
- package/src/provider/presets.mjs +72 -72
- package/src/provider/share-command.mjs +79 -79
- package/src/provider/share-payload.mjs +52 -52
- package/src/session/attachment-display.mjs +16 -16
- package/src/session/attachment-references.mjs +65 -65
- package/src/session/attachments.mjs +140 -140
- package/src/session/persist.mjs +1 -1
- package/src/session/pi-manager.mjs +34 -34
- package/src/session/session-utils.mjs +16 -16
- package/src/session/sidecar-sync.mjs +19 -19
- package/src/session/sidecar.mjs +69 -69
- package/src/session/transcript.mjs +83 -83
- package/src/session/tree.mjs +42 -42
- package/src/shell/cli-runtime.mjs +11 -11
- package/src/shell/hints.mjs +12 -12
- package/src/shell/node-pty-adapter.mjs +81 -81
- package/src/shell/runtime-state.mjs +126 -126
- package/src/shell/runtime.mjs +252 -252
- package/src/shell/screen-buffer.mjs +136 -136
- package/src/shell/tool-read.mjs +74 -74
- package/src/shell/tools.mjs +299 -299
- package/src/supergrok/actions/image-generate.mjs +60 -60
- package/src/supergrok/actions/search.mjs +78 -78
- package/src/supergrok/auth.mjs +36 -36
- package/src/supergrok/constants.mjs +18 -18
- package/src/supergrok/oauth-provider.mjs +278 -278
- package/src/supergrok/provider.mjs +35 -35
- package/src/supergrok/response.mjs +76 -76
- package/src/supergrok/tool.mjs +61 -61
- package/src/text/ansi.mjs +3 -3
- package/src/web/config-command.mjs +43 -43
- package/src/web/fetch.mjs +78 -78
- package/src/web/presets.mjs +16 -16
- package/src/web/search.mjs +83 -83
- package/src/web/tools.mjs +107 -107
- package/src/memory/database.mjs +0 -219
- package/src/memory/glossary.mjs +0 -124
- package/src/memory/graph/graph-cascades.mjs +0 -109
- package/src/memory/graph/graph-diagnostics.mjs +0 -73
- package/src/memory/graph/graph-path-removal.mjs +0 -50
- package/src/memory/graph/graph-path-utils.mjs +0 -17
- package/src/memory/graph/graph-primitives.mjs +0 -103
- package/src/memory/graph/graph-read.mjs +0 -159
- package/src/memory/graph.mjs +0 -282
- package/src/memory/search.mjs +0 -142
- package/src/memory/snapshot.mjs +0 -86
- package/src/memory/system-views.mjs +0 -120
- package/src/memory/tools.mjs +0 -282
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/march-banner.png" alt="March CLI banner" width="800">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><strong>Forget Skills. Embrace Memory. Keep your model in the sweet spot.</strong></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/march-cli"><img alt="npm" src="https://img.shields.io/npm/v/march-cli?style=flat-square" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="README.md">English</a> |
|
|
13
|
+
<a href="README.zh.md">简体中文</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g march-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Why Token Efficient?
|
|
25
|
+
|
|
26
|
+
March is obsessively token-efficient. After each turn, context resets to ~8K — we **discard** all intermediate execution and keep only two things: the user's question and the AI's final response.
|
|
27
|
+
|
|
28
|
+
Most agents fight context bloat with compression, truncation, retrieval, summarization. March's answer: just throw away what you don't need.
|
|
29
|
+
|
|
30
|
+
The result:
|
|
31
|
+
|
|
32
|
+
- **91% cache hit rate**, individual model calls rarely exceed 50K tokens
|
|
33
|
+
- Context never grows unbounded — **no context rot**
|
|
34
|
+
- Your model always operates in the sweet spot, not drowning in 100K of noise
|
|
35
|
+
|
|
36
|
+
### Memory System
|
|
37
|
+
|
|
38
|
+
March has a built-in memory system. Anything you tell March — preferences, project conventions, technical decisions — it remembers. When you need it again, March **automatically recalls** relevant memories during its thinking process. No manual search required.
|
|
39
|
+
|
|
40
|
+
#### No Skill Files
|
|
41
|
+
|
|
42
|
+
The problem with Skill systems: Skill files inject context upfront. What happens when you have too many?
|
|
43
|
+
|
|
44
|
+
March takes a different approach: every memory is a "latent skill," recalled on-demand rather than always sitting in context. What you've discussed is the best prompt.
|
|
45
|
+
|
|
46
|
+
#### Managing Memories
|
|
47
|
+
|
|
48
|
+
March stores memories as Markdown files under `~/.march/March Memories/`. You can directly edit, delete, or add files — March auto-detects changes.
|
|
49
|
+
|
|
50
|
+
### Built-in Capabilities
|
|
51
|
+
|
|
52
|
+
**Image Generation**: If you have access to ChatGPT Codex, March can generate images directly — no extra API keys or third-party services.
|
|
53
|
+
|
|
54
|
+
**Web Search**: Connect SuperGrok and **all your configured models** gain web search. March dispatches Grok to search and injects results into the current conversation.
|
|
55
|
+
|
|
56
|
+
**More Search**: Tavily Search and Brave Search are built in.
|
|
57
|
+
|
|
58
|
+
### Configuration
|
|
59
|
+
|
|
60
|
+
March uses `~/.march/config.json` (global) or `<project>/.march/config.json` (project-level) for model and provider configuration. Compatible with any OpenAI-compatible API.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"provider": "openai",
|
|
65
|
+
"model": "gpt-5.1"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
See the [docs](docs/custom-provider.md) for custom providers, multi-model setup, and more.
|
|
70
|
+
|
|
71
|
+
### FAQ
|
|
72
|
+
|
|
73
|
+
#### How is this different from Claude Code?
|
|
74
|
+
|
|
75
|
+
March is similarly capable but takes a fundamentally different approach to context. Instead of keeping everything in context and relying on compression, March resets context each turn — you get ~8K clean context every time, with 91% cache hit rate. March also replaces Skill files with a built-in memory system that recalls on demand.
|
|
76
|
+
|
|
77
|
+
#### How is this different from OpenCode?
|
|
78
|
+
|
|
79
|
+
Both are open source, terminal-native agents. March's key differentiators: extreme token efficiency via per-turn context reset, a built-in Markdown memory system with automatic recall, and the philosophy that memories should be recalled on-demand — not injected upfront like Skills.
|
|
80
|
+
|
|
81
|
+
### Documentation
|
|
82
|
+
|
|
83
|
+
- [Full Documentation](docs/) — configuration, context management, memory system
|
|
84
|
+
- [Custom Provider](docs/custom-provider.md) — connect local models or third-party APIs
|
|
85
|
+
- [Context Management](docs/context-core.md) — March's context architecture explained
|
|
86
|
+
- [Memory System](docs/markdown-memory-system.md) — storage and recall mechanism
|
|
87
|
+
|
|
88
|
+
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/march-banner.png" alt="March CLI banner" width="800">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><strong>忘记 Skill,拥抱记忆,让你的模型永远工作在甜点区。</strong></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/march-cli"><img alt="npm" src="https://img.shields.io/npm/v/march-cli?style=flat-square" /></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="README.md">English</a> |
|
|
13
|
+
<a href="README.zh.md">简体中文</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g march-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 为什么省 Token?
|
|
25
|
+
|
|
26
|
+
March 极端地省 Token。每轮对话结束后,上下文回滚到约 8K——我们**丢弃**模型中间的所有执行过程,只保留两样东西:用户的问题和 AI 的最终回复。
|
|
27
|
+
|
|
28
|
+
大多数 Agent 系统用压缩、裁剪、检索、摘要来对抗上下文膨胀。March 的答案是:直接扔掉不需要的。
|
|
29
|
+
|
|
30
|
+
结果:
|
|
31
|
+
|
|
32
|
+
- **缓存命中率 91%**,单次模型调用几乎不超过 50K
|
|
33
|
+
- 上下文不会越聊越大,**不存在上下文腐烂**
|
|
34
|
+
- 你的模型永远在甜点区工作,而不是在 100K 的噪音里大海捞针
|
|
35
|
+
|
|
36
|
+
### 记忆系统
|
|
37
|
+
|
|
38
|
+
March 内置了记忆系统。你在对话中告诉 March 的任何东西——偏好、项目约定、技术决策——它都能记住。当你再次需要时,March 会在思考过程中**自动召回**相关记忆,你不需要手动检索。
|
|
39
|
+
|
|
40
|
+
#### 不需要 Skill 文件
|
|
41
|
+
|
|
42
|
+
Skill 系统的问题是:Skill 文件在一开始就注入了上下文。Skill 多了怎么办?
|
|
43
|
+
|
|
44
|
+
March 换了一种方式:每条记忆就是一条"潜在的 Skill",由 March 在需要时动态召回,而不是常驻在上下文里。你聊过的内容就是最好的提示词。
|
|
45
|
+
|
|
46
|
+
#### 管理记忆
|
|
47
|
+
|
|
48
|
+
March 在 `~/.march/March Memories/` 目录下以 Markdown 文件存储记忆。你可以直接编辑、删除或新增这些文件,March 会自动感知变化。
|
|
49
|
+
|
|
50
|
+
### 更多内置能力
|
|
51
|
+
|
|
52
|
+
**生图**:如果你有 ChatGPT Codex 权限,March 可以直接生图,不需要额外的 API Key 或第三方服务。
|
|
53
|
+
|
|
54
|
+
**联网搜索**:接入 SuperGrok 后,你配置的**所有模型**都会获得联网搜索能力——March 会派遣 Grok 去搜索,搜索结果注入当前对话。
|
|
55
|
+
|
|
56
|
+
**更多搜索渠道**:Tavily Search、Brave Search 均已内置。
|
|
57
|
+
|
|
58
|
+
### 配置
|
|
59
|
+
|
|
60
|
+
March 通过 `~/.march/config.json`(全局)或 `<project>/.march/config.json`(项目级)配置模型和 provider。支持所有 OpenAI 兼容接口。
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"provider": "openai",
|
|
65
|
+
"model": "gpt-5.1"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
自定义 provider、多模型切换等详细配置见 [文档](docs/custom-provider.md)。
|
|
70
|
+
|
|
71
|
+
### FAQ
|
|
72
|
+
|
|
73
|
+
#### 和 Claude Code 有什么区别?
|
|
74
|
+
|
|
75
|
+
March 能力相当,但上下文策略截然不同。Claude Code 尽量保留上下文并用压缩应对膨胀;March 每轮重置上下文——你每次拿到的是干净的 ~8K 上下文,缓存命中率 91%。March 还用内置记忆系统替代了 Skill 文件,记忆按需召回而不是常驻注入。
|
|
76
|
+
|
|
77
|
+
#### 和 OpenCode 有什么区别?
|
|
78
|
+
|
|
79
|
+
两者都是开源、终端原生的 Agent。March 的核心差异:极端的 Token 效率(每轮上下文重置)、内置 Markdown 记忆系统(自动召回),以及"记忆应该按需召回、而非像 Skill 一样提前注入"的设计哲学。
|
|
80
|
+
|
|
81
|
+
### 文档
|
|
82
|
+
|
|
83
|
+
- [完整文档](docs/) — 配置、上下文管理、记忆系统
|
|
84
|
+
- [自定义 Provider](docs/custom-provider.md) — 接入本地模型或第三方 API
|
|
85
|
+
- [上下文管理](docs/context-core.md) — March 的上下文架构详解
|
|
86
|
+
- [记忆系统](docs/markdown-memory-system.md) — 记忆存储与召回机制
|
|
87
|
+
|
|
88
|
+
|
package/bin/march.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const emitWarning = process.emitWarning;
|
|
4
|
-
process.emitWarning = function filteredWarning(warning, ...args) {
|
|
5
|
-
const message = typeof warning === "string" ? warning : warning?.message;
|
|
6
|
-
const type = typeof warning === "string" ? args[0] : warning?.name;
|
|
7
|
-
if (type === "ExperimentalWarning" && String(message).includes("SQLite")) return;
|
|
8
|
-
return emitWarning.call(this, warning, ...args);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const { run } = await import("../src/main.mjs");
|
|
12
|
-
const code = await run(process.argv.slice(2));
|
|
13
|
-
process.exitCode = code;
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const emitWarning = process.emitWarning;
|
|
4
|
+
process.emitWarning = function filteredWarning(warning, ...args) {
|
|
5
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
6
|
+
const type = typeof warning === "string" ? args[0] : warning?.name;
|
|
7
|
+
if (type === "ExperimentalWarning" && String(message).includes("SQLite")) return;
|
|
8
|
+
return emitWarning.call(this, warning, ...args);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const { run } = await import("../src/main.mjs");
|
|
12
|
+
const code = await run(process.argv.slice(2));
|
|
13
|
+
process.exitCode = code;
|
package/package.json
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "march-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "March CLI — terminal-native coding agent with context reconstruction",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./src/main.mjs",
|
|
7
|
-
"bin": {
|
|
8
|
-
"march": "bin/march.mjs"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin/",
|
|
12
|
-
"src/"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"dev": "cd .. && node march-cli/bin/march.mjs",
|
|
16
|
-
"test": "node test/smoke.test.mjs",
|
|
17
|
-
"test:fast": "node test/fast.test.mjs",
|
|
18
|
-
"test:real": "npm run test:shell-runtime-real && npm run test:shell-tui-real && npm run test:tui-key-real",
|
|
19
|
-
"test:shell-runtime-real": "node test/shell-real-runtime.acceptance.mjs",
|
|
20
|
-
"test:shell-tui-real": "node test/shell-tui-real.acceptance.mjs",
|
|
21
|
-
"test:tui-key-real": "node test/tui-key-real.acceptance.mjs",
|
|
22
|
-
"context": "cd .. && node march-cli/bin/march.mjs --dump-context",
|
|
23
|
-
"notify:experiment": "node scripts/notify-experiment.mjs",
|
|
24
|
-
"publish:env": "node scripts/npm-publish-from-env.mjs"
|
|
25
|
-
},
|
|
26
|
-
"dependencies": {
|
|
27
|
-
"@earendil-works/pi-ai": "^0.74.0",
|
|
28
|
-
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
29
|
-
"@earendil-works/pi-tui": "^0.74.0",
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
-
"@xterm/headless": "^5.5.0",
|
|
32
|
-
"marked": "^18.0.3",
|
|
33
|
-
"node-notifier": "^10.0.1",
|
|
34
|
-
"node-pty": "^1.1.0",
|
|
35
|
-
"typebox": "^1.0.58",
|
|
36
|
-
"undici": "^7.25.0",
|
|
37
|
-
"web-tree-sitter": "^0.26.8",
|
|
38
|
-
"ws": "^8.20.1"
|
|
39
|
-
},
|
|
40
|
-
"optionalDependencies": {
|
|
41
|
-
"@vscode/ripgrep": "^1.18.0"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "march-cli",
|
|
3
|
+
"version": "0.1.23",
|
|
4
|
+
"description": "March CLI — terminal-native coding agent with context reconstruction",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/main.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"march": "bin/march.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "cd .. && node march-cli/bin/march.mjs",
|
|
16
|
+
"test": "node test/smoke.test.mjs",
|
|
17
|
+
"test:fast": "node test/fast.test.mjs",
|
|
18
|
+
"test:real": "npm run test:shell-runtime-real && npm run test:shell-tui-real && npm run test:tui-key-real",
|
|
19
|
+
"test:shell-runtime-real": "node test/shell-real-runtime.acceptance.mjs",
|
|
20
|
+
"test:shell-tui-real": "node test/shell-tui-real.acceptance.mjs",
|
|
21
|
+
"test:tui-key-real": "node test/tui-key-real.acceptance.mjs",
|
|
22
|
+
"context": "cd .. && node march-cli/bin/march.mjs --dump-context",
|
|
23
|
+
"notify:experiment": "node scripts/notify-experiment.mjs",
|
|
24
|
+
"publish:env": "node scripts/npm-publish-from-env.mjs"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@earendil-works/pi-ai": "^0.74.0",
|
|
28
|
+
"@earendil-works/pi-coding-agent": "^0.74.0",
|
|
29
|
+
"@earendil-works/pi-tui": "^0.74.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
31
|
+
"@xterm/headless": "^5.5.0",
|
|
32
|
+
"marked": "^18.0.3",
|
|
33
|
+
"node-notifier": "^10.0.1",
|
|
34
|
+
"node-pty": "^1.1.0",
|
|
35
|
+
"typebox": "^1.0.58",
|
|
36
|
+
"undici": "^7.25.0",
|
|
37
|
+
"web-tree-sitter": "^0.26.8",
|
|
38
|
+
"ws": "^8.20.1"
|
|
39
|
+
},
|
|
40
|
+
"optionalDependencies": {
|
|
41
|
+
"@vscode/ripgrep": "^1.18.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,168 +1,172 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import { toolText } from "./tool-result.mjs";
|
|
6
|
-
import { stripAnsi } from "../text/ansi.mjs";
|
|
7
|
-
|
|
8
|
-
const OUTPUT_LIMIT = 64 * 1024;
|
|
9
|
-
const DEFAULT_COMMAND_TIMEOUT_SECONDS = 60;
|
|
10
|
-
const COMMAND_KILL_GRACE_MS = 1000;
|
|
11
|
-
|
|
12
|
-
export function createCommandExecTool({ cwd }) {
|
|
13
|
-
return defineTool({
|
|
14
|
-
name: "command_exec",
|
|
15
|
-
label: "Command Exec",
|
|
16
|
-
description: "Run a one-shot command in the project directory. Use terminal_* for interactive or long-running processes.",
|
|
17
|
-
parameters: Type.Object({
|
|
18
|
-
command: Type.String({ description: "Command to execute" }),
|
|
19
|
-
shell: Type.Optional(Type.String({ description: "auto (default), bash, or powershell" })),
|
|
20
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds; default 60", default: DEFAULT_COMMAND_TIMEOUT_SECONDS })),
|
|
21
|
-
}),
|
|
22
|
-
execute: async (_toolCallId, params, signal) => executeCommand({ cwd, signal, ...params }),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function executeCommand({ cwd, command, shell = "auto", timeout = DEFAULT_COMMAND_TIMEOUT_SECONDS, signal, spawnImpl = spawn, killProcessTreeImpl = killProcessTree, forceSettleMs = COMMAND_KILL_GRACE_MS }) {
|
|
27
|
-
let resolved;
|
|
28
|
-
try {
|
|
29
|
-
resolved = resolveCommandShell(shell);
|
|
30
|
-
} catch (err) {
|
|
31
|
-
return toolText(`Error: ${err.message}`, { error: true });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const timeoutSeconds = Math.max(0.001, Number(timeout) || DEFAULT_COMMAND_TIMEOUT_SECONDS);
|
|
35
|
-
const timeoutMs = timeoutSeconds * 1000;
|
|
36
|
-
const result = await spawnCommand(spawnImpl, resolved.bin, [...resolved.args, String(command ?? "")], {
|
|
37
|
-
cwd,
|
|
38
|
-
timeoutMs,
|
|
39
|
-
signal,
|
|
40
|
-
windowsHide: true,
|
|
41
|
-
killProcessTreeImpl,
|
|
42
|
-
forceSettleMs,
|
|
43
|
-
});
|
|
44
|
-
if (result.error) {
|
|
45
|
-
const detail = result.timedOut ? ` (timed out after ${timeoutSeconds}s)` : "";
|
|
46
|
-
return toolText(`Error: ${result.error.message}${detail}`, { error: true });
|
|
47
|
-
}
|
|
48
|
-
const stdout = stripAnsi(result.stdout ?? "");
|
|
49
|
-
const stderr = stripAnsi(result.stderr ?? "");
|
|
50
|
-
const output = formatCommandOutput({ stdout, stderr, status: result.status });
|
|
51
|
-
return toolText(output, {
|
|
52
|
-
status: result.status,
|
|
53
|
-
stdout,
|
|
54
|
-
stderr,
|
|
55
|
-
shell: resolved.name,
|
|
56
|
-
error: result.status !== 0,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function spawnCommand(spawnImpl, bin, args, options) {
|
|
61
|
-
return new Promise((resolve) => {
|
|
62
|
-
let stdout = "";
|
|
63
|
-
let stderr = "";
|
|
64
|
-
let settled = false;
|
|
65
|
-
let timedOut = false;
|
|
66
|
-
let aborted = false;
|
|
67
|
-
let forceTimer = null;
|
|
68
|
-
const child = spawnImpl(bin, args, {
|
|
69
|
-
cwd: options.cwd,
|
|
70
|
-
windowsHide: options.windowsHide,
|
|
71
|
-
detached: process.platform !== "win32",
|
|
72
|
-
});
|
|
73
|
-
const timer = setTimeout(() => {
|
|
74
|
-
timedOut = true;
|
|
75
|
-
terminateChild(new Error("Command timed out"));
|
|
76
|
-
}, options.timeoutMs);
|
|
77
|
-
const onAbort = () => {
|
|
78
|
-
aborted = true;
|
|
79
|
-
terminateChild(new Error("Command aborted"));
|
|
80
|
-
};
|
|
81
|
-
if (options.signal?.aborted) queueMicrotask(onAbort);
|
|
82
|
-
else options.signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
83
|
-
|
|
84
|
-
child.stdout?.setEncoding?.("utf8");
|
|
85
|
-
child.stderr?.setEncoding?.("utf8");
|
|
86
|
-
child.stdout?.on?.("data", (chunk) => { stdout = appendLimited(stdout, chunk); });
|
|
87
|
-
child.stderr?.on?.("data", (chunk) => { stderr = appendLimited(stderr, chunk); });
|
|
88
|
-
child.once?.("error", (error) => finish({ error }));
|
|
89
|
-
child.once?.("close", (status, signal) => finish({ status, signal }));
|
|
90
|
-
|
|
91
|
-
function terminateChild(error) {
|
|
92
|
-
if (settled) return;
|
|
93
|
-
options.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { toolText } from "./tool-result.mjs";
|
|
6
|
+
import { stripAnsi } from "../text/ansi.mjs";
|
|
7
|
+
|
|
8
|
+
const OUTPUT_LIMIT = 64 * 1024;
|
|
9
|
+
const DEFAULT_COMMAND_TIMEOUT_SECONDS = 60;
|
|
10
|
+
const COMMAND_KILL_GRACE_MS = 1000;
|
|
11
|
+
|
|
12
|
+
export function createCommandExecTool({ cwd }) {
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: "command_exec",
|
|
15
|
+
label: "Command Exec",
|
|
16
|
+
description: "Run a one-shot command in the project directory. Use terminal_* for interactive or long-running processes.",
|
|
17
|
+
parameters: Type.Object({
|
|
18
|
+
command: Type.String({ description: "Command to execute" }),
|
|
19
|
+
shell: Type.Optional(Type.String({ description: "auto (default), bash, or powershell" })),
|
|
20
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds; default 60", default: DEFAULT_COMMAND_TIMEOUT_SECONDS })),
|
|
21
|
+
}),
|
|
22
|
+
execute: async (_toolCallId, params, signal) => executeCommand({ cwd, signal, ...params }),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function executeCommand({ cwd, command, shell = "auto", timeout = DEFAULT_COMMAND_TIMEOUT_SECONDS, signal, spawnImpl = spawn, killProcessTreeImpl = killProcessTree, forceSettleMs = COMMAND_KILL_GRACE_MS }) {
|
|
27
|
+
let resolved;
|
|
28
|
+
try {
|
|
29
|
+
resolved = resolveCommandShell(shell);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return toolText(`Error: ${err.message}`, { error: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const timeoutSeconds = Math.max(0.001, Number(timeout) || DEFAULT_COMMAND_TIMEOUT_SECONDS);
|
|
35
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
36
|
+
const result = await spawnCommand(spawnImpl, resolved.bin, [...resolved.args, String(command ?? "")], {
|
|
37
|
+
cwd,
|
|
38
|
+
timeoutMs,
|
|
39
|
+
signal,
|
|
40
|
+
windowsHide: true,
|
|
41
|
+
killProcessTreeImpl,
|
|
42
|
+
forceSettleMs,
|
|
43
|
+
});
|
|
44
|
+
if (result.error) {
|
|
45
|
+
const detail = result.timedOut ? ` (timed out after ${timeoutSeconds}s)` : "";
|
|
46
|
+
return toolText(`Error: ${result.error.message}${detail}`, { error: true });
|
|
47
|
+
}
|
|
48
|
+
const stdout = stripAnsi(result.stdout ?? "");
|
|
49
|
+
const stderr = stripAnsi(result.stderr ?? "");
|
|
50
|
+
const output = formatCommandOutput({ stdout, stderr, status: result.status });
|
|
51
|
+
return toolText(output, {
|
|
52
|
+
status: result.status,
|
|
53
|
+
stdout,
|
|
54
|
+
stderr,
|
|
55
|
+
shell: resolved.name,
|
|
56
|
+
error: result.status !== 0,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function spawnCommand(spawnImpl, bin, args, options) {
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
let stdout = "";
|
|
63
|
+
let stderr = "";
|
|
64
|
+
let settled = false;
|
|
65
|
+
let timedOut = false;
|
|
66
|
+
let aborted = false;
|
|
67
|
+
let forceTimer = null;
|
|
68
|
+
const child = spawnImpl(bin, args, {
|
|
69
|
+
cwd: options.cwd,
|
|
70
|
+
windowsHide: options.windowsHide,
|
|
71
|
+
detached: process.platform !== "win32",
|
|
72
|
+
});
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
timedOut = true;
|
|
75
|
+
terminateChild(new Error("Command timed out"));
|
|
76
|
+
}, options.timeoutMs);
|
|
77
|
+
const onAbort = () => {
|
|
78
|
+
aborted = true;
|
|
79
|
+
terminateChild(new Error("Command aborted"));
|
|
80
|
+
};
|
|
81
|
+
if (options.signal?.aborted) queueMicrotask(onAbort);
|
|
82
|
+
else options.signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
83
|
+
|
|
84
|
+
child.stdout?.setEncoding?.("utf8");
|
|
85
|
+
child.stderr?.setEncoding?.("utf8");
|
|
86
|
+
child.stdout?.on?.("data", (chunk) => { stdout = appendLimited(stdout, chunk); });
|
|
87
|
+
child.stderr?.on?.("data", (chunk) => { stderr = appendLimited(stderr, chunk); });
|
|
88
|
+
child.once?.("error", (error) => finish({ error }));
|
|
89
|
+
child.once?.("close", (status, signal) => finish({ status, signal }));
|
|
90
|
+
|
|
91
|
+
function terminateChild(error) {
|
|
92
|
+
if (settled) return;
|
|
93
|
+
forceTimer ??= setTimeout(() => finish({ error }), options.forceSettleMs);
|
|
94
|
+
try {
|
|
95
|
+
options.killProcessTreeImpl?.(child);
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function finish(result) {
|
|
100
|
+
if (settled) return;
|
|
101
|
+
settled = true;
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
clearTimeout(forceTimer);
|
|
104
|
+
options.signal?.removeEventListener?.("abort", onAbort);
|
|
105
|
+
const error = result.error ?? (timedOut ? Object.assign(new Error("Command timed out"), { code: "ETIMEDOUT" }) : null) ?? (aborted ? Object.assign(new Error("Command aborted"), { code: "ABORT_ERR" }) : null);
|
|
106
|
+
resolve({ ...result, error, stdout, stderr, timedOut, aborted });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function killProcessTree(child, platform = process.platform, spawnImpl = spawn) {
|
|
112
|
+
if (!child?.pid) {
|
|
113
|
+
child?.kill?.("SIGTERM");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (platform === "win32") {
|
|
117
|
+
try {
|
|
118
|
+
const killer = spawnImpl("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
119
|
+
killer?.on?.("error", () => child.kill?.("SIGTERM"));
|
|
120
|
+
killer?.unref?.();
|
|
121
|
+
return;
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
process.kill(-child.pid, "SIGTERM");
|
|
126
|
+
return;
|
|
127
|
+
} catch {}
|
|
128
|
+
child.kill?.("SIGTERM");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function appendLimited(current, chunk) {
|
|
132
|
+
const next = current + String(chunk ?? "");
|
|
133
|
+
return next.length <= OUTPUT_LIMIT ? next : next.slice(-OUTPUT_LIMIT);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function resolveCommandShell(shell = "auto", platform = process.platform) {
|
|
137
|
+
const normalized = String(shell ?? "auto").trim().toLowerCase();
|
|
138
|
+
if (normalized === "powershell" || (normalized === "auto" && platform === "win32")) {
|
|
139
|
+
return { name: "powershell", bin: findPowerShell() ?? "powershell.exe", args: ["-NoProfile", "-Command"] };
|
|
140
|
+
}
|
|
141
|
+
if (normalized === "bash" || normalized === "auto") {
|
|
142
|
+
return { name: "bash", bin: "bash", args: ["-lc"] };
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`unsupported shell: ${shell}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findPowerShell() {
|
|
148
|
+
for (const name of ["pwsh.exe", "powershell.exe"]) {
|
|
149
|
+
try {
|
|
150
|
+
const result = spawnSync("where", [name], { encoding: "utf-8", timeout: 5000, windowsHide: true });
|
|
151
|
+
if (result.status === 0 && result.stdout) {
|
|
152
|
+
const first = result.stdout.trim().split(/\r?\n/)[0];
|
|
153
|
+
if (first && existsSync(first)) return first;
|
|
154
|
+
}
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function formatCommandOutput({ stdout, stderr, status }) {
|
|
161
|
+
const parts = [];
|
|
162
|
+
if (stdout) parts.push(stdout.trimEnd());
|
|
163
|
+
if (stderr) parts.push(stderr.trimEnd());
|
|
164
|
+
if (status && status !== 0) parts.push(`exit ${status}`);
|
|
165
|
+
const output = parts.filter(Boolean).join("\n");
|
|
166
|
+
return output ? truncateOutput(output) : "(no output)";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function truncateOutput(text) {
|
|
170
|
+
if (text.length <= OUTPUT_LIMIT) return text;
|
|
171
|
+
return `${text.slice(-OUTPUT_LIMIT)}\n... (truncated to last ${OUTPUT_LIMIT} chars)`;
|
|
172
|
+
}
|