Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ rtk gain # Should show token savings stats
rtk init -g # Claude Code / Copilot (default)
rtk init -g --gemini # Gemini CLI
rtk init -g --codex # Codex (OpenAI)
rtk init --agent omp # Oh My Pi (OMP, project hook)
rtk init -g --agent omp # Oh My Pi (OMP, global hook)
rtk init -g --agent cursor # Cursor
rtk init --agent windsurf # Windsurf
rtk init --agent cline # Cline / Roo Code
Expand Down Expand Up @@ -351,7 +353,7 @@ rtk git status

## Supported AI Tools

RTK supports 13 AI coding tools. Each integration rewrites shell commands to `rtk` equivalents for 60-90% token savings where the agent supports command interception.
RTK supports 13 AI coding tools. Each integration transparently rewrites shell commands to `rtk` equivalents for 60-90% token savings.

| Tool | Install | Method |
|------|---------|--------|
Expand All @@ -361,6 +363,7 @@ RTK supports 13 AI coding tools. Each integration rewrites shell commands to `rt
| **Cursor** | `rtk init -g --agent cursor` | preToolUse hook (hooks.json) |
| **Gemini CLI** | `rtk init -g --gemini` | BeforeTool hook |
| **Codex** | `rtk init -g --codex` | AGENTS.md + RTK.md instructions |
| **Oh My Pi (OMP)** | `rtk init --agent omp` / `rtk init -g --agent omp` | OMP hook (`tool_call`) — `./.omp/hooks/pre/rtk.ts` or `~/.omp/agent/hooks/pre/rtk.ts` |
| **Windsurf** | `rtk init --agent windsurf` | .windsurfrules (project-scoped) |
| **Cline / Roo Code** | `rtk init --agent cline` | .clinerules (project-scoped) |
| **OpenCode** | `rtk init -g --opencode` | Plugin TS (tool.execute.before) |
Expand Down
2 changes: 2 additions & 0 deletions docs/contributing/TECHNICAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ Start here, then drill down into each README for file-level details.
| [`cline/`](../hooks/cline/README.md) | Cline / Roo Code | Rules file (prompt-level, no programmatic hook) |
| [`windsurf/`](../hooks/windsurf/README.md) | Windsurf / Cascade | Rules file (workspace-scoped) |
| [`codex/`](../hooks/codex/README.md) | OpenAI Codex CLI | Awareness document, AGENTS.md integration |
| [`omp/`](../hooks/omp/README.md) | Oh My Pi | TypeScript hook module, project/user `.omp/hooks/pre/` or `~/.omp/agent/hooks/pre/` |
| [`opencode/`](../hooks/opencode/README.md) | OpenCode | TypeScript plugin, zx library, in-place mutation |

---
Expand All @@ -334,6 +335,7 @@ RTK supports the following LLM agents through hook integrations:
| Cline/Roo Code | Rules file | Prompt-level guidance | N/A (prompt) |
| Windsurf | Rules file | Prompt-level guidance | N/A (prompt) |
| Codex CLI | Awareness doc | AGENTS.md integration | N/A (prompt) |
| Oh My Pi | Hook module | OMP `tool_call` event | Yes (`event.input`) |
| OpenCode | TS plugin | `tool.execute.before` event | Yes (in-place mutation) |

> **Details**: [`hooks/README.md`](../hooks/README.md) has the full JSON schemas for each agent. [`src/hooks/README.md`](../src/hooks/README.md) covers installation, integrity verification, and the rewrite command.
Expand Down
10 changes: 10 additions & 0 deletions docs/guide/getting-started/supported-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Agent runs "cargo test"
| OpenCode | TypeScript plugin (`tool.execute.before`) | Yes |
| OpenClaw | TypeScript plugin (`before_tool_call`) | Yes |
| Hermes | Python plugin (`terminal` command mutation) | Yes |
| Oh My Pi (OMP) | TypeScript hook (`tool_call`) | Yes |
| Cline / Roo Code | Rules file (prompt-level) | N/A |
| Windsurf | Rules file (prompt-level) | N/A |
| Codex CLI | AGENTS.md instructions | N/A |
Expand Down Expand Up @@ -123,6 +124,15 @@ rtk init --windsurf # creates .windsurfrules in current project
rtk init --codex # creates AGENTS.md or patches existing one
```

### Oh My Pi

```bash
rtk init --agent omp # creates ./.omp/hooks/pre/rtk.ts
rtk init -g --agent omp # creates ~/.omp/agent/hooks/pre/rtk.ts
```

Oh My Pi loads project hooks from `.omp/hooks/pre/` and user hooks from `~/.omp/agent/hooks/pre/`. RTK installs a dedicated `rtk.ts` hook that intercepts `bash` tool calls and delegates rewrite decisions to `rtk rewrite`.

### Kilo Code

