docks-kit 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/AGENTS.md +37 -33
  2. package/README.md +1 -0
  3. package/cli/docs/omp-models.md +114 -58
  4. package/cli/src/argv.ts +38 -469
  5. package/cli/src/argvSurface.ts +371 -0
  6. package/cli/src/argvValidate.ts +122 -0
  7. package/cli/src/commands/docs.ts +57 -42
  8. package/cli/src/commands/harnesses.ts +38 -45
  9. package/cli/src/commands/model.ts +48 -53
  10. package/cli/src/commands/models.ts +24 -26
  11. package/cli/src/commands/omp.ts +178 -88
  12. package/cli/src/commands/plugins.ts +20 -20
  13. package/cli/src/commands/skills.ts +22 -20
  14. package/cli/src/commands/status.ts +87 -80
  15. package/cli/src/commands/sync.ts +113 -87
  16. package/cli/src/commands/toolchain.ts +21 -21
  17. package/cli/src/commands/update.ts +139 -117
  18. package/cli/src/efforts.ts +35 -34
  19. package/cli/src/engine-native/DESIGN.md +30 -30
  20. package/cli/src/engine-native/bun.ts +77 -55
  21. package/cli/src/engine-native/claudeLsp.ts +154 -0
  22. package/cli/src/engine-native/claudeOptionalPlugins.ts +113 -0
  23. package/cli/src/engine-native/claudePluginPasses.ts +348 -0
  24. package/cli/src/engine-native/claudeRemovals.ts +195 -0
  25. package/cli/src/engine-native/claudeRetired.ts +4 -4
  26. package/cli/src/engine-native/claudeRuntime.ts +94 -68
  27. package/cli/src/engine-native/claudeSettings.ts +174 -0
  28. package/cli/src/engine-native/claudeSettingsModifiers.ts +50 -54
  29. package/cli/src/engine-native/claudeSync.ts +176 -456
  30. package/cli/src/engine-native/codexConfig.ts +158 -0
  31. package/cli/src/engine-native/codexHooks.ts +91 -0
  32. package/cli/src/engine-native/codexPlugins.ts +290 -0
  33. package/cli/src/engine-native/codexStatus.ts +24 -0
  34. package/cli/src/engine-native/codexSync.ts +110 -568
  35. package/cli/src/engine-native/codexToml.ts +140 -138
  36. package/cli/src/engine-native/deps.ts +144 -107
  37. package/cli/src/engine-native/engineCtx.ts +126 -0
  38. package/cli/src/engine-native/exec.ts +98 -87
  39. package/cli/src/engine-native/failures.ts +3 -3
  40. package/cli/src/engine-native/harnesses.ts +62 -72
  41. package/cli/src/engine-native/index.ts +34 -315
  42. package/cli/src/engine-native/jq.ts +25 -24
  43. package/cli/src/engine-native/logger.ts +98 -100
  44. package/cli/src/engine-native/models.ts +50 -45
  45. package/cli/src/engine-native/modes.ts +83 -80
  46. package/cli/src/engine-native/ompFileDeploy.ts +111 -0
  47. package/cli/src/engine-native/ompMarketplace.ts +136 -0
  48. package/cli/src/engine-native/ompOverlay.ts +93 -53
  49. package/cli/src/engine-native/ompPaths.ts +49 -38
  50. package/cli/src/engine-native/ompPlugins.ts +170 -0
  51. package/cli/src/engine-native/ompSync.ts +64 -408
  52. package/cli/src/engine-native/ompYaml.ts +43 -41
  53. package/cli/src/engine-native/os/darwin.ts +5 -5
  54. package/cli/src/engine-native/os/index.ts +15 -15
  55. package/cli/src/engine-native/os/linux.ts +5 -5
  56. package/cli/src/engine-native/os/posix.ts +28 -24
  57. package/cli/src/engine-native/os/targets.ts +17 -17
  58. package/cli/src/engine-native/os/types.ts +31 -31
  59. package/cli/src/engine-native/os/windows.ts +61 -61
  60. package/cli/src/engine-native/parseArgs.ts +122 -372
  61. package/cli/src/engine-native/parseHelp.ts +66 -0
  62. package/cli/src/engine-native/parseModifiers.ts +222 -0
  63. package/cli/src/engine-native/services.ts +34 -34
  64. package/cli/src/engine-native/settings.ts +15 -15
  65. package/cli/src/engine-native/sharedTypes.d.ts +46 -0
  66. package/cli/src/engine-native/skillsInstall.ts +105 -0
  67. package/cli/src/engine-native/skillsLinks.ts +203 -0
  68. package/cli/src/engine-native/skillsManifest.ts +16 -0
  69. package/cli/src/engine-native/skillsPrune.ts +108 -0
  70. package/cli/src/engine-native/skillsSync.ts +28 -362
  71. package/cli/src/engine-native/syncConcurrency.ts +55 -0
  72. package/cli/src/engine-native/syncDispatch.ts +141 -0
  73. package/cli/src/engine-native/toolchain.ts +65 -62
  74. package/cli/src/engine.ts +65 -57
  75. package/cli/src/generated/sotPayload.ts +7 -7
  76. package/cli/src/kitHome.ts +44 -40
  77. package/cli/src/main.ts +63 -56
  78. package/cli/src/manifests.ts +53 -54
  79. package/cli/src/md.d.ts +2 -2
  80. package/cli/src/payload.ts +9 -9
  81. package/cli/src/services.ts +21 -13
  82. package/cli/tsconfig.json +4 -1
  83. package/package.json +9 -2
  84. package/cli/src/engine-native/claudePlugins.ts +0 -510
