Yash030/claude-code-proxy
2
1"""Command parsing utilities for API optimizations."""2 3import re4import shlex5 6_ENV_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")7 8 9def _is_env_assignment(part: str) -> bool:10 """Return True when a token is a shell-style env assignment."""11 return bool(_ENV_ASSIGNMENT_RE.match(part))12 13 14def _strip_env_assignments(parts: list[str]) -> list[str]:15 """Return command parts after leading shell-style env assignments."""16 cmd_start = 017 for i, part in enumerate(parts):18 if _is_env_assignment(part):19 cmd_start = i + 120 else:21 break22 return parts[cmd_start:]23 24 25def extract_command_prefix(command: str) -> str:26 """Extract the command prefix for fast prefix detection.27 28 Parses a shell command safely, handling environment variables and29 command injection attempts. Returns the command prefix suitable30 for quick identification.31 32 Returns:33 Command prefix (e.g., "git", "git commit", "npm install")34 or "none" if no valid command found35 """36 if "`" in command or "$(" in command:37 return "command_injection_detected"38 39 try:40 parts = shlex.split(command, posix=False)41 if not parts:42 return "none"43 44 env_prefix = []45 cmd_start = 046 for i, part in enumerate(parts):47 if _is_env_assignment(part):48 env_prefix.append(part)49 cmd_start = i + 150 else:51 break52 53 if cmd_start >= len(parts):54 return "none"55 56 cmd_parts = parts[cmd_start:]57 if not cmd_parts:58 return "none"59 60 first_word = cmd_parts[0]61 two_word_commands = {62 "git",63 "npm",64 "docker",65 "kubectl",66 "cargo",67 "go",68 "pip",69 "yarn",70 }71 72 if first_word in two_word_commands and len(cmd_parts) > 1:73 second_word = cmd_parts[1]74 if not second_word.startswith("-"):75 return f"{first_word} {second_word}"76 return first_word77 return first_word if not env_prefix else " ".join(env_prefix) + " " + first_word78 79 except ValueError:80 parts = command.split()81 if not parts:82 return "none"83 cmd_parts = _strip_env_assignments(parts)84 return cmd_parts[0] if cmd_parts else "none"85 86 87def extract_filepaths_from_command(command: str, output: str) -> str:88 """Extract file paths from a command locally without API call.89 90 Determines if the command reads file contents and extracts paths accordingly.91 Commands like ls/dir/find just list files, so return empty.92 Commands like cat/head/tail actually read contents, so extract the file path.93 94 Returns:95 Filepath extraction result in <filepaths> format96 """97 listing_commands = {98 "ls",99 "dir",100 "find",101 "tree",102 "pwd",103 "cd",104 "mkdir",105 "rmdir",106 "rm",107 }108 109 reading_commands = {"cat", "head", "tail", "less", "more", "bat", "type"}110 111 try:112 parts = shlex.split(command, posix=False)113 if not parts:114 return "<filepaths>\n</filepaths>"115 116 cmd_parts = _strip_env_assignments(parts)117 if not cmd_parts:118 return "<filepaths>\n</filepaths>"119 120 base_cmd = cmd_parts[0].split("/")[-1].split("\\")[-1].lower()121 122 if base_cmd in listing_commands:123 return "<filepaths>\n</filepaths>"124 125 if base_cmd in reading_commands:126 filepaths = []127 for part in cmd_parts[1:]:128 if part.startswith("-"):129 continue130 filepaths.append(part)131 132 if filepaths:133 paths_str = "\n".join(filepaths)134 return f"<filepaths>\n{paths_str}\n</filepaths>"135 return "<filepaths>\n</filepaths>"136 137 if base_cmd == "grep":138 flags_with_args = {"-e", "-f", "-m", "-A", "-B", "-C"}139 pattern_provided_via_flag = False140 positional = []141 142 skip_next = False143 for part in cmd_parts[1:]:144 if skip_next:145 skip_next = False146 continue147 if part.startswith("-"):148 if part in flags_with_args:149 if part in {"-e", "-f"}:150 pattern_provided_via_flag = True151 skip_next = True152 continue153 positional.append(part)154 155 filepaths = positional if pattern_provided_via_flag else positional[1:]156 if filepaths:157 paths_str = "\n".join(filepaths)158 return f"<filepaths>\n{paths_str}\n</filepaths>"159 return "<filepaths>\n</filepaths>"160 161 return "<filepaths>\n</filepaths>"162 163 except ValueError:164 return "<filepaths>\n</filepaths>"165 