+
    #j,                       R t ^ RIHt ^ RIt^ RIt^ RIt^ RIHt ^ RIH	t	 ]P                  P                  ^ ]! ]! ]4      P                  4       P                  4      4       ^ RIHtHtHtHtHtHtHtHt 0 RmtR R ltR^/R	 R
 lltR R ltR R ltR R ltRR R llt] R8X  d   ]PB                  ! ]! 4       4       R# R# )u  
extract_schema.py — Analyze a ComfyUI API-format workflow and extract
controllable parameters.

Improvements over v1:
  - Catalogs live in `_common.py`, shared with `check_deps.py`
  - Coverage expanded for Flux / SD3 / Wan / Hunyuan / LTX / IPAdapter / rgthree
  - Symmetric duplicate-name resolution: ALL duplicates get a node-id suffix
    (instead of "first wins, second renamed"), so callers see consistent names
  - Negative prompt detected by tracing `KSampler.negative` connections back to
    the source CLIPTextEncode (more reliable than meta-title heuristic)
  - Embedding references in prompt text are extracted as model dependencies
  - Detects Primitive nodes that drive other nodes' inputs (and surfaces them
    as the user-facing parameter)
  - Reroutes are followed when tracing connections

Usage:
    python3 extract_schema.py workflow_api.json
    python3 extract_schema.py workflow_api.json --output schema.json

Stdlib-only. Python 3.10+.
)annotationsN)Path)Any)OUTPUT_NODESPARAM_PATTERNSPROMPT_FIELDSis_linkiter_embedding_refsiter_model_deps
iter_nodesunwrap_workflowc                    V ^8  d   QhRRRR/# )   valuer   returnstr )formats   "h/opt/hermes-venv/lib/python3.14/site-packages/../../../skills/creative/comfyui/scripts/extract_schema.py__annotate__r   0   s      c c     c                &   \        V \        4      '       d   R # \        V \        4      '       d   R# \        V \        4      '       d   R# \        V \        4      '       d   R# \        V \
        4      '       d   R# \        V \        4      '       d   R# R# )boolintfloatstringlinkobjectunknown)
isinstancer   r   r   r   listdict)r   s   &r   
infer_typer"   0   se    %%%%%%r   max_hopsc               (    V ^8  d   QhRRRRRRRR/# )	r   workflowr!   r   r    r#   r   r   
str | Noner   )r   s   "r   r   r   @   s(      D  3 z r   c                  \        V4      '       g   R# V^ ,          p\        4       p\        V4       F  pVe   W49   d   Vu # VP                  V4       V P	                  V4      p\        V\        4      '       g    R# VP	                  RR4      pVR9   dQ   VP	                  R/ 4      ;'       g    / p\        R VP                  4        4       R4      p	V	f   Vu # V	^ ,          pK  Vu # 	  V# )zFollow a [node_id, slot] link, hopping through Reroute / Primitive nodes
if needed, to find the *upstream* node id that holds the actual value/input.

Bounded by both `max_hops` AND a visited-set to prevent infinite loops on
pathological graphs.
N
class_type inputsc              3  L   "   T F  p\        V4      '       g   K  Vx  K  	  R # 5iN)r   ).0vs   & r   	<genexpr> trace_to_node.<locals>.<genexpr>W   s     GAGAJaas   $
$>   NoteReroutePrimitiveNodeeasy showAnything)	r   setrangeaddgetr   r!   nextvalues)
r%   r   r#   nidvisited_nodeclsr*   	next_links
   &&$       r   trace_to_noderA   @   s     4==1gCG8_;#.JC||C $%%hh|R(KKXXh+11rFGGNI 
A,C
# $ Jr   c                    V ^8  d   QhRRRR/# r   r%   r!   r   r&   r   )r   s   "r   r   r   `   s        r   c                   \        V 4       F  w  rVR,          \        9  d   K  VP                  R/ 4      ;'       g    / pVP                  R4      p\        V4      '       g   KY  \	        W4      pV'       g   Kn  \        V P                  V4      \        4      '       g   K  W,          P                  RR4      pVP                  R4      '       g
   VR9   g   K  Vu # 	  R# )zDTrace `negative` input of a sampler back to the source text encoder.r(   r*   negativer)   CLIPTextEncodeN   BNK_CLIPTextEncodeAdvancedsmZ CLIPTextEncoder   SAMPLER_NODE_FAMILYr8   r   rA   r   r!   