@@ -1,24 +1,24 @@
1
- import { darwin } from "./darwin"
2
- import { linux } from "./linux"
3
- import type { HostOs, PlatformName } from "./types"
4
- import { windows } from "./windows"
1
+ import { darwin } from "./darwin";
2
+ import { linux } from "./linux";
3
+ import type { HostOs, PlatformName } from "./types";
4
+ import { windows } from "./windows";
5
5
 
6
- export * from "./types"
6
+ export * from "./types";
7
7
 
8
8
  export function rawPlatform(): NodeJS.Platform {
9
- return process.platform
9
+ return process.platform;
10
10
  }
11
11
 
12
12
  export function platformName(pf: NodeJS.Platform = rawPlatform()): PlatformName {
13
13
  switch (pf) {
14
14
  case "linux":
15
- return "linux"
15
+ return "linux";
16
16
  case "darwin":
17
- return "darwin"
17
+ return "darwin";
18
18
  case "win32":
19
- return "windows"
19
+ return "windows";
20
20
  default:
21
- return "unknown"
21
+ return "unknown";
22
22
  }
23
23
  }
24
24
 
@@ -27,16 +27,16 @@ const unknown: HostOs = {
27
27
  ...linux,
28
28
  id: "unknown",
29
29
  toolchainOs: "",
30
- supportsBubblewrap: false
31
- }
30
+ supportsBubblewrap: false,
31
+ };
32
32
 
33
33
  const HOSTS: Readonly<Record<PlatformName, HostOs>> = {
34
34
  linux,
35
35
  darwin,
36
36
  windows,
37
- unknown
38
- }
37
+ unknown,
38
+ };
39
39
 
40
40
  export function hostOs(id: PlatformName = platformName()): HostOs {
41
- return HOSTS[id]
41
+ return HOSTS[id];
42
42
  }
@@ -1,5 +1,5 @@
1
- import { posixHost } from "./posix"
2
- import type { HostOs } from "./types"
1
+ import { posixHost } from "./posix";
2
+ import type { HostOs } from "./types";
3
3
 
4
4
  export const linux: HostOs = posixHost({
5
5
  id: "linux",
@@ -9,6 +9,6 @@ export const linux: HostOs = posixHost({
9
9
  git: "sudo apt install -y git (or your distro's package manager)",
10
10
  jq: "sudo apt install -y jq",
11
11
  curl: "sudo apt install -y curl",
12
- ffplay: "sudo apt install -y ffmpeg"
13
- }
14
- })
12
+ ffplay: "sudo apt install -y ffmpeg",
13
+ },
14
+ });
@@ -1,24 +1,26 @@
1
1
  // No import from "../exec": exec.ts imports "./os", which evaluates linux and darwin
2
2
  // through this module. A slash join keeps the same host-stable path p() would produce.
3
- import type { HintedTool, HostOs } from "./types"
3
+ import type { HintedTool, HostOs } from "./types";
4
4
 
