
    Pi/                    >   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZmZmZmZ ddlmZ d*dZd+dZd,dZ e            Zepdad-dZd Zd.d/dZd0d/dZd1d/dZd2d Zd3d"Zd*d#Zd4d%Z d5d&Z! e"h d'          Z#d( Z$e%d)k    r e$             dS dS )6a  
Dippy - Approval autopilot for Claude Code, Gemini CLI, and Cursor.

A PreToolUse/BeforeTool/beforeShellExecution hook that auto-approves safe
commands while prompting for anything destructive. Stay in the flow.

Usage:
    Claude Code: Add to ~/.claude/settings.json hooks configuration.
    Gemini CLI:  Add to ~/.gemini/settings.json with --gemini flag.
    Cursor:      Add to .cursor/hooks.json with --cursor flag.
    See README.md for details.
    )annotationsN)Path)ConfigConfigErrorconfigure_loggingload_configlog_decisionmatch_after_mcp	match_mcp)analyzenamestrreturnboolc                j    t           j                            | d                                          dv S )z+Check if an environment variable is truthy. )1trueyes)osenvirongetlowerr   s    ,/root/projects/gits/Dippy/src/dippy/dippy.py	_env_flagr   &   s*    :>>$##))++/CCC    
str | Nonec                     dt           j        v st          d          rdS dt           j        v st          d          rdS dt           j        v st          d          rd	S d
S )zIDetect mode from command-line flags or env vars. Returns None if not set.z--claudeDIPPY_CLAUDEclaudez--geminiDIPPY_GEMINIgeminiz--cursorDIPPY_CURSORcursorN)sysargvr    r   r   _detect_mode_from_flagsr)   +   sd    SX>!:!:xSX>!:!:xSX>!:!:x4r   
input_datadictc                    d| v rd| vrdS |                      dd          }|dv rdS |r3|dk    r-|                    d          st          j        d	| d
           dS )z+Auto-detect mode from input JSON structure.command	tool_namer%   r   )shell	run_shellrun_shell_commandexecute_shellr#   Bashmcp__zUnknown tool_name 'z', defaulting to Claude moder!   )r   
startswithloggingwarning)r*   r.   s     r   _detect_mode_from_inputr8   6   s     J;j#@#@x {B//I PPPx  WY&((1E1Eg1N1N(UiUUUVVV8r   r!   r   c                     t           dk    rt          j                    dz  dz  S t           dk    rt          j                    dz  dz  S t          j                    dz  dz  S )z Get log file path based on mode.r#   z.geminizhook-approvals.logr%   z.cursorz.claude)MODEr   homer(   r   r   _get_log_filer<   P   s]    xy{{Y&)===xy{{Y&)===9;;"%999r   c                     	 t                      } | j                            dd           t          j        t          |           t          j        dd           dS # t          t          f$ r Y dS w xY w)z=Configure logging to file. Fails silently if unable to write.T)parentsexist_okz'%(asctime)s [%(levelname)s] %(message)sz%Y-%m-%d %H:%M:%S)filenamelevelformatdatefmtN)	r<   parentmkdirr6   basicConfigr   INFOOSErrorPermissionError)log_files    r   setup_loggingrK   Y   s    
 ??dT:::]],<'		
 	
 	
 	
 	
 	
 _%   s   AA A21A2all commands safereasonc                    t          j        d|             t          dk    rdd|  dS t          dk    rd|  }d||||dS dd	dd|  d
