
    iZ                    6   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 ddl Z ddl!Z!dd
l"m#Z#m$Z$m%Z%  ej&        e'          Z( e            Z)e)dz  Z*e*dz  Z+e+dz  Z,e+dz  Z-e+dz  Z.e+dz  Z/e+dz  Z0dZ1e G d d                      Z2e G d d                      Z3de4de4de5de4fdZ6de4de4fdZ7de4de4fdZ8d e4de4fd!Z9 G d" d#          Z: G d$ d%e
          Z; G d& d'e;          Z< G d( d)e;          Z= G d* d+e;          Z> G d, d-e;          Z? G d. d/e;          Z@ G d0 d1e;          ZA G d2 d3e;          ZB G d4 d5e;          ZCd6e4dee         fd7ZDd6e4d8eddfd9ZEd:e2deFfd;ZG G d< d=          ZH G d> d?          ZI	 dldAe4dBe4dCe4dDe4dEe4dFe4ddfdGZJdmdHZKdIe3defdJZLdKedBe4de4dIe3dLe#defdMZMdBe4dee5e4f         fdNZNdIe3de4fdOZOdCe;dPe4de5fdQZP	 dnddddRdee4         dSeeH         dTeee;                  dUee:         deeF         f
dVZQdWZRe0dXz  ZSdYZTdeeF         fdZZUdeeF         fd[ZV G d\ d]e;          ZWdndUee:         dee;         fd^ZXd_e;d`e4daeYdee4ee2         f         fdbZZ	 	 	 	 	 dodTee;         d`e4deeee4eYf                  dfe4dge[dhee         deee2         ee4eYf         ee4         f         fdiZ\	 dpd`e4dTee;         dfe4daeYdee2         f
dkZ]dS )qu  
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)urlparse
urlunparse)
ScanResultcontent_hashTRUSTED_REPOSskillsz.hubz	lock.json
quarantinez	audit.logz	taps.jsonzindex-cachei  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        5/home/ubuntu/.hermes/hermes-agent/tools/skills_hub.pyr   r   ?   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7r/   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&   r'   r(   r)   r*   r+   r   r   bytesr   r-   r4   r   r.   r/   r0   r2   r2   M   s         HH
IIIU3:&&''''KKKOOO$uT:::Hd38n:::::r/   r2   
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 )) .r.   .0parts     r0   
<listcomp>z*_normalize_bundle_path.<locals>.<listcomp>c   s"    BBBdD	,A,AT,A,A,Ar/   : c              3   "   K   | ]
}|d k    V  dS )z..Nr.   r@   s     r0   	<genexpr>z)_normalize_bundle_path.<locals>.<genexpr>g   s&      77777777r/   z	[A-Za-z]:r      )
isinstancer*   
ValueErrorstripreplacer
   parts
startswithis_absoluteanyre	fullmatchlenjoin)r6   r7   r8   raw
normalizedr!   rL   s          r0   _normalize_bundle_pathrV   X   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??r/   r   c                 &    t          | dd          S )Nz
skill nameFr7   r8   rV   r   s    r0   _validate_skill_namer[   q   s    !$<eTTTTr/   categoryc                 &    t          | dd          S )Nr\   FrX   rY   )r\   s    r0   _validate_category_namer^   u   s    !(zPUVVVVr/   rel_pathc                 &    t          | dd          S )Nzbundle file pathTrX   rY   )r_   s    r0   _validate_bundle_rel_pathra   y   s    !(7IX\]]]]r/   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    r0   __init__zGitHubAuth.__init__   s     ,0-1()r/   r9   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)ri   tokenheaderss      r0   get_headerszGitHubAuth.get_headers   s=    ##%%=> 	8'7'7'7GO$r/   c                 .    |                                  d uS Nro   rh   s    r0   is_authenticatedzGitHubAuth.is_authenticated   s    ""$$D00r/   c                 <    |                                   | j        pdS )zRReturn which auth method is active: 'pat', 'gh-cli', 'github-app', or 'anonymous'.	anonymous)rp   rf   rh   s    r0   auth_methodzGitHubAuth.auth_method   s"    "1k1r/   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  rx   )	re   rf   timerg   osenvironget_try_gh_cli_try_github_app)ri   rq   s     r0   rp   zGitHubAuth._resolve_token   s     	*"l22dikkDDZ6Z6Z)) 
~..L"*..2L2L 	!&D"'DL   "" 	!&D"*DL $$&& 	!&D".D%)Y[[4%7D"L)tr/   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authrq   T   )capture_outputtexttimeoutr   zgh CLI token lookup failed: %sN)	
subprocessrun
returncodestdoutrJ   FileNotFoundErrorTimeoutExpiredloggerdebug)ri   resultes      r0   r   zGitHubAuth._try_gh_cli   s    	>^'''#$  F  A%%&-*=*=*?*?%}**,,,!:#<= 	> 	> 	>LL91========	>ts   AA B0BBc                 V   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 |
                                }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<   iX  )iatexpissRS256)	algorithmz)https://api.github.com/app/installations/z/access_tokenszBearer rm   )rn   rl   
   rr   r      rq   zGitHub App auth failed: )r   r   r   alljwtImportErrorr   r   r	   exists	read_textintr~   encodehttpxpoststatus_codejson	Exception)ri   app_idkey_pathinstallation_idr   key_fileprivate_keynowpayloadencoded_jwtrespr   s               r0   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",,..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 $#E5 	B*E5 5
F&?F!!F&N)r&   r'   r(   r)   rj   r   r*   rs   boolrv   ry   r   rp   r   r   r.   r/   r0   rc   rc      s         * * *
T#s(^    1$ 1 1 1 12S 2 2 2 2
    >Xc]    *# * * * * * *r/   rc   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limitr9   c                     dS )z*Search for skills matching a query string.Nr.   ri   r   r   s      r0   searchzSkillSource.search   	     	r/   r   c                     dS )z&Download a skill bundle by identifier.Nr.   ri   r   s     r0   fetchzSkillSource.fetch  r   r/   c                     dS )z9Fetch metadata for a skill without downloading all files.Nr.   r   s     r0   inspectzSkillSource.inspect	  r   r/   c                     dS )z=Unique identifier for this source (e.g. 'github', 'clawhub').Nr.   rh   s    r0   	source_idzSkillSource.source_id  r   r/   c                     dS )z3Determine trust level for a skill from this source.	communityr.   r   s     r0   trust_level_forzSkillSource.trust_level_for  s    {r/   Nr   )r&   r'   r(   r)   r   r*   r   r   r   r   r   r2   r   r   r   r   r.   r/   r0   r   r      s       88 C  T)_    ^  (=    ^ # (9*=    ^ 3    ^# #      r/   r   c            	          e Zd ZdZddddddddddddd	d
dgZd/d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d0d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d1d 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 )2GitHubSourcez4Fetch skills from GitHub repos via the Contents API.zopenai/skillsskills/r    r!   anthropics/skillszVoltAgent/awesome-agent-skillszgarrytan/gstackr>   zMiniMax-AI/clizskill/Nr   
extra_tapsc                     || _         t          | j                  | _        |r| j                            |           i | _        d| _        d S NF)r   r,   DEFAULT_TAPStapsextend_tree_cache_rate_limited)ri   r   r   s      r0   rj   zGitHubSource.__init__'  sR    	*++	 	)IZ((( ?A#(r/   r9   c                     dS )Ngithubr.   rh   s    r0   r   zGitHubSource.source_id2  s    xr/   c                     | j         S )z8Whether GitHub API rate limit was hit during operations.)r   rh   s    r0   is_rate_limitedzGitHubSource.is_rate_limited5  s     !!r/   r   c                     |                     dd          }t          |          dk    r|d          d|d          }|t          v rdS dS Nr<      r   rG   trustedr   splitrR   r   ri   r   rL   r    s       r0   r   zGitHubSource.trust_level_for:  sV      a((u::??Ah++q++D}$$ y{r/   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!   r>    zFailed to search rD   Nr   rG   r   builtinr   r   )lowerr   _list_skills_in_repor   r   r   rS   r$   appendr   r   r   r   r,   values)ri   r   r   resultsquery_lowertapr   skill
searchabler   _trust_rankseenrs                r0   r   zGitHubSource.searchC  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"
        r<   r      Nr   rG   SKILL.mdr   r   r3   r   r   r   )r   rR   _download_directoryrstripr   r2   )ri   r   rL   r    
skill_pathr3   
skill_nametrusts           r0   r   zGitHubSource.fetch_  s    
   a((u::>>4(''U1X''1X
((z:: 	
%//4&&s++11#66r:
$$Z00!
 
 
 	
r/   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.r<   r   r   Nr   rG   	/SKILL.mdr   r   r   r>   r4   hermesr$   r   c                 ,    g | ]}t          |          S r.   r*   rA   ts     r0   rC   z(GitHubSource.inspect.<locals>.<listcomp>  s    '''Q#a&&'''r/   r   r   r   r   r   r    r!   r$   )r   rR   r   _fetch_file_content_parse_frontmatter_quickr   rH   r-   r,   r   r*   r   )ri   r   rL   r    r   skill_md_pathcontentfmr   r   r$   r4   hermes_metaraw_tagss                 r0   r   zGitHubSource.inspectz  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''$'''	
 	
 	
 		
r/   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<   r   Nc                 &    g | ]}t          d i |S r.   r   rA   ss     r0   rC   z5GitHubSource._list_skills_in_repo.<locals>.<listcomp>  "    333qINNNN333r/   https://api.github.com/repos/
/contents/   Trr   r   follow_redirects   typedirr   r?   r  c                 :    g | ]}                     |          S r.   )_meta_to_dictrA   r  ri   s     r0   rC   z5GitHubSource._list_skills_in_repo.<locals>.<listcomp>  s'    %L%L%Ld&8&8&;&;%L%L%Lr/   )rK   _read_cacher   r   r   r   rs   r   	HTTPErrorr   rH   r,   rM   r   r   _write_cache)ri   r    r!   	cache_keycachedurlr   entriesr   entrydir_nameprefixskill_identifiermetas   `             r0   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).
        r  r  Tr  r  Ndefault_branchmainz/git/trees/	recursive1   )paramsrr   r   r  	truncatedz'Git tree truncated for %s, cannot cachetree)r   r   rs   r   r   r   _check_rate_limit_responser   r!  rI   r   r   )ri   r    rr   r   r-  	tree_datar&  s          r0   _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-Remainingr>   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   rr   r   r   r   warning)ri   r   	remainings      r0   r5  z'GitHubSource._check_rate_limit_response  so    s""(()@"EEIC%)"]    	 #"r/   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_treer   r   _download_directory_recursive)ri   r    r!   r3   s       r0   r   z GitHubSource._download_directory  sS     11$==LSUY[_```11$===r/   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).
        r<   Nc              3   j   K   | ]-}|                     d d                                        V  .dS )r!   r>   N)r   rM   )rA   itemr)  s     r0   rF   z<GitHubSource._download_directory_via_tree.<locals>.<genexpr>(  sP       
 
8<DHHVR  ++F33
 
 
 
 
 
r/   r  blobr!   r>   z"Skipped file (fetch failed): %s/%s)	r   r7  rO   r   rM   rR   r  r   r   )ri   r    r!   r$  _default_branchtree_entrieshas_entriesr3   rB  	item_pathr_   r  r)  s               @r0   r>  z)GitHubSource._download_directory_via_tree  sQ    {{3$$T**>4(.%  
 
 
 
@L
 
 
 
 
  	 I !#  	T 	TDxx6)),,I''//  V.H..tY??G"")hA4SSSS'uu4'r/   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).r  r  r<   r  Tr  r  z"Contents API returned %d for %s/%sr   r>   r  filer!   Nr  z#Empty or failed subdirectory: %s/%s)r   r   r   r   rs   r   r   r   r!  r   rH   r,   r  r?  items)ri   r    r!   r%  r   r&  r3   r'  r   
entry_typer  r_   	sub_filessub_namesub_contents                  r0   r?  z*GitHubSource._download_directory_recursiveA  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``.
        Nr<   r   r  rC  r!   r>   )r7  r   endswithrR   )
