@indigoai-us/hq-cli 5.119.4 → 5.119.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.119.6] — 2026-09-17
6
+
7
+ ### Fixed
8
+
9
+ - Slack token paste links now use the company's canonical Console slug. Links
10
+ built with an internal company ID redirected to the company home and lost the
11
+ agent setup form.
12
+
13
+ ## [5.119.5] — 2026-09-17
14
+
15
+ ### Fixed
16
+
17
+ - When a new Slack agent is waiting for a Socket Mode app-level token, `hq
18
+ agents status` now shows a link to create the token and a direct link to the
19
+ HQ form where it can be pasted. The same links are included in JSON output.
20
+
21
+ ### Changed
22
+
23
+ - Starter now includes 1 integration instead of none, and the lock copy says
24
+ so. The notice reads "too many integrations", quotes "Integrations allowed
25
+ on Starter: 1", and tells you to "disconnect integrations until you are at 1
26
+ or fewer"; the per-turn line reads "over its 1-integration limit". Nothing in
27
+ the CLI says Starter has no integrations any more.
28
+
5
29
  ## [5.119.4] — 2026-09-17
6
30
 
7
31
  ### Fixed
@@ -443,8 +443,31 @@ function currentDeviceSignInAction(status) {
443
443
  code,
444
444
  };
445
445
  }
