# `commands/license/license` _(no submodules)_ ## Purpose Wraps `@suaveplan/license` `LicenseManager` into the five sub-commands the operator-facing `license` bin exposes: `keygen`, `generate`, `validate`, `parse`, `revoke`, plus an `exportPublicKeyFile` helper that derives a public-keys JSON from a freshly generated keypair file. Each sub-command translates a `LicenseError` into `ServerKeysError(SERVER_KEYS_LICENSE_FAILED)` with the original error preserved as `cause`, so the top-level `runFunction` wrapper still prints a stable code on failure. Input boundary errors raise `SERVER_KEYS_INVALID_INPUT`, and missing key files raise `SERVER_KEYS_FILE_NOT_FOUND` — all four error paths are exercised in the spec-conformance tests. The on-disk JSON envelopes (`LicenseKeypairFile`, `LicensePublicKeyFile`) are deliberately minimal: hex-encoded secp256k1 secret + public, a numeric key id, and an ISO-8601 creation timestamp. Operators can edit them by hand without a tool. The revocation store is a JSON array of hex license fingerprints — created on first revoke, append-only. ## Features - `keygen(outputPath, keyId, options?)` — fresh secp256k1 keypair via `crypto-curve`'s `secp256k1.randomPrivateKey()` + `getPublicKey()`. - `generate(opts)` — `LicenseManager.generate` wrapper with hex-encoded signing-key loading. - `validate(licenseKey, opts)` — public-keys JSON loader, supports either an object or an array layout. - `parse(licenseKey, options?)` — synchronous, no signature check. - `revoke(licenseKey, opts)` — appends to a JSON revocation-store file; creates the file if absent. - `exportPublicKeyFile(keypairFile, outputPath)` — derive the public- keys file from a freshly generated keypair (used by the bin's `--export-pub ` flag and by tests). ## Usage: Basic ```ts import { keygen, generate, validate, parse, revoke, exportPublicKeyFile, } from "@suaveplan/server-keys/commands/license/license"; import { PackageTier } from "@suaveplan/license"; const kp = await keygen("/tmp/lic/keys.json", 1); await exportPublicKeyFile(kp, "/tmp/lic/public-keys.json"); const lic = await generate({ signingKeyPath: "/tmp/lic/keys.json", productId: 42, tier: PackageTier.PRO, expirationDays: 365, }); const valid = await validate(lic.key, { publicKeysPath: "/tmp/lic/public-keys.json", }); expect(valid.success).toBe(true); const parsed = parse(lic.key); expect(parsed.payload.productId).toBe(42); await revoke(lic.key, { revocationStorePath: "/tmp/lic/revocations.json" }); const after = await validate(lic.key, { publicKeysPath: "/tmp/lic/public-keys.json", }); // `revoke` modifies the in-memory store only inside the same Manager; // the file-backed store is observed by the next caller that constructs // a Manager with that store path (see bin/license.ts for the wiring). ``` ## Usage: Advanced Flag a license as a TRIAL with a feature mask, then validate against a fixed reference date for deterministic expiration windows: ```ts import { generate, validate } from "@suaveplan/server-keys/commands/license/license"; import { LicenseFlags, PackageTier } from "@suaveplan/license"; const lic = await generate({ signingKeyPath: "/tmp/lic/keys.json", productId: 1, tier: PackageTier.BASIC, expirationDays: 30, flags: LicenseFlags.TRIAL, featureMask: 0b1011, // features 0, 1, 3 useAbsoluteTime: false, useFullSignature: true, }); const checked = await validate(lic.key, { publicKeysPath: "/tmp/lic/public-keys.json", referenceDate: new Date("2026-01-01T00:00:00Z"), }); ``` ## API Reference - `keygen(outputPath, keyId, options?) => Promise` - `generate(opts: GenerateLicenseOptions) => Promise` - `validate(licenseKey, opts: ValidateLicenseOptions) => Promise` - `parse(licenseKey, options?: LicenseCommandOptions) => LicenseParsedResult` - `revoke(licenseKey, opts: RevokeLicenseOptions) => Promise` - `exportPublicKeyFile(keypairFile, outputPath) => Promise` All option bags accept `{ logger?, signal? }` for injection. ## Implementation Notes `generate` constructs a `LicenseManager` per call (instead of caching one across sub-commands) because each call's signing key may be different — the bin invocation is the granularity that owns a key. The revocation store loader tolerates `ENOENT` (no file yet) but treats any other I/O error as a `LICENSE_FAILED` failure so operators don't silently revoke into a misconfigured path. The optional `signal` parameter is checked at the boundary of each sub-command and at the start of generate/validate so an aborted run never burns CPU on the LicenseManager's hash + signature work.