
    `gjg             
         U d Z ddlm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
mZ ddlmZ ddlmZ ddlmZ  ej$                  e      Z	 ddlmZmZ dd	lmZmZ  ed
       G d d             Zg  edddd
       eddddd       edddd
d       edddd
       edddd
       ed d!dd
       ed"d#dd
       ed$d%d       ed&d'dd
d(d)*       ed+d,dd-.       ed/d0dd.       ed1d2dd3d
4       ed5d6dd7d       ed8d9dd:d;       ed<d=dd>.       ed?d@dd
dAdBC       edDdEd       edFdGdd
dH       edIdJdd
dK       edLdMddNdO       edPdQddRS       edTdUddVd
dWdXY       edZd[dd\dO       ed]d^ddO.       ed_d`dda.       edbdcddO.       edddeddf.       edgdhd       edidjdk       edldmdk       edndodd
dpq       edrdsdd.       edtdud       edvdwdxd
       edydzdxd{.       ed|d}dxd~d       edddxd.       edddxd
d       edddxd
ddd       edddxd
d       edddxdd       edddx       edddxdd       edddxdd       edddxd
d       edddxd
dd       edddxdd       edddxd
dd       eddddd
4       edddd
       edddd
dd       eddddd       eddd       edddd
dd       edddd
ddŬC       eddddȬ.       edddd
dd̬       eddddddѬҫ       eddddd֬       eddddd٬       edddddܬ       edddd
       eddddS       eddddS       edddd
dd謤       edddd
       edddkd
d       edddk       edddd
       edddkd.       edddkd
d       edddk       edddkd.       edddkd
d       ed ddkd
d       edddkd
d>       edddkd
       edddkd
