
    `gj|                    |   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 dd	lmZ dd
lmZmZmZmZmZm Z  ddl!m"Z"m#Z#m$Z$m%Z%m&Z& ddl'Z'ddl(Z(ddl)m*Z*m+Z+m,Z, ddl-m.Z. ddl/m0Z0  ejb                  e2      Z3dZ4de5fdZ6defdZ7defdZ8defdZ9defdZ:defdZ;defdZ<defdZ=defdZ>e7e8e9e:e;e<e=e>dZ?de5fdZ@h dZAdZBe G d d              ZCe G d! d"             ZD eEh d#      ZF ej                  d$ej                        ZI ej                  d%      ZJd&e5deeKe5      fd'ZLd(eDde5fd)ZMd*e5d+e5d,eNde5fd-ZOde5de5fd.ZPd/e5de5fd0ZQd1e5d2e5de5fd3ZRd4edeNfd5ZSd1e5d2e5defd6ZTd7d8d9e5d:eUdee'j                     fd;ZWd<e5de5fd=ZX G d> d?      ZY G d@ dAe
      ZZdBdCdDdEdFdGdHdIZ[dJe5dee5   fdKZ\ eEdL e[j                         D              Z^dMed    dNe5ded    fdOZ_ G dP dQeZ      Z` G dR dSeZ      Za G dT dUeZ      Zb G dV dWeZ      Zc G dX dYeZ      Zd G dZ d[eZ      Ze G d\ d]eZ      Zf G d^ d_eZ      Zg G d` daeZ      Zhdbe5dee   fdcZidbe5ddeddfdeZjdfeCdekfdgZl G dh di      Zm G dj dk      Zn	 ddle5d2e5dme5dne5doe5dpe5ddfdqZoddrZpd(eDdefdsZq	 ddted2e5d/e5d(eDdue*dveee5ef      defdwZrd2e5deeNe5f   fdxZsd(eDde5fdyZtdmeZdze5deNfd{Zu	 ddddd|dee5   d}eem   d~eeeZ      deeY   deek   f
dZvdZwdZxdefdZydeek   fdZzdeek   fdZ{ G d deZ      Z|ddeeY   deeZ   fdZ}deZde5deUdee5eeC   f   fdZ~	 	 	 	 	 dd~eeZ   de5deee5eUf      de5dedee   deeeC   ee5eUf   ee5   f   fdZ	 dde5d~eeZ   de5deUdeeC   f
dZy)u  
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)windows_hide_flags)is_excluded_skill_path)AnyDictListOptionalTupleUnion)unquoteurljoinurlparseurlsplit
urlunparse)
ScanResultcontent_hashTRUSTED_REPOS)is_safe_url)check_website_accessi  namec                 4    t               j                  |       S N)globalsgetr   s    C/root/.hermes/venv/lib/python3.12/site-packages/tools/skills_hub.py	_overrider&   ;   s    9==    returnc                      t               S r!   r    r'   r%   _hermes_homer+   ?   s    r'   c                  L    t        d      } | t        |       S t               dz  S )N
SKILLS_DIRskills)r&   r	   r+   forceds    r%   _skills_dirr1   C   s'    |$F!-4<L<>H3LLr'   c                  L    t        d      } | t        |       S t               dz  S )NHUB_DIRz.hub)r&   r	   r1   r/   s    r%   _hub_dirr4   H   s'    y!F!-4<I;=63IIr'   c                  L    t        d      } | t        |       S t               dz  S )N	LOCK_FILEz	lock.jsonr&   r	   r4   r/   s    r%   
_lock_filer8   M   '    {#F!-4<K8:3KKr'   c                  L    t        d      } | t        |       S t               dz  S )NQUARANTINE_DIR
quarantiner7   r/   s    r%   _quarantine_dirr=   R   s(    '(F!-4<L8:3LLr'   c                  L    t        d      } | t        |       S t               dz  S )N	AUDIT_LOGz	audit.logr7   r/   s    r%   
_audit_logr@   W   r9   r'   c                  L    t        d      } | t        |       S t               dz  S )N	TAPS_FILEz	taps.jsonr7   r/   s    r%   
_taps_filerC   \   r9   r'   c                  L    t        d      } | t        |       S t               dz  S )NINDEX_CACHE_DIRzindex-cacher7   r/   s    r%   _index_cache_dirrF   a   s(    ()F!-4<M8:3MMr'   )HERMES_HOMEr-   r3   r6   r;   r?   rB   rE   c                 h    t         j                  |       }| |       S t        dt        d|       )zResolve legacy path constants dynamically (PEP 562) so they reflect the
    active profile override; a test's patch.object-set real attribute shadows it.zmodule z has no attribute )_DYNAMIC_PATH_RESOLVERSr#   AttributeError__name__)r   resolvers     r%   __getattr__rM   r   s;     '**40Hz
78,.@I
JJr'   >   -  .  /  3  4     c                       e Zd ZU dZeed<   eed<   eed<   eed<   eed<   dZee   ed<   dZee   ed	<    e	e

      Zee   ed<    e	e
      Zeeef   ed<   y)	SkillMetaz,Minimal metadata returned by search results.r   descriptionsource
identifiertrust_levelNrepopathdefault_factorytagsextra)rK   
__module____qualname____doc__str__annotations__rZ   r   r[   r   listr^   r   dictr_   r   r   r*   r'   r%   rU   rU      sf    6
IKOD(3-D(3-D1D$s)1!$7E4S>7r'   rU   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<   y	)
SkillBundlez>A downloaded skill ready for quarantine/scanning/installation.r   filesrW   rX   rY   r\   metadataN)rK   r`   ra   rb   rc   rd   r   r   bytesr   rf   rj   r   r*   r'   r%   rh   rh      sJ    H
IU3:&&''KO$T:Hd38n:r'   rh   >   assetsscriptsexamples	templates
referenceszX(?:\]\(|`|(?:^|[\s\"']))((?:references|templates|scripts|assets|examples)/[^\s)`\"'<>]+)zO(?:references|templates|scripts|assets|examples)/(?:[^\s)`\"'<>]*/)?\.\.(?:/|$)skill_mdc                    | j                  dd      }t        j                  |      ryt               }t        j                  |      D ]w  }t        t        |j                  d      j                  d            j                        }	 t        |      }|j                  dd      d   t        v sg|j                  |       y |S # t        $ r Y  yw xY w)zBExtract safe referenced paths; return None on a traversal attempt.\/N   z.,;:r   )replace_SUSPICIOUS_LOCAL_REF_REsearchset_LOCAL_LINK_REfinditerr   r   grouprstripr[   _validate_bundle_rel_path
ValueErrorsplit_ALLOWED_SUPPORT_DIRSadd)rq   
normalizedpathsmatchrawsafes         r%   _referenced_support_pathsr      s    !!$,J&&z2eE((4 hu{{1~44V<=BBC	,S1D ::c1a $99IIdO L	  		s   C	CCbundlec                 f   | j                   j                  d      xs | j                   j                  d      }|rt        |      S | j                  dk(  rR| j                  j                  dd      }t        |      dk\  r(t        |      dk(  rd|d    nd}d	|d
    d|d    | S | j                  S )z<Best available human-facing immutable-source provenance URL.
source_urlurlgithubrt         z/tree/main/ https://github.com/r   ru   )rj   r#   rc   rW   rX   r   len)r   explicitpartssuffixs       r%   source_url_for_bundler      s    ""<0NFOO4G4G4NH8}}} !!''Q/u:?14Uq{58*-bF(q
!E!H:fXFFr'   
path_value
field_nameallow_nestedc                h   t        | t              st        d| d      | j                         }|st        d| d      |j	                  dd      }t        |      }|j                  D cg c]	  }|dvs| }}|j                  d      s|j                         rt        d| d|        |rt        d |D              rt        d| d|        t        j                  d	|d
         rt        d| d|        |st        |      dk7  rt        d| d|        dj                  |      S c c}w )zDNormalize and validate bundle-controlled paths before touching disk.zUnsafe z: expected a stringz: empty pathrs   rt   >   r   .: c              3   &   K   | ]	  }|d k(    yw)z..Nr*   ).0parts     r%   	<genexpr>z)_normalize_bundle_path.<locals>.<genexpr>   s     77s   z	[A-Za-z]:r   ru   )
isinstancerc   r   striprv   r
   r   
startswithis_absoluteanyre	fullmatchr   join)r   r   r   r   r   r[   r   r   s           r%   _normalize_bundle_pathr      s:   j#&7:,.ABCC



C7:,l;<<T3'J$D"jjBdD	,ATBEBS!T%5%5%77:,b=>>C7777:,b=>>	||L%(+7:,b=>>CJ!O7:,b=>>88E? Cs   ,	D/6D/c                     t        | dd      S )Nz
skill nameFr   r   r   r$   s    r%   _validate_skill_namer      s    !$<eTTr'   categoryc                     t        | dd      S )Nzinstall parent pathTr   r   )r   s    r%   _validate_install_parent_pathr      s    !(7L[_``r'   install_path
skill_namec                     t        |      }t        | dd      }|j                  d      }|r|d   |k7  rt        d|        |S )u  Validate a skill install path before it touches the lock file or disk.

    Lock-file ``install_path`` entries are the source-of-truth for where
    ``uninstall_skill`` will call ``shutil.rmtree``. A poisoned or buggy
    entry — empty string, ``"."``, an absolute path, ``../..`` traversal,
    or anything whose final component doesn't match the skill name — would
    let ``rmtree`` wipe either the entire ``skills/`` tree or content
    outside it.

    Enforce that ``install_path`` ends with ``<skill_name>``. Nested
    official optional skills may legitimately install below paths such as
    ``mlops/training/<skill_name>``; traversal, absolute paths, empty paths,
    and mismatched final components are still rejected.
    zinstall pathTr   rt   Unsafe install path: )r   r   r   r   )r   r   safe_skill_namer   r   s        r%   _normalize_lock_install_pathr      sZ     +:6O'!J
 S!EE"I00?@@r'   r[   c                 b    | j                         xs t        | d      xr | j                         S )a   True when ``path`` is a symlink or (on Windows) a directory junction.

    Either form lets an attacker who can write into the ``skills/`` tree
    redirect a subsequent ``rmtree`` to content outside it. ``is_junction``
    only exists on Python 3.12+ Windows; gate with ``hasattr``.
    is_junction)
is_symlinkhasattrr   )r[   s    r%   _is_path_redirectr      s+     ??U}!=!T$BRBRBTUr'   c                 &   t        | |      }t               }|j                         }|}|j                  d      D ]   }||z  }t	        |      st        d|         |j                         }||k(  s|j                  |      st        d|        |S )u  Resolve a lock-file install path without allowing escapes from ``SKILLS_DIR``.

    Two layers of defence on top of the existing ``is_relative_to`` check
    that's been on main:

    1. Walk the path component-by-component and refuse if any intermediate
       component is a symlink/junction (a path resolution that follows a
       symlink to outside skills/ would otherwise be hidden by Path.resolve).
    2. After resolve(), reject not just escape-out but also ``resolved == SKILLS_DIR``
       — an empty/``"."``/``""`` install_path resolves to the skills root itself,
       and ``rmtree(SKILLS_DIR)`` would wipe every installed skill.
    rt   r   )r   r1   resolver   r   r   is_relative_to)r   r   r   
skills_dirskills_roottargetr   s          r%   _resolve_lock_install_pathr   	  s     .lJGJJ$$&KF  % E$V$4\NCDDE
 ^^FF$9$9+$F0?@@Mr'      timeoutr   r   c                &   | }t        t        dz         D ]  }t        |      st        j	                  d|        yt        |      }|rt        j                  d|d   |d           y	 t        j                  ||d      }|j                  t        v r-t        |d
i       j                  d      }|s yt        ||      }|c S  t        j	                  d|        y# t        j                  $ r"}t        j                  d	||       Y d}~ yd}~ww xY w)z5Fetch a URL with SSRF and redirect-target validation.ru   z!Blocked unsafe Skills Hub URL: %sNz*Blocked Skills Hub fetch for %s by rule %shostruleFr   follow_redirectsz"Skills Hub fetch failed for %s: %sheaderslocationz/Skills Hub fetch exceeded redirect limit for %s)range_MAX_SKILL_FETCH_REDIRECTSr   loggerwarningr   infohttpxr#   	HTTPErrordebugstatus_code_REDIRECT_STATUS_CODESgetattrr   )r   r   current_url_blockedrespexcr   s           r%   _guarded_http_getr   &  s   K-12 ;'NN>L&{3KK<
 	99['ERD
 55tY377
CH!+x8K7: NNDcJ  	LL={CP	s   (CD.DDrel_pathc                     t        | dd      S )Nzbundle file pathTr   r   )r   s    r%   r~   r~   K  s    !(7IX\]]r'   c                   z    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y
)
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                 .    d | _         d | _        d| _        y )Nr   )_cached_token_cached_method_app_token_expiryselfs    r%   __init__zGitHubAuth.__init__\  s    ,0-1()r'   r(   c                 B    | j                         }ddi}|rd| |d<   |S )z5Return authorization headers for GitHub API requests.Acceptapplication/vnd.github.v3+jsonztoken Authorization_resolve_token)r   tokenr   s      r%   get_headerszGitHubAuth.get_headersa  s4    ##%=>)/w'7GO$r'   c                 &    | j                         d uS r!   r   r   s    r%   is_authenticatedzGitHubAuth.is_authenticatedi  s    ""$D00r'   c                 B    | j                          | j                  xs dS )zRReturn which auth method is active: 'pat', 'gh-cli', 'github-app', or 'anonymous'.	anonymous)r   r   r   s    r%   auth_methodzGitHubAuth.auth_methodl  s    ""1k1r'   c                    | j                   r<| j                  dk7  s!t        j                         | j                  k  r| j                   S t        j
                  j                  d      xs t        j
                  j                  d      }|r|| _         d| _        |S | j                         }|r|| _         d| _        |S | j                         }|r,|| _         d| _        t        j                         dz   | _        |S d| _        y )Nz
github-appGITHUB_TOKENGH_TOKENpatzgh-clii  r   )	r   r   timer   osenvironr#   _try_gh_cli_try_github_app)r   r   s     r%   r   zGitHubAuth._resolve_tokenq  s    ""l2diikDDZDZ6Z))) 

~.L"**..2L!&D"'DL   "!&D"*DL $$&!&D".D%)YY[4%7D"L)r'   c           	      j   	 t        j                  g ddddt         j                  t                     }|j                  dk(  r4|j
                  j                         r|j
                  j                         S y# t        t         j                  f$ r }t        j                  d|       Y d}~yd}~ww xY w)z#Try to get a token from the gh CLI.)ghauthr   TrS   )capture_outputtextr   stdincreationflagsr   zgh CLI token lookup failed: %sN)
subprocessrunDEVNULLr   
returncodestdoutr   FileNotFoundErrorTimeoutExpiredr   r   )r   resultes      r%   r   zGitHubAuth._try_gh_cli  s    
	>^^'#$ ((02	F   A%&--*=*=*?}}**,,  ":#<#<= 	>LL91==	>s   A5A9 9B2B--B2c                    t         j                  j                  d      }t         j                  j                  d      }t         j                  j                  d      }t        |||g      sy	 ddl}	 t        |      }|j                         sy|j                  d      }t        t        j                               }|d	z
  |d
