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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ rtk init --agent cline # Cline / Roo Code
rtk init --agent kilocode # Kilo Code
rtk init --agent antigravity # Google Antigravity
rtk init --agent hermes # Hermes
rtk init --agent omp # oh-my-pi (alias: oh-my-pi)

# 2. Restart your AI tool, then test
git status # Automatically rewritten to rtk git status
Expand Down Expand Up @@ -352,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 14 AI coding tools. Each integration rewrites shell commands to `rtk` equivalents for 60-90% token savings where the agent supports command interception.

| Tool | Install | Method |
|------|---------|--------|
Expand All @@ -367,6 +368,7 @@ RTK supports 13 AI coding tools. Each integration rewrites shell commands to `rt
| **OpenCode** | `rtk init -g --opencode` | Plugin TS (tool.execute.before) |
| **OpenClaw** | `openclaw plugins install ./openclaw` | Plugin TS (before_tool_call) |
| **Hermes** | `rtk init --agent hermes` | Python plugin adapter (terminal command mutation via `rtk rewrite`) |
| **oh-my-pi** | `rtk init --agent omp` | Extension TS (`tool_call` event) for `omp` binary |
| **Mistral Vibe** | Planned ([#800](https://github.com/rtk-ai/rtk/issues/800)) | Blocked on upstream |
| **Kilo Code** | `rtk init --agent kilocode` | .kilocode/rules/rtk-rules.md (project-scoped) |
| **Google Antigravity** | `rtk init --agent antigravity` | .agents/rules/antigravity-rtk-rules.md (project-scoped) |
Expand Down
6 changes: 4 additions & 2 deletions hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**Deployed hook artifacts** — the actual files installed on user machines by `rtk init`. These are shell scripts, TypeScript plugins, and rules files that run outside the Rust binary. They are **thin delegates**: parse agent-specific JSON, call `rtk rewrite` as a subprocess, format agent-specific response. Zero filtering logic lives here.

Owns: per-agent hook scripts and configuration files for 9 supported agents (Claude Code, Copilot, Cursor, Cline, Windsurf, Codex, OpenCode, Hermes, Pi).
Owns: per-agent hook scripts and configuration files for 10 supported agents (Claude Code, Copilot, Cursor, Cline, Windsurf, Codex, OpenCode, Hermes, Pi, oh-my-pi).

Does **not** own: hook installation/uninstallation (that's `src/hooks/init.rs`), the rewrite pattern registry (that's `discover/registry`), or integrity verification (that's `src/hooks/integrity.rs`).

Expand Down Expand Up @@ -41,6 +41,7 @@ Each agent subdirectory has its own README with hook-specific details:
- **[`codex/`](codex/README.md)** — Awareness document, `AGENTS.md` integration, `$CODEX_HOME` or `~/.codex/` location
- **[`opencode/`](opencode/README.md)** — TypeScript plugin, `zx` library, `tool.execute.before` event, in-place mutation
- **[`pi/`](pi/README.md)** — TypeScript extension, `tool_call` event, `isToolCallEventType` guard, in-place mutation, `~/.pi/agent/extensions/`
- **[`omp/`](omp/README.md)** — TypeScript extension for `oh-my-pi` (`@oh-my-pi/pi-coding-agent`, binary `omp`); `tool_call` event, `~/.omp/agent/extensions/`
- **[`hermes/`](hermes/README.md)** — Python plugin, `pre_tool_call` hook, in-place terminal command mutation

## Supported Agents
Expand All @@ -57,6 +58,7 @@ Each agent subdirectory has its own README with hook-specific details:
| Codex CLI | AGENTS.md / instructions | Prompt-level guidance | N/A |
| OpenCode | TypeScript plugin (`tool.execute.before`) | In-place mutation | Yes |
| Pi | TypeScript extension (`tool_call` event) | In-place mutation | Yes |
| oh-my-pi | Extension TS (`tool_call` event) | In-place mutation | Yes |
| Hermes | Python plugin (`pre_tool_call`) | In-place mutation | Yes |

## JSON Formats by Agent
Expand Down Expand Up @@ -241,7 +243,7 @@ New integrations must follow the [Exit Code Contract](#exit-code-contract) and [
| Tier | Mechanism | Maintenance | Examples |
|------|-----------|-------------|----------|
| **Full hook** | Shell script or Rust binary, intercepts commands via agent's hook API | High — must track agent API changes | Claude Code, Cursor, Copilot, Gemini |
| **Plugin** | TypeScript/JS/Python plugin in agent's plugin system | Medium — agent manages loading | OpenCode, Hermes, Pi |
| **Plugin** | TypeScript/JS/Python plugin in agent's plugin system | Medium — agent manages loading | OpenCode, Hermes, Pi, oh-my-pi |
| **Rules file** | Prompt-level instructions the agent reads | Low — no code to break | Cline, Windsurf, Codex |

### Eligibility
Expand Down
63 changes: 63 additions & 0 deletions hooks/omp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# oh-my-pi Hooks

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

This integration is for `oh-my-pi` (`@oh-my-pi/pi-coding-agent`, binary `omp`). For `@earendil-works/pi-coding-agent` (binary `pi`), see [`../pi/`](../pi/).

## Design Intent

RTK's oh-my-pi extension is a **rewrite-only token optimizer**. It mutates bash commands to their
`rtk`-prefixed equivalents, saving 60–90% context tokens.

**Permission gating is intentionally out of scope.** RTK does not block, confirm, or audit
commands — that concern belongs to a dedicated permission extension (e.g. one that gates
`rm -rf`, `sudo`, etc.). This separation keeps RTK's hook fast, predictable, and composable
with other oh-my-pi extensions.

## Specifics

- TypeScript extension using oh-my-pi's `ExtensionAPI` (not a shell hook, no `zx` dependency)
- Subscribes to `tool_call` event, narrows to `bash` tool via `isToolCallEventType`
- Calls `rtk rewrite` via `pi.exec`; mutates `event.input.command` in-place if rewrite differs
- All error paths return `undefined` (pass through); RTK never blocks execution
- Version guard at load time: checks `rtk >= 0.40.0`; warns and registers no-op if too old or missing
- Installed to `.omp/extensions/rtk.ts` by `rtk init --agent omp` (project-local) or `~/.omp/agent/extensions/rtk.ts` by `rtk init --agent omp --global`

## Uninstall

```bash
# Remove project-local install (run from the project root)
rtk init --uninstall --agent omp
# → removes .omp/extensions/rtk.ts