startswith)r%   r;   r>   r*   negsrcr?   s   &      r   find_negative_prompt_noderO   `   s    )	%88(B'--2jj$s||H*3:hll3/66-##L"5C~~.//3:n3n
 * r   c                    V ^8  d   QhRRRR/# rC   r   )r   s   "r   r   r   q   s        r   c                   \        V 4       F  w  rVR ,          \        9  d   K  VP                  R/ 4      ;'       g    / pVP                  R4      p\        V4      '       g   KY  \	        W4      pV'       g   Kn  \        V P                  V4      \        4      '       g   K  W,          P                  R R4      pVP                  R4      '       g
   VR9   g   K  Vu # 	  R# )r(   r*   positiver)   rF   NrG   rJ   )r%   r;   r>   r*   posrN   r?   s   &      r   find_positive_prompt_noderT   q   s    )	%88(B'--2jj$s||H*3:hll3/66-##L"5C~~.//3:n3n
 * r   c                    V ^8  d   QhRRRR/# )r   r%   r!   r   r   )r   s   "r   r   r      s     N NT Nd Nr   c                   a a  . p\        S 4      p\        S 4      p. p\        S 4       EF<  w  rVVR,          pVP                  R/ 4      ;'       g    / pV\        9   d   VP                  V4       \         F  w  rpWy8w  d   K  W9  d   K  W,          p\        V4      pVR8X  d   K2  TpVR8X  d   WS8X  d
   W#8w  d   RpMWR8X  d   RpM{VP                  R4      ;'       g    / P                  RR4      P                  4       o \        ;QJ d    V 3R	 lR* 4       F  '       g   K   R
M	  RM! V 3R	 lR* 4       4      '       d   RpVP                  RVRVRV
RVRVRV/4       K  	  EK?  	  / pV F+  pVP                  VR,          . 4      P                  V4       K-  	  / pVP                  4        F  w  pp\        V4      ^8X  d>   V^ ,          pRVR,          RVR,          RVR,          RVR,          RVR,          /VV&   KS  VP                  R R7       V FE  pV RVR,           2pRVR,          RVR,          RVR,          RVR,          RVR,          RV/VV&   KG  	  K  	  \        \        S 4      4      p. p\!        4       p\#        S 4       F  w  ppVV3pVV9   d   K  VP%                  V4       S P                  V/ 4      pVP                  R/ 4      ;'       g    / pRpRpVP                  4        F?  w  pp\'        V\(        4      '       g   K  V\*        9   g   K+  VV9   g   K4  TpVR,          p M	  VP                  RVRVRVRVRR/4       K  	  R\        V4      R\        V4      R\        V4      R\        V4      RRV9   R R!V9   ;'       g5    \        ;QJ d    R" V 4       F  '       g   K   R
M	  RM! R" V 4       4      R#\        ;QJ d    V 3R$ lV 4       F  '       g   K   R
M	  RM! V 3R$ lV 4       4      /pR%VR&VR'VR(VR)V/# )+av  Extract controllable parameters from a workflow.

Returns:
    {
      "parameters": { friendly_name: {node_id, field, type, value, ...} },
      "output_nodes": [node_id, ...],
      "model_dependencies": [{node_id, class_type, field, value, folder}],
      "embedding_dependencies": [{node_id, embedding_name, found_in_field, value_excerpt}],
      "summary": {...}
    }
r(   r*   r   promptnegative_prompt_metatitler)   c              3  ,   <"   T F	  qS9   x  K  	  R # 5ir,   r   )r-   t_
meta_titles   & r   r/   !extract_schema.<locals>.<genexpr>   s     ]6\+6\s   TF	name_hintnode_idfieldtyper   c                V    \        V R ,          4      P                  ^4      V R,          3# )r`   ra   )r   zfill)xs   &r   <lambda> extract_schema.<locals>.<lambda>   s!    AiL(9(?(?(BAgJ'Or   )keyr=   alias_ofN:Nx   Nembedding_namevalue_excerptfolder
embeddingsparameter_countoutput_node_countmodel_dep_countembedding_dep_counthas_negative_prompthas_seedseedc              3  B   "   T F  qP                  R 4      x  K  	  R# 5i)seed_N)rL   )r-   ps   & r   r/   r^     s     /Zz!W0E0Ezs   is_video_workflowc              3  n   <"   T F*  pSP                  V/ 4      P                  R R4      R9   x  K,  	  R# 5i)r(   r)   N>   	SaveVideoSaveAnimatedPNGSaveAnimatedWEBPVHS_VideoCombine)r8   )r-   nr%   s   & r   r/   r^     s<      !
 $! LLB##L"5 : #s   25
parametersoutput_nodesmodel_dependenciesembedding_dependenciessummary)rE   rM   z-promptanti)rT   rO   r   r8   r   appendr   r"   lowerany
setdefaultitemslensortr    r
   r5   r	   r7   r   r   r   )!r%   r   pos_nodeneg_node
raw_paramsr`   r>   r?   r*   p_classp_fieldfriendlyr   tactual_nameby_namerr   nameentries	full_name
model_depsembedding_depsseen_embr;   emb_namerh   found_fieldexcerptfnamefvalr   r]   s!   f                               @r   extract_schemar      s    !L )2H(2H
  J#H-< (B'--2,( +9&Gh~$OE5!AF{"K 8#&8+?"3K("*K #'((7"3"9"9r!>!>w!K!Q!Q!SJs]6\]sss]6\]]]&7[7c 1 +9 .X &(G1[>2.55a8  #%J gw<1
A1Y<!G*&	7AgJao Jt LLOLP#fAa	l^4	q|WajAfIw'
 !L/	)
9%  )* oh/0J "$N%(UH,X6XHo(?S||C$(B'--2!<<>KE4$$$-)?HPTDT#t*	 *
 	sh[Wl
 	 72 	3z?S.3z?s>20J>Fj(ZZCC/Zz/ZCCC/Zz/Z,ZSS !
 $!
SSS !
 $!
 
G 	jj .7 r   c                    V ^8  d   QhRRRR/# )r   argvzlist[str] | Noner   r   r   )r   s   "r   r   r     s     % % %3 %r   c                   \         P                  ! R R7      pVP                  RRR7       VP                  RRRR7       VP                  RR	R
R7       VP                  V 4      p\	        VP
                  4      P                  4       pVP                  4       '       g"   \        RV R2\        P                  R7       ^#  VP                  4       ;_uu_ 4       p\        P                  ! V4      pRRR4       \        X4      p\#        T4      pTP$                  '       d!   \        P&                  ! TR,          ^R7      p	M\        P&                  ! T^\(        R7      p	TP*                  '       dO   \	        TP*                  4      P-                  T	4       \        RTP*                   2\        P                  R7       ^ # \        T	4       ^ #   + '       g   i     L; i  \         d*   p\        RT 2\        P                  R7        Rp?^# Rp?i\        P                    d*   p\        RT 2\        P                  R7        Rp?^# Rp?ii ; i)z7Extract controllable parameters from a ComfyUI workflow)descriptionr%   zPath to workflow API JSON file)helpz--outputz-ozOutput file (default: stdout)z--summary-only
store_truezOnly print the summary block)actionr   zError: z
 not found)fileNu   Error: invalid JSON — r   )indent)r   defaultzSchema written to )argparseArgumentParseradd_argument
parse_argsr   r%   
expanduserexistsprintsysstderropenjsonloadr   
ValueErrorJSONDecodeErrorr   summary_onlydumpsr   output
write_text)
r   rx   argswf_pathfpayloadr%   eschemaouts
   &         r   mainr     s   ,efANN:$DNENN:t*INJNN#L6  8<<D4==!,,.G>>y
+#**=	\\^^qiilG "7+ H%Fjj	*15jj37{{{T[[$$S)"4;;-0szzB  	c
/ ^  sm#**- (,3::>sB   ?G G.G G	G I&H

I!I"II__main__>   KSampler	CFGGuiderBasicGuiderDualCFGGuiderSamplerCustomKSamplerAdvancedSamplerCustomAdvancedr,   )"__doc__
__future__r   r   r   r   pathlibr   typingr   pathinsertr   __file__resolveparent_commonr   r   r   r   r	   r
   r   r   rK   r"   rA   rO   rT   r   r   __name__exitr   r   r   <module>r      s   . #   
   3tH~--/667 8     @" Nb%P zHHTV r   