
    `gjp(                    ,   d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	 ddl
mZ ddlmZ ddlmZ  ej"                  e      Zd	Zd
ZdZ ej.                  dej0                        Ze G d d             ZddZddZddd	 	 	 	 	 	 	 ddZddddZy)u  Profile describer — auto-generate ``description`` for a profile.

Used by ``hermes profile describe <name> --auto`` and the dashboard's
"auto-generate description" button. Reads the profile's installed
skills, model+provider, name, and optionally a small slice of memory,
then asks the auxiliary LLM to produce a 1-2 sentence description of
what the profile is good at.

Result is written to ``<profile_dir>/profile.yaml`` with
``description_auto: true`` so the dashboard can surface a "review"
badge. User can edit afterward to confirm.

Design notes
------------
- Mirrors the shape of ``hermes_cli/kanban_specify.py``: lazy aux
  client import inside the function, lenient response parse, never
  raises on expected failure modes.
- Reads at most ``MAX_SKILLS_FOR_PROMPT`` skill names to keep the
  prompt bounded. No skill body — names + categories are enough
  signal and avoid blowing context on profiles with 100+ skills.
- Memory is intentionally NOT read here. Memories are personal and
  the orchestrator routes work to a *role* not a *biography*. If we
  find later that memory adds signal we can wire it; for now,
  skills + name + model is plenty.
    )annotationsN)	dataclass)Path)Optional)profilesis_excluded_skill_path<   u  You are a profile-describer for the Hermes Agent kanban board.

A user runs multiple "profiles" — distinct agent identities, each with their
own skills, model, and configuration. The kanban board's orchestrator routes
work to whichever profile best fits each task. To do that well, every
profile needs a short, concrete description of what it's good at.

You are given a profile's:
  - Name
  - Model / provider
  - List of installed skill names (a strong signal of role / domain)

Produce a single JSON object with exactly one key:

  {
    "description": "<1-2 sentence description, plain prose, no preamble>"
  }

Rules:
  - The description is what an orchestrator will read to decide whether to
    route a task here. Lead with the profile's strongest capability.
  - Stay concrete. Bad: "an AI agent that helps users."
                  Good: "Reads and modifies Python codebases — runs tests,
                         refactors functions, opens GitHub PRs."
  - 1-2 sentences, <= 280 characters total.
  - Never invent capabilities the skills don't suggest.
  - Never write "Hermes Agent profile" or other meta-narration.
  - No code fences, no preamble, no closing remarks. Output only JSON.
zProfile name: {name}
Default model: {model}
Provider: {provider}
Installed skill count: {skill_count}
Notable skills (up to {skill_cap}):
{skill_list}
z^```(?:json)?\s*|\s*```$c                  B    e Zd ZU dZded<   ded<   dZded<   dZd	ed
<   y)DescribeOutcomez&Result of describing a single profile.strprofile_nameboolok reasonNzOptional[str]description)__name__
__module____qualname____doc____annotations__r   r        O/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/profile_describer.pyr   r   Z   s$    0HFC!%K%r   r   c                ,   | dz  }|j                         sg S g }|j                  d      D ]q  }t        |      r	 |j                  |      }|j
                  dd }|s3t        |      dk(  r|j                  |d          V|j                  |d    d|d           s |j                          t        |      t        k  r|S t        |      t        z  }t        t              D cg c]  }|t        ||z            }}|S # t        $ r Y w xY wc c}w )a  Return a stable, capped list of skill names for the prompt.

    Format: ``category/skill_name`` where category is the immediate
    subdir under ``skills/`` (e.g. ``devops``, ``research``). Skills
    that live directly under ``skills/`` show as bare ``skill_name``.
    skillsSKILL.mdN   r   /)is_dirrglobr	   relative_to
ValueErrorpartslenappendsortMAX_SKILLS_FOR_PROMPTrangeint)	profile_dir
skills_dirnamesmdrelr&   stepisampleds	            r   _collect_skillsr5   d   s    x'J	Ez* 4!"%	..,C 		#2u:?LLq"LLE!H:QuRyk234 
JJL
 5z**u:--D-23H-IJuST]#JGJN'  		$ Ks   D&D	DDc                T   | sy t         j                  d| j                               }|j                  d      }|j	                  d      }|dk(  s
|dk(  s||k  ry |||dz    }	 t        j                  |      }t        |t              sy |S # t        t
        j                  f$ r Y y w xY w)Nr   {}r   r    )	_FENCE_REsubstripfindrfindjsonloadsr%   JSONDecodeError
isinstancedict)rawstrippedfirstlast	candidatevals         r   _extract_json_blobrI      s    }}R-HMM#E>>#D{dbjDEM*Ijj# c4 J	 ,,- s   #B B'&B'F)	overwritetimeoutc          	        t        j                  |       }t        j                  |      st        |dd      S 	 |dk(  rddlm} t         |             }nt        j                  |      }t        j                  |      }|j                  d      r |j                  d	      s|st        |dd
      S t        |      }dj                  d |D              xs d}	|dz  j                         r$t        d |dz  j                  d      D              nd}
	 t        j                   |      \  }}	 ddlm} t*        j-                  ||xs d|xs d|
t.        |	      }	  |ddt0        dd|dgdd|xs d      }	 |j8                  d   j:                  j<                  xs d!}t?        |      }|8|jA                         jC                  d"d#      d   }|st        |dd$      S |dd% }nQ|j                  d      }tE        |tF              r|jA                         st        |dd&      S |jA                         dd% }	 t        jH                  ||d'(       t        |d'd*|+      S # t        $ r}t        |dd|       cY d}~S d}~ww xY w# t        $ r	 d\  }}Y iw xY w# t        $ r-}t&        j)                  d|       t        |dd      cY d}~S d}~ww xY w# t        $ rD}t&        j3                  d||       t        |dd t5        |      j6                         cY d}~S d}~ww xY w# t        $ r d!}Y w xY w# t        $ r}t        |dd)|       cY d}~S d}~ww xY w),uU  Auto-generate a description for one profile.

    Returns an outcome describing what happened. Never raises for
    expected failure modes (profile missing, no aux client configured,
    API error, malformed response) — those surface via ``ok=False`` so
    a sweep can continue past individual failures.

    ``overwrite`` controls whether an existing user-authored description
    is replaced. By default we refuse to overwrite a description with
    ``description_auto: false`` to protect curated text. Auto-generated
    descriptions (``description_auto: true``) are always replaceable.
    Fzprofile not founddefaultr   )get_hermes_homezcannot resolve profile dir: Nr   description_autozLprofile already has a user-authored description (use --overwrite to replace)
c              3  &   K   | ]	  }d |   yw)z  - Nr   ).0ns     r   	<genexpr>z#describe_profile.<locals>.<genexpr>   s     ;!T!:;s   z  (no skills installed)r   c              3  6   K   | ]  }t        |      sd   yw)r    Nr   )rR   _s     r   rT   z#describe_profile.<locals>.<genexpr>   s       %a( 	
s   r   )NN)call_llmz,describe: auxiliary client import failed: %szauxiliary client unavailablez(unset))namemodelproviderskill_count	skill_cap
skill_listprofile_describersystem)rolecontentuserg333333?i  r
   )taskmessagestemperature
max_tokensrK   z%describe: API call failed for %s (%s)zLLM error: r   z

r    zLLM returned an empty responsei  z(LLM response missing 'description' fieldT)r   rO   zfailed to write profile.yaml: 	described)r   )%profiles_modnormalize_profile_nameprofile_existsr   hermes_constantsrN   r   get_profile_dir	Exceptionread_profile_metagetr5   joinr"   sumr#   _read_config_modelagent.auxiliary_clientrW   loggerdebug_USER_TEMPLATEformatr*   _SYSTEM_PROMPTinfotyper   choicesmessagera   rI   r;   splitrA   r   write_profile_meta)r   rJ   rK   canonrN   r-   excexistingskill_namesr]   r[   rY   rZ   rW   user_msgresprC   parsedtextr   rH   s                        r   describe_profiler      s   $ //=E&&u- ue-@AASI801K&66u=K
 --k:H||M"8<<8J+KT]+
 	
 "+.K;{;;X?XJ 
 	(	(	*  (*11*=  12 %&99+FxM3
 $$!	'i' % HQ $!n=H5 Mr	
ll1o%%--3  $F~yy{  +A."5%1QRR4Cjjj'#s#399;"uH  iik$3'U''#!	
 5$MMq  Sue/KC5-QRRS.  %$x%
  MCSIue-KLLM6  Q;UCHue{49;M;M:N-OPPQ  0  Uue/McU-STTUs   1I J *J K 6'L 4L1 	I>$I93I>9I>JJ	K"KKK	L9LLLL.-L.1	M:M	MMT)missing_onlyc                    g }t        j                         D ]J  }| r+|j                  xs dj                         r|j                  s0|j                  |j                         L |S )zReturn profile names that can be described.

    ``missing_only=True`` (default) returns only profiles without a
    description. ``missing_only=False`` returns every profile.
    r   )rh   list_profilesr   r;   rO   r(   rX   )r   outps      r   list_describable_profilesr     sZ     C'') Q]]0b779!BTBT

166 Jr   )r-   r   return	list[str])rC   r   r   zOptional[dict])r   r   rJ   r   rK   zOptional[int]r   r   )r   r   r   r   )r   
__future__r   r>   loggingredataclassesr   pathlibr   typingr   
hermes_clir   rh   agent.skill_utilsr	   	getLoggerr   rt   r*   rx   rv   compile	MULTILINEr9   r   r5   rI   r   r   r   r   r   <module>r      s   4 #   	 !   / 4			8	$
  > BJJ2BLLA	 & & &#L* !	vNvN vN 	vN
 vNr 7; r   