
    `gja                       d Z ddlmZ ddlZddlZddlmZmZ ddlmZ ddl	m
Z
 ddZddZdd	Zdd
ZddZddZddZddZddZddZddZddZddZddZddZddZdddZedk(  r ej:                   e              yy)u0  CLI subcommand: `hermes curator <subcommand>`.

Thin shell around agent/curator.py and tools/skill_usage.py. Renders a status
table, triggers a run, pauses/resumes, and pins/unpins skills.

This module intentionally has no side effects at import time — main.py wires
the argparse subparsers on demand.
    )annotationsN)datetimetimezone)Path)Optionalc                   | sy	 t        j                  |       }|j
                   |j                  t        j                        }t        j                  t        j                        |z
  }t        |j                               }|dk  r| dS |dk  r|dz   dS |dk  r|dz   dS |dz   d	S # t        t        f$ r t	        |       cY S w xY w)
Nnevertzinfo<   zs agoi  zm agoiQ zh agozd ago)r   fromisoformat	TypeError
ValueErrorstrr   replacer   utcnowinttotal_seconds)tsdtdeltasecss       E/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/curator.py_fmt_tsr      s    ##B' 
yyZZx||Z,LL&+Eu""$%Dbyu~d{"*U##e|$,u%%em_E"" z" 2ws   B1 1CCc                
   ddl m} ddlm} |j	                         }|j                         }|j                  dd      }|j                  d      }|j                  d      xs d}|j                  d	d      }|r|sd
n|rdnd}	t        d|	        t        d|        t        dt        |              d|v r6|j                         ^}
}t        d|
        |D ]  }t        d|         nt        d|        |j                  d      }|r-t        |      j                         rdnd}t        d| |        |j                         }|dz  dk(  r|dk\  r|dz   dn| d}t        d|        t        d|j                          d       t        d|j                          d       t        d|j                         rdnd  |j                         rdnd!        |j!                         }|st        d"       yg g g d#}g }|D ][  }|j                  d$d%      }|j#                  |g       j%                  |       |j                  d&      sH|j%                  |d'          ] t        d(t'        |       d)       d#D ]/  }|j                  |g       }t        d*|d+d,t'        |              1 |r)t        d-t'        |       d.d/j)                  |              t+        |j                  d%g       d0 1      d d2 }|rt        d3       |D ]  }t        |j                  d4            }t        d*|d'   d5d6|j                  d7d      d8d9|j                  d:d      d8d;|j                  d<d      d8d=|j                  d>d      d8d?|         |j                  d%g       }|rdt+        |d@ dAB      d d2 }|r|d   j                  d7      xs ddkD  rt        dC       |D ]  }t        |j                  d4            }t        d*|d'   d5d6|j                  d7d      d8d9|j                  d:d      d8d;|j                  d<d      d8d=|j                  d>d      d8d?|         t+        |dD 1      d d2 }|rt        dE       |D ]  }t        |j                  d4            }t        d*|d'   d5d6|j                  d7d      d8d9|j                  d:d      d8d;|j                  d<d      d8d=|j                  d>d      d8d?|         y)FNr   curatorskill_usagepausedFlast_run_atlast_run_summaryz(none)	run_countENABLEDPAUSEDDISABLED	curator: z  runs:           z  last run:       
z  last summary:   z                  last_report_path z
 (missing)z  last report:       dhz  interval:       every z  stale after:    zd unusedz  archive after:  z  consolidate:    onoffz$ (prune-only; LLM merge pass opt-in)z
no agent-created skills)activestalearchivedstater1   pinnednamez
agent-created skills: z total  10s z	
pinned (z): z, c                R    | j                  d      xs | j                  d      xs dS )Nlast_activity_at
created_atr+   getrs    r   <lambda>z_cmd_status.<locals>.<lambda>o   s#    aee./L1553FL"     key   z
least recently active (top 5):r;   40sz  activity=activity_count3dz  use=	use_countz  view=
view_countz
  patches=patch_countz  last_activity=c                V    | j                  d      xs d| j                  d      xs dfS NrG   r   r;   r+   r=   r?   s    r   rA   z_cmd_status.<locals>.<lambda>   *    155!127a?Q9R9XVXY rB   TrD   reversez
most active (top 5):c                V    | j                  d      xs d| j                  d      xs dfS rM   r=   r?   s    r   rA   z_cmd_status.<locals>.<lambda>   rN   rB   z
least active (top 5):)agentr   toolsr    
load_state
is_enabledr>   printr   
splitlinesr   existsget_interval_hoursget_stale_after_daysget_archive_after_daysget_consolidateagent_created_report
setdefaultappendlenjoinsorted)argsr   r    r4   enabledr!   last_runsummaryrunsstatus_linefirstrestline_reportsuffix_ih_interval_labelrowsby_stater5   r@   
state_namebucketr1   last
active_allmost_activeleast_actives                               r   _cmd_statusrx   '   s   ! E  "GYYx'Fyy'Hii*+7xG99[!$D V	 
 
Ik]
#$	tf
%&	wx01
23 w))+"5'*+ 	/D&tf-.	/ 	"7),-ii*+GG}++-<"7)F845

$
$
&C8q=SBY3"9+QuAY  
$_$5
67	w;;=>h
GH	w==?@
IJ	
W%<%<%>TEJ((*20V
W	Y
 ++-D)*rr:HF %UU7H-
J+221555?MM!F)$	% 
$SYKv
675 3
j"-:c"!CK=123 
3v;-s499V+<*=>?
 Xr"L 	q
F 01 		A155!345DQvYsO $EE"2A6r: ;uu[!,R0 1lA.r2 355226 7!!%(		" h+JY
 1	
 KN../?@EAJ*+  	quu%78963 (  !&6 :2> ?55a04 5EE,226 7 uu]A6r: ;%%)F,	 Y
 1 +,! 	quu%78963 (  !&6 :2> ?55a04 5EE,226 7 uu]A6r: ;%%)F,	 rB   c                   ddl m} |j                         st        d       yt	        t        | dd            }t	        t        | dd            }t	        t        | dd            xs | }t	        t        | d	d            rd
nd }|rt        d       nt        d       ||j                         st        d       dd}|j                  ||||      }|j                  di       }|ry|r t        d|j                  dd       d       nWt        d|j                  dd       d|j                  dd       d|j                  dd       d|j                  dd              |st        d       |r|rt        d       yt        d       y)Nr   r   zAcurator: disabled via config; enable with `curator.enabled: true`   dry_runF
backgroundsynchronousconsolidateTz7curator: running DRY-RUN (report only, no mutations)...zcurator: running review pass...u   curator: consolidation is off — running prune-only (deterministic stale/archive). Pass --consolidate or set `curator.consolidate: true` to enable the LLM merge pass.c                    t        |        y N)rV   )msgs    r   _on_summaryz_cmd_run.<locals>._on_summary   s	    c
rB   )
on_summaryr}   r{   r~   auto_transitionszauto (preview): checkedu9    candidate skill(s) — no transitions applied in dry-runzauto: checked=z stale=marked_stalez
 archived=r3   z reactivated=reactivateduF   llm pass running in background — check `hermes curator status` laterzzdry-run: no changes applied. Read the report with `hermes curator status` and run `hermes curator run` (no flag) to apply.zdry-run: no changes applied. When the report lands, read it with `hermes curator status` and run `hermes curator run` (no flag) to apply.)r   r   returnNone)	rR   r   rU   rV   boolgetattrr\   run_curator_reviewr>   )	rc   r   dryr|   r}   r~   r   resultautos	            r   _cmd_runr      s   QR
wtY.
/CgdL%89Jwt]E:;M:~K wt]EBC$K
GH/07#:#:#<H	
 ''	 ( F ::("-D"488Iq#9": ;8 8
  )Q!7 8 9.!45 6 HHZ34 5#xxq9:< VW
[ 	 [ rB   c                H    ddl m} |j                  d       t        d       y)Nr   r   Tzcurator: pausedrR   r   
set_pausedrV   rc   r   s     r   
_cmd_pauser      s    t	
rB   c                H    ddl m} |j                  d       t        d       y)Nr   r   Fzcurator: resumedr   r   s     r   _cmd_resumer      s    u	
rB   c                    ddl m} |j                  | j                        st	        d| j                   d       y|j                  | j                  d       t	        d| j                   d       y)	Nr   r   
curator: 'u`   ' is bundled or hub-installed — cannot pin (only agent-created skills participate in curation)rz   Tzcurator: pinned 'z ' (will bypass auto-transitions)rS   r    is_agent_createdskillrV   
set_pinnedrc   r    s     r   _cmd_pinr      si    !''

