
    Pmj                        d Z ddlZddlmZ ddlmZmZmZmZm	Z	m
Z
mZ ddlmZmZ eeee
eee         f                  f         ZdedefdZd	ee         d
efdZdedededdfdZdedededed
eddfdZdededefdZ ed          ZdededefdZdededededz  fdZdedededz  fdZdee         dedee         fdZe G d  d!                      Zd"ee         d#ede eef         fd$Z!d"ee         d#ed%ed&edef
d'Z"d(ed%ed)ed*edef
d+Z#d,ed-ede eeef         fd.Z$dQd/Z%d0Z&d1Z'd2Z(d3Z)d4Z*d5Z+defd6Z,d,edefd7Z- e.            Z/d8dd9d9dd:d;Z0ddd<d=edee         d>e	e         d?e	e         dz  d@eee	e         gef                  de	e         fdAZ1	 dRddd9ddBd=edee         d>ed?edz  dCedz  dDedEee         dz  defdFZ2dedefdGZ3d=edee         d>ed?edef
dHZ4	 dRdId9dJd=edee         dKedLedDededz  fdMZ5d=edee         dNededz  fdOZ6	 dSd=edee         d>e	e         d?e	e         d@eee	e         gef                  de	e         fdPZ7dS )TzShared curses-based UI components for Hermes CLI.

Used by `hermes tools` and `hermes skills` for interactive checklists.
Provides a curses multi-select with keyboard navigation, plus a
text-based numbered fallback for terminals without curses support.
    N)	dataclass)CallableListOptionalSequenceSetTupleUnion)Colorscoloritemreturnc                 n    t          | t                    r| S d                    d | D                       S )z:Flatten a radiolist item to searchable/plain display text. c              3       K   | ]	\  }}|V  
d S N ).0text_styles      :/home/thesage/.hermes/hermes-agent/hermes_cli/curses_ui.py	<genexpr>z#radio_item_plain.<locals>.<genexpr>   s&      11LD&4111111    )
isinstancestrjoin)r   s    r   radio_item_plainr      s:    $ 7711D111111r   style	is_cursorc                v   |r5| j         }|                                 r||                     d          z  }|S |dk    r)|                                 r|                     d          S |dk    rG| j        }|                                 r*	 ||                     d          z  }n# | j        $ r Y nw xY w|S | j        S )zMap a segment style to a curses attribute.

    Cursor rows force the whole line green (pair 1) so selection stays
    one solid highlight; unselected sale chrome uses yellow / dim.
       yellow   dim   )A_BOLD
has_colors
color_pairA_DIMerrorA_NORMAL)cursesr   r   attrs       r   _curses_style_attrr.      s      } 	)F%%a(((DV..00  ###~~| 	))!,,,<   ?s   	B" "
B/.B/yr   max_xc                    ddl }d}d}|j        }|                                r|                    d          }|t	          |          k     r||dz
  k     r |dz
  |z
  }|dk    rdS ||         dk    r6	 |                     ||d||           n# |j        $ r Y nw xY w|dz  }|dz  }p|                    d|          }	|	dk     r
||d         n	|||	         }
|
d|         }
	 |                     |||
||j                   n# |j        $ r Y nw xY w|t	          |
          z  }|t	          |
          z  }|t	          |          k     r||dz
  k     dS dS dS dS )uF   Draw a description line, highlighting ★ in yellow when colors exist.r   Nr#   r!   u   ★)r,   r+   r'   r(   lenaddnstrr*   find)stdscrr/   r   r0   r,   coli	star_attr	remaining	next_starchunks              r   _draw_description_liner<   2   s   MMM
C	AI )%%a((	
c$ii--C%!)OOAIO	>>E7eq#uiCCCC<   1HCFAIIeQ''	%MMQRRtAiK/@jyj!	NN1c5)V_EEEE| 	 	 	D	s5zz	SZZ) c$ii--C%!)OOOO--OO--s$   4B 
BB$D 
DDxc          
         ddl }t          |t                    rSt          |d|          }	 |                     |||t          d|dz
  |z
            |           n# |j        $ r Y nw xY wdS |}|D ]{\  }	}
||dz
  k    r dS |dz
  |z
  }|dk    r dS |	d|         }t          ||
|          }	 |                     |||||           n# |j        $ r Y nw xY w|t          |          z  }|dS )zBDraw a plain or segmented radiolist item starting at column ``x``.r   Nr   r!   )r,   r   r   r.   r3   maxr*   r2   )r5   r/   r=   r   r0   r   r,   r-   r6   r   r   r9   r;   s                r   _draw_radio_itemrA   R   sY   MMM$ !&$)DDD	NN1as1eai!m'<'<dCCCC| 	 	 	D	
C  e%!)EEAIO	>>EEZiZ !&%9EEE	NN1c5)T::::| 	 	 	D	s5zz s#   -A 
A('A(0C


CClabelqueryc                     |                                  }|                                                                 }|sdS |D ].}d}|D ]'}|                    ||          }|dk     r  dS |dz  }(/dS )zEReturn True when every query token is a case-insensitive subsequence.Tr   Fr!   )lowersplitr4   )rB   rC   
normalizedtokenstokenposchs          r   _query_matchesrL   n   s    J[[]]  ""F t 	 	 	 	B//"c**CQwwuuu1HCC	 4r   z-_/. targetindexc                     |dk    rdS | |dz
           }|t           v rdS | |         }||                                k    o/||                                k    o||                                k    S )zTrue if position ``index`` in ``target`` starts a word.

    Mirrors ``isBoundary`` in the TS scorer: start-of-string, after a
    separator char, or a lower->upper camelCase transition.
    r   Tr!   )_WORD_BOUNDARYrE   upper)rM   rN   prevcurs       r   _is_boundaryrT      sr     zzt%!)D~t -C4::<<MC399;;$6M3#))++;MMr   origrE   rI   c                    d}d}d}g }|D ]}|                     ||          }|dk     r dS |                    |           |dz  }|dk    r||dz   k    r|dz  }n|dk    r|t          ||z
  dz
  d          z  }t          | |          r|dz  }|dk    r|dz  }|}|dz   }|r-|d         dk    r!|d         t	          |          dz
  k    r|dz  }||k    r|d	z  }|t	          |          d
z  z  }|S )a#  Score one token against a target. None if the token isn't a subsequence.

    A faithful port of ``fuzzyScore`` in ui-tui/src/lib/fuzzy.ts and
    web/src/lib/fuzzy.ts so all three surfaces rank model ids identically:
    contiguous runs, word-boundary / first-char starts, prefix matches, and
    exact matches all score higher than scattered subsequence hits.

    ``lower`` is ``orig`` lowercased; matching is done against ``lower`` while
    boundary detection uses ``orig`` (so the camelCase rule works), exactly as
    in the TS scorer.
            r   Nr!      r%         g{Gz?)r4   appendminrT   r2   )	rU   rE   rI   scorerR   search_from	positionsrK   idxs	            r   _token_scorerb      sS    EDKI  jj[))7744
199qQJEEQYYSta+++Ec"" 	QJE!88QJEAg  Yq\Q&&9R=C	NNQ<N+N+N
 ~~ 
SZZ$ELr   c                     |                                  }|                                                                 }|sdS d}|D ]}t          | ||          }| dS ||z  }|S )zAggregate score for a multi-token query (AND). None if any token fails.

    Mirrors ``fuzzyScoreMulti`` in the TS scorer: every whitespace-separated
    token must match; per-token scores are summed.
    rW   N)rE   rF   rb   )rB   rC   rE   rH   totalrI   token_scores          r   _fuzzy_scorerf      sz     KKMME[[]]  ""F sE  "5%7744Lr   itemsc                 F   |                                 }|s)t          t          t          |                               S g }t	          |           D ].\  }}t          ||          }||                    ||f           /|                    d            d |D             S )a  Return item indices matching *query*, ranked best-first.

    An empty query keeps every item in original order. Otherwise items are
    filtered to fuzzy matches and sorted by score descending, ties broken by
    original index so equal-scoring rows keep their catalog order.
    Nc                 $    | d          | d         fS )Nr!   r   r   )pairs    r   <lambda>z!_filter_indices.<locals>.<lambda>   s    47(DG!4 r   )keyc                     g | ]\  }}|S r   r   )r   r7   _s      r   
