
    `gjh#                   <   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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 ddlmZ ddlmZ ddlmZ  ej.                  e      Z ej4                  d	
      dJd       Z G d de      Zd	ZdKdZdd	 	 	 	 	 	 	 dLdZ h dZ!dMdZ"dNdZ#dOdZ$dPdZ%dQdZ&dRdZ'dSdZ(dTdZ)dUdZ*dVdZ+dWdZ,	 	 dX	 	 	 	 	 	 	 dYdZ-dZdZ.dZd Z/d[d!Z0d\d"Z1 e2d#d$h      Z3d]d%Z4d[d&Z5d^d'Z6d_d(Z7d`d)Z8dad*Z9dbdcd+Z:	 	 	 	 	 	 ddd,Z;dZd-Z<ded.Z=dfd/Z>	 	 	 	 	 	 	 	 	 	 	 	 	 	 dgd0Z?dhd1Z@did2ZAdjdkd3ZBdld4ZCdbdmd5ZDdnd6ZEdnd7ZFdod8ZGdod9ZHdZd:ZIdZd;ZJdpd<ZKdpd=ZLdqd>ZMd? ZNd@ ZO	 	 	 	 	 	 	 	 drdAZPd_dBZQdsdCZRdtdDZSdudEZTdvdFZUdwdGZVdvdHZWdqdIZXy)xuq  ``hermes plugins`` CLI subcommand — install, update, remove, and list plugins.

Plugins are installed from Git repositories into ``~/.hermes/plugins/``.
Supports full URLs and ``owner/repo`` shorthand (resolves to GitHub).

After install, if the plugin ships an ``after-install.md`` file it is
rendered with Rich Markdown.  Otherwise a default confirmation is shown.
    )annotationsN)Path)AnyOptional)get_hermes_home)cfg_get)masked_secret_prompt   )maxsizec            
     <   t        j                  d      } | r| S t        j                  dk(  r>t        j                  j                  dd      }t        j                  j                  dd      }t        j                  j                  dd      }t        j                  j                  |d	d
d      t        j                  j                  |d	dd      t        j                  j                  |d	d
d      t        j                  j                  |d	dd      g}|rZ|j                  t        j                  j                  |dd	d
d      t        j                  j                  |dd	dd      f       ng d}|D ](  }|st        j                  j                  |      s&|c S  y)zResolve a git binary for subprocess use when ``PATH`` may be minimal.

    Matches other Hermes subprocess resolution: :func:`shutil.which` first,
    then common Git for Windows install paths and POSIX defaults.
    gitntProgramFileszC:\Program FileszProgramFiles(x86)zC:\Program Files (x86)LOCALAPPDATA Gitcmdzgit.exebinPrograms)z/usr/bin/gitz/usr/local/bin/gitz/bin/gitN)
shutilwhichosnameenvirongetpathjoinextendisfile)foundprogprog_x86local
candidatescs         I/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/plugins_cmd.py_resolve_git_executabler'      s>    LLE	ww$zz~~n.AB::>>"57PQ

~r2GGLLueY7GGLLueY7GGLL5%;GGLL5%;	

 GGLL
E5)LGGLL
E5)L H
 "H     c                      e Zd ZdZy)PluginOperationErrorzHRecoverable plugin install/update failure (CLI exits; HTTP maps to 4xx).N)__name__
__module____qualname____doc__ r(   r&   r*   r*   A   s    Rr(   r*   c                 F    t               dz  } | j                  dd       | S )z9Return the user plugins directory, creating it if needed.pluginsT)parentsexist_ok)r   mkdir)r1   s    r&   _plugins_dirr5   K   s$    )+GMM$M.Nr(   Fallow_subdirc                  | st        d      |r| j                  d      } | st        d      | dv rt        d|  d      |rdnd}|D ]  }|| v st        d|  d| d	       || z  j                         }|j                         }||k(  rt        d|  d
      	 |j                  |       |S # t         $ r t        d|  d      w xY w)a  Validate a plugin name and return the safe target path inside *plugins_dir*.

    Raises ``ValueError`` if the name contains path-traversal sequences or would
    resolve outside the plugins directory.

    ``allow_subdir=True`` permits a single forward slash inside *name* so
    category-namespaced plugin keys like ``observability/langfuse`` or
    ``image_gen/openai`` (the registry keys emitted by ``_discover_all_plugins``)
    can be looked up. ``..`` and backslash are still rejected, leading and
    trailing slashes are stripped, and the resolved target must still live
    inside *plugins_dir*. Install paths leave this at the default ``False``
    because a freshly-cloned plugin always lands top-level under
    ``~/.hermes/plugins/<name>/``.
    zPlugin name must not be empty./>   ...zInvalid plugin name 'z3': must not reference the plugins directory itself.)\r;   )r9   r<   r;   z': must not contain 'z'.z,': resolves to the plugins directory itself.z*': resolves outside the plugins directory.)
ValueErrorstripresolverelative_to)r   plugins_dirr7   	bad_charsbadtargetplugins_resolveds          r&   _sanitize_plugin_namerF   R   s#   ( 9::zz#=>>{#D6)\]
 	

 !-2CI Y$;4TF:OPSuTVWXXY D ))+F"**,!!#D6)UV
 	

+, M  
#D6)ST
 	

s   B/ /C>
   blobpulltreewikipullscommitissuesactionscommitsreleasesc                <   | j                  d      r{| j                  d      r| t        d      d }|j                  dd      d   j                  dd      d   j                  d      }|j                  d      }t        |      d	k\  rt	        |dd
       rs|d
   t
        v rh|d   j                  d      }d}|d
   dk(  r<t        |      dk\  r.dj                  d |dd D              j                  d      xs d}d|d    d| d|fS d| v r,| j                  d      \  }}}||j                  d      xs dfS d}| j                  |      }	|	dk7  r9| d|	t        d      z    }| |	t        |      z   d j                  d      }||xs dfS | dfS | j                  d      j                  d      D 
cg c]  }
|
s|
	 }}
t        |      d
k\  r>|d   |d   }}dj                  |d
d       j                  d      }d| d| d}||xs dfS t        d|  d      c c}
w )u  Turn an identifier into a cloneable Git URL and optional subdirectory.

    Returns ``(git_url, subdir)`` where ``subdir`` is the path within the
    cloned repository that contains the plugin (``None`` when the plugin lives
    at the repo root).

    Accepted formats:
    - Full URL: https://github.com/owner/repo.git
    - Full URL: git@github.com:owner/repo.git
    - Full URL: ssh://git@github.com/owner/repo.git
    - Browser URL: https://github.com/owner/repo/tree/main/path
      →  (https://github.com/owner/repo.git, "path")
    - Shorthand: owner/repo  →  https://github.com/owner/repo.git
    - Shorthand w/ subdir: owner/repo/path/to/plugin
      →  (https://github.com/owner/repo.git, "path/to/plugin")
    - Full URL w/ subdir (``.git`` boundary):
      https://github.com/owner/repo.git/path/to/plugin
      →  (https://github.com/owner/repo.git, "path/to/plugin")
    - Any URL w/ explicit subdir fragment (works for every scheme, incl.
      ``file://`` and ssh): <url>#path/to/plugin
      →  (<url>, "path/to/plugin")

    NOTE: ``http://`` and ``file://`` schemes are accepted but will trigger a
    security warning at install time.
    )zhttps://http://zgit@zssh://file://zhttps://github.com/N?r
   r   #r9         .gitrI      c              3  &   K   | ]	  }|s|  y wNr/   ).0ps     r&   	<genexpr>z#_resolve_git_url.<locals>.<genexpr>   s     %@Aaa%@s      z.git/zInvalid plugin identifier: 'zi'. Use a Git URL or 'owner/repo' shorthand (optionally with a subdirectory: 'owner/repo/path/to/plugin').)
startswithlensplitr>   all_GITHUB_BROWSER_SEGMENTSremovesuffixr   	partitionfindr=   )
identifierr   partsreposubdirgit_url_fragmarkeridxr]   owners               r&   _resolve_git_urlrs      s`   6 QR  !67c"78:;D::c1%a(..sA6q9??DDJJsOE5zQ3uRay>eAhBZ6ZQx,,V48v%#e*/ XX%@qr%@@FFsKStF,U1XJavTBFJJ *)33C8GQTZZ_455oof%"9 !43V#45Gc&k 1 34::3?FV^t,,4 #((-33C8>1AQ>E>
5zQAhat%)$**3/'wavT:4((

&zl 3( 	(  ?s   0H8Hc                   | j                         } | |z  j                         }|| k7  r| |j                  vrt        d| d      |j                         st        d| d      |j	                         st        d| d      |S )aI  Resolve ``subdir`` inside ``clone_root``, rejecting path traversal.

    Guards against ``..`` segments, absolute paths, and symlinks that would
    escape the cloned repository. Returns the resolved directory path.
    Raises ``PluginOperationError`` if the path escapes the clone, doesn't
    exist, or is not a directory.
    zPlugin subdirectory 'z' escapes the repository.z#' does not exist in the repository.z' is not a directory.)r?   r2   r*   existsis_dir)
clone_rootrl   	candidates      r&   _resolve_subdir_withinry      s     ##%Jf$--/I J:Y5F5F#F"#F8+DE
 	
 "#F8+NO
 	
 "#F8+@A
 	
 r(   c                    | j                  d      }|j                  d      r|dd }|j                  dd      d   }d|v r(|j                  dd      d   j                  dd      d   }|S )zCExtract the repo name from a Git URL for the plugin directory name.r9   rX   Nr
   r`   :)rstripendswithrsplit)urlr   s     r&   _repo_name_from_urlr      sp     ::c?D}}VCRy;;sAr"D