ri   r    r   r$  rD  rE  skill_md_suffixr'  r!   	skill_dirs
             r0   _find_skill_in_repo_treez%GitHubSource._find_skill_in_repo_treec  s     $$T**>4(.% 4j333! 	- 	-Eyy  F**99VR((D}}_-- -J9Q9Q9Q1Q1Q !4C$4$4#4!45	,,,,,,, 2R
 tr/   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.r  r  rl   application/vnd.github.v3.rawr  Tr  r  z$GitHub contents API fetch failed: %sN)
r   r   r   rs   r   r   r5  r!  r   r   )ri   r    r!   r%  r   r   s         r0   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)ri   rV  
cache_filer[  s       r0   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_okrX  F)ensure_asciiCould not write cache: %sN)rZ  mkdir
write_textr   dumpsr_  r   r   )ri   rV  rc  ra  r   s        r0   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r+  c           	      h    | j         | j        | j        | j        | j        | j        | j        | j        dS )Nr  r  r+  s    r0   r  zGitHubSource._meta_to_dict  s=     I+k/+III	
 	
 		
r/   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	rM   rP   r   startyaml	safe_loadrH   r-   	YAMLErrorr  match	yaml_textparseds       r0   r  z%GitHubSource._parse_frontmatter_quick       !!%(( 	I	-55 	IAekkmma//0		^I..F'55=662=~ 	 	 	III	   ,B BBru   r   )r   r8  r9   N)&r&   r'   r(   r)   r   rc   r   r   r   rj   r*   r   propertyr   r   r   r   r   r   r2   r   r   r   r   r-   r7  r5  r   r>  r?  rS  r  r,   r   r"  staticmethodr  r  r.   r/   r0   r   r     s       >> !)44$i88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 

$ 

 

 

 \

 # $    \  r/   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/skillsr9   c                     dS )N
well-knownr.   rh   s    r0   r   zWellKnownSkillSource.source_id  s    |r/   r   c                     dS Nr   r.   r   s     r0   r   z$WellKnownSkillSource.trust_level_for      {r/   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   r>   r3   r   r  base_urlr   	index_url)r  r  r3   r   r   r   r   r   r!   r%   )	_query_to_index_url_parse_indexr   rH   r*   r   r   _wrap_identifierr,   )
ri   r   r   r  rz  r   r'  r   r   r3   s
             r0   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<        r/   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   r>   r   r  r  r   r3   r   )r  r  r3   endpointr  )	_parse_identifier_index_entry_fetch_textr   r  r*   r   r   r  )ri   r   rz  r'  skill_mdr	  r   r   s           r0   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";/	 
 
 
 	
