@indigoai-us/hq-cli 5.202.0 → 5.203.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 (40) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +24 -0
  3. package/assets/lanes-hooks/hq-lanes-drain.mjs +36 -15
  4. package/dist/command-catalog.generated.d.ts +168 -0
  5. package/dist/command-catalog.generated.js +214 -0
  6. package/dist/command-registration-plan.d.ts +6 -0
  7. package/dist/command-registration-plan.js +1 -0
  8. package/dist/commands/integrations-connect.d.ts +38 -0
  9. package/dist/commands/integrations-connect.js +39 -38
  10. package/dist/commands/integrations-core.d.ts +4 -6
  11. package/dist/commands/integrations-core.js +15 -12
  12. package/dist/commands/integrations-import.js +8 -6
  13. package/dist/commands/integrations-manage.js +8 -6
  14. package/dist/commands/integrations.js +9 -5
  15. package/dist/commands/lanes.d.ts +1 -0
  16. package/dist/commands/lanes.js +150 -83
  17. package/dist/commands/monitor.d.ts +58 -0
  18. package/dist/commands/monitor.js +965 -0
  19. package/dist/lib/doctor/checks/integrations.js +11 -0
  20. package/dist/lib/lanes/delivery-core.d.ts +15 -0
  21. package/dist/lib/lanes/delivery-core.js +21 -0
  22. package/dist/lib/lanes/delivery.d.ts +21 -5
  23. package/dist/lib/lanes/delivery.js +71 -7
  24. package/dist/lib/lanes/dropbox.d.ts +13 -1
  25. package/dist/lib/lanes/dropbox.js +61 -5
  26. package/dist/lib/lanes/index.d.ts +1 -1
  27. package/dist/lib/lanes/index.js +1 -1
  28. package/dist/lib/lanes/move-account.js +1 -1
  29. package/dist/lib/lanes/spawn.d.ts +6 -3
  30. package/dist/lib/lanes/spawn.js +41 -8
  31. package/dist/lib/lanes/types.d.ts +3 -0
  32. package/dist/lib/monitor.d.ts +153 -0
  33. package/dist/lib/monitor.js +519 -0
  34. package/dist/main.js +40 -28
  35. package/dist/utils/cli-network-lifecycle.d.ts +4 -0
  36. package/dist/utils/cli-network-lifecycle.js +11 -0
  37. package/dist/utils/redact-error-text.js +2 -1
  38. package/dist/utils/redact-url.d.ts +14 -0
  39. package/dist/utils/redact-url.js +218 -0
  40. package/package.json +1 -1
@@ -20,6 +20,7 @@ import chalk from "chalk";
20
20
  import open from "open";
21
21
  import { DEFAULT_VAULT_API_URL, ensureCognitoIdToken, } from "../utils/cognito-session.js";
22
22
  import { getCompanyUid } from "../utils/vault-api.js";
23
+ import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
23
24
  import { IntegrationsCliError, bareProvider, connectionDomain, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, } from "./integrations-core.js";
24
25
  import { completeOAuth, discoverDocs, installIntegration, listCatalog, pullBlueprint, startOAuth, } from "./integrations-api.js";
25
26
  import { OAUTH_LOOPBACK_TIMEOUT_CODE, startLoopbackListener, } from "./integrations-oauth.js";
@@ -292,7 +293,7 @@ async function resolveTarget(token, companyUid, app, opts, preserveDomain = fals
292
293
  : found.discovery.authMode === "oauth"
293
294
  ? "oauth"
294
295
  : "none",
295
- label: found.discovery.displayName,
296
+ label: redactUrlsInText(found.discovery.displayName),
296
297
  };
297
298
  }
298
299
  if (opts.mcpUrl) {
@@ -302,11 +303,11 @@ async function resolveTarget(token, companyUid, app, opts, preserveDomain = fals
302
303
  ...(opts.provider ? { provider: opts.provider } : {}),
303
304
  ...(opts.displayName ? { displayName: opts.displayName } : {}),
304
305
  },
305
- label: opts.displayName ?? opts.provider ?? opts.mcpUrl,
306
+ label: redactUrlsInText(opts.displayName ?? opts.provider ?? opts.mcpUrl),
306
307
  };
307
308
  }
308
309
  if (opts.entryId) {
309
- return { ref: { catalogEntryId: opts.entryId }, label: opts.entryId };
310
+ return { ref: { catalogEntryId: opts.entryId }, label: redactUrlsInText(opts.entryId) };
310
311
  }
