
    0biK                        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
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                       dS dS )&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)OptionaltsOptional[str]returnstrc                   | sdS 	 t          j        |           }n&# t          t          f$ r t	          |           cY S w xY w|j         |                    t          j                  }t          j	        t          j                  |z
  }t          |                                          }|dk     r| dS |dk     r|dz   dS |dk     r|dz   dS |dz   d	S )
Nnevertzinfo<   zs agoi  zm agoiQ zh agozd ago)r   fromisoformat	TypeError
ValueErrorr
   r   replacer   utcnowinttotal_seconds)r   dtdeltasecss       0/DATA/AppData/hermes-agent/hermes_cli/curator.py_fmt_tsr      s    w#B''z"   2ww	yZZx|Z,,L&&+Eu""$$%%Dbyy~~~d{{"*####e||$,%%%%em""""s     >>r   c                ,   ddl m} ddlm} |                                }|                                }|                    dd          }|                    d          }|                    d          pd}|                    d	d          }|r|sd
n|rdnd}	t          d|	            t          d|            t          dt          |                      t          d|            |                    d          }
|
rt          d|
            |	                                }|dz  dk    r|dk    r|dz   dn| d}t          d|            t          d|
                                 d           t          d|                                 d           |                                }|st          d           dS g g g d}g }|D ]q}|                    dd          }|                    |g                               |           |                    d          r|                    |d                    rt          d t          |           d!           dD ];}|                    |g           }t          d"|d#d$t          |                      <|r5t          d%t          |           d&d'                    |                      t#          |                    dg           d( )          d d*         }|rt          d+           |D ]}t          |                    d,                    }t          d"|d         d-d.|                    d/d          d0d1|                    d2d          d0d3|                    d4d          d0d5|                    d6d          d0d7|            |                    dg           }|rt#          |d8 d9:          d d*         }|r|d                             d/          pddk    rt          d;           |D ]}t          |                    d,                    }t          d"|d         d-d.|                    d/d          d0d1|                    d2d          d0d3|                    d4d          d0d5|                    d6d          d0d7|            t#          |d< )          d d*         }|rt          d=           |D ]}t          |                    d,                    }t          d"|d         d-d.|                    d/d          d0d1|                    d2d          d0d3|                    d4d          d0d5|                    d6d          d0d7|            dS )>Nr   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:   last_report_pathz  last report:       dhz  interval:       every z  stale after:    zd unusedz  archive after:  z