z   |d}|j                  ||d      }	t        j                  d| dd|	 ddd      }
|
j                   dk(  r|
j#                         j                  d      S 	 y# t
        $ r t        j                  d       Y yw xY w# t$        $ r"}t        j                  d|        Y d}~yd}~ww xY w)z@Try GitHub App JWT authentication if credentials are configured.GITHUB_APP_IDGITHUB_APP_PRIVATE_KEY_PATHGITHUB_APP_INSTALLATION_IDNr   z-PyJWT not installed, skipping GitHub App authutf-8encoding<   X  )iatexpissRS256)	algorithmz)https://api.github.com/app/installations/z/access_tokenszBearer r   )r   r   
   )r   r      r   zGitHub App auth failed: )r   r   r#   alljwtImportErrorr   r   r	   exists	read_textintr   encoder   postr   json	Exception)r   app_idkey_pathinstallation_idr!  key_fileprivate_keynowpayloadencoded_jwtr   r  s               r%   r   zGitHubAuth._try_github_app  sm   0::>>"?@**..)EFFHo67	
	9H~H??$",,g,>Kdiik"CRxgG
 **WkW*MK::;O;LN['.{m%<> D 3&yy{w// '
 ?  	LLHI	8  	9LL3A3788	9s1   .D0 3E BE 0EE	E?E::E?N)rK   r`   ra   rb   r   r   rc   r   boolr   r   r   r   r   r   r*   r'   r%   r   r   S  sh    *
T#s(^ 1$ 12S 2
 >Xc] *# *r'   r   c            	           e Zd ZdZeddededee   fd       Z	edede
e   fd       Zedede
e   fd       Zedefd	       Zdedefd
Zy)SkillSourcez.Abstract base for all skill registry adapters.querylimitr(   c                      y)z*Search for skills matching a query string.Nr*   r   r5  r6  s      r%   rx   zSkillSource.search       	r'   rX   c                      y)z&Download a skill bundle by identifier.Nr*   r   rX   s     r%   fetchzSkillSource.fetch  r9  r'   c                      y)z9Fetch metadata for a skill without downloading all files.Nr*   r;  s     r%   inspectzSkillSource.inspect  r9  r'   c                      y)z=Unique identifier for this source (e.g. 'github', 'clawhub').Nr*   r   s    r%   	source_idzSkillSource.source_id  r9  r'   c                      y)z3Determine trust level for a skill from this source.	communityr*   r;  s     r%   trust_level_forzSkillSource.trust_level_for  s    r'   Nr  )rK   r`   ra   rb   r   rc   r%  r   rU   rx   r   rh   r<  r>  r@  rC  r*   r'   r%   r4  r4    s    8C  T)_    (=   # (9*=   3  # # r'   r4  OpenAI	AnthropicHuggingFaceNVIDIA	VoltAgentgstackMiniMax)openai/skillsanthropics/skillshuggingface/skillsznvidia/skillszvoltagent/awesome-agent-skillsgarrytan/gstackzminimax-ai/clirZ   c                 j    | syt         j                  | j                         j                               S )zReturn the provider label for a GitHub tap repo, or None.

    ``repo`` is ``owner/repo``; matched case-insensitively so ``NVIDIA/skills``
    and ``nvidia/skills`` both resolve to ``"NVIDIA"``.
    N)GITHUB_TAP_PROVIDERSr#   r   lower)rZ   s    r%   github_provider_forrS    s*     ##DJJL$6$6$899r'   c              #   <   K   | ]  }|j                           y wr!   )rR  )r   vs     r%   r   r     s     #U!AGGI#Us   resultsproviderc           	          |j                         j                         }| D cg c]>  }t        |j                  xs i j	                  dd            j                         |k(  r|@ c}S c c}w )u)  Keep only results whose ``extra.provider`` matches ``provider``.

    An explicit provider filter (e.g. ``--source nvidia``) means "show me that
    provider's skills" — so it narrows to exactly those, without injecting the
    official catalog the unfiltered browse/search would lead with.
    rW  r   )r   rR  rc   r_   r#   )rV  rW  wantrs       r%   _filter_results_by_providerr[    sd     >>!!#D2"":r2399;tC 	
  s   AA)c                      e Zd ZdZddddddddddddd	ddd
ddgZ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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de
deee
ee   f      fdZd<dZddd d!d"d#e
d$ee   d%ee   d&ed'eded   fd(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
dee"   fd/Z#de
deee
e
f      fd0Z$e%d1e
deee
e
f      fd2       Z&d3e
dee'   fd4Z(d3e
d5e'ddfd6Z)e%d7edefd8       Z*e%d1e
defd9       Z+y)=GitHubSourcez4Fetch skills from GitHub repos via the Contents API.rL  zskills/.curated/rZ   r[   zskills/.system/rM  skills/rN  zNVIDIA/skillsrO  r   Nr  
extra_tapsc                     || _         t        | j                        | _        |r| j                  j	                  |       i | _        i | _        i | _        d| _        y NF)	r  re   DEFAULT_TAPStapsextend_tree_cache_tree_revisions_skillsh_groupings_rate_limited)r   r  r`  s      r%   r   zGitHubSource.__init__:  sW    	**+	IIZ( ?A/1 HJ#(r'   r(   c                      y)Nr   r*   r   s    r%   r@  zGitHubSource.source_idJ  s    r'   c                     | j                   S )z8Whether GitHub API rate limit was hit during operations.)ri  r   s    r%   is_rate_limitedzGitHubSource.is_rate_limitedM  s     !!!r'   rX   c                 p    |j                  dd      }t        |      dk\  r|d    d|d    }|t        v ryyNrt   r   r   ru   trustedrB  r   r   r   r   rX   r   rZ   s       r%   rC  zGitHubSource.trust_level_forR  sE      a(u:?AhZqq
+D}$ r'   r5  r6  c           	         g }|j                         }| j                  D ]  }	 | j                  |d   |j                  dd            }|D ]]  }|j                   d|j
                   ddj                  |j                         j                         }||v sM|j                  |       _  dd	d
d}
i }|D ]v  }|j                  |vr|||j                  <   !|
j                  |j                  d
      |
j                  ||j                     j                  d
      kD  sh|||j                  <   x t        |j                               }|d| S # t        $ r)}	t        j                  d|d    d|	        Y d}	~	[d}	~	ww xY w)z.Search all taps for skills matching the query.rZ   r[   r    zFailed to search r   Nr   ru   r   builtinro  rB  )rR  rd  _list_skills_in_repor#   r   rV   r   r^   appendr)  r   r   rX   rY   re   values)r   r5  r6  rV  query_lowertapr.   skill
searchabler  _trust_rankseenrZ  s                r%   rx   zGitHubSource.search[  st   #%kkm99 		C223v;PR@ST# .E$)JJ<q1B1B0C1SXXejjEYDZ![!a!a!cJ"j0u-.		 #$B 	'A||4'%&Q\\"2[__T!,,EWEcEcef5gg%&Q\\"		'
 t{{}%v!  0VRsCDs   A3EE	E?E::E?c                    |j                  dd      }t        |      dk  ry|d    d|d    }|d   }| j                  ||j                  d       d      }|yt	        |      }|yd|i}| j                  |      }||\  }	}
|j                  d       d}|
D ci c]  }|j                  d	d
      | }}t        |      D ]  }| | }|j                  |      }|t        j                  d|        y|j                  d      dk7  s|j                  d      dk(  rt        j                  d|        y| j                  ||      }| y|||<    | j                  j                  |      xs |	}n8|D ]1  }| j                  ||j                  d       d|       }| y|||<   3 d
}|j                  d      j                  d      d   }| j                  |      }t        ||d|||rd| d| d| nd| d| |d      S c c}w )zi
        Download a skill from GitHub.
        identifier format: "owner/repo/path/to/skill-dir"
        rt   r   r   Nr   ru   	/SKILL.mdSKILL.mdr[   r   z,Referenced skill support file is missing: %stypeblobmode120000z-Rejected non-regular file in skill bundle: %sr   r   r   z/tree/)r   source_revisionr   ri   rW   rX   rY   rj   )r   r   _fetch_file_contentr}   r   _get_repo_treer#   sortedr   r   _fetch_file_bytesrg  rC  rh   )r   rX   r   rZ   
skill_pathrq   
referencedri   treebranchentriesprefixitementries_by_pathr   	item_pathcontentrevisionr   trusts                       r%   r<  zGitHubSource.fetchy  s   
   a(u:>(1U1XJ'1X
++DZ5F5Fs5K4LI2VW.x8
/98.D""4("OFG"))#./q1FFMNdtxx3T9NON":. *%hxj1	&**95<NN#QS\]88F#v-&1AX1MNN#RT]^00yA?")h* ++//5?H& *00*:K:KC:P9QQRS[R\7]^?")h	*
 H&&s+11#6r:
$$Z0!   *$vhZqM':4&*%N#+
 	
5 Os   Hc                     |j                  dd      }t        |      dk  ry|d    d|d    }|d   j                  d      }| d}| j                  ||      }|sy| j	                  |      }|j                  d|j                  d      d	         }|j                  d
d      }	g }
|j                  di       }t        |t              r4|j                  di       }t        |t              r|j                  dg       }
|
s&|j                  dg       }t        |t              r|ng }
t        |      }i }|r||d<   t        |t        |	      d|| j                  |      |||
D cg c]  }t        |       c}|	      S c c}w )z-Fetch just the SKILL.md metadata for preview.rt   r   r   Nr   ru   r  r   r   rV   r   rj   hermesr^   rW  r   	r   rV   rW   rX   rY   rZ   r[   r^   r_   )r   r   r}   r  _parse_frontmatter_quickr#   r   rf   re   rS  rU   rc   rC  )r   rX   r   rZ   r  skill_md_pathr  fmr   rV   r^   rj   hermes_metaraw_tagsrW  r_   ts                    r%   r>  zGitHubSource.inspect  s     a(u:>(1U1XJ'1X__S)
%,i0**4?**73VVFJ$4$4S$9"$=>
ff]B/66*b)h%",,x4K+t,"vr2vvfb)H)(D98rD&t, " (E*K(!,,Z8"&'Q#a&'

 
	
 (s    E;rZ   r[   c           	      x   | d| j                  dd      j                  dd      }| j                  |      }||D cg c]  }t        di | c}S d| d|j                  d       }| j	                  |      }||j
                  dk7  rg S |j                         }t        |t              sg S g }	| j                  |      }
|D ]  }|j                  d      dk7  r|d	   }|j                  d
      r/|j                  d      }|r
| d| d| n| d| }| j                  |      }|sg|
r?|
j                  |j                        xs |
j                  |      }|r||j                  d<   |	j                  |        | j!                  ||	D cg c]  }| j#                  |       c}       |	S c c}w c c}w )zAList skill directories in a GitHub repo path, using cached index.r   rt   rs  https://api.github.com/repos/
/contents/   r  dirr   r   r   r   r*   )rv   _read_cacherU   r}   _github_getr   r(  r   re   _get_skillsh_groupingsr#   r   r>  r   r_   rw  _write_cache_meta_to_dict)r   rZ   r[   	cache_keycachedsr   r   r  r.   	groupingsentrydir_namer  skill_identifiermetar   s                    r%   rv  z!GitHubSource._list_skills_in_repo  s   fAdV$,,S#6>>sCH	!!),,23qINN33-dV:dkk#>N=OP$<4++s2I))+'4(I"$//5	 	$Eyy E)V}H"":.[[%F@F$q(<tfTUV^U_L`<< 01D(}}TYY7R9==;RH19

:.d#!	$& 	)V%Ld&8&8&;%LMC 4@ &Ms   F2F7
c                 T   || j                   v r| j                   |   S | j                  j                         }	 t        j                  d| |dd      }|j
                  dk7  r| j                  |       y|j                         j	                  dd      }	 t        j                  d| d	| d
di|dd      }|j
                  dk7  r| j                  |       y|j                         }|j	                  d      rt        j                  d|       y	 |j	                  dg       }|j	                  d      }t        |t              r|r|| j                  |<   ||f| j                   |<   ||fS # t        j                  t        f$ r Y yw xY w# t        j                  t        f$ r Y yw xY w)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     Tr   r   r   r  Ndefault_branchmainz/git/trees/	recursive1   paramsr   r   r   	truncatedz'Git tree truncated for %s, cannot cacher  sha)rf  r  r   r   r#   r   _check_rate_limit_responser(  r   r   r   r   r   rc   rg  )r   rZ   r   r   r  	tree_datar  r  s           r%   r  zGitHubSource._get_repo_tree  s    4#####D))))'')
	99/v6dD 3&//5!YY[__-=vFN
	99/v[@PQ#S)dD
 3&//5		I}}[)FM * --+=='h$)1D  &"0'!:((3 , 		" , 		s0   <E, 6 E, AF 7F ,FFF'&F'httpx.Responsec                     |j                   dv rN|j                  j                  dd      }|dk(  s|j                   dk(  rd| _        t        j                  d       yyy)	zLFlag the instance as rate-limited when GitHub returns 403 + exhausted quota.i    X-RateLimit-Remainingr   0r  TzGitHub 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   r   r#   ri  r   r   )r   r   	remainings      r%   r  z'GitHubSource._check_rate_limit_responseH  s`    z)(()@"EIC4#3#3s#:%)"] $; *r'   g      .@r   )r  r   r   max_retriesr   r  r   r   r  c          	         ||n| j                   j                         }d}d}t        |      D ]  }		 t        j                  ||||d      }
|
}|
j                  d	k(  r|
c S |
j                  d
v r3|
j                  j	                  dd      }|dk(  xs |
j                  dk(  }|r|	|dz
  k  r|}|
j                  j	                  dd      }|
j                  j	                  dd      }|j                         rt        t        |      d      }n@|j                         r0t        |      t        j                         z
  }d|cxk  rdk  rn n|}t        j                  d|||	dz   |       t        j                  |       t        |dz  d      }a| j                  |
       |
c S d|
j                  cxk  rdk  r4n |
c S |	|dz
  k  r&t        j                  |       t        |dz  d      }|
c S  |S # t        j
                  $ rY}t        j                  d||	dz   ||       |	|dz
  k  r*t        j                  |       t        |dz  d      }Y d}~'Y d}~ yd}~ww xY w)uj  GET against the GitHub API with retry/backoff on transient failures.

        Returns the final ``httpx.Response`` (caller inspects status) or
        ``None`` when every attempt raised a transport error.

        Retries on:
          - 403/429 with ``X-RateLimit-Remaining: 0`` — waits until the
            reset time (capped) when the header is present, else exponential
            backoff. This is the all-GitHub-tap-collapse case: a single
            shared rate limit zeroes github + claude-marketplace + well-known
            at once during the index build.
          - 5xx and connection/timeout errors — exponential backoff.

        On terminal rate-limit exhaustion the instance is flagged via
        ``_check_rate_limit_response`` so the build can fail loud instead of
        silently shipping an index with the GitHub sources dropped to zero.
        Ng      ?Tr  z(GitHub GET %s failed (attempt %d/%d): %sru   r   g      >@r  r  r  r   r  r  zX-RateLimit-ResetzRetry-Afterg      N@r   z8GitHub rate limited on %s, waiting %.1fs (attempt %d/%d)  r  )r  r   r   r   r#   r   r   r   r   sleepminr   r   isdigitfloatr  )r   r   r  r   r   r  hdrsbackoff	last_respattemptr   r  r  is_rlwaitresetretry_afterdeltas                     r%   r  zGitHubSource._github_getS  sJ   4 "-w4993H3H3J04	[) 1	Gyy#d I3& :- LL,,-DbI	!S(CD,<,<,CW{Q6"D LL,,-@"EE"&,,"2"2=""EK"**,"5#5t< %etyy{ :u,,#(DLLRT7Q; JJt$!'A+t4G//5 d&&,, K	 29;?1J

7#gk40Kc1	f [ ?? G 'A+{A?[1_,JJw'!'A+t4Gs   G44I AII c                     | j                  ||      }||S t        j                  d||       | j                  ||      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.
        z<Tree API unavailable for %s/%s, falling back to Contents API)_download_directory_via_treer   r   _download_directory_recursive)r   rZ   r[   ri   s       r%   _download_directoryz GitHubSource._download_directory  sG     11$=LSUY[_`11$==r'   c                    |j                  d      }| j                  |      }|y|\  }}| dt        fd|D              }|si S i }|D ]z  }|j                  d      dk7  r|j                  dd      }	|	j	                        s<|	t              d }
| j                  ||	      }||||
<   dt        j                  d||	       | |r|S 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).
        rt   Nc              3   `   K   | ]%  }|j                  d d      j                         ' yw)r[   r   N)r#   r   )r   r  r  s     r%   r   z<GitHubSource._download_directory_via_tree.<locals>.<genexpr>  s,      
8<DHHVR ++F3
s   +.r  r  r[   r   z"Skipped file (fetch failed): %s/%s)	r}   r  r   r#   r   r   r  r   r   )r   rZ   r[   r  _default_branchtree_entrieshas_entriesri   r  r  r   r  r  s               @r%   r  z)GitHubSource._download_directory_via_tree  s    {{3$$T*>(.% 6 
@L
 
  I !#  	TDxx6),I''/ V.H..tY?G"")hA4S	T u'4'r'   c           	         d| d|j                  d       }| j                  |      }|i S |j                  dk7  r$t        j	                  d|j                  ||       i S |j                         }t        |t              si S i }|D ]  }|j                  dd      }|j                  dd      }	|	d	k(  r-| j                  ||j                  d
d            }
|
Q|}|
||<   Y|	dk(  s_| j                  ||j                  d
d            }|s't        j	                  d||j                  d
d             |j                         D ]  \  }}||| d| <     |S )z1Recursively download via Contents API (fallback).r  r  rt   r  z"Contents API returned %d for %s/%sr   r   r  filer[   r  z#Empty or failed subdirectory: %s/%s)r}   r  r   r   r   r(  r   re   r#   r  r  items)r   rZ   r[   r   r   r  ri   r  r   
entry_typer  r   	sub_filessub_namesub_contents                  r%   r  z*GitHubSource._download_directory_recursive  sm   -dV:dkk#>N=OP $<Is"LL=t?O?OQUW[\I))+'4(I " 	>E99VR(D62.JV#224629NO&#H&-E(Ou$ >>tUYYvWYEZ[	 LL!FeiiX^`bNcd-6__-> >)Hk2=ETF!H:./>	>  r'   r   c                     | j                  |      }|y|\  }}d| d}|D ]Z  }|j                  d      dk7  r|j                  dd      }|j                  |      s	|| dk(  sD|dt        d        }	| d|	 c S  y)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``.
        Nrt   r  r  r  r[   r   )r  r#   endswithr   )
