
    j}                     z   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mZmZ ddlm	Z	m
Z
 ddlmZmZmZ ddlmZ ddlmZmZmZ ddlmZ  ej        e          Z e            Zed	z  Zed
z  ZdZde	fdZde	fdZdee e f         fdZ!de"fdZ#dee e f         fdZ$de	de de fdZ%de	deee e	f                  fdZ&de	de	de	fdZ'de	de fdZ(de	de	de fdZ)de	dee          fdZ*de	de fd Z+dee ee e e	f         f         fd!Z,de	d"e	de fd#Z-d$d%d&e d'e.de/fd(Z0dJd)e.dee          fd*Z1dJd)e.de/fd+Z2de	ddfd,Z3dJd&e d'e.de/fd-Z4d.e.de/fd/Z5de.fd0Z6dJd1e.de/fd2Z7ed3k    rW e8d4            e2d$5          Z9 e:e9d6                    d7 e:e9d8                    d9e9d:          d;gZ;e9d<         rne9d<         Z<d=Z=d>>                    e<de=                   Z? e:e<          e=k    re?d? e:e<          e=z
   d@z  Z?e;@                     e:e<           dAe?            e9dB         r'e;@                     e:e9dB                    dC           e9A                    dD          r'e;@                     e:e9dD                    dE            e8dFd>>                    e;           dGe9dH          dI           dS dS )Ku  
Skills Sync -- Manifest-based seeding and updating of bundled skills.

Copies bundled skills from the repo's skills/ directory into ~/.hermes/skills/
and uses a manifest to track which skills have been synced and their origin hash.

Manifest format (v2): each line is "skill_name:origin_hash" where origin_hash
is the MD5 of the bundled skill at the time it was last synced to the user dir.
Old v1 manifests (plain names without hashes) are auto-migrated.

Update logic:
  - NEW skills (not in manifest): copied to user dir, origin hash recorded.
  - EXISTING skills (in manifest, present in user dir):
      * If user copy matches origin hash: user hasn't modified it → safe to
        update from bundled if bundled changed. New origin hash recorded.
      * If user copy differs from origin hash: user customized it → SKIP.
  - DELETED by user (in manifest, absent from user dir): respected, not re-added.
  - REMOVED from bundled (in manifest, gone from repo): cleaned from manifest.

The manifest lives at ~/.hermes/skills/.bundled_manifest.
    N)datetimetimezone)PathPurePosixPath)get_bundled_skills_dirget_hermes_homeget_optional_skills_dir)is_excluded_skill_path)DictListTuple)atomic_replaceskillsz.bundled_manifestz.no-bundled-skillsreturnc                  ^    t          t          t                    j        j        dz            S )zLocate the bundled skills/ directory.

    Checks HERMES_BUNDLED_SKILLS env var first (set by Nix wrapper),
    then a wheel-installed data dir, then falls back to the relative
    path from this source file.
    r   )r   r   __file__parent     6/home/ubuntu/.hermes/hermes-agent/tools/skills_sync.py_get_bundled_dirr   5   s#     "$x.."7">"IJJJr   c                  ^    t          t          t                    j        j        dz            S )z/Locate the official optional-skills/ directory.optional-skills)r	   r   r   r   r   r   r   _get_optional_dirr   ?   s"    "4>>#8#?BS#STTTr   c                     t                                           si S 	 i } t                               d                                          D ]e}|                                }|sd|v rC|                    d          \  }}}|                                | |                                <   `d| |<   f| S # t          t          f$ r i cY S w xY w)z
    Read the manifest as a dict of {skill_name: origin_hash}.

    Handles both v1 (plain names) and v2 (name:hash) formats.
    v1 entries get an empty hash string which triggers migration on next sync.
    utf-8encoding: )MANIFEST_FILEexists	read_text
splitlinesstrip	partitionOSErrorIOError)resultlinename_hash_vals        r   _read_manifestr.   D   s     !! 	!++W+==HHJJ 
	" 
	"D::<<D d{{$(NN3$7$7!a'/~~'7'7tzz||$$  "tW   			s   BB4 4C
	C
c                     	 ddl m}   |             S # t          $ r t          dz  }|                                st                      cY S t                      }	 |                    d                                          D ]B}|                                }|r*|	                    d          s|
                    |           Cn# t          $ r Y nw xY w|cY S w xY w)u  Built-in skills the curator pruned — must NOT be re-seeded on sync.

    Delegates to ``tools.skill_usage`` (single source of truth) and falls back
    to reading ``~/.hermes/skills/.curator_suppressed`` directly if that import
    is unavailable in a packaged/update context.
    r   )read_suppressed_namesz.curator_suppressedr   r   #)tools.skill_usager0   	Exception
SKILLS_DIRr"   setr#   r$   r%   
startswithaddr'   )r0   pathnamesr*   s       r   _read_suppressed_namesr:   _   s
   ;;;;;;$$&&&   11{{}} 	55LLL	88CCEE $ $zz|| $ 4 4 $IIdOOO$  	 	 	D	s:    6C
CA+CC
CCCCCentriesc                 @   ddl }t          j                            dd           d                    d t          |                                           D                       dz   }	 |                    t          t          j                  dd	          \  }}	 t          j
        |d
d          5 }|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   t          |t                     dS # t           $ r( 	 t          j        |           n# t$          $ r Y nw xY w w xY w# t&          $ r.}t(                              dt          |d           Y d}~dS d}~ww xY w)zWrite the manifest file atomically in v2 format (name:hash).

    Uses a temp file + os.replace() to avoid corruption if the process
    crashes or is interrupted mid-write.
    r   NTparentsexist_ok
c              3   *   K   | ]\  }}| d | V  dS )r   Nr   ).0r+   r-   s      r   	<genexpr>z"_write_manifest.<locals>.<genexpr>   s4      XXndH))x))XXXXXXr   z.bundled_manifest_.tmpdirprefixsuffixwr   r   z&Failed to write skills manifest %s: %sexc_info)tempfiler!   r   mkdirjoinsorteditemsmkstempstrosfdopenwriteflushfsyncfilenor   BaseExceptionunlinkr'   r3   loggerdebug)r;   rL   datafdtmp_pathfes          r   _write_manifestrb   y   s    OOOtd;;;99XXw}}@W@WXXXXX[__D`''M())' ( 
 
H
	2sW555 %			$$$% % % % % % % % % % % % % % % 8]33333 	 	 		(####   	  ` ` `=}aZ^_________`ss   (2E% D0 2ADD0 DD0 DD0 0
E";EE"
EE"EE""E% %
F/#FFskill_mdfallbackc                    	 |                      dd          dd         }n# t          $ r |cY S w xY wd}|                    d          D ]}|                                }|dk    r|r nbd	}#|r\|                    d
          rG|                    dd          d                                                             d          }|r|c S |S )zORead the name field from SKILL.md YAML frontmatter, falling back to *fallback*.r   replace)r   errorsNi  Fr@   z---Tzname:r      z"')r#   r'   splitr%   r6   )rc   rd   contentin_frontmatterr*   strippedvalues          r   _read_skill_namern      s   $$gi$HH$O   Nd## 
 
::<<u !N 	h11':: 	NN3**1-3355;;EBBE Os   " 11bundled_dirc                     g }|                                  s|S |                     d          D ]E}t          |          r|j        }t	          ||j                  }|                    ||f           F|S )zz
    Find all SKILL.md files in the bundled directory.
    Returns list of (skill_name, skill_directory_path) tuples.
    SKILL.md)r"   rglobr
   r   rn   r+   append)ro   r   rc   	skill_dir
skill_names        r   _discover_bundled_skillsrv      s    
 F %%j11 / /!(++ 	O	%h	??
z9-....Mr   rt   c                 @    |                      |          }t          |z  S )z
    Compute the destination path in SKILLS_DIR preserving the category structure.
    e.g., bundled/skills/mlops/axolotl -> ~/.hermes/skills/mlops/axolotl
    )relative_tor4   )rt   ro   rels      r   _compute_relative_destrz      s"    
 


,
,Cr   	directoryc                    t          j                    }	 t          |                     d                    D ]}|                                rq|                    |           }|                    t          |                              d                     |                    |	                                           n# t          t          f$ r Y nw xY w|                                S )zHCompute a hash of all file contents in a directory for change detection.*r   )hashlibmd5rO   rr   is_filerx   updaterR   encode
read_bytesr'   r(   	hexdigest)r{   hasherfpathry   s       r   	_dir_hashr      s    []]FIOOC0011 	2 	2E}} 2''	22c#hhoog66777e..00111		2
 W   s   B*C   CCr8   basec                 @   |                      |          }|                                }t          |          }d |j        D             }|                                s|rt          d |D                       rt          d|           d                    |          S )zLReturn a normalized relative POSIX path, rejecting traversal/absolute paths.c                     g | ]}|d v|	S )>   r    .r   rB   parts     r   
<listcomp>z*_safe_rel_install_path.<locals>.<listcomp>   s"    BBBdD	,A,AT,A,A,Ar   c              3   "   K   | ]
}|d k    V  dS )z..Nr   r   s     r   rC   z)_safe_rel_install_path.<locals>.<genexpr>   s&      -M-Mtddl-M-M-M-M-M-Mr   zUnsafe optional skill path: /)rx   as_posixr   partsis_absoluteany
ValueErrorrN   )r8   r   ry   posixpurer   s         r   _safe_rel_install_pathr      s    


4
 
 CLLNNEDBBdjBBBE A A#-M-Mu-M-M-M*M*M A???@@@88E??r   c                     g }t          |                     d                    D ]P}|                                r:|                    |                    |                                                      Q|S )z8List files inside a skill directory in lock-file format.r}   )rO   rr   r   rs   rx   r   )rt   filesr   s      r   _skill_file_listr      so    E	,,-- B B==?? 	BLL**955>>@@AAALr   c                 d    	 ddl m}  ||           S # t          $ r t          |           cY S w xY w)zJReturn the same hash style the skills hub lock uses, falling back locally.r   )content_hash)tools.skills_guardr   r3   r   )r{   r   s     r   _content_hashr      sZ    $333333|I&&& $ $ $ #####$s    //c                  Z   t                      } i }|                                 s|S t          |                     d                    D ]a}t	          |          r|j        }	 t          ||           }n# t          $ r Y 7w xY w|j        }t          ||          }|||f}|||<   |||<   b|S )a%  Return official optional skills keyed by folder name and frontmatter name.

    Values are ``(folder_name, install_path, source_dir)``. Multiple keys may
    point to the same skill so callers can accept either the folder slug used
    by the hub lock or the user-facing frontmatter name.
    rq   )
r   r"   rO   rr   r
   r   r   r   r+   rn   )optional_dirindexrc   srcinstall_pathfolder_namefrontmatter_namerm   s           r   _optional_skill_indexr      s     %&&L.0E   <--j99:: ( (!(++ 	o	1#|DDLL 	 	 	H	h+HkBBlC0"k"'Ls   #A44
B Bbackup_rootc                 6   |                      t                    }||z  }|j                            dd           |                                rd}|                    |j         d|                                           r6|dz  }|                    |j         d|                                           6|                    |j         d|           }t          j        t          |           t          |                     |
                                S )zLMove an existing skill directory into a restore backup, preserving rel path.Tr=   rh   -)rx   r4   r   rM   r"   	with_namer+   shutilmoverR   r   )r8   r   ry   targetrH   s        r   _move_to_restore_backupr     s   


:
&
&C3F
Mt444}} =&+888899@@BB 	aKF &+888899@@BB 	!!V[";";6";";<<
KD		3v;;'''<<>>r   F)restorer+   r   c                   t                      }|sddg g g dS | dv r1t          t          |                                          d           ng }|s%|                    |           }|dd|  g g g dS |g}g }g }t          j        t          j                  	                    d	          }t          d
z  d| z  }|D ]\  }	}
}t          t          |
                    d           z  }t          |          }|                                ot          |          |k    }t          |dz  |	          }g }t                                          rt          t                              d                    D ]}t#          |          r|j        }	 |                    t                     n# t(          $ r Y Aw xY wt          ||j                  }||k    ra|j        |	k    s||	|hv r|                    |           |r|D ]9}|                                r#|                    t/          ||                     :|                                r%|s#|                    t/          ||                     |                                sF|j                            dd           t3          j        ||           |                    |	           |st7          d          }dd||||rt9          |          nddS )a3  Restore one or all official optional skills from repo source.

    ``restore=False`` only performs exact-match provenance backfill. ``restore=True``
    repairs already-mutated/reorganized skills by backing up matching active
    copies and copying the official optional source into its canonical path.
    Fz,No official optional skills directory found.)okmessagerestored
backfilled	backed_up>   r}   allc                     | d         S )Nrh   r   )items    r   <lambda>z1restore_official_optional_skill.<locals>.<lambda>-  s
    47 r   )keyNz#Official optional skill not found: z%Y%m%d-%H%M%Sz.restore-backupszofficial-optional-r   rq   Tr=   quietz(Official optional skill repair complete.r    )r   r   r   r   r   
backup_dir)r   rO   r5   valuesgetr   nowr   utcstrftimer4   r   ri   r   r"   rn   rr   r
   r   rx   r   r+   rs   r   rM   r   copytree_backfill_optional_provenancerR   )r+   r   r   targetsr   r   r   	timestampr   r   r   r   destsrc_hashcanonical_oksrc_frontmattermatchesrc   	candidatecandidate_namematchr   s                         r   restore_official_optional_skillr   "  s    "##E K(Vdfvx  HJ  K  K  	KGK|G[G[fS((.B.BCCCCacG 4>,XRV,X,Xfhxz  JL  M  M  M(HIX\**33ODDI114T4T4TTK*1 # #&\3D,"4"4S"9"9::S>>{{}}D4H)D +3+;[II  	.":#3#3J#?#?@@ . .)(33 $O	))*5555!   H!1(IN!K!K$$>[00N{TcFd4d4dNN9--- 	  R R<<>> R$$%<UK%P%PQQQ{{}} M\ M  !8{!K!KLLL;;== -!!$!>>>T***,,, 		 /T:::J= *3;c+&&&  s   F))
F65F6r   c                 <   t                      }|                                sg S t          dz  dz  }	 |                                r&t          j        |                                          ndi d}n!# t          j        t          f$ r di d}Y nw xY w|                    di           }d |	                                D             }g }d}t          |                    d                    D ]}}t          |          r|j        }		 t          |	|          }
n3# t          $ r&}t                               d	|	|           Y d
}~Wd
}~ww xY wt          t%          |
                    d           z  }|                                r|                                st+          |          t+          |	          k    r|	j        }||v s|
|v rt/          j        t2          j                                                  }dd|
 ddt9          |          |
t;          |          ddi||d
||<   |                    |
           |                    |           d}| stA          d| d           |r,|j        !                    dd           dd
l"}t          j#        |dd          dz   }|$                    tK          |j                  dd          \  }}	 tM          j'        |dd           5 }|(                    |           |)                                 tM          j*        |+                                           d
d
d
           n# 1 swxY w Y   tY          ||           n5# tZ          $ r( 	 tM          j.        |           n# t          $ r Y nw xY w w xY w|S )!a  Mark already-present official optional skills as hub-installed.

    This covers the migration case where a skill used to be bundled (or was
    manually copied into the active skills tree) and later lives under
    optional-skills/. If the active copy is byte-identical to the official
    optional source, record official hub provenance without copying or
    reinstalling anything. Modified/local skills are left alone.
    z.hubz	lock.jsonrh   )version	installedr   c                 b    h | ],}t          |t                    |                    d           -S )r   )
isinstancedictr   )rB   entrys     r   	<setcomp>z0_backfill_optional_provenance.<locals>.<setcomp>|  sD       eT""		.!!  r   Frq   z/Skipping optional skill with unsafe path %s: %sNr   officialz	official/builtinr   backfilled_fromr   )
source
identifiertrust_levelscan_verdictr   r   r   metadatainstalled_at
updated_atTz  = z* (official optional provenance backfilled)r=   r      )indentensure_asciir@   z.lock_rD   rE   rI   r   r   )/r   r"   r4   jsonloadsr#   JSONDecodeErrorr'   
setdefaultr   rO   rr   r
   r   r   r   r[   r\   r   ri   is_dirr   r+   r   r   r   r   	isoformatr   r   r7   rs   printrM   rL   dumpsrQ   rR   rS   rT   rU   rV   rW   rX   r   rY   rZ   )r   r   	lock_pathr]   r   existing_pathsr   changedrc   r   r   ra   r   	lock_namer   rL   payloadr^   r_   r`   s                       r   r   r   i  sZ    %&&L   	V#k1I/4=4D4D4F4Fktz)--//000XYhjLkLk '* / / /2../R00I %%''  N JG<--j99:: $P $P!(++ 	o	1#|DDLL 	 	 	LLJCQRSSSHHHH	 D,"4"4S"9"9::{{}} 	DKKMM 	T??inn,,H		!!\^%C%CL..88::	 4l44$()$//(%d++*,=>%# 
  
	) 	<((()$$$ 	PNNNNOOO td;;; 	*T!%@@@4G''I$%% ( 
 
H
	2sW555 %   			$$$% % % % % % % % % % % % % % % 8Y//// 	 	 		(####   	 s~   ?A3 3BB	D
E
$EE
M' /AM?M' MM' MM' '
N2NN
NNNNc           	      N
   t           t          z                                  r| st          d           g g dg g dg ddS t	                      }|                                sg g dg g g dg dS t
                              dd           t                      }t          |          }d |D             }t                      }g }g }g }g }	d}
|D ]3\  }}||v r|	
                    |            t          ||          }t          |          }||vr	 |                                r7|
dz  }
t          |          |k    r|||<   nx| st          d	| d
| d           n_|j                            dd           t          j        ||           |
                    |           |||<   | st          d|            # t           t"          f$ r"}| st          d| d|            Y d}~d}~ww xY w|                                r|                    |d          }t          |          }|s|||<   ||k    r|
dz  }
n|
dz  }
y||k    r,|
                    |           | st          d| d           ||k    ru	 |                    d          }t          j        t+          |          t+          |                     	 t          j        ||           |||<   |
                    |           | st          d| d           	 t-          |           n4# t           t"          f$ r  t.                              d|d           Y nw xY wnm# t           t"          f$ rY |                                rC|                                s/t          j        t+          |          t+          |                      w xY w# t           t"          f$ r"}| st          d| d|            Y d}~d}~ww xY w|
dz  }
.|
dz  }
5t3          t5          |                                          |z
            }|D ]}||= |                    d          D ]}|                    |          }t
          |z  }|                                sm	 |j                            dd           t          j        ||           h# t           t"          f$ r&}t.                              d||           Y d}~d}~ww xY wt?          |           tA          |           }|||
|||	tC          |          |dS )z
    Sync bundled skills into ~/.hermes/skills/ using the manifest.

    Returns:
        dict with keys: copied (list), updated (list), skipped (int),
                        user_modified (list), cleaned (list), total_bundled (int)
    uJ     (skipped — profile opted out of bundled skills via .no-bundled-skills)r   T)copiedupdatedskippeduser_modifiedcleanedtotal_bundledoptional_provenance_backfilledskipped_opt_out)r   r   r   r   r   
suppressedr   r   r=   c                     h | ]\  }}|S r   r   )rB   r+   r,   s      r   r   zsync_skills.<locals>.<setcomp>  s    888gdAT888r   rh   u     ⚠ uw   : bundled version shipped but you already have a local skill by this name — yours was kept. Run `hermes skills reset z)` to replace it with the bundled version.z  + z  ! Failed to copy : Nr    z  ~ z (user-modified, skipping)z.baku     ↑ z
 (updated)zCould not remove backup %srJ   z  ! Failed to update zDESCRIPTION.mdzCould not copy %s: %sr   )"HERMES_HOMENO_BUNDLED_SKILLS_MARKERr"   r   r   r4   rM   r.   rv   r:   rs   rz   r   r   r   r   r'   r(   r   with_suffixr   rR   _rmtree_writabler[   r\   rO   r5   keysrr   rx   copy2rb   r   len)r   ro   manifestbundled_skillsbundled_namesr   r   r   r   suppressed_skippedr   ru   	skill_srcr   bundled_hashra   origin_hash	user_hashbackupr   r+   desc_mdry   	dest_descr   s                            r   sync_skillsr    s    	..6688 
 	`^___RAB.0T
 
 	
 #$$K 
RABbST.0
 
 	
 TD111H-k::N88888M'))JFGM$&G!/ e e
I ##%%j111%i== ++X%%C;;== 3 qLG ,66/;,," GZ G GBLG G G   K%%dT%BBBOIt444MM*---+7HZ(  31Z11222W% C C C CA
AAaAABBBC
 [[]] 5	",,z266K!$I 	 (1$,,qLGG qLGK''$$Z000 IGGGGHHH {**I!--f55FKD		3v;;777	4888/;,z222$ C!"A:"A"A"ABBB^,V4444 '1 ^ ^ ^"LL)EvX\L]]]]]^#W-   !==?? @4;;== @"KFSYY???	
  ) I I I  IGjGGAGGHHHI 1 qLGG S))M9::G  TNN $$%566 B B!!+..$	!! 	BB &&td&CCCWi0000W% B B B4gqAAAAAAAAB		B H%B%O%O%O" &(^,,*H	 	 	s   B*F99G,
G''G,=AN;AMLM.MMMMN;A*N66N;;O.O))O.1R55S,S''S,c                 F    ddl fd}t          j        | |           dS )a  Remove a directory tree, making read-only entries writable first.

    Handles immutable package sources (Nix store, deb/rpm installs) that
    preserve read-only permissions on copied files *and* directories
    (``r-xr-xr-x``).  Removing a child requires write permission on its
    parent directory, so the retry handler makes the failing path **and its
    parent** writable before re-attempting.  See #34860, #34972.
    r   Nc                     t           j                            |          |fD ]-}	 t          j        |j                   # t
          $ r Y *w xY w | |           d S )N)rS   r8   dirnamechmodS_IRWXUr'   )funcr   rK   r   stats       r   	_on_errorz#_rmtree_writable.<locals>._on_error  st     wu--u5 	 	F....   Us   A
AA)onerror)r  r   rmtree)r8   r  r  s     @r   r  r  v  sD     KKK     M$	******r   c                 .   t                      }t                      }t          |          }t          |          }| |v }| |v }|s|sddd|  dddS d}|rv|sddd|  dddS t	          ||          |          }	|	                                r?	 t          |	           d	}n,# t          t          f$ r}
dd
d|	 d|
 dddcY d}
~
S d}
~
ww xY w|r|| = t          |           t          d	          }|r|r	d}d|  d}n|r	d}d|  d}nd}d|  d}d	|||dS )u;  
    Reset a bundled skill's manifest tracking so future syncs work normally.

    When a user edits a bundled skill, subsequent syncs mark it as
    ``user_modified`` and skip it forever — even if the user later copies
    the bundled version back into place, because the manifest still holds
    the *old* origin hash. This function breaks that loop.

    Args:
        name: The skill name (matches the manifest key / skill frontmatter name).
        restore: If True, also delete the user's copy in SKILLS_DIR and let
                 the next sync re-copy the current bundled version. If False
                 (default), only clear the manifest entry — the user's
                 current copy is preserved but future updates work again.

    Returns:
        dict with keys:
          - ok: bool, whether the reset succeeded
          - action: one of "manifest_cleared", "restored", "not_in_manifest",
                    "bundled_missing"
          - message: human-readable description
          - synced: dict from sync_skills() if a sync was triggered, else None
    Fnot_in_manifest'zi' is not a tracked bundled skill. Nothing to reset. (Hub-installed skills use `hermes skills uninstall`.)N)r   actionr   syncedbundled_missingur   ' has no bundled source — manifest entry preserved but cannot restore from bundled (skill was removed upstream).T	not_resetzCould not delete user copy at r   u3   . Manifest entry preserved — nothing was changed.r   r   z
Restored 'z' from bundled source.z/' (no prior user copy, re-copied from bundled).manifest_clearedzCleared manifest entry for 'zf'. Future `hermes update` runs will re-baseline against your current copy and accept upstream changes.)r.   r   rv   r   rz   r"   r  r'   r(   rb   r  )r+   r   r  ro   r  bundled_by_namein_manifest
is_bundleddeleted_user_copyr   ra   r   r  r   s                 r   reset_bundled_skillr(    sG   0 H"$$K-k::N>**O("K(J 	
z 	
'ID I I I 
 
 	
   		+U U U U    &od&;[II;;== 	 &&&$(!!W% 	 	 	)M M M M M M #       	  "TN!!! t$$$F 
$ 
;t;;;	 	
TtTTT#W4 W W W 	
 &WOOOs   B   C	1C>C	C	enabledc                    t           t          z  }|                                }	 | r=t                               dd           |                    dd           | }|rdnd}n|r|                                 |}|rdnd	}n1# t          $ r$}d
d
t          |          d| d| dcY d}~S d}~ww xY wd|t          |          |dS )aj  Toggle the .no-bundled-skills opt-out marker for the active profile.

    When ``enabled`` is True, writes HERMES_HOME/.no-bundled-skills so the
    installer, ``hermes update``, and any direct sync stop seeding bundled
    skills. When False, removes the marker so seeding resumes on the next
    sync. This is the on-disk-state half of ``hermes skills opt-out`` /
    ``opt-in``; removal of already-present skills is a separate, explicit
    step (see ``remove_pristine_bundled_skills``).

    Returns:
        dict with keys: ok (bool), changed (bool), marker (str path),
                        message (str).
    Tr=   zThis profile opted out of bundled-skill seeding (`hermes skills opt-out`).
Delete this file to re-enable sync on the next `hermes update`.
r   r   zpOpted out of bundled skills. Future install / update / sync runs will not seed bundled skills into this profile.u1   Already opted out — marker was already present.zgOpted back in. The next `hermes update` (or `hermes skills opt-in --sync`) will re-seed bundled skills.u&   Not opted out — no marker to remove.Fz#Could not update opt-out marker at r   )r   r   markerr   N)r   r   r"   rM   
write_textrZ   r'   rR   )r)  r+  existedr   r   ra   s         r   set_bundled_skills_opt_outr.    sU    33FmmooG
 	dT:::T !	     "kG I G G I	 G   G > ? ? >	   
 
 
ES[[JVJJqJJ
 
 	
 	
 	
 	
 	
 	


 7c&kkgVVVs   AB 
B1B,&B1,B1c                  D    t           t          z                                  S )z=Return True if the active profile carries the opt-out marker.)r   r   r"   r   r   r   is_bundled_skills_opt_outr0  #  s    22::<<<r   dry_runc                 l   t                      }t                      }t          t          |                    }g }g }t	          |                                          D ]\  }}|                    |          }||                    |dd           6t          ||          }	|		                                s
| s||v r||= dt          |	          }
|
|k    r|                    |dd           | r|                    |           	 t          |	           n9# t          t          f$ r%}|                    |d| d           Y d}~d}~ww xY w||v r||= |                    |           | s|rt          |           | rdnd}| dt          |           d	t          |           d
}d||| |dS )a  Delete bundled skills that are present, manifest-tracked, AND unmodified.

    Safety is the whole point of this function. A skill on disk is removed
    ONLY when all of these hold:
      - it is recorded in the sync manifest (so it is genuinely a bundled
        skill, not a hub-installed or hand-written one), AND
      - it still exists in the bundled source (so we can hash-compare), AND
      - its on-disk copy is byte-identical to the manifest origin hash
        (so the user has not edited it).

    Anything user-modified, hub-installed, or locally authored is left
    untouched and reported under ``skipped``. The manifest entry for each
    removed skill is dropped so a later opt-in re-seed treats it as new.

    Args:
        dry_run: When True, compute what would be removed without deleting.

    Returns:
        dict with keys: ok (bool), removed (list[str]),
                        skipped (list[dict]) where each dict is
                        {name, reason}, dry_run (bool), message (str).
    Nz$no bundled source (removed upstream))r+   reasonzuser-modified (kept)zdelete failed: zWould removeRemoved z! pristine bundled skill(s); kept r   T)r   removedr   r1  r   )r.   r   r   rv   rO   rP   r   rs   rz   r"   r   r  r'   r(   rb   r  )r1  r  ro   r$  r6  r   r+   r  r   r   on_diskra   verbr   s                 r   remove_pristine_bundled_skillsr9  (  s#   . H"$$K3K@@AAOGG#HNN$4$455  k!!$'';NND4Z[[\\\%c;77{{}} 	 #tx//TND//k!!NND4JKKLLL 	NN4   	T""""! 	 	 	NND4Ia4I4IJJKKKHHHH	 8t "w "!!!$3>>)DVVGVVs7||VVVGw7w  s   	DE*E

E__main__z1Syncing bundled skills into ~/.hermes/skills/ ...r   r   z newr   z updatedr   z
 unchangedr      z, z, +z morez user-modified (kept): r   z cleaned from manifestr   z official optional backfilledz
Done: z. r   z total bundled.)F)B__doc__r~   r   loggingrS   r   r   r   pathlibr   r   hermes_constantsr   r   r	   agent.skill_utilsr
   typingr   r   r   utilsr   	getLogger__name__r[   r   r4   r!   r   r   r   rR   r.   r5   r:   rb   rn   rv   rz   r   r   r   r   r   r   boolr   r   r   r  r  r(  r.  r0  r9  r   r)   r  r   r9   MAX_SHOWrN   shownrs   r   r   r   r   <module>rH     sl   ,    				  ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ] ] ] ] ] ] ] ] ] ] 4 4 4 4 4 4 $ $ $ $ $ $ $ $ $ $            		8	$	$ o8#
00 0 K$ K K K KU4 U U U U
S#X    6    4`T#s(^ ` ` ` `Bt s s    *$ 4c4i8H3I    &d  $     #     T c     c    	$T 	$c 	$ 	$ 	$ 	$tCsC~)>$>?    6$ T c     CH D D D# D4 DD D D D DNZ Z Z$s) Z Z Z Zzm mt m m m m m`+4 +D + + + +0`P `Pc `PD `PT `P `P `P `PF/W /W /W /W /W /Wd=4 = = = =
C CD CT C C C CL z	E
=>>>[u%%%F3vh  &&&3vi !!+++)(((E
 o D'		%		*++3u::  733u::07777EE

BB5BBCCCi HF9-..FFFGGGzz233 fF#CDEEdddeee	E
QTYYu%%
Q
Q)@
Q
Q
QRRRRR' r   