@indigoai-us/hq-cli 5.345.156 → 5.345.160
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 +255 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
- package/assets/scaffold/core/scripts/worktree.sh +4 -0
- package/dist/command-catalog.generated.d.ts +535 -10
- package/dist/command-catalog.generated.js +678 -31
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +4 -0
- package/dist/commands/_patterns.d.ts +17 -0
- package/dist/commands/_patterns.js +22 -0
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agent-kit.js +8 -6
- package/dist/commands/agents.js +5 -11
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/bot.js +7 -4
- package/dist/commands/channel-suggest.d.ts +56 -0
- package/dist/commands/channel-suggest.js +170 -0
- package/dist/commands/channels.js +7 -1
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.d.ts +45 -0
- package/dist/commands/cloud.js +157 -17
- package/dist/commands/company.js +4 -3
- package/dist/commands/core-checkpoint-native.d.ts +18 -0
- package/dist/commands/core-checkpoint-native.js +67 -2
- package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
- package/dist/commands/core-pipeline-driver-native.js +131 -5
- package/dist/commands/core.js +12 -0
- package/dist/commands/crm.js +83 -6
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/deploy-access.d.ts +33 -0
- package/dist/commands/deploy-access.js +322 -0
- package/dist/commands/dm-attach.d.ts +106 -0
- package/dist/commands/dm-attach.js +184 -0
- package/dist/commands/dm.d.ts +37 -0
- package/dist/commands/dm.js +172 -10
- package/dist/commands/feedback-show.d.ts +40 -0
- package/dist/commands/feedback-show.js +261 -0
- package/dist/commands/feedback.d.ts +21 -0
- package/dist/commands/feedback.js +152 -2
- package/dist/commands/files.d.ts +24 -0
- package/dist/commands/files.js +71 -8
- package/dist/commands/integrations-connect.js +7 -3
- package/dist/commands/lanes-accounts.js +16 -1
- package/dist/commands/lanes-loop.js +14 -4
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-resume.js +36 -13
- package/dist/commands/lanes-shared.d.ts +5 -0
- package/dist/commands/lanes-shared.js +13 -3
- package/dist/commands/lanes-watch.js +1 -0
- package/dist/commands/lanes.d.ts +20 -1
- package/dist/commands/lanes.js +201 -51
- package/dist/commands/meetings.d.ts +4 -4
- package/dist/commands/meetings.js +17 -8
- package/dist/commands/mesh.js +149 -1
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -1
- package/dist/commands/repo.d.ts +41 -0
- package/dist/commands/repo.js +331 -0
- package/dist/commands/scan.d.ts +3 -1
- package/dist/commands/scan.js +7 -3
- package/dist/commands/secrets.d.ts +21 -0
- package/dist/commands/secrets.js +114 -14
- package/dist/commands/signals.js +17 -5
- package/dist/commands/skill.js +6 -6
- package/dist/commands/staff/listings.d.ts +41 -0
- package/dist/commands/staff/listings.js +160 -0
- package/dist/commands/staff/reset-test-account.d.ts +3 -2
- package/dist/commands/staff/reset-test-account.js +4 -3
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3392 -1243
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/run/inbox.js +2 -1
- package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
- package/dist/lib/agent-kit/wake-secrets.js +32 -0
- package/dist/lib/agent-kit/wake.d.ts +4 -0
- package/dist/lib/agent-kit/wake.js +49 -3
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/bot/inflight.d.ts +7 -1
- package/dist/lib/bot/inflight.js +10 -2
- package/dist/lib/bot/prompt.js +3 -0
- package/dist/lib/bot/run.js +72 -20
- package/dist/lib/bot/session.d.ts +14 -0
- package/dist/lib/bot/session.js +70 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
- package/dist/lib/capacity/lane-login.d.ts +6 -0
- package/dist/lib/capacity/lane-login.js +5 -0
- package/dist/lib/core-utils/worktree.js +2 -0
- package/dist/lib/daemon/exit-cause.d.ts +43 -0
- package/dist/lib/daemon/exit-cause.js +88 -0
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +6 -1
- package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +13 -0
- package/dist/lib/daemon/supervisor.d.ts +54 -1
- package/dist/lib/daemon/supervisor.js +297 -21
- package/dist/lib/daemon/unit-holds.d.ts +22 -0
- package/dist/lib/daemon/unit-holds.js +86 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.js +10 -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/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/index-render/company-knowledge.js +1 -3
- package/dist/lib/index-render/projects.js +1 -3
- package/dist/lib/lanes/account-company.d.ts +28 -0
- package/dist/lib/lanes/account-company.js +53 -0
- package/dist/lib/lanes/accounts.d.ts +22 -3
- package/dist/lib/lanes/accounts.js +38 -2
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +6 -1
- package/dist/lib/lanes/claude-credential.d.ts +78 -0
- package/dist/lib/lanes/claude-credential.js +163 -0
- package/dist/lib/lanes/compaction.d.ts +2 -0
- package/dist/lib/lanes/compaction.js +9 -0
- package/dist/lib/lanes/config.js +6 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
- package/dist/lib/lanes/hooks-install.js +2 -1
- package/dist/lib/lanes/host-capacity.d.ts +2 -0
- package/dist/lib/lanes/host-capacity.js +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.js +3 -1
- package/dist/lib/lanes/links.js +10 -4
- package/dist/lib/lanes/loop.js +16 -1
- package/dist/lib/lanes/node-binary.d.ts +17 -0
- package/dist/lib/lanes/node-binary.js +30 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.js +2 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +119 -9
- package/dist/lib/lanes/store.d.ts +6 -0
- package/dist/lib/lanes/store.js +107 -18
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/watch.js +28 -5
- package/dist/lib/lanes/watcher-main.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +4 -0
- package/dist/lib/lanes/watcher.js +110 -21
- package/dist/lib/mesh/api.d.ts +32 -0
- package/dist/lib/mesh/api.js +75 -0
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/lib/secret-scan/detect.js +9 -0
- package/dist/lib/secret-scan/repo-config.d.ts +41 -0
- package/dist/lib/secret-scan/repo-config.js +237 -0
- package/dist/lib/secret-scan/rules.d.ts +7 -1
- package/dist/lib/secret-scan/rules.js +3 -1
- package/dist/lib/secret-scan/staged.d.ts +5 -0
- package/dist/lib/secret-scan/staged.js +19 -2
- package/dist/lib/secret-scan/types.d.ts +2 -0
- package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
- package/dist/lib/sync/server-owned-local-only.js +75 -0
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/package-use-lease.js +27 -9
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +66 -0
- package/package.json +8 -8
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-only files under server-owned vault folders (feedback_0414d71c).
|
|
3
|
+
*
|
|
4
|
+
* The sync engine treats `sources/`, `signals/` and the non-entity part of
|
|
5
|
+
* `ontology/` as cloud-authoritative: the server pipeline writes them and a
|
|
6
|
+
* client push refuses them (`refuse-server-owned` in hq-cloud's share plan,
|
|
7
|
+
* see hq-cloud `src/lib/cloud-authoritative.ts`). The recurring runner skips
|
|
8
|
+
* them quietly, so a file written locally into one of these folders never
|
|
9
|
+
* reaches the vault and nothing reports it as pending. `hq sync status` lists
|
|
10
|
+
* them so the gap is visible.
|
|
11
|
+
*
|
|
12
|
+
* Read-only and network-free: the walk covers only the server-owned folders
|
|
13
|
+
* and the "already synced" set is the company's sync journal the caller
|
|
14
|
+
* already has.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
/** Company-relative folders the server owns and a client push never uploads. */
|
|
19
|
+
export const SERVER_OWNED_FOLDERS = ["sources", "signals", "ontology"];
|
|
20
|
+
/**
|
|
21
|
+
* True for a company-relative path a client push refuses as server-owned.
|
|
22
|
+
* Mirrors hq-cloud's `isLocallyIneligibleForPush` with the entity exception:
|
|
23
|
+
* `ontology/entities/` can be client-owned, so it is not reported here.
|
|
24
|
+
*/
|
|
25
|
+
export function isServerOwnedNeverPushed(rel) {
|
|
26
|
+
const vaultRel = rel.split("\\").join("/").replace(/^\.?\//, "");
|
|
27
|
+
if (vaultRel.startsWith("ontology/entities/"))
|
|
28
|
+
return false;
|
|
29
|
+
return SERVER_OWNED_FOLDERS.some((folder) => vaultRel.startsWith(`${folder}/`));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Walk the server-owned folders under `companies/<slug>/` and return files
|
|
33
|
+
* that are not in the journal. A journaled file arrived by pull and is in
|
|
34
|
+
* sync; anything else was written locally and will never upload.
|
|
35
|
+
*/
|
|
36
|
+
export function findServerOwnedLocalOnly(opts) {
|
|
37
|
+
const root = path.join(opts.hqRoot, "companies", opts.slug);
|
|
38
|
+
const limit = opts.limit ?? 20;
|
|
39
|
+
const found = [];
|
|
40
|
+
const stack = SERVER_OWNED_FOLDERS.map((folder) => path.join(root, folder));
|
|
41
|
+
while (stack.length > 0) {
|
|
42
|
+
const dir = stack.pop();
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const abs = path.join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (entry.name === ".git" || entry.name === "node_modules")
|
|
54
|
+
continue;
|
|
55
|
+
if (opts.shouldSync && !opts.shouldSync(abs, true))
|
|
56
|
+
continue;
|
|
57
|
+
stack.push(abs);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!entry.isFile())
|
|
61
|
+
continue;
|
|
62
|
+
if (opts.shouldSync && !opts.shouldSync(abs, false))
|
|
63
|
+
continue;
|
|
64
|
+
const rel = path.relative(root, abs).split(path.sep).join("/");
|
|
65
|
+
if (!isServerOwnedNeverPushed(rel))
|
|
66
|
+
continue;
|
|
67
|
+
if (opts.journalKeys.has(rel))
|
|
68
|
+
continue;
|
|
69
|
+
found.push(rel);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
found.sort((a, b) => a.localeCompare(b));
|
|
73
|
+
return { files: found.slice(0, limit), total: found.length };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=server-owned-local-only.js.map
|
package/dist/main.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export { handleStreamError } from "./stream-error-boundary.js";
|
|
|
9
9
|
export type { StreamErrorDependencies } from "./stream-error-boundary.js";
|
|
10
10
|
import { type PlanGateError } from "./utils/plan-gate-error.js";
|
|
11
11
|
import { type PricingPlan } from "./lib/billing/usage-only.js";
|
|
12
|
-
import { upgradeToTeam } from "./utils/team-upgrade.js";
|
|
13
12
|
/**
|
|
14
13
|
* Total time one CLI invocation may await foreground network work. This spans
|
|
15
14
|
* the hard version decision, pre-action observability, and release-health
|
|
@@ -44,7 +43,6 @@ export type TopLevelErrorDependencies = {
|
|
|
44
43
|
argv?: readonly string[];
|
|
45
44
|
ask?: (question: string) => Promise<string>;
|
|
46
45
|
openUrl?: (url: string) => Promise<void>;
|
|
47
|
-
upgradeToTeam?: typeof upgradeToTeam;
|
|
48
46
|
/**
|
|
49
47
|
* Usage pricing (US-012): resolve the account's pricing plan for a plan gate
|
|
50
48
|
* whose 402 body did not carry one. Absent = use the body only.
|
package/dist/main.js
CHANGED
|
@@ -40,9 +40,8 @@ import { browserLoginAbandonedMessage } from "./utils/browser-login-abandoned.js
|
|
|
40
40
|
import { callbackPortBusyMessage, DEFAULT_CALLBACK_PORT } from "./utils/callback-port-busy.js";
|
|
41
41
|
import { DEFAULT_COGNITO, isMachineIdentity, loadMachineCachedTokens, } from "./utils/cognito-session.js";
|
|
42
42
|
import { isCompanySelectionError } from "./utils/company-selection-error.js";
|
|
43
|
-
import { canOfferTeamUpgrade, formatPlanGateError,
|
|
43
|
+
import { canOfferTeamUpgrade, formatPlanGateError, offerUpgradeLink, planGateActionUrl, planGateErrorFromVaultError, withPricingPlan, } from "./utils/plan-gate-error.js";
|
|
44
44
|
import { isUsageOnly, resolveCompanyPricingPlan, resolvePersonPricingPlan, } from "./lib/billing/usage-only.js";
|
|
45
|
-
import { upgradeToTeam } from "./utils/team-upgrade.js";
|
|
46
45
|
import { printCachedVersionNudge, printVersionAndNudge, refreshVersionCache, staleAgainstCachedLatest, } from "./utils/version-check.js";
|
|
47
46
|
import { enforceVersionGate, getVersionGatePhase, isLocalDependencyInstall, resolveRunningInstall, shouldSkipGate, } from "./utils/version-gate.js";
|
|
48
47
|
import { autoUpdateAndReexec } from "./utils/self-update.js";
|
|
@@ -654,8 +653,8 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
654
653
|
}
|
|
655
654
|
else if (planGate) {
|
|
656
655
|
// Plan denials are expected product limits, never a CLI crash. The
|
|
657
|
-
// server URL is always shown.
|
|
658
|
-
//
|
|
656
|
+
// server URL is always shown. The flat-plan checkout is closed, so the
|
|
657
|
+
// only offer is to open the server's billing link.
|
|
659
658
|
//
|
|
660
659
|
// A plan-limit refusal (a Starter hard stop) always prints its reason,
|
|
661
660
|
// usage and fix options, even non-interactively: the user can fix it
|
|
@@ -671,60 +670,23 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
671
670
|
? queueCliLimitNoticeExposure(planGate.details.resource, "refusal", planGate.details.companyUid)
|
|
672
671
|
: null;
|
|
673
672
|
deps.setExitCode(1);
|
|
674
|
-
// A usage-only account has no plan to buy: no
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
interactive: true,
|
|
688
|
-
argv: deps.argv,
|
|
689
|
-
ask: deps.ask,
|
|
690
|
-
});
|
|
691
|
-
if (upgraded)
|
|
692
|
-
queueCliLimitNoticeEngagement(promptExposure);
|
|
693
|
-
}
|
|
694
|
-
catch (upgradeErr) {
|
|
695
|
-
const message = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
696
|
-
deps.stderr.write(`hq: ${message}\n`);
|
|
697
|
-
try {
|
|
698
|
-
const opened = await offerUpgradeLink(planGate.details.upgradeUrl, {
|
|
699
|
-
interactive: true,
|
|
700
|
-
argv: deps.argv,
|
|
701
|
-
ask: deps.ask,
|
|
702
|
-
openUrl: deps.openUrl,
|
|
703
|
-
});
|
|
704
|
-
if (opened)
|
|
705
|
-
queueCliLimitNoticeEngagement(promptExposure);
|
|
706
|
-
}
|
|
707
|
-
catch (linkErr) {
|
|
708
|
-
const linkMessage = linkErr instanceof Error ? linkErr.message : String(linkErr);
|
|
709
|
-
deps.stderr.write(`hq: ${linkMessage}\n`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
673
|
+
// A usage-only account has no plan to buy: no upgrade offer. A usage
|
|
674
|
+
// pause offers its own Billing link.
|
|
675
|
+
const actionUrl = planGateActionUrl(planGate);
|
|
676
|
+
if (interactive && actionUrl && (!usageOnlyGate || planGate.details.usagePause)) {
|
|
677
|
+
try {
|
|
678
|
+
const opened = await offerUpgradeLink(actionUrl, {
|
|
679
|
+
interactive: true,
|
|
680
|
+
argv: deps.argv,
|
|
681
|
+
ask: deps.ask,
|
|
682
|
+
openUrl: deps.openUrl,
|
|
683
|
+
});
|
|
684
|
+
if (opened)
|
|
685
|
+
queueCliLimitNoticeEngagement(promptExposure);
|
|
712
686
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
interactive: true,
|
|
717
|
-
argv: deps.argv,
|
|
718
|
-
ask: deps.ask,
|
|
719
|
-
openUrl: deps.openUrl,
|
|
720
|
-
});
|
|
721
|
-
if (opened)
|
|
722
|
-
queueCliLimitNoticeEngagement(promptExposure);
|
|
723
|
-
}
|
|
724
|
-
catch (upgradeErr) {
|
|
725
|
-
const message = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
726
|
-
deps.stderr.write(`hq: ${message}\n`);
|
|
727
|
-
}
|
|
687
|
+
catch (upgradeErr) {
|
|
688
|
+
const message = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
689
|
+
deps.stderr.write(`hq: ${message}\n`);
|
|
728
690
|
}
|
|
729
691
|
}
|
|
730
692
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { utimes } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -152,6 +152,29 @@ export function packageUseLeaseDirectory(prefix, stateDirectory = packageUseStat
|
|
|
152
152
|
function packageUseUpdateRequestPath(prefix, stateDirectory) {
|
|
153
153
|
return path.join(packageUseLeaseDirectory(prefix, stateDirectory), UPDATE_REQUEST);
|
|
154
154
|
}
|
|
155
|
+
// os.uptime() has whole-second resolution, so allow a small margin before
|
|
156
|
+
// calling a request older than the current boot.
|
|
157
|
+
const BOOT_TIME_MARGIN_MS = 5_000;
|
|
158
|
+
/**
|
|
159
|
+
* An update request is stale when it was written before the current boot (its
|
|
160
|
+
* pid may have been reused by an unrelated process since), or when its pid is
|
|
161
|
+
* gone (ESRCH) or belongs to another user (EPERM). The updater always runs as
|
|
162
|
+
* the same user as the CLI, so a pid we may not signal is not our updater.
|
|
163
|
+
*/
|
|
164
|
+
function updateRequestIsStale(requestPath, pid) {
|
|
165
|
+
const bootTimeMs = Date.now() - os.uptime() * 1000;
|
|
166
|
+
if (statSync(requestPath).mtimeMs < bootTimeMs - BOOT_TIME_MARGIN_MS)
|
|
167
|
+
return true;
|
|
168
|
+
try {
|
|
169
|
+
process.kill(pid, 0);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const code = error.code;
|
|
173
|
+
if (code === "ESRCH" || code === "EPERM")
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
155
178
|
function waitForUpdateRequest(requestPath, timeoutMs, notify = null) {
|
|
156
179
|
const started = Date.now();
|
|
157
180
|
let notified = false;
|
|
@@ -163,14 +186,9 @@ function waitForUpdateRequest(requestPath, timeoutMs, notify = null) {
|
|
|
163
186
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
|
|
164
187
|
throw new Error("invalid update request");
|
|
165
188
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
catch (error) {
|
|
170
|
-
if (error.code === "ESRCH") {
|
|
171
|
-
rmSync(requestPath, { force: true });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
189
|
+
if (updateRequestIsStale(requestPath, pid)) {
|
|
190
|
+
rmSync(requestPath, { force: true });
|
|
191
|
+
return;
|
|
174
192
|
}
|
|
175
193
|
}
|
|
176
194
|
catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PlanLimitFreeMonth } from "../lib/billing/free-month.js";
|
|
2
|
-
import { type PricingPlan } from "../lib/billing/usage-only.js";
|
|
2
|
+
import { type PricingPlan, type UsagePause } from "../lib/billing/usage-only.js";
|
|
3
3
|
export type PlanGateCode = "PLAN_LIMIT_EXCEEDED" | "PLAN_REQUIRED" | "MEETING_PLAN_REQUIRED"
|
|
4
|
-
/** A PERSONAL scope asked for a feature
|
|
4
|
+
/** A PERSONAL scope asked for a feature its current plan does not include. */
|
|
5
5
|
| "PERSONAL_PLAN_REQUIRED"
|
|
6
6
|
/** External agent enrollment was denied by the company plan. */
|
|
7
7
|
| "BILLING_REQUIRED";
|
|
@@ -29,19 +29,41 @@ export interface PlanGateDetails {
|
|
|
29
29
|
/** Hard-stop 402: the free-month offer, present only when it stands. */
|
|
30
30
|
freeMonth?: PlanLimitFreeMonth;
|
|
31
31
|
/**
|
|
32
|
-
* Usage pricing (US-012): from the 402 body
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* upgrade link. Absent
|
|
32
|
+
* Usage pricing (US-012): from the 402 body (`billingState` first, then the
|
|
33
|
+
* legacy `pricingPlan`), or attached by the caller from the membership row,
|
|
34
|
+
* usage-limits or the billing summary. `"usage-only"` renders no retired
|
|
35
|
+
* plan copy and no plan upgrade link. Absent (unknown) renders neutral copy
|
|
36
|
+
* that names no plan.
|
|
36
37
|
*/
|
|
37
38
|
pricingPlan?: PricingPlan;
|
|
39
|
+
/**
|
|
40
|
+
* The server's usage pause (`usagePause` on the 402 body, hq-pro
|
|
41
|
+
* src/billing/usage-pause.ts). When present its message is the whole
|
|
42
|
+
* refusal and its `actionUrl` the only link.
|
|
43
|
+
*/
|
|
44
|
+
usagePause?: UsagePause;
|
|
45
|
+
/**
|
|
46
|
+
* Hard-stop 402: true when HQ Workforce is closed to the company (hq-pro
|
|
47
|
+
* `billing.legacy-plans-closed`, owner decision 2026-10-19). `upgradeUrl`
|
|
48
|
+
* is then the usage-pricing switch page; the refusal offers the switch and
|
|
49
|
+
* never the free Workforce month.
|
|
50
|
+
*/
|
|
51
|
+
legacyPlansClosed?: boolean;
|
|
38
52
|
}
|
|
39
53
|
/** Copy a plan gate with the caller's pricing plan attached. */
|
|
40
54
|
export declare function withPricingPlan(err: PlanGateError, pricingPlan: PricingPlan | null | undefined): PlanGateError;
|
|
41
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* One renderer for server-selected billing links across CLI refusals. The
|
|
57
|
+
* link is labelled `Billing:`, or `Switch:` when the server said it is the
|
|
58
|
+
* usage-pricing switch page. It never says "Upgrade": the flat plans that
|
|
59
|
+
* word led to are closed.
|
|
60
|
+
*/
|
|
42
61
|
export declare function formatUpgradeRefusal(reason: string, upgradeUrl: string | undefined, options?: {
|
|
43
62
|
urlOnly?: boolean;
|
|
63
|
+
legacyPlansClosed?: boolean;
|
|
44
64
|
}): string;
|
|
65
|
+
/** The link a refusal offers: the usage pause's action, else the server's upgrade URL. */
|
|
66
|
+
export declare function planGateActionUrl(err: PlanGateError): string | undefined;
|
|
45
67
|
/** Recover a company UID only from the server's UID-keyed billing callback. */
|
|
46
68
|
export declare function companyUidFromPlanUpgradeUrl(value: unknown): string | undefined;
|
|
47
69
|
export declare class PlanGateError extends Error {
|
|
@@ -61,14 +83,8 @@ export interface UpgradeLinkOfferDependencies {
|
|
|
61
83
|
argv?: readonly string[];
|
|
62
84
|
interactive?: boolean;
|
|
63
85
|
}
|
|
64
|
-
/** Ask before opening the exact URL returned by the server. */
|
|
86
|
+
/** Ask before opening the exact billing URL returned by the server. */
|
|
65
87
|
export declare function offerUpgradeLink(url: string, deps?: UpgradeLinkOfferDependencies): Promise<boolean>;
|
|
66
|
-
/** Legacy checkout callback retained for callers outside the shared refusal paths. */
|
|
67
|
-
export declare function offerTeamUpgrade(onConfirm: () => Promise<void>, deps?: {
|
|
68
|
-
ask?: (question: string) => Promise<string>;
|
|
69
|
-
argv?: readonly string[];
|
|
70
|
-
interactive?: boolean;
|
|
71
|
-
}): Promise<boolean>;
|
|
72
88
|
/** Decode an already-read JSON envelope from either shared HTTP client. */
|
|
73
89
|
export declare function planGateErrorFromPayload(status: number, body: unknown, companyUid?: string): PlanGateError | null;
|
|
74
90
|
/**
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { PLAN_LIMIT_DIMENSION_LABELS, renderPersonalPlanFeatureNotice, } from "../lib/plan-limit-nag.js";
|
|
7
7
|
import { withCliLimitEntrySurface } from "../lib/billing/plan-limit-upgrade-url.js";
|
|
8
8
|
import { parseFreeMonth, } from "../lib/billing/free-month.js";
|
|
9
|
-
import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, USAGE_PRICING_POINTER, } from "../lib/billing/usage-only.js";
|
|
10
|
-
/**
|
|
11
|
-
const LEGACY_PLAN_COPY = /Starter|Workforce|Individual|\$500|\$50\b|free month|upgrade/i;
|
|
9
|
+
import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, usagePauseFromBody, USAGE_PRICING_POINTER, } from "../lib/billing/usage-only.js";
|
|
10
|
+
/** Retired plan names, prices and offers a usage-only account must never be shown. */
|
|
11
|
+
const LEGACY_PLAN_COPY = /Starter|Workforce|Individual|\$500|\$50\b|free month|upgrade|plan limit/i;
|
|
12
12
|
/** Copy a plan gate with the caller's pricing plan attached. */
|
|
13
13
|
export function withPricingPlan(err, pricingPlan) {
|
|
14
14
|
if (!pricingPlan || err.details.pricingPlan === pricingPlan)
|
|
@@ -100,23 +100,28 @@ function planLimitRefusalText(head, details) {
|
|
|
100
100
|
`${formatResourceAmount(row.resource, row.used)} of ` +
|
|
101
101
|
`${formatResourceAmount(row.resource, row.limit)} used.`);
|
|
102
102
|
}
|
|
103
|
+
// The free month led to the flat-plan checkout, which is closed; it is
|
|
104
|
+
// never offered. The way out besides trimming is the switch to usage
|
|
105
|
+
// pricing, offered only when the server said the company is on an earlier
|
|
106
|
+
// plan (or that the flat plans are closed to it).
|
|
103
107
|
const fixes = [];
|
|
104
|
-
const freeMonthUrl = safeUpgradeUrl(details.freeMonth?.url);
|
|
105
|
-
if (details.freeMonth?.eligible && freeMonthUrl) {
|
|
106
|
-
fixes.push(`Start your free month — add a card, first month free: ${freeMonthUrl}`);
|
|
107
|
-
}
|
|
108
108
|
for (const [resource, target] of Object.entries(details.fixOptions ?? {})) {
|
|
109
|
-
fixes.push(`${resourceFixAction(resource)} to make room (
|
|
109
|
+
fixes.push(`${resourceFixAction(resource)} to make room (the limit is ` +
|
|
110
110
|
`${formatResourceAmount(resource, target)}).`);
|
|
111
111
|
}
|
|
112
112
|
if (fixes.length > 0) {
|
|
113
|
-
fixes.push(
|
|
113
|
+
fixes.push(details.legacyPlansClosed || details.pricingPlan === "current"
|
|
114
|
+
? "Switch to usage pricing: no plan fee, and these limits stop applying."
|
|
115
|
+
: "Open the company's Billing page.");
|
|
114
116
|
lines.push(" Ways to sort it:");
|
|
115
117
|
fixes.forEach((fix, index) => lines.push(` ${index + 1}. ${fix}`));
|
|
116
118
|
}
|
|
117
119
|
return lines.join("\n");
|
|
118
120
|
}
|
|
119
121
|
function planGateReason(code, details) {
|
|
122
|
+
// The server's usage pause is the whole answer: its message, nothing added.
|
|
123
|
+
if (details.usagePause)
|
|
124
|
+
return details.usagePause.message;
|
|
120
125
|
if (isUsageOnly(details.pricingPlan))
|
|
121
126
|
return usageOnlyPlanGateReason(code, details);
|
|
122
127
|
if (code === "PLAN_LIMIT_EXCEEDED") {
|
|
@@ -125,9 +130,9 @@ function planGateReason(code, details) {
|
|
|
125
130
|
: typeof details.resource === "string" &&
|
|
126
131
|
typeof details.used === "number" &&
|
|
127
132
|
typeof details.limit === "number"
|
|
128
|
-
? `
|
|
133
|
+
? `Limit reached: ${details.resource} ${details.used}/${details.limit} used.`
|
|
129
134
|
: details.resources?.length || details.fixOptions || details.freeMonth
|
|
130
|
-
? "
|
|
135
|
+
? "This account has reached a limit."
|
|
131
136
|
: undefined;
|
|
132
137
|
if (head)
|
|
133
138
|
return planLimitRefusalText(head, details);
|
|
@@ -138,14 +143,19 @@ function planGateReason(code, details) {
|
|
|
138
143
|
return renderPersonalPlanFeatureNotice(details.feature ?? "this feature", null);
|
|
139
144
|
}
|
|
140
145
|
if (code === "MEETING_PLAN_REQUIRED") {
|
|
141
|
-
return "
|
|
146
|
+
return "Meeting notetaker bots are not available on this company's current billing.";
|
|
142
147
|
}
|
|
143
148
|
if (code === "BILLING_REQUIRED") {
|
|
144
|
-
return "
|
|
149
|
+
return "Set up billing for this company before enrolling an external agent.";
|
|
145
150
|
}
|
|
146
|
-
return "
|
|
151
|
+
return "This feature is not available on this company's current billing.";
|
|
147
152
|
}
|
|
148
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* One renderer for server-selected billing links across CLI refusals. The
|
|
155
|
+
* link is labelled `Billing:`, or `Switch:` when the server said it is the
|
|
156
|
+
* usage-pricing switch page. It never says "Upgrade": the flat plans that
|
|
157
|
+
* word led to are closed.
|
|
158
|
+
*/
|
|
149
159
|
export function formatUpgradeRefusal(reason, upgradeUrl, options = {}) {
|
|
150
160
|
const safeUrl = safeUpgradeUrl(upgradeUrl);
|
|
151
161
|
const cliLimitUrl = safeUrl ? withCliLimitEntrySurface(safeUrl) : undefined;
|
|
@@ -153,7 +163,11 @@ export function formatUpgradeRefusal(reason, upgradeUrl, options = {}) {
|
|
|
153
163
|
return cliLimitUrl;
|
|
154
164
|
if (!cliLimitUrl)
|
|
155
165
|
return reason;
|
|
156
|
-
return `${reason}\
|
|
166
|
+
return `${reason}\n${options.legacyPlansClosed ? "Switch" : "Billing"}: ${cliLimitUrl}`;
|
|
167
|
+
}
|
|
168
|
+
/** The link a refusal offers: the usage pause's action, else the server's upgrade URL. */
|
|
169
|
+
export function planGateActionUrl(err) {
|
|
170
|
+
return err.details.usagePause?.actionUrl ?? err.details.upgradeUrl;
|
|
157
171
|
}
|
|
158
172
|
function safeUpgradeUrl(value) {
|
|
159
173
|
if (typeof value !== "string" ||
|
|
@@ -187,6 +201,9 @@ export function companyUidFromPlanUpgradeUrl(value) {
|
|
|
187
201
|
return match?.[1];
|
|
188
202
|
}
|
|
189
203
|
function formatPlanGateErrorText(code, details) {
|
|
204
|
+
if (details.usagePause) {
|
|
205
|
+
return formatUpgradeRefusal(details.usagePause.message, details.usagePause.actionUrl);
|
|
206
|
+
}
|
|
190
207
|
if (isUsageOnly(details.pricingPlan))
|
|
191
208
|
return planGateReason(code, details);
|
|
192
209
|
return formatUpgradeRefusal(planGateReason(code, details), details.upgradeUrl);
|
|
@@ -206,17 +223,21 @@ export function isPlanGateError(err) {
|
|
|
206
223
|
}
|
|
207
224
|
/** Plain, stable CLI copy for plan-gated resource creation. */
|
|
208
225
|
export function formatPlanGateError(err, options = {}) {
|
|
226
|
+
// A usage pause: the server's message and its Billing link, nothing else.
|
|
227
|
+
if (err.details.usagePause) {
|
|
228
|
+
return formatUpgradeRefusal(err.details.usagePause.message, err.details.usagePause.actionUrl, { urlOnly: options.urlOnly });
|
|
229
|
+
}
|
|
209
230
|
// A usage-only account has no plan to upgrade to: no upgrade link.
|
|
210
231
|
if (isUsageOnly(err.details.pricingPlan))
|
|
211
232
|
return planGateReason(err.code, err.details);
|
|
212
|
-
return formatUpgradeRefusal(planGateReason(err.code, err.details), err.details.upgradeUrl, options);
|
|
233
|
+
return formatUpgradeRefusal(planGateReason(err.code, err.details), err.details.upgradeUrl, { ...options, legacyPlansClosed: err.details.legacyPlansClosed === true });
|
|
213
234
|
}
|
|
214
235
|
export function canOfferTeamUpgrade(argv = process.argv) {
|
|
215
236
|
return Boolean(process.stdin.isTTY && process.stderr.isTTY) &&
|
|
216
237
|
!process.env.CI &&
|
|
217
238
|
!argv.includes("--json");
|
|
218
239
|
}
|
|
219
|
-
/** Ask before opening the exact URL returned by the server. */
|
|
240
|
+
/** Ask before opening the exact billing URL returned by the server. */
|
|
220
241
|
export async function offerUpgradeLink(url, deps = {}) {
|
|
221
242
|
const safeUrl = safeUpgradeUrl(url);
|
|
222
243
|
if (!safeUrl)
|
|
@@ -226,13 +247,13 @@ export async function offerUpgradeLink(url, deps = {}) {
|
|
|
226
247
|
return false;
|
|
227
248
|
let answer;
|
|
228
249
|
if (deps.ask) {
|
|
229
|
-
answer = await deps.ask("Open
|
|
250
|
+
answer = await deps.ask("Open the billing link now? [y/N] ");
|
|
230
251
|
}
|
|
231
252
|
else {
|
|
232
253
|
const { createInterface } = await import("node:readline/promises");
|
|
233
254
|
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
234
255
|
try {
|
|
235
|
-
answer = await readline.question("Open
|
|
256
|
+
answer = await readline.question("Open the billing link now? [y/N] ");
|
|
236
257
|
}
|
|
237
258
|
finally {
|
|
238
259
|
readline.close();
|
|
@@ -249,38 +270,29 @@ export async function offerUpgradeLink(url, deps = {}) {
|
|
|
249
270
|
}
|
|
250
271
|
return true;
|
|
251
272
|
}
|
|
252
|
-
/** Legacy checkout callback retained for callers outside the shared refusal paths. */
|
|
253
|
-
export async function offerTeamUpgrade(onConfirm, deps = {}) {
|
|
254
|
-
if (!(deps.interactive ?? canOfferTeamUpgrade(deps.argv)))
|
|
255
|
-
return false;
|
|
256
|
-
if (deps.ask) {
|
|
257
|
-
const answer = await deps.ask("Upgrade now? (opens checkout) [y/N] ");
|
|
258
|
-
if (!/^y(?:es)?$/i.test(answer.trim()))
|
|
259
|
-
return false;
|
|
260
|
-
await onConfirm();
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
const { createInterface } = await import("node:readline/promises");
|
|
264
|
-
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
265
|
-
try {
|
|
266
|
-
const answer = await readline.question("Upgrade now? (opens checkout) [y/N] ");
|
|
267
|
-
if (!/^y(?:es)?$/i.test(answer.trim()))
|
|
268
|
-
return false;
|
|
269
|
-
await onConfirm();
|
|
270
|
-
return true;
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
readline.close();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
273
|
/** Decode an already-read JSON envelope from either shared HTTP client. */
|
|
277
274
|
export function planGateErrorFromPayload(status, body, companyUid) {
|
|
278
275
|
if (status !== 402 || !body || typeof body !== "object")
|
|
279
276
|
return null;
|
|
280
277
|
const payload = body;
|
|
281
|
-
const
|
|
278
|
+
const usagePause = usagePauseFromBody(payload);
|
|
279
|
+
// A usage pause in a body this decoder does not otherwise know is still a
|
|
280
|
+
// refusal: the server's message is the whole answer.
|
|
281
|
+
const decoded = decodePlanGatePayload(payload, companyUid) ??
|
|
282
|
+
(usagePause
|
|
283
|
+
? new PlanGateError("PLAN_LIMIT_EXCEEDED", {
|
|
284
|
+
reason: usagePause.message,
|
|
285
|
+
...(usagePause.actionUrl ? { upgradeUrl: usagePause.actionUrl } : {}),
|
|
286
|
+
...(companyUid ? { companyUid } : {}),
|
|
287
|
+
})
|
|
288
|
+
: null);
|
|
289
|
+
if (!decoded)
|
|
290
|
+
return null;
|
|
282
291
|
const pricingPlan = pricingPlanFromBody(payload);
|
|
283
|
-
|
|
292
|
+
const withPlan = pricingPlan ? withPricingPlan(decoded, pricingPlan) : decoded;
|
|
293
|
+
return usagePause
|
|
294
|
+
? new PlanGateError(withPlan.code, { ...withPlan.details, usagePause })
|
|
295
|
+
: withPlan;
|
|
284
296
|
}
|
|
285
297
|
function decodePlanGatePayload(payload, companyUid) {
|
|
286
298
|
// US-054: the personal feature denial. hq-pro identifies it with
|
|
@@ -392,6 +404,7 @@ function planLimitRefusalFromPayload(payload, upgradeUrl, companyUid) {
|
|
|
392
404
|
: undefined;
|
|
393
405
|
const resources = parseResources(payload.resources);
|
|
394
406
|
const fixOptions = parseFixOptions(payload.fixOptions);
|
|
407
|
+
const legacyPlansClosed = payload.legacyPlansClosed === true;
|
|
395
408
|
const parsedFreeMonth = parseFreeMonth(payload.freeMonth);
|
|
396
409
|
const freeMonthUrl = safeUpgradeUrl(parsedFreeMonth?.url);
|
|
397
410
|
const freeMonth = parsedFreeMonth
|
|
@@ -416,6 +429,7 @@ function planLimitRefusalFromPayload(payload, upgradeUrl, companyUid) {
|
|
|
416
429
|
...(upgradeUrl ? { upgradeUrl } : {}),
|
|
417
430
|
...(reason ? { reason } : {}),
|
|
418
431
|
...(companyUid ? { companyUid } : {}),
|
|
432
|
+
...(legacyPlansClosed ? { legacyPlansClosed } : {}),
|
|
419
433
|
});
|
|
420
434
|
}
|
|
421
435
|
/** Wire codes that mark a vault/sync error as a plan-limit refusal. */
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `hq billing upgrade` for a company not on usage pricing (US-024).
|
|
3
|
+
*
|
|
4
|
+
* The flat plans are closed to new subscribers (hq-pro
|
|
5
|
+
* `billing.legacy-plans-closed`, owner decision 2026-10-19), so there is no
|
|
6
|
+
* checkout to open. The command points at the console's switch to usage
|
|
7
|
+
* pricing for the company. It POSTs nothing: the switch itself, and its owner
|
|
8
|
+
* gate, live on that page.
|
|
9
|
+
*/
|
|
10
|
+
export interface UsageSwitchOptions {
|
|
11
|
+
/** The company slug, for the company's switch page. */
|
|
12
|
+
companySlug?: string;
|
|
4
13
|
noBrowser?: boolean;
|
|
5
14
|
/** Testable override for environments where browser availability is known. */
|
|
6
15
|
headless?: boolean;
|
|
@@ -9,9 +18,13 @@ export interface TeamUpgradeOptions {
|
|
|
9
18
|
write?: (text: string) => void;
|
|
10
19
|
error?: (text: string) => void;
|
|
11
20
|
}
|
|
21
|
+
/** The lines printed before the switch page link. Names no plan. */
|
|
22
|
+
export declare function usageSwitchLines(url: string): string[];
|
|
23
|
+
/** The fallback sentence when no console origin can be derived. */
|
|
24
|
+
export declare const USAGE_SWITCH_NO_URL_MESSAGE = "Switch this company to usage pricing from its Billing page in the HQ Console.";
|
|
12
25
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
26
|
+
* Print (and open, unless told not to) the company's usage-pricing switch
|
|
27
|
+
* page. Returns the URL, or null when none could be built.
|
|
15
28
|
*/
|
|
16
|
-
export declare function
|
|
29
|
+
export declare function openUsageSwitch(opts?: UsageSwitchOptions): Promise<string | null>;
|
|
17
30
|
//# sourceMappingURL=team-upgrade.d.ts.map
|