@indigoai-us/hq-cli 5.345.80 → 5.345.82

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.
@@ -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"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.80",
3
+ "version": "5.345.82",
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.54",
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",