
    Ki_i                        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
 ddl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ZdZ  ej!        d          Z"h dZ#d
e$f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(dHdeded
ee         fdZ)dId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,dJdeded"ed
dfd#Z-dIdededed
eeef         fd$Z.deded
eeef         fd%Z/	 	 dKded&ed'eded(e$d
eeef         fd)Z0ded
eeef         fd*Z1deded+ed
eeef         fd,Z2deded
eeef         fd-Z3	 	 	 	 	 	 	 dLd.ededededed+ed&ed'ed(e$d
efd/Z4d0d1d2d3g d4d5d6d3d7d8d3d9d8d3d:d8d3d;d8d<d=d8d3d>d8d3d?d8d3d@d8dA	d.dgdBdCZ5ddDl6m7Z7  e7j8        d0de5dE dFG           dS )Mu  
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)DictAnyOptional)
scan_skillshould_allow_installformat_scan_reportTF	skill_dirreturnc                 ^   t           sdS 	 t          | d          }t          |          \  }}|du rt          |          }d| d| S |,t          |          }t                              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):
z-Agent-created skill has security findings: %szSecurity scan failed for %s: %sTexc_info)_GUARD_AVAILABLEr   r	   r
   loggerwarning	Exception)r   resultallowedreasonreportes         5/home/ubuntu/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JFSSS4   W W W8)QQUVVVVVVVVW4s   =A8 	,A8 8
B*B%%B*skills@   i   i i   z^[a-z0-9][a-z0-9._-]*$>   assetsscripts	templates
referencesc                      dS )zBSkill management has no external requirements -- always available.T r#       r   check_skill_manage_requirementsr%   _   s    4r$   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_namer-   h   sn     )((
4yy?""B_BBBBt$$ 
R4 R R R	
 4r$   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*   r+   r,   )r.   s    r   _validate_categoryr5   v   s    th$$ ,++~~H t
h$(**Z Z Z Z	
 8}}&&@?@@@@x(( 
Z Z Z Z	
 4r$   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.).)r4   
startswithresearchstartyaml	safe_load	YAMLErrorr2   dictr)   r3   MAX_DESCRIPTION_LENGTHend)r6   	end_matchyaml_contentparsedr   bodys         r   _validate_frontmatterrH      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)r6   rJ   s     r   _validate_content_sizerN      sY    
 7||--- - -#g,,> - -.5- - -	
 4r$   c                 4    |rt           |z  | z  S t           | z  S )zFBuild the directory path for a new skill, optionally under a category.)
SKILLS_DIR)r&   r.   s     r   _resolve_skill_dirrQ      s%     ,H$t++r$   c                     t                                           sdS t                               d          D ]}|j        j        | k    rd|j        ic S dS )zX
    Find a skill by name in ~/.hermes/skills/.
    Returns {"path": Path} or None.
    NrI   path)rP   existsrglobparentr&   )r&   skill_mds     r   _find_skillrX      sg    
  t$$Z00 - -?4''HO,,,, (4r$   	file_pathc                 *   | sdS t          |           }d|j        v 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.
    zfile_path is required.z..z%Path traversal ('..') is not allowed.r   z, zFile must be under one of: z. Got: ''   z5Provide a file path, not just a directory. Example: 'z/myfile.md'N)r   partsALLOWED_SUBDIRSjoinsortedr)   )rY   
normalizedr   s      r   _validate_file_pathrb      s    
  (''iJ z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4r$   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rd   Nz6Failed to remove temporary file %s during atomic writer   )rV   mkdirtempfilemkstempr3   r&   osfdopenwritereplacer   unlinkOSErrorr   error)rY   r6   rd   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successry   zA skill named 'z' already exists at rS   ri   Trf   rI   )ignore_errorsSkill 'z
' created.)r   messagerS   rW   r.   zTo add reference files, templates, or scripts, use skill_manage(action='write_file', name='{}', file_path='references/example.md', file_content='...')hint)r-   r5   rH   rN   rX   rQ   rp   r}   r   shutilrmtreer3   relative_torP   format)	r&   r6   r.   errexistingr   rW   
scan_errorr   s	            r   _create_skillr     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 Mr$   c                    t          |          }|rd|dS t          |          }|rd|dS t          |           }|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.rS   rI   rc   ro   NTz
' updated.r   r   rS   )rH   rN   rX   rT   	read_textr}   r   r3   )r&   r6   r   r   rW   original_contentr   s          r   _edit_skillr   P  s6   

(
(C
 0 3///
 
)
)C
 0 3///4  H t +rT+r+r+rsss*,H?G?P?PZx))7);;;VZx))) &hv&677J 7'x)9::: :666 -T---HV$%%  r$   
old_string
new_stringreplace_allc           
         |sdddS |dddS t          |           }|s	dd|  ddS |d         }|rt          |          }|rd|dS ||z  }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.rS   rI   zFile not found: rc   ro   r   )fuzzy_find_and_replacei  z...rj   )r   ry   file_previewrJ   z&Patch would break SKILL.md structure: TzPatched z in skill 'z' (z replacement   sz).r   r   )rX   rb   rT   r   r   tools.fuzzy_matchr   r)   rN   rH   r}   r   )r&   r   r   rY   r   r   r   r   targetr6   r   new_contentmatch_countmatch_errorpreviewtarget_labelr   r   s                     r   _patch_skillr   q  s     R +PQQQ +|}}}4  H I +GT+G+G+GHHH I (!),, 	4$s333Y& Z'==?? _ +]f>P>PQZ>[>[+]+]^^^00G 988888,B,BZ- -)Kk  