<listcomp>z#_filter_indices.<locals>.<listcomp>   s    !!!$!QA!!!r   )striplistranger2   	enumeraterf   r\   sort)rg   rC   qscoredr7   rB   r^   s          r   _filter_indicesrw      s     	A 'E#e**%%&&&Fe$$ & &5UA&&MM1e*%%%
KK44K555!!&!!!!r   c                   0    e Zd ZU dZdZeed<   dZeed<   dS )_SearchStatez3Mutable search state shared by curses picker loops.Factiver   rC   N)	__name__
__module____qualname____doc__rz   bool__annotations__rC   r   r   r   r   ry   ry     s3         ==FDE3OOOOOr   ry   filteredcursorc                 T    | s|dfS || vr| d         }||                      |          fS )z?Return ``(cursor, cursor_pos)`` inside the filtered index list.r   )rN   )r   r   s     r   _reconcile_cursorr     s?     qyX!8>>&))))r   
cursor_posdeltac                 @    | s|S | ||z   t          |           z           S )zEMove through the filtered index list, wrapping like the legacy menus.)r2   )r   r   r   r   s       r   _move_filtered_cursorr     s,      Z%'3x==899r   scroll_offsetvisible_rows
total_rowsc                     t          d|          }|| k     r|} n|| |z   k    r||z
  dz   } t          dt          | t          d||z
                                S )z2Clamp scroll offset so the cursor remains visible.r!   r   )r@   r]   )r   r   r   r   s       r   _scroll_for_cursorr      sm     q,''LM!!"	}|3	3	3"\1A5q#mSJ,E%F%FGGHHHr   rl   searchc                 N   |j         sdS |dk    r't          |j                  }d|_         d|_        dd|fS || j        ddfv r|j        dd	         |_        d
S |dk    r	d|_        d
S || j        ddfv rdS d|cxk    rdk     r"n n|xj        t          |          z  c_        d
S dS )zHandle a key while the search prompt is active.

    Returns ``(handled, confirm, changed)``. Active search consumes query
    editing keys, but leaves navigation keys for the menu loop to handle.
    )FFF   Fr   T   rZ   NrX   )TFT   
      )TTF    )rz   r   rC   KEY_BACKSPACE	KEY_ENTERchr)
curses_modrl   r   	had_querys       r   _handle_active_search_keyr   .  s     = #""
byy &&	UI%%
z'a000|CRC(  
byy  
z#R,,,  	S3C   r   c                      	 t           j                                        sdS ddl} |                     t           j        | j                   dS # t          $ r Y dS w xY w)u  Flush any stray bytes from the stdin input buffer.

    Must be called after ``curses.wrapper()`` (or any terminal-mode library
    like simple_term_menu) returns, **before** the next ``input()`` /
    ``getpass.getpass()`` call.  ``curses.endwin()`` restores the terminal
    but does NOT drain the OS input buffer — leftover escape-sequence bytes
    (from arrow keys, terminal mode-switch responses, or rapid keypresses)
    remain buffered and silently get consumed by the next ``input()`` call,
    corrupting user data (e.g. writing ``^[^[`` into .env files).

    On non-TTY stdin (piped, redirected) or Windows, this is a no-op.
    Nr   )sysstdinisattytermiostcflushTCIFLUSH	Exception)r   s    r   flush_stdinr   T  sl    y!! 	F	7#344444   s   A )A 
AAupdownselecttogglecancelnonec                 F    t          | |                                           S )uV  Read one keypress and normalize it to a menu action.

    Decodes raw arrow-key escape sequences in addition to the translated
    ``curses.KEY_*`` values.  Even with ``keypad(True)`` (which
    ``curses.wrapper`` sets), some terminals/terminfo entries deliver cursor
    keys as raw CSI/SS3 byte sequences — ``getch()`` then returns ``27`` (ESC)
    followed by e.g. ``[`` ``A``.  Treating that leading ``27`` as a cancel is
    what made the setup wizard's provider/model pickers bail to the numbered
    fallback the moment a user pressed up/down.

    Returns one of the ``NAV_*`` constants.  A lone ESC (no continuation byte
    within a short window) is the only thing that maps to ``NAV_CANCEL`` via
    the escape path; ``q`` also cancels.  Unknown sequences map to
    ``NAV_NONE`` so the caller simply ignores them rather than misfiring.
    )_decode_menu_keygetch)r5   s    r   read_menu_keyr   t  s      FFLLNN333r   c                    ddl }||j        t          d          fv rt          S ||j        t          d          fv rt
          S ||j        ddfv rt          S |t          d          k    rt          S |t          d          k    rt          S |d	k    r*	 | 
                    d
           |                                 }| 
                    d           n# | 
                    d           w xY w|dk    rt          S |t          d          t          d          fv r|                                 }|t          d          t          d          fv rt          S |t          d          t          d          fv rt
          S d|cxk    rdk    r&n n#|                                 }d|cxk    rdk    !n t          S t          S t          S )zNormalize an already-read keypress to a menu action.

    Split out from ``read_menu_key`` so search-aware loops can peek the raw
    key (e.g. to catch ``/``) before falling back to nav decoding.
    r   Nkjr   r    ru   r   <   rX   [OABr   ?   )r,   KEY_UPordNAV_UPKEY_DOWNNAV_DOWNr   