3 %B B	
 4::t,	djj\)I
JKrB   c                    ddl m} |j                  | j                        st	        d| j                   d       y|j                  | j                  d       t	        d| j                   d       y)	Nr   r   r   ue   ' is bundled or hub-installed — there's nothing to unpin (curator only tracks agent-created skills)rz   Fzcurator: unpinned ''r   r   s     r   
_cmd_unpinr     sh    !''

3 %R R	
 4::u-	

|1
-.rB   c                r    ddl m} |j                  | j                        \  }}t	        d|        |rdS dS )Nr   r   r(   rz   )rS   r    restore_skillr   rV   rc   r    okr   s       r   _cmd_restorer     s9    !''

3GB	IcU
1rB   c                   ddl m} |j                  | j                        j	                  d      r't        d| j                   d| j                   d       y|j                  | j                        \  }}t        d|        |rdS dS )	zManually archive an agent-created skill. Refuses if pinned.

    The auto-curator archives stale skills on its own schedule; this verb is
    for the user who wants to archive *now* without waiting for a run.
    r   r   r5   r   u7   ' is pinned — unpin first with `hermes curator unpin `rz   r(   )rS   r    
get_recordr   r>   rV   archive_skillr   s       r   _cmd_archiver     s     "djj)--h7 %%%)ZZL3	
 ''

