@@ -13,6 +13,13 @@ import { useExtensionState } from "@src/context/ExtensionStateContext"
1313import { cn } from "@src/lib/utils"
1414import { Button } from "@src/components/ui"
1515import CodeBlock from "../common/CodeBlock"
16+ import { CommandPatternSelector } from "./CommandPatternSelector"
17+ import {
18+ extractCommandPatterns ,
19+ getPatternDescription ,
20+ parseCommandAndOutput as parseCommandAndOutputUtil ,
21+ CommandPattern ,
22+ } from "../../utils/commandPatterns"
1623
1724interface CommandExecutionProps {
1825 executionId : string
@@ -22,21 +29,91 @@ interface CommandExecutionProps {
2229}
2330
2431export const CommandExecution = ( { executionId, text, icon, title } : CommandExecutionProps ) => {
25- const { terminalShellIntegrationDisabled = false } = useExtensionState ( )
26-
27- const { command, output : parsedOutput } = useMemo ( ( ) => parseCommandAndOutput ( text ) , [ text ] )
32+ const {
33+ terminalShellIntegrationDisabled = false ,
34+ allowedCommands = [ ] ,
35+ deniedCommands = [ ] ,
36+ setAllowedCommands,
37+ setDeniedCommands,
38+ } = useExtensionState ( )
39+
40+ const {
41+ command,
42+ output : parsedOutput ,
43+ suggestions,
44+ } = useMemo ( ( ) => {
45+ // First try our enhanced parser
46+ const enhanced = parseCommandAndOutputUtil ( text || "" )
47+ // If it found a command, use it, otherwise fall back to the original parser
48+ if ( enhanced . command && enhanced . command !== text ) {
49+ return enhanced
50+ }
51+ // Fall back to original parser
52+ const original = parseCommandAndOutput ( text )
53+ return { ...original , suggestions : [ ] }
54+ } , [ text ] )
2855
2956 // If we aren't opening the VSCode terminal for this command then we default
3057 // to expanding the command execution output.
3158 const [ isExpanded , setIsExpanded ] = useState ( terminalShellIntegrationDisabled )
3259 const [ streamingOutput , setStreamingOutput ] = useState ( "" )
3360 const [ status , setStatus ] = useState < CommandExecutionStatus | null > ( null )
61+ const [ showSuggestions ] = useState ( true )
3462
3563 // The command's output can either come from the text associated with the
3664 // task message (this is the case for completed commands) or from the
3765 // streaming output (this is the case for running commands).
3866 const output = streamingOutput || parsedOutput
3967
68+ // Extract command patterns
69+ const commandPatterns = useMemo < CommandPattern [ ] > ( ( ) => {
70+ const patterns : CommandPattern [ ] = [ ]
71+
72+ // Use AI suggestions if available
73+ if ( suggestions . length > 0 ) {
74+ suggestions . forEach ( ( suggestion ) => {
75+ patterns . push ( {
76+ pattern : suggestion ,
77+ description : getPatternDescription ( suggestion ) ,
78+ } )
79+ } )
80+ } else {
81+ // Extract patterns programmatically
82+ const extractedPatterns = extractCommandPatterns ( command )
83+ extractedPatterns . forEach ( ( pattern ) => {
84+ patterns . push ( {
85+ pattern,
86+ description : getPatternDescription ( pattern ) ,
87+ } )
88+ } )
89+ }
90+
91+ return patterns
92+ } , [ command , suggestions ] )
93+
94+ // Handle pattern changes
95+ const handleAllowPatternChange = ( pattern : string ) => {
96+ const isAllowed = allowedCommands . includes ( pattern )
97+ const newAllowed = isAllowed ? allowedCommands . filter ( ( p ) => p !== pattern ) : [ ...allowedCommands , pattern ]
98+ const newDenied = deniedCommands . filter ( ( p ) => p !== pattern )
99+
100+ setAllowedCommands ( newAllowed )
101+ setDeniedCommands ( newDenied )
102+ vscode . postMessage ( { type : "allowedCommands" , commands : newAllowed } )
103+ vscode . postMessage ( { type : "deniedCommands" , commands : newDenied } )
104+ }
105+
106+ const handleDenyPatternChange = ( pattern : string ) => {
107+ const isDenied = deniedCommands . includes ( pattern )
108+ const newDenied = isDenied ? deniedCommands . filter ( ( p ) => p !== pattern ) : [ ...deniedCommands , pattern ]
109+ const newAllowed = allowedCommands . filter ( ( p ) => p !== pattern )
110+
111+ setAllowedCommands ( newAllowed )
112+ setDeniedCommands ( newDenied )
113+ vscode . postMessage ( { type : "allowedCommands" , commands : newAllowed } )
114+ vscode . postMessage ( { type : "deniedCommands" , commands : newDenied } )
115+ }
116+
40117 const onMessage = useCallback (
41118 ( event : MessageEvent ) => {
42119 const message : ExtensionMessage = event . data
@@ -121,9 +198,20 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
121198 </ div >
122199 </ div >
123200
124- < div className = "w-full bg-vscode-editor-background border border-vscode-border rounded-xs p-2" >
125- < CodeBlock source = { command } language = "shell" />
126- < OutputContainer isExpanded = { isExpanded } output = { output } />
201+ < div className = "w-full bg-vscode-editor-background border border-vscode-border rounded-xs" >
202+ < div className = "p-2" >
203+ < CodeBlock source = { command } language = "shell" />
204+ < OutputContainer isExpanded = { isExpanded } output = { output } />
205+ </ div >
206+ { showSuggestions && commandPatterns . length > 0 && (
207+ < CommandPatternSelector
208+ patterns = { commandPatterns }
209+ allowedCommands = { allowedCommands }
210+ deniedCommands = { deniedCommands }
211+ onAllowPatternChange = { handleAllowPatternChange }
212+ onDenyPatternChange = { handleDenyPatternChange }
213+ />
214+ ) }
127215 </ div >
128216 </ >
129217 )
0 commit comments