@indigoai-us/hq-cli 5.345.188 → 5.345.189

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/command-catalog.generated.d.ts +34 -0
  3. package/dist/command-catalog.generated.js +42 -0
  4. package/dist/commands/bot.js +15 -3
  5. package/dist/commands/core-company-index-scope-flag.d.ts +2 -1
  6. package/dist/commands/core-company-index-scope-flag.js +2 -1
  7. package/dist/commands/core.js +0 -5
  8. package/dist/commands/dm-attach.d.ts +73 -0
  9. package/dist/commands/dm-attach.js +104 -0
  10. package/dist/commands/dm.d.ts +10 -0
  11. package/dist/commands/dm.js +114 -2
  12. package/dist/commands/sources.js +15 -0
  13. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +45054 -36140
  14. package/dist/hq-cloud-sync-runner/package.json +1 -1
  15. package/dist/lib/bot/fleet-local.d.ts +3 -1
  16. package/dist/lib/bot/fleet-local.js +9 -3
  17. package/dist/lib/bot/fleet-python.d.ts +6 -0
  18. package/dist/lib/bot/fleet-python.js +24 -5
  19. package/dist/lib/bot/probe.d.ts +7 -1
  20. package/dist/lib/bot/probe.js +11 -3
  21. package/dist/lib/bot/run.d.ts +9 -0
  22. package/dist/lib/bot/run.js +114 -8
  23. package/dist/lib/bot/runtime-failure.d.ts +3 -0
  24. package/dist/lib/bot/runtime-failure.js +20 -2
  25. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  26. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  27. package/dist/lib/index-render/session-scope.d.ts +3 -3
  28. package/dist/lib/index-render/session-scope.js +11 -9
  29. package/dist/lib/index-render/shared.d.ts +6 -4
  30. package/dist/lib/index-render/shared.js +33 -4
  31. package/dist/utils/cognito-session.d.ts +4 -2
  32. package/dist/utils/cognito-session.js +27 -42
  33. package/dist/utils/hq-root-resolver.d.ts +27 -1
  34. package/dist/utils/hq-root-resolver.js +65 -33
  35. package/package.json +14 -14
