apex-code 0.0.1-alpha.7 → 0.0.1-alpha.8
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/CHANGELOG.md +146 -0
- package/README.md +5 -0
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/concurrent-session.d.ts +3 -0
- package/dist/cli/concurrent-session.d.ts.map +1 -0
- package/dist/cli/concurrent-session.js +13 -0
- package/dist/cli/concurrent-session.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +20 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +15 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +9 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +10 -9
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +4 -0
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +1 -2
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -17
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +12 -0
- package/dist/core/http-idle-timeout.d.ts.map +1 -0
- package/dist/core/http-idle-timeout.js +27 -0
- package/dist/core/http-idle-timeout.js.map +1 -0
- package/dist/core/keybindings.d.ts +24 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/lsp/client.d.ts +2 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +27 -2
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +1 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +8 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +3 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +25 -4
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +53 -13
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +15 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +24 -1
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.js +170 -0
- package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +11 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-size.d.ts +40 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-size.js +105 -0
- package/dist/core/sandbox/terminal-size.js.map +1 -0
- package/dist/core/sdk.d.ts +6 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +16 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-lease.d.ts +31 -0
- package/dist/core/session-lease.d.ts.map +1 -0
- package/dist/core/session-lease.js +138 -0
- package/dist/core/session-lease.js.map +1 -0
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +3 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +26 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +42 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -2
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +47 -62
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +24 -0
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +35 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/web-search-exa.d.ts +33 -0
- package/dist/core/tools/web-search-exa.d.ts.map +1 -0
- package/dist/core/tools/web-search-exa.js +128 -0
- package/dist/core/tools/web-search-exa.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/web-search-provider.d.ts +64 -0
- package/dist/core/web-search-provider.d.ts.map +1 -0
- package/dist/core/web-search-provider.js +149 -0
- package/dist/core/web-search-provider.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +105 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +11 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +24 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +6 -3
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-use-hints.js +22 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +12 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +128 -9
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +2 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +20 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/splash-header.d.ts +29 -4
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
- package/dist/modes/interactive/components/splash-header.js +58 -23
- package/dist/modes/interactive/components/splash-header.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +113 -35
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-panel.js +130 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +44 -9
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +282 -70
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/apex.json +62 -63
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +55 -6
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +4 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +5 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +2 -0
- package/dist/server/create-harness.js.map +1 -1
- package/dist/themes/apex-logo.d.ts +36 -16
- package/dist/themes/apex-logo.d.ts.map +1 -1
- package/dist/themes/apex-logo.js +32 -28
- package/dist/themes/apex-logo.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +15 -20
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +3 -3
- package/docs/custom-provider.md +1 -1
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +4 -0
- package/docs/json.md +6 -4
- package/docs/keybindings.md +8 -2
- package/docs/rpc.md +15 -4
- package/docs/sdk.md +9 -0
- package/docs/settings.md +63 -0
- package/docs/terminal-setup.md +23 -2
- package/docs/themes.md +24 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +3 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +2 -0
- package/examples/extensions/subagent/agents.ts +40 -9
- package/examples/extensions/subagent/index.ts +21 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +27 -78
- package/package.json +6 -6
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The Apex Code brand mark, in the forms a terminal can actually render.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - the ASCII peak honours `terminal.symbolPreset: "ascii"`, whose users opted
|
|
10
|
-
* out of block drawing because it renders badly for them.
|
|
4
|
+
* Only the five-row block wordmark survives a monospace grid. Every monospace
|
|
5
|
+
* face leaves a hairline gap at the cell boundary, so a letterform two or three
|
|
6
|
+
* rows tall dissolves into texture rather than reading as a word. The mark
|
|
7
|
+
* therefore does not shrink: below the width the block form needs, it is
|
|
8
|
+
* replaced outright by a glyph-and-type lockup.
|
|
11
9
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* A mark is a list of rows, each split into an accent run and a text run. That
|
|
11
|
+
* one shape covers both forms: the block wordmark puts its whole baseline row
|
|
12
|
+
* in the accent so the letters sit on an ember footing, and the inline lockup
|
|
13
|
+
* puts only its leading glyph there.
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
export interface MarkRow {
|
|
16
|
+
/** Leading run, painted in the brand accent. */
|
|
17
|
+
readonly accent: string;
|
|
18
|
+
/** Trailing run, painted in the neutral text tone. */
|
|
19
|
+
readonly text: string;
|
|
20
|
+
}
|
|
21
|
+
export interface BrandMark {
|
|
22
|
+
readonly rows: readonly MarkRow[];
|
|
23
|
+
/** Widest row, in terminal columns. */
|
|
24
|
+
readonly width: number;
|
|
25
|
+
}
|
|
26
|
+
export type MarkSymbolPreset = "unicode" | "ascii";
|
|
27
|
+
/** 5 rows x 30 columns. The brand mark, with the baseline row in the accent. */
|
|
28
|
+
export declare const APEX_MARK_BLOCK: BrandMark;
|
|
29
|
+
/** 1 row x 11 columns. Used when the block mark will not fit. */
|
|
30
|
+
export declare const APEX_MARK_INLINE: BrandMark;
|
|
31
|
+
/**
|
|
32
|
+
* 1 row x 12 columns. Honours `terminal.symbolPreset: "ascii"`, whose users
|
|
33
|
+
* opted out of block drawing because it renders badly for them. The previous
|
|
34
|
+
* ASCII mark was line-drawn art whose first letter was an O, so it read "OPEX".
|
|
35
|
+
*/
|
|
36
|
+
export declare const APEX_MARK_INLINE_ASCII: BrandMark;
|
|
37
|
+
/** Pick the widest mark that fits. ASCII users always get the type lockup. */
|
|
38
|
+
export declare function selectBrandMark(contentWidth: number, symbolPreset: MarkSymbolPreset): BrandMark;
|
|
39
|
+
/** Wrap a mark supplied by a rebranding extension. It carries no accent run. */
|
|
40
|
+
export declare function customBrandMark(logo: string): BrandMark;
|
|
21
41
|
//# sourceMappingURL=apex-logo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apex-logo.d.ts","sourceRoot":"","sources":["../../src/themes/apex-logo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apex-logo.d.ts","sourceRoot":"","sources":["../../src/themes/apex-logo.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,OAAO;IACvB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAiBnD,gFAAgF;AAChF,eAAO,MAAM,eAAe,WAI3B,CAAC;AAEF,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,WAA8C,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,WAAgD,CAAC;AAEpF,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAG/F;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAEvD","sourcesContent":["import { visibleWidth } from \"@earendil-works/pi-tui\";\n\n/**\n * The Apex Code brand mark, in the forms a terminal can actually render.\n *\n * Only the five-row block wordmark survives a monospace grid. Every monospace\n * face leaves a hairline gap at the cell boundary, so a letterform two or three\n * rows tall dissolves into texture rather than reading as a word. The mark\n * therefore does not shrink: below the width the block form needs, it is\n * replaced outright by a glyph-and-type lockup.\n *\n * A mark is a list of rows, each split into an accent run and a text run. That\n * one shape covers both forms: the block wordmark puts its whole baseline row\n * in the accent so the letters sit on an ember footing, and the inline lockup\n * puts only its leading glyph there.\n */\n\nexport interface MarkRow {\n\t/** Leading run, painted in the brand accent. */\n\treadonly accent: string;\n\t/** Trailing run, painted in the neutral text tone. */\n\treadonly text: string;\n}\n\nexport interface BrandMark {\n\treadonly rows: readonly MarkRow[];\n\t/** Widest row, in terminal columns. */\n\treadonly width: number;\n}\n\nexport type MarkSymbolPreset = \"unicode\" | \"ascii\";\n\nfunction mark(rows: readonly MarkRow[]): BrandMark {\n\treturn {\n\t\trows,\n\t\twidth: rows.reduce((max, row) => Math.max(max, visibleWidth(row.accent + row.text)), 0),\n\t};\n}\n\nconst BLOCK_ROWS = [\n\t\" ▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄ ▄▄\",\n\t\"██ ██ ██ ██ ██ ██ ██ \",\n\t\"███████ ██████ █████ ███ \",\n\t\"██ ██ ██ ██ ██ ██ \",\n\t\"▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀\",\n];\n\n/** 5 rows x 30 columns. The brand mark, with the baseline row in the accent. */\nexport const APEX_MARK_BLOCK = mark(\n\tBLOCK_ROWS.map((row, index) =>\n\t\tindex === BLOCK_ROWS.length - 1 ? { accent: row, text: \"\" } : { accent: \"\", text: row },\n\t),\n);\n\n/** 1 row x 11 columns. Used when the block mark will not fit. */\nexport const APEX_MARK_INLINE = mark([{ accent: \"◤\", text: \" apex code\" }]);\n\n/**\n * 1 row x 12 columns. Honours `terminal.symbolPreset: \"ascii\"`, whose users\n * opted out of block drawing because it renders badly for them. The previous\n * ASCII mark was line-drawn art whose first letter was an O, so it read \"OPEX\".\n */\nexport const APEX_MARK_INLINE_ASCII = mark([{ accent: \"/\\\\\", text: \" apex code\" }]);\n\n/** Pick the widest mark that fits. ASCII users always get the type lockup. */\nexport function selectBrandMark(contentWidth: number, symbolPreset: MarkSymbolPreset): BrandMark {\n\tif (symbolPreset === \"ascii\") return APEX_MARK_INLINE_ASCII;\n\treturn contentWidth >= APEX_MARK_BLOCK.width ? APEX_MARK_BLOCK : APEX_MARK_INLINE;\n}\n\n/** Wrap a mark supplied by a rebranding extension. It carries no accent run. */\nexport function customBrandMark(logo: string): BrandMark {\n\treturn mark(logo.split(\"\\n\").map((row) => ({ accent: \"\", text: row })));\n}\n"]}
|
package/dist/themes/apex-logo.js
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
function mark(rows) {
|
|
3
|
+
return {
|
|
4
|
+
rows,
|
|
5
|
+
width: rows.reduce((max, row) => Math.max(max, visibleWidth(row.accent + row.text)), 0),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const BLOCK_ROWS = [
|
|
9
|
+
" ▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄ ▄▄",
|
|
10
|
+
"██ ██ ██ ██ ██ ██ ██ ",
|
|
11
|
+
"███████ ██████ █████ ███ ",
|
|
12
|
+
"██ ██ ██ ██ ██ ██ ",
|
|
13
|
+
"▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀",
|
|
14
|
+
];
|
|
15
|
+
/** 5 rows x 30 columns. The brand mark, with the baseline row in the accent. */
|
|
16
|
+
export const APEX_MARK_BLOCK = mark(BLOCK_ROWS.map((row, index) => index === BLOCK_ROWS.length - 1 ? { accent: row, text: "" } : { accent: "", text: row }));
|
|
17
|
+
/** 1 row x 11 columns. Used when the block mark will not fit. */
|
|
18
|
+
export const APEX_MARK_INLINE = mark([{ accent: "◤", text: " apex code" }]);
|
|
1
19
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - the half-block peak is the brand mark, used whenever the terminal is wide
|
|
6
|
-
* enough and block glyphs are in play;
|
|
7
|
-
* - the compact peak keeps a mark on screen in narrow terminals rather than
|
|
8
|
-
* truncating the full one mid-glyph;
|
|
9
|
-
* - the ASCII peak honours `terminal.symbolPreset: "ascii"`, whose users opted
|
|
10
|
-
* out of block drawing because it renders badly for them.
|
|
11
|
-
*
|
|
12
|
-
* All three are rendered in a single flat tone by the header. A flat tone is
|
|
13
|
-
* what makes the mark read as a mark rather than as decoration.
|
|
20
|
+
* 1 row x 12 columns. Honours `terminal.symbolPreset: "ascii"`, whose users
|
|
21
|
+
* opted out of block drawing because it renders badly for them. The previous
|
|
22
|
+
* ASCII mark was line-drawn art whose first letter was an O, so it read "OPEX".
|
|
14
23
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
▀▀ ▀▀`;
|
|
27
|
-
/** 9 rows x 19 cols. Line-drawing fallback for `symbolPreset: "ascii"`. */
|
|
28
|
-
export const APEX_PEAK_LOGO_ASCII = ` _ _ ____ __
|
|
29
|
-
/ \\ |_) |__ \\/
|
|
30
|
-
\\_/ | |___ /\\ `;
|
|
24
|
+
export const APEX_MARK_INLINE_ASCII = mark([{ accent: "/\\", text: " apex code" }]);
|
|
25
|
+
/** Pick the widest mark that fits. ASCII users always get the type lockup. */
|
|
26
|
+
export function selectBrandMark(contentWidth, symbolPreset) {
|
|
27
|
+
if (symbolPreset === "ascii")
|
|
28
|
+
return APEX_MARK_INLINE_ASCII;
|
|
29
|
+
return contentWidth >= APEX_MARK_BLOCK.width ? APEX_MARK_BLOCK : APEX_MARK_INLINE;
|
|
30
|
+
}
|
|
31
|
+
/** Wrap a mark supplied by a rebranding extension. It carries no accent run. */
|
|
32
|
+
export function customBrandMark(logo) {
|
|
33
|
+
return mark(logo.split("\n").map((row) => ({ accent: "", text: row })));
|
|
34
|
+
}
|
|
31
35
|
//# sourceMappingURL=apex-logo.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apex-logo.js","sourceRoot":"","sources":["../../src/themes/apex-logo.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"apex-logo.js","sourceRoot":"","sources":["../../src/themes/apex-logo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAgCtD,SAAS,IAAI,CAAC,IAAwB,EAAa;IAClD,OAAO;QACN,IAAI;QACJ,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;KACvF,CAAC;AAAA,CACF;AAED,MAAM,UAAU,GAAG;IAClB,0EAAgC;IAChC,4DAAgC;IAChC,0EAAgC;IAChC,wDAAgC;IAChC,gEAAgC;CAChC,CAAC;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAClC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAC7B,KAAK,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CACvF,CACD,CAAC;AAEF,iEAAiE;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,KAAG,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAEpF,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,YAA8B,EAAa;IAChG,IAAI,YAAY,KAAK,OAAO;QAAE,OAAO,sBAAsB,CAAC;IAC5D,OAAO,YAAY,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB,CAAC;AAAA,CAClF;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,IAAY,EAAa;IACxD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAAA,CACxE","sourcesContent":["import { visibleWidth } from \"@earendil-works/pi-tui\";\n\n/**\n * The Apex Code brand mark, in the forms a terminal can actually render.\n *\n * Only the five-row block wordmark survives a monospace grid. Every monospace\n * face leaves a hairline gap at the cell boundary, so a letterform two or three\n * rows tall dissolves into texture rather than reading as a word. The mark\n * therefore does not shrink: below the width the block form needs, it is\n * replaced outright by a glyph-and-type lockup.\n *\n * A mark is a list of rows, each split into an accent run and a text run. That\n * one shape covers both forms: the block wordmark puts its whole baseline row\n * in the accent so the letters sit on an ember footing, and the inline lockup\n * puts only its leading glyph there.\n */\n\nexport interface MarkRow {\n\t/** Leading run, painted in the brand accent. */\n\treadonly accent: string;\n\t/** Trailing run, painted in the neutral text tone. */\n\treadonly text: string;\n}\n\nexport interface BrandMark {\n\treadonly rows: readonly MarkRow[];\n\t/** Widest row, in terminal columns. */\n\treadonly width: number;\n}\n\nexport type MarkSymbolPreset = \"unicode\" | \"ascii\";\n\nfunction mark(rows: readonly MarkRow[]): BrandMark {\n\treturn {\n\t\trows,\n\t\twidth: rows.reduce((max, row) => Math.max(max, visibleWidth(row.accent + row.text)), 0),\n\t};\n}\n\nconst BLOCK_ROWS = [\n\t\" ▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄ ▄▄\",\n\t\"██ ██ ██ ██ ██ ██ ██ \",\n\t\"███████ ██████ █████ ███ \",\n\t\"██ ██ ██ ██ ██ ██ \",\n\t\"▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀\",\n];\n\n/** 5 rows x 30 columns. The brand mark, with the baseline row in the accent. */\nexport const APEX_MARK_BLOCK = mark(\n\tBLOCK_ROWS.map((row, index) =>\n\t\tindex === BLOCK_ROWS.length - 1 ? { accent: row, text: \"\" } : { accent: \"\", text: row },\n\t),\n);\n\n/** 1 row x 11 columns. Used when the block mark will not fit. */\nexport const APEX_MARK_INLINE = mark([{ accent: \"◤\", text: \" apex code\" }]);\n\n/**\n * 1 row x 12 columns. Honours `terminal.symbolPreset: \"ascii\"`, whose users\n * opted out of block drawing because it renders badly for them. The previous\n * ASCII mark was line-drawn art whose first letter was an O, so it read \"OPEX\".\n */\nexport const APEX_MARK_INLINE_ASCII = mark([{ accent: \"/\\\\\", text: \" apex code\" }]);\n\n/** Pick the widest mark that fits. ASCII users always get the type lockup. */\nexport function selectBrandMark(contentWidth: number, symbolPreset: MarkSymbolPreset): BrandMark {\n\tif (symbolPreset === \"ascii\") return APEX_MARK_INLINE_ASCII;\n\treturn contentWidth >= APEX_MARK_BLOCK.width ? APEX_MARK_BLOCK : APEX_MARK_INLINE;\n}\n\n/** Wrap a mark supplied by a rebranding extension. It carries no accent run. */\nexport function customBrandMark(logo: string): BrandMark {\n\treturn mark(logo.split(\"\\n\").map((row) => ({ accent: \"\", text: row })));\n}\n"]}
|
|
@@ -58,5 +58,14 @@ export declare function resolveHostToolBinary(tool: "fd" | "rg", options?: {
|
|
|
58
58
|
* workspace, and it happens on the side of the boundary that still has network access.
|
|
59
59
|
*/
|
|
60
60
|
export declare function prepareHostToolBinaries(silent?: boolean): Promise<HostToolBinary[]>;
|
|
61
|
-
export
|
|
61
|
+
export interface ToolStatus {
|
|
62
|
+
type: "info" | "warning";
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Ensure a tool is available, downloading if necessary.
|
|
67
|
+
* Reports progress through `onStatus`; status messages are otherwise silent.
|
|
68
|
+
* Returns the tool path, or undefined if unavailable.
|
|
69
|
+
*/
|
|
70
|
+
export declare function ensureTool(tool: "fd" | "rg", onStatus?: (status: ToolStatus) => void): Promise<string | undefined>;
|
|
62
71
|
//# sourceMappingURL=tools-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAqEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAwBvG;AA6GD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;CAC9B;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CAetB;AAmJD,MAAM,WAAW,gCAAgC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1G;AAmCD,MAAM,WAAW,cAAc;IAC9B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACb;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACvF,MAAM,GAAG,SAAS,CA0BpB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhG;AAUD,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2CxG","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent);\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAoEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAwBvG;AA6GD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;CAC9B;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CAetB;AAmJD,MAAM,WAAW,gCAAgC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1G;AAmCD,MAAM,WAAW,cAAc;IAC9B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACb;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACvF,MAAM,GAAG,SAAS,CA0BpB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhG;AAQD,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoC7B","sourcesContent":["import { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\nexport interface ToolStatus {\n\ttype: \"info\" | \"warning\";\n\tmessage: string;\n}\n\n/**\n * Ensure a tool is available, downloading if necessary.\n * Reports progress through `onStatus`; status messages are otherwise silent.\n * Returns the tool path, or undefined if unavailable.\n */\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tonStatus?: (status: ToolStatus) => void,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Offline mode enabled, skipping download.` });\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Install with: pkg install ${pkgName}` });\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tonStatus?.({ type: \"info\", message: `${config.name} not found. Downloading...` });\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tonStatus?.({ type: \"info\", message: `${config.name} installed to ${path}` });\n\t\treturn path;\n\t} catch (e) {\n\t\tonStatus?.({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { spawnSync } from "child_process";
|
|
3
2
|
import { createHash } from "crypto";
|
|
4
3
|
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, } from "fs";
|
|
@@ -428,7 +427,7 @@ export function resolveHostToolBinary(tool, options = {}) {
|
|
|
428
427
|
export async function prepareHostToolBinaries(silent = false) {
|
|
429
428
|
const binaries = [];
|
|
430
429
|
for (const tool of ["fd", "rg"]) {
|
|
431
|
-
await ensureTool(tool, silent);
|
|
430
|
+
await ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));
|
|
432
431
|
const hostPath = resolveHostToolBinary(tool);
|
|
433
432
|
if (hostPath) {
|
|
434
433
|
binaries.push({ name: TOOLS[tool].binaryName + (platform() === "win32" ? ".exe" : ""), path: hostPath });
|
|
@@ -441,9 +440,12 @@ const TERMUX_PACKAGES = {
|
|
|
441
440
|
fd: "fd",
|
|
442
441
|
rg: "ripgrep",
|
|
443
442
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Ensure a tool is available, downloading if necessary.
|
|
445
|
+
* Reports progress through `onStatus`; status messages are otherwise silent.
|
|
446
|
+
* Returns the tool path, or undefined if unavailable.
|
|
447
|
+
*/
|
|
448
|
+
export async function ensureTool(tool, onStatus) {
|
|
447
449
|
const existingPath = getToolPath(tool);
|
|
448
450
|
if (existingPath) {
|
|
449
451
|
return existingPath;
|
|
@@ -452,35 +454,28 @@ export async function ensureTool(tool, silent = false) {
|
|
|
452
454
|
if (!config)
|
|
453
455
|
return undefined;
|
|
454
456
|
if (isOfflineModeEnabled()) {
|
|
455
|
-
|
|
456
|
-
console.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));
|
|
457
|
-
}
|
|
457
|
+
onStatus?.({ type: "warning", message: `${config.name} not found. Offline mode enabled, skipping download.` });
|
|
458
458
|
return undefined;
|
|
459
459
|
}
|
|
460
460
|
// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.
|
|
461
461
|
// Users must install via pkg.
|
|
462
462
|
if (platform() === "android") {
|
|
463
463
|
const pkgName = TERMUX_PACKAGES[tool] ?? tool;
|
|
464
|
-
|
|
465
|
-
console.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));
|
|
466
|
-
}
|
|
464
|
+
onStatus?.({ type: "warning", message: `${config.name} not found. Install with: pkg install ${pkgName}` });
|
|
467
465
|
return undefined;
|
|
468
466
|
}
|
|
469
467
|
// Tool not found - download it
|
|
470
|
-
|
|
471
|
-
console.log(chalk.dim(`${config.name} not found. Downloading...`));
|
|
472
|
-
}
|
|
468
|
+
onStatus?.({ type: "info", message: `${config.name} not found. Downloading...` });
|
|
473
469
|
try {
|
|
474
470
|
const path = await downloadTool(tool);
|
|
475
|
-
|
|
476
|
-
console.log(chalk.dim(`${config.name} installed to ${path}`));
|
|
477
|
-
}
|
|
471
|
+
onStatus?.({ type: "info", message: `${config.name} installed to ${path}` });
|
|
478
472
|
return path;
|
|
479
473
|
}
|
|
480
474
|
catch (e) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
475
|
+
onStatus?.({
|
|
476
|
+
type: "warning",
|
|
477
|
+
message: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,
|
|
478
|
+
});
|
|
484
479
|
return undefined;
|
|
485
480
|
}
|
|
486
481
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent);\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\nexport interface ToolStatus {\n\ttype: \"info\" | \"warning\";\n\tmessage: string;\n}\n\n/**\n * Ensure a tool is available, downloading if necessary.\n * Reports progress through `onStatus`; status messages are otherwise silent.\n * Returns the tool path, or undefined if unavailable.\n */\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tonStatus?: (status: ToolStatus) => void,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Offline mode enabled, skipping download.` });\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Install with: pkg install ${pkgName}` });\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tonStatus?.({ type: \"info\", message: `${config.name} not found. Downloading...` });\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tonStatus?.({ type: \"info\", message: `${config.name} installed to ${path}` });\n\t\treturn path;\n\t} catch (e) {\n\t\tonStatus?.({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n"]}
|