Guidelines for agents operating on this Flutter SSH application codebase.
- Type: Flutter Desktop/Mobile Application (SSH Server & Client)
- State Management: Provider
- Persistence: shared_preferences (via ConfigService)
- Architecture: Separated models, services, providers, screens, and widgets.
# Install dependencies
flutter pub get
# Run development app
flutter run
# Build for specific platforms
flutter build apk # Android
flutter build ios # iOS
flutter build windows # Windows
flutter build macos # macOS
flutter build linux # Linux
flutter build web # Web (if supported)# Run static analysis (required before commits)
flutter analyze# Run all tests
flutter test
# Run tests in a specific directory
flutter test test/Version lives in pubspec.yaml as MAJOR.MINOR.PATCH+BUILD. User-facing semver is shown in Settings via package_info_plus; display name is kAppDisplayName in lib/constants/app_metadata.dart (PocketShell).
# Patch release (default): changelog bullet is the version tag (e.g. v1.0.1)
./scripts/release.ps1
# Preview bump without writing
./scripts/release.ps1 -DryRun
# Custom changelog note
./scripts/release.ps1 -Message "Fix connection timeout"
# Options: -Bump patch|minor|major, -NoPush, -SkipTestsRelease script steps: assert clean tree → bump pubspec semver and build number → prepend ## [X.Y.Z] - date to CHANGELOG.md → flutter pub get / analyze / test → commit chore(release): vX.Y.Z → annotated tag vX.Y.Z → push branch and tag.
- Use
package:prefix for all project imports. - Grouping: Dart SDK -> external packages -> project imports.
- Sort alphabetically within groups.
- Classes: PascalCase (e.g.,
SSHProvider). - Methods/Variables: camelCase (e.g.,
connectClient()). - Private members: Prefix with
_(e.g.,_client). - Constants:
kprefix (e.g.,kDefaultPort). - Files: snake_case (e.g.,
ssh_provider.dart).
- Explicit return types for methods.
- Prefer
finalovervar. - Use
constconstructors for static widgets and constant data.
lib/
├── models/ # Data Models (SSHProfile, SSHKey, KeyboardShortcut, Snippet)
├── services/ # Services (ConfigService, NetworkDiscoveryService, SSHKeyGenerator)
├── providers/ # State Management (SSHProvider, SettingsProvider, SnippetProvider)
├── screens/ # Main Screens (HomeScreen, SettingsScreen, SplashScreen)
├── widgets/ # Reusable Widgets (KeyManager, ProfileManager, LogViewer, forms)
└── main.dart # App Entry Point
connectClient({required SSHProfile profile})- Connect to a remote SSH server using a profile.startServer({required int port})- Start local SSH server.terminal- The current activeTerminalinstance (xterm).connectionLog- List of strings for displaying logs in UI.
themeMode- Current application theme (Light/Dark/System).accentColor- Primary application accent color.updateTheme(ThemeMode mode)- Change app theme.
snippets- List of saved command fragments.addSnippet(Snippet snippet)- Create new snippet.
- Always use ConfigService for persistence. Do not call
shared_preferencesdirectly from widgets. - Handle Async Safety: Use
try-catchfor all SSH and network operations. - Notify Listeners: Ensure
notifyListeners()is called in providers after state changes. - UI Decoupling: Keep business logic in providers/services; widgets should only handle display and user interaction.
- Clean Resources: Always implement
dispose()in providers to close sockets, PTYs, and timers. - Strict Typing: Maintain
strict-castsandstrict-raw-typescompliance.
- Prefer a native OpenCode HTTP API UI for the Agents tab over WebView or an external browser.
- Handle agent mode switching only through
/agentslash commands; do not add a separate agent picker UI. - Support model and provider configuration via slash commands (
/model,/models,/connect) plus a config sheet in the Agents tab chat toolbar. - Use the SFTP directory browser for agent project directory selection, not the local FilePicker.
- Target home-screen quick-connect widgets at Android only; the Agent widget should connect and resume the most recently updated session.
- Keep the Server tab optional behind a Settings toggle (
showServerTab). - Use PocketShell as the user-facing display name (display-only rename; keep
ssh_apppackage/id unchanged). - Primary usage is Android client connecting to Windows hosts for SSH and OpenCode agents.
- OpenCode config should use the connected server API (
getConfig/updateConfig) plus remote import from the Windows host over SSH, not local mobile config files. - Agents tab should use master-detail navigation on all screen sizes (hide session list when chat is open), not a side-by-side split on wide screens.
- Home navigation uses
AppTab(Client, Server, Agents, Logs) with anIndexedStack; Server is omitted whenshowServerTabis false. - OpenCode integrates through
opencode_apiandOpenCodeConnectionService; sessions are scoped by adirectoryquery param;getConfig/updateConfigexposed for server-side config. SSHProfileincludesagentPort(default 5000) anduseHttps;agentBaseUrlbuilds the OpenCode URL.AgentProvideris aChangeNotifierProxyProviderthat routesonLogtoSSHProvider.addLog.- Agent directory path persists in ConfigService as
agent_last_directory. - Android widgets (
SshQuickConnectWidget,AgentQuickConnectWidget) sync profiles viaWidgetProfileServiceand deep-link viasshapp://widget/ssh|agent?profileId=throughWidgetLaunchHandler. - Terminal styling is centralized in
TerminalStyleBuilder(font family, weight, style, size) and usesgoogle_fontsfor bold/italic variants. lib/utils/agent_session_utils.dartholds session sort helpers andagentDirectoryScopeForConnection.- Agents tab uses master-detail navigation on all screen sizes with
PopScopeand Android predictive back. AppLifecycleServiceandConnectionForegroundServicekeep SSH/agent sessions alive when Android is backgrounded.- Agent chat groups consecutive same-role messages via
groupAgentMessagesinlib/utils/agent_message_grouping.dart; rendering usesAgentMessageBubbleandAgentMessagePartTile. OpenCodeRemoteConfigServiceimports Windows host config over SSH from%USERPROFILE%\.config\opencode\; Android build uses AGP 9.0.1 with vendoredpackages/home_widget.
The standard commands (flutter pub get / flutter analyze / flutter test) are documented in the Build/Lint/Testing sections above. Notes below cover only non-obvious caveats for the headless cloud VM.
- Flutter SDK is installed at
~/flutterand onPATHvia~/.bashrc. The startup update script only runsflutter pub get; the SDK and system libraries live in the VM snapshot. - Running the app: Android is the primary product target, but this VM has no Android device. The runnable dev target here is the Linux desktop against the virtual display:
export DISPLAY=:1 && flutter run -d linux.flutter run -d web-serveris not viable becauseflutter_ptyhas no web support. - Linux desktop build deps (already in the snapshot):
ninja-build,libgtk-3-dev,build-essential, and cruciallylibstdc++-14-dev. Clang 18 selects GCC 14, so withoutlibstdc++-14-devthe build fails with/usr/bin/ld: cannot find -lstdc++. flutter_secure_storagethrowsPlatformException(KeyringLocked)on startup here because the headless VM has no unlocked libsecret/GNOME keyring. This is non-fatal: the app runs normally and SSH profiles (stored viashared_preferences) work. Only secure-storage-backed values (e.g. the OpenCode Zen API key) are affected.- Pre-existing
assets/warning:flutter analyze/testprintasset directory 'assets/' doesn't existbecausepubspec.yamllistsassets/but the dir is absent. It is a harmless warning, not a failure. - Testing the SSH client E2E locally: start a local
sshd(sudo /usr/sbin/sshd), set a password for theubuntuuser, and connect the app's Client tab tolocalhost:22. This exercises the real SSH connect + terminal path without an external host.