@indigoai-us/hq-cli 5.311.0 → 5.311.1

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.311.1] — 2026-10-01
6
+ - Preserve shell-compatible diagnostics when delegate pickup receives a malformed manifest.
7
+ - Print URLs returned by `hq integrations call` without redacting their paths or query values. Other integration JSON output keeps its existing URL redaction.
8
+
5
9
  ## [5.311.0] — 2026-10-01
6
10
  - Add native `hq core hq-session` and `checkpoint-stop-gate` handlers on the fast route behind the default-off `core-native-session` flag, retaining bundled shell rollback companions.
7
11
 
@@ -6,7 +6,7 @@ import * as path from "node:path";
6
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
8
8
  import { describeCoreCommand } from "./core-command-catalog.js";
9
- import { isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
9
+ import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
10
10
  import { resolveLiveRoot } from "../utils/hq-roots.js";
11
11
  import { runBundledScript } from "../utils/run-bundled-script.js";
12
12
  const FLAG = "core-native-delegate";
@@ -61,6 +61,15 @@ function jsonFile(file) {
61
61
  return undefined;
62
62
  }
63
63
  }
64
+ function malformedJsonFile(file) {
65
+ try {
66
+ JSON.parse(fs.readFileSync(file, "utf8"));
67
+ return false;
68
+ }
69
+ catch (error) {
70
+ return error instanceof SyntaxError;
71
+ }
72
+ }
64
73
  function jsonText(value) { return serializeJqJson(value, "pretty"); }
