@indigoai-us/hq-cli 5.109.5 → 5.109.7
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 +43 -6
- package/dist/commands/files.js +11 -1
- package/dist/commands/people.d.ts +1 -0
- package/dist/commands/people.js +10 -0
- package/dist/commands/pkg-install.d.ts +0 -14
- package/dist/commands/pkg-install.js +0 -127
- package/dist/commands/whoami.js +28 -2
- package/dist/lib/doctor/checks/runtime-probe.d.ts +11 -11
- package/dist/lib/doctor/checks/runtime-probe.js +15 -15
- package/dist/lib/doctor/compat.d.ts +2 -2
- package/dist/lib/doctor/compat.js +1 -1
- package/dist/main.js +18 -15
- package/dist/sentry.js +25 -9
- package/dist/utils/incomplete-install-error.d.ts +38 -16
- package/dist/utils/incomplete-install-error.js +227 -24
- package/dist/utils/people.d.ts +3 -0
- package/dist/utils/registry-client.d.ts +0 -6
- package/dist/utils/registry-client.js +0 -3
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.109.7] — 2026-09-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Add `whoami --json`, structured `files acl --json` with explicit absence,
|
|
10
|
+
and `people resolve --membership-only` with canonical company/person IDs.
|
|
11
|
+
Delegation helpers can now verify exact grants without parsing display tables
|
|
12
|
+
or treating a missing ACL as a transport failure. Existing human output stays available.
|
|
13
|
+
|
|
14
|
+
## [5.109.6] — 2026-09-10
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- A torn hq install no longer files an unactionable crash when a bundled
|
|
19
|
+
third-party dependency of a dependency is missing (HQ-CLI-1Q). `hq packs list`
|
|
20
|
+
loaded the mesh presence client, whose `import mqtt` chain reached
|
|
21
|
+
mqtt-packet's `parser.js` requiring `bl` — mqtt-packet's OWN declared
|
|
22
|
+
transitive dependency — which a partial Windows global install had left
|
|
23
|
+
unwritten. The in-process incomplete-install classifier shipped in 5.108.14
|
|
24
|
+
recognised only a RELATIVE missing specifier for the CJS shape, so this BARE
|
|
25
|
+
specifier fell through to a bare Sentry crash plus an `hq:` line the operator
|
|
26
|
+
could not act on. The classifier now also recognises a bare miss from UNDER
|
|
27
|
+
the running install's own `node_modules/`, but ONLY when the missing specifier
|
|
28
|
+
names a WHOLE package (not a subpath like `bl/lib/x`, which can mean the
|
|
29
|
+
package is present and only that file gone to a version mismatch) AND the
|
|
30
|
+
REQUIRING third-party package's own manifest declares it in its REQUIRED
|
|
31
|
+
`dependencies` (`optionalDependencies` are excluded, being not guaranteed
|
|
32
|
+
installed) — a correct install always writes such a
|
|
33
|
+
dependency, so its absence beside a present requirer can only be a torn
|
|
34
|
+
install, never an hq-cli manifest defect. Those are printed with the same
|
|
35
|
+
input-free reinstall remedy and skipped, at the top-level boundary and in the
|
|
36
|
+
shared `beforeSend`. A bare miss the requirer does NOT declare (a possible
|
|
37
|
+
undeclared or peer-only dependency) stays reported, now enriched on every
|
|
38
|
+
capture route with a bounded `incomplete_install` context naming both packages
|
|
39
|
+
so the next occurrence is attributable instead of bare.
|
|
40
|
+
|
|
5
41
|
## [5.109.5] — 2026-09-10
|
|
6
42
|
|
|
7
43
|
## [5.109.4] — 2026-09-09
|
|
@@ -344,12 +380,13 @@
|
|
|
344
380
|
`npm i -g @indigoai-us/hq-cli` / `pnpm add -g @indigoai-us/hq-cli`) while
|
|
345
381
|
skipping Sentry capture, the same disposition established for the qmd child in
|
|
346
382
|
HQ-CLI-Y. An hq-cli packaging fault stays reportable: a miss under the
|
|
347
|
-
package's own `dist/` or `assets/`, a bare-specifier miss (a
|
|
348
|
-
undeclared dependency
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
383
|
+
package's own `dist/` or `assets/`, a bare-specifier miss (then kept loud as a
|
|
384
|
+
possible undeclared dependency — refined for a dependency the requiring package
|
|
385
|
+
actually declares in HQ-CLI-1Q, see Unreleased), and an esm-loader ENOENT whose
|
|
386
|
+
path did not survive delivery are NOT suppressed — the last is captured WITH a
|
|
387
|
+
bounded `incomplete_install` context so the next occurrence is attributable.
|
|
388
|
+
The drop is wired both at the top-level boundary and in the shared
|
|
389
|
+
`beforeSend`, so it covers every capture route.
|
|
353
390
|
|
|
354
391
|
## [5.108.13] — 2026-09-06
|
|
355
392
|
|
package/dist/commands/files.js
CHANGED
|
@@ -276,7 +276,8 @@ export function registerFilesCommand(program) {
|
|
|
276
276
|
files
|
|
277
277
|
.command("acl <prefix>")
|
|
278
278
|
.description("Show the ACL (access control list) for a file prefix")
|
|
279
|
-
.
|
|
279
|
+
.option("--json", "Output structured ACL data, including explicit absence")
|
|
280
|
+
.action(async (prefix, opts) => {
|
|
280
281
|
try {
|
|
281
282
|
// Read-only, so stripping is safe — but a mismatched anchor would show
|
|
282
283
|
// a DIFFERENT company's ACL than the one the operator pasted.
|
|
@@ -321,7 +322,16 @@ export function registerFilesCommand(program) {
|
|
|
321
322
|
process.exit(1);
|
|
322
323
|
}
|
|
323
324
|
const tree = (await treeRes.json());
|
|
325
|
+
if (!tree || typeof tree.prefix !== 'string' || !Array.isArray(tree.direct)
|
|
326
|
+
|| !Array.isArray(tree.inherited) || !Array.isArray(tree.children)
|
|
327
|
+
|| !(tree.directRow === null || (typeof tree.directRow === 'object' && tree.directRow))) {
|
|
328
|
+
throw new Error('Invalid ACL tree response');
|
|
329
|
+
}
|
|
324
330
|
const row = tree.directRow;
|
|
331
|
+
if (opts.json) {
|
|
332
|
+
console.log(JSON.stringify({ ...tree, schemaVersion: 1, companyUid, exists: row !== null }, null, 2));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
325
335
|
// No own row AND nothing inherited or granted below — preserve the
|
|
326
336
|
// original "no ACL record" exit path.
|
|
327
337
|
if (!row && tree.inherited.length === 0 && tree.children.length === 0) {
|
package/dist/commands/people.js
CHANGED
|
@@ -41,6 +41,8 @@ export function activeMemberToPersonRecord(m) {
|
|
|
41
41
|
slug,
|
|
42
42
|
name,
|
|
43
43
|
email,
|
|
44
|
+
personUid: m.personUid,
|
|
45
|
+
companyUid: m.companyUid,
|
|
44
46
|
role: m.role || undefined,
|
|
45
47
|
type: "internal",
|
|
46
48
|
source: `hq-pro membership: /membership/company/${m.companyUid}`,
|
|
@@ -147,6 +149,13 @@ async function tryFetchRoster(fetchRoster, hqRoot, slug) {
|
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
export async function resolvePersonWithRosterFallback(input) {
|
|
152
|
+
if (input.opts?.membershipOnly) {
|
|
153
|
+
if (input.opts.localOnly)
|
|
154
|
+
throw new Error('--membership-only cannot be combined with --local-only');
|
|
155
|
+
// No local fallback: callers need authoritative IDs and must see transport errors.
|
|
156
|
+
const roster = await (input.fetchRoster ?? fetchMembershipRoster)(input.hqRoot, input.slug);
|
|
157
|
+
return resolveNameToEmail(roster, input.name);
|
|
158
|
+
}
|
|
150
159
|
const localPeople = listCompanyPeople(input.hqRoot, input.slug);
|
|
151
160
|
const local = resolveNameToEmail(localPeople, input.name);
|
|
152
161
|
if (local.status !== "not_found" || input.opts?.localOnly)
|
|
@@ -239,6 +248,7 @@ export function registerPeopleCommand(program, deps = {}) {
|
|
|
239
248
|
people
|
|
240
249
|
.command("resolve <name>")
|
|
241
250
|
.description("Resolve a person name to their email address")
|
|
251
|
+
.option("--membership-only", "Resolve from authoritative company membership, ignoring local records")
|
|
242
252
|
.option("--json", "Output JSON instead of plain text")
|
|
243
253
|
.option("--local-only", "Skip network fallback; resolve only from the local people roster")
|
|
244
254
|
.action(async (name, opts) => {
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq packages install <slug> — download, verify, and extract a package (US-005)
|
|
3
|
-
*
|
|
4
|
-
* Flow:
|
|
5
|
-
* 1. Check auth (prompt login if needed)
|
|
6
|
-
* 2. Check entitlement via API
|
|
7
|
-
* 3. Download tarball via presigned URL
|
|
8
|
-
* 4. Verify SHA256 hash
|
|
9
|
-
* 5. Verify RSA signature (if provided)
|
|
10
|
-
* 6. Extract to packages/installed/<slug>/
|
|
11
|
-
* 7. Validate extracted package.yaml slug matches
|
|
12
|
-
* 8. Update packages/registry.yaml
|
|
13
|
-
* 9. Print next-step message
|
|
14
|
-
*/
|
|
15
1
|
import { Command } from 'commander';
|
|
16
2
|
export declare function registerPackageInstallCommand(parent: Command): void;
|
|
17
3
|
//# sourceMappingURL=pkg-install.d.ts.map
|
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq packages install <slug> — download, verify, and extract a package (US-005)
|
|
3
|
-
*
|
|
4
|
-
* Flow:
|
|
5
|
-
* 1. Check auth (prompt login if needed)
|
|
6
|
-
* 2. Check entitlement via API
|
|
7
|
-
* 3. Download tarball via presigned URL
|
|
8
|
-
* 4. Verify SHA256 hash
|
|
9
|
-
* 5. Verify RSA signature (if provided)
|
|
10
|
-
* 6. Extract to packages/installed/<slug>/
|
|
11
|
-
* 7. Validate extracted package.yaml slug matches
|
|
12
|
-
* 8. Update packages/registry.yaml
|
|
13
|
-
* 9. Print next-step message
|
|
14
|
-
*/
|
|
15
|
-
import * as fs from 'fs';
|
|
16
|
-
import * as os from 'os';
|
|
17
|
-
import * as path from 'path';
|
|
18
|
-
import { parseUserYaml } from '../utils/user-yaml-error.js';
|
|
19
|
-
import { execSync } from 'child_process';
|
|
20
1
|
import chalk from 'chalk';
|
|
21
|
-
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
22
|
-
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
23
|
-
import { getRegistryUrl, RegistryClient, } from '../utils/registry-client.js';
|
|
24
|
-
import { verifySha256, verifyRsaSignature } from '../utils/integrity.js';
|
|
25
|
-
import { addToRegistry } from '../utils/registry.js';
|
|
26
2
|
import { peekHqApiKey, requireApiKeyCapability, } from '../utils/resolve-vault-credential.js';
|
|
27
3
|
import { MARKETPLACE_PREFIX, installPack, sourceMatchesPackPattern, } from './pack-install.js';
|
|
28
4
|
/**
|
|
@@ -105,107 +81,4 @@ export function registerPackageInstallCommand(parent) {
|
|
|
105
81
|
}
|
|
106
82
|
});
|
|
107
83
|
}
|
|
108
|
-
async function installPackage(slug, company) {
|
|
109
|
-
// 1. Auth
|
|
110
|
-
const accessToken = await ensureCognitoToken();
|
|
111
|
-
const registryUrl = getRegistryUrl();
|
|
112
|
-
const client = new RegistryClient(registryUrl, accessToken);
|
|
113
|
-
// 2. Check entitlement
|
|
114
|
-
console.log(chalk.dim(`Checking entitlement for ${slug}...`));
|
|
115
|
-
const entitlement = await client.checkEntitlement(slug);
|
|
116
|
-
if (!entitlement.entitled) {
|
|
117
|
-
throw new Error(`You are not entitled to package "${slug}". Visit the registry to purchase or request access.`);
|
|
118
|
-
}
|
|
119
|
-
// 3. Get download URL
|
|
120
|
-
console.log(chalk.dim('Fetching download URL...'));
|
|
121
|
-
const download = await client.getDownloadUrl(slug);
|
|
122
|
-
// 4. Download to temp file
|
|
123
|
-
const tmpDir = os.tmpdir();
|
|
124
|
-
const tmpFile = path.resolve(tmpDir, `hq-pkg-${slug}-${Date.now()}.tar.gz`);
|
|
125
|
-
try {
|
|
126
|
-
console.log(chalk.dim('Downloading package...'));
|
|
127
|
-
const response = await fetch(download.url, {
|
|
128
|
-
signal: AbortSignal.timeout(120_000),
|
|
129
|
-
});
|
|
130
|
-
if (!response.ok) {
|
|
131
|
-
throw new Error(`Download failed (${response.status})`);
|
|
132
|
-
}
|
|
133
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
134
|
-
fs.writeFileSync(tmpFile, buffer);
|
|
135
|
-
// 5. Verify SHA256
|
|
136
|
-
console.log(chalk.dim('Verifying integrity...'));
|
|
137
|
-
const hashValid = await verifySha256(tmpFile, download.sha256);
|
|
138
|
-
if (!hashValid) {
|
|
139
|
-
throw new Error('SHA256 hash mismatch — the downloaded file may be corrupted or tampered with.');
|
|
140
|
-
}
|
|
141
|
-
// 6. Verify RSA signature (if provided)
|
|
142
|
-
if (download.signature) {
|
|
143
|
-
const sigValid = verifyRsaSignature(download.sha256, download.signature);
|
|
144
|
-
if (!sigValid) {
|
|
145
|
-
throw new Error('RSA signature verification failed — the package may have been tampered with or the public key is missing/invalid. Aborting install.');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// 7. Extract
|
|
149
|
-
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
150
|
-
const installDir = path.resolve(hqRoot, 'packages', 'installed', slug);
|
|
151
|
-
// Clean existing installation
|
|
152
|
-
if (fs.existsSync(installDir)) {
|
|
153
|
-
fs.rmSync(installDir, { recursive: true, force: true });
|
|
154
|
-
}
|
|
155
|
-
fs.mkdirSync(installDir, { recursive: true });
|
|
156
|
-
execSync(`tar -xzf "${tmpFile}" -C "${installDir}"`, {
|
|
157
|
-
stdio: 'pipe',
|
|
158
|
-
});
|
|
159
|
-
// 8. Validate package.yaml slug
|
|
160
|
-
const packageYamlPath = path.resolve(installDir, 'package.yaml');
|
|
161
|
-
if (fs.existsSync(packageYamlPath)) {
|
|
162
|
-
const pkgContent = fs.readFileSync(packageYamlPath, 'utf-8');
|
|
163
|
-
const pkgMeta = parseUserYaml(pkgContent, packageYamlPath);
|
|
164
|
-
if (pkgMeta?.slug && pkgMeta.slug !== slug) {
|
|
165
|
-
// Mismatch — clean up and abort
|
|
166
|
-
fs.rmSync(installDir, { recursive: true, force: true });
|
|
167
|
-
throw new Error(`Package slug mismatch: expected "${slug}", got "${pkgMeta.slug}"`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// 9. Get package info for registry entry
|
|
171
|
-
let version = 'unknown';
|
|
172
|
-
let name = slug;
|
|
173
|
-
try {
|
|
174
|
-
const pkgInfo = await client.getPackage(slug);
|
|
175
|
-
version = pkgInfo.package.latest_version;
|
|
176
|
-
name = pkgInfo.package.name;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// Best-effort — fall back to package.yaml if present
|
|
180
|
-
const packageYaml = path.resolve(installDir, 'package.yaml');
|
|
181
|
-
if (fs.existsSync(packageYaml)) {
|
|
182
|
-
const content = fs.readFileSync(packageYaml, 'utf-8');
|
|
183
|
-
const meta = parseUserYaml(content, packageYaml);
|
|
184
|
-
if (meta?.version)
|
|
185
|
-
version = meta.version;
|
|
186
|
-
if (meta?.name)
|
|
187
|
-
name = meta.name;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// 10. Update registry.yaml
|
|
191
|
-
const now = new Date().toISOString();
|
|
192
|
-
addToRegistry(hqRoot, {
|
|
193
|
-
name,
|
|
194
|
-
slug,
|
|
195
|
-
version,
|
|
196
|
-
source: registryUrl,
|
|
197
|
-
scope: company,
|
|
198
|
-
installed_at: now,
|
|
199
|
-
updated_at: now,
|
|
200
|
-
});
|
|
201
|
-
console.log(chalk.green(`\nInstalled ${slug}@${version} to packages/installed/${slug}/`));
|
|
202
|
-
console.log(chalk.cyan('Run /package-install ' + slug + ' in Claude to merge into your HQ.'));
|
|
203
|
-
}
|
|
204
|
-
finally {
|
|
205
|
-
// Clean up temp file — never leave partial downloads
|
|
206
|
-
if (fs.existsSync(tmpFile)) {
|
|
207
|
-
fs.unlinkSync(tmpFile);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
84
|
//# sourceMappingURL=pkg-install.js.map
|
package/dist/commands/whoami.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { loadCachedTokens, isExpiring, loadMachineCreds, } from '@indigoai-us/hq-cloud';
|
|
6
6
|
import { peekIdToken as decodeIdToken } from "../utils/id-token.js";
|
|
7
|
-
import { resolveCognitoTokenSource } from "../utils/cognito-session.js";
|
|
7
|
+
import { loadMachineCachedTokens, resolveCognitoTokenSource } from "../utils/cognito-session.js";
|
|
8
8
|
function peekIdToken(idToken) {
|
|
9
9
|
const decoded = decodeIdToken(idToken);
|
|
10
10
|
return {
|
|
@@ -22,13 +22,39 @@ export function registerWhoamiCommand(program) {
|
|
|
22
22
|
program
|
|
23
23
|
.command('whoami')
|
|
24
24
|
.description('Show the currently authenticated user')
|
|
25
|
-
.
|
|
25
|
+
.option('--json', 'Output identity metadata as JSON (never tokens)')
|
|
26
|
+
.action(async (opts) => {
|
|
26
27
|
try {
|
|
27
28
|
// Effective source (same as auth status / ensureCognitoToken): env
|
|
28
29
|
// opt-in, fresh-box default-creds when no usable person session, or
|
|
29
30
|
// after a definitive person-refresh rejection marked the session rejected.
|
|
30
31
|
const machine = resolveCognitoTokenSource() === "machine";
|
|
31
32
|
const cached = loadCachedTokens();
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
const machineCreds = machine ? loadMachineCreds() : null;
|
|
35
|
+
const identityCache = machine ? loadMachineCachedTokens() : cached;
|
|
36
|
+
const identity = identityCache ? peekIdToken(identityCache.idToken) : {};
|
|
37
|
+
const claims = identityCache ? decodeIdToken(identityCache.idToken) : {};
|
|
38
|
+
// Modern credentials carry the canonical UID. Legacy caches may only
|
|
39
|
+
// supply a UID when their subject is bound to the selected username.
|
|
40
|
+
const boundLegacyAgent = machineCreds && identity.entityType === 'agent'
|
|
41
|
+
&& (claims['cognito:username'] === machineCreds.username
|
|
42
|
+
|| claims.username === machineCreds.username || claims.sub === machineCreds.username)
|
|
43
|
+
? identity.entityUid ?? null : null;
|
|
44
|
+
const agentUid = machineCreds?.entityUid !== undefined
|
|
45
|
+
? (machineCreds.entityUid.startsWith('agt_') ? machineCreds.entityUid : null)
|
|
46
|
+
: boundLegacyAgent;
|
|
47
|
+
console.log(JSON.stringify({
|
|
48
|
+
schemaVersion: 1,
|
|
49
|
+
authenticated: machine ? Boolean(machineCreds) : Boolean(cached && !isExpiring(cached, 0)),
|
|
50
|
+
tokenSource: machine ? 'machine' : 'person',
|
|
51
|
+
email: machine ? null : identity.email ?? null,
|
|
52
|
+
personUid: !machine && identity.entityType === 'person' ? identity.entityUid ?? null : null,
|
|
53
|
+
agentUid,
|
|
54
|
+
username: machineCreds?.username ?? null,
|
|
55
|
+
}, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
32
58
|
if (machine) {
|
|
33
59
|
// Machine identities (company agents) mint sessions on demand from
|
|
34
60
|
// long-lived creds — report the machine identity honestly even when
|
|
@@ -50,7 +50,7 @@ export declare const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/pol
|
|
|
50
50
|
/** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
|
|
51
51
|
export declare const REQUIRED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse"];
|
|
52
52
|
/**
|
|
53
|
-
* Default runtime marker path an agents-v2 (
|
|
53
|
+
* Default runtime marker path an agents-v2 (hq-fleet) box writes its runtime mode
|
|
54
54
|
* to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
|
|
55
55
|
* `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
|
|
56
56
|
*/
|
|
@@ -129,31 +129,31 @@ export interface AgentsV2AttestationOptions {
|
|
|
129
129
|
*/
|
|
130
130
|
export declare function isAgentsV2Runtime(hqRoot: string, env?: NodeJS.ProcessEnv): boolean;
|
|
131
131
|
/**
|
|
132
|
-
* Env var overriding the agents-v2 runtime (
|
|
132
|
+
* Env var overriding the agents-v2 runtime (hq-fleet) config path (chiefly for
|
|
133
133
|
* tests), mirroring `HQ_RUNTIME_MARKER_FILE` and the shell's
|
|
134
|
-
* `
|
|
134
|
+
* `HQ_FLEET_CONFIG_FILE`.
|
|
135
135
|
*/
|
|
136
|
-
export declare const
|
|
136
|
+
export declare const HQ_FLEET_CONFIG_ENV = "HQ_FLEET_CONFIG_FILE";
|
|
137
137
|
/**
|
|
138
|
-
* The agents-v2 runtime (
|
|
139
|
-
* adapter into every lifecycle event — the env override, else `~/.
|
|
138
|
+
* The agents-v2 runtime (hq-fleet) config whose `hooks:` block wires the on-box
|
|
139
|
+
* adapter into every lifecycle event — the env override, else `~/.hq-fleet/config.yaml`
|
|
140
140
|
* (where `provision/render-config.sh` renders it). Mirrors the shell's
|
|
141
|
-
* `
|
|
141
|
+
* `HQ_FLEET_CONFIG_FILE="${HQ_FLEET_CONFIG_FILE:-$HOME/.hq-fleet/config.yaml}"`.
|
|
142
142
|
*/
|
|
143
|
-
export declare function
|
|
143
|
+
export declare function resolveHqFleetConfigPath(env?: NodeJS.ProcessEnv): string;
|
|
144
144
|
/**
|
|
145
145
|
* Whether the agents-v2 runtime actually wires the on-box hook adapter.
|
|
146
146
|
*
|
|
147
147
|
* The wiring lives in the RUNTIME's hook config, not `.claude/settings.json`. On
|
|
148
148
|
* a real box the v2 runtime's shell-hook dispatcher (`agent/shell_hooks.py`)
|
|
149
|
-
* reads a `hooks:` block from `~/.
|
|
149
|
+
* reads a `hooks:` block from `~/.hq-fleet/config.yaml` with one entry per
|
|
150
150
|
* lifecycle event, each invoking `hq-agents-v2-hook-adapter.sh`; the adapter in
|
|
151
151
|
* turn READS `.claude/settings.json` (via `hook-adapter-core.sh`) to fan out to
|
|
152
152
|
* the classic `.claude/hooks` set. So `.claude/settings.json` never NAMES the
|
|
153
153
|
* adapter — it wires the classic `hook-gate.sh` hooks — and grepping it for the
|
|
154
154
|
* adapter always fails on a real box (verified on the v2.17 canary
|
|
155
155
|
* i-0277243ad3aed8109, 2026-09-05: settings.json had 0 adapter refs / 94
|
|
156
|
-
* hook-gate.sh refs, while ~/.
|
|
156
|
+
* hook-gate.sh refs, while ~/.hq-fleet/config.yaml wired the adapter across 7
|
|
157
157
|
* events). Require BOTH the adapter installed under the tree at
|
|
158
158
|
* {@link AGENTS_V2_ADAPTER_RELPATH} AND the runtime config invoking it. Mirrors
|
|
159
159
|
* `hq_runtime_config_wires_v2_adapter` in `check-hq-hooks.sh`.
|
|
@@ -169,7 +169,7 @@ export declare function runtimeConfigWiresV2Adapter(hqRoot: string, env?: NodeJS
|
|
|
169
169
|
export declare function v2LedgerPresent(opts: AgentsV2AttestationOptions): boolean;
|
|
170
170
|
/**
|
|
171
171
|
* All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
|
|
172
|
-
*
|
|
172
|
+
* hq-fleet box that host detection leaves platform-unknown; never to withhold it.
|
|
173
173
|
* The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
|
|
174
174
|
*/
|
|
175
175
|
export declare function agentsV2Attested(opts: AgentsV2AttestationOptions): boolean;
|
|
@@ -53,7 +53,7 @@ export const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trig
|
|
|
53
53
|
/** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
|
|
54
54
|
export const REQUIRED_HOOK_EVENTS = ["SessionStart", "PreToolUse"];
|
|
55
55
|
/**
|
|
56
|
-
* Default runtime marker path an agents-v2 (
|
|
56
|
+
* Default runtime marker path an agents-v2 (hq-fleet) box writes its runtime mode
|
|
57
57
|
* to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
|
|
58
58
|
* `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
|
|
59
59
|
*/
|
|
@@ -161,36 +161,36 @@ function readRuntimeMarkerMode(markerPath) {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* Env var overriding the agents-v2 runtime (
|
|
164
|
+
* Env var overriding the agents-v2 runtime (hq-fleet) config path (chiefly for
|
|
165
165
|
* tests), mirroring `HQ_RUNTIME_MARKER_FILE` and the shell's
|
|
166
|
-
* `
|
|
166
|
+
* `HQ_FLEET_CONFIG_FILE`.
|
|
167
167
|
*/
|
|
168
|
-
export const
|
|
168
|
+
export const HQ_FLEET_CONFIG_ENV = "HQ_FLEET_CONFIG_FILE";
|
|
169
169
|
/**
|
|
170
|
-
* The agents-v2 runtime (
|
|
171
|
-
* adapter into every lifecycle event — the env override, else `~/.
|
|
170
|
+
* The agents-v2 runtime (hq-fleet) config whose `hooks:` block wires the on-box
|
|
171
|
+
* adapter into every lifecycle event — the env override, else `~/.hq-fleet/config.yaml`
|
|
172
172
|
* (where `provision/render-config.sh` renders it). Mirrors the shell's
|
|
173
|
-
* `
|
|
173
|
+
* `HQ_FLEET_CONFIG_FILE="${HQ_FLEET_CONFIG_FILE:-$HOME/.hq-fleet/config.yaml}"`.
|
|
174
174
|
*/
|
|
175
|
-
export function
|
|
176
|
-
const override = env[
|
|
175
|
+
export function resolveHqFleetConfigPath(env = process.env) {
|
|
176
|
+
const override = env[HQ_FLEET_CONFIG_ENV]?.trim();
|
|
177
177
|
if (override)
|
|
178
178
|
return override;
|
|
179
|
-
return path.join(os.homedir(), ".
|
|
179
|
+
return path.join(os.homedir(), ".hq-fleet", "config.yaml");
|
|
180
180
|
}
|
|
181
181
|
/**
|
|
182
182
|
* Whether the agents-v2 runtime actually wires the on-box hook adapter.
|
|
183
183
|
*
|
|
184
184
|
* The wiring lives in the RUNTIME's hook config, not `.claude/settings.json`. On
|
|
185
185
|
* a real box the v2 runtime's shell-hook dispatcher (`agent/shell_hooks.py`)
|
|
186
|
-
* reads a `hooks:` block from `~/.
|
|
186
|
+
* reads a `hooks:` block from `~/.hq-fleet/config.yaml` with one entry per
|
|
187
187
|
* lifecycle event, each invoking `hq-agents-v2-hook-adapter.sh`; the adapter in
|
|
188
188
|
* turn READS `.claude/settings.json` (via `hook-adapter-core.sh`) to fan out to
|
|
189
189
|
* the classic `.claude/hooks` set. So `.claude/settings.json` never NAMES the
|
|
190
190
|
* adapter — it wires the classic `hook-gate.sh` hooks — and grepping it for the
|
|
191
191
|
* adapter always fails on a real box (verified on the v2.17 canary
|
|
192
192
|
* i-0277243ad3aed8109, 2026-09-05: settings.json had 0 adapter refs / 94
|
|
193
|
-
* hook-gate.sh refs, while ~/.
|
|
193
|
+
* hook-gate.sh refs, while ~/.hq-fleet/config.yaml wired the adapter across 7
|
|
194
194
|
* events). Require BOTH the adapter installed under the tree at
|
|
195
195
|
* {@link AGENTS_V2_ADAPTER_RELPATH} AND the runtime config invoking it. Mirrors
|
|
196
196
|
* `hq_runtime_config_wires_v2_adapter` in `check-hq-hooks.sh`.
|
|
@@ -201,7 +201,7 @@ export function runtimeConfigWiresV2Adapter(hqRoot, env = process.env) {
|
|
|
201
201
|
return false;
|
|
202
202
|
let raw;
|
|
203
203
|
try {
|
|
204
|
-
raw = fs.readFileSync(
|
|
204
|
+
raw = fs.readFileSync(resolveHqFleetConfigPath(env), "utf8");
|
|
205
205
|
}
|
|
206
206
|
catch {
|
|
207
207
|
return false;
|
|
@@ -265,7 +265,7 @@ function ledgerDirHasFreshTxt(dir, cutoffMs) {
|
|
|
265
265
|
}
|
|
266
266
|
/**
|
|
267
267
|
* All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
|
|
268
|
-
*
|
|
268
|
+
* hq-fleet box that host detection leaves platform-unknown; never to withhold it.
|
|
269
269
|
* The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
|
|
270
270
|
*/
|
|
271
271
|
export function agentsV2Attested(opts) {
|
|
@@ -286,7 +286,7 @@ export function checkRuntimeProbe(context) {
|
|
|
286
286
|
const checkId = `${RUNTIME_PROBE_PREFIX}.enforcement`;
|
|
287
287
|
const target = POLICY_TRIGGER_LEDGER_RELPATH;
|
|
288
288
|
const scope = sessionId ? ` for session ${sessionId}` : "";
|
|
289
|
-
// agents-v2 (
|
|
289
|
+
// agents-v2 (hq-fleet) self-attestation. hq doctor leaves the hq-fleet host
|
|
290
290
|
// platform-unknown and, on an unknown host, the probe would report UNKNOWN
|
|
291
291
|
// below. But the on-box adapter provably wrote the ledger through the same
|
|
292
292
|
// .claude hooks, so grant PASS — and report platform "agents-v2" — when, and
|
|
@@ -75,12 +75,12 @@ export interface DeriveVerdictOptions {
|
|
|
75
75
|
*/
|
|
76
76
|
requireLedger?: boolean;
|
|
77
77
|
/**
|
|
78
|
-
* Whether this is an attested agents-v2 (
|
|
78
|
+
* Whether this is an attested agents-v2 (hq-fleet) box: the runtime is
|
|
79
79
|
* agents-v2, `.claude/settings.json` wires the on-box adapter, and a
|
|
80
80
|
* policy-trigger ledger exists (the exact session's when a session id is
|
|
81
81
|
* given; otherwise a ledger fresh within the freshness window). When true,
|
|
82
82
|
* OBSERVED is granted even though the doctor's runtime check did not report
|
|
83
|
-
* PASS — because hq doctor leaves the
|
|
83
|
+
* PASS — because hq doctor leaves the hq-fleet host platform-unknown, yet the
|
|
84
84
|
* on-box adapter provably wrote the ledger through the same .claude hooks.
|
|
85
85
|
*
|
|
86
86
|
* This is the exact twin of the `agents_v2_attested` override in
|
|
@@ -81,7 +81,7 @@ export function deriveCheckHqHooksVerdict(doc, options = {}) {
|
|
|
81
81
|
// ledger" contract.
|
|
82
82
|
const runtimeResult = doc.results.find((result) => result.checkId === RUNTIME_ENFORCEMENT_CHECK_ID);
|
|
83
83
|
let runtime = observeRuntime(runtimeResult?.status, requireLedger);
|
|
84
|
-
// agents-v2 self-attestation: hq doctor leaves the
|
|
84
|
+
// agents-v2 self-attestation: hq doctor leaves the hq-fleet host
|
|
85
85
|
// platform-unknown and so does not report the runtime check as PASS, but the
|
|
86
86
|
// on-box adapter provably wrote the ledger through the same .claude hooks.
|
|
87
87
|
// Grant the identical OBSERVED verdict rather than relaying the host-unknown
|
package/dist/main.js
CHANGED
|
@@ -563,21 +563,24 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
563
563
|
// An IN-PROCESS module-load failure that means hq-cli's OWN installed
|
|
564
564
|
// package tree is incomplete at load time — a partial/interrupted global
|
|
565
565
|
// install left a bundled file unwritten (HQ-CLI-1N, a CJS relative-sibling
|
|
566
|
-
// MODULE_NOT_FOUND
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
//
|
|
574
|
-
// the
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
566
|
+
// MODULE_NOT_FOUND; HQ-CLI-1Q, a CJS BARE-specifier MODULE_NOT_FOUND for a
|
|
567
|
+
// dependency the requiring third-party package's own manifest declares), or
|
|
568
|
+
// a concurrent global install rewrote the running tree so an ESM module
|
|
569
|
+
// present at resolve was gone at read (HQ-CLI-1M, an esm-loader ENOENT).
|
|
570
|
+
// All carry no hq-cli frames and reached the final else, filing a bare
|
|
571
|
+
// crash and an unactionable line. An incomplete install is the caller's
|
|
572
|
+
// machine, the disposition HQ-CLI-Y already established for the qmd CHILD —
|
|
573
|
+
// print the input-free reinstall remedy and skip capture. Placed with the
|
|
574
|
+
// environmental family (after the typed qmd carriers and the hq state-write
|
|
575
|
+
// carrier, before environmentalFsErrorMessage): the signatures are disjoint
|
|
576
|
+
// — ENVIRONMENTAL_FS_CODES is only ENOSPC/EDQUOT/EROFS (never
|
|
577
|
+
// ENOENT/MODULE_NOT_FOUND), no qmd carrier sets requireStack or an
|
|
578
|
+
// esm-loader frame, and the classified file must sit under
|
|
579
|
+
// <packageRoot>/node_modules — so ordering changes nothing that exists. The
|
|
580
|
+
// UNATTRIBUTABLE shapes are deliberately NOT suppressed and are captured
|
|
581
|
+
// WITH bounded context on the generic path below: an esm-loader ENOENT
|
|
582
|
+
// whose path did not survive, and a bare miss the requirer does NOT declare
|
|
583
|
+
// (a possible undeclared/peer dependency, now named in the context).
|
|
581
584
|
const incompleteInstallMsg = qmdMsg || collectionMsg || terminatedMsg || llmDisabledMsg || moduleMissingMsg || storeMissingMsg || storeUnopenableMsg || queryDocumentMsg || modelDownloadMsg || workdirMissingMsg || stateWriteMsg
|
|
582
585
|
? null
|
|
583
586
|
: incompleteInstallMessage(err);
|
package/dist/sentry.js
CHANGED
|
@@ -6,7 +6,7 @@ import { CLI_VERSION } from "./cli-version.js";
|
|
|
6
6
|
import { getCachedSentryUser } from "./utils/sentry-identity.js";
|
|
7
7
|
import { isEpipe } from "./utils/epipe.js";
|
|
8
8
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
9
|
-
import { incompleteInstallMessage } from "./utils/incomplete-install-error.js";
|
|
9
|
+
import { incompleteInstallCaptureContext, incompleteInstallMessage, } from "./utils/incomplete-install-error.js";
|
|
10
10
|
import { sentryFingerprintFor } from "./utils/sentry-fingerprint.js";
|
|
11
11
|
/**
|
|
12
12
|
* Drop broken-pipe (EPIPE) crashes before scrubbing/send. A closed downstream
|
|
@@ -35,17 +35,33 @@ export function epipeAwareBeforeSend(event, hint) {
|
|
|
35
35
|
// Path-independent belt for an IN-PROCESS incomplete-install module-load
|
|
36
36
|
// failure — hq-cli's own globally installed tree is not intact at load time
|
|
37
37
|
// (HQ-CLI-1N, a CJS relative-sibling MODULE_NOT_FOUND under its bundled
|
|
38
|
-
// node_modules; HQ-CLI-
|
|
39
|
-
//
|
|
40
|
-
// for
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
38
|
+
// node_modules; HQ-CLI-1Q, a CJS BARE-specifier MODULE_NOT_FOUND the requiring
|
|
39
|
+
// third-party package's own manifest declares; HQ-CLI-1M, an esm-loader ENOENT
|
|
40
|
+
// for a file present at resolve and gone at read). handleTopLevelError already
|
|
41
|
+
// prints the reinstall remedy for the top-level route; dropping the event here
|
|
42
|
+
// suppresses the fatal regardless of route — the unhandled-rejection boundary,
|
|
43
|
+
// the command-level captureException sites, and bin/hq-auth-refresh —
|
|
44
|
+
// mirroring the EPIPE and environmental-fs drops above. The classifier reads
|
|
45
|
+
// only structured fields (plus, for the bare shape, the requiring package's
|
|
46
|
+
// own manifest) and the failing file must sit under the running install's
|
|
47
|
+
// node_modules, so an hq-cli packaging fault (a dist/ miss, or a bare miss the
|
|
48
|
+
// requirer does NOT declare — a possible undeclared/peer dependency) and the
|
|
46
49
|
// path-less unattributable shape are NOT dropped here and stay captured.
|
|
47
50
|
if (incompleteInstallMessage(hint?.originalException))
|
|
48
51
|
return null;
|
|
52
|
+
// An incomplete-install shape that is NOT suppressed (a bare miss the requirer
|
|
53
|
+
// does not declare, or a path-less esm-loader ENOENT) should still arrive
|
|
54
|
+
// ATTRIBUTABLE on every capture route, not only the top-level boundary:
|
|
55
|
+
// handleTopLevelError in main.ts is the ONLY caller of
|
|
56
|
+
// incompleteInstallCaptureContext, so events reaching this hook directly — the
|
|
57
|
+
// unhandled-rejection integration, the command-level captureException sites,
|
|
58
|
+
// and bin/hq-auth-refresh — would otherwise survive bare. Merge the bounded
|
|
59
|
+
// context here too. Idempotent: main.ts attaches the same block on its route,
|
|
60
|
+
// and an already-present context wins the spread.
|
|
61
|
+
const installContext = incompleteInstallCaptureContext(hint?.originalException);
|
|
62
|
+
if (installContext) {
|
|
63
|
+
event.contexts = { ...installContext, ...event.contexts };
|
|
64
|
+
}
|
|
49
65
|
// Group an event that survives to send by a BOUNDED machine discriminator so
|
|
50
66
|
// unrelated gateway/HTTP failures stop colliding into one fungible issue
|
|
51
67
|
// (HQ-CLI collision, Sentry 7642756130). Placed here — path-independent,
|
|
@@ -21,9 +21,9 @@ export type PackageRootResolver = () => string | null;
|
|
|
21
21
|
* means "handle as usual (capture to Sentry)". Never throws — a resolver that
|
|
22
22
|
* fails yields null.
|
|
23
23
|
*/
|
|
24
|
-
export declare function incompleteInstallMessage(err: unknown, resolvePackageRoot?: PackageRootResolver): string | null;
|
|
25
|
-
/** Bounded, scrubber-safe diagnostics for an unattributable esm-loader ENOENT. */
|
|
26
|
-
export type
|
|
24
|
+
export declare function incompleteInstallMessage(err: unknown, resolvePackageRoot?: PackageRootResolver, fileSystem?: Pick<typeof fs, "readFileSync">): string | null;
|
|
25
|
+
/** Bounded, scrubber-safe diagnostics for an unattributable esm-loader ENOENT (HQ-CLI-1M). */
|
|
26
|
+
export type IncompleteInstallEsmDiagnostics = {
|
|
27
27
|
packageRoot: string;
|
|
28
28
|
packageJsonExists: boolean;
|
|
29
29
|
nodeModulesExists: boolean;
|
|
@@ -31,20 +31,42 @@ export type IncompleteInstallDiagnostics = {
|
|
|
31
31
|
code: string;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
* Bounded, scrubber-safe diagnostics for a NOT-suppressed third-party bare miss
|
|
35
|
+
* (HQ-CLI-1Q): a possible undeclared / peer-only dependency defect, made
|
|
36
|
+
* attributable — the missing package, the requiring package, and the fact that
|
|
37
|
+
* the requirer's own manifest did NOT declare it. Both names live inside
|
|
38
|
+
* hq-cli's dependency graph, so grouping cardinality stays bounded.
|
|
39
|
+
*/
|
|
40
|
+
export type IncompleteInstallBareDiagnostics = {
|
|
41
|
+
missingPackage: string;
|
|
42
|
+
requiringPackage: string;
|
|
43
|
+
requirerDeclaresMissing: false;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Both enriched shapes as a single OPEN record — every field optional so a
|
|
47
|
+
* consumer can forward either shape to Sentry without narrowing (the boundary
|
|
48
|
+
* and beforeSend only pass the block through). Every value CONSTRUCTED here is
|
|
49
|
+
* exactly one of the two strict shapes above; the looseness is only at the read
|
|
50
|
+
* boundary.
|
|
51
|
+
*/
|
|
52
|
+
export type IncompleteInstallDiagnostics = Partial<IncompleteInstallEsmDiagnostics & IncompleteInstallBareDiagnostics>;
|
|
53
|
+
/**
|
|
54
|
+
* When an incomplete-install failure reaches the capture path WITHOUT being
|
|
55
|
+
* suppressed, return a bounded `contexts.incomplete_install` block so the next
|
|
56
|
+
* occurrence carries the evidence this one lacked; otherwise return undefined
|
|
57
|
+
* (bare capture). Two enriched shapes:
|
|
58
|
+
* - a third-party BARE-specifier miss the requirer did not declare (HQ-CLI-1Q)
|
|
59
|
+
* → { missingPackage, requiringPackage, requirerDeclaresMissing:false };
|
|
60
|
+
* - an esm-loader ENOENT whose path did not survive delivery (HQ-CLI-1M) — the
|
|
61
|
+
* shape the delivered payload arrived in, where neither the exception value
|
|
62
|
+
* nor node_system_error carried a `path` → { packageRoot, packageJsonExists,
|
|
63
|
+
* nodeModulesExists, esmLoaderFrame, code }.
|
|
64
|
+
* Built with the byte-capped, scrubber-safe discipline of
|
|
65
|
+
* package-root-diagnostics.ts — never a caller argv, query, or user-minted
|
|
66
|
+
* value. Never throws. main.ts attaches this on the generic capture path
|
|
67
|
+
* exactly as qmdSpawnFailureCaptureContext already does.
|
|
46
68
|
*/
|
|
47
|
-
export declare function incompleteInstallCaptureContext(err: unknown, resolvePackageRoot?: PackageRootResolver, fileSystem?: Pick<typeof fs, "existsSync">): {
|
|
69
|
+
export declare function incompleteInstallCaptureContext(err: unknown, resolvePackageRoot?: PackageRootResolver, fileSystem?: Pick<typeof fs, "existsSync"> & Partial<Pick<typeof fs, "readFileSync">>): {
|
|
48
70
|
incomplete_install: IncompleteInstallDiagnostics;
|
|
49
71
|
} | undefined;
|
|
50
72
|
//# sourceMappingURL=incomplete-install-error.d.ts.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// failure inside THIS process rather than a qmd child's captured stderr, which
|
|
8
8
|
// is the gap HQ-CLI-Y's classifier cannot cover.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
10
|
+
// Three shapes, one cause — a partial/interrupted global install left a file
|
|
11
11
|
// unwritten, or a concurrent global install rewrote the running tree
|
|
12
12
|
// underneath a command:
|
|
13
13
|
//
|
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
// specifier internal to a third-party package can only be a truncated on-disk
|
|
20
20
|
// copy, never an hq-cli manifest defect.
|
|
21
21
|
//
|
|
22
|
+
// HQ-CLI-1Q (Sentry 7716586928) — CJS, in-process. The SAME `import mqtt`
|
|
23
|
+
// chain, one hop further down: mqtt-packet's `parser.js` requires the BARE
|
|
24
|
+
// specifier `bl` — mqtt-packet's OWN declared transitive dependency — and it
|
|
25
|
+
// is absent inside hq-cli's bundled node_modules after a torn Windows global
|
|
26
|
+
// install. A bare miss looks like an undeclared dependency, but when the
|
|
27
|
+
// REQUIRING third-party package's own manifest declares it, its absence
|
|
28
|
+
// beside a present requirer can only be a torn install. hq-cli declares
|
|
29
|
+
// nothing about a contract between two third-party packages, so this is never
|
|
30
|
+
// an hq-cli manifest defect.
|
|
31
|
+
//
|
|
22
32
|
// HQ-CLI-1M (Sentry 7714870912) — ESM load, in-process. A module that existed
|
|
23
33
|
// at RESOLVE was gone at READ (a concurrent writer rewrote the install tree),
|
|
24
34
|
// so Node's ESM loader raised `ENOENT` from getSourceSync/readFileSync/openSync
|
|
@@ -26,7 +36,7 @@
|
|
|
26
36
|
// ERR_MODULE_NOT_FOUND at resolve, never ENOENT at load; an ENOENT at load
|
|
27
37
|
// proves the file vanished between resolve and read.
|
|
28
38
|
//
|
|
29
|
-
//
|
|
39
|
+
// All shapes carry no hq-cli frames, reach the boundary's final `else`, and —
|
|
30
40
|
// before this classifier — filed a bare captureException plus an unactionable
|
|
31
41
|
// `hq: <fallback>` line. The disposition is the one HQ-CLI-Y already
|
|
32
42
|
// established: an incomplete install is the caller's machine, so the CLI prints
|
|
@@ -35,19 +45,26 @@
|
|
|
35
45
|
// The gate is deliberately narrow so neither an hq-cli packaging fault nor
|
|
36
46
|
// user free-text can trip it. Only structured fields are read — `code`,
|
|
37
47
|
// `syscall`, `path`, `requireStack`, and the stack's loader-frame marker, plus
|
|
38
|
-
// the first message line for the CJS specifier
|
|
48
|
+
// the first message line for the CJS specifier — plus, for the bare CJS shape
|
|
49
|
+
// only, the requiring package's own on-disk manifest. Independent narrowings
|
|
39
50
|
// keep a genuine hq-cli defect reportable:
|
|
40
51
|
// 1. The failing file must sit under `<packageRoot>/node_modules/` — a
|
|
41
52
|
// third-party file hq-cli does not author. A miss under `<packageRoot>/dist`
|
|
42
53
|
// or `/assets` is hq-cli's OWN shipped output and stays captured.
|
|
43
|
-
// 2. The CJS shape
|
|
44
|
-
//
|
|
45
|
-
//
|
|
54
|
+
// 2. The CJS shape splits on the specifier. A RELATIVE specifier internal to
|
|
55
|
+
// a package under node_modules can only be a truncated copy (HQ-CLI-1N). A
|
|
56
|
+
// BARE PACKAGE-ROOT specifier stays captured UNLESS the requiring
|
|
57
|
+
// third-party package's own manifest declares it in its REQUIRED
|
|
58
|
+
// `dependencies` (HQ-CLI-1Q). A subpath miss (`bl/lib/x`), an
|
|
59
|
+
// optional-dependency miss (not guaranteed installed), and an undeclared or
|
|
60
|
+
// peer-only miss (`Cannot find module 'mqtt'` from a requirer that does not
|
|
61
|
+
// declare it) all stay loud — the last two enriched with both package names.
|
|
46
62
|
// 3. The ESM shape additionally requires an esm-loader frame — an ordinary
|
|
47
63
|
// `fs.readFileSync` ENOENT written by hq's own code stays captured.
|
|
48
64
|
import * as fs from "fs";
|
|
49
65
|
import * as path from "path";
|
|
50
66
|
import { packageRoot } from "./hq-roots.js";
|
|
67
|
+
import { packageNameOf } from "./install-tree-torn.js";
|
|
51
68
|
import { boundedDiagnosticValue } from "./package-root-diagnostics.js";
|
|
52
69
|
/**
|
|
53
70
|
* The actionable remedy shown to the operator. Input-free — nothing from the
|
|
@@ -72,6 +89,11 @@ const RELATIVE_SPECIFIER = /^\.\.?[\\/]/;
|
|
|
72
89
|
const ESM_LOADER_FRAME = /node:internal[\\/]modules[\\/]esm[\\/]/;
|
|
73
90
|
const ROOT_DIAGNOSTIC_BYTES = 256;
|
|
74
91
|
const CODE_DIAGNOSTIC_BYTES = 32;
|
|
92
|
+
// Package names live inside hq-cli's own dependency graph, so their universe is
|
|
93
|
+
// bounded; the cap is a belt for a pathological requireStack, not a real limit.
|
|
94
|
+
const PACKAGE_NAME_DIAGNOSTIC_BYTES = 128;
|
|
95
|
+
/** The `<something>/node_modules/<something>` directory-boundary marker. */
|
|
96
|
+
const NODE_MODULES_SEGMENT = "/node_modules/";
|
|
75
97
|
/**
|
|
76
98
|
* packageRoot() walks up from the compiled module and THROWS
|
|
77
99
|
* PackageRootResolutionError when it cannot resolve. This classifier runs inside
|
|
@@ -132,6 +154,114 @@ function parseMissingSpecifier(message) {
|
|
|
132
154
|
const match = message.match(/Cannot find module ['"]([^'"]+)['"]/);
|
|
133
155
|
return match ? match[1] : null;
|
|
134
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* True when `specifier` is a BARE package specifier — neither a RELATIVE
|
|
159
|
+
* specifier (`./x`, `..\x`) nor an ABSOLUTE POSIX (`/x`) or Windows (`C:\x`,
|
|
160
|
+
* `\\unc`) path. Shape-based (never `process.platform`) so a reported Windows
|
|
161
|
+
* path classifies on a Linux CI runner, mirroring normalizeForCompare's win32
|
|
162
|
+
* detection.
|
|
163
|
+
*/
|
|
164
|
+
function isBareSpecifier(specifier) {
|
|
165
|
+
if (specifier.length === 0)
|
|
166
|
+
return false;
|
|
167
|
+
if (RELATIVE_SPECIFIER.test(specifier))
|
|
168
|
+
return false;
|
|
169
|
+
if (specifier.startsWith("/"))
|
|
170
|
+
return false; // absolute POSIX
|
|
171
|
+
if (looksWin32(specifier))
|
|
172
|
+
return false; // absolute Windows (drive letter or UNC)
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* True when `specifier` is a bare specifier naming a PACKAGE ROOT with no
|
|
177
|
+
* subpath — `bl` or `@scope/name`, never `bl/lib/inner.js` or `@scope/name/sub`.
|
|
178
|
+
* Only a WHOLE-package miss is a proven torn install; a subpath miss can mean the
|
|
179
|
+
* package IS installed but that one file is absent because of a version or
|
|
180
|
+
* packaging mismatch — a real defect that must stay reported, not be silenced as
|
|
181
|
+
* a torn install.
|
|
182
|
+
*/
|
|
183
|
+
function isBarePackageRoot(specifier) {
|
|
184
|
+
return isBareSpecifier(specifier) && specifier === packageNameOf(specifier);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* From a file under `<root>/node_modules/…`, derive the REQUIRING package's
|
|
188
|
+
* directory and its package name, nesting- and scope-aware:
|
|
189
|
+
* `<root>/node_modules/a/node_modules/b/index.js` → dir `…/b`, name `b`
|
|
190
|
+
* `<root>/node_modules/@scope/pkg/index.js` → dir `…/@scope/pkg`, name `@scope/pkg`
|
|
191
|
+
* Keyed on the LAST `node_modules` segment so the INNERMOST (actually requiring)
|
|
192
|
+
* package is chosen, consuming two path segments for an `@scope/name` dir. The
|
|
193
|
+
* returned dir preserves the original case and is folded to `/` (Node's fs
|
|
194
|
+
* accepts `/` on every platform); the win32 shape only case-folds the SEARCH so
|
|
195
|
+
* the segment is found, never the returned value. Returns null when the path
|
|
196
|
+
* holds no usable package segment.
|
|
197
|
+
*/
|
|
198
|
+
function requiringPackage(requiringFile) {
|
|
199
|
+
const folded = foldSeparators(requiringFile);
|
|
200
|
+
const win = looksWin32(requiringFile);
|
|
201
|
+
const haystack = win ? folded.toLowerCase() : folded;
|
|
202
|
+
const marker = win ? NODE_MODULES_SEGMENT.toLowerCase() : NODE_MODULES_SEGMENT;
|
|
203
|
+
const lastNm = haystack.lastIndexOf(marker);
|
|
204
|
+
if (lastNm === -1)
|
|
205
|
+
return null;
|
|
206
|
+
const prefixEnd = lastNm + NODE_MODULES_SEGMENT.length;
|
|
207
|
+
const prefix = folded.slice(0, prefixEnd); // `…/node_modules/`, original case
|
|
208
|
+
const rest = folded
|
|
209
|
+
.slice(prefixEnd)
|
|
210
|
+
.split("/")
|
|
211
|
+
.filter((segment) => segment.length > 0);
|
|
212
|
+
if (rest.length === 0)
|
|
213
|
+
return null;
|
|
214
|
+
const take = rest[0].startsWith("@") ? 2 : 1;
|
|
215
|
+
if (rest.length < take)
|
|
216
|
+
return null;
|
|
217
|
+
const name = rest.slice(0, take).join("/");
|
|
218
|
+
return { dir: prefix + name, name };
|
|
219
|
+
}
|
|
220
|
+
/** True when `deps` is an object carrying `packageName` as an OWN key. */
|
|
221
|
+
function declaresDependency(deps, packageName) {
|
|
222
|
+
return (typeof deps === "object" &&
|
|
223
|
+
deps !== null &&
|
|
224
|
+
Object.prototype.hasOwnProperty.call(deps, packageName));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Whether the third-party package that REQUIRED `requiringFile` declares
|
|
228
|
+
* `packageName` in its own REQUIRED `dependencies` — the decisive discriminator
|
|
229
|
+
* for Shape C (HQ-CLI-1Q). A correct install of a package that declares a
|
|
230
|
+
* REQUIRED dependency ALWAYS writes it, so its absence beside a present requirer
|
|
231
|
+
* can only be a torn install. `optionalDependencies` are deliberately EXCLUDED:
|
|
232
|
+
* they are not guaranteed installed (`npm install --omit optional`, or a
|
|
233
|
+
* tolerated optional-install failure), so a missing optional package can be a
|
|
234
|
+
* persistent, expected omission — not a torn install — and must stay captured. A
|
|
235
|
+
* peer-only / undeclared / hoisting assumption is likewise NOT declared here.
|
|
236
|
+
* Fails CLOSED: an unresolved dir, an unreadable or invalid-JSON manifest, or a
|
|
237
|
+
* non-object dependency map all return false. Never throws — every fs and
|
|
238
|
+
* JSON.parse call is individually guarded. Reached only after the cheap
|
|
239
|
+
* in-memory narrowings of Shape C pass, so at most one small manifest read per
|
|
240
|
+
* process.
|
|
241
|
+
*/
|
|
242
|
+
function requirerDeclaresDependency(requiringFile, packageName, fileSystem) {
|
|
243
|
+
const requirer = requiringPackage(requiringFile);
|
|
244
|
+
if (!requirer)
|
|
245
|
+
return false;
|
|
246
|
+
let raw;
|
|
247
|
+
try {
|
|
248
|
+
raw = fileSystem.readFileSync(`${requirer.dir}/package.json`, "utf-8");
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
let manifest;
|
|
254
|
+
try {
|
|
255
|
+
manifest = JSON.parse(raw);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (manifest === null || typeof manifest !== "object")
|
|
261
|
+
return false;
|
|
262
|
+
const record = manifest;
|
|
263
|
+
return declaresDependency(record.dependencies, packageName);
|
|
264
|
+
}
|
|
135
265
|
/** True when `stack` carries a Node ESM loader frame. */
|
|
136
266
|
function hasEsmLoaderFrame(stack) {
|
|
137
267
|
return typeof stack === "string" && ESM_LOADER_FRAME.test(stack);
|
|
@@ -161,7 +291,7 @@ function isEsmLoaderEnoent(err) {
|
|
|
161
291
|
* means "handle as usual (capture to Sentry)". Never throws — a resolver that
|
|
162
292
|
* fails yields null.
|
|
163
293
|
*/
|
|
164
|
-
export function incompleteInstallMessage(err, resolvePackageRoot = safePackageRoot) {
|
|
294
|
+
export function incompleteInstallMessage(err, resolvePackageRoot = safePackageRoot, fileSystem = fs) {
|
|
165
295
|
if (err === null || typeof err !== "object")
|
|
166
296
|
return null;
|
|
167
297
|
const record = err;
|
|
@@ -172,16 +302,36 @@ export function incompleteInstallMessage(err, resolvePackageRoot = safePackageRo
|
|
|
172
302
|
if (!root)
|
|
173
303
|
return null;
|
|
174
304
|
if (code === "MODULE_NOT_FOUND") {
|
|
175
|
-
// Shape A (CJS, HQ-CLI-1N): a RELATIVE specifier internal to a package under
|
|
176
|
-
// the running install's node_modules can only be a truncated on-disk copy.
|
|
177
305
|
const requireStack = record.requireStack;
|
|
178
306
|
if (!Array.isArray(requireStack) || typeof requireStack[0] !== "string") {
|
|
179
307
|
return null;
|
|
180
308
|
}
|
|
309
|
+
const requiringFile = requireStack[0];
|
|
181
310
|
const specifier = parseMissingSpecifier(record.message);
|
|
182
|
-
if (specifier === null
|
|
311
|
+
if (specifier === null)
|
|
312
|
+
return null;
|
|
313
|
+
// Shape A (CJS, HQ-CLI-1N) — UNCHANGED: a RELATIVE specifier internal to a
|
|
314
|
+
// package under the running install's node_modules can only be a truncated
|
|
315
|
+
// on-disk copy. Kept byte-for-byte so 1N cannot regress.
|
|
316
|
+
if (RELATIVE_SPECIFIER.test(specifier)) {
|
|
317
|
+
return isUnderNodeModules(requiringFile, root)
|
|
318
|
+
? INCOMPLETE_INSTALL_REMEDY
|
|
319
|
+
: null;
|
|
320
|
+
}
|
|
321
|
+
// Shape C (CJS, HQ-CLI-1Q): a BARE PACKAGE-ROOT specifier missing from UNDER
|
|
322
|
+
// the running install's node_modules is a torn install ONLY when the
|
|
323
|
+
// REQUIRING third-party package's own manifest declares it in its required
|
|
324
|
+
// `dependencies`. A subpath specifier (`bl/lib/inner.js`) is excluded — the
|
|
325
|
+
// package may be present and only that file gone to a version mismatch — and
|
|
326
|
+
// an absolute-path specifier is neither relative nor bare, so both stay
|
|
327
|
+
// captured. The manifest read is the ONLY filesystem access here and is
|
|
328
|
+
// reached only after the cheap in-memory narrowings above — a combination
|
|
329
|
+
// that cannot occur on a healthy run — so a healthy event never touches disk.
|
|
330
|
+
if (!isBarePackageRoot(specifier))
|
|
331
|
+
return null;
|
|
332
|
+
if (!isUnderNodeModules(requiringFile, root))
|
|
183
333
|
return null;
|
|
184
|
-
return
|
|
334
|
+
return requirerDeclaresDependency(requiringFile, specifier, fileSystem)
|
|
185
335
|
? INCOMPLETE_INSTALL_REMEDY
|
|
186
336
|
: null;
|
|
187
337
|
}
|
|
@@ -197,27 +347,80 @@ export function incompleteInstallMessage(err, resolvePackageRoot = safePackageRo
|
|
|
197
347
|
? INCOMPLETE_INSTALL_REMEDY
|
|
198
348
|
: null;
|
|
199
349
|
}
|
|
350
|
+
/** A readFileSync surface, defaulting to the real fs when the caller injects none. */
|
|
351
|
+
function readFileFrom(fileSystem) {
|
|
352
|
+
return { readFileSync: fileSystem.readFileSync ?? fs.readFileSync };
|
|
353
|
+
}
|
|
200
354
|
/**
|
|
201
|
-
* When
|
|
202
|
-
* the
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
355
|
+
* When a third-party BARE-specifier CJS miss UNDER the running install's
|
|
356
|
+
* node_modules reached the capture path WITHOUT being suppressed (HQ-CLI-1Q) —
|
|
357
|
+
* i.e. the requiring package did NOT declare it, so it is a possible undeclared
|
|
358
|
+
* or peer-only dependency defect — return a bounded `incomplete_install` block
|
|
359
|
+
* naming both packages so the next occurrence is attributable instead of bare;
|
|
360
|
+
* otherwise undefined. The suppression decision is delegated to
|
|
361
|
+
* incompleteInstallMessage against the SAME injected filesystem, so a declared
|
|
362
|
+
* (torn-install) miss is never double-attributed here. Never throws.
|
|
363
|
+
*/
|
|
364
|
+
function bareSpecifierCaptureContext(err, resolvePackageRoot, fileSystem) {
|
|
365
|
+
if (err === null || typeof err !== "object")
|
|
366
|
+
return undefined;
|
|
367
|
+
const record = err;
|
|
368
|
+
if (record.code !== "MODULE_NOT_FOUND")
|
|
369
|
+
return undefined;
|
|
370
|
+
const requireStack = record.requireStack;
|
|
371
|
+
if (!Array.isArray(requireStack) || typeof requireStack[0] !== "string")
|
|
372
|
+
return undefined;
|
|
373
|
+
const requiringFile = requireStack[0];
|
|
374
|
+
const specifier = parseMissingSpecifier(record.message);
|
|
375
|
+
// Only a WHOLE-package bare miss is attributed here, matching the classifier's
|
|
376
|
+
// Shape C gate: a subpath miss is a version/packaging defect, not "the package
|
|
377
|
+
// is missing", so naming the package would misattribute it.
|
|
378
|
+
if (specifier === null || !isBarePackageRoot(specifier))
|
|
379
|
+
return undefined;
|
|
380
|
+
const root = resolveRootSafely(resolvePackageRoot);
|
|
381
|
+
if (!root || !isUnderNodeModules(requiringFile, root))
|
|
382
|
+
return undefined;
|
|
383
|
+
// Suppressed (the requirer declares it) → printed-and-skipped, never captured.
|
|
384
|
+
if (incompleteInstallMessage(err, resolvePackageRoot, readFileFrom(fileSystem)) !== null) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
incomplete_install: {
|
|
389
|
+
missingPackage: boundedDiagnosticValue(specifier, PACKAGE_NAME_DIAGNOSTIC_BYTES),
|
|
390
|
+
requiringPackage: boundedDiagnosticValue(requiringPackage(requiringFile)?.name ?? "<unresolved>", PACKAGE_NAME_DIAGNOSTIC_BYTES),
|
|
391
|
+
requirerDeclaresMissing: false,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* When an incomplete-install failure reaches the capture path WITHOUT being
|
|
397
|
+
* suppressed, return a bounded `contexts.incomplete_install` block so the next
|
|
398
|
+
* occurrence carries the evidence this one lacked; otherwise return undefined
|
|
399
|
+
* (bare capture). Two enriched shapes:
|
|
400
|
+
* - a third-party BARE-specifier miss the requirer did not declare (HQ-CLI-1Q)
|
|
401
|
+
* → { missingPackage, requiringPackage, requirerDeclaresMissing:false };
|
|
402
|
+
* - an esm-loader ENOENT whose path did not survive delivery (HQ-CLI-1M) — the
|
|
403
|
+
* shape the delivered payload arrived in, where neither the exception value
|
|
404
|
+
* nor node_system_error carried a `path` → { packageRoot, packageJsonExists,
|
|
405
|
+
* nodeModulesExists, esmLoaderFrame, code }.
|
|
406
|
+
* Built with the byte-capped, scrubber-safe discipline of
|
|
407
|
+
* package-root-diagnostics.ts — never a caller argv, query, or user-minted
|
|
408
|
+
* value. Never throws. main.ts attaches this on the generic capture path
|
|
409
|
+
* exactly as qmdSpawnFailureCaptureContext already does.
|
|
213
410
|
*/
|
|
214
411
|
export function incompleteInstallCaptureContext(err, resolvePackageRoot = safePackageRoot, fileSystem = fs) {
|
|
412
|
+
// (A) HQ-CLI-1Q — the not-suppressed third-party bare miss, made attributable.
|
|
413
|
+
const bare = bareSpecifierCaptureContext(err, resolvePackageRoot, fileSystem);
|
|
414
|
+
if (bare)
|
|
415
|
+
return bare;
|
|
416
|
+
// (B) HQ-CLI-1M — the path-less esm-loader ENOENT, unchanged.
|
|
215
417
|
if (!isEsmLoaderEnoent(err))
|
|
216
418
|
return undefined;
|
|
217
419
|
// Only instrument what we did NOT already confidently suppress: a path under
|
|
218
420
|
// node_modules is classified and printed above, never captured.
|
|
219
|
-
if (incompleteInstallMessage(err, resolvePackageRoot) !== null)
|
|
421
|
+
if (incompleteInstallMessage(err, resolvePackageRoot, readFileFrom(fileSystem)) !== null) {
|
|
220
422
|
return undefined;
|
|
423
|
+
}
|
|
221
424
|
const record = err;
|
|
222
425
|
const root = resolveRootSafely(resolvePackageRoot);
|
|
223
426
|
const code = typeof record.code === "string" ? record.code : "";
|
package/dist/utils/people.d.ts
CHANGED
|
@@ -22,6 +22,9 @@ export interface PersonRecord {
|
|
|
22
22
|
name: string;
|
|
23
23
|
/** Contact email, when recorded. */
|
|
24
24
|
email?: string;
|
|
25
|
+
/** Canonical identity from authoritative company membership, never local YAML. */
|
|
26
|
+
personUid?: string;
|
|
27
|
+
companyUid?: string;
|
|
25
28
|
/** "internal" (team member) | "external" (client, vendor, …). */
|
|
26
29
|
type?: string;
|
|
27
30
|
/** Role/title inside (or relative to) the company. */
|
|
@@ -31,11 +31,6 @@ export interface EntitlementEntry {
|
|
|
31
31
|
export interface EntitlementListResponse {
|
|
32
32
|
entitlements: EntitlementEntry[];
|
|
33
33
|
}
|
|
34
|
-
export interface EntitlementCheckResponse {
|
|
35
|
-
entitled: boolean;
|
|
36
|
-
tier?: string;
|
|
37
|
-
expires_at?: string;
|
|
38
|
-
}
|
|
39
34
|
export interface DownloadResponse {
|
|
40
35
|
url: string;
|
|
41
36
|
sha256: string;
|
|
@@ -59,7 +54,6 @@ export declare class RegistryClient {
|
|
|
59
54
|
}): Promise<PackageListResponse>;
|
|
60
55
|
getPackage(slug: string): Promise<PackageResponse>;
|
|
61
56
|
getMyEntitlements(): Promise<EntitlementListResponse>;
|
|
62
|
-
checkEntitlement(slug: string): Promise<EntitlementCheckResponse>;
|
|
63
57
|
getDownloadUrl(slug: string, version?: string): Promise<DownloadResponse>;
|
|
64
58
|
}
|
|
65
59
|
//# sourceMappingURL=registry-client.d.ts.map
|
|
@@ -88,9 +88,6 @@ export class RegistryClient {
|
|
|
88
88
|
auth: true,
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
-
async checkEntitlement(slug) {
|
|
92
|
-
return this.request('GET', `/entitlements/${encodeURIComponent(slug)}`, { auth: true });
|
|
93
|
-
}
|
|
94
91
|
async getDownloadUrl(slug, version) {
|
|
95
92
|
const params = new URLSearchParams();
|
|
96
93
|
if (version)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.109.
|
|
4
|
-
"description": "HQ by Indigo management CLI
|
|
3
|
+
"version": "5.109.7",
|
|
4
|
+
"description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"hq": "dist/index.js",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary",
|
|
27
27
|
"vitest": "vitest",
|
|
28
28
|
"clean": "rm -rf dist",
|
|
29
|
-
"prepare": "husky || true"
|
|
29
|
+
"prepare": "husky || true",
|
|
30
|
+
"check:no-hermes": "bash scripts/check-no-hermes.sh"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|