Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0fcd6ad
fix(hooks): stop Copilot from ever asserting a permissionDecision on …
KuSh Jul 25, 2026
b754b85
fix(hooks): drop redundant camelCase preToolUse entry from Copilot ho…
KuSh Jul 26, 2026
1ebc271
fix(hooks): recognize VS Code Copilot Chat's run_in_terminal tool name
KuSh Jul 26, 2026
1253d15
docs(hooks): clarify CopilotCli schema is now legacy/host-specific, n…
KuSh Jul 26, 2026
2cb3908
docs(hooks): confirm PascalCase Bash mapping already covers Windows p…
KuSh Jul 27, 2026
fc8054e
feat(rewrite): rewrite each line of multi-line Bash blocks
revopsrocks Jul 24, 2026
ac5febe
fix(rewrite): bail on swallowed newlines and cross-line arithmetic
revopsrocks Jul 24, 2026
93aab9a
fix(rewrite): harden line-independence checks from second review round
revopsrocks Jul 24, 2026
d171449
fix(rewrite): narrow multiline passthrough bails from #3188 review
aeppling Jul 30, 2026
3c8892f
fix(rewrite): keep rewriting quoted multi-line strings as one command
aeppling Jul 30, 2026
b09da2d
fix(rewrite): bail on cross-line [[ ]], share quote-state walker
aeppling Aug 1, 2026
8722378
Merge pull request #3212 from rtk-ai/fix/copilot-parity-omit-permissi…
KuSh Aug 2, 2026
3044911
Merge pull request #3319 from rtk-ai/feat/rewrite-multiline-blocks
aeppling Aug 3, 2026
d480f1e
feat(hooks): add transparent hook support for Mistral Vibe CLI
xavierpestel-ai Aug 4, 2026
1847b07
fix(vibe): address PR review — exit code contract, tests, telemetry, …
xavierpestel-ai Aug 5, 2026
94ae76b
docs(vibe): add hooks/vibe/README.md and link from Directory Structure
xavierpestel-ai Aug 5, 2026
0430df4
refactor(vibe): drop defensive-only comment on Host::Vibe arm
xavierpestel-ai Aug 5, 2026
d1f7139
fix(hooks): self-heal stale dual-schema Copilot hook config
aeppling Aug 5, 2026
db31da9
fix(hooks): heal only rtk's own legacy camelCase entry, keep user config
aeppling Aug 5, 2026
f676ad7
test(hooks): end-to-end matrix for Copilot self-heal safety
aeppling Aug 5, 2026
157b0ac
refacto(copilot): rm migration tests
aeppling Aug 5, 2026
99841dc
chore(hooks): suppress semgrep on temp-file cleanup
aeppling Aug 5, 2026
4ff41bd
ci(semgrep): suppress filesystem-deletion warnings on Vibe uninstall …
xavierpestel-ai Aug 5, 2026
9dae4d7
refactor(vibe): address second-round nits from @aeppling
xavierpestel-ai Aug 5, 2026
de1f568
Merge pull request #3391 from xavierpestel-ai/feat/vibe-hook-support
aeppling Aug 6, 2026
9936b2b
Merge pull request #3449 from rtk-ai/feat/copilot-hook-self-heal
aeppling Aug 6, 2026
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ rtk init -g

## Supported AI Tools

RTK supports 15 AI coding tools. Each integration rewrites shell commands to `rtk` equivalents, reducing the bash output the agent reads where the agent supports command interception.
RTK supports 16 AI coding tools. Each integration rewrites shell commands to `rtk` equivalents, reducing the bash output the agent reads where the agent supports command interception.

