@indigoai-us/hq-cli 5.345.41 → 5.345.43
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 +12 -0
- package/dist/command-catalog.generated.d.ts +7 -3
- package/dist/command-catalog.generated.js +8 -3
- package/dist/commands/db-remote-deps.js +3 -0
- package/dist/commands/db-usage.js +11 -4
- package/dist/commands/install-global.d.ts +8 -1
- package/dist/commands/install-global.js +128 -25
- package/dist/commands/lanes-message.js +1 -0
- package/dist/commands/lanes-resume.js +3 -0
- package/dist/commands/lanes.js +203 -44
- package/dist/lib/anywhere-person-setting.d.ts +2 -0
- package/dist/lib/anywhere-person-setting.js +2 -1
- package/dist/lib/anywhere-runtime-fast-flag.js +8 -2
- package/dist/lib/daemon/anywhere-listener.d.ts +16 -0
- package/dist/lib/daemon/anywhere-listener.js +79 -0
- package/dist/lib/daemon/run.js +30 -23
- package/dist/lib/db/control-plane.d.ts +46 -0
- package/dist/lib/db/control-plane.js +14 -0
- package/dist/lib/db/usage-format.d.ts +4 -1
- package/dist/lib/db/usage-format.js +66 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +10 -0
- package/dist/lib/doctor/checks/anywhere.js +45 -17
- package/dist/lib/flag-registry.d.ts +4 -0
- package/dist/lib/flag-registry.js +43 -2
- package/dist/lib/flag-snapshot-file.d.ts +17 -0
- package/dist/lib/flag-snapshot-file.js +153 -37
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/types.d.ts +1 -1
- package/dist/utils/resident-package-preload.js +5 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.43] — 2026-10-07
|
|
6
|
+
- The published core-command E2E workflow now retries the exact npm release install for up to 10 minutes while registry propagation completes.
|
|
7
|
+
|
|
8
|
+
- HQ Anywhere resolves person-level flags in unlinked folders. The daemon refreshes the flag and person setting every 60 seconds, then opens or closes its socket when either changes.
|
|
9
|
+
- Claude and Codex global installs use one skill prefix, keep company skills out of global scope, remove old company links during upgrades, and skip broken sources. Dry runs use `~/.hq/root` when run outside a checkout.
|
|
10
|
+
- Doctor checks for a live desktop-hosted daemon instead of treating the sync LaunchAgent as proof. Registry refreshes retain the daemon's machine scope and ID token.
|
|
11
|
+
- Let a failed lane's ancestors or same-company manager resume it, and backfill missing company attribution when reparenting it to a company senior.
|
|
12
|
+
- Let Claude lanes fall back to eligible accounts when every profile-pinned account is unavailable, behind the default-off `lanes.profile-pin-exhausted-fallback` flag.
|
|
13
|
+
|
|
14
|
+
## [5.345.42] — 2026-10-07
|
|
15
|
+
- `hq db usage --app <name>` shows one database app's included amounts, use, overage, read-only ceiling and estimated monthly charge.
|
|
16
|
+
|
|
5
17
|
## [5.345.41] — 2026-10-07
|
|
6
18
|
- Short-lived hq commands now use a fresh cached company flag snapshot while the registry refresh is pending.
|
|
7
19
|
- Report failed and quota-interrupted keep-alive lead lanes as stranded in the keep-alive audit and `hq doctor`, with the senior recovery command and quota reset time.
|
|
@@ -2012,7 +2012,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2012
2012
|
readonly subcommands: readonly [];
|
|
2013
2013
|
}, {
|
|
2014
2014
|
readonly name: "usage";
|
|
2015
|
-
readonly description: "Show this month's remote database usage
|
|
2015
|
+
readonly description: "Show this month's remote database usage. With --app: included, used, overage, ceiling and the estimated charge for one app";
|
|
2016
2016
|
readonly aliases: readonly [];
|
|
2017
2017
|
readonly hidden: false;
|
|
2018
2018
|
readonly helpOption: true;
|
|
@@ -2024,6 +2024,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
2024
2024
|
readonly flags: "--company <slug>";
|
|
2025
2025
|
readonly description: "Company slug";
|
|
2026
2026
|
readonly hidden: false;
|
|
2027
|
+
}, {
|
|
2028
|
+
readonly flags: "--app <name>";
|
|
2029
|
+
readonly description: "Show one app: included amounts, use, overage, ceiling and estimated charge";
|
|
2030
|
+
readonly hidden: false;
|
|
2027
2031
|
}];
|
|
2028
2032
|
readonly subcommands: readonly [];
|
|
2029
2033
|
}, {
|
|
@@ -12460,7 +12464,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
12460
12464
|
readonly subcommands: readonly [];
|
|
12461
12465
|
}, {
|
|
12462
12466
|
readonly name: "resume";
|
|
12463
|
-
readonly description: "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with
|
|
12467
|
+
readonly description: "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with --from-failed as a senior ancestor or same-company manager";
|
|
12464
12468
|
readonly aliases: readonly [];
|
|
12465
12469
|
readonly hidden: false;
|
|
12466
12470
|
readonly helpOption: true;
|
|
@@ -12479,7 +12483,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
12479
12483
|
readonly hidden: false;
|
|
12480
12484
|
}, {
|
|
12481
12485
|
readonly flags: "--from-failed";
|
|
12482
|
-
readonly description: "Explicitly resume a failed lane as
|
|
12486
|
+
readonly description: "Explicitly resume a failed lane as an ancestor or same-company manager";
|
|
12483
12487
|
readonly hidden: false;
|
|
12484
12488
|
}, {
|
|
12485
12489
|
readonly flags: "--reason <text>";
|
|
@@ -2414,7 +2414,7 @@ export const COMMAND_CATALOG = [
|
|
|
2414
2414
|
},
|
|
2415
2415
|
{
|
|
2416
2416
|
"name": "usage",
|
|
2417
|
-
"description": "Show this month's remote database usage
|
|
2417
|
+
"description": "Show this month's remote database usage. With --app: included, used, overage, ceiling and the estimated charge for one app",
|
|
2418
2418
|
"aliases": [],
|
|
2419
2419
|
"hidden": false,
|
|
2420
2420
|
"helpOption": true,
|
|
@@ -2427,6 +2427,11 @@ export const COMMAND_CATALOG = [
|
|
|
2427
2427
|
"flags": "--company <slug>",
|
|
2428
2428
|
"description": "Company slug",
|
|
2429
2429
|
"hidden": false
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
"flags": "--app <name>",
|
|
2433
|
+
"description": "Show one app: included amounts, use, overage, ceiling and estimated charge",
|
|
2434
|
+
"hidden": false
|
|
2430
2435
|
}
|
|
2431
2436
|
],
|
|
2432
2437
|
"subcommands": []
|
|
@@ -15151,7 +15156,7 @@ export const COMMAND_CATALOG = [
|
|
|
15151
15156
|
},
|
|
15152
15157
|
{
|
|
15153
15158
|
"name": "resume",
|
|
15154
|
-
"description": "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with
|
|
15159
|
+
"description": "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with --from-failed as a senior ancestor or same-company manager",
|
|
15155
15160
|
"aliases": [],
|
|
15156
15161
|
"hidden": false,
|
|
15157
15162
|
"helpOption": true,
|
|
@@ -15174,7 +15179,7 @@ export const COMMAND_CATALOG = [
|
|
|
15174
15179
|
},
|
|
15175
15180
|
{
|
|
15176
15181
|
"flags": "--from-failed",
|
|
15177
|
-
"description": "Explicitly resume a failed lane as
|
|
15182
|
+
"description": "Explicitly resume a failed lane as an ancestor or same-company manager",
|
|
15178
15183
|
"hidden": false
|
|
15179
15184
|
},
|
|
15180
15185
|
{
|
|
@@ -54,6 +54,9 @@ export function describeRemoteError(error) {
|
|
|
54
54
|
if (status === 404 && code === "DB_USAGE_NOT_PROVISIONED") {
|
|
55
55
|
return "This company has no remote database yet. Run hq db provision first.";
|
|
56
56
|
}
|
|
57
|
+
if (status === 404 && code === "DB_USAGE_APP_NOT_FOUND") {
|
|
58
|
+
return "No database found for that app in this company. Check the app name, or deploy it with database enabled.";
|
|
59
|
+
}
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
62
|
//# sourceMappingURL=db-remote-deps.js.map
|
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { safeErrorMessage } from "../lib/db/redact.js";
|
|
6
|
-
import { formatUsage } from "../lib/db/usage-format.js";
|
|
6
|
+
import { formatAppUsageResponse, formatUsage } from "../lib/db/usage-format.js";
|
|
7
7
|
import { isPlanGateError } from "../utils/plan-gate-error.js";
|
|
8
8
|
import { controlPlaneClient, defaultRemoteDeps, describeRemoteError, } from "./db-remote-deps.js";
|
|
9
9
|
export function registerDbUsageCommand(db, depsFactory = defaultRemoteDeps) {
|
|
10
10
|
db.command("usage")
|
|
11
|
-
.description("Show this month's remote database usage
|
|
11
|
+
.description("Show this month's remote database usage. With --app: included, used, overage, ceiling and the estimated charge for one app")
|
|
12
12
|
.requiredOption("--company <slug>", "Company slug")
|
|
13
|
+
.option("--app <name>", "Show one app: included amounts, use, overage, ceiling and estimated charge")
|
|
13
14
|
.action(async (opts) => {
|
|
14
15
|
try {
|
|
15
16
|
const deps = depsFactory();
|
|
16
17
|
const resolved = await deps.resolveCompany(String(opts.company ?? "").trim());
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const client = controlPlaneClient(deps);
|
|
19
|
+
const app = String(opts.app ?? "").trim();
|
|
20
|
+
if (app) {
|
|
21
|
+
console.log(formatAppUsageResponse(await client.appUsage(resolved.companyUid, app)));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(formatUsage(await client.usage(resolved.companyUid)));
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
27
|
catch (error) {
|
|
21
28
|
if (isPlanGateError(error))
|
|
@@ -75,8 +75,15 @@ export interface GlobalCommandOpts {
|
|
|
75
75
|
log?: (line: string) => void;
|
|
76
76
|
/** Test seam for the hq-pro person preference read. */
|
|
77
77
|
personSettingReader?: () => Promise<boolean | null>;
|
|
78
|
+
/** Root-discovery seams used by command tests and wrappers. */
|
|
79
|
+
home?: string;
|
|
80
|
+
cwd?: string;
|
|
78
81
|
}
|
|
79
|
-
export declare function resolveInstallRoot(explicit: string | undefined
|
|
82
|
+
export declare function resolveInstallRoot(explicit: string | undefined, options?: {
|
|
83
|
+
env?: NodeJS.ProcessEnv;
|
|
84
|
+
cwd?: string;
|
|
85
|
+
home?: string;
|
|
86
|
+
}): string;
|
|
80
87
|
/** Entry used by `hq install --global` (wired from pkg-install.ts). */
|
|
81
88
|
export declare function runInstallGlobal(opts: GlobalCommandOpts): Promise<void>;
|
|
82
89
|
export declare function registerUninstallCommand(program: Command): void;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* ~/.hq/anywhere/claude-install.json so uninstall can restore files byte-for-byte.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
12
13
|
import * as path from 'path';
|
|
13
14
|
import { spawnSync } from 'node:child_process';
|
|
14
15
|
import { buildCodexServerDef, claudeConfigFormat, codexConfigFormat, codexRemovalFormat, isCodexInstalled, jsonFormat, mergeCodexServer, removeCodexServer, registerCodexServer, unregisterCodexServer, mergeClaudeServer, readConfigDoc, realpathOrSelf, registerClaudeServer, resolveEnv, unregisterClaudeServer, writeConfigAtomic, } from './mcp-registration.js';
|
|
@@ -20,6 +21,8 @@ import { chatgptConnectorInstructions } from '../lib/install/chatgpt-targets.js'
|
|
|
20
21
|
import { anywhereRuntimeAllowed } from '../lib/anywhere-person-setting.js';
|
|
21
22
|
import { grokConnectorInstructions } from '../lib/install/grok-targets.js';
|
|
22
23
|
import { readRegistry, registryDir, registryFile, writeRegistry } from '../lib/registry/registry.js';
|
|
24
|
+
import { readCompaniesManifestMap } from '../lib/work-context/repo-remote.js';
|
|
25
|
+
import { resolveDefaultHqRoot } from '../utils/hq-root-resolver.js';
|
|
23
26
|
import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK, SYSTEM_UNIT_DIR } from '../lib/daemon/install.js';
|
|
24
27
|
import { readDaemonConfig } from '../lib/daemon/config.js';
|
|
25
28
|
import { daemonPaths } from '../lib/daemon/paths.js';
|
|
@@ -201,9 +204,13 @@ function mcpManifest(opts) {
|
|
|
201
204
|
}
|
|
202
205
|
return { type: 'stdio', command, args: [entry, 'mcp', 'serve'], env: { PATH: MCP_SUBPROCESS_PATH } };
|
|
203
206
|
}
|
|
207
|
+
function skillLinkName(name) {
|
|
208
|
+
return name.startsWith(SKILL_LINK_PREFIX) ? name : `${SKILL_LINK_PREFIX}${name}`;
|
|
209
|
+
}
|
|
204
210
|
function planSkills(hqRoot, t) {
|
|
205
211
|
const source = hqSkillsSource(hqRoot);
|
|
206
|
-
const plan = { link: [], already: [], skipped: [] };
|
|
212
|
+
const plan = { link: [], already: [], skipped: [], unresolved: [], companyScoped: [] };
|
|
213
|
+
const companySlugs = new Set(Object.keys(readCompaniesManifestMap(hqRoot) ?? {}));
|
|
207
214
|
let names;
|
|
208
215
|
try {
|
|
209
216
|
names = fs
|
|
@@ -218,8 +225,23 @@ function planSkills(hqRoot, t) {
|
|
|
218
225
|
throw e;
|
|
219
226
|
}
|
|
220
227
|
for (const name of names) {
|
|
221
|
-
const
|
|
228
|
+
const namespace = name.includes(':') ? name.slice(0, name.indexOf(':')) : '';
|
|
229
|
+
if (namespace && companySlugs.has(namespace)) {
|
|
230
|
+
plan.companyScoped.push(name);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
222
233
|
const target = path.join(source, name);
|
|
234
|
+
try {
|
|
235
|
+
if (!fs.statSync(target).isDirectory()) {
|
|
236
|
+
plan.unresolved.push(name);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
plan.unresolved.push(name);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const link = path.join(t.skillsDir, skillLinkName(name));
|
|
223
245
|
let st = null;
|
|
224
246
|
try {
|
|
225
247
|
st = fs.lstatSync(link);
|
|
@@ -237,6 +259,15 @@ function planSkills(hqRoot, t) {
|
|
|
237
259
|
}
|
|
238
260
|
return plan;
|
|
239
261
|
}
|
|
262
|
+
function reportSkippedSkills(plan, log, dryRun) {
|
|
263
|
+
const prefix = dryRun ? 'Would skip' : 'Skipped';
|
|
264
|
+
if (plan.unresolved.length > 0) {
|
|
265
|
+
log(`${prefix} skill source(s) that do not resolve to directories: ${plan.unresolved.join(', ')}`);
|
|
266
|
+
}
|
|
267
|
+
if (plan.companyScoped.length > 0) {
|
|
268
|
+
log(`${prefix} company-scoped skill(s) at global scope: ${plan.companyScoped.join(', ')}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
240
271
|
function hqSkillLinksForRoot(skillsDir, hqRoot) {
|
|
241
272
|
let names;
|
|
242
273
|
try {
|
|
@@ -261,6 +292,27 @@ function hqSkillLinksForRoot(skillsDir, hqRoot) {
|
|
|
261
292
|
}
|
|
262
293
|
}).map((name) => path.join(skillsDir, name));
|
|
263
294
|
}
|
|
295
|
+
function hqCompanySkillLinksForRoot(skillsDir, hqRoot) {
|
|
296
|
+
const companySlugs = new Set(Object.keys(readCompaniesManifestMap(hqRoot) ?? {}));
|
|
297
|
+
if (companySlugs.size === 0)
|
|
298
|
+
return [];
|
|
299
|
+
const source = hqSkillsSource(hqRoot);
|
|
300
|
+
return hqSkillLinksForRoot(skillsDir, hqRoot).filter((link) => {
|
|
301
|
+
try {
|
|
302
|
+
const target = path.resolve(skillsDir, fs.readlinkSync(link));
|
|
303
|
+
if (path.dirname(target) !== source)
|
|
304
|
+
return false;
|
|
305
|
+
const sourceName = path.basename(target);
|
|
306
|
+
const separator = sourceName.indexOf(':');
|
|
307
|
+
return separator > 0 && companySlugs.has(sourceName.slice(0, separator));
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
if (error.code === 'ENOENT')
|
|
311
|
+
return false;
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
264
316
|
function removeSeeded(seeded, regFile) {
|
|
265
317
|
if (seeded.length === 0 || !fs.existsSync(regFile))
|
|
266
318
|
return;
|
|
@@ -320,7 +372,13 @@ export function installGlobalClaude(opts) {
|
|
|
320
372
|
: mdOld === null ? null : (removeClaudeMdBlock(mdOld) || null))
|
|
321
373
|
: prior ? prior.claudeMd.original : mdOld;
|
|
322
374
|
const mdNew = pluginSource ? mdOriginal : addClaudeMdBlock(mdOld, hqRoot);
|
|
323
|
-
const skills = pluginSource ? {
|
|
375
|
+
const skills = pluginSource ? {
|
|
376
|
+
link: [],
|
|
377
|
+
already: [],
|
|
378
|
+
skipped: [],
|
|
379
|
+
unresolved: [],
|
|
380
|
+
companyScoped: [],
|
|
381
|
+
} : planSkills(hqRoot, t);
|
|
324
382
|
const settingsOld = readOrNull(t.settingsJson);
|
|
325
383
|
const settingsDoc = readConfigDoc(t.settingsJson, jsonFormat).doc;
|
|
326
384
|
const directHooksRemoved = pluginSource
|
|
@@ -347,7 +405,9 @@ export function installGlobalClaude(opts) {
|
|
|
347
405
|
const settingsNext = pluginSource
|
|
348
406
|
? cleanupRoots.reduce((doc, root) => removeHqHooks(doc, root), settingsDoc)
|
|
349
407
|
: addHqHooks(settingsDoc, hqRoot, hookTarget);
|
|
350
|
-
const skillLinksToRemove =
|
|
408
|
+
const skillLinksToRemove = [...new Set(pluginSource
|
|
409
|
+
? cleanupRoots.flatMap((root) => hqSkillLinksForRoot(t.skillsDir, root))
|
|
410
|
+
: cleanupRoots.flatMap((root) => hqCompanySkillLinksForRoot(t.skillsDir, root)))];
|
|
351
411
|
const claudeDoc = readConfigDoc(t.claudeJson, claudeConfigFormat).doc;
|
|
352
412
|
// Collision check runs here too, so a dry run surfaces a conflicting "hq" server.
|
|
353
413
|
const claudeNext = pluginSource ? claudeDoc : mergeClaudeServer(HQ_MCP_SERVER_NAME, {
|
|
@@ -359,7 +419,7 @@ export function installGlobalClaude(opts) {
|
|
|
359
419
|
if (mdOld !== mdNew)
|
|
360
420
|
touched.push(t.claudeMd);
|
|
361
421
|
for (const s of skills.link)
|
|
362
|
-
touched.push(path.join(t.skillsDir,
|
|
422
|
+
touched.push(path.join(t.skillsDir, skillLinkName(s)));
|
|
363
423
|
touched.push(...skillLinksToRemove);
|
|
364
424
|
if (settingsNext !== settingsDoc)
|
|
365
425
|
touched.push(t.settingsJson);
|
|
@@ -374,8 +434,12 @@ export function installGlobalClaude(opts) {
|
|
|
374
434
|
for (const f of touched)
|
|
375
435
|
log(` ${f}`);
|
|
376
436
|
if (skills.skipped.length > 0) {
|
|
377
|
-
log(`Would skip ${skills.skipped.length} skill(s) whose
|
|
437
|
+
log(`Would skip ${skills.skipped.length} skill(s) whose target already exists and is not an HQ link: ${skills.skipped.join(', ')}`);
|
|
378
438
|
}
|
|
439
|
+
if (!pluginSource && skillLinksToRemove.length > 0) {
|
|
440
|
+
log(`Would remove ${skillLinksToRemove.length} previous company-scoped skill link(s)`);
|
|
441
|
+
}
|
|
442
|
+
reportSkippedSkills(skills, log, true);
|
|
379
443
|
log(`\n--- ${t.settingsJson}`);
|
|
380
444
|
log(lineDiff(jsonFormat.serialize(settingsDoc), jsonFormat.serialize(settingsNext)));
|
|
381
445
|
log(`\n--- ${t.claudeJson}`);
|
|
@@ -429,11 +493,10 @@ export function installGlobalClaude(opts) {
|
|
|
429
493
|
if (!pluginSource)
|
|
430
494
|
fs.mkdirSync(t.skillsDir, { recursive: true });
|
|
431
495
|
for (const name of skills.link) {
|
|
432
|
-
fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir,
|
|
496
|
+
fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir, skillLinkName(name)));
|
|
433
497
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
fs.unlinkSync(link);
|
|
498
|
+
for (const link of skillLinksToRemove)
|
|
499
|
+
fs.unlinkSync(link);
|
|
437
500
|
if (pluginSource) {
|
|
438
501
|
// Migrate a prior direct install: remove only our old hook/MCP entries while
|
|
439
502
|
// retaining the original user settings for a later uninstall restore.
|
|
@@ -498,6 +561,7 @@ export function installGlobalClaude(opts) {
|
|
|
498
561
|
writeFileAtomic(t.stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
499
562
|
log(`HQ installed for Claude Code from ${hqRoot}.`);
|
|
500
563
|
log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
|
|
564
|
+
reportSkippedSkills(skills, log, false);
|
|
501
565
|
if (pluginSource)
|
|
502
566
|
log(` plugin: ${pluginSource.pluginId} installed from ${pluginSource.sourceDir}; direct hooks and MCP entries removed`);
|
|
503
567
|
else {
|
|
@@ -691,6 +755,7 @@ export function installGlobalCodex(opts) {
|
|
|
691
755
|
const charter = fs.readFileSync(codexCharterSource(hqRoot), 'utf-8');
|
|
692
756
|
const mdOld = readOrNull(t.agentsMd);
|
|
693
757
|
const mdNew = addAgentsMdBlock(mdOld, charter);
|
|
758
|
+
const cleanupRoots = [...new Set([hqRoot, ...(prior?.hqRoot ? [prior.hqRoot] : [])])];
|
|
694
759
|
const legacy = classifyLegacySkills(t.skillsDir, hqRoot);
|
|
695
760
|
const legacyPath = path.join(t.skillsDir, LEGACY_SKILLS_NAME);
|
|
696
761
|
const legacyBackupPath = path.join(t.skillsDir, LEGACY_BACKUP_NAME);
|
|
@@ -698,6 +763,7 @@ export function installGlobalCodex(opts) {
|
|
|
698
763
|
throw new Error(`${legacyBackupPath} already exists; move it away before installing.`);
|
|
699
764
|
}
|
|
700
765
|
const skills = planSkills(hqRoot, t);
|
|
766
|
+
const companySkillLinksToRemove = [...new Set(cleanupRoots.flatMap((root) => hqCompanySkillLinksForRoot(t.skillsDir, root)))];
|
|
701
767
|
const hooksOld = readOrNull(t.hooksJson);
|
|
702
768
|
const hooksDoc = readConfigDoc(t.hooksJson, jsonFormat).doc;
|
|
703
769
|
const hookTarget = hookTargetFor(hqRoot, home, opts.daemonInstalled, env);
|
|
@@ -719,7 +785,8 @@ export function installGlobalCodex(opts) {
|
|
|
719
785
|
if (legacy.kind === 'hq-symlink' || legacy.kind === 'hq-dir')
|
|
720
786
|
touched.push(path.join(t.skillsDir, LEGACY_SKILLS_NAME));
|
|
721
787
|
for (const s of skills.link)
|
|
722
|
-
touched.push(path.join(t.skillsDir,
|
|
788
|
+
touched.push(path.join(t.skillsDir, skillLinkName(s)));
|
|
789
|
+
touched.push(...companySkillLinksToRemove);
|
|
723
790
|
if (hooksNext !== hooksDoc)
|
|
724
791
|
touched.push(t.hooksJson);
|
|
725
792
|
if (tomlNext !== tomlDoc || legacyPackServerPresent)
|
|
@@ -736,8 +803,12 @@ export function installGlobalCodex(opts) {
|
|
|
736
803
|
else if (legacy.kind === 'foreign')
|
|
737
804
|
log(legacyWarning(t.skillsDir));
|
|
738
805
|
if (skills.skipped.length > 0) {
|
|
739
|
-
log(`Would skip ${skills.skipped.length} skill(s) whose
|
|
806
|
+
log(`Would skip ${skills.skipped.length} skill(s) whose target already exists and is not an HQ link: ${skills.skipped.join(', ')}`);
|
|
807
|
+
}
|
|
808
|
+
if (companySkillLinksToRemove.length > 0) {
|
|
809
|
+
log(`Would remove ${companySkillLinksToRemove.length} previous company-scoped skill link(s)`);
|
|
740
810
|
}
|
|
811
|
+
reportSkippedSkills(skills, log, true);
|
|
741
812
|
log(`\n--- ${t.hooksJson}`);
|
|
742
813
|
log(lineDiff(jsonFormat.serialize(hooksDoc), jsonFormat.serialize(hooksNext)));
|
|
743
814
|
log(`\n--- ${t.configToml}`);
|
|
@@ -761,8 +832,10 @@ export function installGlobalCodex(opts) {
|
|
|
761
832
|
log(legacyWarning(t.skillsDir));
|
|
762
833
|
fs.mkdirSync(t.skillsDir, { recursive: true });
|
|
763
834
|
for (const name of skills.link) {
|
|
764
|
-
fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir,
|
|
835
|
+
fs.symlinkSync(path.join(hqSkillsSource(hqRoot), name), path.join(t.skillsDir, skillLinkName(name)));
|
|
765
836
|
}
|
|
837
|
+
for (const link of companySkillLinksToRemove)
|
|
838
|
+
fs.unlinkSync(link);
|
|
766
839
|
writeConfigAtomic({
|
|
767
840
|
target: t.hooksJson,
|
|
768
841
|
pack: HQ_ANYWHERE_PACK,
|
|
@@ -803,6 +876,7 @@ export function installGlobalCodex(opts) {
|
|
|
803
876
|
writeFileAtomic(t.stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
804
877
|
log(`HQ installed for Codex from ${hqRoot}.`);
|
|
805
878
|
log(` skills linked: ${skills.link.length} new, ${skills.already.length} already present, ${skills.skipped.length} skipped`);
|
|
879
|
+
reportSkippedSkills(skills, log, false);
|
|
806
880
|
log(` hooks: ${hookTarget === 'shim' ? shimPath(hqRoot) : codexAdapterPath(hqRoot)} registered in ${t.hooksJson}`);
|
|
807
881
|
log(` MCP: "${HQ_MCP_SERVER_NAME}" registered in ${t.configToml}`);
|
|
808
882
|
return touched;
|
|
@@ -946,34 +1020,63 @@ function assertRuntime(runtime) {
|
|
|
946
1020
|
}
|
|
947
1021
|
return r;
|
|
948
1022
|
}
|
|
949
|
-
export function resolveInstallRoot(explicit) {
|
|
950
|
-
const
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
1023
|
+
export function resolveInstallRoot(explicit, options = {}) {
|
|
1024
|
+
const env = options.env ?? process.env;
|
|
1025
|
+
const fromFlag = explicit?.trim() || env.HQ_ROOT?.trim();
|
|
1026
|
+
if (fromFlag)
|
|
1027
|
+
return path.resolve(fromFlag);
|
|
1028
|
+
const cwdRoot = resolveHqRoot(options.cwd ?? process.cwd());
|
|
1029
|
+
if (cwdRoot)
|
|
1030
|
+
return cwdRoot;
|
|
1031
|
+
const home = options.home?.trim() || env.HOME?.trim() || os.homedir();
|
|
1032
|
+
try {
|
|
1033
|
+
const pointer = fs.readFileSync(path.join(home, '.hq', 'root'), 'utf8').trim();
|
|
1034
|
+
if (pointer) {
|
|
1035
|
+
const pointedRoot = resolveHqRoot(pointer);
|
|
1036
|
+
if (pointedRoot)
|
|
1037
|
+
return pointedRoot;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
catch {
|
|
1041
|
+
// Fall through to the regular installer/config root discovery.
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
const rootEnv = { ...env };
|
|
1045
|
+
delete rootEnv.HQ_ROOT;
|
|
1046
|
+
return resolveDefaultHqRoot({
|
|
1047
|
+
onMissing: 'throw',
|
|
1048
|
+
env: rootEnv,
|
|
1049
|
+
cwd: options.cwd,
|
|
1050
|
+
home,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
953
1054
|
throw Object.assign(new Error('Run from inside your HQ checkout, or pass --hq-root <dir>.'), { expected: true });
|
|
954
1055
|
}
|
|
955
|
-
return root;
|
|
956
1056
|
}
|
|
957
1057
|
/** Entry used by `hq install --global` (wired from pkg-install.ts). */
|
|
958
1058
|
export async function runInstallGlobal(opts) {
|
|
959
1059
|
const runtime = assertRuntime(opts.runtime);
|
|
960
1060
|
const env = opts.env ?? process.env;
|
|
961
|
-
if (runtime === 'chatgpt' || runtime === 'grok') {
|
|
1061
|
+
if (runtime === 'chatgpt' || runtime === 'grok' || opts.dryRun) {
|
|
962
1062
|
const enabled = await anywhereRuntimeAllowed(env, {
|
|
963
1063
|
...(opts.personSettingReader ? { readSetting: opts.personSettingReader } : {}),
|
|
964
1064
|
onError: (name) => (opts.log ?? console.log)(`HQ Anywhere person setting unavailable (${name}); access remains off.`),
|
|
965
1065
|
});
|
|
966
1066
|
if (!enabled)
|
|
967
1067
|
throw new AnywhereRuntimeDisabledError();
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
(
|
|
972
|
-
|
|
1068
|
+
if (runtime === 'chatgpt' || runtime === 'grok') {
|
|
1069
|
+
const endpoint = resolveHqMcpEndpoint(env).toString();
|
|
1070
|
+
const lines = runtime === 'chatgpt' ? chatgptConnectorInstructions(endpoint) : grokConnectorInstructions(endpoint);
|
|
1071
|
+
for (const line of lines)
|
|
1072
|
+
(opts.log ?? console.log)(line);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
973
1075
|
}
|
|
974
1076
|
const install = runtime === 'codex' ? installGlobalCodex : installGlobalClaude;
|
|
975
1077
|
install({
|
|
976
|
-
hqRoot: resolveInstallRoot(opts.hqRoot),
|
|
1078
|
+
hqRoot: resolveInstallRoot(opts.hqRoot, { env, cwd: opts.cwd, home: opts.home }),
|
|
1079
|
+
home: opts.home,
|
|
977
1080
|
dryRun: opts.dryRun,
|
|
978
1081
|
via: opts.via,
|
|
979
1082
|
marketplaceDir: opts.marketplaceDir,
|
|
@@ -738,6 +738,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
738
738
|
...(automatic.preservedModel !== undefined ? { preservedModel: automatic.preservedModel } : {}),
|
|
739
739
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
740
740
|
...(refreshProfileSettings ? { refreshProfileSettings: true, accountSource: "balanced" } : {}),
|
|
741
|
+
...(automatic.historyReason === "profile_pin_exhausted" ? { preserveAdmissionAccounts: true } : {}),
|
|
741
742
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
742
743
|
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
743
744
|
pending: {
|
|
@@ -409,6 +409,9 @@ export function createLaneResumeHandlers(deps) {
|
|
|
409
409
|
: {}),
|
|
410
410
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
411
411
|
...(refreshProfileSettings ? { refreshProfileSettings: true, accountSource: "balanced" } : {}),
|
|
412
|
+
...(automaticAccountSelection.historyReason === "profile_pin_exhausted"
|
|
413
|
+
? { preserveAdmissionAccounts: true }
|
|
414
|
+
: {}),
|
|
412
415
|
...(profileRepin !== undefined ? { profileRepin } : {}),
|
|
413
416
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
414
417
|
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|