
    PL
j                       d 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
mZ ddlmZmZ ddlmZmZ ddlmZmZ ddlmZ ddlmZmZmZmZmZmZ dd	lmZmZm Z  ddl!Z!ddl"Z"dd
l#m$Z$m%Z%m&Z& ddl'm(Z( ddl)m*Z*  ej+        e,          Z- e            Z.e.dz  Z/e/dz  Z0e0dz  Z1e0dz  Z2e0dz  Z3e0dz  Z4e0dz  Z5dZ6h dZ7dZ8e G d d                      Z9e G d d                      Z:de;de;de<de;fdZ=d e;de;fd!Z>d"e;de;fd#Z?d$d%d&e;d'e@dee!jA                 fd(ZBd)e;de;fd*ZC G d+ d,          ZD G d- d.e
          ZE G d/ d0eE          ZF G d1 d2eE          ZG G d3 d4eE          ZH G d5 d6eE          ZI G d7 d8eE          ZJ G d9 d:eE          ZK G d; d<eE          ZL G d= d>eE          ZMd?e;dee         fd@ZNd?e;dAeddfdBZOdCe9dePfdDZQ G dE dF          ZR G dG dH          ZS	 dudJe;dKe;dLe;dMe;dNe;dOe;ddfdPZTdvdQZUdRe:defdSZVdTedKe;d"e;dRe:dUe$defdVZWdKe;dee<e;f         fdWZXdRe:de;fdXZYdLeEdYe;de<fdZZZ	 dwdddd[d ee;         d\eeR         d]eeeE                  d^eeD         deeP         f
d_Z[d`Z\e5daz  Z]dbZ^deeP         fdcZ_deeP         fddZ` G de dfeE          Zadwd^eeD         deeE         fdgZbdheEdie;dje@dee;ee9         f         fdkZc	 	 	 	 	 dxd]eeE         die;dneee;e@f                  doe;dpeddqee         deee9         ee;e@f         ee;         f         fdrZe	 dydie;d]eeE         doe;dje@dee9         f
dtZfdS )zu  
Skills Hub — Source adapters and hub state management for the Hermes Skills Hub.

This is a library module (not an agent tool). It provides:
  - GitHubAuth: Shared GitHub API authentication (PAT, gh CLI, GitHub App)
  - SkillSource ABC: Interface for all skill registry adapters
  - OptionalSkillSource: Official optional skills shipped with the repo (not activated by default)
  - GitHubSource: Fetch skills from any GitHub repo via the Contents API
  - HubLockFile: Track provenance of installed hub skills
  - Hub state directory management (quarantine, audit log, taps, index cache)

Used by hermes_cli/skills_hub.py for CLI commands and the /skills slash command.
    N)ABCabstractmethod)	dataclassfield)datetimetimezone)PathPurePosixPath)get_hermes_home)AnyDictListOptionalTupleUnion)urljoinurlparse
urlunparse)
ScanResultcontent_hashTRUSTED_REPOS)is_safe_url)check_website_accessskillsz.hubz	lock.json
quarantinez	audit.logz	taps.jsonzindex-cachei  >   -  .  /  3  4     c                       e Zd ZU dZeed<   eed<   eed<   eed<   eed<   dZee         ed<   dZee         ed	<    e	e

          Zee         ed<    e	e
          Zeeef         ed<   dS )	SkillMetaz,Minimal metadata returned by search results.namedescriptionsource
identifiertrust_levelNrepopathdefault_factorytagsextra)__name__
__module____qualname____doc__str__annotations__r)   r   r*   r   listr-   r   dictr.   r   r        4/home/kuhnn/.hermes/hermes-agent/tools/skills_hub.pyr#   r#   D   s         66
IIIKKKOOOD(3-D(3-eD111D$s)111!E$777E4S>77777r8   r#   c                       e Zd ZU dZeed<   eeeeef         f         ed<   eed<   eed<   eed<    e	e
          Zeeef         ed<   d	S )
SkillBundlez>A downloaded skill ready for quarantine/scanning/installation.r$   filesr&   r'   r(   r+   metadataN)r/   r0   r1   r2   r3   r4   r   r   bytesr   r6   r=   r   r7   r8   r9   r;   r;   R   s         HH
IIIU3:&&''''KKKOOO$uT:::Hd38n:::::r8   r;   
path_value
field_nameallow_nestedreturnc                   t          | t                    st          d| d          |                                 }|st          d| d          |                    dd          }t          |          }d |j        D             }|                    d          s|                                rt          d| d|            |rt          d |D                       rt          d| d|            t          j        d	|d
                   rt          d| d|            |s(t          |          dk    rt          d| d|            d                    |          S )zDNormalize and validate bundle-controlled paths before touching disk.zUnsafe z: expected a stringz: empty path\/c                     g | ]}|d v|	S )>    .r7   .0parts     r9   
<listcomp>z*_normalize_bundle_path.<locals>.<listcomp>h   s"    BBBdD	,A,AT,A,A,Ar8   : c              3   "   K   | ]
}|d k    V  dS )z..Nr7   rI   s     r9   	<genexpr>z)_normalize_bundle_path.<locals>.<genexpr>l   s&      77777777r8   z	[A-Za-z]:r      )
isinstancer3   
ValueErrorstripreplacer
   parts
startswithis_absoluteanyre	fullmatchlenjoin)r?   r@   rA   raw
normalizedr*   rU   s          r9   _normalize_bundle_pathr_   ]   s   j#&& DB:BBBCCC




C =;:;;;<<<T3''J$$DBBdjBBBES!! ?T%5%5%7%7 ?=:====>>> ?C7777777 ?=:====>>>	|L%(++ ?=:====>>> ?CJJ!OO=:====>>>88E??r8   r$   c                 &    t          | dd          S )Nz
skill nameFr@   rA   r_   r$   s    r9   _validate_skill_namerd   v   s    !$<eTTTTr8   categoryc                 &    t          | dd          S )Nre   Fra   rb   )re   s    r9   _validate_category_namerg   z   s    !(zPUVVVVr8      timeouturlrj   c                   | }t          t          dz             D ]
}t          |          st                              d|            dS t          |          }|r+t                              d|d         |d                     dS 	 t          j        ||d          }n:# t          j	        $ r(}t          
                    d	||           Y d}~ dS d}~ww xY w|j        t          v r;t          |d
i                               d          }|s dS t          ||          }|c S t                              d|            dS )z5Fetch a URL with SSRF and redirect-target validation.rP   z!Blocked unsafe Skills Hub URL: %sNz*Blocked Skills Hub fetch for %s by rule %shostruleFrj   follow_redirectsz"Skills Hub fetch failed for %s: %sheaderslocationz/Skills Hub fetch exceeded redirect limit for %s)range_MAX_SKILL_FETCH_REDIRECTSr   loggerwarningr   infohttpxget	HTTPErrordebugstatus_code_REDIRECT_STATUS_CODESgetattrr   )rk   rj   current_url_blockedrespexcrr   s           r9   _guarded_http_getr   ~   sm   K-122  ;'' 	NN>LLL44&{33 	KK<  
 44	9['ERRRDD 	 	 	LL={CPPP444444	 555tY3377
CCH tt!+x88K
NNDcJJJ4s   BC.CCrel_pathc                 &    t          | dd          S )Nzbundle file pathTra   rb   )r   s    r9   _validate_bundle_rel_pathr      s    !(7IX\]]]]r8   c                       e Zd ZdZd Zdeeef         fdZdefdZ	defdZ
dee         fdZdee         fdZdee         fd	Zd
S )
GitHubAuthuF  
    GitHub API authentication. Tries methods in priority order:
      1. GITHUB_TOKEN / GH_TOKEN env var (PAT — the default)
      2. `gh auth token` subprocess (if gh CLI is installed)
      3. GitHub App JWT + installation token (if app credentials configured)
      4. Unauthenticated (60 req/hr, public repos only)
    c                 0    d | _         d | _        d| _        d S )Nr   )_cached_token_cached_method_app_token_expiryselfs    r9   __init__zGitHubAuth.__init__   s     ,0-1()r8   rB   c                 J    |                                  }ddi}|rd| |d<   |S )z5Return authorization headers for GitHub API requests.Acceptapplication/vnd.github.v3+jsonztoken Authorization_resolve_token)r   tokenrq   s      r9   get_headerszGitHubAuth.get_headers   s=    ##%%=> 	8'7'7'7GO$r8   c                 .    |                                  d uS Nr   r   s    r9   is_authenticatedzGitHubAuth.is_authenticated   s    ""$$D00r8   c                 <    |                                   | j        pdS )zRReturn which auth method is active: 'pat', 'gh-cli', 'github-app', or 'anonymous'.	anonymous)r   r   r   s    r9   auth_methodzGitHubAuth.auth_method   s"    "1k1r8   c                    | j         r.| j        dk    st          j                    | j        k     r| j         S t          j                            d          pt          j                            d          }|r|| _         d| _        |S |                                 }|r|| _         d| _        |S |                                 }|r+|| _         d| _        t          j                    dz   | _        |S d| _        d S )Nz
github-appGITHUB_TOKENGH_TOKENpatzgh-clii  r   )	r   r   timer   osenvironry   _try_gh_cli_try_github_app)r   r   s     r9   r   zGitHubAuth._resolve_token   s     	*"l22dikkDDZ6Z6Z)) 
~..L"*..2L2L 	!&D"'DL   "" 	!&D"*DL $$&& 	!&D".D%)Y[[4%7D"L)tr8   c                 4   	 t          j        g dddd          }|j        dk    r2|j                                        r|j                                        S n># t
          t           j        f$ r%}t                              d|           Y d}~nd}~ww xY wdS )z#Try to get a token from the gh CLI.)ghauthr   Tr!   )capture_outputtextrj   r   zgh CLI token lookup failed: %sN)	
subprocessrun
returncodestdoutrS   FileNotFoundErrorTimeoutExpiredru   r{   )r   resultes      r9   r   zGitHubAuth._try_gh_cli   s    	>^'''#$  F  A%%&-*=*=*?*?%}**,,,!:#<= 	> 	> 	>LL91========	>ts   AA B0BBc                 Z   t           j                            d          }t           j                            d          }t           j                            d          }t          |||g          sdS 	 ddl}n+# t
          $ r t                              d           Y dS w xY w	 t          |          }|	                                sdS |
                    d          }t          t          j                              }|d	z
  |d
z   |d}|                    ||d          }	t          j        d| dd|	 ddd          }
|
j        dk    r'|
                                                    d          S n4# t$          $ r'}t                              d|            Y d}~nd}~ww xY wdS )z@Try GitHub App JWT authentication if credentials are configured.GITHUB_APP_IDGITHUB_APP_PRIVATE_KEY_PATHGITHUB_APP_INSTALLATION_IDNr   z-PyJWT not installed, skipping GitHub App authutf-8encoding<   iX  )iatexpissRS256)	algorithmz)https://api.github.com/app/installations/z/access_tokenszBearer r   )r   r   
   rq   rj      r   zGitHub App auth failed: )r   r   ry   alljwtImportErrorru   r{   r	   exists	read_textintr   encoderx   postr|   json	Exception)r   app_idkey_pathinstallation_idr   key_fileprivate_keynowpayloadencoded_jwtr   r   s               r9   r   zGitHubAuth._try_github_app   s   00:>>"?@@*..)EFFFHo677 	4	JJJJ 	 	 	LLHIII44		9H~~H??$$ t",,g,>>Kdikk""CRxg G
 **WkW*MMK:[O[[[%<{%<%<>    D 3&&yy{{w/// ' 	9 	9 	9LL7A7788888888	9 ts1   3A8 8$B B $#E7 	B,E7 7
F(F##F(N)r/   r0   r1   r2   r   r   r3   r   boolr   r   r   r   r   r   r7   r8   r9   r   r      s         * * *
T#s(^    1$ 1 1 1 12S 2 2 2 2
    >Xc]    *# * * * * * *r8   r   c            	           e Zd ZdZeddededee         fd            Z	edede
e         fd            Zedede
e         fd	            Zedefd
            ZdedefdZdS )SkillSourcez.Abstract base for all skill registry adapters.r   querylimitrB   c                     dS )z*Search for skills matching a query string.Nr7   r   r   r   s      r9   searchzSkillSource.search)  	     	r8   r'   c                     dS )z&Download a skill bundle by identifier.Nr7   r   r'   s     r9   fetchzSkillSource.fetch.  r   r8   c                     dS )z9Fetch metadata for a skill without downloading all files.Nr7   r   s     r9   inspectzSkillSource.inspect3  r   r8   c                     dS )z=Unique identifier for this source (e.g. 'github', 'clawhub').Nr7   r   s    r9   	source_idzSkillSource.source_id8  r   r8   c                     dS )z3Determine trust level for a skill from this source.	communityr7   r   s     r9   trust_level_forzSkillSource.trust_level_for=  s    {r8   Nr   )r/   r0   r1   r2   r   r3   r   r   r#   r   r   r;   r   r   r   r   r7   r8   r9   r   r   &  s       88 C  T)_    ^  (=    ^ # (9*=    ^ 3    ^# #      r8   r   c            	          e Zd ZdZdddddddddddddd	dd
ddgZd0dedeee                  fdZ	de
fdZedefd            Zde
de
fdZd1de
dedee         fdZde
dee         fdZde
dee         fdZde
de
dee         fdZde
deee
ee         f                  fdZd2d!Zde
de
dee
e
f         fd"Zde
de
deee
e
f                  fd#Zde
de
dee
e
f         fd$Zde
d%e
dee
         fd&Zde
de
dee
         fd'Zd(e
dee          fd)Z!d(e
d*e ddfd+Z"e#d,edefd-            Z$e#d.e
defd/            Z%dS )3GitHubSourcez4Fetch skills from GitHub repos via the Contents API.zopenai/skillsskills/r)   r*   anthropics/skillszhuggingface/skillszVoltAgent/awesome-agent-skillszgarrytan/gstackrG   zMiniMax-AI/clizskill/Nr   
extra_tapsc                     || _         t          | j                  | _        |r| j                            |           i | _        d| _        d S NF)r   r5   DEFAULT_TAPStapsextend_tree_cache_rate_limited)r   r   r   s      r9   r   zGitHubSource.__init__R  sR    	*++	 	)IZ((( ?A#(r8   rB   c                     dS )Ngithubr7   r   s    r9   r   zGitHubSource.source_id]  s    xr8   c                     | j         S )z8Whether GitHub API rate limit was hit during operations.)r   r   s    r9   is_rate_limitedzGitHubSource.is_rate_limited`  s     !!r8   r'   c                     |                     dd          }t          |          dk    r|d          d|d          }|t          v rdS dS NrE      r   rP   trustedr   splitr[   r   r   r'   rU   r)   s       r9   r   zGitHubSource.trust_level_fore  sV      a((u::??Ah++q++D}$$ y{r8   r   r   r   c           	         g }|                                 }| j        D ]}	 |                     |d         |                    dd                    }|D ]Y}|j         d|j         dd                    |j                                                    }||v r|                    |           Z# t          $ r0}	t                              d|d          d|	            Y d}	~	d}	~	ww xY wdd	d
d}
i }|D ]c}|j        |vr|||j        <   |
                    |j        d
          |
                    ||j                 j        d
          k    r
