471 lines
20 KiB
Nix
471 lines
20 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
inputs,
|
|
...
|
|
}:
|
|
let
|
|
# sisyphus/oracle/prometheus → default/slow/plan = opus
|
|
# librarian/explore/quick → smol/commit = haiku
|
|
ompSettings = {
|
|
modelRoles = {
|
|
default = "openrouter/deepseek/deepseek-v4-pro:high";
|
|
smol = "openrouter/deepseek/deepseek-v4-pro:medium";
|
|
slow = "openrouter/deepseek/deepseek-v4-pro:high";
|
|
plan = "openrouter/deepseek/deepseek-v4-pro:high";
|
|
commit = "openrouter/deepseek/deepseek-v4-pro:low";
|
|
};
|
|
};
|
|
|
|
# provider config — secrets read at eval time via builtins.readFile
|
|
# (omp treats apiKey as env-var-name-or-literal, not a shell command)
|
|
ompModels = {
|
|
providers = {
|
|
openrouter = {
|
|
apiKey = lib.strings.trim (builtins.readFile ../../secrets/home/openrouter_api_key);
|
|
};
|
|
"llama.cpp" = {
|
|
baseUrl = "https://llm.sigkill.computer";
|
|
apiKey = lib.strings.trim (builtins.readFile ../../secrets/home/llama_cpp_api_key);
|
|
api = "openai-responses";
|
|
authHeader = true;
|
|
discovery.type = "llama.cpp";
|
|
};
|
|
};
|
|
};
|
|
|
|
# Pull Google's official agent-skills (github:android/skills, Apache 2.0).
|
|
# The upstream tree nests skills as <category>/<name>/SKILL.md (build/agp/…,
|
|
# jetpack-compose/migration/…, performance/r8-analyzer, etc.). omp expects a
|
|
# flat layout, so we walk the tree, find every SKILL.md, and mount each
|
|
# parent directory at ~/.omp/agent/skills/<basename>/. Every leaf basename
|
|
# in upstream is unique, so flattening is lossless. New skills upstream show
|
|
# up automatically on `nix flake update --input-name android-skills`.
|
|
findSkillDirs =
|
|
path:
|
|
let
|
|
entries = builtins.readDir path;
|
|
hasSkillMd = builtins.pathExists (path + "/SKILL.md");
|
|
subdirs = lib.filterAttrs (_: t: t == "directory") entries;
|
|
recurse = lib.concatLists (lib.mapAttrsToList (n: _: findSkillDirs (path + "/${n}")) subdirs);
|
|
in
|
|
if hasSkillMd then [ path ] else recurse;
|
|
|
|
androidSkillFiles = lib.listToAttrs (
|
|
map (
|
|
dir:
|
|
lib.nameValuePair ".omp/agent/skills/${builtins.unsafeDiscardStringContext (baseNameOf dir)}" {
|
|
source = dir;
|
|
}
|
|
) (findSkillDirs inputs.android-skills)
|
|
);
|
|
|
|
# ghidrecomp: command-line Ghidra decompiler. Required by pyghidra-mcp,
|
|
# not in nixpkgs as of now.
|
|
ghidrecomp = pkgs.python3Packages.buildPythonPackage rec {
|
|
pname = "ghidrecomp";
|
|
version = "0.5.9";
|
|
pyproject = true;
|
|
src = pkgs.python3Packages.fetchPypi {
|
|
inherit pname version;
|
|
hash = "sha256-ocluLUic2qMREO7kXWum8l3VZ/parj/WtQ9JgOood6I=";
|
|
};
|
|
nativeBuildInputs = [ pkgs.python3Packages.setuptools ];
|
|
propagatedBuildInputs = [ pkgs.python3Packages.pyghidra ];
|
|
pythonImportsCheck = [ "ghidrecomp" ];
|
|
meta = {
|
|
description = "Python command-line Ghidra decompiler";
|
|
homepage = "https://github.com/clearbluejar/ghidrecomp";
|
|
license = lib.licenses.mit;
|
|
};
|
|
};
|
|
|
|
# pyghidra-mcp: headless MCP server exposing Ghidra analysis primitives over
|
|
# the Model Context Protocol (clearbluejar/pyghidra-mcp). Replaces the
|
|
# better-known LaurieWired/GhidraMCP which has been stale since mid-2025.
|
|
# Pure-Python via pyghidra/jpype — no Ghidra GUI required.
|
|
pyghidra-mcp = pkgs.python3Packages.buildPythonApplication rec {
|
|
pname = "pyghidra-mcp";
|
|
version = "0.2.2";
|
|
pyproject = true;
|
|
src = pkgs.python3Packages.fetchPypi {
|
|
pname = "pyghidra_mcp";
|
|
inherit version;
|
|
hash = "sha256-d3I9TP+OkLu6lU2994PR+77vIqB+4z8pHkHl56GNreY=";
|
|
};
|
|
nativeBuildInputs = [ pkgs.python3Packages.hatchling ];
|
|
propagatedBuildInputs = with pkgs.python3Packages; [
|
|
pyghidra
|
|
mcp
|
|
click
|
|
click-option-group
|
|
chromadb
|
|
ghidrecomp
|
|
];
|
|
# pyghidra discovers the Ghidra install via GHIDRA_INSTALL_DIR; bake it in
|
|
# at the wrapper level so the agent doesn't need to set it.
|
|
makeWrapperArgs = [
|
|
"--set"
|
|
"GHIDRA_INSTALL_DIR"
|
|
"${pkgs.ghidra}/lib/ghidra"
|
|
];
|
|
pythonImportsCheck = [ "pyghidra_mcp" ];
|
|
meta = {
|
|
description = "Python command-line Ghidra MCP server";
|
|
homepage = "https://github.com/clearbluejar/pyghidra-mcp";
|
|
license = lib.licenses.mit;
|
|
mainProgram = "pyghidra-mcp";
|
|
};
|
|
};
|
|
|
|
# OMP's `eval` tool drives a Jupyter kernel for Python execution. It refuses
|
|
# ("Python backend is unavailable") unless both `kernel_gateway`
|
|
# (jupyter-kernel-gateway) and `ipykernel` are importable from the python it
|
|
# selects. nixpkgs ships ipykernel but not jupyter-kernel-gateway, so we
|
|
# build it here from the PyPI sdist.
|
|
jupyter-kernel-gateway = pkgs.python3Packages.buildPythonPackage rec {
|
|
pname = "jupyter_kernel_gateway";
|
|
version = "3.0.1";
|
|
pyproject = true;
|
|
src = pkgs.python3Packages.fetchPypi {
|
|
inherit pname version;
|
|
hash = "sha256-kAaQxMDnloZzVUaNaF9/oc88d3XQjoccFX931l+9bX8=";
|
|
};
|
|
nativeBuildInputs = [ pkgs.python3Packages.hatchling ];
|
|
propagatedBuildInputs = with pkgs.python3Packages; [
|
|
jupyter-client
|
|
jupyter-core
|
|
jupyter-server
|
|
requests
|
|
tornado
|
|
traitlets
|
|
];
|
|
# Upstream test suite hits the network and pulls pytest-jupyter; the import
|
|
# check is enough to confirm the package wires up correctly.
|
|
doCheck = false;
|
|
pythonImportsCheck = [ "kernel_gateway" ];
|
|
meta = {
|
|
description = "Jupyter Kernel Gateway: headless web server for Jupyter kernels";
|
|
homepage = "https://github.com/jupyter/kernel_gateway";
|
|
license = lib.licenses.bsd3;
|
|
};
|
|
};
|
|
|
|
# Python env exposed to omp as its "managed venv" at ~/.omp/python-env. omp's
|
|
# runtime resolver (packages/coding-agent/src/eval/py/runtime.ts) probes that
|
|
# path before falling back to PATH, so symlinking it here keeps PATH clean
|
|
# (no conflict with the python313 env in home/profiles/no-gui.nix) while
|
|
# guaranteeing kernel_gateway + ipykernel are importable without `omp setup
|
|
# python` ever needing to write to the store.
|
|
ompPythonEnv = pkgs.python3.withPackages (ps: [
|
|
ps.ipykernel
|
|
jupyter-kernel-gateway
|
|
]);
|
|
|
|
# Browser path for the playwright skill body.
|
|
playwrightChromium =
|
|
let
|
|
browsers = pkgs.playwright-driver.browsers;
|
|
chromiumDir = builtins.head (
|
|
builtins.filter (n: builtins.match "chromium-[0-9]+" n != null) (
|
|
builtins.attrNames browsers.passthru.entries
|
|
)
|
|
);
|
|
in
|
|
{
|
|
browsers = "${browsers}";
|
|
chrome = "${browsers}/${chromiumDir}/chrome-linux64/chrome";
|
|
};
|
|
in
|
|
{
|
|
home.packages = [
|
|
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.omp
|
|
pyghidra-mcp
|
|
];
|
|
|
|
home.file = androidSkillFiles // {
|
|
# main settings: ~/.omp/agent/config.yml (JSON is valid YAML)
|
|
".omp/agent/config.yml".text = builtins.toJSON ompSettings;
|
|
|
|
# see comment above ompPythonEnv definition.
|
|
".omp/python-env".source = ompPythonEnv;
|
|
|
|
# model/provider config: ~/.omp/agent/models.yml
|
|
".omp/agent/models.yml".text = builtins.toJSON ompModels;
|
|
|
|
# MCP server config: ~/.omp/agent/mcp.json
|
|
# OMP discovers servers from this file at startup. The ghidra entry below
|
|
# spawns pyghidra-mcp on stdio when the agent invokes any of its tools.
|
|
".omp/agent/mcp.json".text = builtins.toJSON {
|
|
"$schema" =
|
|
"https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json";
|
|
mcpServers = {
|
|
ghidra = {
|
|
command = lib.getExe pyghidra-mcp;
|
|
# --project-path defaults to the relative ./pyghidra_mcp_projects, which
|
|
# pyghidra-mcp materializes inside whatever cwd OMP launched it from.
|
|
# Pin it to a stable cache location so opening any directory doesn't
|
|
# litter it with a project folder.
|
|
args = [
|
|
"--transport"
|
|
"stdio"
|
|
"--project-path"
|
|
"${config.home.homeDirectory}/.cache/pyghidra-mcp"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
|
|
# global instructions loaded at startup
|
|
".omp/agent/AGENTS.md".text = ''
|
|
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.
|
|
|
|
## 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.
|
|
Never `grep` or `find` over the entire `/nix/store`, this is wasteful and will usually turn up weird results or will timeout.
|
|
- Before editing any file, always `read` it with an explicit line range
|
|
(e.g. `path/to/file:1-100`), never as a bare path. Without a selector,
|
|
the tool returns a structural summary with no line anchors, and the
|
|
edit will fail with "lines shifted" — wasting a round-trip.
|
|
|
|
## Implementation
|
|
When sketching out an implementation of something, always look for tools that already exist in the space first before implementing something custom. This is also the case when it comes to submodules and sections of code, I don't want you to implement things in-house when it isn't needed.
|
|
## Code Review
|
|
When working on a section of code (a function, a module block, a config stanza), you MUST run a multi-round adversarial review. Subagents are one-shot, so you chain rounds by feeding each round's full transcript into the next.
|
|
|
|
### Round 1 — ELI5
|
|
Explain every line in plain English as if to a five-year-old. Do not skip lines, summarize groups, or hand-wave. State what each line does, why it's there, and what it assumes.
|
|
|
|
### Round 2 — Attack
|
|
Spawn a reviewer subagent (via `task`) with the original code and your ELI5. Its sole job: find flaws — bugs, edge cases, incorrect assumptions, missing error handling, race conditions, off-by-one errors, performance traps, logical contradictions. It MUST NOT praise or sugar-coat; it must argue against the code.
|
|
|
|
### Round 3 — Rebuttal
|
|
Address every finding point-by-point. For each: either acknowledge it and describe your fix, or rebut it with a concrete reason the reviewer is wrong. Do not dismiss findings without a specific counter-argument.
|
|
|
|
### Round 4 — Second Opinion
|
|
Spawn a fresh reviewer with the full transcript (original code + ELI5 + attack findings + your rebuttals). Its job: did you properly address every finding? Are your rebuttals valid? What did Round 2 miss? If it finds new unresolved issues, return to Round 3 for those. Cap at 3 attack rounds total.
|
|
|
|
Amend your code based on every substantiated, unrebutted finding before proceeding.
|
|
## Upstream contributions (patches/)
|
|
|
|
When working on patches against an external project under `patches/`,
|
|
you are submitting code to a codebase you did not write. You MUST learn
|
|
its conventions before touching anything, or the MR will be rejected.
|
|
|
|
### Before writing code
|
|
- Find the project's file-registration mechanism (build system, i18n
|
|
file list, module registry) and add every new file to it. Projects
|
|
reject MRs that add orphaned files.
|
|
- Scan adjacent code for API conventions. If you see two ways to do the
|
|
same thing (e.g. legacy raw setters vs. modern type-safe wrappers),
|
|
use the newer/cleaner one. Do not copy-paste old patterns.
|
|
- Look for higher-level abstractions that do what you need in one call.
|
|
If the project already has a "replace entire payload" method, don't
|
|
mutate fields one by one.
|
|
|
|
### Behavioral claims require evidence
|
|
- Comments that claim parity with an external system or specification
|
|
MUST cite a specific source. Unsubstantiated claims will be challenged
|
|
in review.
|
|
- Before implementing protocol-level behavior, verify the expected
|
|
behavior against a reference implementation or specification. Do not
|
|
guess.
|
|
|
|
### Comments
|
|
- Write comments for the non-obvious "why", not the "what". Do not
|
|
explain how the framework or widget tree works unless the behavior is
|
|
genuinely surprising.
|
|
- Do not write comments that are factually wrong. If you don't know
|
|
whether a claim about library behavior is true, either verify or omit
|
|
the comment. Wrong comments are worse than no comments.
|
|
|
|
### Architecture
|
|
- Prefer the simplest implementation that works. Remove redundant
|
|
wrappers, duplicate callbacks, and unnecessary nesting before
|
|
submitting.
|
|
- Before building a custom widget or container, check whether the
|
|
platform toolkit already provides the pattern. Consult actual docs;
|
|
do not guess about API capabilities.
|
|
|
|
### Commit hygiene
|
|
- Never commit binary files (images, test data) to the repo. Use
|
|
existing project assets or generate test data at runtime.
|
|
- After changes, verify the code compiles and tests pass.
|
|
'';
|
|
|
|
".omp/agent/skills/android-ui/SKILL.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
|
|
|
|
## 1. Taking Screenshots
|
|
```
|
|
adb exec-out screencap -p > /tmp/screen.png
|
|
```
|
|
Captures the current screen state as a PNG image.
|
|
|
|
## 2. Analyzing Screenshots
|
|
Read the screenshot file to understand the current screen state and identify UI elements.
|
|
|
|
## 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 it (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
|
|
'';
|
|
|
|
# omp has a built-in browser tool with NixOS auto-detection,
|
|
# but this skill provides playwright MCP as a supplementary option
|
|
".omp/agent/skills/playwright/SKILL.md".text = ''
|
|
---
|
|
name: playwright
|
|
description: Browser automation via Playwright MCP. Use as an alternative to the built-in browser tool for Playwright-specific workflows, testing, and web scraping. Chromium is provided by NixOS.
|
|
---
|
|
|
|
# Playwright
|
|
|
|
## Browser Setup
|
|
Chromium is provided by NixOS. Do NOT attempt to download browsers.
|
|
|
|
- Chromium path: `${playwrightChromium.chrome}`
|
|
- Browsers path: `${playwrightChromium.browsers}`
|
|
|
|
## Usage
|
|
Launch the Playwright MCP server for browser automation:
|
|
```bash
|
|
npx @playwright/mcp@latest --executable-path "${playwrightChromium.chrome}" --user-data-dir "${config.home.homeDirectory}/.cache/playwright-mcp"
|
|
```
|
|
|
|
Set these environment variables if not already set:
|
|
```bash
|
|
export PLAYWRIGHT_BROWSERS_PATH="${playwrightChromium.browsers}"
|
|
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
|
```
|
|
'';
|
|
".omp/agent/skills/ghidra/SKILL.md".text = ''
|
|
---
|
|
name: ghidra
|
|
description: Static binary reverse engineering via the Ghidra MCP server (pyghidra-mcp). Use for analyzing compiled binaries (Windows .exe, Linux ELF, Mach-O, .NET, JVM, raw firmware blobs) when you need decompiled C-pseudocode, function-level analysis, string searches inside .rdata, cross-references to imported APIs, or call-graph navigation.
|
|
---
|
|
|
|
# Ghidra (via pyghidra-mcp)
|
|
|
|
A headless MCP server is configured at `mcpServers.ghidra` in
|
|
`~/.omp/agent/mcp.json` and binds Ghidra's analysis engine to MCP tools
|
|
you can call directly. The Ghidra install lives at
|
|
`''${pkgs.ghidra}/lib/ghidra`; pyghidra-mcp picks it up via the
|
|
GHIDRA_INSTALL_DIR env var that's wired into the binary's wrapper.
|
|
|
|
## When to use this
|
|
|
|
- Static analysis of any compiled binary you have on disk.
|
|
- Finding the decision logic behind an observed runtime behavior, when
|
|
the source isn't available.
|
|
- Extracting embedded tables/constants from `.rdata`/`.data` sections.
|
|
- Discovering which APIs (libc, OS, vendor SDKs) a binary imports and
|
|
where it calls them.
|
|
- Recovering structures, function signatures, and type information
|
|
from a stripped binary.
|
|
|
|
## Workflow
|
|
|
|
The first invocation imports a binary into a fresh Ghidra project and
|
|
runs auto-analysis (10-90 minutes depending on size). Subsequent calls
|
|
hit the cached project and are fast.
|
|
|
|
Typical exploration sequence for a stripped binary:
|
|
|
|
1. `list_strings(filter="<substring>")` to find string literals
|
|
related to the behavior you're investigating.
|
|
2. `list_imports()` filtered for the API surface you care about
|
|
(e.g. HID, networking, crypto) to find call sites.
|
|
3. `get_xrefs_to(<address-of-string-or-import>)` to surface every
|
|
function that touches the symbol.
|
|
4. `decompile_function_by_address(<addr>)` to read C-pseudocode.
|
|
5. `set_decompiler_comment` and `rename_function` as you identify
|
|
components, so the database remembers your findings across calls.
|
|
|
|
## Loading a binary
|
|
|
|
Drop the binary somewhere readable (don't commit to git — size + often
|
|
proprietary) and pass the absolute path to pyghidra-mcp's import tool.
|
|
Auto-analysis runs once; the project database persists in
|
|
`~/.cache/pyghidra-mcp/` so re-invocations are fast.
|
|
|
|
## What this is NOT for
|
|
|
|
- Dynamic / runtime analysis — use a debugger, usbmon/strace, or a
|
|
protocol sniffer for that.
|
|
- Encrypted/DRM-protected binaries — out of scope without the keys.
|
|
- Network-traffic decoding on the wire — separate tooling.
|
|
|
|
Reverse engineering for interoperability, security research, and
|
|
bug-fix purposes is permitted under DMCA §1201(f) and analogous EU
|
|
provisions. Don't share decrypted or cracked binaries.
|
|
'';
|
|
|
|
};
|
|
}
|