d	       ed
ddk       edddkdS       edddkd.       edddd
ddC      Zded<   dWdZ  e        Z!ded<   dXdZ"dYdZ#i Z$ded<   eD ]^  Z%e%jL                  r e#e%      e$d e%jN                   <   e%jP                  D ]'  Z)e%jT                   d!e%jN                   d"e$d e) <   ) ` i Z+d#ed$<   eD ]p  Z%e%jL                  re+jY                  e%jZ                  i       Z.e$d e%jN                      e.d e%jN                   <   e%jP                  D ]  Z)e$d e)    e.d e) <    r i Z/d%ed&<   eD ]2  Z%e%j`                  s e1e%j`                        e/d e%jN                   <   4  ejd                  d'      Z3eD ]e  Z%d e%jN                   Z4e4e/v se%jj                  s$e3jm                  e%jj                        Z7e7sBe7jq                  d      js                  d(      e/e4<   g  e:d) eD              Z;d*ed+<   dZd,Z< e:h d-      Z=d*ed.<   d[d/Z>d\d0Z?d]d^d1Z@d_d2ZAd`d3ZBdad4ZCdbd5ZDd6ZEd7ZFh d8ZGd9ZH	 dcd:ZIddd;ZJded<ZKdfd=ZLdgd>ZM	 	 	 	 dhd?ZNd@ZO	 eOZP ejd                  dA      ZQ ejd                  dB      ZRdidCZS	 	 	 	 	 	 djdDZTeTZU	 	 dk	 	 	 	 	 	 	 	 	 	 	 dldEZVdmdndFZW	 	 	 	 	 	 dodGZX	 	 	 	 dpdHZYdIZZd@Z[ ejd                  dJ      Z\ e:h dK      Z]dLZ^ e:h dM      Z_didNZ`dadOZadqdrdPZbdsdQZc G dR dSe      Zd G dT dUe      ZedtdVZfy# e$ r eZeZdZdZY zw xY w(u  a  Slash command definitions and autocomplete for the Hermes CLI.

Central registry for all slash commands. Every consumer -- CLI help, gateway
dispatch, Telegram BotCommands, Slack subcommand mapping, autocomplete --
derives its data from ``COMMAND_REGISTRY``.

To add a command: add a ``CommandDef`` entry to ``COMMAND_REGISTRY``.
To add an alias: set ``aliases=("short",)`` on the existing ``CommandDef``.
    )annotationsN)CallableMapping)	dataclass)Any)is_truthy_value)AutoSuggest
Suggestion)	Completer
CompletionT)frozenc                      e Zd ZU dZded<   ded<   ded<   dZded<   d	Zded
<   dZded<   dZded<   dZ	ded<   dZ
ded<   y)
CommandDefz%Definition of a single slash command.strnamedescriptioncategory tuple[str, ...]aliases 	args_hintsubcommandsFboolcli_onlygateway_onlyN
str | Nonegateway_config_gate)__name__
__module____qualname____doc____annotations__r   r   r   r   r   r   r       F/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/commands.pyr   r   -   sR    /
IM!G_!Is#%K%HdL$&**r$   r   startz0Acknowledge platform start pings without a replySession)r   newz0Start a new session (fresh session ID + history))resetz[name])r   r   topicz,Enable or inspect Telegram DM topic sessionsz[off|help|session-id])r   r   clearz$Clear screen and start a new session)r   redrawz6Force a full UI repaint (recovers from terminal drift)historyzShow conversation historysavezSave the current conversationretryz(Retry the last message (resend to agent)promptz<Compose your next prompt in $EDITOR (markdown), then send itz[initial text])compose)r   r   r   undoz.Back up N user turns and re-prompt (default 1)z[N]r   titlez#Set a title for the current sessionhandoffzGHand off this session to a messaging platform (Telegram, Discord, etc.)z
<platform>)r   r   branchz5Branch the current session (explore a different path))forkcompresszhCompress conversation context (add 'here [N]' to keep recent N turns; --preview shows what would happen))compactz.[here [N] | focus topic | --preview|--dry-run]rollbackz&List or restore filesystem checkpointsz[number]snapshotz8Create or restore state snapshots of Hermes config/state)snapz[create|restore <id>|prune])r   r   r   stopz%Kill all running background processesapprovez#Approve a pending dangerous commandz[session|always]denyz;Deny a pending dangerous command (optionally with a reason)z[all] [reason]
backgroundzRun a prompt in the background)bgbtwz<prompt>agentsz$Show active agents and running tasks)tasks)r   journeyz"Open the learning journey timeline)learningzmemory-graphz[list|delete <id>|edit <id>])listdeleteedit)r   r   r   r   queuez4Queue a prompt for the next turn (doesn't interrupt))qsteerz>Inject a message after the next tool call without interruptinggoalz?Set a standing goal Hermes works on across turns until achievedzT[text | draft <text> | show | pause | resume | clear | status | wait <pid> | unwait]moazTRun one prompt through the default Mixture of Agents preset, then restore your modelsubgoalz/Add or manage extra criteria on the active goalz[text | remove N | clear]statusz,Show session, model, token, and context infowhoamiz-Show your slash command access (admin / user)Infoprofilez+Show active profile name and home directorysethomez!Set this chat as the home channel)zset-home)r   r   resumez!Resume a previously-named sessionsessionsz#Browse and resume previous sessionsconfigzShow current configurationConfigurationmodelz2Switch model (session-scoped; --global to persist)z:[model] [--provider name] [--global|--session] [--refresh]zcodex-runtimez7Toggle codex app-server runtime for OpenAI/Codex models)codex_runtimez[auto|codex_app_server]personalityzSet a predefined personality	statusbarz#Toggle the context/model status bar)sb)r   r   
timestampsz2Toggle [HH:MM] timestamps on messages and /historyz[on|off|status])onoffrP   )ts)r   r   r   r   verbosez@Cycle tool progress display: off -> new -> all -> verbose -> logzdisplay.tool_progress_command)r   r   footerz7Toggle gateway runtime-metadata footer on final replies)r   r   yoloz7Toggle YOLO mode (skip all dangerous command approvals)	reasoningz#Manage reasoning effort and displayz'[level|show|hide|full|clamp] [--global])noneminimallowmediumhighxhighmaxultrashowhider_   r`   fullclamp--globalfastuS   Toggle fast mode — OpenAI Priority Processing / Anthropic Fast Mode (Normal/Fast)z[normal|fast|status] [--global])normalrs   rP   r_   r`   rr   skinz%Show or change the display skin/theme)r   r   	indicatorz!Pick the TUI busy-indicator stylez[kaomoji|emoji|unicode|ascii])kaomojiemojiunicodeascii)r   r   r   voicezToggle voice modez[on|off|tts|status])r_   r`   ttsrP   busyz/Control what Enter does while Hermes is workingz[queue|steer|interrupt|status])rJ   rL   	interruptrP   toolsz4Manage tools: /tools [list|disable|enable] [name...]zTools & Skillsz[list|disable|enable] [name...]toolsetszList available toolsetsskillsz*Search, install, inspect, or manage skillszskills.write_approval)
searchbrowseinspectinstallauditpendingr>   rejectdiffapproval)r   r   r   memoryz7Review pending memory writes / toggle the approval gatez-[pending|approve|reject|approval] [id|on|off])r   r>   r   r   bundlesz8List skill bundles (aliases /<name> for multiple skills)petz>Toggle or adopt a petdex mascot (/pet, /pet list, /pet <slug>)z[toggle|list|scale <n>|<slug>])togglerG   scaler`   hatchz,Generate a new petdex pet from a description)zgenerate-petz[description]learnzPLearn a reusable skill from anything you describe (dirs, URLs, this chat, notes)z<what to learn from>cronzManage scheduled tasksz[subcommand])rG   addcreaterI   pauserU   runremovesuggestionsz-Review suggested automations (accept/dismiss))suggestz[accept|dismiss N | catalog])acceptdismisscatalogr+   )r   r   r   	blueprintz.Set up an automation from a blueprint template)bpz[name] [slot=value ...]curatorzGBackground skill maintenance (status, run, pin, archive, list-archived))rP   r   r   rU   pinunpinrestorezlist-archivedkanbanz:Multi-profile collaboration board (tasks, links, comments))!initboardsr   rG   lsrn   assignreclaimreassigndiagnosticsdiaglinkunlinkclaimcommentcompleterI   blockunblockarchivetaildispatchstatsznotify-subscribeznotify-listznotify-unsubscribelogruns	heartbeat	assigneescontextspecifygcreloadz.Reload .env variables into the running sessionz
reload-mcpzReload MCP servers from config)
reload_mcpzreload-skillsz?Re-scan ~/.hermes/skills/ for newly installed or removed skills)reload_skillsbrowserzBConnect browser tools to your live Chromium-family browser via CDPz[connect|disconnect|status])connect
disconnectrP   pluginsz'List installed plugins and their statuscommandsz*Browse all commands and skills (paginated)z[page]helpzShow available commandsrestartz9Gracefully restart the gateway after draining active runsusagezLShow token usage and rate limits; `reset` redeems a banked Codex limit resetz[reset [--force]]subscriptionz0View your Nous plan and change it in the browser)upgradetopupz7Show your Nous balance and manage billing on the portalinsightsz!Show usage insights and analyticsz[days]	platformsz&Show gateway/messaging platform status)gatewayplatformz1Pause, resume, or list a failing gateway platformz<pause|resume|list> [name]copyz-Copy the last assistant response to clipboardpastez*Attach clipboard image from your clipboardimagez.Attach a local image file for your next promptz<path>updatez)Update Hermes Agent to the latest versionversionzShow Hermes Agent version)vdebugz@Upload debug report (system info + logs) and get shareable linksz[nous|local]quitz:Exit the CLI (use --delete to also remove session history)Exit)exitz
[--delete]zlist[CommandDef]COMMAND_REGISTRYdict[str, CommandDef]c                 j    i } t         D ]'  }|| |j                  <   |j                  D ]  }|| |<   	 ) | S )z+Map every name and alias to its CommandDef.)r   r   r   )lookupcmdaliass      r%   _build_command_lookupr     sG    $&F  sxx[[ 	 EF5M	   Mr$   _COMMAND_LOOKUPc                f    t         j                  | j                         j                  d            S )zmResolve a command name or alias to its CommandDef.

    Accepts names with or without the leading slash.
    /)r   getlowerlstrip)r   s    r%   resolve_commandr     s&    
 tzz|223788r$   c                    | j                   r)| j                   d| j                   d| j                    dS | j                  S )z;Build a CLI-facing description string including usage hint.z
 (usage: / ))r   r   r   )r   s    r%   _build_descriptionr     s9    
}}//"*SXXJaaHH??r$   dict[str, str]COMMANDSr   z (alias for /r   zdict[str, dict[str, str]]COMMANDS_BY_CATEGORYzdict[str, list[str]]SUBCOMMANDSz[a-z]+(?:\|[a-z]+)+|c              #     K   | ]=  }|j                   r|j                  r#|j                  g|j                  D ]  }|  ? y wN)r   r   r   r   ).0r   r   s      r%   	<genexpr>r   P  sL      3<<322(CKK(	3 	 	33s   AAzfrozenset[str]GATEWAY_KNOWN_COMMANDSc                P    | sy| t         v ryt               D ]  \  }}}|| k(  s y y)u  Return True if ``name`` resolves to a gateway-dispatchable slash command.

    This covers both built-in commands (``GATEWAY_KNOWN_COMMANDS`` derived
    from ``COMMAND_REGISTRY``) and plugin-registered commands, which are
    looked up lazily so importing this module never forces plugin
    discovery. Gateway code uses this to decide whether to emit
    ``command:<name>`` hooks — plugin commands get the same lifecycle
    events as built-ins.
    FT)r   _iter_plugin_command_entries)r   plugin_name_description
_args_hints       r%   is_gateway_known_commandr   X  s?     %%1M1O -\:$ r$   >   r(   r?   r   r=   rJ   rL   rC   rP   r   r>   rS   r   r   r   r@   ACTIVE_SESSION_BYPASS_COMMANDSc                $    | rt        |       duS dS )u  Return True for any resolvable slash command.

    Rationale: every gateway-registered slash command either has a
    specific Level-2 handler in gateway/run.py (/stop, /new, /model,
    /approve, etc.) or reaches the running-agent catch-all that returns
    a "busy — wait or /stop first" response. In both paths the command
    is dispatched, not queued.

    Queueing is always wrong for a recognized slash command because the
    safety net in gateway.run discards any command text that reaches
    the pending queue — which meant a mid-run /model (or /reasoning,
    /voice, /insights, /title, /resume, /retry, /undo, /compress,
    /usage, /reload-mcp, /sethome, /reset) would silently
    interrupt the agent AND get discarded, producing a zero-char
    response. See issue #5057 / PRs #6252, #10370, #4665.

    ACTIVE_SESSION_BYPASS_COMMANDS remains the subset of commands with
    explicit Level-2 handlers; the rest fall through to the catch-all.
    NF)r   )command_names    r%   should_bypass_active_sessionr     s    ( 9E?<(4O%Or$   c                    t         D  cg c]  } | j                  s|  }} |s
t               S 	 ddlm}  |       }t               }|D ]r  }|}|j                  j                  d      D ]'  }t        |t              r|j                  |      }%d} n t        |d      sX|j                  |j                         t |S c c} w # t
        $ r t               cY S w xY w)zReturn canonical names of commands whose ``gateway_config_gate`` is truthy.

    Reads ``config.yaml`` and walks the dot-separated key path for each
    config-gated command.  Returns an empty set on any error so callers
    degrade gracefully.
    r   read_raw_config.NF)default)r   r   sethermes_cli.configr  	Exceptionsplit
isinstancedictr   r   r   r   )cgatedr  cfgresultr   valkeys           r%   _resolve_config_gatesr    s     )B1A,A,AQBEBu5 uF 	!**005 	C#t$ggcl	 3.JJsxx 	! M' C  us   B>B>C CCc                n    | j                   sy| j                  r||n	t               }| j                  |v S y)a}  Check if *cmd* should appear in gateway surfaces (help, menus, mappings).

    Unconditionally available when ``cli_only`` is False.  When ``cli_only``
    is True but ``gateway_config_gate`` is set, the command is available only
    when the config value is truthy.  Pass *config_overrides* (from
    ``_resolve_config_gates()``) to avoid re-reading config for every command.
    TF)r   r   r  r   )r   config_overrides	overridess      r%   _is_gateway_availabler    s:     <<
(8(D$J_Ja	xx9$$r$   c                @    | j                         j                  d      S )zFReturn True when selecting a command without text would be incomplete.<)strip
startswithr3   s    r%   _requires_argumentr    s    ??'',,r$   c            	        t               } g }t        D ]  }t        ||       s|j                  rd|j                   nd}g }|j                  D ]V  }|j                  dd      |j                  j                  dd      k(  r||j                  k7  rB|j                  d| d       X |rddj                  |       d	nd}|j                  d|j                   | d
|j                   |         |S )z3Generate gateway help text lines from the registry.r   r   -_z`/`z	 (alias: z, r   z` -- )
r  r   r  r   r   replacer   appendjoinr   )r  linesr   argsalias_partsa
alias_notes          r%   gateway_help_linesr'    s    %'IE N$S)4&)mm3==/"!# 	*Ayyc"chh&6&6sC&@@Q#((]A3ay)		*
 ?Jy;!7 8:r
