@clipboard-health/groundcrew 4.3.0 → 4.3.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.
@@ -7,6 +7,7 @@ export interface UpgradeCliOptions {
7
7
  version: string;
8
8
  npmBin: string;
9
9
  }) => Promise<NpmRunResult>;
10
+ readInstalledVersion: (installPath: string) => string | undefined;
10
11
  }
11
12
  export interface UpgradeInstallDetails {
12
13
  installKind: InstallKind;
@@ -1 +1 @@
1
- {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACrD,UAAU,EAAE,CAAC,OAAO,EAAE;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAOD,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,CAAC,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAiD5F,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,EAAE,sBAAsB,GACnC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAsCD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAqB5B"}
1
+ {"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACrD,UAAU,EAAE,CAAC,OAAO,EAAE;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5B,oBAAoB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CACnE;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAYD,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,CAAC,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAiD5F,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,EAAE,sBAAsB,GACnC,OAAO,CAAC,IAAI,CAAC,CAkBf;AAmED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAsB5B"}
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { runCommand } from "../lib/commandRunner.js";
2
4
  import { which } from "../lib/host.js";
3
5
  import { classifyInstall, createDefaultNpmSpawner, detectInstallPath, detectIsSymlink, detectNpmRootGlobal, runNpmInstallGlobal, } from "../lib/npmGlobal.js";
@@ -54,13 +56,13 @@ export async function upgradeCli(argv, optionsInput) {
54
56
  return;
55
57
  }
56
58
  const options = await resolveOptions(optionsInput);
57
- const npmBin = await resolveGlobalNpmBin(options);
58
- if (npmBin === undefined) {
59
+ const install = await resolveGlobalInstall(options);
60
+ if (install === undefined) {
59
61
  return;
60
62
  }
61
- await runInstallAndReport(options, npmBin, parsed.version);
63
+ await runInstallAndReport(options, install, parsed.version);
62
64
  }
63
- async function resolveGlobalNpmBin(options) {
65
+ async function resolveGlobalInstall(options) {
64
66
  const install = await options.resolveInstall();
65
67
  if (install.installKind !== "global") {
66
68
  writeError(refusalMessage(install.installKind, install.installPath, options.packageName));
@@ -72,22 +74,45 @@ async function resolveGlobalNpmBin(options) {
72
74
  process.exitCode = 1;
73
75
  return undefined;
74
76
  }
75
- return install.npmBin;
77
+ return { installPath: install.installPath, npmBin: install.npmBin };
76
78
  }
77
- async function runInstallAndReport(options, npmBin, version) {
79
+ async function runInstallAndReport(options, install, version) {
80
+ const fromVersion = options.readInstalledVersion(install.installPath);
81
+ writeOutput("Upgrading crew…");
78
82
  const result = await options.runInstall({
79
83
  packageName: options.packageName,
80
84
  version,
81
- npmBin,
85
+ npmBin: install.npmBin,
82
86
  });
83
87
  if (result.exitCode === 0) {
88
+ const toVersion = options.readInstalledVersion(install.installPath);
89
+ writeOutput(formatUpgradeSuccess({ fromVersion, toVersion }));
84
90
  return;
85
91
  }
92
+ if (result.outputText.length > 0) {
93
+ process.stderr.write(result.outputText);
94
+ if (!result.outputText.endsWith("\n")) {
95
+ process.stderr.write("\n");
96
+ }
97
+ }
86
98
  if (result.sawEacces) {
87
99
  writeError("crew upgrade: install failed with EACCES (permission denied). Your global npm prefix may require elevated permissions - see https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally");
88
100
  }
89
101
  process.exitCode = result.exitCode;
90
102
  }
103
+ function formatUpgradeSuccess(versions) {
104
+ const { fromVersion, toVersion } = versions;
105
+ if (toVersion === undefined) {
106
+ return "crew upgrade complete";
107
+ }
108
+ if (fromVersion === undefined) {
109
+ return `crew is now on version ${toVersion}`;
110
+ }
111
+ if (fromVersion === toVersion) {
112
+ return `crew is already on version ${toVersion}`;
113
+ }
114
+ return `Upgraded crew from ${fromVersion} to ${toVersion}`;
115
+ }
91
116
  export async function createDefaultUpgradeCliOptions(args) {
92
117
  return {
93
118
  packageName: args.packageName,
@@ -104,7 +129,31 @@ export async function createDefaultUpgradeCliOptions(args) {
104
129
  },
105
130
  runInstall: async (options) => await runNpmInstallGlobal({
106
131
  ...options,
107
- spawner: createDefaultNpmSpawner(process.stderr),
132
+ spawner: createDefaultNpmSpawner(),
108
133
  }),
134
+ readInstalledVersion: readInstalledVersionFromDisk,
109
135
  };
110
136
  }
137
+ function readInstalledVersionFromDisk(installPath) {
138
+ let raw;
139
+ try {
140
+ raw = readFileSync(join(installPath, "package.json"), "utf8");
141
+ }
142
+ catch {
143
+ return undefined;
144
+ }
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(raw);
148
+ }
149
+ catch {
150
+ return undefined;
151
+ }
152
+ if (typeof parsed === "object" &&
153
+ parsed !== null &&
154
+ "version" in parsed &&
155
+ typeof parsed.version === "string") {
156
+ return parsed.version;
157
+ }
158
+ return undefined;
159
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAGzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;GAGG;AACH,eAAO,MAAM,aAAa,mFACwD,CAAC;AAiCnF,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAQ7E"}
1
+ {"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAGA,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAGzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;GAGG;AACH,eAAO,MAAM,aAAa,mFACwD,CAAC;AAiHnF,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CAQ7E"}
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { dirname, resolve } from "node:path";
2
+ import { basename, dirname, resolve } from "node:path";
3
3
  import { BUILD_SECRET_NAMES } from "./config.js";
4
4
  import { shellSingleQuote } from "./shell.js";
5
5
  export { shellSingleQuote } from "./shell.js";
@@ -53,6 +53,77 @@ function sourceSecretsLine(secretsFile) {
53
53
  function unsetSecretsLine() {
54
54
  return `unset ${BUILD_SECRET_NAMES.join(" ")}`;
55
55
  }
56
+ function tokenizeShellPrefix(command) {
57
+ const tokens = [];
58
+ let current = "";
59
+ let quote;
60
+ let isEscaped = false;
61
+ for (const character of command.trim()) {
62
+ if (isEscaped) {
63
+ current += character;
64
+ isEscaped = false;
65
+ continue;
66
+ }
67
+ if (character === "\\" && quote !== "'") {
68
+ isEscaped = true;
69
+ continue;
70
+ }
71
+ if (quote !== undefined) {
72
+ if (character === quote) {
73
+ quote = undefined;
74
+ }
75
+ else {
76
+ current += character;
77
+ }
78
+ continue;
79
+ }
80
+ if (character === "'" || character === '"') {
81
+ quote = character;
82
+ continue;
83
+ }
84
+ if (/\s/.test(character)) {
85
+ if (current.length > 0) {
86
+ tokens.push(current);
87
+ current = "";
88
+ }
89
+ continue;
90
+ }
91
+ current += character;
92
+ }
93
+ if (current.length > 0) {
94
+ tokens.push(current);
95
+ }
96
+ return tokens;
97
+ }
98
+ function isEnvironmentAssignment(token) {
99
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
100
+ }
101
+ function safehouseProfileCommandName(agentCmd) {
102
+ const tokens = tokenizeShellPrefix(agentCmd);
103
+ let tokenIndex = tokens[0] === "env" ? 1 : 0;
104
+ if (tokens[0] === "env" && tokens[tokenIndex] === "--") {
105
+ tokenIndex += 1;
106
+ }
107
+ let commandToken;
108
+ for (const token of tokens.slice(tokenIndex)) {
109
+ if (isEnvironmentAssignment(token)) {
110
+ continue;
111
+ }
112
+ commandToken = token;
113
+ break;
114
+ }
115
+ if (commandToken === undefined) {
116
+ throw new Error(`Cannot infer Safehouse agent profile command from model cmd ${JSON.stringify(agentCmd)}.`);
117
+ }
118
+ const commandName = basename(commandToken);
119
+ if (commandName === "." ||
120
+ commandName === ".." ||
121
+ commandName.startsWith("-") ||
122
+ !/^[A-Za-z0-9._-]+$/.test(commandName)) {
123
+ throw new Error(`Cannot use ${JSON.stringify(commandName)} as a Safehouse agent profile command name inferred from model cmd ${JSON.stringify(agentCmd)}.`);
124
+ }
125
+ return commandName;
126
+ }
56
127
  /**
57
128
  * Build the shell command that runs inside the workspace. The prompt is
58
129
  * staged in a temp file (so backticks/quotes/$ in the description survive),
@@ -106,35 +177,46 @@ function buildUnwrappedHostLaunchCommand(arguments_) {
106
177
  return lines.join(" && ");
107
178
  }
108
179
  /**
109
- * Safehouse launch. Setup runs *inside* the `safehouse-clearance` wrap (mirroring
110
- * the sdx runner) so the repo's `.groundcrew/setup.sh` and its `npm install` are
111
- * filesystem-isolated and egress-restricted, rather than running on the bare host.
180
+ * Safehouse launch. Setup runs *inside* a plain `safehouse-clearance` wrap
181
+ * (mirroring the sdx runner) so the repo's `.groundcrew/setup.sh` and its
182
+ * `npm install` are filesystem-isolated and egress-restricted without inheriting
183
+ * agent credentials/state grants. The agent then runs in a second Safehouse wrap
184
+ * through an agent-named shim so Safehouse can select only the agent profile.
112
185
  *
113
186
  * Build secrets are sourced into the host launch shell so Safehouse can forward
114
187
  * them into the sandbox via `--env-pass` (Safehouse's `--env=FILE` mode otherwise
115
- * strips them); they're `unset` inside the wrap after setup so the agent process
116
- * never inherits them. The host keeps only `cd`, the prompt read, and the wrap exec.
188
+ * strips them); they're `unset` on the host after setup and not passed to the
189
+ * agent wrap. The host keeps `cd`, the prompt read, and a temporary
190
+ * command-named shim so Safehouse can select the intended agent profile while
191
+ * the actual agent command remains `sh -lc`.
117
192
  */
118
193
  function buildSafehouseLaunchCommand(arguments_) {
119
194
  const promptDir = dirname(arguments_.promptFile);
195
+ const safehouseCommandName = safehouseProfileCommandName(arguments_.definition.cmd);
120
196
  const agentCmd = renderAgentCommand({
121
197
  agentCmd: arguments_.definition.cmd,
122
198
  worktreeDir: arguments_.worktreeDir,
123
199
  sandboxName: "",
124
200
  });
125
- const innerParts = [setupWithStatusReporting(SETUP_COMMAND)];
126
- if (arguments_.secretsFile !== undefined) {
127
- innerParts.push(unsetSecretsLine());
128
- }
129
- innerParts.push(`exec ${agentCmd} "$@"`);
130
- const innerCommand = innerParts.join("; ");
131
- // Trailing space keeps the flag and `sh` separated; empty when no secrets.
201
+ const setupCommand = setupWithStatusReporting(SETUP_COMMAND);
202
+ const agentCommand = `exec ${agentCmd} "$@"`;
203
+ // Trailing space keeps the flag and setup command separated; empty when no secrets.
132
204
  const envPassFlag = arguments_.secretsFile === undefined ? "" : `--env-pass=${BUILD_SECRET_NAMES.join(",")} `;
205
+ const safehouseWrapper = shellSingleQuote(SAFEHOUSE_CLEARANCE_WRAPPER_PATH);
133
206
  const lines = [`cd ${shellSingleQuote(arguments_.worktreeDir)}`];
134
207
  if (arguments_.secretsFile !== undefined) {
135
208
  lines.push(sourceSecretsLine(arguments_.secretsFile));
136
209
  }
137
- lines.push(`_p=$(cat ${shellSingleQuote(arguments_.promptFile)})`, `rm -rf ${shellSingleQuote(promptDir)}`, `exec ${shellSingleQuote(SAFEHOUSE_CLEARANCE_WRAPPER_PATH)} ${envPassFlag}sh -lc ${shellSingleQuote(innerCommand)} sh "$_p"`);
210
+ lines.push(`_p=$(cat ${shellSingleQuote(arguments_.promptFile)})`, `rm -rf ${shellSingleQuote(promptDir)}`, `${safehouseWrapper} ${envPassFlag}sh -lc ${shellSingleQuote(setupCommand)}`);
211
+ if (arguments_.secretsFile !== undefined) {
212
+ lines.push(unsetSecretsLine());
213
+ }
214
+ lines.push(`_safehouse_shim_dir=$(mktemp -d "\${TMPDIR:-/tmp}/groundcrew-safehouse-XXXXXX")`, `trap 'rm -rf "$_safehouse_shim_dir"' EXIT`, `_safehouse_shim="$_safehouse_shim_dir/${safehouseCommandName}"`, `ln -s /bin/sh "$_safehouse_shim"`,
215
+ // Safehouse selects an agent profile from the wrapped command's basename.
216
+ // Running the real launch chain as `sh -lc` would make it see `sh`, so use
217
+ // an agent-named symlink to /bin/sh. This preserves per-agent profile
218
+ // selection without enabling every agent profile.
219
+ `${safehouseWrapper} "$_safehouse_shim" -lc ${shellSingleQuote(agentCommand)} sh "$_p"; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"; trap - EXIT; exit "$_safehouse_status"`);
138
220
  return lines.join(" && ");
139
221
  }
140
222
  function buildSdxLaunchCommand(arguments_) {
@@ -7,12 +7,13 @@ export interface ClassifyInstallOptions {
7
7
  export declare function classifyInstall(options: ClassifyInstallOptions): InstallKind;
8
8
  export interface NpmSpawnerResult {
9
9
  exitCode: number;
10
- stderrText: string;
10
+ outputText: string;
11
11
  }
12
12
  export type NpmSpawner = (command: string, args: readonly string[]) => Promise<NpmSpawnerResult>;
13
13
  export interface NpmRunResult {
14
14
  exitCode: number;
15
15
  sawEacces: boolean;
16
+ outputText: string;
16
17
  }
17
18
  export interface RunNpmInstallOptions {
18
19
  packageName: string;
@@ -25,5 +26,5 @@ export declare function detectInstallPath(cliMetaUrl: string): string;
25
26
  export type NpmRootRunner = (command: string, args: readonly string[]) => string;
26
27
  export declare function detectNpmRootGlobal(npmBin: string, runner: NpmRootRunner): string | undefined;
27
28
  export declare function detectIsSymlink(path: string): boolean;
28
- export declare function createDefaultNpmSpawner(passthroughStderr: NodeJS.WritableStream): NpmSpawner;
29
+ export declare function createDefaultNpmSpawner(): NpmSpawner;
29
30
  //# sourceMappingURL=npmGlobal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"npmGlobal.d.ts","sourceRoot":"","sources":["../../src/lib/npmGlobal.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,WAAW,CAY5E;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEjG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAO9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAEjF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAM7F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,wBAAgB,uBAAuB,CAAC,iBAAiB,EAAE,MAAM,CAAC,cAAc,GAAG,UAAU,CAmB5F"}
1
+ {"version":3,"file":"npmGlobal.d.ts","sourceRoot":"","sources":["../../src/lib/npmGlobal.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,WAAW,CAY5E;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEjG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAQ9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAEjF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAM7F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,wBAAgB,uBAAuB,IAAI,UAAU,CAmBpD"}
@@ -20,7 +20,8 @@ export async function runNpmInstallGlobal(options) {
20
20
  const result = await options.spawner(options.npmBin, args);
21
21
  return {
22
22
  exitCode: result.exitCode,
23
- sawEacces: result.stderrText.includes("EACCES"),
23
+ sawEacces: result.outputText.includes("EACCES"),
24
+ outputText: result.outputText,
24
25
  };
25
26
  }
26
27
  export function detectInstallPath(cliMetaUrl) {
@@ -42,20 +43,20 @@ export function detectIsSymlink(path) {
42
43
  return false;
43
44
  }
44
45
  }
45
- export function createDefaultNpmSpawner(passthroughStderr) {
46
+ export function createDefaultNpmSpawner() {
46
47
  return async (command, args) => await new Promise((resolve, reject) => {
47
- const child = spawn(command, [...args], { stdio: ["inherit", "inherit", "pipe"] });
48
- const { stderr } = child;
48
+ const child = spawn(command, [...args], { stdio: ["inherit", "pipe", "pipe"] });
49
49
  const chunks = [];
50
- stderr.on("data", (chunk) => {
50
+ const collect = (chunk) => {
51
51
  chunks.push(chunk);
52
- passthroughStderr.write(chunk);
53
- });
52
+ };
53
+ child.stdout.on("data", collect);
54
+ child.stderr.on("data", collect);
54
55
  child.on("error", reject);
55
56
  child.on("close", (code) => {
56
57
  resolve({
57
58
  exitCode: code ?? 1,
58
- stderrText: Buffer.concat(chunks).toString("utf8"),
59
+ outputText: Buffer.concat(chunks).toString("utf8"),
59
60
  });
60
61
  });
61
62
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",