r   rZ   r   r  r  r  skill_md_suffixr  r[   	skill_dirs
             r%   _find_skill_in_repo_treez%GitHubSource._find_skill_in_repo_tree  s     $$T*>(.% j\3! 	-Eyy F*99VR(D}}_-J<y9Q1Q !4C$4#45	q,,	- r'   c                 n    | j                  ||      }|y	 |j                  d      S # t        $ r Y yw xY w)z%Fetch a single text file from GitHub.Nr  )r  decodeUnicodeDecodeError)r   rZ   r[   r  s       r%   r  z GitHubSource._fetch_file_content  sB    ((t4?	>>'**! 		s   ( 	44c                     d| d| }| j                  |i | j                  j                         ddi      }||j                  dk(  r|j                  S y)z9Fetch exact file bytes from GitHub without text decoding.r  r  r   application/vnd.github.v3.rawr   Nr  )r  r  r   r   r  )r   rZ   r[   r   r   s        r%   r  zGitHubSource._fetch_file_bytes&  sk    -dV:dVDZtyy,,.Z:YZ   
  0 0C 7<<r'   c                     || j                   v r| j                   |   S | j                  |d      }|r| j                  |      nd}|| j                   |<   |S )u  Fetch and parse the repo-root ``skills.sh.json`` grouping sidecar.

        ``skills.sh.json`` is a published cross-ecosystem standard
        (``$schema: https://skills.sh/schemas/skills.sh.schema.json``) that
        lets a tap declare human-readable category groupings for its skills:

            {"groupings": [{"title": "Inference AI", "skills": ["dynamo-..."]}]}

        We flatten it into ``{skill_name: grouping_title}`` so the Skills Hub
        UI can show a real category pill instead of a tag-derived guess. Any
        tap that ships this file gets categorization for free — this is not
        NVIDIA-specific.

        Returns the map (possibly empty) on success, or ``None`` when the repo
        has no sidecar / it couldn't be parsed. Cached per-repo on the instance.
        zskills.sh.jsonN)rh  r  _parse_skillsh_groupings)r   rZ   r  r  s       r%   r  z#GitHubSource._get_skillsh_groupings1  s`    " 4*****400**41AB>ED11':4	(1%r'   r  c                    	 t        j                  |       }t	        |t
              sy|j                  d      }t	        |t              syi }|D ]  }t	        |t
              s|j                  d      }|j                  d      }t	        |t              rt	        |t              sW|D ](  }t	        |t              s|s|j                  ||       *  |S # t         j                  t        f$ r Y yw xY w)zFlatten a ``skills.sh.json`` document into ``{skill_name: title}``.

        Returns ``None`` when the content isn't a usable grouping document.
        Nr  titler.   )
r(  loadsJSONDecodeError	TypeErrorr   rf   r#   re   rc   
setdefault)r  datar  mappingr|   r  membersmembers           r%   r  z%GitHubSource._parse_skillsh_groupingsJ  s    	::g&D $%HH[)	)T*"$ 
	6EeT*IIg&Eii)GeS)GT1J! 6fc*v&&vu56
	6 ) $$i0 		s   C C21C2keyc                 <   t               | dz  }|j                         sy	 |j                         }t        j                         |j                  z
  t
        kD  ryt        j                  |j                               S # t        t        j                  f$ r Y yw xY w)z!Read cached index if not expired..jsonNrF   r#  statr   st_mtimeINDEX_CACHE_TTLr(  r  r$  OSErrorr  )r   r   
cache_filer  s       r%   r  zGitHubSource._read_cacheh  s    %'SE-7
  "	??$Dyy{T]]*_<::j22455--. 		   8A? "A? ?BBr  c                     t               }|j                  dd       || dz  }	 |j                  t        j                  |d             y# t
        $ r }t        j                  d|       Y d}~yd}~ww xY w)zWrite index data to cache.Tparentsexist_okr  F)ensure_asciiCould not write cache: %sN)rF   mkdir
write_textr(  dumpsr  r   r   )r   r   r  index_cache_dirr  r  s         r%   r  zGitHubSource._write_cacheu  sm    *,dT:$#e}4
	9!!$**T"FG 	9LL4a88	9s   &A 	A7A22A7r  c           
          | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                  d	S )Nr  r  r  s    r%   r  zGitHubSource._meta_to_dict  sQ     II++kk//++IIIIIIZZ

 
	
r'   c                 :   | j                  d      } | j                  d      si S t        j                  d| dd       }|si S | d|j	                         dz    }	 t        j                  |      }t        |t              r|S i S # t
        j                  $ r i cY S w xY wz-Parse YAML frontmatter from SKILL.md content.u   ﻿---z
\n---\s*\nr   N
lstripr   r   rx   startyaml	safe_loadr   rf   	YAMLErrorr  r   	yaml_textparseds       r%   r  z%GitHubSource._parse_frontmatter_quick       ..*!!%(I		-5IAekkma/0		^^I.F'56=2=~~ 	I	   &B  B BBr!   rD  )r   r  r(   N),rK   r`   ra   rb   rc  r   r   r   r   r   rc   r@  propertyr2  rl  rC  r%  rU   rx   rh   r<  r>  rv  r   rf   r  r  r  r  r  r  r  r  r  rk   r  r  staticmethodr  re   r  r  r  r  r*   r'   r%   r]  r]  #  s#   > !*<= *;<$i8%y9 !)4"B/#L()Z )Xd4j5I ) 3  " " "# # C  T)_ <?
 ?
(= ?
B+
# +
(9*= +
^& &C &DO &T3)3 3)8E#tDz/4J+K 3)j	 "&"&PP 	P
 $P P P 
"	#Pf> >3 >4S> >)( )(C )(HTRUWZRZ^D\ )(V!# !S !T#s(^ !FS c hsm 4 3 8C= 	c 	 	% 	3 8DcN3K 2 # (4S>2J  :s x~ 9 94 9D 9 
I 
$ 
 
 # $  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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y)WellKnownSkillSourcezBRead skills from a domain exposing /.well-known/skills/index.json.z/.well-known/skillsr(   c                      y)N
well-knownr*   r   s    r%   r@  zWellKnownSkillSource.source_id  s    r'   rX   c                      yNrB  r*   r;  s     r%   rC  z$WellKnownSkillSource.trust_level_for      r'   r5  r6  c                    | j                  |      }|sg S | j                  |      }|sg S g }|d   d | D ]  }|j                  d      }t        |t              r|s'|j                  dd      }|j                  ddg      }	|j                  t        |t	        |      d| j                  |d   |      d	||d
   |d   t        |	t              r|	ndgd              |S )Nr.   r   rV   r   ri   r  r)  base_urlrB  	index_url)r/  r.  ri   r   rV   rW   rX   rY   r[   r_   )	_query_to_index_url_parse_indexr#   r   rc   rw  rU   _wrap_identifierre   )
r   r5  r6  r/  r!  rV  r  r   rV   ri   s
             r%   rx   zWellKnownSkillSource.search  s    ,,U3	I""9-I#%H%fu- 	E99V$DdC())M26KIIg
|4ENN9,#00
1CTJ'!'!4 &z 2&0&=UJ< 	& r'   c                    | j                  |      }|sy | j                  |d   |d         }|sy | j                  |d    d      }|y t        j	                  |      }t        |j                  d      xs |j                  d      xs d      }t        |j                  d      xs |d         }t        ||d| j                  |d	   |d         d
|d   |d   |d	   |j                  ddg      |d   d      S )Nr/  r   	skill_urlr  rV   r   r   r)  r.  rB  ri   r  )r/  r.  ri   endpointr0  )	_parse_identifier_index_entry_fetch_textr]  r  rc   r#   rU   r3  )r   rX   r!  r  rq   r  rV   r   s           r%   r>  zWellKnownSkillSource.inspect  s   ''
3!!&"5vl7KL##vk':&;9$EF228<"&&/Q599]3KQrR266&>9VL%9:#,,VJ-?AUV#%#K0":.7ZL9";/	
 	
r'   c                 h   | j                  |      }|sy 	 t        |d         }| j                  |d   |d         }|sy |j                  ddg      }t        |t              r|sdg}i }|D ]C  }t        |t              r|s	 t        |      }| j                  |d    d|       }	|	 y |	||<   E d|vry t        ||d	| j                  |d
   |      d|d   |d
   |d   |d      S # t        $ r t        j	                  d|       Y y w xY w# t        $ r t        j	                  d||       Y  y w xY w)Nr   z;Well-known skill identifier contained unsafe skill name: %sr/  ri   r  z3Well-known skill %s advertised unsafe file path: %rr5  rt   r)  r.  rB  )r/  r.  r6  ri   r  )r7  r   r   r   r   r8  r#   r   re   rc   r~   r9  rh   r3  )
r   rX   r!  r   r  ri   
downloadedr   safe_rel_pathr  s
             r%   r<  zWellKnownSkillSource.fetch  s   ''
3	-f\.BCJ
 !!&"5vl7KL		'J<0%&eLE%'
 	-Hh,H 9( C ##vk':&;1]O$LMD|(,J}%	-" Z',,VJ-?L##K0":.";/	
 	
C  	NNXZde	$  I
 s#   C( D(D
	D
 D10D1c                 8   |j                         }|j                  d      sy |j                  d      r|S | j                   d|v r4|j	                  | j                   dd      d   | j                  z   }| dS |j                  d      | j                   dz   S )Nzhttp://zhttps:///index.jsonrt   ru   r   )r   r   r  	BASE_PATHr   r}   )r   r5  r.  s      r%   r1  z(WellKnownSkillSource._query_to_index_url!  s     78>>-(LnnQ5({{dnn%5Q#7;A>OHZ{++||C dnn%5[#AAAr'   c                    |j                  d      r|t        d      d  n|}|j                  d      sy t        |      }t        |j	                  d            }|j
                  }|j                  d      r"|sy |d t        d        }|}| d| }||||dS |j                  d      r|d t        d        }n|j                  d      }| j                   d|vry |j                  dd	      \  }}| d|||dS )
Nwell-known:r>  r   )fragmentr?  rt   )r/  r.  r   r5  r  ru   )
r   r   r   r   _replacerC  r  r}   r@  rsplit)	r   rX   r   
parsed_url	clean_urlrC  r.  r   r5  s	            r%   r7  z&WellKnownSkillSource._parse_identifier,  s4   1;1F1F}1Uj]+,-[e~~56c]
z22B2?@	&&m, !53}#5"56H!J#*Aj\2I&$(&	  k*!"4C$4#45I!((-InnQy0(//Q7*$:[1 $"	
 	
r'   r/  c                     dt        j                  |j                               j                          }t	        |      }t        |t              r!t        |j                  d      t              r|S t        |d      }||j                  dk7  ry 	 |j                         }t        |t              r|j                  dg       ng }t        |t              sy ||d t        d        |d}t        ||       |S # t        j                  $ r Y y w xY w)Nwell_known_index_r.   r   r   r  r?  )r/  r.  r.   )hashlibmd5r&  	hexdigest_read_index_cacher   rf   r#   re   r   r   r(  r  r   _write_index_cache)r   r/  r  r  r   r  r.   r!  s           r%   r2  z!WellKnownSkillSource._parse_indexR  s    'I4D4D4F(G(Q(Q(S'TU	"9-fd#
6::h3G(NM B7<4++s2	99;D ,6dD+A(B'r&$' #!"6C$6#67

 	9f- ## 		s   C7 7DDr   c                     | j                  |      }|sy |d   D ]+  }t        |t              s|j                  d      |k(  s)|c S  y )Nr.   r   )r2  r   rf   r#   )r   r/  r   r!  r  s        r%   r8  z!WellKnownSkillSource._index_entryl  sP    ""9-H% 	E%&599V+<
+J	 r'   r   c                 X    t        | d      }||j                  dk(  r|j                  S y Nr   r   r  r   r   r  r   r   s     r%   r9  z WellKnownSkillSource._fetch_textu  .     b1 0 0C 799r'   r.  c                 0    d| j                  d       d| S )NrB  rt   )r}   )r.  r   s     r%   r3  z%WellKnownSkillSource._wrap_identifier|  s    X__S12!J<@@r'   NrD  )rK   r`   ra   rb   r@  rc   r@  rC  r%  r   rU   rx   r   r>  rh   r<  r1  rf   r7  r2  r8  r%  r9  r3  r*   r'   r%   r'  r'    s3   L%I3 # # C  T)_ >
# 
(9*= 
>4
 4
(= 4
l	B 	B# 	B$
C $
HTN $
Lc htn 4c s x~   #   A3 AC AC 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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y)	UrlSourcea%  Fetch SKILL.md plus explicitly referenced, allowlisted support files.

    The identifier IS the URL (e.g. ``https://example.com/path/SKILL.md``).
    Bare URLs cannot safely enumerate a repository, so only exact references
    below references/templates/scripts/assets are fetched. Other repository
    files are never copied.

    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.
    r(   c                      y)Nr   r*   r   s    r%   r@  zUrlSource.source_id  s    r'   rX   c                      yr+  r*   r;  s     r%   rC  zUrlSource.trust_level_for  r,  r'   r5  r6  c                     g S r!   r*   r8  s      r%   rx   zUrlSource.search  s    	r'   c                 V   t        |t              sy|j                         }|j                         j	                  d      syd|v s |j                  d      j                  d      ry	 t        |      j                  }|j                         j                  d      S # t        $ r Y yw xY w)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/rt   r?  z.md)
r   rc   r   rR  r   r}   r  r   r[   r   )r   rX   identr[   s       r%   _matcheszUrlSource._matches  s     *c*  "{{}''(?@!U*ell3.?.H.H.W	E?''D zz|$$U++  		s   (B 	B('B(c                 B   | j                  |      sy |j                         }| j                  |      }|y t        j	                  |      }| j                  ||      }t        |j                  d      xs d      }g }|j                  di       }t        |t              r\|j                  di       }	t        |	t              r:|	j                  dg       }
t        |
t              r|
D cg c]  }t        |       }}t        |xs d|d|d|xs d|||d u d	      S c c}w )
NrV   r   rj   r  r^   r   rB  )r   awaiting_name)r   rV   rW   rX   rY   r[   r^   r_   )r]  r   r9  r]  r  _resolve_skill_namerc   r#   r   rf   re   rU   )r   rX   r   r  r  r   rV   r^   rj   r  r  r  s               r%   r>  zUrlSource.inspect  s   }}Z( $<2248''C0"&&/52666*b)h%",,x4K+t,&??626h-,45qCF5D5##=	
 		
 6s   (Dc                 n   | j                  |      sy |j                         }| j                  |      }|y t        j	                  |      }| j                  ||      }t        |      }|y d|i}|j                  dd      d   dz   }t        |      D ]U  }	t        ||	      }
t        |
      j                  t        |      j                  k7  r y | j                  |
      }| y |||	<   W d}|	 t        |      }t#        ||d|d||| d	
      S # t        $ r t        j!                  d||       Y y w xY w)Nr  rt   ru   r   r   z+URL skill %s produced unsafe skill name: %rr   rB  )r   r   r_  r  )r]  r   r9  r]  r  r`  r   rE  r  r   r   netloc_fetch_bytesr   r   r   r   rh   )r   rX   r   r  r  r   r  ri   r.  r   support_urlr  r   s                r%   r<  zUrlSource.fetch  s]   }}Z( $<2248''C0.t4
/94.@::c1%a(3.z* 	&H!(H5K$++x}/C/CC''4G%E(O	& 
1$7

 # *nU
 	
	  LcSWXs   0D  D43D4r   c                 X    t        | d      }||j                  dk(  r|j                  S y rQ  rR  rS  s     r%   r9  zUrlSource._fetch_text  rT  r'   c                 X    t        | d      }||j                  dk(  r|j                  S y rQ  )r   r   r  rS  s     r%   rc  zUrlSource._fetch_bytes  s.     b1 0 0C 7<<r'   z^[a-z][a-z0-9_-]*$r   c                     t        |t              sy|j                         j                         }|r|dv ryt	        | j
                  j                  |            S )NF>   unnamed-skillindexr{  readme)r   rc   r   rR  r2  _VALID_NAME_REr   )clsr   	candidates      r%   _is_valid_skill_namezUrlSource._is_valid_skill_name  sM    $$JJL&&(	I)VVC&&,,Y788r'   r  c                 <   t        |t              r|j                  d      nd}t        |t              r!| j	                  |      r|j                         S 	 t        |      j                  }|j                  d      D cg c]  }|s|	 }}|r<|d   j                         dk(  r&t        |      dk\  r|d   }| j	                  |      r|S |r=t        j                  dd	|d   t        j                  
      }| j	                  |      r|S y# t        $ r Y yw xY wc c}w )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   Nrt   r   zskill.mdr   z\.md$r   )flags)r   rf   r#   rc   rn  r   r   r[   r   r   rR  r   r   sub
