@indigoai-us/hq-cli 5.345.13 → 5.345.15
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 +9 -0
- package/dist/commands/core-archive-old-threads-fast.d.ts +2 -0
- package/dist/commands/core-archive-old-threads-fast.js +5 -0
- package/dist/commands/core-codex-skill-bridge-fast.d.ts +2 -0
- package/dist/commands/core-codex-skill-bridge-fast.js +5 -0
- package/dist/commands/core-hq-status-summary-fast.d.ts +2 -0
- package/dist/commands/core-hq-status-summary-fast.js +11 -0
- package/dist/commands/core-resize-screenshot-fast.d.ts +2 -0
- package/dist/commands/core-resize-screenshot-fast.js +5 -0
- package/dist/commands/core-sentry-report-fast.d.ts +11 -0
- package/dist/commands/core-sentry-report-fast.js +55 -0
- package/dist/commands/core-soft-timeout-fast.d.ts +2 -0
- package/dist/commands/core-soft-timeout-fast.js +5 -0
- package/dist/commands/core-token-usage-report-fast.d.ts +2 -0
- package/dist/commands/core-token-usage-report-fast.js +5 -0
- package/dist/commands/core-worktree-fast.d.ts +2 -0
- package/dist/commands/core-worktree-fast.js +5 -0
- package/dist/commands/install-global.d.ts +5 -1
- package/dist/commands/install-global.js +12 -6
- package/dist/commands/lanes.js +45 -4
- package/dist/commands/pkg-install.js +23 -1
- package/dist/fast-core.d.ts +1 -1
- package/dist/fast-core.js +10 -4
- package/dist/fast-scaffold.d.ts +6 -3
- package/dist/fast-scaffold.js +100 -4
- package/dist/lib/core-utils/sentry-report.js +8 -2
- package/dist/lib/lanes/constants.d.ts +1 -1
- package/dist/lib/lanes/constants.js +1 -1
- package/dist/lib/lanes/reconcile.d.ts +4 -1
- package/dist/lib/lanes/reconcile.js +5 -2
- package/dist/main.js +2 -6
- package/dist/sentry-init.d.ts +10 -0
- package/dist/sentry-init.js +48 -0
- package/dist/sentry.js +5 -42
- package/dist/utils/command-breadcrumb.d.ts +7 -0
- package/dist/utils/command-breadcrumb.js +9 -0
- package/dist/utils/feedback-diagnostics.d.ts +1 -2
- package/dist/utils/feedback-diagnostics.js +2 -29
- package/dist/utils/sanitize-argv.d.ts +2 -0
- package/dist/utils/sanitize-argv.js +30 -0
- package/dist/utils/scaffold-fast-import-guard.d.ts +1 -1
- package/dist/utils/scaffold-fast-import-guard.js +7 -4
- package/docs/anywhere-runtime.md +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.15] — 2026-10-06
|
|
6
|
+
- Route live hook Sentry reports through the fast CLI path.
|
|
7
|
+
|
|
8
|
+
- When `hq-anywhere-runtime` is enabled, `hq install --global --runtime claude --via plugin` installs the `hq` plugin from the `indigoai-us/hq-packages` Claude marketplace, and `hq install @indigoai-us/hq-anywhere` installs the Codex pack from that repository. The flag-off path refuses the HQ Anywhere alias without fetching a package source.
|
|
9
|
+
|
|
10
|
+
## [5.345.14] — 2026-10-06
|
|
11
|
+
- Route frequently called native core commands through isolated fast handlers.
|
|
12
|
+
- Allow lane hierarchies up to four levels and include actionable senior-chain details when depth limits refuse a create or re-parent.
|
|
13
|
+
|
|
5
14
|
## [5.345.13] — 2026-10-06
|
|
6
15
|
- Stream provider transcript scans and account-move comparisons so large session files do not require whole-file reads.
|
|
7
16
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { runCodexSkillBridgeCommand } from "../lib/core-utils/codex-skill-bridge-entry.js";
|
|
2
|
+
export function runCodexSkillBridgeFast(args, _stdin, _cwd, hqRoot) {
|
|
3
|
+
return runCodexSkillBridgeCommand([...args], { root: hqRoot });
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=core-codex-skill-bridge-fast.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { summarizeHqStatus } from "../lib/core-utils/hq-status-summary.js";
|
|
2
|
+
export function runHqStatusSummaryFast(args, _stdin, cwd, _hqRoot, _commandName, hqRootArg) {
|
|
3
|
+
// The full graph canonicalizes an explicit --hq-root through resolveLiveRoot.
|
|
4
|
+
// Keep that spelling on the authoritative path so the command graph can
|
|
5
|
+
// preserve its canonicalization and diagnostics. This adapter owns only the
|
|
6
|
+
// ordinary cwd-rooted invocation.
|
|
7
|
+
if (hqRootArg)
|
|
8
|
+
return undefined;
|
|
9
|
+
return summarizeHqStatus([...args], { cwd });
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=core-hq-status-summary-fast.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NodeOptions } from "@sentry/node";
|
|
2
|
+
import { reportSentryEvent } from "../lib/core-utils/sentry-report.js";
|
|
3
|
+
export type FastSentryReportDependencies = {
|
|
4
|
+
initSentry: (overrides?: Partial<Pick<NodeOptions, "transport">>) => void;
|
|
5
|
+
finishSentrySession: () => void;
|
|
6
|
+
flushSentry: (timeoutMs: number) => PromiseLike<boolean>;
|
|
7
|
+
addCommandBreadcrumb: () => void;
|
|
8
|
+
report: typeof reportSentryEvent;
|
|
9
|
+
};
|
|
10
|
+
export declare function runSentryReportFast(args: readonly string[], stdin: string, _cwd: string, hqRoot: string | undefined, _commandName?: string, _hqRootArg?: string, _worktreeScopeFixEnabled?: boolean, dependencies?: FastSentryReportDependencies): Promise<number | undefined>;
|
|
11
|
+
//# sourceMappingURL=core-sentry-report-fast.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { settleWithin } from "../utils/settle-with-timeout.js";
|
|
2
|
+
import { beforeSend } from "../sentry-before-send.js";
|
|
3
|
+
import { addSentryRuntimeBreadcrumb, flushSentryRuntime, finishSentryRuntimeSession, initSentryRuntime, } from "../sentry-init.js";
|
|
4
|
+
import { addCommandBreadcrumb } from "../utils/command-breadcrumb.js";
|
|
5
|
+
import { reportSentryEvent } from "../lib/core-utils/sentry-report.js";
|
|
6
|
+
const SENTRY_FINALIZATION_TIMEOUT_MS = 500;
|
|
7
|
+
const defaultDependencies = {
|
|
8
|
+
initSentry: (overrides) => { initSentryRuntime(beforeSend, overrides); },
|
|
9
|
+
finishSentrySession: finishSentryRuntimeSession,
|
|
10
|
+
flushSentry: flushSentryRuntime,
|
|
11
|
+
addCommandBreadcrumb: () => addCommandBreadcrumb(process.argv.slice(2), addSentryRuntimeBreadcrumb),
|
|
12
|
+
report: reportSentryEvent,
|
|
13
|
+
};
|
|
14
|
+
export function runSentryReportFast(args, stdin, _cwd, hqRoot, _commandName, _hqRootArg, _worktreeScopeFixEnabled, dependencies = defaultDependencies) {
|
|
15
|
+
let dryRun = false;
|
|
16
|
+
let timeoutMs;
|
|
17
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
18
|
+
const arg = args[index];
|
|
19
|
+
if (arg === "--dry-run") {
|
|
20
|
+
dryRun = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (arg === "--timeout-ms") {
|
|
24
|
+
const value = args[index + 1];
|
|
25
|
+
if (value === undefined || value.startsWith("-"))
|
|
26
|
+
return Promise.resolve(undefined);
|
|
27
|
+
timeoutMs = Number(value);
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg?.startsWith("--timeout-ms=")) {
|
|
32
|
+
timeoutMs = Number(arg.slice("--timeout-ms=".length));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Let Commander retain its authoritative diagnostics for forms this
|
|
36
|
+
// lightweight parser does not own. This branch emits no output.
|
|
37
|
+
return Promise.resolve(undefined);
|
|
38
|
+
}
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
return dependencies.report(stdin, { dryRun: true, timeoutMs, hqRoot }).then((result) => result.code);
|
|
41
|
+
}
|
|
42
|
+
// The report command's constrained message path uses the same DSN, release,
|
|
43
|
+
// environment, repo tag, user attribution, breadcrumb hook and release-health
|
|
44
|
+
// session as main.ts. The shared initializer keeps that setup in one place;
|
|
45
|
+
// the report payload itself is a message without an exception, so the shared
|
|
46
|
+
// fleet scrubber is the effective beforeSend behavior on this path.
|
|
47
|
+
dependencies.initSentry();
|
|
48
|
+
dependencies.addCommandBreadcrumb();
|
|
49
|
+
return dependencies.report(stdin, { dryRun: false, timeoutMs, hqRoot }).then(async (result) => {
|
|
50
|
+
dependencies.finishSentrySession();
|
|
51
|
+
await settleWithin([Promise.resolve(dependencies.flushSentry(SENTRY_FINALIZATION_TIMEOUT_MS))], SENTRY_FINALIZATION_TIMEOUT_MS);
|
|
52
|
+
return result.code;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=core-sentry-report-fast.js.map
|
|
@@ -21,6 +21,8 @@ export interface GlobalInstallOptions {
|
|
|
21
21
|
via?: 'direct' | 'plugin';
|
|
22
22
|
/** Local Claude Code marketplace directory (or HQ_CLAUDE_PLUGIN_MARKETPLACE). */
|
|
23
23
|
marketplaceDir?: string;
|
|
24
|
+
/** GitHub repository for the published HQ plugin marketplace. */
|
|
25
|
+
marketplaceRepository?: string;
|
|
24
26
|
/** Test seam for the Claude Code CLI; production uses `claude`. */
|
|
25
27
|
runClaude?: (args: string[], home: string, env: NodeJS.ProcessEnv) => void;
|
|
26
28
|
}
|
|
@@ -38,7 +40,7 @@ export interface ClaudePluginSource {
|
|
|
38
40
|
pluginId: string;
|
|
39
41
|
}
|
|
40
42
|
/** Resolve the local marketplace source; published hosting remains an operator decision. */
|
|
41
|
-
export declare function resolveClaudePluginSource(explicitDir?: string, env?: NodeJS.ProcessEnv): ClaudePluginSource;
|
|
43
|
+
export declare function resolveClaudePluginSource(explicitDir?: string, env?: NodeJS.ProcessEnv, repository?: string): ClaudePluginSource;
|
|
42
44
|
export { AnywhereRuntimeDisabledError as AnywhereDisabledError };
|
|
43
45
|
export declare const isAnywhereEnabled: (_home: string, env?: NodeJS.ProcessEnv) => boolean;
|
|
44
46
|
/** Install HQ into Claude Code user scope. Returns the files touched (or that would be). */
|
|
@@ -67,10 +69,12 @@ export interface GlobalCommandOpts {
|
|
|
67
69
|
hqRoot?: string;
|
|
68
70
|
via?: 'direct' | 'plugin';
|
|
69
71
|
marketplaceDir?: string;
|
|
72
|
+
marketplaceRepository?: string;
|
|
70
73
|
/** Dependency injection for tests. */
|
|
71
74
|
env?: NodeJS.ProcessEnv;
|
|
72
75
|
log?: (line: string) => void;
|
|
73
76
|
}
|
|
77
|
+
export declare function resolveInstallRoot(explicit: string | undefined): string;
|
|
74
78
|
/** Entry used by `hq install --global` (wired from pkg-install.ts). */
|
|
75
79
|
export declare function runInstallGlobal(opts: GlobalCommandOpts): void;
|
|
76
80
|
export declare function registerUninstallCommand(program: Command): void;
|
|
@@ -46,9 +46,12 @@ export function hookTargetFor(hqRoot, home, daemonInstalled, env = process.env)
|
|
|
46
46
|
return installed && daemonMatchesRoot && fs.existsSync(shimPath(hqRoot)) ? 'shim' : 'master';
|
|
47
47
|
}
|
|
48
48
|
/** Resolve the local marketplace source; published hosting remains an operator decision. */
|
|
49
|
-
export function resolveClaudePluginSource(explicitDir, env = process.env) {
|
|
49
|
+
export function resolveClaudePluginSource(explicitDir, env = process.env, repository) {
|
|
50
50
|
const configured = explicitDir?.trim() || env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim();
|
|
51
51
|
if (!configured) {
|
|
52
|
+
if (repository === 'indigoai-us/hq-packages') {
|
|
53
|
+
return { sourceDir: repository, marketplaceName: 'hq-packages', pluginName: 'hq', pluginId: 'hq@hq-packages' };
|
|
54
|
+
}
|
|
52
55
|
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
56
|
}
|
|
54
57
|
const sourceDir = path.resolve(configured);
|
|
@@ -94,7 +97,9 @@ function removePluginSettings(doc, plugin, removePlugin, removeMarketplace, plug
|
|
|
94
97
|
if (marketplaces && typeof marketplaces === 'object' && !Array.isArray(marketplaces)) {
|
|
95
98
|
const entries = { ...marketplaces };
|
|
96
99
|
const registration = entries[plugin.marketplaceName];
|
|
97
|
-
|
|
100
|
+
const sameDirectory = registration?.source?.source === 'directory' && registration.source.path === plugin.sourceDir;
|
|
101
|
+
const sameRepository = registration?.source?.source === 'github' && registration.source.repo === plugin.sourceDir;
|
|
102
|
+
if (removeMarketplace && (sameDirectory || sameRepository))
|
|
98
103
|
delete entries[plugin.marketplaceName];
|
|
99
104
|
if (Object.keys(entries).length === 0)
|
|
100
105
|
delete next.extraKnownMarketplaces;
|
|
@@ -278,7 +283,7 @@ export function installGlobalClaude(opts) {
|
|
|
278
283
|
throw new AnywhereRuntimeDisabledError();
|
|
279
284
|
const t = claudeTargets(home);
|
|
280
285
|
const prior = readState(t);
|
|
281
|
-
const pluginSource = opts.via === 'plugin' ? resolveClaudePluginSource(opts.marketplaceDir, env) : undefined;
|
|
286
|
+
const pluginSource = opts.via === 'plugin' ? resolveClaudePluginSource(opts.marketplaceDir, env, opts.marketplaceRepository) : undefined;
|
|
282
287
|
if (prior?.plugin && !pluginSource) {
|
|
283
288
|
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
289
|
}
|
|
@@ -312,7 +317,8 @@ export function installGlobalClaude(opts) {
|
|
|
312
317
|
const enabledPlugins = settingsDoc.enabledPlugins;
|
|
313
318
|
const knownMarketplaces = settingsDoc.extraKnownMarketplaces;
|
|
314
319
|
const existingMarketplace = pluginSource ? knownMarketplaces?.[pluginSource.marketplaceName] : undefined;
|
|
315
|
-
if (pluginSource && existingMarketplace && (existingMarketplace.source?.source
|
|
320
|
+
if (pluginSource && existingMarketplace && !((existingMarketplace.source?.source === 'directory' && existingMarketplace.source.path === pluginSource.sourceDir) ||
|
|
321
|
+
(existingMarketplace.source?.source === 'github' && existingMarketplace.source.repo === pluginSource.sourceDir))) {
|
|
316
322
|
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
323
|
}
|
|
318
324
|
const hadPluginSetting = !!pluginSource && !!enabledPlugins && Object.prototype.hasOwnProperty.call(enabledPlugins, pluginSource.pluginId);
|
|
@@ -927,7 +933,7 @@ function assertRuntime(runtime) {
|
|
|
927
933
|
}
|
|
928
934
|
return r;
|
|
929
935
|
}
|
|
930
|
-
function resolveInstallRoot(explicit) {
|
|
936
|
+
export function resolveInstallRoot(explicit) {
|
|
931
937
|
const fromFlag = explicit?.trim() || process.env.HQ_ROOT?.trim();
|
|
932
938
|
const root = fromFlag ? path.resolve(fromFlag) : resolveHqRoot();
|
|
933
939
|
if (!root) {
|
|
@@ -948,7 +954,7 @@ export function runInstallGlobal(opts) {
|
|
|
948
954
|
return;
|
|
949
955
|
}
|
|
950
956
|
const install = runtime === 'codex' ? installGlobalCodex : installGlobalClaude;
|
|
951
|
-
install({ hqRoot: resolveInstallRoot(opts.hqRoot), dryRun: opts.dryRun, via: opts.via, marketplaceDir: opts.marketplaceDir });
|
|
957
|
+
install({ hqRoot: resolveInstallRoot(opts.hqRoot), dryRun: opts.dryRun, via: opts.via, marketplaceDir: opts.marketplaceDir, marketplaceRepository: opts.marketplaceRepository });
|
|
952
958
|
}
|
|
953
959
|
export function registerUninstallCommand(program) {
|
|
954
960
|
program
|
package/dist/commands/lanes.js
CHANGED
|
@@ -2029,6 +2029,37 @@ function depthOfRef(hqRoot, ref, ctx, visited = new Set(), override) {
|
|
|
2029
2029
|
function derivedLaneDepth(hqRoot, senior, ctx, override) {
|
|
2030
2030
|
return depthOfRef(hqRoot, senior, ctx, new Set(), override) + 1;
|
|
2031
2031
|
}
|
|
2032
|
+
function seniorLaneChain(hqRoot, ref, ctx, visited = new Set(), override) {
|
|
2033
|
+
if (ref.kind === "user")
|
|
2034
|
+
return [];
|
|
2035
|
+
const key = seniorKey(ref);
|
|
2036
|
+
if (visited.has(key)) {
|
|
2037
|
+
throw new LanesError("senior_cycle", `Cycle in senior chain at ${key}`, { at: ref });
|
|
2038
|
+
}
|
|
2039
|
+
visited.add(key);
|
|
2040
|
+
if (ref.kind === "session") {
|
|
2041
|
+
if (!ref.id) {
|
|
2042
|
+
throw new LanesError("invalid_senior_ref", "Session senior is missing an id", { senior: ref });
|
|
2043
|
+
}
|
|
2044
|
+
return seniorLaneChain(hqRoot, seniorOfSession(hqRoot, ref.id, ctx), ctx, visited, override);
|
|
2045
|
+
}
|
|
2046
|
+
if (!ref.id) {
|
|
2047
|
+
throw new LanesError("invalid_senior_ref", "Lane senior is missing an id", { senior: ref });
|
|
2048
|
+
}
|
|
2049
|
+
const lane = readLane(hqRoot, ref.id);
|
|
2050
|
+
const isOverridden = override?.laneId === lane.lane_id;
|
|
2051
|
+
const senior = isOverridden ? override.senior : lane.senior;
|
|
2052
|
+
const prefix = seniorLaneChain(hqRoot, senior, ctx, visited, override);
|
|
2053
|
+
const depth = isOverridden ? override.depth : derivedLaneDepth(hqRoot, lane.senior, ctx, override);
|
|
2054
|
+
return [...prefix, { lane_id: lane.lane_id, depth }];
|
|
2055
|
+
}
|
|
2056
|
+
function maxLaneDepthError(depth, chain, description, extraDetails = {}) {
|
|
2057
|
+
const parentLane = [...chain].reverse().find((entry) => entry.depth < MAX_LANE_DEPTH);
|
|
2058
|
+
const nextStep = parentLane
|
|
2059
|
+
? `Ask your senior lane ${parentLane.lane_id} to create this worker instead, or re-parent this lane under a shallower senior.`
|
|
2060
|
+
: "Ask a shallower senior to create this lane, or re-parent it to a shallower senior.";
|
|
2061
|
+
return new LanesError("max_lane_depth", `${description}: senior chain length ${depth} exceeds cap ${MAX_LANE_DEPTH}`, { ...extraDetails, depth, max: MAX_LANE_DEPTH, senior_chain: chain, next_step: nextStep });
|
|
2062
|
+
}
|
|
2032
2063
|
function callerDepth(hqRoot, ctx) {
|
|
2033
2064
|
const laneId = ctx.env.HQ_LANE_ID?.trim();
|
|
2034
2065
|
if (laneId) {
|
|
@@ -3012,7 +3043,13 @@ async function runCreate(ctx, opts) {
|
|
|
3012
3043
|
}
|
|
3013
3044
|
const depth = callerDepth(hqRoot, ctx);
|
|
3014
3045
|
if (depth >= MAX_LANE_DEPTH) {
|
|
3015
|
-
|
|
3046
|
+
const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
|
|
3047
|
+
const chain = callerLaneId
|
|
3048
|
+
? seniorLaneChain(hqRoot, { kind: "lane", id: callerLaneId }, ctx)
|
|
3049
|
+
: seniorLaneChain(hqRoot, sessionIdFromEnv(ctx.env)
|
|
3050
|
+
? { kind: "session", id: sessionIdFromEnv(ctx.env) }
|
|
3051
|
+
: { kind: "user" }, ctx);
|
|
3052
|
+
throw maxLaneDepthError(depth + 1, chain, `A depth-${depth} caller may not spawn a depth-${depth + 1} lane`);
|
|
3016
3053
|
}
|
|
3017
3054
|
const senior = opts.senior ? parseSeniorRef(opts.senior) : defaultSenior(ctx);
|
|
3018
3055
|
assertSeniorExists(hqRoot, senior, ctx);
|
|
@@ -3033,7 +3070,7 @@ async function runCreate(ctx, opts) {
|
|
|
3033
3070
|
const hireGrant = requestedHireGrant ?? 0;
|
|
3034
3071
|
const laneDepth = derivedLaneDepth(hqRoot, senior, ctx);
|
|
3035
3072
|
if (laneDepth > MAX_LANE_DEPTH) {
|
|
3036
|
-
throw
|
|
3073
|
+
throw maxLaneDepthError(laneDepth, seniorLaneChain(hqRoot, senior, ctx), `Creating lane at depth ${laneDepth} is refused`);
|
|
3037
3074
|
}
|
|
3038
3075
|
// A no-company lane must not call company resolution (openMeshTransport).
|
|
3039
3076
|
if (storyIds.length > 0 && ctx.storyLedgerEnabled()) {
|
|
@@ -7081,7 +7118,9 @@ function runReparentLocked(ctx, laneId, opts) {
|
|
|
7081
7118
|
}
|
|
7082
7119
|
const newDepth = derivedLaneDepth(hqRoot, nextSenior, ctx);
|
|
7083
7120
|
if (newDepth > MAX_LANE_DEPTH) {
|
|
7084
|
-
|
|
7121
|
+
const chain = seniorLaneChain(hqRoot, nextSenior, ctx);
|
|
7122
|
+
chain.push({ lane_id: lane.lane_id, depth: newDepth });
|
|
7123
|
+
throw maxLaneDepthError(newDepth, chain, `Re-parent would put lane at depth ${newDepth}`);
|
|
7085
7124
|
}
|
|
7086
7125
|
const pending = pendingQuestionsForLane(hqRoot, laneId);
|
|
7087
7126
|
const oldDepth = derivedLaneDepth(hqRoot, lane.senior, ctx);
|
|
@@ -7099,7 +7138,9 @@ function runReparentLocked(ctx, laneId, opts) {
|
|
|
7099
7138
|
const oldDescendantDepth = derivedLaneDepth(hqRoot, other.senior, ctx);
|
|
7100
7139
|
const descendantDepth = derivedLaneDepth(hqRoot, other.senior, ctx, override);
|
|
7101
7140
|
if (descendantDepth > MAX_LANE_DEPTH) {
|
|
7102
|
-
|
|
7141
|
+
const chain = seniorLaneChain(hqRoot, other.senior, ctx, new Set(), override);
|
|
7142
|
+
chain.push({ lane_id: other.lane_id, depth: descendantDepth });
|
|
7143
|
+
throw maxLaneDepthError(descendantDepth, chain, `Re-parent would put descendant ${other.lane_id} at depth ${descendantDepth}`, { lane_id: other.lane_id });
|
|
7103
7144
|
}
|
|
7104
7145
|
if (descendantDepth > oldDescendantDepth &&
|
|
7105
7146
|
pendingQuestionsForLane(hqRoot, other.lane_id).length > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { peekHqApiKey, requireApiKeyCapability, } from '../utils/resolve-vault-credential.js';
|
|
3
3
|
import { MARKETPLACE_PREFIX, installPack, sourceMatchesPackPattern, } from './pack-install.js';
|
|
4
|
+
import { anywhereRuntimeEnabled } from './anywhere-runtime-flag.js';
|
|
4
5
|
/**
|
|
5
6
|
* True when `source` installs through the marketplace — an explicit
|
|
6
7
|
* `marketplace:` source, or a bare slug (which the dispatch below rewrites into
|
|
@@ -67,7 +68,15 @@ export function registerPackageInstallCommand(parent) {
|
|
|
67
68
|
process.exitCode = 1;
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
let marketplaceDir = opts.marketplaceDir;
|
|
72
|
+
if (opts.via === 'plugin' &&
|
|
73
|
+
!marketplaceDir?.trim() &&
|
|
74
|
+
!process.env.HQ_CLAUDE_PLUGIN_MARKETPLACE?.trim() &&
|
|
75
|
+
anywhereRuntimeEnabled(process.env)) {
|
|
76
|
+
runInstallGlobal({ ...opts, marketplaceRepository: 'indigoai-us/hq-packages' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
runInstallGlobal({ ...opts, marketplaceDir });
|
|
71
80
|
return;
|
|
72
81
|
}
|
|
73
82
|
if (!source) {
|
|
@@ -85,6 +94,19 @@ export function registerPackageInstallCommand(parent) {
|
|
|
85
94
|
// with packs:install (hq-pro serves POST /v1/keys/listings/…/installs).
|
|
86
95
|
requireApiKeyCapability('packs:install', 'hq install');
|
|
87
96
|
assertApiKeyCanAuthorizeInstall(source);
|
|
97
|
+
if (source === '@indigoai-us/hq-anywhere') {
|
|
98
|
+
if (!anywhereRuntimeEnabled()) {
|
|
99
|
+
console.error(chalk.red('Install failed:'), 'HQ Anywhere installation is disabled by the hq-anywhere-runtime flag.');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
await installPack('github:indigoai-us/hq-packages#packages/hq-pack-hq-anywhere', {
|
|
103
|
+
company: opts.company,
|
|
104
|
+
allowHooks: opts.allowHooks,
|
|
105
|
+
allowMcp: opts.allowMcp,
|
|
106
|
+
followBranch: opts.branch,
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
88
110
|
if (sourceMatchesPackPattern(source)) {
|
|
89
111
|
await installPack(source, {
|
|
90
112
|
company: opts.company,
|
package/dist/fast-core.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* module. The registration in `core.ts` remains the complete fallback path.
|
|
8
8
|
*/
|
|
9
9
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
10
|
-
export declare function readStdinIfPiped(stream: NodeJS.ReadStream): Promise<string>;
|
|
10
|
+
export declare function readStdinIfPiped(stream: NodeJS.ReadStream, maxBytes?: number): Promise<string>;
|
|
11
11
|
/**
|
|
12
12
|
* Run the fast-path core command named in `argv` and exit with its code.
|
|
13
13
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
package/dist/fast-core.js
CHANGED
|
@@ -14,12 +14,18 @@ const FAST_ROUTE_PROFILE_TOKEN = "hq-fast-route";
|
|
|
14
14
|
function wantsFastRouteProfile(env) {
|
|
15
15
|
return env.NODE_DEBUG === FAST_ROUTE_PROFILE_TOKEN;
|
|
16
16
|
}
|
|
17
|
-
export async function readStdinIfPiped(stream) {
|
|
17
|
+
export async function readStdinIfPiped(stream, maxBytes) {
|
|
18
18
|
if (stream.isTTY)
|
|
19
19
|
return "";
|
|
20
20
|
const chunks = [];
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
let bytesRead = 0;
|
|
22
|
+
for await (const chunk of stream) {
|
|
23
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
24
|
+
bytesRead += buffer.length;
|
|
25
|
+
if (maxBytes !== undefined && bytesRead > maxBytes)
|
|
26
|
+
return "";
|
|
27
|
+
chunks.push(buffer);
|
|
28
|
+
}
|
|
23
29
|
return Buffer.concat(chunks).toString("utf8");
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
@@ -105,7 +111,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
|
|
|
105
111
|
handlerImportMs = profileElapsed(handlerImportStartedAt, profileMark());
|
|
106
112
|
const stdinStartedAt = profileMark();
|
|
107
113
|
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
108
|
-
const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
|
|
114
|
+
const stdin = readsStdin ? await readStdinIfPiped(process.stdin, entry.kind === "native" ? entry.stdinMaxBytes : undefined) : "";
|
|
109
115
|
stdinReadMs = profileElapsed(stdinStartedAt, profileMark());
|
|
110
116
|
if (profile && profileStart !== undefined) {
|
|
111
117
|
process.stderr.write(`HQ_FAST_ROUTE_PROFILE ${JSON.stringify({
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type FastScaffoldScript = {
|
|
|
42
42
|
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
43
43
|
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
44
44
|
readsStdin?: boolean;
|
|
45
|
+
/** Bound bytes buffered from stdin; overflow is treated as empty input. */
|
|
46
|
+
stdinMaxBytes?: number;
|
|
45
47
|
/** Per-invocation stdin policy for handlers whose shell reads conditionally. */
|
|
46
48
|
readsStdinFor?: (args: readonly string[]) => boolean;
|
|
47
49
|
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
@@ -78,9 +80,10 @@ export declare function parseSecretsEnvFastRequest(argv: readonly string[]): Par
|
|
|
78
80
|
* Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
|
|
79
81
|
* fast-path script. Returns null for anything else so the caller defers to the
|
|
80
82
|
* full CLI. Mirrors the `core` group's single `--hq-root` option (both the
|
|
81
|
-
* `--hq-root <path>` and `--hq-root=<path>` forms)
|
|
82
|
-
*
|
|
83
|
-
*
|
|
83
|
+
* `--hq-root <path>` and `--hq-root=<path>` forms) before the command name.
|
|
84
|
+
* Commander also accepts that parent option after a subcommand; those
|
|
85
|
+
* invocations are deferred because the fast parser cannot safely distinguish
|
|
86
|
+
* a parent option from a command's pass-through arguments.
|
|
84
87
|
*/
|
|
85
88
|
export declare function parseFastCore(argv: readonly string[]): ParsedFastCore | null;
|
|
86
89
|
/** Whether this invocation is a fast-path scaffold dispatch. */
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -11,6 +11,98 @@
|
|
|
11
11
|
*/
|
|
12
12
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
13
13
|
export const FAST_SCAFFOLD_SCRIPTS = [
|
|
14
|
+
{
|
|
15
|
+
name: "sentry report",
|
|
16
|
+
kind: "native",
|
|
17
|
+
modulePath: "./commands/core-sentry-report-fast.js",
|
|
18
|
+
exportName: "runSentryReportFast",
|
|
19
|
+
flagKey: "hq-core-native-fast-route",
|
|
20
|
+
root: "live",
|
|
21
|
+
readsStdinFor: (args) => {
|
|
22
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
23
|
+
const arg = args[index];
|
|
24
|
+
if (arg === "--dry-run")
|
|
25
|
+
continue;
|
|
26
|
+
if (arg === "--timeout-ms") {
|
|
27
|
+
const value = args[index + 1];
|
|
28
|
+
if (value === undefined || value.startsWith("-"))
|
|
29
|
+
return false;
|
|
30
|
+
index += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (arg?.startsWith("--timeout-ms="))
|
|
34
|
+
continue;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
stdinMaxBytes: 2_048,
|
|
40
|
+
requiresNodeNetworkCompatibility: true,
|
|
41
|
+
fallbackToCommandGraph: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "soft-timeout",
|
|
45
|
+
kind: "native",
|
|
46
|
+
modulePath: "./commands/core-soft-timeout-fast.js",
|
|
47
|
+
exportName: "runSoftTimeoutFast",
|
|
48
|
+
flagKey: "hq-core-native-fast-route",
|
|
49
|
+
root: "cwd",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "hq-status-summary",
|
|
53
|
+
kind: "native",
|
|
54
|
+
modulePath: "./commands/core-hq-status-summary-fast.js",
|
|
55
|
+
exportName: "runHqStatusSummaryFast",
|
|
56
|
+
flagKey: "hq-core-native-fast-route",
|
|
57
|
+
asset: "core/scripts/hq-status-summary.sh",
|
|
58
|
+
root: "cwd",
|
|
59
|
+
fallbackToCommandGraph: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "archive-old-threads",
|
|
63
|
+
kind: "native",
|
|
64
|
+
modulePath: "./commands/core-archive-old-threads-fast.js",
|
|
65
|
+
exportName: "runArchiveOldThreadsFast",
|
|
66
|
+
flagKey: "hq-core-native-fast-route",
|
|
67
|
+
asset: "core/scripts/archive-old-threads.sh",
|
|
68
|
+
root: "live",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "worktree",
|
|
72
|
+
kind: "native",
|
|
73
|
+
modulePath: "./commands/core-worktree-fast.js",
|
|
74
|
+
exportName: "runWorktreeFast",
|
|
75
|
+
flagKey: "hq-core-native-fast-route",
|
|
76
|
+
asset: "core/scripts/worktree.sh",
|
|
77
|
+
root: "live",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "token-usage-report",
|
|
81
|
+
kind: "native",
|
|
82
|
+
modulePath: "./commands/core-token-usage-report-fast.js",
|
|
83
|
+
exportName: "runTokenUsageReportFast",
|
|
84
|
+
flagKey: "hq-core-native-fast-route",
|
|
85
|
+
asset: "core/scripts/token-usage-report.sh",
|
|
86
|
+
root: "cwd",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "resize-screenshot",
|
|
90
|
+
kind: "native",
|
|
91
|
+
modulePath: "./commands/core-resize-screenshot-fast.js",
|
|
92
|
+
exportName: "runResizeScreenshotFast",
|
|
93
|
+
flagKey: "hq-core-native-fast-route",
|
|
94
|
+
asset: "core/scripts/resize-screenshot.sh",
|
|
95
|
+
root: "cwd",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "codex-skill-bridge",
|
|
99
|
+
kind: "native",
|
|
100
|
+
modulePath: "./commands/core-codex-skill-bridge-fast.js",
|
|
101
|
+
exportName: "runCodexSkillBridgeFast",
|
|
102
|
+
flagKey: "hq-core-native-fast-route",
|
|
103
|
+
asset: "core/scripts/codex-skill-bridge.sh",
|
|
104
|
+
root: "live",
|
|
105
|
+
},
|
|
14
106
|
...["notify", "probe", "remediate", "run"].map((name) => ({
|
|
15
107
|
name: `job ${name}`,
|
|
16
108
|
kind: "native",
|
|
@@ -454,9 +546,10 @@ export function parseSecretsEnvFastRequest(argv) {
|
|
|
454
546
|
* Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
|
|
455
547
|
* fast-path script. Returns null for anything else so the caller defers to the
|
|
456
548
|
* full CLI. Mirrors the `core` group's single `--hq-root` option (both the
|
|
457
|
-
* `--hq-root <path>` and `--hq-root=<path>` forms)
|
|
458
|
-
*
|
|
459
|
-
*
|
|
549
|
+
* `--hq-root <path>` and `--hq-root=<path>` forms) before the command name.
|
|
550
|
+
* Commander also accepts that parent option after a subcommand; those
|
|
551
|
+
* invocations are deferred because the fast parser cannot safely distinguish
|
|
552
|
+
* a parent option from a command's pass-through arguments.
|
|
460
553
|
*/
|
|
461
554
|
export function parseFastCore(argv) {
|
|
462
555
|
// argv is process.argv: [node, hq, core, ...rest].
|
|
@@ -482,7 +575,10 @@ export function parseFastCore(argv) {
|
|
|
482
575
|
.find((candidate) => candidate.name.split(" ").every((part, index) => commandArgs[index] === part));
|
|
483
576
|
if (!entry)
|
|
484
577
|
return null;
|
|
485
|
-
|
|
578
|
+
const routeArgs = argv.slice(i + entry.name.split(" ").length);
|
|
579
|
+
if (routeArgs.some((arg) => arg === "--hq-root" || arg.startsWith("--hq-root=")))
|
|
580
|
+
return null;
|
|
581
|
+
return { entry, hqRootArg, args: routeArgs };
|
|
486
582
|
}
|
|
487
583
|
/** Whether this invocation is a fast-path scaffold dispatch. */
|
|
488
584
|
export function isFastCoreRequest(argv) {
|
|
@@ -24,7 +24,6 @@ import { gte as semverGte, valid as validSemver } from "semver";
|
|
|
24
24
|
import { CLI_VERSION } from "../../cli-version.js";
|
|
25
25
|
import { beforeSend } from "../../sentry-before-send.js";
|
|
26
26
|
import { resolveLiveRoot } from "../../utils/hq-roots.js";
|
|
27
|
-
import { hookTimeoutDebugContextEnabled } from "./hook-timeout-debug-context-flag.js";
|
|
28
27
|
const requireSentry = createRequire(import.meta.url);
|
|
29
28
|
let sentryModule;
|
|
30
29
|
function loadSentryModule() {
|
|
@@ -208,7 +207,14 @@ const defaultDependencies = {
|
|
|
208
207
|
},
|
|
209
208
|
stdout: process.stdout,
|
|
210
209
|
stderr: process.stderr,
|
|
211
|
-
timeoutDebugContextEnabled: (signal) =>
|
|
210
|
+
timeoutDebugContextEnabled: async (signal) => {
|
|
211
|
+
// This optional diagnostic gate is needed only for reports that carry
|
|
212
|
+
// hook-timeout debug context. Load it on demand so ordinary dry-run
|
|
213
|
+
// validation stays on the isolated fast route without importing the full
|
|
214
|
+
// flag registry.
|
|
215
|
+
const { hookTimeoutDebugContextEnabled } = await import("./hook-timeout-debug-context-flag.js");
|
|
216
|
+
return hookTimeoutDebugContextEnabled({}, signal);
|
|
217
|
+
},
|
|
212
218
|
};
|
|
213
219
|
/**
|
|
214
220
|
* Read no more than the accepted event size. Returning early keeps a malformed
|
|
@@ -15,7 +15,10 @@ import type { LaneCompanyRef, LaneAccountSelection, LaneRecord, LivenessState, Q
|
|
|
15
15
|
import { type LaneMesh } from "./mesh-events.js";
|
|
16
16
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
17
17
|
export declare const QUESTION_AGE_MS: number;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Questions can advance through a session and its senior lane separately at
|
|
20
|
+
* each lane level, so allow both routing hops per lane before reaching user.
|
|
21
|
+
*/
|
|
19
22
|
export declare const MAX_AGED_ADVANCES: number;
|
|
20
23
|
/** Aligns with watcher silence: a host that has not reported for this long is dead. */
|
|
21
24
|
export declare const DEAD_HOST_MS: number;
|
|
@@ -41,8 +41,11 @@ import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from
|
|
|
41
41
|
import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
42
42
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
43
43
|
export const QUESTION_AGE_MS = 60 * 60 * 1000;
|
|
44
|
-
/**
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Questions can advance through a session and its senior lane separately at
|
|
46
|
+
* each lane level, so allow both routing hops per lane before reaching user.
|
|
47
|
+
*/
|
|
48
|
+
export const MAX_AGED_ADVANCES = MAX_LANE_DEPTH * 2;
|
|
46
49
|
/** Aligns with watcher silence: a host that has not reported for this long is dead. */
|
|
47
50
|
export const DEAD_HOST_MS = SILENCE_MS;
|
|
48
51
|
const RECONCILE_LOCK_STALE_MS = 30_000;
|
package/dist/main.js
CHANGED
|
@@ -11,7 +11,7 @@ import path from "node:path";
|
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
import { finishSentrySession, initSentry, Sentry } from "./sentry.js";
|
|
14
|
-
import {
|
|
14
|
+
import { addCommandBreadcrumb } from "./utils/command-breadcrumb.js";
|
|
15
15
|
import { environmentalFsErrorMessage, filesystemErrorCaptureContext, filesystemErrorMessage, } from "./utils/environmental-error.js";
|
|
16
16
|
import { syncStateLockMessage } from "./utils/sync-state-lock-error.js";
|
|
17
17
|
import { networkTransportErrorMessage } from "./utils/network-transport-error.js";
|
|
@@ -191,11 +191,7 @@ export async function runCli() {
|
|
|
191
191
|
let versionUpdateHandled = false;
|
|
192
192
|
let commandParsed = false;
|
|
193
193
|
try {
|
|
194
|
-
runGlobalNetworkLifecycle(process.argv, () => Sentry.addBreadcrumb(
|
|
195
|
-
category: "command",
|
|
196
|
-
message: sanitizeArgv(process.argv.slice(2)).join(" "),
|
|
197
|
-
level: "info",
|
|
198
|
-
}));
|
|
194
|
+
runGlobalNetworkLifecycle(process.argv, () => addCommandBreadcrumb(process.argv.slice(2), (breadcrumb) => Sentry.addBreadcrumb(breadcrumb)));
|
|
199
195
|
// Version handling, both halves. Skipped for inspection flags
|
|
200
196
|
// (`--version`, `--help`) so users debugging a broken install can still
|
|
201
197
|
// introspect what they have, and silent on any failure — neither half may
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NodeOptions } from "@sentry/node";
|
|
2
|
+
type SentryModule = typeof import("@sentry/node");
|
|
3
|
+
type SentryBreadcrumb = Parameters<SentryModule["addBreadcrumb"]>[0];
|
|
4
|
+
/** Shared Sentry SDK setup for the command graph and isolated fast handlers. */
|
|
5
|
+
export declare function initSentryRuntime(beforeSend: NodeOptions["beforeSend"], overrides?: Partial<Pick<NodeOptions, "transport">>, pendingBreadcrumbs?: readonly SentryBreadcrumb[]): SentryModule | undefined;
|
|
6
|
+
export declare function finishSentryRuntimeSession(): void;
|
|
7
|
+
export declare function addSentryRuntimeBreadcrumb(breadcrumb: SentryBreadcrumb): void;
|
|
8
|
+
export declare function flushSentryRuntime(timeoutMs: number): PromiseLike<boolean>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=sentry-init.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { BUNDLED_DSN } from "./sentry-dsn.generated.js";
|
|
3
|
+
import { CLI_VERSION } from "./cli-version.js";
|
|
4
|
+
import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
|
|
5
|
+
import { getCachedSentryUser } from "./utils/sentry-identity.js";
|
|
6
|
+
const requireSentry = createRequire(import.meta.url);
|
|
7
|
+
let sentryModule;
|
|
8
|
+
function loadSentryModule() {
|
|
9
|
+
sentryModule ??= requireSentry("@sentry/node");
|
|
10
|
+
return sentryModule;
|
|
11
|
+
}
|
|
12
|
+
/** Shared Sentry SDK setup for the command graph and isolated fast handlers. */
|
|
13
|
+
export function initSentryRuntime(beforeSend, overrides = {}, pendingBreadcrumbs = []) {
|
|
14
|
+
const dsn = BUNDLED_DSN || process.env.SENTRY_DSN;
|
|
15
|
+
if (!dsn)
|
|
16
|
+
return undefined;
|
|
17
|
+
const sdk = loadSentryModule();
|
|
18
|
+
sdk.init({
|
|
19
|
+
dsn,
|
|
20
|
+
// This async ESM hook adds large startup cost. HQ retains its explicit
|
|
21
|
+
// breadcrumbs without enabling automatic third-party instrumentation.
|
|
22
|
+
registerEsmLoaderHooks: false,
|
|
23
|
+
release: `hq-cli@${CLI_VERSION}`,
|
|
24
|
+
environment: process.env.HQ_CLI_ENV ?? "production",
|
|
25
|
+
initialScope: { tags: { repo: "hq-cli" } },
|
|
26
|
+
beforeSend,
|
|
27
|
+
beforeBreadcrumb,
|
|
28
|
+
integrations: (defaults) => defaults.filter((integration) => integration.name !== "ProcessSession"),
|
|
29
|
+
...overrides,
|
|
30
|
+
});
|
|
31
|
+
const user = getCachedSentryUser();
|
|
32
|
+
if (user)
|
|
33
|
+
sdk.setUser(user);
|
|
34
|
+
for (const breadcrumb of pendingBreadcrumbs)
|
|
35
|
+
sdk.addBreadcrumb(breadcrumb);
|
|
36
|
+
sdk.startSession();
|
|
37
|
+
return sdk;
|
|
38
|
+
}
|
|
39
|
+
export function finishSentryRuntimeSession() {
|
|
40
|
+
sentryModule?.endSession();
|
|
41
|
+
}
|
|
42
|
+
export function addSentryRuntimeBreadcrumb(breadcrumb) {
|
|
43
|
+
sentryModule?.addBreadcrumb(breadcrumb);
|
|
44
|
+
}
|
|
45
|
+
export function flushSentryRuntime(timeoutMs) {
|
|
46
|
+
return sentryModule?.flush(timeoutMs) ?? Promise.resolve(true);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=sentry-init.js.map
|
package/dist/sentry.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { BUNDLED_DSN } from "./sentry-dsn.generated.js";
|
|
3
2
|
import { beforeSend } from "./sentry-before-send.js";
|
|
4
|
-
import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
|
|
5
|
-
import { CLI_VERSION } from "./cli-version.js";
|
|
6
|
-
import { getCachedSentryUser } from "./utils/sentry-identity.js";
|
|
7
3
|
import { isEpipe } from "./utils/epipe.js";
|
|
8
4
|
import { companySkillWrapperSentryTagsFor } from "./lib/company-skill-wrapper.js";
|
|
9
5
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
10
6
|
import { incompleteInstallCaptureContext, incompleteInstallMessage, } from "./utils/incomplete-install-error.js";
|
|
11
7
|
import { installTreeTornSentryTagsFor, sentryFingerprintFor, sentryMcpCollisionContextFor, } from "./utils/sentry-fingerprint.js";
|
|
8
|
+
import { finishSentryRuntimeSession, initSentryRuntime } from "./sentry-init.js";
|
|
12
9
|
const inertSentryScope = {
|
|
13
10
|
setTag: () => inertSentryScope,
|
|
14
11
|
setUser: () => inertSentryScope,
|
|
@@ -17,11 +14,10 @@ const inertSentryScope = {
|
|
|
17
14
|
setLevel: () => inertSentryScope,
|
|
18
15
|
setFingerprint: () => inertSentryScope,
|
|
19
16
|
};
|
|
20
|
-
const requireSentry = createRequire(import.meta.url);
|
|
21
17
|
let sentryModule;
|
|
22
18
|
let pendingSentryBreadcrumbs = [];
|
|
23
19
|
function loadSentryModule() {
|
|
24
|
-
sentryModule ??=
|
|
20
|
+
sentryModule ??= createRequire(import.meta.url)("@sentry/node");
|
|
25
21
|
return sentryModule;
|
|
26
22
|
}
|
|
27
23
|
/** Load the SDK through the same resolver used by the lazy Sentry facade. */
|
|
@@ -227,46 +223,13 @@ function flagRegistryWorkerMissingFileTags(error, event) {
|
|
|
227
223
|
* exercised without an ingest request. Production callers pass nothing.
|
|
228
224
|
*/
|
|
229
225
|
export function initSentry(overrides = {}) {
|
|
230
|
-
|
|
231
|
-
if (!
|
|
226
|
+
sentryModule = initSentryRuntime(epipeAwareBeforeSend, overrides, pendingSentryBreadcrumbs);
|
|
227
|
+
if (!sentryModule)
|
|
232
228
|
return;
|
|
233
|
-
const sdk = loadSentryModule();
|
|
234
|
-
sdk.init({
|
|
235
|
-
dsn,
|
|
236
|
-
// import-in-the-middle's async ESM loader makes every later CLI import
|
|
237
|
-
// substantially slower. Keep HQ's explicit breadcrumbs, but give up
|
|
238
|
-
// automatic ESM third-party instrumentation (its spans/breadcrumbs).
|
|
239
|
-
registerEsmLoaderHooks: false,
|
|
240
|
-
// CLI_VERSION reads package.json at runtime; npm_package_version is only
|
|
241
|
-
// set under `npm run`, so an installed `hq` binary would always report
|
|
242
|
-
// 0.0.0 and never match the uploaded source maps.
|
|
243
|
-
release: `hq-cli@${CLI_VERSION}`,
|
|
244
|
-
environment: process.env.HQ_CLI_ENV ?? "production",
|
|
245
|
-
initialScope: {
|
|
246
|
-
tags: { repo: "hq-cli" },
|
|
247
|
-
},
|
|
248
|
-
beforeSend: epipeAwareBeforeSend,
|
|
249
|
-
beforeBreadcrumb,
|
|
250
|
-
// HQ owns this short-lived CLI session explicitly below and closes it from
|
|
251
|
-
// main.ts. Leaving the SDK default enabled starts a competing session.
|
|
252
|
-
integrations: (defaults) => defaults.filter((integration) => integration.name !== "ProcessSession"),
|
|
253
|
-
...overrides,
|
|
254
|
-
});
|
|
255
|
-
// Attribute events to the logged-in HQ identity (best-effort; null when not
|
|
256
|
-
// logged in). A CLI process is one user, so global setUser is correct here.
|
|
257
|
-
const user = getCachedSentryUser();
|
|
258
|
-
if (user)
|
|
259
|
-
sdk.setUser(user);
|
|
260
|
-
for (const breadcrumb of pendingSentryBreadcrumbs)
|
|
261
|
-
sdk.addBreadcrumb(breadcrumb);
|
|
262
229
|
pendingSentryBreadcrumbs = [];
|
|
263
|
-
// Release health: one session per CLI run. Ended + flushed in index.ts's
|
|
264
|
-
// finally (covers every exit path). A captured error auto-marks the session
|
|
265
|
-
// "errored", so crash-free numbers per release stay accurate.
|
|
266
|
-
sdk.startSession();
|
|
267
230
|
}
|
|
268
231
|
/** Finish HQ's one per-invocation release-health session. */
|
|
269
232
|
export function finishSentrySession() {
|
|
270
|
-
|
|
233
|
+
finishSentryRuntimeSession();
|
|
271
234
|
}
|
|
272
235
|
//# sourceMappingURL=sentry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type CommandBreadcrumb = {
|
|
2
|
+
category: "command";
|
|
3
|
+
message: string;
|
|
4
|
+
level: "info";
|
|
5
|
+
};
|
|
6
|
+
export declare function addCommandBreadcrumb(argv: readonly string[], addBreadcrumb: (breadcrumb: CommandBreadcrumb) => void): void;
|
|
7
|
+
//# sourceMappingURL=command-breadcrumb.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { sanitizeArgv } from "./sanitize-argv.js";
|
|
2
|
+
export function addCommandBreadcrumb(argv, addBreadcrumb) {
|
|
3
|
+
addBreadcrumb({
|
|
4
|
+
category: "command",
|
|
5
|
+
message: sanitizeArgv(argv).join(" "),
|
|
6
|
+
level: "info",
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=command-breadcrumb.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type VersionInfo } from "./feedback-versions.js";
|
|
2
2
|
import type { FeedbackLogsBlob } from "./feedback-logs.js";
|
|
3
|
+
export { sanitizeArgv } from "./sanitize-argv.js";
|
|
3
4
|
interface GitContext {
|
|
4
5
|
branch: string | null;
|
|
5
6
|
head: string | null;
|
|
@@ -37,7 +38,5 @@ export interface DiagnosticsBlob {
|
|
|
37
38
|
*/
|
|
38
39
|
logs?: FeedbackLogsBlob;
|
|
39
40
|
}
|
|
40
|
-
export declare function sanitizeArgv(argv: string[]): string[];
|
|
41
41
|
export declare function collectDiagnostics(): DiagnosticsBlob;
|
|
42
|
-
export {};
|
|
43
42
|
//# sourceMappingURL=feedback-diagnostics.d.ts.map
|
|
@@ -3,35 +3,8 @@ import { execFileSync } from "child_process";
|
|
|
3
3
|
import { getRecentBreadcrumbs } from "./breadcrumb-buffer.js";
|
|
4
4
|
import { CLI_VERSION } from "../cli-version.js";
|
|
5
5
|
import { collectVersions } from "./feedback-versions.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"--secret",
|
|
9
|
-
"--password",
|
|
10
|
-
"--key",
|
|
11
|
-
"--api-key",
|
|
12
|
-
"--access-token",
|
|
13
|
-
"--auth-token",
|
|
14
|
-
]);
|
|
15
|
-
export function sanitizeArgv(argv) {
|
|
16
|
-
const result = [];
|
|
17
|
-
for (let i = 0; i < argv.length; i++) {
|
|
18
|
-
const arg = argv[i];
|
|
19
|
-
if (arg.startsWith("--") && arg.includes("=")) {
|
|
20
|
-
const eqIdx = arg.indexOf("=");
|
|
21
|
-
const flag = arg.slice(0, eqIdx);
|
|
22
|
-
if (SECRET_FLAGS.has(flag)) {
|
|
23
|
-
result.push(`${flag}=***`);
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
result.push(arg);
|
|
28
|
-
if (SECRET_FLAGS.has(arg) && i + 1 < argv.length) {
|
|
29
|
-
result.push("***");
|
|
30
|
-
i++;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
6
|
+
import { sanitizeArgv } from "./sanitize-argv.js";
|
|
7
|
+
export { sanitizeArgv } from "./sanitize-argv.js";
|
|
35
8
|
function sanitizeRemoteUrl(url) {
|
|
36
9
|
return url.replace(/https?:\/\/[^@]+@/, "https://***@");
|
|
37
10
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const SECRET_FLAGS = new Set([
|
|
2
|
+
"--token",
|
|
3
|
+
"--secret",
|
|
4
|
+
"--password",
|
|
5
|
+
"--key",
|
|
6
|
+
"--api-key",
|
|
7
|
+
"--access-token",
|
|
8
|
+
"--auth-token",
|
|
9
|
+
]);
|
|
10
|
+
export function sanitizeArgv(argv) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
13
|
+
const arg = argv[i];
|
|
14
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
15
|
+
const eqIdx = arg.indexOf("=");
|
|
16
|
+
const flag = arg.slice(0, eqIdx);
|
|
17
|
+
if (SECRET_FLAGS.has(flag)) {
|
|
18
|
+
result.push(`${flag}=***`);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
result.push(arg);
|
|
23
|
+
if (SECRET_FLAGS.has(arg) && i + 1 < argv.length) {
|
|
24
|
+
result.push("***");
|
|
25
|
+
i += 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=sanitize-argv.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): void;
|
|
1
|
+
export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[], allowedSentryPackageRoots?: readonly string[]): void;
|
|
2
2
|
//# sourceMappingURL=scaffold-fast-import-guard.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = []) {
|
|
3
|
+
function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = [], allowedSentryPackageRoots = []) {
|
|
4
4
|
const dist = path.resolve(distDirectory);
|
|
5
5
|
const main = path.join(dist, "main.js");
|
|
6
6
|
const commands = `${path.join(dist, "commands")}${path.sep}`;
|
|
@@ -9,6 +9,7 @@ function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandMo
|
|
|
9
9
|
const fullNetworkCompat = path.join(dist, "node-network-compat.js");
|
|
10
10
|
const sentryEntrypoint = path.join(dist, "sentry.js");
|
|
11
11
|
const allowedCommands = new Set(allowedCommandModules.map((module) => path.resolve(module)));
|
|
12
|
+
const allowedSentryRoots = allowedSentryPackageRoots.map((module) => path.resolve(module));
|
|
12
13
|
return [...new Set(loadedModules.map((loaded) => {
|
|
13
14
|
if (loaded.startsWith("file:")) {
|
|
14
15
|
try {
|
|
@@ -26,13 +27,15 @@ function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandMo
|
|
|
26
27
|
path.resolve(loaded) === coreCommand ||
|
|
27
28
|
path.resolve(loaded) === fullNetworkCompat ||
|
|
28
29
|
path.resolve(loaded) === sentryEntrypoint ||
|
|
29
|
-
path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}`)
|
|
30
|
+
(path.resolve(loaded).includes(`${path.sep}node_modules${path.sep}@sentry${path.sep}`) &&
|
|
31
|
+
!allowedSentryRoots.some((root) => path.resolve(loaded) === root ||
|
|
32
|
+
path.resolve(loaded).startsWith(root + path.sep))))
|
|
30
33
|
.filter((loaded) => !allowedCommands.has(path.resolve(loaded)) ||
|
|
31
34
|
path.resolve(loaded) === main || path.resolve(loaded) === coreCommand)
|
|
32
35
|
.sort();
|
|
33
36
|
}
|
|
34
|
-
export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory, allowedCommandModules = []) {
|
|
35
|
-
const forbidden = forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules);
|
|
37
|
+
export function assertFastPathStaysCommandGraphFree(loadedModules, distDirectory, allowedCommandModules = [], allowedSentryPackageRoots = []) {
|
|
38
|
+
const forbidden = forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules, allowedSentryPackageRoots);
|
|
36
39
|
if (forbidden.length > 0) {
|
|
37
40
|
throw new Error(`fast core loaded forbidden command modules:\n${forbidden.join("\n")}`);
|
|
38
41
|
}
|
package/docs/anywhere-runtime.md
CHANGED
|
@@ -80,6 +80,14 @@ resident daemon does not support the health operation, doctor falls back to
|
|
|
80
80
|
`ping` for reachability, version and uptime, then warns that a restart is needed
|
|
81
81
|
for full diagnostics.
|
|
82
82
|
|
|
83
|
+
The hq-anywhere-runtime flag also gates the published Claude plugin marketplace
|
|
84
|
+
and Codex pack source. When enabled, hq-cli adds the `indigoai-us/hq-packages`
|
|
85
|
+
Claude marketplace and installs the `hq` plugin, or resolves the exact
|
|
86
|
+
`@indigoai-us/hq-anywhere` alias to
|
|
87
|
+
`github:indigoai-us/hq-packages#packages/hq-pack-hq-anywhere`. The flag-off
|
|
88
|
+
path does not resolve either published source. Use `--marketplace-dir` to
|
|
89
|
+
override the Claude marketplace with a local build.
|
|
90
|
+
|
|
83
91
|
## Link a repository
|
|
84
92
|
|
|
85
93
|
Run `hq link <company>` from a repository to associate it with a company. The
|