r/   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  r3   r   z3Well-known skill %s advertised unsafe file path: %rr  r<   r  r  r   )r  r  r  r3   r   r3   r   r   r   r4   )r  r[   rI   r   r;  r  r   rH   r,   r*   ra   r  r2   r  )
ri   r   rz  r   r'  r3   
downloadedr_   safe_rel_pathr   s
             r0   r   zWellKnownSkillSource.fetch  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.jsonr<   rG   r   )rJ   rM   rP  	BASE_PATHr   r   )ri   r   r  s      r0   r  z(WellKnownSkillSource._query_to_index_urlC  s     788 	4>>-(( 	Ln5(({{dn#7#7#7;;A>OH++++||C  dn#A#A#AAAr/   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  r>   )fragmentr  r<   )r  r  r   r  r   rG   )
rM   rR   r   r   _replacer  rP  r   r  rsplit)	ri   r   rT   
parsed_url	clean_urlr  r  r   r  s	            r0   r  z&WellKnownSkillSource._parse_identifierN  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 $"	
 
 	
r/   r  c                    dt          j        |                                                                           }t	          |          }t          |t                    r*t          |                    d          t                    r|S 	 t          j        |dd          }|j
        dk    rd S |                                }n"# t          j        t          j        f$ 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      Tr   r  r  r  )r  r  r   )hashlibmd5r   	hexdigest_read_index_cacherH   r-   r   r,   r   r   r   r!  r`  rR   _write_index_cache)ri   r  r#  r$  r   rc  r   rz  s           r0   r  z!WellKnownSkillSource._parse_indext  s_   UI4D4D4F4F(G(G(Q(Q(S(SUU	"9--fd## 	
6::h3G3G(N(N 	M	9YTJJJD3&&t99;;DD!56 	 	 	44	 ,6dD+A+AI(B'''r&$'' 	4 #!"6C$6$6#6"67
 

 	9f---s   "C /C C#"C#r   c                     |                      |          }|sd S |d         D ]4}t          |t                    r|                    d          |k    r|c S 5d S )Nr   r   )r  rH   r-   r   )ri   r  r   rz  r'  s        r0   r  z!WellKnownSkillSource._index_entry  sm    ""9-- 	4H% 	 	E%&& 599V+<+<
+J+Jtr/   r%  c                     	 t          j        | dd          }|j        dk    r|j        S n# t           j        $ r Y d S w xY wd S )Nr  Tr  r  r   r   r   r   r!  )r%  r   s     r0   r  z WellKnownSkillSource._fetch_text  sa    	9S"tDDDD3&&y  ' 	 	 	44	ts   (, ??r  c                 8    d|                      d           d| S )Nr  r<   )r   )r  r   s     r0   r  z%WellKnownSkillSource._wrap_identifier  s$    @X__S11@@J@@@r/   Nr   )r&   r'   r(   r)   r  r*   r   r   r   r   r   r   r   r   r2   r   r  r-   r  r  r  r~  r  r  r.   r/   r0   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r/   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.
    r9   c                     dS )Nr%  r.   rh   s    r0   r   zUrlSource.source_id  s    ur/   r   c                     dS r  r.   r   s     r0   r   zUrlSource.trust_level_for  r  r/   r   r   r   c                     g S ru   r.   r   s      r0   r   zUrlSource.search  s    	r/   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/r<   r  z.md)
rH   r*   rJ   r   rM   r   rP  r   r!   rI   )ri   r   identr!   s       r0   _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   r>   r4   r   r$   c                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z%UrlSource.inspect.<locals>.<listcomp>  s    555qCFF555r/   r%  r   r%  awaiting_name)r   r   r   r   r   r!   r$   r%   )r  rJ   r  r   r  _resolve_skill_namer*   r   rH   r-   r,   r   )ri   r   r%  r   r	  r   r   r$   r4   r
  r  s              r0   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##==	
 	
 	
 		
r/   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 )Nr>   z+URL skill %s produced unsafe skill name: %rr   r%  r   r  r  )r  rJ   r  r   r  r  r[   rI   r   r;  r2   )ri   r   r%  r   r	  r   r   s          r0   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r%  c                     	 t          j        | dd          }|j        dk    r|j        S n9# t           j        $ r'}t
                              d| |           Y d }~d S d }~ww xY wd S )Nr  Tr  r  z!UrlSource fetch failed for %s: %s)r   r   r   r   r!  r   r   )r%  r   excs      r0   r  zUrlSource._fetch_text  s    	9S"tDDDD3&&y  ' 	 	 	LL<c3GGG44444	 ts   (, A"AA"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)rH   r*   rJ   r   r   _VALID_NAME_RErx  )clsr   	candidates      r0   _is_valid_skill_namezUrlSource._is_valid_skill_name&  si    $$$ 	5JJLL&&((	 	I)VVV5C&,,Y77888r/   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 r.   r.   )rA   ps     r0   rC   z1UrlSource._resolve_skill_name.<locals>.<listcomp>B  s    111qq1111r/   r<   r   zskill.mdr   z\.md$r>   )flags)rH   r-   r   r*   r  rJ   r   r!   rI   r   r   rR   rP   sub
IGNORECASE)r  r	  r%  fm_namer!   rL   r  s          r0   r  zUrlSource._resolve_skill_name/  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&   r'   r(   r)   r*   r   r   r   r   r   r   r   r  r   r   r2   r   r~  r  rP   compiler  classmethodr  r-   r  r.   r/   r0   r  r    s       
 
3    # #     C  T)_    ,3 ,4 , , , ,.
# 
(9*= 
 
 
 
:
 
(= 
 
 
 
B  #    \  RZ 566N9 9$ 9 9 9 [9 T      [  r/   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   ri   r   s     r0   rj   zSkillsShSource.__init__j  s    	"---r/   r9   c                     dS )N	skills-shr.   rh   s    r0   r   zSkillsShSource.source_idn  r  r/   r   c                 \    | j                             |                     |                    S ru   )r   r   _normalize_identifierr   s     r0   r   zSkillsShSource.trust_level_forq  s&    {**4+E+Ej+Q+QRRRr/   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  rA   rB  s     r0   rC   z)SkillsShSource.search.<locals>.<listcomp>{  &    999$I%%%%999r/   )qr   r  r2  r   r  r   c                 ,    g | ]}t          |          S r.   _skill_meta_to_dictr  s     r0   rC   z)SkillsShSource.search.<locals>.<listcomp>  !    &U&U&UT':4'@'@&U&U&Ur/   )rJ   _featured_skillsr  r  r   r  r  r   r   
SEARCH_URLr   r   r!  r`  rH   r-   r,   _meta_from_search_itemr   r  )ri   r   r   r#  r$  r   rc  rJ  r   rB  r+  s              r0   r   zSkillsShSource.searcht  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   r4   update_detail_to_metadata_discover_identifier)ri   r   	canonicalr  r  bundleresolveds          r0   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tr/   c                     |                      |          }|                     |          }|                     ||          }|r|                     |||          S d S Nr  )r  r  _resolve_github_meta_finalize_inspect_meta)ri   r   r  r  r+  s        r0   r   zSkillsShSource.inspect  sf    ..z::	((33((6(BB 	H..tYGGGtr/   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     r0   rC   z3SkillsShSource._featured_skills.<locals>.<listcomp>  r  r/   r  r   r  idr<   r   r   r   rG   r   zFeatured on skills.sh from r  )r   r   r   r   r   r    r!   c                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z3SkillsShSource._featured_skills.<locals>.<listcomp>  r  r/   )r  r   r   BASE_URLr   r!  set_SKILL_LINK_REfinditerr   groupaddr   rR   r   r   r  r   r   r  )ri   r   r#  r$  r   r   r   rx  r  rL   r    r   s               r0   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&rB  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   skillIdr<   r   r   r   rG   installs    · ,z	 installsr>   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%   )rH   r-   r   r*   countr   rR   r   r   r  r   r   r  )ri   rB  r  r    r   rL   r	  installs_labels           r0   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 
 
 
 	
r/   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_r<   r  r  r  )r  r  r   r  r  rH   r-   r   r   r  r   r!  _parse_detail_pager   r  )ri   r   r#  r$  r   r  s         r0   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 )Nr<   r   r   r   rG   r    r>   r   r  )
r    install_skill
page_title
body_titlebody_summaryweekly_installsinstall_commandr  r  security_audits)r   rR   _INSTALL_CMD_REr   r  rJ   _extract_repo_slug_extract_first_match_PAGE_H1_RE_PROSE_H1_RE_PROSE_P_RE_extract_weekly_installs_extract_security_auditsr  )ri   r   r  rL   default_reposkill_tokenr    r  r  install_match
repo_valuer  r  r  r  r  s                   r0   r  z!SkillsShSource._parse_detail_page  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.
 
 	
r/   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 )Nr<   r   r   r   rG   r    r   r  r>   r  r  )r   z.agents/skills/z.claude/skills/r  r  r  Tr  r  r  r  r   r  )r   z.agentsz.claude)r   rR   rH   r-   r   r   r   r   r   _matches_skill_tokensr   rS  r   r   rs   r   r   r,   rM   r   )ri   r   r  rL   r%  r    r&  tokens
base_paths	base_pathr   r+  tree_resultroot_urlr   r&  r'  r(  	direct_ids                      r0   r  z#SkillsShSource._discover_identifier6  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  )ri   r   r  r  r+  r  s         r0   r  z#SkillsShSource._resolve_github_meta{  s    44Z@@ 	 	I;&&y11D  ,,Z,GG 	1;&&x000tr/   r+  r  c                    d|_         |                     |          |_        |                     |          |_        t          |j                  }|                    |                     ||                     ||_        t          |t
                    rO|
                    d          }|
                    d          }|r||_        n|j        r|r|j         d| d|_        |S )Nr  r  r  r
  z weekly installs on skills.sh)r   r  r   r   r   r-   r%   r  r  rH   r   r   )ri   r+  r  r  merged_extrar  r  s          r0   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 r/   skill_tokensc                    t                      }|                    |                     |j                             |                    |                     |j                             |                    |                     |j        r!|j                            dd          d         nd                      |D ]}|                     |          }||z  r dS  dS )Nr<   r   r   TF)r  r  _token_variantsr   r!   r   r   )r  r+  r4  
candidatesrq   variantss         r0   r*  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ur/   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 )	Nr<   r   z[^a-z0-9/_-]+-r>   @r  c                     h | ]}||S r.   r.   )rA   vs     r0   	<setcomp>z1SkillsShSource._token_variants.<locals>.<setcomp>  s    )))aq))))r/   )r  r  _strip_htmlr*   rJ   r   r   rP   r  lstriprK   )r9  plainbase	sanitizedsanitized_base
slash_tailslash_tail_cleanr8  s           r0   r6  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))))r/   r(  c                 &   |                                  } |                     d          r| t          d          d          } |                      d          } |                     d          }t          |          dk    r|d          d|d          S d S )Nr  r<   r   r   rG   )rJ   rM   rR   r   )r(  rL   s     r0   r  z!SkillsShSource._extract_repo_slug  s    %%''
  !677 	A#C(=$>$>$?$?@J%%c**
  %%u::??Ah++q+++tr/   patternr   c                     |                      |          }|sd S t          d |                                D             d           }|d S t                              |                                          pd S )Nc              3      K   | ]}||V  	d S ru   r.   )rA   r  s     r0   rF   z6SkillsShSource._extract_first_match.<locals>.<genexpr>  s'      AA5AeAAAAAAr/   )r   nextgroupsr  r@  rJ   )rI  r   rx  r9  s       r0   r  z#SkillsShSource._extract_first_match  su    t$$ 	4AAAAA4HH=4))%006688@D@r/   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 )
Nr<   r   r   rG   r>   r  r  r  )r  r  r  r  r  )r   rR   r  rH   r-   r   )ri   r  r  rL   r    r4   rV  r9  s           r0   r  z"SkillsShSource._detail_to_metadata  s    Q''+.u::??%(''U1X'''T]88Y88
  	@#?#?#?HZ fd## 	*j * *

3 *$)HSMr/   c                 r    t           j                            |           }|sd S |                    d          S )Nr  )r  _WEEKLY_INSTALLS_REr   r  )r  rx  s     r0   r#  z'SkillsShSource._extract_weekly_installs  s7    299$?? 	4{{7###r/   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)rG   )findrP   r   r  r  title)r  r   auditsauditidxwindowrx  s          r0   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r/   c                 .    t          j        dd|           S )Nz<[^>]+>r>   )rP   r  )r9  s    r0   r@  zSkillsShSource._strip_html  s    vj"e,,,r/   c                 p    d}|D ]0}|                      |          r| t          |          d          c S 1| S )N)
skills-sh/
skills.sh/z	skils-sh/z	skils.sh/)rM   rR   )r   prefix_aliasesr)  s      r0   r  z$SkillsShSource._normalize_identifier  sV    
 % 	0 	0F$$V,, 0!#f++,,////0r/   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 )	Nr<   r   r   r   rG   /skills/z/.agents/skills/z/.claude/skills/)r   rR   rA  r  r  r   )r   rL   r    r   r7  r   dedupedr  s           r0   r  z%SkillsShSource._candidate_identifiers  s      a((u::>><(''U1X''1X__S))