IGNORECASE)rl  r  r   fm_namer[   pr   rm  s           r%   r`  zUrlSource._resolve_skill_name  s     %/r4$8"&&.dgs#(@(@(I==?"	C=%%D !JJsO1qq11U2Y__&*4Uqb	I''	2  xU2YbmmLI''	2     		1s   D
 >DD
	DDNrD  )rK   r`   ra   rb   rc   r@  rC  r%  r   rU   rx   r2  r]  r   r>  rh   r<  r%  r9  rk   rc  r   compilerk  classmethodrn  rf   r`  r*   r'   r%   rW  rW    s8   
3 # # C  T)_ ,3 ,4 ,.
# 
(9*= 
:,
 ,
(= ,
\  #   # (5/    RZZ 56N9 9$ 9 9 T    r'   rW  c            	          e Zd ZdZdZe dZdZ ej                  dej                        Z
 ej                  dej                        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d6d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e"   fdZ$dededee"   fdZ%d7ded ee"   dee   fd!Z&d7d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.       Z1d$ed ee"   de2ee3f   fd/Z4e,dedee   fd0       Z5e,dedede2eef   fd1       Z6e,d(edefd2       Z7e,dedefd3       Z8e,dedee   fd4       Z9e,dedefd5       Z:y)8SkillsShSourcezPDiscover skills via skills.sh and fetch content from the underlying GitHub repo.zhttps://skills.shz/api/searchz!https://www.skills.sh/sitemap.xmlz<loc>([^<]+)</loc>zS^https?://(?:www\.)?skills\.sh/(?P<owner>[^/]+)/(?P<repo>[^/]+)/(?P<skill>[^/]+)/?$zIhref=["\']/(?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                 4    || _         t        |      | _        y Nr  r  r]  r   r   r  s     r%   r   zSkillsShSource.__init___  s    	"-r'   r(   c                      y)N	skills-shr*   r   s    r%   r@  zSkillsShSource.source_idc  r,  r'   rX   c                 V    | j                   j                  | j                  |            S r!   )r   rC  _normalize_identifierr;  s     r%   rC  zSkillsShSource.trust_level_forf  s"    {{**4+E+Ej+QRRr'   r5  r6  c           	         |j                         s| j                  |      S dt        j                  | d| j	                               j                          }t        |      }||D cg c]  }t        di | c}d | S 	 t        j                  | j                  ||dd      }|j                  dk7  rg S |j                         }t        |t               r|j                  dg       ng }t        |t"              sg S g }	|d | D ]'  }| j%                  |      }
|
s|	j'                  |
       ) t)        ||	D cg c]  }t+        |       c}       |	S c c}w # t        j                  t        j                  f$ r g cY S w xY wc c}w )	Nskills_sh_search_|)qr6  r   r  r   r  r.   r*   )r   _sitemap_catalogrJ  rK  r&  rL  rM  rU   r   r#   
SEARCH_URLr   r(  r   r  r   rf   re   _meta_from_search_itemrw  rN  _skill_meta_to_dict)r   r5  r6  r  r  r  r   r  r  rV  r  s              r%   rx   zSkillsShSource.searchi  s|   {{} ((//'ugQug4F4M4M4O(P(Z(Z(\']^	"9-289$I%%9&5AA
	99"U3D
 3&	99;D +5T4*@2&b%&I#%&5M 	%D..t4Dt$	%
 	9W&UT':4'@&UV3 : !5!56 	I	 'Vs$   ,E5E ;E <F
%FFc                 >   | j                  |      }| j                  |      }| j                  |      D ]k  }| j                  j	                  |      }|s!d|_        | j                  |      |_        |j                  j                  | j                  ||             |c S  | j                  ||      }|rg| j                  j	                  |      }|rJd|_        | j                  |      |_        |j                  j                  | j                  ||             |S y )N	skills.shdetail)r  _fetch_detail_page_candidate_identifiersr   r<  rW   r3  rX   rj   update_detail_to_metadata_discover_identifier)r   rX   	canonicalr  rm  r   resolveds          r%   r<  zSkillsShSource.fetch  s   ..z:	((344Y? 	I[[&&y1F +$($9$9)$D!&&t'?'?	6'RS	 ,,Yv,F[[&&x0F +$($9$9)$D!&&t'?'?	6'RSr'   c                     | j                  |      }| j                  |      }| j                  ||      }|r| j                  |||      S y Nr  )r  r  _resolve_github_meta_finalize_inspect_meta)r   rX   r  r  r  s        r%   r>  zSkillsShSource.inspect  sR    ..z:	((3((6(B..tYGGr'   c                    d}t        |      }|$|D cg c]  }t        di | }}|dkD  r|d| S |S ddi}g }	 t        j                  | j                  dd|      }|j
                  d	k7  r| j                  |      S | j                  j                  |j                        D ]7  }	|	j                  d
      j                         }
d|
v s'|j                  |
       9 	 |s| j                  |      S t               }g }|D ]Q  }	 t        j                  |dd|      }|j
                  d	k7  r.	 | j                  j                  |j                        D ]  }|j                  d
      j                         }| j                  j!                  |      }|s@|j                  d      }|j                  d      }|j                  d      }| d| d| }||v r|j#                  |       | d| }|j                  t        |d| d| j%                  |      | j&                  j)                  |      ||| j*                   d| d| d              T |s| j                  |      S t-        ||D cg c]  }t/        |       c}       |dkD  r|d| S |S c c}w # t        j                  $ r | j                  |      cY S w xY w# t        j                  $ r Y w xY wc c}w )a-  Walk the skills.sh sitemap to enumerate the full catalog.

        Cached for the standard index TTL so we don't refetch ~2 MB of
        sitemap XML per build. Falls back to ``_featured_skills`` if the
        sitemap is unreachable or empty (network failure, hostname
        change, etc.).
        skills_sh_sitemap_v1Nr   Accept-Encodinggzipr   Tr   r   r   r  ru   zsitemap-skillsr  ownerrZ   r{  rt   Indexed by skills.sh from r  r   )
detail_urlrepo_urlr   rV   rW   rX   rY   rZ   r[   r_   r*   )rM  rU   r   r#   SITEMAP_INDEX_URLr   _featured_skills_SITEMAP_LOC_REr{   r  r|   r   rw  r   ry   _SITEMAP_SKILL_REr   r   r3  r   rC  BASE_URLrN  r  )r   r6  r  r  r  metassitemap_headersskill_sitemap_urlsr   r   locr~  rV  sitemap_url	loc_matchr   mr  	repo_namer   r  rZ   s                         r%   r  zSkillsShSource._sitemap_catalog  s    +	"9-39:4Y&&:E:$)AI5%=858 -f5 )+	099&&!%'	D 3&,,U33--66tyyA 3kk!n**,#s*&--c2	3 "((// #%- %	K
yy%)+	 ##s* + "11::499E 	ooa(..0**005(GGFO	WWW-
$gQyk:,?	$#)-y#"<TF C&#44Y? $ ; ;I F#)-q&D&9$$@  %	N ((//9W&UT':4'@&UV"'!)wv88] ;4  	0((//	0& ?? @ 'Vs<   JAJ >AJ 
J (K1K
$KKKKc                 P   d}t        |      }||D cg c]  }t        di | c}d | S 	 t        j                  | j                  d      }|j
                  dk7  rg S 	 t               }g }| j                  j                  |j                        D ]  }|j                  d      }	|	|v r|j                  |	       |	j                  dd      }
t        |
      dk  rK|
d	    d|
d
    }|
d   }|j                  t        |j                  d      d   d| d| j!                  |	      | j"                  j%                  |	      ||             t        |      |k\  s n t'        ||D cg c]  }t)        |       c}       |S c c}w # t        j                  $ r g cY S w xY wc c}w )Nskills_sh_featuredr   r   r  idrt   r   r   r   ru   r   zFeatured on skills.sh from r  )r   rV   rW   rX   rY   rZ   r[   r*   )rM  rU   r   r#   r  r   r   ry   _SKILL_LINK_REr{   r  r|   r   r   r   rw  r3  r   rC  rN  r  )r   r6  r  r  r  r   r~  rV  r   r  r   rZ   r  s                r%   r  zSkillsShSource._featured_skills  s   (	"9-289$I%%9&5AA	99T]]B7D3&	 '
 #%((11$))< 	ED)ID HHYOOC+E5zA~AhZqq
+DqJNN9%%c*2.9$@"00; KK77	B  7|u$)	, 	9W&UT':4'@&UVE :  	I	6 'Vs   F1F (F#
F F r  c                    t        |t              sy |j                  d      }|j                  d      }|j                  d      }t        |t              r|j	                  d      dk  r(t        |t              rt        |t              sy | d| }|j                  dd      }t        |      dk  ry |d    d|d    }|d   }|j                  d	      }t        |t              rd
t        |      ddnd}t        t        |j                  d      xs |j                  d      d         d| | d| j                  |      | j                  j                  |      |||| j                   d| d| d      S )Nr  rW   skillIdrt   r   r   r   ru   installs    · ,z	 installsr   r   r   r  r  r   )r  r  r  r  )r   rf   r#   rc   countr   r   r%  rU   r3  r   rC  r  )r   r  r  rZ   r  r   r  installs_labels           r%   r  z%SkillsShSource._meta_from_search_item.  sv   $%HHTN	xx!XXi(
)S)Y__S-AA-EtS)jS.I&*.IQ'u:>(1U1XJ'1X
88J'>HSV>W4Ha0	:]_TXXf%B)9)9#)>r)BC4TF>:JK,,Y733I>$!%q<1$8
 	
r'   c                    dt        j                  |j                               j                          }t	        |      }t        |t              r|S 	 t        j                  | j                   d| d      }|j                  dk7  ry 	 | j                  ||j                        }|rt        ||       |S # t        j                  $ r Y y w xY w)Nskills_sh_detail_rt   r   r   r  )rJ  rK  r&  rL  rM  r   rf   r   r#   r  r   r   _parse_detail_pager  rN  )r   rX   r  r  r   r  s         r%   r  z!SkillsShSource._fetch_detail_pageR  s    'J4E4E4G(H(R(R(T'UV	"9-fd#M	99a
|<bID3& '
 ((TYY?y&1  		s   5B6 6CChtmlc                    |j                  dd      }t        |      dk  ry |d    d|d    }|d   }|}|}d }| j                  j                  |      }	|	rz|	j	                  d      j                         }|	j	                  d      xs dj                         }
|	j	                  d      xs |j                         }| j                  |
      xs |}| j                  | j                  |      }| j                  | j                  |      }| j                  | j                  |      }| j                  |      }| j                  ||      }|||||||d	| | j                   d| |d

S )Nrt   r   r   r   ru   rZ   r   r{  r   )
rZ   install_skill
page_title
body_titlebody_summaryweekly_installsinstall_commandr  r  security_audits)r   r   _INSTALL_CMD_RErx   r|   r   _extract_repo_slug_extract_first_match_PAGE_H1_RE_PROSE_H1_RE_PROSE_P_RE_extract_weekly_installs_extract_security_auditsr  )r   rX   r  r   default_reposkill_tokenrZ   r  r  install_match
repo_valuer  r  r  r  r  s                   r%   r  z!SkillsShSource._parse_detail_paged  s     a(u:>(1U1XJ/Ah#,,33D9+11!4::<O'--f5;BBDJ*009J]QQSM**:6>$D..t/?/?F
..t/@/@$G
001A1A4H77=77jI *$$(..-dV4!]]O1ZL9.
 	
r'   Nr  c                    |j                  dd      }t        |      dk  ry |d    d|d    }t        |t              r|j	                  d|      n|}|d   j                  d      d   }|g}t        |t              rD|j                  |j	                  dd	      |j	                  d
d	      |j	                  dd	      g       g d}|D ]H  }		 | j                  j                  ||	      }
|
D ]$  }| j                  ||      s|j                  c c S  J | j                  j                  ||      }|r|S 	 d| d}t        j                  || j                  j                  j                         dd      }|j                  dk(  r|j!                         }t        |t"              r|D ]  }|j	                  d      dk7  r|d   }|j%                  d      r/|dv r4| d| d| }| j                  j'                  |      }|r|j                  c S 	 | j                  j                  ||dz         }
|
D ]$  }| j                  ||      s|j                  c c S   y # t        $ r Y w xY w# t        $ r Y w xY w# t        $ r Y y w xY w)Nrt   r   r   r   ru   rZ   r   r  r   r  r  )r_  z.agents/skills/z.claude/skills/r  r  r  Tr  r  r  r  r   r  >   .agents.clauder.   )r   r   r   rf   r#   re  r   rv  r)  _matches_skill_tokensrX   r  r   r  r   r   r(  re   r   r>  )r   rX   r  r   r  rZ   r  tokens
base_paths	base_pathr.   r  tree_resultroot_urlr   r  r  r  	direct_ids                      r%   r  z#SkillsShSource._discover_identifier  s     a(u:>(1U1XJ/3=fd3Kvzz&,/Q]!HNN3'+}fd#MM

?B/

<,

<,  G
# 	+I99$	J  +--dF;??*+	+ kk::4M	6tfJGH99Xt{{/?/?/K/K/M%'$@D3&))+gt,!( 7 99V,5$#(=#..z:$#'GG$'+fAhZq$F	#{{229=#'??2%%)[[%E%EdHWZN%[F %+ 7D#99$G'+ 67%70 [  J  ) %$%
  		sU   I3CI9 I*.I9 I9 I9 	I'&I'*	I63I9 5I66I9 9	JJc                     | j                  |      D ]#  }| j                  j                  |      }|s!|c S  | j                  ||      }|r| j                  j                  |      S y r  )r  r   r>  r  )r   rX   r  rm  r  r  s         r%   r  z#SkillsShSource._resolve_github_meta  sj    44Z@ 	I;;&&y1D	
 ,,Z,G;;&&x00r'   r  r  c                    d|_         | j                  |      |_        | j                  |      |_        t        |j                        }|j                  | j                  ||             ||_        t        |t
              rR|j                  d      }|j                  d      }|r	||_        |S |j                  r|r|j                   d| d|_        |S )Nr  r  r  r  z weekly installs on skills.sh)rW   r3  rX   rC  rY   rf   r_   r  r  r   r#   rV   )r   r  r  r  merged_extrar  r  s          r%   r  z%SkillsShSource._finalize_inspect_meta  s    !//	://	:DJJ'D44YGH!
fd#!::n5L$jj):;O#/   !!o&*&6&6%7tO;LLi#j r'   skill_tokensc                    t               }|j                  | j                  |j                               |j                  | j                  |j                               |j                  | j                  |j
                  r|j
                  j                  dd      d   nd              |D ]  }| j                  |      }||z  s y y)Nrt   r   r   TF)ry   r  _token_variantsr   r[   rX   r   )rl  r  r  
candidatesr   variantss         r%   r  z$SkillsShSource._matches_skill_tokens  s    U
#--dii89#--dii89#--SWSbSbdoo.C.CC.KB.Ohlmn! 	E**51H*$	 r'   valuec                    | s
t               S t        j                  t        |             j	                         j	                  d      j                         }|s
t               S |j                  d      d   }t        j                  dd|      j	                  d      }|r|j                  d      d   nd}|j                  d      d   }|j                  d      }|j                  d      d   }||j                  dd      |j                  dd      ||j                  dd      |j                  dd      ||r|j                  dd      nd|||j                  dd      h}|D ch c]  }|s|	 c}S c c}w )Nrt   r   z[^a-z0-9/_-]+-r   @r   )ry   ry  _strip_htmlrc   r   rR  r   r   rr  r  rv   )	r  plainbase	sanitizedsanitized_base
slash_tailslash_tail_cleanr  rU  s	            r%   r  zSkillsShSource._token_variants  sU   5L**3u:6<<>DDSIOOQ5L{{3#FF+S%8>>sC	5>-b1B[[%b)
%,,S1+11#6r: MM#s#MM#s#LLc"LLc"+4Ic3'"$$S#.
 $)aq)))s   "E1*E1r  c                     | j                         } | j                  d      r| t        d      d  } | j                  d      } | j                  d      }t        |      dk\  r|d    d|d    S y )Nr   rt   r   r   ru   )r   r   r   r   )r  r   s     r%   r  z!SkillsShSource._extract_repo_slug  sy    %%'
  !67#C(=$>$?@J%%c*
  %u:?AhZqq
++r'   patternr  c                     | j                  |      }|sy t        d |j                         D        d       }|y t        j	                  |      j                         xs d S )Nc              3   &   K   | ]	  }|s|  y wr!   r*   )r   r|   s     r%   r   z6SkillsShSource._extract_first_match.<locals>.<genexpr>&  s     A5eAs   )rx   nextgroupsry  r  r   )r  r  r   r  s       r%   r  z#SkillsShSource._extract_first_match!  sU    t$AA4H=))%0668@D@r'   c                     |j                  dd      }t        |      dk\  r|d    d|d    nd}d| j                   d| i}|rd| |d<   t        |t              r d	D ]  }|j                  |      }|s|||<    |S )
Nrt   r   r   ru   r   r  r   r  )r  r  r  r  r  )r   r   r  r   rf   r#   )r   r  r  r   rZ   rj   r   r  s           r%   r  z"SkillsShSource._detail_to_metadata+  s    Q'+.u:?%(1U1XJ'T]]O1YK8
 %8#?HZ fd#j *

3$)HSM* r'   c                 h    t         j                  j                  |       }|sy |j                  d      S )Nr  )ry  _WEEKLY_INSTALLS_RErx   r|   )r  r   s     r%   r  z'SkillsShSource._extract_weekly_installs:  s-    2299$?{{7##r'   c                     i }dD ]n  }| j                  d|       }|dk(  r| ||dz    }t        j                  d|t        j                        }|sM|j	                  d      j                         ||<   p |S )N)zagent-trust-hubsocketsnykz
/security/r   r  z(Pass|Warn|Fail)ru   )findr   rx   rs  r|   r  )r  rX   auditsauditidxwindowr   s          r%   r  z'SkillsShSource._extract_security_auditsA  s    !#: 	7E))j01Cby#cCi(FII162==IE %A 4 4 6u	7 r'   c                 0    t        j                  dd|       S )Nz<[^>]+>r   )r   rr  )r  s    r%   r  zSkillsShSource._strip_htmlN  s    vvj"e,,r'   c                 Z    d}|D ]#  }| j                  |      s| t        |      d  c S  | S )N)
skills-sh/
skills.sh/z	skils-sh/z	skils.sh/r   r   )rX   prefix_aliasesr  s      r%   r  z$SkillsShSource._normalize_identifierR  sA    
 % 	0F$$V,!#f+,//	0 r'   c                 6   | j                  dd      }t        |      dk  r| gS |d    d|d    }|d   j                  d      }| d| | d| | d| | d| g}t               }g }|D ])  }||vs|j	                  |       |j                  |       + |S )	Nrt   r   r   r   ru   /skills/z/.agents/skills/z/.claude/skills/)r   r   r  ry   r   rw  )rX   r   rZ   r  r  r~  dedupedrm  s           r%   r  z%SkillsShSource._candidate_identifiers_  s      a(u:><(1U1XJ'1X__S)
fAj\"fHZL)f$ZL1f$ZL1	

 u# 	*I$#y)	* r'   c                     d|  S )Nr  r*   )rX   s    r%   r3  zSkillsShSource._wrap_identifierv  s    J<((r'   rD  r!   );rK   r`   ra   rb   r  r  r  r   rv  rs  r  r  r  r  DOTALLr  r  r  r  r   r   rc   r@  rC  r%  r   rU   rx   r   rh   r<  r>  r  r  rf   r  r  r  r  r  r  rw  r2  r  r%  ry   r  r  Patternr  r   r   r  r  r  r  r  r  r3  r*   r'   r%   ry  ry  ?  s;   Z"H:[)J
 < bjj!6FO"

