@indigoai-us/hq-cli 5.345.34 → 5.345.36

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.36] — 2026-10-07
6
+ - A queued `hq sync` no longer pins the installed `hq` version while it waits its turn, so `hq pkg update` is no longer blocked for the length of somebody else's sync.
7
+ - The flag-registry background refresh no longer throws as it starts, so running any `hq` command stops filing a crash report that nothing in the command caused.
8
+
9
+ ## [5.345.35] — 2026-10-07
10
+ - Claude plugin installs now use the HQ packages marketplace by default; local marketplace directory and environment overrides remain available.
11
+ - Pin self-updates to the pnpm version recorded by the global install; preserve store dirs on pnpm 11+ and omit pnpm 10 virtual-store flags.
12
+
5
13
  ## [5.345.34] — 2026-10-07
6
14
  - Protect live remote-control tmux sessions with socket-scoped creator identity before cleanup.
7
15
 
@@ -16,7 +16,8 @@ import { isAccessDeniedError, accessDeniedKeyOf, formatAccessDenied, } from "../
16
16
  import chalk from "chalk";
17
17
  import * as fs from "fs";
18
18
  import * as path from "path";
19
- import { share, sync, getStateDir, listJournals, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
19
+ import { share, getStateDir, listJournals, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
20
+ import { syncLeg } from "../utils/sync-operation-lock.js";
20
21
  import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, loadCachedTokens, } from "../utils/cognito-session.js";
21
22
  import { resolveUploadAuthor } from "../utils/upload-author.js";
22
23
  import { companyFolderExceedsThreshold, emitNarrowHint, isStrictRefusal, resolveBannerLevel, resolveNarrowHintPresentationLevel, resolveNarrowHintMinBytes, } from "../lib/narrow-hint-banner.js";
@@ -1146,7 +1147,7 @@ export function registerCloudCommands(program) {
1146
1147
  }
1147
1148
  const excludePrefixes = readScopeExcludePrefixes(pullScope);
1148
1149
  syncHealth = beginSyncHealthReport();
1149
- const result = await sync({
1150
+ const result = await syncLeg({
1150
1151
  company: options.company,
1151
1152
  onConflict: options.onConflict,
1152
1153
  vaultConfig,
@@ -1437,7 +1438,7 @@ async function runPullAll(hqRoot, onConflict, modeAllOverride, skipPersonal, for
1437
1438
  // via the shared resolver, so the actual pull is scoped — the CLI no
1438
1439
  // longer seeds an all-mode PullRecord that wedges the menubar runner.
1439
1440
  resolveScope: (companyUid, slug) => resolvePullScope(realClient, companyUid, slug, hqRoot),
1440
- sync: (opts) => sync({
1441
+ sync: (opts) => syncLeg({
1441
1442
  company: opts.company,
1442
1443
  hqRoot: opts.hqRoot,
1443
1444
  vaultConfig,
@@ -1508,7 +1509,7 @@ export async function runPullPersonal(hqRoot, onConflict) {
1508
1509
  listPersonEntities: () => client.entity.listByType("person"),
1509
1510
  getEntity: async () => null,
1510
1511
  });
1511
- const result = await sync({
1512
+ const result = await syncLeg({
1512
1513
  company: personalUid,
1513
1514
  ...(onConflict ? { onConflict } : {}),
1514
1515
  vaultConfig,
@@ -1853,7 +1854,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
1853
1854
  }
1854
1855
  console.log(chalk.dim(" → pull leg"));
1855
1856
  const excludePrefixes = readScopeExcludePrefixes(pullScope);
1856
- const pullResult = await sync({
1857
+ const pullResult = await syncLeg({
1857
1858
  company: targetCompany,
1858
1859
  vaultConfig,
1859
1860
  hqRoot,
@@ -19,6 +19,8 @@ export type ResolveCompanyDependencies = {
19
19
  stdin?: string;
20
20
  stdinIsTty?: boolean;
21
21
  spawnHq?: (args: string[], env: NodeJS.ProcessEnv, timeoutMs: number, cwd?: string, command?: string) => Promise<HqResult>;
22
+ onHqSpawn?: (args: readonly string[]) => void;
23
+ onHqExit?: (args: readonly string[]) => void;
22
24
  pid?: number;
23
25
  random?: () => number;
24
26
  };
@@ -1,5 +1,5 @@
1
1
  /** Native port of resolve-company.sh. This module stays on the fast import path. */
2
- import { spawn, spawnSync } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
5
  import { awkCannotOpenFileDiagnostic } from "../lib/core-utils/awk-diagnostics.js";
@@ -272,30 +272,46 @@ function commandOnPath(name, env) {
272
272
  }
273
273
  return false;
274
274
  }
275
- function runHq(args, env, timeoutMs, cwd, command = "hq") {
275
+ function runHq(args, env, timeoutMs, cwd, command = "hq", onExitObserved) {
276
276
  return new Promise((resolve) => {
277
277
  let settled = false;
278
278
  let stdout = "";
279
279
  let child;
280
280
  let timeoutKill;
281
281
  let timedOut = false;
282
- const signalGroup = (signal) => {
283
- if (!child?.pid)
282
+ const signalGroup = (signal, onComplete) => {
283
+ if (!child?.pid) {
284
+ onComplete?.();
284
285
  return;
286
+ }
285
287
  try {
286
288
  if (process.platform === "win32") {
287
289
  const taskkill = path.join(env.SystemRoot ?? env.WINDIR ?? "C:\\Windows", "System32", "taskkill.exe");
288
- const result = spawnSync(taskkill, ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true });
289
- if (result.error || result.status !== 0)
290
- child.kill(signal);
290
+ let completed = false;
291
+ const complete = (code) => {
292
+ if (completed)
293
+ return;
294
+ completed = true;
295
+ if (code !== 0)
296
+ child?.kill(signal);
297
+ onComplete?.();
298
+ };
299
+ const killer = spawn(taskkill, ["/PID", String(child.pid), "/T", "/F"], {
300
+ stdio: "ignore",
301
+ windowsHide: true,
302
+ });
303
+ killer.once("error", () => complete(1));
304
+ killer.once("exit", (code) => complete(code));
291
305
  }
292
306
  else {
293
307
  process.kill(-child.pid, signal);
308
+ onComplete?.();
294
309
  }
295
310
  }
296
311
  catch (error) {
297
312
  if (errorCode(error) !== "ESRCH")
298
313
  finish({ code: 1, stdout });
314
+ onComplete?.();
299
315
  }
300
316
  };
301
317
  const finish = (result) => {
@@ -310,13 +326,13 @@ function runHq(args, env, timeoutMs, cwd, command = "hq") {
310
326
  const timer = setTimeout(() => {
311
327
  timedOut = true;
312
328
  if (child?.pid) {
313
- signalGroup("SIGTERM");
314
- // The shell wrapper returns shortly after its 2 second deadline. Do
315
- // not wait for inherited stdio handles after killing the process group.
316
- timeoutKill = setTimeout(() => {
317
- signalGroup("SIGKILL");
318
- finish({ code: 124, stdout });
319
- }, 250);
329
+ signalGroup("SIGTERM", () => {
330
+ // The shell wrapper returns shortly after its 2 second deadline. Give
331
+ // the tree time to exit, then force-kill and wait for Windows cleanup.
332
+ timeoutKill = setTimeout(() => {
333
+ signalGroup("SIGKILL", () => finish({ code: 124, stdout }));
334
+ }, 250);
335
+ });
320
336
  }
321
337
  else {
322
338
  finish({ code: 124, stdout });
@@ -349,9 +365,24 @@ function runHq(args, env, timeoutMs, cwd, command = "hq") {
349
365
  child.stdout?.setEncoding("utf8");
350
366
  child.stdout?.on("data", (chunk) => { stdout += chunk; });
351
367
  child.once("error", () => finish({ code: 127, stdout }));
368
+ if (onExitObserved)
369
+ child.once("exit", onExitObserved);
352
370
  child.once("exit", (code, signal) => {
353
- signalGroup("SIGTERM");
354
- finish({ code: timedOut ? 124 : typeof code === "number" ? code : signal ? 128 : 1, stdout });
371
+ if (timedOut)
372
+ return;
373
+ clearTimeout(timer);
374
+ const result = { code: typeof code === "number" ? code : signal ? 128 : 1, stdout };
375
+ if (process.platform === "win32") {
376
+ // Tree cleanup is asynchronous on Windows. Start it after the direct
377
+ // child exits, but let the resolver advance without waiting for
378
+ // taskkill; detached descendants must not hold up the next lookup.
379
+ signalGroup("SIGTERM");
380
+ finish(result);
381
+ return;
382
+ }
383
+ signalGroup("SIGTERM", () => {
384
+ finish(result);
385
+ });
355
386
  });
356
387
  });
357
388
  }
@@ -502,7 +533,13 @@ export async function resolveCompany(args, dependencies) {
502
533
  hqEnv.HQ_ROOT = dependencies.hqRoot;
503
534
  hqEnv.CLAUDE_PROJECT_DIR = dependencies.hqRoot;
504
535
  }
505
- const invoke = dependencies.spawnHq ?? runHq;
536
+ const invokeImplementation = dependencies.spawnHq
537
+ ? dependencies.spawnHq
538
+ : (args, childEnv, timeoutMs, cwd, command) => runHq(args, childEnv, timeoutMs, cwd, command, () => dependencies.onHqExit?.(args));
539
+ const invoke = (args, env, timeoutMs, cwd, command) => {
540
+ dependencies.onHqSpawn?.(args);
541
+ return invokeImplementation(args, env, timeoutMs, cwd, command);
542
+ };
506
543
  const defaultResponse = await invoke(["mesh", "context", "default", "get", "--json"], hqEnv, 2_000, root);
507
544
  const defaultSlug = defaultSlugFromJson(defaultResponse.stdout, knownSlugs);
508
545
  if (!defaultSlug)
@@ -39,7 +39,7 @@ export interface ClaudePluginSource {
39
39
  pluginName: string;
40
40
  pluginId: string;
41
41
  }
42
- /** Resolve the local marketplace source; published hosting remains an operator decision. */
42
+ /** Resolve local overrides first, then the published HQ marketplace. */
43
43
  export declare function resolveClaudePluginSource(explicitDir?: string, env?: NodeJS.ProcessEnv, repository?: string): ClaudePluginSource;
44
44
  export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
45
45
  export declare const isAnywhereEnabled: (_home: string, env?: NodeJS.ProcessEnv) => boolean;
@@ -46,14 +46,15 @@ export function hookTargetFor(hqRoot, home, daemonInstalled, env = process.env)
46
46
  const daemonMatchesRoot = configuredRoot !== undefined && realpathOrSelf(configuredRoot) === realpathOrSelf(hqRoot);
47
47
  return installed && daemonMatchesRoot && fs.existsSync(shimPath(hqRoot)) ? 'shim' : 'master';
48
48
  }
49
- /** Resolve the local marketplace source; published hosting remains an operator decision. */
50
- export function resolveClaudePluginSource(explicitDir, env = process.env, repository) {
49
+ const HQ_PACKAGES_MARKETPLACE_REPOSITORY = 'indigoai-us/hq-packages';
50
+ /** Resolve local overrides first, then the published HQ marketplace. */
51
+ export function resolveClaudePluginSource(explicitDir, env = process.env, repository = HQ_PACKAGES_MARKETPLACE_REPOSITORY) {
51
52
  const configured = explicitDir?.trim() || env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim();
52
53
  if (!configured) {
53
- if (repository === 'indigoai-us/hq-packages') {
54
+ if (repository === HQ_PACKAGES_MARKETPLACE_REPOSITORY) {
54
55
  return { sourceDir: repository, marketplaceName: 'hq-packages', pluginName: 'hq', pluginId: 'hq@hq-packages' };
55
56
  }
56
- throw Object.assign(new Error('Published HQ Claude plugin source is not configured; its repository/host awaits an operator decision. Pass --marketplace-dir <dir> or set HQ_CLAUDE_PLUGIN_MARKETPLACE.'), { expected: true });
57
+ throw Object.assign(new Error(`Unsupported published HQ Claude plugin repository "${repository}". Pass --marketplace-dir <dir> or set HQ_CLAUDE_PLUGIN_MARKETPLACE for a local marketplace.`), { expected: true });
57
58
  }
58
59
  const sourceDir = path.resolve(configured);
59
60
  const manifestPath = path.join(sourceDir, '.claude-plugin', 'marketplace.json');
@@ -72,10 +72,12 @@ export function registerPackageInstallCommand(parent) {
72
72
  if (opts.via === 'plugin' &&
73
73
  !marketplaceDir?.trim() &&
74
74
  !process.env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim()) {
75
- if (await anywhereRuntimeAllowed(process.env)) {
76
- await runInstallGlobal({ ...opts, marketplaceRepository: 'indigoai-us/hq-packages' });
77
- return;
75
+ if (!(await anywhereRuntimeAllowed(process.env))) {
76
+ console.error(chalk.red('Install failed:'), 'HQ Anywhere plugin installation requires both the hq-anywhere-runtime flag and your person setting.');
77
+ process.exit(1);
78
78
  }
79
+ await runInstallGlobal({ ...opts, marketplaceRepository: 'indigoai-us/hq-packages' });
80
+ return;
79
81
  }
80
82
  await runInstallGlobal({ ...opts, marketplaceDir });
81
83
  return;