@indigoai-us/hq-cli 5.345.133 → 5.345.135

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +17 -3
  2. package/dist/command-catalog.generated.d.ts +45 -0
  3. package/dist/command-catalog.generated.js +56 -0
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/access.d.ts +2 -0
  7. package/dist/commands/access.js +58 -9
  8. package/dist/commands/core-bucket-c-handoff-knowledge-native.d.ts +3 -1
  9. package/dist/commands/core-bucket-c-handoff-knowledge-native.js +8 -4
  10. package/dist/commands/core-handoff.d.ts +2 -0
  11. package/dist/commands/core-handoff.js +3 -1
  12. package/dist/commands/core-pipeline-workflow-runner-native.js +7 -5
  13. package/dist/commands/fingerprint.d.ts +10 -0
  14. package/dist/commands/fingerprint.js +191 -0
  15. package/dist/commands/lanes.js +9 -1
  16. package/dist/lib/fingerprint/fixtures.d.ts +19 -0
  17. package/dist/lib/fingerprint/fixtures.js +61 -0
  18. package/dist/lib/fingerprint/format.d.ts +50 -0
  19. package/dist/lib/fingerprint/format.js +105 -0
  20. package/dist/lib/fingerprint/offline.d.ts +16 -0
  21. package/dist/lib/fingerprint/offline.js +44 -0
  22. package/dist/lib/fingerprint/registry-client.d.ts +74 -0
  23. package/dist/lib/fingerprint/registry-client.js +141 -0
  24. package/dist/lib/fingerprint/resolve-local.d.ts +14 -0
  25. package/dist/lib/fingerprint/resolve-local.js +67 -0
  26. package/dist/lib/fingerprint/self-core.d.ts +139 -0
  27. package/dist/lib/fingerprint/self-core.js +371 -0
  28. package/dist/lib/fingerprint/self.d.ts +44 -0
  29. package/dist/lib/fingerprint/self.js +191 -0
  30. package/dist/lib/lanes/spawn.d.ts +19 -1
  31. package/dist/lib/lanes/spawn.js +50 -6
  32. package/dist/lib/lanes/story-ledger.d.ts +1 -0
  33. package/dist/lib/lanes/story-ledger.js +3 -1
  34. package/dist/lib/lanes/types.d.ts +12 -0
  35. package/dist/lib/lanes/types.js +12 -1
  36. package/dist/utils/cli-network-lifecycle.d.ts +2 -0
  37. package/dist/utils/cli-network-lifecycle.js +5 -1
  38. package/dist/utils/cognito-session.d.ts +2 -21
  39. package/dist/utils/cognito-session.js +3 -84
  40. package/dist/utils/person-token-cache.d.ts +32 -0
  41. package/dist/utils/person-token-cache.js +95 -0
  42. package/dist/utils/vault-api.d.ts +21 -1
  43. package/dist/utils/vault-api.js +54 -5
  44. package/package.json +1 -1