65
74
  function checksum(file) {
66
75
  return createHash("sha256").update(fs.readFileSync(file)).digest("hex");
@@ -125,7 +134,21 @@ function parseFlags(args, allowed) {
125
134
  }
126
135
  function usage(name) { return USAGE[name] ?? ""; }
127
136
  function usageFailure(name) { err(usage(name)); return 1; }
128
- function runBundle(args, context) {
137
+ function runShellFallback(name, args, context) {
138
+ const result = runBundledScript({
139
+ asset: ASSETS[name],
140
+ args,
141
+ cwd: context.cwd,
142
+ hqRoot: context.root ?? context.cwd,
143
+ captureOutput: true,
144
+ });
145
+ if (result.stdout?.length)
146
+ process.stdout.write(result.stdout);
147
+ if (result.stderr?.length)
148
+ process.stderr.write(result.stderr);
149
+ return result.code;
150
+ }
151
+ function runBundle(args, context, onMalformedWhoami) {
129
152
  const root = context.root ?? context.cwd;
130
153
  if (args[0] !== "build")
131
154
  return usageFailure("bundle");
@@ -175,7 +198,9 @@ function runBundle(args, context) {
175
198
  try {
176
199
  identity = JSON.parse(who.stdout ?? "");
177
200
  }
178
- catch {
201
+ catch (error) {
202
+ if (error instanceof SyntaxError)
203
+ return onMalformedWhoami ? onMalformedWhoami() : runShellFallback("bundle", args, context);
179
204
  identity = undefined;
180
205
  }
181
206
  const identityValue = isRecord(identity) ? identity : {};
@@ -291,6 +316,32 @@ function runBundle(args, context) {
291
316
  function runHq(args, context, stdout = "capture") {
292
317
  return subprocess("hq", args, context.cwd, { stdout });
293
318
  }
319
+ function commandSubstitutionBytes(value) {
320
+ const bytes = value.stdoutBytes ?? Buffer.from(value.stdout ?? "");
321
+ let end = bytes.length;
322
+ while (end > 0 && bytes[end - 1] === 0x0a)
323
+ end -= 1;
324
+ return bytes.subarray(0, end);
325
+ }
326
+ function emitJqParseDiagnostic(input, args, context) {
327
+ if (!commandExists("jq", context.cwd))
328
+ return;
329
+ const result = subprocess("jq", args, context.cwd, { input, stdout: "ignore", stderr: "pipe" });
330
+ const diagnostic = result.stderrBytes ?? Buffer.from(result.stderr ?? "");
331
+ if (diagnostic.length)
332
+ err(diagnostic);
333
+ }
334
+ const RESOLVE_FOUND_FILTER = '.status == "found" and (.person.personUid | type == "string") and (.person.companyUid | type == "string")';
335
+ const RESOLVE_NOT_FOUND_FILTER = '.status == "not_found"';
336
+ const ACL_FILTER = `
337
+ .schemaVersion == 1 and
338
+ (
339
+ .prefix == $prefix or
340
+ (($prefix | endswith("/*")) and .prefix == ($prefix | sub("\\\\*$"; "")))
341
+ ) and
342
+ (.companyUid | type == "string") and ($company == "" or .companyUid == $company) and
343
+ (.direct | type == "array") and (.exists | type == "boolean")
344
+ `;
294
345
  function runGrant(args, context) {
295
346
  const parsed = parseFlags(args, ["--manifest", "--yes"]);
296
347
  if (parsed.help) {
@@ -397,7 +448,12 @@ function runGrant(args, context) {
397
448
  try {
398
449
  response = JSON.parse(resolved.stdout ?? "");
399
450
  }
400
- catch {
451
+ catch (error) {
452
+ if (error instanceof SyntaxError) {
453
+ const input = commandSubstitutionBytes(resolved);
454
+ emitJqParseDiagnostic(input, ["-e", RESOLVE_FOUND_FILTER], context);
455
+ emitJqParseDiagnostic(input, ["-e", RESOLVE_NOT_FOUND_FILTER], context);
456
+ }
401
457
  response = undefined;
402
458
  }
403
459
  if (isRecord(response) && response.status === "found" && isRecord(response.person) && typeof response.person.personUid === "string" && typeof response.person.companyUid === "string") {
@@ -426,7 +482,11 @@ function runGrant(args, context) {
426
482
  try {
427
483
  value = JSON.parse(response.stdout ?? "");
428
484
  }
429
- catch {
485
+ catch (error) {
486
+ if (error instanceof SyntaxError) {
487
+ const input = commandSubstitutionBytes(response);
488
+ emitJqParseDiagnostic(input, ["-e", "--arg", "prefix", prefix, "--arg", "company", expectedCompany, ACL_FILTER], context);
489
+ }
430
490
  value = undefined;
431
491
  }
432
492
  const rawPrefix = isRecord(value) ? value.prefix : undefined;
@@ -501,6 +561,22 @@ function runPublish(args, context) {
501
561
  if (args[0] !== "--manifest" || !args[1] || !fs.existsSync(args[1]) || !fs.statSync(args[1]).isFile())
502
562
  return fail("publish", "usage: --manifest <path>");
503
563
  const manifestPath = args[1];
564
+ if (malformedJsonFile(manifestPath))
565
+ return runShellFallback("publish", args, context);
566
+ let preflightManifest;
567
+ try {
568
+ preflightManifest = readJsonFile(manifestPath);
569
+ }
570
+ catch {
571
+ preflightManifest = undefined;
572
+ }
573
+ if (isRecord(preflightManifest) && jqRawPath(preflightManifest, "mode") === "transfer") {
574
+ const root = context.root ?? context.cwd;
575
+ const prd = path.join(root, "companies", jqRawPath(preflightManifest, "company"), "projects", jqRawPath(preflightManifest, "project.name"), "prd.json");
576
+ if (fs.existsSync(prd) && fs.statSync(prd).isFile() && malformedJsonFile(prd)) {
577
+ return runShellFallback("publish", args, context);
578
+ }
579
+ }
504
580
  const lock = `${manifestPath}.send-lock`;
505
581
  const lockHeld = args[2] === "--lock-held";
506
582
  let ownLock = false;
@@ -658,6 +734,12 @@ function runSend(args, context) {
658
734
  const manifest = jsonFile(manifestPath);
659
735
  if (!manifest)
660
736
  return fail("send", `manifest is not valid JSON: ${manifestPath}`);
737
+ if (send) {
738
+ const publishedPath = path.join(context.root ?? context.cwd, jqRawPath(manifest, "publication.manifestPath"));
739
+ if (fs.existsSync(publishedPath) && fs.statSync(publishedPath).isFile() && malformedJsonFile(publishedPath)) {
740
+ return runShellFallback("send", args, context);
741
+ }
742
+ }
661
743
  const lock = `${manifestPath}.send-lock`;
662
744
  if (send) {
663
745
  try {
@@ -819,14 +901,14 @@ function sleepNative(milliseconds) {
819
901
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
820
902
  }
821
903
  function captureRun(run) {
822
- let stdout = "";
823
- let stderr = "";
904
+ const stdout = [];
905
+ const stderr = [];
824
906
  const stdoutWrite = process.stdout.write.bind(process.stdout);
825
907
  const stderrWrite = process.stderr.write.bind(process.stderr);
826
- process.stdout.write = ((chunk) => { stdout += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); return true; });
827
- process.stderr.write = ((chunk) => { stderr += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); return true; });
908
+ process.stdout.write = ((chunk) => { stdout.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)); return true; });
909
+ process.stderr.write = ((chunk) => { stderr.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk)); return true; });
828
910
  try {
829
- return { code: run(), stdout, stderr };
911
+ return { code: run(), stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) };
830
912
  }
831
913
  finally {
832
914
  process.stdout.write = stdoutWrite;
@@ -854,16 +936,29 @@ function runVerify(args, context) {
854
936
  return fail("verify", "--dry-run requires --project");
855
937
  if (!recipient)
856
938
  return fail("verify", "--dry-run requires --to");
857
- const plan = captureRun(() => runBundle(["build", "--company", company, "--project", project, "--to", recipient, "--mode", mode, "--dry-run"], context));
858
- if (plan.stderr)
939
+ let malformedWhoamiFallbackCode;
940
+ const plan = captureRun(() => runBundle(["build", "--company", company, "--project", project, "--to", recipient, "--mode", mode, "--dry-run"], context, () => {
941
+ malformedWhoamiFallbackCode = runShellFallback("verify", args, context);
942
+ return malformedWhoamiFallbackCode;
943
+ }));
944
+ if (malformedWhoamiFallbackCode !== undefined) {
945
+ if (plan.stdout.length)
946
+ out(plan.stdout);
947
+ if (plan.stderr.length)
948
+ err(plan.stderr);
949
+ return malformedWhoamiFallbackCode;
950
+ }
951
+ if (plan.stderr.length)
859
952
  err(plan.stderr);
860
953
  if (plan.code !== 0)
861
954
  return fail("verify", "could not derive the delegation plan (see builder error above)");
862
955
  let manifest;
863
956
  try {
864
- manifest = JSON.parse(plan.stdout);
957
+ manifest = JSON.parse(plan.stdout.toString("utf8"));
865
958
  }
866
- catch {
959
+ catch (error) {
960
+ if (error instanceof SyntaxError)
961
+ return runShellFallback("verify", args, context);
867
962
  return fail("verify", "could not derive the delegation plan (see builder error above)");
868
963
  }
869
964
  const repoRelative = jqRawPath(manifest, "repo.path");
@@ -893,6 +988,8 @@ function runVerify(args, context) {
893
988
  return fail("verify", "--manifest is required (or --dry-run with --company/--project/--to)");
894
989
  if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
895
990
  return fail("verify", `manifest not found: ${manifestPath}`);
991
+ if (malformedJsonFile(manifestPath))
992
+ return runShellFallback("verify", args, context);
896
993
  const manifest = jsonFile(manifestPath);
897
994
  if (!manifest)
898
995
  return fail("verify", `jq: error: ${manifestPath}: Invalid JSON`);
@@ -9,12 +9,21 @@ type CommandResult = {
9
9
  code: number;
10
10
  stdout?: string;
11
11
  stderr?: string;
12
+ stdoutBytes?: Buffer;
13
+ stderrBytes?: Buffer;
12
14
  };
15
+ export type CommandExistsOptions = {
16
+ platform?: NodeJS.Platform;
17
+ env?: NodeJS.ProcessEnv;
18
+ existsSync?: (candidate: string) => boolean;
19
+ };
20
+ export declare function commandExists(name: string, cwd?: string, options?: CommandExistsOptions): boolean;
13
21
  export declare function subprocess(command: string, args: string[], cwd: string, options?: {
14
22
  input?: Buffer | string;
15
23
  stdinFd?: number;
16
24
  stderr?: "inherit" | "ignore" | "pipe";
17
25
  stdout?: "capture" | "inherit" | "ignore";
26
+ maxBuffer?: number;
18
27
  }): CommandResult;
19
28
  type JsonRecord = Record<string, unknown>;
20
29
  export declare function isRecord(value: unknown): value is JsonRecord;
@@ -4,6 +4,7 @@ import * as fs from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
+ import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
7
8
  import { describeCoreCommand } from "./core-command-catalog.js";
8
9
  import { resolveLiveRoot } from "../utils/hq-roots.js";
9
10
  import { runBundledScript } from "../utils/run-bundled-script.js";
@@ -28,6 +29,20 @@ function writeOut(value) {
28
29
  function writeErr(value) {
29
30
  process.stderr.write(value);
30
31
  }
32
+ function runCapturedShell(asset, args, context) {
33
+ const result = runBundledScript({
34
+ asset,
35
+ args,
36
+ cwd: context.cwd,
37
+ ...(context.root ? { hqRoot: context.root } : {}),
38
+ captureOutput: true,
39
+ });
40
+ if (result.stdout?.length)
41
+ process.stdout.write(result.stdout);
42
+ if (result.stderr?.length)
43
+ process.stderr.write(result.stderr);
44
+ return result.code;
45
+ }
31
46
  const USAGE_TEXT = {
32
47
  resolve: "hq-delegate-resolve.sh — resolve a delegation recipient to a confirmed\nprincipal (person email / prs_ uid, or fleet-agent agt_ uid). Read-only.\n\nUsage:\n core/scripts/hq-delegate-resolve.sh --company <slug> --to <token>\n\nOutput (stdout, JSON): {kind, principal, displayName, source}\n kind — \"person\" | \"agent\"\n principal — confirmed email, prs_… or agt_… uid\n source — \"verbatim\" | \"people-resolve\" | \"agents-list\"\n\nExit codes:\n 0 — resolved\n 1 — usage error (missing/invalid arguments)\n 3 — ambiguous: candidate list printed to stdout as JSON\n {status:\"ambiguous\", matches:[…]} — caller must present a picker\n 4 — not found (or found with no email): plain message on stderr\n\nTenancy: every lookup is scoped with an explicit --company. This helper\nnever enumerates or falls back across companies — single-company by\nconstruction, matching the /dm skill's resolution contract.\n\nFast path: a token containing \"@\" or starting with prs_ / agt_ passes\nthrough verbatim with NO lookup, preserving existing invocations exactly.\n",
33
48
  // Preserve the companion usage function’s historical raw source lines.
@@ -39,8 +54,20 @@ const USAGE_TEXT = {
39
54
  function usage(name) {
40
55
  return USAGE_TEXT[name];
41
56
  }
42
- function commandPath(name, cwd = process.cwd()) {
43
- const directories = (process.env.PATH ?? "").split(path.delimiter);
57
+ function commandPath(name, cwd = process.cwd(), options = {}) {
58
+ const env = options.env ?? process.env;
59
+ if ((options.platform ?? process.platform) === "win32") {
60
+ const existsSync = options.existsSync ?? ((candidate) => {
61
+ try {
62
+ return fs.statSync(candidate).isFile();
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ });
68
+ return resolveWindowsExecutable(name, env, existsSync, cwd) ?? undefined;
69
+ }
70
+ const directories = (env.PATH ?? "").split(path.delimiter);
44
71
  for (const directory of directories) {
45
72
  const candidate = path.resolve(cwd, directory || ".", name);
46
73
  try {
@@ -56,24 +83,30 @@ function commandPath(name, cwd = process.cwd()) {
56
83
  }
57
84
  return undefined;
58
85
  }
59
- function commandExists(name, cwd = process.cwd()) {
60
- return commandPath(name, cwd) !== undefined;
86
+ export function commandExists(name, cwd = process.cwd(), options = {}) {
87
+ return commandPath(name, cwd, options) !== undefined;
61
88
  }
62
89
  export function subprocess(command, args, cwd, options = {}) {
63
90
  const result = spawnSync(command, args, {
64
91
  cwd,
65
92
  env: process.env,
66
93
  input: options.input,
67
- encoding: "utf8",
68
- stdio: [options.stdinFd ?? "inherit", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
94
+ maxBuffer: options.maxBuffer,
95
+ encoding: "buffer",
96
+ stdio: [options.input === undefined ? options.stdinFd ?? "inherit" : "pipe", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
69
97
  });
70
98
  if (result.error) {
71
- return { code: 127, stderr: result.error.message, stdout: "" };
99
+ const stderr = Buffer.from(result.error.message);
100
+ return { code: 127, stderr: stderr.toString("utf8"), stderrBytes: stderr, stdout: "", stdoutBytes: Buffer.alloc(0) };
72
101
  }
102
+ const stdoutBytes = Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.alloc(0);
103
+ const stderrBytes = Buffer.isBuffer(result.stderr) ? result.stderr : Buffer.alloc(0);
73
104
  return {
74
105
  code: result.status ?? (result.signal ? 128 : 1),
75
- stdout: typeof result.stdout === "string" ? result.stdout : "",
76
- stderr: typeof result.stderr === "string" ? result.stderr : "",
106
+ stdout: stdoutBytes.toString("utf8"),
107
+ stderr: stderrBytes.toString("utf8"),
108
+ stdoutBytes,
109
+ stderrBytes,
77
110
  };
78
111
  }
79
112
  export function isRecord(value) {
@@ -82,15 +115,6 @@ export function isRecord(value) {
82
115
  export function readJsonText(text) {
83
116
  return JSON.parse(text);
84
117
  }
85
- function jqParseDiagnostic(text) {
86
- const bareToken = /^\s*([A-Za-z][A-Za-z0-9_-]*)\s*$/.exec(text);
87
- if (bareToken && !["null", "true", "false"].includes(bareToken[1] ?? "")) {
88
- const line = text.split("\n").length;
89
- const column = Buffer.byteLength(text.slice(text.lastIndexOf("\n") + 1));
90
- return `jq: parse error: Invalid numeric literal at line ${line}, column ${column}\n`;
91
- }
92
- return "jq: parse error: Invalid JSON text\n";
93
- }
94
118
  export function readJsonFile(file) {
95
119
  return readJsonText(fs.readFileSync(file, "utf8"));
96
120
  }
@@ -373,11 +397,8 @@ function runPickup(args, context) {
373
397
  document = readJsonFile(file);
374
398
  }
375
399
  catch (error) {
376
- if (error instanceof SyntaxError) {
377
- const diagnostic = jqParseDiagnostic(fs.readFileSync(file, "utf8"));
378
- for (let index = 0; index < 4; index += 1)
379
- writeErr(diagnostic);
380
- }
400
+ if (error instanceof SyntaxError)
401
+ return runCapturedShell(SCRIPT_ASSETS.pickup, args, context);
381
402
  document = {};
382
403
  }
383
404
  const status = jqRawPath(document, "status");
@@ -272,6 +272,8 @@ interface QueuedOutcome {
272
272
  }
273
273
  export declare function queuedOutcome(payload: unknown): QueuedOutcome | null;
274
274
  export declare function printJson(value: unknown): void;
275
+ /** Print the successful payload from `hq integrations call` without altering URLs. */
276
+ export declare function printIntegrationCallResultJson(value: unknown): void;
275
277
  /**
276
278
  * Print why a connection is unusable and how to repair it, directly beneath its
277
279
  * status line.
@@ -485,6 +485,10 @@ export function queuedOutcome(payload) {
485
485
  export function printJson(value) {
486
486
  console.log(JSON.stringify(redactSensitiveUrls(value), null, 2));
487
487
  }
488
+ /** Print the successful payload from `hq integrations call` without altering URLs. */
489
+ export function printIntegrationCallResultJson(value) {
490
+ console.log(JSON.stringify(value, null, 2));
491
+ }
488
492
  /**
489
493
  * Print why a connection is unusable and how to repair it, directly beneath its
490
494
  * status line.
@@ -54,7 +54,7 @@ import chalk from "chalk";
54
54
  import { ensureCognitoIdToken } from "../utils/cognito-session.js";
55
55
  import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
56
56
  import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
57
- import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, isClientError, printHealthNotice, printJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
57
+ import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
58
58
  import { registerConnectCommands } from "./integrations-connect.js";
59
59
  import { registerImportCommands } from "./integrations-import.js";
60
60
  import { registerManageCommands } from "./integrations-manage.js";
@@ -367,7 +367,7 @@ export function registerIntegrationsCommand(program) {
367
367
  // parsing output. The gateway double-wraps the provider result as a
368
368
  // text-content string, so `isError` lands on the UNWRAPPED payload, not
369
369
  // on the raw envelope; check both levels so either shape is caught.
370
- printJson(payload);
370
+ printIntegrationCallResultJson(payload);
371
371
  if (gatewayResultIsError(payload) || gatewayResultIsError(message.result)) {
372
372
  process.exitCode = 1;
373
373
  }
@@ -1,5 +1,7 @@
1
1
  import { type SpawnPlan } from "../../../utils/windows-spawn.js";
2
2
  type ExistsSync = (candidate: string) => boolean;
3
+ /** Resolve an npm-installed provider command using Windows PATH and PATHEXT. */
4
+ export declare function resolveWindowsExecutable(name: string, env: NodeJS.ProcessEnv, existsSync?: ExistsSync, cwd?: string): string | null;
3
5
  /** Preserve non-Windows lookup. Windows returns the first PATH/PATHEXT match. */
4
6
  export declare function resolveRuntimeBinary(name: string, env: NodeJS.ProcessEnv, platform?: NodeJS.Platform, existsSync?: ExistsSync, cwd?: string): string;
5
7
  /** Directly spawn native files; pass only batch shims through cmd.exe. */
@@ -8,7 +8,7 @@ function envValue(env, name) {
8
8
  return key ? env[key] : undefined;
9
9
  }
10
10
  /** Resolve an npm-installed provider command using Windows PATH and PATHEXT. */
11
- function resolveWindowsExecutable(name, env, existsSync = fs.existsSync, cwd = process.cwd()) {
11
+ export function resolveWindowsExecutable(name, env, existsSync = fs.existsSync, cwd = process.cwd()) {
12
12
  const pathValue = envValue(env, "PATH") ?? "";
13
13
  const rawExtensions = (envValue(env, "PATHEXT") || DEFAULT_PATHEXT)
14
14
  .split(";")
@@ -50,10 +50,16 @@ export type RunBundledScriptOptions = {
50
50
  hqRoot?: string;
51
51
  /** Extra environment for the child, applied last. */
52
52
  env?: Record<string, string | undefined>;
53
+ /** Capture stdout and stderr as bytes instead of inheriting those streams. */
54
+ captureOutput?: boolean;
53
55
  };
54
56
  export type RunBundledScriptResult = {
55
57
  /** The child's exit code, or 128+signal if it was terminated by one. */
56
58
  code: number;
59
+ /** Present when captureOutput is true. */
60
+ stdout?: Buffer;
61
+ /** Present when captureOutput is true. */
62
+ stderr?: Buffer;
57
63
  };
58
64
  /**
59
65
  * Execute a bundled scaffold script and return its exit code.
@@ -155,7 +155,8 @@ export function runBundledScript(opts) {
155
155
  const result = spawnSync(executable, [script, ...(opts.args ?? [])], {
156
156
  cwd: opts.cwd,
157
157
  env,
158
- stdio: "inherit",
158
+ stdio: opts.captureOutput ? ["inherit", "pipe", "pipe"] : "inherit",
159
+ ...(opts.captureOutput ? { maxBuffer: Number.MAX_SAFE_INTEGER } : {}),
159
160
  });
160
161
  if (result.error) {
161
162
  const spawnError = result.error;
@@ -166,6 +167,7 @@ export function runBundledScript(opts) {
166
167
  }
167
168
  throw new Error(`Failed to run bundled script ${opts.asset}: ${result.error.message}`);
168
169
  }
170
+ let code;
169
171
  if (result.signal) {
170
172
  // Conventional shell encoding, so a caller checking for termination sees it.
171
173
  const signals = {
@@ -175,8 +177,17 @@ export function runBundledScript(opts) {
175
177
  SIGKILL: 9,
176
178
  SIGTERM: 15,
177
179
  };
178
- return { code: 128 + (signals[result.signal] ?? 0) };
180
+ code = 128 + (signals[result.signal] ?? 0);
179
181
  }
180
- return { code: result.status ?? 0 };
182
+ else {
183
+ code = result.status ?? 0;
184
+ }
185
+ return {
186
+ code,
187
+ ...(opts.captureOutput ? {
188
+ stdout: result.stdout ?? Buffer.alloc(0),
189
+ stderr: result.stderr ?? Buffer.alloc(0),
190
+ } : {}),
191
+ };
181
192
  }
182
193
  //# sourceMappingURL=run-bundled-script.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.311.0",
3
+ "version": "5.311.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {