Files
amethyst/SKILL.md
T
Claude 9afa8127da refactor!: take quartz from vitorpamplona/quartz
Quartz moves to its own repo, the way the crypto primitives moved to
crypto-kmp. Every module depends on com.vitorpamplona.quartz:quartz
2.0.0 (libs.quartz) instead of :quartz, and -PquartzDir=../quartz
builds a local checkout instead of the artifact, for every module and
for quic's own quartz dependency (which replaces the root build's
substitution of the published quartz with :quartz).

What moved with quartz: its CI jobs (lint tasks, test-quartz-linux-native,
test-quartz-ios), the Maven Central publish step of the release
workflow, jitpack.yml, tools/unicode-nfkc, the nostr-expert and
event-store-semantics skills, the agent-skill check, and the README's
library usage docs.

What stays here:
- The four quartz client tests that need a real Ktor socket relay
  (TrustNetworkSync*, GiantReqStreamTest, BulkDownloadBenchmark) move to
  geode's tests; geode forwards -PprodRelayBench for the latter. quartz's
  other relay-client tests now use a test relay of its own, which ends
  quartz's test dependency on geode.
- The benchmark module's resources (previously read from quartz's
  androidDeviceTest resources) move into benchmark/; relayBench --real
  reads the corpus from there.
- NoteComposeKindCoverageTest reads EventFactory.kt from quartz's JVM
  sources jar, or from the -PquartzDir checkout, passed by commonsUI's
  build.
- commons and commonsUI declare crypto-kmp (and commons mls-kmp), which
  they import directly. Through quartz's api alone, a -PcryptoKmpDir
  checkout never reached the common metadata compile.
- The catalog drops what only quartz used (kchesslib, negentropy,
  jackson-databind, sqlite-bundled, secp256k1-kmp common, compose
  runtime-annotation, the maven-publish plugin).

agent_config_check checks a skill's quartz/... paths against a ../quartz
checkout when there is one and skips them otherwise. AGENTS.md, the
skills, CONTRIBUTING, BUILDING, RELEASE_OPS, PLANS and the READMEs point
at the new repo.

Needs quartz 2.0.0 (and crypto-kmp 1.0.0) on Maven Central before CI can
resolve it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEkJaDWYrcTbGvT8JLQCrG
2026-10-21 16:32:25 +00:00

6.1 KiB

Amethyst Builder Skill

Build customized Amethyst Nostr clients for Android. Fork, rebrand, customize, and distribute your own version.

Overview

Amethyst is the premier Nostr client for Android. This skill enables you to:

  • Create rebranded versions (custom name, package, icons)
  • Build F-Droid-compatible releases (no Google Play dependencies)
  • Add or modify features
  • Sign and distribute APKs

Prerequisites

Required Tools

  1. Java 21 (via SDKMAN)

    curl -s "https://get.sdkman.io" | bash
    source "$HOME/.sdkman/bin/sdkman-init.sh"
    sdk install java 21.0.5-tem
    sdk use java 21.0.5-tem
    
  2. Android SDK

  3. Git for cloning the repository

Environment Setup

export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

Build Workflow

1. Clone the Repository

mkdir -p ~/projects/your-app-name
cd ~/projects/your-app-name
git clone https://github.com/vitorpamplona/amethyst.git .

2. Create Signing Key

keytool -genkeypair -v \
  -keystore ./release-key.jks \
  -alias your-app \
  -keyalg RSA -keysize 2048 \
  -validity 10000

Create keystore.properties in project root:

storeFile=release-key.jks
storePassword=your-password
keyAlias=your-app
keyPassword=your-password

3. Configure Signing

Add to amethyst/build.gradle.kts inside the android {} block:

val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
    keystorePropertiesFile.inputStream().use { keystoreProperties.load(it) }
}

signingConfigs {
    create("release") {
        if (keystorePropertiesFile.exists()) {
            storeFile = rootProject.file(keystoreProperties["storeFile"] as String)
            storePassword = keystoreProperties["storePassword"] as String
            keyAlias = keystoreProperties["keyAlias"] as String
            keyPassword = keystoreProperties["keyPassword"] as String
        }
    }
}

This needs import java.util.Properties at the top of the file.

Update the release buildType to use the signing config:

buildTypes {
    getByName("release") {
        signingConfig = signingConfigs.getByName("release")
        // ... existing config
    }
}

Verify with ./gradlew :amethyst:signingReport — the release variants should report your keystore rather than ~/.android/debug.keystore.

4. Disable Google Services (Required for F-Droid)

⚠️ CRITICAL: The Google Services plugin fails when you change the package name. For F-Droid builds, disable it.

Edit amethyst/build.gradle.kts, comment out the plugin:

plugins {
    alias(libs.plugins.androidApplication)
    // alias(libs.plugins.googleServices)  // DISABLED for F-Droid
    alias(libs.plugins.jetbrainsComposeCompiler)
    alias(libs.plugins.serialization)
    alias(libs.plugins.googleKsp)
}

5. Build

source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk use java 21.0.5-tem
./gradlew assembleFdroidRelease

Build time: ~9 minutes first build, faster on subsequent builds.

6. Locate APKs

Output directory: amethyst/build/outputs/apk/fdroid/release/

Files generated:

  • amethyst-fdroid-arm64-v8a-release.apk - ARM64 (most phones)
  • amethyst-fdroid-universal-release.apk - All architectures (larger)

Customizations

Change App Name

Edit amethyst/src/main/res/values/strings.xml:

<string name="app_name" translatable="false">YourAppName</string>
<string name="app_name_debug" translatable="false">YourAppName Debug</string>

Change Package ID

Edit amethyst/build.gradle.kts:

android {
    defaultConfig {
        applicationId = "com.yourcompany.yourapp"
    }
}

Change Project Name

Edit settings.gradle.kts:

rootProject.name = "YourAppName"

Change App Icon

Replace icon files in:

  • amethyst/src/main/res/mipmap-*/ic_launcher.webp
  • amethyst/src/main/res/mipmap-*/ic_launcher_round.webp

Add Client Tag to Posts

Make your app identify itself on posts with ["client", "YourAppName"].

You do not need to add the tag per event type. The client tag is applied centrally by NostrSignerWithClientTag, a signer decorator that appends the tag to everything it signs (and respects the user's "add client tag" privacy setting). Changing the name is a one-constant edit:

Edit amethyst/src/main/java/com/vitorpamplona/amethyst/model/accountsCache/AccountCacheState.kt:

const val CLIENT_TAG_NAME = "YourAppName"

That constant is passed to NostrSignerWithClientTag when the account's signer is built, so every signed event carries your name.

The tag itself lives in the Quartz library (nip89AppHandlers/clientTag/, ClientTag, TagArrayBuilderExt, NostrSignerWithClientTag) — you only need to touch it if you want the optional NIP-89 handler address / relay hint variants.

Modify Default Relays

Edit commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/defaults/Constants.kt (see also AmethystDefaults.kt and DefaultDmIndexerRelays.kt in the same folder).

Troubleshooting

google-services.json error

Disable the Google Services plugin (see step 4).

Java version error

sdk use java 21.0.5-tem
java -version  # Must show 21.x

Out of memory

Edit gradle.properties:

org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m

Clean build

./gradlew --stop
./gradlew clean
./gradlew assembleFdroidRelease

Distribution

Deploy APKs via:

  • Surge.sh: surge ./releases your-app.surge.sh
  • Zapstore: Submit to zapstore.dev
  • Direct download: Host on any web server