no agent-created skills)activestalearchivedstater.   pinnednamez
agent-created skills: z total  10s z	
pinned (z): z, c                Z    |                      d          p|                      d          pdS )Nlast_activity_at
created_at getrs    r   <lambda>z_cmd_status.<locals>.<lambda>`   s)    aee.//L1553F3FL"     key   z
least recently active (top 5):r8   40sz  activity=activity_count3dz  use=	use_countz  view=
view_countz
  patches=patch_countz  last_activity=c                ^    |                      d          pd|                      d          pdfS NrE   r   r8   r:   r;   r=   s    r   r?   z_cmd_status.<locals>.<lambda>y   0    155!1227a?Q9R9R9XVXY r@   T)rB   reversez
most active (top 5):c                ^    |                      d          pd|                      d          pdfS rK   r;   r=   s    r   r?   z_cmd_status.<locals>.<lambda>   rL   r@   z
least active (top 5):)agentr   toolsr!   
load_state
is_enabledr<   printr   get_interval_hoursget_stale_after_daysget_archive_after_daysagent_created_report
setdefaultappendlenjoinsorted)argsr   r!   r1   enabledr"   last_runsummaryrunsstatus_line_report_ih_interval_labelrowsby_stater2   r>   
state_namebucketr.   last
active_allmost_activeleast_actives                           r   _cmd_statusrn   &   s'   !!!!!!  E  ""GYYx''Fyy''Hii*++7xG99[!$$D  	V 			 	 
 

#k
#
#$$$	
%t
%
%&&&	
2wx00
2
2333	
(w
(
()))ii*++G .,7,,---

$
$
&
&C8q==SBYY3"9YYY  

6_
6
6777	
Gw;;==
G
G
GHHH	
Iw==??
I
I
IJJJ++--D )***qrr::HF % %UU7H--
J++22155555?? 	%MM!F)$$$	
6SYY
6
6
67775 3 3
j"--1:111CKK112222 @>3v;;>>499V+<+<>>???
 Xr""LL   	qb
F  0111 		 		A155!34455D(QvY& ( (EE"2A66=( (uu[!,,3( ( lA..5( ( 55229	( (
 "&( (    h++J "YY
 
 
 1"	
  	KN../?@@EAJJ*+++  	 	quu%78899,6* , , !&6 : :A, ,55a007, , EE,229, ,  !uu]A66=	, ,
 &*, ,    YY
 
 
 1"  	+,,,! 	 	quu%78899,6* , , !&6 : :A, ,55a007, , EE,229, ,  !uu]A66=	, ,
 &*, ,    1r@   c                   ddl m} |                                st          d           dS t	          t          | dd                    }|rt          d           nt          d           dd}|                    |t	          | j                  |          }|                    di           }|r|r(t          d|                    dd           d           nkt          d|                    dd           d|                    dd           d|                    dd           d|                    dd                      | j        st          d           |rt          d           dS )Nr   r   zAcurator: disabled via config; enable with `curator.enabled: true`   dry_runFz7curator: running DRY-RUN (report only, no mutations)...zcurator: running review pass...msgr
   r	   Nonec                $    t          |            d S N)rS   )rr   s    r   _on_summaryz_cmd_run.<locals>._on_summary   s    c




r@   )
on_summarysynchronousrq   auto_transitionszauto (preview): checkedu9    candidate skill(s) — no transitions applied in dry-runzauto: checked=z stale=marked_stalez
 archived=r0   z reactivated=reactivateduF   llm pass running in background — check `hermes curator status` laterzdry-run: no changes applied. When the report lands, read it with `hermes curator status` and run `hermes curator run` (no flag) to apply.)rr   r
   r	   rs   )	rO   r   rR   rS   boolgetattrrun_curator_reviewrx   r<   )r]   r   dryrv   resultautos         r   _cmd_runr      s    QRRRq
wtY..
/
/C
 1GHHHH/000    '')** (  F
 ::("--D  	8488Iq#9#9 8 8 8   
 <)Q!7!7 < <.!44< < HHZ33< <  $xxq99< <    XVWWW
 
W	
 	
 	
 1r@   c                Z    ddl m} |                    d           t          d           dS )Nr   r   Tzcurator: pausedrO   r   
set_pausedrS   r]   r   s     r   
_cmd_pauser      s<    t	
1r@   c                Z    ddl m} |                    d           t          d           dS )Nr   r   Fzcurator: resumedr   r   s     r   _cmd_resumer      s<    u	
1r@   c                    ddl m} |                    | j                  st	          d| j         d           dS |                    | j        d           t	          d| j         d           dS )	Nr   r    
curator: 'u`   ' is bundled or hub-installed — cannot pin (only agent-created skills participate in curation)rp   Tzcurator: pinned 'z ' (will bypass auto-transitions)rP   r!   is_agent_createdskillrS   
set_pinnedr]   r!   s     r   _cmd_pinr      s    !!!!!!''
33 B B B B	
 	
 	
 q4:t,,,	
Jdj
J
J
JKKK1r@   c                    ddl m} |                    | j                  st	          d| j         d           dS |                    | j        d           t	          d| j         d           dS )	Nr   r    r   ue   ' is bundled or hub-installed — there's nothing to unpin (curator only tracks agent-created skills)rp   Fzcurator: unpinned ''r   r   s     r   
_cmd_unpinr      s    !!!!!!''
33 R R R R	
 	
 	
 q4:u---	
-

-
-
-...1r@   c                x    ddl m} |                    | j                  \  }}t	          d|            |rdndS )Nr   r    r)   rp   )rP   r!   restore_skillr   rS   r]   r!   okrr   s       r   _cmd_restorer      sR    !!!!!!''
33GB	
c

