
    i<q                     p   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mZ ddl	m
Z
mZ ddlmZmZmZmZ  ej        e          Z	 ddlmZmZmZ dZn# e$ r dZY nw xY wd	ed
ee         fdZddlZ e
            Zedz  ZdZdZ ded
e!fdZ"dZ#dZ$ ej%        d          Z&h dZ'ded
ee         fdZ(dee         d
ee         fdZ)ded
ee         fdZ*dKdeded
ee         fdZ+dLdeded
efdZ,ded
eeeef                  fdZ-d ed
ee         fd!Z.d	ed ed
eee         ee         f         fd"Z/dMd eded$ed
dfd%Z0dLdededed
eeef         fd&Z1deded
eeef         fd'Z2	 	 dNded(ed)ed ed*e!d
eeef         fd+Z3ded
eeef         fd,Z4ded ed-ed
eeef         fd.Z5ded ed
eeef         fd/Z6	 	 	 	 	 	 	 dOd0edededed ed-ed(ed)ed*e!d
efd1Z7d2d3 e             d4d5d6g d7d8d9d6d:d;d6d<d;d6d=d;d6d>d;d?d@d;d6dAd;d6dBd;d6dCd;dD	d0dgdEdFZ8ddGl9m:Z:m;Z;  e:j<        d2de8dH dIJ           dS )Pu  
Skill Manager Tool -- Agent-Managed Skill Creation & Editing

Allows the agent to create, update, and delete skills, turning successful
approaches into reusable procedural knowledge. New skills are created in
~/.hermes/skills/. Existing skills (bundled, hub-installed, or user-created)
can be modified or deleted wherever they live.

Skills are the agent's procedural memory: they capture *how to do a specific
type of task* based on proven experience. General memory (MEMORY.md, USER.md) is
broad and declarative. Skills are narrow and actionable.

Actions:
  create     -- Create a new skill (SKILL.md + directory structure)
  edit       -- Replace the SKILL.md content of a user skill (full rewrite)
  patch      -- Targeted find-and-replace within SKILL.md or any supporting file
  delete     -- Remove a user skill entirely
  write_file -- Add/overwrite a supporting file (reference, template, script, asset)
  remove_file-- Remove a supporting file from a user skill

Directory layout for user skills:
    ~/.hermes/skills/
    ├── my-skill/
    │   ├── SKILL.md
    │   ├── references/
    │   ├── templates/
    │   ├── scripts/
    │   └── assets/
    └── category-name/
        └── another-skill/
            └── SKILL.md
    N)Path)get_hermes_homedisplay_hermes_home)DictAnyOptionalTuple)
scan_skillshould_allow_installformat_scan_reportTF	skill_dirreturnc                 j   t           sdS 	 t          | d          }t          |          \  }}|du rt          |          }d| d| S |2t          |          }t                              d|           d| d| S n5# t          $ r(}t                              d| |d	
           Y d}~nd}~ww xY wdS )zOScan a skill directory after write. Returns error string if blocked, else None.Nzagent-created)sourceFz"Security scan blocked this skill (z):
z4Agent-created skill blocked (dangerous findings): %szSecurity scan failed for %s: %sTexc_info)_GUARD_AVAILABLEr
   r   r   loggerwarning	Exception)r   resultallowedreasonreportes         @/home/agentuser/.hermes/hermes-agent/tools/skill_manager_tool.py_security_scan_skillr   8   s     tWIo>>>.v66e'//FLLLFLLL? (//FNNQSYZZZLLLFLLL   W W W8)QQUVVVVVVVVW4s   =A> 	3A> >
B0B++B0skills@   i   
skill_pathc                     	 |                                                      t                                                      dS # t          $ r Y dS w xY w)zCheck if a skill path is within the local SKILLS_DIR.

    Skills found in external_dirs are read-only from the agent's perspective.
    TF)resolverelative_to
