Skip to content

Commit 62f8d25

Browse files
authored
Merge pull request #1 from ayagmar/develop
android client for pi rpc
2 parents cece08a + cad86f3 commit 62f8d25

File tree

169 files changed

+32731
-1
lines changed

Some content is hidden

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

169 files changed

+32731
-1
lines changed

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = space
7+
indent_size = 4
8+
insert_final_newline = true
9+
max_line_length = 120
10+
trim_trailing_whitespace = true
11+
12+
[*.{kt,kts}]
13+
ktlint_code_style = ktlint_official
14+
ij_kotlin_allow_trailing_comma = true
15+
ij_kotlin_allow_trailing_comma_on_call_site = true
16+
ktlint_function_naming_ignore_when_annotated_with = Composable

.github/workflows/ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: ci
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- master
8+
9+
jobs:
10+
android:
11+
name: Android
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Setup JDK 21
17+
uses: actions/setup-java@v4
18+
with:
19+
distribution: temurin
20+
java-version: 21
21+
cache: gradle
22+
23+
- name: Lint & Test
24+
run: ./gradlew :app:check
25+
26+
bridge:
27+
name: Bridge
28+
runs-on: ubuntu-latest
29+
defaults:
30+
run:
31+
working-directory: bridge
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- name: Setup pnpm
36+
uses: pnpm/action-setup@v4
37+
with:
38+
version: 9
39+
40+
- name: Setup Node.js
41+
uses: actions/setup-node@v4
42+
with:
43+
node-version: 22
44+
cache: pnpm
45+
cache-dependency-path: bridge/pnpm-lock.yaml
46+
47+
- name: Install dependencies
48+
run: pnpm install --frozen-lockfile
49+
50+
- name: Lint, Typecheck & Test
51+
run: pnpm check

.gitignore

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,20 @@ hs_err_pid*
2424
replay_pid*
2525

2626
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
27-
.kotlin/
27+
.kotlin/
28+
29+
# Gradle
30+
.gradle/
31+
**/build/
32+
**/bin/
33+
34+
# Android
35+
local.properties
36+
.idea/
37+
*.iml
38+
39+
# Keep Gradle wrapper binary
40+
!gradle/wrapper/gradle-wrapper.jar
41+
42+
#env files
43+
.env

