ketoy-dev 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +101 -0
  3. package/SECURITY.md +34 -0
  4. package/dist/ketoy.js +3165 -0
  5. package/dist/ketoy.js.map +1 -0
  6. package/package.json +78 -0
  7. package/skills/ketoy/README.md +50 -0
  8. package/skills/ketoy/SKILL.md +148 -0
  9. package/skills/ketoy/examples/capabilities-stubs.kt +60 -0
  10. package/skills/ketoy/examples/hilt-config.kt +192 -0
  11. package/skills/ketoy/examples/no-hilt-config.kt +101 -0
  12. package/skills/ketoy/examples/todo-screen.kt +156 -0
  13. package/skills/ketoy/guides/build-and-analyze.md +87 -0
  14. package/skills/ketoy/guides/diagnose-errors.md +129 -0
  15. package/skills/ketoy/guides/init-project.md +127 -0
  16. package/skills/ketoy/guides/migrate.md +190 -0
  17. package/skills/ketoy/guides/publish-deferred.md +46 -0
  18. package/skills/ketoy/guides/safe-edits.md +141 -0
  19. package/skills/ketoy/reference/architecture-cheatsheet.md +122 -0
  20. package/skills/ketoy/reference/capabilities.md +122 -0
  21. package/skills/ketoy/reference/forbidden-apis.md +149 -0
  22. package/skills/ketoy/reference/supported-composables.md +80 -0
  23. package/skills/ketoy/reference/supported-constructors.md +57 -0
  24. package/skills/ketoy/reference/supported-modifiers.md +76 -0
  25. package/skills/ketoy/templates/app-build.gradle.kts.tmpl +109 -0
  26. package/skills/ketoy/templates/ketoy-capabilities.json.tmpl +21 -0
  27. package/skills/ketoy/templates/manifest-snippet.xml.tmpl +33 -0
  28. package/templates/HelloKetoyScreen.kt.tmpl +51 -0
  29. package/templates/MainActivity.kt.tmpl +53 -0
  30. package/templates/MyApplication.kt.tmpl +88 -0
  31. package/templates/ketoy-capabilities.json.tmpl +5 -0
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "ketoy-dev",
3
+ "version": "0.1.0",
4
+ "description": "AI-powered CLI for Ketoy — scaffold, write, migrate, diagnose, and build .ktx bundles for Android. BYO API key (Anthropic, OpenAI, Google, Mistral, Groq, xAI, OpenRouter, or local Ollama).",
5
+ "type": "module",
6
+ "bin": {
7
+ "ketoy": "./dist/ketoy.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "skills",
12
+ "templates",
13
+ "README.md",
14
+ "LICENSE",
15
+ "SECURITY.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=20.0.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "start": "node dist/ketoy.js",
25
+ "prepack": "node scripts/prepack-skills.mjs",
26
+ "postpack": "node scripts/postpack-skills.mjs",
27
+ "prepublishOnly": "npm run typecheck && npm run build"
28
+ },
29
+ "dependencies": {
30
+ "@ai-sdk/anthropic": "^1.0.0",
31
+ "@ai-sdk/google": "^1.0.0",
32
+ "@ai-sdk/groq": "^1.0.0",
33
+ "@ai-sdk/mistral": "^1.0.0",
34
+ "@ai-sdk/openai": "^1.0.0",
35
+ "@ai-sdk/xai": "^1.0.0",
36
+ "@inquirer/prompts": "^8.4.3",
37
+ "@openrouter/ai-sdk-provider": "^0.0.6",
38
+ "ai": "^4.0.0",
39
+ "commander": "^12.1.0",
40
+ "execa": "^9.4.0",
41
+ "fast-xml-parser": "^4.5.0",
42
+ "ollama-ai-provider": "^1.0.0",
43
+ "picocolors": "^1.1.0",
44
+ "tinyglobby": "^0.2.10",
45
+ "zod": "^3.23.8"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.7.0",
49
+ "tsup": "^8.3.0",
50
+ "typescript": "^5.6.0"
51
+ },
52
+ "keywords": [
53
+ "ketoy",
54
+ "ketoyvm",
55
+ "kbc",
56
+ "android",
57
+ "compose",
58
+ "jetpack-compose",
59
+ "server-driven",
60
+ "cli",
61
+ "ai-agent",
62
+ "vercel-ai-sdk"
63
+ ],
64
+ "author": "Aditya Shinde",
65
+ "license": "Apache-2.0",
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/developerchunk/ketoy-extended.git",
69
+ "directory": "ketoy-cli"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/developerchunk/ketoy-extended/issues"
73
+ },
74
+ "homepage": "https://ketoy.dev",
75
+ "publishConfig": {
76
+ "access": "public"
77
+ }
78
+ }
@@ -0,0 +1,50 @@
1
+ # Ketoy Skill
2
+
3
+ Operational skill for an AI agent helping developers adopt Ketoy.
4
+
5
+ **Entry point**: [SKILL.md](SKILL.md). Read this first.
6
+
7
+ ## Layout
8
+
9
+ ```
10
+ skills/ketoy/
11
+ ├── SKILL.md # entry — frontmatter + operating rules
12
+ ├── reference/ # ground truth for "is X supported"
13
+ │ ├── supported-composables.md
14
+ │ ├── supported-constructors.md
15
+ │ ├── supported-modifiers.md
16
+ │ ├── capabilities.md
17
+ │ ├── forbidden-apis.md
18
+ │ └── architecture-cheatsheet.md
19
+ ├── guides/ # workflow playbooks
20
+ │ ├── init-project.md
21
+ │ ├── safe-edits.md # ← surgical-edits policy (READ FIRST)
22
+ │ ├── migrate.md
23
+ │ ├── diagnose-errors.md
24
+ │ ├── build-and-analyze.md
25
+ │ └── publish-deferred.md
26
+ ├── examples/ # paste-ready Kotlin code
27
+ │ ├── todo-screen.kt
28
+ │ ├── capabilities-stubs.kt
29
+ │ ├── hilt-config.kt
30
+ │ └── no-hilt-config.kt
31
+ └── templates/ # static templates for `ketoy init`
32
+ ├── app-build.gradle.kts.tmpl
33
+ ├── ketoy-capabilities.json.tmpl
34
+ └── manifest-snippet.xml.tmpl
35
+ ```
36
+
37
+ ## When the skill applies
38
+
39
+ The agent should load this skill whenever the user's task involves:
40
+
41
+ - Adding Ketoy to an Android project
42
+ - Writing `@KetoyComposable` / `@KetoyEntryPoint` / `@KetoyViewModel` / `@KetoyCapabilityStub`
43
+ - Migrating an existing Compose screen to KBC
44
+ - Diagnosing `KetoyBC:` errors, `.ktx` runtime errors
45
+ - Building, analyzing, or shipping `.ktx` bundles
46
+ - Working with `dev.ketoy.vm:*` Maven coordinates
47
+
48
+ ## Version this skill targets
49
+
50
+ Ketoy **0.3.4-alpha** on Maven Central. Coordinates use group `dev.ketoy.vm`. Bundle wire format v2 with `minAppVersion` trailing field. Closure conversion via `KBCValue.ClosureRef` shipped. Phase 11E inline-source app bundle mode is the standard adoption path (one `.ktx` per app module, `bundleId = "main"`).
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: ketoy
3
+ description: Operate the Ketoy CLI agent. Use for any task involving Ketoy/KetoyBC — scaffolding a Ketoy-enabled Android project, writing or migrating `@KetoyComposable` screens, defining `@KetoyCapabilityStub` declarations, wiring Hilt/non-Hilt configuration, diagnosing compiler-plugin errors, building `.ktx` bundles, inspecting bundle contents. Triggers on: "ketoy", "KBC", ".ktx", "Ketoy", "server-driven Compose", `@KetoyComposable`, `@KetoyCapabilityStub`, `dev.ketoy.vm:*` dependencies.
4
+ ---
5
+
6
+ # Ketoy Skill — Operating Manual
7
+
8
+ You are a Ketoy CLI agent. Your job is to help Android developers adopt Ketoy: a register-based VM that executes Compose UI from server-shipped `.ktx` bytecode bundles inside their app. The app is the OS; the server ships programs.
9
+
10
+ You run inside the `@ketoy/cli` Node.js CLI built on Vercel AI SDK — the user picked their own AI provider (Anthropic / OpenAI / Google / Mistral / Groq / xAI / OpenRouter / Ollama). The tool surface (`readFile`, `writeFile`, `editFile`, `grep`, `glob`, `bash`, `analyzeKtx`) is identical across providers. See [/KETOY_CLI.md](../../KETOY_CLI.md) at the repo root for the full CLI design.
11
+
12
+ ## Truth sources, in priority order
13
+
14
+ 1. **The user's working directory** — read their actual `build.gradle.kts`, `AndroidManifest.xml`, KBC source. Never assume.
15
+ 2. **`reference/`** in this skill — the curated catalog of what Ketoy actually supports today. Treat as ground truth for "is X supported".
16
+ 3. **`KNOWN_ISSUES.md`** and **`CLAUDE.md`** at the repo root of the Ketoy project, if present. Read them when diagnosing weird behavior.
17
+ 4. **`docs/`** at the Ketoy repo or `ketoy.dev/docs` — secondary. Some pages still describe per-screen bundles; the actual ship state is **one `.ktx` per app module** (`bundleId = "main"`).
18
+
19
+ ## Hard rules (never violate)
20
+
21
+ - **Never rewrite existing user files in full.** All edits to `build.gradle.kts`, `AndroidManifest.xml`, `MainActivity.kt`, `Application` subclasses, `settings.gradle.kts`, or any pre-existing user file MUST be surgical — single-line additions or single-block appends at well-identified anchors. Templates in `templates/` describe the *final shape*, not the *bytes to write*. See [guides/safe-edits.md](guides/safe-edits.md) for the per-file insertion strategy. Show a diff and confirm before touching anything the user is likely to have hand-tuned.
22
+ - KBC source CANNOT call:
23
+ - Reflection (`kotlin.reflect.*`, `Class.get*`, `java.lang.reflect.*`)
24
+ - File I/O (`java.io.*`, `java.nio.*`, `kotlin.io.*`)
25
+ - Direct Android APIs (`android.*`, `androidx.*` outside the registered Compose surface)
26
+ - `GlobalScope`, `runBlocking`, `CoroutineScope(...)` factory
27
+ - Every Android/system side-effect goes through a `@KetoyCapabilityStub` declared in the KBC module and registered in the host's `CapabilityRegistry`.
28
+ - Every visible Compose composable must have a generated adapter — i.e. live in `adapter-scan-roots.txt` (or app-specific scan-roots). If it's not there, it's not callable.
29
+ - KBC closures over outer-scope locals work for `val`/captured reads only (shipped via `KBCValue.ClosureRef`). Don't try clever capture patterns; keep lambdas small.
30
+
31
+ ## Workflow you offer
32
+
33
+ ### A. Bootstrap (`ketoy init`) — deterministic, no AI tokens
34
+
35
+ When the user wants to add Ketoy to a project, **prefer running deterministic scaffolding over generating files**. The CLI's `init` command makes **surgical, additive edits** to existing files (never full rewrites — see [guides/safe-edits.md](guides/safe-edits.md)) and creates new files where none exist:
36
+
37
+ 1. Plugin + dependencies to `build.gradle.kts` (root + app)
38
+ 2. `Application` class (if Hilt: `@HiltAndroidApp`; else: plain `Application`)
39
+ 3. `MainActivity` wired with `KetoyScreen` host (Hilt vs non-Hilt variants)
40
+ 4. `AndroidManifest.xml` updated `android:name` for Application
41
+ 5. `app/ketoy-capabilities.json` skeleton
42
+ 6. `app/src/main/java/<pkg>/ketoyscreens/Capabilities.kt` with standard `NAV_*` / `VM_*` stubs
43
+ 7. The `ketoy { exportFromAppModule = true; bundleId = "main"; bundleVariant = "release" }` block
44
+ 8. `.gitignore` entry: `**/keys/*-private.key`
45
+
46
+ Detect Hilt by grepping for `dagger.hilt.android` in any existing `build.gradle.kts` or `@HiltAndroidApp`. **Non-Hilt setup is the default first pass**; if the user later adds Hilt, run `ketoy init --hilt` to upgrade.
47
+
48
+ See `templates/` for the canonical content.
49
+
50
+ ### B. Write a new `@KetoyComposable` screen
51
+
52
+ Use [examples/todo-screen.kt](examples/todo-screen.kt) as the canonical pattern. Rules:
53
+
54
+ - File goes under `app/src/main/.../ketoyscreens/`
55
+ - Annotate with `@KetoyEntryPoint @KetoyComposable @Composable` if it's a screen-root.
56
+ - Only call composables listed in [reference/supported-composables.md](reference/supported-composables.md).
57
+ - Use `Modifier` chains only with the named-arg forms documented in [reference/supported-modifiers.md](reference/supported-modifiers.md).
58
+ - For state, dispatch capabilities — `vmGetState(key)`, `vmSetState(key, value)`, `observeTodos()`, etc. — declared as `@KetoyCapabilityStub` in `ketoyscreens/Capabilities.kt`.
59
+
60
+ ### C. Migrate an existing Compose screen to KBC
61
+
62
+ Per-file migration only. Never offer "migrate the whole project." Workflow:
63
+
64
+ 1. **Audit** the source: list every composable, every Android/Java API call, every state holder, every `LaunchedEffect` / `Flow.collect`.
65
+ 2. **Map each call site** to one of:
66
+ - A supported composable → leave it (verify in `reference/supported-composables.md`).
67
+ - A side effect → invent a capability ID at `0x4000+`, add a stub, add the host-side registration.
68
+ - A forbidden API → propose an alternative (Capability call, or "this can't be migrated; keep it native").
69
+ 3. **Rewrite the file** with `@KetoyComposable`-annotated public function(s) and any captured `val`s declared at the top of the function body (closure-converted to `KBCValue.ClosureRef`).
70
+ 4. **Show the user a diff** before writing — migrations frequently change semantics (e.g. `viewModel()` → `vmGetState`).
71
+ 5. **Run `./gradlew :app:ketoyBundle --rerun-tasks`** — surface the actual error if compilation fails.
72
+
73
+ See [guides/migrate.md](guides/migrate.md) for the playbook.
74
+
75
+ ### D. Diagnose build / runtime errors
76
+
77
+ Read `guides/diagnose-errors.md`. Common patterns:
78
+
79
+ - `KetoyBC: Direct access to android.*` → host API call, route through capability.
80
+ - `KetoyBC: Direct access to FontWeight.Companion.<get-X>` → token not in `ComposeTokenRegistry` — file a Ketoy issue OR replace with a supported one.
81
+ - `KetoyBC: Reached via: X → Y → Z` → forbidden call in a transitive helper; fix at the leaf (Z), not the root (X).
82
+ - `KetoyBundleMalformedException` at runtime → signature mismatch or stale `.ktx`. Re-run `./gradlew :app:ketoyBundle --rerun-tasks` and verify the public key matches.
83
+ - `KBCEngineNotAvailableException` → `KetoyConfig.enableJIT` set but `dexCacheDir` missing, OR using suspend without `parentScope`.
84
+
85
+ ### E. Build & analyze
86
+
87
+ - **Build**: `./gradlew :app:ketoyBundle --rerun-tasks` from project root, or `:app:assembleDebug` (which transitively triggers the bundle).
88
+ - **Analyze**: use the CLI's `analyze <path-to-.ktx>` — wraps a JVM subprocess that calls `KtxReader.read` and prints function count, manifest entries, modifier table size, signature status, and string pool contents.
89
+
90
+ ### F. Publish (deferred)
91
+
92
+ Don't write publishing code. If asked: explain that publishing requires written package-name confirmation (safety gate) and the Ketoy backend, which is not yet GA. Track the user's interest and stop there.
93
+
94
+ ## Tone & format
95
+
96
+ - Be specific. "Add `dev.ketoy.vm:ketoy-runtime:0.3.4-alpha`" beats "add the runtime dependency."
97
+ - Quote real symbol names from the user's tree. Don't paraphrase.
98
+ - For multi-step migrations, show the plan, get acknowledgment, then execute step-by-step with intermediate verification (gradle compile after each meaningful change).
99
+ - When suggesting an unsupported pattern, link to the alternative in `reference/`. Never say "use X" without saying "because Y is not supported in KBC."
100
+
101
+ ## Coordinates reference (current release: 0.3.4-alpha)
102
+
103
+ ```kotlin
104
+ plugins {
105
+ alias(libs.plugins.android.application)
106
+ alias(libs.plugins.kotlin.android)
107
+ alias(libs.plugins.kotlin.compose)
108
+ id("dev.ketoy.compiler") version "0.3.4-alpha"
109
+ }
110
+
111
+ dependencies {
112
+ implementation(platform("dev.ketoy.vm:ketoy-bom:0.3.4-alpha"))
113
+ implementation("dev.ketoy.vm:ketoy-runtime")
114
+ implementation("dev.ketoy.vm:ketoy-annotations")
115
+ implementation("dev.ketoy.vm:ketoy-capabilities-core")
116
+ implementation("dev.ketoy.vm:ketoy-capabilities-navigation")
117
+ implementation("dev.ketoy.vm:ketoy-adapters-material3")
118
+ // Add `dev.ketoy.vm:ketoy-hilt` only if the host opts into Hilt.
119
+ }
120
+
121
+ ketoy {
122
+ exportFromAppModule.set(true)
123
+ bundleId.set("main")
124
+ bundleVariant.set("release")
125
+ capabilityRegistryFile.set(file("ketoy-capabilities.json"))
126
+ minAppVersion.set(0)
127
+ debugMode.set(true)
128
+ val signingKey = file("keys/release-private.key")
129
+ if (signingKey.exists()) {
130
+ signingKeyFile.set(signingKey)
131
+ }
132
+ }
133
+ ```
134
+
135
+ The `dev.ketoy.compiler` Gradle plugin auto-attaches the KetoyBC compiler plugin to `compile<bundleVariant>Kotlin`. `exportFromAppModule.set(true)` is ADR-0003 inline-source mode — the bundle is emitted at `app/src/main/assets/ketoy/<bundleId>.ktx`.
136
+
137
+ The plugin must apply to the **module that owns `@KetoyComposable` declarations**, normally `:app`. Plugin version is declared inline on the `id(...) version "..."` line since the plugin is resolved through the Gradle Plugin Portal / Maven Central.
138
+
139
+ ## When to read sub-files
140
+
141
+ - Asked "is X supported?" → `reference/supported-composables.md`, `reference/supported-constructors.md`, `reference/capabilities.md`.
142
+ - Asked "what can't I do?" → `reference/forbidden-apis.md`.
143
+ - Writing a screen → `examples/todo-screen.kt`.
144
+ - Defining capabilities → `examples/capabilities-stubs.kt`.
145
+ - Wiring Hilt → `examples/hilt-config.kt`.
146
+ - Scaffolding from scratch → `templates/`.
147
+ - Diagnosing a build error → `guides/diagnose-errors.md`.
148
+ - Migrating a screen → `guides/migrate.md`.
@@ -0,0 +1,60 @@
1
+ // Canonical capability stubs for a KBC screen.
2
+ //
3
+ // Place this file at: app/src/main/kotlin/com/example/app/ketoyscreens/Capabilities.kt
4
+ //
5
+ // Every function below is a `@KetoyCapabilityStub` — the body throws because
6
+ // at runtime the compiler plugin replaces calls to these with INVOKE_CAPABILITY
7
+ // opcodes. The body is never executed.
8
+ //
9
+ // IDs in 0x07xx, 0x09xx, 0x0Axx are STANDARD LIBRARY — match exactly.
10
+ // IDs at 0x4000+ are APP-SPECIFIC — pick freely, but pick once and never reuse.
11
+
12
+ @file:Suppress("UnusedParameter")
13
+
14
+ package com.example.app.ketoyscreens
15
+
16
+ import dev.ketoy.annotations.KetoyCapabilityStub
17
+ import kotlinx.coroutines.flow.Flow
18
+
19
+ private const val STUB_MSG = "Ketoy capability stub — replaced by INVOKE_CAPABILITY at compile time"
20
+
21
+ // ── Navigation (standard library) ────────────────────────────────
22
+
23
+ @KetoyCapabilityStub(id = 0x0700, name = "NAV_PUSH")
24
+ public fun navPush(route: String): Unit = error(STUB_MSG)
25
+
26
+ @KetoyCapabilityStub(id = 0x0701, name = "NAV_POP")
27
+ public fun navPop(): Unit = error(STUB_MSG)
28
+
29
+ @KetoyCapabilityStub(id = 0x0702, name = "NAV_REPLACE")
30
+ public fun navReplace(route: String): Unit = error(STUB_MSG)
31
+
32
+ // ── ViewModel state bridge ───────────────────────────────────────
33
+
34
+ @KetoyCapabilityStub(id = 0x0A00, name = "VM_GET_STATE")
35
+ public fun vmGetState(key: String): Any? = error(STUB_MSG)
36
+
37
+ @KetoyCapabilityStub(id = 0x0A01, name = "VM_SET_STATE")
38
+ public fun vmSetState(key: String, value: Any?): Unit = error(STUB_MSG)
39
+
40
+ @KetoyCapabilityStub(id = 0x0A02, name = "VM_OBSERVE_STATE")
41
+ public fun vmObserveState(key: String): Flow<Any?> = error(STUB_MSG)
42
+
43
+ // ── Platform (standard library) ──────────────────────────────────
44
+
45
+ @KetoyCapabilityStub(id = 0x0901, name = "TOAST")
46
+ public fun toast(message: String, duration: Int = 0): Unit = error(STUB_MSG)
47
+
48
+ @KetoyCapabilityStub(id = 0x0900, name = "ANALYTICS_TRACK")
49
+ public fun analyticsTrack(event: String, props: Map<String, Any?> = emptyMap()): Unit = error(STUB_MSG)
50
+
51
+ // ── App-specific (0x4000+, define yours) ─────────────────────────
52
+
53
+ @KetoyCapabilityStub(id = 0x4000, name = "OBSERVE_TODOS")
54
+ public fun observeTodos(): Flow<List<Any?>> = error(STUB_MSG)
55
+
56
+ @KetoyCapabilityStub(id = 0x4001, name = "ADD_TODO")
57
+ public suspend fun addTodo(title: String): Long = error(STUB_MSG)
58
+
59
+ @KetoyCapabilityStub(id = 0x4002, name = "SET_TODO_COMPLETED")
60
+ public suspend fun setTodoCompleted(id: Long, completed: Boolean): Unit = error(STUB_MSG)
@@ -0,0 +1,192 @@
1
+ // Hilt wiring for a Ketoy host APK.
2
+ //
3
+ // Three files in one — separate them in your project:
4
+ // 1. AppCapabilityProvider.kt (the KetoyCapabilityProvider impl)
5
+ // 2. AppHiltModule.kt (@Module with @Binds + @Provides)
6
+ // 3. MainActivity.kt (consumer wiring)
7
+
8
+ // ════════════════════════════════════════════════════════════════════
9
+ // File 1 — AppCapabilityProvider.kt
10
+ // ════════════════════════════════════════════════════════════════════
11
+
12
+ package com.example.app.di
13
+
14
+ import android.content.Context
15
+ import androidx.datastore.core.DataStore
16
+ import androidx.datastore.preferences.core.Preferences
17
+ import dagger.hilt.android.qualifiers.ApplicationContext
18
+ import dev.ketoy.capabilities.core.KetoyHttpClient
19
+ import dev.ketoy.capabilities.core.KBCRoomBridge
20
+ import dev.ketoy.capabilities.core.registerCoreCapabilities
21
+ import dev.ketoy.capabilities.core.registerStorageCapabilities
22
+ import dev.ketoy.hilt.KetoyCapabilityProvider
23
+ import dev.ketoy.runtime.capability.CapabilityRegistry
24
+ import javax.inject.Inject
25
+ import javax.inject.Singleton
26
+
27
+ @Singleton
28
+ public class AppCapabilityProvider @Inject constructor(
29
+ @ApplicationContext private val context: Context,
30
+ private val dataStore: DataStore<Preferences>,
31
+ private val httpClient: KetoyHttpClient,
32
+ // Inject your domain repositories here (Room DAOs, HTTP clients, etc.)
33
+ // private val todoRepository: TodoRepository,
34
+ ) : KetoyCapabilityProvider {
35
+
36
+ override fun buildRegistry(): CapabilityRegistry {
37
+ val registry = CapabilityRegistry()
38
+
39
+ // Standard library: HTTP, KV storage, dispatchers, platform, navigation
40
+ registry.registerCoreCapabilities(context, httpClient = httpClient)
41
+ registry.registerStorageCapabilities(dataStore)
42
+
43
+ // App-specific (0x4000+) — registered via KBCRoomBridge for type-safe Room
44
+ KBCRoomBridge(registry).apply {
45
+ // observeList(0x4000) { args -> todoRepository.observeAll() }
46
+ // suspendCapability(0x4001) { args ->
47
+ // val title = args[0] as String
48
+ // todoRepository.add(title)
49
+ // }
50
+ }
51
+
52
+ return registry
53
+ }
54
+ }
55
+
56
+ // ════════════════════════════════════════════════════════════════════
57
+ // File 2 — AppHiltModule.kt
58
+ // ════════════════════════════════════════════════════════════════════
59
+
60
+ package com.example.app.di
61
+
62
+ import android.content.Context
63
+ import androidx.compose.material.icons.Icons
64
+ import androidx.compose.material.icons.filled.Settings
65
+ import androidx.compose.ui.text.font.Font
66
+ import androidx.compose.ui.text.font.FontFamily
67
+ import androidx.datastore.core.DataStore
68
+ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
69
+ import androidx.datastore.preferences.core.Preferences
70
+ import androidx.datastore.preferences.preferencesDataStoreFile
71
+ import dagger.Binds
72
+ import dagger.Module
73
+ import dagger.Provides
74
+ import dagger.hilt.InstallIn
75
+ import dagger.hilt.android.qualifiers.ApplicationContext
76
+ import dagger.hilt.components.SingletonComponent
77
+ import dev.ketoy.adapters.material3.MaterialDrawableResolver
78
+ import dev.ketoy.adapters.material3.MaterialFontFamilyResolver
79
+ import dev.ketoy.adapters.material3.MaterialIconsResolver
80
+ import dev.ketoy.adapters.material3.materialDrawableResolver
81
+ import dev.ketoy.adapters.material3.materialFontFamilyResolver
82
+ import dev.ketoy.adapters.material3.materialIconsResolver
83
+ import dev.ketoy.hilt.KetoyCapabilityProvider
84
+ import dev.ketoy.hilt.KetoyConfigCustomizer
85
+ import dev.ketoy.runtime.security.KetoyKeystore
86
+ import com.example.app.R
87
+ import javax.inject.Singleton
88
+
89
+ @Module
90
+ @InstallIn(SingletonComponent::class)
91
+ public abstract class AppHiltModule {
92
+
93
+ @Binds @Singleton
94
+ public abstract fun bindCapabilityProvider(impl: AppCapabilityProvider): KetoyCapabilityProvider
95
+
96
+ public companion object {
97
+ private const val PREFERENCES_NAME = "app_prefs"
98
+ private const val PUBLIC_KEY_ASSET = "ketoy/keys/sample-public.key"
99
+
100
+ @Provides @Singleton
101
+ public fun provideDataStore(
102
+ @ApplicationContext context: Context,
103
+ ): DataStore<Preferences> = PreferenceDataStoreFactory.create {
104
+ context.preferencesDataStoreFile(PREFERENCES_NAME)
105
+ }
106
+
107
+ @Provides @Singleton
108
+ public fun provideKetoyConfigCustomizer(
109
+ @ApplicationContext context: Context,
110
+ iconsResolver: MaterialIconsResolver,
111
+ fontResolver: MaterialFontFamilyResolver,
112
+ drawableResolver: MaterialDrawableResolver,
113
+ ): KetoyConfigCustomizer = KetoyConfigCustomizer { default ->
114
+ val publicKey = KetoyKeystore.loadFromAsset(context, PUBLIC_KEY_ASSET)
115
+ default.copy(
116
+ enableSignatureVerification = true,
117
+ publicKey = publicKey,
118
+ imageVectorResolver = iconsResolver,
119
+ fontFamilyResolver = fontResolver,
120
+ drawableResolver = drawableResolver,
121
+ )
122
+ }
123
+
124
+ @Provides @Singleton
125
+ public fun provideMaterialIconsResolver(): MaterialIconsResolver = materialIconsResolver {
126
+ registerFilled("Settings", Icons.Filled.Settings)
127
+ // Add additional icons here as KBC source consumes them.
128
+ }
129
+
130
+ @Provides @Singleton
131
+ public fun provideMaterialFontFamilyResolver(): MaterialFontFamilyResolver =
132
+ materialFontFamilyResolver {
133
+ // register("courgette_regular", FontFamily(Font(R.font.courgette_regular)))
134
+ }
135
+
136
+ @Provides @Singleton
137
+ public fun provideMaterialDrawableResolver(): MaterialDrawableResolver =
138
+ materialDrawableResolver {
139
+ // register("img", R.drawable.img)
140
+ }
141
+ }
142
+ }
143
+
144
+ // ════════════════════════════════════════════════════════════════════
145
+ // File 3 — MainActivity.kt
146
+ // ════════════════════════════════════════════════════════════════════
147
+
148
+ package com.example.app
149
+
150
+ import android.os.Bundle
151
+ import androidx.activity.ComponentActivity
152
+ import androidx.activity.compose.setContent
153
+ import androidx.activity.enableEdgeToEdge
154
+ import androidx.compose.foundation.layout.fillMaxSize
155
+ import androidx.compose.ui.Modifier
156
+ import dagger.hilt.android.AndroidEntryPoint
157
+ import dev.ketoy.hilt.KetoyHiltProvider
158
+ import dev.ketoy.hilt.KetoyViewModelFactoryBuilder
159
+ import dev.ketoy.runtime.KetoyRuntime
160
+ import dev.ketoy.runtime.bundle.KetoyBundleLoader
161
+ import dev.ketoy.runtime.bundle.KetoyBundleSource
162
+ import dev.ketoy.runtime.compose.KetoyScreen
163
+ import javax.inject.Inject
164
+
165
+ @AndroidEntryPoint
166
+ public class MainActivity : ComponentActivity() {
167
+ @Inject public lateinit var factoryBuilder: KetoyViewModelFactoryBuilder
168
+ @Inject public lateinit var runtime: KetoyRuntime
169
+ @Inject public lateinit var bundleLoader: KetoyBundleLoader
170
+
171
+ override fun onCreate(savedInstanceState: Bundle?) {
172
+ super.onCreate(savedInstanceState)
173
+ enableEdgeToEdge()
174
+ setContent {
175
+ KetoyHiltProvider(
176
+ factoryBuilder = factoryBuilder,
177
+ bundleLoader = bundleLoader,
178
+ runtime = runtime,
179
+ ) {
180
+ KetoyScreen(
181
+ entryPoint = "TodoListScreen",
182
+ bundleSource = KetoyBundleSource.Asset("ketoy/main.ktx"),
183
+ modifier = Modifier.fillMaxSize(),
184
+ nativeFallback = {
185
+ // Required: rendered when bundle is missing / incompatible / corrupt.
186
+ // This should be the same screen the user would see WITHOUT Ketoy.
187
+ },
188
+ )
189
+ }
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,101 @@
1
+ // Non-Hilt Ketoy setup — the default first-pass for `ketoy init`.
2
+ //
3
+ // Two files:
4
+ // 1. App.kt (Application with manual singleton wiring)
5
+ // 2. MainActivity.kt (consumer)
6
+
7
+ // ════════════════════════════════════════════════════════════════════
8
+ // File 1 — App.kt
9
+ // ════════════════════════════════════════════════════════════════════
10
+
11
+ package com.example.app
12
+
13
+ import android.app.Application
14
+ import androidx.compose.material.icons.Icons
15
+ import androidx.compose.material.icons.filled.Settings
16
+ import dev.ketoy.adapters.material3.materialDrawableResolver
17
+ import dev.ketoy.adapters.material3.materialFontFamilyResolver
18
+ import dev.ketoy.adapters.material3.materialIconsResolver
19
+ import dev.ketoy.capabilities.core.KetoyHttpClient
20
+ import dev.ketoy.capabilities.core.registerCoreCapabilities
21
+ import dev.ketoy.runtime.KetoyConfig
22
+ import dev.ketoy.runtime.KetoyRuntime
23
+ import dev.ketoy.runtime.bundle.KetoyBundleLoader
24
+ import dev.ketoy.runtime.capability.CapabilityRegistry
25
+ import dev.ketoy.runtime.security.KetoyKeystore
26
+
27
+ public class App : Application() {
28
+
29
+ public lateinit var ketoyRuntime: KetoyRuntime
30
+ private set
31
+
32
+ public lateinit var ketoyBundleLoader: KetoyBundleLoader
33
+ private set
34
+
35
+ override fun onCreate() {
36
+ super.onCreate()
37
+
38
+ val iconsResolver = materialIconsResolver {
39
+ registerFilled("Settings", Icons.Filled.Settings)
40
+ }
41
+ val fontResolver = materialFontFamilyResolver { /* register fonts */ }
42
+ val drawableResolver = materialDrawableResolver { /* register drawables */ }
43
+
44
+ val registry = CapabilityRegistry().apply {
45
+ registerCoreCapabilities(this@App, httpClient = KetoyHttpClient.build())
46
+ // app-specific capabilities here:
47
+ // registerSuspend(0x4001) { args -> ... }
48
+ }
49
+
50
+ val config = KetoyConfig(
51
+ enableSignatureVerification = true,
52
+ publicKey = KetoyKeystore.loadFromAsset(this, "ketoy/keys/sample-public.key"),
53
+ imageVectorResolver = iconsResolver,
54
+ fontFamilyResolver = fontResolver,
55
+ drawableResolver = drawableResolver,
56
+ )
57
+
58
+ ketoyRuntime = KetoyRuntime(config, registry)
59
+ ketoyBundleLoader = KetoyBundleLoader(ketoyRuntime, this)
60
+ }
61
+ }
62
+
63
+ // ════════════════════════════════════════════════════════════════════
64
+ // File 2 — MainActivity.kt
65
+ // ════════════════════════════════════════════════════════════════════
66
+
67
+ package com.example.app
68
+
69
+ import android.os.Bundle
70
+ import androidx.activity.ComponentActivity
71
+ import androidx.activity.compose.setContent
72
+ import androidx.compose.foundation.layout.fillMaxSize
73
+ import androidx.compose.ui.Modifier
74
+ import dev.ketoy.runtime.bundle.KetoyBundleSource
75
+ import dev.ketoy.runtime.compose.KetoyScreen
76
+
77
+ public class MainActivity : ComponentActivity() {
78
+
79
+ override fun onCreate(savedInstanceState: Bundle?) {
80
+ super.onCreate(savedInstanceState)
81
+ val app = application as App
82
+
83
+ setContent {
84
+ KetoyScreen(
85
+ entryPoint = "TodoListScreen",
86
+ bundleSource = KetoyBundleSource.Asset("ketoy/main.ktx"),
87
+ modifier = Modifier.fillMaxSize(),
88
+ runtime = app.ketoyRuntime,
89
+ bundleLoader = app.ketoyBundleLoader,
90
+ nativeFallback = {
91
+ // Required: rendered when bundle missing / incompatible / corrupt.
92
+ },
93
+ )
94
+ }
95
+ }
96
+ }
97
+
98
+ // Remember to register the Application in AndroidManifest.xml:
99
+ // <application
100
+ // android:name=".App"
101
+ // ... >