d{{{3"2&--c15b9Kr(   c                   | dz  }|j                         si S 	 ddl}t        |d      5 }|j                  |      xs i cddd       S # 1 sw Y   yxY w# t        $ r#}t
        j                  d| |       i cY d}~S d}~ww xY w)z;Read plugin.yaml and return the parsed dict, or empty dict.plugin.yamlr   Nutf-8encodingz$Failed to read plugin.yaml in %s: %s)ru   yamlopen	safe_load	Exceptionloggerwarning)
plugin_dirmanifest_filer   fes        r&   _read_manifestr     s|    .M!	-'2 	+a>>!$*	+ 	+ 	+ =z1M	s9   A A		A 	AA A 	BA<6B<Bc                T   | j                  d      D ]]  }|j                  }| |z  }|j                         r%	 t        j                  ||       |j                  d| d|j                   d       _ y# t        $ r+}|j                  d|j                   d|        Y d}~d}~ww xY w)zCopy any .example files to their real names if they don't already exist.

    For example, ``config.yaml.example`` becomes ``config.yaml``.
    Skips files that already exist to avoid overwriting user config on reinstall.
    z	*.examplez[dim]  Created z from [/dim]z)[yellow]Warning:[/yellow] Failed to copy : N)globstemru   r   copy2printr   OSError)r   consoleexample_file	real_name	real_pathr   s         r&   _copy_example_filesr     s     #4  %%	*	!\95%i[|7H7H6IP  ?@Q@Q?RRTUVTWX s   8A33	B'<!B""B'c                n   | j                  d      xs g }|sg S ddlm} g }|D ]Z  }t        |t              r|j                  d|i       't        |t              s8|j                  d      sJ|j                  |       \ |D cg c]%  }|j                  d      s ||d         r!|d   ' c}S c c}w )zLReturn declared ``requires_env`` names that are unset in ``~/.hermes/.env``.requires_envr   )get_env_valuer   )r   hermes_cli.configr   
isinstancestrappenddict)manifestr   r   	env_specsentryss         r&   _missing_requires_env_namesr   +  s    <</52L	/I $eS!fe_-t$6):U#	$  )[!AEE&M-PQRXPYBZAfI[[[s   B2B2(B2c                D   | j                  d      xs g }|syddlm}m} ddlm} g }|D ]Z  }t        |t              r|j                  d|i       't        |t              s8|j                  d      sJ|j                  |       \ |D cg c]  } ||d         r| }	}|	sy| j                  dd      }
|j                  d|
 d	       |	D ]	  }|d   }|j                  d
d      }|j                  dd      }|j                  dd      }d| }|r|d| z  }|j                  |       |r|j                  d| d       	 |rt        d| d      j                         }nt        d| d      j                         }|r7 |||       |t         j"                  |<   |j                  d |        d       |j                  d| d |        d        |j                          yc c}w # t        t        f$ r |j                  d |        d       Y  yw xY w)a  Prompt for required environment variables declared in plugin.yaml.

    ``requires_env`` accepts two formats:

    Simple list (backwards-compatible)::

        requires_env:
          - MY_API_KEY

    Rich list with metadata::

        requires_env:
          - name: MY_API_KEY
            description: "API key for Acme service"
            url: "https://acme.com/keys"
            secret: true

    Already-set variables are skipped.  Values are saved to the user's ``.env``.
    r   Nr   )r   save_env_value)display_hermes_homer   zthis pluginz
[bold]z6[/bold] requires the following environment variables:
descriptionr   r   secretF      — z  [dim]Get yours at: r   r   z,
[dim]  Skipped (you can set these later in z/.env)[/dim]u     [green]✓[/green] Saved to z/.envz  [dim]  Skipped (set z in z/.env later)[/dim])r   r   r   r   hermes_constantsr   r   r   r   r   r   r	   r>   inputEOFErrorKeyboardInterruptr   r   )r   r   r   r   r   r   r   r   r   missingplugin_namespecr   descr   r   labelvalues                     r&   _prompt_plugin_env_varsr   =  s2   ( <</52L?4 I $eS!fe_-t$6):U#	$ $DQ=6+CqDGD,,v}5KMMH[M)`ab hF|xxr*hhub!(E*TFuTF^#EeMM1#f=>	,r$r];AAC4&m,224
 4'$BJJtMM:;N;P:QQVWXMM24&=P=R<SSefg7h: MMOI E2 +, 	MMIJ]J_I``lmn	s   G-G-=G22)HHc                   ddl m} ddlm} ddlm}  |       }| dz  }|j                         rU|j                  d      } ||      }|j                          |j                   ||dd	
             |j                          y|j                          |j                   |d| d|  ddd	             |j                          y)z@Show after-install.md if it exists, otherwise a default message.r   Console)Markdown)Panelafter-install.mdr   r   greenF)border_styleexpandz![green bold]Plugin installed:[/] z
[dim]Location:[/] u   ✓ Installed)r   titler   N)	rich.consoler   rich.markdownr   
rich.panelr   ru   	read_textr   )	r   ri   r   r   r   r   after_installcontentmds	            r&   _display_after_installr     s    $& iG!33M))7);geBWUCD3J< @%%/L2$%	
 	r(   c                    ddl m}  |       }|j                          |j                  d|  d|        |j                          y)z*Show confirmation after removing a plugin.r   r   u   [red]✗[/red] Plugin [bold]z[/bold] removed from N)r   r   r   )r   rA   r   r   s       r&   _display_removedr     s9    $iGMMOMM06KK=YZMMOr(   c                    t        | |d      }|j                         sYdj                  d |j                         D              xs d}|j	                  d|  d| d|        t        j                  d	       |S )
zUReturn the plugin path if it exists, or exit with an error listing installed plugins.Tr6   z, c              3  V   K   | ]!  }|j                         s|j                   # y wr[   )rv   r   )r\   ds     r&   r^   z,_require_installed_plugin.<locals>.<genexpr>  s     RqxxzaffRs   ))z(none)[red]Error:[/red] Plugin 'z' not found in z.
Installed plugins: r
   )rF   ru   r   iterdirr   sysexit)r   rA   r   rD   	installeds        r&   _require_installed_pluginr     sw    "44HF==?IIRk.A.A.CRR^V^	(ok] K""+.	
 	Mr(   c                  ddl }	 t        |       \  }}t               }|j                         5 }t        |      dz  }t               }	|	st        d      	 t        j                  |	ddd|t	        |      gddd	
      }
