@indigoai-us/hq-cli 5.325.0 → 5.326.0

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 (45) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/bin/hq-auth-refresh.js +2 -3
  3. package/dist/command-catalog.generated.d.ts +10 -1
  4. package/dist/command-catalog.generated.js +11 -1
  5. package/dist/commands/anywhere-runtime-flag.d.ts +2 -2
  6. package/dist/commands/anywhere-runtime-flag.js +2 -2
  7. package/dist/commands/core-job-remote-launcher.js +2 -3
  8. package/dist/commands/daemon.js +1 -1
  9. package/dist/commands/install-global.d.ts +10 -0
  10. package/dist/commands/install-global.js +96 -41
  11. package/dist/commands/mcp-serve.d.ts +28 -0
  12. package/dist/commands/mcp-serve.js +165 -0
  13. package/dist/commands/mcp-status.js +7 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +9 -0
  16. package/dist/lanes-drain-hook.js +2 -2
  17. package/dist/lib/anywhere-runtime-fast-flag.d.ts +3 -0
  18. package/dist/lib/anywhere-runtime-fast-flag.js +23 -0
  19. package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
  20. package/dist/lib/anywhere-runtime-flag-key.js +3 -0
  21. package/dist/lib/daemon/ops/mcp.d.ts +54 -0
  22. package/dist/lib/daemon/ops/mcp.js +217 -0
  23. package/dist/lib/daemon/protocol.d.ts +2 -1
  24. package/dist/lib/daemon/protocol.js +3 -1
  25. package/dist/lib/daemon/server.d.ts +5 -0
  26. package/dist/lib/daemon/server.js +78 -11
  27. package/dist/lib/install/claude-targets.d.ts +11 -4
  28. package/dist/lib/install/claude-targets.js +21 -9
  29. package/dist/lib/install/codex-targets.d.ts +8 -4
  30. package/dist/lib/install/codex-targets.js +18 -9
  31. package/dist/lib/lanes/watcher-main.js +2 -4
  32. package/dist/lib/mcp/hosted-search-tools.d.ts +4 -0
  33. package/dist/lib/mcp/hosted-search-tools.js +28 -0
  34. package/dist/lib/mcp/hq-mcp-tools.generated.d.ts +3 -0
  35. package/dist/lib/mcp/hq-mcp-tools.generated.js +538 -0
  36. package/dist/lib/mcp/prompts.d.ts +30 -0
  37. package/dist/lib/mcp/prompts.js +48 -0
  38. package/dist/lib/mcp/tools.d.ts +22 -0
  39. package/dist/lib/mcp/tools.js +82 -0
  40. package/dist/lib/mcp/types.d.ts +6 -0
  41. package/dist/lib/mcp/types.js +2 -0
  42. package/dist/utils/is-main-module.d.ts +3 -0
  43. package/dist/utils/is-main-module.js +19 -0
  44. package/dist/utils/staged-npm-install.js +2 -1
  45. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.326.0] — 2026-10-02
6
+ - Company-scoped daemon tools now reject an explicit company that conflicts with the active session or repository binding.
7
+ - Added a flag-gated `hq mcp serve` command and global install hook for the Anywhere runtime.
8
+ - `hq mcp serve` can forward supported calls to hosted hq-mcp, including search and fetch, without a local hq-mcp process.
9
+ - Make standalone HQ auth refresh entry points run through npm bin symlinks.
10
+
5
11
  ## [5.325.0] — 2026-10-02
6
12
  - Match Bash pathname expansion in native derive-trigger-facts when a glob has missing suffixes, a trailing slash, or symlink path components.
7
13
 
@@ -15,8 +15,7 @@
15
15
  import "../node-preflight.js";
16
16
  import { acquireCurrentPackageUseLease } from "../utils/package-use-lease.js";
17
17
  import "../node-network-compat.js";
18
- import { fileURLToPath } from "node:url";
19
- import path from "node:path";
18
+ import { isMainModule } from "../utils/is-main-module.js";
20
19
  const MIN_VALIDITY_FLAG = "--min-validity-seconds";