5
5
  function posixLiteral(value: string): string {
6
- return `'${value.replaceAll("'", `'"'"'`)}'`
6
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
7
7
  }
8
8
 
9
9
  /** Package-manager hints for the tools a POSIX host installs through its own package manager. */
10
- export type PackageManagerHints = Readonly<Record<Exclude<HintedTool, "claude" | "codex">, string>>
10
+ export type PackageManagerHints = Readonly<Record<Exclude<HintedTool, "claude" | "codex">, string>>;
11
11
 
12
12
  const UPSTREAM_INSTALLER_HINTS: Readonly<Record<"claude" | "codex", string>> = {
13
- claude: "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
14
- codex: 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"'
15
- }
13
+ claude:
14
+ "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
15
+ codex:
16
+ 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"',
17
+ };
16
18
 
17
19
  interface PosixHostInputs {
18
- readonly id: HostOs["id"]
19
- readonly toolchainOs: HostOs["toolchainOs"]
20
- readonly supportsBubblewrap: boolean
21
- readonly packageManagerHints: PackageManagerHints
20
+ readonly id: HostOs["id"];
21
+ readonly toolchainOs: HostOs["toolchainOs"];
22
+ readonly supportsBubblewrap: boolean;
23
+ readonly packageManagerHints: PackageManagerHints;
22
24
  }
23
25
 
24
26
  /** Members shared by every POSIX host; linux and darwin differ only in the inputs. */
@@ -32,36 +34,38 @@ export function posixHost(inputs: PosixHostInputs): HostOs {
32
34
  invoke: (executablePath, args) => ({ command: executablePath, args }),
33
35
  bunExecutableName: "bun",
34
36
  bunInstaller: (pin, directory) => {
35
- const scriptPath = `${directory}/install.sh`
37
+ const scriptPath = `${directory}/install.sh`;
36
38
  return {
37
39
  scriptPath,
38
40
  download: {
39
41
  command: "curl",
40
- args: ["-fsSL", "https://bun.sh/install", "-o", scriptPath]
42
+ args: ["-fsSL", "https://bun.sh/install", "-o", scriptPath],
41
43
  },
42
44
  run: {
43
45
  command: "bash",
44
- args: [scriptPath, `bun-v${pin}`]
45
- }
46
- }
46
+ args: [scriptPath, `bun-v${pin}`],
47
+ },
48
+ };
47
49
  },
48
50
  environmentSetting: (name, value) => ({
49
51
  kind: "profile",
50
52
  candidates: [".zshrc", ".bashrc", ".bash_profile", ".profile", ".zshenv"],
51
53
  target: (shell) => {
52
- const shellPath = shell ?? "bash"
53
- const shellName = shellPath.slice(shellPath.lastIndexOf("/") + 1)
54
- return shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile"
54
+ const shellPath = shell ?? "bash";
55
+ const shellName = shellPath.slice(shellPath.lastIndexOf("/") + 1);
56
+ return shellName === "zsh" ? ".zshrc" : shellName === "bash" ? ".bashrc" : ".profile";
55
57
  },
56
- line: `export ${name}=${value}`
58
+ line: `export ${name}=${value}`,
57
59
  }),
58
60
  statusLineCommand: (bun, script) => {
59
- const bunLiteral = posixLiteral(bun)
60
- const scriptLiteral = posixLiteral(script)
61
- return `test -x ${bunLiteral} && test -f ${scriptLiteral} && exec ${bunLiteral} ${scriptLiteral} || true`
61
+ const bunLiteral = posixLiteral(bun);
62
+ const scriptLiteral = posixLiteral(script);
63
+ return `test -x ${bunLiteral} && test -f ${scriptLiteral} && exec ${bunLiteral} ${scriptLiteral} || true`;
62
64
  },
63
65
  failureHookCommand: (command) => command,
64
66
  installHint: (tool) =>
65
- tool === "claude" || tool === "codex" ? UPSTREAM_INSTALLER_HINTS[tool] : inputs.packageManagerHints[tool]
66
- }
67
+ tool === "claude" || tool === "codex"
68
+ ? UPSTREAM_INSTALLER_HINTS[tool]
69
+ : inputs.packageManagerHints[tool],
70
+ };
67
71
  }
