fix: four real bugs found by end-to-end manual testing

Ran the full CLI surface against a freshly generated throwaway seed
(profile -> agent unlock -> all 8 entry kinds -> reveal/sinks -> tokens ->
audit -> backup -> relay sync -> seed-only recovery). Four defects that no
unit test had caught:

- clipboard sink hung the CLI forever. xclip/wl-copy fork a resident
  process to own the X/Wayland selection; it inherited our stdio, so any
  reader of the CLI's stdout blocked indefinitely, and waiting for process
  exit never returned. Now detached with ignored stdio, success declared
  once stdin is flushed.
- 'vault import' read the mnemonic straight from the env, so it failed
  whenever the vault was unlocked through the agent -- the normal path.
- 'vault import' never wrote anything; it only printed a summary. It now
  restores into the active vault, refuses to overwrite a non-empty vault
  without --yes, and keeps the old behavior behind --inspect.
- A corrupt/foreign vault file surfaced the raw codec error "invalid token
  encoding". It now names the actual likely causes.

Also documented the variadic-option limitation that a trailing flag is
lost (commander stops collecting at the next dash-prefixed token): a
quoted command spec now splits on whitespace, no shell involved.

Regression tests for all four. 151 core + 44 CLI tests green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-08-14 15:56:34 -04:00
1 parent 274bc0e1b8
commit dcb8fc9219
6 files changed
+195 -24

No files matched your search

+13 -1
View File
@@ -24,7 +24,19 @@ export async function openVault(path: string, mnemonic: string): Promise<OpenedV
const blob = new Uint8Array(await readFile(path));
const { ciphertext } = parseEncryptedFile(blob);
const key = deriveIndexKeyBytes(mnemonic);
const plaintext = await decryptPayload(key, ciphertext);
let plaintext: Uint8Array;
try {
plaintext = await decryptPayload(key, ciphertext);
} catch (cause) {
// Raw decrypt errors ("invalid token encoding") tell the user nothing
// about which of the two likely causes they are looking at.
throw new Error(
`could not decrypt vault at ${path}: the file is corrupt, is not a ` +
`SeedPass vault, or belongs to a different seed ` +
`(restore with 'nostr restore' or 'vault import')`,
{ cause },
);
}
const index = parseVaultIndex(JSON.parse(new TextDecoder().decode(plaintext)));
return { index, mnemonic, path };
}