NAV_SELECT
NAV_TOGGLE
NAV_CANCELtimeoutr   NAV_NONE)r5   rl   r,   nxtfinals        r   r   r     s    MMM
v}c#hh'''
vC)))
vR(((
c#hh
c#hh
byy	NN2,,..CNN2FNN2"993s88SXX&&&LLNNES3s88,,,S3s88,,, %''''4''''' %''''4''''OOs   )C C(r!   F)reserve_bottomdraw_footerextra_color_pairs
searchablesearch_labelsc                     t           j                                        s|	S |
oduot                    k    	 ddlt
          g fd}                    |           t                       d         t
          urd         n|	S # t          $ r |	cY S t          $ r  |            cY S w xY w)a2	  Shared curses single-/multi-select event loop.

    Owns every piece the three public menus used to duplicate verbatim:
    the non-TTY guard, ``curses.wrapper`` setup (cursor hide + color pairs),
    the per-frame ``clear``/``getmaxyx``/``refresh`` cycle, scroll-offset math,
    row iteration, the ``read_menu_key`` dispatch with ``NAV_UP``/``NAV_DOWN``
    cursor wrap, ``flush_stdin``, and the ``KeyboardInterrupt`` / curses-
    unavailable fallback. Per-menu behavior is supplied as callbacks so the
    rendered output stays byte-identical to the old hand-rolled loops.

    Callbacks / params:
        draw_header(stdscr, max_y, max_x) -> int
            Draw the title/hint/description rows. Returns the first screen row
            index where the scrollable item list should start. When search is
            active it receives the live ``_SearchState`` via the optional
            ``search`` keyword (drawn by the menu so the hint line can show it).
        draw_row(stdscr, y, idx, is_cursor, max_x) -> None
            Draw one item row. ``idx`` is always the ORIGINAL item index, so
            per-menu rendering is unchanged whether or not a filter is active.
        on_action(action, cursor) -> value
            Reducer for SELECT/TOGGLE/CANCEL. Return ``_KEEP`` to continue the
            loop; return anything else to resolve the menu with that value.
            (UP/DOWN cursor movement is handled by the driver itself.)
        reserve_bottom: number of bottom screen rows kept clear of items
            (1 = leave the final row blank, matching the old loops).
        draw_footer(stdscr, max_y, max_x) -> None
            Optional bottom-row painter (e.g. a status bar). Drawn after the
            item rows; its row budget must be included in ``reserve_bottom``.
        extra_color_pairs: also init pair 3 (dim gray) for status bars.
        fallback() -> value
            Called when curses errors out on a real TTY (curses unavailable).
        cancel_value: returned on non-TTY stdin, ESC/cancel, or KeyboardInterrupt.
        searchable: when true, ``/`` opens a type-to-filter prompt over
            ``search_labels``. Returned values are always ORIGINAL item indices.
        search_labels: per-item text used for filtering (required when
            ``searchable`` is true; length must equal ``item_count``).
    Nr   c           
                              d                                           r                                                                                      dj        d                               dj        d           r)                    dj        dk    rdnj        d           }d}t                      } rng }	 | 
                                 |                                 \  }} rt          ||j                  nt          t                              }t!          ||          \  }}	  | |||          }	n# t"          $ r  | ||          }	Y nw xY wt%          d||	z
  z
            }
t'          |||
t)          |                    } r<|j        r5|s3	 |                     |	dd	|dz
  j                   n# j        $ r Y nw xY wt1          t          |t3          t)          |          ||