@@ -60,9 +60,9 @@ export interface PlanOptions {
60
60
  * - Unbound session: an explicit company is used as given; otherwise every
61
61
  * company is rebuilt, with a warning unless --all-companies was passed.
62
62
  * - Indeterminate session state: refused.
63
- * - `companies` for one company needs cli.rebuild-index-company-scope, whether
64
- * the company came from the caller or from the bound set. Under `all` the
65
- * companies child is skipped instead when the flag is off.
63
+ * - Bound isolation is not optional. The kill switch may restore every-company
64
+ * writes only for an unbound session (automation). A bound session never
65
+ * writes another company's INDEX.md (feedback_31a4e992).
66
66
  * - Every company is checked to be a real directory before anything runs.
67
67
  */
68
68
  export declare function planScopedRebuild(target: IndexTarget, args: readonly string[], root: string, env: NodeJS.ProcessEnv, options: PlanOptions): RebuildPlan;
@@ -196,9 +196,9 @@ function companyArgs(target, slug) {
196
196
  * - Unbound session: an explicit company is used as given; otherwise every
197
197
  * company is rebuilt, with a warning unless --all-companies was passed.
198
198
  * - Indeterminate session state: refused.
199
- * - `companies` for one company needs cli.rebuild-index-company-scope, whether
200
- * the company came from the caller or from the bound set. Under `all` the
201
- * companies child is skipped instead when the flag is off.
199
+ * - Bound isolation is not optional. The kill switch may restore every-company
200
+ * writes only for an unbound session (automation). A bound session never
201
+ * writes another company's INDEX.md (feedback_31a4e992).
202
202
  * - Every company is checked to be a real directory before anything runs.
203
203
  */
204
204
  export function planScopedRebuild(target, args, root, env, options) {
@@ -261,17 +261,19 @@ export function planScopedRebuild(target, args, root, env, options) {
261
261
  }
262
262
  }
263
263
  }
264
- // cli.rebuild-index-company-scope is a kill switch (default on). Off, the
265
- // companies root-index rebuild goes back to its pre-scoping behavior
266
- // (every company) with a warning, instead of being refused or skipped.
264
+ // cli.rebuild-index-company-scope is a kill switch (default on). Off, an
265
+ // unbound `companies` rebuild goes back to every company (automation). A
266
+ // bound session still rebuilds only its bound companies.
267
267
  const gateOff = !options.companyIndexScopeEnabled;
268
- if (target === "companies" && gateOff && explicit.length === 0) {
268
+ const bound = state.kind === "bound";
269
+ if (target === "companies" && gateOff && explicit.length === 0 && !bound) {
269
270
  return { runs: [{ target, args: [] }], warning: COMPANY_SCOPE_FLAG_OFF_WARNING };
270
271
  }
271
272
  if (target === "all") {
273
+ const unscoped = gateOff && !bound;
272
274
  return {
273
- runs: [{ target, args: [...companies.flatMap((slug) => ["--company", slug]), ...(gateOff ? [UNSCOPED_COMPANIES_INDEX] : [])] }],
274
- warning: gateOff ? COMPANY_SCOPE_FLAG_OFF_WARNING : warning,
275
+ runs: [{ target, args: [...companies.flatMap((slug) => ["--company", slug]), ...(unscoped ? [UNSCOPED_COMPANIES_INDEX] : [])] }],
276
+ warning: unscoped ? COMPANY_SCOPE_FLAG_OFF_WARNING : warning,
275
277
  };
276
278
  }
277
279
  return { runs: companies.map((slug) => ({ target, args: companyArgs(target, slug) })), warning };
@@ -54,10 +54,12 @@ export declare function generatedStamp(content: string): string | undefined;
54
54
  export declare function log(context: RenderContext, message: string): void;
55
55
  export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string, expectedStatePrdPath?: string): string;
56
56
  /**
57
- * Project status from prd.json alone: "complete" when every story passes,
58
- * otherwise `fallback`. Unlike `projectStatus`, this never reads the
59
- * machine-local `workspace/orchestrator/<project>/state.json`, so synced
60
- * output renders the same on every machine.
57
+ * Project status from files that sync with the company: sibling `state.json`
58
+ * (status written by skills such as demand-test), then prd.json `status` /
59
+ * `metadata.status`, then "complete" when every story passes, otherwise
60
+ * `fallback`. Unlike `projectStatus`, this never reads the machine-local
61
+ * `workspace/orchestrator/<project>/state.json`, so synced output renders
62
+ * the same on every machine.
61
63
  */
62
64
  export declare function prdProjectStatus(prdPath: string, fallback: string): string;
63
65
  export declare function basename(file: string): string;
@@ -166,14 +166,43 @@ export function projectStatus(root, project, prdPath, fallback, expectedStatePrd
166
166
  }
167
167
  return prdProjectStatus(prdPath, fallback);
168
168
  }
169
+ function normalizeProjectStatus(raw) {
170
+ if (["completed", "complete"].includes(raw))
171
+ return "complete";
172
+ if (["in_progress", "active", "started"].includes(raw))
173
+ return "active";
174
+ return raw;
175
+ }
176
+ function explicitProjectStatus(record) {
177
+ if (!record)
178
+ return undefined;
179
+ if (typeof record.status === "string" && record.status.trim()) {
180
+ return normalizeProjectStatus(record.status.trim());
181
+ }
182
+ const metadata = record.metadata;
183
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
184
+ const nested = metadata.status;
185
+ if (typeof nested === "string" && nested.trim())
186
+ return normalizeProjectStatus(nested.trim());
187
+ }
188
+ return undefined;
189
+ }
169
190
  /**
170
- * Project status from prd.json alone: "complete" when every story passes,
171
- * otherwise `fallback`. Unlike `projectStatus`, this never reads the
172
- * machine-local `workspace/orchestrator/<project>/state.json`, so synced
173
- * output renders the same on every machine.
191
+ * Project status from files that sync with the company: sibling `state.json`
192
+ * (status written by skills such as demand-test), then prd.json `status` /
193
+ * `metadata.status`, then "complete" when every story passes, otherwise
194
+ * `fallback`. Unlike `projectStatus`, this never reads the machine-local
195
+ * `workspace/orchestrator/<project>/state.json`, so synced output renders
196
+ * the same on every machine.
174
197
  */
