entari-plugin-hyw 3.2.105__py3-none-any.whl → 3.2.107__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of entari-plugin-hyw might be problematic. Click here for more details.

@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "assets",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "tailwind.config.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "commonjs",
13
+ "dependencies": {
14
+ "tailwindcss": "^3.4.17"
15
+ }
16
+ }
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- content: ["./template.html", "../core/render.py"],
2
+ content: ["./template.j2", "../core/render.py"],
3
3
  theme: {
4
4
  extend: {
5
5
  fontFamily: {
@@ -51,7 +51,7 @@ pre::-webkit-scrollbar-thumb {
51
51
  }
52
52
 
53
53
  .markdown-body a {
54
- @apply text-pink-600 underline decoration-pink-300 underline-offset-2 hover:text-pink-700 hover:decoration-pink-700 transition-colors;
54
+ @apply text-blue-600 underline decoration-blue-300 underline-offset-2 hover:text-blue-700 hover:decoration-blue-700 transition-colors;
55
55
  }
56
56
 
57
57
  .markdown-body blockquote {
@@ -109,11 +109,11 @@ pre::-webkit-scrollbar-thumb {
109
109
  }
110
110
 
111
111
  .markdown-body code {
112
- @apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-pink-600;
112
+ @apply bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-gray-700;
113
113
  }
114
114
 
115
115
  .markdown-body pre {
116
- @apply bg-white p-4 rounded-lg overflow-x-auto mb-4 text-sm leading-normal border border-gray-100 shadow-sm;
116
+ @apply bg-gray-50 p-4 rounded-lg overflow-x-auto mb-4 text-sm leading-normal border border-gray-100 shadow-sm;
117
117
  }
118
118
 
119
119
  .markdown-body pre code {
@@ -159,12 +159,12 @@ pre::-webkit-scrollbar-thumb {
159
159
  display: inline-flex;
160
160
  align-items: center;
161
161
  justify-content: center;
162
- vertical-align: super;
162
+ vertical-align: baseline;
163
163
  font-size: 0.75em;
164
164
  font-weight: bold;
165
- color: #db2777;
166
- background-color: #fdf2f8;
167
- border: 1px solid #fce7f3;
165
+ color: #4b5563;
166
+ background-color: #f3f4f6;
167
+ border: 1px solid #e5e7eb;
168
168
  border-radius: 9999px;
169
169
  width: 1.4em;
170
170
  height: 1.4em;
@@ -178,7 +178,7 @@ pre::-webkit-scrollbar-thumb {
178
178
  }
179
179
 
180
180
  .citation-ref:hover {
181
- background-color: #db2777;
181
+ background-color: #4b5563;
182
182
  color: white;
183
183
  text-decoration: none !important;
184
184
  }
@@ -0,0 +1,157 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Entari Render</title>
8
+ <!-- @formatter:off -->
9
+ <!-- prettier-ignore -->
10
+ <!-- IMPORTANT: Asset placeholders MUST be {{ name }} on single line! -->
11
+ <style>
12
+ {
13
+ {
14
+ tailwind_css
15
+ }
16
+ }
17
+ </style>
18
+ <style>
19
+ {
20
+ {
21
+ highlight_css
22
+ }
23
+ }
24
+ </style>
25
+ <script>{ { highlight_js } }</script>
26
+ <style>
27
+ {
28
+ {
29
+ katex_css
30
+ }
31
+ }
32
+ </style>
33
+ <script>{ { katex_js } }</script>
34
+ <script>{ { katex_auto_render_js } }</script>
35
+ <!-- @formatter:on -->
36
+ <style>
37
+ #markdown-content h1,
38
+ #markdown-content h2,
39
+ #markdown-content h3,
40
+ #markdown-content h4,
41
+ #markdown-content h5,
42
+ #markdown-content h6 {
43
+ color: #db2777;
44
+ /* text-pink-600 */
45
+ }
46
+ </style>
47
+ </head>
48
+
49
+ <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
50
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
51
+ <!-- Response Card (Content First) -->
52
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
53
+ <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
54
+ {{ content_html }}
55
+ </div>
56
+ </div>
57
+
58
+
59
+
60
+ <!-- Speculation Card (Optional) -->
61
+ {{ suggestions }}
62
+
63
+ <!-- Model Header (Moved to Bottom) -->
64
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
65
+ {{ response_header }}
66
+ <!-- Pipeline Stages -->
67
+ {{ stages }}
68
+ </div>
69
+
70
+ <!-- Stats Footer -->
71
+ {{ stats }}
72
+ </div>
73
+
74
+ <script>window.REFERENCES = {{ references_json }};</script>
75
+
76
+ <script>
77
+ document.addEventListener("DOMContentLoaded", () => {
78
+ // Render Math (KaTeX)
79
+ const contentDiv = document.getElementById("markdown-content");
80
+ renderMathInElement(contentDiv, {
81
+ delimiters: [
82
+ { left: "$$", right: "$$", display: true },
83
+ { left: "$", right: "$", display: false },
84
+ { left: "\\(", right: "\\)", display: false },
85
+ { left: "\\[", right: "\\]", display: true }
86
+ ],
87
+ throwOnError: false
88
+ });
89
+
90
+ // Process Citations
91
+ function processCitations(rootNode) {
92
+ const walker = document.createTreeWalker(
93
+ rootNode,
94
+ NodeFilter.SHOW_TEXT,
95
+ null,
96
+ false
97
+ );
98
+
99
+ const nodesToReplace = [];
100
+ let node;
101
+ while (node = walker.nextNode()) {
102
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
103
+ if (/\[\d+\]/.test(node.nodeValue)) {
104
+ nodesToReplace.push(node);
105
+ }
106
+ }
107
+
108
+ nodesToReplace.forEach(textNode => {
109
+ const fragment = document.createDocumentFragment();
110
+ let lastIndex = 0;
111
+ const text = textNode.nodeValue;
112
+ const regex = /\[(\d+)\]/g;
113
+ let match;
114
+
115
+ while ((match = regex.exec(text)) !== null) {
116
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
117
+
118
+ const id = match[1];
119
+ const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
120
+
121
+ if (ref) {
122
+ const span = document.createElement("span");
123
+ const title = ref.title.replace(/"/g, "&quot;");
124
+ const url = ref.url;
125
+ let domain = "unknown";
126
+ try { domain = new URL(url).hostname; } catch (e) { }
127
+ const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
128
+
129
+ span.innerHTML = `<a href="${url}" target="_blank" class="citation-ref" data-id="${id}">
130
+ ${id}
131
+ <span class="citation-tooltip">
132
+ <span class="citation-tooltip-title">${title}</span>
133
+ <span class="citation-tooltip-url">
134
+ <img src="${favicon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">
135
+ ${domain}
136
+ </span>
137
+ </span>
138
+ </a>`;
139
+ fragment.appendChild(span.firstElementChild);
140
+ } else {
141
+ fragment.appendChild(document.createTextNode(match[0]));
142
+ }
143
+
144
+ lastIndex = regex.lastIndex;
145
+ }
146
+
147
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
148
+ textNode.parentNode.replaceChild(fragment, textNode);
149
+ });
150
+ }
151
+
152
+ processCitations(contentDiv);
153
+ });
154
+ </script>
155
+ </body>
156
+
157
+ </html>
@@ -0,0 +1,259 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link href="https://cdn.jsdelivr.net/npm/remixicon@4.1.0/fonts/remixicon.css" rel="stylesheet"/>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <title>Entari Render</title>
9
+ <!-- @formatter:off -->
10
+ <!-- prettier-ignore -->
11
+ <style>{{ tailwind_css | safe }}</style>
12
+ <style>{{ highlight_css | safe }}</style>
13
+ <script>{{ highlight_js | safe }}</script>
14
+ <style>{{ katex_css | safe }}</style>
15
+ <script>{{ katex_js | safe }}</script>
16
+ <script>{{ katex_auto_render_js | safe }}</script>
17
+ <!-- @formatter:on -->
18
+
19
+ </head>
20
+
21
+ <body class="bg-[#f2f2f2] p-0 box-border m-0 font-sans text-gray-800">
22
+ <div id="main-container" class="w-full max-w-[450px] flex flex-col gap-4 mx-auto bg-[#f2f2f2] p-0 font-sans h-fit">
23
+
24
+ {# --- MACROS --- #}
25
+
26
+ {% macro icon_container(icon_html, box_class="bg-gray-50 rounded-lg border border-gray-100", size_class="w-10 h-10") %}
27
+ <div class="flex items-center justify-center {{ size_class }} {{ box_class }} shrink-0">
28
+ {{ icon_html | safe }}
29
+ </div>
30
+ {% endmacro %}
31
+
32
+ {% macro card_header(title, icon_html=None, subtitle_html=None, is_plain=False, icon_box_class=None) %}
33
+ {% set container_class = "flex items-center gap-3" if is_plain else "flex items-center gap-3 pb-3 mb-3 border-b border-gray-100" %}
34
+ {% set default_box_class = icon_box_class if icon_box_class else "bg-gray-50 rounded-lg border border-gray-100" %}
35
+ <div class="{{ container_class }}">
36
+ {{ icon_container(icon_html, box_class=default_box_class) }}
37
+ <div class="flex flex-col min-w-0">
38
+ <div class="text-sm font-bold text-gray-900 uppercase tracking-wide whitespace-nowrap overflow-hidden text-ellipsis">{{ title }}</div>
39
+ {% if subtitle_html and not is_plain %}
40
+ {{ subtitle_html | safe }}
41
+ {% endif %}
42
+ </div>
43
+ </div>
44
+ {% endmacro %}
45
+
46
+ {% macro list_card(icon_html, title_html, subtitle_html=None, link_url=None, right_content_html=None, is_compact=False, icon_box_class="bg-gray-50 rounded-md shrink-0 ring-1 ring-inset ring-black/5") %}
47
+ {% set tag = "a" if link_url else "div" %}
48
+ {% set href_attr = 'href="' ~ link_url ~ '" target="_blank"' if link_url else "" %}
49
+ {% set hover_class = "transition-colors hover:bg-gray-50" if link_url else "" %}
50
+
51
+ {% set padding_class = "p-2.5" if is_compact else "px-4 py-3.5" %}
52
+ {% set align_class = "items-center" if is_compact else "items-start" %}
53
+ {% set icon_size = "w-6 h-6" if is_compact else "w-8 h-8" %}
54
+
55
+ <{{ tag }} {{ href_attr | safe }} class="flex {{ align_class }} gap-3 {{ padding_class }} rounded-lg border border-gray-100 bg-white shadow-sm no-underline text-inherit {{ hover_class }}">
56
+ <div class="flex items-center justify-center {{ icon_size }} {{ icon_box_class }}">
57
+ {{ icon_html | safe }}
58
+ </div>
59
+ <div class="flex flex-col flex-1 min-w-0 gap-0.5">
60
+ <div class="flex items-center gap-2 leading-tight min-w-0">
61
+ {{ title_html | safe }}
62
+ </div>
63
+ {% if subtitle_html %}
64
+ <div>{{ subtitle_html | safe }}</div>
65
+ {% endif %}
66
+ </div>
67
+ {% if right_content_html %}
68
+ <div class="shrink-0 ml-2">{{ right_content_html | safe }}</div>
69
+ {% endif %}
70
+ </{{ tag }}>
71
+ {% endmacro %}
72
+
73
+
74
+ {# --- MAIN CONTENT --- #}
75
+
76
+ <!-- Response Card (Content First) -->
77
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
78
+ <div id="markdown-content" class="markdown-body text-[15px] leading-relaxed text-gray-800">
79
+ {{ content_html | safe }}
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Speculation Card (Optional) -->
84
+ {% if suggestions %}
85
+ <div class="flex flex-col gap-2 bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden">
86
+ {% set sug_icon %}
87
+ <i class="ri-magic-line text-lg text-pink-600"></i>
88
+ {% endset %}
89
+ {{ card_header("SUGGESTIONS", icon_html=sug_icon, is_plain=True) }}
90
+
91
+ <div class="grid grid-cols-2 gap-2.5">
92
+ {% for sug in suggestions %}
93
+ <div class="flex items-baseline gap-2 text-sm text-gray-600 px-3.5 py-2.5 bg-white/80 backdrop-blur-sm rounded-full shadow-sm hover:shadow transition-shadow cursor-default">
94
+ <span class="text-gray-500 font-mono font-bold text-[13px] whitespace-nowrap">{{ loop.index }}</span>
95
+ <span class="flex-1 text-[13px] text-gray-600 font-medium whitespace-nowrap overflow-hidden text-ellipsis">{{ sug }}</span>
96
+ </div>
97
+ {% endfor %}
98
+ </div>
99
+ </div>
100
+ {% endif %}
101
+
102
+ <!-- Pipeline & Children (Nested) -->
103
+ <div class="bg-[#f2f2f2] rounded-2xl p-5 overflow-hidden flex flex-col gap-4">
104
+
105
+ {% if stages %}
106
+ {% for stage in stages %}
107
+ <div>
108
+ {# Stage Card #}
109
+ {% set color_class = "bg-white text-gray-700 border-gray-200 shadow-sm" %}
110
+
111
+ {% set icon_box_class = color_class + " rounded-md border shrink-0" %}
112
+
113
+ {% set title_html %}
114
+ <span class="text-[11px] font-bold uppercase text-gray-700 shrink-0">{{ stage.name }}</span>
115
+ <span class="text-[11px] font-medium text-gray-700 truncate min-w-0" title="{{ stage.model }}">{{ stage.model_short }}</span>
116
+ <span class="ml-auto text-[10px] text-gray-400 shrink-0 truncate max-w-[80px]">{{ stage.provider }}</span>
117
+ {% endset %}
118
+
119
+ {% set stats_html %}
120
+ <div class="flex items-center gap-3 text-[11px] text-gray-500 font-mono mt-0.5">
121
+ <span>{{ stage.time_str }}</span><span>{{ stage.cost_str }}</span>
122
+ </div>
123
+ {% endset %}
124
+
125
+ {{ list_card(stage.icon_html, title_html, subtitle_html=stats_html, is_compact=True, icon_box_class=icon_box_class) }}
126
+
127
+ {# Nested Children (Indent & Connect) #}
128
+ {% if stage.references or stage.mcp_steps %}
129
+ <div class="ml-4 pl-4 border-l-2 border-gray-200 mt-2 flex flex-col gap-2">
130
+
131
+ {# References #}
132
+ {% if stage.references %}
133
+ <div class="text-[11px] uppercase font-bold text-blue-600 tracking-wider mb-1 mt-1">References</div>
134
+ {% for ref in stage.references %}
135
+ {% set favicon_url = "https://www.google.com/s2/favicons?domain=" + ref.domain + "&sz=32" %}
136
+
137
+ {% set ref_icon %}
138
+ <img src="{{ favicon_url }}" class="w-3.5 h-3.5 rounded-sm opacity-80 decoration-0">
139
+ {% endset %}
140
+
141
+ {% set ref_icon_box = "bg-white rounded border border-gray-100 w-6 h-6 shrink-0" %}
142
+
143
+ {% set title_html = '<div class="text-[11px] font-medium text-gray-900 truncate underline decoration-gray-300 decoration-1 underline-offset-2 hover:text-black hover:decoration-gray-500 transition-colors">' + ref.title + '</div>' %}
144
+ {% set subtitle_html = '<div class="text-[10px] text-gray-700 truncate">' + ref.domain + '</div>' %}
145
+ {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-blue-600 bg-blue-50 border border-blue-200 rounded">' + (loop.index|string) + '</div>' %}
146
+
147
+ {{ list_card(ref_icon, title_html, subtitle_html=subtitle_html, link_url=ref.url, right_content_html=right_html, is_compact=True, icon_box_class=ref_icon_box) }}
148
+ {% endfor %}
149
+ {% endif %}
150
+
151
+ {# MCP Steps #}
152
+ {% if stage.mcp_steps %}
153
+ <div class="text-[11px] uppercase font-bold text-orange-600 tracking-wider mb-1 mt-1">MCP Flow</div>
154
+ {% for step in stage.mcp_steps %}
155
+ {% set icon_box_class = "rounded-md border border-gray-100 bg-white text-gray-500 shrink-0" %}
156
+
157
+ {% set title_html = '<div class="text-[11px] font-medium text-gray-900 font-mono">' + step.name + '</div>' %}
158
+ {% set subtitle_html = '<div class="text-[10px] text-gray-700 leading-tight">' + step.description + '</div>' if step.description else None %}
159
+ {% set right_html = '<div class="flex items-center justify-center min-w-[14px] h-3.5 px-0.5 text-[9px] font-bold text-orange-600 bg-orange-50 border border-orange-200 rounded">' + ('abcdefghijklmnopqrstuvwxyz'[loop.index0]) + '</div>' %}
160
+
161
+ {{ list_card(step.icon_svg, title_html, subtitle_html=subtitle_html, right_content_html=right_html, is_compact=True, icon_box_class=icon_box_class) }}
162
+ {% endfor %}
163
+ {% endif %}
164
+
165
+ </div>
166
+ {% endif %}
167
+ </div>
168
+ {% endfor %}
169
+ {% endif %}
170
+ </div>
171
+
172
+
173
+ </div>
174
+
175
+ <script>window.REFERENCES = {{ references_json | safe }};</script>
176
+
177
+ <script>
178
+ document.addEventListener("DOMContentLoaded", () => {
179
+ // Render Math (KaTeX)
180
+ const contentDiv = document.getElementById("markdown-content");
181
+ if(typeof renderMathInElement !== 'undefined') {
182
+ renderMathInElement(contentDiv, {
183
+ delimiters: [
184
+ { left: "$$", right: "$$", display: true },
185
+ { left: "$", right: "$", display: false },
186
+ { left: "\\(", right: "\\)", display: false },
187
+ { left: "\\[", right: "\\]", display: true }
188
+ ],
189
+ throwOnError: false
190
+ });
191
+ }
192
+
193
+ // Process Citations
194
+ function processCitations(rootNode) {
195
+ const walker = document.createTreeWalker(
196
+ rootNode,
197
+ NodeFilter.SHOW_TEXT,
198
+ null,
199
+ false
200
+ );
201
+
202
+ const nodesToReplace = [];
203
+ let node;
204
+ while (node = walker.nextNode()) {
205
+ if (node.parentElement.tagName === "SCRIPT" || node.parentElement.tagName === "STYLE" || node.parentElement.tagName === "A") continue;
206
+ if (/\[\d+\]/.test(node.nodeValue)) {
207
+ nodesToReplace.push(node);
208
+ }
209
+ }
210
+
211
+ nodesToReplace.forEach(textNode => {
212
+ const fragment = document.createDocumentFragment();
213
+ let lastIndex = 0;
214
+ const text = textNode.nodeValue;
215
+ const regex = /\[(\d+)\]/g;
216
+ let match;
217
+
218
+ while ((match = regex.exec(text)) !== null) {
219
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
220
+
221
+ const id = match[1];
222
+ const ref = window.REFERENCES ? window.REFERENCES.find(r => r.id == id) : null;
223
+
224
+ if (ref) {
225
+ const span = document.createElement("span");
226
+ const title = ref.title.replace(/"/g, "&quot;");
227
+ const url = ref.url;
228
+ let domain = "unknown";
229
+ try { domain = new URL(url).hostname; } catch (e) { }
230
+ const favicon = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
231
+
232
+ span.innerHTML = `<a href="${url}" target="_blank" class="citation-ref" data-id="${id}">
233
+ ${id}
234
+ <span class="citation-tooltip">
235
+ <span class="citation-tooltip-title">${title}</span>
236
+ <span class="citation-tooltip-url">
237
+ <img src="${favicon}" style="width:12px;height:12px;vertical-align:middle;margin-right:4px;">
238
+ ${domain}
239
+ </span>
240
+ </span>
241
+ </a>`;
242
+ fragment.appendChild(span.firstElementChild);
243
+ } else {
244
+ fragment.appendChild(document.createTextNode(match[0]));
245
+ }
246
+
247
+ lastIndex = regex.lastIndex;
248
+ }
249
+
250
+ fragment.appendChild(document.createTextNode(text.substring(lastIndex)));
251
+ textNode.parentNode.replaceChild(fragment, textNode);
252
+ });
253
+ }
254
+
255
+ processCitations(contentDiv);
256
+ });
257
+ </script>
258
+ </body>
259
+ </html>
@@ -495,29 +495,53 @@ class ProcessingPipeline:
495
495
  current_step = None
496
496
 
497
497
  for line in lines:
498
- # Check if this is a tool line: [icon] tool_name
499
- tool_match = re.match(r'\[(\w+)\]\s+(.+)', line.strip())
500
- if tool_match:
501
- # Save previous step if exists
502
- if current_step:
503
- parsed["mcp_steps"].append(current_step)
498
+ line_stripped = line.strip()
499
+ if not line_stripped: continue
500
+
501
+ # New Format: "1. [icon] name: description" OR "[icon] name: description"
502
+ # Regex details:
503
+ # ^(?:(?:\d+\.|[-*])\s+)? -> Optional numbering (1. or - or *)
504
+ # \[(\w+)\] -> Icon in brackets [icon] -> group 1
505
+ # \s+ -> separating space
506
+ # ([^:]+) -> Tool Name (chars before colon) -> group 2
507
+ # : -> Colon separator
508
+ # \s*(.+) -> Description -> group 3
509
+ new_format_match = re.match(r'^(?:(?:\d+\.|[-*])\s+)?\[(\w+)\]\s+([^:]+):\s*(.+)$', line_stripped)
510
+
511
+ # Old/Flexible Format: "[icon] name" (description might be on next line)
512
+ flexible_match = re.match(r'^(?:(?:\d+\.|[-*])\s+)?\[(\w+)\]\s+(.+)$', line_stripped)
513
+
514
+ if new_format_match:
515
+ if current_step: parsed["mcp_steps"].append(current_step)
504
516
  current_step = {
505
- "icon": tool_match.group(1).lower(),
506
- "name": tool_match.group(2).strip(),
507
- "description": ""
517
+ "icon": new_format_match.group(1).lower(),
518
+ "name": new_format_match.group(2).strip(),
519
+ "description": new_format_match.group(3).strip()
508
520
  }
509
- elif line.startswith(" ") and current_step:
510
- # This is an indented description line
511
- current_step["description"] = line.strip()
512
- elif line.strip() and not line.startswith("[") and current_step is None:
513
- # Fallback: plain tool name without icon
521
+ elif flexible_match:
522
+ # Could be just "[icon] name" without description, or mixed
523
+ if current_step: parsed["mcp_steps"].append(current_step)
514
524
  current_step = {
515
- "icon": "",
516
- "name": line.strip(),
525
+ "icon": flexible_match.group(1).lower(),
526
+ "name": flexible_match.group(2).strip(),
517
527
  "description": ""
518
528
  }
529
+ elif line.startswith(" ") and current_step:
530
+ # Indented description line (continuation)
531
+ if current_step["description"]:
532
+ current_step["description"] += " " + line.strip()
533
+ else:
534
+ current_step["description"] = line.strip()
535
+ elif line_stripped and not line_stripped.startswith("[") and current_step is None:
536
+ # Plain text line without icon, treat as name if no current step
537
+ # (This handles cases where LLM forgets brackets but lists steps)
538
+ if current_step: parsed["mcp_steps"].append(current_step)
539
+ current_step = {
540
+ "icon": "default",
541
+ "name": line_stripped,
542
+ "description": ""
543
+ }
519
544
 
520
- # Don't forget the last step
521
545
  if current_step:
522
546
  parsed["mcp_steps"].append(current_step)
523
547
  remaining_text = remaining_text.replace(mcp_block_match.group(0), "").strip()