^
  RZZ lmN bjj	0
O
 "**<bmmbii>WXK2::\
		!L "**Y
		!K %"**%aceclclm.Z .3 S# S# S#C # #T)_ #J (= *# (9*= Y9c Y9d9o Y9v&c &d9o &P"
4 "
HY4G "
HS Xd^ $#
S #
 #
 #
JCs CHTN CV^_bVc CJ	s 	HTN 	V^_hVi 	9  hW[n aj " 
 
$s) 
PT 
 
 *x} *S * *< s x}   Abjj A A A AS (4. TRUWZRZ^  $s $x} $ $ 
s 
 
S#X 
 
 -3 -3 - - 
# 
# 
 
 3 49  , )S )S ) )r'   ry  c            	       0   e Zd 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(de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"y&)*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/v1   r(   c                      y)Nclawhubr*   r   s    r%   r@  zClawHubSource.source_id      r'   rX   c                      yr+  r*   r;  s     r%   rC  zClawHubSource.trust_level_for  r,  r'   r^   c                     t        | t              r| D cg c]  }t        |       c}S t        | t              r(| D cg c]  }t        |      dk7  st        |       c}S g S c c}w c c}w )Nlatest)r   re   rc   rf   )r^   r  ks      r%   _normalize_tagszClawHubSource._normalize_tags  sX    dD!$()qCF))dD!$(?qCFh,>CF??	 *?s   A$A)A)r  c                     t        | t              sy | j                  d      }t        |t              r)t        |      }| j                  d      }|	d|vr||d<   |S | S )Nr{  latestVersion)r   rf   r#   )r  nestedmergedlatest_versions       r%   _coerce_skill_payloadz#ClawHubSource._coerce_skill_payload  s_    $%'"fd#&\F!XXo6N)oV.K*8'Mr'   r5  c                 v    t        j                  d| j                               D cg c]  }|s|	 c}S c c}w )Nz
[^a-z0-9]+)r   r   rR  )r5  terms     r%   _query_termszClawHubSource._query_terms  s)    !#-!GP4PPPs   66r  c                 j   |j                         j                         }|sy|j                  xs dj                         }|j                  xs dj                         }|j                  xs dj                         }dj                  | j                  |            }dj                  | j                  |            }| j                  |      }	| j                  |      }
| j                  |      }d}||k(  r|dz  }||k(  r|dz  }||k(  r|dz  }||k(  r|dz  }|j                  |      r|d	z  }|j                  |      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 s|dz  }  |S )Nru   r   rs  r         }   x   _   Z   F   A   (   #   r  r  r  r   )	r   rR  rX   r   rV   r   r"  r   r   )rl  r5  r  
query_normrX   r   rV   normalized_identifiernormalized_namequery_termsidentifier_terms
name_termsscorer!  s                 r%   _search_scorezClawHubSource._search_score  s   [[]((*
oo+224
		R&&(''-2446 #)9)9*)E F((3#3#3D#9:&&z2++J7%%d+
#SLESLE J.SLEj(SLE ++J7RKE%%j1RKE+,>c+.>?;NRKE:&8K(89[HRKE#RKERKE$RKE 	D''z!{"
	 r'   rV  c                     t               }g }| D ]Q  }|j                  xs |j                  j                         }||v r0|j	                  |       |j                  |       S |S r!   )ry   rX   r   rR  r   rw  )rV  r~  r  r  r   s        r%   _dedupe_resultszClawHubSource._dedupe_results  sb    #% 	#F$$3::<Cd{HHSMNN6"	# r'   c                    |j                         j                  d      d   }| j                  |      }g }|r't        j                  d|      r|j                  |       |rWdj                  |      }t        |      dk\  r'|j                  | d| d| d| d	| d
|g       n|j                  |       t               }|D ]/  }||v r|j                  |       | j                  |      }|s-|c S  y )Nrt   r   z[A-Za-z0-9][A-Za-z0-9._-]*r  r   z-agentz-skillz-toolz
-assistantz	-playbook)r   r   r"  r   r   rw  r   r   re  ry   r   r>  )	r   r5  slugr1  r  	base_slugr~  rm  r  s	            r%   _exact_slug_metazClawHubSource._exact_slug_meta  s   {{}""3'+''. "
BLL!>Ed#-I;1$!! k( k( k' k, k+#  !!),# 	ID HHY<<	*D	 r'   r6  c                     |j                         s j                  |      d | S |D cg c]  } j                  |      dkD  s| }}|j                   fd        j                  |      } j	                        }|r:|D cg c]  } j                  |      dk\  s| }} j                  |g|z         }|r|d | S t        j                  d      rg S  j                  |      d | S c c}w c c}w )Nr   c                     j                  |        | j                  j                         | j                  j                         fS r!   )r5  r   rR  rX   )r  r.  r   s    r%   <lambda>z8ClawHubSource._finalize_search_results.<locals>.<lambda>	  s;    ##J55		!%%' r'   r   r   z[A-Za-z0-9][A-Za-z0-9._/-]*)r   r7  r5  sortr;  r   r   )r   r5  rV  r6  r  filteredexactr.  s   `      @r%   _finalize_search_resultsz&ClawHubSource._finalize_search_results	  s   [[]
''0%88%,YT0B0B:t0TWX0XDYY 	 	
 ''1%%j1)1`T5G5G
TX5Y]_5_`H`++UGh,>?HFU##<<6
CI##G,Ve44+ Z as   C5C5C:'C:c                 `   |j                         }|rL| j                  |      }t        |      dk\  r| j                  |      }|r|gS | j	                  ||      }|rG|S | j                  |dkD  r|nd      }|r*|dkD  r| j                  |      d | S | j                  |      S dt        j                  |j                               j                          d| }t        |      }|*| j                  ||D 	cg c]  }	t        di |	 c}	|      S 	 t        j                  | j                    d||dd	
      }
|
j"                  dk7  rg S |
j%                         }t+        |t,              r|j                  d|      n|}t+        |t.              sg S g }|d | D ]  }|j                  d      }|s|j                  d      xs |j                  d      xs |}|j                  d      xs |j                  d      xs d}| j1                  |j                  dg             }|j3                  t        ||d|d|              | j                  |||      }t5        ||D 	cg c]  }	t7        |	       c}	       |S c c}	w # t        j&                  t$        j(                  f$ r g cY S w xY wc c}	w )Nr   r6  r   )	max_itemsclawhub_search_listing_v1_r   /skills)rx   r6  r  r  r  r  r9  displayNamer   summaryrV   r   r^   r  rB  r   rV   rW   rX   rY   r^   r*   )r   r"  r   r;  _search_catalog_load_catalog_indexr7  rJ  rK  r&  rL  rM  rC  rU   r   r#   r  r   r(  r   r  r   rf   re   r  rw  rN  r  )r   r5  r6  r1  directrV  catalogr  r  r  r   r  skills_datar  r9  display_namerJ  r^   final_resultss                      r%   rx   zClawHubSource.search'	  s   ++E2K;1$..u5"8O**5*>G ..%!)QR.SG@E	t++G4Ve<ltOcOcdkOll 1U\\^1L1V1V1X0YYZ[`Zab	"9-00)/0AQ0 
	99==/)"'%8D
 3&	99;D 2<D$1Gdhhw-T+t,I' 	D88F#D88M2Ndhhv6FN$Lhhy)JTXXm-DJG''(<=DNN9!# ' 	  55eWeL9}&U!':1'=&UVO 1 !5!56 	I	2 'Vs$   8I;
8J  J   J+
 %J('J(c                 l   |j                  d      d   }| j                  | j                   d|       }t        |t              sy | j                  ||      }|st        j                  d|       y | j                  ||      }d|vr| j                  | j                   d| d|       }t        |t              rP| j                  |      xs |}d|vr7|j                  di       }t        |t              r| j                  |      xs |}d|vrt        j                  d||       y t        ||d	|d
      S )Nrt   r   r
  z=ClawHub fetch failed for %s: could not resolve latest versionr  z
/versions/versionzLClawHub fetch for %s resolved version %s but could not retrieve file contentr  rB  r   ri   rW   rX   rY   )r   	_get_jsonr  r   rf   _resolve_latest_versionr   r   _download_zip_extract_filesr#   rh   )r   rX   r9  
skill_datar  ri   version_datar  s           r%   r<  zClawHubSource.fetcho	  sB   $R(^^t}}oXdV$DE
*d+55dJGNNZ\`a ""48 U">>T]]O8D6TbSc*deL,-++L9BUU*)--i<F!&$/ $ 3 3F ; DuU"NN^
 #
 	
r'   c                    |j                  d      d   }| j                  | j                  | j                   d|             }t	        |t
              sy | j                  |j                  dg             }t        |j                  d      xs( |j                  d      xs |j                  d      xs ||j                  d      xs |j                  d	      xs d
d|j                  d      xs |d|      S )Nrt   r   r
  r^   rI  r   r9  rJ  rV   r   r  rB  rK  )	r   r  rV  r  r   rf   r  r#   rU   )r   rX   r9  r  r^   s        r%   r>  zClawHubSource.inspect	  s    $R())$..DMM?(SWRX9Y*Z[$%##DHHVR$89-(XDHHV,<X@PXTX+Ltxx/FL"xx'/4#
 	
r'   c           	      r   dt        j                  | d| j                               j                          }t	        |      }||D cg c]  }t        di | c}d | S | j                         }|sg S | j                  |||      }t        ||D cg c]  }t        |       c}       |S c c}w c c}w )Nclawhub_search_catalog_v1_r  r*   )
rJ  rK  r&  rL  rM  rU   rM  rC  rN  r  )r   r5  r6  r  r  r  rO  rV  s           r%   rL  zClawHubSource._search_catalog	  s    0waw=O=V=V=X1Y1c1c1e0fg	"9-,23qINN3FU;;**,I//wF9w&O!':1'=&OP 4 'Ps   B/B4
rF  c                 >   d}t        |      }||D cg c]  }t        di | c}S d}g }t               }d}|dkD  r!t        j                         | j
                  z   nd}	d}
d}t        |      D ]  }|	t        j                         |	kD  rd}
 nddi}|r||d	<   	 t        j                  | j                   d
|d      }|j                  dk7  r nm|j                         }t        |t              r|j                  dg       ng }t        |t               r|s n$|D ]  }|j                  d      }t        |t"              r|r||v r+|j%                  |       |j                  d      xs |j                  d      xs |}|j                  d      xs |j                  d      xs d}| j'                  |j                  dg             }|j)                  t        ||d|d|              t        |t              r|j                  d      nd}t        |t"              r|s n|dkD  st+        |      |k\  sd} n |
s%|s#t-        ||D cg c]  }t/        |       c}       |S c c}w # t        j                  t        j                  f$ r Y  Tw xY wc c}w )u  Walk the ClawHub catalog via cursor pagination.

        ``max_items`` bounds the walk: once at least that many distinct skills
        have been gathered the walk stops early. This is what browse's
        cold-start fallback wants — it only renders one page, so walking the
        entire 50k+ catalog just to slice off the first N is pure waste.
        ``max_items=0`` (the default, used by the offline index builder) means
        walk to exhaustion.

        Caching: only a *complete* catalog (cursor exhausted or page cap) is
        written to the shared ``clawhub_catalog_v1`` cache. A walk truncated by
        ``max_items`` OR the wall-clock budget is partial, so caching it would
        poison the full-catalog cache with an incomplete slice.
        clawhub_catalog_v1Ni  r   FTr6  r  cursorrH  r  r  r  r9  rI  r   rJ  rV   r   r^   r  rB  rK  
nextCursorr*   )rM  rU   ry   r   	monotonicCATALOG_WALK_BUDGET_SECONDSr   r   r#   r  r   r(  r   r  r   rf   re   rc   r   r  rw  r   rN  r  )r   rF  r  r  r  ra  rV  r~  	max_pagesdeadlinehit_deadlinehit_max_itemsr   r  r   r  r  r  r9  rQ  rJ  r^   s                         r%   rM  z!ClawHubSource._load_catalog_index	  s    )	"9-,23qINN33 $#% 	 1} NNt??? 	
 y! .	A#(88(C#&-s^F#)x yyDMM?'!:6SUV##s*yy{ .8d-CDHHWb)EeT*% xx'!$,DDDL#xx6R$((6:JRd((9-N-1HNB++DHHVR,@Ay% '$# +  " 0:$/ETXXl+4Ffc*&
 1}W!: $].	h My7*Sa+>q+A*ST[ 4H OOT%9%9: P +Ts#   I+4I0I0J
0#JJr   r   c                     	 t        j                  ||      }|j                  dk7  ry |j                         S # t         j                  t        j
                  f$ r Y y w xY w)Nr   r  )r   r#   r   r(  r   r  )r   r   r   r   s       r%   rV  zClawHubSource._get_json
  sU    	99S'2D3&99;!5!56 		s   &9 9 #AAr9  rZ  c                    |j                  d      }t        |t              r%|j                  d      }t        |t              r|r|S |j                  d      }t        |t              r%|j                  d      }t        |t              r|r|S | j	                  | j
                   d| d      }t        |t              r<|r:|d   }t        |t              r%|j                  d      }t        |t              r|r|S y )Nr  rT  r^   r  r
  z	/versionsr   )r#   r   rf   rc   rV  r  re   )	r   r9  rZ  r  rT  r^   
latest_tagversions_datafirsts	            r%   rW  z%ClawHubSource._resolve_latest_version"
  s    0fd#jj+G'3'G~~f%dD!(+J*c*z!!$--i'PQmT*}!!$E%&))I.gs+"Nr'   r[  c                    i }|j                  d      }t        |t              r5|j                         D ci c]  \  }}t        |t              s|| c}}S t        |t
              s|S |D ]  }t        |t              s|j                  d      xs |j                  d      }|rt        |t              sK|j                  d      }t        |t              r|||<   r|j                  d      xs$ |j                  d      xs |j                  d      }	t        |	t              s|	j                  d      s| j                  |	      }
|
|
||<    |S c c}}w )	Nri   r[   r   r  rawUrldownloadUrlr   http)r#   r   rf   r  rc   re   r   r9  )r   r[  ri   	file_listr  rU  	file_metafnameinline_contentraw_urlr  s              r%   rY  zClawHubSource._extract_files8
  s5    " $$W-	i&%.__%6MTQ*Q:LAqDMM)T*L" 	+Ii.MM&)BY]]6-BE
5# 6&]]95N.#.-emmH-e}1MeQZQ^Q^_dQeG'3'G,>,>v,F**73&#*E%L#	+& 1 Ns   EErT  c                    ddl }ddl}i }d}t        |      D ]  }	 t        j                  | j
                   d||ddd      }|j                  d	k(  rd	 t        |j                  j	                  d
d            }	t        |	d      }	t        j                  d||	|dz   |       t        j                  |	       |j                  dk7  r&t        j                  d|||j                         |c S |j!                  |j#                  |j$                              5 }
|
j'                         D ]  }|j)                         r	 t+        |j,                        }|j.                  dkD  r"t        j                  d||j.                         [	 |
j1                  |j,                        }|j3                  d      ||<    	 ddd       |c S  t        j                  d||       |S # t        t        f$ r d}	Y vw xY w# t        $ r# t        j                  d|j,                         Y w xY w# t4        t6        f$ r t        j                  d|       Y #w xY w# 1 sw Y   xY w# |j8                  $ r t        j;                  d||       |cY c S t        j<                  $ r&}t        j                  d|||       |cY d}~c S d}~ww xY w)zRDownload skill as a ZIP bundle from the /download endpoint and extract text files.r   Nr   z	/download)r9  rT  r  T)r  r   r   r  zretry-after5rS   r  zEClawHub download rate-limited for %s, retrying in %ds (attempt %d/%d)ru   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)r  z!Skipping non-text file in ZIP: %sz'ClawHub returned invalid ZIP for %s v%sz*ClawHub ZIP download failed for %s v%s: %sz1ClawHub ZIP download exhausted retries for %s v%s)iozipfiler   r   r#   r  r   r%  r   r   r  r  r   r   r   r  ZipFileBytesIOr  infolistis_dirr~   filename	file_sizereadr  r  KeyError
BadZipFiler   r   )r   r9  rT  ry  rz  ri   r  r  r   r  zfr   r   r   r   s                  r%   rX  zClawHubSource._download_zipW
  s    "[) 3	G2yy}}oY/$(W=%)	 ##s*(&)$,,*:*:=#*N&O #&k2"6KLL_k7Q; JJ{+##s*LL!NPTV]_c_o_op L__RZZ%=> %" " %;;=$%#<T]]#KD
  >>G3"LL)TVZ\`\j\jk$%"$''$--"8C*-**W*=E$K%%( Y3	j 	H$PWXW '	2 (&'(&  * %"LL)NPTP]P]^$% !3H= %"LL)LdS$%!% %, %% H$PWX?? I4QXZ]^s   8I%G;=I92I.*I%I>G51I/H$4I7	IG2.I1G22I5)H!	I H!	!I$%I		II	II	I(K K J;3K ;K c                 X    t        |d      }||j                  dk(  r|j                  S y rQ  rR  )r   r   r   s      r%   r9  zClawHubSource._fetch_text
  s.     b1 0 0C 799r'   NrD  )r   )r   )#rK   r`   ra   rb   r  rd  rc   r@  rC  r%  r   r   r  r   r   r  r"  rw  rU   r%  r5  r7  r;  rC  rx   rh   r<  r>  rL  rM  rV  rW  rY  rX  r9  r*   r'   r%   r  r    s    +H #%3 # #  c d3i   