r#((D6s.?
|LMN Lr$   c                    	 ddl m}  	  |        xs i }g }|j                         D ]  \  }}t	        |t
              rt	        |t              s't        |j                  d      xs d|       }t        |j                  d      xs d      j                         }|j                  |||f        |S # t        $ r g cY S w xY w# t        $ r g cY S w xY w)am  Yield (name, description, args_hint) tuples for all plugin slash commands.

    Plugin commands are registered via
    :func:`hermes_cli.plugins.PluginContext.register_command`. They behave
    like ``CommandDef`` entries for gateway surfacing: they appear in the
    Telegram command menu, in Slack's ``/hermes`` subcommand mapping, and
    (via :func:`plugins.platforms.discord.adapter._register_slash_commands`) in
    Discord's native slash command picker.

    Lookup is lazy so importing this module never forces plugin discovery
    (which can trigger filesystem scans and environment-dependent
    behavior).
    r   get_plugin_commandsr   Run /r   r   )
hermes_cli.pluginsr*  r  itemsr	  r   r
  r   r  r   )r*  r   entriesr   metar   r   s          r%   r   r     s    :&(.B +-Gnn& 7
d$$JtT,B$((=1CuTF^D-34::<	k9567 N  	  	s"   B2 C 2C ?C CCc                 8   t               } g }t        D ]D  }t        ||       st        |j                        }|s(|j                  ||j                  f       F t               D ]3  \  }}}t        |      rt        |      }|s!|j                  ||f       5 |S )ac  Return (command_name, description) pairs for Telegram setMyCommands.

    Telegram command names cannot contain hyphens, so they are replaced with
    underscores.  Aliases are skipped -- Telegram shows one menu entry per
    canonical command.

    Built-in commands that require arguments (e.g. /queue, /steer, /background)
    are **included** because their handlers return usage text when selected
    without a payload, making them discoverable via autocomplete.

    Plugin-registered slash commands that require arguments are **excluded**
    because plugins may not provide a no-arg usage fallback.
    )	r  r   r  _sanitize_telegram_namer   r   r   r   r  )r  r  r   tg_namer   r   r   s          r%   telegram_bot_commandsr3    s     &'I$&F 6$S)4 *#((3MM7COO456 )E(F 2$k9i()$/MM7K012 Mr$   <   d   >   r   prependr  )r   r(   r=   rP   rU   rV   rY   r   r   r   rb   r   r>   r?   rJ   rL   r@   re   r   r   r   rS   rQ   c                    | }|D ]'  }t        |t              si c S |j                  |      }) t        |t              r|S i S r   )r	  r   r   )rootpathnoder  s       r%   _nested_mappingr;  P  sH    D $(Ixx} dG,44"4r$   c            	        	 ddl m}   |        xs i }t        |t              si }t        t        |dddd            }|j                  dt              }	 t        |      }t        dt        t        |            }t        |j                  d	      xs d
      j!                         j#                         }|t$        vrd
}|j                  d      }t        |t&              r3|D cg c]'  }t        |      j!                         st        |      ) }}ng }|||dS # t        $ r i }Y w xY w# t        t        f$ r	 t        }Y w xY wc c}w )zReturn normalized Telegram command-menu config with safe defaults.

    Canonical user-facing path:
    ``platforms.telegram.extra.command_menu``.
    r   r  r   telegramextracommand_menumax_commands   priority_moder6  priority)r@  rB  rC  )r  r  r  r	  r   r
  r;  r   #_DEFAULT_TELEGRAM_MENU_MAX_COMMANDSint	TypeError
ValueErrorrl   min_TELEGRAM_BOT_API_MAX_COMMANDSr   r  r   _TELEGRAM_PRIORITY_MODESrG   )r  raw_cfgmenu_cfgr@  rB  raw_priorityitemrC  s           r%   _telegram_command_menu_configrO  Y  s8   5!#)r gw'OG[*g~^_H<<0STL;<( q#<lKLL_5BCIIKQQSM44!<<
+L,%*6L$#d)//:KCILL %& 1   z" ;:; Ms/   D D+ "EED('D(+EEc                 .    t        t               d         S )z@Return configured Telegram BotCommand menu cap with safe bounds.r@  )rE  rO  r   r$   r%   telegram_menu_max_commandsrQ    s    ,.~>??r$   c                    g }t               }| D ]@  }t        t        |            }|s||vs|j                  |       |j	                  |       B t        |      S r   )r  r1  r   r   r   tuple)	raw_namesr  seenraw_namer   s        r%   _dedupe_sanitized_namesrW    sX    FUD  &s8}5D$HHTNMM$	 
 =r$   c                     t               } t        t        | d               }t        t        t                    }| d   dk(  r|}t        |      S | d   dk(  r||z   }t        |      S ||z   }t        |      S )NrC  rB  r  r   )rO  rG   rW  _TELEGRAM_MENU_PRIORITY)rL  
configureddefaultsrM  s       r%   _telegram_effective_priorityr\    s    ,.H-hz.BCDJ+,CDEH I-! #<00 
/	"h	.*, #<00 "H,"<00r$   c                    t        t                     D ci c]  \  }}||
 c}}t        t        |       fd      D cg c]  \  }}|	 c}}S c c}}w c c}}w )Nc                H    | d   d   v rd| d   d      | d   fS d| d   fS )NrA  r   r   )rN  rC  s    r%   <lambda>z4_prioritize_telegram_menu_commands.<locals>.<lambda>  sN    
 AwqzX%	 a$Q  Q r$   r  )	enumerater\  sorted)r   indexr   _indexcommandrC  s        @r%   "_prioritize_telegram_menu_commandsrf    sk    
 %%A%CDE4 	eH  &h	 
FG 	 	s   AA    z
[^a-z0-9_]z_{2,}c                    | j                         j                  dd      }t        j                  d|      }t        j                  d|      }|j                  d      S )uR  Convert a command/skill/plugin name to a valid Telegram command name.

    Telegram requires: 1-32 chars, lowercase a-z, digits 0-9, underscores only.
    Steps: lowercase → replace hyphens with underscores → strip all other
    invalid characters → collapse consecutive underscores → strip leading/
    trailing underscores.
    r  r  r   )r   r  _TG_INVALID_CHARSsub_TG_MULTI_UNDERSCOREr  rawr   s     r%   r1  r1    sN     99;sC(D  T*D##C.D::c?r$   c                $   t        |      }g }| D ]}  }|^}}}t        |      t        kD  r8|dt         }||v r)|dt        dz
   }	t        d      D ]  }
|	 |
 }||vs n P|}||v rW|j	                  |       |j                  ||g|        |S )a  Enforce 32-char command name limit with collision avoidance.

    Both Telegram and Discord cap slash command names at 32 characters.
    Names exceeding the limit are truncated.  If truncation creates a duplicate
    (against *reserved* names or earlier entries in the same batch), the name is
    shortened to 31 chars and a digit ``0``-``9`` is appended to differentiate.
    If all 10 digit slots are taken the entry is silently dropped.

    Accepts tuples of any length >= 2.  Extra elements beyond ``(name, desc)``
    (e.g. ``cmd_key``) are passed through unchanged, so callers can attach
    metadata that survives the rename.
    NrA  
   )r  len_CMD_NAME_LIMITranger   r   )r.  reservedusedr  entryr   descr>  	candidateprefixdigits              r%   _clamp_command_namesrz    s      ]DF ,"dUt9&-o.ID 223"2Y E#)(5' 2I , D4<tT*E*+#,$ Mr$   c                   g }g }	 ddl m}  |       }t        |      D ]R  }	|r ||	      n|	}
|
s||	   j                  dd      }t	        |      |kD  r|d|dz
   dz   }|j                  |
|f       T 	 t        ||      }|j                  d |D               |D ]  \  }}|j                  ||d	f        t               }	 dd
l
m}  ||       }g }	 ddlm} ddlm} ddl
m} t#        |j%                               }t#        |dz  j%                               j'                  d      dz   }|j'                  d      dz   g}|j)                  d  |       D                |       }t        |      D ]  }||   }|j                  dd	      st+        fd|D              s2j-                  |      rD|j                  dd	      }||v r[|j/                  d      }|r ||      n|}
|
s{|j                  dd	      }t	        |      |kD  r|d|dz
   dz   }|j                  |
||f        	 t        ||      }t1        d|t	        |      z
        }t1        dt	        |      |z
        }|d| D ]  \  }}}|j                  |||f        |d| |fS # t        $ r Y *w xY w# t        $ r Y w xY w# t        $ r Y w xY w)a  Collect plugin + skill entries for a gateway platform.

    Priority order:
      1. Plugin slash commands (take precedence over skills)
      2. Built-in skill commands (fill remaining slots, alphabetical)

    Only skills are trimmed when the cap is reached.
    Hub-installed skills are excluded.  Per-platform disabled skills are
    excluded.

    Args:
        platform: Platform identifier for per-platform skill filtering
            (``"telegram"``, ``"discord"``, etc.).
        max_slots: Maximum number of entries to return (remaining slots after
            built-in/core commands).
        reserved_names: Names already taken by built-in commands.  Mutated
            in-place as new names are added.
        desc_limit: Max description length (40 for Telegram, 100 for Discord).
        sanitize_name: Optional name transform applied before clamping, e.g.
            :func:`_sanitize_telegram_name` for Telegram.  May return an
            empty string to signal "skip this entry".

    Returns:
        ``(entries, hidden_count)`` where *entries* is a list of
        ``(name, description, cmd_key)`` triples and *hidden_count* is the
        number of skill entries dropped due to the cap.  ``cmd_key`` is the
        original ``/skill-name`` key from :func:`get_skill_commands`.
    r   r)  r   Plugin commandN   ...c              3  &   K   | ]	  \  }}|  y wr   r   )r   nr  s      r%   r   z1_collect_gateway_skill_entries.<locals>.<genexpr>8  s     51!5s   r   get_disabled_skill_namesr   get_skill_commands
