
    epj	                       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ZddlZddl	Z
ddlmZ ddlmZ ddlmZ ddlmZ ddlmZ  ej        e          Zd Zd	ZddZdZd
edz  fdZd
eeedz  edz  f         fdZdZ d
eeedz  edz  f         fdZ!ddedede"d
efdZ#deded
efdZ$ded
efdZ%ded
eedf         fdZ&ddZ'dd Z(dd!Z)d"ed
efd#Z*d$e+e         d
dfd%Z,d&ed'ed
dfd(Z-d&ed'ed
efd)Z.dd*Z/dd+Z0d, Z1d-e+e         d.ed
ee         fd/Z2d-e+e         d.ed0ed
ee         fd1Z3	 dd0ed2ee         d
dfd3Z4d4ed
efd5Z5	 	 dd-e+e         d.ed0ed7ed
ef
d8Z6d-e+e         d.ed0ed
efd9Z7h d:Z8d;Z9d<Z:d-e+e         d.ed
ee         fd=Z;d>ee         d
efd?Z<d-e+e         d.ed
efd@Z=d-e+e         d.ed
efdAZ>d-e+e         d.edBedCed
e?f
dDZ@d
efdEZAdF ZBd-e+e         d.ed
efdGZCd-e+e         d.ed
dfdHZDdI ZEdJedKed
dfdLZFddMZGddNZHdOe+ee?ef                  dPed
efdQZIddRdSe+e         dTeJeef         dz  d
dfdUZK	 dddRdSe+e         dz  dTeJeef         dz  d
efdVZLddWZMd
efdXZNddRdSe+e         dTeJeef         dz  d
dfdYZOdZe+e         d
edz  fd[ZPd
eedf         fd\ZQd
edz  fd]ZRd^ed
efd_ZSd^ed
dfd`ZTd
e+e         fdaZUdbed
dfdcZVdddddeZWdd6dgdhediefdjZXddkZYddlZZdmZ[dnZ\d
efdoZ]d
ee         fdpZ^dqedre?d
efdsZ_dte+e?         de"d
e`e?         fduZad
eeef         fdvZbddwdxe`e?         dz  d
e+ee?eef                  fdyZcdOe+ee?eef                  d
efdzZddte+e?         d
e+e?         fd{ZedOe+ee?eef                  d
e+e?         dz  fd|Zfd
eJdz  fd}Zgdd~Zhded
dfdZide+d
dfdZjddZkdeJdz  d
dfdZld Zmd ZndefdZoddZp	 dde+dede"d
efdZq	 dde+dede"d
e"fdZrdS )u  Hermes update pipeline — extracted from ``hermes_cli/main.py``.

Mechanical move (main.py decomposition): ``_cmd_update_impl``, ``_cmd_update_check``
and every module-level helper used only by the update path, plus the update-only
constants they read. Function bodies are lifted verbatim; the only mechanical
change is that references to helpers/constants that STAY in ``hermes_cli.main``
(and to moved-but-test-patched siblings) are routed through ``_m()`` — a lazy
``hermes_cli.main`` reference — so existing call sites and test monkeypatches
that target ``hermes_cli.main.<name>`` (``PROJECT_ROOT``, ``_is_windows``,
``_run_pre_update_backup``, ...) keep working unchanged. ``main.py`` re-imports
every public-ish name from here (``# noqa: F401``) so the argparse wiring and
the test-patch surface still resolve on ``hermes_cli.main``.

Three self-contained closures nested inside ``_cmd_update_impl``
(``_print_items``, ``_wait_for_service_active``, ``_service_restart_sec``) were
hoisted to module level; they capture no enclosing state (verified via
``symtable``). ``_restart_one_systemd_gateway_unit``, ``_resolve_manage_cmd``
and ``_on_unit_timeout`` DO capture enclosing locals and stay nested,
byte-identical.

Imports are one-way: ``hermes_cli.main`` imports this module, never the reverse
at import time (``_m()`` resolves lazily at call time, when main.py is fully
loaded, so there is no import cycle).
    N)datetime)Path)Optionalget_hermes_home)venv_python_pathc                      ddl m}  | S )a$  Lazy ``hermes_cli.main`` reference.

    Lets callers keep patching ``hermes_cli.main.<helper>`` (the historical
    test surface) and have those patches reach this code path, and defers the
    import so ``hermes_cli.main`` -> ``hermes_cli.update_cmd`` stays one-way
    at import time.
    r   main)
hermes_clir   r
   s    ;/home/thesage/.hermes/hermes-agent/hermes_cli/update_cmd.py_mr   -   s      K    )hermes_constantsztools.environments.localztools.lazy_depsreturnc                     	 ddl } |                                  t          D ]y}t                      j        j                            |          }|0	 |                     |           G# t          $ r&}t          
                    d||           Y d}~rd}~ww xY wdS # t          $ r&}t          
                    d|           Y d}~dS d}~ww xY w)a  Reload update-sensitive modules after the checkout changes in-place.

    ``hermes update`` keeps running in the pre-pull Python process. After a
    large update, modules already present in ``sys.modules`` can still expose
    old symbols even though their source files on disk are new. Refresh the
    small module set used by lazy-backend refresh before that step imports
    newly-updated code paths.
    r   Nz&Could not reload updated module %s: %sz,Could not refresh update runtime modules: %s)	importlibinvalidate_caches_UPDATE_RUNTIME_RELOAD_MODULESr   sysmodulesgetreload	Exceptionloggerdebug)r   module_namemoduleexcs       r   _reload_updated_runtime_modulesr    @   s   J##%%%9 	Y 	YKTTX%))+66F~Y  (((( Y Y YE{TWXXXXXXXXY	Y 	Y  J J JCSIIIIIIIIIJsB   AB A('B (
B2BB BB 
C'CC)	zhermes_cli/main.pyzhermes_cli/config.pyzhermes_cli/__init__.pyzhermes_cli/web_server.pyzcli.pyzrun_agent.pyzmodel_tools.pyztoolsets.pyzhermes_constants.pyc           	          	 t          j        | ddgz   |ddddd          }|j                                        pdS # t           j        t
          f$ r Y dS w xY w)z=Return the current HEAD SHA, or None if it can't be resolved.	rev-parseHEADTutf-8replacecwdcapture_outputtextencodingerrorscheckN)
subprocessrunstdoutstripCalledProcessErrorOSErrorgit_cmdr'   results      r   _capture_head_shar6   j   s    
{F++	
 
 
 }""$$,,)73   tts   := AAc                    ddl }ddl}t          |           } |                    d          5 }t          D ]}| |z  }|                                st          |          |                    dd          dz   z  }	 |                    t          |          t          |          d	           y# |j	        $ r7}d
t          |          t          |          fcY d}~c cddd           S d}~wt          $ r-}d
t          |          d| fcY d}~c cddd           S d}~ww xY w	 ddd           n# 1 swxY w Y   dS )u  Compile each file in ``_UPDATE_CRITICAL_FILES`` to catch SyntaxErrors.

    These are the files imported on every ``hermes`` startup; if any of them
    has a syntax error (orphan merge-conflict markers, bad ref to a name
    that no longer exists, etc.) the CLI can't bootstrap at all. We validate
    them after a successful ``git pull`` so we can auto-roll-back instead of
    leaving the user with a bricked install.

    The compiled ``.pyc`` is written to a temp directory rather than the
    source tree's ``__pycache__/`` so we don't race with concurrent test
    workers that walk the same dir, and so we don't leave a stale pyc
    behind in production if the next interpreter run picks a different
    Python version. The pyc is discarded on function return either way —
    we only care about the compile-or-not signal.

    Returns ``(ok, failing_path, error_message)``. ``ok=True`` means every
    file parsed cleanly.
    r   Nzhermes-syntax-check-prefix/__cT)cfiledoraiseFzcould not read: TNN)
py_compiletempfiler   TemporaryDirectory_UPDATE_CRITICAL_FILESexistsr%   compilestrPyCompileErrorr2   )rootr@   rA   tmpdirrelpathpathr=   r   s           r   _validate_critical_files_syntaxrL   x   s'   & OOO::D		$	$,B	$	C	C Bv- 	B 	BG'>D;;==   LLGOOC$>$>$DEEB""3t99CJJ"MMMM, 2 2 2c$iiS11111111B B B B B B B B  B B Bc$ii)AC)A)AAAAAAAAAB B B B B B B BB	BB B B B B B B B B B B B B B B  sZ   AD5<2B/.D5/
D%9C+D%D5+D%8D D%D5 D%%D55D9<D9)hermes_cli.main	run_agentmodel_toolstoolsetsc           	         ddl m} dt          dt          t	          |                    d}	 t
          j        }	 t          t          |           dz  t                      
                                          }|                                rt          |          }n# t          $ r Y nw xY wt          j        |d|gt          |           d	d	d
dd          }n# t           t          j        f$ r Y dS w xY w|j        dk    rm|j        pd                    dd          }|d                                         pd}t-          |          dk    r|d                                         nd}d||fS dS )u  Import each module in ``_UPDATE_CRITICAL_MODULES`` in a subprocess.

    ``_validate_critical_files_syntax`` only *parses* files, so it cannot see
    cross-module breakage: a partially-updated tree where ``agent/`` is new but
    ``tools/`` is old parses perfectly and still dies at startup with
    ``ImportError: cannot import name 'TODO_INJECTION_HEADER' from
    'tools.todo_tool'``. Every file is valid Python; the *combination* is not.

    That skew is reachable on the Windows ZIP-update path, whose copy loop
    walks top-level entries in ``os.listdir`` order and replaces each one
    independently — ``agent/`` lands long before ``tools/``, so a failure or
    interruption between them leaves exactly that mismatch on disk.

    Runs in a subprocess because importing these modules into the running
    updater would pollute ``sys.modules`` and execute import-time side effects
    against the half-updated tree. Costs ~0.4s.

    Uses the project venv's interpreter when there is one (matching
    ``_venv_core_imports_healthy``): ``hermes update`` can be driven by a
    different Python than the install's own, and probing the wrong
    interpreter would test a tree the user never runs.

    Returns ``(ok, failing_module, error_message)``.
    r   )FIRST_PARTY_MODULE_ROOTSz"import importlib, sys
for name in z:
    try:
        importlib.import_module(name)
    except ModuleNotFoundError as exc:
        missing = (getattr(exc, 'name', '') or '').split('.')[0]
        if missing in a   or missing.startswith('hermes_'):
            sys.stdout.write(name + '\n' + str(exc))
            raise SystemExit(3)
    except ImportError as exc:
        sys.stdout.write(name + '\n' + str(exc))
        raise SystemExit(3)
    except Exception:
        pass
raise SystemExit(0)
venvwindows-cTr$   r%   x   )r'   r(   r)   r*   r+   timeoutr?       
   unknownF)r   rR   _UPDATE_CRITICAL_MODULEStuplesortedr   
executabler   r   r   _is_windowsrD   rF   r   r-   r.   r2   SubprocessError
returncoder/   splitr0   len)	rH   rR   probeinterpretervenv_pythonr5   partsr   details	            r   !_validate_critical_modules_importrl      s   2 :999999, $##U62J+K+K%L%L%L%L'	N 
, n	*T

V#RTT-=-=-?-?  K !!## /!+.. 	 	 	D	$&D		
 
 
 Z/0        A$"++D!44q!!.Y%(ZZ!^^q!!!ff$$s5   C A"B  C  
B-*C ,B--.C C65C6rZ        r@prompt_textdefaultrX   c                    ddl }ddl}ddlm}  |            }|dz  }|dz  }|                    d           | |t          |                                          d}	|                    d	          }
|
                    |	                    |	          d
           |

                    |           t          j                    |z   }t          j                    |k     r|                                rr	 |                    d
                                          }|                    d           |                    d           |r|n|S # t           t"          f$ r Y nw xY wt          j        d           t          j                    |k     |                    d           |                    d           t'          dt)          |           d|d           |S )aw  File-based IPC prompt for gateway mode.

    Writes a prompt marker file so the gateway can forward the question to the
    user, then polls for a response file.  Falls back to *default* on timeout.

    Used by ``hermes update --gateway`` so interactive prompts (stash restore,
    config migration) are forwarded to the messenger instead of being silently
    skipped.
    r   Nr   z.update_prompt.jsonz.update_responseT)
missing_ok)promptro   idz.tmpr$   r*         ?z  (no response after zs, using default: ))jsonuuidr   r   unlinkrF   uuid4with_suffix
write_textdumpsr%   _time	monotonicrD   	read_textr0   r2   
ValueErrorsleepprintint)rn   ro   rX   _json_uuidr   homeprompt_pathresponse_pathpayloadtmpdeadlineanswers                r   _gateway_promptr      s     000000?D..K--M D))) %++--   G
 
!
!&
)
)CNN5;;w'''N:::KK   7*H
/

h
&
&!! 	&00'0BBHHJJ$$$555""d"333!'4vvW4Z(   C /

h
&
& $'''D)))	
N#g,,
N
N'
N
N
NOOONs   4AE E"!E"bin_dirnamec                 T     t           fd|| d| d| dfD                       S )z?True when an npm bin shim for *name* exists (POSIX or Windows).c              3   F   K   | ]}|z                                   V  d S NrD   ).0	candidater   s     r   	<genexpr>z"_npm_bin_exists.<locals>.<genexpr>/  sH         
9	$$&&     r   z.cmdz.ps1z.exeany)r   r   s   ` r   _npm_bin_existsr   -  sV        4$}}}mmmL     r   rootsc                      d d | D             D             t                    ot          fddD                       S )zTrue when ``tsc`` and ``vite`` shims are reachable from any of *roots*.

    Callers must pass every root the build would search; checking only one
    reports a healthy tree as broken.
    c                 :    g | ]}|                                 |S  is_dir)r   r   s     r   
<listcomp>z._web_build_toolchain_ready.<locals>.<listcomp>:  s8       >>  r   c              3   &   K   | ]}|d z  dz  V  dS )node_modulesz.binNr   )r   rH   s     r   r   z-_web_build_toolchain_ready.<locals>.<genexpr><  s,      II4~-6IIIIIIr   c              3   P   K   | ]t          fd D                       V   dS )c              3   8   K   | ]}t          |          V  d S r   )r   )r   r   tools     r   r   z7_web_build_toolchain_ready.<locals>.<genexpr>.<genexpr>@  s-      CCwOGT**CCCCCCr   Nr   )r   r   bin_dirss    @r   r   z-_web_build_toolchain_ready.<locals>.<genexpr>?  sU       " " 	CCCC(CCCCC" " " " " "r   )tscvite)boolall)r   r   s    @r   _web_build_toolchain_readyr   4  sv     II5III  H
 >> c " " " "#" " "   r   web_dir.c                     | | j         fS )a%  Roots whose ``node_modules/.bin`` can satisfy the web build.

    ``npm run build`` prepends ``node_modules/.bin`` for the package and each
    of its ancestors, so shims hoisted to the workspace root and shims nested
    under a package that owns its lockfile (#42973) are equally valid.
    )parent)r   s    r   _web_toolchain_rootsr   D  s     W^$$r   c                     	 ddl m}  n# t          $ r Y dS w xY w	 |                                 sdS |                                 }n# t          $ r Y dS w xY w|                    d          rdS 	 |                                 }n# t          $ r d}Y nw xY wt          d|dz            }t                       t          d           t          d	| d
           t          d           t          d           t          d           dS )a  Print a short heads-up about the skill curator after `hermes update`.

    Only fires when the curator is enabled AND has no recorded run yet, which
    is exactly the window where the gateway ticker used to fire Curator
    against a fresh skill library immediately after an update. We defer the
    first real pass by one ``interval_hours``; this notice tells the user how
    to preview or disable before then. Silent on steady state.
    r   curatorNlast_run_at   r\      u   ℹ Skill curatorzC  Background skill maintenance is enabled. First pass is deferred ~zwd after installation; only agent-created skills are in scope and nothing is ever auto-deleted (archive is recoverable).z,  Preview now:  hermes curator run --dry-runz$  Pause it:     hermes curator pausezV  Docs:         https://hermes-agent.nousresearch.com/docs/user-guide/features/curator)	agentr   r   
is_enabled
load_stater   get_interval_hoursmaxr   )r   statehoursdayss       r   _print_curator_first_run_noticer   M  s}   !!!!!!!   !!## 	F""$$   yy **,,   q%2+D	GGG	
		L	L 	L 	L  
 

8999	
0111	`    s4   	 
A A 
AA/B BBc                     d} 	 ddl m} t           |            pi                     d          pi                     dd                                                                                    } n# t          $ r d} Y nw xY w| dk    rdS 	 ddlm} dd	l	m
} n# t          $ r Y dS w xY w |            d
z  }|                                sdS 	 |                                j        dz  }n# t          $ r Y dS w xY w|dk     rdS d}d}	  ||d          }|j                            d                                          }t%          |j                            d                                          pW|j                            d                                          p+|j                            d                                                    }n;# t          $ r. Y |(	 |                                 dS # t          $ r Y dS w xY wdS w xY w	 |&	 |                                 n># t          $ r Y n2w xY wn-# |&	 |                                 w # t          $ r Y w w xY ww xY w|r|d         ndpd}	|	rd|	v r|sdS |r=t)                       t)          d           t)          d           t)          d           dS |dz  }
t)                       | dk    r(t)          d           t)          d|
dd|dd           n't)          d            t)          d!|
dd|dd"           t)          d#           t)          d$           dS )%a  Advertise the opt-in v23 search-index optimization after `hermes update`.

    Only fires when the current profile's state.db is still on the legacy
    (pre-v23) inline FTS layout. Leads with the reclaimable-space figure and
    points at the exact command. Honors ``sessions.fts_optimize_notice``:
    ``advise`` (default) prints an advisory notice, ``require`` prints a
    firmer required-upgrade notice, ``off`` suppresses it. Silent for
    fresh/already-optimized installs.
    adviser   load_configsessionsfts_optimize_noticeoffNr   )	SessionDBstate.db   @ru   FT)db_path	read_onlyzLSELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'messages_fts'zESELECT 1 FROM state_meta WHERE key = 'fts_rebuild_high_water' LIMIT 1zfSELECT 1 FROM sqlite_master WHERE type = 'table' AND name LIKE 'fts\_v22\_trash\_%' ESCAPE '\' LIMIT 1z]SELECT 1 FROM state_meta WHERE key IN ('fts_cjk_rebuild_high_water', 'fts_cjk_stale') LIMIT 1rZ   	tool_nameu,   ◆ Session database optimization incompletez  A previous `hermes sessions optimize-storage` run was interrupted. Search still works; re-run the command to resume and finish reclaiming disk:z$    hermes sessions optimize-storageg333333?requireu%   ◆ Session database upgrade requiredu|     Your search index uses the OLD storage layout and should be upgraded. The new layout typically frees ~60% of state.db (≈.1fz GB of your current z5 GB) and is required for continued optimal operation.u.   ◆ Reclaim ~60% of your session database diskui     Your search index uses the old storage layout. Upgrading it typically frees ~60% of state.db — about z GB.z8  Run when convenient:  hermes sessions optimize-storagezs  It runs in the foreground with a progress bar, is safe to interrupt/re-run, and never changes your conversations.)hermes_cli.configr   rF   r   r0   lowerr   r   r   hermes_stater   rD   statst_sizer2   _connexecutefetchoner   closer   )moder   r   r   r   size_gbdbinterruptedrowsqlest_reclaims              r   $_print_fts_optimize_available_noticer   u  s    D	111111kmm!r&&z228b==%x 
 
 %''%%''	 	
    u}}444444*******   o*,G>> ,,..(I6    }}	BKYw$777h=
 
 (** 	 H?  hjj xL  hjj xJ  hjj
 
    >




    > 	" >



    2>



    
  3q66b
'RC ;#%%k% 	<===*	
 	
 	

 	4555 C-K	GGGy56669Q9 98?Q9 9 9	
 	
 	
 	
 	>???1:EO1 1&01 1 1	
 	
 	

 

DEEE		B    s   A,A1 1B ?B B 
B'&B'C+ +
C98C9	C
G H9 
HH9 "G8 8
HHHH9 H( (
H54H59I#=II#
II#II#c                     	 ddl m}  n# t          $ r Y dS w xY w	 |                                 }n# t          $ r Y dS w xY w|                    d          }|sdS |                    d          |k    rdS |                    d          pd}|sdS d|vr.	 ||d<   |                     |           n# t          $ r Y nw xY wdS t          |          }t                       t          d	|            |                                D ]}t          d
|            t          d           	 ||d<   |                     |           dS # t          $ r Y dS w xY w)u  Print the most recent curator run summary, exactly once.

    The curator runs in the background (gateway tick + CLI session start),
    so users learn about skill consolidations only by stumbling into a
    rename. ``hermes update`` is a high-attention surface — surface the
    most recent run's rename map here, once.

    Show-once: state stamps ``last_run_summary_shown_at`` after printing.
    Subsequent ``hermes update`` invocations skip the block until a newer
    curator run lands. Silent when the curator has never run, when the
    most recent summary has already been shown, or when the summary has
    no rename information to display (no archives).
    r   r   Nr   last_run_summary_shown_atlast_run_summaryrZ   r[   u   ℹ Skill curator — last run   zP  (This message shows once per curator run. View anytime: hermes curator status))	r   r   r   r   r   
save_state_format_time_agor   
splitlines)r   r   r   summarywhenlines         r    _print_curator_recent_run_noticer     s   !!!!!!!   ""$$    ))M**K yy,--<<ii*++1rG  7	1<E-.u%%%% 	 	 	D	 K((D	GGG	
2D
2
2333""$$  k4kk		/  -8)*5!!!!!   s<   	 
0 
>>B0 0
B=<B=*E 
EEiso_tsc                    	 ddl m }m}  |j        |                     dd                    }|j        |                    |j                  } |j        |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 # t          $ r Y dS w xY w)zGRender an ISO timestamp as `Xh ago` / `Xd ago` / `Xm ago`. Best effort.r   r   timezoneZz+00:00N)tzinfo<   zjust nowi  zm agoiQ zh agozd agorecently)
r   r   fromisoformatr%   r   utcnowr   total_secondsr   )r   r   r   tsdeltasecss         r   r   r   '  s   ////////#X#FNN3$A$ABB98<00BX\**R/5&&(())"99:$;;bj''''%<<dl))))%-&&&&   zzs$   BB5 B5 B5 -B5 5
CCnode_failuresc                 N   | r.t                       t          d           t          d           dS t                                          d          }|                    d          sdS t                       t          d           t          d           t          d	           dS )
zERefresh managed dashboards or stop stale manual ones after an update.uA     ℹ Leaving running dashboard process(es) untouched because thez0    Node.js dependency refresh did not complete.NT)restart_managedunrecoveredu\   ⚠ A web dashboard/serve process was stopped during update and could not be auto-restarted.z-  Re-launch it when you want the web UI back:z"    hermes dashboard --port <port>)r   r   _kill_stale_dashboard_processesr   )r   stop_results     r    _finish_dashboard_update_cleanupr   :  s     QRRR@AAA$$66t6LLK??=)) 	GGG		!   

9:::	
./////r   srcdstc                 F    t          t          | |          |fg           dS )u  Replace directory *dst* with *src* without leaving *dst* half-deleted.

    The naive ``rmtree(dst); copytree(src, dst)`` has a destructive window: if
    the copy fails partway (common on the Windows ZIP-update path, which only
    runs because file I/O is already flaky on that machine), the old directory
    is already gone and nothing replaced it — the install is left with a
    deleted tree (issue #49145, where ``ui-tui/`` vanished and broke the TUI).

    Now a thin single-entry alias over the two-phase helpers below, which
    generalise the same stage-then-swap discipline across every entry the ZIP
    update touches (#76104). Retained because it is part of the mechanical
    ``hermes_cli.main`` re-export surface and guards the #49145 regression.
    N)_commit_staged_replacements_stage_replacement)r  r  s     r   _atomic_replace_dirr  N  s+      "4S#">">!D EFFFFFr   c                 4   | d}| d}t           j                            |          s4t           j                            |          rt          j        ||           ||fD ]k}t           j                            |          rt          j        |d           8t           j                            |          rt          j        |           lt           j                            |           rt          j        | |           nt          j	        | |           |S )zCopy *src* to a sibling staging path for *dst*; return the staging path.

    Phase 1 of the two-phase replace. Handles both directories and plain
    files. Touches nothing live, so a failure here leaves the whole install
    untouched.
    z.hermes-update-staging.hermes-update-oldTignore_errors)
osrK   rD   renameisdirshutilrmtreeremovecopytreecopy2)r  r  stagingbackupleftovers        r   r  r  _  s
    ,,,G'''F 7>># 27>>&#9#9 
	&#f%    7=="" 	 M($77777W^^H%% 	 Ih	w}}S #W%%%%S'"""Nr   c                 P   | D ]\  }}	 t           j                            |          rt          j        |d           n3t           j                            |          rt          j        |           p# t          $ r&}t          	                    d||           Y d}~d}~ww xY wdS )u  Remove staging paths for entries that were never committed.

    Without this a phase-1 failure (typically disk exhaustion) orphans one
    staging copy per entry already processed — up to a full second copy of
    the tree. The user then follows the "re-run `hermes update`" advice with
    *less* free space than before and the retry fails harder than the
    original attempt.
    Tr	  z$could not remove staging path %s: %sN)
r  rK   r  r  r  rD   r  r2   r   warning)stagedr  _dstr   s       r   _discard_stagedr  |  s       Q Q	Qw}}W%% #gT:::::(( #	'""" 	Q 	Q 	QNNA7CPPPPPPPP	QQ Qs   A)A33
B#=BB#c                    g }	 | D ]\  }}| d}t           j                            |          r-t          j        ||           |                    ||f           n|                    |df           t          j        ||           n# t
          $ r t          |          D ]\  }}	 t           j                            |          rt          j	        |d           n3t           j                            |          rt          j
        |           |r4t           j                            |          rt          j        ||           # t
          $ r&}t                              d||           Y d}~d}~ww xY w w xY w|D ]\  }}|r6t           j                            |          rt          j	        |d           =|rEt           j                            |          r&	 t          j
        |           t# t
          $ r Y w xY wdS )u  Phase 2: swap every staged entry into place, rolling back all on failure.

    ``_atomic_replace_dir`` makes each *individual* directory swap safe, but
    the ZIP update replaces ~90 top-level entries in a loop, and nothing made
    the loop atomic *as a whole*. A failure partway left some entries at the
    new version and the rest at the old one — every file valid Python, the
    combination unbootable (issue #76104; the ``ImportError`` in #76091 and
    the field report in #63717 are both this).

    This covers plain files as well as directories: the repo root holds 20
    first-party modules (``run_agent.py``, ``cli.py``, ``hermes_constants.py``
    …), so a files-only failure reproduces exactly the bug class we are
    closing. Every swap is an ``os.rename`` onto a path that was just moved
    aside — a same-filesystem rename is atomic on POSIX and NTFS alike, so a
    file swap can never leave a half-written module the way ``copy2`` onto a
    live path can.

    Splitting stage-all-then-swap-all shrinks the failure window from "the
    duration of a full tree copy" to "the duration of N renames", and makes
    the remaining window recoverable: if a swap fails we restore every entry
    already swapped, so the tree lands wholly new or wholly old.
    r  rZ   Tr	  zrollback failed for %s: %sN)r  rK   rD   r  appendr2   reversedr  r  r  r  r   r  )r  swappedr  r  r  r   r  s          r   r  r    s[   . &(G" 	$ 	$LGS///Fw~~c"" *	#v&&&V}----Ry)))Igs####	$    #G,, 	G 	GKCG7==%% #M#T:::::W^^C(( #IcNNN +bgnnV44 +Ifc*** G G G ;S#FFFFFFFF	G
 	"    f 	bgmmF++ 	M&55555 	v.. 		&!!!!    sI   BB
 
E>)BE	E>	
E9E4/E>4E99E>"G77
HHc           	        45 ddl }ddl}ddlm} t	                                          |           }|dk    rLt          d| d           t          d| d           t	                      j                            d	           d
| d}t          d           |	                    d          }	 t          j                            |d| d          } |||           t          d           ddl}|                    |d          5 }	t          j                            |          }
|	                                D ]}t          j                            t          j                            ||j                            }|                    |
t          j        z             s||
k    rt)          d|j         d          |j        dz	  dz  }|                    |          rt)          d|j                   |	                    |           ddd           n# 1 swxY w Y   t          j                            |d|           4t          j                            4          s`t          j        |          D ]K}t          j                            ||          }t          j                            |          r
|dk    r|4 nLh d55fdt          j        4          D             }t5          4fd|D                       t5          4fd|D                       z   }t7          |dz            }t9          j        t=          t	                      j                            j         }||k     rtC          d|dz   d|dz   d           g }	 |D ]}t          j                            4|          }t          j                            t=          t	                      j                  |          }|"                    tG          ||          |f           n# tH          $ r tK          |            w xY w	 tM          |           n# tH          $ r tK          |            w xY wtO          |          }t          d!| d"           nm# tH          $ r`}t          d#|            t          d$           t          d%           t	                      j                            d	           Y d}~nd}~ww xY wt9          j(        |d&'           n# t9          j(        |d&'           w xY wt	                      )                    t	                      j                  }|rt          d(| d)|d	k    rd*nd+            t	                      *                                 t          d,           dd-l+m,}m-}  |              |            }t	                      j        j.        d.d/g}|st_          |          }|ri t          j0        d0t=          t	                      j        d1z            i}t	                      1                    |          r,|2                    d2d           |2                    d3d           t	                      3                    |d/g|4           n	 ti          j5        |d5gz   t	                      j        d&d&6           nX# th          j6        $ rF ti          j5        t	                      j        j.        d.d7d8d9gt	                      j        d&:           Y nw xY wt	                      3                    |           t	                      7                                 tq          t	                      j                  \  } }!}"| st                       t          d;           t          d<|! d=|"            t                       t          d>           t          d?           t	                      j                            d	           ts                      }#t	                      :                    t	                      j        d@z             	 ddAl;m<}$ t          dB            |$d&C          }%|%dD         rAt          dEtO          |%dD                    dFdG                    |%dD                               |%=                    dH          rAt          dItO          |%dH                    dJdG                    |%dH                               |%=                    dK          r5t          dLtO          |%dK                    dM           t          dN           |%=                    dO          r&t          dPtO          |%dO                    dQ           |%=                    dR          rAt          dStO          |%dR                    dTdG                    |%dR                               |%dD         s$|%=                    dH          st          dU           n# tH          $ r Y nw xY w	 ddVl>m?}&  |&t	                      j                  rt          dW           n2# tH          $ r%}t          A                    dX|           Y d}~nd}~ww xY w	 ddYlBmC}'mD}( t                      dZz  })|)F                                r |(|)d&d&[          }*|*=                    d\          s^t                       t          d]|*=                    d^d_          z               |'t                                }+|+F                                rt          d` |+H                                D             d&a          },|,D ]}-|-dZz  }.|.F                                r |(|.d&d&[          }/|/=                    d\          r	 ddl}0|0I                    |.|)            |(|)d&d&[          }1|1=                    d\          rt          db|-jJ                    nt          dc            n+# t          $ r}2t          dd|2            Y d}2~2 n	d}2~2ww xY wn2# tH          $ r%}3t          A                    de|3           Y d}3~3nd}3~3ww xY wt                       |#rEt          dfdG                    |#           dg           t          dh           t          di           nt          dj           	 t                       n2# tH          $ r%}t          A                    dk|           Y d}~nd}~ww xY w	 t                       n2# tH          $ r%}t          A                    dl|           Y d}~nd}~ww xY wt          |#           dS )mzUpdate Hermes Agent by downloading a ZIP archive.

    Used on Windows when git file I/O is broken (antivirus, NTFS filter
    drivers causing 'Invalid argument' errors on file creation).
    r   N)urlretriever   u   ✗ --branch=z: is not supported on the Windows ZIP-fallback update path.z  This path runs when git file I/O is broken on the system. Either resolve the git-side breakage (typically an antivirus or NTFS filter holding files open) and rerun `hermes update --branch z/`, or update against main with `hermes update`.r\   z@https://github.com/NousResearch/hermes-agent/archive/refs/heads/z.zipu!   → Downloading latest version...zhermes-update-r8   zhermes-agent-u   → Extracting...rzZip-slip detected: z escapes extraction directory   i   z)ZIP contains unsupported symlink member: __MACOSX>   .envrS   r   .gitc                     g | ]}|v|	S r   r   )r   ipreserves     r   r   z#_update_via_zip.<locals>.<listcomp>  s#    IIIq7H7H17H7H7Hr   c           	   3     K   | ]}t          j        t           j                            |                    D ]J\  }}}|D ]A}t           j                            t           j                            ||                    V  BKd S r   )r  walkrK   joingetsize)r   entrydirpath_dirsfilesf	extracteds         r   r   z"_update_via_zip.<locals>.<genexpr>'  s       
 
)+i1O1O)P)P
 
 &	
 
  GOOBGLL!4455
 
 
 
 
 
 
 
r   c              3     K   | ]~}t           j                            t           j                            |                    ?t           j                            t           j                            |                    V  d S r   )r  rK   isfiler+  r,  )r   er2  s     r   r   z"_update_via_zip.<locals>.<genexpr>,  sy       
 
w~~bgll9a8899
GOOBGLLA6677
 
 
 
 
 
r   g333333?z=not enough free disk space to stage the update safely (need ~i   z
 MB, have z MB)u   ✓ Updated z items from ZIPu   ✗ ZIP update failed: z*  Your existing install was left in place.zq  Re-run `hermes update` to retry; if the agent won't start, reinstall from https://hermes-agent.nousresearch.comTr	       ✓ Cleared  stale __pycache__ directoryies#   → Updating Python dependencies...	ensure_uvupdate_managed_uv-mpipVIRTUAL_ENVrS   
PYTHONPATH
PYTHONHOMEenv	--versionr'   r,   r(   	ensurepip	--upgrade--default-pipr'   r,   u5   ✗ Update left the install in an unimportable state:r   : z>  This usually means the copy was interrupted partway through.z(  Re-run `hermes update` to complete it.websync_skills   → Syncing bundled skills...quietcopied  +  new: , updated     ↑ 
 updated: user_modified  ~  user-modified (kept)M       → see them: hermes skills list-modified  (diff/reset to resume updates)cleaned     −  removed from manifest	relocated     →  moved to new upstream paths:      ✓ Skills are up to dateseed_cache_from_checkout1     ✓ Model catalog cache refreshed from checkoutz/Model catalog seed during zip update failed: %s_quick_snapshot_rootverify_sqlite_integrityr   check_header
run_pragmavalid(   ⚠ state.db is corrupted after update: messageunknown errorc              3   B   K   | ]}|                                 |V  d S r   r   )r   ds     r   r   z"_update_via_zip.<locals>.<genexpr>  s/      GGqAHHJJGGGGGGGr   )reverseu"     ✓ Auto-restored from snapshot A     ✗ Auto-restore FAILED — restored copy also failed integrity%     ✗ Auto-restore file copy failed: z:Post-update state.db integrity check (zip path) failed: %s;   ⚠ Update partially complete — Node.js dependencies for  did not refresh.=  Code and Python deps are updated, but the dashboard/TUI may6  be in a mixed state until the Node deps are rebuilt.   ✓ Update complete!#Curator first-run notice failed: %s$Curator recent-run notice failed: %s)OrA   zipfileurllib.requestr   r   _resolve_update_branchr   r   exitmkdtempr  rK   r+  r   ZipFilerealpathinfolistfilename
startswithsepr   external_attrS_ISLNK
extractallr  listdirsumr   r  
disk_usagerF   PROJECT_ROOTfreeRuntimeErrorr  r  r   r  r  rf   r  _clear_bytecode_cache_record_bytecode_fingerprinthermes_cli.managed_uvr<  r=  ra   _ensure_uv_for_termuxenviron_is_termux_envpop3_install_python_dependencies_with_optional_fallbackr-   r.   r1   ,_refresh_active_memory_provider_dependenciesrl   _update_node_dependencies_build_web_uitools.skills_syncrN  r   hermes_cli.model_catalogre  r   r   hermes_cli.backuprh  ri  r   rD   r`   iterdirr  r   r2   r   r   r   )6argsrA   r}  r   branchzip_urltmp_dirzip_path_statzftmp_dir_realmembermember_pathr   rr  r   entriesneedrequiredr  r  itemr  r  update_countr5  removedr<  r=  uv_binpip_cmduv_env	import_okfailing_moduleimport_errorr   rN  r5   re  rh  ri  _state_path	_state_ok
_snap_root
_snap_dirs	_snap_dir_snap_state_snap_ok_shutil_restored_ok_excr   r2  r(  s6                                                       @@r   _update_via_zipr    s    OOONNN****** TT((..FF   	
 	
 	
 	P P P P	
 	
 	
 	aW6WWW  

-...&677G{37<<)E)E)E)EFFGX&&&!"""__Xs++ 	#r 7++G44L++--   g..rw||GV_/U/UVV#..|bf/DEE#|33$\fo\\\  
 ,2h>==&& $UFOUU   MM'"""/	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	#4 GLL*B&*B*BCC	w}}Y'' 	Z((  GLL!44	7==++ Z )IE <;;IIIIbj33III  
 
 
 
 
 
 
 
 

  
 
 
 

 
 
 
 
	
 tcz?? RTT%6!7!788=(??/"{3/ /K(/ / /   )+		 C Cgll9d33gll3rtt'8#9#94@@1#s;;SABBBBC  	 	 	 F###			'//// 	 	 	 F###	 6{{:\:::;;;; 
 
 
+++,,, 	:;;;C	
 	
 	
 	a
 	gT22222gT22222 dd(():;;G 
aWaaSTZ_aa	
 	
 	
 DD%%'''
 

/000BBBBBBBB Y[[Fttx"D%0G 0&w// JOBJOs2443Dv3M/N/NOO44v&& 	+JJ|T***JJ|T***
@@&%V\@]]]]	N;-'DD%#	     , 	 	 	N$dKoVDD%     	 	@@III
 DD55777 /P
/ /+I~|  EFFF3>33\33444NOOO8999
a-//MDDrtt(50111111111-...4(((( 	USVH-..SSdiix@P6Q6QSSTTT::i   	YVI.//YY499VIEV;W;WYY   ::o&& 	LVO455LLLMMM1   ::i   	KI3vi011IIIJJJ::k"" 	4VK011 4 499VK0114 4   h 	1

9(=(= 	1/000   
KEEEEEE##BDD$566 	GEFFF K K KFJJJJJJJJK7
SSSSSSSS%''*4 /	*//$4  I ==)) +*>mmI??@   21/2C2CDD
$$&& $*!'GGJ$6$6$8$8GGG $" " "J &0 * *	&/*&<&--// *'>'> +$4( ( (H  (||G44 *!*$<$<$<$<$+MM+{$K$K$K3J3J(359374& 4& 4&L
 (4'7'7'@'@ 	%*(--@/8~-@ -@)* )* )* )*
 ).-I)* )* )* %*E'. !* !* !*$)(VPT(V(V%& %& %& %*EEEEE	!*
  
 
 
H#	
 	
 	
 	
 	
 	
 	
 	


 
GGG &;yy''; ; ;	
 	
 	
 	MNNNFGGGG$%%%?')))) ? ? ?:A>>>>>>>>?@(**** @ @ @;Q????????@ %]33333s;  0AR 	DHR H  R #H $E-R BP R P66R :Q
 	R 
Q%%%R 
T 
S5AS0+T 0S55T T'-[	 	A\\Gh 
h%$h%)1i 
j
%jj
Dp? -A$pp? 
p:p5/p? 5p::p? ?
q.	q))q.s% %
t/ttt' '
u1uur4   r'   c           	         t          j        | ddgz   |ddddd          }|j                                        sd S t          j        | ddgz   |dddd	          }|j                                        r*t	          d
           t          j        | dgz   |d           ddlm}m}  |j        |j                  	                    d          }t	          d           t          j        | g dz   |dddd	          j                                        }t          j        | dddd|gz   |dddd	          }|j                                        r&t	          |j                                                   t          j        | g dz   |dddd	          }	|	j                                        }
|	j
        dk    ot          |
          o|
|k    }|j
        dk    r |rz|j                                        r&t	          |j                                                   t	          d           t	          d           t          j        | g dz   |d           nt	          d           |j                                        rAt	          d|j                                                                        d                     t	          d           t          j        |j
        |j        |j        |j                  |
S )Nstatusz--porcelainTr$   r%   r&   zls-filesz
--unmergedr'   r(   r)   r*   r+   u?   → Clearing unmerged index entries from a previous conflict...resetr'   r(   r   r   z%hermes-update-autostash-%Y%m%d-%H%M%Su8   → Local changes detected — stashing before update...)r"   --verifyz
refs/stashstashpushz--include-untrackedr>  uZ     ⚠ Some untracked files could not be removed from the working tree (permission denied).u[       They were still saved to the stash and were left in place — the update will continue.r  --hardr#   u5   ✗ Could not stash local changes — update aborted.r   zV  Commit, stash, or clean up your local changes manually, then re-run `hermes update`.)outputstderr)r-   r.   r/   r0   r   r   r   r   r   strftimerd   r   r  r   r1   r  )r4   r'   r  unmergedr   r   
stash_name
prev_stashr  stash_probe	stash_refstash_createds               r   _stash_local_changes_if_neededr  3  s   ^8]++GI  F =   t ~:|,,GI	  H  JOPPPw'*DIIII++++++++hl++44/ J 

DEEE9999GI	  
 UUWW  >7F$94LLGI	  D { #dk!!""".9999GI	  K "((**I!#SYSI<S  ! &	 {  "" +dk''))***4   6   N5555#     IJJJ{  "" B@4;,,..99;;A>@@AAA/   /4;t{    r   r  c           	         t          j        | g dz   |ddddd          }|j                                        D ]I}|                    d          \  }}}|                                |k    r|                                c S Jd S )N)r  listz--format=%gd %HTr$   r%   r&    )r-   r.   r/   r   	partitionr0   )r4   r'   r  
stash_listr   selector_commits           r   _resolve_stash_selectorr    s     6666GI  J !,,.. $ $"nnS11!V<<>>Y&&>>##### '4r   stash_selectorc                     t          d           t          d           |rt          d|            d S t          d|  d           d S )NzS  Check `git status` first so you don't accidentally reapply the same change twice.z@  Find the saved entry with: git stash list --format='%gd %H %s'z!  Remove it with: git stash drop z  Look for commit z7, then drop its selector with: git stash drop stash@{N})r   )r  r  s     r   _print_stash_cleanup_guidancer    sy     
]   

LMMM 
B.BBCCCCCeeee	
 	
 	
 	
 	
r   r  c                     d | pd                                 D             }|sdS d}|D ](}d|v rd}	d|v rd}|                    d          r& dS |S )a  True when a ``git stash apply`` failure is ONLY about untracked files
    that already exist in the working tree.

    This is the tail end of the permission-denied autostash class: ``git stash
    push --include-untracked`` swept undeletable files (e.g. a root-owned
    ``packaging/`` directory) into the stash but could not remove them from
    disk.  On restore, git applies all tracked changes, then refuses to
    overwrite those still-present files (``already exists, no checkout`` /
    ``could not restore untracked files from stash``) and exits non-zero even
    though nothing was lost.  Any other error line (e.g. ``would be
    overwritten by merge`` / ``Aborting``) means the tracked apply itself
    failed and this returns False.
    c                 ^    g | ]*}|                                 |                                 +S r   r0   )r   lns     r   r   zB_stash_apply_failed_only_on_existing_untracked.<locals>.<listcomp>  s-    LLLBLRXXZZLLLr   rZ   Fzalready exists, no checkoutTz,could not restore untracked files from stash)zwarning:zhint:)r   r  )r  linessaw_untracked_errorr  s       r   ._stash_apply_failed_only_on_existing_untrackedr    s     ML6<R";";"="=LLLE u  (B.."&;rAA"&]]011 	55r   Fprompt_userc                    |rt                       t          d           t          d           t          d           t          d           | |dd          }n2t                                                                                      }|dvr2t          d           t          d           t          d	|            d
S t          d           t	          j        | dd|gz   |dddd          }t	          j        | g dz   |dddd          }t          |j                                                  }|j        dk    r'|s%t          |j
                  rt          d           nN|j        dk    s|r@t          d           |j                                        r&t          |j                                                   |j
                                        r&t          |j
                                                   |j                                        }	|	r8t          d           |	                                D ]}
t          d|
            t          d           t          d|            t	          j        | g dz   |d           t          d           t          d|            d
S t          | ||          }|.t          d           t          d           t          |           nt	          j        | dd |gz   |dddd          }|j        dk    rt          d!           |j                                        r&t          |j                                                   |j
                                        r&t          |j
                                                   t          d           t          ||           t          d"           t          d#           dS )$Nu/   ⚠ Local changes were stashed before updating.zL  Restoring them may reapply local customizations onto the updated codebase.z=  Review the result afterward if Hermes behaves unexpectedly.z Restore local changes now? [Y/n]r8  >   rZ   r8  yesz Skipped restoring local changes.z.Your changes are still preserved in git stash.z'Restore manually with: git stash apply Fu   → Restoring local changes...r  applyTr$   r%   r  )diff--name-onlyz--diff-filter=Ur   uY     ⚠ Some stashed untracked files already exist in the working tree and were kept as-is.uF   ✗ Update pulled new code, but restoring local changes hit conflicts.z
Conflicted files:u     • u8   
Your stashed changes are preserved — nothing is lost.z  Stash ref: r  r  z"Working tree reset to clean state.z1Restore your changes later with: git stash apply uR   ⚠ Local changes were restored, but Hermes couldn't find the stash entry to drop.zT  The stash was left in place. You can remove it manually after checking the result.dropuP   ⚠ Local changes were restored, but Hermes couldn't drop the saved stash entry.u?   ⚠ Local changes were restored on top of the updated codebase.zB  Review `git diff` / `git status` if Hermes behaves unexpectedly.)r   inputr0   r   r-   r.   r   r/   rd   r  r  r   r  r  )r4   r'   r  r  input_fnresponserestorer  has_conflictsconflicted_filesr1  r  r  s                r   _restore_stashed_changesr    ss     ?@@@Z	
 	
 	
 	MNNN0111x BCHHHHww}},,..H+++4555BCCCGIGGHHH5	
*+++n7GY//GI	  G ~<<<<GI	  H ..0011MQ}6w~FF  	(	
 	
 	
 	
 
	q	 	 M	 VWWW>!! 	*'.&&(()))>!! 	*'.&&(())) $?0022 	$'(((%0022 $ $lqll####IJJJ)i))***
 	1111	
 	
 	
 	

 	2333M)MMNNN u,Wc9EEN`	
 	
 	
 	b	
 	
 	
 	&i0000~w77		
 
 
 ?ab   {  "" +dk''))***{  "" +dk''))***f   *)^DDD	