3GB	IcU
1rB   c                   | j                  d      xs | j                  d      }|sy	 t        j                  t        |            }|j                   |j                  t        j                        }t        dt        j                  t        j                        |z
  j                        S # t        t
        f$ r Y yw xY w)u  Days since the skill's last activity (view / use / patch).

    Falls back to ``created_at`` so a skill that was authored but never used
    can still be pruned — otherwise never-touched skills would be immortal.
    Returns None only when both fields are missing or unparseable.
    r;   r<   Nr
   r   )r>   r   r   r   r   r   r   r   r   r   maxr   days)recordr   r   s      r   
_idle_daysr   ,  s     
&	'	C6::l+CB##CG, 
yyZZx||Z,q8<<-28899	 z" s   B- -B?>B?c                R   ddl m} t        | dd      }|dk  r t        d| dt        j
                         y	t        t        | d
d            }t        t        | dd            }g }|j                         D ]\  }|j                  d      r|j                  d      |j                  k(  r4t        |      }|||k  rG|j                  |d   |f       ^ |st        d| d       y|j                  d        t        dt        |       d| d       |D ]  \  }}t        d|dd| d        |rt        d       y|sE	 t        dt        |       d      j                         j!                         }	|	dvrt        d        yd}
g }|D ]4  \  }}|j'                  |      \  }}|r|
dz  }
"|j                  ||f       6 t        d!|
 d"t        |              |r't        d#       |D ]  \  }}t        d| d$|         yy# t"        t$        f$ r t        d       Y yw xY w)%a  Bulk-archive agent-created skills idle for >= N days.

    Pinned skills are exempt. Already-archived skills are skipped. Default
    ``--days 90`` matches a conservative read of the curator's own archive
    threshold; adjust with ``--days``. Use ``--dry-run`` to preview.
    r   r   r   Z   rz   z"curator: --days must be >= 1 (got ))file   r{   Fyesr5   r4   r6   z6curator: nothing to prune (no unpinned skills idle >= zd)c                    | d    S )Nrz    )cs    r   rA   z_cmd_prune.<locals>.<lambda>^  s    1Q4% rB   rC   r(   z skill(s) idle >= zd:r7   rF   z idle r-   u   
