@smonn/ids 0.9.0 → 0.9.1

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 (41) hide show
  1. package/README.md +85 -12
  2. package/dist/cli.mjs +155 -50
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{codec-shell-C7_B4oum.mjs → codec-shell-CW2sD6BU.mjs} +3 -3
  5. package/dist/{codec-shell-C7_B4oum.mjs.map → codec-shell-CW2sD6BU.mjs.map} +1 -1
  6. package/dist/drizzle.d.mts +32 -2
  7. package/dist/drizzle.d.mts.map +1 -0
  8. package/dist/index.mjs +1 -1
  9. package/dist/{key-material-DUHhmMq-.mjs → key-material-gOnqTNoV.mjs} +3 -3
  10. package/dist/{key-material-DUHhmMq-.mjs.map → key-material-gOnqTNoV.mjs.map} +1 -1
  11. package/dist/kysely.d.mts.map +1 -1
  12. package/dist/kysely.mjs +2 -3
  13. package/dist/kysely.mjs.map +1 -1
  14. package/dist/{opaque-BQOlZ2oD.mjs → opaque-BpqxV8oB.mjs} +8 -8
  15. package/dist/{opaque-BQOlZ2oD.mjs.map → opaque-BpqxV8oB.mjs.map} +1 -1
  16. package/dist/opaque.mjs +1 -1
  17. package/dist/prisma.d.mts +1 -2
  18. package/dist/prisma.d.mts.map +1 -1
  19. package/dist/prisma.mjs +1 -2
  20. package/dist/prisma.mjs.map +1 -1
  21. package/dist/{reverse-C12D1btB.mjs → reverse-d5uEoIET.mjs} +4 -4
  22. package/dist/{reverse-C12D1btB.mjs.map → reverse-d5uEoIET.mjs.map} +1 -1
  23. package/dist/reverse.mjs +1 -1
  24. package/dist/{signed-CwqKTFaQ.mjs → signed-BnRSC03a.mjs} +13 -13
  25. package/dist/signed-BnRSC03a.mjs.map +1 -0
  26. package/dist/signed.d.mts.map +1 -1
  27. package/dist/signed.mjs +1 -1
  28. package/dist/{timestamp-BjIMQkJf.mjs → timestamp-BbZL8hwg.mjs} +5 -5
  29. package/dist/{timestamp-BjIMQkJf.mjs.map → timestamp-BbZL8hwg.mjs.map} +1 -1
  30. package/dist/{timestamp-bytes-Bbg6Y66Z.mjs → timestamp-bytes-DoFjLjDp.mjs} +3 -2
  31. package/dist/timestamp-bytes-DoFjLjDp.mjs.map +1 -0
  32. package/dist/{wrapped-DKOsN_dq.mjs → wrapped-BI9UXnAm.mjs} +21 -16
  33. package/dist/wrapped-BI9UXnAm.mjs.map +1 -0
  34. package/dist/wrapped.d.mts.map +1 -1
  35. package/dist/wrapped.mjs +1 -1
  36. package/package.json +5 -5
  37. package/dist/drizzle-CHtyDXpv.d.mts +0 -33
  38. package/dist/drizzle-CHtyDXpv.d.mts.map +0 -1
  39. package/dist/signed-CwqKTFaQ.mjs.map +0 -1
  40. package/dist/timestamp-bytes-Bbg6Y66Z.mjs.map +0 -1
  41. package/dist/wrapped-DKOsN_dq.mjs.map +0 -1