|||j        <   dt          |                                          }|d|         S )z.Search all taps for skills matching the query.r)   r*   rG    zFailed to search rM   Nr   rP   r   builtinr   r   )lowerr   _list_skills_in_repory   r$   r%   r\   r-   appendr   ru   r{   r(   r5   values)r   r   r   resultsquery_lowertapr   skill
searchabler   _trust_rankseenrs                r9   r   zGitHubSource.searchn  s   #%kkmm9 		 		C223v;PR@S@STT# . .E$)J![![1B![![SXXejEYEY![![!a!a!c!cJ"j00u---.    CVCCCCDDD
 #$BB 	! 	!AvT!! QV22[__T!&\E]_`5a5aaa QVt{{}}%%vvs   BB..
C(8&C##C(c                 x   |                     dd          }t          |          dk     rdS |d          d|d          }|d         }|                     ||          }|rd|vrdS |                    d                               d          d         }|                     |          }t          ||d	||
          S )zi
        Download a skill from GitHub.
        identifier format: "owner/repo/path/to/skill-dir"
        rE   r      Nr   rP   SKILL.mdr   r$   r<   r&   r'   r(   )r  r[   _download_directoryrstripr   r;   )r   r'   rU   r)   
skill_pathr<   
skill_nametrusts           r9   r   zGitHubSource.fetch  s    
   a((u::>>4(''U1X''1X
((z:: 	
%//4&&s++11#66r:
$$Z00!
 
 
 	
r8   c                 f   |                     dd          }t          |          dk     rdS |d          d|d          }|d                             d          }| d}|                     ||          }|sdS |                     |          }|                    d|                     d          d	                   }|                    d
d          }	g }
|                    di           }t          |t                    rA|                    di           }t          |t                    r|                    dg           }
|
s/|                    dg           }t          |t                    r|ng }
t          |t          |	          d||                     |          ||d |
D                       S )z-Fetch just the SKILL.md metadata for preview.rE   r   r  Nr   rP   	/SKILL.mdr$   r  r%   rG   r=   hermesr-   r   c                 ,    g | ]}t          |          S r7   r3   rJ   ts     r9   rL   z(GitHubSource.inspect.<locals>.<listcomp>  s    '''Q#a&&'''r8   r$   r%   r&   r'   r(   r)   r*   r-   )r  r[   r  _fetch_file_content_parse_frontmatter_quickry   rQ   r6   r5   r#   r3   r   )r   r'   rU   r)   r  skill_md_pathcontentfmr  r%   r-   r=   hermes_metaraw_tagss                 r9   r   zGitHubSource.inspect  s     a((u::>>4(''U1X''1X__S))
%000**4?? 	4**733VVFJ$4$4S$9$9"$=>>
ff]B//66*b))h%% 	3",,x44K+t,, 3"vr22 	Bvvfb))H)(D99A88rDK((!,,Z88''$'''	
 	
 	
 		
r8   r)   r*   c                 L    | d|                      dd                               dd          }                     |          }|d |D             S d| d|                    d           }	 t          j        | j                                        dd	
          }|j        dk    rg S n# t          j        $ r g cY S w xY w|	                                }t          |t                    sg S g }|D ]}	|	                    d          dk    r|	d         }
|
                    d          r:|                    d          }|r
| d| d|
 n| d|
 }                     |          }|r|                    |                                | fd|D                        |S )zAList skill directories in a GitHub repo path, using cached index.r   rE   r  Nc                 &    g | ]}t          d i |S r7   r#   rJ   ss     r9   rL   z5GitHubSource._list_skills_in_repo.<locals>.<listcomp>  "    333qINNNN333r8   https://api.github.com/repos/
/contents/   Trq   rj   rp      typedirr$   rH   r   c                 :    g | ]}                     |          S r7   )_meta_to_dictrJ   r1  r   s     r9   rL   z5GitHubSource._list_skills_in_repo.<locals>.<listcomp>  s'    %L%L%Ld&8&8&;&;%L%L%Lr8   )rT   _read_cacher  rx   ry   r   r   r|   rz   r   rQ   r5   rV   r   r	  _write_cache)r   r)   r*   	cache_keycachedrk   r   entriesr   entrydir_nameprefixskill_identifiermetas   `             r9   r  z!GitHubSource._list_skills_in_repo  s	   $$d$$,,S#66>>sCHH	!!),,33F3333PdPPdkk#>N>NPP	9S$)*?*?*A*A2`deeeD3&&	 ' 	 	 	III	 ))++'4(( 	I"$ 	$ 	$Eyy  E))V}H"":.. [[%%F@F`$<<<<(<<<tL`L`V^L`L`<< 011D $d### 	)%L%L%L%LV%L%L%LMMMs   0;B- -C Cc                    || j         v r| j         |         S | j                                        }	 t          j        d| |dd          }|j        dk    r|                     |           dS |                                                    dd          }n# t          j        t          f$ r Y dS w xY w	 t          j        d| d	| d
di|dd          }|j        dk    r|                     |           dS |                                }|                    d          rt                              d|           dS n# t          j        t          f$ r Y dS w xY w|                    dg           }||f| j         |<   ||fS )u   Get cached or fresh repo tree.

        Returns ``(default_branch, tree_entries)`` or ``None``.
        A single install can call ``_download_directory_via_tree`` and
        ``_find_skill_in_repo_tree`` multiple times for the same repo — this
        cache eliminates the redundant ``GET /repos/{repo}`` +
        ``GET /repos/{repo}/git/trees/{branch}`` round-trips (previously up to
        6 duplicated pairs per install, consuming ~12 of the 60/hr
        unauthenticated rate limit for nothing).
        r3  r5  Tr6  r7  Ndefault_branchmainz/git/trees/	recursive1   )paramsrq   rj   rp   	truncatedz'Git tree truncated for %s, cannot cachetree)r   r   r   rx   ry   r|   _check_rate_limit_responser   rz   rR   ru   r{   )r   r)   rq   r   rI  	tree_datarB  s          r9   _get_repo_treezGitHubSource._get_repo_tree  s    4####D)))''))
	9666d  D 3&&//555t!YY[[__-=vFFNN, 	 	 	44		9QQQQQ#S)d  D
 3&&//555t		I}}[)) FMMMt , 	 	 	44	 --++"0'!:((s1   ;B .(B B10B15AD? 8AD? ?EEr   httpx.Responsec                     |j         dk    rD|j                            dd          }|dk    r%d| _        t                              d           dS dS dS )zLFlag the instance as rate-limited when GitHub returns 403 + exhausted quota.i  zX-RateLimit-RemainingrG   0TzGitHub API rate limit exhausted (unauthenticated: 60 req/hr). Set GITHUB_TOKEN or install the gh CLI to raise the limit to 5,000/hr.N)r|   rq   ry   r   ru   rv   )r   r   	remainings      r9   rQ  z'GitHubSource._check_rate_limit_response(  so    s""(()@"EEIC%)"]    	 #"r8   c                     |                      ||          }||S t                              d||           |                     ||          S )a^  Recursively download all text files from a GitHub directory.

        Uses the Git Trees API first (single call for the entire tree) to
        avoid per-directory rate limiting that causes silent subdirectory
        loss.  Falls back to the recursive Contents API when the tree
        endpoint is unavailable or the response is truncated.
        Nz<Tree API unavailable for %s/%s, falling back to Contents API)_download_directory_via_treeru   r{   _download_directory_recursive)r   r)   r*   r<   s       r9   r  z GitHubSource._download_directory3  sS     11$==LSUY[_```11$===r8   c                    |                     d          }|                     |          }|dS |\  }}| dt          fd|D                       }|si S i }|D ]}|                    d          dk    r|                    dd          }	|	                              sH|	t                    d         }
|                     ||	          }||||
<   }t                              d||	           |r|ndS )	a  Download an entire directory using the Git Trees API (single request).

        Returns:
            dict of files if the path exists and has content,
            empty dict ``{}`` if the tree is cached but the path doesn't exist
            (prevents unnecessary Contents API fallback),
            ``None`` if the tree couldn't be fetched (triggers Contents API fallback).
        rE   Nc              3   j   K   | ]-}|                     d d                                        V  .dS )r*   rG   N)ry   rV   )rJ   itemrE  s     r9   rO   z<GitHubSource._download_directory_via_tree.<locals>.<genexpr>S  sP       
 
8<DHHVR  ++F33
 
 
 
 
 
r8   r8  blobr*   rG   z"Skipped file (fetch failed): %s/%s)	r  rS  rX   ry   rV   r[   r%  ru   r{   )r   r)   r*   rA  _default_branchtree_entrieshas_entriesr<   r]  	item_pathr   r(  rE  s               @r9   rY  z)GitHubSource._download_directory_via_treeA  sQ    {{3$$T**>4(.%  
 
 
 
@L
 
 
 
 
  	 I !#  	T 	TDxx6)),,I''//  V.H..tY??G"")hA4SSSS'uu4'r8   c           	      f   d| d|                     d           }	 t          j        || j                                        dd          }|j        dk    r$t                              d|j        ||           i S n# t          j        $ r i cY S w xY w|	                                }t          |t                    si S i }|D ]}|                    d	d
          }|                    dd
          }	|	dk    r4|                     ||                    dd
                    }
|
|}|
||<   h|	dk    r|                     ||                    dd
                    }|s0t                              d||                    dd
                     |                                D ]\  }}||| d| <   |S )z1Recursively download via Contents API (fallback).r3  r4  rE   r5  Tr6  r7  z"Contents API returned %d for %s/%sr$   rG   r8  filer*   Nr9  z#Empty or failed subdirectory: %s/%s)r  rx   ry   r   r   r|   ru   r{   rz   r   rQ   r5   r%  rZ  items)r   r)   r*   rk   r   rB  r<   rC  r$   
entry_typer(  r   	sub_filessub_namesub_contents                  r9   rZ  z*GitHubSource._download_directory_recursivel  s   PdPPdkk#>N>NPP	9S$)*?*?*A*A2`deeeD3&&A4CSUY[_```	 '  	 	 	III	 ))++'4(( 	I " 	> 	>E99VR((D62..JV##224629N9NOO&#H&-E(Ou$$ >>tUYYvWYEZEZ[[	  eLL!FeiiX^`bNcNcddd-6__->-> > >)Hk2=ET..H..//s   AA< <BBr  c                 6   |                      |          }|dS |\  }}d| d}|D ]q}|                    d          dk    r|                    dd          }|                    |          s	|| dk    r!|dt          d                    }	| d|	 c S rdS )ag  Use the GitHub Trees API to find a skill directory anywhere in the repo.

        Returns the full identifier (``repo/path/to/skill``) or ``None``.
        This is a single API call regardless of repo depth, so it efficiently
        handles deeply nested directory structures like
        ``cli-tool/components/skills/development/<skill>/SKILL.md``.
        NrE   r  r8  r^  r*   rG   )rS  ry   endswithr[   )