446
- function pendingSlackAction(status) {
446
+ function pendingSlackAction(status, companySlug) {
447
447
  const agent = recordValue(status.agent);
448
+ const configuredSlack = recordValue(recordValue(agent?.channels)?.slack);
449
+ const appId = nonEmptyString(configuredSlack?.appId);
450
+ const agentUid = nonEmptyString(agent?.uid);
451
+ const companyUid = nonEmptyString(agent?.companyUid);
452
+ const tokenPage = safeActionUrl(configuredSlack?.appTokenPendingUrl);
453
+ if (tokenPage &&
454
+ appId && /^A[A-Z0-9]+$/.test(appId) &&
455
+ new URL(tokenPage).hostname === "api.slack.com" &&
456
+ agentUid && AGENT_UID_PATTERN.test(agentUid) &&
457
+ companyUid && /^cmp_[A-Za-z0-9_-]+$/.test(companyUid)) {
458
+ const companyUrlKey = companySlug && /^[a-z0-9][a-z0-9-]*$/.test(companySlug)
459
+ ? companySlug
460
+ : companyUid;
461
+ return {
462
+ type: "slack-app-token",
463
+ title: `Finish ${nonEmptyString(agent?.name) ?? "the agent"}'s Slack connection`,
464
+ summary: "connect it to Slack",
465
+ instruction: "Create an app-level token with the connections:write scope, then paste it into HQ. HQ verifies the token and resumes setup; if Slack asks you to install the app, follow the install link shown there.",
466
+ url: `https://api.slack.com/apps/${appId}/general`,
467
+ urlLabel: "Get token",
468
+ pasteUrl: `https://hq.computer/companies/${encodeURIComponent(companyUrlKey)}/agents?setup=${encodeURIComponent(agentUid)}`,
469
+ };
470
+ }
448
471
  const diagnostics = recordValue(agent?.channelDiagnostics);
449
472
  const slack = recordValue(diagnostics?.slack);
450
473
  const url = safeActionUrl(slack?.pendingInstallUrl);
@@ -476,18 +499,47 @@ function pendingSlackAction(status) {
476
499
  * status fields into a list so another operator action can be added without
477
500
  * changing the renderers or command control flow.
478
501
  */
479
- function pendingOperatorActions(status) {
502
+ function pendingOperatorActions(status, companySlug) {
480
503
  return [
481
504
  currentDeviceSignInAction(status),
482
- pendingSlackAction(status),
505
+ pendingSlackAction(status, companySlug),
483
506
  ].filter((action) => action !== null);
484
507
  }
508
+ async function consoleCompanySlug(token, status, companyRef) {
509
+ if (companyRef && /^[a-z0-9][a-z0-9-]*$/.test(companyRef))
510
+ return companyRef;
511
+ const agent = recordValue(status.agent);
512
+ const slack = recordValue(recordValue(agent?.channels)?.slack);
513
+ if (!slack?.appTokenPendingUrl)
514
+ return undefined;
515
+ const companyUid = nonEmptyString(agent?.companyUid);
516
+ if (!companyUid)
517
+ return undefined;
518
+ try {
519
+ const response = await vaultApiFetch({
520
+ token,
521
+ path: "/membership/me",
522
+ signal: AbortSignal.timeout(10_000),
523
+ });
524
+ if (!response.ok)
525
+ return undefined;
526
+ const body = recordValue(await response.json());
527
+ const memberships = Array.isArray(body?.memberships) ? body.memberships : [];
528
+ const membership = memberships.map(recordValue).find((item) => item?.companyUid === companyUid && item?.status === "active");
529
+ const slug = nonEmptyString(membership?.companySlug);
530
+ return slug && /^[a-z0-9][a-z0-9-]*$/.test(slug) ? slug : undefined;
531
+ }
532
+ catch {
533
+ return undefined;
534
+ }
535
+ }
485
536
  function pendingActionsJson(actions) {
486
- return actions.map(({ type, title, instruction, url, code }) => ({
537
+ return actions.map(({ type, title, instruction, url, pasteUrl, code }) => ({
487
538
  type,
488
539
  title,
489
540
  instruction,
490
541
  ...(url ? { url } : {}),
542
+ ...(pasteUrl ? { pasteUrl } : {}),
491
543
  ...(code ? { code } : {}),
492
544
  }));
493
545
  }
@@ -514,6 +566,8 @@ function printPendingOperatorActions(slug, actions) {
514
566
  console.log(action.instruction);
515
567
  if (action.url)
516
568
  console.log(chalk.cyan(`${action.urlLabel ?? "Open"}: ${action.url}`));
569
+ if (action.pasteUrl)
570
+ console.log(chalk.cyan(`Paste token: ${action.pasteUrl}`));
517
571
  if (action.code)
518
572
  console.log(chalk.bold(`Code: ${action.code}`));
519
573
  }
@@ -1167,7 +1221,9 @@ export function registerAgentsCommand(program) {
1167
1221
  const status = identity
1168
1222
  ? await getAgentStatus(token, identity.uid)
1169
1223
  : null;
1170
- const actions = status ? pendingOperatorActions(status) : [];
1224
+ const actions = status
1225
+ ? pendingOperatorActions(status, await consoleCompanySlug(token, status, companyOf(this)))
1226
+ : [];
1171
1227
  const slackInstall = actions.find((action) => action.type === "slack-install");
1172
1228
  if (opts.json) {
1173
1229
  console.log(JSON.stringify({
@@ -1288,7 +1344,7 @@ export function registerAgentsCommand(program) {
1288
1344
  const token = (await resolveVaultCredential()).token;
1289
1345
  const agentUid = await resolveAgentUid(token, agent, companyOf(this));
1290
1346
  const status = await getAgentStatus(token, agentUid);
1291
- const actions = pendingOperatorActions(status);
1347
+ const actions = pendingOperatorActions(status, await consoleCompanySlug(token, status, companyOf(this)));
1292
1348
  if (opts.json) {
1293
1349
  process.stdout.write(JSON.stringify({
1294
1350
  ...status,
@@ -41,6 +41,7 @@ export interface PlanLock {
41
41
  export interface PlanLockFixOptions {
42
42
  removeMembersTo: number;
43
43
  disconnectIntegrations: boolean;
44
+ disconnectIntegrationsTo?: number;
44
45
  removeSecretsTo?: number;
45
46
  deprovisionAgentsTo?: number;
46
47
  }
@@ -64,6 +65,12 @@ export interface PlanLockStatus {
64
65
  }
65
66
  /** Starter member cap quoted when the server did not send `removeMembersTo`. */
66
67
  export declare const STARTER_MEMBER_TARGET = 5;
68
+ /**
69
+ * Starter integration cap quoted when the server did not send
70
+ * `disconnectIntegrationsTo`. One: Starter includes a single integration
71
+ * (US-020 owner decision 11, 2026-09-17 — it was zero before that).
72
+ */
73
+ export declare const STARTER_INTEGRATION_TARGET = 1;
67
74
  /** Starter secret cap quoted when the server did not send `removeSecretsTo`. */
68
75
  export declare const STARTER_SECRET_TARGET = 10;
69
76
  /** Starter agent cap quoted when the server did not send `deprovisionAgentsTo`. */
@@ -36,6 +36,12 @@ function isPlanLockReason(value) {
36
36
  }
37
37
  /** Starter member cap quoted when the server did not send `removeMembersTo`. */
38
38
  export const STARTER_MEMBER_TARGET = 5;
39
+ /**
40
+ * Starter integration cap quoted when the server did not send
41
+ * `disconnectIntegrationsTo`. One: Starter includes a single integration
42
+ * (US-020 owner decision 11, 2026-09-17 — it was zero before that).
43
+ */
44
+ export const STARTER_INTEGRATION_TARGET = 1;
39
45
  /** Starter secret cap quoted when the server did not send `removeSecretsTo`. */
40
46
  export const STARTER_SECRET_TARGET = 10;
41
47
  /** Starter agent cap quoted when the server did not send `deprovisionAgentsTo`. */
@@ -73,6 +79,7 @@ export function parsePlanLock(value) {
73
79
  }
74
80
  const fix = asRecord(rec.fixOptions);
75
81
  const removeMembersTo = finiteNumber(fix?.removeMembersTo) ?? STARTER_MEMBER_TARGET;
82
+ const disconnectIntegrationsTo = finiteNumber(fix?.disconnectIntegrationsTo);
76
83
  const removeSecretsTo = finiteNumber(fix?.removeSecretsTo);
77
84
  const deprovisionAgentsTo = finiteNumber(fix?.deprovisionAgentsTo);
78
85
  const fixOptions = {
@@ -81,6 +88,9 @@ export function parsePlanLock(value) {
81
88
  };
82
89
  // Absent stays absent: an omitted remedy target is UNKNOWN, and the renderer
83
90
  // quotes the published Starter cap rather than inventing a server answer.
91
+ if (disconnectIntegrationsTo !== null) {
92
+ fixOptions.disconnectIntegrationsTo = disconnectIntegrationsTo;
93
+ }
84
94
  if (removeSecretsTo !== null)
85
95
  fixOptions.removeSecretsTo = removeSecretsTo;
86
96
  if (deprovisionAgentsTo !== null) {
@@ -200,13 +210,16 @@ export function reasonLabel(reason) {
200
210
  case "users":
201
211
  return "too many members";
202
212
  case "integrations":
203
- return "integrations are not included on Starter";
213
+ return "too many integrations";
204
214
  case "secrets":
205
215
  return "too many secrets";
206
216
  case "agents":
207
217
  return "agents are not included on Starter";
208
218
  }
209
219
  }
220
+ function integrationTarget(lock) {
221
+ return lock.fixOptions.disconnectIntegrationsTo ?? STARTER_INTEGRATION_TARGET;
222
+ }
210
223
  function secretTarget(lock) {
211
224
  return lock.fixOptions.removeSecretsTo ?? STARTER_SECRET_TARGET;
212
225
  }
@@ -219,7 +232,7 @@ function reasonRemedy(reason, lock) {
219
232
  case "users":
220
233
  return `remove members until you are at ${lock.fixOptions.removeMembersTo} or fewer`;
221
234
  case "integrations":
222
- return "disconnect the workspace's integrations";
235
+ return `disconnect integrations until you are at ${integrationTarget(lock)} or fewer`;
223
236
  case "secrets":
224
237
  return `delete secrets until you are at ${secretTarget(lock)} or fewer`;
225
238
  case "agents": {
@@ -241,7 +254,7 @@ function reasonDetail(reason, status) {
241
254
  : `over its ${target}-member limit`;
242
255
  }
243
256
  case "integrations":
244
- return "integrations are not included on Starter";
257
+ return `over its ${integrationTarget(status.lock)}-integration limit`;
245
258
  case "secrets":
246
259
  return `over its ${secretTarget(status.lock)}-secret limit`;
247
260
  case "agents":
@@ -275,6 +288,9 @@ export function renderPlanLockNotice(status) {
275
288
  ? ` Members: ${members.used} of ${target}.`
276
289
  : ` Members allowed on Starter: ${target}.`);
277
290
  }
291
+ if (lock.reasons.includes("integrations")) {
292
+ lines.push(` Integrations allowed on Starter: ${integrationTarget(lock)}.`);
293
+ }
278
294
  if (lock.reasons.includes("secrets")) {
279
295
  lines.push(` Secrets allowed on Starter: ${secretTarget(lock)}.`);
280
296
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.119.4",
3
+ "version": "5.119.6",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {