@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
  3. package/dist/command-catalog.generated.d.ts +181 -9
  4. package/dist/command-catalog.generated.js +224 -10
  5. package/dist/commands/access.d.ts +32 -1
  6. package/dist/commands/access.js +119 -5
  7. package/dist/commands/agent-kit.d.ts +1 -1
  8. package/dist/commands/agent-kit.js +3 -1
  9. package/dist/commands/agents.js +2 -1
  10. package/dist/commands/bot.d.ts +3 -0
  11. package/dist/commands/bot.js +141 -14
  12. package/dist/commands/cloud.d.ts +78 -1
  13. package/dist/commands/cloud.js +274 -9
  14. package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
  15. package/dist/commands/core-company-index-scope-flag.js +9 -2
  16. package/dist/commands/core-delegate-w1b.js +5 -2
  17. package/dist/commands/core-dm-bind-native.js +19 -4
  18. package/dist/commands/core-rebuild-index-fast.js +24 -6
  19. package/dist/commands/core.js +18 -6
  20. package/dist/commands/dm.d.ts +17 -3
  21. package/dist/commands/dm.js +121 -26
  22. package/dist/commands/feedback-triage.d.ts +43 -1
  23. package/dist/commands/feedback-triage.js +123 -23
  24. package/dist/commands/feedback.d.ts +40 -1
  25. package/dist/commands/feedback.js +184 -4
  26. package/dist/commands/files.d.ts +42 -0
  27. package/dist/commands/files.js +223 -30
  28. package/dist/commands/integrations-api.d.ts +4 -0
  29. package/dist/commands/integrations-api.js +18 -2
  30. package/dist/commands/integrations-connect.d.ts +10 -3
  31. package/dist/commands/integrations-connect.js +70 -6
  32. package/dist/commands/integrations-core.d.ts +23 -0
  33. package/dist/commands/integrations-core.js +100 -0
  34. package/dist/commands/integrations-manage.js +8 -1
  35. package/dist/commands/integrations.js +49 -5
  36. package/dist/commands/lanes-loop.d.ts +20 -0
  37. package/dist/commands/lanes-loop.js +69 -11
  38. package/dist/commands/lanes.js +9 -1
  39. package/dist/commands/mesh.js +6 -0
  40. package/dist/commands/rescue.d.ts +9 -0
  41. package/dist/commands/rescue.js +31 -0
  42. package/dist/commands/run.js +115 -40
  43. package/dist/commands/scan.d.ts +5 -1
  44. package/dist/commands/scan.js +16 -4
  45. package/dist/commands/secrets.js +50 -9
  46. package/dist/commands/sync-manifest.d.ts +16 -0
  47. package/dist/commands/sync-manifest.js +17 -1
  48. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
  49. package/dist/hq-cloud-sync-runner/package.json +1 -1
  50. package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
  51. package/dist/lib/agent-kit/inbox-state.js +41 -20
  52. package/dist/lib/agent-kit/kit-config.d.ts +2 -0
  53. package/dist/lib/agent-kit/kit-config.js +6 -0
  54. package/dist/lib/agent-kit/mcp/tools.js +12 -5
  55. package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
  56. package/dist/lib/agent-kit/run/heartbeat.js +6 -3
  57. package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
  58. package/dist/lib/agent-kit/run/inbox.js +42 -10
  59. package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
  60. package/dist/lib/agent-kit/run/request-timeout.js +51 -0
  61. package/dist/lib/bot/api.d.ts +1 -1
  62. package/dist/lib/bot/config.d.ts +10 -0
  63. package/dist/lib/bot/config.js +17 -1
  64. package/dist/lib/bot/decisions.d.ts +62 -0
  65. package/dist/lib/bot/decisions.js +228 -0
  66. package/dist/lib/bot/fleet-local.d.ts +127 -0
  67. package/dist/lib/bot/fleet-local.js +638 -0
  68. package/dist/lib/bot/index.d.ts +2 -0
  69. package/dist/lib/bot/index.js +2 -0
  70. package/dist/lib/bot/local-config.d.ts +3 -2
  71. package/dist/lib/bot/local-config.js +5 -2
  72. package/dist/lib/bot/promote.d.ts +10 -0
  73. package/dist/lib/bot/promote.js +3 -2
  74. package/dist/lib/bot/run.d.ts +7 -0
  75. package/dist/lib/bot/run.js +104 -1
  76. package/dist/lib/bot/runtime/index.js +8 -4
  77. package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
  78. package/dist/lib/bot/runtime/scratch-dir.js +41 -0
  79. package/dist/lib/caller-principal.d.ts +4 -0
  80. package/dist/lib/caller-principal.js +40 -0
  81. package/dist/lib/capacity/collectors/grok.js +8 -2
  82. package/dist/lib/cli-flag-keys.d.ts +2 -0
  83. package/dist/lib/cli-flag-keys.js +2 -0
  84. package/dist/lib/daemon/catalog.d.ts +4 -0
  85. package/dist/lib/daemon/catalog.js +63 -29
  86. package/dist/lib/daemon/config.d.ts +7 -0
  87. package/dist/lib/daemon/config.js +32 -0
  88. package/dist/lib/daemon/run.d.ts +3 -2
  89. package/dist/lib/daemon/run.js +23 -3
  90. package/dist/lib/daemon/supervisor.d.ts +1 -0
  91. package/dist/lib/daemon/supervisor.js +4 -0
  92. package/dist/lib/daemon/sync-core-canary.d.ts +12 -0
  93. package/dist/lib/daemon/sync-core-canary.js +61 -0
  94. package/dist/lib/doctor/checks/release-links.d.ts +62 -1
  95. package/dist/lib/doctor/checks/release-links.js +188 -2
  96. package/dist/lib/doctor/checks/sync-health.d.ts +32 -0
  97. package/dist/lib/doctor/checks/sync-health.js +54 -1
  98. package/dist/lib/doctor/fix/apply.d.ts +26 -4
  99. package/dist/lib/doctor/fix/apply.js +149 -11
  100. package/dist/lib/doctor/fix/remediation.d.ts +8 -1
  101. package/dist/lib/doctor/fix/remediation.js +22 -0
  102. package/dist/lib/doctor/types.d.ts +1 -1
  103. package/dist/lib/doctor/types.js +1 -0
  104. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  105. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  106. package/dist/lib/index-render/index.d.ts +9 -0
  107. package/dist/lib/index-render/index.js +65 -5
  108. package/dist/lib/index-render/session-scope.d.ts +69 -0
  109. package/dist/lib/index-render/session-scope.js +279 -0
  110. package/dist/lib/lanes/admission.js +7 -20
  111. package/dist/lib/lanes/reconcile.js +13 -15
  112. package/dist/lib/lanes/stale-lock.d.ts +50 -0
  113. package/dist/lib/lanes/stale-lock.js +105 -0
  114. package/dist/lib/lanes/store.js +11 -76
  115. package/dist/lib/mesh/live/daemon/work-subscribe.js +31 -0
  116. package/dist/lib/notify-recipient.d.ts +10 -4
  117. package/dist/lib/notify-recipient.js +16 -4
  118. package/dist/lib/secret-input-link.d.ts +42 -0
  119. package/dist/lib/secret-input-link.js +93 -0
  120. package/dist/lib/secret-scan/range.d.ts +57 -0
  121. package/dist/lib/secret-scan/range.js +266 -0
  122. package/dist/lib/secret-scan/repo-config.d.ts +18 -3
  123. package/dist/lib/secret-scan/repo-config.js +135 -2
  124. package/dist/lib/work-context/organize.d.ts +7 -0
  125. package/dist/lib/work-context/organize.js +21 -1
  126. package/dist/run/script-bound-schema.d.ts +27 -0
  127. package/dist/run/script-bound-schema.js +97 -0
  128. package/dist/secrets-env-action.d.ts +2 -0
  129. package/dist/secrets-env-action.js +7 -2
  130. package/dist/sentry-before-send.js +1 -0
  131. package/dist/utils/cognito-session.d.ts +3 -30
  132. package/dist/utils/cognito-session.js +2 -100
  133. package/dist/utils/machine-principal-state.d.ts +38 -0
  134. package/dist/utils/machine-principal-state.js +110 -0
  135. package/dist/utils/safe-link.js +9 -0
  136. package/dist/utils/script-bound-exec.d.ts +158 -0
  137. package/dist/utils/script-bound-exec.js +568 -0
  138. package/dist/utils/sync-watchdog.d.ts +92 -0
  139. package/dist/utils/sync-watchdog.js +250 -0
  140. 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 raiseForResponse(res, "Failed to connect the app");
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 raiseForResponse(res, "Failed to start sign-in");
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 declare function findAlreadyConnected(connections: readonly AdminConnection[], app: string): AdminConnection | undefined;
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 target = await resolveTarget(token, companyUid, app, opts, expectedRevivedConnectionId !== undefined);
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
- export function findAlreadyConnected(connections, app) {
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 existing = findAlreadyConnected(connections, app);
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
- console.log(chalk.green(`${label} is already connected.`));
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 = selectConnection(connections, opts);
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 = selectConnection(await fetchConnections(token, companyUid), opts);
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
- arguments: { ...parsedArgs, companyUid, connectionId: connection.id, idempotencyKey },
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
- connectionId: connection.id,
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: {