SKILLS_DIRget_external_skills_dirs.hubr   c              3  V   K   | ]!  }t        |      j                  d       d z    # yw)r   N)r   rstrip)r   ds     r%   r   z1_collect_gateway_skill_entries.<locals>.<genexpr>S  s'      !
)*CFMM#$!
s   ')skill_md_pathc              3  @   K   | ]  }j                  |        y wr   )r  )r   rx  
skill_paths     r%   r   z1_collect_gateway_skill_entries.<locals>.<genexpr>\  s     Uz,,V4Us   r   )r,  r*  rb  r   rp  r   r  rz  r   r  agent.skill_utilsr  agent.skill_commandsr  tools.skills_toolr  r  r   resolver  extendanyr  r   rl   ) r   	max_slotsreserved_names
desc_limitsanitize_nameall_entriesplugin_pairsr*  plugin_cmdscmd_namer   rv  r  r  _platform_disabledr  skill_triplesr  r  r  _skills_dir_hub_dir_allowed_prefixes
skill_cmdscmd_keyinfo
skill_namerV  	remaininghidden_countkr  s                                   @r%   _collect_gateway_skill_entriesr    s%   F /1K +-L:)+{+ 	.H.;=*Dx(,,]<LMD4y:%OZ!^,u4t-	. (nEL555 '1Aq":&' $'5>5xH 13M&;0>*,,./
V+4467>>sCcI )//4s:;   !
.F.H!
 	
 ()
j) 	8Gg&D/26JUCTUU$$X.&"-J//~~c*H.;=*D88M2.D4y:%OZ!^,u4  $g!67'	82 )GM Ay3{#334Iq#m,y89L ), &1aAq!9%& z	"L00I    R  s7   A-J J# EJ3 	J J #	J0/J03	J?>J?c                V   t        t        t                           }|D ch c]  \  }}|	 }}}t        |      }t        dt	        |      | z
        }t        d| t	        |      z
        }t        d||dt              \  }}	|j                  d |D               |d|  |	|z   fS c c}}w )u  Return Telegram menu commands capped to the Bot API limit.

    Priority order (higher priority = never bumped by overflow):
      1. Core CommandDef commands (always included)
      2. Plugin slash commands (take precedence over skills)
      3. Built-in skill commands (fill remaining slots, alphabetical)

    Skills are the only tier that gets trimmed when the cap is hit.
    User-installed hub skills are excluded — accessible via /skills.
    Skills disabled for the ``"telegram"`` platform (via ``hermes skills
    config``) are excluded from the menu entirely.

    Returns:
        (menu_commands, hidden_count) where hidden_count is the number of
        commands omitted due to the cap.
    r   r=  (   )r   r  r  r  r  c              3  ,   K   | ]  \  }}}||f  y wr   r   )r   r  r  _ks       r%   r   z)telegram_menu_commands.<locals>.<genexpr>  s     781aA7s   N)rf  rG   r3  rl   rp  r  r1  r  )
r@  core_commandsr  r  r  all_commandshidden_core_countremaining_slotsr.  r  s
             r%   telegram_menu_commandsr    s    " 7t<Q<S7TUM$12DAqa2N2&LAs<0<?@!\C,==>O:!%-G\ 7w77&7H(HHH 3s   B%c                2    t        d| t        |      d      S )u  Return skill entries for Discord slash command registration.

    Same priority and filtering logic as :func:`telegram_menu_commands`
    (plugins > skills, hub excluded, per-platform disabled excluded), but
    adapted for Discord's constraints:

    - Hyphens are allowed in names (no ``-`` → ``_`` sanitization)
    - Descriptions capped at 100 chars (Discord's per-field max)

    Args:
        max_slots: Available command slots (100 minus existing built-in count).
        reserved_names: Names of already-registered built-in commands.

    Returns:
        ``(entries, hidden_count)`` where *entries* is a list of
        ``(discord_name, description, cmd_key)`` triples.  ``cmd_key`` is
        the original ``/skill-name`` key needed for the slash handler callback.
    discordr5  )r   r  r  r  )r  r  )r  r  s     r%   discord_skill_commandsr    s"    , *>*	 r$   c                B   ddl m} t               }	 ddlm}  |d      }i }g }t        j                  | d      }d}	 ddlm	} ddlm
}	 dd	lm}
 |
j                         }|
d
z  j                         }|g}	  |	       D ](  }	 |j                   ||      j                                * 	  |       }t        |      D ]  }||   }|j!                  dd      }|s ||      j                         }t#        |      j%                  t#        |            rXd}|D ]  }	 |j'                  |       |} n |y|j!                  dd      }||v r|j+                  d      }|dd }||v rB||   }|dk(  rt,        j/                  d|||       nt,        j/                  d||||       |dz  }|||<   |j!                  dd      }t1        |      dkD  r|dd dz   }|j2                  j'                  |      }|j4                  }t1        |      dk\  r+|d   }|j7                  |g       j                  |||f       y|j                  |||f        	 |||fS # t
        $ r Y =w xY w# t
        $ r Y w xY w# t
        $ r Y w xY w# t(        $ r Y vw xY w# t
        $ r Y Pw xY w)u'  Return skill entries organized by category for Discord ``/skill`` autocomplete.

    Skills whose directory is nested at least 2 levels under a scan root
    (e.g. ``creative/ascii-art/SKILL.md``) are grouped by their top-level
    category.  Root-level skills (e.g. ``dogfood/SKILL.md``) are returned as
    *uncategorized*.

    Scan roots include the local ``SKILLS_DIR`` **and** any configured
    ``skills.external_dirs`` — matching the widened filter applied to the
    flat ``discord_skill_commands()`` collector in #18741. Without this
    parity, external-dir skills are visible via ``hermes skills list`` and
    the agent's ``/skill-name`` dispatch but silently absent from Discord's
    ``/skill`` autocomplete.

    Filtering mirrors :func:`discord_skill_commands`: hub skills excluded,
    per-platform disabled excluded, names clamped to 32 chars, descriptions
    clamped to 100 chars.

    The legacy 25-group × 25-subcommand caps (from the old nested
    ``/skill <cat> <name>`` layout) are **not** applied — the live caller
    (``_register_skill_group`` in ``gateway/platforms/discord.py``, refactored
    in PR #11580) flattens these results and feeds them into a single
    autocomplete callback, which scales to thousands of entries without any
    per-command payload concerns. ``hidden_count`` is retained in the return
    tuple for backward compatibility and still reports skills dropped for
    other reasons (32-char clamp collision vs a reserved name).

    Returns:
        ``(categories, uncategorized, hidden_count)``

        - *categories*: ``{category_name: [(name, description, cmd_key), ...]}``
        - *uncategorized*: ``[(name, description, cmd_key), ...]``
        - *hidden_count*: skills dropped due to name clamp collisions
          against already-registered command names.
    r   )Pathr  r  r  z
<reserved>r  r  r  r  r  r   Nr   r   rg  u   Discord /skill: %r (from %r) collides on its 32-char clamp with a reserved gateway command name %r — the skill will not appear in the /skill autocomplete. Rename the skill's frontmatter ``name:`` to differ in its first 32 chars.u   Discord /skill: %r and %r both clamp to %r on Discord's 32-char command-name limit — only %r will appear in the /skill autocomplete. Rename one skill's frontmatter ``name:`` to differ in its first 32 chars.rA  r   r5  a   r~     )pathlibr  r  r  r  r  r
  fromkeysr  r  r  r  r  r  r   rb  r   r   r  relative_torG  r   loggerwarningrp  parentparts
setdefault)r  _Pr  r  
categoriesuncategorized_names_usedhiddenr  r  r  r  r  _scan_rootsextr  r  r  r  spmatched_rootr8  r  rV  discord_namepriorrv  relr  cats                                 r%   "discord_skill_commands_by_categoryr    s   L ##&5>5yI
 9;J02M #'--"MKFd;>0 ((*'002 "-	/1 &&r#w'89 ()
j) M	DGg&D/26JJ'')B 2w!!#h-0 '+L# NN4(  $ #&"-J//~~c*H#CR=L{* $L1L(NN1
 %g| NN.
 we !(/K%88M2.D4y3CRy5( ))''5CIIE5zQAh%%c2.55|T76ST$$lD'%BC[M	Db }f,,i  : !  		( " |  s   I 8J 7
