@adhdev/daemon-core 0.9.47 → 0.9.49

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.
@@ -1,4 +1,4 @@
1
- import { execFileSync } from 'child_process';
1
+ import { execFileSync, type ExecFileSyncOptions } from 'child_process';
2
2
  import { spawn } from 'child_process';
3
3
  import * as fs from 'fs';
4
4
  import * as os from 'os';
@@ -17,16 +17,21 @@ export interface DaemonUpgradeHelperPayload {
17
17
 
18
18
  export interface CurrentGlobalInstallSurface {
19
19
  npmExecutable: string;
20
+ npmArgsPrefix?: string[];
20
21
  packageRoot: string | null;
21
22
  installPrefix: string | null;
23
+ execOptions?: NpmExecOptions;
22
24
  }
23
25
 
24
26
  export interface PinnedGlobalInstallCommand {
25
27
  command: string;
26
28
  args: string[];
27
29
  surface: CurrentGlobalInstallSurface;
30
+ execOptions: NpmExecOptions;
28
31
  }
29
32
 
33
+ export type NpmExecOptions = { shell: boolean; windowsHide?: boolean };
34
+
30
35
  function getUpgradeLogPath(): string {
31
36
  const home = os.homedir();
32
37
  const dir = path.join(home, '.adhdev');
@@ -43,18 +48,32 @@ function appendUpgradeLog(message: string): void {
43
48
  }
44
49
  }
45
50
 
46
- function resolveSiblingNpmExecutable(nodeExecutable: string): string {
51
+ function resolveSiblingNpmInvocation(nodeExecutable: string, platform: NodeJS.Platform = process.platform): {
52
+ executable: string;
53
+ argsPrefix: string[];
54
+ execOptions: NpmExecOptions;
55
+ } {
47
56
  const binDir = path.dirname(nodeExecutable);
48
- const candidates = process.platform === 'win32'
49
- ? ['npm.cmd', 'npm.exe', 'npm']
50
- : ['npm'];
51
- for (const candidate of candidates) {
57
+ if (platform === 'win32') {
58
+ const npmCliPath = path.join(binDir, 'node_modules', 'npm', 'bin', 'npm-cli.js');
59
+ if (fs.existsSync(npmCliPath)) {
60
+ return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions: getNpmExecOptions(platform) };
61
+ }
62
+ for (const candidate of ['npm.exe', 'npm']) {
63
+ const candidatePath = path.join(binDir, candidate);
64
+ if (fs.existsSync(candidatePath)) {
65
+ return { executable: candidatePath, argsPrefix: [], execOptions: getNpmExecOptions(platform) };
66
+ }
67
+ }
68
+ return { executable: nodeExecutable, argsPrefix: [npmCliPath], execOptions: getNpmExecOptions(platform) };
69
+ }
70
+ for (const candidate of ['npm']) {
52
71
  const candidatePath = path.join(binDir, candidate);
53
72
  if (fs.existsSync(candidatePath)) {
54
- return candidatePath;
73
+ return { executable: candidatePath, argsPrefix: [], execOptions: getNpmExecOptions(platform) };
55
74
  }
56
75
  }
57
- return 'npm';
76
+ return { executable: 'npm', argsPrefix: [], execOptions: getNpmExecOptions(platform) };
58
77
  }
59
78
 
60
79
  function findCurrentPackageRoot(currentCliPath: string | undefined, packageName: string): string | null {
@@ -117,12 +136,16 @@ export function resolveCurrentGlobalInstallSurface(options: {
117
136
  packageName: string;
118
137
  currentCliPath?: string;
119
138
  nodeExecutable?: string;
139
+ platform?: NodeJS.Platform;
120
140
  }): CurrentGlobalInstallSurface {
121
141
  const packageRoot = findCurrentPackageRoot(options.currentCliPath || process.argv[1], options.packageName);
142
+ const npmInvocation = resolveSiblingNpmInvocation(options.nodeExecutable || process.execPath, options.platform);
122
143
  return {
123
- npmExecutable: resolveSiblingNpmExecutable(options.nodeExecutable || process.execPath),
144
+ npmExecutable: npmInvocation.executable,
145
+ npmArgsPrefix: npmInvocation.argsPrefix,
124
146
  packageRoot,
125
147
  installPrefix: packageRoot ? resolveInstallPrefixFromPackageRoot(packageRoot, options.packageName) : null,
148
+ execOptions: npmInvocation.execOptions,
126
149
  };
127
150
  }
128
151
 
@@ -131,9 +154,10 @@ export function buildPinnedGlobalInstallCommand(options: {
131
154
  targetVersion: string;
132
155
  currentCliPath?: string;
133
156
  nodeExecutable?: string;
157
+ platform?: NodeJS.Platform;
134
158
  }): PinnedGlobalInstallCommand {
135
159
  const surface = resolveCurrentGlobalInstallSurface(options);
136
- const args = ['install', '-g', `${options.packageName}@${options.targetVersion || 'latest'}`, '--force'];
160
+ const args = [...(surface.npmArgsPrefix || []), 'install', '-g', `${options.packageName}@${options.targetVersion || 'latest'}`, '--force'];
137
161
  if (surface.installPrefix) {
138
162
  args.push('--prefix', surface.installPrefix);
139
163
  }
@@ -141,17 +165,38 @@ export function buildPinnedGlobalInstallCommand(options: {
141
165
  command: surface.npmExecutable,
142
166
  args,
143
167
  surface,
168
+ execOptions: surface.execOptions || getNpmExecOptions(options.platform),
144
169
  };
145
170
  }
146
171
 
147
- function getNpmExecOptions(): { shell: boolean } {
148
- return { shell: process.platform === 'win32' };
172
+ export function getNpmExecOptions(platform: NodeJS.Platform = process.platform): NpmExecOptions {
173
+ if (platform === 'win32') {
174
+ return { shell: false, windowsHide: true };
175
+ }
176
+ return { shell: false };
177
+ }
178
+
179
+ export function execNpmCommandSync(
180
+ args: string[],
181
+ options: ExecFileSyncOptions = {},
182
+ surface?: Pick<CurrentGlobalInstallSurface, 'npmExecutable' | 'npmArgsPrefix' | 'execOptions'>,
183
+ ): Buffer | string {
184
+ const execOptions = surface?.execOptions || getNpmExecOptions();
185
+ return execFileSync(
186
+ surface?.npmExecutable || 'npm',
187
+ [...(surface?.npmArgsPrefix || []), ...args],
188
+ {
189
+ ...options,
190
+ ...execOptions,
191
+ ...(process.platform === 'win32' ? { windowsHide: true } : {}),
192
+ },
193
+ );
149
194
  }
150
195
 
151
196
  function killPid(pid: number): boolean {
152
197
  try {
153
198
  if (process.platform === 'win32') {
154
- execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
199
+ execFileSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
155
200
  } else {
156
201
  process.kill(pid, 'SIGTERM');
157
202
  }
@@ -170,7 +215,7 @@ function getWindowsProcessCommandLine(pid: number): string | null {
170
215
  '-ExecutionPolicy', 'Bypass',
171
216
  '-Command',
172
217
  `(Get-CimInstance Win32_Process -Filter "${pidFilter}").CommandLine`,
173
- ], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
218
+ ], { encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }).trim();
174
219
  if (psOut) return psOut;
175
220
  } catch {
176
221
  // fall through to wmic fallback
@@ -179,7 +224,7 @@ function getWindowsProcessCommandLine(pid: number): string | null {
179
224
  try {
180
225
  const wmicOut = execFileSync('wmic', [
181
226
  'process', 'where', pidFilter, 'get', 'CommandLine',
182
- ], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
227
+ ], { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }).trim();
183
228
  if (wmicOut) return wmicOut;
184
229
  } catch {
185
230
  // noop
@@ -249,12 +294,11 @@ function removeDaemonPidFile(): void {
249
294
  }
250
295
 
251
296
  function cleanupStaleGlobalInstallDirs(pkgName: string, surface: CurrentGlobalInstallSurface): void {
252
- const npmExecOpts = getNpmExecOptions();
253
297
  const prefixArgs = surface.installPrefix ? ['--prefix', surface.installPrefix] : [];
254
- const npmRoot = execFileSync(surface.npmExecutable, ['root', '-g', ...prefixArgs], { encoding: 'utf8', ...npmExecOpts }).trim();
298
+ const npmRoot = String(execNpmCommandSync(['root', '-g', ...prefixArgs], { encoding: 'utf8' }, surface)).trim();
255
299
  if (!npmRoot) return;
256
300
  const npmPrefix = surface.installPrefix
257
- || execFileSync(surface.npmExecutable, ['prefix', '-g', ...prefixArgs], { encoding: 'utf8', ...npmExecOpts }).trim();
301
+ || String(execNpmCommandSync(['prefix', '-g', ...prefixArgs], { encoding: 'utf8' }, surface)).trim();
258
302
  const binDir = process.platform === 'win32' ? npmPrefix : path.join(npmPrefix, 'bin');
259
303
  const packageBaseName = pkgName.startsWith('@') ? pkgName.split('/')[1] : pkgName;
260
304
  const binNames = new Set<string>([packageBaseName]);
@@ -331,7 +375,7 @@ async function runDaemonUpgradeHelper(payload: DaemonUpgradeHelperPayload): Prom
331
375
  encoding: 'utf8',
332
376
  stdio: 'pipe',
333
377
  maxBuffer: 20 * 1024 * 1024,
334
- ...getNpmExecOptions(),
378
+ ...installCommand.execOptions,
335
379
  },
336
380
  );
337
381
  if (installOutput.trim()) {
@@ -60,7 +60,11 @@ function resolveCommandPath(command: string): string | null {
60
60
  /** Run a shell command with timeout, returning stdout or null on failure */
61
61
  function execAsync(cmd: string, timeoutMs = 5000): Promise<string | null> {
62
62
  return new Promise((resolve) => {
63
- const child = exec(cmd, { encoding: 'utf-8', timeout: timeoutMs }, (err, stdout) => {
63
+ const child = exec(cmd, {
64
+ encoding: 'utf-8',
65
+ timeout: timeoutMs,
66
+ ...(process.platform === 'win32' ? { windowsHide: true } : {}),
67
+ }, (err, stdout) => {
64
68
  if (err || !stdout?.trim()) {
65
69
  resolve(null);
66
70
  } else {
package/src/index.d.ts CHANGED
@@ -41,8 +41,8 @@ export { DaemonCommandHandler } from './commands/handler.js';
41
41
  export type { CommandResult, CommandContext } from './commands/handler.js';
42
42
  export { DaemonCommandRouter } from './commands/router.js';
43
43
  export type { CommandRouterDeps, CommandRouterResult } from './commands/router.js';
44
- export { maybeRunDaemonUpgradeHelperFromEnv, spawnDetachedDaemonUpgradeHelper, resolveCurrentGlobalInstallSurface, buildPinnedGlobalInstallCommand } from './commands/upgrade-helper.js';
45
- export type { DaemonUpgradeHelperPayload, CurrentGlobalInstallSurface, PinnedGlobalInstallCommand } from './commands/upgrade-helper.js';
44
+ export { maybeRunDaemonUpgradeHelperFromEnv, spawnDetachedDaemonUpgradeHelper, resolveCurrentGlobalInstallSurface, buildPinnedGlobalInstallCommand, execNpmCommandSync, getNpmExecOptions } from './commands/upgrade-helper.js';
45
+ export type { DaemonUpgradeHelperPayload, CurrentGlobalInstallSurface, PinnedGlobalInstallCommand, NpmExecOptions } from './commands/upgrade-helper.js';
46
46
  export { DaemonStatusReporter } from './status/reporter.js';
47
47
  export { buildSessionEntries, findCdpManager, hasCdpManager, isCdpConnected } from './status/builders.js';
48
48
  export { buildStatusSnapshot, buildMachineInfo } from './status/snapshot.js';
package/src/index.ts CHANGED
@@ -140,15 +140,18 @@ export type { CommandResult, CommandContext } from './commands/handler.js';
140
140
  export { DaemonCommandRouter } from './commands/router.js';
141
141
  export type { CommandRouterDeps, CommandRouterResult } from './commands/router.js';
142
142
  export {
143
- maybeRunDaemonUpgradeHelperFromEnv,
144
- spawnDetachedDaemonUpgradeHelper,
145
- resolveCurrentGlobalInstallSurface,
146
- buildPinnedGlobalInstallCommand,
143
+ maybeRunDaemonUpgradeHelperFromEnv,
144
+ spawnDetachedDaemonUpgradeHelper,
145
+ resolveCurrentGlobalInstallSurface,
146
+ buildPinnedGlobalInstallCommand,
147
+ execNpmCommandSync,
148
+ getNpmExecOptions,
147
149
  } from './commands/upgrade-helper.js';
148
150
  export type {
149
- DaemonUpgradeHelperPayload,
150
- CurrentGlobalInstallSurface,
151
- PinnedGlobalInstallCommand,
151
+ DaemonUpgradeHelperPayload,
152
+ CurrentGlobalInstallSurface,
153
+ PinnedGlobalInstallCommand,
154
+ NpmExecOptions,
152
155
  } from './commands/upgrade-helper.js';
153
156
 
154
157
  // ── Status ──
package/src/launch.ts CHANGED
@@ -469,7 +469,7 @@ async function launchMacOS(ide: IDEInfo, port: number, workspace?: string, newWi
469
469
 
470
470
  if (!useAppLauncher && ide.cliCommand) {
471
471
  // CLI based execute
472
- spawn(ide.cliCommand, args, { detached: true, stdio: 'ignore' }).unref();
472
+ spawn(ide.cliCommand, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref();
473
473
  } else if (appName) {
474
474
  // Fallback to `open -a` when no CLI wrapper is available or the provider prefers it.
475
475
  const openArgs = ['-a', appName, '--args', ...args];
@@ -509,7 +509,7 @@ async function launchLinux(ide: IDEInfo, port: number, workspace?: string, newWi
509
509
  if (newWindow) args.push('--new-window');
510
510
  if (workspace) args.push(workspace);
511
511
 
512
- spawn(cli, args, { detached: true, stdio: 'ignore' }).unref();
512
+ spawn(cli, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref();
513
513
  }
514
514
 
515
515
  export function getAvailableIdeIds(): string[] {
@@ -662,6 +662,7 @@ export class AcpProviderInstance implements ProviderInstance {
662
662
  env,
663
663
  stdio: ['pipe', 'pipe', 'pipe'],
664
664
  shell: spawnConfig.shell || false,
665
+ ...(process.platform === 'win32' ? { windowsHide: true } : {}),
665
666
  });
666
667
 
667
668
  // stderr → log + auth failure detection
@@ -601,7 +601,12 @@ export class CliProviderInstance implements ProviderInstance {
601
601
  if (cliCommand?.type === 'send_message' && cliCommand.text) {
602
602
  await this.adapter.sendMessage(cliCommand.text);
603
603
  } else if (cliCommand?.type === 'pty_write' && cliCommand.text) {
604
+ const enterCount = cliCommand.enterCount || 1;
604
605
  await this.adapter.writeRaw(cliCommand.text + '\r');
606
+ for (let i = 1; i < enterCount; i += 1) {
607
+ await new Promise(resolve => setTimeout(resolve, 50));
608
+ await this.adapter.writeRaw('\r');
609
+ }
605
610
  }
606
611
 
607
612
  this.applyProviderResponse(parsed.payload, { phase: 'immediate' });
@@ -18,7 +18,7 @@ export function parseCliScriptResult(result: unknown): { success: boolean; paylo
18
18
  return { success: true, payload: result }
19
19
  }
20
20
 
21
- export function getCliScriptCommand(payload: any): { type: string; text?: string } | null {
21
+ export function getCliScriptCommand(payload: any): { type: string; text?: string; enterCount?: number } | null {
22
22
  if (!payload || typeof payload !== 'object') return null
23
23
 
24
24
  if (typeof payload.sendMessage === 'string' && payload.sendMessage.trim()) {
@@ -35,5 +35,8 @@ export function getCliScriptCommand(payload: any): { type: string; text?: string
35
35
  ? command.message.trim()
36
36
  : ''
37
37
  if (!text) return null
38
- return { type: command.type, text }
38
+ const enterCount = Number.isInteger(command.enterCount) && command.enterCount > 0 && command.enterCount <= 5
39
+ ? command.enterCount
40
+ : undefined
41
+ return { type: command.type, text, ...(enterCount ? { enterCount } : {}) }
39
42
  }