@indigoai-us/hq-cli 5.345.29 → 5.345.31

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.31] — 2026-10-06
6
+ - HQ Anywhere commands now require both the operator runtime switch and the signed-in person's `hq-anywhere` setting. `hq doctor` reports the setting state and access stays off when it cannot be read.
7
+
8
+ ## [5.345.30] — 2026-10-06
9
+ - Sanitize malformed journal scope identifiers before composing per-scope check IDs.
10
+
11
+ - Sync the bundled outpost reconciler with staging's Bash 3.2-safe PATH handling.
12
+
5
13
  ## [5.345.29] — 2026-10-06
6
14
  - Route `hq core checkpoint` through an isolated fast entry and defer identity and ontology imports to the paths that use them.
7
15
 
@@ -73,7 +73,7 @@ if [ "$DRY_RUN" -eq 1 ]; then
73
73
  done
74
74
  SECRET_NAMES="(none — no .env.schema found)"
75
75
  if [ -n "$SCHEMA" ]; then
76
- SECRET_NAMES="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$SCHEMA" | cut -d= -f1 | sort -u | tr '\n' ' ')"
76
+ SECRET_NAMES="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*(/[A-Za-z_][A-Za-z0-9_]*)*=' "$SCHEMA" | cut -d= -f1 | sort -u | tr '\n' ' ')"
77
77
  [ -n "$SECRET_NAMES" ] || SECRET_NAMES="(none declared)"
78
78
  fi
79
79
 
@@ -4,7 +4,7 @@ export { HQ_ANYWHERE_RUNTIME_FLAG };
4
4
  export class AnywhereRuntimeDisabledError extends Error {
5
5
  expected = true;
6
6
  constructor() {
7
- super("HQ Anywhere is not enabled. Enable the hq-anywhere-runtime flag to use this command.");
7
+ super("HQ Anywhere is not enabled. The hq-anywhere-runtime flag and your HQ Anywhere person setting must both be on.");
8
8
  this.name = "AnywhereRuntimeDisabledError";
9
9
  }
10
10
  }
@@ -43,6 +43,8 @@ export interface DaemonCommandDeps {
43
43
  userHome: (user: string) => string;
44
44
  /** The pid is a live `hq daemon run`. */
45
45
  daemonAlive: (pid: number) => boolean;
46
+ /** Test seam for the combined hq-flags and person-setting gate. */
47
+ runtimeAllowed?: () => boolean | Promise<boolean>;
46
48
  /** Test seam for the company sync-mode commands. */
47
49
  syncModeClient?: () => Promise<SyncModeVaultClient>;
48
50
  /** Reads stdin for the one-shot daemon machine login; never reads env or argv credentials. */
@@ -33,7 +33,7 @@ import { runDaemonCall } from "../lib/daemon/command-call.js";
33
33
  import { isSyncRunnerCommandLine, readWatchRunnerOwnerStatus, syncRunnerPathFromCommandLine, watchRunnerOwnerStatusPath } from "../lib/daemon/sync-runner.js";
34
34
  import { stagedSyncRunnerVersion, syncRunnerBundleStageRoot } from "../lib/daemon/sync-runner-bundle.js";
35
35
  import { getSyncModeForCompany, setSyncMode } from "./sync-mode.js";
36
- import { requireAnywhereRuntime } from "./anywhere-runtime-flag.js";
36
+ import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
37
37
  import { hqdRequest, hqdSocketPath, HqdClientError } from "../lib/daemon/protocol.js";
38
38
  import { parseStdinMachineCredentials } from "../lib/daemon/machine-login.js";
39
39
  import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
@@ -130,6 +130,7 @@ async function loadDefaults(over) {
130
130
  windowsHeadlessLauncher: over.windowsHeadlessLauncher ?? (platform === "win32" ? mesh.resolveWindowsHeadlessLauncher(env) : undefined),
131
131
  systemUnitDir: over.systemUnitDir ?? SYSTEM_UNIT_DIR,
132
132
  syncModeClient: over.syncModeClient,
133
+ runtimeAllowed: over.runtimeAllowed,
133
134
  systemRecordPath: over.systemRecordPath ?? path.join(SYSTEM_ADOPTED_ROOT, "adopted.json"),
134
135
  };
135
136
  }