|
j                  dk7  r:|
j                  xs |
j                  xs dj!                         }t        d|       |rt#        ||      }n|}t%        |      }|j'                  d      xs1 |r$|j)                  d      j+                  dd      d   n
t-        |      }	 t/        ||      }|j'                  d      }|=	 t1        |      }|t4        kD  r(ddlm} t        d| d| dt4         d |        d	      d|j;                         r)|st        d| d| d      t=        j>                  |       t=        j@                  t	        |      t	        |             ddd       dz  j;                         xs |dz  j;                         }|s)|dz  j;                         stB        jE                  d        dd!l#m$} tK        | |              t%        |      }|j'                  d      xs |jL                  }|||fS # t        $ r}t        t	        |            |d}~ww xY w# t        $ r}t        d      |d}~wt        j                  $ r}t        d      |d}~ww xY w# t        $ r}t        t	        |            |d}~ww xY w# t        t2        f$ r t        d| d| d      dw xY w# 1 sw Y   TxY w)"zClone Git plugin into ``~/.hermes/plugins``.

    Returns ``(target_dir, installed_manifest, canonical_name)``.
    Raises ``PluginOperationError`` on failure.
    r   Nplugin$git is not installed or not in PATH.clonez--depth1T<   )capture_outputtexttimeoutz%Git clone timed out after 60 seconds.r   zGit clone failed:
r   r9   r
   r`   manifest_versionPlugin 'z ' has invalid manifest_version 'z' (expected an integer).)recommended_update_commandz' requires manifest_version z), but this installer only supports up to z. Run z to update Hermes.zD' already exists. Use force reinstall or run `hermes plugins update z`.r   
plugin.yml__init__.pyz>%s has no plugin.yaml / __init__.py; may not be a valid pluginr   )'tempfilers   r=   r*   r   r5   TemporaryDirectoryr   r'   
subprocessrunFileNotFoundErrorTimeoutExpired
returncodestderrstdoutr>   ry   r   r   r}   r   r   rF   int	TypeError_SUPPORTED_MANIFEST_VERSIONr   r   ru   r   rmtreemover   r   r   r   r   r   )ri   forcer   rm   rl   r   rA   tmp	tmp_clonegit_exeresulterr
tmp_targetr   r   rD   mvmv_intr   has_yamlr   installed_manifestinstalled_names                          r&   _install_plugin_corer    s    2*:6 .K		$	$	& E2#I(	)+&'MNN	^^'9c7C	NK#	F !==7FMM7R>>@C&)<SE'BCC /	6BJ"J!*-ll6* 
5;FMM#%%c1-b1ATU\A] 		6*;DF \\,->R 33H*{m+Gt L>>Y=Z [5788JL 	 ==?*{m ,55@ME  MM&!C
OS[1KE2N &..0TVl5J4R4R4THVm3;;=L	

 %	*'/'++F3Bv{{N%~55q  2"3q6*12& ! 	&6 (( 	&7	,  	6&s1v.A5	6 	* *{m ,t35 _E2 E2s   J &L6(J+=B)L6'K*3L6LBL6	J(J##J(+	K'4K  K'K""K''L6*	L3LLL6#L33L66M c                <   ddl m}  |       }	 t        |       \  }}j                  d      r|j	                  d       r|j	                  d| d	| d
       n|j	                  d| d       	 t        | |      \  }}	}
dz  j                         s;|dz  j                         s(|dz  j                         s|j	                  d
 d       t        	|       t        ||        |}|ot
        j                  j                         rOt
        j                  j                         r1	 t!        d
 d      j#                         j%                         }|dv }nd}|rbt+               }t-               }|j/                  
       |j1                  |
       t3        |       t5        |       |j	                  d|
 d       n|j	                  d
 d       |j	                  d       |j	                  d       |j	                          y# t        $ r4}|j	                  d|        t        j                  d       Y d}~!d}~ww xY w# t        $ r4}|j	                  d|        t        j                  d       Y d}~d}~ww xY w# t&        t(        f$ r d}Y @w xY w)zInstall a plugin from a Git URL or owner/repo shorthand.

    After install, prompt "Enable now? [y/N]" unless *enable* is provided
    (True = auto-enable without prompting, False = install disabled).
    r   r   [red]Error:[/red] r
   NrR   rS   zs[yellow]Warning:[/yellow] Using insecure/local URL scheme. Consider using https:// or git@ for production installs.z[dim]Cloning z
 (subdir: z
)...[/dim]	...[/dim]r   r   r   r   z[yellow]Warning:[/yellow] zQ doesn't contain plugin.yaml or __init__.py. It may not be a valid Hermes plugin.z
  Enable 'z' now? [y/N]: >   yyesF    [green]✓[/green] Plugin [bold]z[/bold] enabled.zB[dim]Plugin installed but not enabled. Run `hermes plugins enable z` to activate.[/dim]z=[dim]Restart the gateway for the plugin to take effect:[/dim]z#[dim]  hermes gateway restart[/dim])r   r   rs   r=   r   r   r   ra   r  r*   ru   r   r   stdinisattyr   r   r>   lowerr   r   _get_enabled_set_get_disabled_setadddiscard_save_enabled_set_save_disabled_set)ri   r   enabler   r   rm   _subdirr   rD   r  r  should_enableanswerenableddisableds                  r&   cmd_installr  &  s    %iG+J7
 01G	

 gYj	LMgYi895I6
2"N ]"**,f|6K5S5S5Ufh_ 	((8 9C D	

 .86:.M99#**"3"3"5&  0?%'%%'  !', 6 "M"$$&N#('"8$.~.>>NO	
 	**8)99MO	

 MMQRMM78MMOG  *1#./(   *1#./0 /0 & %&sA   H 0I (/J 	I)H>>I	J)I>>JJJc                   ddl m}  |       }t               }	 t        | ||      }dz  j                         s*|j                  d|  d       t        j                  d       |j                  d	|  d
       t        |      \  }}|s)|j                  d|        t        j                  d       t        ||       |j                         }d|v r|j                  d|  d       y|j                  d|  d       |j                  d| d       y# t        $ r4}|j                  d|        t        j                  d       Y d}~$d}~ww xY w)zAUpdate an installed plugin by pulling latest from its git remote.r   r   r  r
   NrX   r   z@' was not installed from git (no .git directory). Cannot update.z[dim]Updating r  Already up to dater  z[/bold] is already up to date.z[/bold] updated.[dim]r   )r   r   r5   r   r=   r   r   r   ru   _git_pull_plugin_dirr   r>   )	r   r   r   rA   rD   r   okoutputouts	            r&   
cmd_updater#  |  s@   $iG.K*4gF
 VO##%( /2 3	
 	MMN4&	23%f-JB*6(34 (
,,.Cs".tf4RS	
 	8>NOPcU&)*9  *1#./s   D 	E)E

Ec                   ddl m}  |       }t               }	 t        | ||      }t        j                         t        | |       y# t        $ r3}|j                  d|        t        j                  d       Y d}~Yd}~ww xY w)z#Remove an installed plugin by name.r   r   r  r
   N)r   r   r5   r   r=   r   r   r   r   r   r   )r   r   r   rA   rD   r   s         r&   
cmd_remover%    sq    $iG.K*4gF
 MM&T;'  *1#./s   A 	B)A??Bc                     	 ddl m}   |        }t        |ddg       }t        |t              rt        |      S t               S # t        $ r t               cY S w xY w)zRead the disabled plugins set from config.yaml.

    An explicit deny-list. A plugin name here never loads, even if also
    listed in ``plugins.enabled``.
    r   load_configr1   r  default)r   r(  r   r   listsetr   )r(  configr  s      r&   r  r    sQ    169j"E *8T :s8}EE us   6A 	A AAc                f    ddl m}m}  |       }d|vri |d<   t        |       |d   d<    ||       y)z/Write the disabled plugins list to config.yaml.r   r(  save_configr1   r  Nr   r(  r0  sorted)r  r(  r0  r-  s       r&   r  r    s:    :]Fy$*8$4F9j!r(   basiczdashboard_auth/basicc                    | j                  d      }t        |t              sy|j                  d      }t        |t              syt	        |      t
        z  syt        t	        |      t
        z
        |d<   y)a  Re-enable the bundled basic dashboard-auth plugin in *cfg*.

    ``hermes setup`` / ``hermes plugins disable basic`` can park the plugin
    in ``plugins.disabled`` while ``dashboard.basic_auth`` is configured.
    The basic provider is a bundled backend that still respects the
    deny-list, so password auth silently fails until the block is removed.

    Returns True when ``plugins.disabled`` was modified.
    r1   Fr  T)r   r   r   r+  r,  _BASIC_AUTH_PLUGIN_KEYSr2  )cfgplugins_cfgr  s      r&   *ensure_basic_auth_plugin_enabled_in_configr8    sk     '')$Kk4(z*Hh%M33$H//K
 r(   c                    	 ddl m}   |        }|j                  di       }t        |t              s
t               S |j                  dg       }t        |t              rt        |      S t               S # t        $ r t               cY S w xY w)zRead the enabled plugins allow-list from config.yaml.

    Plugins are opt-in: only names here are loaded. Returns ``set()`` if
    the key is missing (same behaviour as "nothing enabled yet").
    r   r'  r1   r  )r   r(  r   r   r   r,  r+  r   )r(  r-  r7  r  s       r&   r  r    sr    	1jjB/+t,5L//)R0)'48s7|CceC us   8A2 ,A2 (	A2 2BBc                f    ddl m}m}  |       }d|vri |d<   t        |       |d   d<    ||       y)z.Write the enabled plugins list to config.yaml.r   r/  r1   r  Nr1  )r  r(  r0  r-  s       r&   r  r    s9    :]Fy#)'?F9i r(   c                    t               }|D ]  }| |d   k(  s	| |d   k(  s|d   c S  |D cg c]"  }| |d   j                  d      d   k(  s|d   $ }}t        |      dk(  r|d   S yc c}w )u]  Resolve a user-supplied plugin identifier to its canonical registry key.

    Accepts either the bare manifest name (``nemo_relay``), the directory
    name, or the full path-derived key (``observability/nemo_relay``) and
    returns the canonical key the loader gates on (``manifest.key`` or, for a
    flat plugin, the bare name). Returns ``None`` when no plugin matches.

    This is the single normalization point so ``hermes plugins enable`` /
    ``disable`` write the same key that ``PluginManager`` matches against —
    nested category plugins (e.g. ``observability/nemo_relay``) included.
    rY   r   r9   r`   r
   N_discover_all_pluginsrc   rb   r   entriesr   leaf_matchess       r&   _resolve_plugin_keyrA    s     $%G 58tuQx/8O +2UTU1X^^C=PQS=T5TE!HULU
