Skip to content

Commit d1d5507

Browse files
Initial Pocket Pi POC
Single-APK Android wrapper for the Pi coding agent. Bundles a Termux runtime, the @earendil-works/pi-coding-agent engine, the e9n pi-mobile PWA + pi-webserver, and a small set of Pi extensions. Native Compose ModalBottomSheet exposes API keys / AGENTS.md / models.json / Restart Pi / Re-run setup so non-technical users can configure the agent without touching a shell. Targets aarch64 phones. applicationId = com.termux for v0.1 so the upstream Termux bootstrap binaries (which bake in /data/data/com.termux /files/usr) work without recompiling. POC scope; whether to keep this Termux-fork approach or rewrite as a native Android client is the open question this build is meant to inform.
0 parents  commit d1d5507

74 files changed

Lines changed: 4351 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-apk.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# GitHub Actions: build Pocket Pi APK end-to-end.
2+
# Runs on every push to main and on tags `v*`. Tagged builds upload the APK
3+
# as a Release artifact.
4+
5+
name: build-apk
6+
7+
on:
8+
push:
9+
branches: [main]
10+
tags: ["v*"]
11+
pull_request:
12+
branches: [main]
13+
workflow_dispatch:
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-22.04
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up JDK 17
22+
uses: actions/setup-java@v4
23+
with:
24+
distribution: temurin
25+
java-version: 17
26+
27+
- name: Set up Android SDK
28+
uses: android-actions/setup-android@v3
29+
30+
- name: Set up Node 20 + pnpm
31+
uses: actions/setup-node@v4
32+
with: { node-version: "20" }
33+
- uses: pnpm/action-setup@v4
34+
with: { version: 9 }
35+
36+
- name: Build TS extensions
37+
run: |
38+
cd extensions/pi-termux-tools && pnpm install && pnpm build && cd -
39+
cd extensions/pi-skill-learner && pnpm install && pnpm build && cd -
40+
41+
- name: Set up Python 3.12
42+
uses: actions/setup-python@v5
43+
with: { python-version: "3.12" }
44+
- name: Build Python wheel
45+
run: |
46+
python -m pip install --upgrade pip build
47+
cd python/skill_learner_dspy && python -m build --wheel
48+
49+
- name: Build bootstrap zip (aarch64)
50+
run: |
51+
chmod +x bootstrap/build-bootstrap.sh bootstrap/postinstall.sh bootstrap/patches/*.sh
52+
./bootstrap/build-bootstrap.sh aarch64
53+
54+
- name: Build debug APK
55+
working-directory: android
56+
run: ./gradlew --no-daemon assembleDebug
57+
58+
- name: Build release APK (unsigned)
59+
working-directory: android
60+
run: ./gradlew --no-daemon assembleRelease || true
61+
62+
- uses: actions/upload-artifact@v4
63+
with:
64+
name: pocket-pi-debug-apk
65+
path: android/app/build/outputs/apk/debug/app-debug.apk
66+
67+
- name: Attach to Release
68+
if: startsWith(github.ref, 'refs/tags/v')
69+
uses: softprops/action-gh-release@v2
70+
with:
71+
files: android/app/build/outputs/apk/debug/app-debug.apk

.gitignore

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Node
2+
node_modules/
3+
dist/
4+
*.tsbuildinfo
5+
.npmrc
6+
7+
# Python
8+
__pycache__/
9+
*.pyc
10+
*.egg-info/
11+
build/
12+
.venv/
13+
venv/
14+
15+
# Android
16+
.gradle/
17+
local.properties
18+
captures/
19+
*.iml
20+
.idea/
21+
android/app/build/
22+
android/build/
23+
android/.cxx/
24+
android/.kotlin/
25+
26+
# Local tooling state
27+
.claude/
28+
29+
# Bootstrap output + upstream Termux clone (fetched on demand by rebuild-with-prefix.sh)
30+
bootstrap/dist/
31+
bootstrap/work/
32+
bootstrap/termux-packages/
33+
34+
# OS
35+
.DS_Store
36+
Thumbs.db
37+
38+
# Editor
39+
.vscode/
40+
*.swp

LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
MIT License
2+
3+
Copyright (c) 2026 CelestialCreator
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
---
24+
25+
Third-party components fetched at build/runtime keep their own licenses, including:
26+
- Termux bootstrap binaries — https://github.com/termux/termux-packages (GPL-3.0)
27+
- Pi coding agent — https://github.com/earendil-works/pi (MIT)
28+
- pi-mobile / pi-webserver — https://github.com/espennilsen/pi (MIT)

README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Pocket Pi
2+
3+
A [Pi coding agent](https://pi.dev/) shipped as an Android APK. No Termux install, no shell setup — install the APK, paste an LLM key, chat.
4+
5+
> POC, fast-tracked. We bundle Termux's Linux runtime inside an Android app so the team can try a single-tap Pi install on a phone. Whether this approach is worth productizing (vs. building a proper native Android client) is the open question — that's what the POC is for.
6+
7+
## Install (team testers)
8+
9+
1. Grab the latest APK from the [Releases page](https://github.com/CelestialCreator/pocket-pi/releases/latest).
10+
2. Sideload — tap the APK on the phone (allow install from unknown sources for your browser/file manager), or `adb install pocket-pi-vX.Y.Z.apk`.
11+
3. Open the app. First launch runs the bootstrap (3–5 min on Wi-Fi: extracts Termux, installs Node + npm packages, registers Pi extensions).
12+
4. When you see "Send a message to your Pi agent" — tap the ⚙ at the top right.
13+
5. Paste at least one provider API key (NVIDIA NIM is free; OpenRouter / OpenAI / Anthropic / Groq also wired up), then tap **Save keys****Restart Pi**.
14+
6. Tap **Re-run setup** once if extensions are missing (it's idempotent and safe).
15+
7. Chat away.
16+
17+
If anything wedges, tap **⚙ → Re-run setup**. As a last resort, force-stop the app from Android Settings and reopen — the install state on disk is preserved.
18+
19+
## What's inside
20+
21+
| Layer | Component |
22+
|---|---|
23+
| App shell | Android (Kotlin + Jetpack Compose) — `android/` |
24+
| Linux runtime | Termux bootstrap (Node 25, Python, git, ripgrep, openssl) — `bootstrap/` |
25+
| Chat UI | [`@e9n/pi-mobile`](https://www.npmjs.com/package/@e9n/pi-mobile) PWA served by [`@e9n/pi-webserver`](https://www.npmjs.com/package/@e9n/pi-webserver), rendered in a WebView |
26+
| Agent engine | [`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) |
27+
| Pi extensions | `pi-web-access`, `pi-subagents`, `oh-pi`, `@aliou/pi-guardrails`, `pi-mcp-adapter`, `pk-pi-hermes-evolve` |
28+
| Native config | Compose `ModalBottomSheet` with sections for API keys, AGENTS.md, models.json, Restart Pi, Re-run setup |
29+
| Providers wired | NVIDIA NIM (free), OpenRouter, OpenAI, Anthropic, Groq — keys go to `~/.config/<provider>/api-key`; the model registry lives in `~/.pi/agent/models.json` |
30+
31+
## Repo layout
32+
33+
```
34+
.
35+
├── android/ Gradle Android project for the APK
36+
├── bootstrap/ Termux bootstrap zip generator + postinstall
37+
│ ├── build-bootstrap.sh Layer our payload on upstream Termux's aarch64 zip
38+
│ ├── postinstall.sh First-run install: apt, npm, pip, pi install loop
39+
│ ├── npm-packages.txt Pi engine + extensions + peer deps
40+
│ ├── packages.txt Termux apt packages
41+
│ ├── pip-packages.txt Python deps (dspy etc, best-effort)
42+
│ └── patches/ One-shot post-update patches (e.g. hermes-evolve)
43+
├── config/ Baked into the bootstrap at build time
44+
│ ├── AGENTS.md Always-on Pi context
45+
│ ├── models.json Provider/model registry (NVIDIA pre-filled)
46+
│ └── claude-bridge.json Wrapper config (legacy; not active)
47+
├── extensions/ Our own Pi extensions (TypeScript)
48+
│ ├── pi-termux-tools/ Phone surface tools (TTS, notify, share, camera)
49+
│ └── pi-skill-learner/ Hermes-style learning loop
50+
├── python/skill_learner_dspy DSPy reflection backend
51+
├── skills/ Pi Skills bundled into the bootstrap
52+
└── scripts/ Misc dev helpers
53+
```
54+
55+
## Build from source
56+
57+
```bash
58+
# 1. Bootstrap zip (produces bootstrap/dist/bootstrap-aarch64.zip, ~30M)
59+
cd bootstrap && ./build-bootstrap.sh aarch64
60+
61+
# 2. (Optional) the custom Pi extensions
62+
cd ../extensions/pi-termux-tools && pnpm install && pnpm build
63+
cd ../pi-skill-learner && pnpm install && pnpm build
64+
65+
# 3. APK
66+
cd ../../android && ./gradlew :app:assembleDebug
67+
# Output: android/app/build/outputs/apk/debug/app-debug.apk (~67 MB)
68+
```
69+
70+
The current build uses `applicationId = com.termux` so the upstream Termux bootstrap binaries (which bake in the path `/data/data/com.termux/files/usr`) work without recompiling. To ship under a real app id, run `bootstrap/rebuild-with-prefix.sh` (Docker, 4–12 h on Apple Silicon) to produce a bootstrap pinned to a custom prefix, then flip `applicationId` in `android/app/build.gradle.kts`.
71+
72+
## What works / what doesn't (v0.1)
73+
74+
| | Status |
75+
|---|---|
76+
| Single-APK install on aarch64 phones ||
77+
| 8 Pi extensions registered, 17 tools online ||
78+
| NVIDIA NIM + OpenRouter end-to-end (chat, tool use, cost tracking) ||
79+
| Native ⚙ Config sheet — keys, AGENTS.md, models.json, restart, re-run setup ||
80+
| Recovery UI when pi-webserver doesn't bind within 15s ||
81+
| Slash commands `/session`, `/clear`, `/model`, `/threads` intercepted client-side | not yet — currently sent to LLM as text |
82+
| Chat history persistence across tab switches | not yet — JSONL on disk, no resume |
83+
| `applicationId``com.termux` | not yet — requires custom bootstrap rebuild |
84+
| Cosmetic phantom-icon row on some Android WebView builds | accepted — known compositor artifact, no functional impact |
85+
86+
## License
87+
88+
MIT. Third-party runtime components keep their own licenses (Termux GPL, Pi MIT) — see `LICENSE` for the list.
89+
90+
## Status
91+
92+
v0.1 — POC. The Termux-fork-inside-an-APK approach works. Whether to invest in productizing it (custom prefix bootstrap, real applicationId, signed release builds, Play Store, etc.) or rewrite this as a proper native Android client that talks to Pi over the network is the question this POC is meant to inform.

android/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.gradle/
2+
build/
3+
.idea/
4+
local.properties
5+
*.iml
6+
captures/
7+
.cxx/
8+
app/build/

android/app/build.gradle.kts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.kotlin.compose)
5+
alias(libs.plugins.kotlin.serialization)
6+
}
7+
8+
android {
9+
namespace = "com.zosma.pocketpi"
10+
compileSdk = 34
11+
12+
defaultConfig {
13+
// TEMP for emulator validation. The upstream Termux bootstrap zip
14+
// ships compiled binaries (apt, dpkg, busybox helpers) with the path
15+
// /data/data/com.termux/files/usr baked in. Until we rebuild a
16+
// custom bootstrap pinned to our real prefix, run as com.termux so
17+
// that Android's chosen filesDir matches what the binaries expect.
18+
// Production build: applicationId = "com.zosma.pocketpi" + a custom
19+
// bootstrap built via termux-packages/scripts/build-bootstraps.sh.
20+
applicationId = "com.termux"
21+
minSdk = 24
22+
// CRITICAL: Termux itself caps targetSdk at 28. Android 10+ enforces
23+
// W^X on the app's private data directory when targetSdk >= 29 — any
24+
// execve() of a file under /data/user/0/<pkg>/files/ is blocked with
25+
// EACCES regardless of file mode bits. Capping at 28 keeps the
26+
// legacy semantics that allow exec from app-data, which Pi (and the
27+
// entire Termux runtime) relies on.
28+
targetSdk = 28
29+
versionCode = 1
30+
versionName = "0.1.0"
31+
32+
ndk { abiFilters += listOf("arm64-v8a") }
33+
}
34+
35+
// Pocket Pi mirrors Termux's install layout — the bootstrap zip extracts
36+
// into /data/data/com.zosma.pocketpi/files/usr at first launch.
37+
buildTypes {
38+
release {
39+
isMinifyEnabled = false
40+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
41+
}
42+
debug { /* defaults */ }
43+
}
44+
45+
// Ship the bootstrap zip as a raw asset; PocketPiBootstrap unzips on first
46+
// run. The build script in bootstrap/build-bootstrap.sh produces this.
47+
sourceSets["main"].assets.srcDir("../../bootstrap/dist")
48+
49+
compileOptions {
50+
sourceCompatibility = JavaVersion.VERSION_17
51+
targetCompatibility = JavaVersion.VERSION_17
52+
}
53+
kotlinOptions { jvmTarget = "17" }
54+
55+
packaging {
56+
resources.excludes += listOf("/META-INF/{AL2.0,LGPL2.1}", "/META-INF/INDEX.LIST")
57+
// Required when AndroidManifest sets extractNativeLibs="true". Pocket
58+
// Pi needs that flag so .so files end up in nativeLibraryDir with
59+
// executable permission, which is the only reliable way to ship
60+
// executables to the device on Android 6+.
61+
jniLibs.useLegacyPackaging = true
62+
}
63+
}
64+
65+
dependencies {
66+
implementation(libs.androidx.core.ktx)
67+
implementation(libs.androidx.lifecycle.runtime)
68+
implementation(libs.androidx.activity.compose)
69+
implementation(platform(libs.androidx.compose.bom))
70+
implementation(libs.androidx.ui)
71+
implementation(libs.androidx.ui.graphics)
72+
implementation(libs.androidx.ui.tooling.preview)
73+
implementation(libs.androidx.material3)
74+
implementation(libs.androidx.datastore)
75+
implementation(libs.androidx.work)
76+
implementation(libs.kotlinx.serialization.json)
77+
implementation(libs.kotlinx.coroutines.android)
78+
}