iS )zReturn approval response.z
APPROVED: r#   allow   🐤 decisionrM   r%   
permissionuser_messageagent_messageuserMessageagentMessagehookSpecificOutput
PreToolUsehookEventNamepermissionDecisionpermissionDecisionReasonr6   infor:   rM   msgs     r   approverc   k   s    L&f&&'''x#/?v/?/?@@@xf! 
 
 	
 	)")(8(8(8
 
 r   needs approvalc                    t          j        d|             t          dk    rdd|  dS t          dk    rd|  }d||||dS dd	dd|  d
iS )z4Return ask response to prompt user for confirmation.zASK: r#   askrP   rQ   r%   rS   rY   rZ   r[   r_   ra   s     r   rf   rf      s    L!!!"""x!-=V-=-=>>>xf 
 
 	
 	)"'(8(8(8
 
 r   denied by configc                    t          j        d|             t          dk    rdd|  dS t          dk    rd|  }d||||dS dd	dd|  d
iS )z*Return deny response to block the command.zDENY: r#   denyrP   rQ   r%   rS   rY   rZ   r[   r_   ra   s     r   ri   ri      s    L"&""###x".>f.>.>???xf  
 
 	
 	)"((8(8(8
 
 r   r-   configr   cwdc                   t          | ||          }t          |j        dk    rdn|j        |j        |            |j        dk    rt	          |j                  S |j        dk    rt          |j                  S t          |j                  S )z
    Main entry point: check if a command should be approved.

    Uses a single recursive walk of the bash AST to analyze all constructs.
    Returns a hook response dict.
    rO   r-   ri   )r   r	   actionrM   rc   ri   rf   )r-   rj   rk   results       r   check_commandrp      s     Wfc**F=G++    }v}%%%	&	 	 FM"""6=!!!r   Nonec                z    ddl m} ddlm}  ||           } ||||          }|rt	          d|            dS dS )zBHandle PostToolUse hook - output feedback message if rule matches.r   )match_after)tokenizerP   N)dippy.core.configrs   dippy.core.parserrt   print)r-   rj   rk   rs   rt   wordsmessages          r   handle_post_tool_userz      sw    ------******HWEk%--G !g     ! !r   c                ,    |                      d          S )z$Check if a tool name is an MCP tool.r4   )r5   r   s    r   is_mcp_toolr|      s    ??7###r   r.   c                   t          | |          }|i S |j        r|j        n
d|j         d}t          |j        ||j                   |j        dk    rt          |          S |j        dk    rt          |          S t          |          S )a  Check if an MCP tool should be approved based on config rules.

    Args:
        tool_name: MCP tool name (e.g., "mcp__github__get_issue").
        config: Loaded configuration.

    Returns:
        Hook response dict, or empty dict if no rules match (defer to default).
    N[])rulerO   ri   )r   ry   patternr	   rR   rc   ri   rf   )r.   rj   matchrM   s       r   check_mcp_toolr      s     i((E}	#mEU]]1EU]1E1E1EFem<<<<~  v	6	!	!F||6{{r   c                R    t          | |          }|rt          d|            dS dS )zHHandle PostToolUse hook for MCP tools - output feedback if rule matches.rP   N)r
   rw   )r.   rj   ry   s      r   handle_mcp_post_tool_user      s@    i00G !g     ! !r   >   r3   r/   r0   r2   r1   c            	      	   t                       	 t          j        t          j                  } t
          +t          |           at          j	        dt                      | 
                    d          }|s+| 
                    di           }|
                    d          }|r"t          |                                          }nt          j                    }	 t          |          }t          |           n`# t           $ rS}t          j        d|            t%          t          j        t)          d|                                Y d}~dS d}~ww xY w| 
                    dd          }t          d	k    r| 
                    d
d          }d}no| 
                    dd          }| 
                    di           }t+          |          r|dk    rt| 
                    dd          }	|	dv rZt          j	        d|	 d|            t-          d|	           t%          t          j        t/          |	                               dS |dk    r(t          j	        d|            t1          ||           nHt          j	        d|            t3          ||          }
t%          t          j        |
                     dS |t4          vr#t%          t          j        i                      dS |
                    d
d          }|dk    rv| 
                    dd          }	|	dv r\t          j	        d|	 d|            t-          d|	|           t%          t          j        t/          |	                               dS |dk    r*t          j	        d|            t7          |||           dS t          j	        d|            t9          |||          }
t%          t          j        |
                     dS # t          j        $ r9 t          j        d           t%          t          j        i                      Y dS t<          $ rC}t          j        d|            t%          t          j        i                      Y d}~dS d}~ww xY w)zMain entry point for the hook.NzAuto-detected mode: rk   
tool_inputzConfig error: zconfig error: hook_event_namerZ   r%   r-   r   r.   PostToolUsepermission_modedefault)bypassPermissionsdontAskzBypass mode (z): rO   zPostToolUse MCP: zChecking MCP: rm   zPostToolUse: z
Checking: zInvalid JSON inputzError: )rK   jsonloadr&   stdin_EXPLICIT_MODEr8   r:   r6   r`   r   r   resolverk   r   r   r   errorrw   dumpsrf   r|   r	   rc   r   r   SHELL_TOOL_NAMESrz   rp   JSONDecodeError	Exception)r*   cwd_strr   rk   rj   e
hook_eventr-   r.   r   ro   s              r   mainr     s    OOO_Ysy))
 !*:66DL666777
 ..'' 	,#b99J nnU++G 	w--''))CC(**C	 %%Ff%%%% 	 	 	M.1..///$*S!5!!5!56677888FFFFF	  ^^$5|DD

 8 nnY33GII #{B77I#b99J 9%% ..&0nn5F	&R&RO&*JJJ%T_%T%T%T%TUUU$Wo>>>dj)A)ABBCCC..L!@Y!@!@AAA,Y????L!=)!=!=>>>+Iv>>F$*V,,---  000djnn%%% nnY33G &&(nn->	JJO"BBBJ_JJJJKKKWowGGGGdj!9!9::;;; &&L222333 &#66666L/g//000"7FC88F$*V$$%%%%%   *+++djnn   mmm$$$djnnsl   C	O: C9 8O: 9
EAEO: EC1O: 	A6O: *O: -BO: ?.O: /A	O: :AR	R
8RR__main__)r   r   r   r   )r   r   )r*   r+   r   r   )r   r   )rL   )rM   r   r   r+   )rd   )rg   )r-   r   rj   r   rk   r   r   r+   )r-   r   rj   r   rk   r   r   rq   )r.   r   rj   r   r   r+   )r.   r   rj   r   r   rq   )&__doc__
__future__r   r   r6   r   r&   pathlibr   ru   r   r   r   r   r	   r
   r   dippy.core.analyzerr   r   r)   r8   r   r:   r<   rK   rc   rf   ri   rp   rz   r|   r   r   	frozensetr   r   __name__r(   r   r   <module>r      sM    # " " " " "   				 



                        ( ' ' ' ' 'D D D D
      ( )(**!
: : : :  $    0    0    6" " " ".! ! ! !$ $ $ $
   .! ! ! ! 9    e e eP zDFFFFF r   