C 
HT#s(^,D 
 
 QC QDI Q Q .# .Y .3 . .` 	i 	T)_ 	 	c hy.A B5c 5DO 5TW 5\`aj\k 58FC F FT)_ FP(
 (
(= (
T
# 
(9*= 
"S  d9o _S _i _BS 3  C T#s(^ PXY\P] ,4S> d38n >=# = =S#X =~s x} r'   r  c                       e Zd ZdZddg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e   fdZy)ClaudeMarketplaceSourcez
    Discover skills from Claude Code marketplace repos.
    Marketplace repos contain .claude-plugin/marketplace.json with plugin listings.
    rM  zaiskillstore/marketplacer  c                 4    || _         t        |      | _        y r{  r}  r~  s     r%   r   z ClaudeMarketplaceSource.__init__
  s    	 #-r'   r(   c                      yNclaude-marketplacer*   r   s    r%   r@  z!ClaudeMarketplaceSource.source_id
  s    #r'   c                 .    | j                   j                  S )zDWhether the underlying GitHub API hit a rate limit during the crawl.)r   rl  r   s    r%   rl  z'ClaudeMarketplaceSource.is_rate_limited
  s     {{***r'   rX   c                 p    |j                  dd      }t        |      dk\  r|d    d|d    }|t        v ryyrn  rp  rq  s       r%   rC  z'ClaudeMarketplaceSource.trust_level_for
  sE      a(u:?AhZqq
+D}$ r'   r5  r6  c                    g }|j                         }| j                  D ]  }| j                  |      }|D ]  }|j                  dd       d|j                  dd       j                         }||v s=|j                  dd      }	|	j	                  d      r| d|	dd   }
nd|	v r|	}
n| d|	 }
|j                  t        |j                  dd      |j                  dd      d	|
| j                  |
      |
               |d | S )Nr   r   rs  rV   rW   z./rt   r   r  )r   rV   rW   rX   rY   rZ   )rR  KNOWN_MARKETPLACES_fetch_marketplace_indexr#   r   rw  rU   rC  )r   r5  r6  rV  ry  marketplace_repopluginspluginr|  source_pathrX   s              r%   rx   zClaudeMarketplaceSource.search
  s*   #%kkm $ 7 7 	334DEG!  &

62 67qMSU9V8WX^^`
*,"(**Xr":K"--d3(8'9;qr?:K%L
+%0
(8'9;-%H
NN9#ZZ3$*JJ}b$A3#-$($8$8$D-$ 	, vr'   c                 N    | j                   j                  |      }|rd|_        |S r  )r   r<  rW   )r   rX   r   s      r%   r<  zClaudeMarketplaceSource.fetch
  s%    "":.0FMr'   c                 z    | j                   j                  |      }|rd|_        | j                  |      |_        |S r  )r   r>  rW   rC  rY   )r   rX   r  s      r%   r>  zClaudeMarketplaceSource.inspect
  s8    {{"":..DK#33J?Dr'   rZ   c                    d|j                  dd       }t        |      }||S d| d}| j                  j                  |i | j                  j                         ddi      }||j                  d	k7  rg S 	 t        j                  |j                        }|j                  d
g       }t        ||       |S # t        j                  $ r g cY S w xY w)z<Fetch and parse .claude-plugin/marketplace.json from a repo.claude_marketplace_rt   r   r  z)/contents/.claude-plugin/marketplace.jsonr   r  r  r  r  )rv   rM  r   r  r  r   r   r(  r  r  r  r#   rN  )r   rZ   r  r  r   r   r  r  s           r%   r  z0ClaudeMarketplaceSource._fetch_marketplace_index
  s    )$,,sC*@)AB	"9-M-dV3\]{{&&Ztyy,,.Z:YZ ' 
 <4++s2I	::dii(D ((9b)9g. ## 	I	s   :B9 9CCNrD  )rK   r`   ra   rb   r  r   r   rc   r@  r$  r2  rl  rC  r%  r   rU   rx   r   rh   r<  r>  rf   r  r*   r'   r%   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y)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.jsonr(   c                      y)Nlobehubr*   r   s    r%   r@  zLobeHubSource.source_id  r  r'   rX   c                      yr+  r*   r;  s     r%   rC  zLobeHubSource.trust_level_for  r,  r'   r5  r6  c                    | j                         }|sg S |j                         }g }t        |t              r|j	                  d|      n|}t        |t
              sg S |D ]  }|j	                  d|      }|j	                  d|j	                  dd            }	|j	                  dd      }
|j	                  dg       }|	 d|
 dt        |t
              rdj                  |      nd j                         }||v rh|j	                  d|	j                         j                  dd	            }|j                  t        ||
d d
 dd| dt        |t
              r|ng              t        |      |k\  s |S  |S )Nagentsr  r  rX   r   rV   r^   rs  r  r  r  lobehub/rB  rK  )_fetch_indexrR  r   rf   r#   re   r   rv   rw  rU   r   )r   r5  r6  ri  ry  rV  r  agentr  r  descr^   r|  rX   s                 r%   rx   zLobeHubSource.search  sf   !!#Ikkm#%/9%/F8U+E&$'I 	E99VU+DHHWeiib&ABE88M2.D88FB'D!7!D6Zd=S388D>Y[*\]cceJj("YY|U[[]5J5J3PS5TU
y# $Tc
$!)*6 +!+D$!7R   7|u$+	* r'   c                     |j                  d      r|j                  dd      d   n|}| j                  |      }|sy | j                  |      }t	        |d|idd| d      S )	Nr  rt   ru   r   r  r  rB  rU  )r   r   _fetch_agent_convert_to_skill_mdrh   )r   rX   agent_id
agent_datarq   s        r%   r<  zLobeHubSource.fetch;  sw    3=3H3H3T:##C+B/Zd&&x0
,,Z8x(!(,#
 	
r'   c                    |j                  d      r|j                  dd      d   n|}| j                         }|sy t        |t              r|j                  d|      n|}t        |t              sy |D ]  }|j                  d      |k(  s|j                  d|      }t        ||j                  dd	      d
d| dt        |j                  d      t              r|j                  dg       ng       c S  y )Nr  rt   ru   r   r  rX   r  rV   r   r  rB  r^   rK  )r   r   r  r   rf   r#   re   rU   )r   rX   r  ri  r  r  r  s          r%   r>  zLobeHubSource.inspectL  s    3=3H3H3T:##C+B/Zd!!#/9%/F8U+E&$' 
	Eyy&(2yy/ ! $ ;$!)(4 +1;DHHV<Ld1S&"-Y[ 
	 r'   c                    d}t        |      }||S 	 t        j                  | j                  d      }|j                  dk7  ry|j                         }t        ||       |S # t        j                  t
        j                  f$ r Y yw xY w)z2Fetch the LobeHub agent index (cached for 1 hour).lobehub_indexNr  r   r  )	rM  r   r#   	INDEX_URLr   r(  r   r  rN  )r   r  r  r   r  s        r%   r  zLobeHubSource._fetch_indexc  s    #	"9-M	99T^^R8D3&99;D 	9d+	 !5!56 		s   0A" A" "#BBr  c                    d| d}	 t        j                  |d      }|j                  dk(  r|j                         S 	 y# t         j                  t        j
                  f$ r }t        j                  d|       Y d}~yd}~ww xY w)z!Fetch a single agent's JSON file.z https://chat-agents.lobehub.com/r  r  r   r  zLobeHub agent fetch failed: %sN)r   r#   r   r(  r   r  r   r   )r   r  r   r   r  s        r%   r  zLobeHubSource._fetch_agentu  s    0
%@	>99S"-D3&yy{" '  !5!56 	>LL91==	>s   5A   #B#A>>Br  c           
         | j                  d|       }| j                  dd      }|j                  d|      }|j                  dd      }|j                  dg       }| j                  di       j                  d	d      }t        |t              r|ng }d
d| d|dd  ddddj                  d |D               dddd
g	}d| d|ddd|r|ndg}	dj                  |      dz   dj                  |	      z   dz   S )z2Convert a LobeHub agent JSON into SKILL.md format.r  rX   zlobehub-agentr  rV   r   r^   config
systemRoler  zname: zdescription: Nr  z	metadata:z	  hermes:z    tags: [, c              3   2   K   | ]  }t        |        y wr!   )rc   r   r  s     r%   r   z5LobeHubSource._convert_to_skill_md.<locals>.<genexpr>  s     #=qCF#=s   ]z
  lobehub:z    source: lobehubz# z## Instructionsz(No system role defined)
z

)r#   r   re   r   )
r  r  rX   r  rV   r^   system_roletag_listfm_lines
body_liness
             r%   r  z"LobeHubSource._convert_to_skill_md  s$    ~~fj1^^L/B
*-hh}b1xx# nnXr266|RH%dD14rZL!K-./$))#=H#==>a@!

 L&K,F

 yy"V+dii
.CCdJJr'   NrD  )rK   r`   ra   rb   r  rc   r@  rC  r%  r   rU   rx   r   rh   r<  r>  r   r  rf   r  r%  r  r*   r'   r%   r  r  	  s     =I3 # # !C ! !T)_ !F
 
(= 
"# (9*= .hsm $	S 	Xd^ 	  K  K#  K  Kr'   r  c                       e Zd ZdZdZdZdZdefdZdedefdZ	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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fdZy)BrowseShSourceu\  Discover and install site-specific browser automation skills from browse.sh.

    browse.sh (https://browse.sh) is Browserbase's catalog of 200+ SKILL.md files
    that describe how to automate specific websites (Airbnb, Amazon, arXiv, etc.).
    The catalog lives at ``/api/skills`` and each skill's actual SKILL.md content
    is fetched via ``/api/skills/{slug}`` which returns a ``skillMdUrl`` field
    pointing at a CDN-hosted blob — the catalog's ``sourceUrl`` field is a GitHub
    HTML URL whose underlying repository is not always public, so it cannot be
    relied on for content fetch.
    zhttps://browse.sh/api/skillsz#https://browse.sh/api/skills/{slug}browse_sh_catalogr(   c                      y)N	browse-shr*   r   s    r%   r@  zBrowseShSource.source_id  r,  r'   rX   c                      yr+  r*   r;  s     r%   rC  zBrowseShSource.trust_level_for  r,  r'   c                    t        | j                        }||S 	 t        j                  | j                  d      }|j
                  dk7  rg S |j                         }t        |t              r|j                  dg       ng }t        |t              rt        | j                  |       t        |t              r|S g S # t        j                  t        j                  f$ r g cY S w xY w)Nr   r   r  r.   )rM  
_CACHE_KEYr   r#   CATALOG_URLr   r(  r   r  r   rf   re   rN  )r   r  r   r  r.   s        r%   _fetch_catalogzBrowseShSource._fetch_catalog  s    "4??3M	99T--r:D3&	99;D ,6dD+A(B'rfd#t7#FD1v9r9 !5!56 	I	s   1B; B; ;%C#"C#r  c                    |j                  dd      }|j                  dd      }|j                  d|      }|j                  d|      }|r|sy t        |      dkD  r|d d dz   }t        ||d	d
| d|j                  dg       ||j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      d      S )Nr9  r   r   r  rV   i   i  z...r  
browse-sh/rB  r^   hostnamer   	sourceUrlrecommendedMethodproxiesFinstallCountr   )r9  r  r   r   recommended_methodr  install_count)r   rV   rW   rX   rY   r^   r_   )r#   r   rU   )r   r  r9  r   r  rV   s         r%   _item_to_metazBrowseShSource._item_to_meta  s    xx#xx#$'hh}e44{d"%et,u4K##D6*#&"% HHZ4 HHZ4"hh{B7&*hh/BB&G88Iu5!%.!!<
 	
r'   r5  r6  c                    | j                         }|j                         }g }|D ]  }dj                  |j                  dd      |j                  dd      |j                  dd      |j                  dd      |j                  dd      dj                  |j                  dg             g      j                         }|r||v r$| j	                  |      }|r|j                  |       t        |      |k\  s |S  |S )	Nrs  r   r   r  rV   r  r   r^   )r  rR  r   r#   r  rw  r   )	r   r5  r6  rO  ry  rV  r  r  r  s	            r%   rx   zBrowseShSource.search  s    %%'kkm 	D88$"%+R(R(&"-.  uw  +"5))$/NN4(7|u$	 r'   c                     | j                  |      }|sy | j                         }|D ])  }|j                  d      |k(  s| j                  |      c S  y )Nr9  )_slug_from_identifierr  r#   r  )r   rX   r9  rO  r  s        r%   r>  zBrowseShSource.inspect  s\    ))*5%%' 	0Dxx4'))$//	0 r'   c                   	 | j                  |      		sy | j                         }t        	fd|D        d       }|sy | j                  	|      }|sy 	 t	        j
                  |dd      }|j                  dk7  ry |j                  }| j                  |      }|r|j                  n	j                  d      d   }t        |d|id	|d
	|j                  dd      |j                  dd      |d      S # t        j                  $ r Y y w xY w)Nc              3   L   K   | ]  }|j                  d       k(  s|  yw)r9  N)r#   )r   ir9  s     r%   r   z'BrowseShSource.fetch.<locals>.<genexpr>  s      A1155=D+@QAs   $$r   Tr   r  rt   r   r  r  rB  r  r   r  )r9  r  r   skill_md_urlr  )r  r  r  _resolve_skill_md_urlr   r#   r   r  r   r  r   r   rh   )
