@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
|
@@ -400,6 +400,12 @@ export declare const REGISTRATION_PLAN: readonly [{
|
|
|
400
400
|
readonly parent: "program";
|
|
401
401
|
readonly module: "./commands/mcp-status.js";
|
|
402
402
|
readonly exportName: "registerMcpCommand";
|
|
403
|
+
}, {
|
|
404
|
+
readonly type: "registrar";
|
|
405
|
+
readonly root: "repo";
|
|
406
|
+
readonly parent: "program";
|
|
407
|
+
readonly module: "./commands/repo.js";
|
|
408
|
+
readonly exportName: "registerRepoCommand";
|
|
403
409
|
}, {
|
|
404
410
|
readonly type: "registrar";
|
|
405
411
|
readonly root: "crm";
|
|
@@ -533,6 +539,12 @@ export declare const REGISTRATION_PLAN: readonly [{
|
|
|
533
539
|
readonly parent: "deploy";
|
|
534
540
|
readonly module: "./commands/deploy-list.js";
|
|
535
541
|
readonly exportName: "registerDeployListCommand";
|
|
542
|
+
}, {
|
|
543
|
+
readonly type: "registrar";
|
|
544
|
+
readonly root: "deploy";
|
|
545
|
+
readonly parent: "deploy";
|
|
546
|
+
readonly module: "./commands/deploy-access.js";
|
|
547
|
+
readonly exportName: "registerDeployAccessCommand";
|
|
536
548
|
}, {
|
|
537
549
|
readonly type: "group";
|
|
538
550
|
readonly root: "staff";
|
|
@@ -551,6 +563,12 @@ export declare const REGISTRATION_PLAN: readonly [{
|
|
|
551
563
|
readonly parent: "staff";
|
|
552
564
|
readonly module: "./commands/staff/test-account.js";
|
|
553
565
|
readonly exportName: "registerTestAccountCommand";
|
|
566
|
+
}, {
|
|
567
|
+
readonly type: "registrar";
|
|
568
|
+
readonly root: "staff";
|
|
569
|
+
readonly parent: "staff";
|
|
570
|
+
readonly module: "./commands/staff/listings.js";
|
|
571
|
+
readonly exportName: "registerListingsCommand";
|
|
554
572
|
}];
|
|
555
573
|
export declare function registerAllCommands(program: Command): Promise<void>;
|
|
556
574
|
export declare function registerCommandRoot(program: Command, root: string): Promise<boolean>;
|
|
@@ -69,6 +69,7 @@ export const REGISTRATION_PLAN = [
|
|
|
69
69
|
{ type: "registrar", root: "reindex", parent: "program", module: "./commands/reindex.js", exportName: "registerReindexCommand" },
|
|
70
70
|
{ type: "registrar", root: "rescue", parent: "program", module: "./commands/rescue.js", exportName: "registerRescueCommand" },
|
|
71
71
|
{ type: "registrar", root: "mcp", parent: "program", module: "./commands/mcp-status.js", exportName: "registerMcpCommand" },
|
|
72
|
+
{ type: "registrar", root: "repo", parent: "program", module: "./commands/repo.js", exportName: "registerRepoCommand" },
|
|
72
73
|
{ type: "registrar", root: "crm", parent: "program", module: "./commands/crm.js", exportName: "registerCrmCommand" },
|
|
73
74
|
{ type: "registrar", root: "company", parent: "program", module: "./commands/company.js", exportName: "registerCompanyCommand" },
|
|
74
75
|
{ type: "registrar", root: "agents", parent: "program", module: "./commands/agents.js", exportName: "registerAgentsCommand" },
|
|
@@ -91,12 +92,15 @@ export const REGISTRATION_PLAN = [
|
|
|
91
92
|
{ type: "group", root: "deploy", id: "deploy", name: "deploy", description: "Manage deployments in hq-deploy" },
|
|
92
93
|
{ type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-comments.js", exportName: "registerDeployCommentsCommand" },
|
|
93
94
|
{ type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-list.js", exportName: "registerDeployListCommand" },
|
|
95
|
+
{ type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-access.js", exportName: "registerDeployAccessCommand" },
|
|
94
96
|
// onboarding-test-accounts US-007 — super-admin staff CLI wrappers over the
|
|
95
97
|
// hq-pro admin surface. New wrappers land under this group; the reset
|
|
96
98
|
// command mirrors POST /v1/admin/test-account/reset.
|
|
97
99
|
{ type: "group", root: "staff", id: "staff", name: "staff", description: "Staff-only commands (super-admin required)" },
|
|
98
100
|
{ type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/reset-test-account.js", exportName: "registerResetTestAccountCommand" },
|
|
99
101
|
{ type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/test-account.js", exportName: "registerTestAccountCommand" },
|
|
102
|
+
// Marketplace moderation: queue / approve / reject over /v1/moderation.
|
|
103
|
+
{ type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/listings.js", exportName: "registerListingsCommand" },
|
|
100
104
|
];
|
|
101
105
|
async function executePlan(program, steps) {
|
|
102
106
|
const parents = new Map([["program", program]]);
|
|
@@ -7,6 +7,23 @@ export declare const EMAIL_PATTERN: RegExp;
|
|
|
7
7
|
* grantee slot on the wire.
|
|
8
8
|
*/
|
|
9
9
|
export declare const OUTPOST_UID_PATTERN: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* A fleet agent uid: `agt_` plus a 26-character Crockford ULID, upper-case,
|
|
12
|
+
* the same shape as OUTPOST_UID_PATTERN. A typo (wrong length, lower-case,
|
|
13
|
+
* or a letter outside the Crockford alphabet: I, L, O, U) fails locally.
|
|
14
|
+
*/
|
|
15
|
+
export declare const AGENT_UID_PATTERN: RegExp;
|
|
16
|
+
/**
|
|
17
|
+
* The machine email a fleet agent signs in with:
|
|
18
|
+
* `agt-<ulid>@agents.getindigo.ai`, lower-cased. The CLI sends this email as
|
|
19
|
+
* the grantee; hq-pro canonicalizes it onto the agent's `agt_…` principal
|
|
20
|
+
* when the agent is an active member of the company.
|
|
21
|
+
*/
|
|
22
|
+
export declare function agentMachineEmail(agentUid: string): string;
|
|
23
|
+
/** Matches a fleet agent machine email; group 1 is the lower-case ULID. */
|
|
24
|
+
export declare const AGENT_MACHINE_EMAIL_PATTERN: RegExp;
|
|
25
|
+
/** The `agt_…` uid behind a machine email, or null when it is not one. */
|
|
26
|
+
export declare function agentUidFromMachineEmail(email: string): string | null;
|
|
10
27
|
/**
|
|
11
28
|
* Keep an ACL pattern exactly as the caller supplied it.
|
|
12
29
|
*
|
|
@@ -7,6 +7,28 @@ export const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
|
|
|
7
7
|
* grantee slot on the wire.
|
|
8
8
|
*/
|
|
9
9
|
export const OUTPOST_UID_PATTERN = /^out_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
10
|
+
/**
|
|
11
|
+
* A fleet agent uid: `agt_` plus a 26-character Crockford ULID, upper-case,
|
|
12
|
+
* the same shape as OUTPOST_UID_PATTERN. A typo (wrong length, lower-case,
|
|
13
|
+
* or a letter outside the Crockford alphabet: I, L, O, U) fails locally.
|
|
14
|
+
*/
|
|
15
|
+
export const AGENT_UID_PATTERN = /^agt_[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
16
|
+
/**
|
|
17
|
+
* The machine email a fleet agent signs in with:
|
|
18
|
+
* `agt-<ulid>@agents.getindigo.ai`, lower-cased. The CLI sends this email as
|
|
19
|
+
* the grantee; hq-pro canonicalizes it onto the agent's `agt_…` principal
|
|
20
|
+
* when the agent is an active member of the company.
|
|
21
|
+
*/
|
|
22
|
+
export function agentMachineEmail(agentUid) {
|
|
23
|
+
return `agt-${agentUid.slice(4).toLowerCase()}@agents.getindigo.ai`;
|
|
24
|
+
}
|
|
25
|
+
/** Matches a fleet agent machine email; group 1 is the lower-case ULID. */
|
|
26
|
+
export const AGENT_MACHINE_EMAIL_PATTERN = /^agt-([0-9a-hjkmnp-tv-z]{26})@agents\.getindigo\.ai$/i;
|
|
27
|
+
/** The `agt_…` uid behind a machine email, or null when it is not one. */
|
|
28
|
+
export function agentUidFromMachineEmail(email) {
|
|
29
|
+
const m = AGENT_MACHINE_EMAIL_PATTERN.exec(email.trim());
|
|
30
|
+
return m ? `agt_${m[1].toUpperCase()}` : null;
|
|
31
|
+
}
|
|
10
32
|
/**
|
|
11
33
|
* Keep an ACL pattern exactly as the caller supplied it.
|
|
12
34
|
*
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
import { type CompanyBrowseClientFactory, type FilesBrowseVaultClient, type RunGetResult } from "./files-browse.js";
|
|
18
|
+
import { type AclLock } from "./acl-lock.js";
|
|
18
19
|
export type AccessOutcome = "never-existed" | "local" | "not-synced" | "no-access" | "ambiguous" | "pending-confirmation";
|
|
19
20
|
export interface AccessAclTree {
|
|
20
21
|
prefix: string;
|
|
@@ -145,6 +146,11 @@ export interface RunAccessResult {
|
|
|
145
146
|
requestSentAt?: string;
|
|
146
147
|
alreadyAskedAt?: string;
|
|
147
148
|
requestNote?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Most-specific ACL row covering the path when it has no grants. Broader
|
|
151
|
+
* grants (including a root `*` company-wide grant) do not apply.
|
|
152
|
+
*/
|
|
153
|
+
lockedBy?: AclLock;
|
|
148
154
|
}
|
|
149
155
|
export interface AccessAsResult {
|
|
150
156
|
personUid: string;
|
package/dist/commands/access.js
CHANGED
|
@@ -23,6 +23,7 @@ import { getCompanyUid, vaultApiFetch, resolveCallerUid } from "../utils/vault-a
|
|
|
23
23
|
import { parseCompanySlugFromPath, classifyAclSource, resolveBrowseSession, makeCompanyPresignFactory, runGet, toBucketRelative, } from "./files-browse.js";
|
|
24
24
|
import { listJournals, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
25
25
|
import { listActiveMembers } from "./members.js";
|
|
26
|
+
import { aclLockedBy } from "./acl-lock.js";
|
|
26
27
|
import { buildDmBody } from "./dm.js";
|
|
27
28
|
import { peekIdToken } from "../utils/id-token.js";
|
|
28
29
|
import { findRecentAccessRequest, formatTimeAgo, recordAccessRequest, } from "../utils/access-requests.js";
|
|
@@ -458,10 +459,23 @@ async function completeTarget(input, deps, located) {
|
|
|
458
459
|
const { resolvedKey, directory, vendDenied, listDenied, verdict, steps } = located;
|
|
459
460
|
const resolvedPath = `companies/${slug}/${resolvedKey}`;
|
|
460
461
|
let noAccess = vendDenied || listDenied;
|
|
462
|
+
// Name the empty fencing row only when the tree denies access. A role
|
|
463
|
+
// bypass or creator grant still reads the same as the acl/resolve fast
|
|
464
|
+
// path, which never fetches the tree.
|
|
465
|
+
const lockRow = verdict.kind === "tree" && aclDenied(verdict.tree) ? aclLockedBy(verdict.tree) : null;
|
|
466
|
+
const lockedBy = lockRow
|
|
467
|
+
? { ...lockRow, path: `companies/${slug}/${toBucketRelative(lockRow.path, slug)}` }
|
|
468
|
+
: undefined;
|
|
469
|
+
if (lockedBy) {
|
|
470
|
+
steps.push(lockedBy.readUnknown
|
|
471
|
+
? `acl: locked by ${lockedBy.path} unless that row is open (it has no grants; broader grants such as * apply only if it is open)`
|
|
472
|
+
: `acl: locked by ${lockedBy.path} (that row has no grants and is not open; broader grants such as * do not apply)`);
|
|
473
|
+
}
|
|
461
474
|
if (verdict.kind === "resolved") {
|
|
462
475
|
steps.push(`acl: ${verdict.permission}`);
|
|
463
476
|
}
|
|
464
|
-
else if (verdict.tree == null ||
|
|
477
|
+
else if (verdict.tree == null ||
|
|
478
|
+
(treeIsEmpty(verdict.tree) && !verdict.tree.coveringAclPath)) {
|
|
465
479
|
steps.push("acl: no record (membership-distributed)");
|
|
466
480
|
}
|
|
467
481
|
else if (aclDenied(verdict.tree)) {
|
|
@@ -687,6 +701,7 @@ async function completeTarget(input, deps, located) {
|
|
|
687
701
|
...(requestSentAt ? { requestSentAt } : {}),
|
|
688
702
|
...(alreadyAskedAt ? { alreadyAskedAt } : {}),
|
|
689
703
|
...(requestNote ? { requestNote } : {}),
|
|
704
|
+
...(lockedBy ? { lockedBy } : {}),
|
|
690
705
|
}, finishCtx);
|
|
691
706
|
return result;
|
|
692
707
|
}
|
|
@@ -834,6 +849,8 @@ function jsonPayload(result) {
|
|
|
834
849
|
payload.requestSentAt = result.requestSentAt;
|
|
835
850
|
if (result.alreadyAskedAt)
|
|
836
851
|
payload.alreadyAskedAt = result.alreadyAskedAt;
|
|
852
|
+
if (result.lockedBy)
|
|
853
|
+
payload.lockedBy = result.lockedBy;
|
|
837
854
|
return payload;
|
|
838
855
|
}
|
|
839
856
|
function emit(input, result, log) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty-row lock detection for ACL tree responses (feedback dd287b55).
|
|
3
|
+
*
|
|
4
|
+
* The server resolves a path against its most-specific matching FILE_ACL row
|
|
5
|
+
* (`coveringAclPath` on the ACL tree response). Enforcement (files-presign,
|
|
6
|
+
* files-list, content) runs resolveEffectivePermission in hq-pro-core
|
|
7
|
+
* src/tenant-contracts/acl-permission.ts with `unionGrants: true`: grants on
|
|
8
|
+
* every matching row are unioned, except that an EMPTY, NON-OPEN winning row
|
|
9
|
+
* fences the path and grants nothing (only the row's creator and the
|
|
10
|
+
* owner/admin role bypass keep access). An OPEN empty winning row is not a
|
|
11
|
+
* fence: it floors every member at read and broader grants still apply.
|
|
12
|
+
*/
|
|
13
|
+
export interface AclLockTree {
|
|
14
|
+
prefix: string;
|
|
15
|
+
direct: unknown[];
|
|
16
|
+
inherited: Array<{
|
|
17
|
+
sourcePrefix: string;
|
|
18
|
+
}>;
|
|
19
|
+
directRow: {
|
|
20
|
+
open: boolean;
|
|
21
|
+
} | null;
|
|
22
|
+
coveringAclPath?: string | null;
|
|
23
|
+
}
|
|
24
|
+
export interface AclLock {
|
|
25
|
+
/** Path of the empty most-specific row. */
|
|
26
|
+
path: string;
|
|
27
|
+
/**
|
|
28
|
+
* True when the row's `open` flag is unknown. The tree reports `open` only
|
|
29
|
+
* for the exact prefix row (directRow), not for ancestor rows. Such a row is
|
|
30
|
+
* a lock if it is not open; if it is open, broader grants still apply.
|
|
31
|
+
*/
|
|
32
|
+
readUnknown: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The empty, non-open row that fences `tree.prefix`, or null when the
|
|
36
|
+
* most-specific row grants something, is open, or no row covers the path.
|
|
37
|
+
*/
|
|
38
|
+
export declare function aclLockedBy(tree: AclLockTree | null | undefined): AclLock | null;
|
|
39
|
+
/** One-line description of the lock for human output. */
|
|
40
|
+
export declare function describeAclLock(lock: AclLock, display: string): string;
|
|
41
|
+
//# sourceMappingURL=acl-lock.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The empty, non-open row that fences `tree.prefix`, or null when the
|
|
3
|
+
* most-specific row grants something, is open, or no row covers the path.
|
|
4
|
+
*/
|
|
5
|
+
export function aclLockedBy(tree) {
|
|
6
|
+
const covering = tree?.coveringAclPath;
|
|
7
|
+
if (!tree || typeof covering !== "string" || covering === "")
|
|
8
|
+
return null;
|
|
9
|
+
if (covering === tree.prefix) {
|
|
10
|
+
if (!tree.directRow || tree.directRow.open === true)
|
|
11
|
+
return null;
|
|
12
|
+
if (!Array.isArray(tree.direct) || tree.direct.length > 0)
|
|
13
|
+
return null;
|
|
14
|
+
return { path: covering, readUnknown: false };
|
|
15
|
+
}
|
|
16
|
+
const inherited = Array.isArray(tree.inherited) ? tree.inherited : [];
|
|
17
|
+
if (inherited.some((entry) => entry.sourcePrefix === covering))
|
|
18
|
+
return null;
|
|
19
|
+
return { path: covering, readUnknown: true };
|
|
20
|
+
}
|
|
21
|
+
/** One-line description of the lock for human output. */
|
|
22
|
+
export function describeAclLock(lock, display) {
|
|
23
|
+
if (lock.readUnknown) {
|
|
24
|
+
return `Locked by ${display} unless that row is open: it has no grants, so grants on broader prefixes apply only if it is open.`;
|
|
25
|
+
}
|
|
26
|
+
return `Locked by ${display}: that ACL row has no grants and is not open, so grants on broader prefixes do not apply. Only the row's creator and the owner/admin role bypass have access.`;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=acl-lock.js.map
|
|
@@ -60,6 +60,8 @@ export declare function mapEnrollFailure(status: number, body: {
|
|
|
60
60
|
attemptsRemaining?: unknown;
|
|
61
61
|
upgradeUrl?: unknown;
|
|
62
62
|
pricingPlan?: unknown;
|
|
63
|
+
usagePause?: unknown;
|
|
64
|
+
billingState?: unknown;
|
|
63
65
|
}, headers?: {
|
|
64
66
|
retryAfter?: string | null;
|
|
65
67
|
}): EnrollError;
|
|
@@ -21,7 +21,7 @@ import * as path from "node:path";
|
|
|
21
21
|
import { CLI_VERSION } from "../cli-version.js";
|
|
22
22
|
import { DEFAULT_VAULT_API_URL, personTokenCacheFile, } from "../utils/cognito-session.js";
|
|
23
23
|
import { HQ_CLIENT_NAME } from "../utils/vault-api.js";
|
|
24
|
-
import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
|
|
24
|
+
import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, usagePauseFromBody, } from "../lib/billing/usage-only.js";
|
|
25
25
|
import { queueCliLimitNoticeEngagement, queueCliLimitNoticeExposure, } from "../utils/cli-telemetry.js";
|
|
26
26
|
import { networkTransportErrorCode } from "../utils/network-transport-error.js";
|
|
27
27
|
import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
@@ -139,6 +139,11 @@ export function mapEnrollFailure(status, body, headers) {
|
|
|
139
139
|
return new EnrollError(`This agent was revoked; its enrollment code no longer works. An admin must ` +
|
|
140
140
|
`create a new external agent or rotate this one.${suffix}`, status, code);
|
|
141
141
|
}
|
|
142
|
+
// US-024: a usage pause is the server's own answer: its message and link.
|
|
143
|
+
const usagePause = status === 402 ? usagePauseFromBody(body) : null;
|
|
144
|
+
if (usagePause) {
|
|
145
|
+
return new EnrollError(usagePause.message, status, code, usagePause.actionUrl);
|
|
146
|
+
}
|
|
142
147
|
if ((status === 402 || code === "billing_required") &&
|
|
143
148
|
isUsageOnly(pricingPlanFromBody(body))) {
|
|
144
149
|
// Usage pricing (US-012): no paid plan to upgrade to. A company admin adds
|
|
@@ -146,8 +151,8 @@ export function mapEnrollFailure(status, body, headers) {
|
|
|
146
151
|
return new EnrollError(usageOnlyCardRequiredLines("An external agent").join(" ") + suffix, status, code);
|
|
147
152
|
}
|
|
148
153
|
if (status === 402 || code === "billing_required") {
|
|
149
|
-
return new EnrollError(`External agents
|
|
150
|
-
`
|
|
154
|
+
return new EnrollError(`External agents need billing set up for this company. A company admin can ` +
|
|
155
|
+
`add a card with \`hq billing checkout\` or on the console Billing page, then enroll again.${suffix}`, status, code, typeof body.upgradeUrl === "string" ? body.upgradeUrl : undefined);
|
|
151
156
|
}
|
|
152
157
|
if (status === 429 || code === "rate_limited") {
|
|
153
158
|
const retryAfter = headers?.retryAfter?.trim();
|
|
@@ -425,7 +430,7 @@ export function registerAgentEnrollCommand(agent) {
|
|
|
425
430
|
}
|
|
426
431
|
catch (openErr) {
|
|
427
432
|
const message = openErr instanceof Error ? openErr.message : String(openErr);
|
|
428
|
-
console.error(chalk.dim(`Could not open the
|
|
433
|
+
console.error(chalk.dim(`Could not open the billing link automatically: ${message}`));
|
|
429
434
|
}
|
|
430
435
|
}
|
|
431
436
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* output in ~/.hq-agent/logs/kit.log). Logs: ~/.hq-agent/logs/<service>.log. Skills:
|
|
18
18
|
* ~/.hq-agent/skills/<name>/SKILL.md.
|
|
19
19
|
*/
|
|
20
|
+
import { createWakeSecretResolver } from "../lib/agent-kit/wake-secrets.js";
|
|
20
21
|
import chalk from "chalk";
|
|
21
22
|
import * as fs from "node:fs";
|
|
22
23
|
import * as os from "node:os";
|
|
@@ -37,7 +38,7 @@ import { serviceLogPath } from "../lib/agent-kit/paths.js";
|
|
|
37
38
|
import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
|
|
38
39
|
import { spawn } from "node:child_process";
|
|
39
40
|
import { pendingInbox } from "../lib/agent-kit/inbox-state.js";
|
|
40
|
-
import { buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_MS, describeWake, fireWake, readWakeConfig, validateWakeUrl, writeWakeConfig, } from "../lib/agent-kit/wake.js";
|
|
41
|
+
import { parseHeaderSecrets, buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_MS, describeWake, fireWake, readWakeConfig, validateWakeUrl, writeWakeConfig, } from "../lib/agent-kit/wake.js";
|
|
41
42
|
import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
|
|
42
43
|
import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
|
|
43
44
|
import { installServices, servicesStatus, uninstallServices, } from "../lib/service-manager/index.js";
|
|
@@ -380,6 +381,7 @@ export function registerAgentKitCommand(agent) {
|
|
|
380
381
|
.description("POST a notice to a webhook URL (e.g. a Grok Bot routine webhook) when mail is pending. Prefer --stdin so the URL stays out of shell history")
|
|
381
382
|
.option("--stdin", "Read the URL from stdin")
|
|
382
383
|
.option("--retry-minutes <n>", "Re-wake while items stay pending (default 10, minimum 1)")
|
|
384
|
+
.option("--header-secret <Header=SECRET>", "Send a header using a stored company secret (repeatable; value resolved at wake time)", (value, previous) => [...previous, value], [])
|
|
383
385
|
.action(async (url, opts) => {
|
|
384
386
|
try {
|
|
385
387
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
@@ -393,7 +395,8 @@ export function registerAgentKitCommand(agent) {
|
|
|
393
395
|
}
|
|
394
396
|
if (!raw)
|
|
395
397
|
throw new Error("Provide the URL as an argument or with --stdin");
|
|
396
|
-
const
|
|
398
|
+
const headerSecrets = parseHeaderSecrets(opts.headerSecret);
|
|
399
|
+
const config = { kind: "url", url: validateWakeUrl(raw), retryMs: retryMsFrom(opts.retryMinutes), ...(Object.keys(headerSecrets).length ? { headerSecrets } : {}) };
|
|
397
400
|
writeWakeConfig(paths, config);
|
|
398
401
|
console.log(chalk.green(`Wake set: ${describeWake(config)}`));
|
|
399
402
|
console.log("Test it: hq agent kit wake test");
|
|
@@ -450,15 +453,14 @@ export function registerAgentKitCommand(agent) {
|
|
|
450
453
|
.description("Fire the wake once now with the current pending count")
|
|
451
454
|
.action(async () => {
|
|
452
455
|
try {
|
|
453
|
-
const
|
|
454
|
-
const creds =
|
|
455
|
-
ensureKitDirs(paths);
|
|
456
|
+
const runtime = buildKitRuntime("inbox");
|
|
457
|
+
const { paths, creds } = runtime;
|
|
456
458
|
const config = readWakeConfig(paths);
|
|
457
459
|
if (!config)
|
|
458
460
|
throw new Error("No wake configured. Run hq agent kit wake set-url --stdin (or set-command).");
|
|
459
461
|
const log = createKitLogger(paths, "wake", { echo: false });
|
|
460
462
|
const ids = pendingInbox(paths).map((e) => e.id);
|
|
461
|
-
const result = await fireWake(config, buildWakeNotice(creds.entityUid, "test", ids, new Date()), { paths, agentUid: creds.entityUid, log });
|
|
463
|
+
const result = await fireWake(config, buildWakeNotice(creds.entityUid, "test", ids, new Date()), { paths, agentUid: creds.entityUid, log, resolveHeaderSecrets: createWakeSecretResolver(creds.companySlug, runtime.getToken, creds.apiBaseUrl) });
|
|
462
464
|
console.log(result.ok ? chalk.green(result.detail) : chalk.red(result.detail));
|
|
463
465
|
if (!result.ok)
|
|
464
466
|
process.exit(1);
|
package/dist/commands/agents.js
CHANGED
|
@@ -131,12 +131,6 @@ export function agentCreatePlanLimitFromPayload(status, body) {
|
|
|
131
131
|
: {}),
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
-
function formatServerPrice(amountMinor, currency) {
|
|
135
|
-
return new Intl.NumberFormat("en-US", {
|
|
136
|
-
style: "currency",
|
|
137
|
-
currency,
|
|
138
|
-
}).format(amountMinor / 100);
|
|
139
|
-
}
|
|
140
134
|
async function offerAgentCheckout(url) {
|
|
141
135
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
142
136
|
try {
|
|
@@ -146,7 +140,7 @@ async function offerAgentCheckout(url) {
|
|
|
146
140
|
});
|
|
147
141
|
}
|
|
148
142
|
catch {
|
|
149
|
-
console.error("Could not open the
|
|
143
|
+
console.error("Could not open the billing page automatically. Use the URL above.");
|
|
150
144
|
return false;
|
|
151
145
|
}
|
|
152
146
|
finally {
|
|
@@ -156,7 +150,7 @@ async function offerAgentCheckout(url) {
|
|
|
156
150
|
/** Print a server-priced plan block without minting or rewriting its URL. */
|
|
157
151
|
export async function surfaceAgentCreatePlanLimit(planLimit, opts = {}) {
|
|
158
152
|
if (isUsageOnly(opts.pricingPlan)) {
|
|
159
|
-
// Usage pricing (US-012): no
|
|
153
|
+
// Usage pricing (US-012): no flat plan to buy. The company adds a
|
|
160
154
|
// card and each agent box bills by size from the usage card.
|
|
161
155
|
const card = opts.usageCard ?? BUNDLED_USAGE_CARD;
|
|
162
156
|
for (const line of usageOnlyCardRequiredLines("An agent box", card)) {
|
|
@@ -166,13 +160,13 @@ export async function surfaceAgentCreatePlanLimit(planLimit, opts = {}) {
|
|
|
166
160
|
queueCliLimitNoticeExposure("agents", "refusal", opts.companyUid);
|
|
167
161
|
return;
|
|
168
162
|
}
|
|
169
|
-
|
|
170
|
-
const reason =
|
|
163
|
+
// The server's price was the flat plan's, which is closed: name no plan or price.
|
|
164
|
+
const reason = "Agents are not included on this company's current billing.";
|
|
171
165
|
const argv = opts.json ? [...process.argv, "--json"] : process.argv;
|
|
172
166
|
const interactive = canOfferTeamUpgrade(argv);
|
|
173
167
|
if (!planLimit.checkoutUrl) {
|
|
174
168
|
console.error(reason);
|
|
175
|
-
console.error("Ask a company owner to
|
|
169
|
+
console.error("Ask a company owner to check the company's Billing page before creating an agent.");
|
|
176
170
|
queueCliLimitNoticeExposure("agents", "refusal", opts.companyUid);
|
|
177
171
|
return;
|
|
178
172
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* `hq billing sponsor` — the individual-pull half of plan sponsorship, from the
|
|
3
3
|
* terminal ("ask a company to sponsor me", hq-pro #3609).
|
|
4
4
|
*
|
|
5
|
-
* A person who is over a personal limit has exactly three ways out: pay
|
|
6
|
-
* themselves, reduce what they store, or ask a company to
|
|
7
|
-
*
|
|
5
|
+
* A person who is over a personal limit has exactly three ways out: pay for
|
|
6
|
+
* the personal plan themselves, reduce what they store, or ask a company to
|
|
7
|
+
* cover it. This command is the third one, plus the admin side that answers it.
|
|
8
8
|
*
|
|
9
9
|
* hq billing sponsor request <company> — ask that company to cover my plan
|
|
10
10
|
* hq billing sponsor list — the queue (admins), or my own
|
|
@@ -36,7 +36,7 @@ export declare const PLAN_RESOURCE: "plan";
|
|
|
36
36
|
/** What a person is told when hq-pro's plan-sponsorship switch is off. */
|
|
37
37
|
export declare const FEATURE_OFF_MESSAGE = "Asking a company to sponsor your plan isn't available yet.";
|
|
38
38
|
/** What v1 sponsorship covers, stated wherever a person confirms or reads it. */
|
|
39
|
-
export declare const COVERAGE_SENTENCE = "Your company pays your
|
|
39
|
+
export declare const COVERAGE_SENTENCE = "Your company pays your personal plan. Storage above your plan's allowance is still yours.";
|
|
40
40
|
/** What a person is told while an approval waits on the sponsor's card. */
|
|
41
41
|
export declare const HELD_FOR_CARD_SENTENCE = "Your company said yes and is adding a card. Your limits still apply until then.";
|
|
42
42
|
/** One request row as hq-pro persists and returns it. */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* `hq billing sponsor` — the individual-pull half of plan sponsorship, from the
|
|
3
3
|
* terminal ("ask a company to sponsor me", hq-pro #3609).
|
|
4
4
|
*
|
|
5
|
-
* A person who is over a personal limit has exactly three ways out: pay
|
|
6
|
-
* themselves, reduce what they store, or ask a company to
|
|
7
|
-
*
|
|
5
|
+
* A person who is over a personal limit has exactly three ways out: pay for
|
|
6
|
+
* the personal plan themselves, reduce what they store, or ask a company to
|
|
7
|
+
* cover it. This command is the third one, plus the admin side that answers it.
|
|
8
8
|
*
|
|
9
9
|
* hq billing sponsor request <company> — ask that company to cover my plan
|
|
10
10
|
* hq billing sponsor list — the queue (admins), or my own
|
|
@@ -41,7 +41,7 @@ export const PLAN_RESOURCE = "plan";
|
|
|
41
41
|
/** What a person is told when hq-pro's plan-sponsorship switch is off. */
|
|
42
42
|
export const FEATURE_OFF_MESSAGE = "Asking a company to sponsor your plan isn't available yet.";
|
|
43
43
|
/** What v1 sponsorship covers, stated wherever a person confirms or reads it. */
|
|
44
|
-
export const COVERAGE_SENTENCE = "Your company pays your
|
|
44
|
+
export const COVERAGE_SENTENCE = "Your company pays your personal plan. Storage above your plan's allowance is still yours.";
|
|
45
45
|
/** What a person is told while an approval waits on the sponsor's card. */
|
|
46
46
|
export const HELD_FOR_CARD_SENTENCE = "Your company said yes and is adding a card. Your limits still apply until then.";
|
|
47
47
|
/**
|
|
@@ -85,7 +85,7 @@ export function messageForError(status, body) {
|
|
|
85
85
|
if (status === 401)
|
|
86
86
|
return "Your sign-in has expired. Run `hq login` and try again.";
|
|
87
87
|
if (status === 403 && code === "SPONSOR_PLAN_REQUIRED") {
|
|
88
|
-
return "That company
|
|
88
|
+
return "That company can't cover a personal plan on its current billing. Ask an owner of that company to check its Billing page.";
|
|
89
89
|
}
|
|
90
90
|
if (status === 403) {
|
|
91
91
|
return "You're not allowed to do that. Only an owner or admin of the company can answer a sponsorship request.";
|
|
@@ -209,7 +209,7 @@ export function registerBillingSponsorCommands(billing) {
|
|
|
209
209
|
process.stdout.write(JSON.stringify(data.request, null, 2) + "\n");
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
|
-
console.log(`Asked ${company} to cover your
|
|
212
|
+
console.log(`Asked ${company} to cover your personal HQ plan.`);
|
|
213
213
|
console.log(COVERAGE_SENTENCE);
|
|
214
214
|
console.log(`An owner or admin there answers next. Check with \`hq billing sponsor list\`.`);
|
|
215
215
|
}
|
|
@@ -276,11 +276,11 @@ export function registerBillingSponsorCommands(billing) {
|
|
|
276
276
|
}
|
|
277
277
|
const where = named ?? "that company";
|
|
278
278
|
if (!state.active) {
|
|
279
|
-
console.log(`No company is covering your
|
|
279
|
+
console.log(`No company is covering your personal HQ plan right now.`);
|
|
280
280
|
console.log(`If you asked ${where}, they haven't answered yet — an owner or admin there answers next.`);
|
|
281
281
|
return;
|
|
282
282
|
}
|
|
283
|
-
console.log(`Your
|
|
283
|
+
console.log(`Your personal HQ plan: ${statusLabel(state.status ?? "active")}`);
|
|
284
284
|
if (state.requestId)
|
|
285
285
|
console.log(` ${chalk.bold(state.requestId)}`);
|
|
286
286
|
console.log(COVERAGE_SENTENCE);
|
|
@@ -328,7 +328,7 @@ export function registerBillingSponsorCommands(billing) {
|
|
|
328
328
|
}
|
|
329
329
|
console.log((await companyIsUsageOnly(result.companyUid))
|
|
330
330
|
? "Approved. Their personal plan is now on the company bill."
|
|
331
|
-
: "Approved. Their
|
|
331
|
+
: "Approved. Their personal plan is now on the company bill.");
|
|
332
332
|
}
|
|
333
333
|
catch (err) {
|
|
334
334
|
fail(err);
|
|
@@ -370,7 +370,7 @@ export function registerBillingSponsorCommands(billing) {
|
|
|
370
370
|
}
|
|
371
371
|
console.log((await companyIsUsageOnly(result.companyUid))
|
|
372
372
|
? "Ended. Their personal plan goes back to them from the next bill."
|
|
373
|
-
: "Ended. Their
|
|
373
|
+
: "Ended. Their personal plan goes back to them from the next bill.");
|
|
374
374
|
}
|
|
375
375
|
catch (err) {
|
|
376
376
|
fail(err);
|
|
@@ -396,7 +396,7 @@ export function registerBillingSponsorCommands(billing) {
|
|
|
396
396
|
console.log(usageOnlySponsorLines()[2]);
|
|
397
397
|
return;
|
|
398
398
|
}
|
|
399
|
-
console.log("Withdrawn. You can pay
|
|
399
|
+
console.log("Withdrawn. You can pay for your plan yourself, reduce what you store, or ask another company.");
|
|
400
400
|
}
|
|
401
401
|
catch (err) {
|
|
402
402
|
fail(err);
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Tenant boundary: the company comes from the caller's own active memberships
|
|
14
14
|
* (`/membership/me`); a slug outside them is refused before any read. The
|
|
15
|
-
*
|
|
15
|
+
* company statement is owner-gated on the server as well. With no cloud
|
|
16
16
|
* company (or `--personal`) it reads the caller's personal scope.
|
|
17
17
|
*
|
|
18
|
-
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts)
|
|
19
|
-
* the
|
|
18
|
+
* Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts)
|
|
19
|
+
* from the usage statement, `GET /v1/billing/usage` (hq-pro US-006). Only a
|
|
20
|
+
* server without that route (404) falls back to the usage-limits readings.
|
|
20
21
|
*
|
|
21
22
|
* Only an account on usage pricing gets costs. The plan comes from the
|
|
22
|
-
* server
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* server says nothing about get readings only, with no rate, cost or
|
|
23
|
+
* server's shared plan status (`billingState`, hq-pro #5088) on the billing
|
|
24
|
+
* summary, with the legacy `pricingPlan` / `usageOnly` fields for a server
|
|
25
|
+
* that does not send it. An account on an earlier flat plan and an account
|
|
26
|
+
* the server says nothing about get readings only, with no rate, cost or
|
|
27
27
|
* projected bill.
|
|
28
28
|
*/
|
|
29
29
|
import type { Command } from "commander";
|
|
@@ -34,8 +34,19 @@ export interface MembershipRef {
|
|
|
34
34
|
slug: string;
|
|
35
35
|
role?: string;
|
|
36
36
|
}
|
|
37
|
+
/** One `GET /v1/billing/usage` answer: the HTTP status and the decoded body. */
|
|
38
|
+
export interface UsageStatementResponse {
|
|
39
|
+
status: number;
|
|
40
|
+
body: unknown | null;
|
|
41
|
+
}
|
|
37
42
|
export interface BillingUsageDeps {
|
|
38
43
|
listCompanies(): Promise<MembershipRef[]>;
|
|
44
|
+
/**
|
|
45
|
+
* `GET /v1/billing/usage` for the scope (US-015). Absent, or a 404 from a
|
|
46
|
+
* server without the route, falls back to {@link fetchUsageLimits}.
|
|
47
|
+
*/
|
|
48
|
+
fetchUsageStatement?: (scope: UsageScope) => Promise<UsageStatementResponse>;
|
|
49
|
+
/** Legacy readings for a server without the statement route. */
|
|
39
50
|
fetchUsageLimits: ReadMonthUsageDeps["fetchUsageLimits"];
|
|
40
51
|
readStorageSplit?: (company: MembershipRef, opts: {
|
|
41
52
|
fresh: boolean;
|
|
@@ -77,6 +88,9 @@ export declare function pricingPlanFromSummary(body: unknown): PricingPlan | nul
|
|
|
77
88
|
* only when usage-limits does not say.
|
|
78
89
|
*/
|
|
79
90
|
export declare function resolveOnUsagePricing(limitsPlan: PricingPlan | null, summaryPlan: PricingPlan | null): boolean | null;
|
|
91
|
+
/** Thrown when the company statement is refused to a caller who is not its owner. */
|
|
92
|
+
export declare class UsageStatementForbiddenError extends Error {
|
|
93
|
+
}
|
|
80
94
|
export declare function runBillingUsage(opts: BillingUsageOptions, deps: BillingUsageDeps): Promise<BillingUsageReport>;
|
|
81
95
|
/**
|
|
82
96
|
* `hq billing usage --card`: the card from GET /v1/pricing, or the bundled
|