311
312
  if (!app) {
312
313
  throw new IntegrationsCliError("Name an app to connect — a domain (`hq integrations connect linear.app`), or use --mcp-url / --docs-url / --entry-id.", { expected: true });
@@ -324,10 +325,10 @@ async function resolveTarget(token, companyUid, app, opts, preserveDomain = fals
324
325
  return {
325
326
  ref: { domain: app },
326
327
  ...(match?.authClass ? { authClass: match.authClass } : {}),
327
- label: match?.name || app,
328
+ label: redactUrlsInText(match?.name || app),
328
329
  };
329
330
  }
330
- return catalogEntryToTarget(match, { ref: { domain: app }, label: app });
331
+ return catalogEntryToTarget(match, { ref: { domain: app }, label: redactUrlsInText(app) });
331
332
  }
332
333
  // A bare name (`notion`, `atlassian`) is how the catalog reads to a person —
333
334
  // the rows are titled `notion.com`, `atlassian.com`. Resolve it to that row
@@ -337,9 +338,9 @@ async function resolveTarget(token, companyUid, app, opts, preserveDomain = fals
337
338
  // unmatched → fall back to the server-side query resolver.
338
339
  const named = await findCatalogEntryByName(token, companyUid, app);
339
340
  if (named) {
340
- return catalogEntryToTarget(named, { ref: { query: app }, label: app });
341
+ return catalogEntryToTarget(named, { ref: { query: app }, label: redactUrlsInText(app) });
341
342
  }
342
- return { ref: { query: app }, label: app };
343
+ return { ref: { query: app }, label: redactUrlsInText(app) };
343
344
  }
344
345
  /**
345
346
  * Turn a matched catalog entry into a connect target, preferring its opaque
@@ -353,13 +354,13 @@ function catalogEntryToTarget(match, fallback) {
353
354
  return {
354
355
  ref: { catalogEntryId: match.entryId },
355
356
  ...(match.authClass ? { authClass: match.authClass } : {}),
356
- label: match.name || fallback.label,
357
+ label: redactUrlsInText(match.name || fallback.label),
357
358
  };
358
359
  }
359
360
  return {
360
361
  ref: { domain: match.domain },
361
362
  ...(match.authClass ? { authClass: match.authClass } : {}),
362
- label: match.name || fallback.label,
363
+ label: redactUrlsInText(match.name || fallback.label),
363
364
  };
364
365
  }
365
366
  /** Best-effort catalog lookup by domain; never fails the connect. */