android/app/proguard-rules.pro

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Pocket Pi proguard rules.
2+
# Compose / Material handle their own keep rules via the Compose plugin.
3+
-keep class kotlinx.serialization.** { *; }
4+
-keepclassmembers,allowshrinking class kotlinx.serialization.** { *; }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
7+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
8+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
9+
<uses-permission android:name="android.permission.WAKE_LOCK" />
10+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
11+
<uses-permission android:name="android.permission.CAMERA" />
12+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
13+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
14+
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
15+
16+
<application
17+
android:name=".PocketPiApp"
18+
android:label="@string/app_name"
19+
android:icon="@mipmap/ic_launcher"
20+
android:roundIcon="@mipmap/ic_launcher_round"
21+
android:theme="@style/Theme.PocketPi"
22+
android:supportsRtl="true"
23+
android:allowBackup="false"
24+
android:requestLegacyExternalStorage="false"
25+
android:extractNativeLibs="true"
26+
android:networkSecurityConfig="@xml/network_security_config"
27+
tools:targetApi="34"
28+
tools:replace="android:extractNativeLibs">
29+
30+
<activity
31+
android:name=".MainActivity"
32+
android:exported="true"
33+
android:windowSoftInputMode="adjustResize"
34+
android:theme="@style/Theme.PocketPi">
35+
<intent-filter>
36+
<action android:name="android.intent.action.MAIN" />
37+
<category android:name="android.intent.category.LAUNCHER" />
38+
</intent-filter>
39+
</activity>
40+
41+
42+
<service
43+
android:name=".service.PocketPiService"
44+
android:exported="false"
45+
android:foregroundServiceType="dataSync" />
46+
47+
<provider
48+
android:name="androidx.core.content.FileProvider"
49+
android:authorities="${applicationId}.fileprovider"
50+
android:exported="false"
51+
android:grantUriPermissions="true">
52+
<meta-data
53+
android:name="android.support.FILE_PROVIDER_PATHS"
54+
android:resource="@xml/file_paths" />
55+
</provider>
56+
</application>
57+
</manifest>

0 commit comments

Comments
 (0)