| Tool | Install | Method |
|------|---------|--------|
Expand All @@ -397,7 +397,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`) |
| **Mistral Vibe** | Planned ([#800](https://github.com/rtk-ai/rtk/issues/800)) | Blocked on upstream |
| **Mistral Vibe** | `rtk init -g --agent vibe` | `pre_tool` hook (hooks.toml) |
| **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) |
| **Kimi AI** | `rtk init --agent kimi` | AGENTS.md (project-scoped) |
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/TECHNICAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Start here, then drill down into each README for file-level details.
|-----------|-------|-------------------------------|
| [`hooks/`](../hooks/README.md) | _(parent)_ | **All JSON formats**, rewrite registry overview, exit code contract, override controls |
| [`claude/`](../hooks/claude/README.md) | Claude Code | Shell hook mechanism, `PreToolUse` JSON, test script |
| [`copilot/`](../hooks/copilot/README.md) | GitHub Copilot | Rust binary hook, VS Code Chat vs Copilot CLI dual format |
| [`copilot/`](../hooks/copilot/README.md) | GitHub Copilot | Rust binary hook, single `PreToolUse` schema shared by VS Code Chat and Copilot CLI |
| [`cursor/`](../hooks/cursor/README.md) | Cursor IDE | Shell hook, empty JSON response requirement |
| [`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) |
Expand All @@ -331,7 +331,7 @@ RTK supports the following LLM agents through hook integrations:
|-------|-----------|-----------|---------------------|
| Claude Code | Shell hook | `PreToolUse` in `settings.json` | Yes (`updatedInput`) |
| GitHub Copilot (VS Code) | Rust binary | `rtk hook copilot` reads JSON | Yes (`updatedInput`) |
| GitHub Copilot CLI | Rust binary | `rtk hook copilot` reads JSON | No (deny + suggestion) |
| GitHub Copilot CLI | Rust binary | `rtk hook copilot` reads JSON | Yes (`updatedInput`) |
| Cursor | Rust binary | `rtk hook cursor` reads JSON | Yes (`updated_input`) |
| Gemini CLI | Rust binary | `rtk hook gemini` reads JSON | Yes (`hookSpecificOutput`) |
| Cline/Roo Code | Rules file | Prompt-level guidance | N/A (prompt) |
Expand Down
35 changes: 28 additions & 7 deletions docs/guide/getting-started/supported-agents.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
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, Kilo Code, Antigravity, Factory Droid, and Mistral Vibe
sidebar:
order: 3
---

# Supported Agents

RTK supports all major AI coding agents across 3 integration tiers. Mistral Vibe support is planned.
RTK supports all major AI coding agents across 3 integration tiers.

## How it works

