From 52c5a9bc496e25b7317da9e7cd8be77ea34461a5 Mon Sep 17 00:00:00 2001 From: alexneyman Date: Fri, 6 Mar 2026 01:36:15 -0500 Subject: [PATCH 1/3] feat: add OpenClaw plugin for transparent exec rewriting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an OpenClaw plugin that intercepts exec tool calls via the before_tool_call hook and rewrites commands to their RTK equivalents. This is the OpenClaw equivalent of hooks/rtk-rewrite.sh for Claude Code. The plugin: - Registers a before_tool_call hook on the exec tool - Rewrites git, grep, find, ls, gh, docker, kubectl, and test commands - Guards against rewriting piped/compound commands and heredocs - Returns properly typed PluginHookBeforeToolCallResult - Supports enabled/verbose config options Measured savings: 48-87% token reduction on common commands. Files: - openclaw/index.ts — plugin source - openclaw/openclaw.plugin.json — plugin manifest - openclaw/README.md — installation and usage docs --- openclaw/README.md | 106 +++++++++++++++++++++++++++++ openclaw/index.ts | 122 ++++++++++++++++++++++++++++++++++ openclaw/openclaw.plugin.json | 28 ++++++++ 3 files changed, 256 insertions(+) create mode 100644 openclaw/README.md create mode 100644 openclaw/index.ts create mode 100644 openclaw/openclaw.plugin.json diff --git a/openclaw/README.md b/openclaw/README.md new file mode 100644 index 000000000..d4a7d5849 --- /dev/null +++ b/openclaw/README.md @@ -0,0 +1,106 @@ +# RTK Plugin for OpenClaw + +Transparently rewrites shell commands executed via OpenClaw's `exec` tool to their RTK equivalents, achieving 60-90% LLM token savings. + +This is the OpenClaw equivalent of the Claude Code hooks in `hooks/rtk-rewrite.sh`. + +## How it works + +The plugin registers a `before_tool_call` hook that intercepts `exec` tool calls. When the agent runs a command like `git status`, the plugin rewrites it to `rtk git status` before execution. The compressed output enters the agent's context window, saving tokens. + +## Installation + +### Prerequisites + +RTK must be installed and available in `$PATH`: + +```bash +brew install rtk-ai/tap/rtk +# or +curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh +``` + +### Install the plugin + +```bash +# Copy the plugin to OpenClaw's extensions directory +mkdir -p ~/.openclaw/extensions/rtk-rewrite +cp openclaw/index.ts openclaw/openclaw.plugin.json ~/.openclaw/extensions/rtk-rewrite/ + +# Enable in OpenClaw config +openclaw config set plugins.entries.rtk-rewrite.enabled true + +# Restart the gateway +openclaw gateway restart +``` + +### Or install via OpenClaw CLI + +```bash +openclaw plugins install ./openclaw +``` + +## Configuration + +In `openclaw.json`: + +```json5 +{ + plugins: { + entries: { + "rtk-rewrite": { + enabled: true, + config: { + enabled: true, // Toggle rewriting on/off + verbose: false // Log rewrites to console + } + } + } + } +} +``` + +## What gets rewritten + +| Command | Rewritten to | +|---------|-------------| +| `git status/diff/log/...` | `rtk git status/diff/log/...` | +| `gh pr/issue/run` | `rtk gh pr/issue/run` | +| `grep/rg` | `rtk grep` | +| `find` | `rtk find` | +| `ls` | `rtk ls` | +| `tsc/eslint/prettier` | `rtk tsc/lint/prettier` | +| `vitest/pytest/go test` | `rtk vitest/pytest/go test` | +| `docker ps/images/logs` | `rtk docker ps/images/logs` | +| `kubectl get/logs` | `rtk kubectl get/logs` | + +## What's NOT rewritten (guards) + +- Commands already using `rtk` +- Piped commands (`|`, `&&`, `;`) +- Heredocs (`<<`) +- Commands not in the rewrite table (e.g., `cat`, `echo`, `curl`) + +## Measured savings + +| Command | Token savings | +|---------|--------------| +| `git log --stat` | 87% | +| `ls -la` | 78% | +| `git status` | 66% | +| `grep` (single file) | 52% | +| `find -name` | 48% | + +## How it compares to Claude Code hooks + +| Feature | CC Hook (`hooks/rtk-rewrite.sh`) | OpenClaw Plugin | +|---------|----------------------------------|-----------------| +| Hook type | Shell script (PreToolUse) | TypeScript (before_tool_call) | +| Rewrite approach | Bash regex | JS regex | +| Installation | `rtk init --global` | Copy to extensions dir | +| Configuration | `.claude/settings.json` | `openclaw.json` | +| Scope | Claude Code sessions | All OpenClaw agents | + +## License + +MIT — same as RTK. diff --git a/openclaw/index.ts b/openclaw/index.ts new file mode 100644 index 000000000..8404bd791 --- /dev/null +++ b/openclaw/index.ts @@ -0,0 +1,122 @@ +/** + * RTK Rewrite Plugin for OpenClaw + * + * Transparently rewrites exec tool commands to RTK equivalents + * before execution, achieving 60-90% LLM token savings. + */ + +// --------------------------------------------------------------------------- +// Rewrite rules +// --------------------------------------------------------------------------- + +interface RewriteRule { + pattern: RegExp; + replacement: string; +} + +const REWRITE_RULES: RewriteRule[] = [ + // Git + { pattern: /^git\s+status(\s|$)/, replacement: "rtk git status$1" }, + { pattern: /^git\s+diff(\s|$)/, replacement: "rtk git diff$1" }, + { pattern: /^git\s+log(\s|$)/, replacement: "rtk git log$1" }, + { pattern: /^git\s+add(\s|$)/, replacement: "rtk git add$1" }, + { pattern: /^git\s+commit(\s|$)/, replacement: "rtk git commit$1" }, + { pattern: /^git\s+push(\s|$)/, replacement: "rtk git push$1" }, + { pattern: /^git\s+pull(\s|$)/, replacement: "rtk git pull$1" }, + { pattern: /^git\s+branch(\s|$)/, replacement: "rtk git branch$1" }, + { pattern: /^git\s+fetch(\s|$)/, replacement: "rtk git fetch$1" }, + { pattern: /^git\s+show(\s|$)/, replacement: "rtk git show$1" }, + + // GitHub CLI + { pattern: /^gh\s+(pr|issue|run)(\s|$)/, replacement: "rtk gh $1$2" }, + + // File operations + { pattern: /^(rg|grep)\s+/, replacement: "rtk grep " }, + { pattern: /^ls(\s|$)/, replacement: "rtk ls$1" }, + { pattern: /^find\s+/, replacement: "rtk find " }, + + // JS/TS tooling + { pattern: /^(pnpm\s+)?vitest(\s|$)/, replacement: "rtk vitest run$2" }, + { pattern: /^pnpm\s+test(\s|$)/, replacement: "rtk vitest run$1" }, + { pattern: /^(npx\s+)?tsc(\s|$)/, replacement: "rtk tsc$2" }, + { pattern: /^(npx\s+)?eslint(\s|$)/, replacement: "rtk lint$2" }, + { pattern: /^pnpm\s+lint(\s|$)/, replacement: "rtk lint$1" }, + { pattern: /^(npx\s+)?prisma(\s|$)/, replacement: "rtk prisma$2" }, + + // npm/pnpm + { pattern: /^npm\s+test(\s|$)/, replacement: "rtk test npm test$1" }, + { pattern: /^pnpm\s+(list|ls|outdated)(\s|$)/, replacement: "rtk pnpm $1$2" }, + + // Containers + { pattern: /^docker\s+(ps|images|logs)(\s|$)/, replacement: "rtk docker $1$2" }, + { pattern: /^kubectl\s+(get|logs)(\s|$)/, replacement: "rtk kubectl $1$2" }, + + // Python + { pattern: /^pytest(\s|$)/, replacement: "rtk pytest$1" }, + { pattern: /^python\s+-m\s+pytest(\s|$)/, replacement: "rtk pytest$1" }, + + // Go + { pattern: /^go\s+test(\s|$)/, replacement: "rtk go test$1" }, + { pattern: /^go\s+build(\s|$)/, replacement: "rtk go build$1" }, +]; + +// --------------------------------------------------------------------------- +// Guards +// --------------------------------------------------------------------------- + +function shouldSkip(cmd: string): boolean { + // Already using rtk + if (/^rtk\s/.test(cmd)) return true; + // Heredocs, pipes, compound commands — too complex to safely rewrite + if (cmd.includes("<<") || cmd.includes("|") || cmd.includes("&&") || cmd.includes(";")) return true; + return false; +} + +function tryRewrite(cmd: string): string | null { + if (shouldSkip(cmd)) return null; + for (const rule of REWRITE_RULES) { + if (rule.pattern.test(cmd)) { + return cmd.replace(rule.pattern, rule.replacement); + } + } + return null; +} + +// --------------------------------------------------------------------------- +// Plugin +// --------------------------------------------------------------------------- + +export default function register(api: any) { + const pluginConfig = api.config ?? {}; + const enabled = pluginConfig.enabled !== false; + const verbose = pluginConfig.verbose === true; + + if (!enabled) return; + + api.on( + "before_tool_call", + (event: { toolName: string; params: Record }) => { + if (event.toolName !== "exec") return; + + const command = event.params?.command; + if (typeof command !== "string") return; + + const rewritten = tryRewrite(command); + if (!rewritten) return; + + if (verbose) { + console.log(`[rtk-rewrite] ${command} → ${rewritten}`); + } + + // Return updated params per the PluginHookBeforeToolCallResult type + return { params: { ...event.params, command: rewritten } }; + }, + { priority: 10 } + ); + + if (verbose) { + console.log(`[rtk-rewrite] Registered (${REWRITE_RULES.length} rules)`); + } +} + +export { tryRewrite, shouldSkip, REWRITE_RULES }; diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json new file mode 100644 index 000000000..121983abd --- /dev/null +++ b/openclaw/openclaw.plugin.json @@ -0,0 +1,28 @@ +{ + "id": "rtk-rewrite", + "name": "RTK Token Optimizer", + "version": "0.15.1", + "description": "Transparently rewrites shell commands to their RTK equivalents for 60-90% LLM token savings", + "homepage": "https://github.com/aimagist/openrtklaw", + "license": "MIT", + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Enable automatic command rewriting to RTK equivalents" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Log rewrite decisions to console for debugging" + } + } + }, + "uiHints": { + "enabled": { "label": "Enable RTK rewriting" }, + "verbose": { "label": "Verbose logging" } + } +} From 41023b30dfed75c4e1146479373dd4bec2808011 Mon Sep 17 00:00:00 2001 From: Patrick szymkowiak Date: Wed, 18 Mar 2026 11:04:19 +0100 Subject: [PATCH 2/3] refactor: delegate OpenClaw plugin to rtk rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace 60+ hardcoded regex rules with a single call to `rtk rewrite`, matching the OpenCode plugin pattern (hooks/opencode-rtk.ts). Benefits: - Zero maintenance: new RTK filters work automatically - Single source of truth: rewrite logic in Rust (src/discover/registry.rs) - 122 → 73 lines, no rule duplication Also: rebase on develop, fix homepage URL, bump version to 1.0.0. Signed-off-by: Patrick szymkowiak --- openclaw/README.md | 38 +++--------- openclaw/index.ts | 110 ++++++++++------------------------ openclaw/openclaw.plugin.json | 4 +- 3 files changed, 42 insertions(+), 110 deletions(-) diff --git a/openclaw/README.md b/openclaw/README.md index d4a7d5849..301d7c0fa 100644 --- a/openclaw/README.md +++ b/openclaw/README.md @@ -6,7 +6,9 @@ This is the OpenClaw equivalent of the Claude Code hooks in `hooks/rtk-rewrite.s ## How it works -The plugin registers a `before_tool_call` hook that intercepts `exec` tool calls. When the agent runs a command like `git status`, the plugin rewrites it to `rtk git status` before execution. The compressed output enters the agent's context window, saving tokens. +The plugin registers a `before_tool_call` hook that intercepts `exec` tool calls. When the agent runs a command like `git status`, the plugin delegates to `rtk rewrite` which returns the optimized command (e.g. `rtk git status`). The compressed output enters the agent's context window, saving tokens. + +All rewrite logic lives in RTK itself (`rtk rewrite`). This plugin is a thin delegate -- when new filters are added to RTK, the plugin picks them up automatically with zero changes. ## Installation @@ -15,7 +17,7 @@ The plugin registers a `before_tool_call` hook that intercepts `exec` tool calls RTK must be installed and available in `$PATH`: ```bash -brew install rtk-ai/tap/rtk +brew install rtk # or curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh ``` @@ -27,9 +29,6 @@ curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/instal mkdir -p ~/.openclaw/extensions/rtk-rewrite cp openclaw/index.ts openclaw/openclaw.plugin.json ~/.openclaw/extensions/rtk-rewrite/ -# Enable in OpenClaw config -openclaw config set plugins.entries.rtk-rewrite.enabled true - # Restart the gateway openclaw gateway restart ``` @@ -62,24 +61,15 @@ In `openclaw.json`: ## What gets rewritten -| Command | Rewritten to | -|---------|-------------| -| `git status/diff/log/...` | `rtk git status/diff/log/...` | -| `gh pr/issue/run` | `rtk gh pr/issue/run` | -| `grep/rg` | `rtk grep` | -| `find` | `rtk find` | -| `ls` | `rtk ls` | -| `tsc/eslint/prettier` | `rtk tsc/lint/prettier` | -| `vitest/pytest/go test` | `rtk vitest/pytest/go test` | -| `docker ps/images/logs` | `rtk docker ps/images/logs` | -| `kubectl get/logs` | `rtk kubectl get/logs` | +Everything that `rtk rewrite` supports (30+ commands). See the [full command list](https://github.com/rtk-ai/rtk#commands). -## What's NOT rewritten (guards) +## What's NOT rewritten +Handled by `rtk rewrite` guards: - Commands already using `rtk` - Piped commands (`|`, `&&`, `;`) - Heredocs (`<<`) -- Commands not in the rewrite table (e.g., `cat`, `echo`, `curl`) +- Commands without an RTK filter ## Measured savings @@ -91,16 +81,6 @@ In `openclaw.json`: | `grep` (single file) | 52% | | `find -name` | 48% | -## How it compares to Claude Code hooks - -| Feature | CC Hook (`hooks/rtk-rewrite.sh`) | OpenClaw Plugin | -|---------|----------------------------------|-----------------| -| Hook type | Shell script (PreToolUse) | TypeScript (before_tool_call) | -| Rewrite approach | Bash regex | JS regex | -| Installation | `rtk init --global` | Copy to extensions dir | -| Configuration | `.claude/settings.json` | `openclaw.json` | -| Scope | Claude Code sessions | All OpenClaw agents | - ## License -MIT — same as RTK. +MIT -- same as RTK. diff --git a/openclaw/index.ts b/openclaw/index.ts index 8404bd791..17ea4ec93 100644 --- a/openclaw/index.ts +++ b/openclaw/index.ts @@ -3,89 +3,39 @@ * * Transparently rewrites exec tool commands to RTK equivalents * before execution, achieving 60-90% LLM token savings. + * + * All rewrite logic lives in `rtk rewrite` (src/discover/registry.rs). + * This plugin is a thin delegate — to add or change rules, edit the + * Rust registry, not this file. */ -// --------------------------------------------------------------------------- -// Rewrite rules -// --------------------------------------------------------------------------- - -interface RewriteRule { - pattern: RegExp; - replacement: string; -} - -const REWRITE_RULES: RewriteRule[] = [ - // Git - { pattern: /^git\s+status(\s|$)/, replacement: "rtk git status$1" }, - { pattern: /^git\s+diff(\s|$)/, replacement: "rtk git diff$1" }, - { pattern: /^git\s+log(\s|$)/, replacement: "rtk git log$1" }, - { pattern: /^git\s+add(\s|$)/, replacement: "rtk git add$1" }, - { pattern: /^git\s+commit(\s|$)/, replacement: "rtk git commit$1" }, - { pattern: /^git\s+push(\s|$)/, replacement: "rtk git push$1" }, - { pattern: /^git\s+pull(\s|$)/, replacement: "rtk git pull$1" }, - { pattern: /^git\s+branch(\s|$)/, replacement: "rtk git branch$1" }, - { pattern: /^git\s+fetch(\s|$)/, replacement: "rtk git fetch$1" }, - { pattern: /^git\s+show(\s|$)/, replacement: "rtk git show$1" }, - - // GitHub CLI - { pattern: /^gh\s+(pr|issue|run)(\s|$)/, replacement: "rtk gh $1$2" }, - - // File operations - { pattern: /^(rg|grep)\s+/, replacement: "rtk grep " }, - { pattern: /^ls(\s|$)/, replacement: "rtk ls$1" }, - { pattern: /^find\s+/, replacement: "rtk find " }, - - // JS/TS tooling - { pattern: /^(pnpm\s+)?vitest(\s|$)/, replacement: "rtk vitest run$2" }, - { pattern: /^pnpm\s+test(\s|$)/, replacement: "rtk vitest run$1" }, - { pattern: /^(npx\s+)?tsc(\s|$)/, replacement: "rtk tsc$2" }, - { pattern: /^(npx\s+)?eslint(\s|$)/, replacement: "rtk lint$2" }, - { pattern: /^pnpm\s+lint(\s|$)/, replacement: "rtk lint$1" }, - { pattern: /^(npx\s+)?prisma(\s|$)/, replacement: "rtk prisma$2" }, - - // npm/pnpm - { pattern: /^npm\s+test(\s|$)/, replacement: "rtk test npm test$1" }, - { pattern: /^pnpm\s+(list|ls|outdated)(\s|$)/, replacement: "rtk pnpm $1$2" }, +import { execSync } from "node:child_process"; - // Containers - { pattern: /^docker\s+(ps|images|logs)(\s|$)/, replacement: "rtk docker $1$2" }, - { pattern: /^kubectl\s+(get|logs)(\s|$)/, replacement: "rtk kubectl $1$2" }, +let rtkAvailable: boolean | null = null; - // Python - { pattern: /^pytest(\s|$)/, replacement: "rtk pytest$1" }, - { pattern: /^python\s+-m\s+pytest(\s|$)/, replacement: "rtk pytest$1" }, - - // Go - { pattern: /^go\s+test(\s|$)/, replacement: "rtk go test$1" }, - { pattern: /^go\s+build(\s|$)/, replacement: "rtk go build$1" }, -]; - -// --------------------------------------------------------------------------- -// Guards -// --------------------------------------------------------------------------- - -function shouldSkip(cmd: string): boolean { - // Already using rtk - if (/^rtk\s/.test(cmd)) return true; - // Heredocs, pipes, compound commands — too complex to safely rewrite - if (cmd.includes("<<") || cmd.includes("|") || cmd.includes("&&") || cmd.includes(";")) return true; - return false; +function checkRtk(): boolean { + if (rtkAvailable !== null) return rtkAvailable; + try { + execSync("which rtk", { stdio: "ignore" }); + rtkAvailable = true; + } catch { + rtkAvailable = false; + } + return rtkAvailable; } -function tryRewrite(cmd: string): string | null { - if (shouldSkip(cmd)) return null; - for (const rule of REWRITE_RULES) { - if (rule.pattern.test(cmd)) { - return cmd.replace(rule.pattern, rule.replacement); - } +function tryRewrite(command: string): string | null { + try { + const result = execSync(`rtk rewrite ${JSON.stringify(command)}`, { + encoding: "utf-8", + timeout: 2000, + }).trim(); + return result && result !== command ? result : null; + } catch { + return null; } - return null; } -// --------------------------------------------------------------------------- -// Plugin -// --------------------------------------------------------------------------- - export default function register(api: any) { const pluginConfig = api.config ?? {}; const enabled = pluginConfig.enabled !== false; @@ -93,6 +43,11 @@ export default function register(api: any) { if (!enabled) return; + if (!checkRtk()) { + console.warn("[rtk] rtk binary not found in PATH — plugin disabled"); + return; + } + api.on( "before_tool_call", (event: { toolName: string; params: Record }) => { @@ -105,18 +60,15 @@ export default function register(api: any) { if (!rewritten) return; if (verbose) { - console.log(`[rtk-rewrite] ${command} → ${rewritten}`); + console.log(`[rtk] ${command} -> ${rewritten}`); } - // Return updated params per the PluginHookBeforeToolCallResult type return { params: { ...event.params, command: rewritten } }; }, { priority: 10 } ); if (verbose) { - console.log(`[rtk-rewrite] Registered (${REWRITE_RULES.length} rules)`); + console.log("[rtk] OpenClaw plugin registered"); } } - -export { tryRewrite, shouldSkip, REWRITE_RULES }; diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json index 121983abd..3fce418d7 100644 --- a/openclaw/openclaw.plugin.json +++ b/openclaw/openclaw.plugin.json @@ -1,9 +1,9 @@ { "id": "rtk-rewrite", "name": "RTK Token Optimizer", - "version": "0.15.1", + "version": "1.0.0", "description": "Transparently rewrites shell commands to their RTK equivalents for 60-90% LLM token savings", - "homepage": "https://github.com/aimagist/openrtklaw", + "homepage": "https://github.com/rtk-ai/rtk", "license": "MIT", "configSchema": { "type": "object", From 163029716090b039d687975fced3e25cfdafa76a Mon Sep 17 00:00:00 2001 From: Patrick szymkowiak Date: Wed, 18 Mar 2026 11:07:53 +0100 Subject: [PATCH 3/3] feat: add package.json for npm publishing Enables `openclaw plugins install @rtk-ai/rtk-rewrite` for OpenClaw users. Signed-off-by: Patrick szymkowiak --- openclaw/package.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 openclaw/package.json diff --git a/openclaw/package.json b/openclaw/package.json new file mode 100644 index 000000000..18d359ff4 --- /dev/null +++ b/openclaw/package.json @@ -0,0 +1,29 @@ +{ + "name": "@rtk-ai/rtk-rewrite", + "version": "1.0.0", + "description": "RTK plugin for OpenClaw — rewrites shell commands for 60-90% LLM token savings", + "main": "index.ts", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/rtk-ai/rtk", + "directory": "openclaw" + }, + "homepage": "https://github.com/rtk-ai/rtk", + "keywords": [ + "rtk", + "openclaw", + "openclaw-plugin", + "token-savings", + "llm", + "cli-proxy" + ], + "files": [ + "index.ts", + "openclaw.plugin.json", + "README.md" + ], + "peerDependencies": { + "rtk": ">=0.28.0" + } +}