@fireproof/core-cli 0.24.15 → 0.24.16

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 (92) hide show
  1. package/build-cmd.test.js +1 -1
  2. package/build-cmd.test.js.map +1 -1
  3. package/cli-ctx.d.ts +6 -0
  4. package/cli-ctx.js +2 -0
  5. package/cli-ctx.js.map +1 -0
  6. package/cmd-evento.d.ts +24 -0
  7. package/cmd-evento.js +85 -0
  8. package/cmd-evento.js.map +1 -0
  9. package/{build-cmd.d.ts → cmds/build-cmd.d.ts} +41 -4
  10. package/{build-cmd.js → cmds/build-cmd.js} +211 -152
  11. package/cmds/build-cmd.js.map +1 -0
  12. package/{cloud-token-key-cmd.d.ts → cmds/cloud-token-key-cmd.d.ts} +20 -4
  13. package/cmds/cloud-token-key-cmd.js +114 -0
  14. package/cmds/cloud-token-key-cmd.js.map +1 -0
  15. package/cmds/dependabot-cmd.d.ts +33 -0
  16. package/cmds/dependabot-cmd.js +157 -0
  17. package/cmds/dependabot-cmd.js.map +1 -0
  18. package/cmds/device-id-cmd.d.ts +248 -0
  19. package/cmds/device-id-cmd.js +762 -0
  20. package/cmds/device-id-cmd.js.map +1 -0
  21. package/cmds/index.d.ts +12 -0
  22. package/cmds/index.js +13 -0
  23. package/cmds/index.js.map +1 -0
  24. package/{pre-signed-url.d.ts → cmds/pre-signed-url.d.ts} +26 -4
  25. package/{pre-signed-url.js → cmds/pre-signed-url.js} +63 -22
  26. package/cmds/pre-signed-url.js.map +1 -0
  27. package/cmds/retry-cmd.d.ts +31 -0
  28. package/cmds/retry-cmd.js +156 -0
  29. package/cmds/retry-cmd.js.map +1 -0
  30. package/{set-scripts-cmd.d.ts → cmds/set-scripts-cmd.d.ts} +40 -7
  31. package/cmds/set-scripts-cmd.js +213 -0
  32. package/cmds/set-scripts-cmd.js.map +1 -0
  33. package/cmds/test-container-cmd.d.ts +135 -0
  34. package/cmds/test-container-cmd.js +440 -0
  35. package/cmds/test-container-cmd.js.map +1 -0
  36. package/{tsc-cmd.d.ts → cmds/tsc-cmd.d.ts} +19 -3
  37. package/cmds/tsc-cmd.js +75 -0
  38. package/cmds/tsc-cmd.js.map +1 -0
  39. package/cmds/update-deps-cmd.d.ts +33 -0
  40. package/cmds/update-deps-cmd.js +194 -0
  41. package/cmds/update-deps-cmd.js.map +1 -0
  42. package/{well-known-cmd.d.ts → cmds/well-known-cmd.d.ts} +24 -4
  43. package/cmds/well-known-cmd.js +154 -0
  44. package/cmds/well-known-cmd.js.map +1 -0
  45. package/cmds/write-env-cmd.d.ts +43 -0
  46. package/cmds/write-env-cmd.js +145 -0
  47. package/cmds/write-env-cmd.js.map +1 -0
  48. package/create-cli-stream.d.ts +10 -0
  49. package/create-cli-stream.js +34 -0
  50. package/create-cli-stream.js.map +1 -0
  51. package/evento-test-helper.d.ts +11 -0
  52. package/evento-test-helper.js +40 -0
  53. package/evento-test-helper.js.map +1 -0
  54. package/evento.test.d.ts +1 -0
  55. package/evento.test.js +122 -0
  56. package/evento.test.js.map +1 -0
  57. package/index.d.ts +5 -0
  58. package/index.js +6 -0
  59. package/index.js.map +1 -0
  60. package/main.js +130 -31
  61. package/main.js.map +1 -1
  62. package/package.json +9 -8
  63. package/run.js +0 -1
  64. package/version-pinner.d.ts +1 -1
  65. package/build-cmd.js.map +0 -1
  66. package/cloud-token-key-cmd.js +0 -73
  67. package/cloud-token-key-cmd.js.map +0 -1
  68. package/dependabot-cmd.d.ts +0 -16
  69. package/dependabot-cmd.js +0 -102
  70. package/dependabot-cmd.js.map +0 -1
  71. package/device-id-cmd.d.ts +0 -150
  72. package/device-id-cmd.js +0 -576
  73. package/device-id-cmd.js.map +0 -1
  74. package/pre-signed-url.js.map +0 -1
  75. package/retry-cmd.d.ts +0 -14
  76. package/retry-cmd.js +0 -96
  77. package/retry-cmd.js.map +0 -1
  78. package/set-scripts-cmd.js +0 -155
  79. package/set-scripts-cmd.js.map +0 -1
  80. package/test-container-cmd.d.ts +0 -82
  81. package/test-container-cmd.js +0 -332
  82. package/test-container-cmd.js.map +0 -1
  83. package/tsc-cmd.js +0 -39
  84. package/tsc-cmd.js.map +0 -1
  85. package/update-deps-cmd.d.ts +0 -16
  86. package/update-deps-cmd.js +0 -147
  87. package/update-deps-cmd.js.map +0 -1
  88. package/well-known-cmd.js +0 -111
  89. package/well-known-cmd.js.map +0 -1
  90. package/write-env-cmd.d.ts +0 -23
  91. package/write-env-cmd.js +0 -104
  92. package/write-env-cmd.js.map +0 -1