z                                 D ]0\  }}||         }||	z   }||z
  k    r n | ||||k    |           1 | ||           |                                   r|                                 }|j        r{t;          ||          \  }}}|r(d}t!          t          |j                  |          \  }}|r%|r! t<          |          }|t>          ur|d<   d S %|r)tA          | |          }n<|tC          d
          k    r	d|_        VtA          | |          }ntE          |           }|tF          k    rtI          |||d          }na|tJ          k    rtI          |||d          }nC|t<          tL          tN          fv r-|t<          k    r r|s ||          }|t>          ur|d<   d S )Nr   r!   rX   r#   r%   rZ   T)r   z  No matches/)(curs_setr'   start_coloruse_default_colors	init_pairCOLOR_GREENCOLOR_YELLOWCOLORSCOLOR_WHITEry   cleargetmaxyxrw   rC   rq   rr   r   	TypeErrorr@   r   r2   r3   r)   r*   rs   r]   refreshr   rz   r   r   _KEEPr   r   r   r   r   r   r   r   )!r5   r   r   r   labelsmax_yr0   r   r   items_startr   draw_ifiltered_posr7   r/   rl   handledconfirmchangedoutcomeactionr,   r   draw_headerdraw_rowr   initial_cursor
item_count	on_actionr   result_holderr   
use_searchs!                        r   _drawz_run_curses_menu.<locals>._draw  s   OOA  "" ""$$$))+++  F$6;;;  F$7<<<$ $$ 1 111v7I2   $FM!^^F #-S1JQS U%00u "1OFFL999eJ//00 
 &7x%H%H"
D"-+feU6"R"R"RKK  D D D"-+feU"C"CKKKD  #1ek&9N&JKK 2!:|S]]! !  &, x {A~uqyRXR^____!<    -6-S]]ML<X)Y)YZZ- - ? ?(FL !.A,AEN222HVQ1;>>>>*Ku555    3 ,,..C} ? 5N"C5 51' # ,-M1B /v| L Lf2 2.FJ # %' +*3)J*G*G#*%#7#77>M!$4$*F$" %$!1&#!>!>C(, !1&#!>!>*622FV##28VZQSTTFFx''28VZQRSSFF
J
CCC++
+8+ 'i77Ge+++2a(kUs$   E E-,E-1!G 
G G )
r   r   r   r2   r,   r   wrapperr   KeyboardInterruptr   )r   r   r   r   r   r   r   r   fallbackcancel_valuer   r   r   r,   r   r   s   ````````   ` @@@r   _run_curses_menur     sM   n 9 ^T 9^c->P>PT^>^Jvi	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	 i	V 	u#0#35#@#@}QlR      xzzs   AB C-CC)cancel_returns	status_fntitleselectedr   r   c                    	 t                    t                    	 fd}	fd}	fd}	fd}t          dt                    |||rdndr|ndt                     fd	

  
        S )a  Curses multi-select checklist. Returns set of selected indices.

    Args:
        title: Header line displayed above the checklist.
        items: Display labels for each row.
        selected: Indices that start checked (pre-selected).
        cancel_returns: Returned on ESC/q. Defaults to the original *selected*.
        status_fn: Optional callback ``f(chosen_indices) -> str`` whose return
            value is rendered on the bottom row of the terminal.  Use this for
            live aggregate info (e.g. estimated token counts).
    Nc                    dd l }	 |j        }|                                r||                    d          z  }|                     dd|dz
  |           |                     ddd|dz
  |j                   n# |j        $ r Y nw xY wdS )Nr   r#   r!   u:     ↑↓ navigate  SPACE toggle  ENTER confirm  ESC cancelr%   )r,   r&   r'   r(   r3   r)   r*   )r5   r   r0   r,   hattrr   s        r   _draw_headerz&curses_checklist.<locals>._draw_header  s    	ME  "" .**1---NN1a	5999NN1L	6<   
 | 	 	 	D	qs   A0A8 8
BBc                 "   dd l }|
v rdnd}|rdnd}d| d| d|          }|j        }	|r3|j        }	|                                r|	|                    d          z  }		 |                     |d||dz
  |	           d S # |j        $ r Y d S w xY w)Nr   u   ✓r      →z [z] r!   r,   r+   r&   r'   r(   r3   r*   )r5   r/   r7   r   r0   r,   checkarrowliner-   chosenrg   s             r   	_draw_rowz#curses_checklist.<locals>._draw_row  s    f#"+/5//E//U1X// 	-=D  "" -))!,,,	NN1auqy$77777| 	 	 	DD	s   "B   
BBc                 B   dd l }	            }|rzt          d|t          |          z
  dz
            }|j        }|                                r||                    d          z  }|                     |dz
  ||||z
  dz
  |           d S d S # |j        $ r Y d S w xY w)Nr   r!   r%   )r,   r@   r2   r)   r'   r(   r3   r*   )	r5   r   r0   r,   status_textsxsattrr   r   s	          r   _draw_footerz&curses_checklist.<locals>._draw_footer  s    
	#)F++K REC$4$44q899$$&& 2V..q111Euqy"k52:>5QQQQQR R | 	 	 	DD	s   BB 
BBc                     | t           k    r                    |h           t          S | t          k    rt	                    S S r   )r   symmetric_difference_updater   r   set)r   r   r   r   s     r   
_on_actionz$curses_checklist.<locals>._on_action  sF    Z..x888LZv;;r   r   r#   r!   c                  *    t                     S r   )_numbered_fallback)r   rg   r   r   r   s   r   rk   z"curses_checklist.<locals>.<lambda>  s    +E5(NT]^^ r   )
r   r   r   r   r   r   r   r   r   r   )r  r   r2   r   )
r   rg   r   r   r   r   r   r  r	  r   s
   `````    @r   curses_checklistr  s  s   & X]]F                     u:: &-A$-7LL4y//^^^^^^^^#   r   )r   descriptionr   r   r  r   r   c                    g |r|                                 d D             }d
 fd	}fd}	fd}