r   rX   rO  r  md_urlr   r  r  r   r9  s
            @r%   r<  zBrowseShSource.fetch	  s   ))*5%%'AA4H ++D$7	99VR$GD3&iiG !!$' tyydjjob&9w'!# HHZ4"hh{B7 &	
 	
  		s   'C3 <C3 3D	D	r9  c                    	 t        j                  | j                  j                  |      dd      }|j                  dk(  rT|j                         }t        |t              r4|j                  d      }t        |t              r|j                  d      r|S t        |t              r|j                  dd	      nd	}|rd
|v r|S y# t         j                  t
        j                  f$ r Y Rw xY w)a  Resolve the SKILL.md content URL for a slug.

        Primary path: hit ``/api/skills/{slug}`` and read ``skillMdUrl``.
        Fallback: if the catalog item already has a ``raw.githubusercontent.com``
        ``sourceUrl`` (some entries may), use it directly.
        )r9  r   Tr   r  
skillMdUrlrq  r  r   zraw.githubusercontent.comN)r   r#   SKILL_DETAIL_URLformatr   r(  r   rf   rc   r   r   r  )r   r9  r  r  r  r  r   s          r%   r  z$BrowseShSource._resolve_skill_md_url1  s    	YY%%,,$,7!%F
 !!S({{}dD)!XXl3F!&#.63D3DV3L% 3=T42HTXXk2.b
5C !5!56 		s   BC #C*)C*c                 D    |j                  d      r|t        d      d S |S )zMExtract slug from identifier like 'browse-sh/airbnb.com/search-listings-abc'.r  Nr  r;  s     r%   r  z$BrowseShSource._slug_from_identifierL  s)      .c,/011r'   NrD  )rK   r`   ra   rb   r  r  r  rc   r@  rC  r   r   r  r   rU   r  r%  rx   r>  rh   r<  r  r  r*   r'   r%   r  r    s    	 1K<$J3 # # :T
 : 
$ 
8I+> 
6C  T)_ *# (9*= &
 &
(= &
P# T hsm 6  r'   r  c                       e Zd 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y)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.
    zNousResearch/hermes-agentc                 r    ddl m}  |t        t              j                  j                  dz        | _        y )Nr   )get_optional_skills_dirzoptional-skills)hermes_constantsr  r	   __file__parent_optional_dir)r   r  s     r%   r   zOptionalSkillSource.__init__c  s+    <4N!!((+<<
r'   r(   c                      y)Nofficialr*   r   s    r%   r@  zOptionalSkillSource.source_idj  s    r'   rX   c                      y)Nru  r*   r;  s     r%   rC  z#OptionalSkillSource.trust_level_form  r  r'   r5  r6  c                 *   g }|j                         }| j                         D ]m  }|j                   d|j                   ddj	                  |j
                         j                         }||v r|j                  |       t        |      |k\  sl |S  |S )Nrs  )rR  	_scan_allr   rV   r   r^   rw  r   )r   r5  r6  rV  ry  r  r|  s          r%   rx   zOptionalSkillSource.searchr  s    #%kkmNN$ 	D II;a(8(8'9388DII;N:OPVVXJj(t$7|u$	 r'   c           	      D   |j                  d      r|j                  dd      d   n|}| j                  |z  }	 |j                         }| j                  j                         }|j	                  |      sy 	 |j                         s)|j                  dd      d   }| j                  |      }|sy |}i }|j                  d      D ]|  }|j                         s|j                  j                  d      r0d|j                  vs?|j                  dk7  sOt        |j!                  |            }		 |j#                         ||	<   ~ |sy |j                  }
t%        |
|d	d|j!                  | j                         d
      S # t
        t        f$ r Y y w xY w# t
        $ r Y w xY w)N	official/rt   ru   r   *r   __pycache__z.pycr  ru  rU  )r   r   r  r   r   r  r   r~  rE  _find_skill_dirrglobis_filer   r   r   rc   relative_to
read_bytesrh   )r   rX   relr  r  optional_rootr   ri   fr   r   s              r%   r<  zOptionalSkillSource.fetch  s   .8.C.CK.PjsA&r*V`&&,		 ((*H ..668M**=9 :
  C+B/J,,Z8I I.0% 	A		))#.!0HH&q}}Y78&'llnE(O	  ~~"9#8#89K9K#L"MN!
 	
A $ 		.  s#   ;E> .F>FF	FFc                     |j                  d      r|j                  dd      d   n|}|j                  dd      d   }| j                         D ]  }|j                  |k(  s|c S  y )Nr  rt   ru   r   )r   r   rE  r  r   )r   rX   r  r   r  s        r%   r>  zOptionalSkillSource.inspect  sj    .8.C.CK.PjsA&r*V`ZZQ'+
NN$ 	DyyJ&	 r'   r   c                     | j                   j                         sy| j                   j                  d      D ]5  }t        |      r|j                  j
                  |k(  s)|j                  c S  y)z<Find a skill directory by name anywhere in optional-skills/.Nr  )r  r~  r  r   r  r   )r   r   rq   s      r%   r  z#OptionalSkillSource._find_skill_dir  sc    !!((***00< 	'H%h/##t+&		'
 r'   c                 (   | j                   j                         sg S g }t        | j                   j                  d            D ]5  }t	        |      r|j
                  }	 |j                  d      }| j                  |      }|j                  d|j                        }|j                  dd      }g }|j                  di       }	t        |	t              r4|	j                  di       }
t        |
t              r|
j                  d	g       }|j                  | j                         j                         }|j!                  t#        ||d
d dd| d| j$                  d| t        |t&              r|ng              8 |S # t        t        f$ r Y Mw xY w)z,Enumerate all optional skills with metadata.r  r  r  r   rV   r   rj   r  r^   Nr  r  r  ru  zoptional-skills/)r   rV   rW   rX   rY   rZ   r[   r^   )r  r~  r  r  r   r  r$  r  r  _parse_frontmatterr#   r   r   rf   r  as_posixrw  rU   OFFICIAL_REPOre   )r   rV  rq   r  r  r  r   r  r^   
meta_blockr  r   s               r%   r  zOptionalSkillSource._scan_all  s   !!((*I#%t1177
CD  	H%h/__F",,g,> ((1B66&&++.D66-,DD
B/J*d+(nnXr:k40&??626D))$*<*<=FFHHNN9 #J!&xj1%'''z2'd3T
 
- 	D 7 /0 s    E>>FFr  c                 :   | j                  d      } | j                  d      si S t        j                  d| dd       }|si S | d|j	                         dz    }	 t        j                  |      }t        |t              r|S i S # t
        j                  $ r i cY S w xY wr  r  r  s       r%   r  z&OptionalSkillSource._parse_frontmatter  r"  r#  NrD  )rK   r`   ra   rb   r  r   rc   r@  rC  r%  r   rU   rx   r   rh   r<  r>  r	   r  r  r%  rf   r  r*   r'   r%   r  r  W  s     0M
3 # # 
C  T)_ 1
 1
(= 1
j# (9*= 	C 	HTN 	(4	? (T C D  r'   r  r   c                 <   t               |  dz  }|j                         sy	 |j                         }t        j                         |j                  z
  t
        kD  ryt        j                  |j                               S # t        t        j                  f$ r Y yw xY w)z Read cached data if not expired.r  Nr  )r   r  r  s      r%   rM  rM    s    !#Um3J 99;&8zz*..011T))* r	  r  c                 z   t               }|j                  dd       t               dz  }|j                         s	 |j	                  d       ||  dz  }	 |j	                  t        j                  |dt                     y	# t
        $ r Y @w xY w# t
        $ r }t        j                  d|       Y d	}~y	d	}~ww xY w)
zWrite data to cache.Tr  z.ignorez,# Exclude hub internals from search tools
*
r  F)r  defaultr  N)rF   r  r4   r#  r  r  r(  r  rc   r   r   )r   r  r  ignore_filer  r  s         r%   rN  rN    s    &(O$6 *y(K	""#ST !cU%=0J5djjE3OP	  		
  50!445s)   B +B 	BB	B:B55B:r  c           
          | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  | j                  d	S )z*Convert a SkillMeta to a dict for caching.r  r  r  s    r%   r  r  -  sQ     		''++oo''						
 
r'   c                       e Zd ZdZdde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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y)HubLockFileuL   Manages skills/.hub/lock.json — tracks provenance of installed hub skills.Nr[   c                 6    ||| _        y t               | _        y r!   )r8   r[   r   r[   s     r%   r   zHubLockFile.__init__C       ,D	*,	r'   r(   c                     | j                   j                         sdi dS 	 t        j                  | j                   j	                               S # t        j
                  t        f$ r di dcY S w xY w)Nru   )rT  	installed)r[   r#  r(  r  r$  r  r  r   s    r%   loadzHubLockFile.loadF  sc    yy! r22	3::dii11344$$g. 	3 r22	3s   ,A A/.A/r  c                     | j                   j                  j                  dd       | j                   j                  t	        j
                  |dd      dz          y )NTr  r   F)indentr  r  r[   r  r  r  r(  r  r   r  s     r%   savezHubLockFile.saveN  sC    		td;		TZZQUKdRSr'   r   rW   rX   rY   scan_verdict
skill_hashr   ri   rj   scan_provenancec                 h   t        |      }t        ||      }| j                         }||||||||	xs i |
xs i t        j                  t
        j                        j                         t        j                  t
        j                        j                         d|d   |<   | j                  |       y )N)rW   rX   rY   r  r   r   ri   rj   r  installed_at
updated_atr  )	r   r   r	  r   r/  r   utc	isoformatr  )r   r   rW   rX   rY   r  r  r   ri   rj   r  	safe_namesafe_install_pathr  s                 r%   record_installzHubLockFile.record_installR  s    " ).	8yQyy{$&(&- B.4"$LL6@@B",,x||4>>@(
[)$ 			$r'   c                 p    | j                         }|d   j                  |d        | j                  |       y Nr  )r	  popr  r   r   r  s      r%   record_uninstallzHubLockFile.record_uninstallu  s-    yy{[dD)		$r'   c                 J    | j                         }|d   j                  |      S r  )r	  r#   r  s      r%   get_installedzHubLockFile.get_installedz  s#    yy{K $$T**r'   c                     | j                         }g }|d   j                         D ]  \  }}|j                  d|i|        |S )Nr  r   )r	  r  rw  )r   r  r  r   r  s        r%   list_installedzHubLockFile.list_installed~  sN    yy{,224 	3KD%MM641512	3r'   r!   )NN)rK   r`   ra   rb   r   r	   r   rf   r	  r  rc   r   r   r   r  r  r   r"  r*   r'   r%   r  r  @  s	   V?Xd^ ?3d 3T T$ T .248!! ! 	!
 ! ! ! ! Cy! 4S>*! "$sCx.1! 
!FS T 
+# +(4. +T
 r'   r  c                       e Zd ZdZdde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y)TapsManageru:   Manages the taps.json file — custom GitHub repo sources.Nr[   c                 6    ||| _        y t               | _        y r!   )rC   r[   r  s     r%   r   zTapsManager.__init__  r  r'   r(   c                     | j                   j                         sg S 	 t        j                  | j                   j	                               }|j                  dg       S # t        j                  t        f$ r g cY S w xY w)Nrd  )r[   r#  r(  r  r$  r#   r  r  r  s     r%   r	  zTapsManager.load  sg    yy!I	::dii1134D88FB''$$g. 	I	s   >A A;:A;rd  c                     | j                   j                  j                  dd       | j                   j                  t	        j
                  d|id      dz          y )NTr  rd  r   )r  r  r  )r   rd  s     r%   r  zTapsManager.save  sE    		td;		TZZqADHIr'   rZ   c                     | j                         }t        fd|D              ry|j                  |d       | j                  |       y)z+Add a tap. Returns False if already exists.c              3   .   K   | ]  }|d    k(    yw)rZ   Nr*   )r   r  rZ   s     r%   r   z"TapsManager.add.<locals>.<genexpr>  s     /QqyD /s   Fr^  T)r	  r   rw  r  )r   rZ   r[   rd  s    `  r%   r   zTapsManager.add  s>    yy{/$//T401		$r'   c                     | j                         }|D cg c]  }|d   |k7  s| }}t        |      t        |      k(  ry| j                  |       yc c}w )z6Remove a tap by repo name. Returns False if not found.rZ   FT)r	  r   r  )r   rZ   rd  r  new_tapss        r%   removezTapsManager.remove  sR    yy{#9!qyD'8A99x=CI%		(	 :s
   AAc                 "    | j                         S r!   )r	  r   s    r%   	list_tapszTapsManager.list_taps  s    yy{r'   r!   )r_  )rK   r`   ra   rb   r   r	   r   r   rf   r	  r  rc   r2  r   r,  r.  r*   r'   r%   r$  r$    s|    D?Xd^ ?d4j Jd J J 3 t 3 4 4: r'   r$  actionrW   rY   verdictr_   c                    t               }|j                  j                  dd       t        j                  t
        j                        j                  d      }|| || d| |g}|r|j                  |       dj                  |      dz   }		 t        |dd	      5 }
|
j                  |	       d
d
d
       y
# 1 sw Y   y
xY w# t        $ r }t        j                  d|       Y d
}~y
d
}~ww xY w)zAppend a line to the audit log.Tr  z%Y-%m-%dT%H:%M:%SZ:rs  r  ar  r  NzCould not write audit log: %s)r@   r  r  r   r/  r   r  strftimerw  r   openwriter  r   r   )r/  r   rW   rY   r0  r_   	audit_log	timestampr   liner  r  s               r%   append_audit_logr:    s     I4$7X\\*334HII
vha},EwOEU88E?T!D9)S73 	qGGDM	 	 	 94a889s6   C B7.C 7C <C  C 	C,C''C,c                     t               } t               }t               }t               }| j	                  dd       t               j	                  d       t               j	                  d       |j                         s|j                  d       |j                         s|j                          |j                         s|j                  d       yy)z8Create the .hub directory structure if it doesn't exist.Tr  )r  z {"version": 1, "installed": {}}
z{"taps": []}
N)
r4   r8   r@   rC   r  r=   rF   r#  r  touch)hub_dir	lock_filer7  	taps_files       r%   ensure_hub_dirsr@    s    jGIIIMM$M.T*d+@A-. r'   c                 D   t                t        | j                        }g }| j                  j	                         D ]#  \  }}t        |      }|j                  ||f       % t               |z  }|j                         rt        j                  |       |j                  d       |D ]u  \  }} |j                  |j                  d       }|j                  j                  dd       t        |t               r|j#                  |       c|j%                  |d       w |S )z>Write a skill bundle to the quarantine directory for scanning.T)r  rt   r  r  r  )r@  r   r   ri   r  r~   rw  r=   r#  shutilrmtreer  joinpathr   r  r   rk   write_bytesr  )r   r   validated_filesr   file_contentr<  dest	file_dests           r%   quarantine_bundlerJ    s   %fkk2J;=O"(,,"4"4"6 >,1(;|<=> z)D{{}dJJtJ"1 A,!DMM8>>##67	td;lE*!!,/   @A Kr'   quarantine_pathscan_resultr  c                    t        |      }|rt        |      nd}| j                         }t               j                         }	|j	                  |	      st        d|        |r| d| }
n|}
t        |
|      }|j                         rt        j                  |       | dz  }|j                         r9	 |j                         j                  }|dkD  rt        j                  d||d       | j                  d      D ]-  }t!        |      s	 |j#                  |      }t        d	|        |j$                  j'                  d
d
       t        j(                  t+        |       t+        |             t-               }|j/                  ||j0                  |j2                  |j4                  |j6                  t9        |      t+        |j#                  t;                           t=        |j>                  jA                               |jB                  |xs tE        |dd      
       tG        d||j0                  |j4                  |j6                  t9        |             |S # t        $ r Y {w xY w# t
        $ r |}Y [w xY w)z?Move a scanned skill from quarantine into the skills directory.r   zUnsafe quarantine path: rt   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  r  z9Installed skill contains symlinks, which is not allowed: Tr  r  N)
r   rW   rX   rY   r  r  r   ri   rj   r  INSTALL)$r   r   r   r=   r   r   r   r#  rB  rC  r  st_sizer   r   r  r  r   r  r  r  moverc   r  r  rW   rX   rY   r0  r   r1   re   ri   keysrj   r   r:  )rK  r   r   r   rL  r  r   safe_categoryquarantine_resolvedquarantine_rootinstall_rel_pathinstall_dirrq   
skill_sizer  r  locks                    r%   install_from_quarantinerY    si    +:6O?G1(;RM)113%'//1O--o>3O3DEFF+_Ao->?*
 --=OKk" +H	!00JG#Q $!!n !&&s+ 	
 '	##$78C GuM
 	
	
 TD9
