@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 +24 -0
- package/dist/commands/agents.js +62 -6
- package/dist/lib/billing/plan-lock.d.ts +7 -0
- package/dist/lib/billing/plan-lock.js +19 -3
- package/package.json +1 -1
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
|
package/dist/commands/agents.js
CHANGED
|
@@ -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
|
|
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 "
|
|
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
|
|
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
|
|
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
|
}
|