KLLL	
NOOO4r   c                    t          | ||          }| t          d           t          |           dS t          j        | dd|gz   |dddd	          }|j        d
k    r{t          d           |j                                        rAt          d|j                                                                        d
                     t          ||           dS t          d           dS )u  Throw away a stash created before an update, without applying it.

    Used only on a NON-interactive update when the user has set
    ``updates.non_interactive_local_changes: discard`` — i.e. they've opted out
    of keeping local source edits on this machine. Drops the stash entry
    instead of re-applying it, so the working tree stays clean at the freshly
    pulled HEAD. Unlike ``git reset --hard`` + ``git clean -fd``, this only
    affects what was stashed (tracked changes + the untracked files we
    explicitly captured) — ignored paths like node_modules/venv/build outputs
    are never touched, since they were never stashed.

    Returns True if the stash was dropped, False on a git failure (in which
    case the stash is left in place for safety).
    Nut   ⚠ Configured to discard local changes on non-interactive update, but Hermes couldn't find the stash entry to drop.Fr  r  Tr$   r%   r  r   uX   ⚠ Configured to discard local changes, but Hermes couldn't drop the saved stash entry.r   uS   → Discarded local source changes (updates.non_interactive_local_changes=discard).)	r  r   r  r-   r.   rd   r  r0   r   )r4   r'   r  r  r  s        r   _discard_stashed_changesr  H  s   & -Wc9EEN@	
 	
 	
 	&i000u>7FN33GI	  D !%	
 	
 	
 ; 	><t{((**5577:<<===%i@@@u	
_```4r   >   (git@github.com:NousResearch/hermes-agent,git@github.com:NousResearch/hermes-agent.git,https://github.com/NousResearch/hermes-agent0https://github.com/NousResearch/hermes-agent.gitr  z.skip_upstream_promptc                     	 t          j        | g dz   |dddd          }|j        dk    r|j                                        S n# t
          $ r Y nw xY wdS )z5Get the URL of the origin remote, or None if not set.)remoteget-urloriginTr$   r%   r  r   N)r-   r.   rd   r/   r0   r   r3   s      r   _get_origin_urlr     s    
5555		
 
 
 !!=&&((( "   4s   AA 
AA
origin_urlc                    | sdS |                      d          }|                    d          r
|dd         }t          D ]?}|                     d          }|                    d          r
|dd         }||k    r dS @dS )zDCheck if the origin remote points to a fork (not the official repo).Fr:   r%  NT)rstripendswithOFFICIAL_REPO_URLS)r  
normalizedofficialofficial_normalizeds       r   _is_forkr
    s     u""3''J6"" %_
&  &ooc22''// 	;"5crc":,,,55 -4r   c                 z    	 t          j        | g dz   |dddd          }|j        dk    S # t          $ r Y dS w xY w)z-Check if an 'upstream' remote already exists.r  r  upstreamTr$   r%   r  r   Fr-   r.   rd   r   r3   s      r   _has_upstream_remoter    sk    	7777		
 
 
  A%%   uu   ), 
::c                     	 t          j        | dddt          gz   |dddd          }|j        dk    S # t          $ r Y d	S w xY w)
zHAdd the official repo as the 'upstream' remote. Returns True on success.r  addr  Tr$   r%   r  r   F)r-   r.   OFFICIAL_REPO_URLrd   r   r3   s      r   _add_upstream_remoter    sn    	x
4EFF		
 
 
  A%%   uus   03 
A Abaseheadc                     	 t          j        | dd| d| gz   |dddd          }|j        dk    r&t          |j                                                  S n# t          $ r Y nw xY wd	S )
zDCount commits on `head` that are not on `base`. Returns -1 on error.rev-list--countz..Tr$   r%   r  r   )r-   r.   rd   r   r/   r0   r   )r4   r'   r  r  r5   s        r   _count_commits_betweenr    s    
z9.?.?.?.?@@		
 
 
 !!v}**,,--- "   2s   AA 
A&%A&c                  V    ddl m}   |             t          z                                  S )z2Check if user previously declined to add upstream.r   r   )r   r   SKIP_UPSTREAM_PROMPT_FILErD   r   s    r   _should_skip_upstream_promptr    s4    000000O 99AACCCr   c                  ~    	 ddl m}   |             t          z                                   dS # t          $ r Y dS w xY w)z3Create marker file to skip future upstream prompts.r   r   N)r   r   r  touchr   r   s    r   _mark_skip_upstream_promptr!    sa    444444			6	6==?????   s   *. 
<<c                 z    	 t          j        | g dz   |dddd          }|j        dk    S # t          $ r Y dS w xY w)znAttempt to push updated main to origin (sync fork).

    Returns True if push succeeded, False otherwise.
    )r  r  r   z--force-with-leaseTr$   r%   r  r   Fr  r3   s      r   _sync_fork_with_upstreamr#    sk    
	FFFF		
 
 
  A%%   uur  c                     t          | |          }|st                      rdS t                       t          d           t          d           t                       	 t          d                                                                          }n'# t          t          f$ r t                       d}Y nw xY w|dv rBt          d           t          | |          rt          d           d	}n0t          d
           dS t          d           t                       dS t                       t          d           	 t          j        | g dz   |d	d	           n%# t          j        $ r t          d           Y dS w xY wt          | |dd          }t          | |dd          }|dk     s|dk     rt          d           dS |dk    rPt                       t          d| d           t          d           t          d           t          d           dS |dk    rt          d           dS t                       t          d| d           t          d           	 t          j        | g dz   |d	           n%# t          j        $ r t          d           Y dS w xY wt          d            t          d!           t          | |          rt          d"           dS t          d#           t          d$           dS )%a[  Check if fork is behind upstream and sync if safe.

    This implements the fork upstream sync logic:
    - If upstream remote doesn't exist, ask user if they want to add it
    - Compare origin/main with upstream/main
    - If origin/main is strictly behind upstream/main, pull from upstream
    - Try to sync fork back to origin if possible
    Nu=   ℹ Your fork is not tracking the official Hermes repository.zA  This means you may miss updates from NousResearch/hermes-agent.z/Add official repo as 'upstream' remote? [Y/n]: n>   rZ   r8  r  u   → Adding upstream remote...uF     ✓ Added upstream: https://github.com/NousResearch/hermes-agent.gitTu<     ✗ Failed to add upstream remote. Skipping upstream sync.zg  Skipped. Run 'git remote add upstream https://github.com/NousResearch/hermes-agent.git' to add later.u   → Fetching upstream...)fetchr  r   --quietr'   r(   r,   u7     ✗ Failed to fetch upstream. Skipping upstream sync.zupstream/mainzorigin/mainr   u9     ✗ Could not compare branches. Skipping upstream sync.u   ℹ Your fork has z commit(s) not on upstream.z2  Skipping upstream sync to preserve your changes.z-  If you want to merge upstream changes, run:z    git pull upstream mainu&     ✓ Fork is up to date with upstreamu   → Fork is z commit(s) behind upstreamu   → Pulling from upstream...)pull	--ff-onlyr  r   rJ  uO     ✗ Failed to pull from upstream. You may need to resolve conflicts manually.u     ✓ Updated from upstreamu   → Syncing fork...u     ✓ Fork synced with upstreamuL     ℹ Got updates from upstream but couldn't push to fork (no write access?)zF    Your local repo is updated, but your fork on GitHub may be behind.)r  r  r   r  r0   r   EOFErrorKeyboardInterruptr  r!  r-   r.   r1   r  r#  )r4   r'   has_upstreamr  origin_aheadupstream_aheads         r   _sync_with_upstream_if_neededr0    s    (55L !')) 	F 	MNNNQRRR	GHHNNPPVVXX H +, 	 	 	GGGHHH	 '''1222#GS11 \    $TUUUy   '(((F
 
GGG	
$%%%	>>>>		
 	
 	
 	
 	
 (   GHHH
 *'3WWL+m_ N a>A--IJJJ aL<LLLMMMBCCC=>>>*+++ 6777 
GGG	
C
C
C
CDDD	
()))
????	
 	
 	
 	
 	

 (   ]	
 	
 	
 		 

'((( 

   -- X/00000Z	
 	
 	
 	VWWWWWs6   3B !B76B7=E E=<E=I< <JJc                     g } ddl m}  |            }|                     |           |dz  }|                                r@|                                D ]+}|                                r|                     |           ,| D ]@}	 |dz  }|                                r|                                 1# t          $ r Y =w xY wdS )u  Delete the update-check cache for ALL profiles so no banner
    reports a stale "commits behind" count after a successful update.

    The git repo is shared across profiles — when one profile runs
    ``hermes update``, every profile is now current.
    r   get_default_hermes_rootprofilesz.update_checkN)r   r3  r  r   r  rD   ry   r   )homesr3  default_homeprofiles_rootr-  r   
cache_files          r   _invalidate_update_cacher9  \  s    E888888**,,L	LL :-M $"**,, 	$ 	$E||~~ $U###  	/J  "" $!!### 	 	 	D	 s   -B44
C CrK   labelc                v   t                                          | j                  rt                              d|           dS 	 |                     dt          j                     dt          j	                     dd           dS # t          $ r'}t                              d||           Y d}~dS d}~ww xY w)	z1Drop an update-recovery breadcrumb. Never raises.z/Skipping %s marker under pytest (live checkout)Nzstarted=z
pid=r[   r$   rt   zCould not write %s marker: %s)r   _pytest_owns_live_checkoutr   r   r   r|   r~   timer  getpidr2   )rK   r:  r   s      r   _write_marker_filer?  w  s    	tt&&t{33 FNNNB:uz||::29;;:::W 	 	
 	
 	
 	
 	
  B B B4eSAAAAAAAAABs   A B 
B8B33B8c                  d    t          t                                                      d           dS )z;Drop the interrupted core-install breadcrumb. Never raises.zupdate-incompleter:  N)r?  r   _update_marker_pathr   r   r   _write_update_incomplete_markerrC    s,    rtt//119LMMMMMMr   c                  d    t          t                                                      d           dS )z;Drop the interrupted lazy-refresh breadcrumb. Never raises.zlazy-refresh-incompleterA  N)r?  r   _lazy_refresh_marker_pathr   r   r   %_write_lazy_refresh_incomplete_markerrF    s,    rtt5577?XYYYYYYr   matchesscripts_dirc                    |dz  }dg}| D ] \  }}|                     d| d|            !|                     d           |                     d| d           |                     d           |                     d           |                     d	           |                     d
           |                     d           | rwd                    d | D                       }|                     d           |                     d           |                     d| d           |                     d           |                     d           |                     d           d                    |          S )zCBuild a human-readable explanation + remediation hint for the user.z
hermes.exeu"   ✗ Another hermes.exe is running:z    PID r   rZ   z'  Updating now would fail to overwrite z becausez1  Windows blocks REPLACE on a running executable.z9  Close Hermes Desktop, exit any open `hermes` REPLs, andz;  stop the gateway (`hermes gateway stop`) before retrying.r  c              3   &   K   | ]\  }}d | V  dS )z/PID Nr   )r   pidr  s      r   r   z7_format_concurrent_instances_message.<locals>.<genexpr>  s,      @@fc1MCMM@@@@@@r   z8  If you've already closed everything and these PIDs arez8  stale, terminate them directly, then retry the update:z      taskkill z /Fz9  Override with `hermes update --force` if you've alreadyz7  confirmed those processes will not write to the venv.r[   )r  r+  )rG  rH  shimr  rK  r   pid_argss          r   $_format_concurrent_instances_messagerN    s    %D12E / /	T---t--....	LL	LLI4IIIJJJ	LLDEEE	LL	LLLMMM	LLNOOO	LL 88@@@@@@@OPPPOPPP4x444555R	LLLMMM	LLJKKK99Ur   rC  install_cmd_prefixrD  c                    	 t                                          | g dz   |           dS # t          j        $ r&}t                              d|           Y d}~dS d}~ww xY w)zUpgrade pip before lazy-backend refreshes.

    Older pip (e.g. 24.0 on Python 3.11) can fail setuptools-backed source
    builds during lazy installs and leave a partially-written venv (#57828).
    Never raises.
    )installrH  r?  rC  z*pip upgrade before lazy refresh failed: %sN)r   _run_package_only_installr-   r1   r   r   )rO  rD  r   s      r    _upgrade_pip_before_lazy_refreshrS    s    H
&&!@!@!@@ 	' 	
 	
 	
 	
 	
 ( H H HA3GGGGGGGGGHs   (, A!AA!c                L   	 ddl m} n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w	 |                                }n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w|sdS t                       t          dt          |           d           d	}	 |                    d	
          }n-# t          $ r }t          d|            i }d}Y d}~nd}~ww xY wd |	                                D             }d |	                                D             }d |	                                D             }	d |	                                D             }
|r5t          dt          |           dd
                    |                      |r t          dt          |           d           |
rld
                    d |
D                       }|
d         d                             dd          d         }t          dt          |
           d| d|            |	s|sdS |	D ]V\  }}|                    dd          d         }t          |          dk    r|dd         dz   }t          d| d|            W| t          d            d	S t                                          | |!          }|d"k    rt          d#           dS |d$k    r t          d%           t          d&           dS |d'k    rt          d(           d	S ))u  Refresh lazy-installed backends after a code update.

    When pyproject.toml's ``[all]`` extra was slimmed down (May 2026), most
    optional backends moved to ``tools/lazy_deps.py`` and only install on
    first use. ``hermes update`` runs ``uv pip install -e .[all]`` which
    leaves those packages untouched — so if we bump a pin in
    :data:`LAZY_DEPS` (CVE response, transitive bug fix), users who already
    activated the backend keep the stale version forever.

    This function asks lazy_deps which features the user has previously
    activated and reinstalls them under the current pins. Features the
    user never enabled stay quiet — no churn for cold backends.

    Returns True when the venv is safe to use (refresh succeeded, or no
    active lazy backends, or post-failure import repair succeeded). Returns
    False when a failed lazy install left broken core imports that automatic
    repair could not fix (#57828).

    Never raises. A failure here must not block the rest of the update.
    r   )	lazy_depsz(Lazy refresh skipped (import failed): %sNTz1Lazy refresh skipped (active_features failed): %su   → Refreshing z active lazy backend(s)...F)rr   u(     ⚠ Lazy refresh failed unexpectedly: c                 $    g | ]\  }}|d k    |S )	refreshedr   r   r1  ss      r   r   z1_refresh_active_lazy_features.<locals>.<listcomp>  s&    CCCtq!!{2B2B2B2B2Br   c                 $    g | ]\  }}|d k    |S )currentr   rX  s      r   r   z1_refresh_active_lazy_features.<locals>.<listcomp>  s!    ???TQYqr   c                 F    g | ]\  }}|                     d           ||fS )zfailed:r  rX  s      r   r   z1_refresh_active_lazy_features.<locals>.<listcomp>  s1    LLLAALL4K4KLq!fLLLr   c                 F    g | ]\  }}|                     d           ||fS )zskipped:r]  rX  s      r   r   z1_refresh_active_lazy_features.<locals>.<listcomp>  s1    NNN$!QQ\\*5M5MN1vNNNr   rW  z refreshed: rU       ✓ z already currentc              3       K   | ]	\  }}|V  
d S r   r   )r   r1  r  s      r   r   z0_refresh_active_lazy_features.<locals>.<genexpr>  s&      001!000000r   r\   rK  r  u     · z
 skipped (z):    ...     ⚠ z failed to refresh: u?     ⚠ Lazy refresh failed; rerun `hermes update` once resolved.rC  repairedzE  Lazy backend(s) keep their previous version until refresh succeeds.healthyzK  Lazy backend(s) keep their previous version; probed packages look intact.z<  Rerun `hermes update` once the upstream issue is resolved.indeterminateuP     ⚠ Leaving `.lazy-refresh-incomplete` until import probes can confirm health.)toolsrU  r   r   r   active_featuresr   rf   refresh_active_featuresitemsr+  re   r   _repair_venv_via_import_probes)rO  rD  rU  r   activeunexpected_failureresultsrW  r[  failedskippednamesreasonfeaturer  s                  r   _refresh_active_lazy_featuresrt    s   2#######   ?EEEttttt**,,   H#NNNttttt  t	GGG	
CCKK
C
C
CDDD"3353AA " " " 	>>>???!" DCw}}CCCI??W]]__???GLLLLLFNN'--//NNNG KIs9~~II499Y3G3GIIJJJ 75s7||555666 B 		0000000A$$T1--b1@c'll@@f@@@@AAA , t! > >dA&&r*v;;DSD\E)F<w<<F<<====!OPPPu
 TT001C0MMFS	
 	
 	
 tY	
 	
 	
 	LMMMt  ^	
 	
 	
 5s?   	 
949A 
BA==B:C 
C;C66C;c                     	 ddl m}   |             }n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wd}t          |t                    ry|                    d          }t          |t                    rO|                    d          du rdS t          |                    d	          pd          	                                }|r|d
v rdS 	 ddl
m} n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wt                       t          d| d           	  ||d           dS # t          $ r }t          d| d|            Y d}~dS d}~ww xY w)u  Refresh pip dependencies for the configured external memory provider.

    Memory-provider bridge packages are declared in each provider's
    ``plugin.yaml`` (plus mode-dependent extras like Hindsight's
    ``hindsight-all``), NOT in Hermes' editable-install extras or
    ``LAZY_DEPS`` alone — so the core dependency reinstall above can strip
    or downgrade them (#53272 mem0ai, #70636 hindsight-embed). Re-run the
    provider's declared install for the ACTIVE provider only, after the
    core install and lazy refresh, so the last write to any shared package
    is the one the active provider needs.

    Never raises. A failure here must not block the rest of the update.
    r   r   z8Memory provider refresh skipped (config load failed): %sNrZ   memoryenabledFprovider>   nonebuiltinro   )_install_dependenciesz3Memory provider refresh skipped (import failed): %su4   → Refreshing active memory provider dependencies (z)...Tforcerc  z! dependencies failed to refresh: )r   r   r   r   r   
isinstancedictr   rF   r0   hermes_cli.memory_setupr{  r   )r   cfgr   rx  
memory_cfgr{  s         r   r  r    s   111111kmm   OQTUUU H#t EWWX&&
j$'' 	E~~i((E11:>>*55;<<BBDDH  x#AAAAAAAAAA   JCPPP 
GGG	
O
O
O
OPPPIhd333333 I I IGxGG#GGHHHHHHHHHIsB    
A>AC& &
D0DD;E
 

E4E//E4c                  :    t                      j        j        dk    S )Nandroid)r   r   platformr   r   r   _is_android_pythonr  O  s    448	))r   c          	      b   ddl }ddl}ddlm}m} |                                5 }t          |          }|dz  }|j                            ||            |||          }	t                      
                    | ddt          |	          gz   |           ddd           dS # 1 swxY w Y   dS )a  Install psutil on Android by patching upstream platform detection.

    psutil's setup currently gates Linux sources behind
    ``sys.platform.startswith('linux')``. On Termux Python reports
    ``sys.platform == 'android'``, so setup aborts with
    "platform android is not supported" despite compiling fine when using the
    Linux source path.

    We patch only the extracted build tree used for this install attempt;
    nothing is persisted in the repository.

    Stopgap: remove this once https://github.com/giampaolo/psutil/pull/2762
    merges and ships in a release. The standalone installer script uses the
    same shared helper and should be removed together.
    r   N)
PSUTIL_URLprepare_patched_psutil_sdistzpsutil.tar.gzrQ  z--no-build-isolationrC  )rA   r~  hermes_cli.psutil_androidr  r  rB   r   requestr   r   _run_install_with_heartbeatrF   )
rO  rD  rA   urllibr  r  r   tmp_patharchivesrc_roots
             r   _install_psutil_android_compatr  R  s   ( OOORRRRRRRR		$	$	&	& 	
#99_,"":w777//BB
(()-CS]]!SS 	) 	
 	
 	
	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
s   A2B$$B(+B(r  c                    ddl m}  |            }|r|S t                                                      sdS t	          j        d          }|r|S 	 t          d           t          j        | g dz   t                      j	        d          }|j
        dk    rdS n# t          $ r Y nw xY w |            pt	          j        d          S )	a  Best-effort uv bootstrap on Termux for faster update installs.

    The normal path (``ensure_uv()`` in managed_uv) installs the managed
    standalone uv into ``$HERMES_HOME/bin/uv``, but on Termux the official
    installer may not work (glibc vs bionic).  Prefer a uv already on PATH
    (e.g. ``pkg install uv``); only if there is none do we fall back to a
    wheel-only ``pip install uv`` so we never source-build the Rust crate.
    r   )
resolve_uvNuvuL     → Termux detected: trying to install uv for faster dependency updates...)rQ  r  z--only-binaryz:all:FrJ  )r  r  r   r  r  whichr   r-   r.   r  rd   r   )r  r  existing	system_uvr5   s        r   r  r  u  s    100000z||H 44   t T""I 
\]]]AAAA!
 
 

 !!4 "    :<<-6<---s   AB 
B'&B'c                  ~   t                      j        dz  } t                      j        dz  | g}	 t          j        |                     d                                        dg           }t          |t                    r|                    dg           }|D ]s}t          t                      j        	                    t          |                              D ]0}|dz  }|                                r|                    |           1tn"# t          t          j        t          f$ r Y nw xY wt!          |          S )u  Manifests whose changes must defeat the update-skip.

    The lockfile alone is NOT a sufficient key: on a local checkout a dev
    can edit package.json (root or a workspace) without running npm — the
    lockfile is then unchanged but `hermes update` is exactly the step
    expected to sync node_modules (via the `npm install` fallback in
    _run_npm_install_deterministic).

    The workspace list is pulled from the root package.json's `workspaces`
    globs (npm's own source of truth) rather than hardcoded, so adding a
    workspace can never silently escape the skip key. The root install
    (step 1, --workspaces=false) still hoists shared deps for EVERY
    workspace — desktop included — so all of them belong in the key, not
    just the ones step 2 installs. Falls back to hashing just root
    manifests if package.json is unreadable (never skips more than main
    would have installed).
    package.jsonpackage-lock.jsonr$   rt   
workspacespackages)r   r  rw   loadsr   r   r~  r  r`   globrF   is_filer  r2   JSONDecodeError	TypeErrorr_   )root_pkgpathsr  patternmatchmanifests         r   _npm_manifest_pathsr    sA   $ tt >1HTT!44h?EZ 2 2G 2 D DEEII"
 

 j$'' 	8#
B77J! 	+ 	+G 1 6 6s7|| D DEE + + >1##%% +LL***+	+
 T)95   <<s   CD D-,D-c            	         t                      j        dz                                  sdS t          j                    } t                      D ]}|                     t          |                    t                      j                            	                                           	 |                     |
                                           # t          $ r |                     d           Y w xY w|                                 S )zCombined sha256 over the lockfile + all workspace package.json files.

    Returns None when the lockfile is missing (never skip then).
    r  Ns	   <missing>)r   r  rD   hashlibsha256r  updaterF   relative_toencode