21
20
  function parseMinimumValiditySeconds(argv, env) {
22
21
  let raw;
@@ -109,7 +108,7 @@ export async function startAuthRefresh(acquireLease = acquireCurrentPackageUseLe
109
108
  initSentry();
110
109
  return runAuthRefresh();
111
110
  }
112
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
111
+ if (isMainModule(import.meta.url)) {
113
112
  void startAuthRefresh().then((exitCode) => process.exit(exitCode));
114
113
  }
115
114
  //# sourceMappingURL=hq-auth-refresh.js.map
@@ -3720,6 +3720,15 @@ export declare const COMMAND_CATALOG: readonly [{
3720
3720
  readonly arguments: readonly [];
3721
3721
  readonly options: readonly [];
3722
3722
  readonly subcommands: readonly [{
3723
+ readonly name: "serve";
3724
+ readonly description: "Run the HQ stdio MCP server; every tool call is answered by hqd (`hq daemon status`)";
3725
+ readonly aliases: readonly [];
3726
+ readonly hidden: false;
3727
+ readonly usage: "[options]";
3728
+ readonly arguments: readonly [];
3729
+ readonly options: readonly [];
3730
+ readonly subcommands: readonly [];
3731
+ }, {
3723
3732
  readonly name: "status";
3724
3733
  readonly description: "Show installed MCP packs/servers and their health in BOTH Claude and Codex (PARTIAL/DRIFT flagged; secrets redacted)";
3725
3734
  readonly aliases: readonly ["list"];
@@ -9927,7 +9936,7 @@ export declare const COMMAND_CATALOG: readonly [{
9927
9936
  readonly subcommands: readonly [];
9928
9937
  }, {
9929
9938
  readonly name: "call";
9930
- readonly description: "Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, …) and print the result as JSON";
9939
+ readonly description: "Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, mcp.call, …) and print the result as JSON";
9931
9940
  readonly aliases: readonly [];
9932
9941
  readonly hidden: false;
9933
9942
  readonly usage: "[options] <op>";
@@ -4801,6 +4801,16 @@ export const COMMAND_CATALOG = [
4801
4801
  "arguments": [],
4802
4802
  "options": [],
4803
4803
  "subcommands": [
4804
+ {
4805
+ "name": "serve",
4806
+ "description": "Run the HQ stdio MCP server; every tool call is answered by hqd (`hq daemon status`)",
4807
+ "aliases": [],
4808
+ "hidden": false,
4809
+ "usage": "[options]",
4810
+ "arguments": [],
4811
+ "options": [],
4812
+ "subcommands": []
4813
+ },
4804
4814
  {
4805
4815
  "name": "status",
4806
4816
  "description": "Show installed MCP packs/servers and their health in BOTH Claude and Codex (PARTIAL/DRIFT flagged; secrets redacted)",
@@ -12789,7 +12799,7 @@ export const COMMAND_CATALOG = [
12789
12799
  },
12790
12800
  {
12791
12801
  "name": "call",
12792
- "description": "Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, …) and print the result as JSON",
12802
+ "description": "Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, mcp.call, …) and print the result as JSON",
12793
12803
  "aliases": [],
12794
12804
  "hidden": false,
12795
12805
  "usage": "[options] <op>",
@@ -1,5 +1,5 @@
1
- /** hq-flags key for the staged HQ Anywhere runtime. The registry default is off. */
2
- export declare const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
1
+ import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
2
+ export { HQ_ANYWHERE_RUNTIME_FLAG };
3
3
  export declare class AnywhereRuntimeDisabledError extends Error {
4
4
  readonly expected: true;
5
5
  constructor();
@@ -1,6 +1,6 @@
1
1
  import { resolveFlagGate, resolveProcessFlagGate } from "../lib/flag-registry.js";
2
- /** hq-flags key for the staged HQ Anywhere runtime. The registry default is off. */
3
- export const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
2
+ import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
3
+ export { HQ_ANYWHERE_RUNTIME_FLAG };
4
4
  export class AnywhereRuntimeDisabledError extends Error {
5
5
  expected = true;
6
6
  constructor() {
@@ -1,7 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { isMainModule } from "../utils/is-main-module.js";
5
4
  function signalNumber(signal) {
6
5
  if (!signal)
7
6
  return 0;
@@ -40,7 +39,7 @@ export async function runRemoteLauncher(argv, injected = {}) {
40
39
  await deps.sleep(15_000);
41
40
  return exitCode;
42
41
  }
43
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
42
+ if (isMainModule(import.meta.url)) {
44
43
  void runRemoteLauncher(process.argv.slice(2))
45
44
  .then((exitCode) => { process.exitCode = exitCode; })
46
45
  .catch((error) => {
@@ -769,7 +769,7 @@ export function registerDaemonCommand(program, over = {}) {
769
769
  }));
770
770
  daemon
771
771
  .command("call <op>")
772
- .description("Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, …) and print the result as JSON")
772
+ .description("Send one request to hqd (policy.check, journal.append, memory.get, memory.put, mesh.presence, mcp.call, …) and print the result as JSON")
773
773
  .option("--json <args>", "Request args as a JSON object", "{}")
774
774
  .option("--timeout <ms>", "Give up after this many milliseconds")
775
775
  .action(wrap(async (d, op, opts) => {
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { AnywhereRuntimeDisabledError } from './anywhere-runtime-flag.js';
3
+ import { type HookTarget } from '../lib/install/claude-targets.js';
3
4
  export interface GlobalInstallOptions {
4
5
  /** HQ checkout to install from (absolute). */
5
6
  hqRoot: string;
@@ -14,7 +15,16 @@ export interface GlobalInstallOptions {
14
15
  /** Registry file override (default ~/.hq/registry.json under `home`). */
15
16
  registryFile?: string;
16
17
  log?: (line: string) => void;
18
+ /** Is the hq daemon installed? Default: look for its launchd/systemd unit under `home`. */
19
+ daemonInstalled?: boolean;
17
20
  }
21
+ /** True when an hq daemon unit (launchd plist or systemd unit) exists. */
22
+ export declare function isDaemonInstalled(home: string, platform?: NodeJS.Platform, queryTask?: (task: string) => boolean): boolean;
23
+ /**
24
+ * US-010: with hqd installed (and the checkout shipping the shim), hooks go to
25
+ * the hqd shim; otherwise to master-hook.sh / the Codex adapter.
26
+ */
27
+ export declare function hookTargetFor(hqRoot: string, home: string, daemonInstalled?: boolean, env?: NodeJS.ProcessEnv): HookTarget;
18
28
  export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
19
29
  export declare const isAnywhereEnabled: (_home: string, env?: NodeJS.ProcessEnv) => boolean;
20
30
  /** Install HQ into Claude Code user scope. Returns the files touched (or that would be). */
@@ -10,13 +10,38 @@
10
10
  */
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
- import { claudeConfigFormat, codexConfigFormat, isCodexInstalled, jsonFormat, unregisterCodexServer, readConfigDoc, realpathOrSelf, resolveEnv, unregisterClaudeServer, writeConfigAtomic, } from './mcp-registration.js';
13
+ import { spawnSync } from 'node:child_process';
14
+ import { buildCodexServerDef, claudeConfigFormat, codexConfigFormat, isCodexInstalled, jsonFormat, mergeCodexServer, registerCodexServer, unregisterCodexServer, mergeClaudeServer, readConfigDoc, realpathOrSelf, registerClaudeServer, resolveEnv, unregisterClaudeServer, writeConfigAtomic, } from './mcp-registration.js';
14
15
  import { seedRegistryFromHqRoot } from './link.js';
15
16
  import { resolveHqRoot } from './doctor.js';
16
17
  import { anywhereRuntimeEnabled, AnywhereRuntimeDisabledError } from './anywhere-runtime-flag.js';
17
18
  import { readRegistry, registryDir, registryFile, writeRegistry } from '../lib/registry/registry.js';
19
+ import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK, SYSTEM_UNIT_DIR } from '../lib/daemon/install.js';
20
+ import { readDaemonConfig } from '../lib/daemon/config.js';
21
+ import { daemonPaths } from '../lib/daemon/paths.js';
18
22
  import { addAgentsMdBlock, addCodexHooks, classifyLegacySkills, codexAdapterPath, codexCharterSource, codexTargets, hasAllCodexHooks, HQ_OWNED_MARKER, LEGACY_BACKUP_NAME, LEGACY_SKILLS_NAME, removeAgentsMdBlock, removeCodexHooks, } from '../lib/install/codex-targets.js';
19
- import { addClaudeMdBlock, addHqHooks, charterPath, claudeTargets, hasAllHqHooks, HQ_ANYWHERE_PACK, HQ_MCP_SERVER_NAME, hqSkillsSource, lineDiff, masterHookPath, removeClaudeMdBlock, removeHqHooks, SKILL_LINK_PREFIX, } from '../lib/install/claude-targets.js';
23
+ import { addClaudeMdBlock, addHqHooks, charterPath, claudeTargets, hasAllHqHooks, HQ_ANYWHERE_PACK, HQ_MCP_SERVER_NAME, hqSkillsSource, lineDiff, masterHookPath, shimPath, MCP_SUBPROCESS_PATH, removeClaudeMdBlock, removeHqHooks, SKILL_LINK_PREFIX, } from '../lib/install/claude-targets.js';
24
+ /** True when an hq daemon unit (launchd plist or systemd unit) exists. */
25
+ export function isDaemonInstalled(home, platform = process.platform, queryTask = (task) => spawnSync('schtasks.exe', ['/Query', '/TN', task], { encoding: 'utf8', windowsHide: true }).status === 0) {
26
+ if (platform === 'win32')
27
+ return queryTask(DAEMON_WINDOWS_TASK);
28
+ const candidates = platform === 'darwin'
29
+ ? [path.join(home, 'Library', 'LaunchAgents', `${DAEMON_LAUNCHD_LABEL}.plist`)]
30
+ : platform === 'linux'
31
+ ? [path.join(home, '.config', 'systemd', 'user', DAEMON_SYSTEMD_UNIT), path.join(SYSTEM_UNIT_DIR, DAEMON_SYSTEMD_UNIT)]
32
+ : [];
33
+ return candidates.some((p) => fs.existsSync(p));
34
+ }
35
+ /**
36
+ * US-010: with hqd installed (and the checkout shipping the shim), hooks go to
37
+ * the hqd shim; otherwise to master-hook.sh / the Codex adapter.
38
+ */
39
+ export function hookTargetFor(hqRoot, home, daemonInstalled, env = process.env) {
40
+ const installed = daemonInstalled ?? isDaemonInstalled(home);
41
+ const configuredRoot = readDaemonConfig(daemonPaths(home, env).configPath).hqRoot;
42
+ const daemonMatchesRoot = configuredRoot !== undefined && realpathOrSelf(configuredRoot) === realpathOrSelf(hqRoot);
43
+ return installed && daemonMatchesRoot && fs.existsSync(shimPath(hqRoot)) ? 'shim' : 'master';
44
+ }
20
45
  export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
21
46
  export const isAnywhereEnabled = (_home, env = process.env) => anywhereRuntimeEnabled(env);
22
47
  function readOrNull(file) {
@@ -30,11 +55,10 @@ function readOrNull(file) {
30
55
  }
31
56
  }
32
57
  function writeFileAtomic(file, data) {
33
- const target = realpathOrSelf(file);
34
- fs.mkdirSync(path.dirname(target), { recursive: true });
35
- const tmp = `${target}.hq-tmp-${process.pid}`;
58
+ fs.mkdirSync(path.dirname(file), { recursive: true });
59
+ const tmp = `${file}.hq-tmp-${process.pid}`;
36
60
  fs.writeFileSync(tmp, data);
37
- fs.renameSync(tmp, target);
61
+ fs.renameSync(tmp, file);
38
62
  }
39
63
  function canonical(v) {
40
64
  return JSON.stringify(v, (_k, val) => val && typeof val === 'object' && !Array.isArray(val)
@@ -42,12 +66,12 @@ function canonical(v) {
42
66
  : val);
43
67
  }
44
68
  /** JSON settings format whose verify step checks every HQ hook is registered. */
45
- function settingsInstallFormat(hqRoot) {
69
+ function settingsInstallFormat(hqRoot, target) {
46
70
  return {
47
71
  ...jsonFormat,
48
72
  emptyDoc: () => ({}),
49
73
  assertEntry(doc) {
50
- if (!hasAllHqHooks(doc, hqRoot))
74
+ if (!hasAllHqHooks(doc, hqRoot, target))
51
75
  throw new Error('HQ hooks not present after write');
52
76
  },
53
77
  };
@@ -84,9 +108,18 @@ function restoringRemovalFormat(original, stillPresent) {
84
108
  },
85
109
  };
86
110
  }
87
- function planSkills(hqRoot, t, previouslyManaged = []) {
111
+ function mcpManifest(opts) {
112
+ const command = opts.nodePath ?? process.execPath;
113
+ const entry = opts.cliEntry ?? fs.realpathSync(process.argv[1] ?? '');
114
+ for (const p of [command, entry]) {
115
+ if (!path.isAbsolute(p))
116
+ throw new Error(`HQ MCP server path must be absolute: ${p}`);
117
+ }
118
+ return { type: 'stdio', command, args: [entry, 'mcp', 'serve'], env: { PATH: MCP_SUBPROCESS_PATH } };
119
+ }
120
+ function planSkills(hqRoot, t) {
88
121
  const source = hqSkillsSource(hqRoot);
89
- const plan = { link: [], replace: [], already: [], skipped: [] };
122
+ const plan = { link: [], already: [], skipped: [] };
90
123
  let names;
91
124
  try {
92
125
  names = fs
@@ -115,10 +148,6 @@ function planSkills(hqRoot, t, previouslyManaged = []) {
115
148
  plan.link.push(name);
116
149
  else if (st.isSymbolicLink() && path.resolve(t.skillsDir, fs.readlinkSync(link)) === target)
117
150
  plan.already.push(name);
118
- else if (st.isSymbolicLink() && previouslyManaged.includes(name)) {
119
- plan.link.push(name);
120
- plan.replace.push(name);
121
- }
122
151
  else
123
152
  plan.skipped.push(name);
124
153
  }
@@ -164,15 +193,23 @@ export function installGlobalClaude(opts) {
164
193
  const t = claudeTargets(home);
165
194
  const prior = readState(t);
166
195
  const regFile = opts.registryFile ?? registryFile(registryDir(home, env));
196
+ const manifest = mcpManifest(opts);
167
197
  const touched = [];
168
198
  const pointerOld = readOrNull(t.hqPointer);
169
199
  const pointerNew = `${hqRoot}\n`;
170
200
  const mdOld = readOrNull(t.claudeMd);
171
201
  const mdNew = addClaudeMdBlock(mdOld, hqRoot);
172
- const skills = planSkills(hqRoot, t, prior?.skills.linked ?? []);
202
+ const skills = planSkills(hqRoot, t);
173
203
  const settingsOld = readOrNull(t.settingsJson);
174
204
  const settingsDoc = readConfigDoc(t.settingsJson, jsonFormat).doc;
175
- const settingsNext = addHqHooks(settingsDoc, hqRoot);
205
+ const hookTarget = hookTargetFor(hqRoot, home, opts.daemonInstalled, env);
206
+ const settingsNext = addHqHooks(settingsDoc, hqRoot, hookTarget);
207
+ const claudeDoc = readConfigDoc(t.claudeJson, claudeConfigFormat).doc;
208
+ // Collision check runs here too, so a dry run surfaces a conflicting "hq" server.
209
+ const claudeNext = mergeClaudeServer(HQ_MCP_SERVER_NAME, {
210
+ ...manifest,
211
+ _hqPack: HQ_ANYWHERE_PACK,
212
+ })(claudeDoc);
176
213
  if (pointerOld !== pointerNew)
177
214
  touched.push(t.hqPointer);
178
215
  if (mdOld !== mdNew)
@@ -181,6 +218,8 @@ export function installGlobalClaude(opts) {
181
218
  touched.push(path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${s}`));
182
219
  if (settingsNext !== settingsDoc)
183
220
  touched.push(t.settingsJson);
221
+ if (claudeNext !== claudeDoc)
222
+ touched.push(t.claudeJson);
184
223
  touched.push(regFile, t.stateFile);
185
224
  if (opts.dryRun) {
186
225
  log(`hq install --global --runtime claude --dry-run (HQ root ${hqRoot})`);
@@ -192,29 +231,33 @@ export function installGlobalClaude(opts) {
192
231
  }
193
232
  log(`\n--- ${t.settingsJson}`);
194
233
  log(lineDiff(jsonFormat.serialize(settingsDoc), jsonFormat.serialize(settingsNext)));
234
+ log(`\n--- ${t.claudeJson}`);
235
+ log(lineDiff(jsonFormat.serialize(claudeDoc), jsonFormat.serialize(claudeNext)));
195
236
  return touched;
196
237
  }
197
238
  writeFileAtomic(t.hqPointer, pointerNew);
198
239
  writeFileAtomic(t.claudeMd, mdNew);
199
240
  fs.mkdirSync(t.skillsDir, { recursive: true });
200
- for (const name of skills.replace)
201
- fs.unlinkSync(path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${name}`));
202
241
  for (const name of skills.link) {
203
242
  fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${name}`));
204
243
  }
205
244
  writeConfigAtomic({
206
245
  target: t.settingsJson,
207
246
  pack: HQ_ANYWHERE_PACK,
208
- format: settingsInstallFormat(hqRoot),
209
- merge: (doc) => addHqHooks(doc, hqRoot),
247
+ format: settingsInstallFormat(hqRoot, hookTarget),
248
+ merge: (doc) => addHqHooks(doc, hqRoot, hookTarget),
210
249
  assertName: 'hooks',
211
250
  env: { home },
212
251
  });
252
+ registerClaudeServer({ name: HQ_MCP_SERVER_NAME, manifest, pack: HQ_ANYWHERE_PACK, env: { home } });
213
253
  // Seeding is best-effort: the runtime wiring above is already in place, so a
214
254
  // missing or unreadable manifest is reported rather than aborting the install.
215
255
  let seeded = [];
256
+ let ownedSeeded = [];
216
257
  try {
217
- seeded = seedRegistryFromHqRoot(hqRoot, { file: regFile }).owned;
258
+ const result = seedRegistryFromHqRoot(hqRoot, { file: regFile });
259
+ seeded = result.added;
260
+ ownedSeeded = result.owned;
218
261
  }
219
262
  catch (e) {
220
263
  log(`Registry not seeded from companies/manifest.yaml: ${e.message}`);
@@ -231,13 +274,13 @@ export function installGlobalClaude(opts) {
231
274
  skipped: skills.skipped,
232
275
  },
233
276
  mcp: { name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK },
234
- seeded: [...new Set([...(prior?.seeded ?? []), ...seeded])].sort(),
277
+ seeded: [...new Set([...(prior?.seeded ?? []), ...ownedSeeded])].sort(),
235
278
  };
236
279
  writeFileAtomic(t.stateFile, `${JSON.stringify(state, null, 2)}\n`);
237
280
  log(`HQ installed for Claude Code from ${hqRoot}.`);
238
281
  log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
239
- log(` hooks: master-hook.sh registered in ${t.settingsJson}`);
240
- log(' MCP server registration is added by the stacked MCP shim change.');
282
+ log(` hooks: ${hookTarget === 'shim' ? 'hqd-hook-shim.sh' : 'master-hook.sh'} registered in ${t.settingsJson}`);
283
+ log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.claudeJson}`);
241
284
  log(` registry: ${seeded.length} manifest entr${seeded.length === 1 ? 'y' : 'ies'} seeded`);
242
285
  return touched;
243
286
  }
@@ -367,12 +410,12 @@ function assertCodexHqRoot(hqRoot) {
367
410
  throw new Error(`${hqRoot} is not an HQ checkout (missing ${p})`);
368
411
  }
369
412
  }
370
- function hooksInstallFormat(hqRoot) {
413
+ function hooksInstallFormat(hqRoot, target) {
371
414
  return {
372
415
  ...jsonFormat,
373
416
  emptyDoc: () => ({}),
374
417
  assertEntry(doc) {
375
- if (!hasAllCodexHooks(doc, hqRoot))
418
+ if (!hasAllCodexHooks(doc, hqRoot, target))
376
419
  throw new Error('HQ Codex hooks not present after write');
377
420
  },
378
421
  };
@@ -399,6 +442,7 @@ export function installGlobalCodex(opts) {
399
442
  const hqPointer = claudeTargets(home).hqPointer;
400
443
  const prior = readCodexState(t);
401
444
  const regFile = opts.registryFile ?? registryFile(registryDir(home, env));
445
+ const manifest = mcpManifest(opts);
402
446
  const touched = [];
403
447
  const pointerOld = readOrNull(hqPointer);
404
448
  const pointerNew = `${hqRoot}\n`;
@@ -406,11 +450,20 @@ export function installGlobalCodex(opts) {
406
450
  const mdOld = readOrNull(t.agentsMd);
407
451
  const mdNew = addAgentsMdBlock(mdOld, charter);
408
452
  const legacy = classifyLegacySkills(t.skillsDir, hqRoot);
409
- const skills = planSkills(hqRoot, t, prior?.skills.linked ?? []);
453
+ const legacyPath = path.join(t.skillsDir, LEGACY_SKILLS_NAME);
454
+ const legacyBackupPath = path.join(t.skillsDir, LEGACY_BACKUP_NAME);
455
+ if (legacy.kind === 'hq-dir' && fs.existsSync(legacyBackupPath)) {
456
+ throw new Error(`${legacyBackupPath} already exists; move it away before installing.`);
457
+ }
458
+ const skills = planSkills(hqRoot, t);
410
459
  const hooksOld = readOrNull(t.hooksJson);
411
- const tomlOld = readOrNull(t.configToml);
412
460
  const hooksDoc = readConfigDoc(t.hooksJson, jsonFormat).doc;
413
- const hooksNext = addCodexHooks(hooksDoc, hqRoot);
461
+ const hookTarget = hookTargetFor(hqRoot, home, opts.daemonInstalled, env);
462
+ const hooksNext = addCodexHooks(hooksDoc, hqRoot, hookTarget);
463
+ const tomlOld = readOrNull(t.configToml);
464
+ const tomlDoc = readConfigDoc(t.configToml, codexConfigFormat).doc;
465
+ // Collision check runs here too, so a dry run surfaces a conflicting "hq" server.
466
+ const tomlNext = mergeCodexServer(HQ_MCP_SERVER_NAME, buildCodexServerDef(manifest, HQ_ANYWHERE_PACK, () => null, new Set()))(tomlDoc);
414
467
  if (pointerOld !== pointerNew)
415
468
  touched.push(hqPointer);
416
469
  if (mdOld !== mdNew)
@@ -421,6 +474,8 @@ export function installGlobalCodex(opts) {
421
474
  touched.push(path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${s}`));
422
475
  if (hooksNext !== hooksDoc)
423
476
  touched.push(t.hooksJson);
477
+ if (tomlNext !== tomlDoc)
478
+ touched.push(t.configToml);
424
479
  touched.push(regFile, t.stateFile);
425
480
  if (opts.dryRun) {
426
481
  log(`hq install --global --runtime codex --dry-run (HQ root ${hqRoot})`);
@@ -437,16 +492,14 @@ export function installGlobalCodex(opts) {
437
492
  }
438
493
  log(`\n--- ${t.hooksJson}`);
439
494
  log(lineDiff(jsonFormat.serialize(hooksDoc), jsonFormat.serialize(hooksNext)));
495
+ log(`\n--- ${t.configToml}`);
496
+ log(lineDiff(codexConfigFormat.serialize(tomlDoc), codexConfigFormat.serialize(tomlNext)));
440
497
  return touched;
441
498
  }
442
- if (legacy.kind === 'hq-dir' && fs.existsSync(path.join(t.skillsDir, LEGACY_BACKUP_NAME))) {
443
- throw new Error(`${path.join(t.skillsDir, LEGACY_BACKUP_NAME)} already exists; move it away before installing.`);
444
- }
445
499
  writeFileAtomic(hqPointer, pointerNew);
446
500
  writeFileAtomic(t.agentsMd, mdNew);
447
501
  // Legacy mirror: replace only when HQ-owned; record enough to put it back.
448
502
  let legacyRecord = prior?.legacy ?? { kind: 'none' };
449
- const legacyPath = path.join(t.skillsDir, LEGACY_SKILLS_NAME);
450
503
  if (legacy.kind === 'hq-symlink') {
451
504
  fs.unlinkSync(legacyPath);
452
505
  legacyRecord = { kind: 'symlink', target: legacy.target };
@@ -459,22 +512,24 @@ export function installGlobalCodex(opts) {
459
512
  else if (legacy.kind === 'foreign')
460
513
  log(legacyWarning(t.skillsDir));
461
514
  fs.mkdirSync(t.skillsDir, { recursive: true });
462
- for (const name of skills.replace)
463
- fs.unlinkSync(path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${name}`));
464
515
  for (const name of skills.link) {
465
516
  fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${name}`));
466
517
  }
467
518
  writeConfigAtomic({
468
519
  target: t.hooksJson,
469
520
  pack: HQ_ANYWHERE_PACK,
470
- format: hooksInstallFormat(hqRoot),
471
- merge: (doc) => addCodexHooks(doc, hqRoot),
521
+ format: hooksInstallFormat(hqRoot, hookTarget),
522
+ merge: (doc) => addCodexHooks(doc, hqRoot, hookTarget),
472
523
  assertName: 'hooks',
473
524
  env: { home },
474
525
  });
526
+ registerCodexServer({ name: HQ_MCP_SERVER_NAME, manifest, pack: HQ_ANYWHERE_PACK, env: { home } });
475
527
  let seeded = [];
528
+ let ownedSeeded = [];
476
529
  try {
477
- seeded = seedRegistryFromHqRoot(hqRoot, { file: regFile }).owned;
530
+ const result = seedRegistryFromHqRoot(hqRoot, { file: regFile });
531
+ seeded = result.added;
532
+ ownedSeeded = result.owned;
478
533
  }
479
534
  catch (e) {
480
535
  log(`Registry not seeded from companies/manifest.yaml: ${e.message}`);
@@ -492,13 +547,13 @@ export function installGlobalCodex(opts) {
492
547
  },
493
548
  legacy: legacyRecord,
494
549
  mcp: { name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK },
495
- seeded: [...new Set([...(prior?.seeded ?? []), ...seeded])].sort(),
550
+ seeded: [...new Set([...(prior?.seeded ?? []), ...ownedSeeded])].sort(),
496
551
  };
497
552
  writeFileAtomic(t.stateFile, `${JSON.stringify(state, null, 2)}\n`);
498
553
  log(`HQ installed for Codex from ${hqRoot}.`);
499
554
  log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
500
- log(` hooks: ${codexAdapterPath(hqRoot)} registered in ${t.hooksJson}`);
501
- log(' MCP server registration is added by the stacked MCP shim change.');
555
+ log(` hooks: ${hookTarget === 'shim' ? shimPath(hqRoot) : codexAdapterPath(hqRoot)} registered in ${t.hooksJson}`);
556
+ log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.configToml}`);
502
557
  return touched;
503
558
  }
504
559
  /** Reverse every Codex install step; non-HQ entries are left untouched. */
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `hq mcp serve` (hq-anywhere US-011): a stdio MCP server that forwards every
3
+ * tool call to hqd over its socket.
4
+ *
5
+ * It keeps no state between requests, so an orphaned instance costs nothing
6
+ * (policy mcp-process-cleanup). src/index.ts routes `hq mcp serve` straight to
7
+ * {@link runMcpServe} without loading the command graph, which keeps start-up
8
+ * under 100ms. The MCP SDK is not loaded either: the stdio transport is
9
+ * newline-delimited JSON-RPC 2.0 and is handled here directly.
10
+ *
11
+ * When hqd is unreachable every tool call returns an isError result naming
12
+ * `hq daemon status`, and the server keeps running.
13
+ */
14
+ export declare const MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
15
+ export interface McpServeOptions {
16
+ input?: NodeJS.ReadableStream;
17
+ output?: NodeJS.WritableStream;
18
+ env?: NodeJS.ProcessEnv;
19
+ home?: string;
20
+ cwd?: string;
21
+ /** Per-call hqd budget. Default 120s; policy checks and hq-mcp tools can be slow. */
22
+ timeoutMs?: number;
23
+ }
24
+ /** HQ root for prompts: HQ_ROOT, else the ~/.hq/root pointer written by `hq install --global`. */
25
+ export declare function readHqRoot(env: NodeJS.ProcessEnv, home: string): string | null;
26
+ /** Run the server until input ends. Resolves when stdin closes. */
27
+ export declare function runMcpServe(opts?: McpServeOptions): Promise<void>;
28
+ //# sourceMappingURL=mcp-serve.d.ts.map