| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 15 | 7 | 0% |
| Commands | 0 | 8 | 0 | 0% |
| Section tags | 1 | 5 | 0 | 17% |
What each file covers
Sections
0 shared · 15 only in A · 7 only in B- − Android Package (Native Android App)
- − Overview
- − Package Structure
- − Key Architecture Rules
- − Transcript
- − Sync and Encryption
- − Persistence
- − Firebase / Notifications
- − Development
- − Prerequisites
- − Commands
- − Play Store screenshots and video
- − Builds, signing, and CI
- − Agent Guidance
- − Important Files
- + Runtime Package
- + Package Placement
- + AI Providers
- + Provider Factory
- + Codex Binary Path Resolution
- + AI Features
- + Linear Integration
Commands
0 shared · 8 only in A · 0 only in B- − npm run android:build:transcript
- − npm run android:test:unit
- − npm run android:assemble:debug
- − npm run android:assemble:release
- − npm run android:bundle:release
- − npm run android:screenshots
- − npm run android:walkthrough
- − npm run android:bundle:signed
Section tags
1 shared · 5 only in A · 0 only in B- − setup
- − build
- − code-style
- − architecture
- − do-not
- agent-behaviour
Line diff
nimbalyst/nimbalyst · packages/android/CLAUDE.md
@@ −1 @@
1# Android Package (Native Android App)
2
3This package contains the native Android app for Nimbalyst. It mirrors the iOS native app architecture where practical: a pure native mobile shell with a single embedded web transcript view that renders the shared React transcript bundle.
4
5## Overview
6
7The Android app is:
8
9- **Pure native Android** using Kotlin and Jetpack Compose
10- **Room-backed** for local persistence
11- **WebSocket-synced** with CollabV3 Durable Objects
12- **End-to-end encrypted** using the same seed + user-derived key model as iOS
13- **Transcript-rendered** through a single `WebView` that loads the bundled React transcript UI
14
15Voice agent features are intentionally out of scope for Android.
16
17## Package Structure
18
19```text
20packages/android/
21 app/
22 src/main/java/com/nimbalyst/app/
23 attachments/ # Image attachment preparation/compression
24 auth/ # Auth callback parsing
25 crypto/ # AES-GCM + PBKDF2 key derivation
26 data/ # Room entities, DAOs, repository
27 notifications/ # Android notification + FCM token plumbing
28 pairing/ # QR payload parsing and persistent pairing state
29 sync/ # WebSocket sync manager and wire protocol
30 transcript/ # WebView host and JS bridge
31 ui/ # Compose screens and app shell
32 src/test/ # Unit tests
33 src/transcript/ # Shared React transcript bundle entrypoint/assets
34 scripts/ # Transcript asset sync helpers
35```
36
37## Key Architecture Rules
38
39### Transcript
40
41- The transcript UI lives in `src/transcript/main.tsx` and is bundled into Android assets.
42- `TranscriptWebView.kt` is the Android host. `TranscriptBridge.kt` is the only place JS bridge actions should be decoded and routed.
43- Keep transcript behavior aligned with iOS unless Android-specific UX requires a different path.
44
45### Sync and Encryption
46
47- `SyncManager.kt` owns the device sync lifecycle, room joins, index updates, queued prompt handling, and session control messages.
48- `CryptoManager.kt` must remain wire-compatible with iOS and desktop. Be cautious with any PBKDF2, AES-GCM, or payload format changes.
49- User routing identity and crypto identity are distinct. Do not collapse them back into a single field.
50
51### Persistence
52
53- Room is the source of truth for local Android UI state.
54- Prefer repository/DAO changes over screen-local state duplication.
55- If you add persisted fields, update schema, migrations, and any seed/demo paths together.
56
57### Firebase / Notifications
58
59- `app/google-services.json` is local environment config. Do **not** commit it. The `google-services` Gradle plugin is applied conditionally (only when the file exists), so a build without it stays green and push stays inert.
60- Client push registration lives in `notifications/NotificationManager.kt`.
61- Server push delivery lives in the collab server, which is the sibling `nimbalyst-collab` repository, not this monorepo. Clone it next to this repo at `../nimbalyst-collab` (override with `COLLAB_SERVER_PATH`); collab tests are gated by `RUN_COLLAB_TESTS=1`. See `.github/workflows/ci.yml`. Android push changes usually require coordinated client + server work.
62
63## Development
64
65### Prerequisites
66
67- Android Studio Ladybug / AGP-compatible version for this project
68- JDK 17 for Gradle builds. The project targets `JavaVersion.VERSION_17` and `jvmTarget = "17"`, and Temurin 17 matches CI. A non-17 JDK (e.g. GraalVM) can fail the AGP `jlink` step.
69- Android SDK + emulator tooling
70- Node.js 20+ for transcript bundle builds
71
72### Commands
73
74From the repository root the npm scripts wrap the Gradle tasks:
75
76```bash
77npm run android:build:transcript # build the transcript bundle
78npm run android:test:unit # ./gradlew :app:testDebugUnitTest
79npm run android:assemble:debug # ./gradlew :app:assembleDebug
80npm run android:assemble:release # ./gradlew :app:assembleRelease
81npm run android:bundle:release # ./gradlew :app:bundleRelease
82```
83
84To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
85
86```bash
87cd packages/android
88JAVA_HOME=/path/to/temurin-17 ./gradlew :app:assembleDebug
89JAVA_HOME=/path/to/temurin-17 ./gradlew :app:testDebugUnitTest
90```
91
92### Play Store screenshots and video
93
94`npm run android:screenshots` and `npm run android:walkthrough` drive an emulator against the debug-only screenshot mode in `app/src/debug/java/com/nimbalyst/app/screenshots/` (inert stub in `app/src/release/`). Never move that code into `src/main` — it seeds demo data and a fake paired state. See [ANDROID_MARKETING_SCREENSHOTS.md](../../docs/ANDROID_MARKETING_SCREENSHOTS.md).
95
96### Builds, signing, and CI
97
98- The `google-services` plugin is applied only when `app/google-services.json` is present, so a build without it succeeds and push stays inert until the file is added.
99- CI can inject Firebase config from the optional `ANDROID_GOOGLE_SERVICES_JSON_BASE64` GitHub secret by decoding it to `app/google-services.json` before the Gradle build.
100- The release `signingConfig` reads the keystore path and credentials from environment variables: `NIMBALYST_ANDROID_KEYSTORE`, `NIMBALYST_ANDROID_KEYSTORE_PASSWORD`, `NIMBALYST_ANDROID_KEY_ALIAS`, `NIMBALYST_ANDROID_KEY_PASSWORD`. When the keystore is absent the release build is simply unsigned. Minification stays off (signed is not the same as minified).
101- CI builds both the APK and Play-ready AAB via `.github/workflows/android-build.yml`, which supplies the keystore and signing secrets to produce signed release artifacts when secrets are present. CI also decodes `google-services.json` from the `ANDROID_GOOGLE_SERVICES_JSON_BASE64` secret and fails a signed build if that secret is missing, so a signed AAB never ships with push silently inert.
102- To build a signed release locally, run `npm run android:bundle:signed` (wraps `scripts/android-bundle-signed.sh`). It pulls all signing secrets from the 1Password item `Nimbalyst Android Signing` (Nimbalyst vault) at build time via `op read`: the upload keystore is fetched to a temp file deleted on exit, and passwords/alias are injected into the Gradle env only. Never commit a keystore — `*.jks`/`*.keystore` are gitignored.
103
104Open `packages/android/` in Android Studio, not the repo root.
105
106## Agent Guidance
107
108- Read the root `CLAUDE.md` before changing this package.
109- Prefer following iOS behavior and naming when implementing cross-platform mobile features.
110- Do not commit secrets or local machine config such as:
111 - `app/google-services.json`
112 - `local.properties`
113 - build outputs
114- If Android Studio reports AGP incompatibility, the correct fix is usually to update Android Studio rather than downgrade AGP/Kotlin.
115- When changing sync protocol behavior, inspect the matching iOS code paths in this repo and the collab server code paths in the sibling `nimbalyst-collab` repository before editing.
116- When changing transcript bridge behavior, update or add Android tests in `app/src/test/` where possible.
117
118## Important Files
119
120| File | Purpose |
121| --- | --- |
122| `app/src/main/java/com/nimbalyst/app/NimbalystApplication.kt` | App-level dependency setup and startup wiring |
123| `app/src/main/java/com/nimbalyst/app/MainActivity.kt` | Activity entry point and deep-link handling |
124| `app/src/main/java/com/nimbalyst/app/ui/NimbalystAndroidApp.kt` | Root Compose app shell and navigation |
125| `app/src/main/java/com/nimbalyst/app/sync/SyncManager.kt` | Core mobile sync lifecycle and message handling |
126| `app/src/main/java/com/nimbalyst/app/sync/SyncProtocol.kt` | Android wire protocol types |
127| `app/src/main/java/com/nimbalyst/app/crypto/CryptoManager.kt` | Encryption and key derivation |
128| `app/src/main/java/com/nimbalyst/app/data/NimbalystDatabase.kt` | Room database definition |
129| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptWebView.kt` | WebView transcript host |
130| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptBridge.kt` | JS/native bridge handler |
131| `src/transcript/main.tsx` | Shared transcript app entry point for Android |
132
nimbalyst/nimbalyst · packages/runtime/CLAUDE.md
@@ +1 @@
1# Runtime Package
2
3Core runtime logic for Nimbalyst — AI provider implementations and shared services that work across Electron and Capacitor (mobile) platforms.
4
5## Package Placement
6
7Put React components in this package if they might be used by the mobile app. Components specific to Electron go in the `electron` package.
8
9## AI Providers
10
11Two categories — **agent providers** (Claude Agent, OpenAI Codex; full MCP, file-system tools, multi-file ops, session persistence) and **chat providers** (Claude Chat, OpenAI, LM Studio; direct API, files as context, faster, local model support). See [/docs/AI_PROVIDER_TYPES.md](/docs/AI_PROVIDER_TYPES.md).
12
13| Provider ID | Implementation | Notes |
14| --- | --- | --- |
15| `claude` | `src/ai/server/providers/ClaudeProvider.ts` | Anthropic SDK; standard models; streaming with tool use; model list in `src/ai/modelConstants.ts`. |
16| `claude-code` | `src/ai/server/providers/ClaudeCodeProvider.ts` | Dynamically loads `@anthropic-ai/claude-agent-sdk` from user's installation. **Manages its own model selection — do not pass model IDs.** See [/docs/INTERNAL_MCP_SERVERS.md](/docs/INTERNAL_MCP_SERVERS.md). |
17| `openai` | OpenAI API | GPT-4, GPT-3.5. |
18| `openai-codex` | `src/ai/server/providers/OpenAICodexProvider.ts` | Codex app-server transport by default; thread-based streaming; session resume via persisted provider session IDs. The old `@openai/codex-sdk` transport is legacy-only. See [Codex Binary Path](#codex-binary-path-resolution). |
19| `lmstudio` | LM Studio HTTP | Local model support. |
20
21### Provider Factory
22
23- **Location**: `src/ai/server/ProviderFactory.ts`
24- Creates / manages provider instances by type; each provider is cached per session.
25
26### Codex Binary Path Resolution
27
28In Electron packaged apps, the Codex binary cannot be executed from within the asar archive (virtual filesystem). `resolvePackagedCodexBinaryPath()`:
29
301. Maps `process.platform` / `process.arch` to Codex target triples (`aarch64-apple-darwin` for ARM64 macOS, `x86_64-pc-windows-msvc` for x64 Windows, etc.)
312. Checks `app.asar.unpacked/node_modules/@openai/codex-sdk` first (priority location)
323. Falls back to `node_modules/@openai/codex-sdk`
334. Passes the resolved path to the app-server transport or legacy SDK constructor via `codexPathOverride`
34
35**Related files:**
36- `src/ai/server/providers/codex/codexBinaryPath.ts`
37- `packages/electron/package.json` — `asarUnpack` and `extraResources` include Codex SDK/native packages for the app-server binary and the legacy SDK escape hatch
38
39## AI Features
40
41- **AI Chat Panel**: multi-provider, document-aware, no-document handling, multi-session per project, edit streaming
42- **Session Manager**: global view, search, session details, open/export/delete actions
43- **Model Configuration**: dynamic model fetching from provider APIs; no hardcoded models; LM Studio auto-detection; `claude-code` manages its own models
44- **Custom Tool Widgets**: see [/docs/CUSTOM_TOOL_WIDGETS.md](/docs/CUSTOM_TOOL_WIDGETS.md) for replacing the generic tool call display
45
46## Linear Integration
47
48The Linear MCP integration uses the "NIM" project for issue tracking.
49
@@ −1 +1 @@
1−# Android Package (Native Android App)
1+# Runtime Package
22
3−This package contains the native Android app for Nimbalyst. It mirrors the iOS native app architecture where practical: a pure native mobile shell with a single embedded web transcript view that renders the shared React transcript bundle.
3+Core runtime logic for Nimbalyst — AI provider implementations and shared services that work across Electron and Capacitor (mobile) platforms.
44
5−## Overview
5+## Package Placement
66
7−The Android app is:
7+Put React components in this package if they might be used by the mobile app. Components specific to Electron go in the `electron` package.
88
9−- **Pure native Android** using Kotlin and Jetpack Compose
10−- **Room-backed** for local persistence
11−- **WebSocket-synced** with CollabV3 Durable Objects
12−- **End-to-end encrypted** using the same seed + user-derived key model as iOS
13−- **Transcript-rendered** through a single `WebView` that loads the bundled React transcript UI
9+## AI Providers
1410
15−Voice agent features are intentionally out of scope for Android.
11+Two categories — **agent providers** (Claude Agent, OpenAI Codex; full MCP, file-system tools, multi-file ops, session persistence) and **chat providers** (Claude Chat, OpenAI, LM Studio; direct API, files as context, faster, local model support). See [/docs/AI_PROVIDER_TYPES.md](/docs/AI_PROVIDER_TYPES.md).
1612
17−## Package Structure
13+| Provider ID | Implementation | Notes |
14+| --- | --- | --- |
15+| `claude` | `src/ai/server/providers/ClaudeProvider.ts` | Anthropic SDK; standard models; streaming with tool use; model list in `src/ai/modelConstants.ts`. |
16+| `claude-code` | `src/ai/server/providers/ClaudeCodeProvider.ts` | Dynamically loads `@anthropic-ai/claude-agent-sdk` from user's installation. **Manages its own model selection — do not pass model IDs.** See [/docs/INTERNAL_MCP_SERVERS.md](/docs/INTERNAL_MCP_SERVERS.md). |
17+| `openai` | OpenAI API | GPT-4, GPT-3.5. |
18+| `openai-codex` | `src/ai/server/providers/OpenAICodexProvider.ts` | Codex app-server transport by default; thread-based streaming; session resume via persisted provider session IDs. The old `@openai/codex-sdk` transport is legacy-only. See [Codex Binary Path](#codex-binary-path-resolution). |
19+| `lmstudio` | LM Studio HTTP | Local model support. |
1820
19−```text
20−packages/android/
21− app/
22− src/main/java/com/nimbalyst/app/
23− attachments/ # Image attachment preparation/compression
24− auth/ # Auth callback parsing
25− crypto/ # AES-GCM + PBKDF2 key derivation
26− data/ # Room entities, DAOs, repository
27− notifications/ # Android notification + FCM token plumbing
28− pairing/ # QR payload parsing and persistent pairing state
29− sync/ # WebSocket sync manager and wire protocol
30− transcript/ # WebView host and JS bridge
31− ui/ # Compose screens and app shell
32− src/test/ # Unit tests
33− src/transcript/ # Shared React transcript bundle entrypoint/assets
34− scripts/ # Transcript asset sync helpers
35−```
21+### Provider Factory
3622
37−## Key Architecture Rules
23+- **Location**: `src/ai/server/ProviderFactory.ts`
24+- Creates / manages provider instances by type; each provider is cached per session.
3825
39−### Transcript
26+### Codex Binary Path Resolution
4027
41−- The transcript UI lives in `src/transcript/main.tsx` and is bundled into Android assets.
42−- `TranscriptWebView.kt` is the Android host. `TranscriptBridge.kt` is the only place JS bridge actions should be decoded and routed.
43−- Keep transcript behavior aligned with iOS unless Android-specific UX requires a different path.
28+In Electron packaged apps, the Codex binary cannot be executed from within the asar archive (virtual filesystem). `resolvePackagedCodexBinaryPath()`:
4429
45−### Sync and Encryption
30+1. Maps `process.platform` / `process.arch` to Codex target triples (`aarch64-apple-darwin` for ARM64 macOS, `x86_64-pc-windows-msvc` for x64 Windows, etc.)
31+2. Checks `app.asar.unpacked/node_modules/@openai/codex-sdk` first (priority location)
32+3. Falls back to `node_modules/@openai/codex-sdk`
33+4. Passes the resolved path to the app-server transport or legacy SDK constructor via `codexPathOverride`
4634
47−- `SyncManager.kt` owns the device sync lifecycle, room joins, index updates, queued prompt handling, and session control messages.
48−- `CryptoManager.kt` must remain wire-compatible with iOS and desktop. Be cautious with any PBKDF2, AES-GCM, or payload format changes.
49−- User routing identity and crypto identity are distinct. Do not collapse them back into a single field.
35+**Related files:**
36+- `src/ai/server/providers/codex/codexBinaryPath.ts`
37+- `packages/electron/package.json` — `asarUnpack` and `extraResources` include Codex SDK/native packages for the app-server binary and the legacy SDK escape hatch
5038
51−### Persistence
39+## AI Features
5240
53−- Room is the source of truth for local Android UI state.
54−- Prefer repository/DAO changes over screen-local state duplication.
55−- If you add persisted fields, update schema, migrations, and any seed/demo paths together.
41+- **AI Chat Panel**: multi-provider, document-aware, no-document handling, multi-session per project, edit streaming
42+- **Session Manager**: global view, search, session details, open/export/delete actions
43+- **Model Configuration**: dynamic model fetching from provider APIs; no hardcoded models; LM Studio auto-detection; `claude-code` manages its own models
44+- **Custom Tool Widgets**: see [/docs/CUSTOM_TOOL_WIDGETS.md](/docs/CUSTOM_TOOL_WIDGETS.md) for replacing the generic tool call display
5645
57−### Firebase / Notifications
46+## Linear Integration
5847
59−- `app/google-services.json` is local environment config. Do **not** commit it. The `google-services` Gradle plugin is applied conditionally (only when the file exists), so a build without it stays green and push stays inert.
60−- Client push registration lives in `notifications/NotificationManager.kt`.
61−- Server push delivery lives in the collab server, which is the sibling `nimbalyst-collab` repository, not this monorepo. Clone it next to this repo at `../nimbalyst-collab` (override with `COLLAB_SERVER_PATH`); collab tests are gated by `RUN_COLLAB_TESTS=1`. See `.github/workflows/ci.yml`. Android push changes usually require coordinated client + server work.
62−
63−## Development
64−
65−### Prerequisites
66−
67−- Android Studio Ladybug / AGP-compatible version for this project
68−- JDK 17 for Gradle builds. The project targets `JavaVersion.VERSION_17` and `jvmTarget = "17"`, and Temurin 17 matches CI. A non-17 JDK (e.g. GraalVM) can fail the AGP `jlink` step.
69−- Android SDK + emulator tooling
70−- Node.js 20+ for transcript bundle builds
71−
72−### Commands
73−
74−From the repository root the npm scripts wrap the Gradle tasks:
75−
76−```bash
77−npm run android:build:transcript # build the transcript bundle
78−npm run android:test:unit # ./gradlew :app:testDebugUnitTest
79−npm run android:assemble:debug # ./gradlew :app:assembleDebug
80−npm run android:assemble:release # ./gradlew :app:assembleRelease
81−npm run android:bundle:release # ./gradlew :app:bundleRelease
82−```
83−
84−To invoke Gradle directly, point `JAVA_HOME` at a Temurin 17 install (no hard-coded user path):
85−
86−```bash
87−cd packages/android
88−JAVA_HOME=/path/to/temurin-17 ./gradlew :app:assembleDebug
89−JAVA_HOME=/path/to/temurin-17 ./gradlew :app:testDebugUnitTest
90−```
91−
92−### Play Store screenshots and video
93−
94−`npm run android:screenshots` and `npm run android:walkthrough` drive an emulator against the debug-only screenshot mode in `app/src/debug/java/com/nimbalyst/app/screenshots/` (inert stub in `app/src/release/`). Never move that code into `src/main` — it seeds demo data and a fake paired state. See [ANDROID_MARKETING_SCREENSHOTS.md](../../docs/ANDROID_MARKETING_SCREENSHOTS.md).
95−
96−### Builds, signing, and CI
97−
98−- The `google-services` plugin is applied only when `app/google-services.json` is present, so a build without it succeeds and push stays inert until the file is added.
99−- CI can inject Firebase config from the optional `ANDROID_GOOGLE_SERVICES_JSON_BASE64` GitHub secret by decoding it to `app/google-services.json` before the Gradle build.
100−- The release `signingConfig` reads the keystore path and credentials from environment variables: `NIMBALYST_ANDROID_KEYSTORE`, `NIMBALYST_ANDROID_KEYSTORE_PASSWORD`, `NIMBALYST_ANDROID_KEY_ALIAS`, `NIMBALYST_ANDROID_KEY_PASSWORD`. When the keystore is absent the release build is simply unsigned. Minification stays off (signed is not the same as minified).
101−- CI builds both the APK and Play-ready AAB via `.github/workflows/android-build.yml`, which supplies the keystore and signing secrets to produce signed release artifacts when secrets are present. CI also decodes `google-services.json` from the `ANDROID_GOOGLE_SERVICES_JSON_BASE64` secret and fails a signed build if that secret is missing, so a signed AAB never ships with push silently inert.
102−- To build a signed release locally, run `npm run android:bundle:signed` (wraps `scripts/android-bundle-signed.sh`). It pulls all signing secrets from the 1Password item `Nimbalyst Android Signing` (Nimbalyst vault) at build time via `op read`: the upload keystore is fetched to a temp file deleted on exit, and passwords/alias are injected into the Gradle env only. Never commit a keystore — `*.jks`/`*.keystore` are gitignored.
103−
104−Open `packages/android/` in Android Studio, not the repo root.
105−
106−## Agent Guidance
107−
108−- Read the root `CLAUDE.md` before changing this package.
109−- Prefer following iOS behavior and naming when implementing cross-platform mobile features.
110−- Do not commit secrets or local machine config such as:
111− - `app/google-services.json`
112− - `local.properties`
113− - build outputs
114−- If Android Studio reports AGP incompatibility, the correct fix is usually to update Android Studio rather than downgrade AGP/Kotlin.
115−- When changing sync protocol behavior, inspect the matching iOS code paths in this repo and the collab server code paths in the sibling `nimbalyst-collab` repository before editing.
116−- When changing transcript bridge behavior, update or add Android tests in `app/src/test/` where possible.
117−
118−## Important Files
119−
120−| File | Purpose |
121−| --- | --- |
122−| `app/src/main/java/com/nimbalyst/app/NimbalystApplication.kt` | App-level dependency setup and startup wiring |
123−| `app/src/main/java/com/nimbalyst/app/MainActivity.kt` | Activity entry point and deep-link handling |
124−| `app/src/main/java/com/nimbalyst/app/ui/NimbalystAndroidApp.kt` | Root Compose app shell and navigation |
125−| `app/src/main/java/com/nimbalyst/app/sync/SyncManager.kt` | Core mobile sync lifecycle and message handling |
126−| `app/src/main/java/com/nimbalyst/app/sync/SyncProtocol.kt` | Android wire protocol types |
127−| `app/src/main/java/com/nimbalyst/app/crypto/CryptoManager.kt` | Encryption and key derivation |
128−| `app/src/main/java/com/nimbalyst/app/data/NimbalystDatabase.kt` | Room database definition |
129−| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptWebView.kt` | WebView transcript host |
130−| `app/src/main/java/com/nimbalyst/app/transcript/TranscriptBridge.kt` | JS/native bridge handler |
131−| `src/transcript/main.tsx` | Shared transcript app entry point for Android |
48+The Linear MCP integration uses the "NIM" project for issue tracking.
13249
