@@ -178,48 +178,115 @@ MENU_KEYS=(y a n d esc)
178178MENU_COUNT=${# MENU_LABELS[@]}
179179selected=0
180180
181- # Build a human-readable preview from tool_input. Extract the most relevant
182- # field per tool type instead of showing raw JSON.
183- preview=" "
181+ # Build a human-readable preview from tool_input. Each tool type gets a
182+ # tailored display. MCP tools show pretty JSON. Edits show a diff preview.
183+ _extract () { jq -r --arg f " $1 " ' .[$f] // empty' <<< " $TOOL_INPUT_JSON" 2> /dev/null; }
184+ _truncate () {
185+ local s=" $1 " max=" ${2:- 120} "
186+ if [ " ${# s} " -gt " $max " ]; then
187+ printf ' %s...' " ${s: 0: $((max - 3))} "
188+ else
189+ printf ' %s' " $s "
190+ fi
191+ }
192+
193+ # preview_lines: array of lines to display. Populated per tool type.
194+ preview_lines=()
195+ extra_height=0 # additional lines beyond standard 1-line preview
196+
184197if [ -n " $TOOL_INPUT_JSON " ]; then
185- _extract () { jq -r --arg f " $1 " ' .[$f] // empty' <<< " $TOOL_INPUT_JSON" 2> /dev/null; }
186198 case " $TOOL_NAME " in
187199 Bash)
188- preview=" $( _extract command) " ;;
189- WebFetch|WebSearch)
190- preview=" $( _extract url) "
191- [ -z " $preview " ] && preview=" $( _extract query) " ;;
192- Read|Edit|Write|NotebookEdit|NotebookRead)
193- preview=" $( _extract file_path) " ;;
200+ preview_lines+=(" $( _truncate " $( _extract command) " 120) " )
201+ ;;
202+ WebFetch)
203+ preview_lines+=(" $( _extract url) " )
204+ ;;
205+ WebSearch)
206+ _q=" $( _extract query) "
207+ [ -n " $_q " ] && preview_lines+=(" search: $_q " ) || preview_lines+=(" $( _extract url) " )
208+ ;;
209+ Edit|Write)
210+ preview_lines+=(" $( _extract file_path) " )
211+ ;;
212+ Read|NotebookRead)
213+ preview_lines+=(" $( _extract file_path) " )
214+ ;;
215+ NotebookEdit)
216+ _fp=" $( _extract file_path) "
217+ _cell=" $( _extract cell_id) "
218+ preview_lines+=(" $_fp " )
219+ [ -n " $_cell " ] && preview_lines+=(" cell: $_cell " ) && extra_height=1
220+ ;;
194221 Grep)
195- preview =" $( _extract pattern) "
222+ _pat =" $( _extract pattern) "
196223 _path=" $( _extract path) "
197- [ -n " $_path " ] && preview=" ${preview} (in ${_path} )" ;;
224+ preview_lines+=(" /$_pat /" )
225+ [ -n " $_path " ] && preview_lines+=(" in: $_path " ) && extra_height=1
226+ ;;
198227 Glob)
199- preview =" $( _extract pattern) "
228+ _pat =" $( _extract pattern) "
200229 _path=" $( _extract path) "
201- [ -n " $_path " ] && preview=" ${preview} (in ${_path} )" ;;
230+ preview_lines+=(" $_pat " )
231+ [ -n " $_path " ] && preview_lines+=(" in: $_path " ) && extra_height=1
232+ ;;
233+ Skill)
234+ _skill=" $( _extract skill) "
235+ _args=" $( _extract args) "
236+ if [ -n " $_args " ]; then
237+ preview_lines+=(" $_skill $_args " )
238+ else
239+ preview_lines+=(" $_skill " )
240+ fi
241+ ;;
202242 Agent)
203- preview=" $( _extract description) "
204- [ -z " $preview " ] && preview=" $( _extract prompt | head -c 120) " ;;
243+ _desc=" $( _extract description) "
244+ [ -n " $_desc " ] && preview_lines+=(" $_desc " ) || preview_lines+=(" $( _truncate " $( _extract prompt) " 120) " )
245+ ;;
246+ mcp__* )
247+ # MCP tools: pretty-print the JSON args with indentation.
248+ _pretty=" $( jq -r ' .' <<< " $TOOL_INPUT_JSON" 2> /dev/null || echo " $TOOL_INPUT_JSON " ) "
249+ _line_count=0
250+ _total_lines=0
251+ while IFS= read -r _line; do
252+ _total_lines=$(( _total_lines + 1 ))
253+ if [ " $_line_count " -lt 10 ]; then
254+ preview_lines+=(" $_line " )
255+ _line_count=$(( _line_count + 1 ))
256+ fi
257+ done <<< " $_pretty"
258+ if [ " $_total_lines " -gt 10 ]; then
259+ _remaining=$(( _total_lines - 10 ))
260+ preview_lines+=(" ... (${_remaining} more lines)" )
261+ _line_count=$(( _line_count + 1 ))
262+ fi
263+ extra_height=$(( _line_count - 1 ))
264+ ;;
205265 * )
206- preview=" $TOOL_INPUT_JSON " ;;
266+ preview_lines+=(" $( _truncate " $TOOL_INPUT_JSON " 120) " )
267+ ;;
207268 esac
208- # Fallback to raw JSON if extraction yielded nothing.
209- [ -z " $preview " ] && preview=" $TOOL_INPUT_JSON "
210- max_preview=120
211- truncated_len=$(( max_preview - 3 ))
212- if [ " ${# preview} " -gt " $max_preview " ]; then
213- preview=" ${preview: 0: $truncated_len } ..."
214- fi
269+ fi
270+ # Fallback if nothing was extracted.
271+ if [ " ${# preview_lines[@]} " -eq 0 ]; then
272+ preview_lines+=(" $( _truncate " $TOOL_INPUT_JSON " 120) " )
215273fi
216274
217275render_main_menu () {
218- # Move cursor to top-left and clear screen.
219276 printf ' \033[H\033[2J'
220277 printf " ${BOLD} Passthru Permission Prompt${RESET} \n\n"
221278 printf " Tool: ${CYAN} %s${RESET} \n" " ${TOOL_NAME:- (unknown)} "
222- printf " Input: ${DIM} %s${RESET} \n\n" " $preview "
279+ # Render preview lines.
280+ local first=1
281+ for _pline in " ${preview_lines[@]} " ; do
282+ if [ " $first " -eq 1 ]; then
283+ printf " Input: ${DIM} %b${RESET} \n" " $_pline "
284+ first=0
285+ else
286+ printf " ${DIM} %b${RESET} \n" " $_pline "
287+ fi
288+ done
289+ printf ' \n'
223290
224291 local i
225292 for (( i = 0 ; i < MENU_COUNT; i++ )) ; do
301368
302369proposed=" $( propose_rule) "
303370
304- CONFIRM_LABELS=( " [Enter] Accept rule " " [E] Edit rule JSON " " [Esc] Back to menu " )
305- CONFIRM_KEYS=(enter e esc)
306- CONFIRM_COUNT= ${ # CONFIRM_LABELS[@]}
307- confirm_sel=0
371+ # Extract tool regex and match fields from the proposed rule for two-field editing.
372+ prop_tool= " $( jq -r ' .tool // "" ' <<< " $proposed " 2> /dev/null ) "
373+ prop_match_key= " $( jq -r ' .match // empty | keys[0] // empty ' <<< " $proposed " 2> /dev/null ) "
374+ prop_match_val= " $( jq -r ' .match // empty | to_entries[0].value // empty ' <<< " $proposed " 2> /dev/null ) "
308375
309- render_confirm_menu () {
376+ render_rule_editor () {
310377 printf ' \033[H\033[2J'
311378 printf " ${BOLD} Passthru Permission Prompt${RESET} \n\n"
312379 printf " Tool: ${CYAN} %s${RESET} \n" " ${TOOL_NAME:- (unknown)} "
313- printf " Input: ${DIM} %s${RESET} \n\n" " $preview "
380+ local _first=1
381+ for _pl in " ${preview_lines[@]} " ; do
382+ if [ " $_first " -eq 1 ]; then
383+ printf " Input: ${DIM} %b${RESET} \n" " $_pl "
384+ _first=0
385+ else
386+ printf " ${DIM} %b${RESET} \n" " $_pl "
387+ fi
388+ done
389+ printf ' \n'
314390 printf " Suggested rule:\n"
315- printf " ${GREEN} %s${RESET} \n\n" " $proposed "
391+ printf " Tool regex: ${GREEN} %s${RESET} \n" " $prop_tool "
392+ if [ -n " $prop_match_key " ]; then
393+ printf " Match %-6s ${GREEN} %s${RESET} \n" " ${prop_match_key} :" " $prop_match_val "
394+ fi
395+ printf ' \n'
396+ }
316397
398+ CONFIRM_LABELS=(" [Enter] Accept rule" " [E] Edit fields" " [Esc] Back to menu" )
399+ CONFIRM_KEYS=(enter e esc)
400+ CONFIRM_COUNT=${# CONFIRM_LABELS[@]}
401+ confirm_sel=0
402+
403+ render_confirm_screen () {
404+ render_rule_editor
317405 local i
318406 for (( i = 0 ; i < CONFIRM_COUNT; i++ )) ; do
319407 if [ " $i " -eq " $confirm_sel " ]; then
@@ -325,75 +413,82 @@ render_confirm_menu() {
325413 printf " \n\033[2mUse arrow keys or press a letter key\033[0m\n"
326414}
327415
328- render_confirm_menu
416+ render_confirm_screen
329417
330418while true ; do
331419 read_key
332420
333421 case " $KEY " in
334422 up)
335- if [ " $confirm_sel " -gt 0 ]; then
336- confirm_sel=$(( confirm_sel - 1 ))
337- else
338- confirm_sel=$(( CONFIRM_COUNT - 1 ))
339- fi
340- render_confirm_menu
423+ confirm_sel=$(( (confirm_sel - 1 + CONFIRM_COUNT) % CONFIRM_COUNT ))
424+ render_confirm_screen
341425 ;;
342426 down)
343- if [ " $confirm_sel " -lt $(( CONFIRM_COUNT - 1 )) ]; then
344- confirm_sel=$(( confirm_sel + 1 ))
345- else
346- confirm_sel=0
347- fi
348- render_confirm_menu
427+ confirm_sel=$(( (confirm_sel + 1 ) % CONFIRM_COUNT ))
428+ render_confirm_screen
349429 ;;
350430 enter)
351431 case " ${CONFIRM_KEYS[$confirm_sel]} " in
352- enter)
353- write_verdict_always " $answer " " $proposed "
354- exit 0
355- ;;
432+ enter) break ;;
356433 e)
357- # Edit path below.
358- break
434+ # Two-field editor below.
435+ printf ' \033[H\033[2J'
436+ printf " ${BOLD} Edit Rule${RESET} \n\n"
437+ printf " Edit each field. Leave blank to keep the suggested value.\n\n"
438+ printf " Tool regex ${DIM} [%s]${RESET} : " " $prop_tool "
439+ edited_tool=" "
440+ IFS= read -r -e -t " $TIMEOUT " edited_tool || true
441+ [ -z " $edited_tool " ] && edited_tool=" $prop_tool "
442+ if [ -n " $prop_match_key " ]; then
443+ printf " Match %s ${DIM} [%s]${RESET} : " " $prop_match_key " " $prop_match_val "
444+ edited_match=" "
445+ IFS= read -r -e -t " $TIMEOUT " edited_match || true
446+ [ -z " $edited_match " ] && edited_match=" $prop_match_val "
447+ prop_match_val=" $edited_match "
448+ fi
449+ prop_tool=" $edited_tool "
450+ # Rebuild and re-render.
451+ render_confirm_screen
359452 ;;
360453 esc)
361- # Back to main menu. Re-run entire script via exec for simplicity.
362454 exec bash " $0 "
363455 ;;
364456 esac
365457 ;;
366458 e)
367- break # fall through to edit
459+ # Two-field editor (shortcut).
460+ printf ' \033[H\033[2J'
461+ printf " ${BOLD} Edit Rule${RESET} \n\n"
462+ printf " Edit each field. Leave blank to keep the suggested value.\n\n"
463+ printf " Tool regex ${DIM} [%s]${RESET} : " " $prop_tool "
464+ edited_tool=" "
465+ IFS= read -r -e -t " $TIMEOUT " edited_tool || true
466+ [ -z " $edited_tool " ] && edited_tool=" $prop_tool "
467+ if [ -n " $prop_match_key " ]; then
468+ printf " Match %s ${DIM} [%s]${RESET} : " " $prop_match_key " " $prop_match_val "
469+ edited_match=" "
470+ IFS= read -r -e -t " $TIMEOUT " edited_match || true
471+ [ -z " $edited_match " ] && edited_match=" $prop_match_val "
472+ prop_match_val=" $edited_match "
473+ fi
474+ prop_tool=" $edited_tool "
475+ render_confirm_screen
368476 ;;
369477 esc|timeout)
370- # Back to main menu.
371478 exec bash " $0 "
372479 ;;
373480 * )
374- # Unknown key: ignore.
375481 ;;
376482 esac
377483done
378484
379- # Edit path: read a full line with readline.
380- printf ' \033[H\033[2J'
381- printf " ${BOLD} Edit Rule JSON${RESET} \n\n"
382- printf " Current:\n ${GREEN} %s${RESET} \n\n" " $proposed "
383- printf " Type new JSON (leave blank to accept):\n"
384- edited=" "
385- if ! IFS= read -r -e -t " $TIMEOUT " edited; then
386- exit 0
387- fi
388- if [ -z " $edited " ]; then
389- write_verdict_always " $answer " " $proposed "
390- elif jq -e ' type == "object"' > /dev/null 2>&1 <<< " $edited" ; then
391- write_verdict_always " $answer " " $edited "
485+ # Build the final rule JSON from the (possibly edited) fields.
486+ if [ -n " $prop_match_key " ] && [ -n " $prop_match_val " ]; then
487+ final_rule=" $( jq -cn --arg t " $prop_tool " --arg k " $prop_match_key " --arg v " $prop_match_val " \
488+ ' {tool: $t, match: {($k): $v}}' ) "
392489else
393- printf ' \n${RED}Invalid JSON (must be an object)${RESET}\n'
394- printf ' Using suggested rule: %s\n' " $proposed "
395- sleep 2
396- write_verdict_always " $answer " " $proposed "
490+ final_rule=" $( jq -cn --arg t " $prop_tool " ' {tool: $t}' ) "
397491fi
492+ write_verdict_always " $answer " " $final_rule "
398493
399494exit 0
0 commit comments