/** * Bash intent interceptor - redirects common shell patterns to proper tools. * * When an LLM calls bash with patterns like `grep`, `cat`, `find`, etc., * this interceptor provides helpful error messages directing them to use * the specialized tools instead. */ import type { BashInterceptorRule } from "../config/settings-schema"; export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [ { pattern: "^\\s*(cat|head|tail|less|more)\\s+", tool: "read", message: "Use the `read` tool instead of cat/head/tail. It provides better context and handles binary files.", }, { pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+", tool: "grep", message: "Use the `grep` tool instead of grep/rg. It respects .gitignore and provides structured output.", }, { pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)", tool: "find", message: "Use the `find` tool instead of find/fd. It respects .gitignore and is faster for glob patterns.", }, { pattern: "^\\s*sed\\s+(-i|--in-place)", tool: "edit", message: "Use the `edit` tool instead of sed -i. It provides diff preview and fuzzy matching.", }, { pattern: "^\\s*perl\\s+.*-[pn]?i", tool: "edit", message: "Use the `edit` tool instead of perl -i. It provides diff preview and fuzzy matching.", }, { pattern: "^\\s*awk\\s+.*-i\\s+inplace", tool: "edit", message: "Use the `edit` tool instead of awk -i inplace. It provides diff preview and fuzzy matching.", }, { pattern: "^\\s*(echo|printf|cat\\s*<<)\\s+.*[^|]>\\s*\\S", tool: "write", message: "Use the `write` tool instead of echo/cat redirection. It handles encoding and provides confirmation.", }, ]; export interface InterceptionResult { /** If true, the bash command should be blocked */ block: boolean; /** Error message to return instead of executing */ message?: string; /** Suggested tool to use instead */ suggestedTool?: string; } /** * Compile bash interceptor rules into regexes, skipping invalid patterns. */ function compileRules(rules: BashInterceptorRule[]): Array<{ rule: BashInterceptorRule; regex: RegExp }> { const compiled: Array<{ rule: BashInterceptorRule; regex: RegExp }> = []; for (const rule of rules) { const flags = rule.flags ?? ""; try { compiled.push({ rule, regex: new RegExp(rule.pattern, flags) }); } catch { // Skip invalid regex patterns } } return compiled; } /** * Check if a bash command should be intercepted. * * @param command The bash command to check * @param availableTools Set of tool names that are available * @returns InterceptionResult indicating if the command should be blocked */ export function checkBashInterception( command: string, availableTools: string[], rules: BashInterceptorRule[] = DEFAULT_BASH_INTERCEPTOR_RULES, ): InterceptionResult { // Normalize command for pattern matching const normalizedCommand = command.trim(); const compiled = compileRules(rules); for (const { rule, regex } of compiled) { // Only block if the suggested tool is actually available if (!availableTools.includes(rule.tool)) { continue; } if (regex.test(normalizedCommand)) { return { block: true, message: `Blocked: ${rule.message}\n\nOriginal command: ${command}`, suggestedTool: rule.tool, }; } } return { block: false }; }