$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  r$   c                 6   t          |           }|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   rS   Tz
' deleted.r   )	rX   r   r   rV   rP   rT   anyiterdirrmdir)r&   r   r   rV   s       r   _delete_skillr     s    4  H I +GT+G+G+GHHH I
M) FFNN<L<L8M8M -T---  r$   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 |d         |z  }|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   rj   zfile_content is required.rc   zFile content is rL   z bytes (limit: z7 bytes / 1 MiB). Consider splitting into smaller files.r   r   z2' not found. Create it first with action='create'.rS   Trf   ro   N)
missing_okFile 'z' written to skill ''.r   )rb   r)   encodeMAX_SKILL_FILE_BYTESrN   rX   rV   rp   rT   r   r}   r   rw   r3   )	r&   rY   r   r   content_bytesr   r   r   r   s	            r   _write_filer     s   
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f	)F
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  r$   c           	         t          |          }|rd|dS t          |           }|s	dd|  ddS |d         }||z  }|                                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   rS   *r   z' not found in skill 'r   N)r   ry   available_filesTz' removed from skill 'r   )rb   rX   rT   r^   rU   is_fileappendr3   r   rw   rV   r   r   r   )r&   rY   r   r   r   r   	availablesubdirdr|   rV   s              r   _remove_filer     s   
i
(
(C
 0 3///4  H I +GT+G+G+GHHH I"F==?? 
	% 	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  r$   actionc	                 J   | dk    r.|st          j        dddd          S t          |||          }	n!| dk    r,|st          j        dddd          S t          ||          }	n| dk    rJ|st          j        dd	dd          S |t          j        dddd          S t	          |||||          }	n| dk    rt          |          }	n| dk    rH|st          j        dddd          S |t          j        dddd          S t          |||          }	n;| dk    r,|st          j        dd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.
    createFzVcontent is required for 'create'. Provide the full SKILL.md text (frontmatter + body).r   )ensure_ascii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   ) clear_skills_system_prompt_cacheT)clear_snapshot)jsondumpsr   r   r   r   r   r   getagent.prompt_builderr   r   )r   r&   r6   r.   rY   r   r   r   r   r   r   s              r   skill_manager   9  s      	i:%  ;S  T  T  ch  i  i  i  itWh77	6		 	Z:%  ;D  E  E  TY  Z  Z  Z  ZT7++	7		 	P:%:yzz  JO  P  P  P  P:%  ;I  J  J  Y^  _  _  _  _dJ
I{SS	8		t$$	<		 	]:%  ;G  H  H  W\  ]  ]  ]  ]:%:fggv{||||T9l;;	=	 	  	{:%:deetyzzzz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   ,E? ?
FFr   u  Manage skills (create, update, delete). Skills are your procedural memory — reusable approaches for recurring task types. New skills go to ~/.hermes/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enumr9   zSkill name (lowercase, hyphens/underscores, max 64 chars). Must match an existing skill for patch/edit/delete/write_file/remove_file.)r   r9   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&   r6   r   r   r   r.   rY   r   )r   
propertiesrequired)r&   r9   
parameters)registryc                    t          |                     dd          |                     dd          |                     d          |                     d          |                     d          |                     d          |                     d          |                     d	          |                     d
d          	  	        S )Nr   rj   r&   r6   r.   rY   r   r   r   r   F)	r   r&   r6   r.   rY   r   r   r   r   )r   r   )argskws     r   <lambda>r     s    |xx"%%XXfb!!##*%%((;''XXn--88L))88L))HH]E22	 4 	 4 	 4 r$   u   📝)r&   toolsetschemahandleremoji)rI   )N)rc   )NF)NNNNNNF)9__doc__r   loggingrs   r;   r   rq   pathlibr   hermes_constantsr   typingr   r   r   	getLogger__name__r   tools.skills_guardr   r	   r
   r   ImportErrorr3   r   r>   HERMES_HOMErP   r*   rB   rM   r   compiler+   r^   boolr%   r-   r5   rH   rN   rQ   rX   rb   r}   r   r   r   r   r   r   r   SKILL_MANAGE_SCHEMAtools.registryr   registerr#   r$   r   <module>r      sB   B   				 				         , , , , , , & & & & & & & & & &		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=    4 $   RV    H6 6 6c 6S 6DcN 6 6 6 6rc C DcN    J S S
SS S 	S
 S 
#s(^S S S Sl S#X    (/c /c / /c3h / / / /d%s %s %tCH~ % % % %^ :2 :2:2
:2 :2 	:2
 :2 :2 :2 :2 :2 	:2 :2 :2 :2D 	Y&  !ZZZ7  !a  !U  !)  !2  "{ 
 !/  !N  !Q s=
 =
| v&AA A+W W v $ # # # # #  		4 	4      s   A AA