I2 %I"'I2 )A2J J-DJ 	II"	I/+I2 .I//I2 2	I?;J >I??J 	JJ JJ 	JJ2   z[^a-z0-9_\-]>   medndmsgprowhoawayfeedr!  muteopenleaveshrugr*   expandremindr   rP   collapse	shortcuts)rB   rA   >   rN   r   r   c                    | j                         }t        j                  d|      }|j                  d      }|dt         S )zConvert a command name to a valid Slack slash command name.

    Slack allows lowercase a-z, digits, hyphens, and underscores. Max 32
    chars. Uppercase is lowercased; invalid chars are stripped.
    r   z-_N)r   _SLACK_INVALID_CHARSrj  r  _SLACK_NAME_LIMITrl  s     r%   _sanitize_slack_namer    s>     99;D##B-D::dD""##r$   c            	       	 t               } g t               	j                  d       	j                  d       d	fd}t        D ci c]"  }t        ||       r|j                  D ]  }|| $ }}}t        D ]H  }|j                  |      }| ||d|j                   d|j                   |j                  xs d       J t        D ];  }t        ||       s ||j                  |j                  |j                  xs d       = t        D ]R  }t        ||       s|j                  D ]4  } ||d|j                   d|j                   |j                  xs d       6 T t               D ]  \  }}} ||||xs d        S c c}}w )a  Return (slash_name, description, usage_hint) triples for Slack.

    Every gateway-available command in ``COMMAND_REGISTRY`` is surfaced as
    a standalone Slack slash command (e.g. ``/btw``, ``/stop``, ``/model``),
    matching Discord's and Telegram's model where every command is a
    first-class slash and not a ``/hermes <verb>`` subcommand.

    Both canonical names and aliases are included so users can type any
    documented form (e.g. ``/background``, ``/bg``, and ``/btw`` all work).
    Plugin-registered slash commands are included too.

    Commands whose sanitized name collides with a Slack built-in
    (e.g. ``/status``, ``/me``, ``/join``) are silently skipped.  Users
    can still reach them via ``/hermes <command>``.

    Results are clamped to Slack's 50-command limit with duplicate-name
    avoidance. ``/hermes`` is always reserved as the first entry so the
    legacy ``/hermes <subcommand>`` form keeps working for anything that
    gets dropped by the clamp or for free-form questions.
    )hermesz"Talk to Hermes or run a subcommandz[subcommand] [args]r  c                    t        |       }|r|v ry |t        v ry |t        v ry t              t        k\  ry j                  ||d d |d d f       j                  |       y )N   r5  )r  _SLACK_RESERVED_COMMANDS_SLACK_VIA_HERMES_ONLYrp  _SLACK_MAX_SLASH_COMMANDSr   r   )r   rv  hint
slack_namer.  rU  s       r%   _addz"slack_native_slashes.<locals>._add  sm    )$/
Z4/11//w<44
D#JTc
;<r$   zAlias for /u    — r   )r   r   rv  r   r  r   returnNone)r  r  r   r   r   r  r   _SLACK_PRIORITY_ALIASESr   r   r   r   r   )
r  r  r   r   _alias_to_cmdr   r   r   r.  rU  s
           @@r%   slack_native_slashesr    s   * &'I*,GUD NNZ[HHX* $ i0[[	  	s
M  ) ]&?+chhZuS__4EFH[Y[\]   =$S)4SXXs(;<=   ]$S)4[[ 	]E +chhZuS__4EFH[Y[\	]] )E(F 1$k9T;	R01 N=s   'Fc                    g }t               D ]/  \  }}}d| |xs d| d| d}|r||d<   |j                  |       1 dd|iiS )u  Generate a Slack app manifest with all gateway commands as slashes.

    ``request_url`` is required by Slack's manifest schema for every slash
    command, but in Socket Mode (which we use) Slack ignores it and routes
    the command event through the WebSocket. A placeholder URL is fine.

    The returned dict is the ``features.slash_commands`` portion only —
    callers compose it into a full manifest (or merge into an existing
    one). Keeping it narrow avoids coupling us to the rest of the manifest
    schema (display_information, oauth_config, settings, etc.) which users
    set up once in the Slack UI and rarely change.
    r   r+  F)re  r   should_escapeurl
usage_hintfeaturesslash_commands)r  r   )request_urlslashesr   rv  r   ru  s         r%   slack_app_manifestr    st     G13 	dE4&z1U4&>"	
 "'E,u	 )7344r$   c                     t               } i }t        D ]D  }t        ||       sd|j                   ||j                  <   |j                  D ]
  }d| ||<    F t               D ]  \  }}}||vsd| ||<    |S )a0  Return subcommand -> /command mapping for Slack /hermes handler.

    Maps both canonical names and aliases so /hermes bg do stuff works
    the same as /hermes background do stuff.

    Plugin-registered slash commands are included so ``/hermes <plugin-cmd>``
    routes through the plugin handler.
    r   )r  r   r  r   r   r   )r  mappingr   r   r   r   r   s          r%   slack_subcommand_mapr   
  s     &'I G )$S)4zN[[ 	)E [GEN	)	) +G*H '&lJwvJGDM' Nr$   c                  B   e Zd ZdZ	 	 	 d	 	 	 	 	 	 	 ddZddZddZddZedd       Z	ddZ
dd	Z eh d
      Zedd       Zed d       Zed!d"d       Zed d       Zd!d"dZd#dZed$d       Zd%d&dZed'd       Zed'd       Zed'd       Zed'd       Zd Zy)(SlashCommandCompleterzJAutocomplete for built-in slash commands, subcommands, and skill commands.Nc                X    || _         || _        || _        g | _        d| _        d| _        y )Ng        r   )_skill_commands_provider_command_filter_skill_bundles_provider_file_cache_file_cache_time_file_cache_cwd)selfskill_commands_providercommand_filterskill_bundles_providers       r%   __init__zSlashCommandCompleter.__init__)  s5     )@%-'=$&('*$&r$   c                p    | j                   y	 t        | j                  |            S # t        $ r Y yw xY w)NT)r  r   r  )r
  slash_commands     r%   _command_allowedz&SlashCommandCompleter._command_allowed7  s>    '	,,];<< 		s   ) 	55c                j    | j                   i S 	 | j                         xs i S # t        $ r i cY S w xY wr   )r  r  r
  s    r%   _iter_skill_commandsz*SlashCommandCompleter._iter_skill_commands?  s@    ((0I	0028b8 	I	   $ 22c                j    | j                   i S 	 | j                         xs i S # t        $ r i cY S w xY wr   )r  r  r  s    r%   _iter_skill_bundlesz)SlashCommandCompleter._iter_skill_bundlesG  s@    ''/I	//17R7 	I	r  c                f    d| j                  d      j                  dd      j                         z   S )zCanonicalize a typed skill token to its hyphenated /slug form.

        Mirrors resolve_skill_command_key() in agent/skill_commands.py:
        underscores (Telegram bot-command form) are interchangeable with
        hyphens.
        r   r  r  )r   r  r   )tokens    r%   _normalize_skill_tokenz,SlashCommandCompleter._normalize_skill_tokenQ  s.     U\\#&..sC8>>@@@r$   c                D    | j                  |      | j                         v S r   )r  r  )r
  r  s     r%   _is_skill_commandz'SlashCommandCompleter._is_skill_command[  s!    **51T5N5N5PPPr$   c              #    K   	 ddl m} |j                         }|j	                  d      r|d}}n
|dd |d   }}t               }|D ]<  }| j                  |      }|| j                         vs||v r y|j                  |       > t        |      |k\  ry|j                  d      sy| j                  |      }	| j                         j                         D ]x  \  }
}|
|v s|
j                  |	      st        |j                  d	d
            }|dd t        |      dkD  rdndz   }|
|	k(  r|
 dn|
}t        |t        |       |
d|        z y# t        $ r d}Y Uw xY ww)ug  Offer skill-command completions for stacked invocations.

        After ``/skill-a `` the user may chain more leading skills
        (``/skill-a /skill-b do XYZ``). While every whitespace-delimited
        token so far resolves to a distinct skill command and the current
        word under the cursor starts with ``/``, keep offering the remaining
        skill commands. The moment the chain is broken (a non-skill token
        appears, the cap is reached, or the user is typing plain instruction
        text) we offer nothing — instruction text must never be polluted
        with skill suggestions.
        r   )_MAX_STACKED_SKILLS   r   r   Nr   r   Skill commandr  r~     ⚡ start_positiondisplaydisplay_meta)r  r  r  r  endswithr  r  r  r   rp  r  r-  r   r   r   )r
  text_captokens	completedcurrent_wordrU  r  r  word_keyr   r  r   