<AA Vs   A*A*c                    t               }|D ]  }| |d   k(  s	| |d   k(  s|d   |d   fc S  |D cg c]&  }| |d   j                  d      d   k(  r
|d   |d   f( }}t        |      dk(  r|d   S yc c}w )a3  Resolve *name* to ``(canonical_key, source)`` or ``None`` if no match.

    Mirrors :func:`_resolve_plugin_key`'s normalization but also returns the
    plugin's source (``"bundled"``, ``"user"``, ``"project"``, ...) so the
    enable path can tell whether a built-in-override consent prompt is needed.
    rY   r   rV   r9   r`   r
   Nr<  r>  s       r&   _resolve_plugin_key_and_sourcerC     s     $%G (58tuQx/!HeAh''(
 +2!&58>>#&r** 
q58L  <AAs   +A3c                D   ddl m}m}  |       }|j                  di       }t	        |t
              si }||d<   |j                  di       }t	        |t
              si }||d<   |j                  | i       }t	        |t
              si }||| <   t        |      ||<    ||       y)zEWrite ``plugins.entries.<plugin_id>.<key> = value`` into config.yaml.r   r/  r1   r?  N)r   r(  r0  
setdefaultr   r   bool)		plugin_idkeyr   r(  r0  r-  r7  r?  r   s	            r&   _set_plugin_entry_flagrI  5  s    :]F##Ir2Kk4('y$$Y3Ggt$!(Iy"-EeT""	eE#Jr(   c                p   ddl m}  |       }t        |       }|*|j                  d|  d       t	        j
                  d       |\  }}t               }t               }||v xr ||v}	|	s|j                  |       |j                  |       |j                  d      d   }
|
|k7  r|j                  |
       t               D ]   }|d	   |k(  s|j                  |d           n t        |       t        |       |j                  d
| d       n|j                  d| d       |dk(  ryt        |||       y)a  Add a plugin to the enabled allow-list (and remove it from disabled).

    For non-bundled plugins, prompt the operator about granting the
    privileged ``allow_tool_override`` capability (replacing built-in tools
    like ``shell_exec`` / ``write_file``). ``allow_tool_override`` is a
    tri-state: ``True`` grants without prompting, ``False`` declines without
    prompting, ``None`` (default) asks interactively. Bundled plugins are
    trusted and never prompted.
    r   r   N[red]Plugin '$' is not installed or bundled.[/red]r
   r9   r`   rY   r  z.[/bold] enabled. Takes effect on next session.[dim]Plugin 'z' is already enabled.[/dim]bundled)r   r   rC  r   r   r   r  r  r  r  rc   r=  r  r  _resolve_tool_override_grant)r   allow_tool_overrider   r   resolvedrH  sourcer  r  already_enabledbarer   s               r&   
cmd_enablerU  I  sF    %iG .d3HdV+OPQKC G "HWn<H)<OC yy~b!3;T"*, 	EQx3  q*		
 	'"8$.se 4, ,	

 	cU*EFG  #/BCr(   c                "   |4d}	 | j                  |      j                         j                         }|dv }|}t        |d|       |r| j                  d| d| d       y| j                  d	| d
| d       y# t        t        f$ r d}Y Zw xY w)zResolve and persist the ``allow_tool_override`` grant for a plugin.

    ``allow_tool_override`` tri-state: True grants, False declines, None
    prompts interactively (defaulting to deny on a non-interactive stdin).
    Nz[yellow]Allow this plugin to replace built-in tools (e.g. shell_exec, write_file)?[/yellow]
  This is a privileged capability: an override can intercept everything the agent routes through that tool.
  Grant it? [y/N] r   >   r	  r
  rP  u!   [green]✓[/green] Granted [bold]zD[/bold] permission to override built-in tools ([dim]plugins.entries.z".allow_tool_override: true[/dim]).r  z@ may not override built-in tools. Re-run `hermes plugins enable z2 --allow-tool-override` to grant this later.[/dim])r   r>   r  r   r   rI  r   )r   rH  rP  promptr  rG  s         r&   rO  rO    s     "! 		]]6*00288:F %4I9&;=PQ/u 5%%.K/QS	
 	C5 &&)U +  	
 +, 	F	s   -A: :BBc                   ddl m}  |       }t        |       }|*|j                  d|  d       t	        j
                  d       t               }t               }||vr||v r|j                  d| d       y|j                  |       |j                  d	      d
   }||k7  r|j                  |       |j                  |       t        |       t        |       |j                  d| d       y)zBRemove a plugin from the enabled allow-list (and add to disabled).r   r   NrK  rL  r
   rM  z' is already disabled.[/dim]r9   r`   u"   [yellow]⊘[/yellow] Plugin [bold]z/[/bold] disabled. Takes effect on next session.)r   r   rA  r   r   r   r  r  r  rc   r  r  r  )r   r   r   rH  r  r  rT  s          r&   cmd_disablerY    s    $iG
d
#C
{dV+OPQ G "H
'cXocU*FGHOOC 99S>"Ds{LLgx MM
/u 5( 	(r(   c                    t        |       duS )z>Return True if a plugin with *name* (bare name or key) exists.N)rA  )r   s    r&   _plugin_existsr[    s    t$D00r(   c                   | dz  }|j                         s| dz  }|j                         sy	 ddl}| j                  }d}d}|rl	 t	        |d      5 }|j                  |      xs i }ddd       j                  d| j                        }|j                  d	d      }|j                  d
d      }|r| d| j                   n|}	||||	fS # t        $ r d}Y w xY w# 1 sw Y   uxY w# t        $ r Y Cw xY w)z|Read a plugin.yaml manifest and return (name, version, description, key).

    Returns None if no manifest file exists.
    r   r   Nr   r   r   r   r   versionr   r9   )ru   r   ImportErrorr   r   r   r   r   )