@@ -0,0 +1,762 @@
1
+ import { command, option, string, subcommands, flag } from "cmd-ts";
2
+ import { CertificatePayloadSchema } from "@fireproof/core-types-base";
3
+ import { DeviceIdKey, DeviceIdCSR, DeviceIdCA } from "@fireproof/core-device-id";
4
+ import { getKeyBag } from "@fireproof/core-keybag";
5
+ import { decodeJwt } from "jose";
6
+ import fs from "fs-extra";
7
+ import { Hono } from "hono";
8
+ import { serve } from "@hono/node-server";
9
+ import open from "open";
10
+ import { Future, timeouted, isSuccess, isTimeout, BuildURI, Result, Option, } from "@adviser/cement";
11
+ import { sts } from "@fireproof/core-runtime";
12
+ import { type } from "arktype";
13
+ import { sendMsg, sendProgress } from "../cmd-evento.js";
14
+ function getStdin() {
15
+ return new Promise((resolve) => {
16
+ let data = "";
17
+ process.stdin.setEncoding("utf8");
18
+ process.stdin.on("readable", () => {
19
+ let chunk;
20
+ while ((chunk = process.stdin.read()) !== null) {
21
+ data += chunk;
22
+ }
23
+ });
24
+ process.stdin.on("end", () => resolve(data));
25
+ });
26
+ }
27
+ function subjectOptions() {
28
+ return {
29
+ commonName: option({
30
+ long: "common-name",
31
+ short: "cn",
32
+ description: "Common Name (required, e.g., 'My Device' or 'device-serial')",
33
+ type: string,
34
+ }),
35
+ organization: option({
36
+ long: "organization",
37
+ short: "o",
38
+ description: "Organization name",
39
+ type: string,
40
+ defaultValue: () => "You did not set the Organization",
41
+ }),
42
+ locality: option({
43
+ long: "locality",
44
+ short: "l",
45
+ description: "Locality/City",
46
+ type: string,
47
+ defaultValue: () => "You did not set the City",
48
+ }),
49
+ state: option({
50
+ long: "state",
51
+ short: "s",
52
+ description: "State or Province",
53
+ type: string,
54
+ defaultValue: () => "You did not set the State",
55
+ }),
56
+ country: option({
57
+ long: "country",
58
+ short: "c",
59
+ description: "Country (2-letter code)",
60
+ type: string,
61
+ defaultValue: () => "WD",
62
+ }),
63
+ };
64
+ }
65
+ function buildSubject(args) {
66
+ return {
67
+ commonName: args.commonName,
68
+ organization: args.organization,
69
+ locality: args.locality,
70
+ stateOrProvinceName: args.state,
71
+ countryName: args.country,
72
+ };
73
+ }
74
+ export const ReqDeviceIdCreate = type({
75
+ type: "'core-cli.device-id-create'",
76
+ force: "boolean",
77
+ });
78
+ export const ResDeviceIdCreate = type({
79
+ type: "'core-cli.res-device-id-create'",
80
+ output: "string",
81
+ });
82
+ export function isResDeviceIdCreate(u) {
83
+ return !(ResDeviceIdCreate(u) instanceof type.errors);
84
+ }
85
+ export const deviceIdCreateEvento = {
86
+ hash: "core-cli.device-id-create",
87
+ validate: (ctx) => {
88
+ if (!(ReqDeviceIdCreate(ctx.enRequest) instanceof type.errors)) {
89
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
90
+ }
91
+ return Promise.resolve(Result.Ok(Option.None()));
92
+ },
93
+ handle: async (ctx) => {
94
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
95
+ const sthis = cliCtx.sthis;
96
+ const args = ctx.validated;
97
+ const keyBag = await getKeyBag(sthis);
98
+ const existingDeviceIdResult = await keyBag.getDeviceId();
99
+ if (existingDeviceIdResult.deviceId.IsSome() && !args.force) {
100
+ const jwk = existingDeviceIdResult.deviceId.unwrap();
101
+ const deviceIdKey = (await DeviceIdKey.createFromJWK(jwk)).unwrap();
102
+ const fingerprint = await deviceIdKey.fingerPrint();
103
+ return sendMsg(ctx, {
104
+ type: "core-cli.res-device-id-create",
105
+ output: `Existing Device ID Fingerprint: ${fingerprint}`,
106
+ });
107
+ }
108
+ const deviceIdKey = await DeviceIdKey.create();
109
+ const jwkPrivate = await deviceIdKey.exportPrivateJWK();
110
+ await keyBag.setDeviceId(jwkPrivate);
111
+ const fingerprint = await deviceIdKey.fingerPrint();
112
+ const lines = [
113
+ `Created Device ID Fingerprint: ${fingerprint}`,
114
+ "To generate a Certificate Signing Request (CSR), run: core-cli deviceId csr",
115
+ "To export the public and private keys, run: core-cli deviceId export",
116
+ ];
117
+ return sendMsg(ctx, {
118
+ type: "core-cli.res-device-id-create",
119
+ output: lines.join("\n"),
120
+ });
121
+ },
122
+ };
123
+ export const ReqDeviceIdCsr = type({
124
+ type: "'core-cli.device-id-csr'",
125
+ commonName: "string",
126
+ organization: "string",
127
+ locality: "string",
128
+ state: "string",
129
+ country: "string",
130
+ });
131
+ export const ResDeviceIdCsr = type({
132
+ type: "'core-cli.res-device-id-csr'",
133
+ output: "string",
134
+ });
135
+ export function isResDeviceIdCsr(u) {
136
+ return !(ResDeviceIdCsr(u) instanceof type.errors);
137
+ }
138
+ export const deviceIdCsrEvento = {
139
+ hash: "core-cli.device-id-csr",
140
+ validate: (ctx) => {
141
+ if (!(ReqDeviceIdCsr(ctx.enRequest) instanceof type.errors)) {
142
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
143
+ }
144
+ return Promise.resolve(Result.Ok(Option.None()));
145
+ },
146
+ handle: async (ctx) => {
147
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
148
+ const sthis = cliCtx.sthis;
149
+ const args = ctx.validated;
150
+ const keyBag = await getKeyBag(sthis);
151
+ const existingDeviceIdResult = await keyBag.getDeviceId();
152
+ if (existingDeviceIdResult.deviceId.IsNone()) {
153
+ return Result.Err("No Device ID found. Please create one using 'core-cli deviceId create' first.");
154
+ }
155
+ const jwkPrivate = existingDeviceIdResult.deviceId.unwrap();
156
+ const createResult = await DeviceIdKey.createFromJWK(jwkPrivate);
157
+ if (createResult.isErr()) {
158
+ return Result.Err(`Error loading existing device ID: ${createResult.Err()}`);
159
+ }
160
+ const deviceIdKey = createResult.Ok();
161
+ const deviceIdCSR = new DeviceIdCSR(sthis, deviceIdKey);
162
+ const subject = buildSubject(args);
163
+ const csrResult = await deviceIdCSR.createCSR(subject);
164
+ if (csrResult.isErr()) {
165
+ return Result.Err(`Failed to generate CSR: ${csrResult.Err()}`);
166
+ }
167
+ const lines = [
168
+ "\n--- Certificate Signing Request (CSR) ---",
169
+ csrResult.Ok(),
170
+ "---\n",
171
+ "Please send the above CSR to your Certificate Authority (CA) to get a signed certificate.",
172
+ "Once you receive the certificate, you can use a future command to import it.",
173
+ ];
174
+ return sendMsg(ctx, {
175
+ type: "core-cli.res-device-id-csr",
176
+ output: lines.join("\n"),
177
+ });
178
+ },
179
+ };
180
+ export const ReqDeviceIdExport = type({
181
+ type: "'core-cli.device-id-export'",
182
+ private: "boolean",
183
+ json: "boolean",
184
+ public: "boolean",
185
+ cert: "boolean",
186
+ });
187
+ export const ResDeviceIdExport = type({
188
+ type: "'core-cli.res-device-id-export'",
189
+ output: "string",
190
+ });
191
+ export function isResDeviceIdExport(u) {
192
+ return !(ResDeviceIdExport(u) instanceof type.errors);
193
+ }
194
+ export const deviceIdExportEvento = {
195
+ hash: "core-cli.device-id-export",
196
+ validate: (ctx) => {
197
+ if (!(ReqDeviceIdExport(ctx.enRequest) instanceof type.errors)) {
198
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
199
+ }
200
+ return Promise.resolve(Result.Ok(Option.None()));
201
+ },
202
+ handle: async (ctx) => {
203
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
204
+ const sthis = cliCtx.sthis;
205
+ const args = ctx.validated;
206
+ const keyBag = await getKeyBag(sthis);
207
+ const existingDeviceIdResult = await keyBag.getDeviceId();
208
+ if (existingDeviceIdResult.deviceId.IsNone()) {
209
+ return Result.Err("No Device ID found. Please create one using 'core-cli deviceId create' first.");
210
+ }
211
+ const jwkPrivate = existingDeviceIdResult.deviceId.unwrap();
212
+ const createResult = await DeviceIdKey.createFromJWK(jwkPrivate);
213
+ if (createResult.isErr()) {
214
+ return Result.Err(`Error loading device ID: ${createResult.Err()}`);
215
+ }
216
+ const deviceIdKey = createResult.Ok();
217
+ let publicKey;
218
+ let privateKey;
219
+ let certificate;
220
+ const exportPublic = args.public || (!args.private && !args.cert);
221
+ const exportPrivate = args.private;
222
+ const exportCert = args.cert || (!args.private && !args.public && !args.cert);
223
+ if (exportPublic) {
224
+ publicKey = await deviceIdKey.publicKey();
225
+ }
226
+ if (exportPrivate) {
227
+ privateKey = await deviceIdKey.exportPrivateJWK();
228
+ }
229
+ if (exportCert && existingDeviceIdResult.cert.IsSome()) {
230
+ certificate = existingDeviceIdResult.cert.unwrap();
231
+ }
232
+ else if (args.cert && existingDeviceIdResult.cert.IsNone()) {
233
+ return Result.Err("No certificate found for this Device ID.");
234
+ }
235
+ let output;
236
+ if (args.json) {
237
+ const outputObject = {};
238
+ if (publicKey)
239
+ outputObject.publicKey = publicKey;
240
+ if (privateKey)
241
+ outputObject.privateKey = privateKey;
242
+ if (certificate)
243
+ outputObject.certificate = certificate.certificateJWT;
244
+ output = JSON.stringify(outputObject);
245
+ }
246
+ else {
247
+ const lines = [];
248
+ lines.push("--- Device ID Export ---");
249
+ const fingerprint = await deviceIdKey.fingerPrint();
250
+ lines.push(`Fingerprint: ${fingerprint}`);
251
+ if (publicKey) {
252
+ lines.push("\nPublic Key (JWK):");
253
+ lines.push(JSON.stringify(publicKey, null, 2));
254
+ }
255
+ if (privateKey) {
256
+ lines.push("\nPrivate Key (JWK):");
257
+ lines.push(JSON.stringify(privateKey, null, 2));
258
+ }
259
+ if (certificate) {
260
+ lines.push("\nCertificate (JWT):");
261
+ lines.push(certificate.certificateJWT);
262
+ }
263
+ lines.push("---\n");
264
+ output = lines.join("\n");
265
+ }
266
+ return sendMsg(ctx, {
267
+ type: "core-cli.res-device-id-export",
268
+ output,
269
+ });
270
+ },
271
+ };
272
+ export const ReqDeviceIdCert = type({
273
+ type: "'core-cli.device-id-cert'",
274
+ file: "string",
275
+ });
276
+ export const ResDeviceIdCert = type({
277
+ type: "'core-cli.res-device-id-cert'",
278
+ output: "string",
279
+ });
280
+ export function isResDeviceIdCert(u) {
281
+ return !(ResDeviceIdCert(u) instanceof type.errors);
282
+ }
283
+ export const deviceIdCertEvento = {
284
+ hash: "core-cli.device-id-cert",
285
+ validate: (ctx) => {
286
+ if (!(ReqDeviceIdCert(ctx.enRequest) instanceof type.errors)) {
287
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
288
+ }
289
+ return Promise.resolve(Result.Ok(Option.None()));
290
+ },
291
+ handle: async (ctx) => {
292
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
293
+ const sthis = cliCtx.sthis;
294
+ const args = ctx.validated;
295
+ const keyBag = await getKeyBag(sthis);
296
+ const existingDeviceIdResult = await keyBag.getDeviceId();
297
+ if (existingDeviceIdResult.deviceId.IsNone()) {
298
+ return Result.Err("No Device ID found. Please create one using 'core-cli deviceId create' first.");
299
+ }
300
+ const jwkPrivate = existingDeviceIdResult.deviceId.unwrap();
301
+ let certificateContent;
302
+ const lines = [];
303
+ if (args.file) {
304
+ certificateContent = await fs.readFile(args.file, "utf8");
305
+ lines.push(`Certificate read from ${args.file}`);
306
+ }
307
+ else {
308
+ console.log("Waiting for certificate content from stdin (Ctrl+D to finish):");
309
+ certificateContent = await getStdin();
310
+ lines.push("Certificate read from stdin.");
311
+ }
312
+ const decoded = decodeJwt(certificateContent);
313
+ const certPayload = CertificatePayloadSchema.parse(decoded);
314
+ const certToStore = {
315
+ certificateJWT: certificateContent,
316
+ certificatePayload: certPayload,
317
+ };
318
+ await keyBag.setDeviceId(jwkPrivate, certToStore);
319
+ lines.push("Certificate successfully stored with the Device ID.");
320
+ return sendMsg(ctx, {
321
+ type: "core-cli.res-device-id-cert",
322
+ output: lines.join("\n"),
323
+ });
324
+ },
325
+ };
326
+ export const ReqDeviceIdCaCert = type({
327
+ type: "'core-cli.device-id-ca-cert'",
328
+ commonName: "string",
329
+ organization: "string",
330
+ locality: "string",
331
+ state: "string",
332
+ country: "string",
333
+ keyFile: "string",
334
+ outputKey: "string",
335
+ outputCert: "string",
336
+ json: "boolean",
337
+ envVars: "boolean",
338
+ });
339
+ export const ResDeviceIdCaCert = type({
340
+ type: "'core-cli.res-device-id-ca-cert'",
341
+ output: "string",
342
+ });
343
+ export function isResDeviceIdCaCert(u) {
344
+ return !(ResDeviceIdCaCert(u) instanceof type.errors);
345
+ }
346
+ export const deviceIdCaCertEvento = {
347
+ hash: "core-cli.device-id-ca-cert",
348
+ validate: (ctx) => {
349
+ if (!(ReqDeviceIdCaCert(ctx.enRequest) instanceof type.errors)) {
350
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
351
+ }
352
+ return Promise.resolve(Result.Ok(Option.None()));
353
+ },
354
+ handle: async (ctx) => {
355
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
356
+ const sthis = cliCtx.sthis;
357
+ const args = ctx.validated;
358
+ const progress = async (message) => {
359
+ if (!args.json && !args.envVars) {
360
+ await sendProgress(ctx, "info", message);
361
+ }
362
+ };
363
+ let caKey;
364
+ let jwkPrivate;
365
+ if (args.keyFile) {
366
+ const keyContent = await fs.readFile(args.keyFile, "utf8");
367
+ jwkPrivate = JSON.parse(keyContent);
368
+ const keyResult = await DeviceIdKey.createFromJWK(jwkPrivate);
369
+ if (keyResult.isErr()) {
370
+ return Result.Err(`Error loading private key from file: ${keyResult.Err()}`);
371
+ }
372
+ caKey = keyResult.Ok();
373
+ await progress(`Loaded private key from ${args.keyFile}`);
374
+ }
375
+ else {
376
+ caKey = await DeviceIdKey.create();
377
+ jwkPrivate = await caKey.exportPrivateJWK();
378
+ await progress("Generated new CA private key");
379
+ if (args.outputKey) {
380
+ await fs.writeFile(args.outputKey, JSON.stringify(jwkPrivate, null, 2), "utf8");
381
+ await progress(`Private key saved to ${args.outputKey}`);
382
+ }
383
+ }
384
+ const caSubject = buildSubject(args);
385
+ const deviceCA = new DeviceIdCA({
386
+ base64: sthis.txt.base64,
387
+ caKey,
388
+ caSubject,
389
+ actions: {
390
+ generateSerialNumber: async () => sthis.nextId(32).str,
391
+ },
392
+ });
393
+ const issueCertResult = await deviceCA.issueCertificate({
394
+ csr: {
395
+ subject: caSubject,
396
+ publicKey: await caKey.publicKey(),
397
+ extensions: {
398
+ keyUsage: ["digitalSignature", "keyCertSign", "cRLSign"],
399
+ extendedKeyUsage: [],
400
+ },
401
+ },
402
+ });
403
+ if (issueCertResult.isErr()) {
404
+ return Result.Err(`Error issuing CA certificate: ${issueCertResult.Err()}`);
405
+ }
406
+ const certificateJWT = issueCertResult.Ok().certificateJWT;
407
+ let output;
408
+ if (args.envVars) {
409
+ const lines = [`DEVICE_ID_CA_PRIV_KEY=${await sts.jwk2env(jwkPrivate)}`, `DEVICE_ID_CA_CERT=${certificateJWT}`];
410
+ output = lines.join("\n");
411
+ }
412
+ else if (args.json) {
413
+ const jsonOutput = {
414
+ privateKey: jwkPrivate,
415
+ signedCert: certificateJWT,
416
+ };
417
+ output = JSON.stringify(jsonOutput, null, 2);
418
+ }
419
+ else {
420
+ const lines = [];
421
+ lines.push("\n--- CA Private Key (JWK) ---");
422
+ lines.push(JSON.stringify(jwkPrivate, null, 2));
423
+ lines.push("---\n");
424
+ lines.push("\n--- CA Certificate (JWT) ---");
425
+ lines.push(certificateJWT);
426
+ lines.push("---\n");
427
+ lines.push("\nCA Certificate Details:");
428
+ lines.push(` Common Name: ${caSubject.commonName}`);
429
+ lines.push(` Organization: ${caSubject.organization}`);
430
+ lines.push(` Locality: ${caSubject.locality}`);
431
+ lines.push(` State: ${caSubject.stateOrProvinceName}`);
432
+ lines.push(` Country: ${caSubject.countryName}`);
433
+ const fingerprint = await caKey.fingerPrint();
434
+ lines.push(` Key Fingerprint: ${fingerprint}`);
435
+ if (args.outputKey) {
436
+ lines.push(`\n✓ Private key saved to ${args.outputKey}`);
437
+ }
438
+ if (args.outputCert) {
439
+ await fs.writeFile(args.outputCert, certificateJWT, "utf8");
440
+ lines.push(`✓ Certificate saved to ${args.outputCert}`);
441
+ }
442
+ if (!args.outputKey && !args.keyFile) {
443
+ lines.push("\n⚠️ Warning: Private key was not saved to a file. Consider using --output-key to save it.");
444
+ }
445
+ output = lines.join("\n");
446
+ }
447
+ return sendMsg(ctx, {
448
+ type: "core-cli.res-device-id-ca-cert",
449
+ output,
450
+ });
451
+ },
452
+ };
453
+ export const ReqDeviceIdRegister = type({
454
+ type: "'core-cli.device-id-register'",
455
+ commonName: "string",
456
+ organization: "string",
457
+ locality: "string",
458
+ state: "string",
459
+ country: "string",
460
+ caUrl: "string",
461
+ port: "string",
462
+ timeout: "string",
463
+ forceRenew: "boolean",
464
+ });
465
+ export const ResDeviceIdRegister = type({
466
+ type: "'core-cli.res-device-id-register'",
467
+ output: "string",
468
+ });
469
+ export function isResDeviceIdRegister(u) {
470
+ return !(ResDeviceIdRegister(u) instanceof type.errors);
471
+ }
472
+ export const deviceIdRegisterEvento = {
473
+ hash: "core-cli.device-id-register",
474
+ validate: (ctx) => {
475
+ if (!(ReqDeviceIdRegister(ctx.enRequest) instanceof type.errors)) {
476
+ return Promise.resolve(Result.Ok(Option.Some(ctx.enRequest)));
477
+ }
478
+ return Promise.resolve(Result.Ok(Option.None()));
479
+ },
480
+ handle: async (ctx) => {
481
+ const cliCtx = ctx.ctx.getOrThrow("cliCtx");
482
+ const sthis = cliCtx.sthis;
483
+ const args = ctx.validated;
484
+ const keyBag = await getKeyBag(sthis);
485
+ const existingDeviceIdResult = await keyBag.getDeviceId();
486
+ if (existingDeviceIdResult.cert.IsSome() && !args.forceRenew) {
487
+ const jwk = existingDeviceIdResult.deviceId.unwrap();
488
+ const deviceIdKey = (await DeviceIdKey.createFromJWK(jwk)).unwrap();
489
+ const fingerprint = await deviceIdKey.fingerPrint();
490
+ return sendMsg(ctx, {
491
+ type: "core-cli.res-device-id-register",
492
+ output: [
493
+ "Device already has a certificate. Registration not needed.",
494
+ "Use --force-renew to renew the certificate.",
495
+ `Existing Device ID Fingerprint: ${fingerprint}`,
496
+ ].join("\n"),
497
+ });
498
+ }
499
+ if (args.forceRenew && existingDeviceIdResult.cert.IsSome()) {
500
+ await sendProgress(ctx, "info", "Force renewing certificate...");
501
+ }
502
+ let deviceIdKey;
503
+ if (existingDeviceIdResult.deviceId.IsNone()) {
504
+ await sendProgress(ctx, "info", "Creating new device ID key pair...");
505
+ deviceIdKey = await DeviceIdKey.create();
506
+ const jwkPrivate = await deviceIdKey.exportPrivateJWK();
507
+ await keyBag.setDeviceId(jwkPrivate);
508
+ const fingerprint = await deviceIdKey.fingerPrint();
509
+ await sendProgress(ctx, "info", `Created Device ID Fingerprint: ${fingerprint}`);
510
+ }
511
+ else {
512
+ await sendProgress(ctx, "info", "Using existing device ID key...");
513
+ const jwkPrivate = existingDeviceIdResult.deviceId.unwrap();
514
+ const createResult = await DeviceIdKey.createFromJWK(jwkPrivate);
515
+ if (createResult.isErr()) {
516
+ return Result.Err(`Error loading existing device ID: ${createResult.Err()}`);
517
+ }
518
+ deviceIdKey = createResult.Ok();
519
+ const fingerprint = await deviceIdKey.fingerPrint();
520
+ await sendProgress(ctx, "info", `Device ID Fingerprint: ${fingerprint}`);
521
+ }
522
+ await sendProgress(ctx, "info", "Generating Certificate Signing Request (CSR)...");
523
+ const deviceIdCSR = new DeviceIdCSR(sthis, deviceIdKey);
524
+ const subject = buildSubject(args);
525
+ const csrResult = await deviceIdCSR.createCSR(subject);
526
+ if (csrResult.isErr()) {
527
+ return Result.Err(`Failed to generate CSR: ${csrResult.Err()}`);
528
+ }
529
+ const csrJWS = csrResult.Ok();
530
+ await sendProgress(ctx, "info", "CSR generated successfully.");
531
+ const certFuture = new Future();
532
+ const app = new Hono();
533
+ let serverInstance = null;
534
+ app.get("/cert", (c) => {
535
+ const cert = c.req.query("cert");
536
+ if (!cert) {
537
+ certFuture.reject(new Error("Missing cert parameter"));
538
+ return c.text("Missing cert parameter", 400);
539
+ }
540
+ void sendProgress(ctx, "info", "\nCertificate received from CA!");
541
+ certFuture.resolve(cert);
542
+ return c.text("Certificate received successfully. You can close this window.");
543
+ });
544
+ const port = args.port ? parseInt(args.port, 10) : Math.floor(Math.random() * (65535 - 49152) + 49152);
545
+ const callbackUrl = `http://localhost:${port}/cert`;
546
+ await sendProgress(ctx, "info", `Starting local server on port ${port}...`);
547
+ serverInstance = serve({
548
+ fetch: app.fetch,
549
+ port,
550
+ });
551
+ const caUri = BuildURI.from(args.caUrl).setParam("csr", csrJWS).setParam("returnUrl", callbackUrl);
552
+ const caUrlWithParams = caUri.toString();
553
+ await sendProgress(ctx, "info", "\nOpening browser to CA for certificate signing...");
554
+ await sendProgress(ctx, "info", `URL: ${caUrlWithParams}\n`);
555
+ try {
556
+ await open(caUrlWithParams);
557
+ }
558
+ catch (error) {
559
+ await sendProgress(ctx, "warn", "Could not automatically open browser. Please open this URL manually:");
560
+ await sendProgress(ctx, "warn", caUrlWithParams);
561
+ }
562
+ await sendProgress(ctx, "info", "Waiting for certificate from CA...");
563
+ await sendProgress(ctx, "info", "(The browser should redirect back to this application after signing)\n");
564
+ const timeoutMs = parseInt(args.timeout, 10) * 1000;
565
+ const result = await timeouted(certFuture.asPromise(), { timeout: timeoutMs });
566
+ if (serverInstance) {
567
+ serverInstance.close();
568
+ }
569
+ if (!isSuccess(result)) {
570
+ if (isTimeout(result)) {
571
+ return Result.Err(`Timeout waiting for certificate from CA (${args.timeout}s).`);
572
+ }
573
+ else {
574
+ return Result.Err(`Failed to receive certificate: ${result.state === "error" ? result.error : result}`);
575
+ }
576
+ }
577
+ const receivedCert = result.value;
578
+ await sendProgress(ctx, "info", "Storing certificate...");
579
+ const decoded = decodeJwt(receivedCert);
580
+ const certPayload = CertificatePayloadSchema.parse(decoded);
581
+ const jwkPrivate = await deviceIdKey.exportPrivateJWK();
582
+ const certToStore = {
583
+ certificateJWT: receivedCert,
584
+ certificatePayload: certPayload,
585
+ };
586
+ await keyBag.setDeviceId(jwkPrivate, certToStore);
587
+ const fingerprint = await deviceIdKey.fingerPrint();
588
+ return sendMsg(ctx, {
589
+ type: "core-cli.res-device-id-register",
590
+ output: [
591
+ "\n✓ Registration complete! Certificate successfully stored with Device ID.",
592
+ `Device ID Fingerprint: ${fingerprint}`,
593
+ ].join("\n"),
594
+ });
595
+ },
596
+ };
597
+ export function deviceIdCmd(ctx) {
598
+ const createCmd = command({
599
+ name: "create",
600
+ description: "Generate a new device ID key pair and store it.",
601
+ args: {
602
+ force: flag({
603
+ long: "force",
604
+ short: "f",
605
+ description: "Force creation of a new device ID, overwriting any existing one.",
606
+ }),
607
+ },
608
+ handler: ctx.cliStream.enqueue((args) => {
609
+ return {
610
+ type: "core-cli.device-id-create",
611
+ ...args,
612
+ };
613
+ }),
614
+ });
615
+ const csrCmd = command({
616
+ name: "csr",
617
+ description: "Generate a Certificate Signing Request (CSR) for the current device ID.",
618
+ args: subjectOptions(),
619
+ handler: ctx.cliStream.enqueue((args) => {
620
+ return {
621
+ type: "core-cli.device-id-csr",
622
+ ...args,
623
+ };
624
+ }),
625
+ });
626
+ const exportCmd = command({
627
+ name: "export",
628
+ description: "Export the public and private parts of the current device ID.",
629
+ args: {
630
+ private: flag({
631
+ long: "private",
632
+ short: "p",
633
+ description: "Export only the private key. Use with caution!",
634
+ }),
635
+ json: flag({
636
+ long: "json",
637
+ description: "Output in single-line JSON format.",
638
+ }),
639
+ public: flag({
640
+ long: "public",
641
+ description: "Export only the public key. Default if no other flags specified for key type.",
642
+ }),
643
+ cert: flag({
644
+ long: "cert",
645
+ description: "Export the certificate if available.",
646
+ }),
647
+ },
648
+ handler: ctx.cliStream.enqueue((args) => {
649
+ return {
650
+ type: "core-cli.device-id-export",
651
+ ...args,
652
+ };
653
+ }),
654
+ });
655
+ const certCmd = command({
656
+ name: "cert",
657
+ description: "Import and store a signed certificate for the current device ID.",
658
+ args: {
659
+ file: option({
660
+ long: "file",
661
+ short: "f",
662
+ description: "Path to the certificate file. If not provided, reads from stdin.",
663
+ type: string,
664
+ defaultValue: () => "",
665
+ }),
666
+ },
667
+ handler: ctx.cliStream.enqueue((args) => {
668
+ return {
669
+ type: "core-cli.device-id-cert",
670
+ ...args,
671
+ };
672
+ }),
673
+ });
674
+ const caCertCmd = command({
675
+ name: "ca-cert",
676
+ description: "Create a self-signed CA certificate for use in DeviceIdCA.",
677
+ args: {
678
+ ...subjectOptions(),
679
+ keyFile: option({
680
+ long: "key-file",
681
+ short: "k",
682
+ description: "Path to existing private key file (JWK format). If not provided, a new key will be generated.",
683
+ type: string,
684
+ defaultValue: () => "",
685
+ }),
686
+ outputKey: option({
687
+ long: "output-key",
688
+ description: "Path to save the private key (JWK format). Only used when generating a new key.",
689
+ type: string,
690
+ defaultValue: () => "",
691
+ }),
692
+ outputCert: option({
693
+ long: "output-cert",
694
+ description: "Path to save the certificate (JWT format). If not provided, outputs to stdout.",
695
+ type: string,
696
+ defaultValue: () => "",
697
+ }),
698
+ json: flag({
699
+ long: "json",
700
+ description: "Output in JSON format with both privateKey and signedCert.",
701
+ }),
702
+ envVars: flag({
703
+ long: "envVars",
704
+ description: "Output as environment variables (DEVICE_ID_CA_PRIV_KEY and DEVICE_ID_CA_CERT).",
705
+ }),
706
+ },
707
+ handler: ctx.cliStream.enqueue((args) => {
708
+ return {
709
+ type: "core-cli.device-id-ca-cert",
710
+ ...args,
711
+ };
712
+ }),
713
+ });
714
+ const registerCmd = command({
715
+ name: "register",
716
+ description: "Register device by creating key pair, generating CSR, and obtaining certificate from CA.",
717
+ args: {
718
+ ...subjectOptions(),
719
+ caUrl: option({
720
+ long: "ca-url",
721
+ description: "CA URL to open in browser for certificate signing",
722
+ type: string,
723
+ defaultValue: () => "http://localhost:7370/fp/cloud/csr2cert",
724
+ }),
725
+ port: option({
726
+ long: "port",
727
+ description: "Local port for callback server (random port if not specified)",
728
+ type: string,
729
+ defaultValue: () => "",
730
+ }),
731
+ timeout: option({
732
+ long: "timeout",
733
+ description: "Timeout in seconds to wait for certificate from CA",
734
+ type: string,
735
+ defaultValue: () => "60",
736
+ }),
737
+ forceRenew: flag({
738
+ long: "force-renew",
739
+ description: "Force certificate renewal even if one already exists",
740
+ }),
741
+ },
742
+ handler: ctx.cliStream.enqueue((args) => {
743
+ return {
744
+ type: "core-cli.device-id-register",
745
+ ...args,
746
+ };
747
+ }),
748
+ });
749
+ return subcommands({
750
+ name: "device-id",
751
+ description: "Manage device identities.",
752
+ cmds: {
753
+ create: createCmd,
754
+ csr: csrCmd,
755
+ export: exportCmd,
756
+ cert: certCmd,
757
+ "ca-cert": caCertCmd,
758
+ register: registerCmd,
759
+ },
760
+ });
761
+ }
762
+ //# sourceMappingURL=device-id-cmd.js.map