@@ -8,7 +8,7 @@
8
8
  * supported, and `requireSupportedHost` in cli/src/engine.ts admits exactly the
9
9
  * platform/arch pairs this table names.
10
10
  */
11
- import { platformName, type PlatformName } from "./index"
11
+ import { platformName, type PlatformName } from "./index";
12
12
 
13
13
  export type TargetId =
14
14
  | "linux-x64"
@@ -16,26 +16,26 @@ export type TargetId =
16
16
  | "darwin-x64"
17
17
  | "darwin-arm64"
18
18
  | "windows-x64"
19
- | "windows-arm64"
19
+ | "windows-arm64";
20
20
 
21
- export type TargetArch = "x64" | "arm64"
21
+ export type TargetArch = "x64" | "arm64";
22
22
 
23
23
  export interface HostTarget {
24
- readonly id: TargetId
25
- readonly platform: PlatformName
26
- readonly arch: TargetArch
24
+ readonly id: TargetId;
25
+ readonly platform: PlatformName;
26
+ readonly arch: TargetArch;
27
27
  /** `bun build --compile --target=` value. */
28
- readonly bunTarget: string
28
+ readonly bunTarget: string;
29
29
  /** Compiled binary file name, including the Windows `.exe` suffix. */
30
- readonly artifact: string
30
+ readonly artifact: string;
31
31
  /** `uname -s`-`uname -m` keys the Bash launcher matches; empty on Windows. */
32
- readonly unameKeys: ReadonlyArray<string>
32
+ readonly unameKeys: ReadonlyArray<string>;
33
33
  /**
34
34
  * `$env:PROCESSOR_ARCHITECTURE` values the PowerShell launcher matches; empty
35
35
  * off Windows. Windows PowerShell 5.1 runs emulated on ARM64 and reports
36
36
  * `AMD64` there, so a launcher must read `PROCESSOR_ARCHITEW6432` first.
37
37
  */
38
- readonly processorArchitectures: ReadonlyArray<string>
38
+ readonly processorArchitectures: ReadonlyArray<string>;
39
39
  }
40
40
 
41
41
  const target = (
@@ -43,7 +43,7 @@ const target = (
43
43
  platform: PlatformName,
44
44
  arch: TargetArch,
45
45
  unameKeys: ReadonlyArray<string>,
46
- processorArchitectures: ReadonlyArray<string>
46
+ processorArchitectures: ReadonlyArray<string>,
47
47
  ): HostTarget => ({
48
48
  id,
49
49
  platform,
@@ -51,8 +51,8 @@ const target = (
51
51
  bunTarget: `bun-${id}`,
52
52
  artifact: platform === "windows" ? `docks-kit-${id}.exe` : `docks-kit-${id}`,
53
53
  unameKeys,
54
- processorArchitectures
55
- })
54
+ processorArchitectures,
55
+ });
56
56
 
57
57
  export const HOST_TARGETS: ReadonlyArray<HostTarget> = [
58
58
  target("linux-x64", "linux", "x64", ["Linux-x86_64"], []),
@@ -60,14 +60,14 @@ export const HOST_TARGETS: ReadonlyArray<HostTarget> = [
60
60
  target("darwin-x64", "darwin", "x64", ["Darwin-x86_64"], []),
61
61
  target("darwin-arm64", "darwin", "arm64", ["Darwin-arm64"], []),
62
62
  target("windows-x64", "windows", "x64", [], ["AMD64"]),
63
- target("windows-arm64", "windows", "arm64", [], ["ARM64"])
64
- ]
63
+ target("windows-arm64", "windows", "arm64", [], ["ARM64"]),
64
+ ];
65
65
 
66
66
  function targetFor(platform: PlatformName, arch: string): HostTarget | undefined {
67
- return HOST_TARGETS.find((t) => t.platform === platform && t.arch === arch)
67
+ return HOST_TARGETS.find((t) => t.platform === platform && t.arch === arch);
68
68
  }
69
69
 
70
70
  /** Resolve the artifact for a raw Node platform/arch pair. */
71
71
  export function targetForHost(platform: NodeJS.Platform, arch: string): HostTarget | undefined {
72
- return targetFor(platformName(platform), arch)
72
+ return targetFor(platformName(platform), arch);
73
73
  }
