opencode-mobile
v1.2.9
Published
Mobile push notification plugin for OpenCode - enables push notifications via Expo for mobile devices
Maintainers
Readme
OpenCode Mobile Plugin
A mobile push notification plugin for OpenCode, built with TypeScript and Bun runtime. The plugin provides push notification capabilities through Expo Push Notifications service, with tunnel management for mobile device connectivity.
Features
- Push Notifications: Send push notifications via Expo Push Notifications service
- Tunnel Management: Support for Cloudflare and ngrok tunnels for mobile device connectivity
- Reverse Proxy: Built-in HTTP proxy server for handling notifications
- QR Code Support: Generate QR codes for easy tunnel URL sharing
Prerequisites
- Bun runtime (v1.0+)
- TypeScript 5.0+
- Valid Expo account and project credentials
Installation
# Install dependencies
bun installUsage
Run the Main Plugin
bun run index.tsRun Push Notifications Directly
bun run push-notifications.tsBuild and Type-Check
# Type-check only (no emit)
npx tsc --noEmit
# Compile TypeScript to JavaScript
npx tsc
# Build and type-check
npm run buildLinting
npx eslint "**/*.ts" --fixTesting
bun test
bun test <test-file>.test.ts
bun test --test-name-pattern="test name"Project Structure
/Users/rodri/.config/opencode/opencode-mobile-plugin/
├── index.ts # Main barrel export
├── push-notifications.ts # Core plugin logic (main entry point)
├── tunnel-manager.ts # Tunnel management (Cloudflare/ngrok)
├── reverse-proxy.ts # HTTP proxy server
├── hello-world.ts # Example plugin
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
├── AGENTS.md # Agent instructions and project guidelines
└── dist/ # Compiled outputConfiguration
Environment Variables
OPENCODE_PORT: Port for the local server (default: 3000)EXPO_PROJECT_ID: Your Expo project IDTUNNEL_PROVIDER: Tunnel provider to use (cloudflareorngrok)
Tunnel Configuration
The plugin supports two tunnel providers:
- Cloudflare (
cloudflared): Zero-config tunnel with Cloudflare's secure infrastructure - ngrok: Popular ngrok tunnel with custom domain support
Code Style Guidelines
Imports
// Standard library - namespace imports
import * as fs from "fs";
import * as path from "path";
// External modules - named or default imports
import ngrok from "ngrok";
import qrcode from "qrcode";
// Types - use import type when only using types
import type { Plugin } from "@opencode-ai/plugin";
import type { TunnelConfig } from "./tunnel-manager";
// Group imports logically: types → external modules → internal modules
import type { Plugin } from "@opencode-ai/plugin";
import * as fs from "fs";
import * as path from "path";
import { startTunnel } from "./tunnel-manager";Formatting
- 2 spaces for indentation
- Single quotes for strings
- Semicolons at end of statements
- Trailing commas in multi-line objects/arrays
- Max line length: ~100 characters (soft limit)
Types
// Use interfaces for object shapes
interface PushToken {
token: string;
platform: "ios" | "android";
deviceId: string;
registeredAt: string;
}
// Use type aliases for unions/primitives
type NotificationHandler = (notification: Notification) => Promise<void>;
// Explicit return types for public functions
function loadTokens(): PushToken[] {
// ...
}
// Avoid `any` - use `unknown` with type guards when uncertain
function safeParse(data: unknown): Record<string, any> {
if (typeof data === "string") {
try {
return JSON.parse(data);
} catch {
return {};
}
}
return data as Record<string, any>;
}Naming Conventions
| Pattern | Convention | Example |
|---------|------------|---------|
| Constants | UPPER_SNAKE_CASE | TOKEN_FILE, BUN_SERVER_PORT |
| Functions/variables | camelCase | loadTokens, startTunnel |
| Interfaces/classes | PascalCase | PushToken, TunnelConfig |
| Private/internal | prefix with _ | _bunServer, _pluginInitialized |
| Booleans | prefix with is, has, should | isRunning, hasStarted |
Error Handling
// Always wrap async operations in try-catch
try {
await someAsyncOperation();
} catch (error: any) {
// Log errors with module prefix
console.error("[ModuleName] Error message:", error.message);
// Provide context in error messages
if (error.message?.includes("specific case")) {
console.error("[PushPlugin] Handle specific error:", error.message);
} else {
console.error("[PushPlugin] Unexpected error:", error.message);
}
}
// Handle specific error types when possible
if (error instanceof ValidationError) {
// Handle validation errors
}Console Logging
- Use module prefixes in all console output:
[PushPlugin],[Tunnel],[Proxy] - Use emojis for status indicators:
✅,❌,💡,ℹ️ - Log important steps and results
console.log('[PushPlugin] Starting...');
console.error('[PushPlugin] Failed:', error.message);
console.log(`[Tunnel] URL: ${url}`);
console.log('✅ Server started successfully');
console.log('❌ Connection failed:', error.message);Async/Await
// Use async/await over raw promises
async function startServer(): Promise<void> {
try {
await startProxy();
await startTunnel();
} catch (error) {
// handle error
}
}
// Never leave promises unhandled
// Use Promise.all() for parallel operations
const [result1, result2] = await Promise.all([
operation1(),
operation2(),
]);Plugin Architecture
Entry Points
- Main entry:
index.ts(barrel export) - Plugin entry:
push-notifications.ts(main plugin logic) - Compiled output:
dist/index.js
Plugin Interface
All plugins must export a function matching the Plugin type from @opencode-ai/plugin:
import type { Plugin } from "@opencode-ai/plugin";
export const MyPlugin: Plugin = async (ctx) => {
// Initialize plugin
return {
event: async ({ event }) => {
// Handle event
},
};
};
export default MyPlugin;Signal Handling
// Handle process signals for graceful shutdown
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
signals.forEach((signal) => {
process.on(signal, async () => {
await gracefulShutdown();
process.exit(0);
});
});Dependencies
@opencode-ai/plugin: Core plugin interfacengrok: Ngrok tunnel providercloudflared: Cloudflare tunnel providerqrcode: QR code generationbun: Runtime environment
License
MIT License - see LICENSE file for details.
Contributing
- Fork the repository
- Create a feature branch
- Make your changes following the code style guidelines
- Run linting and tests
- Submit a pull request
Support
For issues and feature requests, please use the GitHub issue tracker.
