Contributing
Development setup, code style, testing, and pull request guidelines for TermPod contributors.
Prerequisites
- macOS 13+ (Ventura or later)
- Xcode 16+ (for iOS builds)
- Rust (latest stable via rustup)
- Node.js 20+ (LTS)
- pnpm 10+ (
npm install -g pnpm) - XcodeGen (
brew install xcodegen)
Setup
git clone https://github.com/termpod/termpod.git
cd termpod
pnpm install
cp .env.example .envEdit .env with your values (see Configuration).
Running Locally
# Desktop app (dev mode with hot reload)
pnpm dev:desktop
# Relay (local dev with Miniflare)
pnpm dev:relay
# Both at once
pnpm dev
# iOS app
pnpm ios:generate
open apps/ios/TermPod.xcodeprojCode Style
- TypeScript: Prettier for formatting (
pnpm format),tsc --noEmitfor type checking (pnpm lint) - Rust: Clippy (
cargo clippy) - Swift: Standard Swift conventions
User Configuration
The desktop app stores all user settings in ~/.termpod/ as JSON files (config.json, keybindings.json, workflows.json, and custom themes in themes/). See Desktop → Customization for details.
- Settings use
ConfigStore(apps/desktop/src/lib/configStore.ts) — file I/O with debounced writes and live file watching - Auth tokens stay in localStorage, not in
~/.termpod/ - On first launch, existing localStorage settings are migrated automatically
Making Changes
- Follow the Protocol Specification for any WebSocket changes
- Terminal data = binary frames (channel 0x00), control = JSON text frames
- Shared React components go in
packages/ui/, not duplicated per app - Test relay changes with Miniflare locally before deploying
- iOS project is generated via XcodeGen — edit
project.yml, not.xcodeprojdirectly
Testing P2P Transports
Local (Bonjour)
- Run the desktop app and iOS app on the same WiFi network
- iOS discovers the desktop via Bonjour automatically
- Transport badge shows "Local" (green)
- Terminal data flows directly over LAN (~1-5ms)
WebRTC
- Connect from a different network (e.g. cellular)
- WebRTC signaling flows through the Device WS; data flows P2P via DataChannel
- Transport badge shows "P2P" (blue)
- Falls back to relay after 30s timeout if STUN fails
Relay (fallback)
- If both Bonjour and WebRTC are unavailable, data flows through the relay
- Transport badge shows "Relay" (orange)
- In local dev (Miniflare),
POLAR_WEBHOOK_SECRETis not set, so the relay runs in self-hosted mode with all features unlocked
Security Guidelines
- Never send sensitive data in plaintext to the relay — all terminal data, session names, CWDs, and process names must use E2E encrypted channels
- Update both crypto implementations — Changes to
packages/protocol/src/crypto.tsmust be mirrored inapps/ios/TermPod/Services/CryptoService.swift - Reject plaintext on relay — The relay must reject any
0x00binary frames
Pull Requests
- Branch from
main - Make changes
- Run
pnpm lintand test locally - Open a PR with a clear description of what and why