@klars/agentobs 0.1.2 → 0.1.3

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.
@@ -61,14 +61,20 @@ export async function runWrapped(command, opts = {}) {
61
61
  // Resolving the .cmd/.bat shim directly keeps npm-installed CLIs working
62
62
  // (`agentobs run -- claude`) while spawn still escapes each argument.
63
63
  const executable = process.platform === 'win32' ? resolveWindowsExecutable(command[0]) : command[0];
64
- const child = spawn(executable, command.slice(1), {
65
- cwd,
66
- stdio: 'inherit',
67
- // A .cmd shim is a batch script, so it does need a shell interpreter -
68
- // but only for the shim itself, and cmd.exe applies its own escaping.
69
- shell: /\.(cmd|bat)$/i.test(executable),
70
- windowsHide: true,
71
- });
64
+ // A .cmd/.bat file is a batch script: Windows cannot exec it directly, it
65
+ // must run through cmd.exe. Spawning cmd.exe explicitly with /d /s /c and
66
+ // a quoted command line is the only form that survives BOTH a path
67
+ // containing spaces (C:\Program Files\nodejs\npm.cmd) and arguments
68
+ // containing spaces. `shell: true` cannot do this - it concatenates
69
+ // without quoting (Node DEP0190), so the path breaks at the first space.
70
+ const isBatch = process.platform === 'win32' && /\.(cmd|bat)$/i.test(executable);
71
+ const child = isBatch
72
+ ? spawn(process.env.COMSPEC ?? 'cmd.exe', ['/d', '/s', '/c', `"${quoteWindows(executable, command.slice(1))}"`], { cwd, stdio: 'inherit', windowsHide: true, windowsVerbatimArguments: true })
73
+ : spawn(executable, command.slice(1), {
74
+ cwd,
75
+ stdio: 'inherit',
76
+ windowsHide: true,
77
+ });
72
78
  const finish = (code) => {
73
79
  sink({ type: 'session_end', sessionId, exitCode: code });
74
80
  resolve(code);
@@ -99,6 +105,18 @@ export async function runWrapped(command, opts = {}) {
99
105
  }
100
106
  });
101
107
  }
108
+ /**
109
+ * Builds a cmd.exe command line, quoting each part that needs it.
110
+ *
111
+ * Used with windowsVerbatimArguments so Node passes this string through
112
+ * untouched; cmd.exe then does its own parsing. Each element is quoted only
113
+ * when it contains a space or a quote, because unnecessary quoting can change
114
+ * how some batch scripts interpret their arguments.
115
+ */
116
+ function quoteWindows(executable, args) {
117
+ const quote = (s) => /[\s"]/.test(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
118
+ return [quote(executable), ...args.map(quote)].join(' ');
119
+ }
102
120
  /**
103
121
  * Finds the real file behind a bare command name on Windows.
104
122
  *
@@ -117,14 +135,23 @@ function resolveWindowsExecutable(command) {
117
135
  const exts = (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean);
118
136
  const dirs = (process.env.PATH ?? '').split(';').filter(Boolean);
119
137
  for (const dir of dirs) {
120
- for (const ext of ['', ...exts]) {
121
- const candidate = join(dir, command + ext.toLowerCase());
122
- if (existsSync(candidate))
123
- return candidate;
138
+ // Try the PATHEXT extensions BEFORE the bare name. Node ships both `npm`
139
+ // (an extensionless shell script, for Git Bash) and `npm.cmd` in the same
140
+ // directory; the bare file exists but Windows cannot execute it, so
141
+ // checking it first resolves to something that fails with ENOENT.
142
+ for (const ext of exts) {
143
+ const lower = join(dir, command + ext.toLowerCase());
144
+ if (existsSync(lower))
145
+ return lower;
124
146
  const upper = join(dir, command + ext);
125
147
  if (existsSync(upper))
126
148
  return upper;
127
149
  }
150
+ // Only fall back to the bare name if no extension matched - covers a real
151
+ // extensionless executable, which is rare on Windows but not impossible.
152
+ const bare = join(dir, command);
153
+ if (existsSync(bare))
154
+ return bare;
128
155
  }
129
156
  return command;
130
157
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klars/agentobs",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Observability and control layer for AI coding agents - see every tool call, token, and dollar your agents spend, and stop them before they do something risky.",
5
5
  "license": "MIT",
6
6
  "author": "Klars AI",