@@ -4,72 +4,72 @@
4
4
  * only the order of that fallback chain.
5
5
  */
6
6
 
7
- export type PlatformName = "linux" | "darwin" | "windows" | "unknown"
7
+ export type PlatformName = "linux" | "darwin" | "windows" | "unknown";
8
8
  /** Directory-link mechanisms, tried in order before the copy fallback. */
9
- export type DirectoryLinkKind = "symlink" | "junction"
9
+ export type DirectoryLinkKind = "symlink" | "junction";
10
10
  /** Tools whose install command differs per host OS. */
11
- export type HintedTool = "git" | "jq" | "curl" | "ffplay" | "claude" | "codex"
11
+ export type HintedTool = "git" | "jq" | "curl" | "ffplay" | "claude" | "codex";
12
12
 
13
13
  /** A command plus argv, already shaped for how this host must invoke it. */
14
14
  export interface Invocation {
15
- readonly command: string
16
- readonly args: ReadonlyArray<string>
15
+ readonly command: string;
16
+ readonly args: ReadonlyArray<string>;
17
17
  /** Preserve a fully quoted Windows command line instead of applying libuv quoting. */
18
- readonly windowsVerbatimArguments?: boolean
18
+ readonly windowsVerbatimArguments?: boolean;
19
19
  }
20
20
 
21
21
  /** How this host persists a user-level environment variable. */
22
22
  export type EnvironmentSetting =
23
23
  | {
24
- readonly kind: "profile"
24
+ readonly kind: "profile";
25
25
  /** Home-relative files scanned for an existing setting. */
26
- readonly candidates: ReadonlyArray<string>
26
+ readonly candidates: ReadonlyArray<string>;
27
27
  /** Home-relative file that receives the write for this shell. */
28
- readonly target: (shell: string | undefined) => string
28
+ readonly target: (shell: string | undefined) => string;
29
29
  /** Line appended to the target file. */
30
- readonly line: string
30
+ readonly line: string;
31
31
  }
32
32
  | {
33
- readonly kind: "command"
33
+ readonly kind: "command";
34
34
  /** Exits zero when the variable is already persisted. */
35
- readonly probe: Invocation
35
+ readonly probe: Invocation;
36
36
  /** Persists the variable. */
37
- readonly apply: Invocation
37
+ readonly apply: Invocation;
38
38
  /** Where the value lands, named for the log line. */
39
- readonly location: string
39
+ readonly location: string;
40
40
  /** Manual recovery when `apply` fails. */
41
- readonly manualHint: string
42
- }
41
+ readonly manualHint: string;
42
+ };
43
43
 
44
44
  /** The two steps that fetch and run a pinned Bun installer. */
45
45
  export interface BunInstaller {
46
- readonly scriptPath: string
47
- readonly download: Invocation
48
- readonly run: Invocation
46
+ readonly scriptPath: string;
47
+ readonly download: Invocation;
48
+ readonly run: Invocation;
49
49
  }
50
50
 