t          t                    ||	|
dd fdr|t          |          nr|nd	          S )ab  Curses single-select radio list. Returns the selected index.

    Args:
        title: Header line displayed above the list.
        items: Display labels for each row. Each entry is either a plain
            string or a sequence of ``(text, style)`` segments where
            ``style`` is ``None``, ``"yellow"``, or ``"dim"``. Cursor rows
            force the whole line green; unselected rows honor segment styles
            (used for sale chrome in the model picker).
        selected: Index that starts selected (pre-selected).
        cancel_returns: Returned on ESC/q. Defaults to the original *selected*.
        description: Optional multi-line text shown between the title and
            the item list.  Useful for context that should survive the
            curses screen clear.
        searchable: When true, ``/`` opens a type-to-filter prompt. The
            returned value is always the original item index, not a filtered
            row position.
        search_labels: Optional haystacks for type-to-filter (length must
            match ``items``). Defaults to the display labels when omitted.
    Nc                 ,    g | ]}t          |          S r   )r   )r   r   s     r   ro   z$curses_radiolist.<locals>.<listcomp>  s!    ===t$T**===r   c                    dd l }d}	 |j        }|                                r||                    d          z  }|                     |d|dz
  |           |dz  }	D ]$}||dz
  k    r nt          | |||           |dz  }%
r||j        rd|j         d}n
rd}nd}|                     |d||dz
  |j                   |dz  }n# |j	        $ r Y nw xY w|dz   S )Nr   r#   r!   
  Search: +   ▎  BACKSPACE edit  Ctrl+U clear  ESC stopu;     ↑↓ navigate  ENTER/SPACE select  / search  ESC cancelu1     ↑↓ navigate  ENTER/SPACE select  ESC cancel)
r,   r&   r'   r(   r3   r<   rz   rC   r)   r*   )r5   r   r0   r   r,   rowr   dlinehint
desc_linesr   r   s            r   r   z&curses_radiolist.<locals>._draw_header  sJ   	ME  "" .**1---NN35%!)U;;;1HC $  %!)##E&vsE5AAAq Qf0V]0`FL``` QZPNN34FLAAA1HCC| 	 	 	D	 Qws   B?C	 	
CCc                    dd l }|k    rdnd}|rdnd}d| d| d}t          |d |          }		 |                     |d||d	z
  |	           n# |j        $ r Y nw xY wt	          | |t          |          