SKILLS_DIR
ValueError)r    s    r   _is_local_skillr&   W   sZ    
((););)=)=>>>t   uus   >A 
AAi i   z^[a-z0-9][a-z0-9._-]*$>   assetsscripts	templates
referencesnamec                     | sdS t          |           t          k    rdt           dS t                              |           sd|  dS dS )z>Validate a skill name. Returns error message or None if valid.zSkill name is required.zSkill name exceeds  characters.zInvalid skill name 'ze'. Use lowercase letters, numbers, hyphens, dots, and underscores. Must start with a letter or digit.N)lenMAX_NAME_LENGTHVALID_NAME_REmatch)r+   s    r   _validate_namer2   o   sn     )((
4yy?""B_BBBBt$$ 
R4 R R R	
 4    categoryc                    | dS t          | t                    sdS |                                 } | sdS d| v sd| v rd|  dS t          |           t          k    rdt           dS t
                              |           sd|  dS dS )	zFValidate an optional category name used as a single directory segment.NzCategory must be a string./\zInvalid category 'zn'. Use lowercase letters, numbers, hyphens, dots, and underscores. Categories must be a single directory name.zCategory exceeds r-   )
isinstancestrstripr.   r/   r0   r1   )r4   s    r   _validate_categoryr;   }   s    th$$ ,++~~H t
h$(**Z Z Z Z	
 8}}&&@?@@@@x(( 
Z Z Z Z	
 4r3   contentc                 j   |                                  sdS |                     d          sdS t          j        d| dd                   }|sdS | d|                                dz            }	 t          j        |          }n!# t
          j        $ r}d| cY d}~S d}~ww xY wt          |t                    sd	S d
|vrdS d|vrdS t          t          |d                             t          k    rdt           dS | |                                dz   d                                          }|sdS dS )z
    Validate that SKILL.md content has proper frontmatter with required fields.
    Returns error message or None if valid.
    zContent cannot be empty.z---zPSKILL.md must start with YAML frontmatter (---). See existing skills for format.z
\n---\s*\n   NzISKILL.md frontmatter is not closed. Ensure you have a closing '---' line.zYAML frontmatter parse error: z6Frontmatter must be a YAML mapping (key: value pairs).r+   z&Frontmatter must include 'name' field.descriptionz-Frontmatter must include 'description' field.zDescription exceeds r-   zRSKILL.md must have content after the frontmatter (instructions, procedures, etc.).)r:   
startswithresearchstartyaml	safe_load	YAMLErrorr8   dictr.   r9   MAX_DESCRIPTION_LENGTHend)r<   	end_matchyaml_contentparsedr   bodys         r   _validate_frontmatterrN      s~   
 ==?? *))e$$ baa	-55I [ZZ1Y__..223L4--> 4 4 43333333334 fd## HGGV77F"">>
3vm$%%&&)???J&<JJJJ9==??Q&''(..00D dcc4s   /B B"BB"B"SKILL.mdlabelc                 t    t          |           t          k    r| dt          |           ddt          ddS dS )zCheck that content doesn't exceed the character limit for agent writes.

    Returns an error message or None if within bounds.
    z content is ,z characters (limit: za). Consider splitting into a smaller SKILL.md with supporting files in references/ or templates/.N)r.   MAX_SKILL_CONTENT_CHARS)r<   rP   s     r   _validate_content_sizerT      sY    
 7||--- - -#g,,> - -.5- - -	
 4r3   c                 4    |rt           |z  | z  S t           | z  S )zFBuild the directory path for a new skill, optionally under a category.)r$   )r+   r4   s     r   _resolve_skill_dirrV      s%     ,H$t++r3   c                     ddl m}  |            D ]L}|                                s|                    d          D ]}|j        j        | k    rd|j        ic c S  MdS )z
    Find a skill by name across all skill directories.

    Searches the local skills dir (~/.hermes/skills/) first, then any
    external dirs configured via skills.external_dirs.  Returns
    {"path": Path} or None.
    r   )get_all_skills_dirsrO   pathN)agent.skill_utilsrX   existsrglobparentr+   )r+   rX   