(dry run — no changes made)z	
Archive z skill(s)? [y/N] z
curator: aborted>   yr   zcurator: abortedz
curator: archived /z	failures:z: )rS   r    r   rV   sysstderrr   r]   r>   STATE_ARCHIVEDr   r_   sortr`   inputstriplowerEOFErrorKeyboardInterruptr   )rc   r    r   r{   skip_confirm
candidatesr@   idler6   replyr3   failures_r   r   s                  r   
_cmd_pruner   ?  s`    "4$Dax24&:L74E23GeU34LJ--/ -55?55>[777!}<4$;1V9d+,- FtfBOPOOO(	Ic*o&&8b
AB  ,
d4*F4&*+, /0	Js:&77HIJPPRXXZE $$%HH )a++D1CMHOOT3K() 
 
!C
O+<
=>k! 	&ID#BtfBse$%	&- +, 	&'	s   4H	 	H&%H&c                    ddl m} |j                         st        d       yt	        | dd      xs d}|j                  |      }|t        d	       yt        d
|j                          y)zuTake a manual snapshot of the skills tree. Same mechanism as the
    automatic pre-run snapshot, just user-initiated.r   curator_backupzacurator: backups are disabled via config (`curator.backup.enabled: false`); re-enable to snapshotrz   reasonNmanual)r   uE   curator: snapshot failed — check logs (backup disabled or IO error)z?curator: snapshot created at ~/.hermes/skills/.curator_backups/)rR   r   rU   rV   r   snapshot_skillsr6   )rc   r   r   snaps       r   _cmd_backupr     st     %$$&G	
 T8T*6hF)))8D|UV	KDII;
WXrB   c                   ddl m} t        | dd      rt        |j	                                yt        | dd      }|j                  |      }|b|j                         }|st        d       yt        d|rd	t        |      z   nd
 d       t        d       t        |j	                                y|j                  |      }t        d|j                          |rt        d|j                  dd              t        d|j                  dd              t        d|j                  dd              |j                  d      xs i }t        |t              rR|j                  d      r t        d|j                  dd       d       n!|j                  dd      }t        d| d       t        d       t        | dd      s8	 t        d       j                         j                         }|d"vrt        d#       y|j%                  |j                  $      \  }	}
}|	rt        d%|
        yt        d&|
        y# t         t"        f$ r t        d!       Y yw xY w)'a7  Restore the skills tree from a snapshot. Defaults to newest.

    ``--list`` prints available snapshots and exits. ``--id <stamp>`` picks
    a specific one. Without ``-y``, prompts for confirmation. A safety
    snapshot of the current tree is always taken first, so rollbacks are
    themselves undoable.
    r   r   listF	backup_idNzhcurator: no snapshots exist yet. Take one with `hermes curator backup` or wait for the next curator run.zcurator: no snapshot matching zid z
