@@ -66,6 +66,13 @@ function checkPythonNodeCommand(command: string): void {
6666 const isWhichOrWhereis : boolean =
6767 command . includes ( "which" ) || command . includes ( "whereis" ) ;
6868
69+ // Allow python commands from virtual environments
70+ const venvPatterns : RegExp [ ] = [
71+ / ^ [ . / \\ ] * \. v e n v [ / \\ ] b i n [ / \\ ] p y t h o n \d * $ / , // .venv/bin/python, .venv/bin/python3
72+ / ^ [ . / \\ ] * v e n v [ / \\ ] b i n [ / \\ ] p y t h o n \d * $ / , // venv/bin/python, venv/bin/python3
73+ / ^ [ . / \\ ] * e n v [ / \\ ] b i n [ / \\ ] p y t h o n \d * $ / , // env/bin/python, env/bin/python3
74+ ] ;
75+
6976 for ( const blockedCmd of BLOCKED_COMMANDS ) {
7077 if ( blockedCmd === "git" || blockedCmd === "nix" ) continue ;
7178
@@ -109,29 +116,35 @@ function checkPythonNodeCommand(command: string): void {
109116
110117 // Enhanced pattern matching for complex command structures
111118 if ( ! isWhichOrWhereis ) {
112- // Check for blocked commands in various contexts
113- const patterns : RegExp [ ] = [
114- // Direct command anywhere
115- new RegExp ( `\\b${ blockedCmd } \\b` , "g" ) ,
116- // In command substitution $(...)
117- new RegExp ( `\\$\\([^)]*\\b${ blockedCmd } \\b[^)]*\\)` , "g" ) ,
118- // In backticks `...`
119- new RegExp ( `\`[^\`]*\\b${ blockedCmd } \\b[^\`]*\`` , "g" ) ,
120- // In quoted strings within commands
121- new RegExp ( `["'][^"']*\\b${ blockedCmd } \\b[^"']*["']` , "g" ) ,
122- // After operators like &&, ||, ;, |
123- new RegExp ( `[;&|]{1,2}\\s*\\b${ blockedCmd } \\b` , "g" ) ,
124- // In background execution &
125- new RegExp ( `\\b${ blockedCmd } \\b\\s*&` , "g" ) ,
126- // With redirection
127- new RegExp ( `\\b${ blockedCmd } \\b\\s*[<>]` , "g" ) ,
128- // Escaped characters
129- new RegExp ( `\\b${ blockedCmd . replace ( / ( .) / g, "$1\\\\?" ) } \\b` , "g" ) ,
130- ] ;
131-
132- for ( const pattern of patterns ) {
133- if ( pattern . test ( command ) ) {
134- throw new Error ( BLOCKED_COMMAND_MESSAGES [ blockedCmd ] ) ;
119+ // Check for blocked commands in various contexts, but skip if it's a virtual environment python
120+ const isVenvPython =
121+ blockedCmd . startsWith ( "python" ) &&
122+ venvPatterns . some ( ( pattern ) => pattern . test ( actualFirstCommand ) ) ;
123+
124+ if ( ! isVenvPython ) {
125+ const patterns : RegExp [ ] = [
126+ // Direct command anywhere
127+ new RegExp ( `\\b${ blockedCmd } \\b` , "g" ) ,
128+ // In command substitution $(...)
129+ new RegExp ( `\\$\\([^)]*\\b${ blockedCmd } \\b[^)]*\\)` , "g" ) ,
130+ // In backticks `...`
131+ new RegExp ( `\`[^\`]*\\b${ blockedCmd } \\b[^\`]*\`` , "g" ) ,
132+ // In quoted strings within commands
133+ new RegExp ( `["'][^"']*\\b${ blockedCmd } \\b[^"']*["']` , "g" ) ,
134+ // After operators like &&, ||, ;, |
135+ new RegExp ( `[;&|]{1,2}\\s*\\b${ blockedCmd } \\b` , "g" ) ,
136+ // In background execution &
137+ new RegExp ( `\\b${ blockedCmd } \\b\\s*&` , "g" ) ,
138+ // With redirection
139+ new RegExp ( `\\b${ blockedCmd } \\b\\s*[<>]` , "g" ) ,
140+ // Escaped characters
141+ new RegExp ( `\\b${ blockedCmd . replace ( / ( .) / g, "$1\\\\?" ) } \\b` , "g" ) ,
142+ ] ;
143+
144+ for ( const pattern of patterns ) {
145+ if ( pattern . test ( command ) ) {
146+ throw new Error ( BLOCKED_COMMAND_MESSAGES [ blockedCmd ] ) ;
147+ }
135148 }
136149 }
137150 }
@@ -309,12 +322,6 @@ function checkReadOnlyFileEdit(filePath: string): void {
309322 }
310323}
311324
312-
313-
314-
315-
316-
317-
318325export const CommandBlocker : Plugin = async ( {
319326 app,
320327 client,
@@ -328,12 +335,8 @@ export const CommandBlocker: Plugin = async ({
328335
329336 if ( input . tool === "edit" ) {
330337 const newString = output . args . newString || "" ;
331-
332-
333338 } else if ( input . tool === "write" ) {
334339 const content = output . args . content || "" ;
335-
336-
337340 }
338341 }
339342
0 commit comments