51
51
  export interface HostOs {
52
- readonly id: PlatformName
52
+ readonly id: PlatformName;
53
53
  /** `SoT/toolchain.json` `os` field this host matches; "" matches every row. */
54
- readonly toolchainOs: string
54
+ readonly toolchainOs: string;
55
55
  /** Bubblewrap is the Linux-only Codex sandbox runtime. */
56
- readonly supportsBubblewrap: boolean
56
+ readonly supportsBubblewrap: boolean;
57
57
  /** Directory-link mechanisms tried in order before `skillsSync.ts linkOrCopy` copies. */
58
- readonly directoryLinkKinds: ReadonlyArray<DirectoryLinkKind>
58
+ readonly directoryLinkKinds: ReadonlyArray<DirectoryLinkKind>;
59
59
  /** One-line install command for a tool whose package differs per OS. */
60
- readonly installHint: (tool: HintedTool) => string
60
+ readonly installHint: (tool: HintedTool) => string;
61
61
  /** Filename suffixes tried in order when resolving a bare tool name on PATH. */
62
- readonly executableSuffixes: ReadonlyArray<string>
62
+ readonly executableSuffixes: ReadonlyArray<string>;
63
63
  /** Shape the argv that actually runs a resolved executable on this host. */
64
- readonly invoke: (executablePath: string, args: ReadonlyArray<string>) => Invocation
64
+ readonly invoke: (executablePath: string, args: ReadonlyArray<string>) => Invocation;
65
65
  /** Bun executable filename inside `<bunRoot>/bin`. */
66
- readonly bunExecutableName: string
66
+ readonly bunExecutableName: string;
67
67
  /** Download-then-run installer for a pinned Bun version placed in `directory`. */
68
- readonly bunInstaller: (pin: string, directory: string) => BunInstaller
68
+ readonly bunInstaller: (pin: string, directory: string) => BunInstaller;
69
69
  /** How this host persists a user-level environment variable. */
70
- readonly environmentSetting: (name: string, value: string) => EnvironmentSetting
70
+ readonly environmentSetting: (name: string, value: string) => EnvironmentSetting;
71
71
  /** Claude `statusLine.command` for a resolved bun executable and script. */
72
- readonly statusLineCommand: (bun: string, script: string) => string
72
+ readonly statusLineCommand: (bun: string, script: string) => string;
73
73
  /** Reshape the SoT `PostToolUseFailure` command for this host's shell. */
74
- readonly failureHookCommand: (command: string) => string
74
+ readonly failureHookCommand: (command: string) => string;
75
75
  }
@@ -1,14 +1,14 @@
1
- import { win32 } from "node:path"
2
- import { p } from "../exec"
3
- import type { HostOs } from "./types"
1
+ import { win32 } from "node:path";
2
+ import { p } from "../exec";
3
+ import type { HostOs } from "./types";
4
4
 
5
5
  function powershellLiteral(value: string): string {
6
- return `'${value.replaceAll("'", "''")}'`
6
+ return `'${value.replaceAll("'", "''")}'`;
7
7
  }
8
8
 
9
9
  function encodedPowerShellCommand(script: string): string {
10
- const encoded = Buffer.from(script, "utf16le").toString("base64")
11
- return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`
10
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
11
+ return `powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
12
12
  }
13
13
 
14
14
  /**
@@ -21,22 +21,22 @@ function encodedPowerShellCommand(script: string): string {
21
21
  * https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
22
22
  * https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd
23
23
  */
24
- const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g
24
+ const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
25
25
 
26
26
  function escapeCommand(arg: string): string {
27
- return arg.replace(metaCharsRegExp, "^$1")
27
+ return arg.replace(metaCharsRegExp, "^$1");
28
28
  }
29
29
 
30
30
  function escapeArgument(arg: string, doubleEscapeMetaChars: boolean): string {
31
- arg = `${arg}`
32
- arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"")
33
- arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1")
34
- arg = `"${arg}"`
35
- arg = arg.replace(metaCharsRegExp, "^$1")
31
+ arg = `${arg}`;
32
+ arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
33
+ arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
34
+ arg = `"${arg}"`;
35
+ arg = arg.replace(metaCharsRegExp, "^$1");
36
36
  if (doubleEscapeMetaChars) {
37
- arg = arg.replace(metaCharsRegExp, "^$1")
37
+ arg = arg.replace(metaCharsRegExp, "^$1");
38
38
  }
39
- return arg
39
+ return arg;
40
40
  }
41
41
 
42
42
  function assertCommandLineValue(value: string, kind: "executable path" | "argument"): void {
@@ -46,16 +46,16 @@ function assertCommandLineValue(value: string, kind: "executable path" | "argume
46
46
  ? "carriage return (CR)"
47
47
  : value.includes("\n")
48
48
  ? "line feed (LF)"
49
- : undefined
50
- if (unsupported === undefined) return
49
+ : undefined;
50
+ if (unsupported === undefined) return;
51
51
 
52
52
  const executablePercentHint =
53
53
  kind === "executable path" && unsupported === "percent sign (%)"
54
54
  ? " The tool must be reached through its .exe or moved out of a directory whose name contains a percent sign."
55
- : ""
55
+ : "";
56
56
  throw new Error(
57
- `${kind} ${JSON.stringify(value)} contains a ${unsupported}, which cannot be escaped on a cmd command line; the caller must pass the value another way.${executablePercentHint}`
58
- )
57
+ `${kind} ${JSON.stringify(value)} contains a ${unsupported}, which cannot be escaped on a cmd command line; the caller must pass the value another way.${executablePercentHint}`,
58
+ );
59
59
  }
