@indigoai-us/hq-cli 5.345.169 → 5.345.171
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 +69 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +181 -9
- package/dist/command-catalog.generated.js +224 -10
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +119 -5
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +141 -14
- package/dist/commands/cloud.d.ts +78 -1
- package/dist/commands/cloud.js +274 -9
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/feedback-triage.d.ts +43 -1
- package/dist/commands/feedback-triage.js +123 -23
- package/dist/commands/feedback.d.ts +40 -1
- package/dist/commands/feedback.js +184 -4
- package/dist/commands/files.d.ts +42 -0
- package/dist/commands/files.js +223 -30
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/mesh.js +6 -0
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-manifest.d.ts +16 -0
- package/dist/commands/sync-manifest.js +17 -1
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/decisions.d.ts +62 -0
- package/dist/lib/bot/decisions.js +228 -0
- package/dist/lib/bot/fleet-local.d.ts +127 -0
- package/dist/lib/bot/fleet-local.js +638 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/run.d.ts +7 -0
- package/dist/lib/bot/run.js +104 -1
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +63 -29
- package/dist/lib/daemon/config.d.ts +7 -0
- package/dist/lib/daemon/config.js +32 -0
- package/dist/lib/daemon/run.d.ts +3 -2
- package/dist/lib/daemon/run.js +23 -3
- package/dist/lib/daemon/supervisor.d.ts +1 -0
- package/dist/lib/daemon/supervisor.js +4 -0
- package/dist/lib/daemon/sync-core-canary.d.ts +12 -0
- package/dist/lib/daemon/sync-core-canary.js +61 -0
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/checks/sync-health.d.ts +32 -0
- package/dist/lib/doctor/checks/sync-health.js +54 -1
- package/dist/lib/doctor/fix/apply.d.ts +26 -4
- package/dist/lib/doctor/fix/apply.js +149 -11
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/mesh/live/daemon/work-subscribe.js +31 -0
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/work-context/organize.d.ts +7 -0
- package/dist/lib/work-context/organize.js +21 -1
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
|
@@ -161,7 +161,10 @@ export interface InstallInput {
|
|
|
161
161
|
placement: "header";
|
|
162
162
|
header: string;
|
|
163
163
|
};
|
|
164
|
+
scope?: ConnectScope;
|
|
164
165
|
}
|
|
166
|
+
export type ConnectScope = "personal" | "company";
|
|
167
|
+
export declare function scopeUnavailableMessage(scope: ConnectScope): string;
|
|
165
168
|
export declare function installIntegration(token: string, companyUid: string, input: InstallInput): Promise<InstallResult>;
|
|
166
169
|
export declare function uninstallIntegration(token: string, companyUid: string, target: {
|
|
167
170
|
installationId: string;
|
|
@@ -227,6 +230,7 @@ export interface OAuthStartInput {
|
|
|
227
230
|
* direct `mcpUrl` path.
|
|
228
231
|
*/
|
|
229
232
|
scopes?: string[];
|
|
233
|
+
scope?: ConnectScope;
|
|
230
234
|
}
|
|
231
235
|
export declare function startOAuth(token: string, companyUid: string, input: OAuthStartInput): Promise<OAuthStartResult>;
|
|
232
236
|
export declare function completeOAuth(token: string, companyUid: string, input: {
|
|
@@ -53,6 +53,22 @@ export async function discoverDocs(token, companyUid, docsUrl) {
|
|
|
53
53
|
await raiseForResponse(res, "Failed to read that documentation page");
|
|
54
54
|
return (await res.json());
|
|
55
55
|
}
|
|
56
|
+
export function scopeUnavailableMessage(scope) {
|
|
57
|
+
const flag = scope === "personal" ? "--personal" : "--company-owned";
|
|
58
|
+
return ("Personal and company integrations aren't turned on for this company yet. " +
|
|
59
|
+
`Run the command again without ${flag} to connect the usual way.`);
|
|
60
|
+
}
|
|
61
|
+
async function raiseForConnectResponse(res, fallback, scope) {
|
|
62
|
+
try {
|
|
63
|
+
return await raiseForResponse(res, fallback);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (scope && error instanceof IntegrationsCliError && error.status === 404 && !error.code) {
|
|
67
|
+
throw new IntegrationsCliError(scopeUnavailableMessage(scope), { expected: true, status: 404 });
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
56
72
|
export async function installIntegration(token, companyUid, input) {
|
|
57
73
|
const res = await vaultApiFetch({
|
|
58
74
|
token,
|
|
@@ -62,7 +78,7 @@ export async function installIntegration(token, companyUid, input) {
|
|
|
62
78
|
});
|
|
63
79
|
if (!res.ok) {
|
|
64
80
|
try {
|
|
65
|
-
await
|
|
81
|
+
await raiseForConnectResponse(res, "Failed to connect the app", input.scope);
|
|
66
82
|
}
|
|
67
83
|
catch (error) {
|
|
68
84
|
if (error instanceof IntegrationsCliError &&
|
|
@@ -106,7 +122,7 @@ export async function startOAuth(token, companyUid, input) {
|
|
|
106
122
|
body: { companyUid, ...input },
|
|
107
123
|
});
|
|
108
124
|
if (!res.ok)
|
|
109
|
-
await
|
|
125
|
+
await raiseForConnectResponse(res, "Failed to start sign-in", input.scope);
|
|
110
126
|
return (await res.json());
|
|
111
127
|
}
|
|
112
128
|
export async function completeOAuth(token, companyUid, input) {
|
|
@@ -27,7 +27,7 @@ import { type ResidentCommandLeaseOptions } from "../utils/resident-package-prel
|
|
|
27
27
|
import { consoleOrigin } from "../utils/console-origin.js";
|
|
28
28
|
export { consoleOrigin };
|
|
29
29
|
import type { AdminConnection } from "./integrations-core.js";
|
|
30
|
-
import { type InstallInput } from "./integrations-api.js";
|
|
30
|
+
import { type ConnectScope, type InstallInput } from "./integrations-api.js";
|
|
31
31
|
interface ConnectOptions {
|
|
32
32
|
company?: string;
|
|
33
33
|
entryId?: string;
|
|
@@ -54,7 +54,10 @@ interface ConnectOptions {
|
|
|
54
54
|
* to a person and check `hq integrations list` afterwards.
|
|
55
55
|
*/
|
|
56
56
|
linkOnly?: boolean;
|
|
57
|
+
personal?: boolean;
|
|
58
|
+
companyOwned?: boolean;
|
|
57
59
|
}
|
|
60
|
+
export declare function connectScopeFromOptions(opts: Pick<ConnectOptions, "personal" | "companyOwned">): ConnectScope | undefined;
|
|
58
61
|
/**
|
|
59
62
|
* Validate `--auth` against the supported enum, rejecting an unknown value
|
|
60
63
|
* before anything is installed. A finite provider/runtime option must never be
|
|
@@ -69,7 +72,7 @@ export declare function shellQuote(value: string): string;
|
|
|
69
72
|
/** Everything the connect verbs need to name one app to hq-pro. */
|
|
70
73
|
interface ConnectTarget {
|
|
71
74
|
/** Install/OAuth identity fields, exactly as hq-pro expects them. */
|
|
72
|
-
ref: Pick<InstallInput, "domain" | "query" | "catalogEntryId" | "discoveryReceiptId" | "mcpUrl" | "provider" | "displayName" | "docsUrl">;
|
|
75
|
+
ref: Pick<InstallInput, "domain" | "query" | "catalogEntryId" | "discoveryReceiptId" | "mcpUrl" | "provider" | "displayName" | "docsUrl" | "scope">;
|
|
73
76
|
/** Best-known auth mode before hq-pro is asked; `undefined` = unknown. */
|
|
74
77
|
authClass?: "none" | "key" | "oauth";
|
|
75
78
|
/** Human label for prompts and success lines. */
|
|
@@ -109,6 +112,10 @@ export declare function bringYourOwnClientHandoff(target: ConnectTarget, opts: C
|
|
|
109
112
|
* built, so the hint always names the URL Google will actually be sent.
|
|
110
113
|
*/
|
|
111
114
|
export declare function googleClientTypeHint(clientId: string, authorizationUrl: string): string | null;
|
|
112
|
-
export
|
|
115
|
+
export interface ConnectOwnership {
|
|
116
|
+
scope: ConnectScope;
|
|
117
|
+
viewerPersonUid?: string;
|
|
118
|
+
}
|
|
119
|
+
export declare function findAlreadyConnected(connections: readonly AdminConnection[], app: string, ownership?: ConnectOwnership): AdminConnection | undefined;
|
|
113
120
|
export declare function registerConnectCommands(integrations: Command): void;
|
|
114
121
|
//# sourceMappingURL=integrations-connect.d.ts.map
|
|
@@ -31,7 +31,7 @@ import { consoleOrigin } from "../utils/console-origin.js";
|
|
|
31
31
|
export { consoleOrigin };
|
|
32
32
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
33
33
|
import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
|
|
34
|
-
import { IntegrationsCliError, bareProvider, connectionDomain, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, } from "./integrations-core.js";
|
|
34
|
+
import { IntegrationsCliError, bareProvider, connectionAccountLabel, connectionDomain, connectionOwnerLabel, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, } from "./integrations-core.js";
|
|
35
35
|
import { completeOAuth, discoverDocs, installIntegration, listCatalog, pullBlueprint, startOAuth, } from "./integrations-api.js";
|
|
36
36
|
import { OAUTH_LOOPBACK_TIMEOUT_CODE, startLoopbackListener, } from "./integrations-oauth.js";
|
|
37
37
|
/** hq-pro's machine code for "this endpoint needs a browser sign-in". */
|
|
@@ -103,6 +103,19 @@ const BRING_YOUR_OWN_CLIENT_CODES = new Set([
|
|
|
103
103
|
"OAUTH_REGISTRATION_UNSUPPORTED",
|
|
104
104
|
"CLIENT_REGISTRATION_REFUSED",
|
|
105
105
|
]);
|
|
106
|
+
export function connectScopeFromOptions(opts) {
|
|
107
|
+
if (opts.personal && opts.companyOwned) {
|
|
108
|
+
throw new IntegrationsCliError("--personal and --company-owned cannot be used together; pick one.", { expected: true });
|
|
109
|
+
}
|
|
110
|
+
if (opts.personal)
|
|
111
|
+
return "personal";
|
|
112
|
+
if (opts.companyOwned)
|
|
113
|
+
return "company";
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
function scopeFlag(scope) {
|
|
117
|
+
return scope === "personal" ? "--personal" : "--company-owned";
|
|
118
|
+
}
|
|
106
119
|
/**
|
|
107
120
|
* Server-side limits, restated here so a typo fails in the terminal with a
|
|
108
121
|
* sentence rather than as an opaque 400 three network hops away. Kept in step
|
|
@@ -758,6 +771,10 @@ async function consoleHandoff(target, opts) {
|
|
|
758
771
|
if (opts.scopes) {
|
|
759
772
|
console.error(chalk.yellow(`Note: --scopes cannot be carried into a console sign-in, so this connect will use ${target.label}'s default permissions.`));
|
|
760
773
|
}
|
|
774
|
+
const scope = connectScopeFromOptions(opts);
|
|
775
|
+
if (scope) {
|
|
776
|
+
console.error(chalk.yellow(`Note: ${scopeFlag(scope)} cannot be carried into a console sign-in; choose who owns ${target.label} on the console page.`));
|
|
777
|
+
}
|
|
761
778
|
if (url && opts.browser !== false)
|
|
762
779
|
await open(url).catch((error) => warnBrowserOpenFailure(error));
|
|
763
780
|
console.error(chalk.dim("When the browser says it connected, run `hq integrations list` to confirm."));
|
|
@@ -820,6 +837,9 @@ function byoClientCommand(target, opts) {
|
|
|
820
837
|
// caller a line that reproduces the failure they are recovering from.
|
|
821
838
|
if (opts.scopes)
|
|
822
839
|
parts.push(`--scopes ${shellQuote(opts.scopes)}`);
|
|
840
|
+
const scope = connectScopeFromOptions(opts);
|
|
841
|
+
if (scope)
|
|
842
|
+
parts.push(scopeFlag(scope));
|
|
823
843
|
return parts.join(" ");
|
|
824
844
|
}
|
|
825
845
|
/**
|
|
@@ -1035,6 +1055,7 @@ async function targetForAccessKeyConnect(token, companyUid, target, opts) {
|
|
|
1035
1055
|
return {
|
|
1036
1056
|
ref: {
|
|
1037
1057
|
mcpUrl,
|
|
1058
|
+
...(target.ref.scope ? { scope: target.ref.scope } : {}),
|
|
1038
1059
|
...(opts.provider ? { provider: opts.provider } : blueprint.provider ? { provider: blueprint.provider } : {}),
|
|
1039
1060
|
...(opts.displayName
|
|
1040
1061
|
? { displayName: opts.displayName }
|
|
@@ -1079,7 +1100,9 @@ function reportInstall(result, opts, expectedRevivedConnectionId) {
|
|
|
1079
1100
|
* URL: hq-pro recognizes the domain and revives the revoked row in place.
|
|
1080
1101
|
*/
|
|
1081
1102
|
async function connectApp(token, companyUid, app, opts, expectedRevivedConnectionId) {
|
|
1082
|
-
const
|
|
1103
|
+
const resolved = await resolveTarget(token, companyUid, app, opts, expectedRevivedConnectionId !== undefined);
|
|
1104
|
+
const scope = connectScopeFromOptions(opts);
|
|
1105
|
+
const target = scope ? { ...resolved, ref: { ...resolved.ref, scope } } : resolved;
|
|
1083
1106
|
const authMode = opts.authHeader
|
|
1084
1107
|
? "key"
|
|
1085
1108
|
: opts.auth ?? target.authClass;
|
|
@@ -1205,7 +1228,16 @@ function isSettledInstallation(connection) {
|
|
|
1205
1228
|
const status = connection.installation?.status?.trim().toLowerCase();
|
|
1206
1229
|
return status === undefined || status === "" || status === "installed";
|
|
1207
1230
|
}
|
|
1208
|
-
|
|
1231
|
+
function hasRequestedOwnership(connection, ownership) {
|
|
1232
|
+
const ownerType = connection.ownerType;
|
|
1233
|
+
if (ownership.scope === "personal") {
|
|
1234
|
+
return (ownerType === "person" &&
|
|
1235
|
+
Boolean(ownership.viewerPersonUid) &&
|
|
1236
|
+
connection.ownerId === ownership.viewerPersonUid);
|
|
1237
|
+
}
|
|
1238
|
+
return ownerType === undefined || ownerType === "company" || ownerType === "legacy";
|
|
1239
|
+
}
|
|
1240
|
+
export function findAlreadyConnected(connections, app, ownership) {
|
|
1209
1241
|
const want = app.trim().toLowerCase();
|
|
1210
1242
|
if (want === "")
|
|
1211
1243
|
return undefined;
|
|
@@ -1215,6 +1247,8 @@ export function findAlreadyConnected(connections, app) {
|
|
|
1215
1247
|
return false;
|
|
1216
1248
|
if (!isSettledInstallation(connection))
|
|
1217
1249
|
return false;
|
|
1250
|
+
if (ownership && !hasRequestedOwnership(connection, ownership))
|
|
1251
|
+
return false;
|
|
1218
1252
|
const domain = (connectionDomain(connection) ?? "").toLowerCase();
|
|
1219
1253
|
if (domain === want || domain.endsWith(`.${want}`))
|
|
1220
1254
|
return true;
|
|
@@ -1254,22 +1288,31 @@ async function refuseIfAlreadyConnected(token, companyUid, app, opts) {
|
|
|
1254
1288
|
// the deliberate "yes, add another one" escape hatch.
|
|
1255
1289
|
if (!app || opts.force || opts.mcpUrl || opts.docsUrl || opts.entryId)
|
|
1256
1290
|
return false;
|
|
1291
|
+
const requestedScope = connectScopeFromOptions(opts);
|
|
1257
1292
|
let connections;
|
|
1258
1293
|
let viewer;
|
|
1294
|
+
let personalConnect;
|
|
1259
1295
|
try {
|
|
1260
1296
|
const surface = await fetchAdminSurface(token, companyUid);
|
|
1261
1297
|
connections = surface.connections ?? [];
|
|
1262
1298
|
viewer = surface.viewer;
|
|
1299
|
+
personalConnect = surface.capabilities?.personalConnect === true;
|
|
1263
1300
|
}
|
|
1264
1301
|
catch {
|
|
1265
1302
|
// Never let this convenience check block a connect. If the list cannot be
|
|
1266
1303
|
// read, fall through and let the real flow report whatever is wrong.
|
|
1267
1304
|
return false;
|
|
1268
1305
|
}
|
|
1269
|
-
const
|
|
1306
|
+
const scope = requestedScope ?? (personalConnect ? "personal" : undefined);
|
|
1307
|
+
const ownership = scope
|
|
1308
|
+
? { scope, ...(viewer?.personUid ? { viewerPersonUid: viewer.personUid } : {}) }
|
|
1309
|
+
: undefined;
|
|
1310
|
+
const existing = findAlreadyConnected(connections, app, ownership);
|
|
1270
1311
|
if (!existing)
|
|
1271
1312
|
return false;
|
|
1272
1313
|
const slug = bareProvider(existing.provider);
|
|
1314
|
+
const owner = connectionOwnerLabel(existing);
|
|
1315
|
+
const account = connectionAccountLabel(existing);
|
|
1273
1316
|
if (opts.json) {
|
|
1274
1317
|
printJson({
|
|
1275
1318
|
ok: true,
|
|
@@ -1277,11 +1320,14 @@ async function refuseIfAlreadyConnected(token, companyUid, app, opts) {
|
|
|
1277
1320
|
provider: slug,
|
|
1278
1321
|
connection: existing.id,
|
|
1279
1322
|
...(connectionDomain(existing) ? { domain: connectionDomain(existing) } : {}),
|
|
1323
|
+
...(existing.ownerType ? { ownerType: existing.ownerType } : {}),
|
|
1324
|
+
...(existing.accountLabel ? { accountLabel: existing.accountLabel } : {}),
|
|
1280
1325
|
});
|
|
1281
1326
|
return true;
|
|
1282
1327
|
}
|
|
1283
1328
|
const label = redactUrlsInText(existing.installation?.displayName ?? slug);
|
|
1284
|
-
|
|
1329
|
+
const detail = [owner, account].filter(Boolean).join(", ");
|
|
1330
|
+
console.log(chalk.green(`${label} is already connected${detail ? ` (${detail})` : ""}.`));
|
|
1285
1331
|
if (connectedBySomeoneElse(existing, viewer)) {
|
|
1286
1332
|
// Members may add apps and reconnect their own, but replacing a connection
|
|
1287
1333
|
// someone else created stays with an owner or admin: the server refuses
|
|
@@ -1291,7 +1337,7 @@ async function refuseIfAlreadyConnected(token, companyUid, app, opts) {
|
|
|
1291
1337
|
return true;
|
|
1292
1338
|
}
|
|
1293
1339
|
console.log(chalk.dim(` Sign in again: hq integrations reconnect --provider ${slug}\n` +
|
|
1294
|
-
` Add a second: hq integrations connect ${shellQuote(app)} --force`));
|
|
1340
|
+
` Add a second: hq integrations connect ${shellQuote(app)}${requestedScope ? ` ${scopeFlag(requestedScope)}` : ""} --force`));
|
|
1295
1341
|
return true;
|
|
1296
1342
|
}
|
|
1297
1343
|
export function registerConnectCommands(integrations) {
|
|
@@ -1425,8 +1471,11 @@ export function registerConnectCommands(integrations) {
|
|
|
1425
1471
|
.option("--timeout <seconds>", "How long to wait for a browser sign-in (default 300)")
|
|
1426
1472
|
.option("--link-only", "Print the sign-in link and exit without waiting; the sign-in finishes on the HQ console")
|
|
1427
1473
|
.option("--force", "Connect again even if this app is already connected")
|
|
1474
|
+
.option("--personal", "Connect it as your own personal connection (not --company <slug>, which picks the company)")
|
|
1475
|
+
.option("--company-owned", "Connect it as a connection the whole company owns (needs permission to add company apps)")
|
|
1428
1476
|
.option("--json", "Machine-readable output")
|
|
1429
1477
|
.action(async (app, opts) => {
|
|
1478
|
+
connectScopeFromOptions(opts);
|
|
1430
1479
|
assertAuthMode(opts.auth);
|
|
1431
1480
|
assertAuthHeader(opts.authHeader);
|
|
1432
1481
|
if (opts.authHeader && opts.auth !== undefined && opts.auth !== "key") {
|
|
@@ -1453,6 +1502,8 @@ export function registerConnectCommands(integrations) {
|
|
|
1453
1502
|
.option("--no-browser", "Print the sign-in URL instead of opening a browser")
|
|
1454
1503
|
.option("--timeout <seconds>", "How long to wait for a browser sign-in (default 300)")
|
|
1455
1504
|
.option("--connect", "For a revoked row, run `connect <domain>` to re-add and revive it")
|
|
1505
|
+
.option("--personal", "With --connect: re-add it as your own personal connection")
|
|
1506
|
+
.option("--company-owned", "With --connect: re-add it as a connection the whole company owns")
|
|
1456
1507
|
.option("--json", "Machine-readable output")
|
|
1457
1508
|
.action(async (app, opts) => {
|
|
1458
1509
|
// Same validation as `connect`. Without it a typo like `--auth oauth2`
|
|
@@ -1460,11 +1511,19 @@ export function registerConnectCommands(integrations) {
|
|
|
1460
1511
|
// silently reinstalls with auto-detection — the caller believes they
|
|
1461
1512
|
// forced a mode that was never applied.
|
|
1462
1513
|
assertAuthMode(opts.auth);
|
|
1514
|
+
const scope = connectScopeFromOptions(opts);
|
|
1515
|
+
if (scope && !opts.connect) {
|
|
1516
|
+
throw new IntegrationsCliError(`${scopeFlag(scope)} only applies with --connect, when a revoked app is added again.`, { expected: true });
|
|
1517
|
+
}
|
|
1463
1518
|
const token = await ensureCognitoIdToken();
|
|
1464
1519
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
1465
1520
|
const connection = await resolveConnection(token, companyUid, app, opts, {
|
|
1466
1521
|
allowSingleRevoked: Boolean(opts.connect),
|
|
1467
1522
|
});
|
|
1523
|
+
if (scope && connection.status !== "revoked") {
|
|
1524
|
+
throw new IntegrationsCliError(`${scopeFlag(scope)} only applies when --connect adds a revoked app again; this one is ${connection.status}. ` +
|
|
1525
|
+
"A reconnect keeps the connection's current owner.", { expected: true });
|
|
1526
|
+
}
|
|
1468
1527
|
if (connection.status === "revoked") {
|
|
1469
1528
|
const details = revokedConnectionDetails(connection, opts.company);
|
|
1470
1529
|
if (!opts.connect) {
|
|
@@ -1492,6 +1551,11 @@ export function registerConnectCommands(integrations) {
|
|
|
1492
1551
|
...(connection.installation?.displayName
|
|
1493
1552
|
? { displayName: connection.installation.displayName }
|
|
1494
1553
|
: {}),
|
|
1554
|
+
...(connection.ownerType === "company"
|
|
1555
|
+
? { scope: "company" }
|
|
1556
|
+
: connection.ownerType === "person"
|
|
1557
|
+
? { scope: "personal" }
|
|
1558
|
+
: {}),
|
|
1495
1559
|
},
|
|
1496
1560
|
...(opts.auth ? { authClass: opts.auth } : {}),
|
|
1497
1561
|
label: connection.installation?.displayName ?? bareProvider(connection.provider),
|
|
@@ -74,7 +74,11 @@ export interface AdminConnection {
|
|
|
74
74
|
grantCount?: number;
|
|
75
75
|
};
|
|
76
76
|
installation?: FactoryInstallation | null;
|
|
77
|
+
ownerType?: ConnectionOwnerType;
|
|
78
|
+
ownerId?: string;
|
|
79
|
+
accountLabel?: string;
|
|
77
80
|
}
|
|
81
|
+
export type ConnectionOwnerType = "person" | "company" | "legacy" | "invalid";
|
|
78
82
|
/**
|
|
79
83
|
* The actionable state returned instead of attempting to use a revoked
|
|
80
84
|
* connection. Keep this machine-readable so commands and scripts get the same
|
|
@@ -111,6 +115,9 @@ export interface AdminSurface {
|
|
|
111
115
|
*/
|
|
112
116
|
canAddIntegrations?: boolean;
|
|
113
117
|
};
|
|
118
|
+
capabilities?: {
|
|
119
|
+
personalConnect?: boolean;
|
|
120
|
+
};
|
|
114
121
|
connections: AdminConnection[];
|
|
115
122
|
audit: AdminAuditEvent[];
|
|
116
123
|
}
|
|
@@ -390,4 +397,20 @@ export declare function printIntegrationCallResultJson(value: unknown): void;
|
|
|
390
397
|
* a user to fix something that is not broken.
|
|
391
398
|
*/
|
|
392
399
|
export declare function printHealthNotice(connection: AdminConnection): void;
|
|
400
|
+
export declare function connectionOwnerLabel(connection: Pick<AdminConnection, "ownerType">): string | undefined;
|
|
401
|
+
export declare function connectionAccountLabel(connection: Pick<AdminConnection, "accountLabel">): string | undefined;
|
|
402
|
+
export interface IntegrationAccountCandidate {
|
|
403
|
+
connectionId: string;
|
|
404
|
+
provider: string;
|
|
405
|
+
ownerType?: string;
|
|
406
|
+
accountLabel?: string;
|
|
407
|
+
}
|
|
408
|
+
export declare const ACCOUNT_REQUIRED_CODE = "AccountRequired";
|
|
409
|
+
export declare const ACCOUNT_NOT_FOUND_CODE = "IntegrationAccountNotFound";
|
|
410
|
+
export declare function accountSelectionMessage(code: typeof ACCOUNT_REQUIRED_CODE | typeof ACCOUNT_NOT_FOUND_CODE, candidates: readonly IntegrationAccountCandidate[], account?: string, rerun?: string): string;
|
|
411
|
+
export declare function accountCandidateFor(connection: AdminConnection): IntegrationAccountCandidate;
|
|
412
|
+
export declare function selectAccountConnections(connections: readonly AdminConnection[], opts: {
|
|
413
|
+
account: string;
|
|
414
|
+
provider?: string;
|
|
415
|
+
}): AdminConnection[];
|
|
393
416
|
//# sourceMappingURL=integrations-core.d.ts.map
|
|
@@ -397,6 +397,9 @@ export async function fetchAdminSurface(token, companyUid) {
|
|
|
397
397
|
return {
|
|
398
398
|
companyUid: data.companyUid ?? companyUid,
|
|
399
399
|
...(data.factoryEnabled !== undefined ? { factoryEnabled: data.factoryEnabled } : {}),
|
|
400
|
+
...(data.capabilities && typeof data.capabilities === "object"
|
|
401
|
+
? { capabilities: data.capabilities }
|
|
402
|
+
: {}),
|
|
400
403
|
viewer: data.viewer ?? {
|
|
401
404
|
personUid: "",
|
|
402
405
|
role: "member",
|
|
@@ -717,6 +720,9 @@ export async function callGateway(token, params, idempotencyKey, method = "tools
|
|
|
717
720
|
throw new IntegrationsCliError(`Integration gateway request failed (HTTP ${res.status}).`, { expected: isClientError(res.status), status: res.status, gatewaySubcommand });
|
|
718
721
|
}
|
|
719
722
|
if (terminalMessage?.error) {
|
|
723
|
+
const accountError = accountSelectionError(terminalMessage.error, typeof params.account === "string" ? params.account : undefined);
|
|
724
|
+
if (accountError)
|
|
725
|
+
throw accountError;
|
|
720
726
|
const gatewayError = gatewayErrorDetails(terminalMessage.error);
|
|
721
727
|
const invalidEnvelope = gatewayError.rpcCode === undefined && !gatewayError.message;
|
|
722
728
|
const gatewayFailureCode = invalidEnvelope
|
|
@@ -890,4 +896,98 @@ export function printHealthNotice(connection) {
|
|
|
890
896
|
if (notice.remediation)
|
|
891
897
|
console.log(chalk.yellow(` ${redactUrlsInText(notice.remediation)}`));
|
|
892
898
|
}
|
|
899
|
+
const OWNER_LABELS = {
|
|
900
|
+
person: "Personal",
|
|
901
|
+
company: "Company",
|
|
902
|
+
legacy: "Shared (legacy)",
|
|
903
|
+
invalid: "Unknown owner",
|
|
904
|
+
};
|
|
905
|
+
export function connectionOwnerLabel(connection) {
|
|
906
|
+
const ownerType = connection.ownerType;
|
|
907
|
+
return ownerType && Object.hasOwn(OWNER_LABELS, ownerType) ? OWNER_LABELS[ownerType] : undefined;
|
|
908
|
+
}
|
|
909
|
+
export function connectionAccountLabel(connection) {
|
|
910
|
+
if (typeof connection.accountLabel !== "string")
|
|
911
|
+
return undefined;
|
|
912
|
+
const cleaned = safeAppName(connection.accountLabel, "");
|
|
913
|
+
return cleaned ? redactUrlsInText(cleaned) : undefined;
|
|
914
|
+
}
|
|
915
|
+
export const ACCOUNT_REQUIRED_CODE = "AccountRequired";
|
|
916
|
+
export const ACCOUNT_NOT_FOUND_CODE = "IntegrationAccountNotFound";
|
|
917
|
+
function accountCandidatesFrom(value) {
|
|
918
|
+
if (!Array.isArray(value))
|
|
919
|
+
return [];
|
|
920
|
+
const candidates = [];
|
|
921
|
+
for (const item of value) {
|
|
922
|
+
const record = recordValue(item);
|
|
923
|
+
if (!record || typeof record.connectionId !== "string" || typeof record.provider !== "string")
|
|
924
|
+
continue;
|
|
925
|
+
candidates.push({
|
|
926
|
+
connectionId: record.connectionId,
|
|
927
|
+
provider: record.provider,
|
|
928
|
+
...(typeof record.ownerType === "string" ? { ownerType: record.ownerType } : {}),
|
|
929
|
+
...(typeof record.accountLabel === "string" ? { accountLabel: record.accountLabel } : {}),
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
return candidates;
|
|
933
|
+
}
|
|
934
|
+
function formatAccountCandidate(candidate) {
|
|
935
|
+
const id = safeAppName(candidate.connectionId, "?");
|
|
936
|
+
const owner = connectionOwnerLabel({ ownerType: candidate.ownerType });
|
|
937
|
+
const label = connectionAccountLabel({ accountLabel: candidate.accountLabel });
|
|
938
|
+
const details = [safeAppName(bareProvider(candidate.provider), "?"), owner, label].filter(Boolean).join(" · ");
|
|
939
|
+
return ` ${id} ${details}`;
|
|
940
|
+
}
|
|
941
|
+
export function accountSelectionMessage(code, candidates, account, rerun = "Rerun with --account <connection id or account label>.") {
|
|
942
|
+
const headline = code === ACCOUNT_REQUIRED_CODE
|
|
943
|
+
? `More than one account can run this. ${rerun}`
|
|
944
|
+
: account
|
|
945
|
+
? `No account matches '${safeAppName(account, "?")}'. ${rerun}`
|
|
946
|
+
: "No account you can use has this tool.";
|
|
947
|
+
if (candidates.length === 0)
|
|
948
|
+
return headline;
|
|
949
|
+
return [headline, "Accounts:", ...candidates.map(formatAccountCandidate)].join("\n");
|
|
950
|
+
}
|
|
951
|
+
function accountSelectionError(error, account) {
|
|
952
|
+
const data = recordValue(recordValue(error)?.data);
|
|
953
|
+
const code = data?.code;
|
|
954
|
+
if (code !== ACCOUNT_REQUIRED_CODE && code !== ACCOUNT_NOT_FOUND_CODE)
|
|
955
|
+
return null;
|
|
956
|
+
return new IntegrationsCliError(accountSelectionMessage(code, accountCandidatesFrom(data?.candidates), account), {
|
|
957
|
+
expected: true,
|
|
958
|
+
code,
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
export function accountCandidateFor(connection) {
|
|
962
|
+
return {
|
|
963
|
+
connectionId: connection.id,
|
|
964
|
+
provider: connection.provider,
|
|
965
|
+
...(connection.ownerType ? { ownerType: connection.ownerType } : {}),
|
|
966
|
+
...(connection.accountLabel ? { accountLabel: connection.accountLabel } : {}),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function matchesProviderSelector(connection, provider) {
|
|
970
|
+
const want = provider.trim().toLowerCase();
|
|
971
|
+
const wantHumanSlug = humanSlug(provider);
|
|
972
|
+
const displayName = connection.installation?.displayName ?? "";
|
|
973
|
+
return (bareProvider(connection.provider).toLowerCase() === want ||
|
|
974
|
+
connection.provider.toLowerCase() === want ||
|
|
975
|
+
displayName.trim().toLowerCase() === want ||
|
|
976
|
+
(wantHumanSlug !== "" && humanSlug(displayName) === wantHumanSlug));
|
|
977
|
+
}
|
|
978
|
+
export function selectAccountConnections(connections, opts) {
|
|
979
|
+
const pool = connections.filter((connection) => connection.status !== "revoked" &&
|
|
980
|
+
(!opts.provider || matchesProviderSelector(connection, opts.provider)));
|
|
981
|
+
const want = opts.account.trim().toLowerCase();
|
|
982
|
+
const matches = pool.filter((connection) => connection.id.toLowerCase() === want ||
|
|
983
|
+
(typeof connection.accountLabel === "string" && connection.accountLabel.trim().toLowerCase() === want));
|
|
984
|
+
if (matches.length === 0) {
|
|
985
|
+
throw new IntegrationsCliError(accountSelectionMessage(ACCOUNT_NOT_FOUND_CODE, pool.map(accountCandidateFor), opts.account), { expected: true, code: ACCOUNT_NOT_FOUND_CODE });
|
|
986
|
+
}
|
|
987
|
+
const providers = new Set(matches.map((connection) => connection.provider));
|
|
988
|
+
if (providers.size > 1) {
|
|
989
|
+
throw new IntegrationsCliError(accountSelectionMessage(ACCOUNT_REQUIRED_CODE, matches.map(accountCandidateFor), opts.account, "Rerun with --provider <app> or --account <connection id>."), { expected: true, code: ACCOUNT_REQUIRED_CODE });
|
|
990
|
+
}
|
|
991
|
+
return matches;
|
|
992
|
+
}
|
|
893
993
|
//# sourceMappingURL=integrations-core.js.map
|
|
@@ -17,7 +17,7 @@ import chalk from "chalk";
|
|
|
17
17
|
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
18
18
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
19
19
|
import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
|
|
20
|
-
import { IntegrationsCliError, bareProvider, printHealthNotice, fetchConnections, fetchAdminSurface, fetchAuditPage, AUDIT_PAGE_MAX, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
|
|
20
|
+
import { IntegrationsCliError, bareProvider, printHealthNotice, connectionAccountLabel, connectionOwnerLabel, fetchConnections, fetchAdminSurface, fetchAuditPage, AUDIT_PAGE_MAX, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
|
|
21
21
|
import { fetchPendingApprovals, getConnectionAccess, mutateConnectionAccess, purgeConnection, setReadSafe, terminalSafeText, uninstallIntegration, updateGovernance, } from "./integrations-api.js";
|
|
22
22
|
const WRITE_POLICIES = ["auto-allow", "confirm", "deny"];
|
|
23
23
|
const PERMISSIONS = ["read", "write", "admin"];
|
|
@@ -219,6 +219,13 @@ export function registerManageCommands(integrations) {
|
|
|
219
219
|
if (install?.id)
|
|
220
220
|
console.log(chalk.dim(` installation: ${install.id}`));
|
|
221
221
|
console.log(chalk.dim(` status: ${connection.status}`));
|
|
222
|
+
const owner = connectionOwnerLabel(connection);
|
|
223
|
+
if (owner) {
|
|
224
|
+
console.log(chalk.dim(` owner: ${owner}`));
|
|
225
|
+
const account = connectionAccountLabel(connection);
|
|
226
|
+
if (account)
|
|
227
|
+
console.log(chalk.dim(` account: ${account}`));
|
|
228
|
+
}
|
|
222
229
|
if (connection.status === "revoked") {
|
|
223
230
|
const details = revokedConnectionDetails(connection, opts.company);
|
|
224
231
|
console.log(chalk.yellow(` Revoked — ${details.reason}`));
|
|
@@ -56,7 +56,7 @@ import chalk from "chalk";
|
|
|
56
56
|
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
57
57
|
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
58
58
|
import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
|
|
59
|
-
import { IntegrationsCliError, bareProvider, callGateway, parseCallTimeoutMs, fetchConnections, fetchUsableApps, isBuiltInGatewayProvider, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, } from "./integrations-core.js";
|
|
59
|
+
import { IntegrationsCliError, bareProvider, callGateway, parseCallTimeoutMs, fetchConnections, fetchUsableApps, isBuiltInGatewayProvider, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, connectionAccountLabel, connectionOwnerLabel, selectAccountConnections, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, accountCandidateFor, accountSelectionMessage, ACCOUNT_REQUIRED_CODE, } from "./integrations-core.js";
|
|
60
60
|
import { registerConnectCommands } from "./integrations-connect.js";
|
|
61
61
|
import { registerImportCommands } from "./integrations-import.js";
|
|
62
62
|
import { registerManageCommands } from "./integrations-manage.js";
|
|
@@ -329,6 +329,26 @@ function approvalOutcomeFailed(row) {
|
|
|
329
329
|
return ["failed", "write_unknown"].includes(row.status ?? "") ||
|
|
330
330
|
["failed", "unknown"].includes(row.outcome ?? "");
|
|
331
331
|
}
|
|
332
|
+
function selectToolConnection(connections, opts) {
|
|
333
|
+
if (opts.account === undefined) {
|
|
334
|
+
return { connection: selectConnection(connections, opts), accountMatches: [] };
|
|
335
|
+
}
|
|
336
|
+
if (opts.connection) {
|
|
337
|
+
throw new IntegrationsCliError("Use either --connection or --account, not both.", {
|
|
338
|
+
expected: true,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
if (!opts.account.trim()) {
|
|
342
|
+
throw new IntegrationsCliError("--account was empty — pass a connection id or account label.", {
|
|
343
|
+
expected: true,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
const accountMatches = selectAccountConnections(connections, {
|
|
347
|
+
account: opts.account,
|
|
348
|
+
...(opts.provider ? { provider: opts.provider } : {}),
|
|
349
|
+
});
|
|
350
|
+
return { connection: accountMatches[0], accountMatches };
|
|
351
|
+
}
|
|
332
352
|
export function registerIntegrationsCommand(program) {
|
|
333
353
|
const integrations = program
|
|
334
354
|
.command("integrations")
|
|
@@ -375,7 +395,10 @@ export function registerIntegrationsCommand(program) {
|
|
|
375
395
|
return;
|
|
376
396
|
}
|
|
377
397
|
for (const c of connections) {
|
|
398
|
+
const owner = connectionOwnerLabel(c);
|
|
378
399
|
const flags = [
|
|
400
|
+
owner ?? null,
|
|
401
|
+
owner ? connectionAccountLabel(c) ?? null : null,
|
|
379
402
|
c.status,
|
|
380
403
|
c.writePolicy ? `writes: ${c.writePolicy}` : null,
|
|
381
404
|
c.installation?.status === "needs_credentials" ? "needs sign-in" : null,
|
|
@@ -397,13 +420,17 @@ export function registerIntegrationsCommand(program) {
|
|
|
397
420
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
398
421
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
399
422
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
423
|
+
.option("--account <id-or-label>", "Which account of the app: its connection id or account label")
|
|
400
424
|
.option("--describe <tool>", "Print the full input schema for one tool")
|
|
401
425
|
.option("--json", "Machine-readable output")
|
|
402
426
|
.action(async (opts) => {
|
|
403
427
|
const token = await ensureCognitoIdToken();
|
|
404
428
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
405
429
|
const connections = await fetchConnections(token, companyUid);
|
|
406
|
-
const connection =
|
|
430
|
+
const { connection, accountMatches } = selectToolConnection(connections, opts);
|
|
431
|
+
if (accountMatches.length > 1) {
|
|
432
|
+
throw new IntegrationsCliError(accountSelectionMessage(ACCOUNT_REQUIRED_CODE, accountMatches.map(accountCandidateFor), opts.account, "Rerun with --account <connection id>."), { expected: true, code: ACCOUNT_REQUIRED_CODE });
|
|
433
|
+
}
|
|
407
434
|
if (reportRevokedConnection(connection, opts))
|
|
408
435
|
return;
|
|
409
436
|
let payload;
|
|
@@ -479,6 +506,7 @@ export function registerIntegrationsCommand(program) {
|
|
|
479
506
|
.description("Call one of a connected app's tools (approval-gated when it changes things)")
|
|
480
507
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
481
508
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
509
|
+
.option("--account <id-or-label>", "Which account of the app: its connection id or account label")
|
|
482
510
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
483
511
|
.option("--args <json>", "Tool arguments as a JSON object (default {}); use - to read the JSON from stdin")
|
|
484
512
|
.option("--args-file <path>", "Read tool arguments from a JSON file (use - for stdin); avoids shell quoting of apostrophes and parentheses")
|
|
@@ -490,9 +518,23 @@ export function registerIntegrationsCommand(program) {
|
|
|
490
518
|
const parsedArgs = await resolveCallArgs(opts);
|
|
491
519
|
const token = await ensureCognitoIdToken();
|
|
492
520
|
const companyUid = await getCompanyUid(token, opts.company);
|
|
493
|
-
const connection =
|
|
521
|
+
const { connection, accountMatches } = selectToolConnection(await fetchConnections(token, companyUid), opts);
|
|
494
522
|
if (reportRevokedConnection(connection, { ...opts, fail: true }))
|
|
495
523
|
return;
|
|
524
|
+
const accountSelector = opts.account !== undefined &&
|
|
525
|
+
accountMatches.length !== 1 &&
|
|
526
|
+
(connection.ownerType === "person" || connection.ownerType === "company")
|
|
527
|
+
? opts.account.trim()
|
|
528
|
+
: undefined;
|
|
529
|
+
if (accountSelector === undefined && accountMatches.length > 1) {
|
|
530
|
+
throw new IntegrationsCliError(accountSelectionMessage(ACCOUNT_REQUIRED_CODE, accountMatches.map(accountCandidateFor), opts.account, "Rerun with --account <connection id>."), { expected: true, code: ACCOUNT_REQUIRED_CODE });
|
|
531
|
+
}
|
|
532
|
+
const routing = accountSelector === undefined
|
|
533
|
+
? { connectionId: connection.id }
|
|
534
|
+
: {};
|
|
535
|
+
const accountParams = accountSelector === undefined
|
|
536
|
+
? {}
|
|
537
|
+
: { account: accountSelector, provider: connection.provider };
|
|
496
538
|
const idempotencyKey = opts.idempotencyKey ?? `hq-cli-${randomUUID()}`;
|
|
497
539
|
let gatewayParams;
|
|
498
540
|
if (isBuiltInGatewayProvider(connection.provider)) {
|
|
@@ -506,7 +548,8 @@ export function registerIntegrationsCommand(program) {
|
|
|
506
548
|
companyUid,
|
|
507
549
|
acceptAsync: true,
|
|
508
550
|
name: tool,
|
|
509
|
-
|
|
551
|
+
...accountParams,
|
|
552
|
+
arguments: { ...parsedArgs, companyUid, ...routing, idempotencyKey },
|
|
510
553
|
};
|
|
511
554
|
}
|
|
512
555
|
else {
|
|
@@ -515,9 +558,10 @@ export function registerIntegrationsCommand(program) {
|
|
|
515
558
|
companyUid,
|
|
516
559
|
acceptAsync: true,
|
|
517
560
|
name: `${prefix}.mcp.tools.call`,
|
|
561
|
+
...accountParams,
|
|
518
562
|
arguments: {
|
|
519
563
|
companyUid,
|
|
520
|
-
|
|
564
|
+
...routing,
|
|
521
565
|
toolName: tool,
|
|
522
566
|
arguments: parsedArgs,
|
|
523
567
|
idempotencyKey,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Ctx, LaneResumeOptions } from "./lanes-shared.js";
|
|
2
|
+
import { LanesError } from "../lib/lanes/errors.js";
|
|
2
3
|
import type { LaneRecord } from "../lib/lanes/types.js";
|
|
3
4
|
/**
|
|
4
5
|
* A loop lane that finished a phase and is waiting for its next envelope is
|
|
@@ -14,6 +15,25 @@ export declare function settleLoopLaneIdle(hqRoot: string, laneId: string): Lane
|
|
|
14
15
|
* worker, so it is handed to resume as the `done` lane the phase left.
|
|
15
16
|
*/
|
|
16
17
|
export declare function prepareIdleLoopLaneForResume(hqRoot: string, laneId: string): LaneRecord;
|
|
18
|
+
/**
|
|
19
|
+
* LanesError codes from the resume admission path that mean admission could
|
|
20
|
+
* not run right now and a later admission can retry: the admission or store
|
|
21
|
+
* lock timed out (store_lock_timeout), the lane changed while admission was
|
|
22
|
+
* checked (lane_admission_conflict), or a lane record CAS miss
|
|
23
|
+
* (version_conflict). Any other resume error is permanent and is rethrown.
|
|
24
|
+
*/
|
|
25
|
+
export declare const TRANSIENT_LOOP_ADMISSION_ERRORS: ReadonlySet<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Park a loop phase whose resume was refused before an engine launched. The
|
|
28
|
+
* lane becomes `queued` with `admit_failed` and the error, and carries the
|
|
29
|
+
* phase as `pending_resume`, so admission can launch it later and the lane is
|
|
30
|
+
* never left looking active with no engine.
|
|
31
|
+
*/
|
|
32
|
+
export declare function deferRefusedLoopPhase(hqRoot: string, laneId: string, at: string, error: LanesError, phase: {
|
|
33
|
+
text: string;
|
|
34
|
+
sessionId: string | undefined;
|
|
35
|
+
workingDirectory: string;
|
|
36
|
+
}): LaneRecord;
|
|
17
37
|
export interface LoopControllerDeps {
|
|
18
38
|
resumeLane(laneId: string, options: LaneResumeOptions): Promise<unknown>;
|
|
19
39
|
interruptLane(laneId: string, reason: string, options: {
|