@indigoai-us/hq-cli 5.345.79 → 5.345.81
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 +26 -0
- package/dist/command-catalog.generated.d.ts +142 -0
- package/dist/command-catalog.generated.js +173 -0
- package/dist/command-help.js +2 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/storage-core.d.ts +98 -0
- package/dist/commands/storage-core.js +223 -0
- package/dist/commands/storage-gate.d.ts +35 -0
- package/dist/commands/storage-gate.js +85 -0
- package/dist/commands/storage-offload.d.ts +342 -0
- package/dist/commands/storage-offload.js +943 -0
- package/dist/commands/storage.d.ts +199 -0
- package/dist/commands/storage.js +931 -0
- package/dist/fast-scaffold.js +5 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +5140 -3412
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/cli-flag-keys.d.ts +6 -0
- package/dist/lib/cli-flag-keys.js +6 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +2 -0
- package/dist/lib/doctor/checks/sync-health.js +19 -0
- package/dist/lib/doctor/fix/apply.d.ts +14 -10
- package/dist/lib/doctor/fix/apply.js +30 -16
- package/dist/lib/doctor/fix/remediation.d.ts +5 -3
- package/dist/lib/doctor/fix/remediation.js +21 -7
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -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/storage-feature-flag.d.ts +56 -0
- package/dist/lib/storage-feature-flag.js +104 -0
- package/dist/main.js +12 -1
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/resident-package-preload.js +3 -1
- package/dist/utils/self-update.js +2 -2
- package/dist/utils/version-gate.d.ts +15 -0
- package/dist/utils/version-gate.js +80 -10
- package/package.json +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HQ Storage allowlist gate (hq-flags key `hq-storage`).
|
|
3
|
+
*
|
|
4
|
+
* Owner decision (Corey, 2026-10-11): "let's fully ship the storage feature
|
|
5
|
+
* but all behind a feature flag for indigo users only." That is an explicit
|
|
6
|
+
* hold on the default-on kill-switch rule
|
|
7
|
+
* (`indigo-feature-flags-default-on-as-fail-safes`): the registry row is
|
|
8
|
+
* `defaultValue: false` with a company override for Indigo, so this gate is
|
|
9
|
+
* an ALLOWLIST, not a kill switch. It therefore FAILS CLOSED: a missing,
|
|
10
|
+
* stale, discovered, archived or unreadable value means Storage is off.
|
|
11
|
+
*
|
|
12
|
+
* Who it is on for is decided by hq-pro's shared evaluator (person override,
|
|
13
|
+
* then company override by uid, then slug, then default), resolved
|
|
14
|
+
* server-side for the caller. A company override only fires when the read
|
|
15
|
+
* names that company, so the caller is "on" when the flag resolves true for
|
|
16
|
+
* any company they are an active member of. hq-pro refuses a resolve for a
|
|
17
|
+
* company the caller is not a member of, so the tenant boundary is the
|
|
18
|
+
* membership check on the server, never a local list.
|
|
19
|
+
*
|
|
20
|
+
* This module is the synchronous, cache-only half used to hide the command
|
|
21
|
+
* from root help. It reads only fresh persisted snapshots and never touches
|
|
22
|
+
* the network. The authoritative, live check that runs before any storage
|
|
23
|
+
* subcommand lives in `commands/storage-gate.ts`.
|
|
24
|
+
*/
|
|
25
|
+
import type { Command } from "commander";
|
|
26
|
+
import { HQ_STORAGE_FLAG } from "./cli-flag-keys.js";
|
|
27
|
+
export { HQ_STORAGE_FLAG };
|
|
28
|
+
/** Machine-readable refusal code the desktop app keys on. */
|
|
29
|
+
export declare const STORAGE_FEATURE_DISABLED_CODE = "feature_disabled";
|
|
30
|
+
export declare const STORAGE_FEATURE_DISABLED_MESSAGE = "Storage tools aren't available on your account yet.";
|
|
31
|
+
export interface StorageFlagCacheDeps {
|
|
32
|
+
env?: NodeJS.ProcessEnv;
|
|
33
|
+
/** Company uids whose persisted snapshots may be consulted. */
|
|
34
|
+
companyUids?: () => string[];
|
|
35
|
+
readCompanySnapshot?: (companyUid: string) => {
|
|
36
|
+
flags: Record<string, boolean>;
|
|
37
|
+
} | null;
|
|
38
|
+
readPersonSnapshot?: () => {
|
|
39
|
+
flags: Record<string, boolean>;
|
|
40
|
+
} | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* True only when a fresh persisted snapshot already says `hq-storage` is on
|
|
44
|
+
* for this caller (person scope or one of their companies). Everything else,
|
|
45
|
+
* including any read error, is false.
|
|
46
|
+
*/
|
|
47
|
+
export declare function storageFeatureEnabledFromCache(deps?: StorageFlagCacheDeps): boolean;
|
|
48
|
+
/** The JSON refusal printed on stdout for `--json` callers such as the desktop app. */
|
|
49
|
+
export declare function storageFeatureDisabledJson(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Hide the catalog's `storage` root from root help unless the cached gate is
|
|
52
|
+
* on. Lives here, not in commands/storage-gate.ts, so the root-help path only
|
|
53
|
+
* loads this cache-only module.
|
|
54
|
+
*/
|
|
55
|
+
export declare function hideStorageInCatalog(program: Command, enabled?: () => boolean): void;
|
|
56
|
+
//# sourceMappingURL=storage-feature-flag.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readLocalOverride } from "@indigoai-us/hq-flags-client";
|
|
2
|
+
import { HQ_STORAGE_FLAG } from "./cli-flag-keys.js";
|
|
3
|
+
import { readFlagSnapshotFile, readPersonFlagSnapshotFile } from "./flag-snapshot-file.js";
|
|
4
|
+
import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
5
|
+
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
6
|
+
import { resolveDefaultHqRoot } from "../utils/hq-root-resolver.js";
|
|
7
|
+
export { HQ_STORAGE_FLAG };
|
|
8
|
+
/** Machine-readable refusal code the desktop app keys on. */
|
|
9
|
+
export const STORAGE_FEATURE_DISABLED_CODE = "feature_disabled";
|
|
10
|
+
export const STORAGE_FEATURE_DISABLED_MESSAGE = "Storage tools aren't available on your account yet.";
|
|
11
|
+
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
12
|
+
function knownCompanyUids(env) {
|
|
13
|
+
const uids = new Set();
|
|
14
|
+
try {
|
|
15
|
+
const context = resolveProcessFlagCompanyContext(env);
|
|
16
|
+
if (context.companyUid)
|
|
17
|
+
uids.add(context.companyUid);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// No context: fall through to the manifest.
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const manifest = readCompaniesManifestMap(resolveDefaultHqRoot({ env }));
|
|
24
|
+
for (const entry of Object.values(manifest ?? {})) {
|
|
25
|
+
const uid = entry?.cloud_uid;
|
|
26
|
+
if (typeof uid === "string" && COMPANY_UID_RE.test(uid.trim()))
|
|
27
|
+
uids.add(uid.trim());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// An unreadable manifest only means fewer cached snapshots to check.
|
|
32
|
+
}
|
|
33
|
+
return [...uids];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* True only when a fresh persisted snapshot already says `hq-storage` is on
|
|
37
|
+
* for this caller (person scope or one of their companies). Everything else,
|
|
38
|
+
* including any read error, is false.
|
|
39
|
+
*/
|
|
40
|
+
export function storageFeatureEnabledFromCache(deps = {}) {
|
|
41
|
+
const env = deps.env ?? process.env;
|
|
42
|
+
try {
|
|
43
|
+
const local = readLocalOverride(HQ_STORAGE_FLAG, env);
|
|
44
|
+
if (local !== null)
|
|
45
|
+
return local;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Treat a broken override like no override.
|
|
49
|
+
}
|
|
50
|
+
const on = (snapshot) => snapshot?.flags?.[HQ_STORAGE_FLAG] === true;
|
|
51
|
+
try {
|
|
52
|
+
if (on((deps.readPersonSnapshot ?? (() => readPersonFlagSnapshotFile({ env })))()))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// fail closed
|
|
57
|
+
}
|
|
58
|
+
const readCompany = deps.readCompanySnapshot ?? ((uid) => readFlagSnapshotFile(uid, { env }));
|
|
59
|
+
let uids = [];
|
|
60
|
+
try {
|
|
61
|
+
uids = (deps.companyUids ?? (() => knownCompanyUids(env)))();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
for (const uid of uids) {
|
|
67
|
+
try {
|
|
68
|
+
if (on(readCompany(uid)))
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// fail closed for this company
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
/** The JSON refusal printed on stdout for `--json` callers such as the desktop app. */
|
|
78
|
+
export function storageFeatureDisabledJson() {
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
ok: false,
|
|
81
|
+
code: STORAGE_FEATURE_DISABLED_CODE,
|
|
82
|
+
message: STORAGE_FEATURE_DISABLED_MESSAGE,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Hide the catalog's `storage` root from root help unless the cached gate is
|
|
87
|
+
* on. Lives here, not in commands/storage-gate.ts, so the root-help path only
|
|
88
|
+
* loads this cache-only module.
|
|
89
|
+
*/
|
|
90
|
+
export function hideStorageInCatalog(program, enabled = storageFeatureEnabledFromCache) {
|
|
91
|
+
const storage = program.commands.find((command) => command.name() === "storage");
|
|
92
|
+
if (!storage)
|
|
93
|
+
return;
|
|
94
|
+
let visible = false;
|
|
95
|
+
try {
|
|
96
|
+
visible = enabled();
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
visible = false;
|
|
100
|
+
}
|
|
101
|
+
if (!visible)
|
|
102
|
+
storage._hidden = true;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=storage-feature-flag.js.map
|
package/dist/main.js
CHANGED
|
@@ -273,11 +273,22 @@ export async function runCli() {
|
|
|
273
273
|
const registration = await registerCommandsWithRecovery({
|
|
274
274
|
register: async () => {
|
|
275
275
|
const lazy = findLazyCommand(process.argv);
|
|
276
|
-
if (lazy) {
|
|
276
|
+
if (lazy?.name === "storage") {
|
|
277
|
+
// HQ Storage is an allowlist gate (owner hold 2026-10-11, Indigo-only):
|
|
278
|
+
// resolve it here, where awaiting is free, and fail closed.
|
|
279
|
+
const gate = await import("./commands/storage-gate.js");
|
|
280
|
+
if (await gate.storageFeatureEnabled())
|
|
281
|
+
await lazy.register(program);
|
|
282
|
+
else
|
|
283
|
+
gate.registerStorageRefusal(program, process.argv);
|
|
284
|
+
}
|
|
285
|
+
else if (lazy) {
|
|
277
286
|
await lazy.register(program);
|
|
278
287
|
}
|
|
279
288
|
else {
|
|
280
289
|
registerCommandCatalog(program);
|
|
290
|
+
const { hideStorageInCatalog } = await import("./lib/storage-feature-flag.js");
|
|
291
|
+
hideStorageInCatalog(program);
|
|
281
292
|
}
|
|
282
293
|
},
|
|
283
294
|
argv: process.argv,
|
|
@@ -41,7 +41,7 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
41
41
|
],
|
|
42
42
|
"bot-logs-follow": [],
|
|
43
43
|
mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../lib/anywhere-person-setting.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client"],
|
|
44
|
-
childWrapper: ["../main.js", "../lib/service-manager/startup-migration.js"],
|
|
44
|
+
childWrapper: ["../main.js", "../lib/service-manager/startup-migration.js", "../commands/storage-gate.js", "../lib/storage-feature-flag.js"],
|
|
45
45
|
coreJobRun: ["../commands/core-native-jobs-flag.js", "../commands/core-native-family-flag.js", "../lib/flag-registry.js", "../lib/flag-snapshot-file.js"],
|
|
46
46
|
"daemon-call": ["../lib/daemon/protocol.js"],
|
|
47
47
|
daemon: [
|
|
@@ -201,6 +201,8 @@ const LOADERS = {
|
|
|
201
201
|
childWrapper: [
|
|
202
202
|
() => import("../main.js"),
|
|
203
203
|
() => import("../lib/service-manager/startup-migration.js"),
|
|
204
|
+
() => import("../commands/storage-gate.js"),
|
|
205
|
+
() => import("../lib/storage-feature-flag.js"),
|
|
204
206
|
],
|
|
205
207
|
coreJobRun: [
|
|
206
208
|
() => import("../commands/core-native-jobs-flag.js"),
|
|
@@ -65,7 +65,7 @@ import chalk from "chalk";
|
|
|
65
65
|
import { Sentry } from "../sentry.js";
|
|
66
66
|
import { isCliTelemetryDisabled } from "./cli-telemetry.js";
|
|
67
67
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
68
|
-
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, resolvePnpmPathInstall, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
68
|
+
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, normalizeRunningInstall, nonWritablePrefixNote, openInstallOutput, resolvePnpmPathInstall, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
|
|
69
69
|
import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
|
|
70
70
|
import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
|
|
71
71
|
import { markLatestIneffective } from "./version-check.js";
|
|
@@ -926,7 +926,7 @@ async function attemptUpdateAndReexec(argv, flavor, known, deps, env) {
|
|
|
926
926
|
// actually running would stay exactly as stale as it started while the CLI
|
|
927
927
|
// reported success and re-exec'd into the same build. That is the shape of
|
|
928
928
|
// the 2026-09-02 gate loop — see isLocalDependencyInstall in version-gate.ts.
|
|
929
|
-
let install = (deps.resolveInstall ?? resolveRunningInstall)();
|
|
929
|
+
let install = normalizeRunningInstall((deps.resolveInstall ?? resolveRunningInstall)());
|
|
930
930
|
if (isLocalDependencyInstall(install)) {
|
|
931
931
|
console.error(chalk.dim(`hq-cli ${latest} is available, but this copy is a local dependency (${install.packageRoot}) — update the project that owns it.`));
|
|
932
932
|
return { action: "skipped", latest };
|
|
@@ -31,6 +31,9 @@ import { buildSpawnPlan, quoteForWindowsShell } from "./windows-spawn.js";
|
|
|
31
31
|
import { type UpdateLockHandle } from "./update-lock.js";
|
|
32
32
|
/** Prevent an update package manager's lifecycle child from starting another updater. */
|
|
33
33
|
export declare const UPDATE_RECURSION_GUARD_ENV = "HQ_RESCUE_SELF_UPDATED";
|
|
34
|
+
/** First-party hq-cli is published by this org; the 24h pnpm quarantine must not
|
|
35
|
+
* block the version-gate from installing the server-required build. */
|
|
36
|
+
export declare const PNPM_FIRST_PARTY_RELEASE_AGE_EXCLUDE: string;
|
|
34
37
|
export type VersionGatePhase = "preflight" | "lock_acquired" | "installer_running" | "verifying" | "rollback" | "complete" | "failed" | "unknown";
|
|
35
38
|
/** The last closed version-gate phase observed in this process. */
|
|
36
39
|
export declare function getVersionGatePhase(): VersionGatePhase;
|
|
@@ -86,6 +89,8 @@ export declare function npmPrefixFromPackageDir(pkgDir: string): string | null;
|
|
|
86
89
|
* Those layouts fall through to the ordinary npm-prefix/manual handling.
|
|
87
90
|
*/
|
|
88
91
|
export declare function isPnpmManagedPackageDir(pkgDir: string): boolean;
|
|
92
|
+
/** pnpm's content-addressed `store/vN/links/…` layout (not `store/vN/tmp` dlx). */
|
|
93
|
+
export declare function isPnpmContentStoreLinksPackageDir(pkgDir: string): boolean;
|
|
89
94
|
/** Whether the running package lives inside Bun's global install tree. */
|
|
90
95
|
export declare function isBunManagedPackageDir(pkgDir: string): boolean;
|
|
91
96
|
/**
|
|
@@ -164,6 +169,13 @@ export interface RunningInstall {
|
|
|
164
169
|
packageRoot: string | null;
|
|
165
170
|
}
|
|
166
171
|
export declare function resolveRunningInstall(): RunningInstall;
|
|
172
|
+
/**
|
|
173
|
+
* Repair a RunningInstall whose package root is a pnpm content store but whose
|
|
174
|
+
* manager was recorded as npm (the pre-fix classification of a realpath'd
|
|
175
|
+
* pnpm-global shim). Callers that inject an install snapshot must run this
|
|
176
|
+
* before the local-dependency hard stop, or they replay the 2420 lockout.
|
|
177
|
+
*/
|
|
178
|
+
export declare function normalizeRunningInstall(install: RunningInstall): RunningInstall;
|
|
167
179
|
/** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
|
|
168
180
|
export declare function resolveRunningManager(): InstallManager;
|
|
169
181
|
/** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
|
|
@@ -564,8 +576,11 @@ export declare const __test__: {
|
|
|
564
576
|
isLocalDependencyInstall: typeof isLocalDependencyInstall;
|
|
565
577
|
isLocalDependencyPackageDir: typeof isLocalDependencyPackageDir;
|
|
566
578
|
isNewerVersion: typeof isNewerVersion;
|
|
579
|
+
isPnpmContentStoreLinksPackageDir: typeof isPnpmContentStoreLinksPackageDir;
|
|
567
580
|
isPnpmManagedPackageDir: typeof isPnpmManagedPackageDir;
|
|
568
581
|
isPnpmVirtualStorePackageDir: typeof isPnpmVirtualStorePackageDir;
|
|
582
|
+
normalizeRunningInstall: typeof normalizeRunningInstall;
|
|
583
|
+
PNPM_FIRST_PARTY_RELEASE_AGE_EXCLUDE: string;
|
|
569
584
|
isPrefixWritable: typeof isPrefixWritable;
|
|
570
585
|
nonWritablePrefixNote: typeof nonWritablePrefixNote;
|
|
571
586
|
npmPrefixFromPackageDir: typeof npmPrefixFromPackageDir;
|
|
@@ -49,6 +49,9 @@ const CLIENT_ID = "hq-cli";
|
|
|
49
49
|
const ENDPOINT_PATH = "/v1/client-version/check";
|
|
50
50
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
51
51
|
const LATEST_PACKAGE_SPEC = `${CLI_NAME}@latest`;
|
|
52
|
+
/** First-party hq-cli is published by this org; the 24h pnpm quarantine must not
|
|
53
|
+
* block the version-gate from installing the server-required build. */
|
|
54
|
+
export const PNPM_FIRST_PARTY_RELEASE_AGE_EXCLUDE = `--config.minimumReleaseAgeExclude=${CLI_NAME}`;
|
|
52
55
|
let currentVersionGatePhase = "unknown";
|
|
53
56
|
/** The last closed version-gate phase observed in this process. */
|
|
54
57
|
export function getVersionGatePhase() {
|
|
@@ -294,6 +297,29 @@ export function isPnpmManagedPackageDir(pkgDir) {
|
|
|
294
297
|
return true;
|
|
295
298
|
}
|
|
296
299
|
}
|
|
300
|
+
// Node realpaths pnpm's generated shim into the content-addressed store:
|
|
301
|
+
// $PNPM_HOME/store/v11/links/@indigoai-us/hq-cli/<ver>/<hash>/node_modules/@indigoai-us/hq-cli
|
|
302
|
+
// There is no `global/` segment after realpath, so the checks above miss it
|
|
303
|
+
// and the gate used to label this a "local dependency" (feedback 2420).
|
|
304
|
+
// `store/vN/tmp/` is pnpm dlx and must stay non-global.
|
|
305
|
+
if (isPnpmContentStoreLinksPackageDir(normalized))
|
|
306
|
+
return true;
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
/** pnpm's content-addressed `store/vN/links/…` layout (not `store/vN/tmp` dlx). */
|
|
310
|
+
export function isPnpmContentStoreLinksPackageDir(pkgDir) {
|
|
311
|
+
const normalized = pkgDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
312
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
313
|
+
for (let i = 0; i < segments.length - 2; i += 1) {
|
|
314
|
+
if (segments[i] !== "store")
|
|
315
|
+
continue;
|
|
316
|
+
if (!/^v\d+$/.test(segments[i + 1]))
|
|
317
|
+
continue;
|
|
318
|
+
if (segments[i + 2] === "tmp")
|
|
319
|
+
return false;
|
|
320
|
+
if (segments[i + 2] === "links")
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
297
323
|
return false;
|
|
298
324
|
}
|
|
299
325
|
/** Whether the running package lives inside Bun's global install tree. */
|
|
@@ -449,6 +475,21 @@ export function resolveRunningInstall() {
|
|
|
449
475
|
return { manager: "npm", prefix: null, packageRoot: null };
|
|
450
476
|
}
|
|
451
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* Repair a RunningInstall whose package root is a pnpm content store but whose
|
|
480
|
+
* manager was recorded as npm (the pre-fix classification of a realpath'd
|
|
481
|
+
* pnpm-global shim). Callers that inject an install snapshot must run this
|
|
482
|
+
* before the local-dependency hard stop, or they replay the 2420 lockout.
|
|
483
|
+
*/
|
|
484
|
+
export function normalizeRunningInstall(install) {
|
|
485
|
+
if (!install.packageRoot)
|
|
486
|
+
return install;
|
|
487
|
+
if (install.manager === "pnpm")
|
|
488
|
+
return install;
|
|
489
|
+
if (!isPnpmManagedPackageDir(install.packageRoot))
|
|
490
|
+
return install;
|
|
491
|
+
return { manager: "pnpm", prefix: null, packageRoot: install.packageRoot };
|
|
492
|
+
}
|
|
452
493
|
/** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
|
|
453
494
|
export function resolveRunningManager() {
|
|
454
495
|
return resolveRunningInstall().manager;
|
|
@@ -652,11 +693,15 @@ export function derivePnpmHome(install) {
|
|
|
652
693
|
if (install.manager !== "pnpm" || !install.packageRoot)
|
|
653
694
|
return null;
|
|
654
695
|
const normalized = install.packageRoot.replace(/\\/g, "/");
|
|
655
|
-
const
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
696
|
+
const globalMarker = normalized.indexOf("/global/");
|
|
697
|
+
if (globalMarker !== -1) {
|
|
698
|
+
const home = normalized.slice(0, globalMarker);
|
|
699
|
+
return home || null;
|
|
700
|
+
}
|
|
701
|
+
const storeMatch = normalized.match(/^(.*)\/store\/v\d+\/links\//);
|
|
702
|
+
if (storeMatch)
|
|
703
|
+
return storeMatch[1] || null;
|
|
704
|
+
return null;
|
|
660
705
|
}
|
|
661
706
|
/**
|
|
662
707
|
* Environment for the pnpm self-update spawn. pnpm requires its configured
|
|
@@ -713,7 +758,7 @@ export function buildPrefixedInstallArgv(prefix) {
|
|
|
713
758
|
* the new version — which is the whole point of routing here instead of npm.
|
|
714
759
|
*/
|
|
715
760
|
export function buildPnpmInstallArgv(install) {
|
|
716
|
-
const args = ["add", "-g", LATEST_PACKAGE_SPEC];
|
|
761
|
+
const args = ["add", "-g", LATEST_PACKAGE_SPEC, PNPM_FIRST_PARTY_RELEASE_AGE_EXCLUDE];
|
|
717
762
|
const dirs = install ? pnpmInstallDirs(install) : null;
|
|
718
763
|
if (dirs) {
|
|
719
764
|
args.push("--global-dir", dirs.globalDir, "--store-dir", dirs.storeDir);
|
|
@@ -732,8 +777,18 @@ export function pnpmInstallDirs(install) {
|
|
|
732
777
|
const normalized = install.packageRoot.replace(/\\/g, "/");
|
|
733
778
|
const global = normalized.match(/^(.*\/global\/\d+\/node_modules)(?:\/|$)|^(.*\/global\/v\d+\/[^/]+\/node_modules)(?:\/|$)/);
|
|
734
779
|
const globalNodeModules = global?.[1] ?? global?.[2];
|
|
735
|
-
if (!globalNodeModules)
|
|
736
|
-
|
|
780
|
+
if (!globalNodeModules) {
|
|
781
|
+
const storeMatch = normalized.match(/^(.*)\/store\/(v\d+)\/links\//);
|
|
782
|
+
if (!storeMatch)
|
|
783
|
+
return null;
|
|
784
|
+
const pnpmHome = storeMatch[1];
|
|
785
|
+
const storeVersion = storeMatch[2];
|
|
786
|
+
return {
|
|
787
|
+
globalDir: path.join(pnpmHome, "global"),
|
|
788
|
+
storeDir: path.join(pnpmHome, "store", storeVersion),
|
|
789
|
+
virtualStoreDir: path.join(pnpmHome, "global", storeVersion, "node_modules", ".pnpm"),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
737
792
|
const metadataPath = path.join(globalNodeModules, ".modules.yaml");
|
|
738
793
|
try {
|
|
739
794
|
const metadata = yaml.load(readFileSync(metadataPath, "utf8"));
|
|
@@ -966,6 +1021,10 @@ async function fetchVersionDecision(timeoutMs = FETCH_TIMEOUT_MS) {
|
|
|
966
1021
|
return null;
|
|
967
1022
|
}
|
|
968
1023
|
}
|
|
1024
|
+
function isReleaseAgeInstallFailure(result) {
|
|
1025
|
+
const text = `${result.detail ?? ""}\n${result.stdout ?? ""}`;
|
|
1026
|
+
return /minimumReleaseAge|minimum-release-age|published less than/i.test(text);
|
|
1027
|
+
}
|
|
969
1028
|
// The Windows spawn recipe (`buildSpawnPlan` + `quoteForWindowsShell`) lives in
|
|
970
1029
|
// ./windows-spawn.ts so the self-update path and the qmd spawn path share one
|
|
971
1030
|
// implementation. Imported at the top and re-exported here to keep this module's
|
|
@@ -1353,6 +1412,7 @@ function manualUpdateCommand(install, decision) {
|
|
|
1353
1412
|
* remains for callers that pass no handler.
|
|
1354
1413
|
*/
|
|
1355
1414
|
function nudgeUpdateRecommended(decision, install = resolveRunningInstall()) {
|
|
1415
|
+
install = normalizeRunningInstall(install);
|
|
1356
1416
|
if (!isNewerVersion(decision.latestVersion, CLI_VERSION))
|
|
1357
1417
|
return;
|
|
1358
1418
|
const msg = chalk.yellow(`⚠ A new version of hq-cli is available: ${decision.latestVersion} (current: ${CLI_VERSION}).`);
|
|
@@ -1663,7 +1723,7 @@ function enforceUpdateRequired(decision, deps = {}) {
|
|
|
1663
1723
|
// server's `updateCommand` and any npm prefix are wrong for those layouts, so
|
|
1664
1724
|
// `resolveRunningInstall`
|
|
1665
1725
|
// already reports `prefix: null` there and neither is consulted below.
|
|
1666
|
-
const install = (deps.resolveInstall ?? resolveRunningInstall)();
|
|
1726
|
+
const install = normalizeRunningInstall((deps.resolveInstall ?? resolveRunningInstall)());
|
|
1667
1727
|
// Loop protection #1 — nothing here can update a copy this process does not
|
|
1668
1728
|
// own. Installing globally would leave the local copy that is actually
|
|
1669
1729
|
// running stale, so the gate would fire again on the very next invocation.
|
|
@@ -1886,6 +1946,13 @@ function attemptRequiredUpdate(decision, deps, install) {
|
|
|
1886
1946
|
console.error(chalk.dim(" Continuing on the current hq-cli version."));
|
|
1887
1947
|
return "continue";
|
|
1888
1948
|
}
|
|
1949
|
+
// A 24h pnpm quarantine that still blocks first-party hq-cli must not
|
|
1950
|
+
// lock the user out of secrets (or any other command) for the rest of
|
|
1951
|
+
// that window. Warn and run the current CLI — same as a timed-out updater.
|
|
1952
|
+
if (isReleaseAgeInstallFailure(result)) {
|
|
1953
|
+
console.error(chalk.dim(" The package manager refused a build newer than its 24h install guard; continuing on the current hq-cli version."));
|
|
1954
|
+
return "continue";
|
|
1955
|
+
}
|
|
1889
1956
|
return noticeFlush ? noticeFlush.then(() => 75) : 75;
|
|
1890
1957
|
}
|
|
1891
1958
|
// Read-your-writes: a "successful" install into the npm prefix does not
|
|
@@ -1914,7 +1981,7 @@ export async function enforceVersionGate(onUpdateRecommended, options = {}) {
|
|
|
1914
1981
|
return "continue";
|
|
1915
1982
|
// Resolved once, here, so the up-to-date case never pays for the walk and
|
|
1916
1983
|
// neither downstream path repeats it.
|
|
1917
|
-
const install = resolveRunningInstall();
|
|
1984
|
+
const install = normalizeRunningInstall(resolveRunningInstall());
|
|
1918
1985
|
if (decision.updateRequired) {
|
|
1919
1986
|
// Load the staged installer only when an npm prefix is resolved. The version
|
|
1920
1987
|
// gate is imported during every CLI startup, so keep the filesystem-heavy
|
|
@@ -1985,8 +2052,11 @@ export const __test__ = {
|
|
|
1985
2052
|
isLocalDependencyInstall,
|
|
1986
2053
|
isLocalDependencyPackageDir,
|
|
1987
2054
|
isNewerVersion,
|
|
2055
|
+
isPnpmContentStoreLinksPackageDir,
|
|
1988
2056
|
isPnpmManagedPackageDir,
|
|
1989
2057
|
isPnpmVirtualStorePackageDir,
|
|
2058
|
+
normalizeRunningInstall,
|
|
2059
|
+
PNPM_FIRST_PARTY_RELEASE_AGE_EXCLUDE,
|
|
1990
2060
|
isPrefixWritable,
|
|
1991
2061
|
nonWritablePrefixNote,
|
|
1992
2062
|
npmPrefixFromPackageDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.81",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
49
49
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
50
50
|
"@aws-sdk/dsql-signer": "^3.1146.0",
|
|
51
|
-
"@indigoai-us/hq-cloud": "~6.18.
|
|
51
|
+
"@indigoai-us/hq-cloud": "~6.18.61",
|
|
52
52
|
"@indigoai-us/hq-flags-client": "^0.1.2",
|
|
53
53
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
54
54
|
"@sentry/node": "^10.49.0",
|