package/README.md CHANGED
@@ -105,7 +105,7 @@ try {
105
105
  | `empty_keyring` | the wrapping keyring is empty | `createWrappedKeyId({ keys })` | Supply at least one `WrappingKey` |
106
106
  | `duplicate_keyring_entry` | two keyring entries share the same raw secret | `createWrappedKeyId({ keys })` | Deduplicate the key list |
107
107
  | `invalid_lookup_key` | lookup key is out of range or the wrong JS type | `wrap(lookupKey)` | Check the kind's range and JS type |
108
- | `verification_failed` | no keyring entry verifies the payload tag | `unwrap(id)` | Check keyring; tamper or wrong key |
108
+ | `verification_failed` | no keyring entry verifies the payload tag | `unwrap(id)`, `verify(id)` | Check keyring; tamper or wrong key |
109
109
  | `invalid_id` | string is not a valid ID for this brand | `parse()`, ORM adapter read paths | Use `safeParse()` for untrusted input |
110
110
 
111
111
  `invalid_id` carries the originating `ParseError` string on `cause` — check `err.cause` for `"not_string"`, `"invalid_prefix"`, or `"invalid_base32"` when you need to distinguish the failure mode.
@@ -905,7 +905,7 @@ Brand-agnostic subcommands, no install required. Run `npx @smonn/ids --help` for
905
905
 
906
906
  ### `inspect` (`i`)
907
907
 
908
- Decode an ID and print brand, timestamp, canonical form, and whether the input was already canonical.
908
+ Decode an ID and print brand, timestamp (or lookup key), canonical form, and whether the input was already canonical.
909
909
 
910
910
  ```bash
911
911
  $ npx @smonn/ids inspect usr_01h7b3k9rqxn1cw3p9r8t2sgkz
@@ -915,13 +915,34 @@ canonical: usr_01h7b3k9rqxn1cw3p9r8t2sgkz
915
915
  input: canonical
916
916
  ```
917
917
 
918
- Accepts non-canonical input (uppercase, Crockford aliases). Assumes the **Timestamp codec** if the brand uses the **Opaque Timestamp codec**, pass `--opaque` and set `IDS_KEY` (below); otherwise the timestamp line is meaningless garbage.
918
+ Accepts non-canonical input (uppercase, Crockford aliases). Pass the flag that matches the codec variant used at generation without a flag, the **Timestamp codec** is assumed.
919
+
920
+ | Flag | Codec variant | Env var | Notes |
921
+ | ---------------------- | ----------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------- |
922
+ | _(none)_ | Timestamp codec | — | Timestamp readable directly |
923
+ | `--opaque` | Opaque Timestamp codec | `IDS_KEY` | Wrong key yields plausible-but-wrong timestamp, not an error (see [CONTEXT.md](./CONTEXT.md)) |
924
+ | `--reverse` | Reverse Timestamp codec | — | No key required; timestamp decoded from inverted bytes |
925
+ | `--wrapped --kind <k>` | Wrapped key codec | `IDS_WRAPPING_KEY` | `--kind` required: `u32`, `i32`, `u64`, `i64`; prints `lookup-key` |
926
+ | `--signed` | Signed Timestamp codec | `IDS_SIGNING_KEY` (optional) | Without key: prints timestamp only. With key: adds `verification: ok` or `verification: failed` |
927
+
928
+ Key format defaults to `hex` for all keyed modes; override with `--key-format hex|base64url` or the matching `_FORMAT` env var (see [Environment variables](#environment-variables) below).
919
929
 
920
930
  ```bash
931
+ # Opaque Timestamp (IDS_KEY required):
921
932
  IDS_KEY=<hex-or-base64url-key> npx @smonn/ids inspect inv_… --opaque
922
- ```
923
933
 
924
- Prints the decrypted timestamp **assuming `IDS_KEY` matches the key used at generation** — a well-formed but wrong key yields a plausible but incorrect timestamp, not an error (see [CONTEXT.md](./CONTEXT.md)).
934
+ # Wrapped key (IDS_WRAPPING_KEY and --kind required):
935
+ IDS_WRAPPING_KEY=<hex-or-base64url-key> npx @smonn/ids inspect item_… --wrapped --kind u64
936
+
937
+ # Reverse Timestamp (no key):
938
+ npx @smonn/ids inspect feed_… --reverse
939
+
940
+ # Signed Timestamp — timestamp only (no key):
941
+ npx @smonn/ids inspect evt_… --signed
942
+
943
+ # Signed Timestamp — with verification:
944
+ IDS_SIGNING_KEY=<hex-or-base64url-key> npx @smonn/ids inspect evt_… --signed
945
+ ```
925
946
 
926
947
  ### `generate` (`g`)
927
948
 
@@ -934,15 +955,29 @@ usr_…
934
955
  usr_…
935
956
  ```
936
957
 
937
- Flags: `--count` / `-c N` (default 1, max 10000). Uses the Timestamp codec unless `--opaque` is set.
958
+ Flags: `--count` / `-c N` (default 1, max 10000). Uses the Timestamp codec unless a mode flag is set.
959
+
960
+ | Flag | Codec variant | Env var |
961
+ | ----------- | ----------------------- | ----------------- |
962
+ | _(none)_ | Timestamp codec | — |
963
+ | `--opaque` | Opaque Timestamp codec | `IDS_KEY` |
964
+ | `--reverse` | Reverse Timestamp codec | — |
965
+ | `--signed` | Signed Timestamp codec | `IDS_SIGNING_KEY` |
938
966
 
939
967
  ```bash
968
+ # Opaque Timestamp:
940
969
  IDS_KEY=<hex-or-base64url-key> npx @smonn/ids generate inv --opaque --count 2
970
+
971
+ # Reverse Timestamp (newest-first sort order):
972
+ npx @smonn/ids generate feed --reverse --count 5
973
+
974
+ # Signed Timestamp:
975
+ IDS_SIGNING_KEY=<hex-or-base64url-key> npx @smonn/ids generate evt --signed
941
976
  ```
942
977
 
943
978
  ### `keygen` (`k`)
944
979
 
945
- Emit a random Opaque key to stdout (a secret — do not log or commit). Default: 256-bit hex.
980
+ Emit a random key to stdout — for use with `importOpaqueKey`, `importWrappingKey`, or `importSigningKey` (a secret — do not log or commit). Default: 256-bit hex for the Opaque key domain.
946
981
 
947
982
  ```bash
948
983
  $ npx @smonn/ids keygen
@@ -952,15 +987,53 @@ $ npx @smonn/ids keygen --bits 128 --key-format base64url
952
987
  AbCdEf…
953
988
  ```
954
989
 
955
- Flags: `--bits 128|192|256` (default 256), `--key-format hex|base64url` (default `hex`). `IDS_KEY_FORMAT` does not affect `keygen` — only `--key-format` on the command line. Output round-trips through `decodeOpaqueKey` / `importOpaqueKey`.
990
+ | Flag | Key domain | Intended for | Import function |
991
+ | ----------- | ---------- | ------------------ | ------------------- |
992
+ | _(none)_ | Opaque | `IDS_KEY` | `importOpaqueKey` |
993
+ | `--wrapped` | Wrapping | `IDS_WRAPPING_KEY` | `importWrappingKey` |
994
+ | `--signed` | Signing | `IDS_SIGNING_KEY` | `importSigningKey` |
995
+
996
+ Flags: `--bits 128|192|256` (default 256), `--key-format hex|base64url` (default `hex`). Key-format env vars do not affect `keygen` — only `--key-format` on the command line.
997
+
998
+ ```bash
999
+ # Wrapping key:
1000
+ npx @smonn/ids keygen --wrapped
1001
+
1002
+ # Signing key (base64url):
1003
+ npx @smonn/ids keygen --signed --key-format base64url
1004
+ ```
1005
+
1006
+ ### Environment variables
1007
+
1008
+ All keyed modes read secrets from environment variables — not from argv (argv leaks via `ps` and shell history). Missing or malformed key env vars print a clear stderr message and exit non-zero. Invalid input prints the parse error to stderr and exits non-zero.
1009
+
1010
+ | Env var | Used by | Default format |
1011
+ | ------------------------- | ----------------------------- | -------------- |
1012
+ | `IDS_KEY` | `--opaque` | `hex` |
1013
+ | `IDS_KEY_FORMAT` | `--opaque` (format override) | — |
1014
+ | `IDS_WRAPPING_KEY` | `--wrapped` | `hex` |
1015
+ | `IDS_WRAPPING_KEY_FORMAT` | `--wrapped` (format override) | — |
1016
+ | `IDS_SIGNING_KEY` | `--signed` | `hex` |
1017
+ | `IDS_SIGNING_KEY_FORMAT` | `--signed` (format override) | — |
1018
+
1019
+ Key format defaults to `hex` for all modes; override per-invocation with `--key-format hex|base64url` or set the matching `_FORMAT` env var for a session default. `--key-format` on the command line wins over the env var. Key-format env vars do not affect `keygen` output — only `--key-format` applies there.
1020
+
1021
+ ### Signed mode (`--signed`)
1022
+
1023
+ `generate --signed` and `inspect --signed` read the HMAC signing key from `IDS_SIGNING_KEY` — not from argv.
956
1024
 
957
- ### Opaque mode (`--opaque`)
1025
+ `inspect --signed` always emits a full timestamp report on stdout and carries a `verification:` line with a three-value verdict:
958
1026
 
959
- `generate --opaque` and `inspect --opaque` read the AES key from the `IDS_KEY` environment variable — not from argv (argv leaks via `ps` and shell history). Missing or malformed `IDS_KEY` prints a clear stderr message and exits non-zero.
1027
+ | Case | stdout | stderr | exit |
1028
+ | ------------- | ------------------------------------ | ---------------------------------------------- | ---- |
1029
+ | Correct key | report + `verification: ok` | — | 0 |
1030
+ | Tag mismatch | report + `verification: failed` | `verification_failed: <message>` | 1 |
1031
+ | Key missing | report + `verification: unavailable` | `missing IDS_SIGNING_KEY environment variable` | 1 |
1032
+ | Key malformed | report + `verification: unavailable` | specific key diagnostic | 1 |
960
1033
 
961
- Key format defaults to `hex`; override per-invocation with `--key-format` or set `IDS_KEY_FORMAT=hex|base64url` for a session default. `--key-format` on the command line wins over `IDS_KEY_FORMAT`.
1034
+ The timestamp is always readable (Signed Timestamp IDs carry a plaintext timestamp), so `inspect` without `--signed` also decodes it but without verification.
962
1035
 
963
- Invalid input prints the parse error to stderr and exits non-zero.
1036
+ Key format defaults to `hex`; override with `--key-format` or `IDS_SIGNING_KEY_FORMAT`. `--key-format` wins over the environment variable.
964
1037
 
965
1038
  ## Design
966
1039
 
package/dist/cli.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as isIdsError } from "./error-Cp5qYZcv.mjs";
3
- import { t as createTimestampId } from "./timestamp-BjIMQkJf.mjs";
4
- import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BQOlZ2oD.mjs";
5
- import { t as createReverseTimestampId } from "./reverse-C12D1btB.mjs";
6
- import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-CwqKTFaQ.mjs";
7
- import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-DKOsN_dq.mjs";
3
+ import { t as createTimestampId } from "./timestamp-BbZL8hwg.mjs";
4
+ import { i as importOpaqueKey, n as decodeOpaqueKey, r as encodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-BpqxV8oB.mjs";
5
+ import { t as createReverseTimestampId } from "./reverse-d5uEoIET.mjs";
6
+ import { i as importSigningKey, n as decodeSigningKey, r as encodeSigningKey, t as createSignedTimestampId } from "./signed-BnRSC03a.mjs";
7
+ import { i as importWrappingKey, n as decodeWrappingKey, r as encodeWrappingKey, t as createWrappedKeyId } from "./wrapped-BI9UXnAm.mjs";
8
8
  //#region src/cli/codec-options.ts
9
9
  function codecOpts(opts) {
10
10
  const o = { allowDuplicateBrand: true };
@@ -31,7 +31,7 @@ function formatSignedInspectOutput(result) {
31
31
  const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
32
32
  const inputLine = describeInputForm(result.input, result.canonical);
33
33
  const lines = [`brand: ${result.brand}`, `timestamp: ${result.timestamp.toISOString()} (${relative})`];
34
- if (result.verification !== void 0) lines.push(`verification: ${result.verification}`);
34
+ lines.push(`verification: ${result.verification}`);
35
35
  lines.push(`canonical: ${result.canonical}`, `input: ${inputLine}`, "");
36
36
  return lines.join("\n");
37
37
  }
@@ -95,19 +95,12 @@ function splitFlagToken(arg) {
95
95
  inlineValue: arg.slice(eq + 1)
96
96
  };
97
97
  }
98
- function splitFlags(args) {
98
+ function splitFlags(args, valueFlags) {
99
99
  const flags = /* @__PURE__ */ new Set();
100
100
  const values = /* @__PURE__ */ new Map();
101
101
  const positionals = [];
102
102
  const errors = [];
103
103
  const seenFlags = /* @__PURE__ */ new Set();
104
- const valueFlags = new Set([
105
- "--count",
106
- "-c",
107
- "--bits",
108
- "--key-format",
109
- "--kind"
110
- ]);
111
104
  const addFlag = (flag) => {
112
105
  const canonical = canonicalFlag(flag);
113
106
  if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
@@ -117,11 +110,6 @@ function splitFlags(args) {
117
110
  for (let i = 0; i < args.length; i++) {
118
111
  const raw = args[i];
119
112
  const { flag, inlineValue } = splitFlagToken(raw);
120
- if (flag === "--opaque" || flag === "--wrapped" || flag === "--reverse" || flag === "--signed") {
121
- addFlag(flag);
122
- if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
123
- continue;
124
- }
125
113
  if (valueFlags.has(flag)) {
126
114
  if (inlineValue !== void 0) {
127
115
  addFlag(flag);
@@ -141,6 +129,7 @@ function splitFlags(args) {
141
129
  }
142
130
  if (flag.startsWith("-")) {
143
131
  addFlag(flag);
132
+ if (inlineValue !== void 0) errors.push(`flag does not take a value: ${flag}`);
144
133
  continue;
145
134
  }
146
135
  positionals.push(raw);
@@ -156,7 +145,7 @@ function canonicalFlag(flag) {
156
145
  if (flag === "-c") return "--count";
157
146
  return flag;
158
147
  }
159
- const knownFlags = new Set([
148
+ const knownFlags = /* @__PURE__ */ new Set([
160
149
  "--opaque",
161
150
  "--wrapped",
162
151
  "--reverse",
@@ -240,9 +229,6 @@ const opaqueFacet = {
240
229
  decode: decodeOpaqueKey,
241
230
  import: importOpaqueKey
242
231
  };
243
- function parseKeygenFormat(values) {
244
- return parseKeyFormatFromFlag(values);
245
- }
246
232
  function parseOpaqueKeyFormat(values, opts) {
247
233
  return parseKeyFormat(values, opts, opaqueFacet);
248
234
  }
@@ -266,8 +252,14 @@ async function loadSigningKey(opts, format) {
266
252
  //#endregion
267
253
  //#region src/cli/commands/generate.ts
268
254
  function runGenerate(args, opts) {
269
- const { flags, values, positionals, errors } = splitFlags(args);
270
- const unsupported = unsupportedFlagForCommand("generate", flags, new Set([
255
+ const { flags, values, positionals, errors } = splitFlags(args, /* @__PURE__ */ new Set([
256
+ "--count",
257
+ "-c",
258
+ "--bits",
259
+ "--key-format",
260
+ "--kind"
261
+ ]));
262
+ const unsupported = unsupportedFlagForCommand("generate", flags, /* @__PURE__ */ new Set([
271
263
  "--count",
272
264
  "-c",
273
265
  "--opaque",
@@ -433,8 +425,14 @@ function usage() {
433
425
  //#endregion
434
426
  //#region src/cli/commands/inspect.ts
435
427
  function runInspect(args, opts) {
436
- const { flags, values, positionals, errors } = splitFlags(args);
437
- const unsupported = unsupportedFlagForCommand("inspect", flags, new Set([
428
+ const { flags, values, positionals, errors } = splitFlags(args, /* @__PURE__ */ new Set([
429
+ "--count",
430
+ "-c",
431
+ "--bits",
432
+ "--key-format",
433
+ "--kind"
434
+ ]));
435
+ const unsupported = unsupportedFlagForCommand("inspect", flags, /* @__PURE__ */ new Set([
438
436
  "--opaque",
439
437
  "--wrapped",
440
438
  "--reverse",
@@ -663,18 +661,16 @@ async function runSignedInspect(brand, input, format, opts) {
663
661
  const canonical = validation.value;
664
662
  const timestamp = structCodec.extractTimestamp(canonical);
665
663
  const nowMs = (opts.now ?? Date.now)();
666
- if ((opts.env ?? process.env).IDS_SIGNING_KEY === void 0) {
664
+ const keyResult = await loadSigningKey(opts, format);
665
+ if (typeof keyResult === "string") {
667
666
  opts.stdout(formatSignedInspectOutput({
668
667
  brand,
669
668
  timestamp,
670
669
  canonical,
671
670
  input,
672
- nowMs
671
+ nowMs,
672
+ verification: "unavailable"
673
673
  }));
674
- return 0;
675
- }
676
- const keyResult = await loadSigningKey(opts, format);
677
- if (typeof keyResult === "string") {
678
674
  opts.stderr(keyResult + "\n");
679
675
  return 1;
680
676
  }
@@ -698,6 +694,7 @@ async function runSignedInspect(brand, input, format, opts) {
698
694
  nowMs,
699
695
  verification: "failed"
700
696
  }));
697
+ opts.stderr("verification_failed: verification failed\n");
701
698
  return 1;
702
699
  }
703
700
  opts.stdout(formatSignedInspectOutput({
@@ -711,15 +708,123 @@ async function runSignedInspect(brand, input, format, opts) {
711
708
  return 0;
712
709
  }
713
710
  //#endregion
711
+ //#region src/cli/variants.ts
712
+ const opaqueVariant = {
713
+ flag: "--opaque",
714
+ key: {
715
+ envVar: "IDS_KEY",
716
+ formatEnvVar: "IDS_KEY_FORMAT",
717
+ encode: encodeOpaqueKey,
718
+ decode: decodeOpaqueKey,
719
+ import: importOpaqueKey
720
+ },
721
+ inspectMode: "keyed-readable",
722
+ construct(brand, opts, key) {
723
+ try {
724
+ return createOpaqueTimestampId(brand, {
725
+ key,
726
+ ...codecOpts(opts)
727
+ });
728
+ } catch (err) {
729
+ return formatCliError(err);
730
+ }
731
+ }
732
+ };
733
+ const reverseVariant = {
734
+ flag: "--reverse",
735
+ inspectMode: "readable",
736
+ construct(brand, opts) {
737
+ try {
738
+ return createReverseTimestampId(brand, codecOpts(opts));
739
+ } catch (err) {
740
+ return formatCliError(err);
741
+ }
742
+ }
743
+ };
744
+ const wrappedVariant = {
745
+ flag: "--wrapped",
746
+ key: {
747
+ envVar: "IDS_WRAPPING_KEY",
748
+ formatEnvVar: "IDS_WRAPPING_KEY_FORMAT",
749
+ encode: encodeWrappingKey,
750
+ decode: decodeWrappingKey,
751
+ import: importWrappingKey
752
+ },
753
+ inspectMode: "unwrap",
754
+ extraFlags: ["--kind"],
755
+ construct(brand, _opts, key, values) {
756
+ const kind = parseKind(values ?? /* @__PURE__ */ new Map());
757
+ if (kind === void 0) return "--kind is required with --wrapped";
758
+ if (isKindError(kind)) return kind;
759
+ try {
760
+ return createWrappedKeyId(brand, {
761
+ kind,
762
+ keys: [key],
763
+ allowDuplicateBrand: true
764
+ });
765
+ } catch (err) {
766
+ return formatCliError(err);
767
+ }
768
+ }
769
+ };
770
+ const signedVariant = {
771
+ flag: "--signed",
772
+ key: {
773
+ envVar: "IDS_SIGNING_KEY",
774
+ formatEnvVar: "IDS_SIGNING_KEY_FORMAT",
775
+ encode: encodeSigningKey,
776
+ decode: decodeSigningKey,
777
+ import: importSigningKey
778
+ },
779
+ inspectMode: "verify",
780
+ construct(brand, opts, key) {
781
+ try {
782
+ return createSignedTimestampId(brand, {
783
+ keys: [key],
784
+ ...codecOpts(opts)
785
+ });
786
+ } catch (err) {
787
+ return formatCliError(err);
788
+ }
789
+ }
790
+ };
791
+ const conflictPriorityOrder = [
792
+ signedVariant,
793
+ reverseVariant,
794
+ wrappedVariant,
795
+ opaqueVariant
796
+ ];
797
+ const keygenPolicy = {
798
+ default: opaqueVariant,
799
+ selectable: [wrappedVariant, signedVariant],
800
+ intrinsicFlags: ["--bits"]
801
+ };
802
+ //#endregion
803
+ //#region src/cli/dispatch.ts
804
+ function deriveAllowedFlags(policy) {
805
+ const flags = new Set(policy.intrinsicFlags);
806
+ let hasKeyed = policy.default.key !== void 0;
807
+ for (const v of policy.selectable) {
808
+ if (v.flag !== void 0) flags.add(v.flag);
809
+ if (v.key !== void 0) hasKeyed = true;
810
+ if (v.extraFlags !== void 0) for (const f of v.extraFlags) flags.add(f);
811
+ }
812
+ if (hasKeyed) flags.add("--key-format");
813
+ return flags;
814
+ }
815
+ function resolveVariant(policy, flags) {
816
+ const selected = conflictPriorityOrder.filter((v) => policy.selectable.includes(v) && v.flag !== void 0 && flags.has(v.flag));
817
+ if (selected.length === 0) return policy.default;
818
+ if (selected.length === 1) return selected[0];
819
+ return `cannot use ${selected[0].flag} and ${selected[1].flag} together`;
820
+ }
821
+ //#endregion
714
822
  //#region src/cli/commands/keygen.ts
715
823
  function runKeygen(args, opts) {
716
- const { flags, values, positionals, errors } = splitFlags(args);
717
- const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([
718
- "--signed",
719
- "--wrapped",
720
- "--bits",
721
- "--key-format"
722
- ]));
824
+ const allowedFlags = deriveAllowedFlags(keygenPolicy);
825
+ const variantExtraFlags = new Set(keygenPolicy.selectable.flatMap((v) => v.extraFlags ?? []));
826
+ const { flags, values, positionals, errors } = splitFlags(args, allowedFlags);
827
+ const unsupported = unsupportedFlagForCommand("keygen", flags, new Set([...allowedFlags].filter((f) => !variantExtraFlags.has(f))));
723
828
  if (unsupported !== void 0) {
724
829
  opts.stderr(unsupported + "\n");
725
830
  return Promise.resolve(1);
@@ -733,10 +838,9 @@ function runKeygen(args, opts) {
733
838
  opts.stderr(`unexpected argument: ${extra}\n`);
734
839
  return Promise.resolve(1);
735
840
  }
736
- const signed = flags.has("--signed");
737
- const wrapped = flags.has("--wrapped");
738
- if (signed && wrapped) {
739
- opts.stderr("cannot use --signed and --wrapped together\n");
841
+ const variant = resolveVariant(keygenPolicy, flags);
842
+ if (typeof variant === "string") {
843
+ opts.stderr(variant + "\n");
740
844
  return Promise.resolve(1);
741
845
  }
742
846
  const bits = parseBits(values);
@@ -744,18 +848,19 @@ function runKeygen(args, opts) {
744
848
  opts.stderr(bits + "\n");
745
849
  return Promise.resolve(1);
746
850
  }
747
- const format = parseKeygenFormat(values);
851
+ const format = parseKeyFormatFromFlag(values);
748
852
  if (isKeyFormatError(format)) {
749
853
  opts.stderr(format + "\n");
750
854
  return Promise.resolve(1);
751
855
  }
856
+ /* v8 ignore next 4 -- defensive guard; all keygenPolicy variants have key defined */
857
+ if (variant.key === void 0) {
858
+ opts.stderr("internal: keygen policy variant has no key facet\n");
859
+ return Promise.resolve(1);
860
+ }
752
861
  const bytes = new Uint8Array(bits / 8);
753
862
  crypto.getRandomValues(bytes);
754
- let encoded;
755
- if (signed) encoded = encodeSigningKey(bytes, format);
756
- else if (wrapped) encoded = encodeWrappingKey(bytes, format);
757
- else encoded = encodeOpaqueKey(bytes, format);
758
- opts.stdout(encoded + "\n");
863
+ opts.stdout(variant.key.encode(bytes, format) + "\n");
759
864
  return Promise.resolve(0);
760
865
  }
761
866
  //#endregion