@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.
- package/CHANGELOG.md +15 -0
- package/README.md +24 -0
- package/assets/lanes-hooks/hq-lanes-drain.mjs +36 -15
- package/dist/command-catalog.generated.d.ts +168 -0
- package/dist/command-catalog.generated.js +214 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/integrations-connect.d.ts +38 -0
- package/dist/commands/integrations-connect.js +39 -38
- package/dist/commands/integrations-core.d.ts +4 -6
- package/dist/commands/integrations-core.js +15 -12
- package/dist/commands/integrations-import.js +8 -6
- package/dist/commands/integrations-manage.js +8 -6
- package/dist/commands/integrations.js +9 -5
- package/dist/commands/lanes.d.ts +1 -0
- package/dist/commands/lanes.js +150 -83
- package/dist/commands/monitor.d.ts +58 -0
- package/dist/commands/monitor.js +965 -0
- package/dist/lib/doctor/checks/integrations.js +11 -0
- package/dist/lib/lanes/delivery-core.d.ts +15 -0
- package/dist/lib/lanes/delivery-core.js +21 -0
- package/dist/lib/lanes/delivery.d.ts +21 -5
- package/dist/lib/lanes/delivery.js +71 -7
- package/dist/lib/lanes/dropbox.d.ts +13 -1
- package/dist/lib/lanes/dropbox.js +61 -5
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/move-account.js +1 -1
- package/dist/lib/lanes/spawn.d.ts +6 -3
- package/dist/lib/lanes/spawn.js +41 -8
- package/dist/lib/lanes/types.d.ts +3 -0
- package/dist/lib/monitor.d.ts +153 -0
- package/dist/lib/monitor.js +519 -0
- package/dist/main.js +40 -28
- package/dist/utils/cli-network-lifecycle.d.ts +4 -0
- package/dist/utils/cli-network-lifecycle.js +11 -0
- package/dist/utils/redact-error-text.js +2 -1
- package/dist/utils/redact-url.d.ts +14 -0
- package/dist/utils/redact-url.js +218 -0
- 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.
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
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.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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
|
|
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)}` : ""}`);
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -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>;
|