skills_dirskill_mds       r   _find_skillr`      s     655555))++ 1 1
  "" 	"((44 	1 	1H#t++000000 ,	1 4r3   	file_pathc                 :   ddl m} | sdS t          |           } ||           rdS |j        r|j        d         t          vr0d                    t          t                              }d| d|  dS t          |j                  d	k     rd
|j        d          dS dS )z
    Validate a file path for write_file/remove_file.
    Must be under an allowed subdirectory and not escape the skill dir.
    r   )has_traversal_componentzfile_path is required.z%Path traversal ('..') is not allowed.z, zFile must be under one of: z. Got: ''   z5Provide a file path, not just a directory. Example: 'z/myfile.md'N)tools.path_securityrc   r   partsALLOWED_SUBDIRSjoinsortedr.   )ra   rc   
normalizedr   s       r   _validate_file_pathrl      s    
 <;;;;; (''iJ y)) 766  Kz/2/II))F?3344JWJJiJJJJ :q  gzGWXYGZgggg4r3   c                 D    ddl m} | |z  } |||           }|rd|fS |dfS )zNResolve a supporting-file path and ensure it stays within the skill directory.r   )validate_within_dirN)rf   rn   )r   ra   rn   targeterrors        r   _resolve_skill_targetrq     sL    777777"F	22E U{4<r3   utf-8encodingc                     | j                             dd           t          j        t	          | j                   d| j         dd          \  }}	 t          j        |d|          5 }|                    |           d	d	d	           n# 1 swxY w Y   t          j	        ||            d	S # t          $ rE 	 t          j        |           n-# t          $ r  t                              d
|d           Y nw xY w w xY w)au  
    Atomically write text content to a file.
    
    Uses a temporary file in the same directory and os.replace() to ensure
    the target file is never left in a partially-written state if the process
    crashes or is interrupted.
    
    Args:
        file_path: Target file path
        content: Content to write
        encoding: Text encoding (default: utf-8)
    Tparentsexist_ok.z.tmp. )dirprefixsuffixwrs   Nz6Failed to remove temporary file %s during atomic writer   )r]   mkdirtempfilemkstempr9   r+   osfdopenwritereplacer   unlinkOSErrorr   rp   )ra   r<   rs   fd	temp_pathfs         r   _atomic_write_textr     sk    4$777$	 !!(9>(((  MB	

Yr3222 	aGGG	 	 	 	 	 	 	 	 	 	 	 	 	 	 	

9i(((((   	mIi     	m 	m 	mLLQS\gkLlllll	msT   B. *B B. BB. BB. .
C=9CC='C85C=7C88C=c                    t          |           }|rd|dS t          |          }|rd|dS t          |          }|rd|dS t          |          }|rd|dS t	          |           }|rdd|  d|d          ddS t          | |          }|                    dd           |d	z  }t          ||           t          |          }|rt          j
        |d
           d|dS dd|  dt          |                    t                              t          |          d}|r||d<   d                    |           |d<   |S )z.Create a new user skill with SKILL.md content.Fsuccessrp   zA skill named 'z' already exists at rY   rx   Tru   rO   )ignore_errorsSkill 'z
' created.)r   messagerY   r_   r4   zTo add reference files, templates, or scripts, use skill_manage(action='write_file', name='{}', file_path='references/example.md', file_content='...')hint)r2   r;   rN   rT   r`   rV   r   r   r   shutilrmtreer9   r#   r$   format)	r+   r<   r4   errexistingr   r_   
scan_errorr   s	            r   _create_skillr   0  s    

C
 0 3///
X
&
&C
 0 3///  
(
(C
 0 3///
 
)
)C
 0 3/// 4  H 
TtTT&AQTTT
 
 	
 #422IOOD4O000 :%Hx))) &i00J 7it4444 :666 -T---I))*5566MM	 F  &%z	nntntuynznz 6N Mr3   c                    t          |          }|rd|dS t          |          }|rd|dS t          |           }|s	dd|  ddS t          |d                   s	dd|  ddS |d         dz  }|                                r|                    d	          nd
}t          ||           t          |d                   }|r|t          ||           d|dS dd|  dt          |d                   dS )z:Replace the SKILL.md of any existing skill (full rewrite).Fr   r   z7' not found. Use skills_list() to see available skills.rY   c' is in an external directory and cannot be modified. Copy it to your local skills directory first.rO   rr   r~   NTz
' updated.r   r   rY   )	rN   rT   r`   r&   r[   	read_textr   r   r9   )r+   r<   r   r   r_   original_contentr   s          r   _edit_skillr   i  s{   