your query.z
Available:rz   zRollback target: z  reason:      r   ?z  created_at:  r<   z  skill files: skill_files	cron_jobs	backed_upz  cron jobs:   
jobs_countz. (will be restored for skill-link fields only)znot capturedz   cron jobs:   not in snapshot (r   a  
This will replace the current ~/.hermes/skills/ tree (a safety snapshot of the current state is taken first so this is undoable). Cron jobs that still exist will have their skills/skill fields restored from the snapshot; all other cron fields are left alone.r   zProceed? [y/N] z

cancelled>   r   r   	cancelled)r   r(   u   curator: rollback failed — )rR   r   r   rV   summarize_backups_resolve_backuplist_backupsrepr_read_manifestr6   r>   
isinstancedictr   r   r   r   r   rollback)rc   r   r   target_pathrp   manifestcronr   ansr   r   r   s               r   _cmd_rollbackr     sa    %tVU#n..01k40I 00;K**,L  0.754	?*\J!M ,.2245,,[9H	k../
01Xs ;<=>\3 ?@AB]C @ABC||K(.BdD!xx$%dhh|Q&?%@ AD E
 (N;8BC		L 4&	)*00288:C l"+((;3C3C(DJBQ		# 	)#
/0 +, 	- 	s   'H< <IIc                p    ddl m} |j                         }|st        d       y|D ]  }t        |        y)z#List archived (recoverable) skills.r   r   zcurator: no archived skills)rS   r    list_archived_skill_namesrV   )rc   r    namesr6   s       r   _cmd_list_archivedr     s;    !113E+, drB   c                6   ddl }ddlm} |j                         }t	        | dd      }|r$|D cg c]  }|j                  d      |k(  s| }}t	        | dd      }|dk(  r|j                  d 	       n.|d
k(  r|j                  d d       n|j                  d d       t	        | dd      rt        |j                  |dd             y|st        d       ydddd}|D ]:  }|j                  |j                  dd      d      dz   ||j                  dd      <   < t        dt        |       d|d    d|d    d|d    d	       t                t        ddd dd!d"dd#d$dd%d$dd&d'dd(d$d)       |D ]  }t        |j                  d*            }t        d|d   dd+ d d|j                  dd      d"d|j                  d,d      d-d|j                  d.d      d-d|j                  d/d      d0d|j                  d1d      d-d|         yc c}w )2u   Show usage telemetry for ALL skills, with provenance.

    Unlike `status` (curator-scoped to agent-created candidates), this lists
    every skill on disk — bundled built-ins and hub-installed included — so you
    can see how often each is actually used regardless of curation.
    r   Nr   
