@indigoai-us/hq-cli 5.327.0 → 5.329.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.
- package/CHANGELOG.md +13 -0
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +1 -1
- package/assets/scaffold/core/scripts/hq-job-run.sh +8 -8
- package/assets/scaffold/core/scripts/ontology-audience-grant.sh +1 -1
- package/assets/scaffold/core/scripts/ontology-candidate.sh +1 -1
- package/assets/scaffold/core/scripts/resolve-company.sh +1 -1
- package/dist/command-catalog.generated.d.ts +3699 -3
- package/dist/command-catalog.generated.js +5462 -1759
- package/dist/command-help.d.ts +2 -0
- package/dist/command-help.js +13 -0
- package/dist/commands/install-global.d.ts +21 -0
- package/dist/commands/install-global.js +267 -32
- package/dist/commands/mcp-serve.js +1 -1
- package/dist/commands/mcp-status.js +58 -1
- package/dist/commands/pack-install.d.ts +3 -1
- package/dist/commands/pack-install.js +17 -4
- package/dist/commands/packs.js +17 -0
- package/dist/commands/pkg-install.js +13 -1
- package/dist/fast-scaffold.d.ts +2 -0
- package/dist/fast-scaffold.js +16 -0
- package/dist/index.js +19 -2
- package/dist/lazy-commands.d.ts +1 -0
- package/dist/lazy-commands.js +64 -4
- package/dist/lib/cli-flag-keys.d.ts +3 -0
- package/dist/lib/cli-flag-keys.js +3 -0
- package/dist/lib/daemon/ops/mcp.js +11 -10
- package/dist/lib/daemon/ops/policy.d.ts +3 -2
- package/dist/lib/daemon/ops/policy.js +12 -6
- package/dist/lib/daemon/server.d.ts +3 -1
- package/dist/lib/daemon/server.js +63 -7
- package/dist/lib/doctor/checks/anywhere.js +58 -4
- package/dist/lib/install/chatgpt-targets.d.ts +8 -0
- package/dist/lib/install/chatgpt-targets.js +15 -0
- package/dist/lib/install/codex-pack-contributions.d.ts +8 -0
- package/dist/lib/install/codex-pack-contributions.js +205 -0
- package/dist/lib/install/codex-targets.d.ts +1 -1
- package/dist/lib/install/codex-targets.js +2 -1
- package/dist/lib/install/grok-targets.d.ts +8 -0
- package/dist/lib/install/grok-targets.js +15 -0
- package/dist/lib/lanes/keep-alive-audit.d.ts +3 -2
- package/dist/lib/lanes/keep-alive-audit.js +36 -8
- package/dist/main.d.ts +2 -6
- package/dist/main.js +3 -41
- package/dist/node-network-compat-fast.js +4 -0
- package/dist/stream-error-boundary.d.ts +7 -0
- package/dist/stream-error-boundary.js +33 -0
- package/dist/utils/vault-api.js +16 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { CLI_VERSION } from "./cli-version.js";
|
|
3
|
+
import { registerCommandCatalog } from "./lazy-commands.js";
|
|
4
|
+
export async function runCommandHelp(argv) {
|
|
5
|
+
const program = new Command()
|
|
6
|
+
.name("hq")
|
|
7
|
+
.description("HQ management CLI — modules, packages, and cloud sync")
|
|
8
|
+
.version(CLI_VERSION)
|
|
9
|
+
.option("--verbose", "Show verbose diagnostics, including SDK runtime warnings");
|
|
10
|
+
registerCommandCatalog(program);
|
|
11
|
+
await program.parseAsync([...argv]);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=command-help.js.map
|
|
@@ -17,6 +17,12 @@ export interface GlobalInstallOptions {
|
|
|
17
17
|
log?: (line: string) => void;
|
|
18
18
|
/** Is the hq daemon installed? Default: look for its launchd/systemd unit under `home`. */
|
|
19
19
|
daemonInstalled?: boolean;
|
|
20
|
+
/** Install Claude Code through a local plugin marketplace instead of direct hooks/MCP. */
|
|
21
|
+
via?: 'direct' | 'plugin';
|
|
22
|
+
/** Local Claude Code marketplace directory (or HQ_CLAUDE_PLUGIN_MARKETPLACE). */
|
|
23
|
+
marketplaceDir?: string;
|
|
24
|
+
/** Test seam for the Claude Code CLI; production uses `claude`. */
|
|
25
|
+
runClaude?: (args: string[], home: string, env: NodeJS.ProcessEnv) => void;
|
|
20
26
|
}
|
|
21
27
|
/** True when an hq daemon unit (launchd plist or systemd unit) exists. */
|
|
22
28
|
export declare function isDaemonInstalled(home: string, platform?: NodeJS.Platform, queryTask?: (task: string) => boolean): boolean;
|
|
@@ -25,6 +31,14 @@ export declare function isDaemonInstalled(home: string, platform?: NodeJS.Platfo
|
|
|
25
31
|
* the hqd shim; otherwise to master-hook.sh / the Codex adapter.
|
|
26
32
|
*/
|
|
27
33
|
export declare function hookTargetFor(hqRoot: string, home: string, daemonInstalled?: boolean, env?: NodeJS.ProcessEnv): HookTarget;
|
|
34
|
+
export interface ClaudePluginSource {
|
|
35
|
+
sourceDir: string;
|
|
36
|
+
marketplaceName: string;
|
|
37
|
+
pluginName: string;
|
|
38
|
+
pluginId: string;
|
|
39
|
+
}
|
|
40
|
+
/** Resolve the local marketplace source; published hosting remains an operator decision. */
|
|
41
|
+
export declare function resolveClaudePluginSource(explicitDir?: string, env?: NodeJS.ProcessEnv): ClaudePluginSource;
|
|
28
42
|
export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
|
|
29
43
|
export declare const isAnywhereEnabled: (_home: string, env?: NodeJS.ProcessEnv) => boolean;
|
|
30
44
|
/** Install HQ into Claude Code user scope. Returns the files touched (or that would be). */
|
|
@@ -37,6 +51,8 @@ export interface GlobalUninstallOptions {
|
|
|
37
51
|
env?: NodeJS.ProcessEnv;
|
|
38
52
|
registryFile?: string;
|
|
39
53
|
log?: (line: string) => void;
|
|
54
|
+
/** Test seam for the Claude Code CLI; production uses `claude`. */
|
|
55
|
+
runClaude?: (args: string[], home: string, env: NodeJS.ProcessEnv) => void;
|
|
40
56
|
}
|
|
41
57
|
/** Reverse every install step; non-HQ entries are left untouched. */
|
|
42
58
|
export declare function uninstallGlobalClaude(opts?: GlobalUninstallOptions): string[];
|
|
@@ -49,6 +65,11 @@ export interface GlobalCommandOpts {
|
|
|
49
65
|
runtime?: string;
|
|
50
66
|
dryRun?: boolean;
|
|
51
67
|
hqRoot?: string;
|
|
68
|
+
via?: 'direct' | 'plugin';
|
|
69
|
+
marketplaceDir?: string;
|
|
70
|
+
/** Dependency injection for tests. */
|
|
71
|
+
env?: NodeJS.ProcessEnv;
|
|
72
|
+
log?: (line: string) => void;
|
|
52
73
|
}
|
|
53
74
|
/** Entry used by `hq install --global` (wired from pkg-install.ts). */
|
|
54
75
|
export declare function runInstallGlobal(opts: GlobalCommandOpts): void;
|
|
@@ -15,6 +15,9 @@ import { buildCodexServerDef, claudeConfigFormat, codexConfigFormat, isCodexInst
|
|
|
15
15
|
import { seedRegistryFromHqRoot } from './link.js';
|
|
16
16
|
import { resolveHqRoot } from './doctor.js';
|
|
17
17
|
import { anywhereRuntimeEnabled, AnywhereRuntimeDisabledError } from './anywhere-runtime-flag.js';
|
|
18
|
+
import { resolveHqMcpEndpoint } from '../lib/daemon/mcp-endpoint.js';
|
|
19
|
+
import { chatgptConnectorInstructions } from '../lib/install/chatgpt-targets.js';
|
|
20
|
+
import { grokConnectorInstructions } from '../lib/install/grok-targets.js';
|
|
18
21
|
import { readRegistry, registryDir, registryFile, writeRegistry } from '../lib/registry/registry.js';
|
|
19
22
|
import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK, SYSTEM_UNIT_DIR } from '../lib/daemon/install.js';
|
|
20
23
|
import { readDaemonConfig } from '../lib/daemon/config.js';
|
|
@@ -42,6 +45,64 @@ export function hookTargetFor(hqRoot, home, daemonInstalled, env = process.env)
|
|
|
42
45
|
const daemonMatchesRoot = configuredRoot !== undefined && realpathOrSelf(configuredRoot) === realpathOrSelf(hqRoot);
|
|
43
46
|
return installed && daemonMatchesRoot && fs.existsSync(shimPath(hqRoot)) ? 'shim' : 'master';
|
|
44
47
|
}
|
|
48
|
+
/** Resolve the local marketplace source; published hosting remains an operator decision. */
|
|
49
|
+
export function resolveClaudePluginSource(explicitDir, env = process.env) {
|
|
50
|
+
const configured = explicitDir?.trim() || env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim();
|
|
51
|
+
if (!configured) {
|
|
52
|
+
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 });
|
|
53
|
+
}
|
|
54
|
+
const sourceDir = path.resolve(configured);
|
|
55
|
+
const manifestPath = path.join(sourceDir, '.claude-plugin', 'marketplace.json');
|
|
56
|
+
let manifest;
|
|
57
|
+
try {
|
|
58
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw Object.assign(new Error(`Cannot read Claude marketplace manifest at ${manifestPath}: ${error.message}`), { expected: true });
|
|
62
|
+
}
|
|
63
|
+
if (typeof manifest.name !== 'string' || !Array.isArray(manifest.plugins)) {
|
|
64
|
+
throw Object.assign(new Error(`Invalid Claude marketplace manifest at ${manifestPath}: expected string name and plugins array.`), { expected: true });
|
|
65
|
+
}
|
|
66
|
+
const hqPlugin = manifest.plugins.find((p) => !!p && typeof p === 'object' && p.name === 'hq');
|
|
67
|
+
if (!hqPlugin) {
|
|
68
|
+
throw Object.assign(new Error(`Claude marketplace ${manifest.name} does not contain the HQ plugin named "hq".`), { expected: true });
|
|
69
|
+
}
|
|
70
|
+
return { sourceDir, marketplaceName: manifest.name, pluginName: hqPlugin.name, pluginId: `${hqPlugin.name}@${manifest.name}` };
|
|
71
|
+
}
|
|
72
|
+
function runClaudeCli(args, home, env) {
|
|
73
|
+
const childEnv = { ...process.env, ...env, HOME: home, CLAUDE_CONFIG_DIR: path.join(home, '.claude') };
|
|
74
|
+
const result = spawnSync('claude', ['plugin', ...args], { encoding: 'utf8', env: childEnv, timeout: 120_000, windowsHide: true });
|
|
75
|
+
if (result.error || result.status !== 0) {
|
|
76
|
+
const cause = result.error?.message ?? `exit status ${String(result.status)}`;
|
|
77
|
+
throw Object.assign(new Error(`Claude Code plugin command failed (${cause}). Ensure the Claude Code CLI is installed and try again.`), { expected: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function removePluginSettings(doc, plugin, removePlugin, removeMarketplace, pluginSettingBefore) {
|
|
81
|
+
const next = { ...doc };
|
|
82
|
+
const enabled = next.enabledPlugins;
|
|
83
|
+
if (removePlugin && enabled && typeof enabled === 'object' && !Array.isArray(enabled)) {
|
|
84
|
+
const entries = { ...enabled };
|
|
85
|
+
delete entries[plugin.pluginId];
|
|
86
|
+
if (pluginSettingBefore !== undefined)
|
|
87
|
+
entries[plugin.pluginId] = pluginSettingBefore;
|
|
88
|
+
if (Object.keys(entries).length === 0)
|
|
89
|
+
delete next.enabledPlugins;
|
|
90
|
+
else
|
|
91
|
+
next.enabledPlugins = entries;
|
|
92
|
+
}
|
|
93
|
+
const marketplaces = next.extraKnownMarketplaces;
|
|
94
|
+
if (marketplaces && typeof marketplaces === 'object' && !Array.isArray(marketplaces)) {
|
|
95
|
+
const entries = { ...marketplaces };
|
|
96
|
+
const registration = entries[plugin.marketplaceName];
|
|
97
|
+
if (removeMarketplace && registration?.source?.source === 'directory' && registration.source.path === plugin.sourceDir)
|
|
98
|
+
delete entries[plugin.marketplaceName];
|
|
99
|
+
if (Object.keys(entries).length === 0)
|
|
100
|
+
delete next.extraKnownMarketplaces;
|
|
101
|
+
else
|
|
102
|
+
next.extraKnownMarketplaces = entries;
|
|
103
|
+
}
|
|
104
|
+
return canonical(next) === canonical(doc) ? doc : next;
|
|
105
|
+
}
|
|
45
106
|
export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
|
|
46
107
|
export const isAnywhereEnabled = (_home, env = process.env) => anywhereRuntimeEnabled(env);
|
|
47
108
|
function readOrNull(file) {
|
|
@@ -80,8 +141,9 @@ function settingsInstallFormat(hqRoot, target) {
|
|
|
80
141
|
* Removal format: verifies no HQ hook remains, and serializes a doc that equals
|
|
81
142
|
* the pre-install document back to the ORIGINAL bytes (byte-identical uninstall).
|
|
82
143
|
*/
|
|
83
|
-
function settingsRemovalFormat(
|
|
84
|
-
|
|
144
|
+
function settingsRemovalFormat(hqRoots, original) {
|
|
145
|
+
const roots = Array.isArray(hqRoots) ? hqRoots : [hqRoots];
|
|
146
|
+
return restoringRemovalFormat(original, (doc) => roots.reduce((current, root) => removeHqHooks(current, root), doc) !== doc);
|
|
85
147
|
}
|
|
86
148
|
/** Shared removal format for a JSON hooks file (see settingsRemovalFormat). */
|
|
87
149
|
function restoringRemovalFormat(original, stillPresent) {
|
|
@@ -153,6 +215,30 @@ function planSkills(hqRoot, t) {
|
|
|
153
215
|
}
|
|
154
216
|
return plan;
|
|
155
217
|
}
|
|
218
|
+
function hqSkillLinksForRoot(skillsDir, hqRoot) {
|
|
219
|
+
let names;
|
|
220
|
+
try {
|
|
221
|
+
names = fs.readdirSync(skillsDir).filter((name) => name.startsWith(SKILL_LINK_PREFIX));
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (error.code === 'ENOENT')
|
|
225
|
+
return [];
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
return names.filter((name) => {
|
|
229
|
+
const link = path.join(skillsDir, name);
|
|
230
|
+
try {
|
|
231
|
+
if (!fs.lstatSync(link).isSymbolicLink())
|
|
232
|
+
return false;
|
|
233
|
+
return path.dirname(path.resolve(skillsDir, fs.readlinkSync(link))) === hqSkillsSource(hqRoot);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (error.code === 'ENOENT')
|
|
237
|
+
return false;
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}).map((name) => path.join(skillsDir, name));
|
|
241
|
+
}
|
|
156
242
|
function removeSeeded(seeded, regFile) {
|
|
157
243
|
if (seeded.length === 0 || !fs.existsSync(regFile))
|
|
158
244
|
return;
|
|
@@ -192,21 +278,58 @@ export function installGlobalClaude(opts) {
|
|
|
192
278
|
throw new AnywhereRuntimeDisabledError();
|
|
193
279
|
const t = claudeTargets(home);
|
|
194
280
|
const prior = readState(t);
|
|
281
|
+
const pluginSource = opts.via === 'plugin' ? resolveClaudePluginSource(opts.marketplaceDir, env) : undefined;
|
|
282
|
+
if (prior?.plugin && !pluginSource) {
|
|
283
|
+
throw Object.assign(new Error('HQ is installed through a Claude plugin marketplace. Run `hq uninstall --global --runtime claude` before switching back to direct installation.'), { expected: true });
|
|
284
|
+
}
|
|
285
|
+
if (prior?.plugin && pluginSource && (prior.plugin.pluginId !== pluginSource.pluginId || prior.plugin.sourceDir !== pluginSource.sourceDir)) {
|
|
286
|
+
throw Object.assign(new Error('A different HQ Claude plugin marketplace is already recorded for this install. Uninstall it before changing marketplace source.'), { expected: true });
|
|
287
|
+
}
|
|
288
|
+
const runClaude = opts.runClaude ?? runClaudeCli;
|
|
195
289
|
const regFile = opts.registryFile ?? registryFile(registryDir(home, env));
|
|
196
|
-
const manifest = mcpManifest(opts);
|
|
290
|
+
const manifest = pluginSource ? undefined : mcpManifest(opts);
|
|
197
291
|
const touched = [];
|
|
198
292
|
const pointerOld = readOrNull(t.hqPointer);
|
|
199
293
|
const pointerNew = `${hqRoot}\n`;
|
|
200
294
|
const mdOld = readOrNull(t.claudeMd);
|
|
201
|
-
const
|
|
202
|
-
const
|
|
295
|
+
const legacyRoot = prior?.hqRoot ?? (pluginSource ? pointerOld?.trim() : undefined);
|
|
296
|
+
const cleanupRoots = [...new Set([hqRoot, ...(legacyRoot ? [legacyRoot] : [])])];
|
|
297
|
+
const mdOriginal = pluginSource
|
|
298
|
+
? (prior && mdOld === prior.claudeMd.installed
|
|
299
|
+
? prior.claudeMd.original
|
|
300
|
+
: mdOld === null ? null : (removeClaudeMdBlock(mdOld) || null))
|
|
301
|
+
: prior ? prior.claudeMd.original : mdOld;
|
|
302
|
+
const mdNew = pluginSource ? mdOriginal : addClaudeMdBlock(mdOld, hqRoot);
|
|
303
|
+
const skills = pluginSource ? { link: [], already: [], skipped: [] } : planSkills(hqRoot, t);
|
|
203
304
|
const settingsOld = readOrNull(t.settingsJson);
|
|
204
305
|
const settingsDoc = readConfigDoc(t.settingsJson, jsonFormat).doc;
|
|
306
|
+
const directHooksRemoved = pluginSource
|
|
307
|
+
? cleanupRoots.reduce((doc, root) => removeHqHooks(doc, root), settingsDoc)
|
|
308
|
+
: settingsDoc;
|
|
309
|
+
const settingsOriginal = prior
|
|
310
|
+
? prior.settings.original
|
|
311
|
+
: pluginSource && directHooksRemoved !== settingsDoc ? jsonFormat.serialize(directHooksRemoved) : settingsOld;
|
|
312
|
+
const enabledPlugins = settingsDoc.enabledPlugins;
|
|
313
|
+
const knownMarketplaces = settingsDoc.extraKnownMarketplaces;
|
|
314
|
+
const existingMarketplace = pluginSource ? knownMarketplaces?.[pluginSource.marketplaceName] : undefined;
|
|
315
|
+
if (pluginSource && existingMarketplace && (existingMarketplace.source?.source !== 'directory' || existingMarketplace.source.path !== pluginSource.sourceDir)) {
|
|
316
|
+
throw Object.assign(new Error(`Claude marketplace "${pluginSource.marketplaceName}" is already registered from a different source. Remove or rename that marketplace before installing HQ.`), { expected: true });
|
|
317
|
+
}
|
|
318
|
+
const hadPluginSetting = !!pluginSource && !!enabledPlugins && Object.prototype.hasOwnProperty.call(enabledPlugins, pluginSource.pluginId);
|
|
319
|
+
const currentPluginSetting = pluginSource && hadPluginSetting ? enabledPlugins[pluginSource.pluginId] : undefined;
|
|
320
|
+
const pluginSettingBefore = prior?.plugin ? prior.plugin.pluginSettingBefore : currentPluginSetting;
|
|
321
|
+
const pluginNeedsInstall = !!pluginSource && currentPluginSetting !== true;
|
|
322
|
+
const marketplaceNeedsAdd = !!pluginSource && existingMarketplace === undefined;
|
|
323
|
+
const pluginAdded = !!pluginSource && (prior?.plugin?.pluginAdded === true || pluginNeedsInstall);
|
|
324
|
+
const marketplaceAdded = !!pluginSource && (prior?.plugin?.marketplaceAdded === true || marketplaceNeedsAdd);
|
|
205
325
|
const hookTarget = hookTargetFor(hqRoot, home, opts.daemonInstalled, env);
|
|
206
|
-
const settingsNext =
|
|
326
|
+
const settingsNext = pluginSource
|
|
327
|
+
? cleanupRoots.reduce((doc, root) => removeHqHooks(doc, root), settingsDoc)
|
|
328
|
+
: addHqHooks(settingsDoc, hqRoot, hookTarget);
|
|
329
|
+
const skillLinksToRemove = pluginSource ? cleanupRoots.flatMap((root) => hqSkillLinksForRoot(t.skillsDir, root)) : [];
|
|
207
330
|
const claudeDoc = readConfigDoc(t.claudeJson, claudeConfigFormat).doc;
|
|
208
331
|
// Collision check runs here too, so a dry run surfaces a conflicting "hq" server.
|
|
209
|
-
const claudeNext = mergeClaudeServer(HQ_MCP_SERVER_NAME, {
|
|
332
|
+
const claudeNext = pluginSource ? claudeDoc : mergeClaudeServer(HQ_MCP_SERVER_NAME, {
|
|
210
333
|
...manifest,
|
|
211
334
|
_hqPack: HQ_ANYWHERE_PACK,
|
|
212
335
|
})(claudeDoc);
|
|
@@ -216,10 +339,13 @@ export function installGlobalClaude(opts) {
|
|
|
216
339
|
touched.push(t.claudeMd);
|
|
217
340
|
for (const s of skills.link)
|
|
218
341
|
touched.push(path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${s}`));
|
|
342
|
+
touched.push(...skillLinksToRemove);
|
|
219
343
|
if (settingsNext !== settingsDoc)
|
|
220
344
|
touched.push(t.settingsJson);
|
|
221
345
|
if (claudeNext !== claudeDoc)
|
|
222
346
|
touched.push(t.claudeJson);
|
|
347
|
+
if (pluginSource)
|
|
348
|
+
touched.push(t.settingsJson);
|
|
223
349
|
touched.push(regFile, t.stateFile);
|
|
224
350
|
if (opts.dryRun) {
|
|
225
351
|
log(`hq install --global --runtime claude --dry-run (HQ root ${hqRoot})`);
|
|
@@ -233,23 +359,86 @@ export function installGlobalClaude(opts) {
|
|
|
233
359
|
log(lineDiff(jsonFormat.serialize(settingsDoc), jsonFormat.serialize(settingsNext)));
|
|
234
360
|
log(`\n--- ${t.claudeJson}`);
|
|
235
361
|
log(lineDiff(jsonFormat.serialize(claudeDoc), jsonFormat.serialize(claudeNext)));
|
|
362
|
+
if (pluginSource)
|
|
363
|
+
log(`Would run: claude plugin marketplace add --scope user ${pluginSource.sourceDir}; claude plugin install --scope user -y ${pluginSource.pluginId}`);
|
|
236
364
|
return touched;
|
|
237
365
|
}
|
|
366
|
+
if (pluginSource && (marketplaceNeedsAdd || pluginNeedsInstall)) {
|
|
367
|
+
let marketplaceMayNeedRollback = false;
|
|
368
|
+
try {
|
|
369
|
+
if (marketplaceNeedsAdd) {
|
|
370
|
+
marketplaceMayNeedRollback = true;
|
|
371
|
+
runClaude(['marketplace', 'add', '--scope', 'user', '--json', pluginSource.sourceDir], home, env);
|
|
372
|
+
}
|
|
373
|
+
if (pluginNeedsInstall)
|
|
374
|
+
runClaude(['install', '--scope', 'user', '--json', '-y', pluginSource.pluginId], home, env);
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
const rollbackErrors = [];
|
|
378
|
+
if (pluginNeedsInstall && pluginAdded) {
|
|
379
|
+
try {
|
|
380
|
+
runClaude(['uninstall', '--scope', 'user', '--json', pluginSource.pluginId], home, env);
|
|
381
|
+
}
|
|
382
|
+
catch (rollbackError) {
|
|
383
|
+
rollbackErrors.push(`plugin uninstall: ${rollbackError.message}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (marketplaceMayNeedRollback) {
|
|
387
|
+
try {
|
|
388
|
+
runClaude(['marketplace', 'remove', '--scope', 'user', '--json', pluginSource.marketplaceName], home, env);
|
|
389
|
+
}
|
|
390
|
+
catch (rollbackError) {
|
|
391
|
+
rollbackErrors.push(`marketplace removal: ${rollbackError.message}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (rollbackErrors.length > 0)
|
|
395
|
+
throw Object.assign(new Error(`Claude plugin installation failed and rollback was incomplete (${rollbackErrors.join('; ')})`), { cause: error, expected: true });
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
238
399
|
writeFileAtomic(t.hqPointer, pointerNew);
|
|
239
|
-
|
|
240
|
-
|
|
400
|
+
if (mdOld !== mdNew) {
|
|
401
|
+
if (mdNew === null) {
|
|
402
|
+
if (fs.existsSync(t.claudeMd))
|
|
403
|
+
fs.unlinkSync(t.claudeMd);
|
|
404
|
+
}
|
|
405
|
+
else
|
|
406
|
+
writeFileAtomic(t.claudeMd, mdNew);
|
|
407
|
+
}
|
|
408
|
+
if (!pluginSource)
|
|
409
|
+
fs.mkdirSync(t.skillsDir, { recursive: true });
|
|
241
410
|
for (const name of skills.link) {
|
|
242
411
|
fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir, `${SKILL_LINK_PREFIX}${name}`));
|
|
243
412
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
413
|
+
if (pluginSource)
|
|
414
|
+
for (const link of skillLinksToRemove)
|
|
415
|
+
fs.unlinkSync(link);
|
|
416
|
+
if (pluginSource) {
|
|
417
|
+
// Migrate a prior direct install: remove only our old hook/MCP entries while
|
|
418
|
+
// retaining the original user settings for a later uninstall restore.
|
|
419
|
+
if (fs.existsSync(t.settingsJson)) {
|
|
420
|
+
writeConfigAtomic({
|
|
421
|
+
target: t.settingsJson,
|
|
422
|
+
pack: HQ_ANYWHERE_PACK,
|
|
423
|
+
format: settingsRemovalFormat(cleanupRoots, settingsOriginal),
|
|
424
|
+
merge: (doc) => cleanupRoots.reduce((current, root) => removeHqHooks(current, root), doc),
|
|
425
|
+
assertName: 'hooks',
|
|
426
|
+
env: { home },
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
unregisterClaudeServer({ name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK, env: { home } });
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
writeConfigAtomic({
|
|
433
|
+
target: t.settingsJson,
|
|
434
|
+
pack: HQ_ANYWHERE_PACK,
|
|
435
|
+
format: settingsInstallFormat(hqRoot, hookTarget),
|
|
436
|
+
merge: (doc) => addHqHooks(doc, hqRoot, hookTarget),
|
|
437
|
+
assertName: 'hooks',
|
|
438
|
+
env: { home },
|
|
439
|
+
});
|
|
440
|
+
registerClaudeServer({ name: HQ_MCP_SERVER_NAME, manifest: manifest, pack: HQ_ANYWHERE_PACK, env: { home } });
|
|
441
|
+
}
|
|
253
442
|
// Seeding is best-effort: the runtime wiring above is already in place, so a
|
|
254
443
|
// missing or unreadable manifest is reported rather than aborting the install.
|
|
255
444
|
let seeded = [];
|
|
@@ -262,25 +451,38 @@ export function installGlobalClaude(opts) {
|
|
|
262
451
|
catch (e) {
|
|
263
452
|
log(`Registry not seeded from companies/manifest.yaml: ${e.message}`);
|
|
264
453
|
}
|
|
454
|
+
const recoveredDirectPointer = !!pluginSource && !prior && legacyRoot === pointerOld?.trim() && (directHooksRemoved !== settingsDoc || (mdOld !== null && removeClaudeMdBlock(mdOld) !== mdOld) || skillLinksToRemove.length > 0);
|
|
265
455
|
const state = {
|
|
266
456
|
version: 1,
|
|
267
457
|
hqRoot,
|
|
268
458
|
// A re-install keeps the FIRST install's originals so uninstall restores pre-HQ bytes.
|
|
269
|
-
hqPointer: { original: prior ? prior.hqPointer.original : pointerOld },
|
|
270
|
-
claudeMd: { original:
|
|
271
|
-
settings: { original:
|
|
459
|
+
hqPointer: { original: prior ? prior.hqPointer.original : recoveredDirectPointer ? null : pointerOld },
|
|
460
|
+
claudeMd: { original: mdOriginal, installed: readOrNull(t.claudeMd) ?? '' },
|
|
461
|
+
settings: { original: settingsOriginal, installed: readOrNull(t.settingsJson) ?? '' },
|
|
272
462
|
skills: {
|
|
273
|
-
linked: [...new Set([...(prior?.skills.linked ?? []), ...skills.link, ...skills.already])].sort(),
|
|
463
|
+
linked: pluginSource ? [] : [...new Set([...(prior?.skills.linked ?? []), ...skills.link, ...skills.already])].sort(),
|
|
274
464
|
skipped: skills.skipped,
|
|
275
465
|
},
|
|
276
466
|
mcp: { name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK },
|
|
467
|
+
...(pluginSource ? { plugin: {
|
|
468
|
+
pluginId: pluginSource.pluginId,
|
|
469
|
+
marketplaceName: pluginSource.marketplaceName,
|
|
470
|
+
sourceDir: pluginSource.sourceDir,
|
|
471
|
+
pluginAdded,
|
|
472
|
+
marketplaceAdded,
|
|
473
|
+
...(hadPluginSetting ? { pluginSettingBefore } : {}),
|
|
474
|
+
} } : {}),
|
|
277
475
|
seeded: [...new Set([...(prior?.seeded ?? []), ...ownedSeeded])].sort(),
|
|
278
476
|
};
|
|
279
477
|
writeFileAtomic(t.stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
280
478
|
log(`HQ installed for Claude Code from ${hqRoot}.`);
|
|
281
479
|
log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
|
|
282
|
-
|
|
283
|
-
|
|
480
|
+
if (pluginSource)
|
|
481
|
+
log(` plugin: ${pluginSource.pluginId} installed from ${pluginSource.sourceDir}; direct hooks and MCP entries removed`);
|
|
482
|
+
else {
|
|
483
|
+
log(` hooks: ${hookTarget === 'shim' ? 'hqd-hook-shim.sh' : 'master-hook.sh'} registered in ${t.settingsJson}`);
|
|
484
|
+
log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.claudeJson}`);
|
|
485
|
+
}
|
|
284
486
|
log(` registry: ${seeded.length} manifest entr${seeded.length === 1 ? 'y' : 'ies'} seeded`);
|
|
285
487
|
return touched;
|
|
286
488
|
}
|
|
@@ -294,6 +496,13 @@ export function uninstallGlobalClaude(opts = {}) {
|
|
|
294
496
|
const hqRoot = state?.hqRoot ?? opts.hqRoot ?? readOrNull(t.hqPointer)?.trim();
|
|
295
497
|
if (!hqRoot)
|
|
296
498
|
throw new Error('No HQ global install found (no install state and no ~/.hq/root).');
|
|
499
|
+
const pluginSource = state?.plugin ? {
|
|
500
|
+
sourceDir: state.plugin.sourceDir,
|
|
501
|
+
marketplaceName: state.plugin.marketplaceName,
|
|
502
|
+
pluginName: state.plugin.pluginId.slice(0, state.plugin.pluginId.lastIndexOf('@')),
|
|
503
|
+
pluginId: state.plugin.pluginId,
|
|
504
|
+
} : undefined;
|
|
505
|
+
const runClaude = opts.runClaude ?? runClaudeCli;
|
|
297
506
|
const regFile = opts.registryFile ?? registryFile(registryDir(home, env));
|
|
298
507
|
const touched = [];
|
|
299
508
|
// MCP server (provenance-scoped: a foreign "hq" server is never removed).
|
|
@@ -304,7 +513,9 @@ export function uninstallGlobalClaude(opts = {}) {
|
|
|
304
513
|
// settings.json hooks.
|
|
305
514
|
const settingsOriginal = state ? state.settings.original : null;
|
|
306
515
|
const settingsDoc = readConfigDoc(t.settingsJson, jsonFormat).doc;
|
|
307
|
-
const settingsNext =
|
|
516
|
+
const settingsNext = pluginSource
|
|
517
|
+
? removePluginSettings(removeHqHooks(settingsDoc, hqRoot), pluginSource, state?.plugin?.pluginAdded ?? true, state?.plugin?.marketplaceAdded ?? true, state?.plugin?.pluginSettingBefore)
|
|
518
|
+
: removeHqHooks(settingsDoc, hqRoot);
|
|
308
519
|
if (settingsNext !== settingsDoc)
|
|
309
520
|
touched.push(t.settingsJson);
|
|
310
521
|
// Skills: only symlinks that point into this HQ checkout's skills dir.
|
|
@@ -349,15 +560,27 @@ export function uninstallGlobalClaude(opts = {}) {
|
|
|
349
560
|
log(` ${f}`);
|
|
350
561
|
log(`\n--- ${t.settingsJson}`);
|
|
351
562
|
log(lineDiff(jsonFormat.serialize(settingsDoc), jsonFormat.serialize(settingsNext)));
|
|
563
|
+
if (pluginSource)
|
|
564
|
+
log(`Would run: ${state?.plugin?.pluginAdded ? `claude plugin uninstall --scope user ${pluginSource.pluginId}` : '(plugin was pre-existing; leave it installed)'}${state?.plugin?.marketplaceAdded ? `; claude plugin marketplace remove --scope user ${pluginSource.marketplaceName}` : ''}`);
|
|
352
565
|
return touched;
|
|
353
566
|
}
|
|
567
|
+
if (pluginSource && state?.plugin?.pluginAdded) {
|
|
568
|
+
runClaude(['uninstall', '--scope', 'user', '--json', pluginSource.pluginId], home, env);
|
|
569
|
+
}
|
|
570
|
+
if (pluginSource && state?.plugin?.marketplaceAdded) {
|
|
571
|
+
runClaude(['marketplace', 'remove', '--scope', 'user', '--json', pluginSource.marketplaceName], home, env);
|
|
572
|
+
}
|
|
354
573
|
unregisterClaudeServer({ name: HQ_MCP_SERVER_NAME, pack: HQ_ANYWHERE_PACK, env: { home } });
|
|
355
574
|
if (fs.existsSync(t.settingsJson)) {
|
|
356
575
|
writeConfigAtomic({
|
|
357
576
|
target: t.settingsJson,
|
|
358
577
|
pack: HQ_ANYWHERE_PACK,
|
|
359
|
-
format:
|
|
360
|
-
|
|
578
|
+
format: restoringRemovalFormat(settingsOriginal, (doc) => (pluginSource
|
|
579
|
+
? removePluginSettings(removeHqHooks(doc, hqRoot), pluginSource, state?.plugin?.pluginAdded ?? true, state?.plugin?.marketplaceAdded ?? true, state?.plugin?.pluginSettingBefore) !== doc
|
|
580
|
+
: removeHqHooks(doc, hqRoot) !== doc)),
|
|
581
|
+
merge: (doc) => pluginSource
|
|
582
|
+
? removePluginSettings(removeHqHooks(doc, hqRoot), pluginSource, state?.plugin?.pluginAdded ?? true, state?.plugin?.marketplaceAdded ?? true, state?.plugin?.pluginSettingBefore)
|
|
583
|
+
: removeHqHooks(doc, hqRoot),
|
|
361
584
|
assertName: 'hooks',
|
|
362
585
|
env: { home },
|
|
363
586
|
});
|
|
@@ -393,7 +616,7 @@ export function uninstallGlobalClaude(opts = {}) {
|
|
|
393
616
|
throw e;
|
|
394
617
|
}
|
|
395
618
|
}
|
|
396
|
-
log(`HQ removed from Claude Code user scope (${links.length} skill link(s), hooks
|
|
619
|
+
log(`HQ removed from Claude Code user scope (${links.length} skill link(s), ${pluginSource ? 'plugin' : 'hooks and MCP server'}, CLAUDE.md block).`);
|
|
397
620
|
return touched;
|
|
398
621
|
}
|
|
399
622
|
function readCodexState(t) {
|
|
@@ -686,8 +909,8 @@ export function uninstallGlobalCodex(opts = {}) {
|
|
|
686
909
|
}
|
|
687
910
|
function assertRuntime(runtime) {
|
|
688
911
|
const r = runtime ?? 'claude';
|
|
689
|
-
if (r !== 'claude' && r !== 'codex') {
|
|
690
|
-
throw Object.assign(new Error(`--runtime ${r} is not supported. Use --runtime claude or
|
|
912
|
+
if (r !== 'claude' && r !== 'codex' && r !== 'chatgpt' && r !== 'grok') {
|
|
913
|
+
throw Object.assign(new Error(`--runtime ${r} is not supported. Use --runtime claude, codex, chatgpt, or grok.`), {
|
|
691
914
|
expected: true,
|
|
692
915
|
});
|
|
693
916
|
}
|
|
@@ -704,8 +927,17 @@ function resolveInstallRoot(explicit) {
|
|
|
704
927
|
/** Entry used by `hq install --global` (wired from pkg-install.ts). */
|
|
705
928
|
export function runInstallGlobal(opts) {
|
|
706
929
|
const runtime = assertRuntime(opts.runtime);
|
|
930
|
+
if (runtime === 'chatgpt' || runtime === 'grok') {
|
|
931
|
+
if (!anywhereRuntimeEnabled(opts.env ?? process.env))
|
|
932
|
+
throw new AnywhereRuntimeDisabledError();
|
|
933
|
+
const endpoint = resolveHqMcpEndpoint(opts.env ?? process.env).toString();
|
|
934
|
+
const lines = runtime === 'chatgpt' ? chatgptConnectorInstructions(endpoint) : grokConnectorInstructions(endpoint);
|
|
935
|
+
for (const line of lines)
|
|
936
|
+
(opts.log ?? console.log)(line);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
707
939
|
const install = runtime === 'codex' ? installGlobalCodex : installGlobalClaude;
|
|
708
|
-
install({ hqRoot: resolveInstallRoot(opts.hqRoot), dryRun: opts.dryRun });
|
|
940
|
+
install({ hqRoot: resolveInstallRoot(opts.hqRoot), dryRun: opts.dryRun, via: opts.via, marketplaceDir: opts.marketplaceDir });
|
|
709
941
|
}
|
|
710
942
|
export function registerUninstallCommand(program) {
|
|
711
943
|
program
|
|
@@ -721,7 +953,10 @@ export function registerUninstallCommand(program) {
|
|
|
721
953
|
process.exitCode = 1;
|
|
722
954
|
return;
|
|
723
955
|
}
|
|
724
|
-
const runtime =
|
|
956
|
+
const runtime = opts.runtime ?? 'claude';
|
|
957
|
+
if (runtime !== 'claude' && runtime !== 'codex') {
|
|
958
|
+
throw Object.assign(new Error(`--runtime ${runtime} has no local configuration to uninstall; remove its connector in the host application.`), { expected: true });
|
|
959
|
+
}
|
|
725
960
|
const uninstall = runtime === 'codex' ? uninstallGlobalCodex : uninstallGlobalClaude;
|
|
726
961
|
uninstall({ hqRoot: opts.hqRoot, dryRun: opts.dryRun });
|
|
727
962
|
});
|
|
@@ -15,7 +15,7 @@ import * as fs from "node:fs";
|
|
|
15
15
|
import * as os from "node:os";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { CLI_VERSION } from "../cli-version.js";
|
|
18
|
-
import { HQ_ANYWHERE_RUNTIME_FLAG } from "
|
|
18
|
+
import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
|
|
19
19
|
import { anywhereRuntimeEnabledFromSnapshot } from "../lib/anywhere-runtime-fast-flag.js";
|
|
20
20
|
import { createLineSplitter, HqdClientError, hqdRequest, hqdSocketPath } from "../lib/daemon/protocol.js";
|
|
21
21
|
export const MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import * as fs from 'fs';
|
|
25
25
|
import * as path from 'path';
|
|
26
26
|
import { anywhereRuntimeEnabled } from './anywhere-runtime-flag.js';
|
|
27
|
+
import { claudeTargets } from '../lib/install/claude-targets.js';
|
|
27
28
|
import { HQ_PACK_PROVENANCE_KEY, SECRET_REDACTION, backupRoot, claudeConfigFormat, claudeConfigPath, codexConfigFormat, codexConfigPath, CODEX_MCP_SERVERS_KEY, ConfigParseError, isCodexInstalled, readConfigDoc, resolveEnv, } from './mcp-registration.js';
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Pure status computation.
|
|
@@ -84,6 +85,49 @@ function summarizeDef(def) {
|
|
|
84
85
|
function summariesDiffer(a, b) {
|
|
85
86
|
return JSON.stringify(a) !== JSON.stringify(b);
|
|
86
87
|
}
|
|
88
|
+
function findAnywherePluginMcp(home) {
|
|
89
|
+
const claude = path.join(home, '.claude');
|
|
90
|
+
try {
|
|
91
|
+
const targets = claudeTargets(home);
|
|
92
|
+
const state = JSON.parse(fs.readFileSync(targets.stateFile, 'utf8'));
|
|
93
|
+
const pluginId = state.plugin?.pluginId;
|
|
94
|
+
if (typeof pluginId !== 'string' || pluginId.length === 0)
|
|
95
|
+
return undefined;
|
|
96
|
+
const settings = JSON.parse(fs.readFileSync(targets.settingsJson, 'utf8'));
|
|
97
|
+
if (settings.enabledPlugins?.[pluginId] !== true)
|
|
98
|
+
return undefined;
|
|
99
|
+
const installedFile = path.join(claude, 'plugins', 'installed_plugins.json');
|
|
100
|
+
const installed = JSON.parse(fs.readFileSync(installedFile, 'utf8'));
|
|
101
|
+
const install = installed.plugins?.[pluginId]?.find((entry) => typeof entry.installPath === 'string');
|
|
102
|
+
if (!install || typeof install.installPath !== 'string' || !path.isAbsolute(install.installPath))
|
|
103
|
+
return undefined;
|
|
104
|
+
const installDir = path.resolve(install.installPath);
|
|
105
|
+
const cacheRoot = path.resolve(claude, 'plugins', 'cache');
|
|
106
|
+
const cacheRelative = path.relative(cacheRoot, installDir);
|
|
107
|
+
if (cacheRelative.startsWith('..') || path.isAbsolute(cacheRelative) || !cacheRelative.split(path.sep).includes('hq'))
|
|
108
|
+
return undefined;
|
|
109
|
+
const file = path.join(installDir, '.mcp.json');
|
|
110
|
+
const stat = fs.lstatSync(file);
|
|
111
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
112
|
+
return undefined;
|
|
113
|
+
const doc = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
114
|
+
const server = doc.mcpServers?.hq;
|
|
115
|
+
return server && typeof server === 'object' && !Array.isArray(server) ? server : undefined;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error.code === 'ENOENT')
|
|
119
|
+
return undefined;
|
|
120
|
+
console.warn(`Skipping unavailable hq-anywhere plugin MCP metadata: ${error.message}`);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function pluginAndCodexMcpEquivalent(plugin, codex) {
|
|
125
|
+
const pArgs = Array.isArray(plugin.args) ? plugin.args : [];
|
|
126
|
+
const cArgs = Array.isArray(codex.args) ? codex.args : [];
|
|
127
|
+
const pluginTarget = plugin.type === 'stdio' && plugin.command === '/bin/sh' && pArgs.length === 3 && pArgs[0] === '${CLAUDE_PLUGIN_ROOT}/scripts/hq-claude-plugin-launch.sh' && pArgs[1] === 'mcp' && pArgs[2] === 'serve';
|
|
128
|
+
const codexTarget = codex.type === 'stdio' && typeof codex.command === 'string' && /(^|\/)hq$/.test(codex.command) && cArgs.length === 2 && cArgs[0] === 'mcp' && cArgs[1] === 'serve';
|
|
129
|
+
return pluginTarget && codexTarget && JSON.stringify(headerKeysOf(plugin)) === JSON.stringify(headerKeysOf(codex)) && JSON.stringify(envKeysOf(plugin)) === JSON.stringify(envKeysOf(codex));
|
|
130
|
+
}
|
|
87
131
|
/** Scan the Claude runtime: read `~/.claude.json`, collect HQ-owned servers. */
|
|
88
132
|
function scanClaude(env) {
|
|
89
133
|
const configPath = claudeConfigPath(env);
|
|
@@ -201,6 +245,17 @@ export function computeMcpStatus(env) {
|
|
|
201
245
|
const checkHealth = anywhereRuntimeEnabled();
|
|
202
246
|
const claudeScan = scanClaude(env);
|
|
203
247
|
const codexScan = scanCodex(env);
|
|
248
|
+
// Claude plugins keep their MCP manifest beside the installed plugin rather
|
|
249
|
+
// than in ~/.claude.json. Pair the HQ plugin with the HQ pack's Codex entry
|
|
250
|
+
// so equivalent launchers do not show as absent or drifted.
|
|
251
|
+
const pluginMcp = checkHealth ? findAnywherePluginMcp(env.home) : undefined;
|
|
252
|
+
if (pluginMcp) {
|
|
253
|
+
for (const [name, found] of codexScan.hqServers) {
|
|
254
|
+
if (found.pack === 'hq-anywhere' || found.pack.endsWith('/hq-anywhere')) {
|
|
255
|
+
claudeScan.hqServers.set(name, { def: pluginMcp, pack: found.pack });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
204
259
|
// Union of every HQ-owned server name across both runtimes.
|
|
205
260
|
const names = new Set([...claudeScan.hqServers.keys(), ...codexScan.hqServers.keys()]);
|
|
206
261
|
const servers = [];
|
|
@@ -232,7 +287,9 @@ export function computeMcpStatus(env) {
|
|
|
232
287
|
// DRIFT = present in both but transport-relevant content differs (no header VALUES).
|
|
233
288
|
let drifted = false;
|
|
234
289
|
if (onClaude && onCodex && claudeFound && codexFound) {
|
|
235
|
-
drifted =
|
|
290
|
+
drifted = pluginMcp && claudeFound.def === pluginMcp && pluginAndCodexMcpEquivalent(pluginMcp, codexFound.def)
|
|
291
|
+
? false
|
|
292
|
+
: summariesDiffer(summarizeDef(claudeFound.def), summarizeDef(codexFound.def));
|
|
236
293
|
}
|
|
237
294
|
servers.push({
|
|
238
295
|
server: name,
|
|
@@ -278,7 +278,9 @@ export declare function verifyArtifact(input: VerifyArtifactInput): void;
|
|
|
278
278
|
* @param item the bare server name declared under `contributes.mcp`
|
|
279
279
|
*/
|
|
280
280
|
export declare function validateMcpManifest(payloadDir: string, item: string): void;
|
|
281
|
-
export declare function validateManifest(payloadDir: string, hqVersion: string | null
|
|
281
|
+
export declare function validateManifest(payloadDir: string, hqVersion: string | null, options?: {
|
|
282
|
+
allowReservedName?: boolean;
|
|
283
|
+
}): PackManifest;
|
|
282
284
|
/**
|
|
283
285
|
* Enforce a pack's `requires.packs`: every named dependency MUST already be
|
|
284
286
|
* installed (and satisfy its optional semver RANGE) before we write anything.
|