""j""))Z))11Z1111Z11	

 uu# 	* 	*I$$###y)))r/   c                     d|  S )Nr]  r.   )r   s    r0   r  zSkillsShSource._wrap_identifier#  s    (J(((r/   r   ru   )7r&   r'   r(   r)   r  r  rP   r  r  r  r  DOTALLr   r!  r"  rP  rc   rj   r*   r   r   r   r   r   r   r   r2   r   r   r  r-   r  r  r  r  r  r  r  r   r*  r~  r  r6  r  Patternr  r   r   r  r#  r$  r@  r  r  r  r.   r/   r0   r  r  T  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 ) ) ) \) ) )r/   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/v1r9   c                     dS )Nclawhubr.   rh   s    r0   r   zClawHubSource.source_id5      yr/   r   c                     dS r  r.   r   s     r0   r   zClawHubSource.trust_level_for8  r  r/   r$   c                     t          | t                    rd | D             S t          | t                    rd | D             S g S )Nc                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z1ClawHubSource._normalize_tags.<locals>.<listcomp>>  s    )))qCFF)))r/   c                 R    g | ]$}t          |          d k    t          |          %S )latestr  )rA   ks     r0   rC   z1ClawHubSource._normalize_tags.<locals>.<listcomp>@  s.    ???qCFFh,>,>CFF,>,>,>r/   )rH   r,   r-   )r$   s    r0   _normalize_tagszClawHubSource._normalize_tags;  sT    dD!! 	*))D))))dD!! 	@??D????	r/   rc  c                     t          | t                    sd S |                     d          }t          |t                    r1t          |          }|                     d          }|	d|vr||d<   |S | S )Nr   latestVersion)rH   r-   r   )rc  nestedmergedlatest_versions       r0   _coerce_skill_payloadz#ClawHubSource._coerce_skill_payloadC  s|    $%% 	4'""fd## 	&\\F!XXo66N)oV.K.K*8'Mr/   r   c                 d    d t          j        d|                                           D             S )Nc                     g | ]}||S r.   r.   )rA   terms     r0   rC   z.ClawHubSource._query_terms.<locals>.<listcomp>R  s    PPP4PPPPr/   z