r   r)   r  rA  r_  r`  skill_md_suffixrC  r*   	skill_dirs
             r9   _find_skill_in_repo_treez%GitHubSource._find_skill_in_repo_tree  s     $$T**>4(.% 4j333! 	- 	-Eyy  F**99VR((D}}_-- -J9Q9Q9Q1Q1Q !4C$4$4#4!45	,,,,,,, 2R
 tr8   c                 @   d| d| }	 t          j        |i | j                                        ddidd          }|j        dk    r|j        S |                     |           n7# t           j        $ r%}t          	                    d	|           Y d
}~nd
}~ww xY wd
S )z*Fetch a single file's content from GitHub.r3  r4  r   application/vnd.github.v3.rawr5  Tr6  r7  z$GitHub contents API fetch failed: %sN)
rx   ry   r   r   r|   r   rQ  rz   ru   r{   )r   r)   r*   rk   r   r   s         r9   r%  z GitHubSource._fetch_file_content  s    DdDDdDD
	D9^490022^H>]^^T  D
 3&&y ++D1111 	D 	D 	DLL?CCCCCCCC	Dts   AA' A' 'B6BBkeyc                 D   t           | dz  }|                                sdS 	 |                                }t          j                    |j        z
  t
          k    rdS t          j        |                                          S # t          t          j
        f$ r Y dS w xY w)z!Read cached index if not expired..jsonNINDEX_CACHE_DIRr   statr   st_mtimeINDEX_CACHE_TTLr   loadsr   OSErrorJSONDecodeError)r   rq  
cache_filerv  s       r9   r>  zGitHubSource._read_cache  s    $#}}}4
  "" 	4	??$$Dy{{T]*_<<t:j2244555-. 	 	 	44	   8B %B BBdatac                    t                               dd           t           | dz  }	 |                    t          j        |d                     dS # t
          $ r&}t                              d|           Y d}~dS d}~ww xY w)zWrite index data to cache.Tparentsexist_okrs  F)ensure_asciiCould not write cache: %sN)ru  mkdir
write_textr   dumpsrz  ru   r{   )r   rq  r~  r|  r   s        r9   r?  zGitHubSource._write_cache  s    dT:::$#}}}4
	9!!$*T"F"F"FGGGGG 	9 	9 	9LL4a888888888	9s   )A 
B BBrG  c           	      h    | j         | j        | j        | j        | j        | j        | j        | j        dS )Nr$  r$  rG  s    r9   r<  zGitHubSource._meta_to_dict  s=     I+k/+III	
 	
 		
r8   r(  c                 :   |                      d          si S t          j        d| dd                   }|si S | d|                                dz            }	 t	          j        |          }t          |t                    r|ni S # t          j        $ r i cY S w xY wz-Parse YAML frontmatter from SKILL.md content.---z
\n---\s*\nr  N	rV   rY   r   startyaml	safe_loadrQ   r6   	YAMLErrorr(  match	yaml_textparseds       r9   r&  z%GitHubSource._parse_frontmatter_quick       !!%(( 	I	-55 	IAekkmma//0		^I..F'55=662=~ 	 	 	III	   ,B BBr   r   )r   rT  rB   N)&r/   r0   r1   r2   r   r   r   r   r   r   r3   r   propertyr   r   r   r   r#   r   r;   r   r   r  r   r6   rS  rQ  r  rY  rZ  rn  r%  r5   r>  r?  staticmethodr<  r&  r7   r8   r9   r   r   F  s       >> !)44$i88%y9919EE"B//!844L	) 	)Z 	)Xd4j5I 	) 	) 	) 	)3     " " " " X"# #     C  T)_    8
 
(= 
 
 
 
6%
# %
(9*= %
 %
 %
 %
R$ $C $DO $ $ $ $P0)3 0)8E#tDz/4J+K 0) 0) 0) 0)d	 	 	 	> >3 >4S> > > > >)( )(C )(HTRUWZRZ^D\ )( )( )( )(V #  S  T#s(^        DS c hsm    4 3 8C=     s x~    9 94 9D 9 9 9 9 

I 

$ 

 

 

 \

 # $    \  r8   r   c                   P   e Zd ZdZdZdefdZdedefdZdded	ede	e
         fd
Zdedee
         fdZdedee         fdZdedee         fdZdedee         fdZdedee         fdZdededee         fdZededee         fd            Zedededefd            ZdS )WellKnownSkillSourcezBRead skills from a domain exposing /.well-known/skills/index.json.z/.well-known/skillsrB   c                     dS )N
well-knownr7   r   s    r9   r   zWellKnownSkillSource.source_id  s    |r8   r'   c                     dS Nr   r7   r   s     r9   r   z$WellKnownSkillSource.trust_level_for      {r8   r   r   r   c                 8   |                      |          }|sg S |                     |          }|sg S g }|d         d |         D ]}|                    d          }t          |t                    r|s/|                    dd          }|                    ddg          }	|                    t          |t	          |          d|                     |d         |          d	||d
         |d         t          |	t                    r|	ndgd                     |S )Nr   r$   r%   rG   r<   r  r  base_urlr   	index_url)r  r  r<   r$   r%   r&   r'   r(   r*   r.   )	_query_to_index_url_parse_indexry   rQ   r3   r	  r#   _wrap_identifierr5   )
r   r   r   r  r  r  rC  r$   r%   r<   s
             r9   r   zWellKnownSkillSource.search  sM   ,,U33	 	I""9-- 	I#%H%fuf- 	 	E99V$$DdC((  ))M266KIIg
|44ENN9,,#00
1CTJJ'!'!4 &z 2&0&=&=OUUJ<        r8   c                    |                      |          }|sd S |                     |d         |d                   }|sd S |                     |d          d          }|d S t                              |          }t          |                    d          p|                    d          pd          }t          |                    d          p|d                   }t          ||d|                     |d	         |d                   d
|d         |d         |d	         |                    ddg          |d         d          S )Nr  r  	skill_urlr  r%   rG   r$   r  r  r   r<   r  )r  r  r<   endpointr  )	_parse_identifier_index_entry_fetch_textr   r&  r3   ry   r#   r  )r   r'   r  rC  skill_mdr)  r%   r$   s           r9   r   zWellKnownSkillSource.inspect  sZ   ''
33 	4!!&"5vl7KLL 	4##vk':$E$E$EFF4228<<"&&//Q599]3K3KQrRR266&>>9VL%9::#,,VJ-?AUVV#%#K0":.7ZL99";/	 
 
 
 	
r8   c                    |                      |          }|sd S 	 t          |d                   }n,# t          $ r t                              d|           Y d S w xY w|                     |d         |d                   }|sd S |                    ddg          }t          |t                    r|sdg}i }|D ]}t          |t                    r|s	 t          |          }n.# t          $ r! t                              d||           Y  d S w xY w|                     |d          d|           }	|	 d S |	||<   d|vrd S t          ||d	|                     |d
         |          d|d         |d
         |d         |d          S )Nr  z;Well-known skill identifier contained unsafe skill name: %sr  r<   r  z3Well-known skill %s advertised unsafe file path: %rr  rE   r  r  r   )r  r  r  r<   r$   r<   r&   r'   r(   r=   )r  rd   rR   ru   rv   r  ry   rQ   r5   r3   r   r  r;   r  )
r   r'   r  r  rC  r<   
downloadedr   safe_rel_pathr   s
             r9   r   zWellKnownSkillSource.fetch8  s   ''
33 	4	-f\.BCCJJ 	 	 	NNXZdeee44	 !!&"5vl7KLL 	4		'J<00%&& 	!e 	!LE%'
 	- 	-Hh,, H  9( C C   I  
 ttt ##vk':$L$L]$L$LMMD|tt(,J}%%Z''4,,VJ-?LL##K0":.";/	 
 
 
 	
s!   1 %AAC##&DDc                 @   |                                 }|                    d          sd S |                    d          r|S | j         d|v r1|                    | j         dd          d         | j        z   }| dS |                    d          | j         dz   S )Nzhttp://zhttps:///index.jsonrE   rP   r   )rS   rV   rk  	BASE_PATHr  r  )r   r   r  s      r9   r  z(WellKnownSkillSource._query_to_index_urln  s     788 	4>>-(( 	Ln5(({{dn#7#7#7;;A>OH++++||C  dn#A#A#AAAr8   c                 h   |                     d          r|t          d          d          n|}|                     d          sd S t          |          }t          |                    d                    }|j        }|                    d          r,|sd S |d t          d                    }|}| d| }||||dS |                    d          r|d t          d                    }n|                    d          }| j         d|vrd S |	                    dd	          \  }}| d|||dS )
Nwell-known:r  rG   )fragmentr  rE   )r  r  r  r  r  rP   )
rV   r[   r   r   _replacer  rk  r  r  rsplit)	r   r'   r]   
parsed_url	clean_urlr  r  r  r  s	            r9   r  z&WellKnownSkillSource._parse_identifiery  s   1;1F1F}1U1Uej]++,,--[e~~566 	4c]]
z22B2??@@	&m,, 	 t !53}#5#5"5!56H!J#22j22I&$(&	   k** 	.!"4C$4$4#4"45II!((--Iny004(//Q77*$111 $"	
 
 	
r8   r  c                    dt          j        |                                                                           }t	          |          }t          |t                    r*t          |                    d          t                    r|S t          |d          }||j
        dk    rd S 	 |                                }n# t          j        $ r Y d S w xY wt          |t                    r|                    dg           ng }t          |t                    sd S ||d t          d                    |d}t          ||           |S )Nwell_known_index_r   rh   ri   r7  r  )r  r  r   )hashlibmd5r   	hexdigest_read_index_cacherQ   r6   ry   r5   r   r|   r   r{  r[   _write_index_cache)r   r  r@  rA  r   r~  r   r  s           r9   r  z!WellKnownSkillSource._parse_index  sX   UI4D4D4F4F(G(G(Q(Q(S(SUU	"9--fd## 	
6::h3G3G(N(N 	M B777<4+s224	99;;DD# 	 	 	44	 ,6dD+A+AI(B'''r&$'' 	4 #!"6C$6$6#6"67
 

 	9f---s   +C   CCr  c                     |                      |          }|sd S |d         D ]4}t          |t                    r|                    d          |k    r|c S 5d S )Nr   r$   )r  rQ   r6   ry   )r   r  r  r  rC  s        r9   r  z!WellKnownSkillSource._index_entry  sm    ""9-- 	4H% 	 	E%&& 599V+<+<
+J+Jtr8   rk   c                 P    t          | d          }||j        dk    r|j        S d S Nrh   ri   r7  r   r|   r   rk   r   s     r9   r  z WellKnownSkillSource._fetch_text  4     b111 0C 7 79tr8   r  c                 8    d|                      d           d| S )Nr  rE   )r  )r  r  s     r9   r  z%WellKnownSkillSource._wrap_identifier  s$    @X__S11@@J@@@r8   Nr   )r/   r0   r1   r2   r  r3   r   r   r   r   r#   r   r   r   r;   r   r  r6   r  r  r  r  r  r  r7   r8   r9   r  r    s       LL%I3    # #     C  T)_    >
# 
(9*= 
 
 
 
>4
 4
(= 4
 4
 4
 4
l	B 	B# 	B 	B 	B 	B$
C $
HTN $
 $
 $
 $
Lc htn    4c s x~      #    \ A3 AC AC A A A \A A Ar8   r  c                   @   e Zd ZdZdefdZdedefdZddededee	         fd	Z
dedefd
Zdedee	         fdZdedee         fdZededee         fd            Z ej        d          Zedee         defd            Zedededee         fd            ZdS )	UrlSourceu  Fetch a single-file SKILL.md skill directly from an HTTP(S) URL.

    The identifier IS the URL (e.g. ``https://example.com/path/SKILL.md``).
    Only single-file skills are supported — multi-file skills with
    ``references/`` or ``scripts/`` subfolders need a manifest we can't
    discover from a bare URL.

    The skill name is read from the ``name:`` field in the SKILL.md YAML
    frontmatter (with a URL-slug fallback). Trust level is always
    ``community`` and the same security scan runs as for every other source.
    rB   c                     dS )Nrk   r7   r   s    r9   r   zUrlSource.source_id  s    ur8   r'   c                     dS r  r7   r   s     r9   r   zUrlSource.trust_level_for  r  r8   r   r   r   c                     g S r   r7   r   s      r9   r   zUrlSource.search  s    	r8   c                    t          |t                    sdS |                                }|                                                    d          sdS d|v s(|                    d                              d          rdS 	 t          |          j        }n# t          $ r Y dS w xY w|                                                    d          S )a3  Return True iff this source should handle ``identifier``.

        We claim bare HTTP(S) URLs that end in ``.md`` (typically
        ``.../SKILL.md``). Wrapped identifiers (``github:``,
        ``well-known:``, etc.) and ``/.well-known/skills/`` URLs are
        left for their respective adapters.
        Fr  z/.well-known/skills/rE   r  z.md)
rQ   r3   rS   r  rV   r  rk  r   r*   rR   )r   r'   identr*   s       r9   _matcheszUrlSource._matches  s     *c** 	5  ""{{}}''(?@@ 	5!U**ell3.?.?.H.H.W.W*5	E??'DD 	 	 	55	zz||$$U+++s   B 
B'&B'c                    |                      |          sd S |                                }|                     |          }|d S t                              |          }|                     ||          }t          |                    d          pd          }g }|                    di           }t          |t                    rb|                    di           }	t          |	t                    r7|	                    dg           }
t          |
t                    rd |
D             }t          |pd|d|d|pd|||d u d	
          S )Nr%   rG   r=   r  r-   c                 ,    g | ]}t          |          S r7   r!  r"  s     r9   rL   z%UrlSource.inspect.<locals>.<listcomp>  s    555qCFF555r8   rk   r   rk   awaiting_name)r$   r%   r&   r'   r(   r*   r-   r.   )r  rS   r  r   r&  _resolve_skill_namer3   ry   rQ   r6   r5   r#   )r   r'   rk   r   r)  r$   r%   r-   r=   r*  r+  s              r9   r   zUrlSource.inspect   sY   }}Z(( 	4  $$<422488''C00"&&//526666*b))h%% 	6",,x44K+t,, 6&??6266h-- 655H555D##==	
 	
 	
 		
r8   c           
         |                      |          sd S |                                }|                     |          }|d S t                              |          }|                     ||          }d}|>	 t          |          }n-# t          $ r  t          	                    d||           Y d S w xY wt          |d|id|d|| d          S )NrG   z+URL skill %s produced unsafe skill name: %rr  rk   r   r  r  )r  rS   r  r   r&  r  rd   rR   ru   rv   r;   )r   r'   rk   r   r)  r$   r  s          r9   r   zUrlSource.fetch  s   }}Z(( 	4  $$<422488''C00 
1$77

   LcSWXXXtt t$# z>BB
 
 
 	
s   :B
 
&B43B4rk   c                 P    t          | d          }||j        dk    r|j        S d S r  r  r  s     r9   r  zUrlSource._fetch_text>  r  r8   z^[a-z][a-z0-9_-]*$r$   c                     t          |t                    sdS |                                                                }|r|dv rdS t	          | j                            |                    S )NF>   unnamed-skillindexr  readme)rQ   r3   rS   r  r   _VALID_NAME_REr  )clsr$   	candidates      r9   _is_valid_skill_namezUrlSource._is_valid_skill_nameJ  si    $$$ 	5JJLL&&((	 	I)VVV5C&,,Y77888r8   r)  c                    t          |t                    r|                    d          nd}t          |t                    r)|                     |          r|                                S 	 t          |          j        }n# t          $ r Y dS w xY wd |	                    d          D             }|rP|d         
                                dk    r2t          |          dk    r|d         }|                     |          r|S |r?t          j        d	d
|d         t          j                  }|                     |          r|S dS )a$  Pick a skill name from frontmatter or URL.

        Returns ``None`` when neither source produces a valid identifier;
        callers (CLI ``do_install``) then prompt the user or refuse. Preferring
        a clean failure over a useless auto-name like ``SKILL`` or ``unnamed-skill``.
        r$   Nc                     g | ]}||S r7   r7   )rJ   ps     r9   rL   z1UrlSource._resolve_skill_name.<locals>.<listcomp>f  s    111qq1111r8   rE   r  zskill.mdr   z\.md$rG   )flags)rQ   r6   ry   r3   r  rS   r   r*   rR   r  r  r[   rY   sub
IGNORECASE)r  r)  rk   fm_namer*   rU   r  s          r9   r  zUrlSource._resolve_skill_nameS  sK    %/r4$8$8B"&&...dgs## 	#(@(@(I(I 	#==??"	C==%DD 	 	 	44	11DJJsOO111 	!U2Y__&&*44Uqb	I''	22 !   	!xU2YbmLLLI''	22 !   ts   ,B 
BBNr   )r/   r0   r1   r2   r3   r   r   r   r   r#   r   r   r  r   r   r;   r   r  r  rY   compiler  classmethodr  r6   r  r7   r8   r9   r  r    s       
 
3    # #     C  T)_    ,3 ,4 , , , ,.
# 
(9*= 
 
 
 
:
 
(= 
 
 
 
B  #    \  RZ 566N9 9$ 9 9 9 [9 T      [  r8   r  c            	          e Zd ZdZdZe dZ ej        d          Z ej        dej	                  Z
 ej        dej	        ej        z            Z ej        dej	        ej        z            Z ej        dej	        ej        z            Z ej        d	ej                  Zd
efdZdefdZdedefdZd3dededee         fdZdedee         fdZdedee         fdZdedee         fdZdedee         fdZdedee         fdZ dededee         fdZ!d4dedee         dee         fdZ"d4dedee         dee         fdZ#d ed!edee         defd"Z$e%d ed#ee         de&fd$            Z'e(d%ee         de)e         fd&            Z*e(d'edee         fd(            Z+e(d)ej,        d*edee         fd+            Z-d!edee         de.ee/f         fd,Z0e(dedee         fd-            Z1e(dedede.eef         fd.            Z2e(d%edefd/            Z3e(dedefd0            Z4e(dedee         fd1            Z5e(dedefd2            Z6dS )5SkillsShSourcezPDiscover skills via skills.sh and fetch content from the underlying GitHub repo.zhttps://skills.shz/api/searchzIhref=["\']/(?P<id>(?!agents/|_next/|api/)[^"\'/]+/[^"\'/]+/[^"\'/]+)["\']zgnpx\s+skills\s+add\s+(?P<repo>https?://github\.com/[^\s<]+|[^\s<]+)(?:\s+--skill\s+(?P<skill>[^\s<]+))?z<h1[^>]*>(?P<title>.*?)</h1>zQ<div[^>]*class=["\'][^"\']*prose[^"\']*["\'][^>]*>.*?<h1[^>]*>(?P<title>.*?)</h1>zN<div[^>]*class=["\'][^"\']*prose[^"\']*["\'][^>]*>.*?<p[^>]*>(?P<body>.*?)</p>z9Weekly Installs.*?children\\":\\"(?P<count>[0-9.,Kk]+)\\"r   c                 >    || _         t          |          | _        d S Nr   )r   r   r   r   r   s     r9   r   zSkillsShSource.__init__  s    	"---r8   rB   c                     dS )N	skills-shr7   r   s    r9   r   zSkillsShSource.source_id  r  r8   r'   c                 \    | j                             |                     |                    S r   )r   r   _normalize_identifierr   s     r9   r   zSkillsShSource.trust_level_for  s&    {**4+E+Ej+Q+QRRRr8   r   r   r   c                    |                                 s|                     |          S dt          j        | d|                                                                            }t          |          }|d |D             d |         S 	 t          j        | j	        ||dd          }|j
        dk    rg S |                                }n## t          j        t          j        f$ r g cY S w xY wt          |t                    r|                    dg           ng }t          |t                     sg S g }|d |         D ].}	|                     |	          }
|
r|                    |
           /t'          |d	 |D                        |S )
Nskills_sh_search_|c                 &    g | ]}t          d i |S r.  r/  rJ   r]  s     r9   rL   z)SkillsShSource.search.<locals>.<listcomp>  &    999$I%%%%999r8   )qr   rh   rN  rj   r7  r   c                 ,    g | ]}t          |          S r7   _skill_meta_to_dictr  s     r9   rL   z)SkillsShSource.search.<locals>.<listcomp>  !    &U&U&UT':4'@'@&U&U&Ur8   )rS   _featured_skillsr  r  r   r  r  rx   ry   
SEARCH_URLr|   r   rz   r{  rQ   r6   r5   _meta_from_search_itemr	  r  )r   r   r   r@  rA  r   r~  re  r  r]  rG  s              r9   r   zSkillsShSource.search  s   {{}} 	0((///^u4F4Fu4F4F4M4M4O4O(P(P(Z(Z(\(\^^	"9--99&999&5&AA
	9"U33  D
 3&&	99;;DD!56 	 	 	III	 +5T4*@*@H2&&&b%&& 	I#%&5&M 	% 	%D..t44D %t$$$9&U&UW&U&U&UVVVs   +C <C C10C1c                 t   |                      |          }|                     |          }|                     |          D ]q}| j                            |          }|rSd|_        |                     |          |_        |j        	                    | 
                    ||                     |c S r|                     ||          }|rm| j                            |          }|rQd|_        |                     |          |_        |j        	                    | 
                    ||                     |S d S )N	skills.shdetail)r  _fetch_detail_page_candidate_identifiersr   r   r&   r  r'   r=   update_detail_to_metadata_discover_identifier)r   r'   	canonicalr	  r  bundleresolveds          r9   r   zSkillsShSource.fetch  s?   ..z::	((3344Y?? 	 	I[&&y11F  +$($9$9)$D$D!&&t'?'?	6'R'RSSS	 ,,Yv,FF 	[&&x00F  +$($9$9)$D$D!&&t'?'?	6'R'RSSStr8   c                     |                      |          }|                     |          }|                     ||          }|r|                     |||          S d S Nr  )r  r
  _resolve_github_meta_finalize_inspect_meta)r   r'   r  r	  rG  s        r9   r   zSkillsShSource.inspect  sf    ..z::	((33((6(BB 	H..tYGGGtr8   c                 `   d}t          |          }|d |D             d |         S 	 t          j        | j        d          }|j        dk    rg S n# t          j        $ r g cY S w xY wt                      }g }| j                            |j	                  D ]}|
                    d          }||v r|                    |           |                    dd          }	t          |	          d	k     r[|	d
          d|	d          }