r   prefixr   r   r   r]  r   r   r   rH  s
             r&   _read_manifest_infor`    s   
 %M!L(! 66DGK	mg6 3!>>!,23<</Dll9b1G",,}b9K #)VHAaffX
dC+s**  3 3
  		s<   C
 C' C'AC' 
CCC$ C' '	C32C3c           	        | j                         syt        | j                               D ]  }|j                         s|dk(  r|r|j                  |v r*t	        ||      }|9|\  }}	}
}||v r|dk(  rI|}|dk(  r|dz  j                         rd}||	|
|||f||<   q|dk\  rw|r| d|j                   n|j                  }t        ||t               ||dz   |        y)	zRecursive directory scan matching PluginManager._scan_directory_level.

    Populates *seen* with key -> (name, version, description, source, dir, key).
    Nr   rN  userrX   r   r
   r9   )rv   r2  r   r   r`  ru   _scan_levelr,  )baserR  
skip_namesr_  depthseenr   infor   r]  r   rH  	src_label
sub_prefixs                 r&   rc  rc    s     ;;=DLLN# CxxzA:*:)="1f-.2+D';d{v2IQZ$7$7$9!	wY3GDIA:-3xq)
Avsuj%!)TB%Cr(   c            	         i } ddl m}  |       }|dddhft               dt               ffD ]  \  }}}t	        |||dd|         t               D ]  \  }}}}	|||d|	|f| |<    t        | j                               S )	u^  Return a list of (name, version, description, source, dir_path, key) for
    every plugin the loader can see — user + bundled + project + entry point.

    Matches the ordering/dedup of ``PluginManager.discover_and_load``:
    bundled first, then user, then project, then entry points. Later sources
    override earlier ones on key collision.
    r   get_bundled_plugins_dirrN  memorycontext_enginerb  r   
entrypoint)hermes_cli.pluginsrm  r5   r,  rc  _discover_entrypoint_pluginsr+  values)
rg  rm  repo_pluginsrd  rR  skipr   r]  r   r   s
             r&   r=  r=    s     D ;*,L	y8-=">?	' 5fd 	D&$At4	5 -I,J L(g{DG[,dKT
Lr(   c                    ddl m}  	 t        j                  j	                         }t        |d      r|j                  |       }nBt        |t              r|j                  | g       }n|D cg c]  }|j                  | k(  s| }}g }|D ]  }d}d}t        |dd      }t        |d	d      }	|	9t        t        |d
d      xs d      }t        |	j                  dd      xs d      }|j!                  |j"                  |||j$                  f        |S c c}w # t        $ r"}t        j                  d|       g cY d}~S d}~ww xY w)a!  Return plugin entries advertised through ``hermes_agent.plugins``.

    Entry-point plugins are installed as Python packages, so they do not have a
    plugin directory under ``~/.hermes/plugins``. Include package metadata here
    so ``hermes plugins list`` can show and enable them.
    r   )ENTRY_POINTS_GROUPselect)groupz'Entry-point plugin discovery failed: %sNr   distmetadatar]  Summary)rq  rw  	importlibr{  entry_pointshasattrrx  r   r   r   ry  r   r   debuggetattrr   r   r   r   )
rw  eps	group_epsepexcr?  r]  r   rz  r{  s
             r&   rr  rr  -  s9    6
  --/3!

);
<IT" 2B7I&)LRXX9K-KLIL
 02G Br64(4T2'$	26<"=Ghll9b9?R@K+rxx@AB N M >D	s6   A$D ,DDD D 	E"D?9E?Ec                (    | |v s||v ry| |v s||v ryy)zAReturn the user-facing activation state for a plugin name or key.r  r  znot enabledr/   )r   r  r  rH  s       r&   _plugin_statusr  O  s&    x3(?w#.r(   c           
         | }t        |dd      st        |dd      r|D cg c]  }|d   dk7  s| }}t        |dd      r'|D cg c]  }t        |d   |||d   	      dk(  r| }}|S c c}w c c}w )
z*Apply ``hermes plugins list`` CLI filters.
no_bundledFrb  rV   rN  r  r   rY   rH  )r  r  )r?  argsr  r  filteredr   s         r&   _filter_plugin_entriesr  X  s    Ht\5)WT65-I'/Ie58y3HEIItY&'
eAhuQxHIU 
 
 O J