[^a-z0-9]+)rP   r   r   )r   s    r0   _query_termszClawHubSource._query_termsP  s*    PP-!G!GPPPPr/   r+  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 )NrG   r>   r   r         }   x   _   Z   F   A   (   #   r   r     r   )	rJ   r   r   r   r   rS   r{  rM   rR   )r  r   r+  
query_normr   r   r   normalized_identifiernormalized_namequery_termsidentifier_terms
name_termsscorerz  s                 r0   _search_scorezClawHubSource._search_scoreT  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!!{""
r/   r   c                     t                      }g }| D ]Q}|j        p|j                                        }||v r'|                    |           |                    |           R|S ru   )r  r   r   r   r  r   )r   r   rb  r   rV  s        r0   _dedupe_resultszClawHubSource._dedupe_results  so    #% 	# 	#F$3::<<Cd{{HHSMMMNN6""""r/   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 )Nr<   r   z[A-Za-z0-9][A-Za-z0-9._-]*r;  r   z-agentz-skillz-toolz
-assistantz	-playbook)rJ   r   r{  rP   rQ   r   rS   rR   r   r  r  r   )	ri   r   slugr  r7  	base_slugr   r  r+  s	            r0   _exact_slug_metazClawHubSource._exact_slug_meta  ss   {{}}""3''+''.. "
 	$BL!>EE 	$d### 	---I;1$$!! ((( ((( ''' ,,, +++#     !!),,,# 	 	ID  HHY<<	**D  tr/   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  rA   r+  r  ri   s     r0   rC   z:ClawHubSource._finalize_search_results.<locals>.<listcomp>  s4    YYYT0B0B:t0T0TWX0X0XD0X0X0Xr/   c                                          |            | j                                        | j                                        fS ru   )r  r   r   r   )r+  r  ri   s    r0   <lambda>z8ClawHubSource._finalize_search_results.<locals>.<lambda>  s@    ##J555	!!%%'' r/   )rV  c                 H    g | ]}                     |          d k    |S r  r  r  s     r0   rC   z:ClawHubSource._finalize_search_results.<locals>.<listcomp>  s5    ```T5G5G
TX5Y5Y]_5_5_5_5_5_r/   z[A-Za-z0-9][A-Za-z0-9._/-]*)rJ   r  sortr  rP   rQ   )ri   r   r   r   filteredexactr  s   `     @r0   _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4r/   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  r  s     r0   rC   z(ClawHubSource.search.<locals>.<listcomp>  s"    000AQ000r/   /skills)r   r   r  r  r  rJ  r  displayNamer   summaryr   r>   r$   ri  r   r   r   r   r   r   r$   c                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z(ClawHubSource.search.<locals>.<listcomp>  s!    &U&U&U!':1'='=&U&U&Ur/   )rJ   r{  rR   r  _search_catalogr  r  r   r  r  r  r   r   r  r   r   r!  r`  rH   r-   r,   rq  r   r   r  )ri   r   r   r  directr   r#  r$  r   rc  skills_datarB  r  display_namer  r$   final_resultss                    r0   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 )Nr<   r   ra  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 contentri  r   r   )r   	_get_jsonr  rH   r-   _resolve_latest_versionr   r;  _download_zip_extract_filesr   r2   )ri   r   r  
skill_datarv  r3   version_datart  s           r0   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#
 
 
 	