|	d         }|                    t          |                    d          d         d|
 d|                     |          | j                            |          |
|                     t          |          |k    r nt'          |d |D                        |S )Nskills_sh_featuredc                 &    g | ]}t          d i |S r.  r/  r  s     r9   rL   z3SkillsShSource._featured_skills.<locals>.<listcomp>  r  r8   rh   ri   r7  idrE   r   r  r   rP   r  zFeatured on skills.sh from r  )r$   r%   r&   r'   r(   r)   r*   c                 ,    g | ]}t          |          S r7   r   r  s     r9   rL   z3SkillsShSource._featured_skills.<locals>.<listcomp>  r  r8   )r  rx   ry   BASE_URLr|   rz   set_SKILL_LINK_REfinditerr   groupaddr  r[   r	  r#   r  r   r   r  )r   r   r@  rA  r   r  r  r  r  rU   r)   r  s               r9   r  zSkillsShSource._featured_skills  s   (	"9--99&999&5&AA	9T]B777D3&&	 ' 	 	 	III	 #%(11$)<< 	 	ED))ID  HHYOOC++E5zzA~~Ah++q++DqJNN9%%c**2.@$@@"00;; K77	BB      7||u$$ % 	9&U&UW&U&U&UVVVs   'A A&%A&r]  c                    t          |t                    sd S |                    d          }|                    d          }|                    d          }t          |t                    r|                    d          dk     r3t          |t                    rt          |t                    sd S | d| }|                    dd          }t          |          dk     rd S |d          d|d          }|d         }|                    d	          }t          |t                    rd
t          |          ddnd}t          t          |                    d          p|                    d          d                   d| | d| 	                    |          | j
                            |          |||| j         d| d| d          S )Nr  r&   skillIdrE   r   r  r   rP   installs    · ,z	 installsrG   r$   r  zIndexed by skills.sh from r  https://github.com/)r#  
detail_urlrepo_url)r$   r%   r&   r'   r(   r)   r*   r.   )rQ   r6   ry   r3   countr  r[   r   r#   r  r   r   r  )r   r]  r  r)   r  rU   r#  installs_labels           r9   r  z%SkillsShSource._meta_from_search_item  s   $%% 	4HHTNN	xx!!XXi((
)S)) 	/Y__S-A-AA-E-EtS)) jS.I.I t..*..IQ''u::>>4(''U1X''1X
88J''>HSV>W>W_:H:::::]_TXXf%%B)9)9#)>)>r)BCCKTK>KK,,Y7733I>>$!%<<<<8$88 
 
 
 	
r8   c                    dt          j        |                                                                           }t	          |          }t          |t                    r|S 	 t          j        | j	         d| d          }|j
        dk    rd S n# t          j        $ r Y d S w xY w|                     ||j                  }|rt          ||           |S )Nskills_sh_detail_rE   rh   ri   r7  )r  r  r   r  r  rQ   r6   rx   ry   r  r|   rz   _parse_detail_pager   r  )r   r'   r@  rA  r   r	  s         r9   r
  z!SkillsShSource._fetch_detail_page#  s    VJ4E4E4G4G(H(H(R(R(T(TVV	"9--fd## 	M	9<<
<<bIIID3&&t ' 	 	 	44	 ((TY?? 	2y&111s   #+B B$#B$htmlc                    |                     dd          }t          |          dk     rd S |d          d|d          }|d         }|}|}d }| j                            |          }	|	r|	                    d                                          }|	                    d          pd                                }
|	                    d          p|                                }|                     |
          p|}|                     | j        |          }|                     | j	        |          }|                     | j
        |          }|                     |          }|                     ||          }|||||||d	| | j         d| |d

S )NrE   r   r  r   rP   r)   rG   r  r&  )
r)   install_skill
page_title
body_titlebody_summaryweekly_installsinstall_commandr(  r'  security_audits)r  r[   _INSTALL_CMD_REr   r  rS   _extract_repo_slug_extract_first_match_PAGE_H1_RE_PROSE_H1_RE_PROSE_P_RE_extract_weekly_installs_extract_security_auditsr  )r   r'   r.  rU   default_reposkill_tokenr)   r0  r5  install_match
repo_valuer1  r2  r3  r4  r6  s                   r9   r-  z!SkillsShSource._parse_detail_page5  s     a((u::>>4(//U1X//Ah#,33D99 	?+11!44::<<O'--f55;BBDDJ*0099J]QQSSM**:66>$D..t/?FF
..t/@$GG
001A4HH77==77jII *$$(..4d44!]99Z99.
 
 	
r8   Nr	  c                 z   |                     dd          }t          |          dk     rd S |d          d|d          }t          |t                    r|                    d|          n|}|d                              d          d         }|g}t          |t                    rT|                    |                    dd	          |                    d
d	          |                    dd	          g           g d}|D ]U}		 | j                            ||	          }
n# t          $ r Y +w xY w|
D ]#}| 	                    ||          r|j
        c c S $V| j                            ||          }|r|S 	 d| d}t          j        || j        j                                        dd          }|j        dk    r|                                }t          |t"                    r|D ]}|                    d          dk    r|d         }|                    d          r:|dv r?| d| d| }| j                            |          }|r	|j
        c S 	 | j                            ||dz             }
n# t          $ r Y w xY w|
D ]#}| 	                    ||          r|j
        c c S $n# t          $ r Y nw xY wd S )NrE   r   r  r   rP   r)   r  r0  rG   r1  r2  )r   z.agents/skills/z.claude/skills/r3  r4  r5  Tr6  r7  r8  r9  r$   r:  >   .agents.clauder   )r  r[   rQ   r6   ry   r   r   r  r   _matches_skill_tokensr'   rn  rx   r   r   r|   r   r5   rV   r   )r   r'   r	  rU   r?  r)   r@  tokens
base_paths	base_pathr   rG  tree_resultroot_urlr   rB  rC  rD  	direct_ids                      r9   r  z#SkillsShSource._discover_identifierZ  s}     a((u::>>4(//U1X//3=fd3K3K]vzz&,///Q]!HNN3''+}fd## 	MM

?B//

<,,

<,,    GFF
# 	+ 	+I99$	JJ    + +--dF;; +?*****++ k::4MM 		GtGGGH9Xt{/?/K/K/M/M%'$@ @ @D3&&))++gt,, 7!( 7 7 99V,,55$#(=#..z:: %$#'GGG$'+$F$Fh$F$F$F$F	#{229== 3#'?222%%)[%E%EdHWZN%[%[FF( % % %$H%$* 7 7D#99$GG 7'+ 6 6 6 6 677  	 	 	D	 tsO   D
D+*D+5CJ+ I32J+ 3
J =J+ ?J  'J+ (J+ +
J87J8c                     |                      |          D ]"}| j                            |          }|r|c S #|                     ||          }|r| j                            |          S d S r  )r  r   r   r  )r   r'   r	  r  rG  r  s         r9   r  z#SkillsShSource._resolve_github_meta  s    44Z@@ 	 	I;&&y11D  ,,Z,GG 	1;&&x000tr8   rG  r  c                    d|_         |                     |          |_        |                     |          |_        t          |j                  }|                    |                     ||                     ||_        t          |t
                    rO|
                    d          }|
                    d          }|r||_        n|j        r|r|j         d| d|_        |S )Nr  r3  r4  r$  z weekly installs on skills.sh)r&   r  r'   r   r(   r6   r.   r  r  rQ   ry   r%   )r   rG  r  r	  merged_extrar3  r4  s          r9   r  z%SkillsShSource._finalize_inspect_meta  s    !//	:://	::DJ''D44YGGHHH!
fd## 	k!::n55L$jj):;;O k#/  ! ko k&*&6#j#jO#j#j#j r8   skill_tokensc                    t                      }|                    |                     |j                             |                    |                     |j                             |                    |                     |j        r!|j                            dd          d         nd                      |D ]}|                     |          }||z  r dS  dS )NrE   r   r  TF)r  r  _token_variantsr$   r*   r'   r  )r  rG  rP  
candidatesr   variantss         r9   rF  z$SkillsShSource._matches_skill_tokens  s    UU
#--di88999#--di88999#--SWSb.ldo.C.CC.K.KB.O.Ohlmmnnn! 	 	E**511H*$ ttur8   valuec                    | st                      S t                              t          |                                                                         d                                          }|st                      S |                    d          d         }t          j        dd|                              d          }|r|                    d          d         nd}|                    d          d         }|	                    d          }|                    d          d         }||
                    dd          |
                    dd          ||
                    dd          |
                    dd          ||r|
                    dd          nd|||
                    dd          h}d |D             S )	NrE   r  z[^a-z0-9/_-]+-rG   @r   c                     h | ]}||S r7   r7   )rJ   vs     r9   	<setcomp>z1SkillsShSource._token_variants.<locals>.<setcomp>  s    )))aq))))r8   )r  r  _strip_htmlr3   rS   r  r  rY   r  lstriprT   )rU  plainbase	sanitizedsanitized_base
slash_tailslash_tail_cleanrT  s           r9   rR  zSkillsShSource._token_variants  s    	55L**3u::66<<>>DDSIIOOQQ 	55L{{3#F+S%88>>sCC	5>F--b11B[[%%b)
%,,S11+11#66r: MM#s##MM#s##LLc""LLc""+4<Ic3'''"$$S#..
 *)8))))r8   rB  c                 &   |                                  } |                     d          r| t          d          d          } |                      d          } |                     d          }t          |          dk    r|d          d|d          S d S )Nr&  rE   r   r   rP   )rS   rV   r[   r  )rB  rU   s     r9   r8  z!SkillsShSource._extract_repo_slug  s    %%''
  !677 	A#C(=$>$>$?$?@J%%c**
  %%u::??Ah++q+++tr8   patternr   c                     |                      |          }|sd S t          d |                                D             d           }|d S t                              |                                          pd S )Nc              3      K   | ]}||V  	d S r   r7   )rJ   r  s     r9   rO   z6SkillsShSource._extract_first_match.<locals>.<genexpr>  s'      AA5AeAAAAAAr8   )r   nextgroupsr  r\  rS   )re  r   r  rU  s       r9   r9  z#SkillsShSource._extract_first_match  su    t$$ 	4AAAAA4HH=4))%006688@D@r8   c                    |                     dd          }t          |          dk    r|d          d|d          nd}d| j         d| i}|rd| |d<   t          |t                    r!d	D ]}|                    |          }|r|||<   |S )
NrE   r   r   rP   rG   r'  r&  r(  )r4  r5  r(  r'  r6  )r  r[   r  rQ   r6   ry   )r   r  r	  rU   r)   r=   rq  rU  s           r9   r  z"SkillsShSource._detail_to_metadata  s    Q''+.u::??%(''U1X'''T]88Y88
  	@#?#?#?HZ fd## 	*j * *

3 *$)HSMr8   c                 r    t           j                            |           }|sd S |                    d          S )Nr)  )r  _WEEKLY_INSTALLS_REr   r  )r.  r  s     r9   r=  z'SkillsShSource._extract_weekly_installs  s7    299$?? 	4{{7###r8   c                    i }dD ]z}|                      d|           }|dk    r!| ||dz            }t          j        d|t          j                  }|r*|                    d                                          ||<   {|S )N)zagent-trust-hubsocketsnykz
/security/r    z(Pass|Warn|Fail)rP   )findrY   r   r  r  title)r.  r'   auditsauditidxwindowr  s          r9   r>  z'SkillsShSource._extract_security_audits  s    !#: 	7 	7E))00011Cbyy#cCi-(FI162=IIE 7 %A 4 4 6 6ur8   c                 .    t          j        dd|           S )Nz<[^>]+>rG   )rY   r  )rU  s    r9   r\  zSkillsShSource._strip_html  s    vj"e,,,r8   c                 p    d}|D ]0}|                      |          r| t          |          d          c S 1| S )N)
skills-sh/
skills.sh/z	skils-sh/z	skils.sh/)rV   r[   )r'   prefix_aliasesrE  s      r9   r  z$SkillsShSource._normalize_identifier#  sV    
 % 	0 	0F$$V,, 0!#f++,,////0r8   c                 t   |                      dd          }t          |          dk     r| gS |d          d|d          }|d                             d          }| d| | d| | d| | d| g}t                      }g }|D ]0}||vr*|                    |           |                    |           1|S )	NrE   r   r  r   rP   /skills/z/.agents/skills/z/.claude/skills/)r  r[   r]  r  r   r	  )r'   rU   r)   r  rS  r  dedupedr  s           r9   r  z%SkillsShSource._candidate_identifiers0  s      a((u::>><(''U1X''1X__S))
""j""))Z))11Z1111Z11	

 uu# 	* 	*I$$###y)))r8   c                     d|  S )Nry  r7   )r'   s    r9   r  zSkillsShSource._wrap_identifierG  s    (J(((r8   r   r   )7r/   r0   r1   r2   r  r  rY   r  r  r  r7  DOTALLr:  r;  r<  rl  r   r   r3   r   r   r   r   r#   r   r   r;   r   r   r  r6   r  r
  r-  r  r  r  r  r   rF  r  r  rR  r8  Patternr9  r   r   r  r=  r>  r\  r  r  r  r7   r8   r9   r  r  x  sV       ZZ"H)))JRZ lmmN bj	0
 O
 "*<bmbi>WXXK2:\
	! L "*Y
	! K %"*%aceclmm.Z . . . .3    S# S# S S S S   C    T)_        D (=    *# (9*=    &c &d9o & & & &P"