README.md

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# Pi Mobile
2+
3+
An Android client for the [Pi coding agent](https://github.com/badlogic/pi-mono). Control your coding sessions from your phone over Tailscale.
4+
5+
## What This Does
6+
7+
Pi runs on your laptop. This app lets you:
8+
- Browse and resume coding sessions from anywhere
9+
- Chat with the agent, send prompts, abort, steer, and follow up
10+
- Discover slash commands from an in-app command palette
11+
- View streaming thinking/tool blocks with collapse/expand controls
12+
- Open a built-in bash dialog (run/abort/history/copy output)
13+
- Inspect session stats and pick models from an advanced model picker
14+
- Attach images to prompts
15+
- Navigate session tree branches in-place (jump+continue) and fork from selected entries
16+
- Switch between projects (different working directories)
17+
- Handle extension dialogs (confirmations, inputs, selections)
18+
19+
The connection goes over Tailscale, so it works anywhere without port forwarding.
20+
21+
## Architecture
22+
23+
```
24+
Phone (this app) <--Tailscale--> Laptop (bridge) <--local--> pi --mode rpc
25+
```
26+
27+
The bridge is a small Node.js service that translates WebSocket to pi's stdin/stdout JSON protocol. The app connects to the bridge, not directly to pi.
28+
29+
## Documentation
30+
31+
- [Documentation index](docs/README.md)
32+
- [Codebase guide](docs/codebase.md)
33+
- [Custom extensions](docs/extensions.md)
34+
- [Bridge protocol reference](docs/bridge-protocol.md)
35+
- [Testing guide](docs/testing.md)
36+
37+
> Note: `docs/ai/` contains planning/progress artifacts used during development. User-facing and maintenance docs live in the top-level `docs/` files above.
38+
39+
## Setup
40+
41+
### 1. Laptop Setup
42+
43+
Install pi if you haven't:
44+
```bash
45+
npm install -g @mariozechner/pi-coding-agent
46+
```
47+
48+
Clone and start the bridge:
49+
```bash
50+
git clone https://github.com/yourusername/pi-mobile.git
51+
cd pi-mobile/bridge
52+
pnpm install
53+
pnpm start
54+
```
55+
56+
The bridge binds to `127.0.0.1:8787` by default. Set `BRIDGE_HOST` to your laptop Tailscale IP to allow phone access (avoid `0.0.0.0` unless you enforce firewall restrictions). It spawns pi processes on demand per working directory.
57+
58+
### 2. Phone Setup
59+
60+
Install the APK or build from source:
61+
```bash
62+
./gradlew :app:assembleDebug
63+
adb install app/build/outputs/apk/debug/app-debug.apk
64+
```
65+
66+
### 3. Connect
67+
68+
1. Add a host in the app:
69+
- Host: your laptop's Tailscale MagicDNS hostname (`<device>.<tailnet>.ts.net`)
70+
- Port: 8787 (or whatever the bridge uses)
71+
- Token: set this in bridge/.env as `BRIDGE_AUTH_TOKEN`
72+
73+
2. The app will fetch your sessions from `~/.pi/agent/sessions/`
74+
75+
3. Tap a session to resume it
76+
77+
## How It Works
78+
79+
### Sessions
80+
81+
Sessions are grouped by working directory (cwd). Each session is a JSONL file in `~/.pi/agent/sessions/--path--/`. The bridge reads these files directly since pi's RPC doesn't have a list-sessions command.
82+
83+
### Process Management
84+
85+
The bridge manages one pi process per cwd:
86+
- First connection to a project spawns pi (with small internal extensions for tree in-place navigation parity and mobile workflow commands)
87+
- Process stays alive with idle timeout
88+
- Reconnecting reuses the existing process
89+
- Crash restart with exponential backoff
90+
91+
### Message Flow
92+
93+
```
94+
User types prompt
95+
96+
App sends WebSocket → Bridge
97+
98+
Bridge writes to pi stdin (JSON line)
99+
100+
pi processes, writes events to stdout
101+
102+
Bridge forwards events → App
103+
104+
App renders streaming text/tools
105+
```
106+
107+
## Chat UX Highlights
108+
109+
- **Thinking blocks**: streaming reasoning appears separately and can be collapsed/expanded.
110+
- **Tool cards**: tool args/output are grouped with icons and expandable output.
111+
- **Edit diff viewer**: `edit` tool calls show before/after content.
112+
- **Command palette**: insert slash commands quickly from the prompt field menu, including extension-driven mobile workflows.
113+
- **Bash dialog**: execute shell commands with timeout/truncation handling and history.
114+
- **Session stats sheet**: token/cost/message counters and session path.
115+
- **Model picker**: provider-aware searchable model selection.
116+
- **Tree navigator**: inspect branch points, jump in-place, or fork from chosen entries.
117+
118+
## Troubleshooting
119+
120+
### Can't connect
121+
122+
1. Check Tailscale is running on both devices
123+
2. Verify the bridge is running: `curl http://100.x.x.x:8787/health` (only if `BRIDGE_ENABLE_HEALTH_ENDPOINT=true`)
124+
3. Check the token matches exactly (BRIDGE_AUTH_TOKEN)
125+
4. Prefer the laptop's MagicDNS hostname (`*.ts.net`) over raw IP literals
126+
127+
### Sessions don't appear
128+
129+
1. Check `~/.pi/agent/sessions/` exists on laptop
130+
2. Verify the bridge has read permissions
131+
3. Check bridge logs for errors
132+
133+
### Streaming is slow/choppy
134+
135+
1. Check logcat for `PerfMetrics` - see actual timing numbers
136+
2. Look for `FrameMetrics` jank warnings
137+
3. Verify WiFi/cellular connection is stable
138+
4. Try closer to the laptop (same room)
139+
140+
### App crashes on resume
141+
142+
1. Check logcat for out-of-memory errors
143+
2. Large session histories can cause issues
144+
3. Try compacting the session first: `/compact` in pi, then resume
145+
146+
## Development
147+
148+
### Project Structure
149+
150+
```
151+
app/ - Android app (Compose UI, ViewModels)
152+
core-rpc/ - RPC protocol models and parsing
153+
core-net/ - WebSocket transport and connection management
154+
core-sessions/ - Session caching and repository
155+
bridge/ - Node.js bridge service
156+
```
157+
158+
### Running Tests
159+
160+
```bash
161+
# Android tests
162+
./gradlew test
163+
164+
# Bridge tests
165+
cd bridge && pnpm test
166+
167+
# All quality checks
168+
./gradlew ktlintCheck detekt test
169+
```
170+
171+
### Logs to Watch
172+
173+
```bash
174+
# Performance metrics
175+
adb logcat | grep "PerfMetrics"
176+
177+
# Frame jank during streaming
178+
adb logcat | grep "FrameMetrics"
179+
180+
# General app logs
181+
adb logcat | grep "PiMobile"
182+
183+
# Bridge logs (on laptop)
184+
pnpm start 2>&1 | tee bridge.log
185+
```
186+
187+
## Configuration
188+
189+
### Bridge Environment Variables
190+
191+
Create `bridge/.env`:
192+
193+
```env
194+
BRIDGE_HOST=0.0.0.0 # Use 0.0.0.0 to accept Tailscale connections
195+
BRIDGE_PORT=8787 # Port to listen on
196+
BRIDGE_AUTH_TOKEN=your-secret # Required authentication token
197+
BRIDGE_PROCESS_IDLE_TTL_MS=300000 # 5 minutes idle timeout
198+
BRIDGE_LOG_LEVEL=info # debug, info, warn, error, silent
199+
BRIDGE_ENABLE_HEALTH_ENDPOINT=true # set false to disable /health exposure
200+
```
201+
202+
### App Build Variants
203+
204+
Debug builds include logging and assertions. Release builds (if you make them) strip these for smaller size.
205+
206+
## Security Notes
207+
208+
- Token auth is required - don't expose the bridge without it
209+
- Token comparison is hardened in the bridge (constant-time hash compare)
210+
- The bridge binds to localhost by default; explicitly set `BRIDGE_HOST` to your Tailscale IP for remote access
211+
- Avoid `0.0.0.0` unless you intentionally expose the service behind strict firewall/Tailscale policy
212+
- `/health` exposure is explicit via `BRIDGE_ENABLE_HEALTH_ENDPOINT` (disable it for least exposure)
213+
- Android cleartext traffic is scoped to `localhost` and Tailnet MagicDNS hosts (`*.ts.net`)
214+
- All traffic goes over Tailscale's encrypted mesh
215+
- Session data stays on the laptop; the app only displays it
216+
217+
## Limitations
218+
219+
- No offline mode - requires live connection to laptop
220+
- Session history currently loads in full on resume (no incremental pagination)
221+
- Tree navigation is MVP-level (functional, minimal rendering)
222+
- Mobile keyboard shortcuts vary by device/IME
223+
224+
## Testing
225+
226+
See [docs/testing.md](docs/testing.md) for emulator setup and testing procedures.
227+
228+
Quick start:
229+
```bash
230+
# Start emulator, build, install
231+
./gradlew :app:installDebug
232+
233+
# Watch logs
234+
adb logcat | grep -E "PiMobile|PerfMetrics"
235+
```
236+
237+
## License
238+
239+
MIT

0 commit comments

Comments
 (0)