Installation
This page walks you through adding Ketoy `0.3.4-alpha` to an Android project — either via the **`ketoy` CLI** (fastest path, recommended for new projects) or by editing Gradle files yourself.
Requirements
| Tool | Version |
|---|---|
| Android Gradle Plugin | 8.13.x |
| Kotlin | 2.0.21 |
| JDK | 17 |
| Jetpack Compose BOM | 2024.10.00 |
minSdk | 26 |
compileSdk | 35 or higher |
Ketoy 0.3.x's compiler plugin is pinned to this specific Kotlin / AGP / Compose combination. (ADR-0004 will ship Ketoy's own embedded Kotlin compiler so the version pin can be lifted — until then, match the table above.)
Path 1 — ketoy CLI (recommended)
The CLI scaffolds everything for you with diff-and-confirm on every file
edit. It's an AI-powered agent, but the init command runs deterministic
template edits — no LLM in the loop.
Install
npm install -g ketoy-dev # binary is `ketoy`; npm package is `ketoy-dev`
ketoy versionRequires Node.js ≥ 20.
Authenticate (only needed for chat / migrate / doctor)
ketoy auth anthropic # paste your API key when prompted
# or: openai | google | mistral | groq | xai | openrouter | ollamaCredentials are stored at ~/.ketoy-cli/config.json with mode 0600.
The CLI refuses to print them back.
Initialize a project
From inside an Android project root (the folder with settings.gradle.kts
and app/build.gradle.kts):
ketoy initThe CLI plans every edit, shows a unified diff, asks for confirmation, then:
- Inserts
id("dev.ketoy.compiler") version "0.3.4-alpha"into your app module's plugins block. - Appends the Ketoy dependencies (BOM + runtime + capabilities + adapters).
- Appends a
ketoy { exportFromAppModule = true; bundleId = "main"; bundleVariant = "release"; … }extension block. - Creates
MyApplication.kt(or merges into your existingApplicationclass) with the runtime bootstrap. - Wraps
MainActivity'ssetContent { … }body with aKetoyScreen { /* native fallback */ }(you can opt out with--no-install-screen). - Creates
HelloKetoyScreen.kt— a starter@KetoyEntryPoint @KetoyComposable. - Creates
app/ketoy-capabilities.json(empty — fill in as you add custom capabilities). - Appends
**/keys/*-private.keyto.gitignore.
Then:
./gradlew :app:assembleDebugYou've got a real .ktx bundle running inside your APK.
CLI cheat sheet
ketoy init [--install-screen | --no-install-screen] [--hilt | --no-hilt]
ketoy chat # interactive AI agent
ketoy migrate <file> # AI-driven Compose → KBC port
ketoy doctor [task] # diagnose Gradle errors
ketoy build [--variant bundle|debug|release]
ketoy analyze <path.ktx> [--manifest] [--strings] [--json]
ketoy config list | get | set
ketoy auth [provider] [--remove] [--list]
ketoy versionPath 2 — Manual setup
If you'd rather wire Ketoy by hand, here's the full setup.
1. Top-level settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}2. App module build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("dev.ketoy.compiler") version "0.3.4-alpha" // ← Ketoy plugin
id("kotlin-kapt") // if using Hilt
id("dagger.hilt.android.plugin") // if using Hilt
}
android {
namespace = "com.example.myapp"
compileSdk = 35
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 26
targetSdk = 35
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
buildFeatures { compose = true }
}
dependencies {
val ketoyBom = platform("dev.ketoy.vm:ketoy-bom:0.3.4-alpha")
implementation(ketoyBom)
implementation("dev.ketoy.vm:ketoy-runtime")
implementation("dev.ketoy.vm:ketoy-annotations")
implementation("dev.ketoy.vm:ketoy-capabilities-core")
implementation("dev.ketoy.vm:ketoy-capabilities-navigation")
implementation("dev.ketoy.vm:ketoy-adapters-material3")
// Optional — Hilt integration
implementation("dev.ketoy.vm:ketoy-hilt")
// Standard Compose deps
implementation(platform("androidx.compose:compose-bom:2024.10.00"))
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.activity:activity-compose:1.9.3")
// Test
testImplementation("dev.ketoy.vm:ketoy-test")
}
// Ketoy compiler-plugin configuration
ketoy {
exportFromAppModule.set(true) // emit .ktx into THIS app module
bundleId.set("main") // produces main.ktx
bundleVariant.set("release") // attach plugin to release variant
capabilityRegistryFile.set(
file("ketoy-capabilities.json")
)
signingKeyFile.set(
file("keys/main-private.key") // 32-byte Ed25519 seed
)
minAppVersion.set(0) // gates bundle activation per app version
}3. Generate an Ed25519 key pair
mkdir -p app/keys app/src/main/assets/ketoy/keys
# Generate private key (32-byte raw seed)
openssl genpkey -algorithm Ed25519 -outform DER -out /tmp/ed25519.der
tail -c 32 /tmp/ed25519.der > app/keys/main-private.key
# Derive public key (32 bytes, the trust anchor shipped in the APK)
openssl pkey -in /tmp/ed25519.der -inform DER -pubout -outform DER \
| tail -c 32 > app/src/main/assets/ketoy/keys/main-public.keyThen add to .gitignore:
**/keys/*-private.key4. app/ketoy-capabilities.json
Start empty — populate as you wire custom capabilities (see Custom Capability):
{
"version": 1,
"allowedStdlibFqNames": [],
"capabilities": []
}5. Bootstrap the runtime
Create MyApplication.kt:
package com.example.myapp
import android.app.Application
import dev.ketoy.adapters.material3.registerGeneratedAdapters
import dev.ketoy.adapters.material3.registerGeneratedConstructors
import dev.ketoy.runtime.KetoyConfig
import dev.ketoy.runtime.KetoyRuntime
import dev.ketoy.runtime.bundle.KetoyBundleLoader
import dev.ketoy.runtime.capability.CapabilityRegistry
import dev.ketoy.runtime.security.KetoyKeystore
class MyApplication : Application() {
lateinit var runtime: KetoyRuntime
private set
lateinit var bundleLoader: KetoyBundleLoader
private set
override fun onCreate() {
super.onCreate()
val publicKey = KetoyKeystore.loadFromAsset(
this, "ketoy/keys/main-public.key"
)
val capabilities = CapabilityRegistry()
// capabilities.registerCoreCapabilities(this) — see Networking / DataStore guides
runtime = KetoyRuntime(
capabilityRegistry = capabilities,
config = KetoyConfig(
enableSignatureVerification = true,
publicKey = publicKey,
enableJIT = true,
dexCacheDir = codeCacheDir,
)
)
runtime.adapterRegistry.registerGeneratedAdapters(runtime)
runtime.constructorRegistry.registerGeneratedConstructors()
bundleLoader = KetoyBundleLoader(runtime, this)
}
}Register it in AndroidManifest.xml:
<application android:name=".MyApplication" ...>6. Wire into MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val app = application as MyApplication
setContent {
MyAppTheme {
CompositionLocalProvider(
LocalKetoyCapabilityRegistry provides app.runtime.capabilityRegistry,
LocalKetoyBundleLoader provides app.bundleLoader,
) {
KetoyScreen(
bundleSource = KetoyBundleSource.Asset("ketoy/main.ktx"),
entryPoint = "HelloKetoyScreen",
nativeFallback = { HelloNativeFallback() }
)
}
}
}
}
}
@Composable
private fun HelloNativeFallback() {
Surface { Text("Ketoy bundle not available — using native fallback.") }
}Verify the install
./gradlew :app:ketoyBundle # produces app/src/main/assets/ketoy/main.ktx
./gradlew :app:assembleDebug # builds the APK
./gradlew :app:installDebug # installs onto a connected deviceYou should see the HelloKetoyScreen content on launch. If you instead
see the native fallback, the bundle didn't load — check adb logcat for
KetoyVM / KetoyBundleLoader log lines.