4 "
HY4G "
 "
 "
 "
HS Xd^    $#
S #
 #
 #
 #
 #
 #
JC Cs CHTN CV^_bVc C C C CJ	 	s 	HTN 	V^_hVi 	 	 	 	9  hW[n aj    " 
 
$s) 
PT 
 
 
 [
 *x} *S * * * \*< s x}    \ Abj A A A A A \AS (4. TRUWZRZ^     $s $x} $ $ $ \$ 
s 
 
S#X 
 
 
 \
 -3 -3 - - - \- 
# 
# 
 
 
 \
 3 49    \, )S )S ) ) ) \) ) )r8   r  c            	          e Zd ZdZdZdefdZdedefdZede	de
e         fd            Zed	e	deeee	f                  fd
            Zedede
e         fd            Zedededefd            Zede
e         de
e         fd            Zdedee         fdZdede
e         dede
e         fdZd'dedede
e         fdZdedee         fdZdedee         fdZd'dedede
e         fdZde
e         fdZd(dededee	         fdZdedeee	f         dee         fd Zd!eee	f         deeef         fd"Zded#edeeef         fd$Z dedee         fd%Z!d&S ))ClawHubSourceu   
    Fetch skills from ClawHub (clawhub.ai) via their HTTP API.
    All skills are treated as community trust — ClawHavoc incident showed
    their vetting is insufficient (341 malicious skills found Feb 2026).
    zhttps://clawhub.ai/api/v1rB   c                     dS )Nclawhubr7   r   s    r9   r   zClawHubSource.source_idY      yr8   r'   c                     dS r  r7   r   s     r9   r   zClawHubSource.trust_level_for\  r  r8   r-   c                     t          | t                    rd | D             S t          | t                    rd | D             S g S )Nc                 ,    g | ]}t          |          S r7   r!  r"  s     r9   rL   z1ClawHubSource._normalize_tags.<locals>.<listcomp>b  s    )))qCFF)))r8   c                 R    g | ]$}t          |          d k    t          |          %S )latestr!  )rJ   ks     r9   rL   z1ClawHubSource._normalize_tags.<locals>.<listcomp>d  s.    ???qCFFh,>,>CFF,>,>,>r8   )rQ   r5   r6   )r-   s    r9   _normalize_tagszClawHubSource._normalize_tags_  sT    dD!! 	*))D))))dD!! 	@??D????	r8   r~  c                     t          | t                    sd S |                     d          }t          |t                    r1t          |          }|                     d          }|	d|vr||d<   |S | S )Nr  latestVersion)rQ   r6   ry   )r~  nestedmergedlatest_versions       r9   _coerce_skill_payloadz#ClawHubSource._coerce_skill_payloadg  s|    $%% 	4'""fd## 	&\\F!XXo66N)oV.K.K*8'Mr8   r   c                 d    d t          j        d|                                           D             S )Nc                     g | ]}||S r7   r7   )rJ   terms     r9   rL   z.ClawHubSource._query_terms.<locals>.<listcomp>v  s    PPP4PPPPr8   z
[^a-z0-9]+)rY   r  r  )r   s    r9   _query_termszClawHubSource._query_termst  s*    PP-!G!GPPPPr8   rG  c                    |                                                                 }|sdS |j        pd                                }|j        pd                                }|j        pd                                }d                    |                     |                    }d                    |                     |                    }|                     |          }	|                     |          }
|                     |          }d}||k    r|dz  }||k    r|dz  }||k    r|dz  }||k    r|dz  }|                    |          r|d	z  }|                    |          r|d
z  }|	r |
d t          |	                   |	k    r|dz  }|	r |d t          |	                   |	k    r|dz  }||v r|dz  }||v r|dz  }||v r|dz  }|	D ]}||
v r|dz  }||v r|dz  }||v r|dz  }|S )NrP   rG   r  r         }   x   _   Z   F   A   (   #   r   r5     r  )	rS   r  r'   r$   r%   r\   r  rV   r[   )r  r   rG  
query_normr'   r$   r%   normalized_identifiernormalized_namequery_termsidentifier_terms
name_termsscorer  s                 r9   _search_scorezClawHubSource._search_scorex  sg   [[]]((**
 	1o+2244
	R&&(('-24466 #)9)9*)E)E F F((3#3#3D#9#9::&&z22++J77%%d++
##SLESLE J..SLEj((SLE ++J77 	RKE%%j11 	RKE 	+,>c+.>.>,>?;NNRKE 	:&8K(8(8&89[HHRKE##RKERKE$$RKE 	 	D'''z!!{""
r8   r  c                     t                      }g }| D ]Q}|j        p|j                                        }||v r'|                    |           |                    |           R|S r   )r  r'   r$   r  r   r	  )r  r  r~  r   rq  s        r9   _dedupe_resultszClawHubSource._dedupe_results  so    #% 	# 	#F$3::<<Cd{{HHSMMMNN6""""r8   c                 J   |                                                     d          d         }|                     |          }g }|r*t          j        d|          r|                    |           |rhd                    |          }t          |          dk    r+|                    | d| d| d| d	| d
|g           n|                    |           t                      }|D ]7}||v r|
                    |           |                     |          }|r|c S 8d S )NrE   r  z[A-Za-z0-9][A-Za-z0-9._-]*rW  r   z-agentz-skillz-toolz
-assistantz	-playbook)rS   r  r  rY   rZ   r	  r\   r[   r   r  r   r   )	r   r   slugr  rS  	base_slugr  r  rG  s	            r9   _exact_slug_metazClawHubSource._exact_slug_meta  ss   {{}}""3''+''.. "
 	$BL!>EE 	$d### 	---I;1$$!! ((( ((( ''' ,,, +++#     !!),,,# 	 	ID  HHY<<	**D  tr8   r   c                     |                                 s                     |          d |         S  fd|D             }|                     fd                                |          }                               }|r( fd|D             }                     |g|z             }|r
|d |         S t	          j        d          rg S                      |          d |         S )Nc                 H    g | ]}                     |          d k    |S )r   r  rJ   rG  r  r   s     r9   rL   z:ClawHubSource._finalize_search_results.<locals>.<listcomp>  s4    YYYT0B0B:t0T0TWX0X0XD0X0X0Xr8   c                                          |            | j                                        | j                                        fS r   )r  r$   r  r'   )rG  r  r   s    r9   <lambda>z8ClawHubSource._finalize_search_results.<locals>.<lambda>  s@    ##J555	!!%%'' r8   )rq  c                 H    g | ]}                     |          d k    |S rh   r  r  s     r9   rL   z:ClawHubSource._finalize_search_results.<locals>.<listcomp>  s5    ```T5G5G
TX5Y5Y]_5_5_5_5_5_r8   z[A-Za-z0-9][A-Za-z0-9._/-]*)rS   r  sortr  rY   rZ   )r   r   r  r   filteredexactr  s   `     @r9   _finalize_search_resultsz&ClawHubSource._finalize_search_results  s;   [[]]
 	9''00%88YYYYYWYYY     	 	
 	
 	
 ''11%%j11 	@````````H++UGh,>??H 	$FUF##<6
CC 	I##G,,VeV44r8   r   c                 &   |                                 }|r]|                     |          }t          |          dk    r|                     |          }|r|gS |                     ||          }|r|S dt          j        |                                                                           d| }t          |          }|!| 
                    |d |D             |          S 	 t          j        | j         d||dd	          }|j        d
k    rg S |                                }	n## t          j        t          j        f$ r g cY S w xY wt%          |	t&                    r|	                    d|	          n|	}
t%          |
t(                    sg S g }|
d |         D ]}|                    d          }|s|                    d          p|                    d          p|}|                    d          p|                    d          pd}|                     |                    dg                     }|                    t/          ||d|d|                     | 
                    |||          }t1          |d |D                        |S )Nr   r   clawhub_search_listing_v1_r   c                 &    g | ]}t          d i |S r.  r/  r0  s     r9   rL   z(ClawHubSource.search.<locals>.<listcomp>  s"    000AQ000r8   /skills)r   r   r5  r  r7  re  r  displayNamer$   summaryr%   rG   r-   r  r   r$   r%   r&   r'   r(   r-   c                 ,    g | ]}t          |          S r7   r   r0  s     r9   rL   z(ClawHubSource.search.<locals>.<listcomp>,  s!    &U&U&U!':1'='=&U&U&Ur8   )rS   r  r[   r  _search_catalogr  r  r   r  r  r  rx   ry   r  r|   r   rz   r{  rQ   r6   r5   r  r	  r#   r  )r   r   r   r  directr  r@  rA  r   r~  skills_datar]  r  display_namer  r-   final_resultss                    r9   r   zClawHubSource.search  s    		++E22K;1$$..u55 $"8O**5*>>G  cU\\^^1L1L1V1V1X1Xbb[`bb	"9--0000000  
	9=)))"'%88  D
 3&&	99;;DD!56 	 	 	III	 2<D$1G1GQdhhw---T+t,, 	I' 	 	D88F##D 88M22Ndhhv6F6FN$Lhhy))JTXXm-D-DJG''(<(<==DNN9!# '       55eWeLL9&U&U}&U&U&UVVVs   %.D) D) )E	E	c                    |                     d          d         }|                     | j         d|           }t          |t                    sd S |                     ||          }|st                              d|           d S |                     ||          }d|vr|                     | j         d| d|           }t          |t                    r]| 	                    |          p|}d|vrB|
                    di           }t          |t                    r| 	                    |          p|}d|vrt                              d||           d S t          ||d	|d
          S )NrE   r  r}  z=ClawHub fetch failed for %s: could not resolve latest versionr  z
