Skip to content
Closed
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ rtk init --agent kilocode # Kilo Code
rtk init --agent antigravity # Google Antigravity
rtk init --agent kimi # Kimi AI
rtk init -g --agent pi # Pi
rtk init -g --agent omp # OMP (Oh My Pi)
rtk init --agent hermes # Hermes
rtk init -g --agent droid # Factory Droid

Expand Down Expand Up @@ -369,8 +370,7 @@ rtk init -g
| `rtk gain` / analytics | Full | Full |

## Supported AI Tools

RTK supports 15 AI coding tools. Each integration rewrites shell commands to `rtk` equivalents for 60-90% token savings where the agent supports command interception.
RTK supports 16 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 @@ -386,6 +386,7 @@ RTK supports 15 AI coding tools. Each integration rewrites shell commands to `rt
| **OpenClaw** | `openclaw plugins install ./openclaw` | Plugin TS (before_tool_call) |
| **Pi** | `rtk init -g --agent pi` (global) | TypeScript extension (tool_call) |
| **Hermes** | `rtk init --agent hermes` | Python plugin adapter (terminal command mutation via `rtk rewrite`) |
| **OMP (Oh My Pi)** | `rtk init -g --agent omp` (or per-project) | TypeScript extension (tool_call event) |
| **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
26 changes: 24 additions & 2 deletions docs/guide/getting-started/supported-agents.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Supported Agents
description: How to integrate RTK with Claude Code, Cursor, Copilot, Cline, Windsurf, Codex, OpenCode, Hermes, Kilo Code, Antigravity, and Factory Droid
description: How to integrate RTK with Claude Code, Cursor, Copilot, Cline, Windsurf, Codex, OpenCode, Hermes, Pi, OMP, Kilo Code, Antigravity, and Factory Droid
sidebar:
order: 3
---
Expand Down Expand Up @@ -36,6 +36,7 @@ Agent runs "cargo test"
| OpenCode | TypeScript plugin (`tool.execute.before`) | Yes |
| OpenClaw | TypeScript plugin (`before_tool_call`) | Yes |
| Pi | TypeScript extension (`tool_call` event) | Yes |
| OMP (Oh My Pi) | TypeScript extension (`tool_call` event) | Yes |
| Hermes | Python plugin (`terminal` command mutation) | Yes |
| Factory Droid | Shell hook (`PreToolUse`, matcher `Execute`) | Yes |
| Cline / Roo Code | Rules file (prompt-level) | N/A |
Expand Down Expand Up @@ -120,6 +121,27 @@ rtk init --uninstall --agent pi --global

Removes only the installed Pi extension file.

### OMP (Oh My Pi)

```bash
# Project-local (default)
rtk init --agent omp

