# `commands/keys/keys` _(no submodules)_ ## Purpose Non-chain key-lifecycle commands for `@suaveplan/server-keys`. These functions are the library surface every key/view bin in the package calls into: root keychain creation, server keychain derivation, server keychain rotation, and the four read-only inspectors (`view-keys`, `view-vault`, `view-public-key-file`, `view-chain-public`). The sibling `keys-chain.ts` covers the hierarchical chain variants and `verify-key-chain`; `keys-io.ts` holds the shared filesystem and decryption helpers. Every command writes through `@suaveplan/storage-file`'s `NodeFsStorage`, so vault and public-key persistence flows through the same gated I/O as the rest of the storage stack — no direct `fs.writeFile` calls live in this module. The crypto-vault orchestration (`createRootKeys`, `createServerKeys`, `rollServerKeys`, `openVault`, `viewPublicKeys`) is the format owner. ## Features - Filename convention mirroring upstream: `root.vault`/`root.pub`, `server.vault`/`server.pub`, `server.v.vault`/`server.v.pub`. - Structured `ServerKeysError(SERVER_KEYS_*)` failures everywhere — callers always see a typed code on `error.code`. - Injectable `KeysLogger` (defaults to silent) — bins wire a console shim; tests can capture every printed line as a string array. - Cooperative abort via `options.signal`; each command checks before expensive crypto or filesystem work. ## Usage: Basic ```ts import { createRootKeysCommand, createServerKeysCommand, } from "@suaveplan/server-keys/commands/keys/keys"; await createRootKeysCommand("hunter2hunter2", { output: "/tmp/keys" }); await createServerKeysCommand( "/tmp/keys/root.vault", "hunter2hunter2", "newserverpw", { output: "/tmp/keys" }, ); ``` ## Usage: Advanced Drive the viewers from a higher-level orchestrator with a capturing logger: ```ts import { viewKeysCommand, viewVaultCommand, viewPublicKeyFileCommand, } from "@suaveplan/server-keys/commands/keys/keys"; const lines: string[] = []; const logger = { info: (m: string) => lines.push(`I:${m}`), error: (m: string) => lines.push(`E:${m}`), }; const stat = await viewVaultCommand("/tmp/keys/root.vault", { logger }); const keychain = await viewKeysCommand( "/tmp/keys/root.vault", "hunter2hunter2", { logger }, ); const pubChain = await viewPublicKeyFileCommand("/tmp/keys/root.pub", { logger, }); expect(stat.bytes).toBeGreaterThan(0); expect(Object.keys(pubChain).length).toBeGreaterThan(0); // Rotate with an abort guard: const ac = new AbortController(); setTimeout(() => ac.abort(), 100); await rollServerKeysCommand( "/tmp/keys/root.vault", "hunter2hunter2", "newserverpw", 1, { output: "/tmp/keys", signal: ac.signal }, ); ``` ## API Reference - `createRootKeysCommand(password, options?) => Promise` - `createServerKeysCommand(rootVaultPath, rootPassword, serverPassword, options?) => Promise` - `rollServerKeysCommand(rootVaultPath, rootPassword, serverPassword, currentVersion, options?) => Promise` - `viewKeysCommand(vaultPath, password, options?) => Promise` - `viewVaultCommand(vaultPath, options?) => Promise<{ path, bytes }>` - `viewPublicKeyFileCommand(publicKeyPath, options?) => Promise` - `viewChainPublicCommand(publicKeyPath, signKeyPath?, options?) => Promise` All options are `KeyCommandOptions = { output?, logger?, signal? }`. ## Implementation Notes `viewKeysCommand` returns the decoded keychain instead of `void` so test harnesses can assert on the result without re-decrypting; the information is already in memory by the time the function logs, and exposing it avoids a redundant decrypt in callers. Slot iteration goes through `Object.entries(keychain as Record)` because `Keychain` is `Readonly>` where `PurposeTag` is a branded `string & { __brand }` — TS cannot iterate branded keys directly, so a typed-string view is the correct narrowing. The roll-server output uses `server.v` (dot-separated) instead of upstream's `server-v` to keep version segments visually distinct in operator listings.