- package.json homepage: github.com/avi/Keynctr -> git.atitlan.io/avi/Keynctr
- README: title 'Nostr Feed Manager' -> 'Keynctr', resolve all
[YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO> placeholders with the real
Forgejo URL, fix clone dir and packaged-binary name (nost-feed-manager
-> keynectr), data dir default ~/.local/share/keynectr, refresh test
counts (cargo 225+6 e2e, npm 139/19 files) and project layout
(audit/bunker/feed/updates modules, signer/ dir, Feed+SignerMode screens)
- PRODUCT.md: data dir corrected with migration note
Keynctr is the NIP-46 client; Amber is the scanner. Amber hands out no
link — it scans one — so the signer screen now mints a pairing token:
- start_pairing(): ephemeral key + secret, nostrconnect:// token via
NostrConnectUri::client_with_secret, status().pairing_uri for the GUI
- run_pairing_task(): listens for the signer's connect request, echoes
the secret (anti-spoofing), persists the connection row + secret,
then adopts identity via get_public_key and hands to the demux loop
- pairing subscription is closed at handoff so the demux loop owns the
conversation (relay could otherwise deliver signer replies under the
stale pairing sub id where nobody routes them)
- IPC: nip46_pair_start; status carries pairing_uri
- SignerModeScreen: 'Show QR' button, QR render (qrcode) of the token,
copy-link fallback, cancel; paste-link flow unchanged
- e2e: fake QR scanner consumes the real pairing token end-to-end
(scan -> secret echo -> identity -> sign -> vault persistence)
Introduce three coexisting signing modes:
- Embedded (INTERNAL): vault-held nsec, decrypted in Rust, signs locally.
- Nip46Client (EXTERNAL): Keynctr is the NIP-46 CLIENT; the key never
touches this machine.
- Nip46Bunker: legacy inverted mode (Keynctr as signer serving others).
Data model:
- StoredProfile gains signer_mode (serde-defaults to Embedded for legacy
profiles); SignerMode moves from app.rs to vault.rs to break a circular
dependency; app.rs re-exports it.
- Vault gains nip46_connections (profile-owned) and bumps VAULT_VERSION to
3; migrate_vault_signer_modes() normalises on load (idempotent).
- Nip46Connection gains profile_npub ownership, parsed permissions,
expires_at, and revoked_at.
Signer abstraction (src/signer):
- Signer trait gains pubkey_for() identity validation, a Signing enum
(Local vs External) that re-verifies the returned event, and a permission
surface (permissions/can_*/is_connection_valid) with safe defaults.
- permissions.rs: NIP-46 per-connection permission model (parse, validate,
deny-by-default, no-broadening checks) with 52 unit tests.
- Nip46ClientSigner parses perms from nostrconnect:// URIs, enforces
permissions on every gated request, persists/revokes connections in the
vault, and audits permission denials via the app's audit log.
- App gains audit_log and a nip46_bunker_signer handle; default mode is
Nip46Client (most secure).
Frontend: SignerModeScreen redesigned for the three modes with a
nostr-tools-based SignerManager client, new IPC allowlist entries, and
signer-mode styling.
Verified: cargo test --release 186 passed; clippy/fmt clean; frontend tsc
clean, vitest 110 passed.
Wire the existing profiles::import_profile through the JSON-lines IPC
protocol so the GUI can add an existing account: ImportProfile request
and handler in src/ipc.rs, import_profile added to the Electron method
allowlist, api/AppProvider importProfile, and the Add existing account
buttons in ProfilesScreen.
Also add the active signing identity card on Home and fix the HomeScreen
tests whose text queries now match the identity card as well as the
profile row.
- Crate/binary: nostr-manager-backend -> keynectr
- Data directory: nost-feed-manager -> keynectr, migrated automatically
on first data_dir() call (existing vaults, settings and backups move)
- Electron extraResources/spawn path, executableName, productName,
window title and CLI usage strings updated to match
- Deliberately unchanged: crypto.rs KDF verifier string, so previously
encrypted vault backups remain decryptable
Verified live: existing vault with two profiles migrated to
~/.local/share/keynectr and loads correctly.
- Rust library (nostr-manager-backend) with CLI and JSON-lines IPC serve mode:
profiles, publishing with per-relay reports, relays, settings, vault storage
and legacy-vault migration
- Electron + React + TypeScript desktop GUI using the same backend over stdio IPC
- Vitest suite with a fake backend speaking the real protocol
- electron-builder linux packaging; README with build and usage instructions