/versions/versionzLClawHub fetch for %s resolved version %s but could not retrieve file contentr  r   r  )r  	_get_jsonr  rQ   r6   _resolve_latest_versionru   rv   _download_zip_extract_filesry   r;   )r   r'   r  
skill_datar  r<   version_datar  s           r9   r   zClawHubSource.fetch/  s   $$R(^^t}$D$Dd$D$DEE
*d++ 	455dJGG 	NNZ\`aaa4 ""488 U"">>T]*d*dD*d*dTb*d*deeL,-- E++L99BUU**)--i<<F!&$// E $ 3 3F ; ; DuU""NN^  
 4#
 
 
 	
r8   c                 B   |                     d          d         }|                     |                     | j         d|                     }t	          |t
                    sd S |                     |                    dg                     }t          |                    d          p+|                    d          p|                    d          p||                    d          p|                    d	          pd
d|                    d          p|d|          S )NrE   r  r}  r-   r  r$   r  r  r%   rG   r  r   r  )	r  r  r  r  rQ   r6   r  ry   r#   )r   r'   r  r~  r-   s        r9   r   zClawHubSource.inspectY  s   $$R())$..DM9Y9YSW9Y9Y*Z*Z[[$%% 	4##DHHVR$8$899-((XDHHV,<,<X@P@PXTX++Ltxx/F/FL"xx''/4#
 
 
 	
r8   c                 b   dt          j        | d|                                                                            }t	          |          }|d |D             d |         S |                                 }|sg S |                     |||          }t          |d |D                        |S )Nclawhub_search_catalog_v1_r  c                 &    g | ]}t          d i |S r.  r/  r0  s     r9   rL   z1ClawHubSource._search_catalog.<locals>.<listcomp>n  r2  r8   c                 ,    g | ]}t          |          S r7   r   r0  s     r9   rL   z1ClawHubSource._search_catalog.<locals>.<listcomp>u  !    &O&O&O!':1'='=&O&O&Or8   )r  r  r   r  r  _load_catalog_indexr  r  )r   r   r   r@  rA  catalogr  s          r9   r  zClawHubSource._search_catalogj  s    g=O=O=O=O=V=V=X=X1Y1Y1c1c1e1egg	"9--33F333FUF;;**,, 	I//wFF9&O&Ow&O&O&OPPPr8   c                    d}t          |          }|d |D             S d }g }t                      }d}t          |          D ]}ddi}|r||d<   	 t          j        | j         d|d	          }	|	j        dk    r n|	                                }
n## t          j        t          j	        f$ r Y  nw xY wt          |
t                    r|
                    d
g           ng }t          |t                    r|s n;|D ]}|                    d          }t          |t                    r|r||v r3|                    |           |                    d          p|                    d          p|}|                    d          p|                    d          pd}|                     |                    dg                     }|                    t#          ||d|d|                     t          |
t                    r|
                    d          nd }t          |t                    r|s nt%          |d |D                        |S )Nclawhub_catalog_v1c                 &    g | ]}t          d i |S r.  r/  r0  s     r9   rL   z5ClawHubSource._load_catalog_index.<locals>.<listcomp>|  r2  r8   2   r   r7  cursorr  rM  r  re  r  r  r$   r  r%   rG   r-   r  r   r  
nextCursorc                 ,    g | ]}t          |          S r7   r   r0  s     r9   rL   z5ClawHubSource._load_catalog_index.<locals>.<listcomp>  r  r8   )r  r  rs   rx   ry   r  r|   r   rz   r{  rQ   r6   r5   r3   r   r  r	  r#   r  )r   r@  rA  r  r  r  	max_pagesr   rN  r   r~  re  r]  r  r  r  r-   s                    r9   r  z!ClawHubSource._load_catalog_indexx  s   (	"9--33F3333 $#%	y!! $	 $	A&-s^F *#)x yDM!:!:!:6SUVVV#s**Eyy{{OT%9:    .8d-C-CKDHHWb)))EeT** %   xx''!$,, D DDLL#xx66R$((6:J:JRd((9--N-1H1HNB++DHHVR,@,@AAy% '$# +          0:$/E/EOTXXl+++4Ffc** &  	9&O&Ow&O&O&OPPPs   *B?BB43B4rh   rk   rj   c                     	 t          j        ||          }|j        dk    rd S |                                S # t           j        t          j        f$ r Y d S w xY w)Nri   r7  )rx   ry   r|   r   rz   r{  )r   rk   rj   r   s       r9   r  zClawHubSource._get_json  sh    	9S'222D3&&t99;;!56 	 	 	44	s   !9 9 AAr  r  c                 j   |                     d          }t          |t                    r.|                     d          }t          |t                    r|r|S |                     d          }t          |t                    r.|                     d          }t          |t                    r|r|S |                     | j         d| d          }t          |t                    rM|rK|d         }t          |t                    r.|                     d          }t          |t                    r|r|S d S )Nr  r  r-   r  r}  z	/versionsr   )ry   rQ   r6   r3   r  r  r5   )	r   r  r  r  r  r-   
latest_tagversions_datafirsts	            r9   r  z%ClawHubSource._resolve_latest_version  s4   00fd## 	jj++G'3'' G ~~f%%dD!! 	"(++J*c** "z "!!$-'P'P'P'P'PQQmT** 	#} 	#!!$E%&& #))I..gs++ # #"Ntr8   r  c                    i }|                     d          }t          |t                    rd |                                D             S t          |t                    s|S |D ]}t          |t                    s|                     d          p|                     d          }|rt          |t
                    s[|                     d          }t          |t
                    r|||<   |                     d          p)|                     d          p|                     d          }t          |t
                    r1|                    d	          r|                     |          }||||<   |S )
Nr<   c                 D    i | ]\  }}t          |t                    ||S r7   )rQ   r3   )rJ   r  rZ  s      r9   
<dictcomp>z0ClawHubSource._extract_files.<locals>.<dictcomp>  s-    MMMTQ*Q:L:LMAqMMMr8   r*   r$   r(  rawUrldownloadUrlrk   http)ry   rQ   r6   re  r5   r3   rV   r  )	r   r  r<   	file_list	file_metafnameinline_contentraw_urlr(  s	            r9   r  zClawHubSource._extract_files  sy    " $$W--	i&& 	NMMY__%6%6MMMM)T** 	L" 	+ 	+Ii.. MM&))BY]]6-B-BE 
5# 6 6 &]]955N.#.. -emmH--e}1M1MeQZQ^Q^_dQeQeG'3'' +G,>,>v,F,F +**733&#*E%Lr8   r  c                    ddl }ddl}i }d}t          |          D ]}	 t          j        | j         d||ddd          }|j        d	k    r	 t          |j                            d
d                    }	n# t          t          f$ r d}	Y nw xY wt          |	d          }	t                              d||	|dz   |           t          j        |	           |j        dk    r&t                              d|||j                   |c S |                    |                    |j                            5 }
|
                                D ]}|                                r	 t+          |j                  }n0# t          $ r# t                              d|j                   Y Yw xY w|j        dk    r"t                              d||j                   	 |
                    |j                  }|                    d          ||<   # t4          t6          f$ r t                              d|           Y w xY w	 ddd           n# 1 swxY w Y   |c S # |j        $ r# t                              d||           |cY c S t          j        $ r+}t                              d|||           |cY d}~c S d}~ww xY wt                              d||           |S )zRDownload skill as a ZIP bundle from the /download endpoint and extract text files.r   Nr  z	/download)r  r  rM  T)rN  rj   rp   i  zretry-after5r!   r5  zEClawHub download rate-limited for %s, retrying in %ds (attempt %d/%d)rP   r7  z+ClawHub ZIP download for %s v%s returned %sz#Skipping unsafe ZIP member path: %si  z)Skipping large file in ZIP: %s (%d bytes)r   z!Skipping non-text file in ZIP: %sz'ClawHub returned invalid ZIP for %s v%sz*ClawHub ZIP download failed for %s v%s: %sz1ClawHub ZIP download exhausted retries for %s v%s)iozipfilers   rx   ry   r  r|   r   rq   rR   	TypeErrorminru   r{   r   sleepZipFileBytesIOr(  infolistis_dirr   filename	file_sizereaddecodeUnicodeDecodeErrorKeyError
BadZipFilerv   rz   )r   r  r  r  r  r<   max_retriesattemptr   retry_afterzfrw   r$   r]   r   s                  r9   r  zClawHubSource._download_zip  s   			 "[)) 3	 3	G2y}///$(W==%)	   #s**(&)$,*:*:=#*N*N&O&O&	2 ( ( (&'("%k2"6"6KLL_k7Q;   J{+++#s**LL!NPTV]_c_oppp LLL__RZZ%=%=>> %" " % %;;== %$%#<T]#K#KDD) % % %"LL)NPTP]^^^$H%  >G33"LL)TVZ\`\jkkk$%"$''$-"8"8C*-**W*=*=E$KK 2H= % % %"LL)LdSSS$H%%% % % % % % % % % % % % % % %( %   H$PWXXX?   I4QXZ]^^^ 	H$PWXXXs   .I(A87I8BIBAI.I-I5,I"E76I7*F$	!I#F$	$0I2HI,H7	4I6H7	7I;II	II	I(J;J;J6.J;6J;c                 P    t          |d          }||j        dk    r|j        S d S r  r  )r   rk   r   s      r9   r  zClawHubSource._fetch_text)  s4     b111 0C 7 79tr8   Nr   r  )"r/   r0   r1   r2   r  r3   r   r   r  r   r   r  r   r   r  r  r  r#   r   r  r  r  r  r   r;   r   r   r  r  r  r  r  r  r  r7   r8   r9   r  r  P  s         +H3    # #     c d3i    \ 
C 
HT#s(^,D 
 
 
 \
 QC QDI Q Q Q \Q .# .Y .3 . . . [.` 	i 	T)_ 	 	 	 \	c hy.A    B5c 5DO 5TW 5\`aj\k 5 5 5 58; ;C ; ;T)_ ; ; ; ;z(
 (
(= (
 (
 (
 (
T
# 
(9*= 
 
 
 
" S  d9o    2T)_ 2 2 2 2h S 3     C T#s(^ PXY\P]    ,4S> d38n    >=# = =S#X = = = =~s x}      r8   r  c                       e Zd ZdZddgZdefdZdefdZdedefd	Z	ddede
dee         fdZdedee         fdZdedee         fdZdedee         fdZdS )ClaudeMarketplaceSourcez
    Discover skills from Claude Code marketplace repos.
    Marketplace repos contain .claude-plugin/marketplace.json with plugin listings.
    r   zaiskillstore/marketplacer   c                     || _         d S r   r  r  s     r9   r   z ClaudeMarketplaceSource.__init__?      			r8   rB   c                     dS )Nclaude-marketplacer7   r   s    r9   r   z!ClaudeMarketplaceSource.source_idB  s    ##r8   r'   c                     |                     dd          }t          |          dk    r|d          d|d          }|t          v rdS dS r   r   r  s       r9   r   z'ClaudeMarketplaceSource.trust_level_forE  sV      a((u::??Ah++q++D}$$ y{r8   r   r   r   c                 p   g }|                                 }| j        D ]}|                     |          }|D ]}|                    dd           d|                    dd                                            }||v r|                    dd          }	|	                    d          r| d|	dd           }
nd|	v r|	}
n| d|	 }
|                    t          |                    dd          |                    dd          d	|
|                     |
          |
                     |d |         S )Nr$   rG   r  r%   r&   z./rE   r   r  )r$   r%   r&   r'   r(   r)   )r  KNOWN_MARKETPLACES_fetch_marketplace_indexry   rV   r	  r#   r   )r   r   r   r  r  marketplace_repopluginspluginr  source_pathr'   s              r9   r   zClaudeMarketplaceSource.searchM  sv   #%kkmm $ 7 	 	334DEEG!   &

62 6 6XXMSU9V9VXX^^``
*,,"(**Xr":":K"--d33 I(8%L%L;qrr?%L%L

++%0

(8%H%H;%H%H
NN9#ZZ33$*JJ}b$A$A3#-$($8$8$D$D-$ $ $   ( vvr8   c                 l    t          | j                  }|                    |          }|rd|_        |S Nr  r  )r   r   r   r&   )r   r'   r   r  s       r9   r   zClaudeMarketplaceSource.fetchi  s9    ty)))*%% 	10FMr8   c                     t          | j                  }|                    |          }|r!d|_        |                     |          |_        |S r  )r   r   r   r&   r   r(   )r   r'   r   rG  s       r9   r   zClaudeMarketplaceSource.inspectq  sP    ty)))zz*%% 	@.DK#33J??Dr8   r)   c                    d|                     dd           }t          |          }||S d| d}	 t          j        |i | j                                        ddid	
          }|j        dk    rg S t          j        |j	                  }n## t          j
        t          j        f$ r g cY S w xY w|                    dg           }t          ||           |S )z<Fetch and parse .claude-plugin/marketplace.json from a repo.claude_marketplace_rE   r   Nr3  z)/contents/.claude-plugin/marketplace.jsonr   rp  r5  r   r7  r  )rT   r  rx   ry   r   r   r|   r   ry  r   rz   r{  r  )r   r)   r@  rA  rk   r   r~  r  s           r9   r  z0ClaudeMarketplaceSource._fetch_marketplace_indexy  s   B$,,sC*@*@BB	"9--M]d]]]
	9^490022^H>]^^  D
 3&&	:di((DD!56 	 	 	III	 ((9b))9g...s   A B 5B B/.B/Nr   )r/   r0   r1   r2   r  r   r   r3   r   r   r   r   r#   r   r   r;   r   r   r6   r  r7   r8   r9   r  r  4  s,         	"
Z    $3 $ $ $ $# #     C  T)_    8 (=    # (9*=    S T$Z      r8   r  c                       e Zd ZdZdZdefdZdedefdZdded	ede	e
         fd
Zdedee         fdZdedee
         fdZdee         fdZdedee         fdZededefd            ZdS )LobeHubSourceu   
    Fetch skills from LobeHub's agent marketplace (14,500+ agents).
    LobeHub agents are system prompt templates — we convert them to SKILL.md on fetch.
    Data lives in GitHub: lobehub/lobe-chat-agents.
    z*https://chat-agents.lobehub.com/index.jsonrB   c                     dS )Nlobehubr7   r   s    r9   r   zLobeHubSource.source_id  r  r8   r'   c                     dS r  r7   r   s     r9   r   zLobeHubSource.trust_level_for  r  r8   r   r   r   c                    |                                  }|sg S |                                }g }t          |t                    r|                    d|          n|}t          |t
                    sg S |D ]T}|                    d|          }|                    d|                    dd                    }	|                    dd          }
|                    dg           }|	 d|
 dt          |t
                    rd                    |          nd                                 }||v r|                    d|	                                                    dd	                    }|                    t          ||
d d
         dd| dt          |t
                    r|ng                      t          |          |k    r nV|S )NagentsrG  rr  r'   rG   r%   r-   r  rW  r7  r!  lobehub/r   r  )_fetch_indexr  rQ   r6   ry   r5   r\   rT   r	  r#   r[   )r   r   r   r  r  r  r$  agentrG  rr  descr-   r  r'   s                 r9   r   zLobeHubSource.search  s   !!## 	Ikkmm#%/9%/F/FQ8U+++E&$'' 	I 	 	E99VU++DHHWeiib&A&ABBE88M2..D88FB''D!]]D]]Zd=S=S+[388D>>>Y[]]cceeJj(("YY|U[[]]5J5J3PS5T5TUU
y# $TcT
$6*66 +!+D$!7!7?R         7||u$$ % r8   c                     |                     d          r|                    dd          d         n|}|                     |          }|sd S |                     |          }t	          |d|idd| d          S )	Nr%  rE   rP   r  r  r!  r   r  )rV   r  _fetch_agent_convert_to_skill_mdr;   )r   r'   agent_id
agent_datar  s        r9   r   zLobeHubSource.fetch  s    3=3H3H3T3Td:##C++B//Zd&&x00
 	4,,Z88x(,(,,#
 
 
 	
r8   c                 d   |                     d          r|                    dd          d         n|}|                                 }|sd S t          |t                    r|                    d|          n|}t          |t                    sd S |D ]}|                    d          |k    r|                    d|          }t          ||                    dd	          d
d| dt          |                    d          t                    r|                    dg           ng           c S d S )Nr%  rE   rP   r  r$  r'   rG  r%   rG   r!  r   r-   r  )rV   r  r&  rQ   r6   ry   r5   r#   )r   r'   r,  r  r$  r'  rG  s          r9   r   zLobeHubSource.inspect  sN   3=3H3H3T3Td:##C++B//Zd!!## 	4/9%/F/FQ8U+++E&$'' 	4 
	 
	Eyy&&(22yy// ! $ ; ;$4(44 +1;DHHV<L<Ld1S1S[&"---Y[      3 tr8   c                    d}t          |          }||S 	 t          j        | j        d          }|j        dk    rdS |                                }n"# t          j        t
          j        f$ r Y dS w xY wt          ||           |S )z2Fetch the LobeHub agent index (cached for 1 hour).lobehub_indexNrM  ri   r7  )	r  rx   ry   	INDEX_URLr|   r   rz   r{  r  )r   r@  rA  r   r~  s        r9   r&  zLobeHubSource._fetch_index  s    #	"9--M	9T^R888D3&&t99;;DD!56 	 	 	44	 	9d+++s   &A A A32A3r,  c                    d| d}	 t          j        |d          }|j        dk    r|                                S nC# t           j        t          j        f$ r%}t                              d|           Y d}~nd}~ww xY wdS )z!Fetch a single agent's JSON file.z https://chat-agents.lobehub.com/rs  r5  ri   r7  zLobeHub agent fetch failed: %sN)rx   ry   r|   r   rz   r{  ru   r{   )r   r,  rk   r   r   s        r9   r*  zLobeHubSource._fetch_agent	  s    @@@@	>9S"---D3&&yy{{" '!56 	> 	> 	>LL91========	>ts   4> A>A99A>r-  c           
      Z   |                      d|           }|                      dd          }|                     d|          }|                     dd          }|                     dg           }|                      di                                d	d          }t          |t                    r|ng }d
d| d|dd          dddd                    d |D                        dddd
g	}d| d|ddd|r|ndg}	d                    |          dz   d                    |	          z   dz   S )z2Convert a LobeHub agent JSON into SKILL.md format.rG  r'   zlobehub-agentrr  r%   rG   r-   config
systemRoler  zname: zdescription: Nrp  z	metadata:z	  hermes:z    tags: [, c              3   4   K   | ]}t          |          V  d S r   r!  r"  s     r9   rO   z5LobeHubSource._convert_to_skill_md.<locals>.<genexpr>	  s(      #=#=qCFF#=#=#=#=#=#=r8   ]z
  lobehub:z    source: lobehubz# z## Instructionsz(No system role defined)
z

)ry   rQ   r5   r\   )
r-  rG  r'   rr  r%   r-   system_roletag_listfm_lines
body_liness
             r9   r+  z"LobeHubSource._convert_to_skill_md	  s\    ~~fj11^^L/BB
*--hh}b11xx## nnXr2266|RHH%dD11944r!Z!!/K-//@$))#=#=H#=#=#===@@@!

 LL&FKK,F

 yy""V+dii
.C.CCdJJr8   Nr   )r/   r0   r1   r2   r1  r3   r   r   r   r   r#   r   r   r;   r   r   r   r&  r6   r*  r  r+  r7   r8   r9   r  r    s^         =I3    # #    ! !C ! !T)_ ! ! ! !F
 
(= 
 
 
 
"# (9*=    .hsm    $	S 	Xd^ 	 	 	 	  K  K#  K  K  K \ K  K  Kr8   r  c                       e Zd ZdZd ZdefdZdedefdZdded	ede	e
         fd
Zdedee         fdZdedee
         fdZdedee         fdZde	e
         fdZededefd            ZdS )OptionalSkillSourceu  
    Fetch skills from the optional-skills/ directory shipped with the repo.

    These skills are official (maintained by Nous Research) but not activated
    by default — they don't appear in the system prompt and aren't copied to
    ~/.hermes/skills/ during setup.  They are discoverable via the Skills Hub
    (search / install / inspect) and labelled "official" with "builtin" trust.
    c                 p    ddl m}  |t          t                    j        j        dz            | _        d S )Nr   )get_optional_skills_dirzoptional-skills)hermes_constantsrA  r	   __file__parent_optional_dir)r   rA  s     r9   r   zOptionalSkillSource.__init__?	  sD    <<<<<<44NN!(+<<
 
r8   rB   c                     dS )Nofficialr7   r   s    r9   r   zOptionalSkillSource.source_idF	  s    zr8   r'   c                     dS )Nr  r7   r   s     r9   r   z#OptionalSkillSource.trust_level_forI	  r  r8   r   r   r   c                 8   g }|                                 }|                                 D ]n}|j         d|j         dd                    |j                                                    }||v r|                    |           t          |          |k    r no|S )Nr  )r  	_scan_allr$   r%   r\   r-   r	  r[   )r   r   r   r  r  rG  r  s          r9   r   zOptionalSkillSource.searchN	  s    #%kkmmNN$$ 	 	D IPP(8PP388DI;N;NPPVVXXJj((t$$$7||u$$ % r8   c           	         |                     d          r|                    dd          d         n|}| j        |z  }	 |                                }t	          |                               t	          | j                                                            sd S n# t
          t          f$ r Y d S w xY w|                                s6|                    dd          d         }| 	                    |          }|sd S n|}i }|
                    d          D ]}|                                ry|j                             d          s_d|j        vrV|j        dk    rKt	          |                    |                    }	 |                                ||<   # t
          $ r Y w xY w|sd S |j        }	t#          |	|d	d|                    | j                   d
          S )N	official/rE   rP   r  *rH   __pycache__z.pycrG  r  r  )rV   r  rE  resolver3   rz  rR   r  r  _find_skill_dirrglobis_filer$   rU   suffixrelative_to
read_bytesr;   )
r   r'   relrm  r  r  r<   fr   r$   s
             r9   r   zOptionalSkillSource.fetch]	  s!   .8.C.CK.P.P`jsA&&r**V`&,		 ((**Hx==++C0B0J0J0L0L,M,MNN t$ 	 	 	44	    	!C++B/J,,Z88I t !I.0%% 	 	A		
))#..
 "00H&&q}}Y7788&'llnnE(OO   H  	4 ~N9#8#89K#L#LNN!
 
 
 	
s$   AB B10B1>F
F#"F#c                     |                     d          r|                    dd          d         n|}|                    dd          d         }|                                 D ]}|j        |k    r|c S d S )NrL  rE   rP   r  )rV   r  r  rJ  r$   )r   r'   rV  r  rG  s        r9   r   zOptionalSkillSource.inspect	  s    .8.C.CK.P.P`jsA&&r**V`ZZQ''+
NN$$ 	 	DyJ&& 'tr8   r$   c                     | j                                         sdS | j                             d          D ]}|j        j        |k    r	|j        c S dS )z<Find a skill directory by name anywhere in optional-skills/.Nr  )rE  r  rQ  rD  r$   )r   r$   r  s      r9   rP  z#OptionalSkillSource._find_skill_dir	  sf    !((** 	4*00<< 	' 	'H#t++&&& ,tr8   c                    | j                                         sg S g }t          | j                             d                    D ]}|j        }|                    | j                   j        }t          d |D                       rC	 |                    d          }n# t          t          f$ r Y nw xY w|                     |          }|                    d|j                  }|                    dd          }g }	|                    di           }
t          |
t                    rA|
                    d	i           }t          |t                    r|                    d
g           }	t!          |                    | j                             }|                    t%          ||dd         dd| d|t          |	t&                    r|	ng                      |S )z,Enumerate all optional skills with metadata.r  c              3   @   K   | ]}|                     d           V  dS )rH   N)rV   rI   s     r9   rO   z0OptionalSkillSource._scan_all.<locals>.<genexpr>	  s.      >>D4??3''>>>>>>r8   r   r   r$   r%   rG   r=   r  r-   Nr7  rG  rL  r  )r$   r%   r&   r'   r(   r*   r-   )rE  r  sortedrQ  rD  rT  rU   rX   r   rz  r  _parse_frontmatterry   r$   rQ   r6   r3   r	  r#   r5   )r   r  r  rD  	rel_partsr(  r)  r$   r(  r-   
meta_blockr*  r   s                r9   rJ  zOptionalSkillSource._scan_all	  s   !((** 	I#%t177
CCDD 	 	H_F**4+=>>DI>>I>>>>> ",,g,>>/0    ((11B66&&+..D66-,,DD
B//J*d++ 7(nnXr::k400 7&??6266D6--d.@AABBHNN9 #J!1x11%'d33;TT       s   	B  B43B4r(  c                 :   |                      d          si S t          j        d| dd                   }|si S | d|                                dz            }	 t	          j        |          }t          |t                    r|ni S # t          j        $ r i cY S w xY wr  r  r  s       r9   r]  z&OptionalSkillSource._parse_frontmatter	  r  r  Nr   )r/   r0   r1   r2   r   r3   r   r   r   r   r#   r   r   r;   r   r   r	   rP  rJ  r  r6   r]  r7   r8   r9   r?  r?  5	  s_        
 
 
3    # #    
 C  T)_    0
 0
(= 0
 0
 0
 0
h# (9*=    C HTN    '4	? ' ' ' 'R C D    \  r8   r?  rq  c                 D   t           |  dz  }|                                sdS 	 |                                }t          j                    |j        z
  t
          k    rdS t          j        |                                          S # t          t          j
        f$ r Y dS w xY w)z Read cached data if not expired.rs  Nrt  )rq  r|  rv  s      r9   r  r  	  s     c===0J t  9;;&884z*..00111T)*   ttr}  r~  c                    t                               dd           t          dz  }|                                s'	 |                    d           n# t
          $ r Y nw xY wt           |  dz  }	 |                    t          j        |dt                               d	S # t
          $ r&}t          
                    d|           Y d	}~d	S d	}~ww xY w)
zWrite data to cache.Tr  z.ignorez,# Exclude hub internals from search tools
*
rs  F)r  defaultr  N)ru  r  HUB_DIRr   r  rz  r   r  r3   ru   r{   )rq  r~  ignore_filer|  r   s        r9   r  r  	  s    $666 I%K 	""#STTTT 	 	 	D	 c===0J5djE3OOOPPPPP 5 5 50!4444444445s)   A 
AA0/B! !
C+CCrG  c           
      t    | j         | j        | j        | j        | j        | j        | j        | j        | j        d	S )z*Convert a SkillMeta to a dict for caching.	r$   r%   r&   r'   r(   r)   r*   r-   r.   rg  r  s    r9   r  r  
  sB     	'+o'			
 
 
r8   c                       e Zd ZdZefdefdZdefdZdeddfdZ		 dd	e
d
e
de
de
de
de
de
dee
         deee
ef                  ddfdZd	e
ddfdZd	e
dee         fdZdee         fdZdS )HubLockFileuL   Manages skills/.hub/lock.json — tracks provenance of installed hub skills.r*   c                     || _         d S r   r*   r   r*   s     r9   r   zHubLockFile.__init__
  r  r8   rB   c                     | j                                         sdi dS 	 t          j        | j                                                   S # t          j        t          f$ r di dcY S w xY w)NrP   )r  	installed)r*   r   r   ry  r   r{  rz  r   s    r9   loadzHubLockFile.load
  s    y!! 	3 r222	3:di1133444$g. 	3 	3 	3 r22222	3s   *A A)(A)r~  Nc                     | j         j                            dd           | j                             t	          j        |dd          dz              d S )NTr  r   F)indentr  r9  r*   rD  r  r  r   r  r   r~  s     r9   savezHubLockFile.save$
  sR    	td;;;	TZQUKKKdRSSSSSr8   r$   r&   r'   r(   scan_verdict
skill_hashinstall_pathr<   r=   c
                 @   |                                  }
||||||||	pi t          j        t          j                                                  t          j        t          j                                                  d
|
d         |<   |                     |
           d S )N)
r&   r'   r(   ru  r   rw  r<   r=   installed_at
updated_atrn  )ro  r   r   r   utc	isoformatrt  )r   r$   r&   r'   r(   ru  rv  rw  r<   r=   r~  s              r9   record_installzHubLockFile.record_install(
  s     yy{{$&(&( B$L66@@BB",x|44>>@@#
 #
[$ 			$r8   c                     |                                  }|d                             |d            |                     |           d S Nrn  )ro  poprt  r   r$   r~  s      r9   record_uninstallzHubLockFile.record_uninstallC
  s=    yy{{[dD)))		$r8   c                 `    |                                  }|d                             |          S r  )ro  ry   r  s      r9   get_installedzHubLockFile.get_installedH
  s(    yy{{K $$T***r8   c                     |                                  }g }|d                                         D ]\  }}|                    d|i|           |S )Nrn  r$   )ro  re  r	  )r   r~  r   r$   rC  s        r9   list_installedzHubLockFile.list_installedL
  s\    yy{{,2244 	3 	3KD%MM641512222r8   r   )r/   r0   r1   r2   	LOCK_FILEr	   r   r6   ro  rt  r3   r   r   r   r   r}  r  r  r  r7   r8   r9   ri  ri  
  sx       VV$-  T    3d 3 3 3 3T T$ T T T T .2   	
     Cy 4S>* 
   6S T    