@@ -892,7 +893,8 @@ export function registerDaemonCommand(program, over = {}) {
892
893
  .option("--status", "Show whether hqd has an in-memory machine sign-in")
893
894
  .allowUnknownOption(true)
894
895
  .action(wrap(async (d, extra, opts) => {
895
- requireAnywhereRuntime();
896
+ if (!(await (d.runtimeAllowed ?? (() => anywhereRuntimeAllowed(d.env)))()))
897
+ throw new DaemonCommandError("HQ Anywhere is not enabled for this person or the hq-anywhere-runtime switch is off.");
896
898
  if (extra?.length)
897
899
  throw new DaemonCommandError("Machine credentials are accepted from stdin only; do not pass credentials as arguments.");
898
900
  const socketPath = hqdSocketPath(d.home, d.env, d.platform);
@@ -930,7 +932,8 @@ export function registerDaemonCommand(program, over = {}) {
930
932
  .option("--json <args>", "Request args as a JSON object", "{}")
931
933
  .option("--timeout <ms>", "Give up after this many milliseconds")
932
934
  .action(wrap(async (d, op, opts) => {
933
- requireAnywhereRuntime();
935
+ if (!(await (d.runtimeAllowed ?? (() => anywhereRuntimeAllowed(d.env)))()))
936
+ throw new DaemonCommandError("HQ Anywhere is not enabled for this person or the hq-anywhere-runtime switch is off.");
934
937
  if (op === "auth.login") {
935
938
  throw new Error("Use `hq daemon login --stdin`; credentials are accepted from stdin only.");
936
939
  }
@@ -73,9 +73,11 @@ export interface GlobalCommandOpts {
73
73
  /** Dependency injection for tests. */
74
74
  env?: NodeJS.ProcessEnv;
75
75
  log?: (line: string) => void;
76
+ /** Test seam for the hq-pro person preference read. */
77
+ personSettingReader?: () => Promise<boolean | null>;
76
78
  }
77
79
  export declare function resolveInstallRoot(explicit: string | undefined): string;
78
80
  /** Entry used by `hq install --global` (wired from pkg-install.ts). */
79
- export declare function runInstallGlobal(opts: GlobalCommandOpts): void;
81
+ export declare function runInstallGlobal(opts: GlobalCommandOpts): Promise<void>;
80
82
  export declare function registerUninstallCommand(program: Command): void;
81
83
  //# sourceMappingURL=install-global.d.ts.map
@@ -17,6 +17,7 @@ import { resolveHqRoot } from './doctor.js';
17
17
  import { anywhereRuntimeEnabled, AnywhereRuntimeDisabledError } from './anywhere-runtime-flag.js';
18
18
  import { resolveHqMcpEndpoint } from '../lib/daemon/mcp-endpoint.js';
19
19
  import { chatgptConnectorInstructions } from '../lib/install/chatgpt-targets.js';
20
+ import { anywhereRuntimeAllowed } from '../lib/anywhere-person-setting.js';
20
21
  import { grokConnectorInstructions } from '../lib/install/grok-targets.js';
21
22
  import { readRegistry, registryDir, registryFile, writeRegistry } from '../lib/registry/registry.js';
22
23
  import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK, SYSTEM_UNIT_DIR } from '../lib/daemon/install.js';
@@ -294,8 +295,6 @@ export function installGlobalClaude(opts) {
294
295
  const log = opts.log ?? ((l) => console.log(l));
295
296
  const hqRoot = opts.hqRoot;
296
297
  assertHqRoot(hqRoot);
297
- if (!isAnywhereEnabled(home, env))
298
- throw new AnywhereRuntimeDisabledError();
299
298
  const t = claudeTargets(home);
300
299
  const prior = readState(t);
301
300
  const pluginSource = opts.via === 'plugin' ? resolveClaudePluginSource(opts.marketplaceDir, env, opts.marketplaceRepository) : undefined;
@@ -680,8 +679,6 @@ export function installGlobalCodex(opts) {
680
679
  expected: true,
681
680
  });
682
681
  }
683
- if (!isAnywhereEnabled(home, env))
684
- throw new AnywhereRuntimeDisabledError();
685
682
  const t = codexTargets(home);
686
683
  const hqPointer = claudeTargets(home).hqPointer;
687
684
  const prior = readCodexState(t);
@@ -957,10 +954,15 @@ export function resolveInstallRoot(explicit) {
957
954
  return root;
958
955
  }
959
956
  /** Entry used by `hq install --global` (wired from pkg-install.ts). */
960
- export function runInstallGlobal(opts) {
957
+ export async function runInstallGlobal(opts) {
961
958
  const runtime = assertRuntime(opts.runtime);
959
+ const env = opts.env ?? process.env;
962
960
  if (runtime === 'chatgpt' || runtime === 'grok') {
963
- if (!anywhereRuntimeEnabled(opts.env ?? process.env))
961
+ const enabled = await anywhereRuntimeAllowed(env, {
962
+ ...(opts.personSettingReader ? { readSetting: opts.personSettingReader } : {}),
963
+ onError: (name) => (opts.log ?? console.log)(`HQ Anywhere person setting unavailable (${name}); access remains off.`),
964
+ });
965
+ if (!enabled)
964
966
  throw new AnywhereRuntimeDisabledError();
965
967
  const endpoint = resolveHqMcpEndpoint(opts.env ?? process.env).toString();
966
968
  const lines = runtime === 'chatgpt' ? chatgptConnectorInstructions(endpoint) : grokConnectorInstructions(endpoint);
@@ -969,7 +971,15 @@ export function runInstallGlobal(opts) {
969
971
  return;
970
972
  }
971
973
  const install = runtime === 'codex' ? installGlobalCodex : installGlobalClaude;
972
- install({ hqRoot: resolveInstallRoot(opts.hqRoot), dryRun: opts.dryRun, via: opts.via, marketplaceDir: opts.marketplaceDir, marketplaceRepository: opts.marketplaceRepository });
974
+ install({
975
+ hqRoot: resolveInstallRoot(opts.hqRoot),
976
+ dryRun: opts.dryRun,
977
+ via: opts.via,
978
+ marketplaceDir: opts.marketplaceDir,
979
+ marketplaceRepository: opts.marketplaceRepository,
980
+ env,
981
+ log: opts.log,
982
+ });
973
983
  }
974
984
  export function registerUninstallCommand(program) {
975
985
  program
@@ -15,6 +15,10 @@ export interface LinkDeps {
15
15
  listMemberships?: () => Promise<MembershipCompany[]>;
16
16
  now?: () => Date;
17
17
  }
18
+ export interface LinkCommandDeps {
19
+ /** Test seam for the combined hq-flags and person-setting gate. */
20
+ runtimeAllowed?: () => boolean | Promise<boolean>;
21
+ }
18
22
  /** Link `dir` to `company` after confirming the caller is an active member. */
19
23
  export declare function linkFolder(company: string, dir: string | undefined, deps?: LinkDeps): Promise<{
20
24
  key: string;
@@ -24,7 +28,7 @@ export declare function unlinkFolder(dir: string | undefined, deps?: LinkDeps):
24
28
  export declare function resolveCompany(dir: string | undefined, deps?: LinkDeps): import("../lib/registry/registry.js").ResolveResult;
25
29
  /** Seed manifest-sourced entries; called by `hq install --global` (US-005). */
26
30
  export declare function seedRegistryFromHqRoot(hqRoot: string, deps?: LinkDeps): import("../lib/registry/registry.js").SeedResult;
27
- export declare function registerLinkCommand(program: Command): void;
28
- export declare function registerUnlinkCommand(program: Command): void;
29
- export declare function registerResolveCompanyCommand(program: Command): void;
31
+ export declare function registerLinkCommand(program: Command, deps?: LinkCommandDeps): void;
32
+ export declare function registerUnlinkCommand(program: Command, deps?: LinkCommandDeps): void;
33
+ export declare function registerResolveCompanyCommand(program: Command, deps?: LinkCommandDeps): void;
30
34
  //# sourceMappingURL=link.d.ts.map
@@ -9,9 +9,14 @@ import { Option } from "commander";
9
9
  import chalk from "chalk";
10
10
  import { ensureCognitoToken } from "../utils/cognito-session.js";
11
11
  import { listActiveMembershipCompanies } from "../lib/mesh/api.js";
12
- import { requireAnywhereRuntime } from "./anywhere-runtime-flag.js";
12
+ import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
13
13
  import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
14
14
  import { canonicalDir, keyForDir, readRegistry, registryDir, registryFile, removeEntriesForDir, resolveCompanyForDir, seedFromManifest, setEntry, writeRegistry, } from "../lib/registry/registry.js";
15
+ async function requireAnywhereRuntime(deps) {
16
+ if (!(await (deps.runtimeAllowed ?? (() => anywhereRuntimeAllowed()))())) {
17
+ throw new Error("HQ Anywhere is disabled until both the runtime flag and your person setting are on.");
18
+ }
19
+ }
15
20
  async function defaultListMemberships() {
16
21
  const token = await ensureCognitoToken({ interactive: false });
17
22
  return listActiveMembershipCompanies(token);
@@ -90,7 +95,7 @@ function fail(err) {
90
95
  console.error(chalk.red(err instanceof Error ? err.message : String(err)));
91
96
  process.exitCode = 1;
92
97
  }
93
- export function registerLinkCommand(program) {
98
+ export function registerLinkCommand(program, deps = {}) {
94
99
  program
95
100
  .command("link [company]")
96
101
  .description("Link this folder (or its git remote) to an HQ company")
@@ -100,7 +105,7 @@ export function registerLinkCommand(program) {
100
105
  .addOption(new Option("--seed-manifest <hqRoot>", "Seed entries from an HQ companies/manifest.yaml").hideHelp())
101
106
  .action(async (company, opts) => {
102
107
  try {
103
- requireAnywhereRuntime();
108
+ await requireAnywhereRuntime(deps);
104
109
  if (opts.list) {
105
110
  const registry = readRegistry();
106
111
  if (opts.json)
@@ -132,14 +137,14 @@ export function registerLinkCommand(program) {
132
137
  }
133
138
  });
134
139
  }
135
- export function registerUnlinkCommand(program) {
140
+ export function registerUnlinkCommand(program, deps = {}) {
136
141
  program
137
142
  .command("unlink")
138
143
  .description("Remove the HQ company link for this folder")
139
144
  .option("--path <dir>", "Folder to unlink (default: current directory)")
140
- .action((opts) => {
145
+ .action(async (opts) => {
141
146
  try {
142
- requireAnywhereRuntime();
147
+ await requireAnywhereRuntime(deps);
143
148
  const removed = unlinkFolder(opts.path);
144
149
  if (removed.length === 0)
145
150
  console.log(chalk.dim("Nothing linked here."));
@@ -152,15 +157,15 @@ export function registerUnlinkCommand(program) {
152
157
  }
153
158
  });
154
159
  }
155
- export function registerResolveCompanyCommand(program) {
160
+ export function registerResolveCompanyCommand(program, deps = {}) {
156
161
  program
157
162
  .command("resolve-company")
158
163
  .description("Print the HQ company linked to this folder (never guesses)")
159
164
  .option("--path <dir>", "Folder to resolve (default: current directory)")
160
165
  .option("--json", "Print JSON output")
161
- .action((opts) => {
166
+ .action(async (opts) => {
162
167
  try {
163
- requireAnywhereRuntime();
168
+ await requireAnywhereRuntime(deps);
164
169
  }
165
170
  catch (err) {
166
171
  fail(err);
@@ -24,6 +24,8 @@ export interface McpServeOptions {
24
24
  preloadResidentModules?: (residentPath: "mcp", modules: readonly string[]) => Promise<void>;
25
25
  /** Test seam for releasing the process package lease after resident preload. */
26
26
  releasePackageUseLease?: () => void;
27
+ /** Test seam for combined hq-flags and person-setting authorization. */
28
+ runtimeAllowed?: () => boolean | Promise<boolean>;
27
29
  }
28
30
  /** HQ root for prompts: HQ_ROOT, else the ~/.hq/root pointer written by `hq install --global`. */
29
31
  export declare function readHqRoot(env: NodeJS.ProcessEnv, home: string): string | null;
@@ -16,7 +16,7 @@ import * as os from "node:os";
16
16
  import * as path from "node:path";
17
17
  import { CLI_VERSION } from "../cli-version.js";
18
18
  import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
19
- import { anywhereRuntimeEnabledFromSnapshot } from "../lib/anywhere-runtime-fast-flag.js";
19
+ import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
20
20
  import { createLineSplitter, HqdClientError, hqdRequest, hqdSocketPath } from "../lib/daemon/protocol.js";
21
21
  import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
22
22
  import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, RESIDENT_PACKAGE_MODULES } from "../utils/resident-package-preload.js";
@@ -54,8 +54,10 @@ export async function runMcpServe(opts = {}) {
54
54
  const cwd = opts.cwd ?? process.cwd();
55
55
  const socketPath = hqdSocketPath(home, env);
56
56
  const timeoutMs = opts.timeoutMs ?? 120_000;
57
- if (!anywhereRuntimeEnabledFromSnapshot(env)) {
58
- process.stderr.write(`HQ Anywhere is not enabled. Enable the ${HQ_ANYWHERE_RUNTIME_FLAG} flag to use hq mcp serve.\n`);
57
+ if (!(await (opts.runtimeAllowed ?? (() => anywhereRuntimeAllowed(env, {
58
+ onError: (name) => process.stderr.write(`HQ Anywhere person setting unavailable (${name}); access remains off.\n`),
59
+ })))())) {
60
+ process.stderr.write(`HQ Anywhere is not enabled. The ${HQ_ANYWHERE_RUNTIME_FLAG} flag and your HQ Anywhere person setting must both be on.\n`);
59
61
  process.exitCode = 1;
60
62
  return;
61
63
  }
@@ -20,6 +20,10 @@ import { Command } from 'commander';
20
20
  import { type LatestResult } from './pack-install.js';
21
21
  import { type InstalledPack, type LinkStatus } from '../utils/pack-contributions.js';
22
22
  import type { PackContributeKey } from '../types.js';
23
+ interface CommonOpts {
24
+ json?: boolean;
25
+ hqRoot?: string;
26
+ }
23
27
  interface InstalledPackView {
24
28
  name: string;
25
29
  version?: string;
@@ -49,6 +53,28 @@ interface InstalledPackView {
49
53
  error?: string;
50
54
  }
51
55
  export declare function buildInstalledView(hqRoot: string, hqVersion: string | null, pack: InstalledPack, installedSources: Set<string>, checkUpdates: boolean, latestProbe?: LatestResult): InstalledPackView;
56
+ interface UpdateCheck {
57
+ name: string;
58
+ transport: string;
59
+ current?: string;
60
+ latest?: string;
61
+ updateAvailable: boolean | null;
62
+ applied: boolean;
63
+ reason?: string;
64
+ error?: string;
65
+ }
66
+ interface UpdateOpts extends CommonOpts {
67
+ checkOnly?: boolean;
68
+ refresh?: boolean;
69
+ yes?: boolean;
70
+ allowHooks?: boolean;
71
+ allowMcp?: boolean;
72
+ branch?: boolean;
73
+ }
74
+ export interface UpdateDependencies {
75
+ runtimeAllowed?: () => Promise<boolean>;
76
+ }
77
+ export declare function runUpdate(name: string | undefined, opts: UpdateOpts, deps?: UpdateDependencies): Promise<UpdateCheck[]>;
52
78
  export declare function registerPacksListCommand(parent: Command): void;
53
79
  export declare function registerPacksCommand(parent: Command): void;
54
80
  export {};
@@ -26,6 +26,7 @@ import chalk from 'chalk';
26
26
  import semverSatisfies from 'semver/functions/satisfies.js';
27
27
  import { findHqRoot } from '../utils/manifest.js';
28
28
  import { resolveHqRoot as resolveDetectedHqRoot } from './doctor.js';
29
+ import { anywhereRuntimeAllowed } from '../lib/anywhere-person-setting.js';
29
30
  import { classify, PINNED_HQ_ANYWHERE_SOURCE, resolveLatest, resolveLatestMarketplace, runScanPackages, installPack, } from './pack-install.js';
30
31
  import { contributionLinks, linkStatus, listInstalledPacks, findDependentPacks, readPackManifest, unwirePack, unwirePackMcp, readHqVersion, readRecommendedPackages, packagesDir, } from '../utils/pack-contributions.js';
31
32
  import { syncCodexPackHooks, syncCodexPackSkills } from '../lib/install/codex-pack-contributions.js';
@@ -207,7 +208,7 @@ function printListHuman(view) {
207
208
  console.log(chalk.dim('\n Install with: hq install <source>\n'));
208
209
  }
209
210
  }
210
- async function runUpdate(name, opts) {
211
+ export async function runUpdate(name, opts, deps = {}) {
211
212
  const hqRoot = resolveRoot(opts);
212
213
  let packs = listInstalledPacks(hqRoot).filter((p) => p.manifest);
213
214
  if (name) {
@@ -225,6 +226,16 @@ async function runUpdate(name, opts) {
225
226
  results.push({ name: pname, transport: 'unknown', updateAvailable: null, applied: false, error: 'no stamped source -- cannot update' });
226
227
  continue;
227
228
  }
229
+ if (pname === 'hq-anywhere' && !(await (deps.runtimeAllowed ?? (() => anywhereRuntimeAllowed(process.env)))())) {
230
+ results.push({
231
+ name: pname,
232
+ transport: safeClassify(source) ?? 'unknown',
233
+ updateAvailable: null,
234
+ applied: false,
235
+ error: 'Updating hq-anywhere requires both the hq-anywhere-runtime flag and your person setting.',
236
+ });
237
+ continue;
238
+ }
228
239
  // US-006: marketplace sources need the async listings probe (network) —
229
240
  // the sync `resolveLatest` cannot make that call and returns null. Every
230
241
  // other transport keeps the existing synchronous probe unchanged.
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { peekHqApiKey, requireApiKeyCapability, } from '../utils/resolve-vault-credential.js';
3
3
  import { MARKETPLACE_PREFIX, PINNED_HQ_ANYWHERE_SOURCE, installPack, sourceMatchesPackPattern, } from './pack-install.js';
4
- import { anywhereRuntimeEnabled } from './anywhere-runtime-flag.js';
4
+ import { anywhereRuntimeAllowed } from '../lib/anywhere-person-setting.js';
5
5
  /**
6
6
  * True when `source` installs through the marketplace — an explicit
7
7
  * `marketplace:` source, or a bare slug (which the dispatch below rewrites into
@@ -68,15 +68,16 @@ export function registerPackageInstallCommand(parent) {
68
68
  process.exitCode = 1;
69
69
  return;
70
70
  }
71
- let marketplaceDir = opts.marketplaceDir;
71
+ const marketplaceDir = opts.marketplaceDir;
72
72
  if (opts.via === 'plugin' &&
73
73
  !marketplaceDir?.trim() &&
74
- !process.env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim() &&
75
- anywhereRuntimeEnabled(process.env)) {
76
- runInstallGlobal({ ...opts, marketplaceRepository: 'indigoai-us/hq-packages' });
77
- return;
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;
78
+ }
78
79
  }
79
- runInstallGlobal({ ...opts, marketplaceDir });
80
+ await runInstallGlobal({ ...opts, marketplaceDir });
80
81
  return;
81
82
  }
82
83
  if (!source) {
@@ -95,8 +96,8 @@ export function registerPackageInstallCommand(parent) {
95
96
  requireApiKeyCapability('packs:install', 'hq install');
96
97
  assertApiKeyCanAuthorizeInstall(source);
97
98
  if (source === '@indigoai-us/hq-anywhere') {
98
- if (!anywhereRuntimeEnabled()) {
99
- console.error(chalk.red('Install failed:'), 'HQ Anywhere installation is disabled by the hq-anywhere-runtime flag.');
99
+ if (!(await anywhereRuntimeAllowed(process.env))) {
100
+ console.error(chalk.red('Install failed:'), 'HQ Anywhere installation requires both the hq-anywhere-runtime flag and your person setting.');
100
101
  process.exit(1);
101
102
  }
102
103
  await installPack(PINNED_HQ_ANYWHERE_SOURCE, {
@@ -15,6 +15,7 @@ export interface RestartAndVerifyDeps extends ConvergenceDeps {
15
15
  env?: NodeJS.ProcessEnv;
16
16
  installedVersion?: () => string | null;
17
17
  log?: (line: string) => void;
18
+ runtimeAllowed?: () => boolean | Promise<boolean>;
18
19
  }
19
20
  /**
20
21
  * Restart the daemon after an update and verify it converged on the installed
@@ -10,14 +10,14 @@ import { daemonPaths } from "../lib/daemon/paths.js";
10
10
  import { writeControlRequest } from "../lib/daemon/requests.js";
11
11
  import { hqdSocketPath } from "../lib/daemon/protocol.js";
12
12
  import { describeNonConvergence, installedCliVersion, pingHqdVersion, verifyDaemonConvergence, } from "../lib/daemon/convergence.js";
13
- import { anywhereRuntimeEnabled } from "./anywhere-runtime-flag.js";
13
+ import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
14
14
  /**
15
15
  * Restart the daemon after an update and verify it converged on the installed
16
16
  * hq-cli (policy hq-self-updater-must-verify-convergence-after-install).
17
17
  * Returns false when it did not; the caller must report that as a failure.
18
18
  */
19
19
  export async function restartAndVerifyDaemon(hqRoot, deps = {}) {
20
- if (!anywhereRuntimeEnabled())
20
+ if (!(await (deps.runtimeAllowed ?? (() => anywhereRuntimeAllowed(deps.env ?? process.env)))()))
21
21
  return true;
22
22
  const log = deps.log ?? ((line) => console.log(line));
23
23
  const env = deps.env ?? process.env;
@@ -0,0 +1,17 @@
1
+ export interface ReadHqAnywherePersonSettingOptions {
2
+ readSetting?: () => Promise<boolean | null>;
3
+ getPersonToken?: () => Promise<string>;
4
+ fetchImpl?: typeof fetch;
5
+ apiBaseUrl?: string;
6
+ env?: NodeJS.ProcessEnv;
7
+ onError?: (errorName: string) => void;
8
+ }
9
+ /** Returns null when the person preference cannot be read; callers must fail closed. */
10
+ export declare function readHqAnywherePersonSetting(options?: ReadHqAnywherePersonSettingOptions): Promise<boolean | null>;
11
+ export declare function evaluateAnywhereRuntime(flagEnabled: boolean, setting: boolean | null): {
12
+ enabled: boolean;
13
+ setting: boolean | null;
14
+ };
15
+ /** The operator flag always wins; the person preference is fetched only while it is on. */
16
+ export declare function anywhereRuntimeAllowed(env?: NodeJS.ProcessEnv, options?: ReadHqAnywherePersonSettingOptions): Promise<boolean>;
17
+ //# sourceMappingURL=anywhere-person-setting.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { anywhereRuntimeEnabledFromSnapshot } from "./anywhere-runtime-fast-flag.js";
2
+ /** Returns null when the person preference cannot be read; callers must fail closed. */
3
+ export async function readHqAnywherePersonSetting(options = {}) {
4
+ const env = options.env ?? process.env;
5
+ try {
6
+ const getPersonToken = options.getPersonToken ?? (async () => {
7
+ const { ensureCognitoToken } = await import("../utils/cognito-session.js");
8
+ return ensureCognitoToken({ interactive: false, tokenSource: "person" });
9
+ });
10
+ const token = await getPersonToken();
11
+ const apiBaseUrl = options.apiBaseUrl ?? env.HQ_PRO_API_URL ?? env.HQ_API_URL ?? env.HQ_VAULT_API_URL ?? "https://hqapi.hq.computer";
12
+ const response = await (options.fetchImpl ?? fetch)(`${apiBaseUrl.replace(/\/$/, "")}/v1/bot/auto-schedule?setting=hq-anywhere`, { method: "GET", headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5_000) });
13
+ if (!response.ok)
14
+ throw new Error(`Http${response.status}`);
15
+ const body = await response.json();
16
+ if (typeof body !== "object" || body === null || Array.isArray(body))
17
+ throw new Error("InvalidEnvelope");
18
+ const envelope = body;
19
+ if (envelope.key !== "hq-anywhere" || typeof envelope.value !== "boolean")
20
+ throw new Error("InvalidEnvelope");
21
+ return envelope.value;
22
+ }
23
+ catch (error) {
24
+ const errorName = error instanceof Error && /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(error.name)
25
+ ? error.name
26
+ : "UnknownError";
27
+ (options.onError ?? ((name) => console.error(`HQ Anywhere person setting unavailable (${name}); access remains off.`)))(errorName);
28
+ return null;
29
+ }
30
+ }
31
+ export function evaluateAnywhereRuntime(flagEnabled, setting) {
32
+ return { enabled: flagEnabled && setting === true, setting };
33
+ }
34
+ /** The operator flag always wins; the person preference is fetched only while it is on. */
35
+ export async function anywhereRuntimeAllowed(env = process.env, options = {}) {
36
+ if (!anywhereRuntimeEnabledFromSnapshot(env))
37
+ return false;
38
+ const setting = options.readSetting ? await options.readSetting() : await readHqAnywherePersonSetting({ ...options, env });
39
+ return evaluateAnywhereRuntime(true, setting).enabled;
40
+ }
41
+ //# sourceMappingURL=anywhere-person-setting.js.map
@@ -9,7 +9,7 @@ export interface McpOAuthOptions {
9
9
  fetchImpl?: typeof fetch;
10
10
  openBrowser?: (url: string) => Promise<unknown>;
11
11
  writeOutput?: (message: string) => void;
12
- isEnabled?: () => boolean;
12
+ isEnabled?: () => boolean | Promise<boolean>;
13
13
  isMachine?: () => boolean;
14
14
  now?: () => number;
15
15
  callbackTimeoutMs?: number;
@@ -6,7 +6,7 @@ import * as path from "node:path";
6
6
  import open from "open";
7
7
  import { resolveCognitoTokenSource } from "../../utils/cognito-session.js";
8
8
  import { markExpectedUserError } from "../../utils/expected-cli-error.js";
9
- import { anywhereRuntimeEnabled } from "../../commands/anywhere-runtime-flag.js";
9
+ import { anywhereRuntimeAllowed } from "../anywhere-person-setting.js";
10
10
  import { resolveHqMcpOAuthOrigin } from "./mcp-endpoint.js";
11
11
  const MCP_SCOPE = "openid email profile hq-mcp/use";
12
12
  const EXPIRY_SKEW_MS = 60_000;
@@ -128,8 +128,8 @@ function newPkce() {
128
128
  }
129
129
  async function runLogin(options) {
130
130
  const env = options.env ?? process.env;
131
- if (!(options.isEnabled ?? (() => anywhereRuntimeEnabled(env)))()) {
132
- throw markExpectedUserError(new McpOAuthError("HQ Anywhere is not enabled. Enable the hq-anywhere-runtime flag to use `hq mcp login`."));
131
+ if (!(await (options.isEnabled ?? (() => anywhereRuntimeAllowed(env)))())) {
132
+ throw markExpectedUserError(new McpOAuthError("HQ Anywhere is not enabled. The hq-anywhere-runtime flag and your HQ Anywhere person setting must both be on to use `hq mcp login`."));
133
133
  }
134
134
  if ((options.isMachine ?? (() => resolveCognitoTokenSource(env) === "machine"))()) {
135
135
  throw new McpOAuthError("`hq mcp login` is for person accounts; this box uses machine credentials.");
@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  import { loadCachedTokens } from "@indigoai-us/hq-cloud";
15
15
  import { CLI_VERSION } from "../../cli-version.js";
16
16
  import { anywhereRuntimeEnabled } from "../../commands/anywhere-runtime-flag.js";
17
+ import { anywhereRuntimeAllowed } from "../anywhere-person-setting.js";
17
18
  import { currentPackageUseLeaseInstall, currentPackageUseUpdateRequested } from "../../utils/package-use-lease.js";
18
19
  import { isLocalDependencyInstall, resolveRunningInstall } from "../../utils/version-gate.js";
19
20
  import { agentKitPaths } from "../agent-kit/paths.js";
@@ -836,7 +837,9 @@ export async function runDaemon(opts) {
836
837
  // hqd: the session/company socket every runtime talks to (hq-anywhere US-008).
837
838
  let hqd = null;
838
839
  let hqdShutdown = null;
839
- if (shouldStartHqd(anywhereRuntimeEnabled())) {
840
+ if (shouldStartHqd(await anywhereRuntimeAllowed(env, {
841
+ onError: (name) => log("warn", `hqd person setting read failed (${name}); runtime remains off`),
842
+ }))) {
840
843
  try {
841
844
  const startConfig = readDaemonConfig(paths.configPath);
842
845
  hqd = await startHqdServer({
@@ -43,6 +43,8 @@ export interface HqdServerOptions {
43
43
  mcpEnabled?: () => boolean | Promise<boolean>;
44
44
  /** Test seam for the live hq-flags read used by the daemon's bounded runtime monitor. */
45
45
  runtimeFlagReader?: (options: ReadDaemonFlagOptions) => Promise<boolean>;
46
+ /** Test seam for the human opt-in setting; production reads hq-pro. */
47
+ personSettingReader?: () => Promise<boolean | null>;
46
48
  /** Test seam for the hosted Streamable HTTP MCP transport. */
47
49
  mcpBridge?: HqMcpBridge;
48
50
  /** Test seam for machine login while keeping production credentials off disk. */
@@ -23,6 +23,7 @@ import { defaultMcpToken, HqMcpBridge, jsonResult, McpOpError, runWorker } from
23
23
  import { authenticateMachineCredentials, machineTokenExpiresAt, MachineLoginError, parseStdinMachineCredentials } from "./machine-login.js";
24
24
  import { HQ_MCP_TOOL_NAMES, HQD_NATIVE_TOOL_NAMES } from "../mcp/tools.js";
25
25
  import { HQ_ANYWHERE_RUNTIME_FLAG } from "../../commands/anywhere-runtime-flag.js";
26
+ import { readHqAnywherePersonSetting } from "../anywhere-person-setting.js";
26
27
  import { createBoundedDaemonFlagMonitor } from "./flag-monitor.js";
27
28
  import { readDaemonFlag } from "./flag.js";
28
29
  import { startWindowsPipeHost, validateWindowsPipeAcl, WINDOWS_PIPE_STARTUP_REFUSAL } from "./windows-pipe-host.js";
@@ -396,16 +397,23 @@ export async function startHqdServer(opts) {
396
397
  }
397
398
  }
398
399
  const runtimeFlagMonitor = opts.mcpEnabled ? undefined : createBoundedDaemonFlagMonitor({
399
- read: () => {
400
+ read: async () => {
400
401
  const options = {
401
402
  env,
402
403
  throwOnError: true,
403
404
  // A local opt-out is honored, while a local opt-in cannot override the operator kill switch.
404
405
  localOverrideMode: "off-only",
405
406
  };
406
- return opts.runtimeFlagReader
407
+ const flagEnabled = await (opts.runtimeFlagReader
407
408
  ? opts.runtimeFlagReader(options)
408
- : readDaemonFlag(HQ_ANYWHERE_RUNTIME_FLAG, options);
409
+ : readDaemonFlag(HQ_ANYWHERE_RUNTIME_FLAG, options));
410
+ if (!flagEnabled)
411
+ return false;
412
+ const personSetting = await (opts.personSettingReader ?? (() => readHqAnywherePersonSetting({
413
+ env,
414
+ onError: (name) => log("warn", `hqd person setting refresh failed (${name}); keeping HQ Anywhere off`),
415
+ })))();
416
+ return personSetting === true;
409
417
  },
410
418
  onChange: (enabled) => {
411
419
  if (!enabled) {
@@ -2,6 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { runDoctor as runDoctorCommand, } from "../../../commands/doctor.js";
5
+ import { createAnywhereFamily } from "../checks/anywhere.js";
6
+ import { createDefaultRegistry } from "../registry.js";
5
7
  /**
6
8
  * Run doctor tests against synthetic state. The production registry normally
7
9
  * reads local HQ work-context, sync, daemon, and session state; tests must not
@@ -37,7 +39,14 @@ export async function runDoctorForTest(options = {}) {
37
39
  else
38
40
  process.env[key] = value;
39
41
  }
40
- return await runDoctorCommand({ ...options, integrations: false });
42
+ let registry = options.registry;
43
+ if (!registry) {
44
+ registry = createDefaultRegistry();
45
+ // Command tests exercise doctor rendering and exit behavior offline. The
46
+ // real person preference is covered in the anywhere-family tests.
47
+ registry.register(createAnywhereFamily({ personSettingReader: async () => false }));
48
+ }
49
+ return await runDoctorCommand({ ...options, registry, integrations: false });
41
50
  }
42
51
  finally {
43
52
  for (const [key, value] of originalEnv) {
@@ -31,6 +31,8 @@ export interface AnywhereDeps {
31
31
  health?: (cwd: string) => Promise<HqdHealthResult>;
32
32
  /** Test seam for the local runtime gate used to keep other checks out of a disabled runtime. */
33
33
  runtimeEnabled?: () => boolean;
34
+ /** Test seam for the per-person HQ Anywhere setting; production reads hq-pro. */
35
+ personSettingReader?: () => Promise<boolean | null>;
34
36
  /** The hq-cli version installed on this machine. Default: this CLI's version. */
35
37
  cliVersion?: string;
36
38
  serviceState?: () => DaemonServiceState;
@@ -24,6 +24,7 @@ import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK } from "
24
24
  import { hqdSocketPath } from "../../daemon/protocol.js";
25
25
  import { readRegistry, registryDir, registryFile } from "../../registry/registry.js";
26
26
  import { anywhereRuntimeEnabled } from "../../../commands/anywhere-runtime-flag.js";
27
+ import { readHqAnywherePersonSetting } from "../../anywhere-person-setting.js";
27
28
  const ANYWHERE_FAMILY_ID = "anywhere";
28
29
  export const REMEDIATION = {
29
30
  installClaude: "hq install --global --runtime claude",
@@ -44,7 +45,15 @@ async function runAnywhereChecks(context, deps = {}) {
44
45
  const env = deps.env ?? process.env;
45
46
  const socketPath = hqdSocketPath(home, env);
46
47
  const cwd = context.cwd ?? process.cwd();
47
- const runtimeEnabled = (deps.runtimeEnabled ?? anywhereRuntimeEnabled)();
48
+ const flagEnabled = (deps.runtimeEnabled ?? anywhereRuntimeEnabled)();
49
+ const personSetting = await (deps.personSettingReader ?? (deps.runtimeEnabled ? async () => true : () => readHqAnywherePersonSetting({
50
+ env,
51
+ onError: (name) => process.stderr.write(`HQ Anywhere person setting unavailable (${name}); access remains off.\n`),
52
+ })))();
53
+ const runtimeEnabled = flagEnabled && personSetting === true;
54
+ const settingCheck = personSetting === null
55
+ ? result("UNKNOWN", "person-setting", "HQ Anywhere person setting could not be read; runtime access is off")
56
+ : result(personSetting ? "PASS" : "WARN", "person-setting", `HQ Anywhere person setting is ${personSetting ? "on" : "off"}`);
48
57
  let health;
49
58
  let healthUnsupported = false;
50
59
  try {
@@ -107,12 +116,12 @@ async function runAnywhereChecks(context, deps = {}) {
107
116
  : undefined;
108
117
  const healthCheck = runtimeHealthCheck(health, healthUnsupported, serviceState);
109
118
  if (!runtimeEnabled)
110
- return [healthCheck];
119
+ return [healthCheck, settingCheck];
111
120
  const remoteModes = [
112
121
  result("WARN", "chatgpt-mode", `ChatGPT integration mode: ${CHATGPT_MODE}; host connector setup is managed in ChatGPT`, "hq install --global --runtime chatgpt"),
113
122
  result("WARN", "grok-mode", `Grok integration mode: ${GROK_MODE}; host connector setup is managed in Grok`, "hq install --global --runtime grok"),
114
123
  ];
115
- const out = [healthCheck, ...remoteModes];
124
+ const out = [healthCheck, settingCheck, ...remoteModes];
116
125
  if (!installed) {
117
126
  return [...out, result("NA", "installed", "HQ local anywhere runtime is not installed on this machine", REMEDIATION.installClaude)];
118
127
  }
@@ -170,6 +170,14 @@ const NON_COMPANY_JOURNAL_SLUGS = new Set([
170
170
  PERSONAL_SCOPE_SLUG,
171
171
  PERSONAL_VAULT_SCOPE_SLUG,
172
172
  ]);
173
+ /** Keep malformed scopes distinct without letting them imitate valid slugs. */
174
+ function journalSlugForCheckId(slug) {
175
+ if (NON_COMPANY_JOURNAL_SLUGS.has(slug) || isSafeCompanySlug(slug)) {
176
+ return slug;
177
+ }
178
+ const encoded = Array.from(slug, (character) => character.codePointAt(0).toString(16)).join("-");
179
+ return `__hq_invalid_journal_scope__${encoded}`;
180
+ }
173
181
  /** True when this journal slug names a company rather than the personal tree. */
174
182
  export function isCompanyJournalSlug(slug) {
175
183
  return !NON_COMPANY_JOURNAL_SLUGS.has(slug);
@@ -360,7 +368,7 @@ function journalResults(deps, journals) {
360
368
  const nowMs = deps.now().getTime();
361
369
  return journals.map((entry) => {
362
370
  const lastSync = entry.journal?.lastSync;
363
- const checkId = `sync.journal.${entry.slug}`;
371
+ const checkId = `sync.journal.${journalSlugForCheckId(entry.slug)}`;
364
372
  // A company journal without a local tree is leftover membership state,
365
373
  // not a live sync target. WARN would make `hq doctor --fix` look like it
366
374
  // failed to repair something it was never going to touch (US-015 health
@@ -40,7 +40,7 @@ export const RESIDENT_PACKAGE_MODULES = {
40
40
  "@indigoai-us/hq-cloud",
41
41
  ],
42
42
  "bot-logs-follow": [],
43
- mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js"],
43
+ mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js"],
44
44
  childWrapper: ["../main.js"],
45
45
  coreJobRun: ["../lib/flag-registry.js"],
46
46
  "daemon-call": ["../lib/daemon/protocol.js"],
@@ -170,6 +170,12 @@ const LOADERS = {
170
170
  mcp: [
171
171
  () => import("../lib/mcp/tools.js"),
172
172
  () => import("../lib/mcp/prompts.js"),
173
+ () => import("../utils/cognito-session.js"),
174
+ () => import("@indigoai-us/hq-cloud"),
175
+ async () => {
176
+ const { preloadSentrySdk } = await import("../sentry.js");
177
+ preloadSentrySdk();
178
+ },
173
179
  ],
174
180
  childWrapper: [
175
181
  () => import("../main.js"),
@@ -5,7 +5,9 @@ local hqd daemon on macOS, Linux, and Windows. On Windows, hqd uses a bundled
5
5
  PowerShell pipe host that applies and verifies a user-only named-pipe DACL
6
6
  before becoming ready. A per-user
7
7
  registry links repositories to HQ companies. The `hq-anywhere-runtime` flag
8
- controls these features and is off by default.
8
+ is the operator kill switch and is off by default. Runtime access also
9
+ requires the current person to enable the `hq-anywhere` setting in HQ. If that
10
+ setting cannot be read, runtime access stays off.
9
11
 
10
12
  ## Install
11
13
 
@@ -18,10 +20,12 @@ hq daemon status
18
20
  hq install --global --runtime claude
19
21
  ```
20
22
 
21
- `hq install --global` defaults to Claude. Select other supported runtimes
22
- explicitly with `--runtime codex`, `--runtime chatgpt`, or `--runtime grok`.
23
+ `hq install --global` defaults to Claude and requires both the runtime flag
24
+ and the current person's `hq-anywhere` setting. Select other supported
25
+ runtimes explicitly with `--runtime codex`, `--runtime chatgpt`, or
26
+ `--runtime grok`.
23
27
  Claude and Codex receive local hook configuration. ChatGPT and Grok print
24
- connector setup instructions and require the runtime flag to be enabled.
28
+ connector setup instructions and require both gates to be enabled.
25
29
 
26
30
  The CLI stores the HQ root at `~/.hq/root` and keeps the folder registry in
27
31
  `~/.hq/registry.json`. These files belong to the current host. They are not
@@ -66,7 +70,9 @@ request error. The daemon process itself does not terminate.
66
70
 
67
71
  `hq doctor` reports hqd reachability, version and uptime, the last successful
68
72
  runtime-flag read and its age, and whether hqd is holding that decision after a
69
- read error. It also reports the company selected for the current directory or
73
+ read error. It reports the current person's `hq-anywhere` setting as on, off,
74
+ or unknown when the setting endpoint cannot be read; unknown keeps runtime
75
+ access off. It also reports the company selected for the current directory or
70
76
  the reason it fell back to personal context. On Unix, the socket line includes
71
77
  its permission mode, owner check and whether it is user-only. Windows reports
72
78
  the named-pipe transport without Unix mode or owner fields. Use `hq doctor
@@ -80,8 +86,8 @@ resident daemon does not support the health operation, doctor falls back to
80
86
  `ping` for reachability, version and uptime, then warns that a restart is needed
81
87
  for full diagnostics.
82
88
 
83
- The hq-anywhere-runtime flag also gates the published Claude plugin marketplace
84
- and Codex pack source. When enabled, hq-cli adds the `indigoai-us/hq-packages`
89
+ The flag and person setting also gate the published Claude plugin marketplace
90
+ and Codex pack source. When both are on, hq-cli adds the `indigoai-us/hq-packages`
85
91
  Claude marketplace and installs the `hq` plugin, or resolves the exact
86
92
  `@indigoai-us/hq-anywhere` alias to
87
93
  `github:indigoai-us/hq-packages#packages/hq-pack-hq-anywhere`. The flag-off
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.29",
3
+ "version": "5.345.31",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {