{ config, lib, pkgs, inputs, ... }: let # what model should be used in place of haiku? haiku-model = "anthropic/claude-haiku-4-5"; opus-model = "anthropic/claude-opus-4-6"; opencode-claude-bridge = pkgs.buildNpmPackage { pname = "opencode-claude-bridge"; version = "1.8.0"; src = inputs.opencode-claude-bridge; npmDepsHash = "sha256-jH/UweuHqfeLxICxNRsBODWOBfVdE+ZgIinfW/ITSSc="; buildPhase = '' runHook preBuild npx tsc runHook postBuild ''; # the plugin entry point is dist/index.js installPhase = '' runHook preInstall mkdir -p $out/lib/opencode-claude-bridge cp -r dist $out/lib/opencode-claude-bridge/ runHook postInstall ''; }; ohMyOpencodeConfig = { "$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"; git_master = { commit_footer = false; include_co_authored_by = false; }; agents = { sisyphus.model = opus-model; sisyphus-junior.model = opus-model; oracle.model = opus-model; librarian.model = haiku-model; explore.model = haiku-model; multimodal-looker.model = "anthropic/claude-opus-4-6"; prometheus.model = opus-model; metis.model = opus-model; momus.model = opus-model; atlas.model = opus-model; }; categories = { visual-engineering.model = "openrouter/google/gemini-3-pro"; ultrabrain.model = opus-model; artistry = { model = "openrouter/google/gemini-3-pro"; variant = "max"; }; quick.model = haiku-model; deep.model = opus-model; writing.model = "openrouter/google/gemini-3-flash-preview"; }; }; oh-my-opencode-pkg = inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.oh-my-opencode; in { home.packages = [ oh-my-opencode-pkg pkgs.playwright-driver.browsers inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.claude-code ]; home.sessionVariables = { PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}"; PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1"; }; xdg.configFile."opencode/oh-my-opencode.json".text = builtins.toJSON ohMyOpencodeConfig; # Plugins are placed directly in the plugins directory so opencode # auto-discovers them instead of downloading them from npm at runtime. xdg.configFile."opencode/plugins/oh-my-opencode.js".source = "${oh-my-opencode-pkg}/lib/oh-my-opencode/dist/index.js"; xdg.configFile."opencode/plugins/opencode-claude-bridge.js".source = "${opencode-claude-bridge}/lib/opencode-claude-bridge/dist/index.js"; xdg.configFile."opencode/skills/android-ui.md".text = '' --- name: android-ui description: "Android UI automation via ADB - use for any Android device interaction, UI testing, screenshot analysis, element coordinate lookup, and gesture automation." --- # Android UI Interaction Workflow ## 1. Taking Screenshots ``` adb exec-out screencap -p > /tmp/screen.png ``` Captures the current screen state as a PNG image. ## 2. Analyzing Screenshots Delegate screenshot analysis to an explore agent rather than analyzing images directly: ``` mcp_task(subagent_type="explore", prompt="Analyze /tmp/screen.png. What screen is this? What elements are visible?") ``` The agent describes the UI, identifies elements, and estimates Y coordinates. ## 3. Getting Precise Element Coordinates UI Automator dump - extracts the full UI hierarchy as XML: ``` adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml /tmp/ui.xml ``` Then grep for specific elements: ```sh # Find by text grep -oP 'text="Login".*?bounds="[^"]*"' /tmp/ui.xml # Find by class grep -oP 'class="android.widget.EditText".*?bounds="[^"]*"' /tmp/ui.xml ``` Bounds format: `[left,top][right,bottom]` — tap center: `((left+right)/2, (top+bottom)/2)` ## 4. Tapping Elements ``` adb shell input tap X Y ``` Where X, Y are pixel coordinates from the bounds. ## 5. Text Input ``` adb shell input text "some_text" ``` Note: Special characters need escaping (`\!`, `\;`, etc.) ## 6. Other Gestures ```sh # Swipe/scroll adb shell input swipe startX startY endX endY duration_ms # Key events adb shell input keyevent KEYCODE_BACK adb shell input keyevent KEYCODE_ENTER ``` ## 7. WebView Limitation - UI Automator can see WebView content if accessibility is enabled - Touch events on iframe content (like Cloudflare Turnstile) often fail due to cross-origin isolation - Form fields in WebViews work if you get exact bounds from the UI dump ## Typical Flow 1. Take screenshot → analyze with explore agent (get rough layout) 2. Dump UI hierarchy → grep for exact element bounds - NEVER ASSUME COORDINATES. You must ALWAYS check first. - Do this before ANY tap action as elements on the screen may have changed. 3. Calculate center coordinates from bounds 4. Tap/interact 5. Wait → screenshot → verify result ''; xdg.configFile."opencode/skills/playwright.md".text = let browsers = pkgs.playwright-driver.browsers; chromiumDir = builtins.head ( builtins.filter (n: builtins.match "chromium-[0-9]+" n != null) ( builtins.attrNames browsers.passthru.entries ) ); chromiumPath = "${browsers}/${chromiumDir}/chrome-linux64/chrome"; in '' --- name: playwright description: "MUST USE for any browser-related tasks. Browser automation via Playwright MCP - verification, browsing, information gathering, web scraping, testing, screenshots, and all browser interactions." mcp: playwright: command: npx args: - "@playwright/mcp@latest" - "--executable-path" - "${chromiumPath}" - "--user-data-dir" - "${config.home.homeDirectory}/.cache/playwright-mcp" --- # Playwright Browser Automation This skill provides browser automation capabilities via the Playwright MCP server. ''; programs.opencode = { package = inputs.llm-agents.packages.${pkgs.stdenv.targetPlatform.system}.opencode; enable = true; rules = '' You are an intelligent and observant agent. If instructed to commit, disable gpg signing. You are on nixOS, if you don't have access to a tool, you can access it via the `nix-shell` command. ## Think deeply about everything. When given a problem, break it down, abstract it out, understand the fundamentals, then solve it in the real world. ## Misc For long-running commands, make sure you set the timeout of the Bash tool provided to a larger value. Do NOT read secret files. Do not directly read files that are api keys or are contextually sensitive. Do NOT run `skill_mcp [mcp_name=playwright, tool_name=browser_install]` as browsers are provided by NixOS via PLAYWRIGHT_BROWSERS_PATH. ## Behavior Do not be sycophantic in your responses. Do not use emojis unless explicitly asked to. This includes in code. Use Test Driven Development methodology. ## Nix For using `nix build` append `-L` to get better visibility into the logs. If you get an error that a file can't be found, always try to `git add` the file before trying other troubleshooting steps. ''; settings = { theme = "opencode"; model = opus-model; # small model used for titles small_model = "openrouter/openai/gpt-oss-20b:free"; autoshare = false; autoupdate = false; agent = { }; plugin = [ ]; provider = { openrouter = { models = { "openai/gpt-oss-20b:free" = { }; "qwen/qwen3-vl-30b-a3b-thinking" = { }; }; options = { # TODO! use agenix here instead apiKey = "{file:${../secrets/openrouter_api_key}}"; }; }; }; }; }; }