pi-lens 3.1.2 → 3.2.0

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 (154) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +16 -12
  3. package/clients/ast-grep-client.js +8 -1
  4. package/clients/ast-grep-client.ts +9 -1
  5. package/clients/biome-client.js +51 -38
  6. package/clients/biome-client.ts +60 -58
  7. package/clients/dependency-checker.js +30 -1
  8. package/clients/dependency-checker.ts +35 -1
  9. package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
  10. package/clients/dispatch/bus-dispatcher.js +15 -14
  11. package/clients/dispatch/bus-dispatcher.ts +32 -25
  12. package/clients/dispatch/dispatcher.js +18 -25
  13. package/clients/dispatch/dispatcher.test.ts +2 -1
  14. package/clients/dispatch/dispatcher.ts +17 -28
  15. package/clients/dispatch/plan.js +77 -32
  16. package/clients/dispatch/plan.ts +78 -32
  17. package/clients/dispatch/runners/ast-grep-napi.js +36 -376
  18. package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
  19. package/clients/dispatch/runners/index.js +8 -4
  20. package/clients/dispatch/runners/index.ts +8 -4
  21. package/clients/dispatch/runners/lsp.js +65 -0
  22. package/clients/dispatch/runners/lsp.ts +125 -0
  23. package/clients/dispatch/runners/oxlint.js +2 -2
  24. package/clients/dispatch/runners/oxlint.ts +2 -2
  25. package/clients/dispatch/runners/pyright.js +24 -8
  26. package/clients/dispatch/runners/pyright.ts +28 -14
  27. package/clients/dispatch/runners/rust-clippy.js +2 -2
  28. package/clients/dispatch/runners/rust-clippy.ts +2 -4
  29. package/clients/dispatch/runners/tree-sitter.js +14 -2
  30. package/clients/dispatch/runners/tree-sitter.ts +15 -2
  31. package/clients/dispatch/runners/ts-lsp.js +3 -3
  32. package/clients/dispatch/runners/ts-lsp.ts +8 -5
  33. package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
  34. package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
  35. package/clients/dispatch/types.js +3 -0
  36. package/clients/dispatch/types.ts +3 -0
  37. package/clients/formatters.js +67 -14
  38. package/clients/formatters.ts +68 -15
  39. package/clients/installer/index.js +78 -10
  40. package/clients/installer/index.ts +519 -426
  41. package/clients/jscpd-client.js +28 -0
  42. package/clients/jscpd-client.ts +41 -3
  43. package/clients/knip-client.js +30 -1
  44. package/clients/knip-client.ts +34 -2
  45. package/clients/lsp/__tests__/client.test.ts +64 -41
  46. package/clients/lsp/__tests__/config.test.ts +25 -17
  47. package/clients/lsp/__tests__/launch.test.ts +108 -43
  48. package/clients/lsp/__tests__/service.test.ts +76 -48
  49. package/clients/lsp/client.js +87 -2
  50. package/clients/lsp/client.ts +150 -6
  51. package/clients/lsp/config.js +8 -11
  52. package/clients/lsp/config.ts +24 -21
  53. package/clients/lsp/index.js +69 -0
  54. package/clients/lsp/index.ts +82 -0
  55. package/clients/lsp/interactive-install.js +19 -8
  56. package/clients/lsp/interactive-install.ts +52 -27
  57. package/clients/lsp/launch.js +182 -32
  58. package/clients/lsp/launch.ts +241 -38
  59. package/clients/lsp/path-utils.js +3 -46
  60. package/clients/lsp/path-utils.ts +11 -51
  61. package/clients/lsp/server.js +93 -71
  62. package/clients/lsp/server.ts +173 -131
  63. package/clients/path-utils.js +142 -0
  64. package/clients/path-utils.ts +153 -0
  65. package/clients/ruff-client.js +33 -4
  66. package/clients/ruff-client.ts +44 -13
  67. package/clients/safe-spawn.js +3 -1
  68. package/clients/safe-spawn.ts +3 -1
  69. package/clients/services/effect-integration.js +11 -7
  70. package/clients/services/effect-integration.ts +34 -26
  71. package/clients/sg-runner.js +51 -9
  72. package/clients/sg-runner.ts +58 -15
  73. package/clients/tree-sitter-client.js +12 -0
  74. package/clients/tree-sitter-client.ts +12 -0
  75. package/clients/typescript-client.js +6 -2
  76. package/clients/typescript-client.ts +9 -2
  77. package/commands/booboo.js +2 -4
  78. package/commands/booboo.ts +2 -4
  79. package/index.ts +377 -93
  80. package/package.json +2 -1
  81. package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
  82. package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
  83. package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
  84. package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
  85. package/tsconfig.json +1 -1
  86. package/clients/__tests__/file-time.test.js +0 -216
  87. package/clients/__tests__/format-service.test.js +0 -245
  88. package/clients/__tests__/formatters.test.js +0 -271
  89. package/clients/agent-behavior-client.test.js +0 -94
  90. package/clients/ast-grep-client.test.js +0 -129
  91. package/clients/ast-grep-client.test.ts +0 -155
  92. package/clients/biome-client.test.js +0 -144
  93. package/clients/cache-manager.test.js +0 -197
  94. package/clients/complexity-client.test.js +0 -234
  95. package/clients/dependency-checker.test.js +0 -60
  96. package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
  97. package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
  98. package/clients/dispatch/dispatcher.edge.test.js +0 -82
  99. package/clients/dispatch/dispatcher.format.test.js +0 -46
  100. package/clients/dispatch/dispatcher.inline.test.js +0 -74
  101. package/clients/dispatch/dispatcher.test.js +0 -115
  102. package/clients/dispatch/runners/architect.test.js +0 -138
  103. package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
  104. package/clients/dispatch/runners/oxlint.test.js +0 -230
  105. package/clients/dispatch/runners/pyright.test.js +0 -98
  106. package/clients/dispatch/runners/python-slop.test.js +0 -203
  107. package/clients/dispatch/runners/scan_codebase.test.js +0 -89
  108. package/clients/dispatch/runners/shellcheck.test.js +0 -98
  109. package/clients/dispatch/runners/spellcheck.test.js +0 -158
  110. package/clients/dispatch/runners/ts-slop.test.js +0 -180
  111. package/clients/dispatch/runners/ts-slop.test.ts +0 -230
  112. package/clients/dogfood.test.js +0 -201
  113. package/clients/file-kinds.test.js +0 -169
  114. package/clients/go-client.test.js +0 -127
  115. package/clients/jscpd-client.test.js +0 -127
  116. package/clients/knip-client.test.js +0 -112
  117. package/clients/lsp/__tests__/client.test.js +0 -325
  118. package/clients/lsp/__tests__/config.test.js +0 -166
  119. package/clients/lsp/__tests__/error-recovery.test.js +0 -213
  120. package/clients/lsp/__tests__/integration.test.js +0 -127
  121. package/clients/lsp/__tests__/launch.test.js +0 -260
  122. package/clients/lsp/__tests__/server.test.js +0 -259
  123. package/clients/lsp/__tests__/service.test.js +0 -417
  124. package/clients/metrics-client.test.js +0 -141
  125. package/clients/ruff-client.test.js +0 -132
  126. package/clients/rust-client.test.js +0 -108
  127. package/clients/sanitize.test.js +0 -177
  128. package/clients/secrets-scanner.test.js +0 -100
  129. package/clients/services/__tests__/effect-integration.test.js +0 -86
  130. package/clients/test-runner-client.test.js +0 -192
  131. package/clients/todo-scanner.test.js +0 -301
  132. package/clients/type-coverage-client.test.js +0 -105
  133. package/clients/typescript-client.codefix.test.js +0 -157
  134. package/clients/typescript-client.test.js +0 -105
  135. package/commands/clients/ast-grep-client.js +0 -250
  136. package/commands/clients/ast-grep-parser.js +0 -86
  137. package/commands/clients/ast-grep-rule-manager.js +0 -91
  138. package/commands/clients/ast-grep-types.js +0 -9
  139. package/commands/clients/biome-client.js +0 -380
  140. package/commands/clients/complexity-client.js +0 -667
  141. package/commands/clients/file-kinds.js +0 -177
  142. package/commands/clients/file-utils.js +0 -40
  143. package/commands/clients/jscpd-client.js +0 -169
  144. package/commands/clients/knip-client.js +0 -211
  145. package/commands/clients/ruff-client.js +0 -297
  146. package/commands/clients/safe-spawn.js +0 -88
  147. package/commands/clients/scan-utils.js +0 -83
  148. package/commands/clients/sg-runner.js +0 -190
  149. package/commands/clients/types.js +0 -11
  150. package/commands/clients/typescript-client.js +0 -505
  151. package/commands/rate.test.js +0 -119
  152. package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
  153. package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
  154. package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