short_descreplacements                  r%   _stacked_skill_completionsz0SlashCommandCompleter._stacked_skill_completions^  s    	H ==&,b|I&,Sbk6":|I  	E--e4C$3355HHSM		
 t9 &&s+..|<224::< 	ICd{#..":dhh}oFGK$Sb)c+6F6KUQSTJ (+hSE)CK #L 11#J<0	 	5  	D	s(   E#E EE#E E#E  E#>   ru   rY   r[   c                B    | |k7  r| S | t         j                  v r| S |  dS )u  Return replacement text for a completion.

        When the user has already typed the full command exactly (``/help``),
        returning ``help`` would be a no-op and prompt_toolkit suppresses the
        menu. Appending a trailing space keeps the dropdown visible and makes
        backspacing retrigger it naturally.

        However, commands that open pickers (model, skin, personality) should
        NOT get a trailing space — the TUI would apply the completion on Enter
        and block the picker from opening.
        r   )r  _PICKER_COMMANDS)r  words     r%   _completion_textz&SlashCommandCompleter._completion_text  s1     tO,===O1~r$   c                    | syt        |       dz
  }|dk\  r| |   dk7  r|dz  }|dk\  r	| |   dk7  r| |dz   d }|syd|v ry|j                  d      sd|v r|S y)u  Extract the current word if it looks like a file path.

        Returns the path-like token under the cursor, or None if the
        current word doesn't look like a path.  A word is path-like when
        it starts with ``./``, ``../``, ``~/``, ``/``, or contains a
        ``/`` separator (e.g. ``src/main.py``).

        Tokens containing a ``://`` scheme separator (e.g. URLs like
        ``https://example.com/x``) are excluded even though they contain a
        ``/`` — they are never useful local-path completions.
        NrA  r   r   z://)z./z../~/r   r   rp  r  r(  ir3  s      r%   _extract_path_wordz(SlashCommandCompleter._extract_path_word  s      IM1faCFA 1faCAEF|
 D=??34tKr$   c              #     K   t         j                  j                  |       }|j                  d      r|}d}nBt         j                  j	                  |      xs d}t         j                  j                  |      }	 t        j                  |      }d}|j                         }t        |      D ]=  }|r |j                         j                  |      s&||k\  r yt         j                  j                  ||      }	t         j                  j                  |	      }
| j                  d      rAdt         j                  j                  |	t         j                  j                  d            z   }nAt         j                  j                  |       r|	}nt         j                  j                  |	      }|
r|dz  }|
rdnd}|
rdn
t        |	      }t!        |t#        |        ||z   |	       |d
z  }@ y# t        $ r Y yw xY ww)z8Yield Completion objects for file paths matching *word*.r   r   r  Nr   ~r6  dirr#  rA  )osr9  
expanduserr'  dirnamebasenamelistdirOSErrorr   rb  r  r!  isdirrelpathisabs_file_size_labelr   rp  )r3  limitexpanded
search_dirrx  r.  countprefix_lowerru  	full_pathis_dirdisplay_pathsuffixr/  s                 r%   _path_completionsz'SlashCommandCompleter._path_completions  s     77%%d+S!!JF29cJWW%%h/F	jj,G ||~G_ 	Eekkm66|D~Z7IWW]]9-F s##bggooiASASTWAX&YYt$(  "wwy9#"SF"5(8(CD #D	z!	  QJE=	  		s+   A8G>;G/ EG>/	G;8G>:G;;G>c                    | syt        |       dz
  }|dk\  r| |   dk7  r|dz  }|dk\  r	| |   dk7  r| |dz   d }|j                  d      sy|S )z=Extract a bare ``@`` token for context reference completions.NrA  r   r   @r7  r8  s      r%   _extract_context_wordz+SlashCommandCompleter._extract_context_word  sg     IM1faCFA 1faCAEF|s#r$   c              #    K   |j                         }d}|D ]T  \  }}|j                         j                  |      s&|j                         |k7  s:t        |t        |       ||       V dD ]  }|dd }||k(  s|j                  |      s |dk(  }	||k(  rdn|t        |      d }
t        j
                  j                  |
      }|r|dk(  rd	\  }}nX|j                  d
      r|d}}nBt        j
                  j                  |      xs d}t        j
                  j                  |      }	 t	        j                  |      }d}|j                         }t        |      D ]  }|r |j                         j                  |      s%t        j
                  j                  ||      }t        j
                  j                  |      }|	|k7  rj||k\  r  yt        j
                  j                  |      }|rd
nd}|rdn
t!        |      }| | | }t        |t        |       ||z   |       |dz  }  y |dd }| j#                  |||      E d{    y# t        $ r Y  yw xY w7 w)zYield Claude Code-style @ context completions.

        Bare ``@`` or ``@partial`` shows static references and matching
        files/folders.  ``@file:path`` and ``@folder:path`` are handled
        by the existing path completion path.
        ))z@diffzGit working tree diff)z@stagedzGit staged diff)@file:zAttach a file)@folder:zAttach a folder)z@git:z Git log with diffs (e.g. @git:5))z@url:zFetch web contentr#  )rV  rW  Nr   rW  r   r  )r  r   r   r   r=  rA  )r   r  r   rp  r>  r9  r?  r'  r@  rA  rB  rC  rb  r!  rD  rE  rG  _fuzzy_file_completions)r
  r3  rH  lowered_STATIC_REFSrw  r/  rx  barewant_dir	path_partrI  rJ  match_prefixr.  rK  rL  ru  rM  rN  rO  rP  
completionquerys                           r%   _context_completionsz*SlashCommandCompleter._context_completions  st     **,
  , 	OIt ++G49Jg9U $'I:%!%	 	 - 0	F#2;Dt|tv6!Z/"&$,BDV4F	77--i88s?/6,J&&s+/7J!#!:!AcJ#%77#3#3H#=L jj4G +113#G_ E#EKKM,D,D\,R  "Z ?IWW]]95F  6) ~  $&77??9#=L$*SF$*50@0KD$*8L>&!BJ$"(+D	z %%)	  QJE12 a0	f QR//eUCCCE  D 	DsH   :I*I*>I*BI*/IDI*I(I*	I%!I*$I%%I*c           
     J   t        j                         }t        j                         }| j                  r-| j
                  |k(  r|| j                  z
  dk  r| j                  S g }ddd|gdd|gdddd|gfD ]  }|d	   }t        j                  |      s	 t        j                  |d
d
d|dd      }|j                  d	k(  r|j                  r|j                  j                         r|j                  j                         j                  d      }|dd D ]T  }t         j                  j!                  |      r t         j                  j#                  ||      n|}	|j%                  |	       V  n || _        || _        || _        |S # t        j&                  t(        f$ r Y ,w xY w)z9Return cached list of project files (refreshed every 5s).g      @rgz--filesz--sortr=modifiedfdz--typefz--base-directoryr   Tr  zutf-8r  )capture_outputr(  timeoutcwdencodingerrors
Ni  )r>  getcwdtime	monotonicr  r	  r  shutilwhich
subprocessr   
returncodestdoutr  r  r9  rF  rE  r   TimeoutExpiredrC  )
r
  rh  nowfilesr   toolprocrm  pr  s
             r%   _get_project_filesz(SlashCommandCompleter._get_project_filesi  s   iiknn$$+d+++c1### 90#69c"8S"4c:
 	C
 q6D<<%!~~4gi ??a'DKKDKK<M<M<O++++-33D9C $Z *9;q9Ibggooa5qS)* '	. ! #" --w7 s   CFF"!F"c                   |syt         j                  j                  |       }|j                         }| j                         }|j                         }||k(  ry|j	                  |      ry||v ry||v ryd}|D ]  }|t        |      k  s|||   k(  s|dz  }! |t        |      k(  rFd}d}d}	|D ](  }|t        |      k  r|||   k(  r|	dv r|dz  }|dz  }|}	* |t        |      d	z  k\  ry
yy)z?Score a file path against a fuzzy query. Higher = better match.rA  r5  P   r4  r  r   r  z_-./g      ?#      )r>  r9  rA  r   r  rp  )
filepathr`  filename
lower_file
lower_pathlower_qqir  boundary_hitsprevs
             r%   _score_pathz!SlashCommandCompleter._score_path  s)    77##H-^^%
^^%
++-    )j j   	ACL Q'"+%5a	 WMBD G$gbk)9v~%*!GB Gs 22r$   c           	   #  X  K   | j                         }|s|d| D ]  }|j                  d      }t        j                  j	                  |      }|rdnd}|rdn:t        t        j                  j                  t        j                         |            }	t        d| d| t        |       ||	        yg }
|D ]-  }| j                  ||      }|d	kD  s|
j                  ||f       / |
j                  d
        |
d| D ]  \  }}|j                  d      }t        j                  j	                  |      }|rdnd}|rdn:t        t        j                  j                  t        j                         |            }	t        d| d| t        |       ||	r| d|	 n|        yw)z-Yield fuzzy file completions for bare @query.Nr   folderfiler=  rS  :r#  r   c                    | d    | d   fS )Nr   rA  r   )xs    r%   r_  z?SlashCommandCompleter._fuzzy_file_completions.<locals>.<lambda>  s    AaD5!A$- r$   r`  z  )rz  r'  r>  r9  rA  rG  r!  rl  r   rp  r  r   sort)r
  r3  r`  rH  rv  fprN  r  kindr/  scoredsr  s                r%   rX  z-SlashCommandCompleter._fuzzy_file_completions  s    '')FUm S)77++B/#)xv &u,<GGLLb1- !vQrdO$'I:$!%	    	'B  U+A1uq"g&	' 	/0FU^ 	EAr[[%Fww''+H%86D"5(8RYY["-)D D62$ #D	z 04t2dV_"	 	s   CF*CF*c           
   #    K   	 ddl m}  |       D ]^  }|d   }|j                  |      s||k7  s t        |t	        |        ||j                  dd      xs |j                  dd             ` y# t        $ r Y yw xY ww)	z1Yield completions for /skin from available skins.r   )