60
60
 
61
61
  /**
@@ -65,28 +65,28 @@ function assertCommandLineValue(value: string, kind: "executable path" | "argume
65
65
  * when it is an absolute path; otherwise it is rebuilt under SystemRoot.
66
66
  */
67
67
  function commandInterpreter(environment: NodeJS.ProcessEnv = process.env): string {
68
- const comSpec = environment["ComSpec"] ?? ""
69
- if (win32.isAbsolute(comSpec)) return comSpec
70
- const systemRoot = environment["SystemRoot"] ?? ""
71
- const root = win32.isAbsolute(systemRoot) ? systemRoot : "C:\\Windows"
72
- return win32.join(root, "System32", "cmd.exe")
68
+ const comSpec = environment["ComSpec"] ?? "";
69
+ if (win32.isAbsolute(comSpec)) return comSpec;
70
+ const systemRoot = environment["SystemRoot"] ?? "";
71
+ const root = win32.isAbsolute(systemRoot) ? systemRoot : "C:\\Windows";
72
+ return win32.join(root, "System32", "cmd.exe");
73
73
  }
74
74
 
75
75
  function commandShimInvocation(executablePath: string, args: ReadonlyArray<string>) {
76
- assertCommandLineValue(executablePath, "executable path")
77
- for (const arg of args) assertCommandLineValue(arg, "argument")
76
+ assertCommandLineValue(executablePath, "executable path");
77
+ for (const arg of args) assertCommandLineValue(arg, "argument");
78
78
 
79
- const normalizedPath = win32.normalize(executablePath)
80
- const doubleEscapeMetaChars = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(normalizedPath)
79
+ const normalizedPath = win32.normalize(executablePath);
80
+ const doubleEscapeMetaChars = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(normalizedPath);
81
81
  const commandLine = [
82
82
  escapeCommand(normalizedPath),
83
- ...args.map((arg) => escapeArgument(arg, doubleEscapeMetaChars))
84
- ].join(" ")
83
+ ...args.map((arg) => escapeArgument(arg, doubleEscapeMetaChars)),
84
+ ].join(" ");
85
85
  return {
86
86
  command: commandInterpreter(),
87
87
  args: ["/d", "/v:off", "/s", "/c", `"${commandLine}"`],
88
- windowsVerbatimArguments: true
89
- } as const
88
+ windowsVerbatimArguments: true,
89
+ } as const;
90
90
  }
91
91
 
92
92
  // Authored now, but unreachable until the supported-host admission gate opens.
@@ -102,12 +102,12 @@ export const windows: HostOs = {
102
102
  : { command: executablePath, args },
103
103
  bunExecutableName: "bun.exe",
104
104
  bunInstaller: (pin, directory) => {
105
- const scriptPath = p(directory, "install.ps1")
105
+ const scriptPath = p(directory, "install.ps1");
106
106
  return {
107
107
  scriptPath,
108
108
  download: {
109
109
  command: "curl",
110
- args: ["-fsSL", "https://bun.sh/install.ps1", "-o", scriptPath]
110
+ args: ["-fsSL", "https://bun.sh/install.ps1", "-o", scriptPath],
111
111
  },
112
112
  run: {
113
113
  command: "powershell.exe",
@@ -119,58 +119,58 @@ export const windows: HostOs = {
119
119
  "-File",
120
120
  scriptPath,
121
121
  "-Version",
122
- pin
123
- ]
124
- }
125
- }
122
+ pin,
123
+ ],
124
+ },
125
+ };
126
126
  },
127
127
  environmentSetting: (name, value) => ({
128
128
  kind: "command",
129
129
  probe: {
130
130
  command: "reg",
131
- args: ["query", "HKCU\\Environment", "/v", name]
131
+ args: ["query", "HKCU\\Environment", "/v", name],
132
132
  },
133
133
  apply: {
134
134
  command: "setx",
135
- args: [name, value]
135
+ args: [name, value],
136
136
  },
137
137
  location: "user environment",
138
- manualHint: "set it manually in System Properties > Environment Variables"
138
+ manualHint: "set it manually in System Properties > Environment Variables",
139
139
  }),
