Skip to content

About

Opencode compatible mobile app

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Open Remote Code Mobile

The app's display name is Open Remote Code, its Dart package is openremotecode, and its Android/iOS application identifier is com.openremotecode.app. This is a separate installation from the previous com.example.opremote development app and requires fresh login and pairing. Existing installations can remain installed.

The hosted API/relay origin for internal testing is https://api.openremotecode.com. It is not a development default or a claim of a public release. See Naming and Service Hosts for the landing page and API/relay hosts.

Native Flutter client with real server authentication, secure session restoration, a Chats home with connection/folder filters, and short-code pairing. The top-right menu offers Connect (remote-machine management) and Settings. Conversations open directly from the combined list; Back restores the list and its search/filter. The main color is #DBF4AD. UI widgets depend on view models and repositories; networking, storage, and cryptography stay outside widgets.

Chat rows show a spinner while working, a checkmark for an unread successful completion, and an independent Draft label for unsent text. New unsent chats have a local resumable draft row; draft text is not previewed or persisted to disk. Viewed state follows OpenCode across its clients and clears only when the latest response is rendered in the foreground. Use an updated connector for live list metadata; older connectors remain usable without invented unread indicators. See chat activity semantics.

Local Android development

Start the local Open Remote Code service on port 8080, then run from mobile/:

./tool/run_local_android.sh emulator-5554

This checks server readiness, runs adb reverse tcp:8080 tcp:8080 for the selected device, and launches Flutter. The debug default remains http://127.0.0.1:8080. Loopback means the Android device itself; without ADB forwarding, login reports that it cannot reach the server. Reapply forwarding after an emulator or ADB restart. A USB-connected Android device can use its device ID instead.

For an already running app:

adb -s emulator-5554 reverse tcp:8080 tcp:8080

Then retry sign-in. Local HTTP is allowed only in debug builds and only for 127.0.0.1, localhost, or [::1]. Release/profile builds require HTTPS. iOS simulator loopback reaches the Mac hosting the simulator; physical iPhones need a reachable HTTPS development service. iOS builds and Keychain behavior must be verified on macOS/Xcode before shipping.

Appearance

The app follows the device's light/dark setting by default. Choose System, Light, or Dark under Settings → Appearance to change it. The choice is saved on this device without retaining conversation content. It applies to login, chat lists, conversations, Markdown, forms, and connection/status controls.

Server selection

Tap login background or branding six times, with consecutive taps no more than two seconds apart. The sixth tap opens the server editor. Configuration has no visible shortcut. The read-only signing destination is shown on login so the user knows where their credentials go.

Save a normalized HTTPS origin without paths, embedded credentials, query, or fragment. Only this nonsecret preference uses ordinary preferences. A saved origin overrides REMOTE_SERVER_URL, which overrides the debug default. Changing the origin clears the login form. There is no production endpoint bundled.

flutter run --dart-define=REMOTE_SERVER_URL=https://remote.example.com

Google sign-in

Off unless the build is configured, so the action is absent rather than present and unusable. Pass the OAuth web client ID as GOOGLE_SERVER_CLIENT_ID; both platforms request their ID token for it, and it must match one entry in the server's GOOGLE_OAUTH_AUDIENCES. GOOGLE_IOS_CLIENT_ID is only needed when the iOS client ID is not already in Info.plist. Client IDs are public identifiers, not secrets.

flutter run \
  --dart-define=REMOTE_SERVER_URL=https://remote.example.com \
  --dart-define=GOOGLE_SERVER_CLIENT_ID=111111111111-web.apps.googleusercontent.com \
  --dart-define=GOOGLE_IOS_CLIENT_ID=222222222222-ios.apps.googleusercontent.com

Android needs no file changes; register an Android OAuth client for package com.openremotecode.app with the SHA-1 of each signing certificate you use, debug included, or the SDK returns no token. iOS needs the reversed iOS client ID as a URL scheme so Google can return to the app; ios/Runner/Info.plist carries the block as a comment to fill in, since a wrong scheme breaks the callback silently and the value is per-deployment. See the Google Sign-In section of server/README.md for registering the clients.

One button, Continue with Google, appears on both the sign-in and create-account screens, because it is one operation: the server creates the account when the assertion reaches no existing one. It never leads to the verification screen — Google has already proved the address. Cancelling the account chooser is not an error and shows no banner.

If the server answers An account already uses this email address, that account predates email verification and was never proved to belong to its registrant. Sign in with the password instead; linking automatically would hand the account to whoever controls the address today.

Authentication and connections

Use an existing account on the selected service. Login calls the public API; incorrect credentials never open the workspace. The app saves the rotating refresh credential in Android Keystore-backed encrypted storage or device-only iOS Keychain. Passwords are never persisted; access tokens remain in memory. A fresh launch rotates the saved credential with the same server before opening the home screen. Offline restoration keeps the credential for explicit retry and stays on login. Settings provides account information and Sign out.

Each connection has a three-dot menu with Rename and Delete. Rename saves the display name on the server, so it survives refresh and is visible to your other devices. Delete and revoke confirms account-wide revocation on the server before removing the saved connection. It disconnects all devices using that connector; local OpenCode chats remain. Failed requests keep the connection for retry. The account must own the connector, but this device does not need its pairing keys to revoke it.