r/   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 )Nr<   r   ra  r$   r  r   r  r  r   r>   ri  r   r  )	r   rw  r  r  rH   r-   rq  r   r   )ri   r   r  rc  r$   s        r0   r   zClawHubSource.inspect5  s   $$R())$..DM9Y9YSW9Y9Y*Z*Z[[$%% 	4##DHHVR$8$899-((XDHHV,<,<X@P@PXTX++Ltxx/F/FL"xx''/4#
 
 
 	
r/   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  r  s     r0   rC   z1ClawHubSource._search_catalog.<locals>.<listcomp>J  r  r/   c                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z1ClawHubSource._search_catalog.<locals>.<listcomp>Q  !    &O&O&O!':1'='=&O&O&Or/   )r  r  r   r  r  _load_catalog_indexr  r  )ri   r   r   r#  r$  catalogr   s          r0   r  zClawHubSource._search_catalogF  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r/   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  r  s     r0   rC   z5ClawHubSource._load_catalog_index.<locals>.<listcomp>X  r  r/   2   r   r  cursorr  r1  r  rJ  r  r  r   r  r   r>   r$   ri  r   r  
nextCursorc                 ,    g | ]}t          |          S r.   r  r  s     r0   rC   z5ClawHubSource._load_catalog_index.<locals>.<listcomp>  r  r/   )r  r  ranger   r   r  r   r   r!  r`  rH   r-   r,   r*   r  rq  r   r   r  )ri   r#  r$  r  r   r   	max_pagesr  r2  r   rc  rJ  rB  r  r  r  r$   s                    r0   r  z!ClawHubSource._load_catalog_indexT  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r  r%  r   c                     	 t          j        ||          }|j        dk    rd S |                                S # t           j        t          j        f$ r Y d S w xY w)Nr  r  )r   r   r   r   r!  r`  )ri   r%  r   r   s       r0   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 )Nrs  r  r$   ro  ra  z	/versionsr   )r   rH   r-   r*   r  r  r,   )	ri   r  r  ro  r  r$   
latest_tagversions_datafirsts	            r0   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tr/   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 )
Nr3   c                 D    i | ]\  }}t          |t                    ||S r.   )rH   r*   )rA   rp  r>  s      r0   
<dictcomp>z0ClawHubSource._extract_files.<locals>.<dictcomp>  s-    MMMTQ*Q:L:LMAqMMMr/   r!   r   r  rawUrldownloadUrlr%  http)r   rH   r-   rJ  r,   r*   rM   r  )	ri   r  r3   	file_list	file_metafnameinline_contentraw_urlr  s	            r0   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r/   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  r1  T)r2  r   r  i  zretry-after5r   r  zEClawHub download rate-limited for %s, retrying in %ds (attempt %d/%d)rG   r  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)utf-8z!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zipfiler  r   r   r  r   r   rr   rI   	TypeErrorminr   r   r~   sleepZipFileBytesIOr  infolistis_dirra   filename	file_sizereaddecodeUnicodeDecodeErrorKeyError
BadZipFiler;  r!  )ri   r  r  r  r  r3   max_retriesattemptr   retry_afterzfinfor   rT   r  s                  r0   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                     	 t          j        |d          }|j        dk    r|j        S n# t           j        $ r Y d S w xY wd S )Nr  r  r  r  )ri   r%  r   s      r0   r  zClawHubSource._fetch_text  s_    	9S"---D3&&y  ' 	 	 	44	ts   '+ >>Nr   r  )"r&   r'   r(   r)   r  r*   r   r   r~  r   r   rq  r   r   rw  r{  r  r   r   r  r  r  r  r   r2   r   r   r  r  r  r  r  r  r  r.   r/   r0   rg  rg  ,  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}      r/   rg  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 ru   r  r  s     r0   rj   z ClaudeMarketplaceSource.__init__      			r/   r9   c                     dS )Nclaude-marketplacer.   rh   s    r0   r   z!ClaudeMarketplaceSource.source_id!  s    ##r/   r   c                     |                     dd          }t          |          dk    r|d          d|d          }|t          v rdS dS r   r   r   s       r0   r   z'ClaudeMarketplaceSource.trust_level_for$  sV      a((u::??Ah++q++D}$$ y{r/   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   r>   r   r   r   z./r<   r   r  )r   r   r   r   r   r    )r   KNOWN_MARKETPLACES_fetch_marketplace_indexr   rM   r   r   r   )ri   r   r   r   r   marketplace_repopluginspluginr   source_pathr   s              r0   r   zClaudeMarketplaceSource.search,  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r/   c                 l    t          | j                  }|                    |          }|rd|_        |S Nr  r  )r   r   r   r   )ri   r   r   r  s       r0   r   zClaudeMarketplaceSource.fetchH  s9    ty)))*%% 	10FMr/   c                     t          | j                  }|                    |          }|r!d|_        |                     |          |_        |S r  )r   r   r   r   r   r   )ri   r   r   r+  s       r0   r   zClaudeMarketplaceSource.inspectP  sP    ty)))zz*%% 	@.DK#33J??Dr/   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_r<   r  Nr  z)/contents/.claude-plugin/marketplace.jsonrl   rU  r  r   r  r  )rK   r  r   r   r   rs   r   r   r^  r   r!  r`  r  )ri   r    r#  r$  r%  r   rc  r  s           r0   r  z0ClaudeMarketplaceSource._fetch_marketplace_indexX  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&   r'   r(   r)   r  rc   rj   r*   r   r   r   r   r   r   r   r2   r   r   r-   r  r.   r/   r0   r  r    s,         	"
Z    $3 $ $ $ $# #     C  T)_    8 (=    # (9*=    S T$Z      r/   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.jsonr9   c                     dS )Nlobehubr.   rh   s    r0   r   zLobeHubSource.source_id~  rj  r/   r   c                     dS r  r.   r   s     r0   r   zLobeHubSource.trust_level_for  r  r/   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agentsr+  rV  r   r>   r   r$   r   r;  r  r  lobehub/r   r  )_fetch_indexr   rH   r-   r   r,   rS   rK   r   r   rR   )ri   r   r   r  r   r   r  agentr+  rV  descr$   r   r   s                 r0   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$$ % r/   c                     |                     d          r|                    dd          d         n|}|                     |          }|sd S |                     |          }t	          |d|idd| d          S )	Nr  r<   rG   r   r   r  r   r   )rM   r   _fetch_agent_convert_to_skill_mdr2   )ri   r   agent_id
agent_datar  s        r0   r   zLobeHubSource.fetch  s    3=3H3H3T3Td:##C++B//Zd&&x00
 	4,,Z88x(,(,,#
 
 
 	
r/   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  r<   rG   r   r  r   r+  r   r>   r  r   r$   r  )rM   r   r  rH   r-   r   r,   r   )ri   r   r  r  r  r  r+  s          r0   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 tr/   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_indexNr1  r  r  )	r  r   r   	INDEX_URLr   r   r!  r`  r  )ri   r#  r$  r   rc  s        r0   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/rX  r  r  r  zLobeHub agent fetch failed: %sN)r   r   r   r   r!  r`  r   r   )ri   r  r%  r   r   s        r0   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.r+  r   zlobehub-agentrV  r   r>   r$   config
systemRolerq  zname: zdescription: NrT  z	metadata:z	  hermes:z    tags: [, c              3   4   K   | ]}t          |          V  d S ru   r  r  s     r0   rF   z5LobeHubSource._convert_to_skill_md.<locals>.<genexpr>  s(      #=#=qCFF#=#=#=#=#=#=r/   ]z
  lobehub:z    source: lobehubz# z## Instructionsz(No system role defined)
z

)r   rH   r,   rS   )
r  r+  r   rV  r   r$   system_roletag_listfm_lines
body_liness
             r0   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Jr/   Nr   )r&   r'   r(   r)   r  r*   r   r   r   r   r   r   r   r2   r   r   r   r  r-   r  r~  r  r.   r/   r0   r  r  u  s^         =I3    # #    ! !C ! !T)_ ! ! ! !F
 
(= 
 
 
 
"# (9*=    .hsm    $	S 	Xd^ 	 	 	 	  K  K#  K  K  K \ K  K  Kr/   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_constantsr(  r	   __file__parent_optional_dir)ri   r(  s     r0   rj   zOptionalSkillSource.__init__	  sD    <<<<<<44NN!(+<<
 
r/   r9   c                     dS )Nofficialr.   rh   s    r0   r   zOptionalSkillSource.source_id%	  s    zr/   r   c                     dS )Nr   r.   r   s     r0   r   z#OptionalSkillSource.trust_level_for(	  rj  r/   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   rS   r$   r   rR   )ri   r   r   r   r   r+  r   s          r0   r   zOptionalSkillSource.search-	  s    #%kkmmNN$$ 	 	D IPP(8PP388DI;N;NPPVVXXJj((t$$$7||u$$ % r/   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/r<   rG   r   *r?   __pycache__z.pycr.  r   r   )rM   r   r,  resolver*   r_  rI   r  r  _find_skill_dirrglobis_filer   rL   suffixrelative_to
read_bytesr2   )
ri   r   relrR  r  r   r3   fr_   r   s
             r0   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 )Nr3  r<   rG   r   )rM   r   r  r1  r   )ri   r   r=  r   r+  s        r0   r   zOptionalSkillSource.inspectp	  s    .8.C.CK.P.P`jsA&&r**V`ZZQ''+