list_skinsr   r   r   sourcer#  N)hermes_cli.skin_enginer  r  r   rp  r   r  )sub_text	sub_lowerr  r  r   s        r%   _skin_completionsz'SlashCommandCompleter._skin_completions  s     	9\ y??9-$)2C$(+H~ $%&UU="%=%TxQSAT	   		s3   B&A3 A3 AA3 2B3	A?<B>A??Bc           	   #  j  K   d}| j                         }| j                  d      }t        |      dk(  st        |      dk(  r^|s\|s| nd}|D ]P  }|j                  |j	                               s#||j	                         k7  s7t        |t        |       |       R y|d   j	                         }|dvry|rdn|d	   }|j	                         }t        |r|dd n|dd	       }		 dd
lm}
 ddl	m
}m}m}  |
       } ||dd      }|D ]K  \  }}}||	v s|j                  |      s||v }|dk(  r|r)|dk(  r|s1t        |t        |       ||       M t         |             D ]G  }||	v s|j                  |      s||v }|dk(  r|r%|dk(  r|s-t        |t        |       |d       I |j                  d      xs i }t        |t               rKt        |      D ]<  }| d}||	v s|j                  |      st        |t        |       |d| d       > yy# t"        $ r Y yw xY ww)uh  Yield completions for /tools — subcommand + toolset/MCP-server name.

        Handles both ``/tools <tab>`` (suggesting ``list|disable|enable``) and
        ``/tools enable <tab>`` / ``/tools disable <tab>`` (suggesting toolset
        keys and MCP server prefixes, filtered by current enable state so the
        user only sees actionable options).
        )rG   disableenabler   r   rA  r   r$  r%  N)r  r  r   )load_config)CONFIGURABLE_TOOLSETS_get_platform_tools_get_plugin_toolset_keyscliF)include_default_mcp_serversr  r  r#  zplugin toolsetmcp_serversr  zMCP server '')r  r'  rp  r  r   r   r  r  r  hermes_cli.tools_configr  r  r  rb  r   r	  r
  r  )r  r  SUBSr  trailing_spacepartialrj  
subcommandpartial_loweralreadyr  r  r  r  rW   enabledts_keylabel_descis_onr  serverrx  s                          r%   _tools_completionsz(SlashCommandCompleter._tools_completions  s     - !**3/ u:?s5zQ~&4h"G U>>'--/2sgmmo7M$S#g,PSTTU 1X^^%
22&"E"I>eABiuQr{C6	5  !]F)&%UZ[G(= $uW$F,=,=m,L'))e*5 $'L="!&	  !!9!;< W$F,=,=m,L'))e*5 $'L="!1	  !**]39rK+t,$[1 	F &xq\F(0A0A-0P $(+G} &'3F81%=	 		 -  		s8   A,H3/H3A#H3'D;H$ "H3$	H0-H3/H00H3c              #  "  K   | j                         }| j                  d      }t        |      dkD  st        |      dk(  r|ry|r|rdn|d   }|j                         }	 ddlm}  |       }|j                         }|D ]m  }	|	j                  }
|
j                  |      s!	 |j                  |	      }|rt        |dd      rd	|j                   nd
}t        |
t        |       |
|       o y# t        $ r Y yw xY w# t        $ r d}Y Zw xY ww)u_  Yield platform completions for /handoff.

        Offers connected (enabled + configured) gateway platforms. A recorded
        home channel is NOT required to list a platform — it's often learned at
        runtime — so the meta hints whether one is set yet. Completes only the
        first arg (the platform); once one is chosen, stop.
        r   rA  Nr   r   r   )load_gateway_configr   u   → zsend this session herer#  )r  r'  rp  r   gateway.configr  get_connected_platformsr  valuer  get_home_channelgetattrr   r   )r  r  r  r  r  r  r  gwr   r   r   homer/  s                r%   _handoff_completionsz*SlashCommandCompleter._handoff_completionsP  s      !**3/u:>c%jAo."n"59	:$&B224I " 	H>>D??=1**84 *.'$2MT$))%SkD #G}!	 	  		  sN   ADC/ <#D C>1>D/	C;8D:C;;D>D	DDDc              #    K   	 ddl m}  |       j                  d      xs i j                  di       xs i }dj                  |      r d|k7  rt	        dt        |        dd       |j                         D ]  \  }}|j                  |      s||k7  st        |t              r)|j                  d      xs |j                  d	d
      dd }nt        |      dd }t	        |t        |        ||        y# t        $ r Y yw xY ww)zAYield completions for /personality from configured personalities.r   )load_cli_configagentpersonalitiesrf   zclear personality overlayr#  r   system_promptr   Nr  )r  r  r   r  r   rp  r-  r	  r
  r   r  )r  r  r  r  r   r0   r/  s          r%   _personality_completionsz.SlashCommandCompleter._personality_completionsv  s    	
 ,,.227;ArFFXZ[a_aM  +)0C $'M>"!<	  !. 3 3 5 f??9-$)2C!&$/%zz-8`FJJXZ<[\_]_<`"6{3B/$(+H~ $%)	   		s6   DBC< C< A$C< ;D<	DDDDc              #  "	  K   |j                   }|j                  d      sZ| j                  |      }|| j                  |      E d {    y | j	                  |      }|| j                  |      E d {    y |j                  d      }|d   j                         }t        |      dkD  s!t        |      dk(  rD|j                  d      r2t        |      dkD  r|d   nd}|j                         }	| j                  |      r| j                  |      E d {    y d|vr@|dk(  r| j                  ||	      E d {    y |dk(  r| j                  ||	      E d {    y |d	k(  r| j                  ||	      E d {    y |d
