Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/actions/setup-bun/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ description: "Setup Bun with caching and install dependencies"
runs:
using: "composite"
steps:
- name: Mount Bun Cache
if: ${{ runner.os == 'Linux' }}
uses: useblacksmith/stickydisk@v1
with:
key: ${{ github.repository }}-bun-cache-${{ runner.os }}
path: ~/.bun

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json

- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v4
with:
path: ~/.bun
key: ${{ runner.os }}-bun-${{ hashFiles('package.json') }}-${{ hashFiles('bun.lockb', 'bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-${{ hashFiles('package.json') }}-

- name: Install dependencies
run: bun install
shell: bash
88 changes: 88 additions & 0 deletions .github/workflows/build-mobile-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Build Mobile APK

on:
push:
branches:
- feat/mobile-app
paths:
- "packages/mobile/**"
- ".github/workflows/build-mobile-apk.yml"
pull_request:
branches:
- dev
paths:
- "packages/mobile/**"
workflow_dispatch:
inputs:
branch:
description: "Branch name to build APK from"
required: true
default: "feat/mobile-app"
type: string

jobs:
build-android-apk:
name: Build Android APK
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/mobile

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.ref }}

- name: Setup Bun
uses: ./.github/actions/setup-bun

- name: Setup Expo
uses: expo/expo-github-action@v8
with:
expo-version: latest

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"

- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
packages/mobile/android/.gradle
key: ${{ runner.os }}-gradle-${{ hashFiles('packages/mobile/android/gradle/wrapper/gradle-wrapper.properties', 'packages/mobile/android/build.gradle', 'packages/mobile/android/app/build.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Run Expo prebuild for Android
run: npx expo prebuild --platform android --clean

- name: Build Android APK
working-directory: packages/mobile/android
run: ./gradlew assembleRelease

- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: opencode-mobile-android-${{ github.sha }}
path: packages/mobile/android/app/build/outputs/apk/release/app-release.apk
retention-days: 30

- name: Add build summary
run: |
echo "## 📱 Android APK Build Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ APK built successfully for Android" >> $GITHUB_STEP_SUMMARY
echo "🌿 Branch: ${{ github.event_name == 'workflow_dispatch' && inputs.branch || github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "📦 Artifact: opencode-mobile-android-${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "🔗 Download the APK from the workflow artifacts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** This workflow builds Android APK only. iOS and web platforms are not included." >> $GITHUB_STEP_SUMMARY
1,372 changes: 1,339 additions & 33 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
],
"overrides": {
"@types/bun": "catalog:",
"@types/node": "catalog:"
"@types/node": "catalog:",
"@types/react": "~19.1.10"
},
"patchedDependencies": {
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
Expand Down
34 changes: 34 additions & 0 deletions packages/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Expo
.expo/
dist/
web-build/

# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# Node
node_modules/

# iOS
ios/

# Android
android/

# Environment
.env
.env.local

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
59 changes: 59 additions & 0 deletions packages/mobile/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Mobile App - AGENTS.md

## Overview

React Native / Expo mobile client for opencode. Connects to an opencode server instance via HTTP + SSE for real-time updates.

## Architecture

```
packages/mobile/
├── app/ # Expo Router file-based routing
│ ├── (tabs)/ # Tab navigation (sessions, connections, settings)
│ ├── session/[id].tsx # Chat screen
│ └── connection/ # Add/edit connection screens
├── src/
│ ├── components/ # Reusable UI components
│ │ ├── markdown/ # Markdown renderer (wraps react-native-marked)
│ │ └── AuthGate.tsx # Biometric auth gate
│ ├── lib/
│ │ ├── sdk.ts # HTTP + SSE client for opencode server API
│ │ └── types.ts # Re-exported types
│ └── stores/ # Zustand state stores
│ ├── sessions.ts # Session list, messages, parts
│ ├── connections.ts # Server connections, client lifecycle
│ ├── events.ts # SSE event stream, status tracking, permissions, questions
│ └── auth.ts # Biometric auth
```

## Key Patterns

- **SSE for real-time**: The `events.ts` store connects to `/global/event` and dispatches to other stores
- **Fire-and-forget sends**: `sendMessage` posts to the API but doesn't await response; SSE events drive all UI updates
- **Session status**: Derived from `session.status` events (`idle`/`busy`/`retry`) + last part type for status text
- **Markdown**: `react-native-marked` wrapped in our own `Markdown` component with custom `CodeBlock` (copy button). Designed to be swappable/publishable later.

## Style Guide

Follow the root repo AGENTS.md style guide:

- Prefer `const` over `let`
- Avoid `else` statements, use early returns
- Prefer single-word variable names
- Avoid `try/catch` where possible
- Avoid `any` type
- Use Bun APIs where applicable (for scripts, not in RN runtime)

## Running

```bash
cd packages/mobile
bun install
bun start # Expo dev server
bun run ios # iOS simulator
bun run android # Android emulator
```

## Connecting

Run `opencode serve --hostname 0.0.0.0 --port 4096` on your machine, then add a connection in the app with your machine's local IP and port 4096.
116 changes: 116 additions & 0 deletions packages/mobile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# OpenCode Mobile (Android)

A React Native / Expo app for connecting to OpenCode servers from your Android phone.

**Note:** This app is currently configured for Android only. iOS and web platforms are not supported in this build.

## Features

- **Multiple Connection Types**: Connect via local network, tunnels (Cloudflare/ngrok), or cloud-hosted instances
- **Secure Authentication**: Biometric authentication support (fingerprint/face unlock on Android)
- **Session Management**: View, create, and manage coding sessions
- **Real-time Chat**: Stream responses from your AI assistant
- **File Diff Viewer**: See what changes were made to your code

## Getting Started

### Prerequisites

- Node.js 18+
- Bun (recommended) or npm
- Android device or emulator for testing

### Installation

```bash
# From the monorepo root
cd packages/mobile
bun install

# Start the development server
bun start
```

### Connecting to OpenCode

1. Start OpenCode in server mode on your machine:

```bash
OPENCODE_SERVER_PASSWORD=yourpassword opencode serve --hostname 0.0.0.0 --port 4096
```

2. Open the app and add a connection:
- **Local Network**: Use your machine's local IP (e.g., `http://192.168.1.100:4096`)
- **Tunnel**: Set up a Cloudflare Tunnel or ngrok and use the tunnel URL
- **Cloud**: Connect to a hosted OpenCode instance

## Building for Android

### GitHub Actions (Automated)

The repository includes a GitHub Actions workflow that automatically builds Android APKs:

- **Trigger**: Pushes to `feat/mobile-app` branch or manually via workflow dispatch
- **Output**: APK artifact available for download from the workflow run
- **Location**: `.github/workflows/build-mobile-apk.yml`

The workflow:
1. Sets up the build environment (Bun, Java, Android SDK, Expo)
2. Installs dependencies
3. Runs `expo prebuild --platform android` to generate native Android project
4. Builds the release APK using Gradle
5. Uploads the APK as a workflow artifact

**Note:** This workflow builds Android only. iOS and web platforms are not included.

### Local Android Build

Run the app on Android:

```bash
bun run android
# or use EAS Build
eas build --platform android
```

### Manual Android APK Build

If you want to build the APK locally:

```bash
# Generate the native Android project
npx expo prebuild --platform android --clean

# Build the APK
cd android
./gradlew assembleRelease

# The APK will be at: android/app/build/outputs/apk/release/app-release.apk
```

## Security

- Credentials are stored securely using `expo-secure-store` (Android Keystore)
- Optional biometric authentication for app access (fingerprint/face unlock)
- Optional biometric confirmation for sending messages
- All traffic should use HTTPS for non-local connections

## Architecture

```
packages/mobile/
├── app/ # Expo Router screens
│ ├── (tabs)/ # Tab navigation
│ ├── session/ # Session screens
│ └── connection/ # Connection management
├── src/
│ ├── components/ # Reusable components
│ ├── hooks/ # Custom hooks
│ ├── lib/ # SDK client & types
│ └── stores/ # Zustand state stores
└── assets/ # App icons & images
```

## Contributing

This is part of the OpenCode monorepo. See the root README for contribution guidelines.
36 changes: 36 additions & 0 deletions packages/mobile/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"expo": {
"name": "OpenCode",
"slug": "opencode-mobile",
"version": "1.0.0",
"orientation": "portrait",
"scheme": "opencode",
"userInterfaceStyle": "automatic",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#000000"
},
"android": {
"package": "ai.opencode.mobile",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#000000"
},
"permissions": ["android.permission.USE_BIOMETRIC", "android.permission.USE_FINGERPRINT"]
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": ["expo-router", "expo-secure-store", "expo-local-authentication"],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
},
"ios": {
"bundleIdentifier": "ai.opencode.mobile"
}
}
}
Loading
Loading