s   A+A+!A0c                   ddl m} ddlm}  |       }t	               }|s#|j                  d       |j                  d       yt               }t               }t        || ||      }t        | dd      r]|D 	
cg c]'  \  }}}	}
}}|t        ||||	      t        |      |	|
d
) }}}
}	}}}t        t        j                  |d             yt        | dd      r@|D ]:  \  }}}}
}}t        ||||	      }t        |dd|
ddt        |      dd|        < y|s|j                  d       y |dd      }|j                  dd       |j                  d       |j                  dd       |j                  d       |j                  dd       |D ]H  \  }}}	}
}}t        ||||	      }|dk(  rd}n
|dk(  rd}nd }|j                  ||t        |      |	|
       J |j                          |j                  |       |j                          |j                  d!       |j                  d"       |j                  d#       |j                  d$       yc c}}}
}	}}w )%z>List all plugins (bundled + user) with enabled/disabled state.r   r   )Tablez [dim]No plugins installed.[/dim]:[dim]Install with:[/dim] hermes plugins install owner/repoNjsonFr  )r   statusr]  r   rR  rW   )indentplain12 8z3[dim]No plugins matched the selected filters.[/dim]Plugins)r   
show_linesNamebold)styleStatusVersiondimDescriptionSourcer  z[red]disabled[/red]r  z[green]enabled[/green]z[yellow]not enabled[/yellow]zA[dim]Compact view:[/dim] hermes plugins list --plain --no-bundledz-[dim]Interactive toggle:[/dim] hermes pluginsz?[dim]Enable/disable:[/dim] hermes plugins enable/disable <name>uI   [dim]Plugins are opt-in by default — only 'enabled' plugins load.[/dim])r   r   
rich.tabler  r=  r   r  r  r  r  r  r   r  dumps
add_columnadd_row)r  r   r  r   r?  r  r  r   r]  r   rR  _dirrH  payload_descriptionr  tablestatus_names                     r&   cmd_listr  e  sk   $ iG#%G89RS G "H$WdGXFGtVU# BI	
 	
 >g{FD# (wcJw<* 	
 	
 	djj+,tWe$>E 	E:D'<s#D'8EFVBKq
!CL+;1TFCD	E 	KL	e4E	V6*	X	Ye,	]#	XU+9@ G5g{FD#$T7H#F*$*FI%-F3FdFCL+vFG MMOMM%MMOMMUVMMABMMSTMM]^]	
s   ;,Ic                 ~    	 ddl m}   |        D cg c]
  \  }}}||f c}}}S c c}}}w # t        $ r g cY S w xY w)zAReturn [(name, description), ...] for available memory providers.r   )discover_memory_providers)plugins.memoryr  r   )r  r   r   _avails       r&   _discover_memory_providersr    sC    <7P7RSS!3tVtSSS 	s   . '. . <<c                    g } t               }	 ddlm}  |       D ]/  \  }}}||vs| j                  ||f       |j	                  |       1 	 	 ddlm}m}  |         |       }|r8t        |dd      r+|j                  |vr| j                  |j                  df       | S # t
        $ r Y ^w xY w# t
        $ r Y | S w xY w)aZ  Return [(name, description), ...] for available context engines.

    Includes repo-shipped engines from ``plugins/context_engine/`` AND
    plugin-registered engines (third-party engines installed as Hermes
    plugins via ``ctx.register_context_engine``). Repo-shipped descriptions
    win when a plugin-registered engine collides on name.
    r   )discover_context_engines)discover_pluginsget_plugin_context_enginer   Nzinstalled plugin)r,  plugins.context_enginer  r   r  r   rq  r  r  r  r   )	enginesrg  r  r   r   r  r  r  plugin_engines	            r&   _discover_context_enginesr    s     &(GUDC":"< 	D$4d|,	R13W]FDAmFXFX`dFdNNM..0BCD N    Ns)   B! &B! AB0 !	B-,B-0	B=<B=c                 b    	 ddl m}   |        }t        |ddd      xs dS # t        $ r Y yw xY w)zBReturn the current memory.provider from config (empty = built-in).r   r'  rn  providerr   r)  r   r(  r   r   r(  r-  s     r&   _get_current_memory_providerr    s9    1vxR@FBF    " 	..c                 b    	 ddl m}   |        }t        |ddd      xs dS # t        $ r Y yw xY w)z.Return the current context.engine from config.r   r'  contextengine
compressorr)  r  r  s     r&   _get_current_context_enginer    s9    1vy(LIY\Y r  c                T    ddl m}m}  |       }d|vri |d<   | |d   d<    ||       y)z'Persist memory.provider to config.yaml.r   r/  rn  r  Nr   r(  r0  r   r(  r0  r-  s       r&   _save_memory_providerr    s5    :]Fvx#'F8Z r(   c                T    ddl m}m}  |       }d|vri |d<   | |d   d<    ||       y)z&Persist context.engine to config.yaml.r   r/  r  r  Nr  r  s       r&   _save_context_enginer    s5    :]Fy"&F9hr(   c                    ddl m}  t               }t               }dg}dg}d}|D ]F  \  }}|j	                  |       |r| d| n|}|j	                  |       ||k(  s9t        |      dz
  }H |r7||vr3|j	                  |       |j	                  | d       t        |      dz
  } | d||	      }	||	   }
|
|k7  rt        |
       y
y)zDLaunch a radio picker for memory providers. Returns True if changed.r   curses_radiolistzbuilt-in (default)r   r   r
    (not found)zMemory Provider (select one)r   itemsselectedTF)hermes_cli.curses_uir  r  r  r   rb   r  )r  current	providersr  namesr  r   r   r   choicenew_providers              r&   _configure_memory_providerr    s    5*,G*,I ""EDEH &
dT+/4&'TU7?5zA~H& 7%'Wy-.u:>,F =Lwl+r(   c                    ddl m}  t               }t               }dg}dg}d}|D ]F  \  }}|j	                  |       |r| d| n|}|j	                  |       ||k(  s9t        |      dz
  }H |dk7  r7||vr3|j	                  |       |j	                  | d       t        |      dz
  } | d||	      }	||	   }
|
|k7  rt        |
       y
y)zCLaunch a radio picker for context engines. Returns True if changed.r   r  zcompressor (default)r  r   r
   r  zContext Engine (select one)r  TF)r  r  r  r  r   rb   r  )r  r  r  r  r  r  r   r   r   r  
new_engines              r&   _configure_context_enginer  #  s    5)+G')G $$ENEH &
dT+/4&'TU7?5zA~H& ,7%#7Wy-.u:>+F vJWZ(r(   c            	        ddl m}   |        }t               }t               }t	               }g }g }t               }t        |      D ]m  \  }\  }	}
}}}}|r|	 d| n|	}|dk(  r| d}|j                  |       |j                  |       ||v xs |	|v xr
 ||vxr |	|v}|s]|j                  |       o t               xs d}t               }d|t        fd|t        fg}t        |      }t        |      }|s%|s#|j                  d	       |j                  d
       yt        j                   j#                         s|j                  d       y	 ddl}t'        |||||||       y# t(        $ r t+        ||||||       Y yw xY w)uJ   Interactive composite UI — general plugins + provider plugin categories.r   r   r   rN  z
 [bundled]built-inzMemory ProviderzContext EnginezE[dim]No plugins installed and no provider categories available.[/dim]r  Nz0[dim]Interactive mode requires a terminal.[/dim])r   r   r=  r  r  r,  	enumerater   r  r  r  r  r  rF  r   r   r  r  curses_run_composite_uir^  _run_composite_fallback)r   r   r?  enabled_setdisabled_setplugin_keysplugin_labelsplugin_selectedir   _versionr   rR  _drH  r   is_oncurrent_memorycurrent_context
categorieshas_pluginshas_categoriesr  s                          r&   
cmd_toggler  N  s   $iG $%G"$K$&L KMeO=Fw=O #99D(KS2=4&.4YgZ(E3U#
 K64;#6 )<')L( 	
 "#$ 23AzN13O	N,FG	?,EFJ
 {#K*%N~]^RS 99HIC&+}o&
G	= C]O ,j'	CCs   E# #E?>E?c           	         ddl m} t        |      t        |      t              z   ddd fd} j	                  |        |        t               }	t        |      }
t        |      D ]h  \  }}|j                  d      d   }|v r:|	j                  |       |
j                  |       ||k7  sF|
j                  |       X|
j                  |       j t               }|	|k7  }|
|k7  }|s|rMt        |	       t        |
       |j                  dt        |	       d	t        |      t        |	      z
   d
       ndkD  r|j                  d       d   r0t               xs d}t               }|j                  d| d| d       dkD  sd   r|j                  d       |j                          y)z<Custom curses screen with checkboxes + category action rows.r   )flush_stdinF)plugins_changedproviders_changedc           	        j                  d       j                         rj                          j                          j	                  dj
                  d       j	                  dj                  d       j	                  dj                  d       j	                  dj                  dkD  rdnj                  d       d}d}	 | j                          | j                         \  }}	 j                  }j                         r|j                  d      z  }| j                  ddd	|dz
  |       | j                  ddd
|dz
  j                         |dz
  }||k  r|}n|||z   k\  r||z
  dz   }d}dkD  r
||dz
  k  rN	 j                  }j                         r|j                  d      z  }| j                  |dd|dz
  |       |dz  }|}	t#        |t%        |d      z         }
t'        |	|
      D ]  }||dz
  k\  r n|v rdnd}||k(  rdnd}d| d| d|    }j(                  }||k(  r0j                  }j                         r|j                  d      z  }	 | j                  |d||dz
  |       |dz  } ||dz
  k  r|dz  }dkD  r||dz
  k  r	 j                  }j                         r|j                  d      z  }| j                  |dd|dz
  |       |dz  }t+              D ]  \  }\  }}}||dz
  k\  r n{|z   }||k(  rdnd}d| d|dd| }j(                  }||k(  r0j                  }j                         r|j                  d      z  }	 | j                  |d||dz
  |       |dz  } | j-                          | j/                         }|j0                  t3        d      hv r!dkD  r|dz
  !z  }n|j4                  t3        d      hv r!dkD  r|dz   !z  }n|j6                  t3        d      hv r'!dkD  rt#        !dz
  |t%        d|dz
        z         }n|j8                  t3        d      hv r$!dkD  rzt%        d|t%        d|dz
        z
        }n\|j:                  k(  rd}nI|j<                  k(  rt%        d!dz
        }n)|t3        d      k(  r|k  rj?                  |h       n|z
  }d|cxk  rk  rn njA                          |   \  }}} |       }|r(d d<   ||dk(  rtC               xs dn	tE               |f|<   jG                         } jI                          jK                          | jM                  d       j                         rj                          j                          j	                  dj
                  d       j	                  dj                  d       j	                  dj                  d       j	                  dj                  dkD  rdnj                  d       j                  d       n|jN                  ddhv rr|k  rd d<   y |z
  }d|cxk  rk  rkn ngjA                          |   \  }}} |       }|r(d d<   ||dk(  rtC               xs dn	tE               |f|<   jG                         } jI                          jK                          | jM                  d       j                         rj                          j                          j	                  dj
                  d       j	                  dj                  d       j	                  dj                  d       j	                  dj                  dkD  rdnj                  d       j                  d       n|dt3        d       hv rd d<   y # j                   $ r Y w xY w# j                   $ r Y 0w xY w# j                   $ r Y w xY w# j                   $ r Y @w xY w# j                   $ r Y w xY w)!Nr   r
   r`   rW   rV   r_      Tr  uV     ↑↓/j/k navigate  PgUp/PgDn page  SPACE toggle  ENTER configure/confirm  ESC donez  General Pluginsu   ✓r  u   → [z] z  Provider Pluginsz   z<24u    ▸ kjr   rY   br  r  
      r     q)(curs_set
has_colorsstart_coloruse_default_colors	init_pairCOLOR_GREENCOLOR_YELLOW
COLOR_CYANCOLORSCOLOR_WHITEcleargetmaxyxA_BOLD
color_pairaddnstrA_DIMerrorminmaxrangeA_NORMALr  refreshgetchKEY_UPordKEY_DOWN	KEY_NPAGE	KEY_PPAGEKEY_HOMEKEY_ENDsymmetric_difference_updateendwinr  r  initscrnoechocbreakkeypad	KEY_ENTER)"stdscrcursorscroll_offsetmax_ymax_xhattrvisible_rowsr	  sattrplugin_startplugin_stopr  checkarrowlineattrcicat_namecat_current_cat_fncat_idxrH  	_cat_name_cat_curcat_fnchangedr  chosenr  n_categories	n_pluginsr  result_holdertotal_itemss"                             r&   _drawz _run_composite_ui.<locals>._draw  sZ    %%'Q 2 2B7Q 3 3R8Q 1 126QV]]Q%6F<N<NPRSLLN!??,LE5$$&V..q11Eq!Y	5AqlAIv||  !19L% &=<77 & 5 9A 1}uqy= &!,,.!V%6%6q%99Eq!-@%!)US FA,!)]Sq=Q-QR|[9 AEAI~()VHE()VHEugRwbq1A0BCD!??DF{%}}!,,. F$5$5a$88Dq!T519dC FA$ 519}Q aA	M"MME((*!2!21!55NN1a)=uqy%P Q<Ej<Q 8B8;EAI~'"nG(/6(9HsEugS#h{mLD!??D&(%}}!,,. F$5$5a$88Dq!T519dC FA" NN,,.Cv}}c#h//?$qjK7F#c(33?$qjK7F))3s844? q&3q%!);L2LMF))3s844? FSEAI->$>?F'&Qa0CI%66x@  )+BB--6@n3	8V"(("AEM*=> !*PRVWPW < > L*%@%B &	.JrN "(!1d+!,,."..0"557",,Q0B0BBG",,Q0C0CRH",,Q0A0A2F",,QV]]Q5FFL^L^`bc*))2r22I%7;M"34  )+BB--6@n3	8V"(("AEM*=> )PRVWPW < > L*%@%B &	.JrN "(!1d+!,,."..0"557",,Q0B0BBG",,Q0C0CRH",,Q0A0A2F",,QV]]Q5FFL^L^`bc*SX&37/0}  << B "<< & "<<  || " "<< s]   1A*_ 	A_/ +`%A` -`1_,+_,/`````.-`.1aar9   r`   u%   
[green]✓[/green] General plugins: z
 enabled, z
 disabled.z&
