@botbuddy/cli 1.59.6 → 1.59.8

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.
@@ -0,0 +1,483 @@
1
+ // BOT-2057: an intentionally un-wired enrollment primitive for the first Mini
2
+ // handover. It creates a NEW device-grant OAuth client/family and turns that
3
+ // response plus an already-approved existing P-256 key into the exact private
4
+ // profile consumed by the Go daemon. It never discovers, copies, writes, logs,
5
+ // or rotates a Mini key itself. The caller must supply an exclusive-custody
6
+ // delivery seam; Go has no public stdin profile writer yet.
7
+ import { createECDH, createHash } from "node:crypto";
8
+
9
+ import {
10
+ DEVICE_CODE_GRANT_TYPE,
11
+ REQUESTING_HOST_MAX,
12
+ pollDeviceToken,
13
+ requestDeviceAuthorization,
14
+ } from "../oauth-device.mjs";
15
+
16
+ export const DAEMON_CLIENT_NAME = "BotBuddy Daemon";
17
+ export const DAEMON_DEVICE_SCOPE = "read write";
18
+ export const DAEMON_PROFILE_VERSION = 1;
19
+ export const DAEMON_PRODUCTION_RELAY_ORIGIN = "https://xymjddoobrfnsmawmtbc.supabase.co/functions/v1";
20
+ export const DAEMON_PRODUCTION_MCP_SERVER = `${DAEMON_PRODUCTION_RELAY_ORIGIN}/mcp-server`;
21
+
22
+ const REFRESH_GRANT_TYPE = "refresh_token";
23
+ const SWIFT_REFERENCE_UNIX_SECONDS = 978_307_200;
24
+ const MAX_ACCESS_TOKEN_BYTES = 16 * 1024;
25
+ const MAX_REFRESH_TOKEN_BYTES = 1024;
26
+ const MAX_CLIENT_ID_BYTES = 512;
27
+ const MAX_EXPIRY_SECONDS = 31 * 24 * 60 * 60;
28
+ const MAX_PROFILE_INSTALL_BYTES = 128 * 1024;
29
+ const P256_SPKI_PREFIX = Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex");
30
+ const PROFILE_INSTALL_FILE_ORDER = Object.freeze(["profile.json", "host.key", "credentials.json"]);
31
+ const HELPER_IDENTITY_PROFILE_INSTALL_FILE_ORDER = Object.freeze(["profile.json", "credentials.json"]);
32
+ const HELPER_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION = 2;
33
+ const BOOTSTRAP_IDENTITY_PROFILE_INSTALL_FILE_ORDER = Object.freeze(["profile.json", "credentials.json"]);
34
+ const BOOTSTRAP_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION = 3;
35
+
36
+ export class DaemonEnrollmentError extends Error {
37
+ constructor(code) {
38
+ super(code);
39
+ this.name = "DaemonEnrollmentError";
40
+ this.code = code;
41
+ }
42
+ }
43
+
44
+ const fail = (code) => { throw new DaemonEnrollmentError(code); };
45
+
46
+ function boundedString(value, maximum) {
47
+ return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= maximum
48
+ ? value
49
+ : null;
50
+ }
51
+
52
+ function normalizedHttpsUrl(value, code, { mcpServer = false, relayOrigin = false } = {}) {
53
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048) fail(code);
54
+ let parsed;
55
+ try { parsed = new URL(value); } catch { fail(code); }
56
+ if (
57
+ parsed.protocol !== "https:" || parsed.username || parsed.password ||
58
+ parsed.search || parsed.hash || !parsed.hostname ||
59
+ (mcpServer && parsed.pathname !== "/functions/v1/mcp-server") ||
60
+ (relayOrigin && parsed.pathname !== "/functions/v1")
61
+ ) fail(code);
62
+ return parsed.toString().replace(/\/$/, "");
63
+ }
64
+
65
+ function rawPrivateKey(value) {
66
+ if (!Buffer.isBuffer(value) && !(value instanceof Uint8Array)) fail("daemon_profile_private_key_invalid");
67
+ const raw = Buffer.from(value);
68
+ if (raw.length !== 32) fail("daemon_profile_private_key_invalid");
69
+ return raw;
70
+ }
71
+
72
+ /** Derive the exact bbh- host identifier that daemon/privateKeyFromRaw uses. */
73
+ export function deriveDaemonHostKey(privateKey) {
74
+ const raw = rawPrivateKey(privateKey);
75
+ let publicKey;
76
+ try {
77
+ const curve = createECDH("prime256v1");
78
+ curve.setPrivateKey(raw);
79
+ publicKey = curve.getPublicKey(undefined, "uncompressed");
80
+ } catch {
81
+ fail("daemon_profile_private_key_invalid");
82
+ }
83
+ if (!Buffer.isBuffer(publicKey) || publicKey.length !== 65 || publicKey[0] !== 0x04) {
84
+ fail("daemon_profile_private_key_invalid");
85
+ }
86
+ const der = Buffer.concat([P256_SPKI_PREFIX, publicKey]);
87
+ return `bbh-${createHash("sha256").update(der).digest("hex").slice(0, 24)}`;
88
+ }
89
+
90
+ function verifiedExistingIdentity(privateKey, expectedHostKey) {
91
+ if (typeof expectedHostKey !== "string" || !/^bbh-[0-9a-f]{24}$/.test(expectedHostKey)) {
92
+ fail("daemon_profile_identity_invalid");
93
+ }
94
+ const raw = rawPrivateKey(privateKey);
95
+ if (deriveDaemonHostKey(raw) !== expectedHostKey) fail("daemon_profile_identity_mismatch");
96
+ return raw;
97
+ }
98
+
99
+ function profileToken(token) {
100
+ const accessToken = boundedString(token?.access_token, MAX_ACCESS_TOKEN_BYTES);
101
+ const refreshToken = boundedString(token?.refresh_token, MAX_REFRESH_TOKEN_BYTES);
102
+ const tokenType = token?.token_type;
103
+ const scope = token?.scope;
104
+ const tenantMode = token?.tenant_mode;
105
+ const expiresIn = token?.expires_in;
106
+ const scopes = typeof scope === "string" ? new Set(scope.trim().split(/\s+/).filter(Boolean)) : new Set();
107
+ if (
108
+ !accessToken || !refreshToken || tokenType !== "bearer" || tenantMode !== "user" ||
109
+ !scopes.has("write") || !Number.isSafeInteger(expiresIn) ||
110
+ expiresIn < 60 || expiresIn > MAX_EXPIRY_SECONDS
111
+ ) fail("daemon_profile_token_incomplete");
112
+ return { accessToken, refreshToken, expiresIn };
113
+ }
114
+
115
+ function profileClientId(value) {
116
+ const clientId = boundedString(value, MAX_CLIENT_ID_BYTES);
117
+ if (!clientId) fail("daemon_device_registration_failed");
118
+ return clientId;
119
+ }
120
+
121
+ function nowMilliseconds(now) {
122
+ const value = now();
123
+ if (!Number.isSafeInteger(value) || value <= 0) fail("daemon_profile_clock_invalid");
124
+ return value;
125
+ }
126
+
127
+ /**
128
+ * Build only an in-memory artifact. The delivery callback in
129
+ * enrollDaemonDeviceProfile is deliberately the first place these buffers may
130
+ * go; this module does not write a local profile or construct an SSH command.
131
+ */
132
+ export function buildDaemonProfileArtifact({
133
+ relayOrigin = DAEMON_PRODUCTION_RELAY_ORIGIN,
134
+ existingHostPrivateKey,
135
+ expectedHostKey,
136
+ clientId,
137
+ token,
138
+ now = () => Date.now(),
139
+ } = {}) {
140
+ const origin = normalizedHttpsUrl(relayOrigin, "daemon_profile_relay_origin_invalid", { relayOrigin: true });
141
+ const rawKey = verifiedExistingIdentity(existingHostPrivateKey, expectedHostKey);
142
+ const oauthClientId = profileClientId(clientId);
143
+ const profile = profileToken(token);
144
+ const expiresAt = Math.floor(nowMilliseconds(now) / 1000) - SWIFT_REFERENCE_UNIX_SECONDS + profile.expiresIn;
145
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= 0) fail("daemon_profile_clock_invalid");
146
+
147
+ const profileJSON = Buffer.from(JSON.stringify({
148
+ version: DAEMON_PROFILE_VERSION,
149
+ key_file: "host.key",
150
+ credential_file: "credentials.json",
151
+ expected_host_key: expectedHostKey,
152
+ }));
153
+ const credentialsJSON = Buffer.from(JSON.stringify({
154
+ accessToken: profile.accessToken,
155
+ expiresAt,
156
+ baseURL: origin,
157
+ clientID: oauthClientId,
158
+ refreshToken: profile.refreshToken,
159
+ }));
160
+ return {
161
+ rootMode: 0o700,
162
+ directories: [],
163
+ files: [
164
+ { path: "profile.json", mode: 0o600, body: profileJSON },
165
+ { path: "host.key", mode: 0o600, body: Buffer.from(rawKey) },
166
+ { path: "credentials.json", mode: 0o600, body: credentialsJSON },
167
+ ],
168
+ };
169
+ }
170
+
171
+ // Schema v2 is for an already-enrolled Mini only. It deliberately omits
172
+ // host.key: the target daemon reads the fixed, existing Helper identity via its
173
+ // own descriptor-pinned custody path and verifies this same public host key.
174
+ export function buildDaemonProfileArtifactFromHelperIdentity({
175
+ relayOrigin = DAEMON_PRODUCTION_RELAY_ORIGIN,
176
+ expectedHostKey,
177
+ clientId,
178
+ token,
179
+ now = () => Date.now(),
180
+ } = {}) {
181
+ if (typeof expectedHostKey !== "string" || !/^bbh-[0-9a-f]{24}$/.test(expectedHostKey)) {
182
+ fail("daemon_profile_identity_invalid");
183
+ }
184
+ const origin = normalizedHttpsUrl(relayOrigin, "daemon_profile_relay_origin_invalid", { relayOrigin: true });
185
+ const oauthClientId = profileClientId(clientId);
186
+ const profile = profileToken(token);
187
+ const expiresAt = Math.floor(nowMilliseconds(now) / 1000) - SWIFT_REFERENCE_UNIX_SECONDS + profile.expiresIn;
188
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= 0) fail("daemon_profile_clock_invalid");
189
+ return {
190
+ schemaVersion: HELPER_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION,
191
+ files: [
192
+ { path: "profile.json", mode: 0o600, body: Buffer.from(JSON.stringify({
193
+ version: DAEMON_PROFILE_VERSION,
194
+ key_file: "host.key",
195
+ credential_file: "credentials.json",
196
+ expected_host_key: expectedHostKey,
197
+ })) },
198
+ { path: "credentials.json", mode: 0o600, body: Buffer.from(JSON.stringify({
199
+ accessToken: profile.accessToken,
200
+ expiresAt,
201
+ baseURL: origin,
202
+ clientID: oauthClientId,
203
+ refreshToken: profile.refreshToken,
204
+ })) },
205
+ ],
206
+ };
207
+ }
208
+
209
+ // Schema v3 is for a genuinely fresh target identity. Like schema v2, it
210
+ // deliberately omits host.key: the target daemon reads the scalar it created
211
+ // in its fixed private bootstrap root and verifies this same public host key.
212
+ export function buildDaemonProfileArtifactFromBootstrapIdentity({
213
+ relayOrigin = DAEMON_PRODUCTION_RELAY_ORIGIN,
214
+ expectedHostKey,
215
+ clientId,
216
+ token,
217
+ now = () => Date.now(),
218
+ } = {}) {
219
+ if (typeof expectedHostKey !== "string" || !/^bbh-[0-9a-f]{24}$/.test(expectedHostKey)) {
220
+ fail("daemon_profile_identity_invalid");
221
+ }
222
+ const origin = normalizedHttpsUrl(relayOrigin, "daemon_profile_relay_origin_invalid", { relayOrigin: true });
223
+ const oauthClientId = profileClientId(clientId);
224
+ const profile = profileToken(token);
225
+ const expiresAt = Math.floor(nowMilliseconds(now) / 1000) - SWIFT_REFERENCE_UNIX_SECONDS + profile.expiresIn;
226
+ if (!Number.isSafeInteger(expiresAt) || expiresAt <= 0) fail("daemon_profile_clock_invalid");
227
+ return {
228
+ schemaVersion: BOOTSTRAP_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION,
229
+ files: [
230
+ { path: "profile.json", mode: 0o600, body: Buffer.from(JSON.stringify({
231
+ version: DAEMON_PROFILE_VERSION,
232
+ key_file: "host.key",
233
+ credential_file: "credentials.json",
234
+ expected_host_key: expectedHostKey,
235
+ })) },
236
+ { path: "credentials.json", mode: 0o600, body: Buffer.from(JSON.stringify({
237
+ accessToken: profile.accessToken,
238
+ expiresAt,
239
+ baseURL: origin,
240
+ clientID: oauthClientId,
241
+ refreshToken: profile.refreshToken,
242
+ })) },
243
+ ],
244
+ };
245
+ }
246
+
247
+ // The Go installer receives this on stdin. Paths, modes and ownership remain
248
+ // Go-owned; the requester may supply only the exact three verified file bodies.
249
+ export function encodeDaemonProfileInstallArtifact(artifact) {
250
+ if (
251
+ !artifact || artifact.rootMode !== 0o700 || !Array.isArray(artifact.directories) || artifact.directories.length !== 0 ||
252
+ !Array.isArray(artifact.files) || artifact.files.length !== PROFILE_INSTALL_FILE_ORDER.length
253
+ ) fail("daemon_profile_artifact_invalid");
254
+ const files = artifact.files.map((file, index) => {
255
+ if (!file || file.path !== PROFILE_INSTALL_FILE_ORDER[index] || file.mode !== 0o600 || !Buffer.isBuffer(file.body)) {
256
+ fail("daemon_profile_artifact_invalid");
257
+ }
258
+ return { name: file.path, content_base64: file.body.toString("base64") };
259
+ });
260
+ const encoded = Buffer.from(JSON.stringify({ schema_version: 1, files }));
261
+ if (encoded.length > MAX_PROFILE_INSTALL_BYTES) fail("daemon_profile_artifact_invalid");
262
+ return encoded;
263
+ }
264
+
265
+ export function encodeDaemonProfileInstallFromHelperIdentityArtifact(artifact) {
266
+ if (
267
+ !artifact || artifact.schemaVersion !== HELPER_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION ||
268
+ !Array.isArray(artifact.files) || artifact.files.length !== HELPER_IDENTITY_PROFILE_INSTALL_FILE_ORDER.length
269
+ ) fail("daemon_profile_artifact_invalid");
270
+ const files = artifact.files.map((file, index) => {
271
+ if (!file || file.path !== HELPER_IDENTITY_PROFILE_INSTALL_FILE_ORDER[index] || file.mode !== 0o600 || !Buffer.isBuffer(file.body)) {
272
+ fail("daemon_profile_artifact_invalid");
273
+ }
274
+ return { name: file.path, content_base64: file.body.toString("base64") };
275
+ });
276
+ const encoded = Buffer.from(JSON.stringify({ schema_version: HELPER_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION, files }));
277
+ if (encoded.length > MAX_PROFILE_INSTALL_BYTES) fail("daemon_profile_artifact_invalid");
278
+ return encoded;
279
+ }
280
+
281
+ export function encodeDaemonProfileInstallFromBootstrapIdentityArtifact(artifact) {
282
+ if (
283
+ !artifact || artifact.schemaVersion !== BOOTSTRAP_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION ||
284
+ !Array.isArray(artifact.files) || artifact.files.length !== BOOTSTRAP_IDENTITY_PROFILE_INSTALL_FILE_ORDER.length
285
+ ) fail("daemon_profile_artifact_invalid");
286
+ const files = artifact.files.map((file, index) => {
287
+ if (!file || file.path !== BOOTSTRAP_IDENTITY_PROFILE_INSTALL_FILE_ORDER[index] || file.mode !== 0o600 || !Buffer.isBuffer(file.body)) {
288
+ fail("daemon_profile_artifact_invalid");
289
+ }
290
+ return { name: file.path, content_base64: file.body.toString("base64") };
291
+ });
292
+ const encoded = Buffer.from(JSON.stringify({ schema_version: BOOTSTRAP_IDENTITY_PROFILE_INSTALL_SCHEMA_VERSION, files }));
293
+ if (encoded.length > MAX_PROFILE_INSTALL_BYTES) fail("daemon_profile_artifact_invalid");
294
+ return encoded;
295
+ }
296
+
297
+ async function registerDaemonDeviceClient(serverUrl, fetchImpl) {
298
+ let response;
299
+ try {
300
+ response = await fetchImpl(`${serverUrl}/register`, {
301
+ method: "POST",
302
+ headers: { "Content-Type": "application/json" },
303
+ body: JSON.stringify({
304
+ client_name: DAEMON_CLIENT_NAME,
305
+ grant_types: [DEVICE_CODE_GRANT_TYPE, REFRESH_GRANT_TYPE],
306
+ token_endpoint_auth_method: "none",
307
+ scope: DAEMON_DEVICE_SCOPE,
308
+ }),
309
+ });
310
+ } catch {
311
+ fail("daemon_device_registration_failed");
312
+ }
313
+ let body;
314
+ try { body = await response.json(); } catch { fail("daemon_device_registration_failed"); }
315
+ if (response.status !== 201) fail("daemon_device_registration_failed");
316
+ return profileClientId(body?.client_id);
317
+ }
318
+
319
+ function devicePrompt(grant, log) {
320
+ const deviceCode = boundedString(grant?.device_code, 512);
321
+ const userCode = typeof grant?.user_code === "string" && /^[A-Z0-9-]{4,32}$/.test(grant.user_code) ? grant.user_code : null;
322
+ const uri = typeof grant?.verification_uri_complete === "string"
323
+ ? grant.verification_uri_complete
324
+ : grant?.verification_uri;
325
+ let verification;
326
+ try {
327
+ const parsed = new URL(uri);
328
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || /[\r\n]/.test(uri)) throw new Error("unsafe URI");
329
+ verification = parsed.toString();
330
+ } catch {
331
+ fail("daemon_device_authorization_failed");
332
+ }
333
+ if (
334
+ !deviceCode || !userCode || !Number.isSafeInteger(grant?.interval) || grant.interval < 1 || grant.interval > 60 ||
335
+ !Number.isSafeInteger(grant?.expires_in) || grant.expires_in < 60 || grant.expires_in > 3600
336
+ ) {
337
+ fail("daemon_device_authorization_failed");
338
+ }
339
+ const minutes = Math.max(1, Math.round(grant.expires_in / 60));
340
+ log("→ Sign in on any device:");
341
+ log(` ${verification}`);
342
+ log(` Code: ${userCode} (expires in ${minutes} minute${minutes === 1 ? "" : "s"})`);
343
+ log(" Waiting for approval…");
344
+ return { deviceCode, intervalSec: grant.interval, expiresInSec: grant.expires_in };
345
+ }
346
+
347
+ function deliveredReceipt(value) {
348
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.installed !== true) {
349
+ fail("daemon_profile_delivery_failed");
350
+ }
351
+ // A delivery implementation may know remote paths or transport diagnostics;
352
+ // neither belongs in a requester receipt that could outlive its secret input.
353
+ return { installed: true };
354
+ }
355
+
356
+ /**
357
+ * Register a distinct public Daemon client, perform RFC 8628 device approval,
358
+ * and hand an in-memory private artifact to a caller-provided delivery seam.
359
+ * No caller may supply an old Helper client/refresh family, and no network or
360
+ * remote process is started unless this function itself is explicitly invoked.
361
+ */
362
+ async function enrollDaemonDeviceProfileWithArtifact({
363
+ serverUrl,
364
+ relayOrigin = DAEMON_PRODUCTION_RELAY_ORIGIN,
365
+ expectedHostKey,
366
+ requestingHost,
367
+ caller = null,
368
+ callerHarness = null,
369
+ callerTicket = null,
370
+ fetch: fetchImpl = fetch,
371
+ sleep,
372
+ now = () => Date.now(),
373
+ signal = null,
374
+ log = (line) => console.log(line),
375
+ deliver,
376
+ buildArtifact,
377
+ } = {}) {
378
+ const endpoint = normalizedHttpsUrl(serverUrl, "daemon_device_endpoint_invalid", { mcpServer: true });
379
+ const origin = normalizedHttpsUrl(relayOrigin, "daemon_profile_relay_origin_invalid", { relayOrigin: true });
380
+ if (typeof fetchImpl !== "function" || typeof log !== "function" || typeof deliver !== "function") {
381
+ fail("daemon_profile_delivery_required");
382
+ }
383
+ if (typeof expectedHostKey !== "string" || !/^bbh-[0-9a-f]{24}$/.test(expectedHostKey) || typeof buildArtifact !== "function") {
384
+ fail("daemon_profile_identity_invalid");
385
+ }
386
+ const host = boundedString(requestingHost, REQUESTING_HOST_MAX);
387
+ if (!host) fail("daemon_device_requesting_host_invalid");
388
+
389
+ const clientId = await registerDaemonDeviceClient(endpoint, fetchImpl);
390
+ let grant;
391
+ try {
392
+ grant = await requestDeviceAuthorization({
393
+ serverUrl: endpoint,
394
+ clientId,
395
+ scope: DAEMON_DEVICE_SCOPE,
396
+ caller,
397
+ callerHarness,
398
+ callerTicket,
399
+ requestingHost: host,
400
+ daemonHostKey: expectedHostKey,
401
+ fetch: fetchImpl,
402
+ });
403
+ } catch {
404
+ fail("daemon_device_authorization_failed");
405
+ }
406
+ const device = devicePrompt(grant, log);
407
+
408
+ let token;
409
+ try {
410
+ token = await pollDeviceToken({
411
+ serverUrl: endpoint,
412
+ clientId,
413
+ deviceCode: device.deviceCode,
414
+ intervalSec: device.intervalSec,
415
+ expiresInSec: device.expiresInSec,
416
+ fetch: fetchImpl,
417
+ ...(typeof sleep === "function" ? { sleep } : {}),
418
+ now,
419
+ signal,
420
+ });
421
+ } catch {
422
+ fail("daemon_device_token_failed");
423
+ }
424
+ const artifact = buildArtifact({
425
+ relayOrigin: origin,
426
+ expectedHostKey,
427
+ clientId,
428
+ token,
429
+ now,
430
+ });
431
+ let delivery;
432
+ try { delivery = deliveredReceipt(await deliver(artifact)); } catch (error) {
433
+ if (error instanceof DaemonEnrollmentError) throw error;
434
+ fail("daemon_profile_delivery_failed");
435
+ }
436
+ return {
437
+ outcome: "profile_delivered",
438
+ expected_host_key: expectedHostKey,
439
+ oauth_client_id: clientId,
440
+ delivery,
441
+ };
442
+ }
443
+
444
+ export async function enrollDaemonDeviceProfile({
445
+ existingHostPrivateKey,
446
+ expectedHostKey,
447
+ ...options
448
+ } = {}) {
449
+ const rawKey = verifiedExistingIdentity(existingHostPrivateKey, expectedHostKey);
450
+ return enrollDaemonDeviceProfileWithArtifact({
451
+ ...options,
452
+ expectedHostKey,
453
+ buildArtifact: (input) => buildDaemonProfileArtifact({ ...input, existingHostPrivateKey: rawKey }),
454
+ });
455
+ }
456
+
457
+ // Existing-Mini public enrollment: the requester sees only the public host
458
+ // identity. The target confirms and reads the original Helper key itself.
459
+ export async function enrollDaemonDeviceProfileFromHelperIdentity({
460
+ expectedHostKey,
461
+ ...options
462
+ } = {}) {
463
+ return enrollDaemonDeviceProfileWithArtifact({
464
+ ...options,
465
+ expectedHostKey,
466
+ buildArtifact: buildDaemonProfileArtifactFromHelperIdentity,
467
+ });
468
+ }
469
+
470
+ // Fresh-host public enrollment has the identical OAuth/device-family boundary
471
+ // to the preserved-Helper path, but the target daemon alone owns the bootstrap
472
+ // scalar. The requester can therefore supply only the public host key and the
473
+ // two-file schema-v3 delivery seam.
474
+ export async function enrollDaemonDeviceProfileFromBootstrapIdentity({
475
+ expectedHostKey,
476
+ ...options
477
+ } = {}) {
478
+ return enrollDaemonDeviceProfileWithArtifact({
479
+ ...options,
480
+ expectedHostKey,
481
+ buildArtifact: buildDaemonProfileArtifactFromBootstrapIdentity,
482
+ });
483
+ }
@@ -0,0 +1,14 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ // Node's symlink flags can preserve either filesystem spelling. Canonicalize
5
+ // both operands so aliases and escaped URLs cannot silently skip CLI checks.
6
+ // Imports (including stdin/eval contexts without a main file) stay inert.
7
+ export function isMainModule(moduleUrl, entry = process.argv[1]) {
8
+ if (typeof entry !== "string" || !entry) return false;
9
+ try {
10
+ return realpathSync(entry) === realpathSync(fileURLToPath(moduleUrl));
11
+ } catch {
12
+ return false;
13
+ }
14
+ }