```bash
Expand Down
2 changes: 2 additions & 0 deletions hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Each agent subdirectory has its own README with hook-specific details:
- **[`cline/`](cline/README.md)** — Rules file (prompt-level), `.clinerules` project-local installation
- **[`windsurf/`](windsurf/README.md)** — Rules file (prompt-level), `.windsurfrules` workspace-scoped
- **[`codex/`](codex/README.md)** — Awareness document, `AGENTS.md` integration, `$CODEX_HOME` or `~/.codex/` location
- **[`omp/`](omp/README.md)** — TypeScript hook, OMP `tool_call` rewrite via `./.omp/hooks/pre/rtk.ts` or `~/.omp/agent/hooks/pre/rtk.ts`
- **[`opencode/`](opencode/README.md)** — TypeScript plugin, `zx` library, `tool.execute.before` event, in-place mutation
- **[`hermes/`](hermes/README.md)** — Python plugin, `pre_tool_call` hook, in-place terminal command mutation

Expand All @@ -54,6 +55,7 @@ Each agent subdirectory has its own README with hook-specific details:
| Cline / Roo Code | Custom instructions (rules file) | Prompt-level guidance | N/A |
| Windsurf | Custom instructions (rules file) | Prompt-level guidance | N/A |
| Codex CLI | AGENTS.md / instructions | Prompt-level guidance | N/A |
| Oh My Pi (OMP) | TypeScript hook (`tool_call`) | In-place mutation | Yes (`event.input`) |
| OpenCode | TypeScript plugin (`tool.execute.before`) | In-place mutation | Yes |
| Hermes | Python plugin (`pre_tool_call`) | In-place mutation | Yes |

Expand Down
13 changes: 13 additions & 0 deletions hooks/omp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Oh My Pi Hooks

> Part of [`hooks/`](../README.md) — see also [`src/hooks/`](../../src/hooks/README.md) for installation code

## Specifics

- TypeScript hook module (HookAPI), not a shell hook or rules file
- Installs to `./.omp/hooks/pre/rtk.ts` with `rtk init --agent omp`, or to `~/.omp/agent/hooks/pre/rtk.ts` with `rtk init -g --agent omp`
- Intercepts OMP `tool_call` events for the `bash` tool and delegates rewrite decisions to `rtk rewrite`
- Uses `ctx.ui.confirm()` to prompt the user when `rtk rewrite` returns an "ask" permission verdict (exit code 3)
- Deny verdicts (exit code 2) pass through unchanged (host tool handles denial)
- Fail-open: if `rtk` is unavailable or `rtk rewrite` fails, commands run raw unchanged
- Multi-hook chaining: OMP dispatches `tool_call` handlers sequentially. Downstream handlers observe the RTK-rewritten `event.input.command` when RTK rewrites it
103 changes: 103 additions & 0 deletions hooks/omp/rtk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// RTK - Rust Token Killer
// OMP hook: rewrite bash tool calls through `rtk rewrite`.
//
// Installed to .omp/hooks/pre/rtk.ts (project) or
// ~/.omp/agent/hooks/pre/rtk.ts (global).
// OMP auto-discovers and loads hooks from these paths.
//
// Fail-open: if rtk is unavailable or rewrite fails, commands run raw.

import type { HookAPI } from "@oh-my-pi/pi-coding-agent/extensibility/hooks";
import { $which } from "@oh-my-pi/pi-utils";

type Verdict = "allow" | "ask";

export interface RewriteResult {
rewritten: string;
verdict: Verdict;
}

/** Thin wrapper over Bun.spawn for test injection. */
export const spawner = {
spawn: (cmd: string[], opts: Parameters<typeof Bun.spawn>[1]) => Bun.spawn(cmd, opts),
};

export async function rewrite(command: string): Promise<RewriteResult | null> {
// `rtk rewrite` exit codes:
// 0 = rewrite allowed (auto-apply)
// 1 = no RTK equivalent (passthrough)
// 2 = deny rule matched (passthrough — host tool handles denial)
// 3 = ask rule matched (prompt user before applying)
let timeout: ReturnType<typeof setTimeout> | undefined;
let timedOut = false;
try {
const proc = spawner.spawn(["rtk", "rewrite", command], {
stdout: "pipe", stderr: "pipe",
});
timeout = setTimeout(() => {
timedOut = true;
proc.kill();
}, 2_000);
const [exitCode, stdout, stderr] = await Promise.all([
proc.exited,
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);

if (timedOut) {
warn("rtk rewrite timed out");
return null;
}

const rewritten = stdout.trim();

if (exitCode === 1 || exitCode === 2) return null;
if (exitCode === 0 || exitCode === 3) {
if (rewritten && rewritten !== command) {
return { rewritten, verdict: exitCode === 3 ? "ask" : "allow" };
}
return null;
}

const details = `rtk rewrite failed with exit ${exitCode}`;
const errDetail = stderr.trim();
warn(errDetail ? `${details}: ${errDetail}` : details);
} catch (e) {
warn(`rtk rewrite error: ${e instanceof Error ? e.message : String(e)}`);
} finally {
if (timeout !== undefined) clearTimeout(timeout);
}
return null;
}

function warn(message: string): void {
console.error(`rtk: omp hook warning: ${message}`);
}

let rtkMissingWarned = false;

export default function (pi: HookAPI): void {
const hasRtk = Boolean($which("rtk"));

if (!hasRtk && !rtkMissingWarned) {
rtkMissingWarned = true;
warn("rtk binary not found in PATH; OMP hook not registered");
}

pi.on("tool_call", async (event, ctx) => {
if (event.toolName !== "bash") return;
if (!hasRtk) return;
const result = await rewrite(event.input.command as string);
if (!result) return;

if (result.verdict === "ask" && ctx.hasUI) {
const ok = await ctx.ui.confirm(
"RTK rewrite",
`Rewrote:\n ${event.input.command}\nto:\n ${result.rewritten}\n\nUse rewritten command?`,
);
if (!ok) return; // user declined — passthrough original
}

event.input.command = result.rewritten;
});
}
Loading