@indigoai-us/hq-cli 5.248.0 → 5.249.0
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 +7 -0
- package/dist/commands/integrations-connect.d.ts +2 -14
- package/dist/commands/integrations-connect.js +3 -32
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +13 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +38 -0
- package/dist/lib/billing/plan-lock.d.ts +1 -0
- package/dist/lib/billing/plan-lock.js +20 -8
- package/dist/lib/plan-limit-nag.js +37 -4
- package/dist/lib/test-utils/isolated-flag-company-context.d.ts +3 -0
- package/dist/lib/test-utils/isolated-flag-company-context.js +18 -0
- package/dist/utils/console-origin.d.ts +9 -0
- package/dist/utils/console-origin.js +28 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.249.0] — 2026-09-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Plan-limit notices prefer the current usage-limits upgrade URL and can use
|
|
10
|
+
the company console billing page as a default-off hq-flags fallback.
|
|
11
|
+
|
|
5
12
|
## [5.248.0] — 2026-09-27
|
|
6
13
|
|
|
7
14
|
### Changed
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* "key" but is really OAuth-protected still connects on the first try.
|
|
18
18
|
*/
|
|
19
19
|
import { Command } from "commander";
|
|
20
|
+
import { consoleOrigin } from "../utils/console-origin.js";
|
|
21
|
+
export { consoleOrigin };
|
|
20
22
|
import type { AdminConnection } from "./integrations-core.js";
|
|
21
23
|
import { type InstallInput } from "./integrations-api.js";
|
|
22
24
|
interface ConnectOptions {
|
|
@@ -57,19 +59,6 @@ interface ConnectTarget {
|
|
|
57
59
|
/** Human label for prompts and success lines. */
|
|
58
60
|
label: string;
|
|
59
61
|
}
|
|
60
|
-
/**
|
|
61
|
-
* The console that fronts the control plane this CLI is pointed at.
|
|
62
|
-
*
|
|
63
|
-
* A handoff must land on the console for the SAME backend, because that
|
|
64
|
-
* console starts its own OAuth flow against its own backend. Sending a staging
|
|
65
|
-
* session to the production console would connect the app in the wrong place —
|
|
66
|
-
* so when the control plane is overridden and its console cannot be derived,
|
|
67
|
-
* return undefined and say so rather than guess a host.
|
|
68
|
-
*
|
|
69
|
-
* `HQ_CONSOLE_URL` is the explicit override; otherwise the console is the
|
|
70
|
-
* control-plane host with its `hqapi.` prefix removed.
|
|
71
|
-
*/
|
|
72
|
-
export declare function consoleOrigin(vaultApiUrl?: string): string | undefined;
|
|
73
62
|
/**
|
|
74
63
|
* The console's Integrations page for a company, the console root when the
|
|
75
64
|
* caller never named one (a single-membership session resolves the company
|
|
@@ -95,5 +84,4 @@ export declare function bringYourOwnClientHandoff(target: ConnectTarget, opts: C
|
|
|
95
84
|
export declare function googleClientTypeHint(clientId: string, authorizationUrl: string): string | null;
|
|
96
85
|
export declare function findAlreadyConnected(connections: readonly AdminConnection[], app: string): AdminConnection | undefined;
|
|
97
86
|
export declare function registerConnectCommands(integrations: Command): void;
|
|
98
|
-
export {};
|
|
99
87
|
//# sourceMappingURL=integrations-connect.d.ts.map
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import chalk from "chalk";
|
|
20
20
|
import open from "open";
|
|
21
|
-
import {
|
|
21
|
+
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
22
|
+
import { consoleOrigin } from "../utils/console-origin.js";
|
|
23
|
+
export { consoleOrigin };
|
|
22
24
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
23
25
|
import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
|
|
24
26
|
import { IntegrationsCliError, bareProvider, connectionDomain, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, } from "./integrations-core.js";
|
|
@@ -536,37 +538,6 @@ async function connectViaOAuth(token, companyUid, target, opts) {
|
|
|
536
538
|
listener.close();
|
|
537
539
|
}
|
|
538
540
|
}
|
|
539
|
-
const PRODUCTION_CONSOLE_ORIGIN = "https://hq.computer";
|
|
540
|
-
const PRODUCTION_VAULT_API_HOST = "hqapi.hq.computer";
|
|
541
|
-
/**
|
|
542
|
-
* The console that fronts the control plane this CLI is pointed at.
|
|
543
|
-
*
|
|
544
|
-
* A handoff must land on the console for the SAME backend, because that
|
|
545
|
-
* console starts its own OAuth flow against its own backend. Sending a staging
|
|
546
|
-
* session to the production console would connect the app in the wrong place —
|
|
547
|
-
* so when the control plane is overridden and its console cannot be derived,
|
|
548
|
-
* return undefined and say so rather than guess a host.
|
|
549
|
-
*
|
|
550
|
-
* `HQ_CONSOLE_URL` is the explicit override; otherwise the console is the
|
|
551
|
-
* control-plane host with its `hqapi.` prefix removed.
|
|
552
|
-
*/
|
|
553
|
-
export function consoleOrigin(vaultApiUrl = DEFAULT_VAULT_API_URL) {
|
|
554
|
-
const explicit = process.env.HQ_CONSOLE_URL?.trim();
|
|
555
|
-
if (explicit)
|
|
556
|
-
return explicit.replace(/\/+$/, "");
|
|
557
|
-
let api;
|
|
558
|
-
try {
|
|
559
|
-
api = new URL(vaultApiUrl);
|
|
560
|
-
}
|
|
561
|
-
catch {
|
|
562
|
-
return undefined;
|
|
563
|
-
}
|
|
564
|
-
if (api.host === PRODUCTION_VAULT_API_HOST)
|
|
565
|
-
return PRODUCTION_CONSOLE_ORIGIN;
|
|
566
|
-
if (!api.hostname.startsWith("hqapi."))
|
|
567
|
-
return undefined;
|
|
568
|
-
return `${api.protocol}//${api.host.slice("hqapi.".length)}`;
|
|
569
|
-
}
|
|
570
541
|
/**
|
|
571
542
|
* The console's Integrations page for a company, the console root when the
|
|
572
543
|
* caller never named one (a single-membership session resolves the company
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const PLAN_LIMIT_UPGRADE_FALLBACK_FLAG = "cli.plan-limit-upgrade-fallback";
|
|
2
|
+
export declare const PLAN_LIMIT_UPGRADE_FALLBACK_DEFAULT = false;
|
|
3
|
+
export interface PlanLimitUpgradeUrlChoices {
|
|
4
|
+
topLevelUrl?: string | null;
|
|
5
|
+
nestedUrl?: string | null;
|
|
6
|
+
legacyUrl: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function isPlanLimitUpgradeFallbackEnabled(): boolean;
|
|
9
|
+
/** Resolve a server URL without changing legacy behavior while the flag is off. */
|
|
10
|
+
export declare function selectPlanLimitUpgradeUrl(companySlug: string | undefined, choices: PlanLimitUpgradeUrlChoices): string;
|
|
11
|
+
/** Keep a caller's previous URL until the hq-flags rollout is enabled. */
|
|
12
|
+
export declare function planLimitUpgradeFallbackUrl(companySlug: string | undefined, previousFallbackUrl: string): string;
|
|
13
|
+
//# sourceMappingURL=plan-limit-upgrade-url.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolveProcessFlagGate } from "../flag-registry.js";
|
|
2
|
+
import { DEFAULT_VAULT_API_URL } from "../../utils/cognito-session.js";
|
|
3
|
+
import { consoleOrigin } from "../../utils/console-origin.js";
|
|
4
|
+
export const PLAN_LIMIT_UPGRADE_FALLBACK_FLAG = "cli.plan-limit-upgrade-fallback";
|
|
5
|
+
export const PLAN_LIMIT_UPGRADE_FALLBACK_DEFAULT = false;
|
|
6
|
+
export function isPlanLimitUpgradeFallbackEnabled() {
|
|
7
|
+
return resolveProcessFlagGate(PLAN_LIMIT_UPGRADE_FALLBACK_FLAG, { fallback: PLAN_LIMIT_UPGRADE_FALLBACK_DEFAULT }, () => PLAN_LIMIT_UPGRADE_FALLBACK_DEFAULT);
|
|
8
|
+
}
|
|
9
|
+
function companyBillingUrl(companySlug) {
|
|
10
|
+
const controlPlaneUrl = process.env.HQ_VAULT_API_URL?.trim() || DEFAULT_VAULT_API_URL;
|
|
11
|
+
const origin = consoleOrigin(controlPlaneUrl);
|
|
12
|
+
if (!origin)
|
|
13
|
+
return null;
|
|
14
|
+
const slug = companySlug?.trim();
|
|
15
|
+
const route = slug
|
|
16
|
+
? `/companies/${encodeURIComponent(slug)}/billing?upgrade=1`
|
|
17
|
+
: "/billing";
|
|
18
|
+
return `${origin}${route}`;
|
|
19
|
+
}
|
|
20
|
+
/** Resolve a server URL without changing legacy behavior while the flag is off. */
|
|
21
|
+
export function selectPlanLimitUpgradeUrl(companySlug, choices) {
|
|
22
|
+
if (!isPlanLimitUpgradeFallbackEnabled())
|
|
23
|
+
return choices.legacyUrl;
|
|
24
|
+
const topLevelUrl = choices.topLevelUrl?.trim() || undefined;
|
|
25
|
+
const nestedUrl = choices.nestedUrl?.trim() || undefined;
|
|
26
|
+
const generatedUrl = companyBillingUrl(companySlug);
|
|
27
|
+
if (companySlug?.trim()) {
|
|
28
|
+
return generatedUrl ?? topLevelUrl ?? nestedUrl ?? choices.legacyUrl;
|
|
29
|
+
}
|
|
30
|
+
return topLevelUrl ?? nestedUrl ?? generatedUrl ?? choices.legacyUrl;
|
|
31
|
+
}
|
|
32
|
+
/** Keep a caller's previous URL until the hq-flags rollout is enabled. */
|
|
33
|
+
export function planLimitUpgradeFallbackUrl(companySlug, previousFallbackUrl) {
|
|
34
|
+
return selectPlanLimitUpgradeUrl(companySlug, {
|
|
35
|
+
legacyUrl: previousFallbackUrl,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=plan-limit-upgrade-url.js.map
|
|
@@ -85,6 +85,7 @@ export declare function parseFreeMonth(value: unknown): PlanLimitFreeMonth | und
|
|
|
85
85
|
*/
|
|
86
86
|
export declare function selectCompanyMembership(body: unknown, companyRef: string): {
|
|
87
87
|
companyUid?: string;
|
|
88
|
+
companySlug?: string;
|
|
88
89
|
} | null;
|
|
89
90
|
/** Parse the resolved plan id out of a usage-limits body. Absent → null. */
|
|
90
91
|
export declare function selectPlan(body: unknown): "free" | "paid" | "enterprise" | null;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import chalk from "chalk";
|
|
24
24
|
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
25
|
+
import { selectPlanLimitUpgradeUrl } from "./plan-limit-upgrade-url.js";
|
|
25
26
|
/**
|
|
26
27
|
* Dimensions the bind notice names with a remedy. Owner decision 7 (2026-09-17)
|
|
27
28
|
* fixes the nagging set at these four: `deployments` and `storageBytes` are
|
|
@@ -133,6 +134,7 @@ export function selectCompanyMembership(body, companyRef) {
|
|
|
133
134
|
continue;
|
|
134
135
|
return {
|
|
135
136
|
companyUid: typeof row.companyUid === "string" ? row.companyUid : undefined,
|
|
137
|
+
companySlug: typeof row.companySlug === "string" ? row.companySlug : undefined,
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
140
|
return null;
|
|
@@ -157,12 +159,20 @@ export function selectMemberUsage(body) {
|
|
|
157
159
|
: STARTER_MEMBER_TARGET;
|
|
158
160
|
return { used: users.used, limit };
|
|
159
161
|
}
|
|
160
|
-
function selectUpgradeUrl(body) {
|
|
162
|
+
function selectUpgradeUrl(body, companySlug) {
|
|
161
163
|
const rec = asRecord(body);
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
const nested = asRecord(rec?.planLimits);
|
|
165
|
+
const topLevelUrl = typeof rec?.upgradeUrl === "string" && rec.upgradeUrl.trim().length > 0
|
|
166
|
+
? rec.upgradeUrl.trim()
|
|
167
|
+
: undefined;
|
|
168
|
+
const nestedUrl = typeof nested?.upgradeUrl === "string" && nested.upgradeUrl.trim().length > 0
|
|
169
|
+
? nested.upgradeUrl.trim()
|
|
170
|
+
: undefined;
|
|
171
|
+
return selectPlanLimitUpgradeUrl(companySlug, {
|
|
172
|
+
topLevelUrl,
|
|
173
|
+
nestedUrl,
|
|
174
|
+
legacyUrl: topLevelUrl ?? DEFAULT_UPGRADE_URL,
|
|
175
|
+
});
|
|
166
176
|
}
|
|
167
177
|
/**
|
|
168
178
|
* Read over-limit dimensions from a usage-limits body. `planLimits` nested
|
|
@@ -195,7 +205,7 @@ export function selectUsages(body) {
|
|
|
195
205
|
}
|
|
196
206
|
return out;
|
|
197
207
|
}
|
|
198
|
-
function statusFromUsage(companySlug, companyUid, body) {
|
|
208
|
+
function statusFromUsage(companySlug, companyUid, body, upgradeCompanySlug) {
|
|
199
209
|
const usages = selectUsages(body);
|
|
200
210
|
const reasons = selectOverReasons(body);
|
|
201
211
|
const rec = asRecord(body);
|
|
@@ -207,7 +217,7 @@ function statusFromUsage(companySlug, companyUid, body) {
|
|
|
207
217
|
reasons,
|
|
208
218
|
members: selectMemberUsage(body) ?? usages.users,
|
|
209
219
|
usages,
|
|
210
|
-
upgradeUrl: selectUpgradeUrl(body),
|
|
220
|
+
upgradeUrl: selectUpgradeUrl(body, upgradeCompanySlug),
|
|
211
221
|
freeMonth: parseFreeMonth(rec?.freeMonth),
|
|
212
222
|
plan: selectPlan(body) ?? undefined,
|
|
213
223
|
checkedAt: new Date().toISOString(),
|
|
@@ -242,7 +252,9 @@ export async function fetchPlanLimitStatus(token, companyRef, opts = {}) {
|
|
|
242
252
|
const body = await usage.json().catch(() => null);
|
|
243
253
|
if (!body)
|
|
244
254
|
return null;
|
|
245
|
-
|
|
255
|
+
const selectedSlug = selected.companySlug ??
|
|
256
|
+
(!companyRef.startsWith("cmp_") ? companyRef : undefined);
|
|
257
|
+
return statusFromUsage(companyRef, selected.companyUid, body, selectedSlug);
|
|
246
258
|
}
|
|
247
259
|
catch {
|
|
248
260
|
return null;
|
|
@@ -36,6 +36,8 @@ import chalk from "chalk";
|
|
|
36
36
|
import * as fs from "node:fs";
|
|
37
37
|
import * as os from "node:os";
|
|
38
38
|
import * as path from "node:path";
|
|
39
|
+
import { resolveProcessFlagCompanyContext } from "./flag-registry.js";
|
|
40
|
+
import { isPlanLimitUpgradeFallbackEnabled, planLimitUpgradeFallbackUrl, selectPlanLimitUpgradeUrl, } from "./billing/plan-limit-upgrade-url.js";
|
|
39
41
|
export const PLAN_LIMIT_UPGRADE_URL = "https://hq.computer/billing/upgrade";
|
|
40
42
|
/**
|
|
41
43
|
* Cadence of the over-limit per-turn line (US-036). Was once a day; the nag
|
|
@@ -155,6 +157,18 @@ function parsePlanLimits(body) {
|
|
|
155
157
|
}
|
|
156
158
|
: null;
|
|
157
159
|
}
|
|
160
|
+
function parseNestedUpgradeUrl(body) {
|
|
161
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const planLimits = body.planLimits;
|
|
165
|
+
if (planLimits === null ||
|
|
166
|
+
typeof planLimits !== "object" ||
|
|
167
|
+
Array.isArray(planLimits)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return parseUpgradeUrl(planLimits.upgradeUrl);
|
|
171
|
+
}
|
|
158
172
|
/**
|
|
159
173
|
* Defensively parse the top-level `thresholdCrossings` array.
|
|
160
174
|
*
|
|
@@ -208,14 +222,31 @@ function parseThresholdCrossings(body) {
|
|
|
208
222
|
export function recordPlanLimitStatus(body, opts = {}) {
|
|
209
223
|
try {
|
|
210
224
|
const status = parsePlanLimits(body);
|
|
225
|
+
const crossings = parseThresholdCrossings(body);
|
|
226
|
+
let resolvedUpgradeUrl = null;
|
|
227
|
+
if (status !== null || crossings.length > 0) {
|
|
228
|
+
const bodyRecord = body !== null && typeof body === "object" && !Array.isArray(body)
|
|
229
|
+
? body
|
|
230
|
+
: null;
|
|
231
|
+
const topLevelUpgradeUrl = parseUpgradeUrl(bodyRecord?.upgradeUrl);
|
|
232
|
+
const nestedUpgradeUrl = parseNestedUpgradeUrl(body);
|
|
233
|
+
const companySlug = isPlanLimitUpgradeFallbackEnabled()
|
|
234
|
+
? resolveProcessFlagCompanyContext().companySlug
|
|
235
|
+
: undefined;
|
|
236
|
+
resolvedUpgradeUrl = selectPlanLimitUpgradeUrl(companySlug, {
|
|
237
|
+
topLevelUrl: topLevelUpgradeUrl,
|
|
238
|
+
nestedUrl: nestedUpgradeUrl,
|
|
239
|
+
legacyUrl: status?.upgradeUrl ?? PLAN_LIMIT_UPGRADE_URL,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
211
242
|
if (status !== null) {
|
|
212
243
|
const anyOver = Object.values(status.limits).some((e) => e.over);
|
|
213
|
-
lastSeen = { ...status, anyOver };
|
|
244
|
+
lastSeen = { ...status, anyOver, upgradeUrl: resolvedUpgradeUrl };
|
|
214
245
|
}
|
|
215
246
|
// Independent of `planLimits`: the server can announce a crossing on a
|
|
216
247
|
// response whose nag map this CLI could not parse, and the notice is the
|
|
217
248
|
// one surface that must land inside a session that is already running.
|
|
218
|
-
emitPlanLimitThresholdNotice(
|
|
249
|
+
emitPlanLimitThresholdNotice(crossings, resolvedUpgradeUrl, opts);
|
|
219
250
|
}
|
|
220
251
|
catch {
|
|
221
252
|
// Never throw from record path.
|
|
@@ -402,7 +433,8 @@ export function emitPlanLimitThresholdNotice(crossings, upgradeUrl, opts = {}) {
|
|
|
402
433
|
const write = opts.write ?? ((s) => process.stderr.write(s));
|
|
403
434
|
const statePath = opts.statePath ?? defaultStatePath();
|
|
404
435
|
const stored = readNagState(statePath).notifiedThresholds ?? {};
|
|
405
|
-
const resolvedUpgradeUrl = upgradeUrl ??
|
|
436
|
+
const resolvedUpgradeUrl = upgradeUrl ??
|
|
437
|
+
planLimitUpgradeFallbackUrl(resolveProcessFlagCompanyContext().companySlug, PLAN_LIMIT_UPGRADE_URL);
|
|
406
438
|
const printed = {};
|
|
407
439
|
for (const crossing of crossings) {
|
|
408
440
|
const key = crossingKey(crossing);
|
|
@@ -440,7 +472,8 @@ export function emitPlanLimitNag(opts = {}) {
|
|
|
440
472
|
const now = opts.now ?? (() => new Date());
|
|
441
473
|
const statePath = opts.statePath ?? defaultStatePath();
|
|
442
474
|
const { limits, anyOver, upgradeUrl } = lastSeen;
|
|
443
|
-
const resolvedUpgradeUrl = upgradeUrl ??
|
|
475
|
+
const resolvedUpgradeUrl = upgradeUrl ??
|
|
476
|
+
planLimitUpgradeFallbackUrl(resolveProcessFlagCompanyContext().companySlug, PLAN_LIMIT_UPGRADE_URL);
|
|
444
477
|
const entries = Object.entries(limits);
|
|
445
478
|
if (entries.length === 0)
|
|
446
479
|
return;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { vi } from "vitest";
|
|
5
|
+
/** Keep process-wide flag tests independent of the developer machine binding. */
|
|
6
|
+
export function isolateFlagCompanyContext() {
|
|
7
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), "hq-flag-company-context-"));
|
|
8
|
+
vi.stubEnv("HOME", home);
|
|
9
|
+
vi.stubEnv("HQ_COMPANY_UID", undefined);
|
|
10
|
+
vi.stubEnv("HQ_COMPANY_SLUG", undefined);
|
|
11
|
+
vi.stubEnv("HQ_SPAWN_COMPANY", undefined);
|
|
12
|
+
vi.stubEnv("HQ_SESSION_ID", undefined);
|
|
13
|
+
vi.stubEnv("HQ_AGENT_IDENTITY_FILE", "");
|
|
14
|
+
vi.stubEnv("HQ_ROOT", undefined);
|
|
15
|
+
vi.stubEnv("HQ_WORK_CONTEXT_ROOT", undefined);
|
|
16
|
+
return () => fs.rmSync(home, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=isolated-flag-company-context.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The console that fronts the control plane this CLI is pointed at.
|
|
3
|
+
*
|
|
4
|
+
* A handoff must land on the console for the same backend. When an override is
|
|
5
|
+
* active, derive its console from the control-plane host instead of assuming
|
|
6
|
+
* production. `HQ_CONSOLE_URL` remains an explicit override.
|
|
7
|
+
*/
|
|
8
|
+
export declare function consoleOrigin(vaultApiUrl?: string): string | undefined;
|
|
9
|
+
//# sourceMappingURL=console-origin.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DEFAULT_VAULT_API_URL } from "./cognito-session.js";
|
|
2
|
+
const PRODUCTION_CONSOLE_ORIGIN = "https://hq.computer";
|
|
3
|
+
const PRODUCTION_VAULT_API_HOST = "hqapi.hq.computer";
|
|
4
|
+
/**
|
|
5
|
+
* The console that fronts the control plane this CLI is pointed at.
|
|
6
|
+
*
|
|
7
|
+
* A handoff must land on the console for the same backend. When an override is
|
|
8
|
+
* active, derive its console from the control-plane host instead of assuming
|
|
9
|
+
* production. `HQ_CONSOLE_URL` remains an explicit override.
|
|
10
|
+
*/
|
|
11
|
+
export function consoleOrigin(vaultApiUrl = DEFAULT_VAULT_API_URL) {
|
|
12
|
+
const explicit = process.env.HQ_CONSOLE_URL?.trim();
|
|
13
|
+
if (explicit)
|
|
14
|
+
return explicit.replace(/\/+$/, "");
|
|
15
|
+
let api;
|
|
16
|
+
try {
|
|
17
|
+
api = new URL(vaultApiUrl);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (api.host === PRODUCTION_VAULT_API_HOST)
|
|
23
|
+
return PRODUCTION_CONSOLE_ORIGIN;
|
|
24
|
+
if (!api.hostname.startsWith("hqapi."))
|
|
25
|
+
return undefined;
|
|
26
|
+
return `${api.protocol}//${api.host.slice("hqapi.".length)}`;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=console-origin.js.map
|