read_bytesr2   	hexdigest)hps     r   _npm_manifests_digestr    s    
 DD 33;;== tA "" # #	Q]]244#45566==??@@@	#HHQ\\^^$$$$ 	# 	# 	#HH\"""""	#;;==s   ''CC10C1hermes_rootc                 x   t                      }|dS t                      j        dz                                  sdS t                      j        dz  }|dz                                  rt          t          |           sdS 	 t          j        t          t                      j                  
                                                                          d d         }| d| z  }|                                sdS |                    d                                          |k    S # t          $ r Y dS w xY w)	NTr   rL  r     .npm_lock_hash_r$   rt   )r  r   r  r   r  r   r   r  r  rF   r  r  rD   r   r0   r2   )r  r[  r   	cache_keyr8  s        r   _npm_lockfile_changedr    sF   #%%Gt DD.6688 t
 dd%'G. ))++ 4N	g	&	&5  tN3rtt'8#9#9#@#@#B#BCCMMOOPSQSPST	 #@Y#@#@@
  "" 	4##W#55;;==HH   tts   A:D+ ?+D+ +
D98D9c                 |   t                      }|d S 	 t          j        t          t	                      j                                                                                            d d         }| d| z  }|                    |d           d S # t          $ r t                              d           Y d S w xY w)Nr  r  r$   rt   z'Could not write npm lockfile hash cache)r  r  r  rF   r   r  r  r  r|   r2   r   r   )r  digestr  r8  s       r   _record_npm_lockfile_hashr    s    "$$F~@N3rtt'8#9#9#@#@#B#BCCMMOOPSQSPST	 #@Y#@#@@
fw77777 @ @ @>??????@s   A=B $B;:B;c                     t                      j        dz                                  sg S t                                                      } | sddlm} t          j        d          } |            r|rt                                          |          rot          d           t          d           t          d           t          d           d	g}t          d
 dD                       r|                    d           |S g S ddlm}  |            }t                                          |          st                              d           g S t          d           dt           dt"          t                    fd}g d}ddlm}  |t                                                                }	g |d}
t                                          | t                      j        t+          |
          d|	          }|j        dk    rjt          d           |j        r|j        pd                                nd}|r*t          d|                                d                      |d	          S g |dddd}t                                          | t                      j        t+          |          d|	          }|j        dk    r t5          |           t          d           g S t          d           |j        r|j        pd                                nd}|r*t          d|                                d                      |d          S ) a  Refresh Node deps in the repo root and update workspaces.

    Returns the list of labels whose npm install failed (empty on success),
    so the caller can treat a Node refresh failure as a partial update rather
    than silently reporting ``Update complete!`` (#30271).
    r  r   )is_wslnpmu$   → Updating Node.js dependencies...uC     ⚠ Skipped: only a Windows npm is reachable from this WSL shell.z@    Install Node.js inside the WSL distro (nvm, or your distro'sz2    package manager), then re-run `hermes update`.z	repo rootc              3   l   K   | ]/}t                      j        |z  d z                                  V  0dS )r  N)r   r  rD   )r   	workspaces     r   r   z,_update_node_dependencies.<locals>.<genexpr>
  sS         "Y.?GGII     r   )ui-tuirL  zui-tui, web workspacesr2  z,npm lockfile unchanged, skipping npm installlabelsr   c                      t                       t          d           t          d           t          d           t          |           S )Nu>     ⚠ Node.js dependency refresh did not complete cleanly; thezB    installation may be in a mixed state (updated code, stale Nodez.    deps). Fix npm and re-run `hermes update`.)r   r  )r  s    r   _partial_update_failurez:_update_node_dependencies.<locals>._partial_update_failure%  sD    NOOORSSS>???F||r   )z	--no-fundz
--no-auditz--prefer-offlinez--progress=falsewith_hermes_node_pathz--workspaces=falseF)
extra_argsr(   rD  u%     ⚠ npm install failed in repo rootrZ       r  z--workspacer  rL  u:     ✓ repo root + ui-tui, web workspaces (desktop skipped)u"     ⚠ npm workspace install failed)r   r  rD   _resolve_node_runtime_npmr   r  r  r  _is_windows_npm_pathr   r   r  r3  r  r   inforF   r  r  _nixos_build_env_run_npm_install_deterministicr_   rd   r  r0   r   r  )r  r  path_npmro  r3  shared_hermes_rootr  r  r  	nixos_env	root_argsroot_resultr  ws_args	ws_results                  r   r  r    s    DD.6688 	
$$
(
(
*
*C  	,+++++<&&688 	 	RTT%>%>x%H%H 	8999WXXXTUUUFGGG!]F  !2     8 6777M	888888
 102244%%&899 BCCC	 

0111 c     UTTJ666666%%bdd&;&;&=&=>>I 4*323I$$55
## 6  K ""56667B7IQ+$*11333r 	42**,,R022333&&{333 K
JMJ8J]JEJG33
>> 4  I q  !"4555JKKK		
.///1:1AIi$"++---rF 0.V&&((,..///""#;<<<r   r)   c                    | sdS t                      j        j        }t          |dd          }|dS 	 |                    |                     d          r| n| dz              |                                 dS # t          $ r Y dS w xY w)a  Write ``text`` to ``~/.hermes/logs/update.log`` only, never the terminal.

    During ``hermes update`` ``sys.stdout`` is an ``_UpdateOutputStream`` that
    mirrors to both the terminal and ``update.log``. Loud, low-signal
    subprocess output (npm installs, the Electron/vite build, the cua-driver
    installer's "Next steps" wall) should be captured and tucked into the log
    so failures stay debuggable, without flooding the user's terminal. This
    reaches past the mirroring stream straight to the underlying log handle.
    N_logr[   )r   r   r/   getattrwriter  flushr   )r)   streamlog_files      r   _log_only_writer  \  s      TTX_Fvvt,,Ht}}T22CtttDDD   s   AA8 8
BBr'   rD  c                    t          j        | ||dt           j        t           j        ddd	  	        }t	          |j        pd           |S )zRun ``cmd`` capturing combined output into update.log (not the terminal).

    Returns the ``CompletedProcess`` (with ``stdout`` populated) so the caller
    can decide whether to surface the captured output on failure.
    FTr$   r%   )r'   rD  r,   r/   r  r)   r*   r+   rZ   )r-   r.   PIPESTDOUTr  r/   )cmdr'   rD  r5   s       r   _run_logged_subprocessr  r  sV     ^ 
 
 
F FM'R(((Mr   r   )branch_explicitr  r  c          	      p
   ddl m}m}  |t                      j                  }|dk    r1ddl m} t           |                       t          j        d           |dv r,t           ||                     t          j        d           t                      j        dz  }|	                                s#t          d           t          j        d           d	g}t          j
        d
k    rg d}t          j        |ddgz   t                      j        dddd          j                                        dk    }|rddgng }	| dk    rt          j        |g dz   t                      j        dddd          j        dk    }
d}|
rFt          d           t          j        |dgz   |	z   d| gz   t                      j        dddd          }||j        dk    rd}d|  }nt          d           t          j        |dgz   |	z   d| gz   t                      j        dddd          }d}d|  }nMt          d           t          j        |dgz   |	z   d| gz   t                      j        dddd          }d}d|  }|j        dk    r|j                                        }d |v sd!|v rt          d"           nSd#|v sd$|v rt          d%           n;t          d&           |r*t          d'|                                d                     t          j        d           t          j        |dd(d)|gz   t                      j        dddd          }|j        dk    rDt          d*|  d+|                    d,d          d          d-           t          j        d           |rt          j        |dd.gz   t                      j        dddd          j                                        }t          j        |d|gz   t                      j        dddd          j                                        }|r|r||k    rt          d/           n4t          d0| d1           dd2l m} t          d3 |             d4           dS t          j        |d5d6| d7gz   t                      j        ddddd8          }t)          |j                                                  }|dk    rt          d/           dS |dk    rd9nd:}t          d;| d<| d=| d-           dd2l m} t          d3 |             d4           dS )>a  Implement ``hermes update --check``: fetch and report without installing.

    ``branch`` selects which branch the check compares against. Default is
    "main"; callers can pass another branch to ask "are there new commits
    on origin/<branch>?" without performing the update.

    ``branch_explicit`` is True iff the caller passed --branch on the CLI.
    Installs that can't honor non-default branches (e.g. Docker) surface a
    one-line notice instead of silently dropping the flag.
    r   )detect_install_method%recommended_update_command_for_methoddocker)format_docker_update_messager\   >   nixnixosr%  u6   ✗ Not a git repository — cannot check for updates.gitwin32r  rV   windows.appendAtomically=falser"   z--is-shallow-repositoryTr$   r%   r  truez--depth1r   r  Nu   → Fetching from upstream...r&  r  z	upstream/u   → Fetching from origin...r  Forigin/Could not resolve hostunable to access9   ✗ Network error — cannot reach the remote repository.Authentication failedcould not read UsernameD   ✗ Authentication failed — check your git credentials or SSH key.u   ✗ Failed to fetch.r   r  r'     ✗ Branch 'z' not found on r:   .r#   u   ✓ Already up to date.u   ⚕ Update available (behind z).)recommended_update_commandz  Run 'z' to install.r  zHEAD..r  r&   r  commitsu   ⚕ Update available: r  z behind )r   r  r  r   r  r  r   r   r  rD   r  r-   r.   r/   r0   rd   r  r   re   r  r   )r  r  r  r  methodr  git_dirr4   
is_shallow
depth_argshas_upstream_remotefetch_resultupstream_existscompare_branchr  verify_resulthead_sha
target_shar  
rev_resultbehindcommits_words                         r   _cmd_update_checkr
    s    _^^^^^^^""244#455F
 	CBBBBB**,,---!!!33F;;<<<dd&(G>> FGGGgG
|wAAA 	{$=>>!			
 	
 	

 	  &07)S!!RJ N;;;;DD%#GI	  
  	  	1222%>7)#j0J3GGDD%#GI	  L #(?1(D(D"O111NN /000%>7)#j0Hf3EEDD%#GI	  L $O/v//NN 	+,,,!~wi*,&/AA!		
 
 
  +6++!##$**,,#v--1Cv1M1MMNNNN$..2Kv2U2UXYYYY())) 536,,..q133444 N;
I~FFDDGI	  M 1$$VVVVN4H4Ha4P4PQR4SVVVWWW  >{F++!$TG\e
 
 
  	  ^{N33!$TG\e
 
 
  	  	I
 	Ix:'='=+,,,,D.DDDEEEDDDDDDG6688GGGHHH:888)DDDDGI  J "((**++F{{'(((((#)Q;;xxIWvWWWWnWWWXXX@@@@@@C2244CCCDDDDDr   c            
         t                      j        j        dk    rdS 	 t          j                    dk    rdS n# t
          $ r Y dS w xY wt          d          } |                                 s|                                 sdS t          j	        
                    d          pd}	 t          j        ddd	| d
t          j	        
                    dd           ddddgddddd          }n# t          t          j        f$ r Y dS w xY w|j        dk    rdS d}d}d}dD ]}t          |          |z  }|                                s*	 |                    dd          }n# t$          $ r Y Ow xY wt'          d |                                D                       }	|	r	 |                    dd          5 }
|
                    d|z   dz   |z   dz              ddd           n# 1 swxY w Y   n,# t$          $ r}t/          d| d|            Y d}~d}~ww xY wt/          d |            d}|rt/          d!           dS dS )"aH  Ensure /usr/local/bin is on PATH for RHEL-family root non-login shells.

    Mirrors the post-symlink probe added to ``scripts/install.sh`` so that
    existing FHS-layout root installs on RHEL/CentOS/Rocky/Alma 8+ get
    repaired on ``hermes update`` without requiring a reinstall.  The
    installer's assumption that ``/usr/local/bin`` is on PATH for every
    standard shell breaks on those distros in non-login interactive shells
    (su, sudo -s, tmux panes, some web terminals): /etc/bashrc doesn't
    add /usr/local/bin and /root/.bash_profile doesn't either.  Symptom:
    ``hermes`` prints ``command not found`` even though the symlink lives
    at /usr/local/bin/hermes.

    Silent no-op on: non-Linux, non-root, non-FHS installs, and any system
    where ``bash -i -c 'command -v hermes'`` already resolves.  Idempotent.
    linuxNr   z/usr/local/bin/hermesHOMEz/rootrD  z-izHOME=zTERM=TERMdumbbashrV   zcommand -v hermesTr$   r%   
   r(   r)   r*   r+   rX   z"export PATH="/usr/local/bin:$PATH"uK   # Hermes Agent — ensure /usr/local/bin is on PATH (RHEL non-login shells)F)z.bashrcz.bash_profile)r+   r*   c              3   v   K   | ]4}d |v o+d|v o'|                                                     d           V  5dS )/usr/local/binPATH#N)lstripr  r   r   s     r   r   z)_ensure_fhs_path_guard.<locals>.<genexpr>u	  sj       
 
  $ 2$2KKMM,,S111
 
 
 
 
 
r   art   r[   u     ⚠ Could not update rK  u&     ✓ Added /usr/local/bin to PATH in z?    (reload your shell or run 'source ~/.bashrc' to pick it up))r   r   r  r  geteuidAttributeErrorr   
is_symlinkrD   r  r   r-   r.   FileNotFoundErrorTimeoutExpiredrd   r  r   r2   r   r   openr  r   )fhs_linkr   rg   	path_linepath_comment	wrote_anyr   r  r  already_guardedr1  r5  s               r   _ensure_fhs_path_guardr%  0	  sZ     
ttxG##:<<1F     +,,H   ):): 
 :>>&!!,WD8
vv6688#	  	
 
 
 z89   14IX  I1  	4jj9${{}} 		}}I}HHHH 	 	 	H	  
 
 !++--	
 
 
 
 
  		#00 GA|+d2Y>EFFFG G G G G G G G G G G G G G G 	 	 	6C66166777HHHH	 	<s<<===		 QOPPPPPQ Qsp   : 
AA&AC- -DD
E""
E/.E/!G28"G&G2&G*	*G2-G*	.G22
H<HHc                  0   t                      j        j        dk    rdS t          j                    dz  dz  t          d          fD ]} | dz  }| dz  }	 |                                s|                                s6|                                s|                                r_d| d	}|                    |d
           |	                    |
                                j        dz             n# t          $ r Y w xY wt          d|            dS )aL  Self-heal: install a ``hermes-acp`` launcher next to the ``hermes`` one.

    Mirrors the launcher block in ``scripts/install.sh`` so existing installs
    gain the ACP command on ``hermes update`` without a reinstall.  ACP hosts
    (Zed, JetBrains, Buzz Desktop) spawn the agent by resolving the
    ``hermes-acp`` command name against the login-shell PATH; the console
    script of that name lives inside the install's venv, which is not on that
    PATH, so those hosts report Hermes as not installed even when it is.

    The shim simply delegates to the sibling ``hermes`` launcher with the
    ``acp`` subcommand, which makes it correct for every install layout
    (venv wrapper, FHS symlink, pipx/pip console script) without having to
    reconstruct interpreter/entrypoint paths.

    No-op on Windows (install.ps1 puts ``venv\Scripts`` on the user PATH, so
    ``hermes-acp.exe`` already resolves) and wherever a ``hermes-acp`` is
    already present next to the ``hermes`` command.  Unwritable directories
    (e.g. ``/usr/local/bin`` as non-root) are skipped silently.  Idempotent.
    r  Nz.localbinr  hermesz
hermes-acpu   #!/usr/bin/env bash
# Hermes Agent — ACP launcher (written by `hermes update`).
# ACP hosts (Zed, JetBrains, Buzz) resolve the agent by this
# command name on the login-shell PATH.
exec "z" acp "$@"
r$   rt   i  u(     ✓ Installed hermes-acp launcher → )r   r   r  r   r   r  r  rD   r|   chmodr   st_moder2   r   )r   
hermes_cmdacp_cmdrL  s       r   _ensure_acp_launcherr-  	  s]   ( 
ttxG##IKK(*U2D9I4J4JK D Dx'
L(	&&(( J,A,A,C,C 
 ~~ 7#5#5#7#7 2 $	2 2 2  tg666MM',,..0589999 	 	 	H	BBBCCCC/D Ds   (C3=(C3&AC33
D ?D r\   r   c                    t          | dd          rdS t          | dd          rdS 	 ddlm}  |            }nF# t          $ r9}t	          j        t                                        d|           i }Y d	}~nd	}~ww xY wt          |t                    r|
                    d
i           ni }|
                    dd          }|du rdS |du rdS t          |                                                                          }|dv rdS |dv rdS |dk    rdS t	          j        t                                        d|           dS )u  Resolve the pre-update backup mode: ``"off"``, ``"quick"``, or ``"full"``.

    CLI flags win over config; ``--no-backup`` beats ``--backup`` when both
    are set. Config accepts the mode strings plus legacy booleans:
    ``true`` → ``full`` (the old zip behavior), ``false`` → ``off``
    (an explicit opt-out now disables the quick snapshot too — previously
    it ran unconditionally, ignoring the user's setting). A missing key
    defaults to ``quick``.
    	no_backupFr   r  fullr   r   z/Could not load config for pre-update backup: %sNupdatespre_update_backupquickT)r   falsery  disabled)r0  zipr  u<   Unknown updates.pre_update_backup value %r — using 'quick')r  r   r   r   logging	getLogger__name__r   r~  r  r   rF   r0   r   r  )r  r   r  r   updates_cfgrawr   s          r   _resolve_pre_update_backup_moder<  	  s    t[%(( utXu%% v111111kmm   (##))=s	
 	
 	
 	 -7sD,A,AI#'')R(((rK
//-w
7
7C
d{{v
e||us88>>!!##D333u&&&vwwh''F   7s   9 
A</A77A<c           	         t          |           }|dk    r0t          | dd          rt          d           t                       dS d}	 ddlm}m}m} ddlm}  |d	t          t          
          }|r |            dz  }|                                r ||ddt                    }|                    d          s|                    dd          }	t          d|	             | |                      }