>11r@   c                   ddl m} |                    | j                                      d          r"t          d| j         d| j         d           dS |                    | j                  \  }}t          d|            |rdn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    r2   r   u7   ' is pinned — unpin first with `hermes curator unpin `rp   r)   )rP   r!   
get_recordr   r<   rS   archive_skillr   s       r   _cmd_archiver      s     "!!!!!dj))--h77 3 3 3%)Z3 3 3	
 	
 	
 q''
33GB	
c

>11r@   recorddictOptional[int]c                   |                      d          p|                      d          }|sdS 	 t          j        t          |                    }n# t          t
          f$ r Y dS w xY w|j         |                    t          j	                  }t          dt          j        t          j	                  |z
  j                  S )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.
    r8   r9   Nr   r   )r<   r   r   r
   r   r   r   r   r   r   maxr   days)r   r   r   s      r   
_idle_daysr   
  s     
&	'	'	C6::l+C+CB t#CGG,,z"   tt	yZZx|Z,,q8<--28999s   !A A'&A'c                8   ddl m} t          | dd          }|dk     r!t          d| dt          j                   d	S t          t          | d
d                    }t          t          | dd                    }g }|                                D ]l}|                    d          r|                    d          |j	        k    r7t          |          }|||k     rO|                    |d         |f           m|st          d| d           dS |                    d            t          dt          |           d| d           |D ]\  }}t          d|dd| d           |rt          d           dS |s	 t          dt          |           d                                                                          }	n'# t"          t$          f$ r t          d           Y dS w xY w|	d vrt          d!           dS d}
g }|D ]<\  }}|                    |          \  }}|r|
dz  }
%|                    ||f           =t          d"|
 d#t          |                      |r.t          d$           |D ]\  }}t          d| d%|            dS dS )&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   rp   z"curator: --days must be >= 1 (got ))file   rq   Fyesr2   r1   Nr3   z6curator: nothing to prune (no unpinned skills idle >= zd)c                    | d          S )Nrp    )cs    r   r?   z_cmd_prune.<locals>.<lambda><  s    1Q4% r@   rA   r)   z skill(s) idle >= zd:r4   rD   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: )rP   r!   r~   rS   sysstderrr}   rW   r<   STATE_ARCHIVEDr   rY   sortrZ   inputstriplowerEOFErrorKeyboardInterruptr   )r]   r!   r   rq   skip_confirm
