@indigoai-us/hq-cli 5.328.0 → 5.330.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/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/cloud.d.ts +5 -0
- package/dist/commands/cloud.js +51 -1
- package/dist/commands/install-global.d.ts +21 -0
- package/dist/commands/install-global.js +267 -32
- package/dist/commands/lanes-message.js +59 -6
- package/dist/commands/lanes-resume.js +66 -15
- package/dist/commands/lanes-shared.d.ts +9 -2
- package/dist/commands/lanes.js +97 -8
- 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/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/keep-alive-audit.d.ts +3 -2
- package/dist/lib/lanes/keep-alive-audit.js +36 -8
- package/dist/lib/lanes/spawn.js +14 -10
- package/dist/lib/lanes/store.js +29 -2
- package/dist/lib/lanes/types.d.ts +10 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/sync-invalid-entity-warning.d.ts +32 -0
- package/dist/lib/sync-invalid-entity-warning.js +205 -0
- package/dist/main.d.ts +2 -6
- package/dist/main.js +3 -41
- package/dist/stream-error-boundary.d.ts +7 -0
- package/dist/stream-error-boundary.js +33 -0
- 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
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -239,6 +239,11 @@ export interface ShareCallResult {
|
|
|
239
239
|
export interface PushAllDeps {
|
|
240
240
|
vaultClient: PullAllVaultClient;
|
|
241
241
|
share: (options: ShareCallOptions) => Promise<ShareCallResult>;
|
|
242
|
+
beforeShare?: (input: {
|
|
243
|
+
slug: string;
|
|
244
|
+
companyUid: string;
|
|
245
|
+
companyRoot: string;
|
|
246
|
+
}) => Promise<void>;
|
|
242
247
|
/** DEV-1768: resolve membership push scope (same as pull). */
|
|
243
248
|
resolveScope?: (companyUid: string, slug: string) => Promise<PullScope>;
|
|
244
249
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -17,10 +17,11 @@ import chalk from "chalk";
|
|
|
17
17
|
import * as fs from "fs";
|
|
18
18
|
import * as path from "path";
|
|
19
19
|
import { share, sync, getStateDir, listJournals, VaultClient, computePersonalVaultPaths, PERSONAL_VAULT_JOURNAL_SLUG, resolvePullScope, syncDoctor, } from "@indigoai-us/hq-cloud";
|
|
20
|
-
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
20
|
+
import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
21
21
|
import { resolveUploadAuthor } from "../utils/upload-author.js";
|
|
22
22
|
import { companyFolderExceedsThreshold, emitNarrowHint, isStrictRefusal, resolveBannerLevel, resolveNarrowHintPresentationLevel, resolveNarrowHintMinBytes, } from "../lib/narrow-hint-banner.js";
|
|
23
23
|
import { beginSyncHealthReport, } from "../utils/client-health.js";
|
|
24
|
+
import { companySlugFromSyncPaths, warnAboutInvalidOntologyEntities, } from "../lib/sync-invalid-entity-warning.js";
|
|
24
25
|
import { isWorkBearingSyncResult, recordFirstWorkBearingSync, } from "../lib/first-sync-next-step.js";
|
|
25
26
|
import { maybeShowCliInviteNudge } from "../lib/cli-invite-nudge.js";
|
|
26
27
|
import { createPlanLimitRefusalTracker, describeRefusedCount, describeRefusedFiles, mergePlanLimitRefusals, renderPlanLimitRefusalLines, trackingProgressRecorder, } from "../lib/billing/plan-limit-refusals.js";
|
|
@@ -504,6 +505,22 @@ export async function pushAll(options, deps) {
|
|
|
504
505
|
}
|
|
505
506
|
}
|
|
506
507
|
try {
|
|
508
|
+
if (entry.slug !== "personal") {
|
|
509
|
+
try {
|
|
510
|
+
await deps.beforeShare?.({
|
|
511
|
+
slug: entry.slug,
|
|
512
|
+
companyUid: entry.companyUid,
|
|
513
|
+
companyRoot: path.join(options.hqRoot, "companies", entry.slug),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const name = error instanceof Error ? error.name : "UnknownError";
|
|
518
|
+
const errorClass = /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(name)
|
|
519
|
+
? name
|
|
520
|
+
: "UnknownError";
|
|
521
|
+
process.stderr.write(`HQ Sync entity diagnostic failed (${errorClass}); continuing upload.\n`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
507
524
|
const r = await deps.share(entry.shareOptions);
|
|
508
525
|
result.filesUploaded += r.filesUploaded;
|
|
509
526
|
result.bytesUploaded += r.bytesUploaded;
|
|
@@ -926,6 +943,20 @@ export function registerCloudCommands(program) {
|
|
|
926
943
|
});
|
|
927
944
|
const planLimitTracker = createPlanLimitRefusalTracker(companySlugForBilling(progressCompany));
|
|
928
945
|
syncHealth = beginSyncHealthReport();
|
|
946
|
+
if (!personalMode) {
|
|
947
|
+
const companyUid = entityContext?.uid ??
|
|
948
|
+
(targetCompany?.startsWith("cmp_") ? targetCompany : "");
|
|
949
|
+
const companySlug = companySlugFromSyncPaths(options.hqRoot, targetPaths);
|
|
950
|
+
if (companySlug) {
|
|
951
|
+
await warnAboutInvalidOntologyEntities({
|
|
952
|
+
hqRoot: options.hqRoot,
|
|
953
|
+
companyRoot: path.join(options.hqRoot, "companies", companySlug),
|
|
954
|
+
companyUid,
|
|
955
|
+
companySlug,
|
|
956
|
+
idToken: loadCachedTokens()?.idToken ?? "",
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
929
960
|
const result = await share({
|
|
930
961
|
paths: targetPaths,
|
|
931
962
|
company: targetCompany,
|
|
@@ -1541,6 +1572,13 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
|
|
|
1541
1572
|
}, {
|
|
1542
1573
|
vaultClient: adapter,
|
|
1543
1574
|
resolveScope: (companyUid, slug) => resolvePullScope(realClient, companyUid, slug, hqRoot),
|
|
1575
|
+
beforeShare: async ({ slug, companyUid, companyRoot }) => warnAboutInvalidOntologyEntities({
|
|
1576
|
+
hqRoot,
|
|
1577
|
+
companyRoot,
|
|
1578
|
+
companyUid,
|
|
1579
|
+
companySlug: slug,
|
|
1580
|
+
idToken: loadCachedTokens()?.idToken ?? "",
|
|
1581
|
+
}),
|
|
1544
1582
|
share: async (opts) => {
|
|
1545
1583
|
const progressCompany = opts.personalMode
|
|
1546
1584
|
? null
|
|
@@ -1692,6 +1730,18 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1692
1730
|
const pushScope = await resolveCliPullScope(scopeClient, targetCompany ?? pushSlug, hqRoot, (companyUid) => { nudgeResolvedCompanyUid = companyUid; });
|
|
1693
1731
|
nowPushPrefixSet = pushScope?.prefixSet;
|
|
1694
1732
|
}
|
|
1733
|
+
if (!personalMode) {
|
|
1734
|
+
const companySlug = companySlugFromSyncPaths(hqRoot, pushPaths);
|
|
1735
|
+
if (companySlug) {
|
|
1736
|
+
await warnAboutInvalidOntologyEntities({
|
|
1737
|
+
hqRoot,
|
|
1738
|
+
companyRoot: path.join(hqRoot, "companies", companySlug),
|
|
1739
|
+
companyUid: targetCompany?.startsWith("cmp_") ? targetCompany : "",
|
|
1740
|
+
companySlug,
|
|
1741
|
+
idToken: loadCachedTokens()?.idToken ?? "",
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1695
1745
|
const pushProgressCompany = personalMode ? null : (pushSlug ?? null);
|
|
1696
1746
|
const planLimitTracker = createPlanLimitRefusalTracker(companySlugForBilling(pushProgressCompany));
|
|
1697
1747
|
const pushResult = await share({
|
|
@@ -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
|
});
|