@leanandmean/coding-agent 0.74.1-scramjet.6 → 0.74.1-scramjet.60
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 +32 -0
- package/LICENSE +202 -0
- package/README.md +21 -9
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/initial-message.d.ts +1 -0
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js +3 -0
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -0
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +4 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +33 -22
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +12 -3
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +64 -38
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +2 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +86 -16
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +865 -181
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +21 -17
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +3 -5
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +43 -10
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/exec.d.ts +12 -0
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +74 -28
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/template.js +4 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +7 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +9 -5
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +162 -3
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +110 -6
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +8 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/model-registry.d.ts +4 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +179 -28
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +24 -6
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-throughput-history.d.ts +25 -0
- package/dist/core/output-throughput-history.d.ts.map +1 -0
- package/dist/core/output-throughput-history.js +240 -0
- package/dist/core/output-throughput-history.js.map +1 -0
- package/dist/core/output-throughput.d.ts +43 -0
- package/dist/core/output-throughput.d.ts.map +1 -0
- package/dist/core/output-throughput.js +161 -0
- package/dist/core/output-throughput.js.map +1 -0
- package/dist/core/resource-loader.d.ts +6 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +76 -21
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/scramjet-command-parser.d.ts +4 -0
- package/dist/core/scramjet-command-parser.d.ts.map +1 -1
- package/dist/core/scramjet-command-parser.js +72 -1
- package/dist/core/scramjet-command-parser.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +61 -10
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +11 -3
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +46 -11
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +18 -6
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +84 -16
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -2
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -2
- 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 +4 -3
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +16 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +38 -10
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/armin.d.ts +4 -2
- package/dist/modes/interactive/components/armin.d.ts.map +1 -1
- package/dist/modes/interactive/components/armin.js +14 -8
- package/dist/modes/interactive/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +19 -8
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +4 -1
- package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js +14 -6
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +5 -3
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +4 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +22 -18
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +3 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +9 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +3 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +8 -2
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +17 -3
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +87 -16
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +2 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +19 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +129 -36
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +3 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js +2 -2
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +37 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +876 -175
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/spellcheck.d.ts +26 -0
- package/dist/modes/interactive/spellcheck.d.ts.map +1 -0
- package/dist/modes/interactive/spellcheck.js +144 -0
- package/dist/modes/interactive/spellcheck.js.map +1 -0
- package/dist/modes/interactive/theme/{dark.json → pi-dark.json} +3 -2
- package/dist/modes/interactive/theme/{light.json → pi-light.json} +27 -26
- package/dist/modes/interactive/theme/theme-schema.json +6 -1
- package/dist/modes/interactive/theme/theme.d.ts +24 -4
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +123 -22
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +5 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +6 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +33 -14
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +6 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +278 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +7 -6
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +17 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +88 -21
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +9 -3
- package/docs/custom-provider.md +13 -4
- package/docs/extensions.md +139 -21
- package/docs/keybindings.md +25 -1
- package/docs/models.md +34 -2
- package/docs/packages.md +2 -0
- package/docs/prompt-templates.md +2 -0
- package/docs/quickstart.md +1 -4
- package/docs/rpc.md +8 -6
- package/docs/sdk.md +22 -3
- package/docs/session-format.md +2 -1
- package/docs/settings.md +13 -3
- package/docs/skills.md +7 -5
- package/docs/terminal-setup.md +79 -2
- package/docs/terminal-verification.md +71 -0
- package/docs/themes.md +63 -10
- package/docs/tui.md +120 -25
- package/docs/usage.md +6 -4
- package/examples/extensions/border-status-editor.ts +2 -3
- package/examples/extensions/doom-overlay/doom-component.ts +3 -3
- package/examples/extensions/mac-system-theme.ts +3 -2
- package/examples/extensions/modal-editor.ts +13 -8
- package/examples/extensions/overlay-qa-tests.ts +22 -9
- package/examples/extensions/overlay-test.ts +5 -3
- package/examples/extensions/snake.ts +3 -1
- package/examples/extensions/space-invaders.ts +6 -3
- package/examples/extensions/subagent/index.ts +20 -17
- package/package.json +69 -58
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAkCA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FjE","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\nimport { clipboard } from \"./clipboard-native.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\t\t\tproc.unref();\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAoCA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CA0DzD;AAGD,wBAAgB,wBAAwB;;;;cA2LvC;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiHjE","sourcesContent":["import { execFile, execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { waitForChildProcess } from \"./child-process.js\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\nimport { clipboard } from \"./clipboard-native.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\n// SCRAMJET-DIVERGENCE: mouse reporting replaces the terminal's editor right-click paste.\nexport async function readClipboardText(): Promise<string> {\n\tif (isRemoteSession()) throw new Error(\"Use your terminal's Paste command in remote sessions\");\n\tconst p = platform();\n\tconst wsl = p === \"linux\" && Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n\tif (clipboard && !wsl && p !== \"linux\") {\n\t\ttry {\n\t\t\treturn await clipboard.getText();\n\t\t} catch {\n\t\t\t// The platform command remains available if the optional addon fails.\n\t\t}\n\t}\n\tconst commands: [string, string[]][] =\n\t\twsl || p === \"win32\"\n\t\t\t? [\n\t\t\t\t\t[\n\t\t\t\t\t\t\"powershell.exe\",\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\t\t\"-STA\",\n\t\t\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\t\t\"Add-Type -AssemblyName System.Windows.Forms; [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::Write([System.Windows.Forms.Clipboard]::GetText())\",\n\t\t\t\t\t\t],\n\t\t\t\t\t],\n\t\t\t\t]\n\t\t\t: p === \"darwin\"\n\t\t\t\t? [[\"pbpaste\", []]]\n\t\t\t\t: process.env.TERMUX_VERSION\n\t\t\t\t\t? [[\"termux-clipboard-get\", []]]\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t...(process.env.WAYLAND_DISPLAY\n\t\t\t\t\t\t\t\t? [[\"wl-paste\", [\"--no-newline\", \"--type\", \"text\"]] as [string, string[]]]\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t\t...(process.env.DISPLAY\n\t\t\t\t\t\t\t\t? ([\n\t\t\t\t\t\t\t\t\t\t[\"xclip\", [\"-selection\", \"clipboard\", \"-o\"]],\n\t\t\t\t\t\t\t\t\t\t[\"xsel\", [\"--clipboard\", \"--output\"]],\n\t\t\t\t\t\t\t\t\t] as [string, string[]][])\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t];\n\tfor (const [command, args] of commands) {\n\t\ttry {\n\t\t\treturn await new Promise<string>((resolve, reject) => {\n\t\t\t\texecFile(\n\t\t\t\t\tcommand,\n\t\t\t\t\targs,\n\t\t\t\t\t{ encoding: \"utf8\", timeout: 5000, maxBuffer: 10 * 1024 * 1024, killSignal: \"SIGKILL\" },\n\t\t\t\t\t(error, stdout) => {\n\t\t\t\t\t\tif (error) reject(error);\n\t\t\t\t\t\telse resolve(stdout);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t} catch {\n\t\t\t// Try the next local backend without requesting remote clipboard access.\n\t\t}\n\t}\n\tthrow new Error(\"Cannot read clipboard text; use your terminal's Paste command\");\n}\n\n// SCRAMJET-DIVERGENCE: prewarm WSL interop without polling or caching clipboard contents.\nexport function createWslClipboardReader() {\n\tif (platform() !== \"linux\" || !(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) || isRemoteSession())\n\t\treturn undefined;\n\tconst maxBytes = 10 * 1024 * 1024;\n\tconst maxWireBytes = 4 * Math.ceil(maxBytes / 3) + 6;\n\tconst script = `\n$ErrorActionPreference = 'Stop'\nAdd-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class ScramjetClipboard {\n [DllImport(\"user32.dll\")] static extern bool OpenClipboard(IntPtr owner);\n [DllImport(\"user32.dll\")] static extern bool CloseClipboard();\n [DllImport(\"user32.dll\")] static extern bool IsClipboardFormatAvailable(uint format);\n [DllImport(\"user32.dll\")] static extern IntPtr GetClipboardData(uint format);\n [DllImport(\"kernel32.dll\")] static extern IntPtr GlobalLock(IntPtr handle);\n [DllImport(\"kernel32.dll\")] static extern bool GlobalUnlock(IntPtr handle);\n [DllImport(\"kernel32.dll\")] static extern UIntPtr GlobalSize(IntPtr handle);\n public static string ReadText() {\n for (int attempt = 0; !OpenClipboard(IntPtr.Zero); attempt++) {\n if (attempt == 9) throw new InvalidOperationException(\"Clipboard unavailable\");\n System.Threading.Thread.Sleep(5);\n }\n try {\n if (!IsClipboardFormatAvailable(13)) return \"\";\n IntPtr handle = GetClipboardData(13);\n if (handle == IntPtr.Zero) throw new InvalidOperationException(\"Clipboard data unavailable\");\n ulong size = GlobalSize(handle).ToUInt64();\n if (size < 2 || size > ${maxBytes * 2 + 2} || size % 2 != 0) throw new InvalidOperationException(\"Invalid clipboard size\");\n IntPtr data = GlobalLock(handle);\n if (data == IntPtr.Zero) throw new InvalidOperationException(\"Clipboard data unavailable\");\n try {\n string value = Marshal.PtrToStringUni(data, (int)(size / 2));\n int end = value.IndexOf('\\\\0');\n if (end < 0) throw new InvalidOperationException(\"Unterminated clipboard text\");\n return value.Substring(0, end);\n } finally { GlobalUnlock(handle); }\n } finally { CloseClipboard(); }\n }\n}\n'@\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n[Console]::Out.WriteLine('READY'); [Console]::Out.Flush()\nwhile ($null -ne ($request = [Console]::In.ReadLine())) {\n if ($request -ne 'read') { break }\n try {\n $text = [ScramjetClipboard]::ReadText()\n if ([System.Text.Encoding]::UTF8.GetByteCount($text) -gt ${maxBytes}) {\n [Console]::Out.WriteLine('ERR:size')\n } else {\n $bytes = [System.Text.Encoding]::UTF8.GetBytes($text)\n [Console]::Out.WriteLine('OK:' + [Convert]::ToBase64String($bytes))\n }\n } catch { [Console]::Out.WriteLine('ERR:read') }\n finally { $text = $null; $bytes = $null }\n [Console]::Out.Flush()\n}`;\n\tlet worker: ReturnType<typeof launch> | undefined;\n\tfunction launch() {\n\t\tconst child = spawn(\n\t\t\t\"powershell.exe\",\n\t\t\t[\n\t\t\t\t\"-NoLogo\",\n\t\t\t\t\"-NoProfile\",\n\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\"-STA\",\n\t\t\t\t\"-EncodedCommand\",\n\t\t\t\tBuffer.from(script, \"utf16le\").toString(\"base64\"),\n\t\t\t],\n\t\t\t{ stdio: [\"pipe\", \"pipe\", \"ignore\"] },\n\t\t);\n\t\tlet resolveReady!: () => void;\n\t\tlet rejectReady!: (error: Error) => void;\n\t\tconst ready = new Promise<void>((resolve, reject) => {\n\t\t\tresolveReady = resolve;\n\t\t\trejectReady = reject;\n\t\t});\n\t\tlet initialized = false;\n\t\tlet closed = false;\n\t\tlet buffer = \"\";\n\t\tlet pending: { resolve(text: string): void; reject(error: Error): void; promise: Promise<string> } | undefined;\n\t\tconst kill = () => {\n\t\t\tchild.kill(\"SIGKILL\");\n\t\t};\n\t\tconst fail = (error = new Error(\"Clipboard reader stopped; use your terminal's Paste command\")) => {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tclearTimeout(timer);\n\t\t\tbuffer = \"\";\n\t\t\trejectReady(error);\n\t\t\tpending?.reject(error);\n\t\t\tpending = undefined;\n\t\t\tif (worker === instance) worker = undefined;\n\t\t\tkill();\n\t\t};\n\t\tlet timer = setTimeout(\n\t\t\t() => fail(new Error(\"Clipboard reader startup timed out; use your terminal's Paste command\")),\n\t\t\t5000,\n\t\t);\n\t\tconst instance = {\n\t\t\tready,\n\t\t\tclose: fail,\n\t\t\tread(): Promise<string> {\n\t\t\t\tif (isRemoteSession())\n\t\t\t\t\treturn Promise.reject(new Error(\"Use your terminal's Paste command in remote sessions\"));\n\t\t\t\tif (closed || !initialized) return Promise.reject(new Error(\"Clipboard reader is not ready\"));\n\t\t\t\tif (pending) return pending.promise;\n\t\t\t\tlet resolve!: (text: string) => void;\n\t\t\t\tlet reject!: (error: Error) => void;\n\t\t\t\tconst promise = new Promise<string>((done, error) => {\n\t\t\t\t\tresolve = done;\n\t\t\t\t\treject = error;\n\t\t\t\t});\n\t\t\t\tpending = { resolve, reject, promise };\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => fail(new Error(\"Clipboard read timed out; use your terminal's Paste command\")),\n\t\t\t\t\t5000,\n\t\t\t\t);\n\t\t\t\tchild.stdin.write(\"read\\n\", (error) => {\n\t\t\t\t\tif (error) fail(new Error(\"Clipboard reader input failed\"));\n\t\t\t\t});\n\t\t\t\treturn promise;\n\t\t\t},\n\t\t};\n\t\tprocess.once(\"exit\", kill);\n\t\tchild.once(\"spawn\", () => {\n\t\t\tif (closed) kill();\n\t\t});\n\t\tvoid waitForChildProcess(child)\n\t\t\t.then(\n\t\t\t\t() => fail(),\n\t\t\t\t() => fail(new Error(\"Cannot start clipboard reader; use your terminal's Paste command\")),\n\t\t\t)\n\t\t\t.finally(() => process.off(\"exit\", kill));\n\t\tchild.stdin.on(\"error\", () => fail(new Error(\"Clipboard reader input failed\")));\n\t\tchild.stdout.on(\"error\", () => fail(new Error(\"Clipboard reader output failed\")));\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tif (closed) return;\n\t\t\tif (buffer.length + chunk.length > (initialized ? maxWireBytes : 7)) {\n\t\t\t\tfail(new Error(\"Clipboard response exceeded its size limit\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbuffer += chunk.toString(\"utf8\");\n\t\t\tif (!buffer.includes(\"\\n\")) return;\n\t\t\tconst response = buffer;\n\t\t\tbuffer = \"\";\n\t\t\tif (!initialized && /^READY\\r?\\n$/.test(response)) {\n\t\t\t\tinitialized = true;\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolveReady();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst match = /^OK:([A-Za-z0-9+/]*={0,2})\\r?\\n$/.exec(response);\n\t\t\tif (!pending || !match || match[1].length % 4 !== 0) {\n\t\t\t\tfail(new Error(\"Cannot read clipboard text; use your terminal's Paste command\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst bytes = Buffer.from(match[1], \"base64\");\n\t\t\tif (bytes.length > maxBytes || bytes.toString(\"base64\") !== match[1]) {\n\t\t\t\tfail(new Error(\"Invalid or oversized clipboard response\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\tconst request = pending;\n\t\t\tpending = undefined;\n\t\t\trequest.resolve(bytes.toString(\"utf8\"));\n\t\t});\n\t\treturn instance;\n\t}\n\treturn {\n\t\tstart(): Promise<void> {\n\t\t\tif (isRemoteSession())\n\t\t\t\treturn Promise.reject(new Error(\"Use your terminal's Paste command in remote sessions\"));\n\t\t\ttry {\n\t\t\t\tworker ??= launch();\n\t\t\t\treturn worker.ready;\n\t\t\t} catch {\n\t\t\t\treturn Promise.reject(new Error(\"Cannot start clipboard reader; use your terminal's Paste command\"));\n\t\t\t}\n\t\t},\n\t\tread(): Promise<string> {\n\t\t\treturn worker ? worker.read() : Promise.reject(new Error(\"Clipboard reader stopped; try pasting again\"));\n\t\t},\n\t\tclose(): void {\n\t\t\tworker?.close();\n\t\t},\n\t};\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// SCRAMJET-DIVERGENCE: await backend acceptance without execSync's daemon/fork hang.\n\t\t\t\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], {\n\t\t\t\t\t\t\t\t\tstdio: [\"pipe\", \"ignore\", \"ignore\"],\n\t\t\t\t\t\t\t\t\ttimeout: options.timeout,\n\t\t\t\t\t\t\t\t\tkillSignal: \"SIGKILL\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tlet inputFinished = false;\n\t\t\t\t\t\t\t\tlet exited = false;\n\t\t\t\t\t\t\t\tproc.on(\"error\", reject);\n\t\t\t\t\t\t\t\tproc.stdin.on(\"error\", (error) => {\n\t\t\t\t\t\t\t\t\tproc.kill(\"SIGKILL\");\n\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tproc.once(\"exit\", (code) => {\n\t\t\t\t\t\t\t\t\tif (code !== 0) {\n\t\t\t\t\t\t\t\t\t\treject(new Error(\"wl-copy did not accept clipboard content\"));\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\texited = true;\n\t\t\t\t\t\t\t\t\tif (inputFinished) resolve();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tproc.stdin.end(text, (error?: Error | null) => {\n\t\t\t\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tinputFinished = true;\n\t\t\t\t\t\t\t\t\tif (exited) resolve();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
package/dist/utils/clipboard.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { execSync, spawn } from "child_process";
|
|
1
|
+
import { execFile, execSync, spawn } from "child_process";
|
|
2
2
|
import { platform } from "os";
|
|
3
|
+
import { waitForChildProcess } from "./child-process.js";
|
|
3
4
|
import { isWaylandSession } from "./clipboard-image.js";
|
|
4
5
|
import { clipboard } from "./clipboard-native.js";
|
|
5
6
|
function copyToX11Clipboard(options) {
|
|
@@ -22,6 +23,250 @@ function emitOsc52(text) {
|
|
|
22
23
|
process.stdout.write(`\x1b]52;c;${encoded}\x07`);
|
|
23
24
|
return true;
|
|
24
25
|
}
|
|
26
|
+
// SCRAMJET-DIVERGENCE: mouse reporting replaces the terminal's editor right-click paste.
|
|
27
|
+
export async function readClipboardText() {
|
|
28
|
+
if (isRemoteSession())
|
|
29
|
+
throw new Error("Use your terminal's Paste command in remote sessions");
|
|
30
|
+
const p = platform();
|
|
31
|
+
const wsl = p === "linux" && Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);
|
|
32
|
+
if (clipboard && !wsl && p !== "linux") {
|
|
33
|
+
try {
|
|
34
|
+
return await clipboard.getText();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// The platform command remains available if the optional addon fails.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const commands = wsl || p === "win32"
|
|
41
|
+
? [
|
|
42
|
+
[
|
|
43
|
+
"powershell.exe",
|
|
44
|
+
[
|
|
45
|
+
"-NoProfile",
|
|
46
|
+
"-NonInteractive",
|
|
47
|
+
"-STA",
|
|
48
|
+
"-Command",
|
|
49
|
+
"Add-Type -AssemblyName System.Windows.Forms; [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::Write([System.Windows.Forms.Clipboard]::GetText())",
|
|
50
|
+
],
|
|
51
|
+
],
|
|
52
|
+
]
|
|
53
|
+
: p === "darwin"
|
|
54
|
+
? [["pbpaste", []]]
|
|
55
|
+
: process.env.TERMUX_VERSION
|
|
56
|
+
? [["termux-clipboard-get", []]]
|
|
57
|
+
: [
|
|
58
|
+
...(process.env.WAYLAND_DISPLAY
|
|
59
|
+
? [["wl-paste", ["--no-newline", "--type", "text"]]]
|
|
60
|
+
: []),
|
|
61
|
+
...(process.env.DISPLAY
|
|
62
|
+
? [
|
|
63
|
+
["xclip", ["-selection", "clipboard", "-o"]],
|
|
64
|
+
["xsel", ["--clipboard", "--output"]],
|
|
65
|
+
]
|
|
66
|
+
: []),
|
|
67
|
+
];
|
|
68
|
+
for (const [command, args] of commands) {
|
|
69
|
+
try {
|
|
70
|
+
return await new Promise((resolve, reject) => {
|
|
71
|
+
execFile(command, args, { encoding: "utf8", timeout: 5000, maxBuffer: 10 * 1024 * 1024, killSignal: "SIGKILL" }, (error, stdout) => {
|
|
72
|
+
if (error)
|
|
73
|
+
reject(error);
|
|
74
|
+
else
|
|
75
|
+
resolve(stdout);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Try the next local backend without requesting remote clipboard access.
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error("Cannot read clipboard text; use your terminal's Paste command");
|
|
84
|
+
}
|
|
85
|
+
// SCRAMJET-DIVERGENCE: prewarm WSL interop without polling or caching clipboard contents.
|
|
86
|
+
export function createWslClipboardReader() {
|
|
87
|
+
if (platform() !== "linux" || !(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) || isRemoteSession())
|
|
88
|
+
return undefined;
|
|
89
|
+
const maxBytes = 10 * 1024 * 1024;
|
|
90
|
+
const maxWireBytes = 4 * Math.ceil(maxBytes / 3) + 6;
|
|
91
|
+
const script = `
|
|
92
|
+
$ErrorActionPreference = 'Stop'
|
|
93
|
+
Add-Type @'
|
|
94
|
+
using System;
|
|
95
|
+
using System.Runtime.InteropServices;
|
|
96
|
+
public static class ScramjetClipboard {
|
|
97
|
+
[DllImport("user32.dll")] static extern bool OpenClipboard(IntPtr owner);
|
|
98
|
+
[DllImport("user32.dll")] static extern bool CloseClipboard();
|
|
99
|
+
[DllImport("user32.dll")] static extern bool IsClipboardFormatAvailable(uint format);
|
|
100
|
+
[DllImport("user32.dll")] static extern IntPtr GetClipboardData(uint format);
|
|
101
|
+
[DllImport("kernel32.dll")] static extern IntPtr GlobalLock(IntPtr handle);
|
|
102
|
+
[DllImport("kernel32.dll")] static extern bool GlobalUnlock(IntPtr handle);
|
|
103
|
+
[DllImport("kernel32.dll")] static extern UIntPtr GlobalSize(IntPtr handle);
|
|
104
|
+
public static string ReadText() {
|
|
105
|
+
for (int attempt = 0; !OpenClipboard(IntPtr.Zero); attempt++) {
|
|
106
|
+
if (attempt == 9) throw new InvalidOperationException("Clipboard unavailable");
|
|
107
|
+
System.Threading.Thread.Sleep(5);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
if (!IsClipboardFormatAvailable(13)) return "";
|
|
111
|
+
IntPtr handle = GetClipboardData(13);
|
|
112
|
+
if (handle == IntPtr.Zero) throw new InvalidOperationException("Clipboard data unavailable");
|
|
113
|
+
ulong size = GlobalSize(handle).ToUInt64();
|
|
114
|
+
if (size < 2 || size > ${maxBytes * 2 + 2} || size % 2 != 0) throw new InvalidOperationException("Invalid clipboard size");
|
|
115
|
+
IntPtr data = GlobalLock(handle);
|
|
116
|
+
if (data == IntPtr.Zero) throw new InvalidOperationException("Clipboard data unavailable");
|
|
117
|
+
try {
|
|
118
|
+
string value = Marshal.PtrToStringUni(data, (int)(size / 2));
|
|
119
|
+
int end = value.IndexOf('\\0');
|
|
120
|
+
if (end < 0) throw new InvalidOperationException("Unterminated clipboard text");
|
|
121
|
+
return value.Substring(0, end);
|
|
122
|
+
} finally { GlobalUnlock(handle); }
|
|
123
|
+
} finally { CloseClipboard(); }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
'@
|
|
127
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
128
|
+
[Console]::Out.WriteLine('READY'); [Console]::Out.Flush()
|
|
129
|
+
while ($null -ne ($request = [Console]::In.ReadLine())) {
|
|
130
|
+
if ($request -ne 'read') { break }
|
|
131
|
+
try {
|
|
132
|
+
$text = [ScramjetClipboard]::ReadText()
|
|
133
|
+
if ([System.Text.Encoding]::UTF8.GetByteCount($text) -gt ${maxBytes}) {
|
|
134
|
+
[Console]::Out.WriteLine('ERR:size')
|
|
135
|
+
} else {
|
|
136
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
|
|
137
|
+
[Console]::Out.WriteLine('OK:' + [Convert]::ToBase64String($bytes))
|
|
138
|
+
}
|
|
139
|
+
} catch { [Console]::Out.WriteLine('ERR:read') }
|
|
140
|
+
finally { $text = $null; $bytes = $null }
|
|
141
|
+
[Console]::Out.Flush()
|
|
142
|
+
}`;
|
|
143
|
+
let worker;
|
|
144
|
+
function launch() {
|
|
145
|
+
const child = spawn("powershell.exe", [
|
|
146
|
+
"-NoLogo",
|
|
147
|
+
"-NoProfile",
|
|
148
|
+
"-NonInteractive",
|
|
149
|
+
"-STA",
|
|
150
|
+
"-EncodedCommand",
|
|
151
|
+
Buffer.from(script, "utf16le").toString("base64"),
|
|
152
|
+
], { stdio: ["pipe", "pipe", "ignore"] });
|
|
153
|
+
let resolveReady;
|
|
154
|
+
let rejectReady;
|
|
155
|
+
const ready = new Promise((resolve, reject) => {
|
|
156
|
+
resolveReady = resolve;
|
|
157
|
+
rejectReady = reject;
|
|
158
|
+
});
|
|
159
|
+
let initialized = false;
|
|
160
|
+
let closed = false;
|
|
161
|
+
let buffer = "";
|
|
162
|
+
let pending;
|
|
163
|
+
const kill = () => {
|
|
164
|
+
child.kill("SIGKILL");
|
|
165
|
+
};
|
|
166
|
+
const fail = (error = new Error("Clipboard reader stopped; use your terminal's Paste command")) => {
|
|
167
|
+
if (closed)
|
|
168
|
+
return;
|
|
169
|
+
closed = true;
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
buffer = "";
|
|
172
|
+
rejectReady(error);
|
|
173
|
+
pending?.reject(error);
|
|
174
|
+
pending = undefined;
|
|
175
|
+
if (worker === instance)
|
|
176
|
+
worker = undefined;
|
|
177
|
+
kill();
|
|
178
|
+
};
|
|
179
|
+
let timer = setTimeout(() => fail(new Error("Clipboard reader startup timed out; use your terminal's Paste command")), 5000);
|
|
180
|
+
const instance = {
|
|
181
|
+
ready,
|
|
182
|
+
close: fail,
|
|
183
|
+
read() {
|
|
184
|
+
if (isRemoteSession())
|
|
185
|
+
return Promise.reject(new Error("Use your terminal's Paste command in remote sessions"));
|
|
186
|
+
if (closed || !initialized)
|
|
187
|
+
return Promise.reject(new Error("Clipboard reader is not ready"));
|
|
188
|
+
if (pending)
|
|
189
|
+
return pending.promise;
|
|
190
|
+
let resolve;
|
|
191
|
+
let reject;
|
|
192
|
+
const promise = new Promise((done, error) => {
|
|
193
|
+
resolve = done;
|
|
194
|
+
reject = error;
|
|
195
|
+
});
|
|
196
|
+
pending = { resolve, reject, promise };
|
|
197
|
+
timer = setTimeout(() => fail(new Error("Clipboard read timed out; use your terminal's Paste command")), 5000);
|
|
198
|
+
child.stdin.write("read\n", (error) => {
|
|
199
|
+
if (error)
|
|
200
|
+
fail(new Error("Clipboard reader input failed"));
|
|
201
|
+
});
|
|
202
|
+
return promise;
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
process.once("exit", kill);
|
|
206
|
+
child.once("spawn", () => {
|
|
207
|
+
if (closed)
|
|
208
|
+
kill();
|
|
209
|
+
});
|
|
210
|
+
void waitForChildProcess(child)
|
|
211
|
+
.then(() => fail(), () => fail(new Error("Cannot start clipboard reader; use your terminal's Paste command")))
|
|
212
|
+
.finally(() => process.off("exit", kill));
|
|
213
|
+
child.stdin.on("error", () => fail(new Error("Clipboard reader input failed")));
|
|
214
|
+
child.stdout.on("error", () => fail(new Error("Clipboard reader output failed")));
|
|
215
|
+
child.stdout.on("data", (chunk) => {
|
|
216
|
+
if (closed)
|
|
217
|
+
return;
|
|
218
|
+
if (buffer.length + chunk.length > (initialized ? maxWireBytes : 7)) {
|
|
219
|
+
fail(new Error("Clipboard response exceeded its size limit"));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
buffer += chunk.toString("utf8");
|
|
223
|
+
if (!buffer.includes("\n"))
|
|
224
|
+
return;
|
|
225
|
+
const response = buffer;
|
|
226
|
+
buffer = "";
|
|
227
|
+
if (!initialized && /^READY\r?\n$/.test(response)) {
|
|
228
|
+
initialized = true;
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
resolveReady();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const match = /^OK:([A-Za-z0-9+/]*={0,2})\r?\n$/.exec(response);
|
|
234
|
+
if (!pending || !match || match[1].length % 4 !== 0) {
|
|
235
|
+
fail(new Error("Cannot read clipboard text; use your terminal's Paste command"));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const bytes = Buffer.from(match[1], "base64");
|
|
239
|
+
if (bytes.length > maxBytes || bytes.toString("base64") !== match[1]) {
|
|
240
|
+
fail(new Error("Invalid or oversized clipboard response"));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
clearTimeout(timer);
|
|
244
|
+
const request = pending;
|
|
245
|
+
pending = undefined;
|
|
246
|
+
request.resolve(bytes.toString("utf8"));
|
|
247
|
+
});
|
|
248
|
+
return instance;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
start() {
|
|
252
|
+
if (isRemoteSession())
|
|
253
|
+
return Promise.reject(new Error("Use your terminal's Paste command in remote sessions"));
|
|
254
|
+
try {
|
|
255
|
+
worker ??= launch();
|
|
256
|
+
return worker.ready;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return Promise.reject(new Error("Cannot start clipboard reader; use your terminal's Paste command"));
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
read() {
|
|
263
|
+
return worker ? worker.read() : Promise.reject(new Error("Clipboard reader stopped; try pasting again"));
|
|
264
|
+
},
|
|
265
|
+
close() {
|
|
266
|
+
worker?.close();
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
25
270
|
export async function copyToClipboard(text) {
|
|
26
271
|
let copied = false;
|
|
27
272
|
const p = platform();
|
|
@@ -76,16 +321,39 @@ export async function copyToClipboard(text) {
|
|
|
76
321
|
const isWayland = isWaylandSession();
|
|
77
322
|
if (isWayland && hasWaylandDisplay) {
|
|
78
323
|
try {
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
324
|
+
// SCRAMJET-DIVERGENCE: await backend acceptance without execSync's daemon/fork hang.
|
|
325
|
+
await new Promise((resolve, reject) => {
|
|
326
|
+
const proc = spawn("wl-copy", [], {
|
|
327
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
328
|
+
timeout: options.timeout,
|
|
329
|
+
killSignal: "SIGKILL",
|
|
330
|
+
});
|
|
331
|
+
let inputFinished = false;
|
|
332
|
+
let exited = false;
|
|
333
|
+
proc.on("error", reject);
|
|
334
|
+
proc.stdin.on("error", (error) => {
|
|
335
|
+
proc.kill("SIGKILL");
|
|
336
|
+
reject(error);
|
|
337
|
+
});
|
|
338
|
+
proc.once("exit", (code) => {
|
|
339
|
+
if (code !== 0) {
|
|
340
|
+
reject(new Error("wl-copy did not accept clipboard content"));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
exited = true;
|
|
344
|
+
if (inputFinished)
|
|
345
|
+
resolve();
|
|
346
|
+
});
|
|
347
|
+
proc.stdin.end(text, (error) => {
|
|
348
|
+
if (error) {
|
|
349
|
+
reject(error);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
inputFinished = true;
|
|
353
|
+
if (exited)
|
|
354
|
+
resolve();
|
|
355
|
+
});
|
|
85
356
|
});
|
|
86
|
-
proc.stdin.write(text);
|
|
87
|
-
proc.stdin.end();
|
|
88
|
-
proc.unref();
|
|
89
357
|
copied = true;
|
|
90
358
|
}
|
|
91
359
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\nimport { clipboard } from \"./clipboard-native.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead\n\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"] });\n\t\t\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\t\t\tproc.unref();\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile, execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { waitForChildProcess } from \"./child-process.js\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\nimport { clipboard } from \"./clipboard-native.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nconst MAX_OSC52_ENCODED_LENGTH = 100_000;\n\nfunction isRemoteSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.SSH_CONNECTION || env.SSH_CLIENT || env.MOSH_CONNECTION);\n}\n\nfunction emitOsc52(text: string): boolean {\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tif (encoded.length > MAX_OSC52_ENCODED_LENGTH) {\n\t\treturn false;\n\t}\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\treturn true;\n}\n\n// SCRAMJET-DIVERGENCE: mouse reporting replaces the terminal's editor right-click paste.\nexport async function readClipboardText(): Promise<string> {\n\tif (isRemoteSession()) throw new Error(\"Use your terminal's Paste command in remote sessions\");\n\tconst p = platform();\n\tconst wsl = p === \"linux\" && Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP);\n\tif (clipboard && !wsl && p !== \"linux\") {\n\t\ttry {\n\t\t\treturn await clipboard.getText();\n\t\t} catch {\n\t\t\t// The platform command remains available if the optional addon fails.\n\t\t}\n\t}\n\tconst commands: [string, string[]][] =\n\t\twsl || p === \"win32\"\n\t\t\t? [\n\t\t\t\t\t[\n\t\t\t\t\t\t\"powershell.exe\",\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\"-NoProfile\",\n\t\t\t\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\t\t\t\"-STA\",\n\t\t\t\t\t\t\t\"-Command\",\n\t\t\t\t\t\t\t\"Add-Type -AssemblyName System.Windows.Forms; [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); [Console]::Write([System.Windows.Forms.Clipboard]::GetText())\",\n\t\t\t\t\t\t],\n\t\t\t\t\t],\n\t\t\t\t]\n\t\t\t: p === \"darwin\"\n\t\t\t\t? [[\"pbpaste\", []]]\n\t\t\t\t: process.env.TERMUX_VERSION\n\t\t\t\t\t? [[\"termux-clipboard-get\", []]]\n\t\t\t\t\t: [\n\t\t\t\t\t\t\t...(process.env.WAYLAND_DISPLAY\n\t\t\t\t\t\t\t\t? [[\"wl-paste\", [\"--no-newline\", \"--type\", \"text\"]] as [string, string[]]]\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t\t...(process.env.DISPLAY\n\t\t\t\t\t\t\t\t? ([\n\t\t\t\t\t\t\t\t\t\t[\"xclip\", [\"-selection\", \"clipboard\", \"-o\"]],\n\t\t\t\t\t\t\t\t\t\t[\"xsel\", [\"--clipboard\", \"--output\"]],\n\t\t\t\t\t\t\t\t\t] as [string, string[]][])\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t];\n\tfor (const [command, args] of commands) {\n\t\ttry {\n\t\t\treturn await new Promise<string>((resolve, reject) => {\n\t\t\t\texecFile(\n\t\t\t\t\tcommand,\n\t\t\t\t\targs,\n\t\t\t\t\t{ encoding: \"utf8\", timeout: 5000, maxBuffer: 10 * 1024 * 1024, killSignal: \"SIGKILL\" },\n\t\t\t\t\t(error, stdout) => {\n\t\t\t\t\t\tif (error) reject(error);\n\t\t\t\t\t\telse resolve(stdout);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t});\n\t\t} catch {\n\t\t\t// Try the next local backend without requesting remote clipboard access.\n\t\t}\n\t}\n\tthrow new Error(\"Cannot read clipboard text; use your terminal's Paste command\");\n}\n\n// SCRAMJET-DIVERGENCE: prewarm WSL interop without polling or caching clipboard contents.\nexport function createWslClipboardReader() {\n\tif (platform() !== \"linux\" || !(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) || isRemoteSession())\n\t\treturn undefined;\n\tconst maxBytes = 10 * 1024 * 1024;\n\tconst maxWireBytes = 4 * Math.ceil(maxBytes / 3) + 6;\n\tconst script = `\n$ErrorActionPreference = 'Stop'\nAdd-Type @'\nusing System;\nusing System.Runtime.InteropServices;\npublic static class ScramjetClipboard {\n [DllImport(\"user32.dll\")] static extern bool OpenClipboard(IntPtr owner);\n [DllImport(\"user32.dll\")] static extern bool CloseClipboard();\n [DllImport(\"user32.dll\")] static extern bool IsClipboardFormatAvailable(uint format);\n [DllImport(\"user32.dll\")] static extern IntPtr GetClipboardData(uint format);\n [DllImport(\"kernel32.dll\")] static extern IntPtr GlobalLock(IntPtr handle);\n [DllImport(\"kernel32.dll\")] static extern bool GlobalUnlock(IntPtr handle);\n [DllImport(\"kernel32.dll\")] static extern UIntPtr GlobalSize(IntPtr handle);\n public static string ReadText() {\n for (int attempt = 0; !OpenClipboard(IntPtr.Zero); attempt++) {\n if (attempt == 9) throw new InvalidOperationException(\"Clipboard unavailable\");\n System.Threading.Thread.Sleep(5);\n }\n try {\n if (!IsClipboardFormatAvailable(13)) return \"\";\n IntPtr handle = GetClipboardData(13);\n if (handle == IntPtr.Zero) throw new InvalidOperationException(\"Clipboard data unavailable\");\n ulong size = GlobalSize(handle).ToUInt64();\n if (size < 2 || size > ${maxBytes * 2 + 2} || size % 2 != 0) throw new InvalidOperationException(\"Invalid clipboard size\");\n IntPtr data = GlobalLock(handle);\n if (data == IntPtr.Zero) throw new InvalidOperationException(\"Clipboard data unavailable\");\n try {\n string value = Marshal.PtrToStringUni(data, (int)(size / 2));\n int end = value.IndexOf('\\\\0');\n if (end < 0) throw new InvalidOperationException(\"Unterminated clipboard text\");\n return value.Substring(0, end);\n } finally { GlobalUnlock(handle); }\n } finally { CloseClipboard(); }\n }\n}\n'@\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n[Console]::Out.WriteLine('READY'); [Console]::Out.Flush()\nwhile ($null -ne ($request = [Console]::In.ReadLine())) {\n if ($request -ne 'read') { break }\n try {\n $text = [ScramjetClipboard]::ReadText()\n if ([System.Text.Encoding]::UTF8.GetByteCount($text) -gt ${maxBytes}) {\n [Console]::Out.WriteLine('ERR:size')\n } else {\n $bytes = [System.Text.Encoding]::UTF8.GetBytes($text)\n [Console]::Out.WriteLine('OK:' + [Convert]::ToBase64String($bytes))\n }\n } catch { [Console]::Out.WriteLine('ERR:read') }\n finally { $text = $null; $bytes = $null }\n [Console]::Out.Flush()\n}`;\n\tlet worker: ReturnType<typeof launch> | undefined;\n\tfunction launch() {\n\t\tconst child = spawn(\n\t\t\t\"powershell.exe\",\n\t\t\t[\n\t\t\t\t\"-NoLogo\",\n\t\t\t\t\"-NoProfile\",\n\t\t\t\t\"-NonInteractive\",\n\t\t\t\t\"-STA\",\n\t\t\t\t\"-EncodedCommand\",\n\t\t\t\tBuffer.from(script, \"utf16le\").toString(\"base64\"),\n\t\t\t],\n\t\t\t{ stdio: [\"pipe\", \"pipe\", \"ignore\"] },\n\t\t);\n\t\tlet resolveReady!: () => void;\n\t\tlet rejectReady!: (error: Error) => void;\n\t\tconst ready = new Promise<void>((resolve, reject) => {\n\t\t\tresolveReady = resolve;\n\t\t\trejectReady = reject;\n\t\t});\n\t\tlet initialized = false;\n\t\tlet closed = false;\n\t\tlet buffer = \"\";\n\t\tlet pending: { resolve(text: string): void; reject(error: Error): void; promise: Promise<string> } | undefined;\n\t\tconst kill = () => {\n\t\t\tchild.kill(\"SIGKILL\");\n\t\t};\n\t\tconst fail = (error = new Error(\"Clipboard reader stopped; use your terminal's Paste command\")) => {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tclearTimeout(timer);\n\t\t\tbuffer = \"\";\n\t\t\trejectReady(error);\n\t\t\tpending?.reject(error);\n\t\t\tpending = undefined;\n\t\t\tif (worker === instance) worker = undefined;\n\t\t\tkill();\n\t\t};\n\t\tlet timer = setTimeout(\n\t\t\t() => fail(new Error(\"Clipboard reader startup timed out; use your terminal's Paste command\")),\n\t\t\t5000,\n\t\t);\n\t\tconst instance = {\n\t\t\tready,\n\t\t\tclose: fail,\n\t\t\tread(): Promise<string> {\n\t\t\t\tif (isRemoteSession())\n\t\t\t\t\treturn Promise.reject(new Error(\"Use your terminal's Paste command in remote sessions\"));\n\t\t\t\tif (closed || !initialized) return Promise.reject(new Error(\"Clipboard reader is not ready\"));\n\t\t\t\tif (pending) return pending.promise;\n\t\t\t\tlet resolve!: (text: string) => void;\n\t\t\t\tlet reject!: (error: Error) => void;\n\t\t\t\tconst promise = new Promise<string>((done, error) => {\n\t\t\t\t\tresolve = done;\n\t\t\t\t\treject = error;\n\t\t\t\t});\n\t\t\t\tpending = { resolve, reject, promise };\n\t\t\t\ttimer = setTimeout(\n\t\t\t\t\t() => fail(new Error(\"Clipboard read timed out; use your terminal's Paste command\")),\n\t\t\t\t\t5000,\n\t\t\t\t);\n\t\t\t\tchild.stdin.write(\"read\\n\", (error) => {\n\t\t\t\t\tif (error) fail(new Error(\"Clipboard reader input failed\"));\n\t\t\t\t});\n\t\t\t\treturn promise;\n\t\t\t},\n\t\t};\n\t\tprocess.once(\"exit\", kill);\n\t\tchild.once(\"spawn\", () => {\n\t\t\tif (closed) kill();\n\t\t});\n\t\tvoid waitForChildProcess(child)\n\t\t\t.then(\n\t\t\t\t() => fail(),\n\t\t\t\t() => fail(new Error(\"Cannot start clipboard reader; use your terminal's Paste command\")),\n\t\t\t)\n\t\t\t.finally(() => process.off(\"exit\", kill));\n\t\tchild.stdin.on(\"error\", () => fail(new Error(\"Clipboard reader input failed\")));\n\t\tchild.stdout.on(\"error\", () => fail(new Error(\"Clipboard reader output failed\")));\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\tif (closed) return;\n\t\t\tif (buffer.length + chunk.length > (initialized ? maxWireBytes : 7)) {\n\t\t\t\tfail(new Error(\"Clipboard response exceeded its size limit\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbuffer += chunk.toString(\"utf8\");\n\t\t\tif (!buffer.includes(\"\\n\")) return;\n\t\t\tconst response = buffer;\n\t\t\tbuffer = \"\";\n\t\t\tif (!initialized && /^READY\\r?\\n$/.test(response)) {\n\t\t\t\tinitialized = true;\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolveReady();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst match = /^OK:([A-Za-z0-9+/]*={0,2})\\r?\\n$/.exec(response);\n\t\t\tif (!pending || !match || match[1].length % 4 !== 0) {\n\t\t\t\tfail(new Error(\"Cannot read clipboard text; use your terminal's Paste command\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst bytes = Buffer.from(match[1], \"base64\");\n\t\t\tif (bytes.length > maxBytes || bytes.toString(\"base64\") !== match[1]) {\n\t\t\t\tfail(new Error(\"Invalid or oversized clipboard response\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\tconst request = pending;\n\t\t\tpending = undefined;\n\t\t\trequest.resolve(bytes.toString(\"utf8\"));\n\t\t});\n\t\treturn instance;\n\t}\n\treturn {\n\t\tstart(): Promise<void> {\n\t\t\tif (isRemoteSession())\n\t\t\t\treturn Promise.reject(new Error(\"Use your terminal's Paste command in remote sessions\"));\n\t\t\ttry {\n\t\t\t\tworker ??= launch();\n\t\t\t\treturn worker.ready;\n\t\t\t} catch {\n\t\t\t\treturn Promise.reject(new Error(\"Cannot start clipboard reader; use your terminal's Paste command\"));\n\t\t\t}\n\t\t},\n\t\tread(): Promise<string> {\n\t\t\treturn worker ? worker.read() : Promise.reject(new Error(\"Clipboard reader stopped; try pasting again\"));\n\t\t},\n\t\tclose(): void {\n\t\t\tworker?.close();\n\t\t},\n\t};\n}\n\nexport async function copyToClipboard(text: string): Promise<void> {\n\tlet copied = false;\n\n\tconst p = platform();\n\n\t// Prefer direct clipboard writes. Emitting OSC 52 first can make terminals\n\t// write the same native clipboard concurrently with the addon, and very large\n\t// OSC 52 payloads can desynchronize terminal rendering.\n\t//\n\t// On Linux, skip the native addon. The underlying `clipboard-rs` crate is\n\t// X11-only and does not retain selection ownership after `set_text`\n\t// resolves, so on Wayland-only compositors (Hyprland, Niri, ...) and even\n\t// some X11 sessions the call resolves successfully without populating the\n\t// clipboard. The platform tools below (wl-copy, xclip, xsel) properly\n\t// daemonize and keep ownership.\n\ttry {\n\t\tif (clipboard && p !== \"linux\") {\n\t\t\tawait clipboard.setText(text);\n\t\t\tcopied = true;\n\t\t}\n\t} catch {\n\t\t// Fall through to platform-specific clipboard tools.\n\t}\n\n\tconst remote = isRemoteSession();\n\tif (copied && !remote) {\n\t\treturn;\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\tif (!copied) {\n\t\ttry {\n\t\t\tif (p === \"darwin\") {\n\t\t\t\texecSync(\"pbcopy\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else if (p === \"win32\") {\n\t\t\t\texecSync(\"clip\", options);\n\t\t\t\tcopied = true;\n\t\t\t} else {\n\t\t\t\t// Linux. Try Termux, Wayland, or X11 clipboard tools.\n\t\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Fall back to Wayland or X11 tools.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!copied) {\n\t\t\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\t\t\tconst isWayland = isWaylandSession();\n\t\t\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// SCRAMJET-DIVERGENCE: await backend acceptance without execSync's daemon/fork hang.\n\t\t\t\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], {\n\t\t\t\t\t\t\t\t\tstdio: [\"pipe\", \"ignore\", \"ignore\"],\n\t\t\t\t\t\t\t\t\ttimeout: options.timeout,\n\t\t\t\t\t\t\t\t\tkillSignal: \"SIGKILL\",\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tlet inputFinished = false;\n\t\t\t\t\t\t\t\tlet exited = false;\n\t\t\t\t\t\t\t\tproc.on(\"error\", reject);\n\t\t\t\t\t\t\t\tproc.stdin.on(\"error\", (error) => {\n\t\t\t\t\t\t\t\t\tproc.kill(\"SIGKILL\");\n\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tproc.once(\"exit\", (code) => {\n\t\t\t\t\t\t\t\t\tif (code !== 0) {\n\t\t\t\t\t\t\t\t\t\treject(new Error(\"wl-copy did not accept clipboard content\"));\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\texited = true;\n\t\t\t\t\t\t\t\t\tif (inputFinished) resolve();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tproc.stdin.end(text, (error?: Error | null) => {\n\t\t\t\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tinputFinished = true;\n\t\t\t\t\t\t\t\t\tif (exited) resolve();\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\tcopied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to OSC 52 fallback.\n\t\t}\n\t}\n\n\tif (remote || !copied) {\n\t\tconst osc52Copied = emitOsc52(text);\n\t\tcopied = copied || osc52Copied;\n\t}\n\n\tif (!copied) {\n\t\tthrow new Error(\"Failed to copy to clipboard\");\n\t}\n}\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare function getToolPath(tool: "fd" | "rg"): string | null;
|
|
2
|
-
export declare function ensureTool(tool: "fd" | "rg", silent?: boolean): Promise<string | undefined>;
|
|
2
|
+
export declare function ensureTool(tool: "fd" | "rg", silent?: boolean, report?: (message: string) => void): Promise<string | undefined>;
|
|
3
3
|
//# 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":"AAqFA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmB5D;AA8ND,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 { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\n// SCRAMJET-DIVERGENCE: prefer SCRAMJET_OFFLINE, fall back to PI_OFFLINE.\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.SCRAMJET_OFFLINE || process.env.PI_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\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\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\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\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\"): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\treturn localPath;\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// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n\tconst response = await fetch(url, {\n\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t});\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 fileStream = createWriteStream(dest);\n\tawait pipeline(Readable.fromWeb(response.body as any), fileStream);\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\n// Download and install a tool\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tlet version = await getLatestVersion(config.repo);\n\tif (tool === \"fd\" && plat === \"darwin\" && architecture === \"x64\") {\n\t\tversion = \"10.3.0\";\n\t}\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download\n\tawait downloadFile(downloadUrl, archivePath);\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(extractDir, { recursive: true });\n\n\ttry {\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\textractTarGzArchive(archivePath, extractDir, assetName);\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\textractZipArchive(archivePath, extractDir, assetName);\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Cleanup\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\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":"AAqFA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAmB5D;AA+ND,wBAAsB,UAAU,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,MAAM,GAAE,OAAe,EACvB,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAkB,GAC7C,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2C7B","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from \"fs\";\nimport { arch, platform } from \"os\";\nimport { join } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { APP_NAME, getBinDir } from \"../config.js\";\n\nconst TOOLS_DIR = getBinDir();\nconst NETWORK_TIMEOUT_MS = 10_000;\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\n// SCRAMJET-DIVERGENCE: prefer SCRAMJET_OFFLINE, fall back to PI_OFFLINE.\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = process.env.SCRAMJET_OFFLINE || process.env.PI_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\tgetAssetName: (version: string, plat: string, architecture: string) => string | null;\n}\n\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\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-unknown-linux-gnu.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `fd-v${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t\tgetAssetName: (version, plat, architecture) => {\n\t\t\tif (plat === \"darwin\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-apple-darwin.tar.gz`;\n\t\t\t} else if (plat === \"linux\") {\n\t\t\t\tif (architecture === \"arm64\") {\n\t\t\t\t\treturn `ripgrep-${version}-aarch64-unknown-linux-gnu.tar.gz`;\n\t\t\t\t}\n\t\t\t\treturn `ripgrep-${version}-x86_64-unknown-linux-musl.tar.gz`;\n\t\t\t} else if (plat === \"win32\") {\n\t\t\t\tconst archStr = architecture === \"arm64\" ? \"aarch64\" : \"x86_64\";\n\t\t\t\treturn `ripgrep-${version}-${archStr}-pc-windows-msvc.zip`;\n\t\t\t}\n\t\t\treturn null;\n\t\t},\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\"): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first\n\tconst localPath = join(TOOLS_DIR, config.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"));\n\tif (existsSync(localPath)) {\n\t\treturn localPath;\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// Fetch latest release version from GitHub\nasync function getLatestVersion(repo: string): Promise<string> {\n\tconst response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {\n\t\theaders: { \"User-Agent\": `${APP_NAME}-coding-agent` },\n\t\tsignal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub API error: ${response.status}`);\n\t}\n\n\tconst data = (await response.json()) as { tag_name: string };\n\treturn data.tag_name.replace(/^v/, \"\");\n}\n\n// Download a file from URL\nasync function downloadFile(url: string, dest: string): Promise<void> {\n\tconst response = await fetch(url, {\n\t\tsignal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),\n\t});\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 fileStream = createWriteStream(dest);\n\tawait pipeline(Readable.fromWeb(response.body as any), fileStream);\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\n// Download and install a tool\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst config = TOOLS[tool];\n\tif (!config) throw new Error(`Unknown tool: ${tool}`);\n\n\tconst plat = platform();\n\tconst architecture = arch();\n\n\t// Get latest version\n\tlet version = await getLatestVersion(config.repo);\n\tif (tool === \"fd\" && plat === \"darwin\" && architecture === \"x64\") {\n\t\tversion = \"10.3.0\";\n\t}\n\n\t// Get asset name for this platform\n\tconst assetName = config.getAssetName(version, plat, architecture);\n\tif (!assetName) {\n\t\tthrow new Error(`Unsupported platform: ${plat}/${architecture}`);\n\t}\n\n\t// Create tools directory\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\tconst downloadUrl = `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${version}/${assetName}`;\n\tconst archivePath = join(TOOLS_DIR, assetName);\n\tconst binaryExt = plat === \"win32\" ? \".exe\" : \"\";\n\tconst binaryPath = join(TOOLS_DIR, config.binaryName + binaryExt);\n\n\t// Download\n\tawait downloadFile(downloadUrl, archivePath);\n\n\t// Extract into a unique temp directory. fd and rg downloads can run concurrently\n\t// during startup, so sharing a fixed directory causes races.\n\tconst extractDir = join(\n\t\tTOOLS_DIR,\n\t\t`extract_tmp_${config.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(extractDir, { recursive: true });\n\n\ttry {\n\t\tif (assetName.endsWith(\".tar.gz\")) {\n\t\t\textractTarGzArchive(archivePath, extractDir, assetName);\n\t\t} else if (assetName.endsWith(\".zip\")) {\n\t\t\textractZipArchive(archivePath, extractDir, assetName);\n\t\t} else {\n\t\t\tthrow new Error(`Unsupported archive format: ${assetName}`);\n\t\t}\n\n\t\t// Find the binary in extracted files. Some archives contain files directly\n\t\t// at root, others nest under a versioned subdirectory.\n\t\tconst binaryFileName = config.binaryName + binaryExt;\n\t\tconst extractedDir = join(extractDir, assetName.replace(/\\.(tar\\.gz|zip)$/, \"\"));\n\t\tconst extractedBinaryCandidates = [join(extractedDir, binaryFileName), join(extractDir, binaryFileName)];\n\t\tlet extractedBinary = extractedBinaryCandidates.find((candidate) => existsSync(candidate));\n\n\t\tif (!extractedBinary) {\n\t\t\textractedBinary = findBinaryRecursively(extractDir, binaryFileName) ?? undefined;\n\t\t}\n\n\t\tif (extractedBinary) {\n\t\t\trenameSync(extractedBinary, binaryPath);\n\t\t} else {\n\t\t\tthrow new Error(`Binary not found in archive: expected ${binaryFileName} under ${extractDir}`);\n\t\t}\n\n\t\t// Make executable (Unix only)\n\t\tif (plat !== \"win32\") {\n\t\t\tchmodSync(binaryPath, 0o755);\n\t\t}\n\t} finally {\n\t\t// Cleanup\n\t\trmSync(archivePath, { force: true });\n\t\trmSync(extractDir, { recursive: true, force: true });\n\t}\n\n\treturn binaryPath;\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\n// SCRAMJET-DIVERGENCE: interactive startup owns presentation of provisioning diagnostics.\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tsilent: boolean = false,\n\treport: (message: string) => void = console.log,\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\tif (!silent) {\n\t\t\treport(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\treport(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\treport(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\treport(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\treport(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -285,7 +285,8 @@ const TERMUX_PACKAGES = {
|
|
|
285
285
|
};
|
|
286
286
|
// Ensure a tool is available, downloading if necessary
|
|
287
287
|
// Returns the path to the tool, or null if unavailable
|
|
288
|
-
|
|
288
|
+
// SCRAMJET-DIVERGENCE: interactive startup owns presentation of provisioning diagnostics.
|
|
289
|
+
export async function ensureTool(tool, silent = false, report = console.log) {
|
|
289
290
|
const existingPath = getToolPath(tool);
|
|
290
291
|
if (existingPath) {
|
|
291
292
|
return existingPath;
|
|
@@ -295,7 +296,7 @@ export async function ensureTool(tool, silent = false) {
|
|
|
295
296
|
return undefined;
|
|
296
297
|
if (isOfflineModeEnabled()) {
|
|
297
298
|
if (!silent) {
|
|
298
|
-
|
|
299
|
+
report(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));
|
|
299
300
|
}
|
|
300
301
|
return undefined;
|
|
301
302
|
}
|
|
@@ -304,24 +305,24 @@ export async function ensureTool(tool, silent = false) {
|
|
|
304
305
|
if (platform() === "android") {
|
|
305
306
|
const pkgName = TERMUX_PACKAGES[tool] ?? tool;
|
|
306
307
|
if (!silent) {
|
|
307
|
-
|
|
308
|
+
report(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));
|
|
308
309
|
}
|
|
309
310
|
return undefined;
|
|
310
311
|
}
|
|
311
312
|
// Tool not found - download it
|
|
312
313
|
if (!silent) {
|
|
313
|
-
|
|
314
|
+
report(chalk.dim(`${config.name} not found. Downloading...`));
|
|
314
315
|
}
|
|
315
316
|
try {
|
|
316
317
|
const path = await downloadTool(tool);
|
|
317
318
|
if (!silent) {
|
|
318
|
-
|
|
319
|
+
report(chalk.dim(`${config.name} installed to ${path}`));
|
|
319
320
|
}
|
|
320
321
|
return path;
|
|
321
322
|
}
|
|
322
323
|
catch (e) {
|
|
323
324
|
if (!silent) {
|
|
324
|
-
|
|
325
|
+
report(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));
|
|
325
326
|
}
|
|
326
327
|
return undefined;
|
|
327
328
|
}
|