@indigoai-us/hq-cli 5.109.6 → 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 +9 -0
- 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/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,15 @@
|
|
|
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
|
+
|
|
5
14
|
## [5.109.6] — 2026-09-10
|
|
6
15
|
|
|
7
16
|
### Fixed
|
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/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",
|