@@ -81,7 +81,7 @@ export const biomeFormatter = {
81
81
  const found = await findUp(configs, cwd);
82
82
  if (found.length > 0)
83
83
  return true;
84
- // Also check if biome is in package.json devDependencies
84
+ // Check if biome is in package.json devDependencies
85
85
  const pkgPath = path.join(cwd, "package.json");
86
86
  if (await fileExists(pkgPath)) {
87
87
  const pkg = (await readJson(pkgPath));
@@ -177,18 +177,9 @@ export const ruffFormatter = {
177
177
  return true;
178
178
  }
179
179
  }
180
- // Check if ruff binary available and no other Python formatter detected
181
- const hasRuff = (await which("ruff")) !== null;
182
- if (hasRuff) {
183
- // Prefer ruff if no black config found
184
- const blackFound = await findUp(["pyproject.toml"], cwd);
185
- for (const p of blackFound) {
186
- const content = await fs.readFile(p, "utf-8");
187
- if (content.includes("[tool.black]"))
188
- return false; // Prefer black if configured
189
- }
190
- return true;
191
- }
180
+ // Only enable if the project explicitly uses ruff (config or deps).
181
+ // Do NOT fall back to "ruff binary is installed" that would format
182
+ // projects that never asked for ruff.
192
183
  return false;
193
184
  },
