diff --git a/.chezmoitemplates/rtk-agent-instructions.md b/.chezmoitemplates/rtk-agent-instructions.md new file mode 100644 index 0000000..17ddb7c --- /dev/null +++ b/.chezmoitemplates/rtk-agent-instructions.md @@ -0,0 +1,5 @@ +## RTK + +Prefix supported shell commands with `rtk` so command output is compressed before it hits context (`rtk git status`, `rtk rg PATTERN`, `rtk test`, `rtk lint`). If RTK has no filter, it passes through unchanged. Claude Code rewrites Bash automatically; still prefix `rtk` in Codex, Grok, and T3 if a rewrite hook does not fire. + +Meta commands (always `rtk` directly): `rtk gain`, `rtk gain --history`, `rtk proxy `. If `rtk gain` fails, the wrong `rtk` package is on PATH. diff --git a/dot_claude/CLAUDE.md.tmpl b/dot_claude/CLAUDE.md.tmpl index ad69cc4..a1bb956 100644 --- a/dot_claude/CLAUDE.md.tmpl +++ b/dot_claude/CLAUDE.md.tmpl @@ -1,6 +1,7 @@ # Personal Preferences {{ template "shared-agent-working-agreements.md" . }} +{{ template "rtk-agent-instructions.md" . }} ## Commands - Don't start dev server commands such as `pnpm run dev`; assume the server is already running unless instructed otherwise. diff --git a/dot_claude/RTK.md b/dot_claude/RTK.md new file mode 100644 index 0000000..d1c3f00 --- /dev/null +++ b/dot_claude/RTK.md @@ -0,0 +1,28 @@ +# RTK - Rust Token Killer + +**Usage**: Token-optimized CLI proxy (cuts up to 90% of bash output) + +## Meta Commands (always use rtk directly) + +```bash +rtk gain # Show token savings analytics +rtk gain --history # Show command usage history with savings +rtk discover # Analyze Claude Code history for missed opportunities +rtk proxy # Execute raw command without filtering (for debugging) +``` + +## Installation Verification + +```bash +rtk --version # Should show: rtk X.Y.Z +rtk gain # Should work (not "command not found") +which rtk # Verify correct binary +``` + +⚠️ **Name collision**: If `rtk gain` fails, you may have reachingforthejack/rtk (Rust Type Kit) installed instead. + +## Hook-Based Usage + +Claude Code rewrites Bash through `rtk hook claude`. Grok/T3 and Codex use `~/.local/bin/rtk-pretooluse`, which normalizes the payload and then calls that same hook. + +Example: `git status` → `rtk git status` diff --git a/dot_claude/modify_settings.json b/dot_claude/modify_settings.json new file mode 100755 index 0000000..d887673 --- /dev/null +++ b/dot_claude/modify_settings.json @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Merge the RTK PreToolUse adapter into ~/.claude/settings.json without taking over the file.""" +from __future__ import annotations + +import json +import os +import sys + +ADAPTER = os.path.expanduser("~/.local/bin/rtk-pretooluse") +HOOK_ENTRY = { + "matcher": "Bash", + "hooks": [{"type": "command", "command": ADAPTER}], +} + + +def _is_rtk_entry(entry: object) -> bool: + if not isinstance(entry, dict): + return False + for hook in entry.get("hooks") or []: + if not isinstance(hook, dict): + continue + command = hook.get("command") + if not isinstance(command, str): + continue + if "rtk hook claude" in command or command.endswith("rtk-pretooluse") or command == ADAPTER: + return True + return False + + +def main() -> int: + raw = sys.stdin.read() + if not raw.strip(): + data = {} + original = None + else: + original = raw + data = json.loads(raw) + + hooks = data.setdefault("hooks", {}) + if not isinstance(hooks, dict): + hooks = {} + data["hooks"] = hooks + pre = hooks.get("PreToolUse") + if not isinstance(pre, list): + pre = [] + hooks["PreToolUse"] = pre + + existing = [entry for entry in pre if _is_rtk_entry(entry)] + already = ( + len(existing) == 1 + and existing[0].get("matcher") == "Bash" + and (existing[0].get("hooks") or [{}])[0].get("command") == ADAPTER + and sum(1 for entry in pre if _is_rtk_entry(entry)) == 1 + ) + if already and original is not None: + sys.stdout.write(original) + return 0 + + pre[:] = [entry for entry in pre if not _is_rtk_entry(entry)] + pre.append(HOOK_ENTRY) + json.dump(data, sys.stdout, indent=2) + sys.stdout.write("\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/dot_codex/AGENTS.md.tmpl b/dot_codex/AGENTS.md.tmpl index 36c92bc..80b1444 100644 --- a/dot_codex/AGENTS.md.tmpl +++ b/dot_codex/AGENTS.md.tmpl @@ -1,6 +1,7 @@ # Global instructions {{ template "shared-agent-working-agreements.md" . }} +{{ template "rtk-agent-instructions.md" . }} ## Delegating to another agent CLI Grok (`grok`, grok-4.5) and Claude (`claude`) are available locally and already authenticated. Use the `delegate-review` skill for an independent second-pass review and the `delegate-implementation` skill for bounded, well-specified implementation work. Those skills own the verified invocation, sandbox, worktree, and recovery contracts; do not improvise them. diff --git a/dot_codex/hooks.json.tmpl b/dot_codex/hooks.json.tmpl new file mode 100644 index 0000000..eb19664 --- /dev/null +++ b/dot_codex/hooks.json.tmpl @@ -0,0 +1,17 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "{{ .chezmoi.homeDir }}/.local/bin/rtk-pretooluse", + "statusMessage": "RTK rewrite", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/dot_config/packages/Brewfile b/dot_config/packages/Brewfile index 3448ac7..8b9e604 100644 --- a/dot_config/packages/Brewfile +++ b/dot_config/packages/Brewfile @@ -4,6 +4,7 @@ brew "gh" brew "starship" brew "mise" brew "imagemagick" +brew "rtk" cask "1password-cli" cask "font-jetbrains-mono" cask "font-jetbrains-mono-nerd-font" diff --git a/dot_grok/hooks/rtk.json.tmpl b/dot_grok/hooks/rtk.json.tmpl new file mode 100644 index 0000000..1946366 --- /dev/null +++ b/dot_grok/hooks/rtk.json.tmpl @@ -0,0 +1,16 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash|run_terminal_command", + "hooks": [ + { + "type": "command", + "command": "{{ .chezmoi.homeDir }}/.local/bin/rtk-pretooluse", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/dot_local/bin/executable_rtk-pretooluse b/dot_local/bin/executable_rtk-pretooluse new file mode 100755 index 0000000..f9002f6 --- /dev/null +++ b/dot_local/bin/executable_rtk-pretooluse @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Normalize PreToolUse payloads and rewrite shell commands through `rtk hook claude`. + +Claude Code sends snake_case `tool_name` / `tool_input`. Grok sends camelCase +`toolName` / `toolInput` and names the shell tool `run_terminal_command`. +`rtk hook claude` only rewrites the Claude shape, so Grok (and T3-through-Grok) +silently no-op without this adapter. +""" +from __future__ import annotations + +import json +import subprocess +import sys + + +def _command(payload: object) -> str | None: + if not isinstance(payload, dict): + return None + tool_input = payload.get("tool_input") + if not isinstance(tool_input, dict): + tool_input = payload.get("toolInput") + if not isinstance(tool_input, dict): + return None + command = tool_input.get("command") + return command if isinstance(command, str) and command.strip() else None + + +def main() -> int: + raw = sys.stdin.buffer.read() + if not raw.strip(): + return 0 + try: + payload = json.loads(raw) + except json.JSONDecodeError: + return 0 + command = _command(payload) + if command is None: + return 0 + claude_payload = { + "hook_event_name": "PreToolUse", + "tool_name": "Bash", + "tool_input": {"command": command}, + } + try: + result = subprocess.run( + ["rtk", "hook", "claude"], + input=json.dumps(claude_payload).encode(), + capture_output=True, + check=False, + ) + except OSError: + return 0 + if result.returncode != 0 or not result.stdout.strip(): + return 0 + sys.stdout.buffer.write(result.stdout) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())