|         ||           d S )
Nr   u   ●u   ○r   r   z (z) r?   r!   )r,   r.   r3   r*   rA   r2   )r5   r/   r7   r   r0   r,   radior   prefixprefix_attrrg   r   s             r   r   z#curses_radiolist.<locals>._draw_row  s    MMx%.3'U''e'''(KKK	NN1aK@@@@| 	 	 	D	As6{{E!Hey	
 	
 	
 	
 	
 	
s   A 
AAc                 ,    | t           t          fv r|S S r   )r   r   )r   r   r   s     r   r	  z$curses_radiolist.<locals>._on_action!  s    j*---Mr   r!   Tc                  (    t                     S r   )_radio_numbered_fallback)r   rg   r   r   s   r   rk   z"curses_radiolist.<locals>.<lambda>/  s    1%.YY r   )r   r   r   r   r   r   r   r   r   r   r   r   )
splitlinesr   r2   rq   )r   rg   r   r   r  r   r   plain_labelsr   r   r	  r  s   ```` `     @r   curses_radiolistr     s    < !J . ++--
==u===L       <
 
 
 
 
 
    
 u:: YYYYYYY# 8+7 ",6,,$   r   c                 h   t          | t                    r| S g }| D ]\  }}|dk    r.|                    t          |t          j                             9|dk    r.|                    t          |t          j                             m|                    |           d                    |          S )zHApply ANSI colors to a rich radiolist item (numbered fallback / prints).r"   r$   r   )r   r   r\   r   r   YELLOWDIMr   )r   partsr   r   s       r   format_radio_item_ansir%  :  s    $ E  eHLLtV]334444e^^LLtVZ001111LL775>>r   c                    t          t          d|  t          j                             t          t          dt          j                             t          |          D ]P\  }}||k    rt          dt          j                  nd}t          d| d|dz   dd	t          |                      Qt                       	 t          t          d
|dz    dt          j                            	                                }|s|S t          |          dz
  }d|cxk    rt          |          k     rn n|S |S # t          t          t          f$ r |cY S w xY w)z1Text-based numbered fallback for radio selection.
  z&  Select by number, Enter to confirm.
u   (●)u   (○)  r   r!   >2. z  Choice [default ]: r   )printr   r   r"  r#  rs   GREENr%  inputrp   intr2   
ValueErrorr   EOFError)	r   rg   r   r   r7   rB   markervalra   s	            r   r  r  I  st    
%u
.
.///	%96:
F
FGGGe$$ I I545MMz6<000zG6GGAEGGG(>u(E(EGGHHHH	GGG	E@x!|@@@&*MMNNTTVV 	O#hhl    c%jj     J)84   s   AD8 0D8 6D8 8EECancel)cancel_labelr   default_indexr5  c                $   	 t          |          |gz   t          |          	d fd	}fd}	fd}t          t          |t                    dz
            t                    |||d	 fddrt                    nd
  
        S )	ar  Curses single-select menu. Returns selected index or None on cancel.

    Works inside prompt_toolkit because curses.wrapper() restores the terminal
    safely, unlike simple_term_menu which conflicts with /dev/tty.

    When ``searchable`` is true, ``/`` opens a type-to-filter prompt; the
    returned value is always the original item index (or None for cancel).
    Nc                 P   dd l }	 |j        }|                                r||                    d          z  }|                     dd|dz
  |           r||j        rd|j         d}nrd}nd}|                     dd||dz
  |j                   n# |j        $ r Y nw xY wdS )	Nr   r#   r!   r  r  u8     ↑↓ navigate  ENTER confirm  / search  ESC/q cancelu.     ↑↓ navigate  ENTER confirm  ESC/q cancelr%   )	r,   r&   r'   r(   r3   rz   rC   r)   r*   )	r5   r   r0   r   r,   r   r  r   r   s	          r   r   z*curses_single_select.<locals>._draw_headerv  s    	ME  "" .**1---NN1a	5999 Hf0V]0`FL``` HQGNN1auqy&,????| 	 	 	D	qs   BB 
B#"B#c                    dd l }|rdnd}d| d	|          }|j        }|r3|j        }|                                r||                    d          z  }	 |                     |d||dz
  |           d S # |j        $ r Y d S w xY w)Nr   r   r   r!   r   )
r5   r/   r7   r   r0   r,   r   r   r-   	all_itemss
            r   r   z'curses_single_select.<locals>._draw_row  s    "+)5))9Q<)) 	-=D  "" -))!,,,	NN1auqy$77777| 	 	 	DD	s   A5 5
BBc                 V    | t           k    r
|k    rd n|S | t          k    rd S t          S r   )r   r   r   )r   r   
cancel_idxs     r   r	  z(curses_single_select.<locals>._on_action  s9    Z "Z//44V;Z4r   r!   c                  &    t                     S r   )_numbered_single_fallback)r:  r<  r   s   r   rk   z&curses_single_select.<locals>.<lambda>  s    25)ZPP r   )
r   r   r   r   r   r   r   r   r   r   r   )rq   r2   r   r]   )
r   rg   r6  r5  r   r   r   r	  r:  r<  s
   `   `   @@r   curses_single_selectr?  c  s      U|n,IUJ      $         =#i..1*<==y>> PPPPPP)3=d9ooo   r   r<  c                    t          d|  d           t          |d          D ]\  }}t          d| d|            t                       	 t          dt          |           d                                          }|sdS t          |          dz
  }d	|cxk    rt          |          k     rn n||k     r|S ||k    rdS n# t          t          t          f$ r Y nw xY wdS )
z/Text-based numbered fallback for single-select.r'  
r!   r(  r*  z  Choice [1-r+  Nr   )	r,  rs   r.  r2   rp   r/  r0  r   r1  )r   rg   r<  r7   rB   r3  ra   s          r   r>  r>    s+    




eQ'' ! !51    	GGG
23u::2223399;; 	4#hhl    c%jj     S:%5%5J*4 )84   4s   4C 6C ;C CCc           	      \   t          |          }t          t          d|  t          j                             t          t          dt          j                             	 t          |          D ]A\  }}||v rt          dt          j                  nd}t          d| d|dz   d	d
|            B|r7 ||          }	|	r*t          t          d|	 t          j                             t                       	 t          t          dt          j                            	                                }
|
snut          |
          dz
  }d|cxk    rt          |          k     rn n|                    |h           n# t          t          t          f$ r |cY S w xY wt                       J|S )z8Text-based toggle fallback for terminals without curses.r'  z&  Toggle by number, Enter to confirm.
Tu   [✓]z[ ]r(  r   r!   r)  r*  z"  Toggle # (or Enter to confirm): r   )r  r,  r   r   r"  r#  rs   r-  r.  rp   r/  r2   r  r0  r   r1  )r   rg   r   r   r   r   r7   rB   r2  r  r3  ra   s               r   r  r    s    ]]F	%u
.
.///	%96:
F
FGGG!%(( 	5 	5HAu56&[[U7FL111eF3v33A333E334444 	?#)F++K ?e0;00&*==>>>	"BFJOOPPVVXXC c((Q,CC$$$$#e**$$$$$22C5999-x8 	" 	" 	"!!!!	"%( Ms   ;;E= 7AE= =FF)r   N)r   r   )8r~   r   dataclassesr   typingr   r   r   r   r   r	   r
   hermes_cli.colorsr   r   r   	RadioItemr   r   r.   r/  r<   rA   rL   	frozensetrP   rT   floatrb   rf   rw   ry   tupler   r   r   r   r   r   r   r   r   r   r   r   r   objectr   r   r  r   r%  r  r?  r>  r  r   r   r   <module>rK     s    


 ! ! ! ! ! ! H H H H H H H H H H H H H H H H H H + + + + + + + + #xc8C=&8 9::;	29 2 2 2 2 2hsm 4    2c  S T    @  9 S X\ ae    8# c d    , 7##N NS NT N N N N(3s 33 3s 3ut| 3 3 3 3l C EDL    2"49 "S "T#Y " " " "2        *S	 *3 *5c? * * * *:3i:!$:25:>A:: : : :II$'I7:IHKII I I I##".#
4t# # # #L   0 



4S 4 4 4 4&/# /# / / / /h 	  r r r r rt '+59X X XX9X #hX
 HtOX #c(S12X 	XX X X X| i
 "&"&*i i ii	?i i
 $Ji ti i 9t#i 	i i i iX s    	?  	
 	   : G
 !G G GG9G G
 G G 	4ZG G G GT9  	4Z	   : 6:    9  #h  H	 
 #c(S12  	X           r   