(
(C
 0 3///
 
)
)C
 0 3///4  H t +rT+r+r+rsss8F+,, `   ,_T  ,_  ,_  ,_  `  `  	`*,H?G?P?PZx))7);;;VZx))) &hv&677J 7'x)9::: :666 -T---HV$%%  r3   
old_string
new_stringreplace_allc           
      <   |sdddS |dddS t          |           }|s	dd|  ddS t          |d                   s	dd|  d	dS |d         }|r1t          |          }|rd|dS t          ||          \  }}|rd|dS n|d
z  }|                                sdd|                    |           dS |                    d          }	ddlm}
  |
|	|||          \  }}}}|r(|	dd         t          |	          dk    rdndz   }d||dS |sd
n|}t          ||          }|rd|dS |st          |          }|rdd| dS |	}t          ||           t          |          }|rt          ||           d|dS dd|sd
n| d|  d| d|dk    rdnd d	dS )zTargeted find-and-replace within a skill file.

    Defaults to SKILL.md. Use file_path to patch a supporting file instead.
    Requires a unique match unless replace_all is True.
    Fz#old_string is required for 'patch'.r   NzOnew_string is required for 'patch'. Use an empty string to delete matched text.r   ' not found.rY   r   rO   zFile not found: rr   r~   r   )fuzzy_find_and_replacei  z...ry   )r   rp   file_previewrP   z&Patch would break SKILL.md structure: TzPatched z in skill 'z' (z replacement   sz).r   r   )r`   r&   rl   rq   r[   r#   r   tools.fuzzy_matchr   r.   rT   rN   r   r   )r+   r   r   ra   r   r   r   r   ro   r<   r   new_contentmatch_count	_strategymatch_errorpreviewtarget_labelr   r   s                      r   _patch_skillr     s.     R +PQQQ +|}}}4  H I +GT+G+G+GHHH8F+,, `   ,_T  ,_  ,_  ,_  `  `  	` I 
(!),, 	4$s333+IyAA 	4$s333	4 Z'==?? _ +]f>P>PQZ>[>[+]+]^^^00G 9888887M7MZ8 84Ki  
$3$-CLL3,>,>55BG #
 
 	
 &/=::IL
 L
A
A
AC
 0 3///  #K00 	 G#GG  
 v{+++ &i00J 76#3444 :666  Z	Hjjy  Z  ZUY  Z  Z^i  Z  Z  J  MN  N  Nwzwz  TV  Z  Z  Z  r3   c                 r   t          |           }|s	dd|  ddS t          |d                   s	dd|  ddS |d         }t          j        |           |j        }|t
          k    rI|                                r5t          |                                          s|	                                 dd|  dd	S )
