Files
Vitor PamplonaandClaude Opus 5.5 395af4dc9c chore(skills): consolidate 33 skills into 14, verified against the tree
The 33 descriptions (~16k chars, ~19k with paths) overran Codex's skill-list
budget (2% of the context window, or 8,000 chars when unknown), and a review
found most bodies stale: paths from before the commons/commonsUI moves, the
deleted legacy desktop app, a callback signer API that no longer exists, and
example APIs that were never in the tree.

Kept, rewritten and checked against the code (every path, class and command):
account-state (+feeds), amy-expert, android-expert, auth-signers,
compose-expert (+API style, Navigation 3 back stacks), desktop-expert
(+run/package), event-store-semantics (+NIP-50 search), find-missing-
translations, kotlin-coroutines (+structured concurrency, flow modeling),
kotlin-multiplatform (+Gradle, composable extraction), ngit-pr,
nip85-trusted-assertions, nostr-expert (+NIP lookup, generated NIP catalog),
relay-client.

Dropped: textbook Compose/Kotlin technique skills, kotlin-expert,
kotlin-types-value-class, gradle-expert, quartz-integration (external-facing;
see the Quartz docs commit), find-non-lambda-logs (audit finished; the rule is
now in AGENTS.md Kotlin Style), and the /nip, /desktop-run, /extract commands.
The skill list is now ~4k chars. Also drops .claude/core-skills-plan.md
(stale history) and repoints SearchableEvent's KDoc to event-store-semantics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:21:01 -04:00

2.4 KiB

NIP-55 Android external signer

Another Android app (Amber, …) holds the key; Amethyst asks it through Intents (foreground, with UI) or its ContentProvider (background, pre-approved). Android only.

Layout (quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/)

  • api/CommandType.kt — sign_event, nip04_encrypt/decrypt, nip44_encrypt/decrypt, get_public_key, decrypt_zap_event, derive_key, sign_psbt.
  • api/foreground/ — IntentRequestManager (id → channel, launches the intent, waits foregroundApprovalTimeout = 30 s) and intents/requests|responses|results/ per command.
  • api/background/queries/ — one ContentResolver query per command (SignQuery, Nip44EncryptQuery, LoginQuery, …).
  • api/permission/ — Permission + its serializers (the permissions requested at login).
  • client/ — NostrSignerExternal(pubKey, packageName, contentResolver) (the NostrSigner), ExternalSignerLogin (createIntent(permissions, packageName), parseResult(intent)), IActivityLauncher (registerForegroundLauncher, unregisterForegroundLauncher, newResponse, hasForegroundActivity), IsExternalSignerInstalled.kt (isExternalSignerInstalled(context), getExternalSignersInstalled(context)), handlers/BackgroundRequestHandler + ForegroundRequestHandler.
  • SignString.kt, JsonMapperNip55.kt.

Flow

  1. Login: ExternalSignerLogin.createIntent(...) → the signer app returns pubkey + package → parseResult(data) → NostrSignerExternal(pubKey, packageName, contentResolver).
  2. Each call tries the background query first; if the signer has not pre-approved that command, it falls back to a foreground intent through the registered launcher.
  3. No registered Activity → SignerExceptions.RunningOnBackgroundWithoutAutomaticPermissionException; no answer in 30 s → TimedOutException.

The Android entry point is amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/ExternalSignerButton.kt.

Gotchas

  • Without "always allow", every reaction or zap is an Activity round-trip — bad UX; ask for the permissions at login.
  • Desktop has no NIP-55; it uses NostrSignerInternal or NostrSignerRemote.
  • Tests: quartz/src/androidHostTest/.../nip55AndroidSigner/ (JVM host) and quartz/src/androidDeviceTest/.../nip55AndroidSigner/ (device/emulator only).