NN$$ 	 	DyJ&& 'tr/   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   )r,  r  r8  r+  r   )ri   r   r  s      r0   r7  z#OptionalSkillSource._find_skill_dir{	  sf    !((** 	4*00<< 	' 	'H#t++&&& ,tr/   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 )r?   N)rM   r@   s     r0   rF   z0OptionalSkillSource._scan_all.<locals>.<genexpr>	  s.      >>D4??3''>>>>>>r/   r  encodingr   r   r>   r4   r   r$   Nr  r.  r3  r   )r   r   r   r   r   r!   r$   )r,  r  sortedr8  r+  r;  rL   rO   r   r_  r  _parse_frontmatterr   r   rH   r-   r*   r   r   r,   )ri   r   r  r+  	rel_partsr  r	  r   r  r$   
meta_blockr
  r_   s                r0   r1  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rp  rr  rw  s       r0   rF  z&OptionalSkillSource._parse_frontmatter	  r{  r|  Nr   )r&   r'   r(   r)   rj   r*   r   r   r   r   r   r   r   r2   r   r   r	   r7  r1  r~  r-   rF  r.   r/   r0   r&  r&  	  s_        
 
 
3    # #    
 C  T)_    0
 0
(= 0
 0
 0
 0
h# (9*=    C HTN    '4	? ' ' ' 'R C D    \  r/   r&  rV  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.rX  NrY  )rV  ra  r[  s      r0   r  r  	  s     c===0J t  9;;&884z*..00111T)*   ttrb  rc  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.Tre  z.ignorez,# Exclude hub internals from search tools
*
rX  F)rh  defaultri  N)rZ  rj  HUB_DIRr   rk  r_  r   rl  r*   r   r   )rV  rc  ignore_filera  r   s        r0   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r+  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%   rP  rn  s    r0   r  r  	  sB     	'+o'			
 
 
r/   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 ru   r!   ri   r!   s     r0   rj   zHubLockFile.__init__	  r  r/   r9   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)NrG   )r  	installed)r!   r   r   r^  r   r`  r_  rh   s    r0   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)rc  Nc                     | j         j                            dd           | j                             t	          j        |dd          dz              d S )NTre  r   F)indentrh  r   r!   r+  rj  rk  r   rl  ri   rc  s     r0   savezHubLockFile.save
  sR    	td;;;	TZQUKKKdRSSSSSr/   r   r   r   r   scan_verdict
skill_hashinstall_pathr3   r4   c
                 @   |                                  }
||||||||	pi t          j        t          j                                                  t          j        t          j                                                  d
|
d         |<   |                     |
           d S )N)
r   r   r   r^  r   r`  r3   r4   installed_at
updated_atrW  )rX  r   r   r   utc	isoformatr]  )ri   r   r   r   r   r^  r_  r`  r3   r4   rc  s              r0   record_installzHubLockFile.record_install
  s     yy{{$&(&( B$L66@@BB",x|44>>@@#
 #
[$ 			$r/   c                     |                                  }|d                             |d            |                     |           d S NrW  )rX  popr]  ri   r   rc  s      r0   record_uninstallzHubLockFile.record_uninstall"
  s=    yy{{[dD)))		$r/   c                 `    |                                  }|d                             |          S rh  )rX  r   rj  s      r0   get_installedzHubLockFile.get_installed'
  s(    yy{{K $$T***r/   c                     |                                  }g }|d                                         D ]\  }}|                    d|i|           |S )NrW  r   )rX  rJ  r   )ri   rc  r   r   r'  s        r0   list_installedzHubLockFile.list_installed+
  s\    yy{{,2244 	3 	3KD%MM641512222r/   ru   )r&   r'   r(   r)   	LOCK_FILEr	   rj   r-   rX  r]  r*   r   r   r   r   rf  rk  rm  ro  r.   r/   r0   rR  rR  	  sx       VV$-  T    3d 3 3 3 3T T$ T T T T .2   	
     Cy 4S>* 
   6S T    
+# +(4. + + + +T
      r/   rR  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 ru   rT  rU  s     r0   rj   zTapsManager.__init__:
  r  r/   r9   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   r^  r   r   r`  r_  r\  s     r0   rX  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 )NTre  r   r   )rZ  r   r[  )ri   r   s     r0   r]  zTapsManager.saveF
  sT    	td;;;	TZqAAADHIIIIIr/   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    Nr.   rA   r  r    s     r0   rF   z"TapsManager.add.<locals>.<genexpr>M
  s,      //QqyD //////r/   Fr   T)rX  rO   r   r]  )ri   r    r!   r   s    `  r0   r  zTapsManager.addJ
  sh    yy{{////$///// 	5T400111		$tr/   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    r.   rx  s     r0   rC   z&TapsManager.remove.<locals>.<listcomp>V
  s'    999!qyD'8'8A'8'8'8r/   FT)rX  rR   r]  )ri   r    r   new_tapss    `  r0   removezTapsManager.removeS
  s\    yy{{9999t999x==CII%%5		(tr/   c                 *    |                                  S ru   )rX  rh   s    r0   	list_tapszTapsManager.list_taps\
  s    yy{{r/   )r   )r&   r'   r(   r)   	TAPS_FILEr	   rj   r   r-   rX  r]  r*   r   r  r|  r~  r.   r/   r0   rr  rr  7
  s        DD$-  T    d4j    Jd J J J J J  3 t    3 4    4:      r/   rr  r>   actionr   r   r   verdictr%   c                    t           j                            dd           t          j        t
          j                                      d          }|| || d| |g}|r|                    |           d	                    |          dz   }	 t          t           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.Tre  z%Y-%m-%dT%H:%M:%SZ:r   r   aNzCould not write audit log: %s)	AUDIT_LOGr+  rj  r   r   r   rd  strftimer   rS   openwriter_  r   r   )r  r   r   r   r  r%   	timestamprL   liner>  r   s              r0   append_audit_logr  d
  sW    4$777X\**334HIII
v,E,E,E,EwOE U88E??T!D9)S!! 	QGGDMMM	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 9 9 94a8888888889s<   C $C:C CC CC 
DC??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.Tre  )rg  z {"version": 1, "installed": {}}
z{"taps": []}
N)
rM  rj  QUARANTINE_DIRrZ  rp  r   rk  r  touchr  r.   r/   r0   ensure_hub_dirsr  x
  s    MM$M...$'''4((( B@AAA  /-...../ /r/   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)rf  r<   re  r  rC  )r  r[   r   r3   rJ  ra   r   r  r   shutilrmtreerj  joinpathr   r+  rH   r5   write_bytesrk  )r  r   validated_filesr_   file_contentr  dest	file_dests           r0   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!!,////   @@@@Kr/   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.r>   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  Tre  )	r   r   r   r   r^  r_  r`  r3   r4   INSTALL) r[   r^   r6  r  is_relative_torI   
SKILLS_DIRr   r  r  r[  st_sizer   r;  r_  r+  rj  mover*   rR  rf  r   r   r   r  r   r;  r,   r3   keysr4   r  )r  r   r\   r  r  safe_skill_namesafe_categoryquarantine_resolvedquarantine_rootinstall_dirr  
skill_sizelocks                r0   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)r`  	UNINSTALLr   r   zn/auser_requestTzUninstalled 'z' from )rR  rm  r  r   r  r  rk  r  )r   r  r'  r`  s       r0   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Kr/   c                     t          j                    }t          | j                  D ]5}|                    | j        |                             d                     6d|                                dd          S )z;Compute a deterministic hash for an in-memory skill bundle.r  zsha256:N   )r  sha256rE  r3   r  r   r  )r  hr_   s      r0   bundle_content_hashr  
  sq    A6<(( 9 9	h'..w778888)Q[[]]3B3')))r/   source_namec                 f    ddi}|                     ||          }|                                 |k    S )Nr  r  )r   r   )r   r  aliasesrU   s       r0   _source_matchesr  
  s:    [G [+66J++r/   )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 rZ   )r   )rA   r'  r   s     r0   rC   z+check_for_skill_updates.<locals>.<listcomp>  s/    OOOuUYYv5F5F$5N5NU5N5N5Nr/   Nr  r   r>   r   c                 4    g | ]}t          |          |S r.   )r  )rA   srcr  s     r0   rC   z+check_for_skill_updates.<locals>.<listcomp>  s(    YYYSsK7X7XYSYYYr/   r   unavailable)r   r   r   statusr   