KKO$c+&67 =D}}$$&& ((,00?@6<<$$&''X7;@QSW+X   ?FMMK//[! U  		  	C	s$   58I I,	I)(I),I;:I;c                 x   t               }|j                  |       }|sdd|  dfS 	 t        |j                  dd      |       }|j                         rt        j                  |       |j                  |        t        d	| |d
   |d   dd       dd|  d|d    fS # t        $ r}dd|  d| fcY d}~S d}~ww xY w)z9Remove a hub-installed skill. Refuses to remove builtins.F'z1' is not a hub-installed skill (may be a builtin)r   r   zRefusing to uninstall 'z': N	UNINSTALLrW   rY   zn/auser_requestTzUninstalled 'z' from )
r  r   r   r#   r   r#  rB  rC  r  r:  )r   rX  r  r   r   s        r%   uninstall_skillr^  M  s    =Dz*E*%VWWWE1IInb):
 l#*%[*eHou]?SUZ\jk=GE.4I3JKKK  E/
|3seDDDEs   B 	B9%	B4.B94B9c                    t        j                         }t        | j                        D ]  }|j	                  |j                  d             |j	                  d       | j                  |   }t        |t              r|j	                  |       e|j	                  |j                  d              d|j                         dd  S )z;Compute a deterministic hash for an in-memory skill bundle.r      zsha256:N   )	rJ  sha256r  ri   r  r&  r   rk   rL  )r   hr   r  s       r%   bundle_content_hashrd  k  s    A6<<( 	. 	
)*	,,x(gu%HHWHHW^^G,-	. Q[[]3B'())r'   source_namec                 T    ddi}|j                  ||      }| j                         |k(  S )Nr  r  )r#   r@  )rW   re  aliasesr   s       r%   _source_matchesrh  {  s4    [G [+6J++r'   )rX  sourcesr  rX  ri  r  c                   |xs
 t               }|j                         }| r$|D cg c]  }|j                  d      | k(  s| }}|t        |      }g }|D ]  }|j                  dd      }|j                  dd      }|D 	cg c]  }	t	        |	|      s|	 c}	xs |}
d}|
D ]  }		 |	j                  |      }|s n |s'|j                  |j                  dd      ||dd       |j                  d	d      }t        |      }||k(  rd
nd}|j                  |j                  dd      ||||||d        |S c c}w c c}	w # t        $ r d}Y w xY w)z0Check installed hub skills for upstream changes.r   Nr|  rX   r   rW   unavailable)r   rX   rW   statusr   
up_to_dateupdate_available)r   rX   rW   rl  current_hashlatest_hashr   )	r  r"  r#   create_source_routerrh  r<  r)  rw  rd  )r   rX  ri  r  r  r  rV  rX   re  srccandidate_sourcesr   ro  rp  rl  s                  r%   check_for_skill_updatesrt    s     ;=D##%I(1OuUYYv5F$5NUO	O&D1G "YY|R0
ii"-,3YSsK7XSYd]d$ 	C:. 	 NN		&"-(%'	  yy4)&1!-!<BTIIfb)$!(&
 	5"H NU P Z  s(   D:D:D?D?'EEEz@https://hermes-agent.nousresearch.com/docs/api/skills-index.jsoni`T  c                      t               dz  S )Nzhermes-index.json)rF   r*   r'   r%   _hermes_index_cache_filerv    s     333r'   c                     t               } | j                         r]	 t        j                         | j                         j                  z
  }|t
        k  r#t        j                  | j                               S 	 d}dD ]n  }	 t        j                  t        ddd|i      }|j                  dk7  r,t        j!                  d|j                         t#               c S |j                         } n |
t#               S t)        |t*              rd|vr
t#               S 	 | j,                  j/                  dd       | j1                  t        j2                  |             |S # t        t        j                  f$ r Y w xY w# t        j$                  $ r"}t        j!                  d	||       Y d}~.d}~wt        j&                  t        j                  f$ r,}t        j!                  d
|       t#               cY d}~c S d}~ww xY w# t        $ r Y |S w xY w)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.
    N)zgzip, deflateidentityr  Tr  r  r  zHermes index fetch returned %dz3Hermes index decode failed (Accept-Encoding=%s): %szHermes index fetch failed: %sr.   r  )rv  r#  r   r  r  HERMES_INDEX_TTLr(  r  r$  r  r  r   r#   HERMES_INDEX_URLr   r   r   _load_stale_index_cacheDecodingErrorr   r   rf   r  r  r  r  )hermes_index_cache_fileager  accept_encodingr   r  s         r%   _load_hermes_indexr    s    78%%'	))+ 7 < < > G GGC%%zz"9"C"C"EFF &$ D8 -	-99 !%*O<	D 3&=t?O?OP.0099;D-4 |&(( dD!XT%9&((&&,,TD,I**4::d+;< Ks --. 		< "" 	 LLE
 !5!56 	-LL8!<*,,	-  KsV   AE  AE:E:AG? E76E7:G<F**&G<G7/G<7G<?	HHc                      t               } | j                         r$	 t        j                  | j	                               S y# t
        t        j                  f$ r Y yw xY w)z6Fall back to stale cache when the network fetch fails.N)rv  r#  r(  r  r$  r  r  )r}  s    r%   r{  r{    s\    68%%'	::5??ABB  --. 		s   "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y)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 | _        y rb  )_index_loadedr  _githubr~  s     r%   r   zHermesIndexSource.__init__(  s     &*	 04r'   r(   c                 f    | j                   st               | _        d| _         | j                  xs i S )NT)r  r  r  r   s    r%   _ensure_loadedz HermesIndexSource._ensure_loaded0  s)    ||,.DKDL{{ b r'   c                 h    | j                   t        | j                        | _         | j                   S r{  )r  r]  r  r   s    r%   _get_githubzHermesIndexSource._get_github6  s&    <<'TYY7DL||r'   c                      y)Nhermes-indexr*   r   s    r%   r@  zHermesIndexSource.source_id;  s    r'   c                 V    | j                         }t        |j                  d            S )z+Whether the index is loaded and has skills.r.   )r  r2  r#   )r   ri  s     r%   is_availablezHermesIndexSource.is_available>  s&     ##%EIIh'((r'   rX   c                     | j                         }|j                  dg       D ]*  }|j                  d      |k(  s|j                  dd      c S  y)Nr.   rX   rY   rB  )r  r#   )r   rX   ri  r{  s       r%   rC  z!HermesIndexSource.trust_level_forD  sQ    ##%YYx, 	=Eyy&*4yy<<	= r'   r5  r6  c                 D   | j                         }|j                  dg       }|sg S |j                         s"|d| D cg c]  }| j                  |       c}S |j	                         }g }t        |      D ]b  \  }}t        |j                  dd            j	                         }	t        |j                  d      xs i j                  dd            j	                         }
dj                  |	t        |j                  dd            j	                         dj                  d	 |j                  d
g       D              t        |j                  dd            j	                         |
g      }||vr|	|k(  rd}nL|	j                  |      rd}n8|
|k(  rd}n0||	j                         v s||
j                         v rd}n	||	v rd}nd}|j                  |||f       e |j                  d        |d| D cg c]  \  }}}| j                  |       c}}S c c}w c c}}w )ui  Search the cached index.  Zero API calls.

        Matches against name, description, tags, identifier, and the per-tap
        ``extra.provider`` label (so a query like ``nvidia`` surfaces the
        ``NVIDIA/skills/...`` entries even though their ``source`` is the bare
        ``github``).  Results are scored and ranked (exact name > name prefix >
        whole-word > substring) rather than returned in raw index order and
        truncated at the first ``limit`` hits — that earlier break-at-limit
        behaviour returned an arbitrary file-order slice and buried the most
        relevant skills.
        r.   Nr   r   r_   rW  rs  rV   c              3   N   K   | ]  }t        |      j                           y wr!   )rc   rR  r  s     r%   r   z+HermesIndexSource.search.<locals>.<genexpr>h  s     CAQCs   #%r^   rX   r   ru   r   r      rS   c                     | d   | d   fS )Nr   ru   r*   )xs    r%   r>  z*HermesIndexSource.search.<locals>.<lambda>~  s    1Q41, r'   r?  )r  r#   r   _to_metarR  	enumeraterc   r   r   r   rw  r@  )r   r5  r6  ri  r.   r  ry  scoredr  r   rW  haystackr4  r   s                 r%   rx   zHermesIndexSource.searchK  s    ##%8R(I{{}.4Ven=DMM!$==kkm.0f% 	)DAqquuVR()//1DAEE'N0b55j"EFLLNHxxAEE-,-335Cvr1BCCAEE,+,224! H (*{"-[(

,x~~?O0O$MM5!Q-(5	)8 	./06v?WQ1a ??C >B @s   H7Hc                    | j                         }| j                  ||      }|sy|j                  d      }|rA| j                         j	                  |      }|r |j                  dd      |_        ||_        |S |j                  dd      }|j                  dd      }|rJ|rH| d| }| j                         j	                  |      }|r |j                  dd      |_        ||_        |S y)	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_idrW   r  rZ   r   r[   rt   )r  _find_entryr#   r  r<  rW   rX   )	r   rX   ri  r  r  r   rZ   r[   	github_ids	            r%   r<  zHermesIndexSource.fetch  s     ##%  U3 9912%%'--h7F %		(N C$.! yy$yy$D&$(I%%'--i8F %		(N C$.!r'   c                 n    | j                         }| j                  ||      }|r| j                  |      S y)z0Return metadata from the index.  Zero API calls.N)r  r  r  )r   rX   ri  r  s       r%   r>  zHermesIndexSource.inspect  s7    ##%  U3==''r'   ri  c                 R   |j                  dg       }|D ]  }|j                  d      |k(  s|c S  |}dD ]#  }|j                  |      s|t        |      d } n |D ]G  }|j                  dd      }|}dD ]#  }|j                  |      s|t        |      d } n ||k(  sE|c S  y)z3Look up a skill in the index by identifier or name.r.   rX   )r  r  r  zgithub/zclawhub/Nr   )r#   r   r   )	r   rX   ri  r.   r  r   r  sidstored_normalizeds	            r%   r  zHermesIndexSource._find_entry  s    8R(  	Auu\"j0	
  
V 	F$$V,'F5
	  		A%%b)C #Z >>&)(+CKL(9% !J.		 r'   r  c                 F   t        | j                  dd      | j                  dd      | j                  dd      | j                  dd      | j                  dd      | j                  d	      | j                  d
      | j                  dg       | j                  di       	      S )Nr   r   rV   rW   r  rX   rY   rB  rZ   r[   r^   r_   r  )rU   r#   )r  s    r%   r  zHermesIndexSource._to_meta  s    62&		-499X~6yyr2		-=6"6"62&))GR(

 
	
r'   NrD  )rK   r`   ra   rb   r   r   rf   r  r]  r  rc   r@  r$  r2  r  rC  r%  r   rU   rx   r   rh   r<  r>  r  r%  r  r*   r'   r%   r  r    s    	4Z 4! !\ 
3  )d ) )
# # 4@C 4@ 4@T)_ 4@l! !(= !F# (9*= c $ 8D> < 
 
 
 
r'   r  c                    | 
t               } t               }|j                         }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.
    r|  )r  r`  )r   r$  r.  r  r  ry  r'  rW  r]  r  r  r  r  )r  taps_mgrr`  ri  s       r%   rq  rq    sx    
 ||}H##%J 	t$D!$:6T*"G Nr'   rr  r5  r6  c                     	 | j                         | j                  ||      fS # t        $ rA}t        j	                  d| j                         |       | j                         g fcY d}~S d}~ww xY w)z:Search a single source.  Runs in a thread for parallelism.rE  zSearch failed for %s: %sN)r@  rx   r)  r   r   )rr  r5  r6  r  s       r%   _search_one_sourcer    s]    #}}

5
 >>> #/!D}}""#s   "% 	A/6A*$A/*A/per_source_limitssource_filteroverall_timeouton_source_donec                 l   ddl m}m} |xs i }|j                         j	                         t
        v }|rdn|}	g }
d}t        h d      }|	dk(  r,| D ]'  }|j                         dk(  st        |dd      s%d} n | D ]:  }|j                         }|	dk7  r||	k7  r|d	k7  r#|r||v r*|
j                  |       < g }i }g }|
s|||fS dd
l
m}  |t        t        |
      d            }i }|
D ]M  }|j                  |j                         d      }|j                  t         |||      }|j                         ||<   O 	 	  |||      D ]K  }	 |j#                  d      \  }}t        |      ||<   |j%                  |       |r ||t        |             M 	 |j3                  dd       |||fS # t&        $ r Y rw xY w# t(        $ rS |D cg c]  }|j+                         r||    nc c}w }}|r%t,        j/                  ddj1                  |             Y w xY w# |j3                  dd       w xY w)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_completedr   F>   r   r  r  r  r)  r  r  r  Tr  )DaemonThreadPoolExecutor   )max_workers2   r   z'Skills browse timed out waiting for: %sr  )r  cancel_futures)concurrent.futuresr  r  r   rR  _PROVIDER_FILTER_VALUES	frozensetr@  r   rw  tools.daemon_poolr  r  r   r#   submitr  r  re  r)  TimeoutErrordoner   r   r   shutdown)ri  r5  r  r  r  r  r  r  _provider_filter_effective_filteractive_index_available_api_source_idsrr  r  all_resultssource_countstimed_out_idsr  poolfutureslimfutrV  r  s                            r%   parallel_search_sourcesr    sv    D)/R %**,2248OO!1} "F
  !Q ROE! 	C>1^U;#' 		  mmo%#1B*BsjGX 6c $&K$&M!MM=88 ;#CK0CDDG '##CMMOR8kk,c5#>}}'
7	#G_E #&::a:#8LC),WM#&&&w/%&sCL9* 	56}44! !  	$+ 1668
 M  =IIm,	 	56sa   ?F? AF0F? H 0	F<9F? ;F<<F? ?HG*"G*)/HH HH H3c                    t        || |d      \  }}}|j                         j                         t        v rt	        ||      }dddd}i }|D ]v  }|j
                  |vr|||j
                  <   !|j                  |j                  d      |j                  ||j
                     j                  d      kD  sh|||j
                  <   x t        |j                               }	|	d| S )z3Search all sources (in parallel) and merge results.r  )r5  r  r  r   ru   r   rt  N)
r  r   rR  r  r[  rX   r#   rY   re   rx  )
r5  ri  r  r6  r  r   _TRUST_RANKr~  rZ  r  s
             r%   unified_searchr  f  s     0#	KA ""$(??1+}M  AA>K!#D #<<t#!"D__Q]]A.allASA_A_ab1cc!"D	#
 4;;=!G6E?r'   )r   )r(   Nr!   )r   Nr   r  N)r   r  )rb   rJ  r(  loggingr   r   rB  r  r   abcr   r   dataclassesr   r   r   r   pathlibr	   r
   r  r   hermes_cli._subprocess_compatr   agent.skill_utilsr   typingr   r   r   r   r   r   urllib.parser   r   r   r   r   r   r  tools.skills_guardr   r   r   tools.url_safetyr   tools.website_policyr   	getLoggerrK   r   r  rc   r&   r+   r1   r4   r8   r=   r@   rC   rF   rI   rM   r   r   rU   rh   r  r   rv  	MULTILINErz   rw   ry   r   r   r2  r   r   r   r   r   r   r%  Responser   r~   r   r4  rQ  rS  rx  r  r[  r]  r'  rW  ry  r  r  r  r  r  rM  rN  rf   r  r  r$  r:  r@  rJ  rY  r^  rd  rh  rt  rz  ry  rv  r  r{  r  rq  r  r  r  r  r*   r'   r%   <module>r     s      	 	    # ( ' ' , < 4 : : I I    ) 5			8	$ 
C d MT M
J$ J
LD L
M M
LD L
LD L
N$ N  %'	 Kc K 3   
8 
8 
8 ; ; ; ""^_ _LL &2::V 
 S0B "
+ 
# 
s 3 d WZ 2Us Us UaC aC as   6VD VT VS c d : 35 "3 "C "%..9Q "J^ ^ ^v vz# P $'&1 :c :hsm : $#U7K7R7R7T#UU +*-	+ x	; x	~\A; \AFs sty)[ y)@[K [Dak aPXKK XK@g[ g\m+ mh3 8C= 5C 5s 5t 5(i D &C CT& &\ CE9S 9c 93 9"%9039<?9IM9*/"k d > 15TTT T 	T
 T d38n-T 
TnL LdCi(8 L<* * * ,K ,c ,d , 5 #'+/!%5
3-5 ;
5 d;'(	5
 :
5 
$Z5x V  4$ 4GHTN GT$ z
 z
zx
3 tK?P 6#	# #),#
3Y # 26$(d5+d5d5  S#X/d5 	d5
 d5 SMd5 4	?DcNDI56d5P =?# [(9 "%69CG	?r'   