# Remove global install
rtk init --uninstall --agent omp --global
# → removes ~/.omp/agent/extensions/rtk.ts
```

Uninstall is idempotent — re-running when nothing is installed is a no-op.
Only the extension file is managed by install/uninstall.

## Testing

```bash
# Load the extension directly without installing
omp -e ./hooks/omp/rtk.ts

# Verify rewrites are active — ask the agent to run a command, then check history
rtk gain --history # should show rtk-prefixed commands with savings %

# Test RTK_DISABLED passthrough
RTK_DISABLED=1 omp -e ./hooks/omp/rtk.ts
# → commands pass through unchanged; no rewrites in rtk gain --history

# Test version guard — temporarily shadow rtk with a stub that prints "rtk 0.39.0"
# → extension logs a warning at startup and registers a no-op; omp starts normally
```

## Design Notes

- All filtering logic lives in `rtk rewrite` (the Rust registry), not in this file
- Exit codes 0 and 3 both mean "rewrite and allow"; they are handled identically
- Uses `pi.exec` for subprocess management — consistent with oh-my-pi's extension API
- oh-my-pi reuses the same `PI_CODING_AGENT_DIR` environment variable as the upstream pi integration; the default base directory differs (`.omp/agent` vs `.pi/agent`)
82 changes: 82 additions & 0 deletions hooks/omp/rtk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// RTK oh-my-pi extension - rewrites bash commands to use rtk for token savings.
// For oh-my-pi (`@oh-my-pi/pi-coding-agent`, binary: `omp`).
// Distinct from the @earendil-works/pi-coding-agent integration in hooks/pi/.
// Requires: rtk >= 0.40.0 in PATH.
//
// This is a thin delegating extension: all rewrite logic lives in `rtk rewrite`,
// which is the single source of truth (src/discover/registry.rs).
// To add or change rewrite rules, edit the Rust registry - not this file.
//
// Exit code contract for `rtk rewrite`:
// 0 + stdout Rewrite found -> mutate command
// 1 No RTK equivalent -> pass through unchanged
// 3 + stdout Rewrite (advisory) -> mutate command

import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"
import { isToolCallEventType } from "@oh-my-pi/pi-coding-agent"

const REWRITE_TIMEOUT_MS = 2_000
const MIN_SUPPORTED_RTK_MINOR = 40

// Parse "X.Y.Z" semver, return [major, minor, patch] or null.
function parseSemver(raw: string): [number, number, number] | null {
const m = raw.trim().match(/(\d+)\.(\d+)\.(\d+)/)
if (!m) return null
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)]
}

// Calls `rtk rewrite`; returns the rewritten command or null (pass through).
async function rewriteCommand(
pi: ExtensionAPI,
cmd: string,
signal?: AbortSignal
): Promise<string | null> {
const result = await pi.exec("rtk", ["rewrite", cmd], {
timeout: REWRITE_TIMEOUT_MS,
signal,
})
if (result.killed) return null
if (result.code !== 0 && result.code !== 3) return null
return result.stdout.trim() || null
}

export default async function (pi: ExtensionAPI) {
// Probe rtk version at load time; disables extension if missing or too old.
const ver = await pi.exec("rtk", ["--version"], { timeout: REWRITE_TIMEOUT_MS })
if (ver.code !== 0) {
console.warn("[rtk] rtk binary not found in PATH - extension disabled")
return
}

// Warn and bail if rtk predates the minimum supported version.
const parsed = parseSemver(ver.stdout.replace(/^rtk\s+/, ""))
if (parsed) {
const [major, minor] = parsed
if (major === 0 && minor < MIN_SUPPORTED_RTK_MINOR) {
console.warn(`[rtk] rtk ${ver.stdout.trim()} is too old (need >= 0.${MIN_SUPPORTED_RTK_MINOR}.0) - extension disabled`)
return
}
}

pi.on("tool_call", async (event, ctx) => {
try {
if (!isToolCallEventType("bash", event)) return

const cmd = event.input.command
if (typeof cmd !== "string" || cmd.trim() === "") return

if (cmd.startsWith("rtk ")) return
if (process.env.RTK_DISABLED === "1") return

// Delegate to RTK.
const rewritten = await rewriteCommand(pi, cmd, ctx.signal)
if (rewritten && rewritten !== cmd) {
event.input.command = rewritten
}
} catch (err) {
// Fail open: never block execution on an unexpected error.
console.warn("[rtk] unexpected error in tool_call handler; passing through command", err)
return
}
})
}
9 changes: 9 additions & 0 deletions src/hooks/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ pub const PI_EXTENSIONS_SUBDIR: &str = "extensions";
pub const PI_PLUGIN_FILE: &str = "rtk.ts";
pub const PI_CODING_AGENT_DIR_ENV: &str = "PI_CODING_AGENT_DIR";

// oh-my-pi (`@oh-my-pi/pi-coding-agent`, binary `omp`).
// Distinct from the PI_* constants above which target `@earendil-works/pi-coding-agent`.
// Both integrations share the PI_CODING_AGENT_DIR override env (the oh-my-pi runtime
// reads the same variable); only the default dirname differs.
pub const OMP_DIR: &str = ".omp/agent";
pub const OMP_LOCAL_DIR: &str = ".omp";
pub const OMP_EXTENSIONS_SUBDIR: &str = "extensions";
pub const OMP_PLUGIN_FILE: &str = "rtk.ts";

pub const HERMES_DIR: &str = ".hermes";
pub const HERMES_PLUGINS_SUBDIR: &str = "plugins";
pub const HERMES_PLUGIN_NAME: &str = "rtk-rewrite";
Expand Down
Loading