@@ -0,0 +1,139 @@
1
+ import { type HostKind, type IdentityKind } from "./format.js";
2
+ export declare const SESSION_ID_ENV_VARS: readonly ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "GROK_SESSION_ID"];
3
+ export declare const IDENTITY_LOOKUP_TIMEOUT_MS = 3000;
4
+ export declare const FINGERPRINT_FILE = "fingerprint.yaml";
5
+ export declare const IDENTITY_UID_RE: RegExp;
6
+ export type IdentitySource = "token" | "machine-creds" | "cache" | "lookup" | "none";
7
+ export interface SelfFingerprint {
8
+ fingerprint: string | null;
9
+ company: string | null;
10
+ identityUid: string | null;
11
+ identityKind: IdentityKind | null;
12
+ machineId: string | null;
13
+ hostKind: HostKind;
14
+ sessionId: string | null;
15
+ source: IdentitySource;
16
+ reason?: string;
17
+ }
18
+ export interface MachineCredsSummary {
19
+ entityType?: string;
20
+ entityUid?: string;
21
+ }
22
+ export interface SelfFingerprintDeps {
23
+ hqRoot: string;
24
+ env?: NodeJS.ProcessEnv;
25
+ offline?: boolean;
26
+ /** Returns the cached ID token, or null when signed out. */
27
+ loadIdToken?: () => string | null | Promise<string | null>;
28
+ /** Returns the machine creds, or null when this host has none or they are unreadable. */
29
+ loadMachineCreds?: () => MachineCredsSummary | null | Promise<MachineCredsSummary | null>;
30
+ /** Resolves the caller's uid over the network. The signal aborts on timeout. */
31
+ resolveIdentity?: (idToken: string, signal: AbortSignal) => Promise<{
32
+ kind: IdentityKind;
33
+ uid: string;
34
+ }>;
35
+ readMachineId?: (hqRoot: string) => string | null;
36
+ /**
37
+ * Never create or write a file: read an existing machine id only, return a
38
+ * null fingerprint without one, and skip the session cache write. For the
39
+ * handoff commits, which must leave the tree as the shell script does.
40
+ */
41
+ readOnly?: boolean;
42
+ /**
43
+ * The caller found no HQ root, so `hqRoot` names no HQ tree. The machine is
44
+ * `-`, nothing under `hqRoot` is read or written, and the result carries
45
+ * `reason: "no HQ root"`.
46
+ */
47
+ noRoot?: boolean;
48
+ lookupTimeoutMs?: number;
49
+ now?: () => Date;
50
+ warn?: (message: string) => void;
51
+ }
52
+ interface FingerprintCacheFile {
53
+ fingerprint?: string | null;
54
+ company?: string | null;
55
+ identity_uid?: string | null;
56
+ identity_kind?: IdentityKind | null;
57
+ identity_lookup_done?: boolean;
58
+ /** sha256 of the ID token `sub` the identity was resolved for, or null without one. */
59
+ token_owner?: string | null;
60
+ machine_id?: string | null;
61
+ host_kind?: HostKind;
62
+ session_id?: string;
63
+ built_at?: string;
64
+ /** When hq-pro recorded this fingerprint (US-007). Set by the registry step in self.ts. */
65
+ registered_at?: string;
66
+ /** Why registration will not be tried again for this fingerprint, such as "outpost". */
67
+ registration_skipped?: string;
68
+ }
69
+ /** Registration keys carried across rewrites while the fingerprint stays the same. */
70
+ type RegistrationKeys = Pick<FingerprintCacheFile, "registered_at" | "registration_skipped">;
71
+ export declare function resolveSessionId(env: NodeJS.ProcessEnv): string | null;
72
+ export declare function sessionDir(hqRoot: string, sessionId: string): string;
73
+ /**
74
+ * Read `<hqRoot>/.hq/machine-id`, creating it when missing. Mirrors
75
+ * getOrCreateMachineId in hq-cloud, which the package does not export (its
76
+ * exports map has no ./lib/machine-id entry). Falls back to the menubar id in
77
+ * ~/.hq/menubar.json before generating a new UUID, as hq-cloud does.
78
+ */
79
+ export declare function readOrCreateMachineId(hqRoot: string): string | null;
80
+ /** Read `<hqRoot>/.hq/machine-id` without creating it. Null when it is missing or empty. */
81
+ export declare function readMachineIdIfPresent(hqRoot: string): string | null;
82
+ /** Machine ids are usually UUIDs. A free-form id is hashed so the fingerprint keeps 8 hex chars. */
83
+ export declare function machineIdForFingerprint(machineId: string): string;
84
+ export interface IdentityContext {
85
+ env: NodeJS.ProcessEnv;
86
+ hqRoot: string;
87
+ warn: (message: string) => void;
88
+ sessionId: string | null;
89
+ idToken: string | null;
90
+ owner: string | null;
91
+ hostKind: HostKind;
92
+ cacheFile: string | null;
93
+ identityUid: string | null;
94
+ source: IdentitySource;
95
+ lookupDone: boolean;
96
+ /** True when no local source named the identity and a person lookup may run. */
97
+ needsLookup: boolean;
98
+ /** The cached fingerprint and its registration keys, kept when the value is unchanged. */
99
+ cachedRegistration?: {
100
+ fingerprint: string | null;
101
+ } & RegistrationKeys;
102
+ }
103
+ export declare function identityContext(deps: SelfFingerprintDeps, idToken: string | null, creds: MachineCredsSummary | null): IdentityContext;
104
+ export interface SelfFingerprintSyncDeps extends Omit<SelfFingerprintDeps, "offline" | "loadIdToken" | "loadMachineCreds" | "resolveIdentity" | "lookupTimeoutMs"> {
105
+ /** Returns the cached ID token, or null when signed out. */
106
+ loadIdToken: () => string | null;
107
+ /** Returns the machine creds, or null when this host has none or they are unreadable. */
108
+ loadMachineCreds: () => MachineCredsSummary | null;
109
+ }
110
+ /**
111
+ * The offline build as a synchronous call, for callers that cannot await
112
+ * (lane start). It never does the person lookup, so the loaders are required:
113
+ * the default ones live in self.ts, which loads them lazily and only
114
+ * asynchronously.
115
+ */
116
+ export declare function buildSelfFingerprintOfflineSync(deps: SelfFingerprintSyncDeps): SelfFingerprint;
117
+ export declare function finishFingerprint(deps: SelfFingerprintDeps, ctx: IdentityContext): SelfFingerprint;
118
+ export interface FingerprintRegistrationState {
119
+ registeredAt: string | null;
120
+ skipped: string | null;
121
+ }
122
+ /**
123
+ * The registration keys of this session's fingerprint.yaml, or null when the
124
+ * file is missing or holds another fingerprint (the registry step then has no
125
+ * record to mark).
126
+ */
127
+ export declare function readRegistrationState(hqRoot: string, sessionId: string, fingerprint: string, warn: (message: string) => void): FingerprintRegistrationState | null;
128
+ /**
129
+ * Writes registered_at or registration_skipped into fingerprint.yaml. Re-reads
130
+ * the file first and writes nothing when it now holds another fingerprint.
131
+ * Returns true when the file was rewritten.
132
+ */
133
+ export declare function recordRegistrationState(hqRoot: string, sessionId: string, fingerprint: string, update: {
134
+ registeredAt: string;
135
+ } | {
136
+ skipped: string;
137
+ }, warn: (message: string) => void): boolean;
138
+ export {};
139
+ //# sourceMappingURL=self-core.d.ts.map
@@ -0,0 +1,371 @@
1
+ /**
2
+ * The network-free part of the session fingerprint builder: session id,
3
+ * company, machine id, the fingerprint.yaml cache, and the synchronous offline
4
+ * build. Lane start and the handoff commits import this module directly, so
5
+ * it must never import vault-api, cognito-session, hq-cloud, or any other
6
+ * network client, not even lazily. The person lookup lives in self.ts.
7
+ */
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ import * as fs from "node:fs";
10
+ import * as os from "node:os";
11
+ import * as path from "node:path";
12
+ import yaml from "js-yaml";
13
+ import { peekIdToken } from "../../utils/id-token.js";
14
+ import { build, COMPANY_RE } from "./format.js";
15
+ export const SESSION_ID_ENV_VARS = [
16
+ "HQ_SESSION_ID",
17
+ "CLAUDE_CODE_SESSION_ID",
18
+ "CLAUDE_SESSION_ID",
19
+ "CODEX_SESSION_ID",
20
+ "CODEX_THREAD_ID",
21
+ "GROK_SESSION_ID",
22
+ ];
23
+ export const IDENTITY_LOOKUP_TIMEOUT_MS = 3000;
24
+ export const FINGERPRINT_FILE = "fingerprint.yaml";
25
+ const SESSION_ID_RE = /^[A-Za-z0-9._-]+$/;
26
+ export const IDENTITY_UID_RE = /^(prs|agt)_[A-Za-z0-9]{8,}$/;
27
+ function registrationKeys(cache) {
28
+ const keys = {};
29
+ if (typeof cache?.registered_at === "string" && cache.registered_at)
30
+ keys.registered_at = cache.registered_at;
31
+ if (typeof cache?.registration_skipped === "string" && cache.registration_skipped) {
32
+ keys.registration_skipped = cache.registration_skipped;
33
+ }
34
+ return keys;
35
+ }
36
+ export function resolveSessionId(env) {
37
+ for (const name of SESSION_ID_ENV_VARS) {
38
+ const value = env[name];
39
+ if (!value)
40
+ continue;
41
+ if (value === "." || value === "..")
42
+ continue;
43
+ if (!SESSION_ID_RE.test(value))
44
+ continue;
45
+ return value;
46
+ }
47
+ return null;
48
+ }
49
+ export function sessionDir(hqRoot, sessionId) {
50
+ return path.join(hqRoot, "workspace", "sessions", sessionId);
51
+ }
52
+ /** Reads a YAML mapping. Unreadable or unparseable files warn and count as missing. */
53
+ function readYamlRecord(file, warn) {
54
+ try {
55
+ const parsed = yaml.load(fs.readFileSync(file, "utf8"));
56
+ return parsed && typeof parsed === "object" ? parsed : null;
57
+ }
58
+ catch (err) {
59
+ if (err.code === "ENOENT")
60
+ return null;
61
+ warn(`hq fingerprint: ignoring unreadable ${file}: ${err.message}`);
62
+ return null;
63
+ }
64
+ }
65
+ function resolveCompany(hqRoot, sessionId, env, warn) {
66
+ let company = null;
67
+ let from = "";
68
+ if (sessionId) {
69
+ const meta = readYamlRecord(path.join(sessionDir(hqRoot, sessionId), "meta.yaml"), warn);
70
+ const slug = meta?.company_slug;
71
+ if (typeof slug === "string" && slug.trim()) {
72
+ company = slug.trim();
73
+ from = "meta.yaml company_slug";
74
+ }
75
+ }
76
+ if (!company && env.HQ_COMPANY)
77
+ [company, from] = [env.HQ_COMPANY, "HQ_COMPANY"];
78
+ if (!company && env.HQ_SPAWN_COMPANY)
79
+ [company, from] = [env.HQ_SPAWN_COMPANY, "HQ_SPAWN_COMPANY"];
80
+ if (company && !COMPANY_RE.test(company)) {
81
+ warn(`hq fingerprint: ignoring ${from}: not a company slug`);
82
+ return null;
83
+ }
84
+ return company;
85
+ }
86
+ /**
87
+ * Read `<hqRoot>/.hq/machine-id`, creating it when missing. Mirrors
88
+ * getOrCreateMachineId in hq-cloud, which the package does not export (its
89
+ * exports map has no ./lib/machine-id entry). Falls back to the menubar id in
90
+ * ~/.hq/menubar.json before generating a new UUID, as hq-cloud does.
91
+ */
92
+ export function readOrCreateMachineId(hqRoot) {
93
+ const file = path.join(hqRoot, ".hq", "machine-id");
94
+ try {
95
+ const existing = fs.readFileSync(file, "utf8").trim();
96
+ if (existing)
97
+ return existing;
98
+ }
99
+ catch (err) {
100
+ if (err.code !== "ENOENT")
101
+ throw err;
102
+ }
103
+ let id = null;
104
+ try {
105
+ const menubar = JSON.parse(fs.readFileSync(path.join(os.homedir(), ".hq", "menubar.json"), "utf8"));
106
+ if (typeof menubar?.machineId === "string" && menubar.machineId)
107
+ id = menubar.machineId;
108
+ }
109
+ catch (err) {
110
+ const code = err.code;
111
+ if (code !== "ENOENT" && !(err instanceof SyntaxError))
112
+ throw err;
113
+ }
114
+ id ??= randomUUID();
115
+ fs.mkdirSync(path.dirname(file), { recursive: true });
116
+ fs.writeFileSync(file, `${id}\n`);
117
+ return id;
118
+ }
119
+ /** Read `<hqRoot>/.hq/machine-id` without creating it. Null when it is missing or empty. */
120
+ export function readMachineIdIfPresent(hqRoot) {
121
+ try {
122
+ return fs.readFileSync(path.join(hqRoot, ".hq", "machine-id"), "utf8").trim() || null;
123
+ }
124
+ catch (err) {
125
+ if (err.code === "ENOENT")
126
+ return null;
127
+ throw err;
128
+ }
129
+ }
130
+ /** Machine ids are usually UUIDs. A free-form id is hashed so the fingerprint keeps 8 hex chars. */
131
+ export function machineIdForFingerprint(machineId) {
132
+ const hex = machineId.toLowerCase().replace(/-/g, "");
133
+ if (/^[0-9a-f]{8,}$/.test(hex))
134
+ return machineId;
135
+ return createHash("sha256").update(machineId).digest("hex");
136
+ }
137
+ function tokenOwner(sub) {
138
+ return typeof sub === "string" && sub ? createHash("sha256").update(sub).digest("hex") : null;
139
+ }
140
+ function writeCacheAtomic(file, record, warn) {
141
+ const tmp = `${file}.${process.pid}.tmp`;
142
+ try {
143
+ fs.mkdirSync(path.dirname(file), { recursive: true });
144
+ fs.writeFileSync(tmp, yaml.dump(record));
145
+ fs.renameSync(tmp, file);
146
+ }
147
+ catch (err) {
148
+ warn(`hq fingerprint: could not write ${file}: ${err.message}`);
149
+ fs.rmSync(tmp, { force: true });
150
+ }
151
+ }
152
+ function kindOfUid(uid) {
153
+ return uid.startsWith("agt_") ? "agent" : "person";
154
+ }
155
+ export function identityContext(deps, idToken, creds) {
156
+ const env = deps.env ?? process.env;
157
+ const hqRoot = deps.hqRoot;
158
+ const warn = deps.warn ?? ((message) => process.stderr.write(`${message}\n`));
159
+ const sessionId = resolveSessionId(env);
160
+ const claims = idToken ? peekIdToken(idToken) : {};
161
+ const entityType = typeof claims["custom:entityType"] === "string" ? claims["custom:entityType"] : null;
162
+ const entityUid = typeof claims["custom:entityUid"] === "string" ? claims["custom:entityUid"] : null;
163
+ const owner = tokenOwner(claims.sub);
164
+ let hostKind = "laptop";
165
+ if (entityType === "agent" || creds)
166
+ hostKind = "fleet";
167
+ if (entityType === "outpost" ||
168
+ entityType === "company-outpost" ||
169
+ entityUid?.startsWith("out_") ||
170
+ creds?.entityType === "outpost" ||
171
+ creds?.entityType === "company-outpost") {
172
+ hostKind = "outpost";
173
+ }
174
+ const cacheFile = sessionId && !deps.noRoot ? path.join(sessionDir(hqRoot, sessionId), FINGERPRINT_FILE) : null;
175
+ const rawCache = cacheFile ? readYamlRecord(cacheFile, warn) : null;
176
+ // A cache written for another signed-in user says nothing about this one.
177
+ const cached = rawCache && (rawCache.token_owner ?? null) === owner ? rawCache : null;
178
+ const agentPrincipal = entityType === "agent" || creds?.entityType === "agent" || entityUid?.startsWith("agt_") === true;
179
+ let identityUid = null;
180
+ let source = "none";
181
+ let needsLookup = false;
182
+ if (entityUid && IDENTITY_UID_RE.test(entityUid)) {
183
+ identityUid = entityUid;
184
+ source = "token";
185
+ }
186
+ else if (creds?.entityUid && IDENTITY_UID_RE.test(creds.entityUid)) {
187
+ identityUid = creds.entityUid;
188
+ source = "machine-creds";
189
+ }
190
+ else if (cached?.identity_lookup_done) {
191
+ identityUid = typeof cached.identity_uid === "string" ? cached.identity_uid : null;
192
+ source = "cache";
193
+ }
194
+ else if (sessionId && !deps.offline && idToken && hostKind !== "outpost" && !agentPrincipal) {
195
+ needsLookup = true;
196
+ }
197
+ return {
198
+ env,
199
+ hqRoot,
200
+ warn,
201
+ sessionId,
202
+ idToken,
203
+ owner,
204
+ hostKind,
205
+ cacheFile,
206
+ identityUid,
207
+ source,
208
+ lookupDone: cached?.identity_lookup_done === true,
209
+ needsLookup,
210
+ cachedRegistration: {
211
+ fingerprint: typeof rawCache?.fingerprint === "string" ? rawCache.fingerprint : null,
212
+ ...registrationKeys(rawCache),
213
+ },
214
+ };
215
+ }
216
+ /**
217
+ * The offline build as a synchronous call, for callers that cannot await
218
+ * (lane start). It never does the person lookup, so the loaders are required:
219
+ * the default ones live in self.ts, which loads them lazily and only
220
+ * asynchronously.
221
+ */
222
+ export function buildSelfFingerprintOfflineSync(deps) {
223
+ const warn = deps.warn ?? ((message) => process.stderr.write(`${message}\n`));
224
+ let idToken = null;
225
+ try {
226
+ idToken = deps.loadIdToken();
227
+ }
228
+ catch (err) {
229
+ warn(`hq fingerprint: could not read the cached token: ${err.message}`);
230
+ }
231
+ let creds = null;
232
+ try {
233
+ creds = deps.loadMachineCreds();
234
+ }
235
+ catch (err) {
236
+ warn(`hq fingerprint: could not read machine creds: ${err.message}`);
237
+ }
238
+ const offlineDeps = { ...deps, offline: true };
239
+ return finishFingerprint(offlineDeps, identityContext(offlineDeps, idToken, creds));
240
+ }
241
+ export function finishFingerprint(deps, ctx) {
242
+ const result = finishFingerprintAtRoot(deps, ctx);
243
+ if (!deps.noRoot)
244
+ return result;
245
+ return { ...result, reason: result.reason ? `no HQ root; ${result.reason}` : "no HQ root" };
246
+ }
247
+ function finishFingerprintAtRoot(deps, ctx) {
248
+ const { env, hqRoot, warn, sessionId, idToken, owner, hostKind, cacheFile, identityUid, source, lookupDone } = ctx;
249
+ let rawMachineId = deps.noRoot ? null : env.HQ_MACHINE_ID?.trim() || null;
250
+ if (!rawMachineId && !deps.noRoot) {
251
+ try {
252
+ rawMachineId = (deps.readMachineId ?? (deps.readOnly ? readMachineIdIfPresent : readOrCreateMachineId))(hqRoot);
253
+ }
254
+ catch (err) {
255
+ warn(`hq fingerprint: could not read the machine id: ${err.message}`);
256
+ }
257
+ }
258
+ const machineId = rawMachineId ? machineIdForFingerprint(rawMachineId) : null;
259
+ const company = resolveCompany(hqRoot, deps.noRoot ? null : sessionId, env, warn);
260
+ const identityKind = identityUid ? kindOfUid(identityUid) : null;
261
+ if (!identityUid && !sessionId) {
262
+ return {
263
+ fingerprint: null,
264
+ company,
265
+ identityUid: null,
266
+ identityKind: null,
267
+ machineId: rawMachineId,
268
+ hostKind,
269
+ sessionId: null,
270
+ source,
271
+ reason: idToken
272
+ ? "no session id in the environment and no identity uid on the cached token"
273
+ : "no session id in the environment and not signed in",
274
+ };
275
+ }
276
+ if (deps.readOnly && !machineId) {
277
+ return {
278
+ fingerprint: null,
279
+ company,
280
+ identityUid,
281
+ identityKind,
282
+ machineId: null,
283
+ hostKind,
284
+ sessionId,
285
+ source,
286
+ reason: "no machine id and read-only mode does not create one",
287
+ };
288
+ }
289
+ let fingerprint;
290
+ try {
291
+ fingerprint = build({ company, identityUid, hostKind, machineId, sessionId });
292
+ }
293
+ catch (err) {
294
+ return {
295
+ fingerprint: null,
296
+ company,
297
+ identityUid,
298
+ identityKind,
299
+ machineId: rawMachineId,
300
+ hostKind,
301
+ sessionId,
302
+ source,
303
+ reason: `could not build fingerprint: ${err.message}`,
304
+ };
305
+ }
306
+ if (sessionId && cacheFile && !deps.readOnly) {
307
+ const record = {
308
+ fingerprint,
309
+ company,
310
+ identity_uid: identityUid,
311
+ identity_kind: identityKind,
312
+ identity_lookup_done: lookupDone,
313
+ token_owner: owner,
314
+ machine_id: rawMachineId,
315
+ host_kind: hostKind,
316
+ session_id: sessionId,
317
+ built_at: (deps.now ?? (() => new Date()))().toISOString(),
318
+ // The fingerprint names company, identity, machine and session, so the
319
+ // same value means hq-pro already holds this exact record.
320
+ ...(ctx.cachedRegistration?.fingerprint === fingerprint ? registrationKeys(ctx.cachedRegistration) : {}),
321
+ };
322
+ writeCacheAtomic(cacheFile, record, warn);
323
+ }
324
+ return {
325
+ fingerprint,
326
+ company,
327
+ identityUid,
328
+ identityKind,
329
+ machineId: rawMachineId,
330
+ hostKind,
331
+ sessionId,
332
+ source,
333
+ };
334
+ }
335
+ function cachePathFor(hqRoot, sessionId) {
336
+ return path.join(sessionDir(hqRoot, sessionId), FINGERPRINT_FILE);
337
+ }
338
+ /**
339
+ * The registration keys of this session's fingerprint.yaml, or null when the
340
+ * file is missing or holds another fingerprint (the registry step then has no
341
+ * record to mark).
342
+ */
343
+ export function readRegistrationState(hqRoot, sessionId, fingerprint, warn) {
344
+ const cache = readYamlRecord(cachePathFor(hqRoot, sessionId), warn);
345
+ if (!cache || cache.fingerprint !== fingerprint)
346
+ return null;
347
+ const keys = registrationKeys(cache);
348
+ return { registeredAt: keys.registered_at ?? null, skipped: keys.registration_skipped ?? null };
349
+ }
350
+ /**
351
+ * Writes registered_at or registration_skipped into fingerprint.yaml. Re-reads
352
+ * the file first and writes nothing when it now holds another fingerprint.
353
+ * Returns true when the file was rewritten.
354
+ */
355
+ export function recordRegistrationState(hqRoot, sessionId, fingerprint, update, warn) {
356
+ const file = cachePathFor(hqRoot, sessionId);
357
+ const cache = readYamlRecord(file, warn);
358
+ if (!cache || cache.fingerprint !== fingerprint)
359
+ return false;
360
+ const record = { ...cache };
361
+ if ("registeredAt" in update) {
362
+ record.registered_at = update.registeredAt;
363
+ delete record.registration_skipped;
364
+ }
365
+ else {
366
+ record.registration_skipped = update.skipped;
367
+ }
368
+ writeCacheAtomic(file, record, warn);
369
+ return true;
370
+ }
371
+ //# sourceMappingURL=self-core.js.map
@@ -0,0 +1,44 @@
1
+ import type { RegistryFetch } from "./registry-client.js";
2
+ import { type MachineCredsSummary, type SelfFingerprint, type SelfFingerprintDeps } from "./self-core.js";
3
+ export * from "./self-core.js";
4
+ /** An unparseable creds file yields null, so it never marks the host as fleet. */
5
+ export declare function defaultLoadMachineCreds(): Promise<MachineCredsSummary | null>;
6
+ export declare function buildSelfFingerprint(deps: SelfFingerprintDeps): Promise<SelfFingerprint>;
7
+ export interface RegisterSelfDeps {
8
+ hqRoot: string;
9
+ env?: NodeJS.ProcessEnv;
10
+ offline?: boolean;
11
+ /** false when the caller passed --no-register. */
12
+ register?: boolean;
13
+ loadIdToken?: () => string | null | Promise<string | null>;
14
+ fetch?: RegistryFetch;
15
+ timeoutMs?: number;
16
+ /** Directory for fingerprint.log. Defaults to ~/.hq. */
17
+ logDir?: string;
18
+ now?: () => Date;
19
+ }
20
+ export type RegisterSelfOutcome = {
21
+ status: "registered";
22
+ } | {
23
+ status: "already-registered";
24
+ } | {
25
+ status: "skipped";
26
+ reason: string;
27
+ } | {
28
+ status: "failed";
29
+ reason: string;
30
+ };
31
+ /**
32
+ * Records this session's fingerprint with hq-pro (POST /v1/fingerprints), at
33
+ * most once per session. Fire-and-forget: it never writes to stdout or stderr
34
+ * and never throws, so the printed fingerprint and the exit code stay the
35
+ * same whatever the registry does.
36
+ *
37
+ * hq-pro denies company-outpost machine tokens, so outpost hosts skip the call
38
+ * and record `registration_skipped: outpost`. A 403 is also final and is
39
+ * recorded the same way. A 404 means the registry is not deployed for this
40
+ * API, so it records `registration_skipped: unavailable` and the session stops
41
+ * calling. Any other failure is logged and retried next run.
42
+ */
43
+ export declare function registerSelfFingerprint(self: SelfFingerprint, deps: RegisterSelfDeps): Promise<RegisterSelfOutcome>;
44
+ //# sourceMappingURL=self.d.ts.map