provenancer   activityr6   c                    | d   S )Nr6   r   r?   s    r   rA   z_cmd_usage.<locals>.<lambda>  s
    &	 rB   rC   recentc                ,    | j                  d      xs dS )Nr;   r+   r=   r?   s    r   rA   z_cmd_usage.<locals>.<lambda>   s    &8 9 ?R rB   TrO   c                &    | j                  dd      S )NrG   r   r=   r?   s    r   rA   z_cmd_usage.<locals>.<lambda>  s    &6 : rB   jsonFr   )indentensure_asciizcurator: no skills foundrR   bundledhubrR   rz   zskills: z total  (agent=z
  bundled=r   z  hub=r   r   r7   r   rF   origin8susez>4sviewpatchz>5sactz  last_activityr;   (   rI   z>4drJ   rK   z>5drG   )r   rS   r    usage_reportr   r>   r   rV   dumpsr`   r   )	rc   _jsonr    rp   prov_filterr@   sort_keycountsrt   s	            r   
_cmd_usager
    sn    !##%D$d3KFa155#6+#EFFtVZ0H6		)	*	X			?	N		:D	ItVU#ekk$quk=>() Qq1F _/5zz!%%g:VXY/Z]^/^quu\7+,_	
3t9+ /"*VI->,?vfUm_TU	W 
G	
WSMHR=+Rs|2gc]"U3K	P  

quu/0163B$Buu\7+B/ruu[!$S)uu\1%c*"uu]A&s+2uu%q)#.bf	


 W Gs   HHc                b     j                   fd        j                  d      }|j                  dd      }|j                  t               |j                  dd	      }|j	                  d
ddd       |j	                  dddd       |j	                  ddd       |j                  t
               |j                  dd      }|j	                  ddddd       |j	                  dddd        |j	                  d!d"dd#       |j	                  d$d%dd&       |j                  t               |j                  d'd(      }|j                  t               |j                  d)d*      }|j                  t               |j                  d+d,      }|j	                  d-d.       |j                  t               |j                  d/d0      }|j	                  d-d.       |j                  t               |j                  d1d2      }	|	j	                  d-d.       |	j                  t               |j                  d3d4      j                  t               |j                  d5d6      }
|
j	                  d-d.       |
j                  t               |j                  d7d8      }|j	                  d9t        d:d;<       |j	                  d=d>dd?       |j	                  d!d"dd@       |j                  t               |j                  dAdB      }|j	                  dCddDE       |j                  t                |j                  dFdG      }|j	                  dHddI       |j	                  dJdKddLM       |j	                  d=d>ddN       |j                  t"               y)OzAttach `curator` subcommands to *parent*.

    main.py calls this with the ArgumentParser returned by
    ``subparsers.add_parser("curator", ...)``.
    c                .    j                         dfd   S )Nr   rz   )
print_help)aparents    r   rA   zregister_cli.<locals>.<lambda>1  s    (9(9(;Q'?'B rB   )funccurator_command)deststatusz#Show curator status and skill stats)helpusagezJShow usage telemetry for ALL skills (built-in, hub, agent) with provenancez--sort)r   r   r6   r   zlSort order: activity (most-used first, default), recent (most-recently-active first), or name (alphabetical))choicesdefaultr  z--provenancer   NzOnly show skills of this originz--json
store_truez/Emit the full report as JSON instead of a table)actionr  runzTrigger a curator review nowz--syncz--synchronousr}   z@Wait for the LLM review pass to finish (default for manual runs))r  r  r  z--backgroundr|   zGStart the LLM review pass in a background thread and return immediatelyz	--dry-runr{   uk   Report only — no state changes, no archives, no consolidation (use this to preview what curator would do)z--consolidater~   zForce the LLM umbrella-building consolidation pass on for this run, overriding the config default (off). Without this flag the run is prune-only unless `curator.consolidate: true` is set.pausezPause the curator until resumedresumezResume a paused curatorpinz4Pin a skill so the curator never auto-transitions itr   z
Skill nameunpinzUnpin a skillrestorezRestore an archived skillzlist-archivedzList archived skillsarchivezBManually archive a skill (move to .archive/, excluded from prompt)prunezABulk-archive agent-created skills idle for >= N days (default 90)z--daysr   z5Archive skills idle for at least N days (default: 90))typer  r  z-yz--yeszSkip the confirmation promptz,Show what would be archived without doing itbackupzoTake a manual tar.gz snapshot of ~/.hermes/skills/ (curator also does this automatically before every real run)z--reasonz;Free-text label stored in manifest.json (default: 'manual'))r  r  r   zJRestore ~/.hermes/skills/ from a curator snapshot (defaults to the newest)z--listz3List available snapshots and exit without restoringz--idr   z6Snapshot id to restore (see `--list`); default: newest)r  r  r  zSkip confirmation prompt)set_defaultsadd_subparsers
add_parserrx   add_argumentr
  r   r   r   r   r   r   r   r   r   r   r   r   )r  subsp_statusp_usagep_runp_pausep_resumep_pinp_unpin	p_restore	p_archivep_prunep_backup
p_rollbacks   `             r   register_clir5  +  s)    BC  &7 8Dx.STH{+ooY  G 8*D  
  ;T.   >   j)OOE(FOGE	/lO   
\,V   
)L;  
 
mLL   
H%oog,MoNGj)x.GHH{+OOE(^O_E	w\2	H%oogOo<G|4j)	0KLI76-OOO*@OA	-	.Q   I 76-ooP  G sBD   gl+   )L;   j)L  H
 DJ   {+( ! J
 B   [$E   gl'   /rB   c                    t        j                  d      }t        |       |j                  |       }t	        |dd      }||j                          yt         ||      xs d      S )z>Standalone entry (also usable by hermes_cli.main fallthrough).zhermes curator)progr  Nr   )argparseArgumentParserr5  
parse_argsr   r  r   )argvparserrc   fns       r   cli_mainr>    s`    $$*:;FT"D	vt	$B	zr$x}1rB   __main__)r   zOptional[str]r   r   )r   r   )r   r   r   zOptional[int])r  zargparse.ArgumentParserr   r   r   )__doc__
__future__r   r8  r   r   r   pathlibr   typingr   r   rx   r   r   r   r   r   r   r   r   r   r   r   r   r
  r5  r>  __name__exitr   rB   r   <module>rF     s    #  
 '  #(BJ<~

$:&AH&FR	9@0D	 zCHHXZ rB   