[dim]General plugins unchanged.[/dim]r  r  u*   [green]✓[/green] Memory provider: [bold]z[/bold]  Context engine: [bold]z[/bold]z/[dim]Changes take effect on next session.[/dim]N)r  r  r,  rb   wrapperr  rc   r  r  r  r  r  r   r  r  )r  r  r  r  r  r  r   r  r?  new_enablednew_disabledr  rH  rT  prev_enabledenabled_changeddisabled_changed
new_memorynew_contextr:  r;  r<  r=  r>  s   ` `  `             @@@@@r&   r  r    s    1!FK I z?Ll*K(-EJMJ JX NN5M uKHLK( 
"3yy~b!;OOC   % s{$$T*S!
" $%L!\1O#x/*+&<(7K8H7I;#k"223:?	
 
Q?@()13Az
13;J< H%%0M:	

 1}&9:GHMMOr(   c           
        ddl m}m} t         |d|j                               | rt        |      }t         |d|j                               t         |d|j                               	 t        |      D ]6  \  }	}
|	|v r |d|j                        nd}t        d| d	|	d
z   dd|
        8 t                	 t         |d|j                              j                         }|snBt        |      d
z
  }d|cxk  rt        |       k  rn n|j                  |h       t                t               }t        |      }t        |       D ]h  \  }	}|j#                  d      d   }|	|v r:|j%                  |       |j'                  |       ||k7  sF|j'                  |       X|j%                  |       j t)               }||k7  s||k7  rt+        |       t-        |       |rt         |d|j                               t        |      D ]!  \  }\  }}}t        d|d
z    d| d| d       # t                	 t         |d|j                              j                         }|r2t        |      d
z
  }d|cxk  rt        |      k  rn n ||   d           t                yt                y# t        t        t         f$ r Y yw xY w# t        t        t         f$ r Y t                yw xY w)z1Text-based fallback for the composite plugins UI.r   )Colorscolorz

  Pluginsz
  General Pluginsz&  Toggle by number, Enter to confirm.
u   [✓]z[ ]r   r  r
   z>2z. z"  Toggle # (or Enter to confirm): Nr9   r`   z
  Provider Pluginsr  ]z"  Configure # (or Enter to skip): rW   )hermes_cli.colorsrI  rJ  r   YELLOWr,  DIMr  GREENr   r>   r   rb   r  r=   r   r   rc   r  r  r  r  r  )r  r  r  r  r  r   rI  rJ  r:  r  r   rp   valrq   rA  rB  rH  rT  rC  r1  r2  r3  r8  s                          r&   r  r    s    0	%v}}
-. _%e)6==9:e=vzzJK%m4 95<=Kz6<<8U6(!AE":Rw789 GE"F

STZZ\#hl.c+..66u= G & 5M, 	&FAs99S>"%DF{$$$S)3; ((.  %	& (),&,(*Bk*|, e*FMM:;3<Z3H 	=/B/;BrAvhb
"[M;<	=	BFJJOPVVXCX\,S_,%JrN1%' 
GEGM  18< F -x8 		G	s+   ;,J+ (7J+ 7AK +KKK&%K&c                  g }	 t        |       \  }}|j                  d      r|j                  d       	 t	        | |      \  }}}t        |      }
|rLt               }t               }|j                  |       |j                  |       t        |       t        |       d}|dz  }|j                         rt        |      }d|||
||d	S # t        $ r Y w xY w# t
        $ r}	dt        |	      dcY d}	~	S d}	~	ww xY w)
zPNon-interactive install for the web dashboard. Returns a JSON-serializable dict.r  zEInsecure URL scheme; prefer https:// or git@ for production installs.r  Fr   r  Nr   T)r   r   warningsmissing_envafter_install_pathr  )rs   ra   r   r=   r  r*   r   r   r  r  r  r  r  r  ru   )ri   r   r  rS  rm   r  rD   r  r  r  rT  endishintaps                  r&   dashboard_install_pluginrZ    s    H+J745OOW05I6
2"N ..@AK!
~N#"3D	$	$B	yy{2w %"" 3     0c#h//0s.   0C
 C 
	CC	C:"C5/C:5C:c                   	 ddl m} 	 ddlm}m}  |         |       }|j                  j                         D ]f  \  }}|j                  j                  | k(  s|| k(  s%|j                  D ]2  }|j                  |      }|s|j                  s$|j                  c c S   n 	 ddlm}	  |	       t               fD ]~  }
|
j                         s|
| z  }|j                         s*t!        |      }|j#                  d      xs g D ]2  }|j                  |      }|s|j                  s$|j                  c c S   	 y# t        $ r Y yw xY w# t        $ r Y w xY w# t        $ r Y yw xY w)u!  Return the toolset key a plugin registers its tools under, or None.

    Queries the live tool registry — the plugin must already be loaded.
    Falls back to reading ``provides_tools`` from plugin.yaml and looking
    up the toolset from the registry for the first tool name found.
    r   )registryN)r  get_plugin_managerrl  provides_tools)tools.registryr\  r   rq  r  r]  _pluginsr  r   r   tools_registered	get_entrytoolsetrm  r5   rv   r   r   )r   r\  r  r]  manager_keyloaded	tool_namer   rm  rd  rx   r   s                r&   _get_plugin_toolset_keyrh    sg   +