194
185
  };
@@ -308,6 +299,53 @@ export const ktlintFormatter = {
308
299
  return (await which("ktlint")) !== null;
309
300
  },
310
301
  };
302
+ export const rubocopFormatter = {
303
+ name: "rubocop",
304
+ command: ["rubocop", "-a", "--no-color", "$FILE"],
305
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
306
+ async detect(cwd) {
307
+ // Only run if project has explicit RuboCop config
308
+ const configs = [".rubocop.yml", ".rubocop.yaml"];
309
+ const found = await findUp(configs, cwd);
310
+ if (found.length > 0)
311
+ return (await which("rubocop")) !== null;
312
+ // Or rubocop in Gemfile
313
+ const gemfile = path.join(cwd, "Gemfile");
314
+ if (await fileExists(gemfile)) {
315
+ const content = await fs.readFile(gemfile, "utf-8");
316
+ if (content.includes("rubocop"))
317
+ return (await which("rubocop")) !== null;
318
+ }
319
+ return false;
320
+ },
321
+ };
322
+ export const standardrbFormatter = {
323
+ name: "standardrb",
324
+ command: ["standardrb", "--fix", "$FILE"],
325
+ extensions: [".rb", ".rake"],
326
+ async detect(cwd) {
327
+ // standardrb is only used if explicitly in Gemfile (no config file — it is the config)
328
+ const gemfile = path.join(cwd, "Gemfile");
329
+ if (await fileExists(gemfile)) {
330
+ const content = await fs.readFile(gemfile, "utf-8");
331
+ if (content.includes("standard"))
332
+ return (await which("standardrb")) !== null;
333
+ }
334
+ return false;
335
+ },
336
+ };
337
+ export const gleamFormatter = {
338
+ name: "gleam",
339
+ command: ["gleam", "format", "$FILE"],
340
+ extensions: [".gleam"],
341
+ async detect(cwd) {
342
+ // Present if gleam.toml exists (any Gleam project)
343
+ const found = await findUp(["gleam.toml"], cwd);
344
+ if (found.length > 0)
345
+ return (await which("gleam")) !== null;
346
+ return false;
347
+ },
348
+ };
311
349
  export const terraformFormatter = {
312
350
  name: "terraform",
313
351
  command: ["terraform", "fmt", "$FILE"],
@@ -333,6 +371,9 @@ const ALL_FORMATTERS = [
333
371
  clangFormatFormatter,
334
372
  ktlintFormatter,
335
373
  terraformFormatter,
374
+ rubocopFormatter,
375
+ standardrbFormatter,
376
+ gleamFormatter,
336
377
  ];
337
378
  // Cache for detection results - stores array of enabled formatter names per cwd+ext
338
379
  const detectionCache = new Map();
@@ -402,9 +443,21 @@ export function clearFormatterCache() {
402
443
  export async function formatFile(filePath, formatter) {
403
444
  try {
404
445
  const absolutePath = path.resolve(filePath);
446
+ const cwd = path.dirname(absolutePath);
405
447
  const contentBefore = await fs.readFile(absolutePath, "utf-8");
406
448
  // Replace $FILE placeholder
407
- const cmd = formatter.command.map((c) => c.replace("$FILE", absolutePath));
449
+ let cmd = formatter.command.map((c) => c.replace("$FILE", absolutePath));
450
+ // OPTIMIZATION: Use local biome binary instead of npx if available
451
+ if (formatter.name === "biome" && cmd[0] === "npx") {
452
+ const localBiome = path.join(cwd, "node_modules", ".bin", "biome");
453
+ const localBiomeWin = path.join(cwd, "node_modules", ".bin", "biome.cmd");
454
+ if (await fileExists(localBiome)) {
455
+ cmd = [localBiome, ...cmd.slice(2)]; // Replace "npx" "@biomejs/biome" with local path
456
+ }
457
+ else if (await fileExists(localBiomeWin)) {
458
+ cmd = [localBiomeWin, ...cmd.slice(2)];
459
+ }
460
+ }
408
461
  // Run formatter
409
462
  const result = safeSpawn(cmd[0], cmd.slice(1), { timeout: 15000 });
410
463
  if (result.error) {
@@ -111,7 +111,7 @@ export const biomeFormatter: FormatterInfo = {
111
111
  const found = await findUp(configs, cwd);
112
112
  if (found.length > 0) return true;
113
113
 
114
- // Also check if biome is in package.json devDependencies
114
+ // Check if biome is in package.json devDependencies
115
115
  const pkgPath = path.join(cwd, "package.json");
116
116
  if (await fileExists(pkgPath)) {
117
117
  const pkg = (await readJson(pkgPath)) as {
@@ -215,18 +215,9 @@ export const ruffFormatter: FormatterInfo = {
215
215
  }
216
216
  }
217
217
 
218
- // Check if ruff binary available and no other Python formatter detected
219
- const hasRuff = (await which("ruff")) !== null;
220
- if (hasRuff) {
221
- // Prefer ruff if no black config found
222
- const blackFound = await findUp(["pyproject.toml"], cwd);
223
- for (const p of blackFound) {
224
- const content = await fs.readFile(p, "utf-8");
225
- if (content.includes("[tool.black]")) return false; // Prefer black if configured
226
- }
227
- return true;
228
- }
229
-
218
+ // Only enable if the project explicitly uses ruff (config or deps).
219
+ // Do NOT fall back to "ruff binary is installed" that would format
220
+ // projects that never asked for ruff.
230
221
  return false;
231
222
  },
232
223
  };
@@ -356,6 +347,53 @@ export const ktlintFormatter: FormatterInfo = {
356
347
  },
357
348
  };
358
349
 
350
+ export const rubocopFormatter: FormatterInfo = {
351
+ name: "rubocop",
352
+ command: ["rubocop", "-a", "--no-color", "$FILE"],
353
+ extensions: [".rb", ".rake", ".gemspec", ".ru"],
354
+ async detect(cwd: string) {
355
+ // Only run if project has explicit RuboCop config
356
+ const configs = [".rubocop.yml", ".rubocop.yaml"];
357
+ const found = await findUp(configs, cwd);
358
+ if (found.length > 0) return (await which("rubocop")) !== null;
359
+ // Or rubocop in Gemfile
360
+ const gemfile = path.join(cwd, "Gemfile");
361
+ if (await fileExists(gemfile)) {
362
+ const content = await fs.readFile(gemfile, "utf-8");
363
+ if (content.includes("rubocop")) return (await which("rubocop")) !== null;
364
+ }
365
+ return false;
366
+ },
367
+ };
368
+
369
+ export const standardrbFormatter: FormatterInfo = {
370
+ name: "standardrb",
371
+ command: ["standardrb", "--fix", "$FILE"],
372
+ extensions: [".rb", ".rake"],
373
+ async detect(cwd: string) {
374
+ // standardrb is only used if explicitly in Gemfile (no config file — it is the config)
375
+ const gemfile = path.join(cwd, "Gemfile");
376
+ if (await fileExists(gemfile)) {
377
+ const content = await fs.readFile(gemfile, "utf-8");
378
+ if (content.includes("standard"))
379
+ return (await which("standardrb")) !== null;
380
+ }
381
+ return false;
382
+ },
383
+ };
384
+
385
+ export const gleamFormatter: FormatterInfo = {
386
+ name: "gleam",
387
+ command: ["gleam", "format", "$FILE"],
388
+ extensions: [".gleam"],
389
+ async detect(cwd: string) {
390
+ // Present if gleam.toml exists (any Gleam project)
391
+ const found = await findUp(["gleam.toml"], cwd);
392
+ if (found.length > 0) return (await which("gleam")) !== null;
393
+ return false;
394
+ },
395
+ };
396
+
359
397
  export const terraformFormatter: FormatterInfo = {
360
398
  name: "terraform",
361
399
  command: ["terraform", "fmt", "$FILE"],
@@ -383,6 +421,9 @@ const ALL_FORMATTERS: FormatterInfo[] = [
383
421
  clangFormatFormatter,
384
422
  ktlintFormatter,
385
423
  terraformFormatter,
424
+ rubocopFormatter,
425
+ standardrbFormatter,
426
+ gleamFormatter,
386
427
  ];
387
428
 
388
429
  // Cache for detection results - stores array of enabled formatter names per cwd+ext
@@ -470,13 +511,25 @@ export async function formatFile(
470
511
  ): Promise<FormatterResult> {
471
512
  try {
472
513
  const absolutePath = path.resolve(filePath);
514
+ const cwd = path.dirname(absolutePath);
473
515
  const contentBefore = await fs.readFile(absolutePath, "utf-8");
474
516
 
475
517
  // Replace $FILE placeholder
476
- const cmd = formatter.command.map((c) => c.replace("$FILE", absolutePath));
518
+ let cmd = formatter.command.map((c) => c.replace("$FILE", absolutePath));
519
+
520
+ // OPTIMIZATION: Use local biome binary instead of npx if available
521
+ if (formatter.name === "biome" && cmd[0] === "npx") {
522
+ const localBiome = path.join(cwd, "node_modules", ".bin", "biome");
523
+ const localBiomeWin = path.join(cwd, "node_modules", ".bin", "biome.cmd");
524
+ if (await fileExists(localBiome)) {
525
+ cmd = [localBiome, ...cmd.slice(2)]; // Replace "npx" "@biomejs/biome" with local path
526
+ } else if (await fileExists(localBiomeWin)) {
527
+ cmd = [localBiomeWin, ...cmd.slice(2)];
528
+ }
529
+ }
477
530
 
478
531
  // Run formatter
479
- const result = safeSpawn(cmd[0], cmd.slice(1), { timeout: 15000 });
532
+ const result = safeSpawn(cmd[0], cmd.slice(1), { timeout: 15000, cwd });
480
533
 
481
534
  if (result.error) {
482
535
  return {
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Auto-Installation System for pi-lens
3
3
  *
4
- * Minimal auto-install: Only TypeScript and Python ecosystems.
4
+ * Minimal auto-install: Core tools that run frequently.
5
5
  * Other tools require manual installation with clear instructions.
6
6
  *
7
- * Auto-install (4 tools):
7
+ * Auto-install (8 tools):
8
8
  * - typescript-language-server (TypeScript LSP)
9
9
  * - pyright (Python LSP)
10
10
  * - ruff (Python linting)
11
11
  * - @biomejs/biome (JS/TS/JSON linting/formatting)
12
+ * - madge (circular dependency detection)
13
+ * - jscpd (duplicate code detection)
14
+ * - @ast-grep/cli (structural code search)
15
+ * - knip (dead code detection)
12
16
  *
13
17
  * Manual install required (25+ tools):
14
18
  * - yaml-language-server: npm install -g yaml-language-server
@@ -18,7 +22,6 @@
18
22
  * - vscode-eslint-language-server: npm install -g vscode-langservers-extracted
19
23
  * - vscode-css-languageserver: npm install -g vscode-langservers-extracted
20
24
  * - @prisma/language-server: npm install -g @prisma/language-server
21
- * - @ast-grep/cli: npm install -g @ast-grep/cli
22
25
  * - dockerfile-language-server: npm install -g dockerfile-language-server-nodejs
23
26
  * - @vue/language-server: npm install -g @vue/language-server
24
27
  * - And all language-specific servers (gopls, rust-analyzer, etc.)
@@ -72,6 +75,65 @@ const TOOLS = [
72
75
  packageName: "@biomejs/biome",
73
76
  binaryName: "biome",
74
77
  },
78
+ // Analysis tools (run at session start / turn end)
79
+ {
80
+ id: "madge",
81
+ name: "Madge",
82
+ checkCommand: "madge",
83
+ checkArgs: ["--version"],
84
+ installStrategy: "npm",
85
+ packageName: "madge",
86
+ binaryName: "madge",
87
+ },
88
+ {
89
+ id: "jscpd",
90
+ name: "jscpd",
91
+ checkCommand: "jscpd",
92
+ checkArgs: ["--version"],
93
+ installStrategy: "npm",
94
+ packageName: "jscpd",
95
+ binaryName: "jscpd",
96
+ },
97
+ // Structural search and dead code detection
98
+ {
99
+ id: "ast-grep",
100
+ name: "ast-grep CLI",
101
+ checkCommand: "sg",
102
+ checkArgs: ["--version"],
103
+ installStrategy: "npm",
104
+ packageName: "@ast-grep/cli",
105
+ binaryName: "sg",
106
+ },
107
+ {
108
+ id: "knip",
109
+ name: "Knip",
110
+ checkCommand: "knip",
111
+ checkArgs: ["--version"],
112
+ installStrategy: "npm",
113
+ packageName: "knip",
114
+ binaryName: "knip",
115
+ },
116
+ // GitHub release LSP servers
117
+ {
118
+ id: "clangd",
119
+ name: "clangd",
120
+ checkCommand: "clangd",
121
+ checkArgs: ["--version"],
122
+ installStrategy: "github",
123
+ binaryName: process.platform === "win32" ? "clangd.exe" : "clangd",
124
+ githubRepo: "clangd/clangd",
125
+ },
126
+ {
127
+ id: "lua-language-server",
128
+ name: "Lua Language Server",
129
+ checkCommand: "lua-language-server",
130
+ checkArgs: ["--version"],
131
+ installStrategy: "github",
132
+ binaryName: process.platform === "win32"
133
+ ? "bin/lua-language-server.exe"
134
+ : "bin/lua-language-server",
135
+ githubRepo: "LuaLS/lua-language-server",
136
+ },
75
137
  ];
76
138
  // --- Check Functions ---
77
139
  /**
@@ -142,9 +204,7 @@ async function verifyToolBinary(binPath) {
142
204
  return new Promise((resolve) => {
143
205
  // Add .cmd extension on Windows for the actual binary
144
206
  const isWindows = process.platform === "win32";
145
- const execPath = isWindows && !binPath.endsWith(".cmd")
146
- ? `${binPath}.cmd`
147
- : binPath;
207
+ const execPath = isWindows && !binPath.endsWith(".cmd") ? `${binPath}.cmd` : binPath;
148
208
  const proc = spawn(execPath, ["--version"], {
149
209
  timeout: 10000, // 10 second timeout for verification
150
210
  stdio: ["ignore", "pipe", "pipe"],
@@ -190,8 +250,12 @@ async function installNpmTool(packageName, binaryName) {
190
250
  // Install via npm or bun (use .cmd on Windows)
191
251
  const isWindows = process.platform === "win32";
192
252
  const pm = process.env.BUN_INSTALL
193
- ? isWindows ? "bun.exe" : "bun"
194
- : isWindows ? "npm.cmd" : "npm";
253
+ ? isWindows
254
+ ? "bun.exe"
255
+ : "bun"
256
+ : isWindows
257
+ ? "npm.cmd"
258
+ : "npm";
195
259
  const proc = spawn(pm, ["install", packageName], {
196
260
  cwd: TOOLS_DIR,
197
261
  stdio: ["ignore", "pipe", "pipe"],
@@ -208,7 +272,9 @@ async function installNpmTool(packageName, binaryName) {
208
272
  try {
209
273
  await fs.chmod(binPath, 0o755);
210
274
  }
211
- catch { /* ignore */ }
275
+ catch {
276
+ /* ignore */
277
+ }
212
278
  }
213
279
  // NEW: Verify the binary actually works before returning
214
280
  console.error(`[auto-install] Verifying ${binaryName}...`);
@@ -225,7 +291,9 @@ async function installNpmTool(packageName, binaryName) {
225
291
  await fs.rm(`${binPath}.ps1`, { force: true });
226
292
  }
227
293
  }
228
- catch { /* ignore cleanup errors */ }
294
+ catch {
295
+ /* ignore cleanup errors */
296
+ }
229
297
  resolve(undefined);
230
298
  return;
231
299
  }