@indigoai-us/hq-cli 5.275.0 → 5.277.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 +112 -0
- package/README.md +12 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +4 -4
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-checkpoint.d.ts +4 -2
- package/dist/commands/core-checkpoint.js +34 -3
- package/dist/commands/core-command-catalog.d.ts +4 -2
- package/dist/commands/core-command-catalog.js +5 -2
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core-ontology.js +3 -3
- package/dist/commands/core.js +11 -1
- package/dist/commands/daemon.d.ts +4 -0
- package/dist/commands/daemon.js +175 -2
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/feedback.d.ts +6 -0
- package/dist/commands/feedback.js +86 -37
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +402 -49
- package/dist/commands/run-feature-telemetry.d.ts +3 -0
- package/dist/commands/run-feature-telemetry.js +6 -0
- package/dist/commands/run.js +3 -2
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +80 -45
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/commands/whoami.d.ts +4 -6
- package/dist/commands/whoami.js +39 -8
- package/dist/fast-core.d.ts +6 -7
- package/dist/fast-core.js +22 -8
- package/dist/fast-scaffold.d.ts +33 -7
- package/dist/fast-scaffold.js +104 -8
- package/dist/index.js +32 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/billing/plan-lock.d.ts +3 -1
- package/dist/lib/billing/plan-lock.js +7 -1
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +60 -3
- package/dist/lib/checkpoint-ontology/company.d.ts +8 -4
- package/dist/lib/checkpoint-ontology/company.js +15 -7
- package/dist/lib/checkpoint-ontology/plan.d.ts +36 -0
- package/dist/lib/checkpoint-ontology/plan.js +66 -4
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +32 -0
- package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
- package/dist/lib/core-utils/timeout-guard-native.js +40 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
- package/dist/lib/core-utils/timeout-guard.js +28 -13
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -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/flag-registry.d.ts +12 -7
- package/dist/lib/flag-registry.js +66 -75
- package/dist/lib/flag-snapshot-file.d.ts +20 -0
- package/dist/lib/flag-snapshot-file.js +91 -0
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.js +15 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/reconcile.js +20 -4
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -2
- package/dist/lib/process-flag-company-context.d.ts +8 -0
- package/dist/lib/process-flag-company-context.js +67 -0
- package/dist/main.js +3 -1
- package/dist/native-fast-route.d.ts +3 -0
- package/dist/native-fast-route.js +39 -0
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/dist/utils/cli-telemetry.d.ts +26 -2
- package/dist/utils/cli-telemetry.js +178 -3
- package/dist/utils/hq-root-resolver.d.ts +13 -0
- package/dist/utils/hq-root-resolver.js +103 -0
- package/dist/utils/scaffold-fast-import-guard.js +6 -1
- package/dist/utils/vault-api.d.ts +8 -0
- package/dist/utils/vault-api.js +13 -1
- package/package.json +2 -2
|
@@ -1,23 +1,117 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { CLI_VERSION } from "../cli-version.js";
|
|
5
|
-
import { isExpiring, isMachineIdentity, loadCachedTokens, } from "./cognito-session.js";
|
|
6
|
+
import { isExpiring, isMachineIdentity, loadCachedTokens, loadMachineCachedTokens, } from "./cognito-session.js";
|
|
7
|
+
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
6
8
|
import { vaultApiFetch } from "./vault-api.js";
|
|
7
9
|
import { firstSyncStepTelemetryProperties, isFirstSyncNextStepEnabled, isHumanInteractiveCliSession, } from "../lib/first-sync-next-step.js";
|
|
8
10
|
export { CLI_FIRST_SYNC_NEXT_STEP_FLAG, FIRST_SYNC_CONFIRMATION_COPY, FIRST_SYNC_NEXT_STEP_COPY, claimFirstSyncStepMarker, firstSyncStepMarkerPaths, firstSyncStepTelemetryProperties, isFirstSyncNextStepEnabled, isHumanInteractiveCliSession, isWorkBearingSyncResult, maybeShowFirstSyncNextStep, recordFirstWorkBearingSync, shouldConfirmFirstWorkBearingSync, shouldShowFirstSyncNextStep, takeFirstSyncStepOutcomes, } from "../lib/first-sync-next-step.js";
|
|
9
11
|
export const TELEMETRY_TIMEOUT_MS = 1_200;
|
|
10
12
|
/**
|
|
11
13
|
* Exact stderr line printed once on the first signed-in TTY run that is not
|
|
12
|
-
* `--json`. Keep this notice and its marker stable for
|
|
13
|
-
*
|
|
14
|
+
* `--json`. Keep this notice and its marker stable for session telemetry;
|
|
15
|
+
* first-sync and feature-used fields have separate opt-in disclosures below.
|
|
14
16
|
*/
|
|
15
17
|
export const TELEMETRY_DISCLOSURE_LINE = "HQ sends a usage event when you run a signed-in command (CLI version and a timestamp, never command names, file contents, or secrets) to improve the CLI. Set HQ_TELEMETRY=0 to turn this off.";
|
|
16
18
|
export const FIRST_SYNC_TELEMETRY_DISCLOSURE_LINE = "HQ sends a usage event when you run a signed-in command (CLI version and a timestamp; when enabled, first-sync guidance shown or followed and the flag key cli.first-sync-next-step; never command names, file contents, or secrets) to improve the CLI. Set HQ_TELEMETRY=0 to turn this off.";
|
|
19
|
+
export const CLI_FEATURE_USED_TELEMETRY_FLAG = "cli.feature-used-telemetry-v1";
|
|
20
|
+
export const CLI_FEATURE_USED_TELEMETRY_DISCLOSURE_LINE = "HQ sends a feature-use event when you run a supported CLI feature (feature kind and outcome, plus your company UID when available; never command names, file contents, or secrets). The hq-flags key cli.feature-used-telemetry-v1 controls these events. Set HQ_TELEMETRY=0 to turn this off.";
|
|
17
21
|
const TELEMETRY_DISABLED_VALUES = new Set(["0", "false", "off"]);
|
|
18
22
|
const DISCLOSURE_MARKER = "cli-telemetry-disclosed";
|
|
19
23
|
const FIRST_SYNC_DISCLOSURE_MARKER = "cli-telemetry-disclosed-first-sync-v1";
|
|
24
|
+
const FEATURE_USED_DISCLOSURE_MARKER = "cli-telemetry-disclosed-feature-used-v1";
|
|
20
25
|
let cliSessionStartedPromise;
|
|
26
|
+
const pendingPlanLimitInviteTeammateExposures = [];
|
|
27
|
+
/** Queue one exposure after the CLI has printed the invite suggestion. */
|
|
28
|
+
export function queuePlanLimitInviteTeammateExposure(companyUid) {
|
|
29
|
+
const normalizedCompanyUid = companyUid.trim();
|
|
30
|
+
if (normalizedCompanyUid.length === 0)
|
|
31
|
+
return;
|
|
32
|
+
pendingPlanLimitInviteTeammateExposures.push(normalizedCompanyUid);
|
|
33
|
+
}
|
|
34
|
+
/** Drain invite-suggestion exposures for the bounded command-finalization flush. */
|
|
35
|
+
export function takePlanLimitInviteTeammateExposures() {
|
|
36
|
+
return pendingPlanLimitInviteTeammateExposures.splice(0, pendingPlanLimitInviteTeammateExposures.length);
|
|
37
|
+
}
|
|
38
|
+
const COMPANY_UID_PATTERN = /^cmp_[A-Za-z0-9_-]{1,128}$/;
|
|
39
|
+
/**
|
|
40
|
+
* Emit one bounded, privacy-limited feature-use event with a cached identity.
|
|
41
|
+
* It never refreshes credentials or turns telemetry failure into command failure.
|
|
42
|
+
*/
|
|
43
|
+
export async function emitCliFeatureUsed(usage, timeoutMs = TELEMETRY_TIMEOUT_MS) {
|
|
44
|
+
try {
|
|
45
|
+
// Unit tests may run with a real cached user/agent token on the host. Never
|
|
46
|
+
// let their command fixtures write telemetry to a live account.
|
|
47
|
+
if (process.env.NODE_ENV === "test")
|
|
48
|
+
return;
|
|
49
|
+
if (isCliTelemetryDisabled() || !isCliFeatureUsedTelemetryEnabled())
|
|
50
|
+
return;
|
|
51
|
+
const machineIdentity = isMachineIdentity();
|
|
52
|
+
const cached = machineIdentity ? loadMachineCachedTokens() : loadCachedTokens();
|
|
53
|
+
if (!cached || isExpiring(cached, 120))
|
|
54
|
+
return;
|
|
55
|
+
const token = machineIdentity ? cached.idToken : cached.accessToken;
|
|
56
|
+
if (!token)
|
|
57
|
+
return;
|
|
58
|
+
const consentBasis = maybeDiscloseFeatureUsedTelemetry() ? "disclosed" : "no-consent";
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
61
|
+
const companyUid = usage.companyUid && COMPANY_UID_PATTERN.test(usage.companyUid)
|
|
62
|
+
? usage.companyUid
|
|
63
|
+
: undefined;
|
|
64
|
+
try {
|
|
65
|
+
await vaultApiFetch({
|
|
66
|
+
token,
|
|
67
|
+
path: "/v1/telemetry/events",
|
|
68
|
+
method: "POST",
|
|
69
|
+
body: {
|
|
70
|
+
events: [
|
|
71
|
+
{
|
|
72
|
+
eventName: "cli_feature_used",
|
|
73
|
+
app: "hq-cli",
|
|
74
|
+
source: "cli",
|
|
75
|
+
occurredAt: new Date().toISOString(),
|
|
76
|
+
consentBasis,
|
|
77
|
+
schemaVersion: 1,
|
|
78
|
+
...(companyUid ? { companyUid } : {}),
|
|
79
|
+
properties: { kind: usage.kind, outcome: usage.outcome },
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Feature telemetry is best effort and must never affect the CLI action.
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Cached-token reads are also best effort (for example, a malformed cache).
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Run one action and emit exactly one success/failure event without masking errors. */
|
|
98
|
+
export async function withCliFeatureUsed(kind, action, companyUid) {
|
|
99
|
+
let outcome = "success";
|
|
100
|
+
try {
|
|
101
|
+
return await action();
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
outcome = "failure";
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await emitCliFeatureUsed({
|
|
109
|
+
kind,
|
|
110
|
+
outcome,
|
|
111
|
+
...(companyUid ? { companyUid } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
21
115
|
/**
|
|
22
116
|
* `HQ_TELEMETRY=0` (also `false` and `off`, case-insensitive) disables every
|
|
23
117
|
* CLI POST to `/v1/telemetry/events`. `HQ_TELEMETRY_TIMEOUT_MS` is unchanged.
|
|
@@ -28,6 +122,10 @@ export function isCliTelemetryDisabled(env = process.env) {
|
|
|
28
122
|
return false;
|
|
29
123
|
return TELEMETRY_DISABLED_VALUES.has(raw);
|
|
30
124
|
}
|
|
125
|
+
/** Feature-use collection stays off until the HQ flag is explicitly enabled. */
|
|
126
|
+
export function isCliFeatureUsedTelemetryEnabled() {
|
|
127
|
+
return resolveProcessFlagGate(CLI_FEATURE_USED_TELEMETRY_FLAG, { fallback: false }, () => false);
|
|
128
|
+
}
|
|
31
129
|
function hqConfigDir() {
|
|
32
130
|
const home = process.env.HOME?.trim() || os.homedir();
|
|
33
131
|
return path.join(home, ".hq");
|
|
@@ -38,6 +136,9 @@ export function telemetryDisclosureMarkerPath() {
|
|
|
38
136
|
function firstSyncTelemetryDisclosureMarkerPath() {
|
|
39
137
|
return path.join(hqConfigDir(), FIRST_SYNC_DISCLOSURE_MARKER);
|
|
40
138
|
}
|
|
139
|
+
function featureUsedTelemetryDisclosureMarkerPath() {
|
|
140
|
+
return path.join(hqConfigDir(), FEATURE_USED_DISCLOSURE_MARKER);
|
|
141
|
+
}
|
|
41
142
|
function wantsJsonOutput(argv = process.argv) {
|
|
42
143
|
return argv.includes("--json");
|
|
43
144
|
}
|
|
@@ -75,6 +176,9 @@ export function maybeDiscloseCliTelemetry(stderr = process.stderr, stdout = proc
|
|
|
75
176
|
function maybeDiscloseFirstSyncTelemetry() {
|
|
76
177
|
return maybeDiscloseTelemetry(FIRST_SYNC_TELEMETRY_DISCLOSURE_LINE, firstSyncTelemetryDisclosureMarkerPath());
|
|
77
178
|
}
|
|
179
|
+
function maybeDiscloseFeatureUsedTelemetry() {
|
|
180
|
+
return maybeDiscloseTelemetry(CLI_FEATURE_USED_TELEMETRY_DISCLOSURE_LINE, featureUsedTelemetryDisclosureMarkerPath());
|
|
181
|
+
}
|
|
78
182
|
function maybeDiscloseTelemetry(line, markerPath, stderr = process.stderr, stdout = process.stdout, argv = process.argv) {
|
|
79
183
|
if (hasShownDisclosure(markerPath))
|
|
80
184
|
return true;
|
|
@@ -207,4 +311,75 @@ export async function emitCliFirstSyncNextStepOutcome(outcome, timeoutMs = TELEM
|
|
|
207
311
|
// Cached-token reads are also best effort (for example, a malformed cache).
|
|
208
312
|
}
|
|
209
313
|
}
|
|
314
|
+
/** Emit one company-scoped event for a printed invite-teammate suggestion. */
|
|
315
|
+
function logPlanLimitInviteTeammateExposureFailure(cause, status) {
|
|
316
|
+
const errorClass = cause instanceof Error ? cause.name : "unknown";
|
|
317
|
+
const result = status === undefined ? errorClass : `http_${status}`;
|
|
318
|
+
console.error(`hq: Could not send invite-teammate exposure telemetry (${result}).`);
|
|
319
|
+
}
|
|
320
|
+
export async function emitPlanLimitInviteTeammateExposure(companyUid, timeoutMs = TELEMETRY_TIMEOUT_MS) {
|
|
321
|
+
try {
|
|
322
|
+
if (isCliTelemetryDisabled())
|
|
323
|
+
return;
|
|
324
|
+
const cached = loadCachedTokens();
|
|
325
|
+
const machineIdentity = isMachineIdentity();
|
|
326
|
+
if (!cached ||
|
|
327
|
+
isExpiring(cached, 120) ||
|
|
328
|
+
!isHumanInteractiveCliSession({
|
|
329
|
+
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
330
|
+
argv: process.argv,
|
|
331
|
+
env: process.env,
|
|
332
|
+
machineIdentity,
|
|
333
|
+
})) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const token = cached.accessToken;
|
|
337
|
+
if (!token)
|
|
338
|
+
return;
|
|
339
|
+
const controller = new AbortController();
|
|
340
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
341
|
+
try {
|
|
342
|
+
const response = await vaultApiFetch({
|
|
343
|
+
token,
|
|
344
|
+
path: "/v1/telemetry/events",
|
|
345
|
+
method: "POST",
|
|
346
|
+
body: {
|
|
347
|
+
events: [
|
|
348
|
+
{
|
|
349
|
+
eventName: "plan_limit_prompt_exposed",
|
|
350
|
+
app: "hq-cli",
|
|
351
|
+
source: "cli",
|
|
352
|
+
occurredAt: new Date().toISOString(),
|
|
353
|
+
consentBasis: "no-consent",
|
|
354
|
+
schemaVersion: 2,
|
|
355
|
+
companyUid,
|
|
356
|
+
idempotencyKey: randomUUID(),
|
|
357
|
+
properties: {
|
|
358
|
+
surface: "cli_whoami_plan_limit",
|
|
359
|
+
resourceKind: "users",
|
|
360
|
+
presentationKind: "advisory",
|
|
361
|
+
exposureId: randomUUID(),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
signal: controller.signal,
|
|
367
|
+
});
|
|
368
|
+
if (!response.ok) {
|
|
369
|
+
logPlanLimitInviteTeammateExposureFailure(undefined, response.status);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
// This measurement is best-effort and must not affect the CLI command.
|
|
374
|
+
logPlanLimitInviteTeammateExposureFailure(error);
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
clearTimeout(timeout);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
// Cached-token reads and identity checks are best-effort too.
|
|
382
|
+
logPlanLimitInviteTeammateExposureFailure(error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
210
385
|
//# sourceMappingURL=cli-telemetry.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Lightweight HQ-root resolver shared by cloud commands and fast flag scope. */
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the HQ tree root using the same precedence as the Cognito helpers:
|
|
4
|
+
* explicit HQ_ROOT, nearest cwd root, installer records, known locations, then
|
|
5
|
+
* the historical ~/hq fallback (or an error when requested).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveDefaultHqRoot(opts?: {
|
|
8
|
+
onMissing?: "throw" | "fallback";
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
home?: string;
|
|
12
|
+
}): string;
|
|
13
|
+
//# sourceMappingURL=hq-root-resolver.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Lightweight HQ-root resolver shared by cloud commands and fast flag scope. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as yaml from "js-yaml";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the HQ tree root using the same precedence as the Cognito helpers:
|
|
8
|
+
* explicit HQ_ROOT, nearest cwd root, installer records, known locations, then
|
|
9
|
+
* the historical ~/hq fallback (or an error when requested).
|
|
10
|
+
*/
|
|
11
|
+
export function resolveDefaultHqRoot(opts = {}) {
|
|
12
|
+
const env = opts.env ?? process.env;
|
|
13
|
+
const home = opts.home ?? os.homedir();
|
|
14
|
+
if (env.HQ_ROOT)
|
|
15
|
+
return path.resolve(env.HQ_ROOT);
|
|
16
|
+
let cur = path.resolve(opts.cwd ?? process.cwd());
|
|
17
|
+
while (cur !== path.dirname(cur)) {
|
|
18
|
+
if (isHqRoot(cur))
|
|
19
|
+
return cur;
|
|
20
|
+
cur = path.dirname(cur);
|
|
21
|
+
}
|
|
22
|
+
const fromConfig = readHqRootFromHqConfig(path.join(home, ".hq"));
|
|
23
|
+
if (fromConfig)
|
|
24
|
+
return path.resolve(fromConfig);
|
|
25
|
+
const discovered = discoverHqRootViaCoreYaml(home);
|
|
26
|
+
if (discovered)
|
|
27
|
+
return discovered;
|
|
28
|
+
if (opts.onMissing === "throw") {
|
|
29
|
+
throw new Error("Could not find HQ root. Run this command from within your HQ directory " +
|
|
30
|
+
"(must contain core.yaml AND a companies/ subdirectory), set $HQ_ROOT, " +
|
|
31
|
+
"or install HQ via the installer/menubar so ~/.hq/menubar.json records " +
|
|
32
|
+
"its location.");
|
|
33
|
+
}
|
|
34
|
+
return path.join(home, "hq");
|
|
35
|
+
}
|
|
36
|
+
function readHqRootFromHqConfig(hqConfigDir) {
|
|
37
|
+
const fromMenubar = readJsonStringField(path.join(hqConfigDir, "menubar.json"), "hqPath");
|
|
38
|
+
if (fromMenubar)
|
|
39
|
+
return fromMenubar;
|
|
40
|
+
return readJsonStringField(path.join(hqConfigDir, "config.json"), "hqFolderPath");
|
|
41
|
+
}
|
|
42
|
+
function readJsonStringField(file, field) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
45
|
+
const value = parsed[field];
|
|
46
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function hqDiscoveryCandidates(home) {
|
|
53
|
+
return [
|
|
54
|
+
path.join(home, "HQ"),
|
|
55
|
+
path.join(home, "hq"),
|
|
56
|
+
path.join(home, "Documents", "HQ"),
|
|
57
|
+
path.join(home, "Documents", "hq"),
|
|
58
|
+
path.join(home, "Desktop", "HQ"),
|
|
59
|
+
path.join(home, "Desktop", "hq"),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
function discoverHqRootViaCoreYaml(home) {
|
|
63
|
+
return hqDiscoveryCandidates(home).find(isValidHqRootSignature);
|
|
64
|
+
}
|
|
65
|
+
function isValidHqRootSignature(dir) {
|
|
66
|
+
const canonical = path.join(dir, "core", "core.yaml");
|
|
67
|
+
const legacy = path.join(dir, "core.yaml");
|
|
68
|
+
let file;
|
|
69
|
+
if (fileExists(canonical))
|
|
70
|
+
file = canonical;
|
|
71
|
+
else if (fileExists(legacy))
|
|
72
|
+
file = legacy;
|
|
73
|
+
else
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = yaml.load(fs.readFileSync(file, "utf-8"));
|
|
77
|
+
return !!parsed && typeof parsed === "object" && "version" in parsed && "hqVersion" in parsed;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function fileExists(file) {
|
|
84
|
+
try {
|
|
85
|
+
return fs.statSync(file).isFile();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function isHqRoot(dir) {
|
|
92
|
+
const hasCoreYaml = fileExists(path.join(dir, "core", "core.yaml")) ||
|
|
93
|
+
fileExists(path.join(dir, "core.yaml"));
|
|
94
|
+
if (!hasCoreYaml)
|
|
95
|
+
return false;
|
|
96
|
+
try {
|
|
97
|
+
return fs.statSync(path.join(dir, "companies")).isDirectory();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=hq-root-resolver.js.map
|
|
@@ -4,6 +4,8 @@ export function forbiddenFastPathModules(loadedModules, distDirectory) {
|
|
|
4
4
|
const dist = path.resolve(distDirectory);
|
|
5
5
|
const main = path.join(dist, "main.js");
|
|
6
6
|
const commands = `${path.join(dist, "commands")}${path.sep}`;
|
|
7
|
+
const flagRegistry = path.join(dist, "lib", "flag-registry.js");
|
|
8
|
+
const coreCommand = path.join(dist, "commands", "core.js");
|
|
7
9
|
return [...new Set(loadedModules.map((loaded) => {
|
|
8
10
|
if (loaded.startsWith("file:")) {
|
|
9
11
|
try {
|
|
@@ -16,7 +18,10 @@ export function forbiddenFastPathModules(loadedModules, distDirectory) {
|
|
|
16
18
|
return path.resolve(loaded);
|
|
17
19
|
}))]
|
|
18
20
|
.filter((loaded) => path.resolve(loaded) === main ||
|
|
19
|
-
path.resolve(loaded).startsWith(commands)
|
|
21
|
+
path.resolve(loaded).startsWith(commands) ||
|
|
22
|
+
path.resolve(loaded) === flagRegistry ||
|
|
23
|
+
path.resolve(loaded) === coreCommand ||
|
|
24
|
+
path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}node${path.sep}`))
|
|
20
25
|
.sort();
|
|
21
26
|
}
|
|
22
27
|
export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory) {
|
|
@@ -47,6 +47,13 @@ export declare function rewritePathForApiKey(path: string): string | null;
|
|
|
47
47
|
* authorization or request signing depends on it.
|
|
48
48
|
*/
|
|
49
49
|
export declare const HQ_CLIENT_NAME = "@indigoai-us/hq-cli";
|
|
50
|
+
type VaultApiRequestTargetOptions = Pick<VaultApiOptions, "baseUrl" | "token" | "path" | "query">;
|
|
51
|
+
/**
|
|
52
|
+
* Run vaultApiFetch's synchronous, client-side request checks without sending
|
|
53
|
+
* the request. Commands with prior uploads use this to fail before creating
|
|
54
|
+
* objects that a later local refusal would leave orphaned.
|
|
55
|
+
*/
|
|
56
|
+
export declare function assertVaultApiRequestSupported(opts: VaultApiRequestTargetOptions): void;
|
|
50
57
|
export declare function vaultApiFetch(opts: VaultApiOptions): Promise<Response>;
|
|
51
58
|
/**
|
|
52
59
|
* Public (NONE-auth) GET against the vault API — no bearer token. The
|
|
@@ -77,4 +84,5 @@ export declare function getEntityUid(token: string, opts: {
|
|
|
77
84
|
}, options?: {
|
|
78
85
|
allowOfflineScopeFallback?: boolean;
|
|
79
86
|
}): Promise<string>;
|
|
87
|
+
export {};
|
|
80
88
|
//# sourceMappingURL=vault-api.d.ts.map
|
package/dist/utils/vault-api.js
CHANGED
|
@@ -155,7 +155,7 @@ export function rewritePathForApiKey(path) {
|
|
|
155
155
|
* authorization or request signing depends on it.
|
|
156
156
|
*/
|
|
157
157
|
export const HQ_CLIENT_NAME = "@indigoai-us/hq-cli";
|
|
158
|
-
|
|
158
|
+
function resolveVaultApiRequestTarget(opts) {
|
|
159
159
|
let path = opts.path;
|
|
160
160
|
if (opts.token.startsWith("hqk_")) {
|
|
161
161
|
const rewritten = rewritePathForApiKey(path);
|
|
@@ -172,6 +172,18 @@ export async function vaultApiFetch(opts) {
|
|
|
172
172
|
url.searchParams.set(k, v);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
return { path, url };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Run vaultApiFetch's synchronous, client-side request checks without sending
|
|
179
|
+
* the request. Commands with prior uploads use this to fail before creating
|
|
180
|
+
* objects that a later local refusal would leave orphaned.
|
|
181
|
+
*/
|
|
182
|
+
export function assertVaultApiRequestSupported(opts) {
|
|
183
|
+
resolveVaultApiRequestTarget(opts);
|
|
184
|
+
}
|
|
185
|
+
export async function vaultApiFetch(opts) {
|
|
186
|
+
const { path, url } = resolveVaultApiRequestTarget(opts);
|
|
175
187
|
const method = opts.method ?? 'GET';
|
|
176
188
|
const safeUrl = url.search ? `${url.origin}${url.pathname}?<redacted>` : `${url.origin}${url.pathname}`;
|
|
177
189
|
Sentry.addBreadcrumb({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.277.0",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
46
46
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
47
|
-
"@indigoai-us/hq-cloud": "~6.18.
|
|
47
|
+
"@indigoai-us/hq-cloud": "~6.18.13",
|
|
48
48
|
"@indigoai-us/hq-flags-client": "^0.1.2",
|
|
49
49
|
"@sentry/node": "^10.49.0",
|
|
50
50
|
"@tobilu/qmd": "2.5.3",
|