The Connect screen lists authorized connectors, puts online peers first, and retains offline peers. Unknown presence and identities requiring verification are labeled explicitly. Add connection accepts one short typed or pasted code. Compare the safety code with OpenCode. If they match, approve the phone in OpenCode's dialog, then explicitly confirm in the app; confirmation waits for that approval. An existing account connection may say Verification required when this device has never paired with it. The current plugin has no add-another-device action while already paired; that flow remains to be implemented. Do not treat account login as connector verification or revoke an existing connector merely to dismiss the label. A changed saved identity is displayed as a separate warning. The connector must also review the claim before confirmation succeeds. Private device keys, device credentials, and pinned public identities are scoped to both the server and account and stored securely. Existing keys cannot be silently replaced. Presence uses single-use relay tickets and suspends in the background. Chat/session browsing and encrypted message exchange are available when a trusted connector is online; cards also show inventory and presence.

Release versions

Pushing a tag such as v1.2.3 runs the release workflow, which builds the signed APK and AAB with version name 1.2.3 and a build number equal to the workflow run number (Android's version code must only ever increase). The tag must be exactly vMAJOR.MINOR.PATCH; anything else fails the build before it starts. The version: in pubspec.yaml is only the default for local and CI test builds, so it does not need to be edited for a release. The workflow runs mobile analysis and tests first, requires the release signing secrets, and refuses a release destination other than the hosted HTTPS API. Before uploading, verify the generated version code exceeds the current Play Console version and confirm that Play recognizes the expected upload key.

Verification

Chat title taps open the existing Chat details route, including a read-only MCP servers section. It uses a separately encrypted project subscription, renews only while details are visible and foregrounded, and labels stale/offline status without live green dots. No MCP tool, authentication, or configuration actions are exposed. See MCP status for the wire contract, lifecycle, threat analysis, and tests.

Use Flutter with Dart 3.13.2 or a compatible version. Android requires the SDK, JDK, accepted licenses, and an emulator/device; iOS requires macOS and Xcode. The lockfile selects secure storage 10.3.1, compatible with Android compile SDK 36; version 11 requires a newer SDK target.

flutter pub get
flutter analyze
flutter test
flutter build apk --debug
./tool/test_local_android.sh emulator-5554

The integration test requires a disposable local server with registration enabled. It creates a random test account and builds a separate Android application (com.openremotecode.app.integration) with its own Keystore/data sandbox. It verifies login/restore/rotation/logout, pairs a simulated connector, verifies live presence and persisted identity, and revokes its connector and sessions. Test accounts remain in the development database; no credentials are printed or saved in source. The launcher also sets --no-uninstall: this Flutter SDK otherwise uninstalls the test application after execution. Do not run plain native flutter test integration_test/... on the normal app ID; uninstall deletes its login and private pairing keys even when test storage keys have a prefix. Refreshing, hot restart, and closing/reopening the normal app should preserve pairing. Uninstall/clear-data creates a new device and requires pairing again.

Read architecture requirements, design conventions, and authentication and pairing threat analysis. Rendered Flutter design previews (synthetic sample data): login, server address, registration, email verification, empty connections, navigation, code entry, safety verification, connections, settings, projects, chats, conversation, sub-agent task, child conversation, and conversations with voice available or voice unavailable. Regenerate them all with flutter test tool/render_previews_test.dart (PREVIEW_DIR overrides the output directory); re-run it after any UI change. The root pnpm scripts do not run Flutter checks.

Voice notes and calls

Keep Hex running on the paired computer with a GGUF speech model loaded (Hex 3.3.0 / streaming API 2 is the integration target). Update the Open Remote Code plugin as well as the mobile app; older connectors do not advertise voice support. No extra server address, public port, or cloud speech credentials are needed. Both voice buttons stay hidden until the connector confirms that Hex's local streaming API is running. Availability refreshes on entering/resuming a chat, after reconnecting, and every ten seconds while the chat is visible. Stopping Hex hides the buttons and stops capture; starting it restores them on the next check. An older connector without voice.status keeps voice controls hidden. A running API can still be busy or lack a loaded model; those conditions show a recoverable error when starting voice.

Tap the microphone beside Send to record a voice note. Its level reacts to speech; Stop opens a playback preview with Send and Discard. Sending keeps an audio bubble in the conversation and uses local transcription as the agent's input, without replacing your typed draft. Transcribe reveals the transcript beneath the bubble; it does not submit another prompt. Up to eight recordings remain in workspace memory. After an app restart or eviction, the transcript remains in OpenCode but the bubble says audio is unavailable on this device. Audio is never cached to disk. See voice-note presentation and threat analysis.

Tap Call in the chat header to keep listening and send completed utterances automatically after a pause. The call has Mute and Hang up; OpenCode replies as text. Mute discards unfinished speech. Calls stop when leaving the chat, backgrounding, disconnecting, or waiting for an explicit permission or question response. Speech detection is based on audio level and pauses; use quick notes when you want to review the recording before sending.

Recordings remain in memory and are streamed end-to-end encrypted to the local connector. See the voice contract for limits, failure behavior and the threat analysis.

About

Opencode compatible mobile app

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages