
    i@                     .   d Z ddlZddlZddlZddlZddlmZ ddlmZ 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	efd
Zd	e
eef         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/ded	efdZd/deded	efdZ edk    r e!d            ed          Z" e#e"d                     d! e#e"d"                    d#e"d$          d%gZ$e"d&         r'e$%                     e#e"d&                    d'           e"d(         r'e$%                     e#e"d(                    d)            e!d*d+&                    e$           d,e"d-          d.           dS dS )0u  
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)Path)get_hermes_home)DictListTuple)atomic_replaceskillsz.bundled_manifestreturnc                      t          j        d          } | rt          |           S t          t                    j        j        dz  S )zLocate the bundled skills/ directory.

    Checks HERMES_BUNDLED_SKILLS env var first (set by Nix wrapper),
    then falls back to the relative path from this source file.
    HERMES_BUNDLED_SKILLSr	   )osgetenvr   __file__parent)env_overrides    6/home/ubuntu/.hermes/hermes-agent/tools/skills_sync.py_get_bundled_dirr   )   sA     9455L "L!!!>> '(22    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(   5   s     !! 	!++W+==HHJJ 
	" 
	"D::<<D d{{$(NN3$7$7!a'/~~'7'7tzz||$$  "tW   			s   BB4 4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   N ).0r%   r'   s      r   	<genexpr>z"_write_manifest.<locals>.<genexpr>Y   s4      XXndH))x))XXXXXXr   z.bundled_manifest_z.tmp)dirprefixsuffixwr   r   z&Failed to write skills manifest %s: %s)exc_info)tempfiler   r   mkdirjoinsorteditemsmkstempstrr   fdopenwriteflushfsyncfilenor   BaseExceptionunlinkr!   	Exceptionloggerdebug)r)   r8   datafdtmp_pathfes          r   _write_manifestrN   P   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   
startswith)rO   rP   contentin_frontmatterr$   strippedvalues          r   _read_skill_namer[   q   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 ]Q}t          |          }d|v sd|v sd|v r|j        }t	          ||j                  }|                    ||f           R|S )zz
    Find all SKILL.md files in the bundled directory.
    Returns list of (skill_name, skill_directory_path) tuples.
    zSKILL.mdz/.git/z	/.github/z/.hub/)r   rglobr>   r   r[   r%   append)r\   r	   rO   path_str	skill_dir
skill_names         r   _discover_bundled_skillsrc      s    
 F %%j11 / /x==x;(#:#:h(>R>RO	%h	??
z9-....Mr   ra   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_to
SKILLS_DIR)ra   r\   rels      r   _compute_relative_destrh      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md5r;   r^   is_filere   updater>   encode
read_bytesr!   r"   	hexdigest)ri   hasherfpathrg   s       r   	_dir_hashru      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Fquietc                    t                      }|                                s	g g dg g ddS t                              dd           t	                      }t          |          }d |D             }g }g }g }d}|D ]\  }	}
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  }_||k    r,|                    |	           | st          d|	 d           ||k    rF	 |                    d          }t          j        t%          |          t%          |                     	 t          j        |
|           |||	<   |                    |	           | st          d|	 d           t          j        |d           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  }t)          t+          |                                          |z
            }|D ]}||= |                    d          D ]}|                    |          }t          |z  }|                                sm	 |j	                            dd           t          j        ||           h# t          t          f$ r&}t4                              d||           Y d}~d}~ww xY wt9          |           |||||t;          |          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)
    r   )copiedupdatedskippeduser_modifiedcleanedtotal_bundledTr+   c                     h | ]\  }}|S r0   r0   )r1   r%   r&   s      r   	<setcomp>zsync_skills.<locals>.<setcomp>   s    888gdAT888r   rT   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))ignore_errorsz  ! Failed to update zDESCRIPTION.mdzCould not copy %s: %s)r   r   rf   r9   r(   rc   rh   ru   printr   shutilcopytreer_   r!   r"   getwith_suffixmover>   rmtreer;   setkeysr^   re   copy2rG   rH   rN   len)rv   r\   manifestbundled_skillsbundled_namesrx   ry   r{   rz   rb   	skill_srcdestbundled_hashrM   origin_hash	user_hashbackupr|   r%   desc_mdrg   	dest_descs                         r   sync_skillsr      s    #$$K 
RAB
 
 	

 TD111H-k::N88888MFGMG!/ Y Y
I%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
 [[]] 2	",,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fDAAAAA#W-   !==?? @4;;== @"KFSYY???	 B  ) 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 &^,,  sd   %B*EF!E>>FAL#AJ43L#4A*LL##M4MM+1PQ.QQr%   restorec           	      6   t                      }t                      }t          |          }d |D             }| |v }| |v }|s|sddd|  dddS |r|| = t          |           d}|r}|sddd|  d	ddS t	          ||          |          }	|	                                rF	 t          j        |	           d
}n.# t          t          f$ r}
ddd|  d|	 d|
 ddcY d}
~
S d}
~
ww xY w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
    c                     i | ]\  }}||	S r0   r0   )r1   rb   ra   s      r   
<dictcomp>z'reset_bundled_skill.<locals>.<dictcomp>\  s    YYY1FYz9YYYr   Fnot_in_manifest'zi' is not a tracked bundled skill. Nothing to reset. (Hub-installed skills use `hermes skills uninstall`.)N)okactionmessagesyncedbundled_missingup   ' has no bundled source — manifest entry cleared but cannot restore from bundled (skill was removed upstream).Tmanifest_clearedzCleared manifest entry for 'z$' but could not delete user copy at r   rv   restoredz
Restored 'z' from bundled source.z/' (no prior user copy, re-copied from bundled).zf'. Future `hermes update` runs will re-baseline against your current copy and accept upstream changes.)r(   r   rc   rN   rh   r   r   r   r!   r"   r   )r%   r   r   r\   r   bundled_by_namein_manifest
is_bundleddeleted_user_copyr   rM   r   r   r   s                 r   reset_bundled_skillr   A  sR   0 H"$$K-k::NYY.YYYO("K(J 	
z 	
'ID I I I 
 
 	
  "TN!!!   		+U U U U    &od&;[II;;== 	d###$(!!W% 	 	 	0;t ; ;/3; ;78; ; #       	 t$$$F 
$ 
;t;;;	 	
TtTTT#W4 W W W 	
 &WOOOs   B6 6C!CC!C!__main__z1Syncing bundled skills into ~/.hermes/skills/ ...r   rx   z newry   z updatedrz   z
 unchangedr{   z user-modified (kept)r|   z cleaned from manifestz
Done: z, z. r}   z total bundled.)F)'__doc__rl   loggingr   r   pathlibr   hermes_constantsr   typingr   r   r   utilsr   	getLogger__name__rG   HERMES_HOMErf   r   r   r>   r(   rN   r[   rc   rh   ru   booldictr   r   r   r#   r   partsr_   r:   r0   r   r   <module>r      sk   ,   				        , , , , , , $ $ $ $ $ $ $ $ $ $            		8	$	$ o8#
00	3$ 	3 	3 	3 	3S#X    6`T#s(^ ` ` ` `Bt s s    *$ 4c4i8H3I    (d  $     #    M Mt M M M M M`_P _Pc _PD _PT _P _P _P _PD z	E
=>>>[u%%%F3vh  &&&3vi !!+++)(((E
 o MF?344KKKLLLi HF9-..FFFGGG	E
QTYYu%%
Q
Q)@
Q
Q
QRRRRR r   