k(  r| j                  ||	      E d {    y d|vrY|t         v rQ| j#                  |      r@t         |   D ]4  }
|
j                  |	      s|
|	k7  st%        |
t        |       |
       6 y |dd  }t&        j)                         D ]Y  \  }}| j#                  |      s|dd  }|j                  |      s/t%        | j+                  ||      t        |       ||       [ | j-                         j)                         D ]  \  }}|dd  }|j                  |      st/        |j1                  dd            }|d d t        |      dkD  rdndz   }t        |j1                  dg             }t%        | j+                  ||      t        |       |d| d| d        | j3                         j)                         D ]}  \  }}|dd  }|j                  |      st/        |j1                  dd            }|d d t        |      dkD  rdndz   }t%        | j+                  ||      t        |       |d|         	 ddlm}  |       j)                         D ]{  \  }}|j                  |      st/        |j1                  dd            }|d d t        |      dkD  rdndz   }t%        | j+                  ||      t        |       d| d|        } y 7 $7 7 Y7 87 7 7 # t8        $ r Y y w xY ww)Nr   rA  maxsplitr   r   r   z/skinz/personalityz/toolsz/handoffr  r#  r   zSkill bundler  r~  r   u   ▣ z (z skills)r!  r"  r)  r|  u   🔌 )text_before_cursorr  rT  ra  r:  rQ  r  r   rp  r'  r  r0  r  r  r  r  r   r  r   r   r-  r4  r  r   r   r  r,  r*  r  )r
  documentcomplete_eventr(  ctx_word	path_wordr  base_cmdr  r  rj  r3  r   rv  r  r  r   r.  skill_countr*  cmd_infos                        r%   get_completionsz%SlashCommandCompleter.get_completions  s    **s#11$7H#44X>>>//5I$11)<<< 

A
&8>>#u:>c%jAo$--2D#&u:>uQxrH (I %%h/::4@@@ ("w&#55h	JJJ~-#<<XyQQQ
 8#228YGGG:%44XyIII ("x;'>4CXCXYaCb&x0 C~~i0SI5E(,/M>$'  ABx!) 
	IC((-12wH""4( ))(D9$'I:!%	 
	 11399; 	IC12wH""4(!$((=."IJ("-#k:JR:OUWX
!$((8R"89 ))(D9$'I:#'
|2k](!K	 	 224::< 
	IC12wH""4(!$((=/"JK("-#k:JR:OUWX
 ))(D9$'I:#'
|!4	 
		>&9&;&A&A&C 	"(&&t,x||M;KLMD!%crs4y2~e2!NJ$--h=(+D	z"#H:',ZL%9	 		C ?
 =  A K R H JD  		s   ARQ+,R4Q.5B#RQ1#R<Q4=RQ7R<Q:=RQ=?RR#A$RA'R0B<R-A#R2R  A&R  *R.R1R4R7R:R=R 	R	RRR)NNN)r  1Callable[[], Mapping[str, dict[str, Any]]] | Noner  zCallable[[str], bool] | Noner  r  r  r  )r  r   r  r   )r  zMapping[str, dict[str, Any]])r  r   r  r   )r  r   r  r   )r(  r   )r  r   r3  r   r  r   )r(  r   r  r   )   )r3  r   rH  rE  r  z	list[str])r  r   r`  r   r  rE  )   )r3  r   r`  r   rH  rE  )r  r   r  r   )r   r    r!   r"   r  r  r  r  staticmethodr  r  r0  	frozensetr2  r4  r:  rQ  rT  ra  rz  r  rX  r  r  r  r  r  r   r$   r%   r  r  &  sQ   T VZ7;TX	'!R' 5' !R	'
 
' A AQ6x !!AB &  B 0 0d  TDl'R * *X)V    R Rh # #J  @rr$   r  c                  .    e Zd ZdZ	 	 d	 	 	 	 	 ddZd Zy)SlashCommandAutoSuggestzInline ghost-text suggestions for slash commands and their subcommands.

    Shows the rest of a command or subcommand in dim text as you type.
    Falls back to history-based suggestions for non-slash input.
    Nc                     || _         || _        y r   )_history
_completer)r
  history_suggest	completers      r%   r  z SlashCommandAutoSuggest.__init__  s    
 (#r$   c                   |j                   }|j                  d      s)| j                  r| j                  j                  ||      S y |j	                  d      }|d   j                         }t        |      dk(  r|j                  d      s||dd  j                         }t        D ]_  }| j                  | j                  j                  |      s+|dd  }|j                  |      sB||k7  sHt        |t        |      d        c S  y t        |      dkD  r|d   nd}	|	j                         }
| j                  | j                  j                  |      ro| j                  j                  |      D ]Q  }|j                  r|j                  |j                   d  n|j                  }|j!                         sFt        |      c S  | j                  | j                  j                  |      sy |t"        v rKt"        |   rBd|	vr>t"        |   D ]2  }|j                  |
      s||
k7  st        |t        |	      d        c S  | j                  r| j                  j                  ||      S y )Nr   rA  r  r   r   r   )r  r  r  get_suggestionr  r   rp  r'  r   r  r  r
   r  r0  r$  r(  r  r   )r
  bufferr  r(  r  r  r3  r   r  r  r  r_  	remainderrj  s                 r%   r  z&SlashCommandAutoSuggest.get_suggestion  s(   ** s#}}}}33FHEE

A
&8>>#u:?4==#58>>#D <??.t7W7WX[7\qr7&&t,T1A%hs4yz&:;;<   #5zA~582NN$	 OO'11(;"ooHHN 1
!00 'OOZ-F-F,F,GH6@oo ??$%i00	1 ??&t/O/OPX/Y{"{8'<("&x0 ?C~~i0SI5E)#c(mn*=>>?
 ====//AAr$   )NN)r  zAutoSuggest | Noner  zSlashCommandCompleter | Noner  r  )r   r    r!   r"   r  r  r   r$   r%   r  r    s5     /326$+$ 0$ 
	$6r$   r  c                    	 t         j                  j                  |       }|dk  r| dS |dk  r	|dz  ddS |dk  r	|dz  dd	S |dz  dd
S # t        $ r Y yw xY w)z:Return a compact human-readable file size, or '' on error.r   i   Bi   z.0fKi   @z.1fMG)r>  r9  getsizerC  )r9  sizes     r%   rG  rG  X  s    wwt$ d{qzk+c"!$$  +&s+1--()#.a00  s   A 	AA)r  r   )r   r   r  zCommandDef | None)r   r   r  r   )r   r   r  r   )r   r   r  r   )r  set[str]r   )r   r   r  zset[str] | Noner  r   )r   r   r  r   r  )r  zlist[tuple[str, str, str]])r  list[tuple[str, str]])r8  Mapping[str, Any]r9  r   r  r  )r  dict[str, Any])r  rE  )rT  zlist[str] | tuple[str, ...]r  r   )r  r   )r   r  r  r  )rm  r   r  r   )r.  list[tuple[str, ...]]rs  r  r  r  )r5  N)r   r   r  rE  r  r  r  rE  r  z'Callable[[str], str] | None'r  &tuple[list[tuple[str, str, str]], int])r5  )r@  rE  r  z!tuple[list[tuple[str, str]], int])r  rE  r  r  r  r  )r  r  r  zMtuple[dict[str, list[tuple[str, str, str]]], list[tuple[str, str, str]], int])z)https://hermes-agent.local/slack/commands)r  r   r  r  )r  r   )r9  r   r  r   )gr"   
__future__r   loggingr>  rero  rq  rm  collections.abcr   r   dataclassesr   typingr   utilsr   	getLoggerr   r  prompt_toolkit.auto_suggestr	   r
   prompt_toolkit.completionr   r   ImportErrorobjectr   r   r#   r   r   r   r   r   _cmdr   r   r   _aliasr   r   r  r   _catr   r   rG   compile_PIPE_SUBS_REr  r   r   mgroupr  r  r   r   r   r   r  r  r  r'  r   r3  rD  rI  rJ  rY  r;  rO  rQ  rW  r\  rf  rq  _TG_NAME_LIMITri  rk  r1  rz  _clamp_telegram_namesr  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  rG  r   r$   r%   <module>r     s,   #  	 	    - !  !			8	$C? $+ + +$@&wJI "@& uH)!X7	@& wF	 ,CE@& w>	@& xQS\@& y5y@& v6	@&  wBIN!@&" xWYb(8,P#@&& vG '@&* w=y!#+@&. ycen%6/@&2 xPR[ H63@&6 z  F  HQ#/_a7@&: zCY#%;@&> zUW`i;XZ?@&B v>	JC@&D y? ,>@E@&H vTV_ ,<>I@&L |=y$
<M@&P x?!#Q@&T y>">757U@&\ wNPY5]@&` wXZc#%a@&d vXZcoqe@&h ulnw#%i@&l yKY46m@&p xGSq@&r xH&Qs@&t yGPu@&v y=y -9w@&z x<i!#{@&B z@)LC@&H x5I@&L wLoUWM@&P  Y(:24Q@&X }<o!#Y@&\ {A?g/]@&` |QSb(92GEa@&f y\#BDg@&l xR*;24m@&r vP s@&v {A?B [\w@&| vln}:PR}@&B v>2C@&F {?(GCEG@&L w+_.<Z\M@&P vH/(HDFQ@&Z wNP`:TK[@&^ z46F_@&b xE$#:PQc@&l xRHGIm@&t yT!u@&x uVXh(HVxzy@&| wF$8IUdf}@&@ wj+ACA@&D v/1A`bE@&J }MA_DFK@&P {L<UWQ@&T yc>kmU@&Z xU>RS[@&j xIK[k@&n |=?O&(o@&r  a);=s@&v y^`p(E>@w@&| yC$0}@&D zG H6E@&H v0&9I@&J yUW` "K@&N wfhn,.O@&R ~QSYl4S@&V wQSYZW@&X z>!#Y@&\ {Dfl4]@&` zNPV ,HJa@&d vF
4e@&h wDfi@&l wH&2m@&p xDfMq@&r y5vvNs@&t wZ\b')u@&| vSU[i<I}@& " @N *?)@& @9 .  TD$6t$<1TYYK!ll 	TF(,(8(8'9tyykQR%SHq\"	TT 35 / 4 8D#..t}}bA (1TYYK 9q_ll 	8F!)AfX,!7D1VH	8	8 %'! & >D'+D,<,<'=a		{O$> 

12 1Ddii[/C
kT^^,A771:++C0C1  *3 33 *  0 2;2  *P.: -
&@L ') #!$ ;  :5%P@
1#2  = ! BJJ}- !rzz(+ $"$$ $P -  37v1v1v1 v1 	v1
 1v1 ,v1z IF ,<`-`-R`-T   !rzz/2 $ &  & (  ##<= 	$Nb568cI cTEk EP1mA  KIJJ	s   _	 	__