|
|z  dz  }|                                rR ||dd          }|                    d          rt          d           t          d           nt          d           nt          d           t                       |rt          d|            nD# t          $ r7}t          j        t                                         d|           Y d}~nd}~ww xY w|dk    r|rt                       |S 	 ddlm} n:# t          $ r-}t          d| d           t                       |cY d}~S d}~ww xY w	 ddlm}  |            pi                     di                               dd           }n# t          $ r d }Y nw xY wt          d!           t)          j                    }	  |t-          |          "          }nH# t          $ r;}t          d#|            t          d$           t                       |cY d}~S d}~ww xY wt)          j                    |z
  }|t          d%           t                       |S 	 |                                j        }n# t2          $ r d}Y nw xY w| d&}d'D ]}|d(k     r n|d(z  }|d)d*| }	 dd+lm}m}  |            }	  |             d,|                    |           }n# t:          $ r t=          |          }Y nw xY wn# t          $ r t=          |          }Y nw xY wt          d-| d.| d/|d)d0           t          d1|            t          d2           t                       |S )3u3  Run the pre-update safety backup and return the quick-snapshot id.

    Single consolidated mechanism gated on ``updates.pre_update_backup``:

    - ``off``   — nothing runs. Explicit user opt-out is honored fully.
    - ``quick`` (default) — a state snapshot of critical small files
      (pairing JSONs, cron jobs, config, auth; see ``_QUICK_STATE_FILES``)
      under ``state-snapshots/``. Files over 1 GiB are skipped with a
      warning so a bloated state.db can never stall the update
      (issues #15733, #34600 are the reason this safety net exists).
    - ``full``  — the quick snapshot PLUS a full zip of HERMES_HOME under
      ``backups/`` (restorable via ``hermes import``; the #48200 wrong-path
      wipe is the reason this level exists).

    ``--backup`` forces ``full`` for one run; ``--no-backup`` forces ``off``.
    Never raises — a backup failure should not block the update itself.

    Returns the quick-snapshot id (used by the post-update cron-jobs
    restore safety net), or ``None`` when mode is ``off`` or the snapshot
    failed.
    r   r/  Fu,   ◆ Pre-update backup: skipped (--no-backup)Nr   )rh  create_quick_snapshotri  r   z
pre-update)r:  keepmax_file_sizer   T)rk  rl  	max_bytesrm  ro  rp  u6     ⚠ state.db integrity check FAILED after snapshot: rj  u3     ✓ Snapshot copy is valid — continuing update.z>    If state.db is lost after update it will be auto-restored.ua     ✗ Snapshot copy ALSO failed integrity — the source was already corrupted before the backup.uD     ⚠ Snapshot does not contain state.db (was skipped or too large).u   ◆ Pre-update snapshot: zPre-update snapshot failed: %sr0  )create_pre_update_backupu5   ⚠ Pre-update backup: could not load backup module (z); continuing update.r   r1  backup_keep   u!   ◆ Creating pre-update backup...)r?  u     ⚠ Backup failed: z  Continuing with update.uI     ⚠ Backup skipped (no files found or write failed); continuing update.z B)KBMBGBi   r   r  )r   display_hermes_homer:   z  Saved:     (rU  zs)z  Restore:  hermes import zH  Disable:  set updates.pre_update_backup: quick (or off) in config.yaml)r<  r  r   r  rh  r>  ri  r   r   _PRE_UPDATE_SNAPSHOT_KEEP"_PRE_UPDATE_SNAPSHOT_MAX_FILE_SIZErD   r   r   r7  r8  r9  r   rB  r   r~   r   r   r   r   r2   r   rH  r  r   rF   )r  r   snapshot_idrh  r>  ri  	_get_home	_src_path
_integrity_msgr  r  r  r   rB  r   _keept0out_pathelapsed
size_bytessize_strunitr   rH  r   display_paths                              r   _run_pre_update_backuprY  	  s.   , +400Du}}4e,, 	@AAAGGG tKBQ	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	CBBBBB++*<
 
 
  %	!	j0I!! #44!%#@	  
 "~~g.. %>>)_EEDWQUWW   "6!5iikk!B!BJ",{":Z"GK"))++ #:#:'dt$ $ $ $<<00 ! U   " `    "!V   
 b   GGG 	=;k;;<<< Q Q Q(##))*JCPPPPPPPPQ v~~ 	GGG>>>>>>>   ^C^^^	
 	
 	
 	111111$")))R88<<]ANN    

-...			B++U<<<   +c++,,,)***	 o"$GYZZZ]]__,

   


    H" . .Ed
 ---t--	%IIIIIIII  	)1133RRh6J6J46P6PRRLL 	) 	) 	)x==LLL	) % % %8}}% 

D
D
D
D
DW
D
D
D
DEEE	
1x
1
1222	
TUUU	GGGs   	EF 
G#-GG6G= =
H4"H/)H4/H48:I3 3JJ(K 
L0L<LLM M+*M+O$ !"O O$ O O$ O  O$ $P ?P profile_pathrK  c                 .   	 ddl m} ddlm} ddlm} | ||          t          j                    t          j        |j	                  
                                d} |t          |           dz  |dd	           d
S # t          t          f$ r Y dS w xY w)z9Write a planned-stop marker into a specific profile home.r   )r   )_get_process_start_time)atomic_json_write)
target_pidtarget_start_timestopper_pid
written_atz.gateway-planned-stop.jsonN),:)indent
separatorsTF)r   r   gateway.statusr\  utilsr]  r  r>  r   r   	isoformatr   r2   PermissionError)rZ  rK  r   r\  r]  records         r   !_write_update_planned_stop_markerrk  
  s    %%%%%%::::::++++++ !8!8!=!=9;;",x|44>>@@	
 
 	!==!		
 	
 	
 	
 t_%   uus   A;A? ?BBpidsc                `   | st                      S ddlm} t          |           }t          j                    t          |d          z   }|rt          j                    |k     rt          |          D ]H}	  ||          s|                    |           $# t          $ r |                    |           Y Ew xY w|rt          j	        d           |rt          j                    |k     t                      }|D ]3}	  ||          r|
                    |           $# t          $ r Y 0w xY w|S )z=Wait for the given gateway PIDs to exit, returning survivors.r   )_pid_exists        g      ?)setrf  rn  r~   r   r   r  discardr   r   r  )rl  rX   rn  	remainingr   rK  	survivorss          r   %_wait_for_windows_update_gateway_exitrt  
  s|     uu******D		I  3w#4#44H
 ))H44	?? 	' 	'C'"{3'' +%%c*** ' ' '!!#&&&&&' 	K  ))H44 %%I  	{3 #c""" 	 	 	D	s$   5 BB87B8= D
D+*D+c            
      l   t                      j        dz  } t          | t                                                                }|                                sYt                      j        dz  t                                                      f}t          d |D                       rdd| dfS dS d	}	 t          j        t          |          d
|gdddddt                      j                  }n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wd |j        pd                                D             }|j        dk    r=|s;|j        pd                                                                }d|r|d         ndfS |rdd                    |dd                   fS dS )uN  Probe the project venv for the core imports the backend needs to boot.

    Runs a tiny import check inside the venv interpreter (NOT this process —
    ``hermes update`` may be driven by a different Python). Catches the
    half-updated-venv state: git checkout current but a dependency sync that
    failed or was killed partway (e.g. Windows access-denied on a loaded
    .pyd), leaving imports like ``fastapi``'s new transitive deps missing.
    Without this probe, ``hermes update`` on a current checkout prints
    "Already up to date!" and returns without ever re-syncing dependencies —
    the user's install stays broken no matter how many times they update
    (ryanc's incident, July 2026).

    Returns ``(healthy, detail)``. Never raises; unknown states report
    healthy so a probe failure can't force needless reinstalls.
    rS   rT   z.hermes-bootstrap-completec              3   >   K   | ]}|                                 V  d S r   r   )r   ms     r   r   z-_venv_core_imports_healthy.<locals>.<genexpr>
  s*      33aqxxzz333333r   Fzvenv python missing (rv   )TrZ   zimport importlib
mods = ['fastapi', 'uvicorn', 'pydantic', 'openai', 'yaml']
missing = []
for m in mods:
    try: importlib.import_module(m)
    except Exception as e: missing.append(f'{m}: {e}')
print('\n'.join(missing))
rV   Tr$   r%   r   )r(   r)   r*   r+   rX   r'   z#venv health probe failed to run: %sNc                 ^    g | ]*}|                                 |                                 +S r   r  r  s     r   r   z._venv_core_imports_healthy.<locals>.<listcomp>  s-    [[[djjll[tzz||[[[r   rZ   r   zvenv python failed to runz;    )r   r  r   rb   rD   rB  r   r-   r.   rF   r   r   r   r/   r   rd   r  r0   r+  )venv_dirri   managed_markersr,   r5   r   missingrk   s           r   _venv_core_imports_healthyr}  
  s     tt 6)H"8RTT5E5E5G5GHHHK  DD <<DD$$&&
 33?33333 	A@+@@@@@x	' 

tU+	!
 
 
    :C@@@xxxxx \[)<"(H(H(J(J[[[GAg-%2,,..99;;6JfQii/JJJ -dii,,,,8s   6<C3 3
D#=DD#exclude_pidsr  c                 	   t                                                      sg S 	 ddl}n# t          $ r g cY S w xY wt                      j        dz  }	 t          |                                                                                              t          j
                  t          j
        z   }n[# t          $ rN t          |                                                              t          j
                  t          j
        z   }Y nw xY w	 t          t                      j                                                                                                      t          j
                  t          j
        z   }nl# t          $ r_ t          t                      j                                                                      t          j
                  t          j
        z   }Y nw xY wt          | pt                                }|                    t          j                               	 |                                                                D ])}|                    t#          |j                             *n# t          $ r Y nw xY wg }	 |                    g d          }n# t          $ r g cY S w xY w|D ]}		 |	j        }
n# t          $ r Y w xY w|
                    d          }|
                    d          }|r|t#          |          |v r\	 t          t-          |                                                                                    }n8# t          t.          f$ r$ t          |                                          }Y nw xY wd                    |
                    d          pg           }|                                }t          |
                    d	          pd
                                                              t          j
                  t          j
        z   }|                    |          }|s||v rd}|sd|v r||v s|                    |          rd}|s|
                    d          pt-          |          j        }|                    t#          |          t          |          |dd         f           |S )u  Find live processes running from the project venv's interpreter.

    The hermes.exe shim guard misses the biggest lock-holder class on
    Windows: the Desktop app's backend (``python.exe -m hermes_cli.main
    serve``) and anything else running straight off ``venv\Scripts\python
    (w).exe``. Those processes keep native ``.pyd`` extensions mapped, so a
    dependency sync mid-update dies with access-denied and strands the venv
    half-updated (ryanc's brotlicffi/_sodium.pyd incidents, July 2026).

    Killing them from here is pointless — the Desktop app supervises its
    backend and respawns it within seconds — so the caller should refuse and
    tell the user to close the app instead. Returns ``(pid, name, cmdline)``
    tuples; empty off-Windows / without psutil / when nothing matches. The
    calling process and its ancestors are always excluded (a CLI ``hermes
    update`` itself runs from the venv python). Never raises.
    r   NrS   )rK  exer   cmdliner'   rK  r  r  r  r'   rZ   TrM   r   rW   )r   rb   psutilr   r  rF   resolver   r  r  r  r2   rp  r  r>  Processparentsr   rK  process_iterr  r   r   r   r+  r  r   r  )r  r  rz  venv_prefixroot_prefixskipancrG  	proc_iterprocr  rK  r  exe_normcmdline_rawcmdline_lowcwd_low	is_holderr   s                      r   _detect_venv_python_processesr    s   & 44 	   			 tt 6)HD(**,,--3355<<RVDDrvM D D D(mm))++2226::RVCDM"$$+335566<<>>EEbfMMPRPVV M M M"$$+,,2244;;BFCCbfLM .//DHHRY[[>>##++-- 	# 	#CHHS\\""""	#    +-G''(P(P(PQQ		   			 !A !A	9DD 	 	 	H	hhuoohhuoo 	ckSXX%5%5	(499,,..//5577HH$ 	( 	( 	(3xx~~''HHH	(hhtxx	228b99!''))dhhuoo+,,2244;;BFCCbfL ''44	  	[K77I 	!.+==k))W-?-?-L-L) 	 	xx1499>C#d))[#->?@@@@Ns~   ) 88AB0 0ADDA.E; ;A&G$#G$+AI< <
J	J	J' 'J65J6?K
KKA M2NNc           
      V   dg}| dd         D ]M\  }}}d}|                                 }d|v sd|v rd}nd|v rd	}|                    d
| d| d| |            Nt          |           dk    r)|                    dt          |           dz
   d           |                    d           |                    d           |                    d           |                    d           |                    d           |                    d           d                    |          S )zDExplain which venv processes block the update and how to clear them.u@   ✗ Other Hermes processes are running from this install's venv:N   rZ   serve	dashboardu4     ← Hermes Desktop backend (close the desktop app)gatewayu     ← gatewayz  PID r   z
  ... and  morezD  On Windows these keep native extension files (.pyd) locked, so thezB  dependency update would fail partway and leave a broken install.zE  Close the Hermes desktop app / other Hermes terminals, then re-run:z    hermes updatezJ  (or use `hermes update --force-venv` to proceed anyway at your own risk)r[   )r   r  rf   r+  )rG  r  rK  r   r  hintlows          r   #_format_venv_python_holders_messager  e  ss    	KE &bqbk > >T7mmooc>>[C//IDD#"D<c<<T<<W<d<<====
7||a9#g,,"2999:::	LL	LLN   
LLL   
LLO   
LL$%%%	LL]^^^99Ur   c                    t                                                      r| sg S 	 ddl}n# t          $ r g cY S w xY wt                      j        dz  }	 t          |                                                                                              t          j
                  t          j
        z   }n[# t          $ rN t          |                                                              t          j
                  t          j
        z   }Y nw xY wt          j                    h}	 |                                                                D ])}|                    t!          |j                             *n# t          $ r Y nw xY wg }| D ]}	 |                    t!          |                                                    }n# t          $ r Y Dw xY w|Kt!          |j                  }	|	|v s|	|v s|	t'          |           v ry	 |                                pd                                }
n# t          $ r Y w xY w|
                    |          r|                    |	           |S )u7  Return venv-interpreter ancestors of *pids* that hold the install open.

    On Windows a gateway started through the venv shim is a **two-process
    chain**: ``venv\Scripts\python.exe`` (the launcher, which keeps native
    ``.pyd`` files from the venv mapped) spawns the actual interpreter from
    uv's managed CPython directory (``AppData\Roaming\uv\python\...``).
    The gateway writes its PID file from the *child*, so
    ``find_gateway_pids()`` — and therefore this module's pause set — only
    ever sees the uv-side worker.

    ``_detect_venv_python_processes()`` matches on the venv path prefix, so
    the guard downstream of the pause sees the *launcher* instead. The two
    sets are disjoint, which meant a paused gateway still tripped the
    venv-holder guard and aborted the update every time (the Desktop
    "venv-blocked: N process(es) hold the install" dead-end, where the
    reported holder is a gateway the updater believes it already stopped).

    Walking one hop up from each mapped gateway PID and keeping ancestors
    that live under the project venv closes the gap. Only the venv-side
    parent is returned — unrelated ancestors (the Scheduled Task's
    ``cmd.exe``, an operator's shell) are ignored so we never widen the
    blast radius beyond the gateway's own launcher. Never raises.
    r   NrS   rZ   )r   rb   r  r   r  rF   r  r   r  r  r  r2   r>  r  r  r  r   rK  r   rp  r  r  r  )rl  r  rz  r  r  r  foundrK  r   ppidr  s              r   _venv_launcher_ancestorsr    s   0 44 T 	   			 tt 6)HD(**,,--3355<<RVDDrvM D D D(mm))++2226::RVCD
 ikk]D>>##++-- 	# 	#CHHS\\""""	#    E  	^^CHH--4466FF 	 	 	H	>6:4<<45==DCII,=,=	::<<%2,,..CC 	 	 	H	>>+&& 	LLLsW   + ::AB2 2AD
	D
"AE3 3
F ?F 
4F??
GG(H**
H76H7c                 v   ddl m} 	 ddl}n# t          $ r d}Y nw xY wg }| D ]\  }}}|}|[	 d                    |                    t          |                                                              p|}n# t          $ r Y nw xY w ||          s dS |                    t          |                     |S )u  PIDs from *matches* when every remaining venv holder is a pausable gateway.

    ``_pause_windows_gateways_for_update()`` stops every gateway its discovery
    finds, but the venv-holder guard downstream sees the process table as it
    is *now*: a gateway respawned by its supervisor (Scheduled Task, login
    watchdog) inside the pause→guard window, or one started through a spawn
    path the discovery does not map, still holds venv ``.pyd`` files and
    would dead-end the update — an abort pointed at exactly the kind of
    process the pause machinery exists to stop.

    Holders are classified with the same matcher the Desktop preflight uses
    to exempt them (``_is_pausable_gateway``), so the preflight's exemption
    and this guard's tolerance cannot drift apart — matcher drift between
    two views of the same process table is what produced the launcher/worker
    dead-end fixed above. The scan captures only a 120-char cmdline prefix,
    so the live argv is re-read where psutil allows; an unreadable argv
    falls back to the captured prefix.

    Returns ``None`` when any holder is not a pausable gateway — an operator
    REPL, a stray script, or the Desktop backend has no pause machinery
    downstream, and the guard must keep refusing exactly as before.
    r   )_is_pausable_gatewayNr  )	hermes_cli._scan_venv_blockersr  r  r   r+  r  r   r  r  )rG  r  r  rl  rK  _namer  argvs           r   _leftover_pausable_gateway_pidsr    s   2 DCCCCC    D& 	 	UGxxs3xx 8 8 @ @ B BCCNw   ##D)) 	44CHHKs    A	A88
BBc                     t                                                      sdS 	 ddlm}  ddlm}m}m}m} n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w	 t          t                               |d                              }n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w|sX	 dd	lm} |                                rdi g g dd
S n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS i 	 d  |            D             n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wi }g }	|D ]}
                    |
          }|t'          |
          |t)          |j                  <   |	                    t'          |
                     t/          t1          |j                  t'          |
                     t                                          |	          }t7          d           	 t9          t;           |                      d          }n# t          $ r d}Y nw xY wt                                          |	|          }fd|D             }g }|D ]v}
d}	  |t'          |
                    }n3# t          $ r&}t                              d|
|           Y d}~nd}~ww xY w|                    t'          |
          |d           wg }t?          tA          |          !                    |          !                    |                    D ]\}
	  | t'          |
          d           |                    t'          |
                     @# tD          tF          tH          f$ r Y Yw xY w|r2t7          dd%                    t?          |                                |r t7          dtM          |           d           |r[tO          d |D                       }t7          dtM          |           d           |tM          |          k     rt7          d           d|||dS )a  Stop running Windows gateways before mutating the checkout or venv.

    Windows scheduled/startup gateways run through pythonw.exe, so the generic
    hermes.exe concurrent-instance guard does not see them. They still import
    from the checkout and can keep files locked while ``git`` or ``uv`` updates
    the install. Stop only PIDs that the gateway discovery code identifies.
    Nr   terminate_pid)_capture_gateway_argv_get_restart_drain_timeoutfind_gateway_pidsfind_profile_gateway_processesz6Could not prepare Windows gateway pause for update: %sTall_profilesz9Could not discover Windows gateway PIDs before update: %sgateway_windows)resume_neededr4  unmapped_pidsunmappedcold_start_if_installedzACould not check Windows gateway autostart state before update: %sc                     i | ]
}|j         |S r   rK  )r   r  s     r   
<dictcomp>z6_pause_windows_gateways_for_update.<locals>.<dictcomp>,  s)     
 
 
#DHd
 
 
r   z2Could not map Windows gateway PIDs to profiles: %suB   → Stopping Windows gateway process(es) before updating Hermes...      ?      $@rX   c                     g | ]}|v|	S r   r   )r   rK  profile_processess     r   r   z6_pause_windows_gateways_for_update.<locals>.<listcomp>S  s$    QQQSC?P4P4PS4P4P4Pr   z2Could not capture argv for unmapped gateway %s: %s)rK  r  r|  u!     ✓ Paused gateway profile(s): rU  u     → Force-stopped z gateway process(es)c              3   D   K   | ]}|                     d           dV  dS )r  r\   Nr   r   us     r   r   z5_pause_windows_gateways_for_update.<locals>.<genexpr>w  s1      ??v?!??????r        → Stopped z, gateway process(es) without profile mappingz5    Restart manually after update: hermes gateway run)r  r4  r  r  )(r   rb   rf  r  hermes_cli.gatewayr  r  r  r  r   r   r   r  r  fromkeysr   r  is_installedr   r   rF   profiler  rk  r   rK   r  r   r   floatrt  r`   rp  unionProcessLookupErrorri  r2   r+  rf   r  )r  r  r  r  r  r   running_pidsr  r4  mapped_pidsrK  r  launcher_pidsdrain_timeoutrs  r  r  r  force_killedrespawnabler  s                       @r   "_pause_windows_gateways_for_updater    s    44 t
000000	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
    MsSSStttttDMM*;*;*N*N*NOOPP   PRUVVVttttt  	222222++-- %) "%' "/3    	 	 	LLS       	
 tP
 