Expand All @@ -30,7 +30,7 @@ Agent runs "cargo test"
|-------|-----------------|---------------------------|
| Claude Code | Shell hook (`PreToolUse`) | Yes |
| VS Code Copilot Chat | Shell hook (`PreToolUse`) | Yes |
| GitHub Copilot CLI | Shell hook (`preToolUse` `modifiedArgs`) | Yes |
| GitHub Copilot CLI | Shell hook (`PreToolUse`) | Yes |
| Cursor | Shell hook (`preToolUse`) | Yes |
| Gemini CLI | Rust binary (`BeforeTool`) | Yes |
| OpenCode | TypeScript plugin (`tool.execute.before`) | Yes |
Expand All @@ -43,7 +43,7 @@ Agent runs "cargo test"
| Codex CLI | AGENTS.md instructions | N/A |
| Kilo Code | Rules file (prompt-level) | N/A |
| Google Antigravity | Rules file (prompt-level) | N/A |
| Mistral Vibe | Planned ([#800](https://github.com/rtk-ai/rtk/issues/800)) | Pending upstream |
| Mistral Vibe | Rust binary (`pre_tool`) | Yes |

## Installation by agent

Expand Down Expand Up @@ -74,7 +74,9 @@ rtk init --copilot # project-scoped (.github/hooks/)
rtk init --global --copilot # user-scoped (~/.copilot/hooks/, respects $COPILOT_HOME)
```

Project-scoped writes `.github/hooks/rtk-rewrite.json` (both hosts get transparent rewrite — VS Code Chat via `updatedInput`, Copilot CLI via `modifiedArgs`) plus the RTK block in `.github/copilot-instructions.md`. User-scoped writes the same hook config to `~/.copilot/hooks/rtk-rewrite.json` and the RTK block to `~/.copilot/copilot-instructions.md` (both respect `$COPILOT_HOME` if set).
Project-scoped writes `.github/hooks/rtk-rewrite.json` — a single `PreToolUse` entry shared by both hosts, each getting transparent rewrite via `updatedInput` — plus the RTK block in `.github/copilot-instructions.md`. User-scoped writes the same hook config to `~/.copilot/hooks/rtk-rewrite.json` and the RTK block to `~/.copilot/copilot-instructions.md` (both respect `$COPILOT_HOME` if set).

Earlier `rtk` versions also registered a second, camelCase `preToolUse` entry for Copilot CLI's native schema. Copilot CLI treats `PreToolUse`/`preToolUse` as independent hooks and runs both sequentially for the same tool call — a redundant process spawn with no behavioral benefit, since Copilot CLI honors the single `PreToolUse` schema on its own. Re-run `rtk init --copilot` (or `--global --copilot`) to upgrade an existing install to the single-hook config.

Uninstall:

Expand Down Expand Up @@ -195,9 +197,28 @@ rtk init --agent antigravity # creates .agents/rules/antigravity-rtk-rules.md

Antigravity reads `.agents/rules/` as custom instructions. RTK adds guidance telling Antigravity to prefer `rtk <cmd>` over raw commands.

### Mistral Vibe (planned)
### Mistral Vibe

```bash
rtk init -g --agent vibe # user-scoped (~/.vibe/hooks.toml)
rtk init -g --agent vibe --hook-only # skip the ~/.vibe/prompts/rtk.md prompt file
```

Installs a `pre_tool` hook entry (`match = "bash"`, `command = "rtk hook vibe"`, `strict = false`) into `~/.vibe/hooks.toml`, following the contract at [docs.mistral.ai/vibe/code/cli/hooks](https://docs.mistral.ai/vibe/code/cli/hooks). Vibe invokes the native `rtk hook vibe` binary before every bash tool call; RTK reads Vibe's stdin JSON payload and emits `{"hook_specific_output": {"tool_input": {"command": "rtk ..."}}}` to rewrite the command in place. The Vibe UI surfaces `[rtk-rewrite] rtk: rewrote to \`…\`` via RTK's `system_message` field so the rewrite is visible.

Unlike Droid, Vibe does not yet expose a denylist / allowlist surface in `hooks.toml` for RTK to honor. RTK therefore rewrites every bash command it knows how to compress and defers to Vibe's own permission prompt on the rewritten command; commands RTK doesn't handle pass through unchanged. `strict = false` ensures a hook crash degrades to a warning rather than blocking the tool call.

Alongside the hook, RTK drops a system prompt at `~/.vibe/prompts/rtk.md` describing the RTK conventions to Vibe as a belt-and-suspenders fallback. Use `--hook-only` to skip it.

Install is global-only (Vibe's hook registry is user-scoped). Re-running the installer is a no-op; the RTK entry is detected by its `name = "rtk-rewrite"` field and never duplicated.

Uninstall:

```bash
rtk init -g --agent vibe --uninstall
```

Support is blocked on upstream `BeforeToolCallback` ([mistral-vibe#531](https://github.com/mistralai/mistral-vibe/issues/531)). Tracked in [#800](https://github.com/rtk-ai/rtk/issues/800).
Strips only RTK's `[[hooks]]` block and the `~/.vibe/prompts/rtk.md` file. Any other user-declared hooks in `hooks.toml` are preserved byte-for-byte. `hooks.toml` is removed only when the RTK entry was the sole content.

## Integration tiers explained

Expand Down
30 changes: 29 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, Mistral Vibe).

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 @@ -42,6 +42,7 @@ Each agent subdirectory has its own README with hook-specific details:
- **[`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/`
- **[`hermes/`](hermes/README.md)** — Python plugin, `pre_tool_call` hook, in-place terminal command mutation
- **[`vibe/`](vibe/README.md)** — Rust binary hook (`rtk hook vibe`), `pre_tool` entry in `~/.vibe/hooks.toml`, `hook_specific_output.tool_input` rewrite plus `system_message` for UI visibility

## Supported Agents

Expand All @@ -58,6 +59,7 @@ Each agent subdirectory has its own README with hook-specific details:
| OpenCode | TypeScript plugin (`tool.execute.before`) | In-place mutation | Yes |
| Pi | TypeScript extension (`tool_call` event) | In-place mutation | Yes |
| Hermes | Python plugin (`pre_tool_call`) | In-place mutation | Yes |
| Mistral Vibe | Rust binary (`rtk hook vibe`) | Transparent rewrite | Yes (`hook_specific_output.tool_input`) |

## JSON Formats by Agent

Expand Down Expand Up @@ -157,6 +159,32 @@ Returns `{}` when no rewrite (Cursor requires JSON for all paths).

**No rewrite**: `{"decision": "allow"}`

### Mistral Vibe (Rust Binary)

**Input** (stdin):

```json
{
"tool_name": "bash",
"tool_input": { "command": "git status" },
"hook_event_name": "pre_tool",
"session_id": "..."
}
```

**Output** (when rewritten):

```json
{
"hook_specific_output": {
"tool_input": { "command": "rtk git status" }
},
"system_message": "rtk: rewrote to `rtk git status`"
}
```

**No rewrite**: exit 0 with empty stdout (Vibe's contract for "no opinion" from a `pre_tool` hook).

### OpenCode (TypeScript Plugin)

Mutates `args.command` in-place via the zx library:
Expand Down
19 changes: 19 additions & 0 deletions hooks/vibe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Mistral Vibe Hooks

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

## Specifics

- Uses the `rtk hook vibe` Rust binary (not a shell script) -- no `jq` dependency
- `pre_tool` hook declared in `~/.vibe/hooks.toml` (user-scoped) with `match = "bash"` and `strict = false`
- Reads Vibe's stdin JSON payload (`tool_name`, `tool_input.command`, `hook_event_name`, `session_id`)
- Returns `hook_specific_output.tool_input.command` for transparent rewrite plus a `system_message` for UI visibility
- Non-bash tool / empty command / malformed JSON / RTK-unknown command → passthrough (exit 0, empty stdout)
- RTK permission deny → `{"decision":"deny","reason":"..."}`
- Alongside the hook, a system prompt at `~/.vibe/prompts/rtk.md` is installed as a belt-and-suspenders fallback (skip with `--hook-only`)
- Installed globally via `rtk init -g --agent vibe`; there is no project-scoped variant

## Notes

- This directory intentionally holds only this README — the hook is a subcommand of the RTK binary (`rtk hook vibe`), not a standalone script or plugin file, so nothing is deployed here
- Vibe hook contract reference: https://docs.mistral.ai/vibe/code/cli/hooks
5 changes: 3 additions & 2 deletions src/core/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ fn detect_hook_type() -> String {
(home.join(".gemini/hooks/rtk-hook.sh"), "gemini"),
(home.join(".codex/AGENTS.md"), "codex"),
(home.join(".cursor/hooks/rtk-rewrite.json"), "cursor"),
(home.join(".vibe/hooks.toml"), "vibe"),
];

for (path, name) in &checks {
Expand Down Expand Up @@ -576,7 +577,7 @@ mod tests {
assert!(stats.low_savings_commands.len() <= 5);
assert!((0.0..=100.0).contains(&stats.avg_savings_per_command));
assert!(
["claude", "gemini", "codex", "cursor", "copilot", "none", "unknown"]
["claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown"]
.iter()
.any(|&h| stats.hook_type.starts_with(h)),
"Unexpected hook type: {}",
Expand All @@ -588,7 +589,7 @@ mod tests {
fn test_detect_hook_type_returns_known() {
let ht = detect_hook_type();
assert!(
["claude", "gemini", "codex", "cursor", "copilot", "none", "unknown"]
["claude", "gemini", "codex", "cursor", "copilot", "vibe", "none", "unknown"]
.contains(&ht.as_str()),
"Unexpected hook type: {}",
ht
Expand Down
20 changes: 20 additions & 0 deletions src/discover/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ pub fn tokenize(input: &str) -> Vec<ParsedToken> {
tokenize_inner(input, false)
}

/// Like [`tokenize`] but emits a `\n` operator token for each newline that
/// sits outside quotes. Newlines inside quoted strings stay part of their
/// argument, so callers can use the emitted offsets as safe line-split points.
pub fn tokenize_with_newlines(input: &str) -> Vec<ParsedToken> {
tokenize_inner(input, true)
}

fn tokenize_inner(input: &str, emit_newline: bool) -> Vec<ParsedToken> {
let mut tokens = Vec::new();
let mut current = String::new();
Expand Down Expand Up @@ -1342,4 +1349,17 @@ mod tests {
assert!(split_for_permissions("").is_empty());
assert!(split_for_permissions(" ").is_empty());
}

#[test]
fn test_tokenize_with_newlines_emits_operator_outside_quotes_only() {
let newline_ops = |input: &str| {
tokenize_with_newlines(input)
.iter()
.filter(|t| t.kind == TokenKind::Operator && t.value == "\n")
.count()
};
assert_eq!(newline_ops("git status\ngit log"), 1);
assert_eq!(newline_ops("echo 'line1\nline2'"), 0);
assert_eq!(newline_ops("git status\r\ngit log"), 2);
}
}
Loading
Loading