# Global — all projects
rtk init --agent omp --global
```

Creates `.omp/hooks/pre/rtk.ts` (local) or `~/.omp/agent/hooks/pre/rtk.ts` (global). OMP auto-discovers extensions from the `hooks/pre/` directory on startup. The extension uses `rtk rewrite` for in-place command mutation — same architecture as the Pi extension.

Uninstall:

```bash
rtk init --uninstall --agent omp
rtk init --uninstall --agent omp --global
```

Removes only the installed OMP extension file.

### OpenClaw

```bash
Expand Down Expand Up @@ -207,7 +229,7 @@ Support is blocked on upstream `BeforeToolCallback` ([mistral-vibe#531](https://
| **Plugin** | TypeScript, JavaScript, or Python in agent's plugin system | Transparent, in-place mutation when the agent allows it |
| **Rules file** | Prompt-level instructions | Guidance only — agent is told to prefer `rtk <cmd>` |

Rules file integrations (Cline, Windsurf, Codex, Kilo Code, Antigravity) rely on the model following instructions. Full hook integrations (Claude Code, Cursor, Gemini) are guaranteed — the command is rewritten before the agent sees it. Plugin integrations (OpenCode, Pi) use in-place mutation via the agent's TypeScript extension API.
Rules file integrations (Cline, Windsurf, Codex, Kilo Code, Antigravity) rely on the model following instructions. Full hook integrations (Claude Code, Cursor, Gemini) are guaranteed — the command is rewritten before the agent sees it. Plugin integrations (OpenCode, Pi, OMP) use in-place mutation via the agent's TypeScript extension API.

## Windows support

Expand Down
4 changes: 3 additions & 1 deletion 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, OMP).

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, `tool_call` event, in-place mutation, `~/.omp/agent/hooks/pre/`
- **[`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 |
| OMP | TypeScript extension (`tool_call` event) | In-place mutation | Yes |
| Hermes | Python plugin (`pre_tool_call`) | In-place mutation | Yes |

## JSON Formats by Agent
Expand Down
89 changes: 89 additions & 0 deletions hooks/omp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# OMP (Oh My Pi) Hooks

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

## Design Intent

RTK's OMP 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. This separation keeps
RTK's hook fast, predictable, and composable with other OMP extensions.

## Specifics

- TypeScript extension using OMP's `ExtensionAPI` (loaded via the `hooks/pre/` discovery path)
- Subscribes to `tool_call` event, narrows to `bash` tool via `toolName` check
- 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
- Sets UI label to "RTK" via `pi.setLabel("RTK")` so the extension is identifiable in OMP's status line
- Version guard at load time: probes `rtk --version`; if the binary is missing, sets a persistent status-line warning via `session_start` (`ctx.ui.setStatus`) and disables rewrites; if `< 0.23.0`, logs a warning and disables rewrites — the extension never blocks on a missing or stale binary
- Installed to `.omp/hooks/pre/rtk.ts` (project-local) or `~/.omp/agent/hooks/pre/rtk.ts` (global)

## Architecture

OMP's extension runner loads `hooks/pre/*.ts` files as extension modules at startup. The
`ExtensionToolWrapper` passes tool input by reference, so mutating `event.input.command`
inside the handler directly modifies the params that OMP executes — the command is rewritten
transparently before the bash tool runs.

This is the same architecture as the Pi extension. OMP and Pi share a common extension API
lineage (Earendil Works).

## Install

```bash
# Project-local (default)
rtk init --agent omp
# → creates .omp/hooks/pre/rtk.ts

# Global — all projects
rtk init -g --agent omp
# → creates ~/.omp/agent/hooks/pre/rtk.ts
```

OMP auto-discovers extensions from the `hooks/pre/` directory on startup. Set the
`OMP_AGENT_DIR` environment variable to override the global install location.

Preview the install without writing files:

```bash
rtk init --agent omp --dry-run
```

## Uninstall

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

# Remove global install
rtk init --uninstall --agent omp --global
# → removes ~/.omp/agent/hooks/pre/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 loads extensions from hooks/pre/ automatically)

# 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 -p --mode text --no-session "Run: git status"
# → commands pass through unchanged; no rewrites in rtk gain --history
```

## 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 OMP's extension API
- Local interfaces (no `import type`) for maximum portability across OMP versions
138 changes: 138 additions & 0 deletions hooks/omp/rtk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// RTK OMP extension — rewrites bash commands to use rtk for token savings.
// Requires: rtk >= 0.23.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.
//
// Same architecture as the Pi extension. Uses OMP's `tool_call` event to
// intercept bash commands BEFORE execution, calling `rtk rewrite` to obtain
// the token-optimized equivalent, then mutating `event.input.command` in-place.
//
// 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

// Minimal local interfaces — no import dependency, works across OMP versions.
interface ExecResult {
stdout: string;
stderr: string;
code: number;
killed: boolean;
}
interface ToolCallEvent {
type: "tool_call";
toolName: string;
toolCallId: string;
input: Record<string, unknown>;
}
interface ExtensionContext {
cwd: string;
signal?: AbortSignal;
ui?: {
setStatus(key: string, text: string | undefined): void;
};
}
interface ExtensionAPI {
setLabel(label: string): void;
on(
event: "tool_call",
handler: (
event: ToolCallEvent,
ctx: ExtensionContext,
) =>
| Promise<{ block?: boolean; reason?: string } | void>
| { block?: boolean; reason?: string }
| void,
): void;
on(
event: "session_start",
handler: (
event: unknown,
ctx: ExtensionContext,
) => Promise<void> | void,
): void;
exec(
command: string,
args: string[],
options?: { cwd?: string; timeout?: number; signal?: AbortSignal },
): Promise<ExecResult>;
logger: { warn(msg: string): void; error(msg: string): void };
}

const REWRITE_TIMEOUT_MS = 2_000;
const MIN_SUPPORTED_RTK_MINOR = 23;

// 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): Promise<void> {
pi.setLabel("RTK");

// 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) {
pi.logger.warn("[rtk] rtk binary not found in PATH — extension disabled");
pi.on("session_start", (_event, ctx) => {
ctx?.ui?.setStatus("rtk", "RTK extension disabled: rtk binary not found in PATH.");
});
return;
}

const parsed = parseSemver(ver.stdout.replace(/^rtk\s+/, ""));
if (parsed) {
const [major, minor] = parsed;
if (major === 0 && minor < MIN_SUPPORTED_RTK_MINOR) {
pi.logger.warn(
`[rtk] rtk ${ver.stdout.trim()} is too old (need >= 0.23.0) — extension disabled`,
);
return;
}
}

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

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

// Skip already-rewritten or disabled.
if (command.trimStart().startsWith("rtk ")) return;
if (process.env.RTK_DISABLED === "1") return;

// Delegate to RTK.
const rewritten = await rewriteCommand(pi, command, ctx?.signal);
if (rewritten && rewritten !== command) {
event.input.command = rewritten;
}
} catch (err) {
// Fail open: never block execution on an unexpected error.
pi.logger.warn(
`[rtk] unexpected error in tool_call handler; passing through command: ${err instanceof Error ? err.message : String(err)}`,
);
}

// Return undefined = transparent passthrough (no block).
});
}
6 changes: 6 additions & 0 deletions src/hooks/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ 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";

pub const OMP_DIR: &str = ".omp/agent";
pub const OMP_LOCAL_DIR: &str = ".omp";
pub const OMP_HOOKS_PRE_SUBDIR: &str = "hooks/pre";
pub const OMP_PLUGIN_FILE: &str = "rtk.ts";
pub const OMP_AGENT_DIR_ENV: &str = "OMP_AGENT_DIR";

/// Factory Droid config directory, joined onto the resolved home directory.
pub const DROID_DIR: &str = ".factory";
/// Canonical Droid hooks file (Droid's own /hooks UI reads and writes this).
Expand Down
Loading