'E'E'G'G
 
 
  P P PI3OOOOOOOOP  "HK E E $$S))<&)#hhT\""#3s88$$$)$ty//3s88DDDD DD11+>>M	
NOOOE"<"<">">??EE   :: ;  I RQQQLQQQM H 9 9	Y((S22DD 	Y 	Y 	YLLMsTWXXXXXXXX	YC$778888 Lc)nn**=99??NNOO  	M#c(($////C))))"OW= 	 	 	D	  QO$))F8<L<L2M2MOOPPP NLS%6%6LLLMMM K??X?????]S//]]]	
 	
 	
 ]++++ IJJJ &	  s   8 
A(A##A(,1B 
C(C		C!C7 7
D&D!!D&.E 
E2E--E2<%I" "I10I1.K
K7K22K7'<N$$N>=N>c                  H   t                                                      sdS 	 ddlm}  ddlm} n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w	 t           |d                    rdS n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w	 | 
                                }n3# t          $ r&}t                              d	|           Y d}~dS d}~ww xY w|r#t                       t          d
| d           dS dS )u  Start a fresh detached gateway after update when one is installed but down.

    Invoked from ``_resume_windows_gateways_after_update`` for the
    ``cold_start_if_installed`` case: no gateway was running when the update
    began, but an autostart entry (Scheduled Task / Startup-folder login item)
    is installed, signalling the user wants a gateway. Unlike the relaunch
    paths — which watch an old PID and respawn once it exits — this is a direct
    fresh spawn via the same hidden-console + breakaway path that
    ``hermes gateway start`` uses (``gateway_windows._spawn_detached``).

    Best-effort and idempotent: re-checks that nothing is running first so a
    concurrent start (e.g. the autostart entry firing) can't produce a
    duplicate gateway.
    Nr   r  )r  z5Could not load Windows gateway cold-start helpers: %sTr  z9Could not re-check gateway liveness before cold-start: %sz5Could not cold-start Windows gateway after update: %su1     ✓ Starting Windows gateway after update (PID rv   )r   rb   r   r  r  r  r   r   r   r  _spawn_detachedr   )r  r  r   rK  s       r   (_cold_start_windows_gateway_after_updater    s    44 ......8888888   LcRRR!!t44455 	F	   PRUVVV--//   LcRRR  JH#HHHIIIIIJ JsD   1 
A!AA!%B 
B1B,,B15C
 

C:C55C:list_units_stdoutc                   | pd                                                                 D ]}|                                }|s|d         }|                    d          s7|                    d          sM|                    d          }	  ||           o# t          j        $ r} |||           Y d}~d}~ww xY wdS )a  Process each ``hermes-gateway*.service`` from ``systemctl list-units``.

    ``subprocess.TimeoutExpired`` raised by ``process_unit`` is isolated to
    that unit via ``on_unit_timeout`` so one wedged systemctl call cannot
    abort the rest of the fleet (#68523).
    rZ   r   z.servicezhermes-gatewayN)r0   r   re   r  r  removesuffixr-   r  )r  process_uniton_unit_timeoutr   rj   rW  svc_namer   s           r   _for_each_systemd_gateway_unitr    s     #(b//11<<>> + +

 	Qx}}Z(( 	 /00 	$$Z00	+L""""( 	+ 	+ 	+OHc********	++ +s   BB='B88B=failed_unitsc                    | sdS t                      }g }| D ]1}||v r|                    |           |                    |           2t                       t          d           |D ]}t          d|            t          d           t          d           t          d           t          d           t          d           dS )	zBPrint an explicit incomplete-update warning for unrestarted units.Nu@   ⚠ Update incomplete — some gateway units were not restarted:z    - z;  Skipped units may still be running pre-update code (mixedz3  sys.modules). Restart them manually, then verify:z    hermes gateway statusz2    systemctl --user restart <unit>   # user-scopez4    sudo systemctl restart <unit>     # system-scope)rp  r  r  r   )r  seenorderedr   s       r   &_warn_incomplete_gateway_fleet_restartr    s     55DG  4<<t	GGG	
LMMM  otoo	
GHHH	
?@@@	
%&&&	
>???	
@AAAAAr   c                  0   t                                                      sdS 	 ddlm}  |                                 sdS |                                  t          d           dS # t          $ r&}t          	                    d|           Y d}~dS d}~ww xY w)u  Regenerate installed Windows gateway launcher scripts after update.

    The Scheduled Task / Startup-folder launchers (``gateway.cmd`` +
    ``gateway.vbs``) are persistence artifacts written once at install time —
    ``hermes update`` never touched them, so installs created before the
    hidden-console rework (aa2ae36c3f) kept launching the gateway through
    ``pythonw.exe`` forever: every descendant spawn flashed a conhost
    (#54220/#56747) and, since #70344, the console-less gateway died at
    startup with ``RuntimeError: sys.stderr is None`` (#71671).

    The task's /TR points at a stable script path, so rewriting the files in
    place retargets the task without any schtasks call (no UAC needed).
    ``_write_task_script`` is idempotent and renders from current code, so
    this is a no-op for modern installs. Best-effort: a failed refresh must
    never fail the update.
    Nr   r  u0     ✓ Refreshed Windows gateway launcher scriptsz<Could not refresh Windows gateway launchers after update: %s)
r   rb   r   r  r  _write_task_scriptr   r   r   r   )r  r   s     r   "_refresh_windows_gateway_launchersr    s    " 44 Z......++-- 	F**,,,@AAAAA Z Z ZSUXYYYYYYYYYZs   A%  #A% %
B/BBtokenc                    | r|                      d          sdS d| d<   t                                                      sdS t                                                       |                      d          pi }|                      d          pg }t	          |                      d                    }|s=t          d |D                       s$|r t                                                       dS 	 dd	lm}m	} n3# t          $ r&}t                              d
|           Y d}~dS d}~ww xY wg }t          |                                          D ]\  }}		  |t          |          t!          |	                    r"|                    t          |                     O# t          $ r&}t                              d||           Y d}~zd}~ww xY wd}
|D ]}|                     d          }|                     d          }	|r|	s1	  |t!          |	          t%          |                    r|
dz  }
^# t          $ r&}t                              d|	|           Y d}~d}~ww xY w|r3t'                       t'          dd                    |                      |
r%|st'                       t'          d|
 d           dS dS )z>Restart Windows profile gateways previously paused for update.r  NFr4  r  r  c              3   @   K   | ]}|                     d           V  dS )r  Nr  r  s     r   r   z8_resume_windows_gateways_after_update.<locals>.<genexpr>  s,      @@!f@@@@@@r   r   )*launch_detached_gateway_restart_by_cmdline'launch_detached_profile_gateway_restartz1Could not load Windows gateway restart helper: %sz=Could not restart Windows gateway profile %s after update: %sr  rK  r\   zDCould not restart unmapped Windows gateway (pid %s) after update: %su-     ✓ Restarting Windows gateway profile(s): rU  u     ✓ Restarting z% unmapped Windows gateway process(es))r   r   rb   r  r   r   r  r  r  r  r   r   r   r`   rj  rF   r   r  r  r   r+  )r  r4  r  
cold_startr  r  r   
relaunchedr  old_pidunmapped_relaunchedr-  r  s                r   %_resume_windows_gateways_after_updater    s    		/22 "E/44 
 DD++---yy$$*Hyy$$*Heii 9::;;J C@@x@@@@@  	<DD99;;;	
 	
 	
 	
 	
 	
 	
 	
 	
    H#NNN J"8>>#3#344 	 		66s7||S\\RR 0!!#g,,/// 	 	 	LLO       	   yy  ))E"" 	7 		99#g,,T

SS )#q(# 	 	 	LLV       	  WUdii
>S>SUUVVV 
 	GGGZ 3ZZZ	
 	
 	
 	
 	

 
sI   1C: :
D*D%%D*AF
G)G

G+H44
I$>II$c           	         	 t          j        | ddgz   |dddd          }|j        dk    rdS d	 |j                                        D             fd
|j                                        D             }|sdS t          j        | ddg|z   |ddddd           t          dt          |           d           dS # t          $ r Y dS w xY w)av  Restore tracked ``package-lock.json`` files that npm dirtied locally.

    npm rewrites lockfiles non-deterministically at install/build time. On a
    managed install those diffs are never intentional, so we discard them so
    ``hermes update`` sees a clean tree instead of autostashing every run.
    Best-effort; only ever touches files named ``package-lock.json``.
    r  r  Tr$   r%   r  r   Nc                     h | ]O}|                                                     d           )t          |                                           j        PS )r  r0   r  r   r   r  s     r   	<setcomp>z*_discard_lockfile_churn.<locals>.<setcomp>Z  sW     
 
 
zz||$$^44
%
 
 
r   c                     g | ]e}|                                                     d           r<t          |                                           j        vQ|                                 fS )r  r  )r   r   dirty_package_dirss     r   r   z+_discard_lockfile_churn.<locals>.<listcomp>_  sn     
 
 
zz||$$%899
 TZZ\\"")1CCC JJLL DCCr   checkout--Fr&   u"   → Discarded npm lockfile churn (	 file(s)))r-   r.   rd   r/   r   r   rf   r   )r4   	repo_rootr  dirtyr  s       @r   _discard_lockfile_churnr  I  sC    ~v}--		
 
 
 ?aF
 
..00
 
 


 
 
 
..00
 
 
  	Fz40%00		
 	
 	
 	
 	H3u::HHHIIIII   s   *B? A
B? ;AB? ?
CCc           
      8   | ddgz   fdfdfd}	 t          j        | g dz   dddd	
          }|j                                                                        dk    rdS  |            }|dS |rmt          j        g dz   d                    t          |                    dddd	d            |            rdS t          dt          |           d           t          j        | g dz   dd           dS # t          $ r Y dS w xY w)ub  Take a managed checkout off ``core.autocrlf=true`` without leaving it dirty.

    Git for Windows ships ``core.autocrlf=true`` in its system config, which
    renormalizes this repo's LF text files to CRLF in the working tree. That
    breaks ``git checkout`` on update with "Your local changes would be
    overwritten", so ``install.ps1`` pins ``core.autocrlf=false`` on the managed
    clone (#67730). Checkouts created before that landed never got the pin and
    cannot receive it — the bootstrap installer reuses its build-pinned
    ``install.ps1`` forever — so ``hermes update``, which ships with the checkout
    itself, is the only path left that can fix them.

    The pin and the cleanup are one operation. Under ``autocrlf=true`` git
    compares normalized content, so a CRLF working tree reads clean; pinning
    alone would expose every text file as modified and hand the update an
    autostash of the whole tree. So the pin is written only after the tree is
    verified clean under it, and a checkout we cannot fully normalize is left
    exactly as it was. Best-effort: never blocks an update.
    rV   zcore.autocrlf=falsec                      t          j        dddg| z   dddd          }|j        dk    rd S d	 |j                            d
          D             S )Nr  z-zr  Tr$   r%   r  r   c                     h | ]}||S r   r   )r   r  s     r   r  z9_normalize_managed_eol.<locals>._dirty.<locals>.<setcomp>  s    777aQ7777r    )r-   r.   rd   r/   re   )extraoutrg   r  s     r   _dirtyz&_normalize_managed_eol.<locals>._dirty  sr    nVT=9599		
 
 
 >Q4773:++D117777r   c                  v   t          j        g dz   dddd          } | j        dk    rd S t                      }| j                                        D ]c}|                                s|                    dd          }t          |          d	k    r#|d         r|	                    |d                    d|S )
N)rV   zcore.quotepath=falser  z	--numstatz--ignore-cr-at-eolTr$   r%   r  r   	   rY   )
r-   r.   rd   rp  r/   r   r0   re   rf   r  )r  r  r   rj   rg   r  s       r   _real_dirtyz+_normalize_managed_eol.<locals>._real_dirty  s     n @ @ @ @	
 
 
 >Q4J))++ 	$ 	$D::<<  JJtQ''E5zzQ58		%(###r   c                  B                              }} | |d S | |z
  S r   r   )	all_dirty
real_dirtyr  r  s     r   	_eol_onlyz)_normalize_managed_eol.<locals>._eol_only  s3     &++--:	
 24:%%r   )configz--getcore.autocrlfTr$   r%   r  r  N)r  z--pathspec-from-file=-z--pathspec-file-nulr  r  F)r'   r  r(   r)   r*   r+   r,   u"   → Normalized line-ending churn (r  )r  r	  r4  r(  )
r-   r.   r/   r0   r   r+  r`   r   rf   r   )r4   r  r  	effectiveeol_onlyr  r  rg   s    `   @@@r   _normalize_managed_eolr  s  s   * t233E	8 	8 	8 	8 	8 	8     8& & & & & &)N::::		
 
 
	 !!##))++v55F9;;F 	Q NUUUViix 0 011#GI    y{{  Os8}}OOOPPP::::		
 	
 	
 	
 	
 	
    s%   AD /D =AD =D 
DDgateway_modec                 l_   |rd^dnd}t          t          | dd                    }|p>|p<t          j                                        ot          j                                         }d}|r	 ddlm}  |            pi                     di           }t          |t                    r;t          |                    d	d
                                                    }|dk    }n4# t          $ r'}	t                              d|	           d}Y d}	~	nd}	~	ww xY wt!          d           t!                       t#                                                      rt          | dd          svt#                                                      }
|
Tt#                                          |
          }|r1t!          t+          ||
                     t          j        d           t#                                          |           }t#                                                      }|r+ddl}|                    t#                      j        |           t#                                                      rWt          | dd          sEt#                                                      }|rt#                                          |          }|ddlm} t!          dtA          |           d           |D ]P}	  |tC          |          d           # t          $ r&}	t                              d||	           Y d}	~	Id}	~	ww xY wtE          j#        d           t#                                                      }|rQt!          tI          |                     t#                                          |           t          j        d           d}t#                      j%        dz  }|&                                sEt          j'        dk    rd}n2t!          d           t!          d           t          j        d           t          j'        dk    r?|&                                r+tQ          j)        g dt#                      j%        dd           dg}t          j'        dk    rg d }tU          |t#                      j%                   tW          |t#                      j%                   t#                      ,                    |t#                      j%                  }t[          |          }|r/t!          d!           t!          d"|            t!                       |rZ	 t]          |            t#                                          |           n&# t#                                          |           w xY wdS 	 t#                      /                    |           }t!          d#           tQ          j)        |d$d%|gz   t#                      j%        ddd&d'(          }|j0        dk    r|j1        2                                }d)|v sd*|v r>t!          d+           t!          |rd"|3                                d          nd           nSd,|v sd-|v rt!          d.           n;t!          d/           |r*t!          d"|3                                d                     t          j        d           tQ          j)        |g d0z   t#                      j%        ddd&d'd1          }|j        2                                }||k    r|d2k    rd3nd4| d5}t!          d6| d7| d8           t#                      4                    |t#                      j%                  }tQ          j)        |d9|gz   t#                      j%        ddd&d'(          }|j0        dk    rtQ          j)        |d9d:|d;| gz   t#                      j%        ddd&d'(          } | j0        dk    r|7t#                      5                    |t#                      j%        |d|<           t!          d=| d>           | j1        2                                rAt!          d"| j1        2                                3                                d                     t          j        d           n3t#                      4                    |t#                      j%                  }|duo@| o=|p;t          j                                        ot          j                                        }!tQ          j)        |d?d@| dAgz   t#                      j%        ddd&d'd1          }tC          |j        2                                          }"|"dk    rutm                       |r9|dBk    r3t#                      7                    |t#                      j%                   |7t#                      5                    |t#                      j%        ||!|<           ||d2hvr1tQ          j)        |d9|gz   t#                      j%        ddd&d'd1           ddCl8m9}#m:}$ g }% |$|%j;        D            |#|%j;        D           ty          dE |%D             d          }&t{                      \  }'}(|'st!          dF           t!          d"|(            t!          dG           t}                       ddHl8m9}#  |#            })t          t#                      j%        dIz  t#                                                      J          &                                 }*|*r<|)r:t!          dK           tQ          j)        |)dIdIgt#                      j%        dL           |)rZi t          jA        dMt          t#                      j%        dIz            i}+t#                      B                    |)dNg|+dOP           n0t#                      B                    t          jC        dQdNgdOR           t#                      D                                 t{                      \  },}-|,rt!          dS           n1t!          dT|-            t!          dU           nt!          dV           |&[t#                                                      s;t!                       t!          dW           t!          dX           t!          dY           t#                                          |           dS t!          dZ|" d[           t!          d\           d}.t          |t#                      j%                  }/	 tQ          j)        |d]d^d;| gz   t#                      j%        ddd&d'(          }0|0j0        dk    rt!          d_           tQ          j)        |d`dad;| gz   t#                      j%        ddd&d'(          }1|1j0        dk    r{t!          db| dc           |1j1        2                                r)t!          d"|1j1        2                                            t!          dd|            t          j        d           t          t#                      j%                  \  }2}3}4|2st!                       t!          de           t!          d"|3            |4r>t          |4          3                                ddf         D ]}5t!          dg|5            |/rt!                       t!          dh|/ddi          dj           tQ          j)        |d`da|/gz   t#                      j%        ddd&d'(          }6|6j0        dk    rt!          dk           t!          dl           nt!          dm           t!          dnt#                      j%         do|/            |6j1        2                                rBt!          dp|6j1        2                                3                                d          dq           nAt!                       t!          dr           t!          dnt#                      j%         ds           t          j        d           d}.||.s$t!          dt| dq           t!          du           n
|r5t#                      G                    |t#                      j%        |           nt#                      5                    |t#                      j%        ||!|<           n# ||.s#t!          dt| dq           t!          du           w |r5t#                      G                    |t#                      j%        |           w t#                      5                    |t#                      j%        ||!|<           w w xY wtm                       t#                      H                    t#                      j%                  }7|7rt!          dv|7 dw|7dk    rdxndy            t#                      I                                 |r9|dBk    r3t#                      7                    |t#                      j%                   t}                       t!          dz           ddCl8m9}#m:}$  |$              |#            }8t          jC        dQdNg}9|8st          |9          }8dO}:|8r
i t          jA        dMt          t#                      j%        dIz            i};t#                      K                    |;          r=|;L                    d{d           |;L                    d|d           d}}:t!          d~           t#                      K                    |;          r0t                      r"t!          d           t          |8dNg|;           t#                      B                    |8dNg|;|:P           n*t          jC        dQdNg}9	 tQ          j)        |9dgz   t#                      j%        dd           nL# tP          jO        $ r: tQ          j)        t          jC        dQdddgt#                      j%        dL           Y nw xY wt#                      K                                rd}}:t!          d           t#                      K                                r,t                      rt!          d           t          |9           t#                      B                    |9|:R           |8r|8dNgn|9}<|8r|;nd}=t#                      D                                 t#                      H                    t#                      j%                  }7|7rt!          dv|7 dw|7dk    rdxndy            t#                      I                                 t#                      P                                 t                       t#                      R                    |<|=           t#                      S                    |<|=          }>|>r!t#                      T                                 nt!          d           t#                      U                                 t          t#                      j%                  \  }?}@}A|?sQt!                       t!          d|@ d           t!          d|A            t!          d           t!          d           t                      }Bt#                      X                    t#                      j%        dz             t#                      j%        dz  dz  }Ct#                      Y                    |C          dup t#                      Z                    |C          }D|Cdz  &                                rt#                      [                                r|Drt!          d           d}E	 t#                      \                    |Ct#                      j%        d           }En# t          $ r d}EY nw xY w|Ert!          d           n:t          jC        dQdddg}Fddl]m^}G  |G            }Ht#                      _                    |Ft#                      j%        |H          }I|Ij0        dk    r5t#                      _                    |Ft#                      j%        |H          }I|Ij0        dk    rt!          d           d`                    |Ij        pd2                                3                                dd                   }J|Jrt!          |J           ddl]ma}K t!          d |K             d           nt!          d           t!                       t!          d           	 ddlbmc}Lmd}M t                      dz  }N|N&                                r |M|Ndd          }O|O                    d          r0t                              d|O                    d                     nTt!                       t!          d|O                    dd          z              |}P|Pr |Lt                                |Pz  dz  }Q|Q&                                r |M|Qdd          }R|R                    d          r	 ddlf}S|Sg                    |Q|N            |M|Ndd          }T|T                    d          rt!          d|P dq           nt!          d           nX# t          $ r}Ut!          d|U            Y d}U~Un7d}U~Uww xY wt!          d           nt!          d           nt!          d           t!                       n2# t          $ r%}	t                              d|	           Y d}	~	nd}	~	ww xY w	 ddlimj}V  |Vt#                      j%                  rt!          d           n2# t          $ r%}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 ddlkml}X t!                       t!          d            |Xd          }|d         rAt!          dtA          |d                    dd`                    |d                               |                    d          rAt!          dtA          |d                    dd`                    |d                               |                    d          r5t!          dtA          |d                    d           t!          d           |                    d          r&t!          dtA          |d                    d           |                    d          rAt!          dtA          |d                    dd`                    |d                               |d         s$|                    d          st!          dæ           n2# t          $ r%}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 ddlmmn}Ymo}Z  |Y            }[|[rxt!                       t!          dƦ           |[D ]W}\	  |Z|\jp        d          }]|]r|]                    dǦ          rd}^n|]rtA          |]                    dg                     }_tA          |]                    dg                     }`tA          |]                    dg                     }ag }b|_r|b;                    d|_ dʝ           |`r|b;                    d|` d̝           |ar|b;                    d|a dΝ           |brd`                    |b          nd}^nd}^t!          d"|\jq         d|^            &# t          $ r&}ct!          d"|\jq         d|c dq           Y d}c~cQd}c~cww xY wn# t          $ r Y nw xY w	 ddlmmr}d  |dd          }e|erCt!                       t!          dtA          |e           dd`                    |e                      n# t          $ r Y nw xY w	 ddlsmt}f  |f            }g|grt!          d|g d؝           n# t          $ r Y nw xY wt!                       t!          d٦           ddlmu}hmv}imw}jmx}k  |hdۦ          }l |i            }m |j            \  }n}ot          |lp|m          }p|p o|n|ok     }q|pp|n|ok     }r|qr|t!                       t!          d|n d|o dޝ           	  |kddߦ           t!          d           n;# t          $ r,}st!          d|s            t!          d           Y d}s~sn
d}s~sww xY w|rrt!                       d_d}t|lr-t!          dtA          |l           d            |t|ldd           |mr-t!          dtA          |m           d            |t|mdd           t!                       |rt!          d           dx}un|r5t          dd          2                                                                }unt          j                                        rt          j                                        st!          d           d}unG	 t          d          2                                                                }un# t          $ r d}uY nw xY w|udv rot!                       |p|p|udk     }v |k|vdߦ          }w|wd         s|wd         rt!                       t!          d           |s|s|udk    r|lrt!          d           n-t!                       t!          d           nt!          d           	 ddlbm|}x  |x|          }y|yr0t!                       t!          d|yd          d|yd          dc           n2# t          $ r%}	t                              d|	           Y d}	~	nd}	~	ww xY wt!                       |BrHt!          dd`                    |B           d            t!          d           t!          d           nt!          d           	 t                       n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 t                       n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 t                       n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 t                       n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 t                       n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w	 d}z	 ddlm}  |            pi                     di           }t          |t                    r$t          |                    d	d                    }zn3# t          $ r&}{t                              d
|{           Y d}{~{nd}{~{ww xY w|zrXt          j'        dv rIt          j        d          r4ddlm}| t!                       t!          d            ||ddd           n3# t          $ r&}Wt                              d|W           Y d}W~Wnd}W~Www xY w|r=t                      dz  }}	 |}                    dd&           n# t          $ r Y nw xY wd}~	 ddlm}m}m}m}m}m}m}mm} ddl}	 d`dt"          dt          dt$          dt           fd	 dadt"          dt          dt$          dt$          fdi dt          dt"          dt          ffd	 dd lm} t)          t%           |                      d!          n# t          $ r d!Y nw xY wg g t+                      g }g } |            r	  |             n# t          $ r Y nw xY wd"d#d$gfd%d#gffD ]\  	 tQ          j)        g d&z   ddd&d'di'          }nP# t,          $ r Y 5tP          j        $ r2}	t!          d( d)|	j        r|	j        nd* d+           Y d}	~	pd}	~	ww xY wd,t          ddff	d-}d,t          d.tP          j        ddffd/}t3          |j        ||0            |            r	 dd1lm}m}m}  |            }|&                                rtQ          j)        d2d3 |            gddd&d'd4'          }|j0        dk    r~	  |             ;                     |                       nU# tP          jO        $ rC}Wt          |Wd5d          pd2                                }t!          d6|            Y d}W~Wnd}W~Www xY wn$# t,          tP          j        t:          f$ r Y nw xY w |            } ||d7          fd8 ||9          D             }|                                D ]\  }} ||j        |          }|t!          d|j         d:| d;tC                     d<            |=          }|s5	 t          j        ||j                   n# tD          tF          f$ r Y nw xY w |d>d?                               |           |d@k    r|;                    |j                   |;                    |j                   퉭D ]P}||v r	 t          j        ||j                                       |           8# tD          tF          f$ r Y Mw xY wsrt!                       D ]}t!          dA|            |r(d`                    |          }t!          dB|            |r(d`                    |          }t!          dC|            tA                    tA          |          z
  tA          |          z
  }|r;t!          dD| dE           t!          dF           |dk    rt!          dG           rAd}~|r=t                      dz  }}	 |}                    dHd&           n# t          $ r Y nw xY wtK                     ss	 	 tE          j#        dI            |            } ||d7          }fdJ|D             }|r|t!                       t!          dtA          |           dK           ddlm} |D ]/}	  ||d           # tD          tF          t          f$ r Y ,w xY wtE          j#        dL           n3# t          $ r&}t                              dM|           Y d}~nd}~ww xY wn3# t          $ r&}Wt                              dN|W           Y d}W~Wnd}W~Www xY wt#                                          |           	 ddOlm}m}m}  |            r |            rt!                       t!          dP            |            D ]&\  }}}|rd%nd"t!          dg| dQ dR           't!                       t!          dS           t!          dT           t!          dU           t!                       t!          dV           t!                       t!          dW           n3# t          $ r&}Wt                              dX|W           Y d}W~Wnd}W~Www xY wtQ          |B           t!                       t!          dY           t!          dZ           |~rt          j        d           dS dS # tP          jO        $ r}Wt          j'        dk    rAt!          d[|W            t!          d\           t!                       t]          |            n-t!          d]|W            t          j        d           Y d}W~WdS Y d}W~WdS d}W~Www xY w(b  uj   Body of ``cmd_update`` — kept separate so the wrapper can always
    restore stdio even on ``sys.exit``.rZ   c                 "    t          | |          S r   )r   )rr   ro   s     r   <lambda>z"_cmd_update_impl.<locals>.<lambda>  s    OFG$D$D r   Nr  Fr   r   r1  non_interactive_local_changesr  rq  z8Could not read updates.non_interactive_local_changes: %su   ⚕ Updating Hermes Agent...r}  r  
force_venvr  rc  zG gateway process(es) still hold the venv after the pause; stopping themTr|  z&Could not stop leftover gateway %s: %sr  r%  r  u+   ✗ Not a git repository. Please reinstall:zD  curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bashr\   )r  rV   r  r  zwindows.appendAtomicallyr4  rF  r  r  u   ⚠ Updating from fork:r   u   → Fetching updates...r&  r  r$   r%   r  r  r  r  r  r  r  u(   ✗ Failed to fetch updates from origin.)r"   z--abbrev-refr#   r&   r#   zdetached HEADzbranch ''u     ⚠ Currently on u    — switching to z for update...r  z-Br  )r  r  r  z&' does not exist locally or on origin.r  zHEAD..origin/r  r   r;  )repair_observerc              3   (   K   | ]}|j         	|V  d S r   )rd  )r   r5   s     r   r   z#_cmd_update_impl.<locals>.<genexpr>  s)      IIFIIIIIIIr   u3   ⚠ Checkout is current, but the venv is unhealthy:u$   → Repairing Python dependencies...)r<  rS   rT   u%   → Recreating virtual environment...rJ  r@  r?  r   )rD  groupr>  )r  u   ✓ Dependencies repaired!u'   ⚠ Venv still unhealthy after repair: z=  Close all Hermes windows/gateways and re-run: hermes updateu   ✓ Already up to date!uA   ⚠ Restart required to finish the managed Python runtime repair.zn  Any running Hermes gateways, Desktop backends, or other long-lived processes still use the previous runtime.z7  Restart each of them to pick up the repaired runtime.u
   → Found z new commit(s)u   → Pulling updates...merger*  uP     ⚠ Fast-forward not possible (history diverged), resetting to match remote...r  r  u   ✗ Failed to reset to origin/r  z<  Try manually: git fetch origin && git reset --hard origin/u6   ✗ Pulled code has a syntax error in a critical file:r  r  u   → Rolling back to r  rb  u6     ✓ Rollback complete — your install is unchanged.z5  Try ``hermes update`` again later once a fix lands.u-     ✗ Rollback failed. Recover manually with:z    cd z && git reset --hard z    (rv   u;     Could not capture pre-pull SHA — recover manually with:z- && git reflog && git reset --hard <prev-sha>u1     ℹ️  Local changes preserved in stash (ref: z(  Restore manually with: git stash applyr6  r7  r8  r9  r:  rA  rB  z
termux-alluH     → Termux detected: using uv + curated termux-all optional profile...uY     → Termux/Android detected: prebuilding psutil with Linux source path compatibility...rC  rE  rG  rH  rI  uC     → Termux detected: using curated termux-all optional profile...ua     ⚠ Lazy-refresh recovery incomplete — run `hermes` again to finish import-based venv repair.z& still fails to import after updating:z      u<       Run `hermes update` again — if it persists, reinstall:z)    https://hermes-agent.nousresearch.comrL  appsdesktopr  u/   → Checking if desktop app needs rebuilding...)source_modeu     ✓ Desktop app up to daterM   z--build-onlyr  r  uE     ⚠ Desktop build failed (non-fatal; run `hermes desktop` to retry)r[   i)rH  z  Full build log: z/logs/update.logu   ✓ Code updated!rg  r   rj  rm  z(Post-update state.db integrity check: %sro  rn  rp  u.     ✓ Auto-restored from pre-update snapshot (rt  ru  u/     ✗ Pre-update snapshot also failed integrityu3     ⚠ Pre-update snapshot does not contain state.dbu&     ⚠ No pre-update snapshot was takenz/Post-update state.db integrity check failed: %srd  rf  z+Model catalog seed during update failed: %srM  rO  rP  rR  rS  rT  rU  rV  rW  rX  rY  rZ  r[  r\  r]  r^  r_  r`  ra  rb  rc  z$Skills sync during update failed: %s)list_profilesseed_profile_skillsu-   → Syncing bundled skills to all profiles...skipped_opt_outzopted out (--no-skills)+z newu   ↑z updated~z user-modifiedz
up to datezsync failedrK  z	: error ()backfill_profile_envsu   → Seeded .env for z# profile(s) (copied from default): )sync_honcho_profiles_quietz
-> Honcho: synced z profile(s)u-   → Checking configuration for new options...)get_missing_env_varsget_missing_config_fieldscheck_config_versionmigrate_config)required_onlyu     ℹ Updating config format (vu    → vu   )…)interactiverQ  u:     ✓ Config format updated (no new settings to configure)u'     ⚠️  Config format update failed: z*     Run 'hermes config migrate' to retry.c                    | sd S t          d| d           | d d         }|D ]}t          |t                    rX|                    |          p|r|                    |          pd}|                    d          pd                                }nt          |          }d}|rt          d| d|            t          d|            t          |           t          |          z
  }|d	k    rt          d
| d           d S d S Nr   rc     ?descriptionrZ   u
         • u    — r   u         … and r  r   r~  r  r   r0   rF   rf   	rj  r:  keyfallback_keyshownitr   descr  s	            r   _print_itemsz&_cmd_update_impl.<locals>._print_items  s>    Fm5mmm$$$bqb	 3 3B!"d++ "!vvc{{\|/T|@T@T\Y\ "} 5 5 ;BBDD  #2ww! 3<4<<d<<====14112222E

SZZ/1997577788888 9r   u
     ⚠️  z+ new required setting(s) need configurationzNew settingsr   u
     ℹ️  z new config option(s) availablezNew optionsr/  uG     ℹ --yes: auto-applying config migration (skipping API-key prompts).z2Would you like to configure new options now? [Y/n]r%  uB     ℹ Non-interactive session — applying safe config migrations.autoz-Would you like to configure them now? [Y/n]: >   rZ   r8  r  r5  	env_addedconfig_addedu   ✓ Configuration updated!u:     ℹ API keys require manual entry: hermes config migratez8Skipped. Run 'hermes config migrate' later to configure.u!     ✓ Configuration is up to date)restore_cron_jobs_if_emptieduC     ⚠️  cron/jobs.json lost jobs during this update — restored 	job_countz! job(s) from pre-update snapshot rL  z'Cron jobs auto-restore check failed: %srv  rw  rx  ry  rz  zFTS optimize notice failed: %sr{  r|  zFHS PATH guard check failed: %sz(hermes-acp launcher self-heal failed: %srefresh_cua_driverz-Could not read updates.refresh_cua_driver: %s)darwinr  r  z
cua-driver)install_cua_driveru+   → Refreshing cua-driver (Computer Use)...)upgraderequire_confirmed_updateshow_installer_progresszcua-driver refresh failed: %sz.update_exit_code0rt   )	is_macossupports_systemd_services_ensure_user_systemd_envr  r  '_prepare_profile_gateway_update_restart_get_service_pids_graceful_restart_via_sigusr1_wait_for_gateway_exitr  
scope_cmd_	svc_name_rX   r   c                 `   t          j                    t          |d          z   }	 	 t          j        | d|gz   ddddd          }|j                                        dk    rdS n# t          t          j        f$ r Y nw xY wt          j                    |k    rd	S t          j	        d           )
az  Poll ``systemctl is-active`` until the unit reports active.

                systemd's Stopped -> Started transition after a graceful exit
                (or a hard restart) is not instantaneous; a one-shot check
                races that window and falsely reports the unit as down.
                Poll every 0.5s up to ``timeout`` seconds before giving up.
                ru   T	is-activer$   r%   rD  r  rl  F
r~   r   r   r-   r.   r/   r0   r  r  r   rH  rI  rX   r   _verifys        r   _wait_for_service_activez2_cmd_update_impl.<locals>._wait_for_service_activev  s     !?,,s7C/@/@@%
",.&+y)AA+/!%	$%	# # # #>//11X==#'4 >-z/HI   ((H44$uK$$$%   <A& &A?>A?ro  ro   c           
         	 t          j        | d|ddgz   ddddd          }n# t          t           j        f$ r |cY S w xY w|j        pd	                                }|r|d
k    r|S d}d}|                                D ]`}dD ][\  }}	|                    |          rA	 |t          |dt          |                              |	z  z  }d}n# t          $ r Y nw xY w n\a|r|n|S )a  Read the unit's ``RestartUSec`` (RestartSec) in seconds.

                After a graceful exit-75, systemd waits ``RestartSec`` before
                respawning the unit.  Callers that poll for ``is-active``
                must use a timeout >= ``RestartSec`` + transition slack, or
                they'll give up *during* the cooldown window and wrongly
                conclude the unit didn't relaunch.
                show--property=RestartUSec--valueTr$   r%   rD  r  rZ   infinityro  F)msgMbP?)usgư>)ming      N@)rY  r  Nr-   r.   r  r  r/   r0   re   r  r  rf   r   
rH  rI  ro   _showr;  totalmatchedpart_suf_mults
             r   _service_restart_secz._cmd_update_impl.<locals>._service_restart_sec  sb   #&N""%4%	 (,!GI !  EE *:+DE # # #"NNN#|)r0022  #cZ//"NIIKK " "D( " "e  ==.. "% %tLs4yyjL/A)B)BU)J J*.#- % % % $%!E" !(4uuW4   !$ ??-C
CCscope_c                    | v r|          S |dgz   }| dk    rt          t          d          rt          j                    dk    rddg|z   dgz   }d}	 t          j        g dd	d
          }|j        dk    }|s't          j        |d|gz   d	d
          }|j        dk    }n# t          t          j        f$ r d}Y nw xY w|r|nd}|| <   |S )u  Resolve the command prefix for manage-units operations.

                Read-only systemctl calls (``is-active``, ``show``,
                ``list-units``) work unprivileged, but manage-units verbs
                (``reset-failed``, ``start``, ``restart``) on a *system*
                service trigger a polkit ``org.freedesktop.systemd1.manage-units``
                authentication prompt when run as a non-root user.  That
                interactive prompt runs inside our captured subprocess with a
                10-15s timeout — the user sees the prompt flash and "exit
                directly" before they can answer, and the resulting
                TimeoutExpired used to be swallowed silently.

                Strategy: if root, plain systemctl.  If not root, try
                non-interactive sudo (``sudo -n``) — first a blanket probe,
                then a targeted ``systemctl reset-failed`` probe so a
                least-privilege sudoers entry scoped to
                ``systemctl ... hermes-gateway*`` also qualifies
                (``reset-failed`` is an idempotent no-op we run before every
                privileged restart anyway).  If neither works, return None —
                the caller must SKIP the restart (without draining the
                gateway first!) and tell the user how to restart manually.
                ``--no-ask-password`` guarantees polkit can never hang a
                captured subprocess on this path.
                z--no-ask-passwordsystemr  r   sudo-nF)rg  rh  r  TrD  )r(   rX   reset-failedN)hasattrr  r  r-   r.   rd   r  r  )rd  rH  rI  r  sudo_cmdsudo_ok_probe_manage_cmd_caches          r   _resolve_manage_cmdz-_cmd_update_impl.<locals>._resolve_manage_cmd  s:   2 ...,V44 $7#88h&&I.. '
)) &~
:>Q=RRH#G(!+222+/$%" " "
 #)"3q"8& 	= &0^ (NI+F F/3()& & &F
 '-&71&<G-z/HI ( ( ("'(&-7((4C,/!&)
s   AB" "B=<B=)_get_restart_exit_wait_budgetg     F@user	systemctlz--userrf  )z
list-unitszhermes-gateway*z--plainz--no-legendz
--no-pagerr  u"     ⚠ systemctl timed out listing z-scope gateway units (unknown commandz0). Check the gateway with: hermes gateway statusr  c                 J  	 t          j        d| gz   ddddd          }|j                                        dk    rd S  |           }d}	 t          j        d	| d
dgz   ddddd          }t	          |j        pd                                pd          }n$# t
          t           j        t          f$ r d}Y nw xY wd}|dk    r0t          d|  dt	                     d            |          }|r|ct          j        |d| gz   ddddd           t          j        |d| gz   ddddd            | d          r	                    |            d S  | d          }t          d|dz             }|)|dk    r#t          d|  dt	          |           d            | |          r	                    |            d S t          d|  d           |-	                    |            t          d|  d|  d            d S t          j        |d| gz   ddddd           t          j        |d!| gz   ddddd          }|j        dk    r | d          r	                    |            d S t          d|  d"           t          j        |d| gz   ddddd           t          j        |d!| gz   ddddd            | d          r*	                    |            t          d#|  d$           d S 	                    |            d%k    rd&nd}	d'k    rd(nd}
t          d)|  d*|
 d+|	 d,|  d-|
 d.|	 d/|  d0|
 d.|	 d1|             d S 	                    |            t          d2|  d3|j                                                    d S )4NrK  Tr$   r%   rD  r  rl  r   rR  z--property=MainPIDrT  rZ   Fra  z: draining (up to s)...r  ri  r  start   r  r  ro  )ro         @z%: waiting for systemd auto-restart (~z's; no root for an immediate restart)...rc  u0    drained but didn't relaunch — forcing restartz is a system service and restarting it needs root.
    Restart it manually to load the new version:
      sudo systemctl restart zz
    To let `hermes update` restart it automatically, allow
    passwordless sudo for systemctl, or run updates with sudo.restartz  died after restart, retrying...r_  z recovered on retryrq  z--user rf  zsudo u     ✗ z7 failed to stay running after restart.
    Check logs: zjournalctl z-u z1 --since '2 min ago'
    Recover manually:
      z
systemctl zreset-failed z
      zrestart u     ⚠ Failed to restart rK  )r-   r.   r/   r0   r   r   r  r  r   r  r   rd   r  )r  r,   _manage_cmd	_main_pidr\  _graceful_ok_restart_sec_post_drain_timeoutrz  _scope_flag
_sudo_hint_drain_budgetrF  ro  rb  rO  failed_or_stale_unitsrestarted_servicesscope	scope_cmds              r   !_restart_one_systemd_gateway_unitz;_cmd_update_impl.<locals>._restart_one_systemd_gateway_unit<  s|    *%h(??+/!%	$%	! ! ! !<--//8;;"F ':&9!9h' ' %&	*$.N )$*$,$8$-	#"!" 04%)GI()% % %E ),U\-?R,F,F,H,H,MA(N(NII&&5-  * * *
 )*III* (-$q==! ^ ^ ^SEWEW ^ ^ ^   ,I+H ).;, , ,L
 ( T8  +6 *$/>82L$L37)-	,.	!" !" !" !" !+$/7H2E$E37)-	,.	!" !" !" !" $<#;$-$,,0$" $" $" !+
 %7$=$=h$G$G$G$*F ,@+? ) ((+, , ,L
 36 $ ,t 33 3/  +2|c7I7I %%KX %K %K69,6G6G%K %K %K!" !" !"
  87 ) ((;      '
 !3 9 9( C C C &
 " c c c c   '.188BBB!!b !b !b@H!b !b !b   #F$ #'>8*DD+/!%	$&	    #-.'9h*??+/!%	$&	# # # #-22  87 ) ((,      -&
 !3 9 9( C C C C C !&$WX$W$W$W!" !" !" !+$/>82L$L37)-	,.	!" !" !" !" !+$/9h2G$G37)-	,.	!" !" !" !" $<#;$-$,,0$" $" $" !&
 %7$=$=h$G$G$G$)*P8*P*P*P$Q$Q$Q$Q$Q$9$@$@$J$J$J?D))TVK<AX<M<MSUJ$))g )g )g;E)g )gR])g )gbj)g )g 2<)g )g HS)g )g bj)g )g 2<	)g )g HS	)g )g ]e	)g )g%& %& %& %& %& 288BBB! _8 _ _w~G[G[G]G] _ _    s   AB B=<B=r   c                 z                         |            t          d|  d|j        r|j        nd d           d S )Nu%     ⚠ systemctl timed out restarting rI  rs  z%); continuing with remaining gateways)r  r   r  )r  r   r  s     r   _on_unit_timeoutz*_cmd_update_impl.<locals>._on_unit_timeout2  sl     .44X>>>BH B B+.7 I8IB B B    r   )r  r  )launchd_restartget_launchd_labelget_launchd_plist_path	launchctlr  rD  r  u     ⚠ Gateway restart failed: )r  r  c                 2    i | ]}|j         v |j         |S r   r  )r   r  manual_pidss     r   r  z$_cmd_update_impl.<locals>.<dictcomp>f  s4     ! ! !8{** $***r   r~  z: draining gateway PID z (up to ru  rv  ry  )rX   force_afterzexternal-supervisoru     ✓ Restarted u,     ✓ Restarting manual gateway profile(s): uC     ✓ Handed gateway profile(s) back to their external supervisor: r  z manual gateway process(es)z(    Restart manually: hermes gateway runz;    (or: hermes -p <profile> gateway run  for each profile)r  g      @c                     g | ]}|v |	S r   r   )r   rK  killed_pidss     r   r   z$_cmd_update_impl.<locals>.<listcomp>  s#    JJJ#sk7I7I#7I7I7Ir   u6    gateway process(es) ignored SIGTERM — force-killingg      ?z&Post-restart survivor sweep failed: %sz(Gateway restart during update failed: %s)has_legacy_hermes_units_find_legacy_hermes_unitsrB  u+   ⚠ Legacy Hermes gateway unit(s) detected:z  (z scope)z;  These pre-rename units (hermes.service) fight the currentz<  hermes-gateway.service for the bot token and cause SIGTERMz  flap loops. Remove them with:z!    hermes gateway migrate-legacyz)  (add `sudo` if any are in system scope)z*Legacy unit check during update failed: %sz-Tip: You can now select a provider and model:z7  hermes model              # Select provider and modelu   ⚠ Git update failed: u#   → Falling back to ZIP download...u   ✗ Update failed: )rZ   r   r  ro  )r   r  r   stdinisattyr/   r   r   r   r~  r  rF   r   r   r   r   r   r   rb   _venv_scripts_dir#_detect_concurrent_hermes_instancesrN  r  rY  r  atexitregisterr  r  r  rf  r  rf   r   r~   r   r  r  rD   r  r-   r.   r  r  r   r
  r  r  rd   r  r0   r   r  r  r9  r0  r  r<  r=  r  nextr}  rC  r   r  r  r  ra   _clear_update_incomplete_markerr6   rL   r  r  r  r  r  r  r  r  r1   r    rF  rS  rt  %_clear_lazy_refresh_incomplete_markerr  rl   r  r  _desktop_packaged_executable_desktop_dist_existsr  _desktop_build_neededr   r  r  r+  rH  r  rh  ri  r   r  r  r2   r  re  r  rN  hermes_cli.profilesr  r  rK   r   r   plugins.memory.honcho.clir!  r"  r#  r$  r%  r   r  r+  r8  r   r   r   r%  r-  r  hermes_cli.tools_configr<  r|   r  rA  rB  rC  r  r  rD  rE  rF  rG  signalr  r  rp  r   rp  r  r  r  r  r  r  r  ImportErrorrj  r  killSIGTERMr  ri  r  r  r  r  r   )r  r  gw_input_fn
assume_yes_non_interactive_updatediscard_local_changesr   _update_cfg_moder   rH  
concurrentpre_update_snapshot_id_windows_gateway_resume_atexit_venv_holders_gateway_holdersr  _piduse_zip_updater  r4   r  is_forkr  r  r  r5   current_branchr:  auto_stash_refcheckout_resulttrack_resultprompt_for_restorecommit_countr<  r=  runtime_repairsruntime_repairedre  rk   	repair_uvvenv_python_missing
repair_envhealthy_afterdetail_afterupdate_succeededpre_pull_shapull_resultreset_result	syntax_okfailing_pathsyntax_errorr   rollback_resultr  r  r  install_groupr  install_prefixlazy_envlazy_okr  r  r  r   desktop_dirhas_desktop_app_skip_desktop_build_desktop_build_cmdr  
_build_envbuild_resulttail_dhhrh  ri  r  r  _pre_snap_idr  r  r  r  r  re  r5  rN  r  r  r  r  r!  r  rR  rV  modifiedrj   per   
backfilledr!  syncedr"  r#  r$  r%  missing_envmissing_configcurrent_ver
latest_verhas_new_optionsversion_bump_onlyneeds_migration_mig_errr4  r  interactive_migrationrn  r8  cron_restorer:  cfg_excr<  _exit_code_path gateway_fleet_restart_incompleterA  rB  rC  r  r  rD  rE  rG  _signalrp  relaunched_profilesexternally_supervised_profilesr  r  r  r  r  
plist_pathr,   service_pidsr  rK  r  restart_modedrainedsvcrq  unmapped_count_service_pids_after
_surviving_stuck_terminate_pid
_sweep_excr  r  r   rK   is_sysr  rF  rn  ro  rb  rO  r  r  r  r  r  r  s                                                                                                                                                                        @@@@@@@@@@@@r   _cmd_update_implr    s;    		D	D	D	D 
 gdE51122J 	 	<	<	  "":sz'8'8':':; 
 " *
	*555555&;==.B33IrBBK+t,, ;KOO,KWUUVV\\^^(-(:% 	* 	* 	*LLSUXYYY$)!!!!!!	*
 

()))	GGG 
tt '$"?"? dd,,.."AA+NNJ ::{SSTTT  TT88>> ddEEGG 
    DD6#	
 	
 	
 
tt '$e"D"D ::<< 	E!ttCCMRR+ 988888CS!122 C C C   -  D%c$iit<<<<<$   DdC        C    " B B D D 	5mDDEEEDD667NOOOHQKKK Ndd&(G>> <7""!NN?@@@V   HQKKK |w7>>#3#3   !	
 	
 	
 	
 gG
|wAAA GRTT%6777 7BDD$5666 %%grtt/@AAJz""G '(((:    	PD!!!DD667NOOOOBDD667NOOOOb ,,T22'(((!~w&11!		
 
 
 "a''!(..00F'6115G65Q5QQRRRvE36,,..q13332FFFF'6115NRX5X5XZ    @AAA 97v00221577888HQKKK ;;;;!	
 
 
  ,,.. V## "V++  1111 
 WWWWWWXXXTT@@"$$J[\\N(n:v..DD%#GI	  O )Q..
  *~z49K69K9KLL)#'		       *a// &155#DD-*(-%0 6    WWWWXXX#*0022 RP<#6#<#<#>#>#I#I#K#KA#NPPQQQHQKKKTT@@"$$J[\\N $& OOM#)"2"2"4"4"L9J9J9L9L 	 z#;6#;#;YGG!	
 
 
 6=..00111$&&&  O6V++227BDD<MNNN )--DD%" 2( .    ff%555z>::)#'	    KJJJJJJJ Oo.DEEEEIo&<====#IIoIII    9::OGV (1KLLLm6mm$$$<===/111;;;;;;%IKK	
 %)F2BDD<L<L<N<N   &((	'#
 ' 9 ABBBN"FF3DD-#   
  !_BJ!_s244CTW]C]?^?^!_!_JDDLL"E*
% M     DDLLu5U M    44666.H.J.J+|  [67777RLRRSSSYZZZZ/000+BDD4D4D4F4F+W   K   OPPPDD667NOOOF7<777888&'''  )"$$2CDDf	 %.7K1C61C1CDDDD%#GI	  K %** f    *~w2DF2D2DEE)#'		       *a//D6DDDEEE#*0022 B@<#6#<#<#>#>@@AAA_W]__   HQKKK 5T!5 51I|\  NOOO)<))*** - !$L 1 1 < < > >rr B - -mTmm,,,, fGGGGcrc1BGGGHHH&0n7Hl"CCDD-'+!GI	' ' 'O '1Q66VWWWUVVVVMNNN^(9^^P\^^___*17799 ]!"[/*@*F*F*H*H*S*S*U*UVW*X"["["[\\\GGGWXXXdBDD$5dddeee#) ( ]N]]]   DEEEE*  DD11)&    DD11)&$6!, 2   % ) ( ]N]]]   DEEEE*  DD11)&    DD11)&$6!, 2    % *4 	!"""
 $$,,RTT->?? 	eeeGWXLLSS^cee   	))+++  	Kv''DD..w8IJJJ 	()))3444FFFFFFFF 	>4/ 	4*733F &	cS