zDelete a skill.Fr   r   r   rY   z4' is in an external directory and cannot be deleted.Tz
' deleted.r   )
r`   r&   r   r   r]   r$   r[   anyiterdirrmdir)r+   r   r   r]   s       r   _delete_skillr     s    4  H I +GT+G+G+GHHH8F+,, q +oT+o+o+oppp I
M) FFNN<L<L8M8M -T---  r3   file_contentc                    t          |          }|rd|dS |s|dk    rdddS t          |                    d                    }|t          k    rdd|ddt          dd	dS t	          ||
          }|rd|dS t          |           }|s	dd|  ddS t          |d                   s	dd|  ddS t          |d         |          \  }}|rd|dS |j        	                    dd           |
                                r|                    d          nd}t          ||           t          |d                   }|r.|t          ||           n|                    d           d|dS dd| d|  dt          |          dS )z>Add or overwrite a supporting file within any skill directory.Fr   ry   zfile_content is required.rr   zFile content is rR   z bytes (limit: z7 bytes / 1 MiB). Consider splitting into smaller files.r   r   z2' not found. Create it first with action='create'.rY   r   Tru   r~   N)
missing_okFile 'z' written to skill ''.r   )rl   r.   encodeMAX_SKILL_FILE_BYTESrT   r`   r&   rq   r]   r   r[   r   r   r   r   r9   )	r+   ra   r   r   content_bytesr   ro   r   r   s	            r   _write_filer     sX   
i
(
(C
 0 3/// HLB.. +FGGG ++G4455M+++:=; : :/D: : :
 
 	
 !Y
?
?
?C
 0 3///4  H o +mT+m+m+mnnn8F+,, `   ,_T  ,_  ,_  ,_  `  `  	`'(8)DDKFC
 0 3///
Mt444=C]]__Vv'''999RVv|,,, &hv&677J 7'v'78888MMTM*** :666 CICC4CCCF  r3   c           	      $   t          |          }|rd|dS t          |           }|s	dd|  ddS t          |d                   s	dd|  ddS |d         }t          ||          \  }}|rd|dS |                                sg }t
          D ]|}||z  }|                                ra|                    d          D ]K}	|	                                r5|                    t          |	
                    |                               L}dd| d	|  d
|r|nddS |                                 |j        }
|
|k    rI|
                                r5t          |
                                          s|
                                 dd| d|  d
dS )z2Remove a supporting file from any skill directory.Fr   r   r   rY   z5' is in an external directory and cannot be modified.*r   z' not found in skill 'r   N)r   rp   available_filesTz' removed from skill 'r   )rl   r`   r&   rq   r[   rh   r\   is_fileappendr9   r#   r   r]   r   r   r   )r+   ra   r   r   r   ro   	availablesubdirdr   r]   s              r   _remove_filer   6  s   
i
(
(C
 0 3///4  H I +GT+G+G+GHHH8F+,, r +pT+p+p+pqqq I'	9==KFC
 0 3///==?? 
	% 	H 	HFF"Axxzz H H HAyy{{ H!((Q]]9-E-E)F)FGGGGiGGtGGG,5?yy4
 
 	
 MMOOO ]Fv}}s6>>;K;K7L7L EIEETEEE  r3   actionc	                    | dk    r%|st          dd          S t          |||          }	n| dk    r$|st          dd          S t          ||          }	n| dk    r:|st          dd          S |t          d
d          S t          |||||          }	n| dk    rt	          |          }	nq| dk    r8|st          dd          S |t          dd          S t          |||          }	n3| dk    r$|st          dd          S t          ||          }	n	dd|  dd}	|	                    d          r$	 ddlm	}
  |
d           n# t          $ r Y nw xY wt          j        |	d          S )zz
    Manage user-created skills. Dispatches to the appropriate action handler.

    Returns JSON string with results.
    createzVcontent is required for 'create'. Provide the full SKILL.md text (frontmatter + body).F)r   editzGcontent is required for 'edit'. Provide the full updated SKILL.md text.patchz=old_string is required for 'patch'. Provide the text to find.NzLnew_string is required for 'patch'. Use empty string to delete matched text.delete
write_filezJfile_path is required for 'write_file'. Example: 'references/api-guide.md'z*file_content is required for 'write_file'.remove_filez(file_path is required for 'remove_file'.zUnknown action 'z<'. Use: create, edit, patch, delete, write_file, remove_filer   r   r   ) clear_skills_system_prompt_cacheT)clear_snapshot)ensure_ascii)
tool_errorr   r   r   r   r   r   getagent.prompt_builderr   r   jsondumps)r   r+   r<   r4   ra   r   r   r   r   r   r   s              r   skill_manager   h  s6      	Gv  AF  G  G  G  GtWh77	6		 	xgqvwwwwT7++	7		 	n]glmmmmlv{||||dJ
I{SS	8		t$$	<		 	{jtyzzzzJTYZZZZT9l;;	=	 	  	YHRWXXXXdI.. #  .E  .E  .E  .E  F  Fzz) 	MMMMMM,,DAAAAA 	 	 	D	 :f51111s   3E 
EEr   u   Manage skills (create, update, delete). Skills are your procedural memory — reusable approaches for recurring task types. New skills go to u{  /skills/; existing skills can be modified wherever they live.

Actions: create (full SKILL.md + optional category), patch (old_string/new_string — preferred for fixes), edit (full SKILL.md rewrite — major overhauls only), delete, write_file, remove_file.

Create when: complex task succeeded (5+ calls), errors overcome, user-corrected approach worked, non-trivial workflow discovered, or user asks you to remember a procedure.
Update when: instructions stale/wrong, OS-specific failures, missing steps or pitfalls found during use. If you used a skill and hit issues not covered by it, patch it immediately.

After difficult/iterative tasks, offer to save as a skill. Skip for simple one-offs. Confirm with user before creating/deleting.

Good skills: trigger conditions, numbered steps with exact commands, pitfalls section, verification steps. Use skill_view() to see format examples.objectstring)r   r   r   r   r   r   zThe action to perform.)typeenumr?   zSkill name (lowercase, hyphens/underscores, max 64 chars). Must match an existing skill for patch/edit/delete/write_file/remove_file.)r   r?   zFull SKILL.md content (YAML frontmatter + markdown body). Required for 'create' and 'edit'. For 'edit', read the skill first with skill_view() and provide the complete updated text.zText to find in the file (required for 'patch'). Must be unique unless replace_all=true. Include enough surrounding context to ensure uniqueness.zXReplacement text (required for 'patch'). Can be empty string to delete the matched text.booleanzZFor 'patch': replace all occurrences instead of requiring a unique match (default: false).zOptional category/domain for organizing the skill (e.g., 'devops', 'data-science', 'mlops'). Creates a subdirectory grouping. Only used with 'create'.zPath to a supporting file within the skill directory. For 'write_file'/'remove_file': required, must be under references/, templates/, scripts/, or assets/. For 'patch': optional, defaults to SKILL.md if omitted.z0Content for the file. Required for 'write_file'.)	r   r+   r<   r   r   r   r4   ra   r   )r   
propertiesrequired)r+   r?   
parameters)registryr   c                    t          |                     dd          |                     dd          |                     d          |                     d          |                     d          |                     d          |                     d          |                     d	          |                     d
d          	  	        S )Nr   ry   r+   r<   r4   ra   r   r   r   r   F)	r   r+   r<   r4   ra   r   r   r   r   )r   r   )argskws     r   <lambda>r   
  s    |xx"%%XXfb!!##*%%((;''XXn--88L))88L))HH]E22	 4 	 4 	 4 r3   u   📝)r+   toolsetschemahandleremoji)rO   )N)rr   )NF)NNNNNNF)=__doc__r   loggingr   rA   r   r   pathlibr   hermes_constantsr   r   typingr   r   r   r	   	getLogger__name__r   tools.skills_guardr
   r   r   r   ImportErrorr9   r   rD   HERMES_HOMEr$   r/   rH   boolr&   rS   r   compiler0   rh   r2   r;   rN   rT   rV   r`   rl   rq   r   r   r   r   r   r   r   r   SKILL_MANAGE_SCHEMAtools.registryr   r   register r3   r   <module>r      s   B   				 				         A A A A A A A A - - - - - - - - - - - -		8	$	$WWWWWWWWWW   D Xc]    (  o8#
 	 	 	 	 	 	 "    
455 CBB #    # 8C=    2$3 $8C= $ $ $ $N C  Xc]     S C 4    c htCH~6    $3 8C=    8T c eHTNT\]`TaDa>b     $   RV    H6 6 6c 6S 6DcN 6 6 6 6r!c !C !DcN ! ! ! !P X X
XX X 	X
 X 
#s(^X X X Xv S#X    .4c 4c 4 4c3h 4 4 4 4n+s +s +tCH~ + + + +j :2 :2:2
:2 :2 	:2
 :2 :2 :2 :2 :2 	:2 :2 :2 :2D 	Y//11	Y 	Y 	Y&  !ZZZ7  !a  !U  !)  !2  "{ 
 !/  !N  !Q s=
 =
| v&AA A+W W v 0 / / / / / / /  		4 	4      s   A AA