+# +(4. + + + +T
      r8   ri  c                       e Zd ZdZefdefdZdee         fdZ	dee         ddfdZ
dd
ededefdZd
edefdZdee         fdZdS )TapsManageru:   Manages the taps.json file — custom GitHub repo sources.r*   c                     || _         d S r   rk  rl  s     r9   r   zTapsManager.__init__[
  r  r8   rB   c                     | j                                         sg S 	 t          j        | j                                                   }|                    dg           S # t          j        t          f$ r g cY S w xY w)Nr   )r*   r   r   ry  r   ry   r{  rz  rs  s     r9   ro  zTapsManager.load^
  s    y!! 	I	:di113344D88FB'''$g. 	 	 	III	s   A A A98A9r   Nc                     | j         j                            dd           | j                             t	          j        d|id          dz              d S )NTr  r   r   )rq  r9  rr  )r   r   s     r9   rt  zTapsManager.saveg
  sT    	td;;;	TZqAAADHIIIIIr8   r   r)   c                     |                                  }t          fd|D                       rdS |                    |d           |                     |           dS )z+Add a tap. Returns False if already exists.c              3   0   K   | ]}|d          k    V  dS )r)   Nr7   rJ   r#  r)   s     r9   rO   z"TapsManager.add.<locals>.<genexpr>n
  s,      //QqyD //////r8   Fr   T)ro  rX   r	  rt  )r   r)   r*   r   s    `  r9   r   zTapsManager.addk
  sh    yy{{////$///// 	5T400111		$tr8   c                     |                                  }fd|D             }t          |          t          |          k    rdS |                     |           dS )z6Remove a tap by repo name. Returns False if not found.c                 ,    g | ]}|d          k    |S )r)   r7   r  s     r9   rL   z&TapsManager.remove.<locals>.<listcomp>w
  s'    999!qyD'8'8A'8'8'8r8   FT)ro  r[   rt  )r   r)   r   new_tapss    `  r9   removezTapsManager.removet
  s\    yy{{9999t999x==CII%%5		(tr8   c                 *    |                                  S r   )ro  r   s    r9   	list_tapszTapsManager.list_taps}
  s    yy{{r8   )r   )r/   r0   r1   r2   	TAPS_FILEr	   r   r   r6   ro  rt  r3   r   r   r  r  r7   r8   r9   r  r  X
  s        DD$-  T    d4j    Jd J J J J J  3 t    3 4    4:      r8   r  rG   actionr  r&   r(   verdictr.   c                    t           j                            dd           t          j        t
          j                                      d          }|| || d| |g}|r|                    |           d	                    |          dz   }	 t          t           dd	          5 }	|	                    |           d
d
d
           d
S # 1 swxY w Y   d
S # t          $ r&}
t                              d|
           Y d
}
~
d
S d
}
~
ww xY w)zAppend a line to the audit log.Tr  z%Y-%m-%dT%H:%M:%SZ:r  r9  ar   r   NzCould not write audit log: %s)	AUDIT_LOGrD  r  r   r   r   r{  strftimer	  r\   openwriterz  ru   r{   )r  r  r&   r(   r  r.   	timestamprU   linerW  r   s              r9   append_audit_logr  
  s\    4$777X\**334HIII
v,E,E,E,EwOE U88E??T!D9)S7333 	qGGDMMM	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 9 9 94a8888888889s<   C &C	<C 	CC CC 
D DDc                     t                               dd           t                              d           t                              d           t                                          st                              d           t                                          st                                           t                                          st                              d           dS dS )z8Create the .hub directory structure if it doesn't exist.Tr  )r  z {"version": 1, "installed": {}}
z{"taps": []}
N)
rd  r  QUARANTINE_DIRru  r  r   r  r  touchr  r7   r8   r9   ensure_hub_dirsr  
  s    MM$M...$'''4((( B@AAA  /-...../ /r8   r  c                 n   t                       t          | j                  }g }| j                                        D ]+\  }}t          |          }|                    ||f           ,t          |z  }|                                rt          j
        |           |                    d           |D ]\  }} |j        |                    d           }|j                            dd           t          |t                     r|                    |           i|                    |d           |S )z>Write a skill bundle to the quarantine directory for scanning.T)r  rE   r  r   r   )r  rd   r$   r<   re  r   r	  r  r   shutilrmtreer  joinpathr  rD  rQ   r>   write_bytesr  )r  r  validated_filesr   file_contentr  dest	file_dests           r9   quarantine_bundler  
  sB   %fk22J;=O"(,"4"4"6"6 > >,1(;;|<====J&D{{}} dJJtJ"1 A A,!DM8>>##6#67	td;;;lE** 	A!!,////   @@@@Kr8   quarantine_pathscan_resultc                 r   t          |          }|rt          |          nd}|                                 }t                                          }|                    |          st          d|            |rt          |z  |z  }	n
t          |z  }	|	                                rt          j	        |	           | dz  }
|
                                rO	 |

                                j        }|dk    rt                              d||d           n# t          $ r Y nw xY w|	j                            dd           t          j        t%          |           t%          |	                     t'                      }|                    ||j        |j        |j        |j        t3          |	          t%          |	                    t                              t7          |j                                                  |j        		  	         t?          d
||j        |j        |j        t3          |	                     |	S )z?Move a scanned skill from quarantine into the skills directory.rG   zUnsafe quarantine path: r  i zSkill '%s' has a large SKILL.md (%s chars). Large skills consume significant context when loaded. Consider asking the author to split it into smaller files.r%  Tr  )	r$   r&   r'   r(   ru  rv  rw  r<   r=   INSTALL) rd   rg   rO  r  is_relative_torR   
SKILLS_DIRr   r  r  rv  st_sizeru   rv   rz  rD  r  mover3   ri  r}  r&   r'   r(   r  r   rT  r5   r<   keysr=   r  )r  r  re   r  r  safe_skill_namesafe_categoryquarantine_resolvedquarantine_rootinstall_dirr  
skill_sizelocks                r9   install_from_quarantiner  
  sK    +:66O9AI+H555rM)1133$,,..O--o>> GEOEEFFF 3 =0?B ?2 #k""" +H 	!0JG##Q $!%%    	 	 	D	 TD999
KO$$c+&6&6777 ==D}$& (,,00<<==6<$$&&''  
 
 
 ?FMK/[!!   s   =D 
DDc                 V   t                      }|                    |           }|sdd|  dfS t          |d         z  }|                                rt	          j        |           |                    |            t          d| |d         |d         dd	           d
d|  d|d          fS )z9Remove a hub-installed skill. Refuses to remove builtins.F'z1' is not a hub-installed skill (may be a builtin)rw  	UNINSTALLr&   r(   zn/auser_requestTzUninstalled 'z' from )ri  r  r  r   r  r  r  r  )r  r  rC  rw  s       r9   uninstall_skillr     s    ==Dz**E XW*WWWWWn 55L $l###*%%%[*eHou]?SUZ\jkkkKKKE.4IKKKKr8   c                 T   t          j                    }t          | j                  D ]b}| j        |         }t	          |t
                    r|                    |           :|                    |                    d                     cd|                                dd          S )z;Compute a deterministic hash for an in-memory skill bundle.r   zsha256:N   )	r  sha256r\  r<   rQ   r>   r  r   r  )r  hr   r(  s       r9   bundle_content_hashr    s    A6<(( . .,x(gu%% 	.HHWHHW^^G,,----)Q[[]]3B3')))r8   source_namec                 f    ddi}|                     ||          }|                                 |k    S )Nr  r  )ry   r   )r&   r  aliasesr^   s       r9   _source_matchesr    s:    [G [+66J++r8   )r  sourcesr   r  r  r   c                    |pt                      }|                                } r fd|D             }|t          |          }g }|D ]}|                    dd          }|                    dd          fd|D             p|}d}	|D ]/}
	 |
                    |          }	n# t
          $ r d}	Y nw xY w|	r n0|	s/|                    |                    dd          |d	d
           |                    dd          }t          |	          }||k    rdnd}|                    |                    dd          |||||	d           |S )z0Check installed hub skills for upstream changes.c                 F    g | ]}|                     d           k    |S rc   )ry   )rJ   rC  r$   s     r9   rL   z+check_for_skill_updates.<locals>.<listcomp>0  s/    OOOuUYYv5F5F$5N5NU5N5N5Nr8   Nr  r'   rG   r&   c                 4    g | ]}t          |          |S r7   )r  )rJ   srcr  s     r9   rL   z+check_for_skill_updates.<locals>.<listcomp>9  s(    YYYSsK7X7XYSYYYr8   r$   unavailable)r$   r'   r&   statusr   