SM3rtt7H67Q3R3RSSFtt""6** b

<...

<... ,`aaatt""6** L/A/C/C Lqrrr.FKKKKDDDDV= E     ~tU3G{m+)#'	     0   ^T;_U)      tt""$$ ] ,[\\\tt""$$ 8);)=)= 8qrrr.w777DDDDWTaDbbb,2?&%#-66 	,,... $$,,RTT->?? 	eeeGWXLLSS^cee   	))+++
,,... 	.///
--n(-KKK $$44^4RR 	DD6688886   	99;;; 3TDD3
 3
/	><  	?GGGQ>QQQRRR)<))***PQQQ=>>>133
244,u4555 dd'&09<$$;;KHHPTT~XZX\X\XqXqr}X~X~.(0022 .	:rtt7U7U7W7W .	:\k .	:CDDD #(,*,$$*D*D!2 +E + + '##  , , ,&+###," :45555&)nd<MyZh%i" CBBBBB2244
!tt::;MSUSWSWSdjt:uu*a//#%44#>#>?QWYW[W[Whnx#>#y#yL*a//abbb99l&9&?R%F%F%H%H%S%S%U%UVYVZVZ%[\\D $dLLLLLLGttvvGGGHHHH8999!"""D	QWWWWWWWW)++j8K!!## >33!%#  	
 ==)) 8LLB!i00   
 GGGB#--	?CCD   $:L# +H001B1BCC*+() $
 '--// #'>'> +$4( ( (H  (||G44 "!&$<$<$<$<$+MM+{$K$K$K3J3J(359374& 4& 4&L
 (4'7'7'@'@ 	%*(--I9E-I -I -I)* )* )* )*
 ).-I)* )* )* (/ !& !& !&$)(VPT(V(V%& %& %& %& %& %& %& %&!&
 !&$U!" !" !" !" " U    FGGGGGG 	Q 	Q 	QLLJCPPPPPPPP	Q	KIIIIII''(9:: KIJJJ 	K 	K 	KLLFJJJJJJJJ	K	D555555GGG1222 [t,,,Fh YWS!122WW$))F8DT:U:UWWXXXzz)$$ ]S	!233]]tyyPYIZ?[?[]]   zz/** PS!899PPPQQQ5   zz)$$ OMs6)#455MMMNNNzz+&& 8S!455 8 8yy!4558 8   (# 5FJJy,A,A 53444 	D 	D 	DLL?CCCCCCCC	D!	       
 )=??L ;EFFF% ; ;A;//dCCC 3'8!9!9 3%>FF 3%(x)<)<%=%=F&)!%%	2*>*>&?&?G'*155"+E+E'F'FH$&E% ? %-=-=-=-= > > >& F %-D7-D-D-D E E E' K %-I-I-I-I J J J9>%PTYYu%5%5%5LFF%2F51655V556666$ ; ; ;91699B999::::::::; 	 	 	D		AAAAAA..T:::J F3z?? F F.2ii
.C.CF F    	 	 	D		LLLLLL//11F B@V@@@AAA 	 	 	D	 	=>>>	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 	
 +*>>>2244"6"6"8"8Z{<n==<K*$< 	 *E[:-E b	7 GGGU+UUZUUU  D5====RSSSS D D DJJJKKKBCCCCCCCCD  R	7GGG9 9 9 9*  B^[!1!1^^^   [.&AAA CW3~#6#6WWWXXX^]EBBBGGG #]    ##Lc  UWWUWW  i&&(( #SZ->->-@-@ #Z[[[!#MNN H
   # # #"HHH# 333 !DJD(f2D)% ).5JRWXXX;' 87>+B 8GGG6777  XJ X(f2D2D+2DVWWWPQQQQ5666	IFFFFFF778NOOL J ,[ 9J J+7+FJ J J  
  	I 	I 	ILLBCHHHHHHHH	I 	 	*?99]++? ? ?   QRRRJKKKK()))	>02222 	> 	> 	>LL91========	>	C+---- 	C 	C 	CLL>BBBBBBBB	C	D,.... 	D 	D 	DLL?CCCCCCCC	D
	?"$$$$ 	? 	? 	?LL:A>>>>>>>>	?	H """" 	H 	H 	HLLCQGGGGGGGG	H#	=!%	W999999*{}}277	2FFk400 )-#(<dCC* *&  W W WLgVVVVVVVVW #L$@@@L.. A GFFFFFCDDD #" -1,1   
  	= 	= 	=LL8!<<<<<<<<	=&  	-//2EEO**3*AAAA    ,1(
P
	H
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 %$$$
 "&% % %% % 	% % % %@ "%15 15 1515 15 	15 15 15 15f ')8C 8T 8c 8 8 8 8 8 8D%LLLLLL #E*G*G*I*I$J$JD Q Q % % % $% "$$&!%%K"$-/* )(** h,,....    D k845}-) b b$E9!!+%   ,0!%	$&" " " - ! ! ! %4 ! ! !M M M9<.WcggFWM M M  
 !!tC tD t t t t t t t t t t t t t tl	3 	Z=V 	[_ 	 	 	 	 	 	 3%F(8     xzz           "8!7!9!9J!((** Q *(&2C2C2E2EF+/!%	$%	! ! ! !+q00Q / 1 1 1 2 9 9:K:K:M:M N N N N#-#@ Q Q Q*1!Xr*B*B*Hb)O)O)Q)Q %&Ov&O&O P P P P P P P PQ *:+DkR   D -,..L++)  K! ! ! !::UUU! ! !
 /4466 /= /=	TFFL#     ' 8T\ 8 8# 8 8!-008 8 8   87"/    W_5555.@     '&sEEEE$$$#888299$,GGGG'..t|<<<<"  +++GC111OOC((((*O<   D " [ - 4 4C2S223333& R II&9::EPPPQQQ1  II&DEEE/',/ /  
 $$-../899: 
 " V>VVVWWWDEEE%))Y   % 370 &5&7&7:M&MO'2232IIII"   23HIII% k SC   &7&7&9&9#..!4!%  
 KJJJJJJ %GGGdVddd   ONNNNN% ! !!
 +N3d;;;;; 2OWM ! ! ! D! K$$$ S S SEzRRRRRRRRS  	H 	H 	HLLCQGGGGGGGG	H 	223JKKK	J          )(** C/F/F/H/H CCDDD*C*C*E*E : :&D$(.:HHFE888%8889999STTTTUUU78889:::ABBB 	J 	J 	JLLEqIIIIIIII	J 	)777=>>>GHHH+ 	 HQKKKKK		 	 (   <7""/A//0007888GGGD!!!!+++,,,HQKKKKKKKKK "!!!!!sz  :A6C1 1
D";DD":L
MM  MV #V28]B| AB| LAD A*BB| DBAFFH4B| O-AO< O;B| O<AAQQB| QAQQM,B| ^26A_) _(B| _)A_8_5B| _7A_8_8E-B| e&DAl< i4A Ak kAl< k
Ak;kAk6k1Al< k6Ak;k;A Al< l;B| l<
Am+mAm&m!B| m&Am+m+B| m/1An! n B| n!
Aon+AooB| oAooB| oGAv* v)B| v*
Awv4AwwB| wAwwB| w7A}+ xD A|7|5A}+ |7
A}'}A}"}A}+ }"A}'}'A}+ }*B| }+
A}8}5B| }7A}8}8B| }<AA B| 
A!B|  A!!B| %%B@ @
B| @
B@@B| @B@@BB| B0BC CB| C
BDC!BC?C9B| C?BDDDB| H#3BI IB| IBI&I#B| I%BI&I&B#B| L
ABM MB| M
BM=MBM8M3B| M8BM=M=A+B| O)BO8 O7B| O8
BP(PBP#PB| P#BP(P(B| P,BP; P:B| P;
BQ+QBQ&Q!B| Q&BQ+Q+B| Q/BQ> Q=B| Q>
BR.RBR)R$B| R)BR.R.B| R2BS SB| S
BS2SBS-S(B| S-BS2S2B| S6BT TB| T
BT6TBT1T,B| T1BT6T6B| T:BX+ T=ABV VBX+ V
BWV'BWWBX+ WBWWABX+ X*B| X+
BYX5BYYB| YBYYB| Y3BZ ZB| Z
BZZB| ZBZZB| Z BBu \3/B]# ]"Bu ]#B]3]0Bu ]2B]3]3$Bu ^
B^# ^"Bu ^#
B^0^-Bu ^/B^0^0Bu _	!B_+_*Bu _+B`8_6Bu _8B`8`(B`3`.Bu `3B`8`8A!Bu bABe6 c8'Bd  dBe6 d Be2d/9Be-e(Be6 e-Be2e2Be6 e5Bu e6BffBu fBffB Bu h8BiiBu iBi)i&Bu i(Bi)i)A-Bu k/BllBu lBllBu lBllDBu p#Bp= p<Bu p=
Bq
qBu q	Bq
q
Bu q#A7Bt sBs)s(Bt s)BttBt tBttBt tBu t
But(Bu	uBu u	BuuBu uB| u
BvuBu=u8B| u=Bvv$B| v'C(Bz zB| z
B{ zBz;z6B| z;B{ { AB| |B~3|*A8B~.~.B~3c                    | sd S t          d| d           | d d         }|D ]}t          |t                    rX|                    |          p|r|                    |          pd}|                    d          pd                                }nt          |          }d}|rt          d| d|            t          d|            t          |           t          |          z
  }|d	k    rt          d
| d           d S d S r)  r-  r.  s	            r   r4  r4  6  s=    	-u---"1"IE ' 'b$ 	66#;;P<#HBFF<4H4HPSDFF=))/R6688DD r77DD 	'0t00$001111%t%%&&&&JJU#Eqyy+u+++,,,,, yr   r  rH  rI  c                 `   t          j                    t          |d          z   }	 	 t          j        | d|gz   ddddd          }|j                                        dk    rdS n# t          t          j        f$ r Y nw xY wt          j                    |k    rd	S t          j	        d           )
a>  Poll ``systemctl is-active`` until the unit reports active.

    systemd's Stopped -> Started transition after a graceful exit
    (or a hard restart) is not instantaneous; a one-shot check
    races that window and falsely reports the unit as down.
    Poll every 0.5s up to ``timeout`` seconds before giving up.
    ru   TrK  r$   r%   rD  r  rl  FrL  rM  s        r   rO  rO  K  s       3w#4#44H
	 nk955#GI	  G ~##%%11t 2!:#<= 	 	 	D	?((5CrP  ro  c           
         	 t          j        | d|ddgz   ddddd          }n# t          t           j        f$ r |cY S w xY w|j        pd	                                }|r|d