@@ -507,15 +508,15 @@ async function connectViaOAuth(token, companyUid, target, opts) {
507
508
  // that beats it, but ordering it this way means the happy path never
508
509
  // depends on that buffer.
509
510
  const pendingCode = listener.waitForCode(started.state);
510
- console.error(chalk.dim(`Waiting for you to sign in to ${started.displayName || target.label}…`));
511
+ console.error(chalk.dim(`Waiting for you to sign in to ${redactUrlsInText(started.displayName || target.label)}…`));
511
512
  if (opts.browser === false) {
512
- console.error(`Open this URL to sign in:\n ${started.authorizationUrl}`);
513
+ console.error(`Open this URL to sign in:\n ${redactSensitiveUrl(started.authorizationUrl)}`);
513
514
  }
514
515
  else {
515
516
  // A failed browser launch (headless box, no handler) is recoverable —
516
517
  // print the URL and keep waiting rather than aborting the sign-in.
517
518
  await open(started.authorizationUrl).catch(() => {
518
- console.error(`Open this URL to sign in:\n ${started.authorizationUrl}`);
519
+ console.error(`Open this URL to sign in:\n ${redactSensitiveUrl(started.authorizationUrl)}`);
519
520
  });
520
521
  }
521
522
  let code;
@@ -596,9 +597,9 @@ companySlug, origin) {
596
597
  */
597
598
  function targetSelectionHint(opts) {
598
599
  if (opts.mcpUrl)
599
- return `Server address to enter there: ${opts.mcpUrl}`;
600
+ return `Server address to enter there: ${redactSensitiveUrl(opts.mcpUrl)}`;
600
601
  if (opts.docsUrl)
601
- return `Found from these docs: ${opts.docsUrl}`;
602
+ return `Found from these docs: ${redactSensitiveUrl(opts.docsUrl)}`;
602
603
  if (opts.entryId)
603
604
  return `Catalog entry: ${opts.entryId}`;
604
605
  return undefined;
@@ -656,10 +657,10 @@ function loopbackTimeoutHandoff(target, opts) {
656
657
  }
657
658
  function printConsoleDestination(target, opts, url) {
658
659
  if (url) {
659
- console.error(`Open this page and connect ${target.label} from it:\n ${url}`);
660
+ console.error(`Open this page and connect ${redactUrlsInText(target.label)} from it:\n ${redactSensitiveUrl(url)}`);
660
661
  }
661
662
  else {
662
- console.error(`Open your HQ console's Integrations page and connect ${target.label} there. ` +
663
+ console.error(`Open your HQ console's Integrations page and connect ${redactUrlsInText(target.label)} there. ` +
663
664
  "(This session points at a control plane whose console address is not " +
664
665
  "derivable — set HQ_CONSOLE_URL to name it.)");
665
666
  }
@@ -707,9 +708,9 @@ async function consoleHandoff(target, opts) {
707
708
  * credentials — but only the console can collect them, so say what is needed
708
709
  * and hand the page over rather than restating the refusal and stopping.
709
710
  */
710
- async function bringYourOwnClientHandoff(target, opts, reason, redirectUri) {
711
- console.error(chalk.yellow(reason));
712
- console.error(`${target.label} needs an OAuth app you register with the provider yourself.`);
711
+ export async function bringYourOwnClientHandoff(target, opts, reason, redirectUri) {
712
+ console.error(chalk.yellow(redactUrlsInText(reason)));
713
+ console.error(`${redactUrlsInText(target.label)} needs an OAuth app you register with the provider yourself.`);
713
714
  // Prefer finishing here. The recovery needs two things the terminal can
714
715
  // supply — the callback URL to register, and a client id + secret — and
715
716
  // hq-pro sends the first one back on exactly these errors. Falling through
@@ -719,7 +720,7 @@ async function bringYourOwnClientHandoff(target, opts, reason, redirectUri) {
719
720
  if (redirectUri && command) {
720
721
  console.error("");
721
722
  console.error(`In the provider's developer portal, register this callback URL:`);
722
- console.error(` ${redirectUri}`);
723
+ console.error(` ${redactSensitiveUrl(redirectUri)}`);
723
724
  console.error(`Then run:`);
724
725
  console.error(` ${command}`);
725
726
  console.error(chalk.dim("The secret is read from stdin, so it stays out of your shell history. " +
@@ -747,7 +748,7 @@ function byoClientCommand(target, opts) {
747
748
  const { mcpUrl, provider } = target.ref;
748
749
  if (!mcpUrl)
749
750
  return null;
750
- const parts = ["hq integrations connect", `--mcp-url ${shellQuote(mcpUrl)}`];
751
+ const parts = ["hq integrations connect", `--mcp-url ${shellQuote(redactSensitiveUrl(mcpUrl))}`];
751
752
  if (opts.company)
752
753
  parts.push(`--company ${shellQuote(opts.company)}`);
753
754
  if (provider)
@@ -849,8 +850,8 @@ async function userClientSignIn(token, companyUid, target, opts, client, request
849
850
  // authorize step on this path too.
850
851
  ...(requestedScopes ? { scopes: requestedScopes } : {}),
851
852
  });
852
- console.error(`Open this URL to sign in to ${started.displayName || target.label}:`);
853
- console.error(` ${started.authorizationUrl}`);
853
+ console.error(`Open this URL to sign in to ${redactUrlsInText(started.displayName || target.label)}:`);
854
+ console.error(` ${redactSensitiveUrl(started.authorizationUrl)}`);
854
855
  const googleHint = googleClientTypeHint(client.clientId, started.authorizationUrl);
855
856
  if (googleHint)
856
857
  console.error(chalk.yellow(googleHint));
@@ -879,7 +880,7 @@ export function googleClientTypeHint(clientId, authorizationUrl) {
879
880
  // An unparseable URL still gets the client-type half of the hint.
880
881
  }
881
882
  return ("This Google OAuth client must be of type \"Web application\" (a \"Desktop app\" client will not work)" +
882
- (redirectUri ? `, with this authorized redirect URI:\n ${redirectUri}` : "."));
883
+ (redirectUri ? `, with this authorized redirect URI:\n ${redactSensitiveUrl(redirectUri)}` : "."));
883
884
  }
884
885
  /**
885
886
  * Second chance at a credential when the target's auth mode wasn't known up
@@ -917,7 +918,7 @@ function reportInstall(result, opts, expectedRevivedConnectionId) {
917
918
  const { installation, connection } = result;
918
919
  const toolCount = result.mcp?.tools?.length;
919
920
  const revived = connection.id === expectedRevivedConnectionId;
920
- console.log(chalk.green(`${revived ? "Revived" : "Connected"} ${chalk.bold(installation.displayName)}`) +
921
+ console.log(chalk.green(`${revived ? "Revived" : "Connected"} ${chalk.bold(redactUrlsInText(installation.displayName))}`) +
921
922
  (typeof toolCount === "number" ? chalk.dim(` — ${toolCount} tools available`) : ""));
922
923
  console.log(chalk.dim(` connection: ${connection.id}`));
923
924
  if (installation.status === "needs_credentials") {
@@ -1073,7 +1074,7 @@ async function refuseIfAlreadyConnected(token, companyUid, app, opts) {
1073
1074
  });
1074
1075
  return true;
1075
1076
  }
1076
- const label = existing.installation?.displayName ?? slug;
1077
+ const label = redactUrlsInText(existing.installation?.displayName ?? slug);
1077
1078
  console.log(chalk.green(`${label} is already connected.`));
1078
1079
  console.log(chalk.dim(` Sign in again: hq integrations reconnect --provider ${slug}\n` +
1079
1080
  ` Add a second: hq integrations connect ${shellQuote(app)} --force`));
@@ -1104,7 +1105,7 @@ export function registerConnectCommands(integrations) {
1104
1105
  }
1105
1106
  if (entries.length === 0) {
1106
1107
  console.log(query
1107
- ? `Nothing matched '${query}'. Try a domain, e.g. hq integrations catalog linear.app`
1108
+ ? `Nothing matched '${redactUrlsInText(query)}'. Try a domain, e.g. hq integrations catalog linear.app`
1108
1109
  : "The catalog is empty right now.");
1109
1110
  return;
1110
1111
  }
@@ -1129,9 +1130,9 @@ export function registerConnectCommands(integrations) {
1129
1130
  ]
1130
1131
  .filter(Boolean)
1131
1132
  .join(" · ");
1132
- console.log(`${chalk.bold(entry.name)} ${chalk.dim(entry.domain)} ${chalk.dim(tags)}`);
1133
+ console.log(`${chalk.bold(redactUrlsInText(entry.name))} ${chalk.dim(redactUrlsInText(entry.domain))} ${chalk.dim(tags)}`);
1133
1134
  if (entry.description)
1134
- console.log(chalk.dim(` ${entry.description}`));
1135
+ console.log(chalk.dim(` ${redactUrlsInText(entry.description)}`));
1135
1136
  }
1136
1137
  console.log(chalk.dim(`\n${entries.length} apps. Connect one with: hq integrations connect <domain>`));
1137
1138
  });
@@ -1151,18 +1152,18 @@ export function registerConnectCommands(integrations) {
1151
1152
  printJson(blueprint);
1152
1153
  return;
1153
1154
  }
1154
- console.log(`${chalk.bold(blueprint.displayName)} ${chalk.dim(blueprint.domain)}`);
1155
+ console.log(`${chalk.bold(redactUrlsInText(blueprint.displayName))} ${chalk.dim(redactUrlsInText(blueprint.domain))}`);
1155
1156
  if (blueprint.summary)
1156
- console.log(chalk.dim(blueprint.summary));
1157
+ console.log(chalk.dim(redactUrlsInText(blueprint.summary)));
1157
1158
  for (const surface of blueprint.surfaces) {
1158
1159
  const auth = surface.authStatus === "none" ? "no auth" : `auth: ${surface.authStatus}`;
1159
- console.log(` ${chalk.bold(surface.kind)} ${surface.name} ${chalk.dim(`${auth} · ${surface.readiness.strategy}`)}`);
1160
+ console.log(` ${chalk.bold(redactUrlsInText(surface.kind))} ${redactUrlsInText(surface.name)} ${chalk.dim(`${auth} · ${redactUrlsInText(surface.readiness.strategy)}`)}`);
1160
1161
  }
1161
1162
  for (const credential of blueprint.credentials) {
1162
- console.log(chalk.dim(` needs: ${credential.label}${credential.generateUrl ? ` (${credential.generateUrl})` : ""}`));
1163
+ console.log(chalk.dim(` needs: ${redactUrlsInText(credential.label)}${credential.generateUrl ? ` (${redactSensitiveUrl(credential.generateUrl)})` : ""}`));
1163
1164
  }
1164
1165
  for (const warning of blueprint.warnings) {
1165
- console.log(chalk.yellow(` ! ${warning.message}`));
1166
+ console.log(chalk.yellow(` ! ${redactUrlsInText(warning.message)}`));
1166
1167
  }
1167
1168
  });
1168
1169
  integrations
@@ -1183,11 +1184,11 @@ export function registerConnectCommands(integrations) {
1183
1184
  return;
1184
1185
  }
1185
1186
  const { discovery } = found;
1186
- console.log(`${chalk.bold(discovery.displayName)} ${chalk.dim(discovery.mcpUrl)}`);
1187
+ console.log(`${chalk.bold(redactUrlsInText(discovery.displayName))} ${chalk.dim(redactSensitiveUrl(discovery.mcpUrl))}`);
1187
1188
  console.log(chalk.dim(` sign-in: ${discovery.authMode} · ${discovery.verification} · confidence ${discovery.confidence}`));
1188
1189
  // Quoted: a docs URL routinely carries `&`, which would background the
1189
1190
  // command when pasted, and `;` would run whatever follows it.
1190
- console.log(chalk.dim(`\nConnect it with: hq integrations connect --docs-url ${shellQuote(docsUrl)}`));
1191
+ console.log(chalk.dim(`\nConnect it with: hq integrations connect --docs-url ${shellQuote(redactSensitiveUrl(docsUrl))}`));
1191
1192
  });
1192
1193
  integrations
1193
1194
  .command("connect [app]")
@@ -1249,8 +1250,8 @@ export function registerConnectCommands(integrations) {
1249
1250
  if (opts.json)
1250
1251
  printJson(details);
1251
1252
  else {
1252
- console.log(chalk.yellow(details.reason));
1253
- console.log(chalk.yellow(`Re-add it with: ${details.fixPath}`));
1253
+ console.log(chalk.yellow(redactUrlsInText(details.reason)));
1254
+ console.log(chalk.yellow(`Re-add it with: ${redactUrlsInText(details.fixPath)}`));
1254
1255
  }
1255
1256
  return;
1256
1257
  }
@@ -152,12 +152,10 @@ export declare class IntegrationsCliError extends Error {
152
152
  readonly rpcCode?: number;
153
153
  /**
154
154
  * HQ's own OAuth callback URL, carried on the errors that say a provider
155
- * refused to register HQ automatically. It is what the admin must register
156
- * with the provider before their own OAuth app can be used, and it is NOT a
157
- * secret — every authorization server already receives it in the authorize
158
- * URL. Taken from the response rather than assembled locally so the CLI
159
- * prints the callback this backend will actually redirect to, instead of a
160
- * guess at its route that would send the admin to register the wrong URL.
155
+ * refused to register HQ automatically. The provider must register the exact
156
+ * callback this backend will redirect to, so the CLI preserves the server
157
+ * value for the OAuth recovery flow. It may contain sensitive query data;
158
+ * keep it non-enumerable and redact it at display boundaries.
161
159
  */
162
160
  readonly redirectUri?: string;
163
161
  constructor(message: string, opts?: {
@@ -13,6 +13,7 @@ import chalk from "chalk";
13
13
  import { vaultApiFetch } from "../utils/vault-api.js";
14
14
  import { AuthError } from "../utils/auth-error.js";
15
15
  import { redactErrorText } from "../utils/redact-error-text.js";
16
+ import { redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
16
17
  import { connectionHealthNotice } from "../lib/integrations/health.js";
17
18
  import { bareProvider } from "../lib/integrations/provider-slug.js";
18
19
  export { bareProvider } from "../lib/integrations/provider-slug.js";
@@ -53,16 +54,14 @@ export class IntegrationsCliError extends Error {
53
54
  rpcCode;
54
55
  /**
55
56
  * HQ's own OAuth callback URL, carried on the errors that say a provider
56
- * refused to register HQ automatically. It is what the admin must register
57
- * with the provider before their own OAuth app can be used, and it is NOT a
58
- * secret — every authorization server already receives it in the authorize
59
- * URL. Taken from the response rather than assembled locally so the CLI
60
- * prints the callback this backend will actually redirect to, instead of a
61
- * guess at its route that would send the admin to register the wrong URL.
57
+ * refused to register HQ automatically. The provider must register the exact
58
+ * callback this backend will redirect to, so the CLI preserves the server
59
+ * value for the OAuth recovery flow. It may contain sensitive query data;
60
+ * keep it non-enumerable and redact it at display boundaries.
62
61
  */
63
62
  redirectUri;
64
63
  constructor(message, opts = {}) {
65
- super(message);
64
+ super(redactUrlsInText(message));
66
65
  this.name = "IntegrationsCliError";
67
66
  this.expected = opts.expected ?? false;
68
67
  if (opts.code)
@@ -73,8 +72,12 @@ export class IntegrationsCliError extends Error {
73
72
  this.oauthProtected = opts.oauthProtected;
74
73
  if (opts.rpcCode !== undefined)
75
74
  this.rpcCode = opts.rpcCode;
76
- if (opts.redirectUri !== undefined)
77
- this.redirectUri = opts.redirectUri;
75
+ if (opts.redirectUri !== undefined) {
76
+ Object.defineProperty(this, "redirectUri", {
77
+ value: opts.redirectUri,
78
+ enumerable: false,
79
+ });
80
+ }
78
81
  }
79
82
  }
80
83
  /**
@@ -473,7 +476,7 @@ export function queuedOutcome(payload) {
473
476
  return null;
474
477
  }
475
478
  export function printJson(value) {
476
- console.log(JSON.stringify(value, null, 2));
479
+ console.log(JSON.stringify(redactSensitiveUrls(value), null, 2));
477
480
  }
478
481
  /**
479
482
  * Print why a connection is unusable and how to repair it, directly beneath its
@@ -492,8 +495,8 @@ export function printHealthNotice(connection) {
492
495
  const notice = connectionHealthNotice(connection);
493
496
  if (!notice)
494
497
  return;
495
- console.log(chalk.yellow(` ${notice.headline}`));
498
+ console.log(chalk.yellow(` ${redactUrlsInText(notice.headline)}`));
496
499
  if (notice.remediation)
497
- console.log(chalk.yellow(` ${notice.remediation}`));
500
+ console.log(chalk.yellow(` ${redactUrlsInText(notice.remediation)}`));
498
501
  }
499
502
  //# sourceMappingURL=integrations-core.js.map
@@ -10,6 +10,7 @@ import * as path from "node:path";
10
10
  import chalk from "chalk";
11
11
  import { ensureCognitoIdToken, ensureCognitoToken, resolveDefaultHqRoot } from "../utils/cognito-session.js";
12
12
  import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
13
+ import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
13
14
  import { bareProvider, IntegrationsCliError, printJson } from "./integrations-core.js";
14
15
  import { installIntegration, startOAuth } from "./integrations-api.js";
15
16
  import { loadRevealedSecrets } from "./secrets.js";
@@ -222,11 +223,11 @@ async function importRemote(token, companyUid, name, entry, dryRun) {
222
223
  function renderOutcomes(outcomes) {
223
224
  console.log("Connector Outcome Details");
224
225
  for (const outcome of outcomes) {
225
- console.log(`${outcome.name.padEnd(25)} ${outcome.status.padEnd(19)} ${outcome.reason ?? outcome.path ?? ""}`);
226
+ console.log(`${redactUrlsInText(outcome.name).padEnd(25)} ${outcome.status.padEnd(19)} ${redactUrlsInText(outcome.reason ?? outcome.path ?? "")}`);
226
227
  }
227
228
  for (const outcome of outcomes.filter((item) => item.status === "shared-for-local")) {
228
- console.log(chalk.dim(` Shared manifest: ${outcome.path}`));
229
- console.log(chalk.dim(` Teammate install: ${outcome.installCommand}`));
229
+ console.log(chalk.dim(` Shared manifest: ${redactUrlsInText(outcome.path ?? "")}`));
230
+ console.log(chalk.dim(` Teammate install: ${redactUrlsInText(outcome.installCommand ?? "")}`));
230
231
  }
231
232
  if (outcomes.some((item) => item.status === "shared-for-local")) {
232
233
  console.log(chalk.yellow(" Local connector credentials were replaced with ${secret:ENV_NAME}. Set them with `hq secrets` before installing."));
@@ -235,9 +236,10 @@ function renderOutcomes(outcomes) {
235
236
  console.log(chalk.yellow(" Local connector argument credentials were replaced with ${secret:FLAG_NAME}. Set them with `hq secrets` before installing."));
236
237
  }
237
238
  for (const outcome of outcomes.filter((item) => item.status === "needs-signin")) {
238
- console.log(chalk.yellow(` ${outcome.name} needs sign-in: ${outcome.reason}`));
239
- if (outcome.authorizationUrl)
240
- console.log(chalk.yellow(` Open this URL to sign in:\n ${outcome.authorizationUrl}`));
239
+ console.log(chalk.yellow(` ${redactUrlsInText(outcome.name)} needs sign-in: ${redactUrlsInText(outcome.reason ?? "")}`));
240
+ if (outcome.authorizationUrl) {
241
+ console.log(chalk.yellow(` Open this URL to sign in:\n ${redactSensitiveUrl(outcome.authorizationUrl)}`));
242
+ }
241
243
  }
242
244
  }
243
245
  export function registerImportCommands(integrations) {
@@ -16,6 +16,7 @@ import readline from "node:readline";
16
16
  import chalk from "chalk";
17
17
  import { ensureCognitoIdToken } from "../utils/cognito-session.js";
18
18
  import { getCompanyUid } from "../utils/vault-api.js";
19
+ import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
19
20
  import { IntegrationsCliError, bareProvider, printHealthNotice, fetchConnections, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
20
21
  import { fetchPendingApprovals, getConnectionAccess, mutateConnectionAccess, purgeConnection, setReadSafe, uninstallIntegration, updateGovernance, } from "./integrations-api.js";
21
22
  const WRITE_POLICIES = ["auto-allow", "confirm", "deny"];
@@ -95,7 +96,7 @@ function confirm(message) {
95
96
  });
96
97
  }
97
98
  function connectionLabel(connection) {
98
- return connection.installation?.displayName ?? bareProvider(connection.provider);
99
+ return redactUrlsInText(connection.installation?.displayName ?? bareProvider(connection.provider));
99
100
  }
100
101
  /**
101
102
  * The category of match must mirror `selectConnection`: an exact provider id
@@ -219,12 +220,13 @@ export function registerManageCommands(integrations) {
219
220
  if (connection.status === "revoked") {
220
221
  const details = revokedConnectionDetails(connection, opts.company);
221
222
  console.log(chalk.yellow(` Revoked — ${details.reason}`));
222
- console.log(chalk.yellow(` Re-add: ${details.fixPath}`));
223
+ console.log(chalk.yellow(` Re-add: ${redactUrlsInText(details.fixPath)}`));
223
224
  }
224
225
  if (install?.domain)
225
- console.log(chalk.dim(` domain: ${install.domain}`));
226
- if (install?.surface?.url)
227
- console.log(chalk.dim(` endpoint: ${install.surface.url}`));
226
+ console.log(chalk.dim(` domain: ${redactUrlsInText(install.domain)}`));
227
+ if (install?.surface?.url) {
228
+ console.log(chalk.dim(` endpoint: ${redactSensitiveUrl(install.surface.url)}`));
229
+ }
228
230
  const policy = connection.writePolicy;
229
231
  if (policy) {
230
232
  console.log(chalk.dim(` changes: ${policy} — ${POLICY_BLURB[policy] ?? ""}`));
@@ -547,7 +549,7 @@ export function registerManageCommands(integrations) {
547
549
  const who = row.memberOrAgentName ?? row.memberOrAgent;
548
550
  console.log(`${chalk.dim(row.timestamp)} ${chalk.bold(row.outcome)} ${row.toolName} ${chalk.dim(who)}`);
549
551
  if (row.reason)
550
- console.log(chalk.dim(` ${row.reason}`));
552
+ console.log(chalk.dim(` ${redactUrlsInText(row.reason)}`));
551
553
  }
552
554
  // hq-pro caps its own feed at 50 events; say so rather than letting a
553
555
  // truncated list read as "that is everything that ever happened".
@@ -52,6 +52,7 @@ import { randomUUID } from "node:crypto";
52
52
  import chalk from "chalk";
53
53
  import { ensureCognitoIdToken } from "../utils/cognito-session.js";
54
54
  import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
55
+ import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
55
56
  import { IntegrationsCliError, bareProvider, callGateway, fetchConnections, isClientError, printHealthNotice, printJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
56
57
  import { registerConnectCommands } from "./integrations-connect.js";
57
58
  import { registerImportCommands } from "./integrations-import.js";
@@ -130,7 +131,7 @@ function connectionLabel(connection) {
130
131
  const slug = bareProvider(connection.provider);
131
132
  const displayName = connection.installation?.displayName?.trim();
132
133
  return displayName && displayName.toLowerCase() !== slug.toLowerCase()
133
- ? `${slug} (${displayName})`
134
+ ? `${slug} (${redactUrlsInText(displayName)})`
134
135
  : slug;
135
136
  }
136
137
  function printToolSchema(tool) {
@@ -138,7 +139,7 @@ function printToolSchema(tool) {
138
139
  console.log(chalk.dim(" Input schema was not reported by this app."));
139
140
  return;
140
141
  }
141
- const schema = JSON.stringify(tool.inputSchema, null, 2);
142
+ const schema = JSON.stringify(redactSensitiveUrls(tool.inputSchema), null, 2);
142
143
  console.log(chalk.dim(" Input schema:"));
143
144
  console.log(chalk.dim(schema.split("\n").map((line) => ` ${line}`).join("\n")));
144
145
  }
@@ -152,7 +153,7 @@ function reportRevokedConnection(connection, opts) {
152
153
  else {
153
154
  console.log(chalk.yellow(`${connectionLabel(connection)}: status=revoked`));
154
155
  console.log(chalk.yellow(` ${details.reason}`));
155
- console.log(` Re-add: ${details.fixPath}`);
156
+ console.log(` Re-add: ${redactUrlsInText(details.fixPath)}`);
156
157
  }
157
158
  if (opts.fail) {
158
159
  throw new IntegrationsCliError(`status=revoked. ${details.reason} Re-add with: ${details.fixPath}`, { expected: true });
@@ -190,6 +191,9 @@ export function registerIntegrationsCommand(program) {
190
191
  .join(" · ");
191
192
  console.log(`${chalk.bold(connectionLabel(c))} ${chalk.dim(flags)}`);
192
193
  console.log(chalk.dim(` connection: ${c.id}`));
194
+ const endpoint = c.installation?.surface?.url;
195
+ if (endpoint)
196
+ console.log(chalk.dim(` endpoint: ${redactSensitiveUrl(endpoint)}`));
193
197
  printHealthNotice(c);
194
198
  }
195
199
  });
@@ -236,7 +240,7 @@ export function registerIntegrationsCommand(program) {
236
240
  const badge = formatToolBadge(tool);
237
241
  console.log(`${chalk.bold(tool.name)}${badge ? ` ${badge}` : ""} ${chalk.dim(`on ${connectionLabel(connection)}`)}`);
238
242
  if (tool.description)
239
- console.log(chalk.dim(` ${tool.description}`));
243
+ console.log(chalk.dim(` ${redactUrlsInText(tool.description)}`));
240
244
  printToolSchema(tool);
241
245
  return;
242
246
  }
@@ -246,7 +250,7 @@ export function registerIntegrationsCommand(program) {
246
250
  }
247
251
  console.log(chalk.dim(`App: ${connectionLabel(connection)}`));
248
252
  for (const tool of tools) {
249
- const label = tool.title && tool.title !== tool.name ? ` ${chalk.dim(tool.title)}` : "";
253
+ const label = tool.title && tool.title !== tool.name ? ` ${chalk.dim(redactUrlsInText(tool.title))}` : "";
250
254
  const required = requiredInputHint(tool.inputSchema);
251
255
  const badge = formatToolBadge(tool);
252
256
  console.log(`${chalk.bold(tool.name)}${badge ? ` ${badge}` : ""}${label}${required ? ` ${chalk.dim(required)}` : ""}`);
@@ -46,6 +46,7 @@ export interface LanesCommandDeps {
46
46
  throttleDeps?: ThrottleDeps;
47
47
  existsSync?: (p: string) => boolean;
48
48
  readFileSync?: (p: string, encoding: "utf8") => string;
49
+ readStdin?: () => string;
49
50
  homedir?: () => string;
50
51
  mesh?: LanesMesh;
51
52
  sleep?: (ms: number) => Promise<void>;