up_to_dateupdate_available)r$   r'   r&   r  current_hashlatest_hashr  )ri  r  create_source_routerry   r   r   r	  r  )r$   r  r  r   rn  r  rC  r'   candidate_sourcesr  r  r  r  r  r  s   `             @r9   check_for_skill_updatesr  %  s     ;==D##%%I POOOO	OOO	&D111G " "YY|R00
ii"--YYYYGYYYd]d$ 	 	C:..      	NN		&"--(%'	     yy44)&11!-!<!<BTIIfb))$!(&
 
 	 	 	 	 Ns   B))B87B8z@https://hermes-agent.nousresearch.com/docs/api/skills-index.jsonzhermes-index.jsoni`T  c                     t                                           r	 t          j                    t                                           j        z
  } | t
          k     r+t          j        t                                                     S n# t          t          j
        f$ r Y nw xY w	 t          j        t          dd          }|j        dk    r.t                              d|j                   t#                      S |                                }nQ# t          j        t          j
        f$ r3}t                              d|           t#                      cY d}~S d}~ww xY wt'          |t(                    rd|vrt#                      S 	 t           j                            dd	           t                               t          j        |                     n# t          $ r Y nw xY w|S )
zFetch the centralized skills index, with local cache.

    The index is a JSON file hosted on the docs site, rebuilt daily by CI.
    We cache it locally for HERMES_INDEX_TTL seconds to avoid repeated
    downloads within a session.
    r5  Tro   r7  zHermes index fetch returned %dzHermes index fetch failed: %sNr   r  )HERMES_INDEX_CACHE_FILEr   r   rv  rw  HERMES_INDEX_TTLr   ry  r   rz  r{  rx   ry   HERMES_INDEX_URLr|   ru   r{   _load_stale_index_cacherz   rQ   r6   rD  r  r  r  )ager   r~  r   s       r9   _load_hermes_indexr  f  s    %%'' 	)++ 7 < < > > GGC%%%z"9"C"C"E"EFFF &-. 	 	 	D	)y)2MMMs""LL94;KLLL*,,,yy{{OT12 ) ) )4a888&(((((((()
 dD!! )XT%9%9&(((&,,TD,III**4:d+;+;<<<<    KsJ   A'B BB!AD 6D E&(EEEAG 
GGc                      t                                           rH	 t          j        t                                                     S # t
          t          j        f$ r Y nw xY wdS )z6Fall back to stale cache when the network fetch fails.N)r  r   r   ry  r   rz  r{  r7   r8   r9   r  r    se    %%'' 	:5??AABBB-. 	 	 	D	4s   *A AAc                      e Zd ZdZdefdZdefdZdefdZ	de
fdZedefd            Zd	e
de
fd
Zdde
dedee         fdZd	e
dee         fdZd	e
dee         fdZd	e
dedee         fdZededefd            ZdS )HermesIndexSourcea  Skill source backed by the centralized Hermes Skills Index.

    The index is a JSON catalog published to the docs site and rebuilt
    daily by CI.  It contains metadata + resolved GitHub paths for every
    skill, eliminating the need for users to hit the GitHub API for
    search or path discovery.

    When the index is unavailable, all methods return empty / None so
    downstream sources take over transparently.
    r   c                 >    d | _         d| _        || _        d | _        d S r   )_index_loadedr   _githubr  s     r9   r   zHermesIndexSource.__init__  s$    &*	 04r8   rB   c                 V    | j         st                      | _        d| _         | j        pi S )NT)r  r  r  r   s    r9   _ensure_loadedz HermesIndexSource._ensure_loaded  s-    | 	 ,..DKDL{ b r8   c                 R    | j         t          | j                  | _         | j         S r  )r  r   r   r   s    r9   _get_githubzHermesIndexSource._get_github  s&    <'TY777DL|r8   c                     dS )Nhermes-indexr7   r   s    r9   r   zHermesIndexSource.source_id  s    ~r8   c                 n    |                                  }t          |                    d                    S )z+Whether the index is loaded and has skills.r   )r  r   ry   )r   r  s     r9   is_availablezHermesIndexSource.is_available  s/     ##%%EIIh''(((r8   r'   c                     |                                  }|                    dg           D ]3}|                    d          |k    r|                    dd          c S 4dS )Nr   r'   r(   r   )r  ry   )r   r'   r  r  s       r9   r   z!HermesIndexSource.trust_level_for  sk    ##%%YYx,, 	= 	=Eyy&&*44yy<<<<< 5{r8   r   r   r   c                 F                                      }|                    dg           }|sg S |                                s fd|d|         D             S |                                }g }|D ]}|                    dd           d|                    dd           dd                    |                    dg                                                      }||v r=|                                         |                     t          |          |k    r n|S )	z)Search the cached index.  Zero API calls.r   c                 :    g | ]}                     |          S r7   )_to_metar=  s     r9   rL   z,HermesIndexSource.search.<locals>.<listcomp>  s%    ===DMM!$$===r8   Nr$   rG   r  r%   r-   )r  ry   rS   r  r\   r	  r  r[   )	r   r   r   r  r   r  r  r1  r  s	   `        r9   r   zHermesIndexSource.search  s.   ##%%8R(( 	I{{}} 	>====fVeVn====kkmm#% 	 	AEE&"--hhmR0H0Hhh388TUTYTYZ`bdTeTeKfKfhhnnppJj((t}}Q//000w<<5((Er8   c                 2   |                                  }|                     ||          }|sdS |                    d          }|rM|                                                     |          }|r$|                    dd          |_        ||_        |S |                    dd          }|                    dd          }|rV|rT| d| }|                                                     |          }|r$|                    dd          |_        ||_        |S dS )	ab  Fetch a skill using the resolved path from the index.

        If the index has a ``resolved_github_id`` for this skill, we skip
        the entire candidate/discovery chain and go directly to GitHub
        with the exact path.  This reduces install from ~31 API calls to
        just the file content downloads (~5-22 depending on skill size).
        Nresolved_github_idr&   r  r)   rG   r*   rE   )r  _find_entryry   r  r   r&   r'   )	r   r'   r  rC  r  r  r)   r*   	github_ids	            r9   r   zHermesIndexSource.fetch  s4    ##%%  U33 	4 99122 	%%''--h77F  %		(N C C$.! yy$$yy$$ 	D 	(($((I%%''--i88F  %		(N C C$.!tr8   c                     |                                  }|                     ||          }|r|                     |          S dS )z0Return metadata from the index.  Zero API calls.N)r  r  r  )r   r'   r  rC  s       r9   r   zHermesIndexSource.inspect   sG    ##%%  U33 	(=='''tr8   r  c                    |                     dg           }|D ]}|                     d          |k    r|c S  |}dD ]0}|                    |          r|t          |          d         } n1|D ]W}|                     dd          }|}dD ]0}|                    |          r|t          |          d         } n1||k    r|c S XdS )z3Look up a skill in the index by identifier or name.r   r'   )ry  rz  rL  zgithub/zclawhub/NrG   )ry   rV   r[   )	r   r'   r  r   r1  r^   rE  sidstored_normalizeds	            r9   r  zHermesIndexSource._find_entry  s#   8R((  	 	Auu\""j00 1  
V 	 	F$$V,, 'F5

  		 		A%%b))C #Z  >>&)) (+CKKLL(9%E !J.. / tr8   rC  c                    t          |                     dd          |                     dd          |                     dd          |                     dd          |                     dd          |                     d	          |                     d
          |                     dg           |                     di           	  	        S )Nr$   rG   r%   r&   r  r'   r(   r   r)   r*   r-   r.   rg  )r#   ry   )rC  s    r9   r  zHermesIndexSource._to_meta&  s    62&&		-4499X~66yyr22		-==6""6""62&&))GR((

 

 

 
	
r8   Nr   )r/   r0   r1   r2   r   r   r6   r  r   r  r3   r   r  r   r  r   r   r   r#   r   r   r;   r   r   r  r  r  r7   r8   r9   r  r    s       	 	4Z 4 4 4 4! ! ! ! !\    
3     )d ) ) ) X)
# #     C  T)_    *! !(= ! ! ! !F# (9*=    c $ 8D>    < 
 
 
 
 
 \
 
 
r8   r  c           
      j   | t                      } t                      }|                                }t                      t	          |           t          |           t                      t                      t          | |          t                      t          |           t                      g	}|S )zr
    Create all configured source adapters.
    Returns a list of active sources for search/fetch operations.
    Nr  )r   r   )r   r  r  r?  r  r  r  r  r   r  r  r  )r   taps_mgrr   r  s       r9   r  r  5  s    
 |||}}H##%%J 	t$$$D!!!$:666T***
"G Nr8   r  r   r   c                    	 |                                  |                     ||          fS # t          $ rN}t                              d|                                  |           |                                  g fcY d}~S d}~ww xY w)z:Search a single source.  Runs in a thread for parallelism.r  zSearch failed for %s: %sN)r   r   r   ru   r{   )r  r   r   r   s       r9   _search_one_sourcer  O  s    #}}

5
 > >>> # # #/!DDD}}"""""""#s   *- 
BAB :B Br   rM  per_source_limitssource_filteroverall_timeouton_source_donec                 F   ddl m}m} |pi }g }d}	t          h d          }
|dk    r2| D ]/}|                                dk    rt          |dd          rd}	 n0| D ]E}|                                }|dk    r||k    r|d	k    r)|	r||
v r0|                    |           Fg }i }g }|s|||fS  |t          t          |          d
                    5 }i |D ]^}|	                    |                                d          }|
                    t          |||          }|                                |<   _	  ||          D ]n}	 |                    d          \  }}t          |          ||<   |                    |           |r ||t          |                     _# t          $ r Y kw xY wnN# t          $ rA fdD             }|r.t                               dd                    |                     Y nw xY wddd           n# 1 swxY w Y   |||fS )u  Search all sources in parallel with per-source timeout.

    Returns ``(all_results, source_counts, timed_out_ids)``.

    *on_source_done* is an optional callback ``(source_id, count) -> None``
    invoked as each source completes — useful for progress indicators.
    r   )ThreadPoolExecutoras_completedF>   r   r  r!  r  r  r  r   r  r  TrG     )max_workersr  ri   c                 H    g | ]}|                                 |         S r7   )done)rJ   rW  futuress     r9   rL   z+parallel_search_sources.<locals>.<listcomp>  s:        16688
  r8   z'Skills browse timed out waiting for: %sr6  N)concurrent.futuresr
  r  	frozensetr   r~   r	  r  r[   ry   submitr  r   r   r   TimeoutErrorru   r{   r\   )r  r   r  r  r  r  r
  r  active_index_available_api_source_idsr  r  all_resultssource_countstimed_out_idspoollimfutr  r  s                       @r9   parallel_search_sourcesr  Z  s5    DCCCCCCC)/R "F
  !Q !Q !Q R RO 	 	C>11^U;; 2#'   mmooE!!c]&:&:sj?P?P 	 6 6c#%K$&M!M 9M=88		CKK(;(;	<	<	<  	+ 	+C#''<<C++0#ucBBC==??GCLL	#|G_EEE  #&::a:#8#8LC),WM#&&&w///% :&sCLL999    D  	 	 	   $+  M  =IIm,,  	#              6 }44s]   A$H<F9AF('F9(
F52F94F55F98H9AHHHHHHr   c                 b   t          || |d          \  }}}dddd}i }|D ]c}|j        |vr|||j        <   |                    |j        d          |                    ||j                 j        d          k    r
|||j        <   dt	          |                                          }	|	d|         S )z3Search all sources (in parallel) and merge results.rM  )r   r  r  r   rP   r   r  N)r  r$   ry   r(   r5   r
  )
r   r  r  r   r  r   _TRUST_RANKr  r  r~  s
             r9   unified_searchr!    s     0#	  KA  AA>>K!#D  6DLL__Q]A..afAY[\1]1]]]DL4;;==!!G6E6?r8   )rG   )rB   Nr   )rG   Nr   rM  N)r   r   )gr2   r  r   loggingr   rY   r  r   r   abcr   r   dataclassesr   r   r   r   pathlibr	   r
   rB  r   typingr   r   r   r   r   r   urllib.parser   r   r   rx   r  tools.skills_guardr   r   r   tools.url_safetyr   tools.website_policyr   	getLoggerr/   ru   HERMES_HOMEr  rd  r  r  r  r  ru  rx  r}   rt   r#   r;   r3   r   r_   rd   rg   r   Responser   r   r   r   r   r  r  r  r  r  r  r?  r  r  r6   r  ri  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  floatr  r!  r7   r8   r9   <module>r/     s	       				 				       # # # # # # # # ( ( ( ( ( ( ( ( ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' , , , , , , : : : : : : : : : : : : : : : : 6 6 6 6 6 6 6 6 6 6            ) ( ( ( ( ( 5 5 5 5 5 5		8	$	$ o8#

v
k!	<'k!	k!	M) 222   
8 
8 
8 
8 
8 
8 
8 
8 ; ; ; ; ; ; ; ;s 3 d WZ    2Us Us U U U UWc Wc W W W W 35 " " "3 "C "%.9Q " " " "J^ ^ ^ ^ ^ ^t t t t t t t tv    #   @b b b b b; b b bR\A \A \A \A \A; \A \A \AF_ _ _ _ _ _ _ _LQ) Q) Q) Q) Q)[ Q) Q) Q)p] ] ] ] ]K ] ] ]H[ [ [ [ [k [ [ [DXK XK XK XK XKK XK XK XK~f f f f f+ f f fZ3 8C=    5C 5s 5t 5 5 5 5&i D    &; ; ; ; ; ; ; ;D& & & & & & & &\ CE9 9S 9c 93 9"%9039<?9IM9 9 9 9(
/ 
/ 
/ 
/k d    2>>> > 	>
 > 
> > > >BL LdCi(8 L L L L"	* 	* 	* 	* 	* 	*,K ,c ,d , , , , 5 #'+/!%5 5 5
3-5 ;
5 d;'(	5
 :
5 
$Z5 5 5 5x V ),??  &HTN & & & &R$    Y
 Y
 Y
 Y
 Y
 Y
 Y
 Y
x x
3 tK?P    4#	# #),#
3Y # # # # 26$(M5 M5+M5M5  S#X/M5 	M5
 M5 SMM5 4	?DcNDI56M5 M5 M5 M5b =? # [(9 "%69CG	?     r8   