k    r|S d}d}|                                D ]`}dD ][\  }}	|                    |          rA	 |t          |dt          |                              |	z  z  }d}n# t          $ r Y nw xY w n\a|r|n|S )ab  Read the unit's ``RestartUSec`` (RestartSec) in seconds.

    After a graceful exit-75, systemd waits ``RestartSec`` before
    respawning the unit.  Callers that poll for ``is-active``
    must use a timeout >= ``RestartSec`` + transition slack, or
    they'll give up *during* the cooldown window and wrongly
    conclude the unit didn't relaunch.
    rR  rS  rT  Tr$   r%   rD  r  rZ   rU  ro  FrV  NrZ  r[  s
             r   rb  rb  h  sb   (	  	
 
 
 z89   <2
$
$
&
&C  ###EG		  
 	 	KD% }}T"" U43t99*#566>>E"GG!   D (55(rc  )r   N)rZ   rm   r   )FNr
   r  r  )s__doc__r  rw   r7  r  shlexr  r-   r   r=  r~   r   pathlibr   typingr   r   r   r   r   r8  r9  r   r   r   r    rC   rF   r6   r_   r   rL   r^   rl   r  r   r   r   r   r   r   r   r   r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r
  r  r  r   r  r  r!  r#  r0  r9  r?  rC  rF  rN  r  rS  rt  r  r  r  r  r  r  r  r  r  r  r  r
  r%  r-  rJ  rK  r<  rY  rk  rp  rt  r}  r  r  r  r  r  r  r  r  r  r  r  r  r  r4  rO  rb  r   r   r   <module>r     s   2    				       



                       - - - - - - - - - - - -		8	$	$
 
 
" J J J J<
 sTz    'U4tS4Z3O-P ' ' ' '\ LuT3:sTz5Q/R L L L L\/ / /s /% /TW / / / /bT      t      %$ %5s+; % % % %& & & &Po o o ob? ? ? ?BS S    &0DI 0$ 0 0 0 0(GS Gs Gt G G G G"C c c    :Q Q Q Q&9 9 9 9xf4 f4 f4PaDI aD aXc] a a a aF#Y!.1c]   " 59
 

$,SM
	
 
 
 
3 4    B u u#Yu	u u 	u 
u u u un-#Y-	- - 
	- - - -^    G 3 T#Y T hsm    # 4     $s) $ 4    $s) $ 4    DI D  3 SV    Dd D D D D  d3i d t     qX49 qX4 qXD qX qX qX qXf  6
BT 
BS 
BT 
B 
B 
B 
BN N N NZ Z Z Z%S/"15   8 "&H H HS	H 
c3h$	H 
	H H H H( ,0f "&f f fS	D(f 
c3h$	f 
	f f f fP.I .I .I .I`*D * * * * "&!
 !
 !
S	!
 
c3h$	!
 
	!
 !
 !
 !
F".49 ".t ". ". ". ".H!U49- ! ! ! !FsTz     t     6	@4 	@D 	@ 	@ 	@ 	@h=49 h= h= h= h=T# $    , (,     (hEu hE hE hEc hE hE hE hE hETVQ VQ VQ VQp-D -D -D -D^   &- "*S * * * *XhHSM h h h hTD s t    0
s)!&X   <BE$)$4 B B B BJ (,S S SX_S	%S#
S S S SjeCcM6J1K PS    :?49 ?c ? ? ? ?D+%S#&'+	#Y+ + + +\SD4K S S S Sj*J *J *J *JX++
 
+ + + +:B B$ B B B B,Z Z Z Z:D
 D
 D
 D
 D
 D
L( ( (Tm m m^P P P P Ph:- - - -0    
	   @ 1) 1)1)1) 1) 	1) 1) 1) 1) 1) 1)r   