K$&#,,224 	LD&##t+tt|!'!8!8 -I$..y9E$}},- 	>,.? 		-D;;=tI!))4!).>!?!E2 -I$..y9E$}},-		- E       sf   D= AE "E E E E #A E $6E E (E 8E =	E	E		EE	E'&E'c               |   t        |       }|syddlm}m}  |       }|j	                  d      }t        |t              si }||d<   d}|j                         D ]I  \  }}	t        |	t              s|r||	vs|	j                  |       d}2||	v s7|	j                  |       d}K |r|s
|s|g|d<   d}|r	 ||       yy)zAdd or remove a plugin's toolset from platform_toolsets for all platforms.

    Only acts if the plugin actually provides tools (has a toolset key).
    Nr   r/  platform_toolsetsFTcli)rh  r   r(  r0  r   r   r   r  r+  r   remove)
r   r  toolset_keyr(  r0  r-  rj  r9  platformts_lists
             r&   _toggle_plugin_toolsetrp  A  s    
 *$/K:]F

#67'.&7"#G.446 	''4('){+G#NN;'G	 g&7$/=% F r(   c                  t        |       s	dd|  ddS t               }t               }|rY| |v r
| |vrd| ddS |j                  |        |j	                  |        t        |       t        |       t        | d       d| ddS | |vr
| |v rd| ddS |j	                  |        |j                  |        t        |       t        |       t        | d       d| ddS )zEnable or disable a plugin in ``config.yaml`` (runtime allow/deny lists).

    For plugins that provide tools (toolsets), also toggles the toolset in
    ``platform_toolsets`` so the agent actually sees the tools in sessions.
    Fr   z' is not installed or bundled.rR  T)r   r   	unchanged)r  )r[  r  r  r  r  r  r  rp  )r   r  rV  rW  s       r&   "dashboard_set_agent_plugin_enabledrs  g  s     $6T&UVV		B

C2:$c/4@@
tD"3tD1Du==2~$#+Dt<<JJtGGDMbs4.599r(   c                z    t               }	 t        | |d      }|j                         r|S dS # t        $ r Y yw xY w)z>Resolved path under ``~/.hermes/plugins/<name>`` if it exists.Tr6   N)r5   rF   r=   rv   )r   rA   rD   s      r&   _user_installed_plugin_dirru    sE    .K&t[tL ]]_6.$.  s   . 	::c                    t        |       }|dd|  dt                ddS |dz  j                         s	dd|  ddS t        |      \  }}|sd|dS dd	lm} t        | |              d
|v }d| ||dS )z1``git pull`` inside ``~/.hermes/plugins/<name>``.Fr   ' was not found under r:   rR  rX   z-' is not a git checkout; cannot pull updates.r   r   r  T)r   r   r!  rr  )ru  r5   ru   r  r   r   r   )r   rD   r   msgr   rr  s         r&   dashboard_update_user_pluginry    s    '-F~v%;LN;K1M
 	

 VO##%v%RS
 	

 #6*GBc**$	*$+I)LLr(   c           	        t               }|sy	 t        j                  |ddgdddt        |             }|j                  dk7  rB|j                  xs d	j                         xs |j                  j                         }d
|xs dfS d|j                  j                         fS # t        $ r Y yt        j
                  $ r Y yw xY w)N)Fr   rH   z	--ff-onlyTr   )r   r   r   cwd)Fz$Git pull timed out after 60 seconds.r   r   Fzgit pull failed.)
r'   r   r   r   r   r   r   r   r>   r   )rD   r   r   r   s       r&   r  r    s    %'G<=fk*F
 A}}"))+Dv}}/B/B/Dc////$$&&&  =<$$ =<=s   &B" "	C-CCc                    t               }t               D ]  \  }}}}}}|| k(  s|dk(  sdddc S  t        |       }|dd|  d| ddS t        j                  |       d| d	S )
z7Delete a plugin tree under ``~/.hermes/plugins/`` only.rN  Fz5Bundled plugins cannot be removed from the dashboard.rR  r   rw  r:   T)r   r   )r5   r=  ru  r   r   )	r   rA   n_verr  src_pathre  rD   s	            r&   dashboard_remove_user_pluginr    s    .K)>)@ c%4S%9	)*abbc (-F~v%;K=J
 	

 MM&%%r(   c                r   t        | dd      }|dk(  rEd}t        | dd      rd}nt        | dd      rd}t        | j                  t        | dd      |	       y|d
k(  rt        | j                         y|dv rt        | j                         y|dk(  r9d}t        | dd      rd}nt        | dd      rd}t        | j                  |       y|dk(  rt        | j                         y|dv rt        |        y|t                yddl
m}  |       j                  d| d       t        j                  d       y)z$Dispatch hermes plugins subcommands.plugins_actionNinstallr  FT	no_enabler   )r   r  update>   rmrl  	uninstallrP  no_allow_tool_override)rP  disable>   lsr+  r   r   z[red]Unknown plugins action: z[/red]r
   )r  r  ri   r#  r   r%  rU  rY  r  r  r   r   r   r   r   )r  action
enable_argallow_overrider   s        r&   plugins_commandr    s   T+T2F
45)JT;.JOO$/	

 
8	499	0	0499	8	 4.6!NT3U;"N499.A	9	DII	>	!	(	7xvFGr(   )returnOptional[str])r  r   )r   r   rA   r   r7   rF  r  r   )ri   r   r  ztuple[str, Optional[str]])rw   r   rl   r   r  r   )r   r   r  r   )r   r   r  r   )r   r   r  None)r   r   r  z	list[str])r   r   r  r  )r   r   ri   r   r  r  )r   r   rA   r   r  r  )r   r   rA   r   r  r   )ri   r   r   rF  r  ztuple[Path, dict, str])FN)ri   r   r   rF  r  Optional[bool]r  r  )r   r   r  r  )r  r,  )r  r,  r  r  )r6  r   r  rF  )r  r,  r  r  )r   r   r  r  )r   r   r  zOptional[tuple])rG  r   rH  r   r   rF  r  r  r[   )r   r   rP  r  r  r  )rH  r   rP  r  r  r  )r   r   r  rF  )r   r   r_  r   )rd  r   rR  r   re  r,  r_  r   rf  r   rg  r   r  r  )r  r+  )r  zlist[tuple[str, str, str, str]])r   )
r   r   r  r,  r  r,  rH  r   r  r   )
r?  r+  r  r   r  r,  r  r,  r  r+  )r  z
Any | Noner  r  )r  zlist[tuple[str, str]])r  r   )r  rF  )r  r  )ri   r   r   rF  r  rF  r  dict[str, Any])r   r   r  rF  r  r  )r   r   r  rF  r  r  )r   r   r  zOptional[Path])r   r   r  r  )rD   r   r  ztuple[bool, str])Yr.   
__future__r   	functoolsimportlib.metadatar}  r  loggingr   r   r   r   pathlibr   typingr   r   r   r   r   r   hermes_cli.secret_promptr	   	getLoggerr+   r   	lru_cacher'   r   r*   r   r5   rF   re   rs   ry   r   r   r   r   r   r   r   r   r  r  r#  r%  r  r  	frozensetr5  r8  r  r  rA  rC  rI  rU  rO  rY  r[  r`  rc  r=  rr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rZ  rh  rp  rs  ru  ry  r  r  r  r/   r(   r&   <module>r     s   #     	   
    , % 9			8	$ Q  DS9 S    	6
66 	6
 
6r @F:*\$HV:
$b6N !SSS S 
	Sl%+P(" $W.D$EF 0$6*(:Dz%
%
,:%
	%
P@1
+< C
 C C  C 	 C
  C  C 
 CF2D
?_N@#L#VDCNHV?D.. . 	.
 .b+\#L:B/M4'.&$'r(   