140
140
  statusLineCommand: (bun, script) => {
141
- const bunLiteral = powershellLiteral(bun)
142
- const scriptLiteral = powershellLiteral(script)
141
+ const bunLiteral = powershellLiteral(bun);
142
+ const scriptLiteral = powershellLiteral(script);
143
143
  // Module auto-loading reports "Preparing modules for first use" as a
144
144
  // progress record, which a redirected host serializes to stderr as CLIXML.
145
145
  // Claude runs this command every turn, and the records recur across runs, so
146
146
  // silencing progress inside the stored command keeps its streams clean.
147
147
  return encodedPowerShellCommand(
148
- `$ProgressPreference = 'SilentlyContinue'; if ((Test-Path -LiteralPath ${bunLiteral} -PathType Leaf) -and (Test-Path -LiteralPath ${scriptLiteral} -PathType Leaf)) { & ${bunLiteral} ${scriptLiteral} }`
149
- )
148
+ `$ProgressPreference = 'SilentlyContinue'; if ((Test-Path -LiteralPath ${bunLiteral} -PathType Leaf) -and (Test-Path -LiteralPath ${scriptLiteral} -PathType Leaf)) { & ${bunLiteral} ${scriptLiteral} }`,
149
+ );
150
150
  },
151
151
  failureHookCommand: (command) => {
152
- const prefix = "echo '"
153
- if (!command.startsWith(prefix) || !command.endsWith("'")) return command
154
- const quotedPayload = command.slice(prefix.length, -1)
155
- const posixApostrophe = `'"'"'`
156
- if (quotedPayload.replaceAll(posixApostrophe, "").includes("'")) return command
157
- const payload = quotedPayload.replaceAll(posixApostrophe, "'")
158
- return encodedPowerShellCommand(`Write-Output ${powershellLiteral(payload)}`)
152
+ const prefix = "echo '";
153
+ if (!command.startsWith(prefix) || !command.endsWith("'")) return command;
154
+ const quotedPayload = command.slice(prefix.length, -1);
155
+ const posixApostrophe = `'"'"'`;
156
+ if (quotedPayload.replaceAll(posixApostrophe, "").includes("'")) return command;
157
+ const payload = quotedPayload.replaceAll(posixApostrophe, "'");
158
+ return encodedPowerShellCommand(`Write-Output ${powershellLiteral(payload)}`);
159
159
  },
160
160
  installHint: (tool) => {
161
161
  switch (tool) {
162
162
  case "git":
163
- return "winget install --id Git.Git -e"
163
+ return "winget install --id Git.Git -e";
164
164
  case "jq":
165
- return "winget install --id jqlang.jq -e"
165
+ return "winget install --id jqlang.jq -e";
166
166
  case "curl":
167
- return "winget install --id cURL.cURL -e"
167
+ return "winget install --id cURL.cURL -e";
168
168
  case "ffplay":
169
- return "winget install --id Gyan.FFmpeg -e"
169
+ return "winget install --id Gyan.FFmpeg -e";
170
170
  case "claude":
171
- return "$tmp = Join-Path $env:TEMP 'claude-install.ps1'; curl.exe -fsSL https://claude.ai/install.ps1 -o $tmp; if ($LASTEXITCODE -eq 0) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmp }"
171
+ return "$tmp = Join-Path $env:TEMP 'claude-install.ps1'; curl.exe -fsSL https://claude.ai/install.ps1 -o $tmp; if ($LASTEXITCODE -eq 0) { powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmp }";
172
172
  case "codex":
173
- return "$tmp = Join-Path $env:TEMP 'codex-install.ps1'; curl.exe -fsSL https://chatgpt.com/codex/install.ps1 -o $tmp; if ($LASTEXITCODE -eq 0) { $env:CODEX_NON_INTERACTIVE = '1'; powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmp }"
173
+ return "$tmp = Join-Path $env:TEMP 'codex-install.ps1'; curl.exe -fsSL https://chatgpt.com/codex/install.ps1 -o $tmp; if ($LASTEXITCODE -eq 0) { $env:CODEX_NON_INTERACTIVE = '1'; powershell.exe -NoProfile -ExecutionPolicy Bypass -File $tmp }";
174
174
  }
175
- }
176
- }
175
+ },
176
+ };