175
198
  export function prdProjectStatus(prdPath, fallback) {
199
+ const localState = explicitProjectStatus(readJson(path.join(path.dirname(prdPath), "state.json")));
200
+ if (localState)
201
+ return localState;
176
202
  const prd = readJson(prdPath);
203
+ const declared = explicitProjectStatus(prd);
204
+ if (declared)
205
+ return declared;
177
206
  if (prd && Array.isArray(prd.userStories) && prd.userStories.length > 0 && prd.userStories.every((story) => Boolean(story.passes)))
178
207
  return "complete";
179
208
  return fallback;
@@ -52,7 +52,7 @@ export declare const DEFAULT_VAULT_API_URL: string;
52
52
  * 5. Discovery: scan well-known locations (`~/HQ`, `~/hq`, `~/Documents/HQ`,
53
53
  * …) for a folder carrying a valid `core.yaml` signature. Safety net for
54
54
  * installs that never wrote the path back to menubar.json.
55
- * 6. Fall back to `~/hq` (the historical CLI default) — or throw, per
55
+ * 6. Fall back to `~/hq` (`C:\hq` on Windows) — or throw, per
56
56
  * `opts.onMissing`.
57
57
  *
58
58
  * Why tiers 3–5 exist:
@@ -71,7 +71,7 @@ export declare const DEFAULT_VAULT_API_URL: string;
71
71
  * the walk from latching onto `<hqRoot>/core/` (Codex P2 on hq#146).
72
72
  *
73
73
  * `opts.onMissing` controls the final arm:
74
- * - `'fallback'` (default) — return `~/hq` if nothing resolves. Preserves the
74
+ * - `'fallback'` (default) — return `~/hq` (`C:\hq` on Windows) if nothing resolves. Preserves the
75
75
  * module-load contract of `DEFAULT_HQ_ROOT`, pinned by commander.js
76
76
  * `.option()` callers at registration time.
77
77
  * - `'throw'` — throw a user-actionable error. Used by module-management
@@ -88,6 +88,8 @@ export declare function resolveDefaultHqRoot(opts?: {
88
88
  env?: NodeJS.ProcessEnv;
89
89
  cwd?: string;
90
90
  home?: string;
91
+ /** Defaults to process.platform; injectable so tests can exercise win32. */
92
+ platform?: NodeJS.Platform;
91
93
  }): string;
92
94
  export declare const DEFAULT_HQ_ROOT: string;
93
95
  /**
@@ -30,6 +30,7 @@ import { hasPersonCachedSession, isMachineCredsFileEnvSet, probeMachineIdentity,
30
30
  import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
31
31
  import { daemonDir } from "../lib/mesh/live/daemon/paths.js";
32
32
  import { workMeshRoot } from "../lib/mesh/live/paths.js";
33
+ import { defaultHqRootFallback, hqDiscoveryCandidates, pathForPlatform, } from "./hq-root-resolver.js";
33
34
  import { acquireLockFileExclusively } from "./exclusive-lock.js";
34
35
  import { reportNoninteractivePersonRefreshRejection } from "./cognito-refresh-sentry.js";
35
36
  import { decodeJwtPayload, loadCachedTokens, personTokenCacheFile, readPersonTokenFile, readTokenFile, } from "./person-token-cache.js";
@@ -78,7 +79,7 @@ export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hq
78
79
  * 5. Discovery: scan well-known locations (`~/HQ`, `~/hq`, `~/Documents/HQ`,
79
80
  * …) for a folder carrying a valid `core.yaml` signature. Safety net for
80
81
  * installs that never wrote the path back to menubar.json.
81
- * 6. Fall back to `~/hq` (the historical CLI default) — or throw, per
82
+ * 6. Fall back to `~/hq` (`C:\hq` on Windows) — or throw, per
82
83
  * `opts.onMissing`.
83
84
  *
84
85
  * Why tiers 3–5 exist:
@@ -97,7 +98,7 @@ export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hq
97
98
  * the walk from latching onto `<hqRoot>/core/` (Codex P2 on hq#146).
98
99
  *
99
100
  * `opts.onMissing` controls the final arm:
100
- * - `'fallback'` (default) — return `~/hq` if nothing resolves. Preserves the
101
+ * - `'fallback'` (default) — return `~/hq` (`C:\hq` on Windows) if nothing resolves. Preserves the
101
102
  * module-load contract of `DEFAULT_HQ_ROOT`, pinned by commander.js
102
103
  * `.option()` callers at registration time.
103
104
  * - `'throw'` — throw a user-actionable error. Used by module-management
@@ -111,32 +112,35 @@ export const DEFAULT_VAULT_API_URL = process.env.HQ_VAULT_API_URL ?? "https://hq
111
112
  export function resolveDefaultHqRoot(opts = {}) {
112
113
  const env = opts.env ?? process.env;
113
114
  const home = opts.home ?? os.homedir();
115
+ const platform = opts.platform ?? process.platform;
116
+ const p = pathForPlatform(platform);
114
117
  // Tier 1: explicit override.
115
118
  if (env.HQ_ROOT)
116
- return path.resolve(env.HQ_ROOT);
119
+ return p.resolve(env.HQ_ROOT);
117
120
  // Tier 2: working-tree walk-up — operate on the HQ tree you're inside.
118
- let cur = path.resolve(opts.cwd ?? process.cwd());
119
- while (cur !== path.dirname(cur)) {
120
- if (isHqRoot(cur))
121
+ let cur = p.resolve(opts.cwd ?? process.cwd());
122
+ while (cur !== p.dirname(cur)) {
123
+ if (isHqRoot(cur, p))
121
124
  return cur;
122
- cur = path.dirname(cur);
125
+ cur = p.dirname(cur);
123
126
  }
124
127
  // Tiers 3–4: the installer/menubar's canonical "where is HQ" records.
125
- const fromConfig = readHqRootFromHqConfig(path.join(home, ".hq"));
128
+ const fromConfig = readHqRootFromHqConfig(p.join(home, ".hq"), p);
126
129
  if (fromConfig)
127
- return path.resolve(fromConfig);
128
- // Tier 5: discover a valid HQ root in the well-known locations.
129
- const discovered = discoverHqRootViaCoreYaml(home);
130
+ return p.resolve(fromConfig);
131
+ // Tier 5: discover a valid HQ root in the well-known locations (on Windows
132
+ // the home-relative ones first, then %SystemDrive%\hq and %LOCALAPPDATA%\hq).
133
+ const discovered = hqDiscoveryCandidates({ platform, env, home }).find((dir) => isValidHqRootSignature(dir, p));
130
134
  if (discovered)
131
135
  return discovered;
132
- // Tier 6: nothing resolved.
136
+ // Tier 6: nothing resolved. Fallback is ~/hq, or %SystemDrive%\hq on Windows.
133
137
  if (opts.onMissing === "throw") {
134
138
  throw new Error("Could not find HQ root. Run this command from within your HQ directory " +
135
139
  "(must contain core.yaml AND a companies/ subdirectory), set $HQ_ROOT, " +
136
140
  "or install HQ via the installer/menubar so ~/.hq/menubar.json records " +
137
141
  "its location.");
138
142
  }
139
- return path.join(home, "hq");
143
+ return defaultHqRootFallback({ platform, env, home });
140
144
  }
141
145
  /**
142
146
  * Read the configured HQ root from the installer/menubar records under
@@ -147,11 +151,11 @@ export function resolveDefaultHqRoot(opts = {}) {
147
151
  * Returns the first non-empty string, or undefined. Best-effort: a missing or
148
152
  * malformed file is treated as "not configured" (never throws).
149
153
  */
150
- function readHqRootFromHqConfig(hqConfigDir) {
151
- const fromMenubar = readJsonStringField(path.join(hqConfigDir, "menubar.json"), "hqPath");
154
+ function readHqRootFromHqConfig(hqConfigDir, p = path) {
155
+ const fromMenubar = readJsonStringField(p.join(hqConfigDir, "menubar.json"), "hqPath");
152
156
  if (fromMenubar)
153
157
  return fromMenubar;
154
- return readJsonStringField(path.join(hqConfigDir, "config.json"), "hqFolderPath");
158
+ return readJsonStringField(p.join(hqConfigDir, "config.json"), "hqFolderPath");
155
159
  }
156
160
  /** Read a top-level string field from a JSON file, or undefined on any miss. */
157
161
  function readJsonStringField(file, field) {
@@ -164,25 +168,6 @@ function readJsonStringField(file, field) {
164
168
  return undefined;
165
169
  }
166
170
  }
167
- /** Well-known parent locations an HQ tree commonly lives in (mirrors hq-sync). */
168
- function hqDiscoveryCandidates(home = os.homedir()) {
169
- return [
170
- path.join(home, "HQ"),
171
- path.join(home, "hq"),
172
- path.join(home, "Documents", "HQ"),
173
- path.join(home, "Documents", "hq"),
174
- path.join(home, "Desktop", "HQ"),
175
- path.join(home, "Desktop", "hq"),
176
- ];
177
- }
178
- /**
179
- * Scan the well-known locations for a folder carrying a valid `core.yaml`
180
- * signature. First match wins; returns undefined if none qualify. Cheap — a
181
- * few stats plus one small YAML parse on a hit.
182
- */
183
- function discoverHqRootViaCoreYaml(home) {
184
- return hqDiscoveryCandidates(home).find(isValidHqRootSignature);
185
- }
186
171
  /**
187
172
  * True iff `dir` carries a valid hq-core `core.yaml` — canonical
188
173
  * (`<dir>/core/core.yaml`, hq-core ≥v14) or legacy (`<dir>/core.yaml`) — that
@@ -190,9 +175,9 @@ function discoverHqRootViaCoreYaml(home) {
190
175
  * field check (matching hq-sync's `is_valid_hq_root`) stops a stray `core.yaml`
191
176
  * from another tool from false-matching.
192
177
  */
193
- function isValidHqRootSignature(dir) {
194
- const canonical = path.join(dir, "core", "core.yaml");
195
- const legacy = path.join(dir, "core.yaml");
178
+ function isValidHqRootSignature(dir, p = path) {
179
+ const canonical = p.join(dir, "core", "core.yaml");
180
+ const legacy = p.join(dir, "core.yaml");
196
181
  let file;
197
182
  if (fileExists(canonical))
198
183
  file = canonical;
@@ -227,13 +212,13 @@ function fileExists(p) {
227
212
  * the walk from latching onto the inner `core/` subtree, which carries its own
228
213
  * `core.yaml` but no `companies/`.
229
214
  */
230
- function isHqRoot(dir) {
231
- const hasCoreYaml = fileExists(path.join(dir, "core", "core.yaml")) ||
232
- fileExists(path.join(dir, "core.yaml"));
215
+ function isHqRoot(dir, p = path) {
216
+ const hasCoreYaml = fileExists(p.join(dir, "core", "core.yaml")) ||
217
+ fileExists(p.join(dir, "core.yaml"));
233
218
  if (!hasCoreYaml)
234
219
  return false;
235
220
  try {
236
- return fs.statSync(path.join(dir, "companies")).isDirectory();
221
+ return fs.statSync(p.join(dir, "companies")).isDirectory();
237
222
  }
238
223
  catch {
239
224
  return false;
@@ -1,13 +1,39 @@
1
1
  /** Lightweight HQ-root resolver shared by cloud commands and fast flag scope. */
2
+ import * as path from "node:path";
3
+ /** Path module matching the target platform's path shapes. */
4
+ export declare function pathForPlatform(platform?: NodeJS.Platform): path.PlatformPath;
5
+ /**
6
+ * The last-resort HQ root when nothing else resolves: `C:\hq` on Windows
7
+ * (`%SystemDrive%\hq`), `~/hq` elsewhere. Pure; all inputs injectable.
8
+ */
9
+ export declare function defaultHqRootFallback(opts?: {
10
+ platform?: NodeJS.Platform;
11
+ env?: NodeJS.ProcessEnv;
12
+ home?: string;
13
+ }): string;
14
+ /**
15
+ * Well-known locations an HQ tree commonly lives in (mirrors hq-sync). On
16
+ * Windows `%SystemDrive%\hq` then `%LOCALAPPDATA%\hq` come AFTER the
17
+ * home-relative candidates, so an existing unrecorded `%USERPROFILE%\hq`
18
+ * install keeps winning over a stray `C:\hq` (avoids a split root).
19
+ */
20
+ export declare function hqDiscoveryCandidates(opts?: {
21
+ platform?: NodeJS.Platform;
22
+ env?: NodeJS.ProcessEnv;
23
+ home?: string;
24
+ }): string[];
2
25
  /**
3
26
  * Resolve the HQ tree root using the same precedence as the Cognito helpers:
4
27
  * explicit HQ_ROOT, nearest cwd root, installer records, known locations, then
5
- * the historical ~/hq fallback (or an error when requested).
28
+ * the platform fallback (`~/hq`, or `C:\hq` on Windows), or an error when
29
+ * requested.
6
30
  */
7
31
  export declare function resolveDefaultHqRoot(opts?: {
8
32
  onMissing?: "throw" | "fallback";
9
33
  env?: NodeJS.ProcessEnv;
10
34
  cwd?: string;
11
35
  home?: string;
36
+ platform?: NodeJS.Platform;
12
37
  }): string;
38
+ export declare function isValidHqRootSignature(dir: string, p?: path.PlatformPath): boolean;
13
39
  //# sourceMappingURL=hq-root-resolver.d.ts.map
@@ -3,26 +3,71 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import * as yaml from "js-yaml";
6
+ /** Path module matching the target platform's path shapes. */
7
+ export function pathForPlatform(platform = process.platform) {
8
+ return platform === "win32" ? path.win32 : path.posix;
9
+ }
10
+ /** `%SystemDrive%` (for example `C:`), defaulting to `C:` when unset. */
11
+ function windowsSystemDrive(env) {
12
+ const drive = env.SystemDrive ?? env.SYSTEMDRIVE;
13
+ return drive && drive.trim().length > 0 ? drive.trim() : "C:";
14
+ }
15
+ /**
16
+ * The last-resort HQ root when nothing else resolves: `C:\hq` on Windows
17
+ * (`%SystemDrive%\hq`), `~/hq` elsewhere. Pure; all inputs injectable.
18
+ */
19
+ export function defaultHqRootFallback(opts = {}) {
20
+ const platform = opts.platform ?? process.platform;
21
+ const env = opts.env ?? process.env;
22
+ if (platform === "win32") {
23
+ return path.win32.join(`${windowsSystemDrive(env)}\\`, "hq");
24
+ }
25
+ return path.posix.join(opts.home ?? os.homedir(), "hq");
26
+ }
27
+ /**
28
+ * Well-known locations an HQ tree commonly lives in (mirrors hq-sync). On
29
+ * Windows `%SystemDrive%\hq` then `%LOCALAPPDATA%\hq` come AFTER the
30
+ * home-relative candidates, so an existing unrecorded `%USERPROFILE%\hq`
31
+ * install keeps winning over a stray `C:\hq` (avoids a split root).
32
+ */
33
+ export function hqDiscoveryCandidates(opts = {}) {
34
+ const platform = opts.platform ?? process.platform;
35
+ const env = opts.env ?? process.env;
36
+ const home = opts.home ?? os.homedir();
37
+ const p = pathForPlatform(platform);
38
+ const candidates = [];
39
+ candidates.push(p.join(home, "HQ"), p.join(home, "hq"), p.join(home, "Documents", "HQ"), p.join(home, "Documents", "hq"), p.join(home, "Desktop", "HQ"), p.join(home, "Desktop", "hq"));
40
+ if (platform === "win32") {
41
+ candidates.push(defaultHqRootFallback({ platform, env, home }));
42
+ const localAppData = env.LOCALAPPDATA?.trim();
43
+ if (localAppData)
44
+ candidates.push(p.join(localAppData, "hq"));
45
+ }
46
+ return [...new Set(candidates)];
47
+ }
6
48
  /**
7
49
  * Resolve the HQ tree root using the same precedence as the Cognito helpers:
8
50
  * explicit HQ_ROOT, nearest cwd root, installer records, known locations, then
9
- * the historical ~/hq fallback (or an error when requested).
51
+ * the platform fallback (`~/hq`, or `C:\hq` on Windows), or an error when
52
+ * requested.
10
53
  */
11
54
  export function resolveDefaultHqRoot(opts = {}) {
12
55
  const env = opts.env ?? process.env;
13
56
  const home = opts.home ?? os.homedir();
57
+ const platform = opts.platform ?? process.platform;
58
+ const p = pathForPlatform(platform);
14
59
  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))
60
+ return p.resolve(env.HQ_ROOT);
61
+ let cur = p.resolve(opts.cwd ?? process.cwd());
62
+ while (cur !== p.dirname(cur)) {
63
+ if (isHqRoot(cur, p))
19
64
  return cur;
20
- cur = path.dirname(cur);
65
+ cur = p.dirname(cur);
21
66
  }
22
- const fromConfig = readHqRootFromHqConfig(path.join(home, ".hq"));
67
+ const fromConfig = readHqRootFromHqConfig(p.join(home, ".hq"), p);
23
68
  if (fromConfig)
24
- return path.resolve(fromConfig);
25
- const discovered = discoverHqRootViaCoreYaml(home);
69
+ return p.resolve(fromConfig);
70
+ const discovered = hqDiscoveryCandidates({ platform, env, home }).find((dir) => isValidHqRootSignature(dir, p));
26
71
  if (discovered)
27
72
  return discovered;
28
73
  if (opts.onMissing === "throw") {
@@ -31,13 +76,13 @@ export function resolveDefaultHqRoot(opts = {}) {
31
76
  "or install HQ via the installer/menubar so ~/.hq/menubar.json records " +
32
77
  "its location.");
33
78
  }
34
- return path.join(home, "hq");
79
+ return defaultHqRootFallback({ platform, env, home });
35
80
  }
36
- function readHqRootFromHqConfig(hqConfigDir) {
37
- const fromMenubar = readJsonStringField(path.join(hqConfigDir, "menubar.json"), "hqPath");
81
+ function readHqRootFromHqConfig(hqConfigDir, p = path) {
82
+ const fromMenubar = readJsonStringField(p.join(hqConfigDir, "menubar.json"), "hqPath");
38
83
  if (fromMenubar)
39
84
  return fromMenubar;
40
- return readJsonStringField(path.join(hqConfigDir, "config.json"), "hqFolderPath");
85
+ return readJsonStringField(p.join(hqConfigDir, "config.json"), "hqFolderPath");
41
86
  }
42
87
  function readJsonStringField(file, field) {
43
88
  try {
@@ -49,22 +94,9 @@ function readJsonStringField(file, field) {
49
94
  return undefined;
50
95
  }
51
96
  }
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");
97
+ export function isValidHqRootSignature(dir, p = path) {
98
+ const canonical = p.join(dir, "core", "core.yaml");
99
+ const legacy = p.join(dir, "core.yaml");
68
100
  let file;
69
101
  if (fileExists(canonical))
70
102
  file = canonical;
@@ -88,13 +120,13 @@ function fileExists(file) {
88
120
  return false;
89
121
  }
90
122
  }
91
- function isHqRoot(dir) {
92
- const hasCoreYaml = fileExists(path.join(dir, "core", "core.yaml")) ||
93
- fileExists(path.join(dir, "core.yaml"));
123
+ function isHqRoot(dir, p = path) {
124
+ const hasCoreYaml = fileExists(p.join(dir, "core", "core.yaml")) ||
125
+ fileExists(p.join(dir, "core.yaml"));
94
126
  if (!hasCoreYaml)
95
127
  return false;
96
128
  try {
97
- return fs.statSync(path.join(dir, "companies")).isDirectory();
129
+ return fs.statSync(p.join(dir, "companies")).isDirectory();
98
130
  }
99
131
  catch {
100
132
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.188",
3
+ "version": "5.345.189",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -53,8 +53,8 @@
53
53
  "@aws-sdk/client-s3": "^3.1049.0",
54
54
  "@aws-sdk/dsql-signer": "^3.1146.0",
55
55
  "@indigoai-us/hq-cloud": "6.18.107",
56
- "@indigoai-us/hq-flags-client": "^0.1.2",
57
- "@modelcontextprotocol/sdk": "1.29.0",
56
+ "@indigoai-us/hq-flags-client": "^0.2.0",
57
+ "@modelcontextprotocol/sdk": "1.32.2",
58
58
  "@sentry/node": "^10.49.0",
59
59
  "@tobilu/qmd": "2.5.3",
60
60
  "ajv": "^8.20.0",
@@ -62,13 +62,13 @@
62
62
  "chalk": "^5.3.0",
63
63
  "commander": "^12.1.0",
64
64
  "js-yaml": "^4.1.0",
65
- "mqtt": "5.15.2",
65
+ "mqtt": "5.16.1",
66
66
  "open": "^10.1.0",
67
67
  "pg": "^8.23.1",
68
68
  "semver": "^7.8.5",
69
69
  "simple-git": "^3.27.0",
70
- "smol-toml": "1.6.1",
71
- "varlock": "1.21.1"
70
+ "smol-toml": "1.9.1",
71
+ "varlock": "1.23.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@eslint/js": "^10.0.1",
@@ -77,8 +77,8 @@
77
77
  "@types/node": "^22.0.0",
78
78
  "@types/pg": "^8.23.1",
79
79
  "@types/semver": "^7.8.0",
80
- "@vitest/coverage-v8": "4.1.6",
81
- "aedes": "1.1.2",
80
+ "@vitest/coverage-v8": "4.1.12",
81
+ "aedes": "1.2.0",
82
82
  "aws-sdk-client-mock": "^4.1.0",
83
83
  "esbuild": "0.28.2",
84
84
  "eslint": "^10.5.0",
@@ -114,11 +114,11 @@
114
114
  "*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
115
115
  },
116
116
  "optionalDependencies": {
117
- "@indigoai-us/hq-hook-win32-x64": "5.345.188",
118
- "@indigoai-us/hq-hook-win32-arm64": "5.345.188",
119
- "@indigoai-us/hq-hook-darwin-x64": "5.345.188",
120
- "@indigoai-us/hq-hook-darwin-arm64": "5.345.188",
121
- "@indigoai-us/hq-hook-linux-x64": "5.345.188",
122
- "@indigoai-us/hq-hook-linux-arm64": "5.345.188"
117
+ "@indigoai-us/hq-hook-win32-x64": "5.345.189",
118
+ "@indigoai-us/hq-hook-win32-arm64": "5.345.189",
119
+ "@indigoai-us/hq-hook-darwin-x64": "5.345.189",
120
+ "@indigoai-us/hq-hook-darwin-arm64": "5.345.189",
121
+ "@indigoai-us/hq-hook-linux-x64": "5.345.189",
122
+ "@indigoai-us/hq-hook-linux-arm64": "5.345.189"
123
123
  }
124
124
  }