up_to_dateupdate_available)r   r   r   r  current_hashlatest_hashr  )rR  ro  create_source_routerr   r   r   r   r  )r   r  r  r   rW  r   r'  r   candidate_sourcesr  r  r  r  r  r  s   `             @r0   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.
    r  Tr  r  zHermes index fetch returned %dzHermes index fetch failed: %sNr   re  )HERMES_INDEX_CACHE_FILEr   r~   r[  r\  HERMES_INDEX_TTLr   r^  r   r_  r`  r   r   HERMES_INDEX_URLr   r   r   _load_stale_index_cacher!  rH   r-   r+  rj  rk  rl  )ager   rc  r   s       r0   _load_hermes_indexr  A  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   r^  r   r_  r`  r.   r/   r0   r  r  j  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     r0   rj   zHermesIndexSource.__init__  s$    &*	 04r/   r9   c                 V    | j         st                      | _        d| _         | j        pi S )NT)r  r  r  rh   s    r0   _ensure_loadedz HermesIndexSource._ensure_loaded  s-    | 	 ,..DKDL{ b r/   c                 R    | j         t          | j                  | _         | j         S r  )r  r   r   rh   s    r0   _get_githubzHermesIndexSource._get_github  s&    <'TY777DL|r/   c                     dS )Nhermes-indexr.   rh   s    r0   r   zHermesIndexSource.source_id  s    ~r/   c                 n    |                                  }t          |                    d                    S )z+Whether the index is loaded and has skills.r   )r  r   r   )ri   r  s     r0   is_availablezHermesIndexSource.is_available  s/     ##%%EIIh''(((r/   r   c                     |                                  }|                    dg           D ]3}|                    d          |k    r|                    dd          c S 4dS )Nr   r   r   r   )r  r   )ri   r   r  r   s       r0   r   z!HermesIndexSource.trust_level_for  sk    ##%%YYx,, 	= 	=Eyy&&*44yy<<<<< 5{r/   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 r.   )_to_metar  s     r0   rC   z,HermesIndexSource.search.<locals>.<listcomp>  s%    ===DMM!$$===r/   Nr   r>   r   r   r$   )r  r   rJ   r   rS   r   r  rR   )	ri   r   r   r  r   r   r   r  r   s	   `        r0   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r/   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    r>   r!   r<   )r  _find_entryr   r  r   r   r   )	ri   r   r  r'  r  r  r    r!   	github_ids	            r0   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$.!tr/   c                     |                                  }|                     ||          }|r|                     |          S dS )z0Return metadata from the index.  Zero API calls.N)r  r  r  )ri   r   r  r'  s       r0   r   zHermesIndexSource.inspect  sG    ##%%  U33 	(=='''tr/   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   )r]  r^  r3  zgithub/zclawhub/Nr>   )r   rM   rR   )	ri   r   r  r   r  rU   r)  sidstored_normalizeds	            r0   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.. / tr/   r'  c                    t          |                     dd          |                     dd          |                     dd          |                     dd          |                     dd          |                     d	          |                     d
          |                     dg           |                     di           	  	        S )Nr   r>   r   r   r  r   r   r   r    r!   r$   r%   rP  )r   r   )r'  s    r0   r  zHermesIndexSource._to_meta  s    62&&		-4499X~66yyr22		-==6""6""62&&))GR((

 

 

 
	
r/   Nr   )r&   r'   r(   r)   rc   rj   r-   r  r   r  r*   r   r}  r   r  r   r   r   r   r   r   r2   r   r   r  r~  r  r.   r/   r0   r  r  t  s       	 	4Z 4 4 4 4! ! ! ! !\    
3     )d ) ) ) X)
# #     C  T)_    *! !(= ! ! ! !F# (9*=    c $ 8D>    < 
 
 
 
 
 \
 
 
r/   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   )rc   rr  r~  r&  r  r  r  r  r   rg  r  r  )r   taps_mgrr   r  s       r0   r  r    s    
 |||}}H##%%J 	t$$$D!!!$:666T***
"G Nr/   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   r   r   )r  r   r   r   s       r0   _search_one_sourcer  *  s    #}}

5
 > >>> # # #/!DDD}}"""""""#s   *- 
BAB :B Br   r1  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   ri  r  r  r  r  r   r  r  Tr.     )max_workersr  r  c                 H    g | ]}|                                 |         S r.   )done)rA   r>  futuress     r0   rC   z+parallel_search_sources.<locals>.<listcomp>y  s:        16688
  r/   z'Skills browse timed out waiting for: %sr  N)concurrent.futuresr  r  	frozensetr   getattrr   r  rR   r   submitr  r   r   r   TimeoutErrorr   r   rS   )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                       @r0   parallel_search_sourcesr  5  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.r1  )r   r  r  r   rG   r   r   N)r  r   r   r   r,   r   )
r   r  r  r   r  r  _TRUST_RANKr   r   rb  s
             r0   unified_searchr    s     0#	  KA  AA>>K!#D  6DLL__Q]A..afAY[\1]1]]]DL4;;==!!G6E6?r/   )r>   )r9   Nru   )r>   Nr   r1  N)r   r   )^r)   r  r   loggingr   rP   r  r   r~   abcr   r   dataclassesr   r   r   r   pathlibr	   r
   r)  r   typingr   r   r   r   r   r   urllib.parser   r   r   rt  tools.skills_guardr   r   r   	getLoggerr&   r   HERMES_HOMEr  rM  rp  r  r  r  rZ  r]  r   r2   r*   r   rV   r[   r^   ra   rc   r   r   r  r  r  rg  r  r  r&  r  r  r-   r  rR  rr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  floatr  r  r.   r/   r0   <module>r     sx	       				 				       # # # # # # # # ( ( ( ( ( ( ( ( ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' , , , , , , : : : : : : : : : : : : : : : : - - - - - - - -            
	8	$	$ o8#

v
k!	<'k!	k!	M)  
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^ ^ ^ ^ ^ ^t t t t t t t tv    #   @a a a a a; a a aP_A _A _A _A _A; _A _A _ALc c c c c c c cTQ) Q) Q) Q) Q)[ Q) Q) Q)p` ` ` ` `K ` ` `N[ [ [ [ [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	?     r/   