candidatesr>   idler3   replyr0   failures_r   rr   s                  r   
_cmd_pruner     sG    "!!!!!4$$Daxx:4:::LLLLq74E2233GeU3344LJ--// - -55?? 	55>>[777!}}<4$;;1V9d+,,,, OtOOOPPPqOOO(((	
Ac*oo
A
A
A
A
ABBB  , ,
d*4***4***++++ /000q 	Is:IIIJJPPRRXXZZEE+, 	 	 	&'''11	 $$$%%%1HH ) )a++D11C 	)MHHOOT3K((((	
=
=
=C
OO
=
=>>> k! 	& 	&ID#$t$$s$$%%%%q1s   AG	 	 G-,G-c                   ddl m} |                                st          d           dS t	          | dd          pd}|                    |          }|t          d	           dS t          d
|j                    dS )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 snapshotrp   reasonNmanual)r   uE   curator: snapshot failed — check logs (backup disabled or IO error)z?curator: snapshot created at ~/.hermes/skills/.curator_backups/)rO   r   rR   rS   r~   snapshot_skillsr3   )r]   r   r   snaps       r   _cmd_backupr   a  s     %$$$$$$$&& G	
 	
 	
 qT8T**6hF)))88D|UVVVq	
WDI
W
WXXX1r@   c                   ddl m} t          | dd          r#t          |                                           dS t          | dd          }|                    |          }||                                }|st          d           nWt          d|rd	t          |          z   nd
 d           t          d           t          |                                           dS |                    |          }t          d|j	                    |rt          d|
                    dd                      t          d|
                    dd                      t          d|
                    dd                      |
                    d          pi }t          |t                    rf|
                    d          r(t          d|
                    dd           d           n)|
                    dd          }t          d| d           t          d           t          | dd          sq	 t          d                                                                           }n'# t           t"          f$ r t          d!           Y dS w xY w|d"vrt          d#           dS |                    |j	        $          \  }	}
}|	rt          d%|
            dS t          d&|
            dS )'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:rp   zRollback target: z  reason:      r   ?z  created_at:  r9   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

cancelledr   	cancelled)r   r)   u   curator: rollback failed — )rO   r   r~   rS   summarize_backups_resolve_backuplist_backupsrepr_read_manifestr3   r<   
isinstancer   r   r   r   r   r   rollback)r]   r   r   target_pathrf   manifestcronr   ansr   rr   r   s               r   _cmd_rollbackr   t  s}    %$$$$$tVU## n..00111qk400I 00;;K**,, 	6L   
 M.7I54	??**\M M M   ,.2244555q,,[99H	
0k.
0
0111 D=Xs ; ;==>>>A\3 ? ?AABBBB]C @ @BBCCC||K((.BdD!! 	Dxx$$ DEdhh|Q&?&? E E E   
 (N;;BBBBCCC		L   4&& 	)**002288::CC+, 	 	 	-   11	 l""+1((;3C(DDJBQ	 #   q	
/#
/
/0001s   63I* * JJparentargparse.ArgumentParserrs   c                4                           fd                                d          }|                    dd          }|                     t                     |                    dd	          }|                    d
dddd           |                    dddd           |                     t
                     |                    dd          }|                     t                     |                    dd          }|                     t                     |                    dd          }|                    dd           |                     t                     |                    dd          }|                    dd           |                     t                     |                    dd          }|                    dd           |                     t                     |                    dd           }	|	                    dd           |	                     t                     |                    d!d"          }
|
                    d#t          d$d%&           |
                    d'd(dd)*           |
                    dddd+           |
                     t                     |                    d,d-          }|                    d.d/d01           |                     t                     |                    d2d3          }|                    d4dd5*           |                    d6d7d/d89           |                    d'd(dd:*           |                     t                     d/S );zAttach `curator` subcommands to *parent*.

    main.py calls this with the ArgumentParser returned by
    ``subparsers.add_parser("curator", ...)``.
    c                <                                     dfd         S )Nr   rp   )
print_help)ar   s    r   r?   zregister_cli.<locals>.<lambda>  s    (9(9(;(;Q'?'B r@   )funccurator_command)deststatusz#Show curator status and skill stats)helprunzTrigger a curator review nowz--syncz--synchronousrx   
store_truezCWait for the LLM review pass to finish (default: background thread))r   actionr   z	--dry-runrq   uk   Report only — no state changes, no archives, no consolidation (use this to preview what curator would do)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 skill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))typedefaultr   z-yz--yeszSkip the confirmation prompt)r   r   z,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--reasonNz;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_parserrn   add_argumentr   r   r   r   r   r   r   r   r   r   r   )r   subsp_statusp_runp_pausep_resumep_pinp_unpin	p_restore	p_archivep_prunep_backup
p_rollbacks   `            r   register_clir	    s3    BBBBCCC  &7 88Dx.STTH{+++OOE(FOGGE	/lR     
)L;    
 
H%%%oog,MoNNGj)))x.GHHH{+++OOE(^O__E	w\222	H%%%oogOo<<G|444j)))	0KLLI7666---Q    I 7666---ooP   G sBD     gl+     )L;     j)))L   H
 DJ     {+++( !  J
 B     [$E     gl'     /////r@   c                    t          j        d          }t          |           |                    |           }t	          |dd          }||                                 dS t           ||          pd          S )z>Standalone entry (also usable by hermes_cli.main fallthrough).zhermes curator)progr   Nr   )argparseArgumentParserr	  
parse_argsr~   r   r   )argvparserr]   fns       r   cli_mainr  #  s~    $*:;;;FT""D	vt	$	$B	zqrr$xx}1r@   __main__)r   r   r	   r
   )r	   r   )r   r   r	   r   )r   r   r	   rs   ru   )__doc__
__future__r   r  r   r   r   typingr   r   rn   r   r   r   r   r   r   r   r   r   r   r   r	  r  __name__exitr   r@   r   <module>r     s    # " " " " "  



 ' ' ' ' ' ' ' '      # # # #(t t t tn) ) ) )X      
 
 
 

 
 
 
      $: : : :&A A A AH   &F F F FZ_0 _0 _0 _0D	 	 	 	 	 zCHXXZZ r@   