1---
2name: awesomewebpagemetaskill
3visibility: experimental
4invocation: experimental_internal
5description: "Build a local multimedia webpage project from a user topic, audience, language, style, and media preferences by framing requirements, researching, planning, acquiring media, generating files, packaging, validating, repairing, and delivering usage guidance."
6description_zh: "根据用户给定的主题、受众、语言、风格和媒体偏好,通过梳理需求、调研、规划、获取媒体、生成文件、打包、校验、修复并交付使用指引,构建一个本地多媒体网页项目。"
7kind: meta
8meta_priority: 65
9always: false
10final_text_mode: "step:delivery_guide"
11request_template:
12 outcome: "A packaged local multimedia webpage project with assets, validation notes, and usage guidance."
13 outcome_zh: "打包好的本地多媒体网页项目,包含素材、验证说明和使用指南。"
14 outcome_en: "A packaged local multimedia webpage project with assets, validation notes, and usage guidance."
15 fields:
16 - name: topic
17 label: "Topic"
18 label_zh: "网页主题"
19 label_en: "Topic"
20 - name: target_audience
21 label: "Target audience"
22 label_zh: "目标受众"
23 label_en: "Target audience"
24 - name: output_language
25 label: "Output language"
26 label_zh: "输出语言"
27 label_en: "Output language"
28 - name: visual_style
29 label: "Visual style"
30 label_zh: "视觉风格"
31 label_en: "Visual style"
32 - name: media_preferences
33 label: "Media preferences"
34 label_zh: "媒体偏好"
35 label_en: "Media preferences"
36triggers:
37 - "create awesome webpage"
38 - "build multimedia webpage project"
39 - "生成图文音视频网页"
40 - "做一个多媒体网页项目"
41 - "AwesomeWebpageMetaSkill"
42provenance:
43 origin: opensquilla-original
44 license: Apache-2.0
45 maintained_by: OpenSquilla
46metadata:
47 opensquilla:
48 risk: high
49 capabilities: [network-read, network-write, filesystem-read, filesystem-write, command-exec]
50 requires:
51 config:
52 - awesome_webpage.openrouter.models.page_generation
53 - awesome_webpage.openrouter.models.image_generation
54 - awesome_webpage.openrouter.models.audio_generation
55 - awesome_webpage.openrouter.models.video_generation
56 - awesome_webpage.output_dir
57 - awesome_webpage.media_strategy
58config:
59 awesome_webpage:
60 openrouter:
61 models:
62 page_generation: moonshotai/kimi-k2.6
63 image_generation: google/gemini-3-pro-image-preview
64 audio_generation: openai/gpt-audio-mini
65 video_generation: bytedance/seedance-2.0-fast
66 output_dir: "{{ inputs.workspace_dir }}/awesome-webpage-output"
67 media_strategy:
68 search_first: true
69 aigc_fallback_when_search_empty: true
70 allow_remote_embeds: false
71 default_modalities: [text, images, audio, video]
72 search_modalities: [images]
73 direct_aigc_modalities: [audio, video]
74 confirmation_steps: [ask_images, ask_audio, ask_video, ask_style, media_provider_approval]
75 aigc_policy: search_images_direct_generate_audio_video
76 placeholder_policy: visible_replacement_slot_when_generation_unavailable
77 target_assets:
78 images: 6
79 audio: 1
80 video: 1
81 licensing: prefer_reusable_or_user_supplied
82 clawhub_skills:
83 web_search:
84 skill: web-search
85 url: https://clawhub.ai/billyutw/web-search
86 image_generation:
87 skill: nano-banana-pro-openrouter
88 url: https://clawhub.ai/skills/nano-banana-pro-openrouter
89 opensquilla_compatibility: deterministic-skill-exec
90 notes: |
91 The image_aigc step runs this deterministic skill_exec adapter.
92 It does not invoke a prompt-building skill as an agent. Its volatile
93 connection comes from ordinary Provider Settings after approval.
94 audio_generation:
95 skill: audio-cog
96 url: https://clawhub.ai/skills/audio-cog
97 opensquilla_compatibility: openrouter-config-first
98 video_generation:
99 skill: openrouter-video-generator
100 url: openrouter-configured-video-generation
101 webpage_generation:
102 skill: html-coder
103 url: https://clawhub.ai/jhauga/html-coder
104 opensquilla_compatibility: scoped-agent
105 notes: |
106 Invoked by the webpage_generation step as the HTML/CSS/JS authoring
107 skill. The meta-skill task constrains it to source JSON only;
108 packaging, validation, repair, and media generation remain separate
109 steps.
110 filesystem:
111 skill: filesystem
112 url: https://clawhub.ai/gtrusler/clawdbot-filesystem
113composition:
114 steps:
115 - id: requirement_framing
116 kind: llm_chat
117 with:
118 system: |
119 You are the Requirement Framing stage for AwesomeWebpageMetaSkill.
120 Produce a compact, structured brief. Do not call tools.
121 task: |
122 {{ inputs.language_instruction }}
123
124 Extract and normalize the user's webpage request.
125
126 Required fields:
127 - topic
128 - target_audience
129 - output_language
130 - visual_style
131 - media_preferences: infer the user's requested images, audio, video, remote embeds, and local-only needs
132 - configured_output_dir: use the resolved config below; only mark CONFIG_NEEDED if it is empty
133 - constraints and risks
134
135 Do not invent provider names, model ids, API keys, output paths, or media strategy values.
136 They must come from the `awesome_webpage` config.
137 A missing OpenRouter model value means CONFIG_NEEDED for that generator; it does not mean
138 the user does not want that media modality. Do not mark audio or video as unwanted only
139 because its configured model is null.
140
141 Resolved awesome_webpage config for this run:
142 provider: openrouter
143 openrouter.models.page_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('page_generation', 'moonshotai/kimi-k2.6') }}
144 openrouter.models.image_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('image_generation', 'google/gemini-3-pro-image-preview') }}
145 openrouter.models.audio_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('audio_generation', 'openai/gpt-audio-mini') }}
146 openrouter.models.video_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('video_generation', 'bytedance/seedance-2.0-fast') }}
147 output_dir: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}
148 This resolved output_dir is configured and must not be reported as CONFIG_NEEDED.
149 media_strategy.search_first: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_first', true) }}
150 media_strategy.search_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_modalities', ['images']) | join(', ') }}
151 media_strategy.direct_aigc_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('direct_aigc_modalities', ['audio', 'video']) | join(', ') }}
152 media_strategy.aigc_fallback_when_search_empty: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('aigc_fallback_when_search_empty', true) }}
153 media_strategy.allow_remote_embeds: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('allow_remote_embeds', false) }}
154
155 User request:
156 {{ inputs.user_message | xml_escape | truncate(1600) }}
157
158 - id: project_slug
159 kind: llm_chat
160 depends_on: [requirement_framing]
161 with:
162 system: |
163 You produce a single filesystem-safe slug identifying this webpage
164 project. Do not call tools.
165 task: |
166 Output ONLY a single slug derived from the topic in the framed
167 requirements below. Constraints:
168 - lowercase ASCII letters, digits, and hyphens only
169 - no spaces, no path separators, no file extension, no quotes
170 - max 40 characters
171 - topic-derived, so different topics produce different slugs
172
173 Examples (do NOT reuse — derive from the actual topic):
174 - "海洋塑料污染" → ocean-plastic-pollution
175 - "Quantum computing intro" → quantum-computing-intro
176 - "我们公司年会回顾" → company-annual-recap
177
178 If you cannot derive a meaningful slug, output `webpage`.
179
180 Output: a single line containing just the slug. No prefix, no
181 explanation, no backticks, no quotes.
182
183 Framed requirements:
184 {{ outputs.requirement_framing | truncate(1500) }}
185
186 - id: ask_images
187 kind: user_input
188 depends_on: [requirement_framing]
189 clarify:
190 mode: chat
191 intro: |
192 先逐项确认网页媒体配置,再继续研究、搜索素材和生成项目。
193 默认需要图片;后续会先搜索,搜不到合适素材才生成。
194 intro_zh: |
195 先逐项确认网页媒体配置,再继续研究、搜索素材和生成项目。
196 默认需要图片;后续会先搜索,搜不到合适素材才生成。
197 intro_en: |
198 I will confirm the webpage media settings before research, asset search,
199 and project generation. Images are included by default; I will search
200 first and generate only when suitable assets are unavailable.
201 nl_extract: true
202 fields:
203 - name: include_images
204 type: enum
205 choices: ["YES", "NO"]
206 default: "YES"
207 prompt: "是否需要图片?"
208 prompt_zh: "是否需要图片?"
209 prompt_en: "Do you want images?"
210 cancel_keywords: ["取消", "算了", "cancel", "stop", "abort"]
211 timeout_hours: 24
212
213 - id: ask_audio
214 kind: user_input
215 depends_on: [ask_images]
216 clarify:
217 mode: chat
218 intro: |
219 已记录图片选择。现在确认音频。
220 默认需要音频;音频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。
221 intro_zh: |
222 已记录图片选择。现在确认音频。
223 默认需要音频;音频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。
224 intro_en: |
225 I recorded the image choice. Now I need to confirm audio.
226 Audio is included by default; it is generated from the OpenRouter
227 configuration or represented as a replaceable slot.
228 nl_extract: true
229 fields:
230 - name: include_audio
231 type: enum
232 choices: ["YES", "NO"]
233 default: "YES"
234 prompt: "是否需要音频?"
235 prompt_zh: "是否需要音频?"
236 prompt_en: "Do you want audio?"
237 cancel_keywords: ["取消", "算了", "cancel", "stop", "abort"]
238 timeout_hours: 24
239
240 - id: ask_video
241 kind: user_input
242 depends_on: [ask_audio]
243 clarify:
244 mode: chat
245 intro: |
246 已记录音频选择。现在确认视频。
247 默认需要视频;视频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。
248 intro_zh: |
249 已记录音频选择。现在确认视频。
250 默认需要视频;视频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。
251 intro_en: |
252 I recorded the audio choice. Now I need to confirm video.
253 Video is included by default; it is generated from the OpenRouter
254 configuration or represented as a replaceable slot.
255 nl_extract: true
256 fields:
257 - name: include_video
258 type: enum
259 choices: ["YES", "NO"]
260 default: "YES"
261 prompt: "是否需要视频?"
262 prompt_zh: "是否需要视频?"
263 prompt_en: "Do you want video?"
264 cancel_keywords: ["取消", "算了", "cancel", "stop", "abort"]
265 timeout_hours: 24
266
267 - id: ask_style
268 kind: user_input
269 depends_on: [ask_video]
270 clarify:
271 mode: chat
272 intro: |
273 已记录视频选择。最后确认网页整体风格,然后开始研究、规划和素材获取。
274 intro_zh: |
275 已记录视频选择。最后确认网页整体风格,然后开始研究、规划和素材获取。
276 intro_en: |
277 I recorded the video choice. Last, confirm the overall visual style;
278 then I will start research, planning, and asset collection.
279 nl_extract: true
280 fields:
281 - name: visual_style
282 type: string
283 prompt: "网页整体风格是什么?例如:科技感、纪录片风、极简、儿童科普、商业发布会风。"
284 prompt_zh: "网页整体风格是什么?例如:科技感、纪录片风、极简、儿童科普、商业发布会风。"
285 prompt_en: "What overall style should the webpage use? For example: futuristic, documentary, minimal, kids science, or product launch."
286 max_chars: 500
287 cancel_keywords: ["取消", "算了", "cancel", "stop", "abort"]
288 timeout_hours: 24
289
290 - id: deep_research
291 kind: agent
292 skill: awesome-webpage-research
293 depends_on: [requirement_framing, ask_images, ask_audio, ask_video, ask_style]
294 with:
295 question: |
296 Research the topic for a multimedia webpage project.
297
298 Single bounded round only. Produce a short cited brief with 3-5
299 page anchors. Do not run multi-round investigation.
300
301 Requirement framing:
302 {{ outputs.requirement_framing | truncate(3000) }}
303
304 Confirmed interactive choices:
305 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
306 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
307 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
308 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
309
310 Original request:
311 {{ inputs.user_message | xml_escape | truncate(1000) }}
312
313 - id: page_outline
314 kind: llm_chat
315 depends_on: [requirement_framing, project_slug, ask_images, ask_audio, ask_video, ask_style, deep_research]
316 with:
317 system: |
318 You are the Page Outline stage for AwesomeWebpageMetaSkill.
319 Produce a structural outline + media intent list — NOT a final
320 asset binding. Later steps will normalize media results into a
321 compact manifest and then bind actual files to sections. Do not
322 call tools.
323 task: |
324 {{ inputs.language_instruction }}
325
326 Output the webpage OUTLINE — section structure + media intents
327 only. Downstream media steps read this to decide WHAT to fetch
328 or generate; the deterministic `media_assets_collect` step later
329 turns producer output into concrete browser-relative asset paths.
330
331 Required sections:
332
333 1. Page hierarchy
334 - Ordered list of sections: section_id, title, narrative
335 purpose, 1-2 sentence summary.
336 - Overall narrative arc (one paragraph).
337
338 2. Visual style summary (one paragraph)
339
340 3. Media intent list — one entry per desired asset, with:
341 - slot_id: short kebab-case key, unique within outline
342 (e.g. `hero-ocean`, `foodchain-flow`, `narration-intro`).
343 - modality: image | audio | video
344 - placement: which section_id it belongs to + role
345 (e.g. "hero background", "section illustration",
346 "section narration").
347 - subject: one-sentence concrete description of what the
348 asset should depict / convey.
349 - prompt_hint: 1-2 sentences of stylistic / compositional
350 guidance for AIGC steps.
351 - search_keywords: 3-6 short keywords for image search
352 (only meaningful for image slots).
353 - load_bearing: true | false. `true` means the section
354 structurally relies on this asset (e.g. hero video). If
355 the asset later fails to produce, the final media binding
356 gate reports it instead of hiding the missing modality.
357 `false` means the section degrades gracefully to
358 text-only.
359
360 HARD: do NOT specify filenames or paths. slot_id is a
361 semantic key; the producer step (image_download,
362 image_aigc, audio_aigc, video_aigc) decides the on-disk
363 filename and the deterministic media binding step performs
364 final src/path checks.
365
366 4. Local file plan: project/index.html, project/style.css,
367 project/script.js.
368
369 5. Accessibility plan: alt-text strategy, ARIA roles,
370 keyboard navigation, reduced-motion handling.
371
372 Hard rules:
373 - No filenames. No paths. No `.jpg` / `.mp3` / `.mp4`.
374 - If the user said NO to a modality, do not include any media
375 intents of that modality.
376 - Include only as many media intents as the section narrative
377 actually needs — do not pad to hit a target count.
378 - Outline must remain coherent even if every media intent
379 fails downstream (text + headings carry the page).
380
381 Requirement framing:
382 {{ outputs.requirement_framing | truncate(3000) }}
383
384 Research report:
385 {{ outputs.deep_research | truncate(6000) }}
386
387 Confirmed interactive choices:
388 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
389 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
390 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
391 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
392
393 - id: media_slots_normalize
394 kind: tool_call
395 tool: exec_command
396 tool_allowlist: [exec_command]
397 depends_on: [page_outline]
398 tool_args:
399 command: |
400 python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_slots_normalize
401 timeout: 20
402 stdin: |
403 {"page_outline": {{ outputs.page_outline | tojson }},
404 "requirement_framing": {{ outputs.requirement_framing | tojson }},
405 "include_image": {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') | tojson }},
406 "include_audio": {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') | tojson }},
407 "include_video": {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') | tojson }},
408 "visual_style": {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') | tojson }}}
409
410 - id: media_search
411 kind: agent
412 skill: web-search
413 depends_on: [media_slots_normalize]
414 when: "inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') == 'YES' and inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_first', True) and 'images' in inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_modalities', ['images'])"
415 with:
416 query: |
417 Search only for reusable webpage image candidates for this plan.
418 Do not search for audio or video; those modalities are generated directly
419 through the configured OpenRouter models when the user requests them.
420 Prefer assets that can be used locally or replaced cleanly. Return URLs, titles,
421 image type, likely license/provenance, and download/replacement notes.
422
423 Tool contract for this step:
424 - Use the platform-provided `web_search` tool ONLY (provider is configured
425 globally; you do not need to know which engine — just call the tool).
426 - Do NOT run any local Python script (no `python scripts/search.py`,
427 no `duckduckgo-search`, no `pip install`).
428 - Do NOT call `web_fetch`. Snippets from `web_search` are enough.
429 - Issue at most 2 `web_search` calls, each using only the supported
430 arguments `query` and `max_results=6`; include image intent in the
431 query text itself, grouped by visual theme — do not search per asset.
432 - Stop as soon as you have ~4-6 usable candidates total.
433 - If results are empty, unusable, off-topic, or licensing is unclear,
434 stop immediately and return:
435 {"status":"NO_USABLE_IMAGE_CANDIDATES","candidates":[]}
436 - Otherwise return one JSON object with:
437 candidates[] (title, url, source_domain, likely_license, suggested_alt).
438
439 Normalized media slots (authoritative):
440 {{ outputs.media_slots_normalize | truncate(3500) }}
441
442 Page plan context:
443 {{ outputs.page_outline | truncate(1200) }}
444
445 Confirmed interactive choices:
446 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
447 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
448 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
449 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
450 format: json
451
452 - id: media_strategy
453 kind: llm_classify
454 output_choices:
455 - IMAGE_SEARCH_READY
456 - NEEDS_AIGC_IMAGE
457 depends_on: [media_search, media_slots_normalize]
458 with:
459 text: |
460 Decide whether searched image media is enough or whether image AIGC fallback is required.
461
462 Fast media policy:
463 - Use the confirmed interactive choices as the source of truth.
464 - Search is image-only and only runs when config.awesome_webpage.media_strategy.search_first
465 is true and `images` is present in search_modalities.
466 - When image search is disabled by config, ignore media_search output and choose
467 NEEDS_AIGC_IMAGE for requested images so the direct image generator handles them.
468 - Audio and video must not be evaluated through web search.
469 - If include_images is NO, choose IMAGE_SEARCH_READY.
470 - If include_images is YES, evaluate only image candidates from web-search.
471 - Choose NEEDS_AIGC_IMAGE when image search is empty, unusable, off-topic,
472 not downloadable/localizable, or has unclear licensing.
473 - Choose IMAGE_SEARCH_READY only when the search results provide enough usable
474 local/downloadable image assets for the requested page.
475 - Audio and video are direct AIGC modalities. They are handled by audio_aigc
476 and video_aigc based on user confirmation and OpenRouter config, not by this classifier.
477
478 Rules:
479 - IMAGE_SEARCH_READY: image search results are sufficient, or images were not requested.
480 - NEEDS_AIGC_IMAGE: image generation is needed.
481
482 If search is empty and config.awesome_webpage.media_strategy.aigc_fallback_when_search_empty is true,
483 choose NEEDS_AIGC_IMAGE. Do not choose a model here; model choice belongs to config.
484
485 Resolved media strategy:
486 search_first: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_first', true) }}
487 search_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_modalities', ['images']) | join(', ') }}
488 aigc_fallback_when_search_empty: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('aigc_fallback_when_search_empty', true) }}
489
490 Requirement framing:
491 {{ outputs.requirement_framing | truncate(2000) }}
492
493 Page plan:
494 {{ outputs.page_outline | truncate(2500) }}
495
496 Normalized media slots:
497 {{ outputs.media_slots_normalize | truncate(2000) }}
498
499 Confirmed interactive choices:
500 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
501 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
502 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
503 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
504
505 Primary search:
506 {{ outputs.media_search | truncate(3500) }}
507
508 - id: image_download
509 kind: skill_exec
510 skill: awesome-webpage-image-download
511 depends_on: [media_strategy, media_slots_normalize]
512 when: "inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') == 'YES' and outputs.media_strategy == 'IMAGE_SEARCH_READY'"
513 with:
514 output_dir: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project/assets/images"
515 local_path_prefix: project/assets/images
516 payload: |
517 {"media_slots": {{ outputs.media_slots_normalize | tojson }},
518 "media_search": {{ outputs.media_search | tojson }}}
519
520 - id: media_provider_approval
521 kind: user_input
522 depends_on: [page_outline, media_strategy]
523 clarify:
524 mode: form
525 intro: |
526 媒体生成需要你明确授权。批准后,媒体提示词、网页大纲及生成所需的
527 相关内容会发送给下方所选的外部 Provider,并且可能产生费用。
528 修改意见(包括备注栏内容)、含糊回答或空回复都不会被视为授权,
529 也不会调用付费生成。
530
531 Selected Provider: OpenRouter (the current code-owned candidate)
532 Images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
533 Audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
534 Video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
535 intro_zh: |
536 媒体生成需要你明确授权。批准后,媒体提示词、网页大纲及生成所需的相关内容会发送给下方所选的外部 Provider,并且可能产生费用。修改意见(包括备注栏内容)、含糊回答或空回复都不会被视为授权,也不会调用付费生成。
537
538 所选 Provider:OpenRouter(当前由代码维护的候选)
539 图片:{{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
540 音频:{{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
541 视频:{{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
542 intro_en: |
543 Media generation requires your explicit approval. If you approve, media prompts, the webpage outline, and related content needed for generation will be sent to the selected external Provider below and may incur charges. Edit requests (including any additional-notes text), ambiguous answers, and empty replies do not count as approval and will not authorize paid generation.
544
545 Selected Provider: OpenRouter (the current code-owned candidate)
546 Images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
547 Audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
548 Video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
549 # Keep extraction off and omit a default so only the user's explicit
550 # structured choice can authorize an external paid submission.
551 nl_extract: false
552 fields:
553 - name: approval
554 type: enum
555 required: true
556 choices: ["APPROVE_MEDIA_SEND_AND_COST", "DECLINE_MEDIA_GENERATION"]
557 prompt: "是否同意向所选 Provider 发送媒体提示词和相关内容,并接受可能产生的费用?"
558 prompt_zh: "是否同意向所选 Provider 发送媒体提示词和相关内容,并接受可能产生的费用?"
559 prompt_en: "Do you approve sending media prompts and related content to the selected Provider and accept that charges may apply?"
560 - name: additional_notes
561 type: string
562 required: false
563 prompt: "可选修改意见。填写任何内容都会阻止本次付费媒体调用;请先修改方案,再重新授权。"
564 prompt_zh: "可选修改意见。填写任何内容都会阻止本次付费媒体调用;请先修改方案,再重新授权。"
565 prompt_en: "Optional edit notes. Any text here blocks paid media calls for this approval; revise the plan before authorizing again."
566 max_chars: 2000
567 cancel_keywords: ["取消", "算了", "cancel", "stop", "abort"]
568 timeout_hours: 24
569
570 - id: image_aigc
571 kind: skill_exec
572 skill: nano-banana-pro-openrouter
573 side_effect: external_paid_submit
574 depends_on: [media_strategy, image_download, media_slots_normalize, media_provider_approval]
575 when: "inputs.get('collected', {}).get('media_provider_approval', {}).get('approval', '') == 'APPROVE_MEDIA_SEND_AND_COST' and not inputs.get('collected', {}).get('media_provider_approval', {}).get('additional_notes', '') and inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') == 'YES' and (outputs.media_strategy == 'NEEDS_AIGC_IMAGE' or 'IMAGE_DOWNLOAD_INCOMPLETE:' in outputs.get('image_download', '') or (outputs.media_strategy == 'IMAGE_SEARCH_READY' and 'IMAGE_READY:' not in outputs.get('image_download', '')))"
576 with:
577 model: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('image_generation', 'google/gemini-3-pro-image-preview') }}"
578 output_dir: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project/assets/images"
579 filename: "{{ outputs.project_slug | slugify }}.png"
580 resolution: "1K"
581 max_images: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('target_assets', {}).get('images', 6) }}"
582 local_path_prefix: project/assets/images
583 payload: |
584 {
585 "requirement_framing": {{ outputs.requirement_framing | tojson }},
586 "media_slots": {{ outputs.media_slots_normalize | tojson }},
587 "page_outline": {{ outputs.page_outline | tojson }},
588 "image_download": {{ outputs.get('image_download', '') | tojson }},
589 "include_images": {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') | tojson }},
590 "visual_style": {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') | tojson }}
591 }
592
593 - id: audio_aigc
594 kind: skill_exec
595 skill: audio-cog
596 side_effect: external_paid_submit
597 depends_on: [audio_script, media_provider_approval]
598 when: "inputs.get('collected', {}).get('media_provider_approval', {}).get('approval', '') == 'APPROVE_MEDIA_SEND_AND_COST' and not inputs.get('collected', {}).get('media_provider_approval', {}).get('additional_notes', '') and inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') == 'YES'"
599 with:
600 model: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('audio_generation', 'openai/gpt-audio-mini') }}"
601 output_dir: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project/assets/audio"
602 filename: "{{ outputs.project_slug | slugify }}-narration.wav"
603 voice: cedar
604 payload: |
605 {
606 "script": {{ outputs.audio_script | tojson }},
607 "requirement_framing": {{ outputs.requirement_framing | tojson }},
608 "media_slots": {{ outputs.media_slots_normalize | tojson }}
609 }
610
611 - id: audio_script
612 kind: llm_chat
613 depends_on: [requirement_framing, page_outline, media_slots_normalize]
614 when: "inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') == 'YES'"
615 with:
616 system: |
617 You write only final spoken narration text for webpage audio.
618 Do not call tools. Do not acknowledge the request.
619 task: |
620 {{ inputs.language_instruction }}
621
622 Write the exact narration script that the audio model should speak.
623 Output spoken text only. No title, no markdown, no filename, no JSON,
624 no labels, no stage directions, no "我明白了", no "接下来", no
625 "我将为你生成", no assistant-style acknowledgement.
626
627 Requirements:
628 - Make it a polished webpage narration or guide, not a response to
629 the user.
630 - 45-75 seconds when spoken.
631 - Match the requested language and style.
632 - Use the audio slot placement/subject from media slots when present.
633 - It must stand alone as content a visitor hears on the page.
634
635 Requirement framing:
636 {{ outputs.requirement_framing | truncate(1800) }}
637
638 Page outline:
639 {{ outputs.page_outline | truncate(2600) }}
640
641 Normalized media slots:
642 {{ outputs.media_slots_normalize | truncate(2200) }}
643
644 - id: video_aigc
645 kind: skill_exec
646 skill: openrouter-video-generator
647 side_effect: external_paid_submit
648 depends_on: [page_outline, media_provider_approval]
649 when: "inputs.get('collected', {}).get('media_provider_approval', {}).get('approval', '') == 'APPROVE_MEDIA_SEND_AND_COST' and not inputs.get('collected', {}).get('media_provider_approval', {}).get('additional_notes', '') and inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') == 'YES'"
650 with:
651 model: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('video_generation', 'bytedance/seedance-2.0-fast') }}"
652 output_dir: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project/assets/video"
653 filename: "{{ outputs.project_slug | slugify }}-intro.mp4"
654 duration: "10"
655 aspect_ratio: "16:9"
656 prompt: |
657 Generate the short video asset requested by the page outline. Do not
658 run web search for video assets.
659
660 Requirement framing:
661 {{ outputs.requirement_framing | truncate(1800) }}
662
663 Page plan:
664 {{ outputs.page_outline | truncate(3000) }}
665
666 Confirmed interactive choices:
667 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
668 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
669
670 - id: media_assets_collect
671 kind: tool_call
672 tool: exec_command
673 tool_allowlist: [exec_command]
674 depends_on:
675 - image_download
676 - image_aigc
677 - audio_aigc
678 - video_aigc
679 tool_args:
680 command: |
681 python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_assets_collect
682 timeout: 30
683 env:
684 PROJECT_ROOT: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project"
685 IMAGE_DOWNLOAD: "{{ outputs.get('image_download', '') }}"
686 IMAGE_AIGC: "{{ outputs.get('image_aigc', '') }}"
687 AUDIO_AIGC: "{{ outputs.get('audio_aigc', '') }}"
688 VIDEO_AIGC: "{{ outputs.get('video_aigc', '') }}"
689
690 - id: webpage_generation
691 kind: agent
692 skill: html-coder
693 depends_on:
694 - requirement_framing
695 - deep_research
696 - page_outline
697 - media_slots_normalize
698 - media_assets_collect
699 with:
700 mode: generate
701 task: |
702 {{ inputs.language_instruction }}
703
704 You are the Webpage Source Authoring stage for AwesomeWebpageMetaSkill.
705 Produce source text only. Do not call tools and do not write files.
706 Apply a professional HTML/CSS quality standard: clear visual
707 hierarchy, semantic HTML, restrained but distinctive typography and
708 color, accessible media controls, responsive layout, and intentional
709 composition rather than a thin placeholder page.
710
711 Generate a complete local webpage as strict JSON with exactly these keys:
712 - `index_html`
713 - `style_css`
714 - `script_js`
715 - `summary`
716
717 Output JSON only. Do not wrap it in Markdown fences. Ignore
718 html-coder's default Markdown/code-block output format for this
719 invocation; the final answer must be the strict JSON object only.
720
721 Scope:
722 - Author only the contents for project/index.html, project/style.css,
723 and project/script.js.
724 - Do not download, search, generate, copy, move, delete, package, validate, repair, normalize paths, or clean up assets.
725 - Do not mention or choose filesystem paths. A deterministic later
726 step writes the files to the exact project root.
727 - Use only browser-relative `assets/...` paths listed in
728 `media_assets_collect.assets[]`.
729 - Include available image, audio, and video assets in the authored
730 page. A deterministic later step patches any asset the model misses.
731 - Do not show "pending", "to be added", or "replace this asset"
732 placeholders for requested media.
733 - Place audio controls according to the audio slot placement in
734 `media_slots_normalize.slots[]`; for narration/guide audio, put
735 the player near the intro/hero or the referenced section, never as
736 footer-only/end-of-page content unless the slot explicitly says so.
737
738 Design-quality contract, adapted from html-coder
739 (https://clawhub.ai/jhauga/html-coder):
740 - Build a real, production-quality webpage, not a short landing-page
741 stub. Represent the page outline in semantic
742 HTML (`header`, `main`, `section`, `figure`, `footer`).
743 - Use strong visual hierarchy, clear grid alignment, intentional
744 whitespace, readable line lengths, and a balanced 2-3 color system.
745 Avoid a one-note dark/slate/blue page unless the requested style
746 specifically requires it.
747 - Include accessible local media: `<audio controls>` for any
748 audio asset, `<video>` for any video asset, and `<img>` with
749 meaningful alt text/captions for every image asset.
750 - If `media_assets_collect` contains extra image assets, render a gallery,
751 evidence wall, or visual sequence section so generated assets do
752 not sit unused on disk.
753 - Add keyboard-accessible controls where interaction exists, visible
754 focus states, reduced-motion handling, and mobile layouts without
755 overlapping text.
756 - Do not show "replace this asset" placeholders when a real asset is
757 present in `media_assets_collect`.
758
759 Media assets:
760 The authoritative asset facts are `media_assets_collect` below.
761 Use only `media_assets_collect.assets[].src` values in generated
762 HTML/CSS/JS. Those values are already `assets/...` browser paths.
763 Do not scan raw producer output and do not use raw
764 `project/assets/...` local_path values as browser src paths.
765
766 Page outline (narrative + section structure):
767 {{ outputs.page_outline | truncate(1500) }}
768
769 Normalized media slots (placement/role contract):
770 {{ outputs.media_slots_normalize | truncate(3500) }}
771
772 Media strategy:
773 {{ outputs.media_strategy }}
774
775 Confirmed interactive choices:
776 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
777 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
778 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
779 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
780
781 Media assets:
782 {{ outputs.media_assets_collect | truncate(6000) }}
783
784 - id: webpage_source_validate
785 kind: tool_call
786 tool: exec_command
787 tool_allowlist: [exec_command]
788 depends_on: [webpage_generation]
789 tool_args:
790 command: |
791 python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.webpage_source_validate
792 timeout: 15
793 stdin: "{{ outputs.webpage_generation | tojson }}"
794
795 - id: webpage_generation_retry
796 kind: llm_chat
797 depends_on: [webpage_generation, webpage_source_validate, media_slots_normalize]
798 when: >-
799 not outputs.get('webpage_generation', '').strip() or
800 'WEBPAGE_SOURCE_INVALID:' in outputs.get('webpage_source_validate', '')
801 with:
802 system: |
803 You are the fallback Webpage Source Authoring stage for
804 AwesomeWebpageMetaSkill. The primary source authoring step returned
805 empty or invalid output. Produce compact source JSON only.
806 task: |
807 {{ inputs.language_instruction }}
808
809 Generate strict JSON with exactly these keys:
810 - `index_html`
811 - `style_css`
812 - `script_js`
813 - `summary`
814
815 Output JSON only. Do not wrap it in Markdown fences.
816
817 Scope:
818 - Author only project/index.html, project/style.css, and
819 project/script.js contents.
820 - Do not call tools, write files, download assets, search, generate
821 media, package, validate, repair, or normalize paths.
822 - Use only `assets/...` browser paths already present in
823 media_assets_collect.assets[].src.
824 - Do not invent pending audio/video controls or "to be added"
825 placeholders.
826 - Place audio controls near the audio slot placement from
827 media_slots_normalize, not as footer-only/end-of-page content
828 unless the slot explicitly says footer.
829 - Keep the page production-quality: semantic sections, accessible
830 local media, responsive layout, meaningful hierarchy, and no
831 overlapping mobile text.
832
833 Page outline:
834 {{ outputs.page_outline | truncate(900) }}
835
836 Normalized media slots:
837 {{ outputs.media_slots_normalize | truncate(2200) }}
838
839 Media assets:
840 {{ outputs.media_assets_collect | truncate(3500) }}
841
842 Confirmed interactive choices:
843 images: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}
844 audio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}
845 video: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}
846 visual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}
847
848 - id: webpage_write
849 kind: tool_call
850 tool: exec_command
851 tool_allowlist: [exec_command]
852 depends_on: [webpage_generation, webpage_source_validate, webpage_generation_retry]
853 tool_args:
854 command: |
855 python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.webpage_write
856 timeout: 30
857 stdin: "{{ (outputs.get('webpage_generation_retry', '') or outputs.webpage_generation) | tojson }}"
858 env:
859 WORKSPACE_DIR: "{{ inputs.workspace_dir }}"
860 PROJECT_ROOT: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}"
861
862 - id: media_bind_validate
863 kind: tool_call
864 tool: exec_command
865 tool_allowlist: [exec_command]
866 depends_on: [webpage_write, media_assets_collect]
867 tool_args:
868 command: |
869 python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_bind_validate
870 timeout: 30
871 env:
872 PROJECT_ROOT: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}"
873 IMAGE_DOWNLOAD: "{{ outputs.get('image_download', '') }}"
874 IMAGE_AIGC: "{{ outputs.get('image_aigc', '') }}"
875 AUDIO_AIGC: "{{ outputs.get('audio_aigc', '') }}"
876 VIDEO_AIGC: "{{ outputs.get('video_aigc', '') }}"
877 INCLUDE_IMAGE: "{{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}"
878 INCLUDE_AUDIO: "{{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}"
879 INCLUDE_VIDEO: "{{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}"
880
881 - id: quick_validate
882 kind: agent
883 skill: filesystem
884 depends_on: [media_bind_validate]
885 with:
886 task: |
887 Quick path-level sanity pass over the generated local webpage project.
888
889 Fixed ClawHub skill URL: https://clawhub.ai/gtrusler/clawdbot-filesystem
890 Use only the per-project root:
891 {{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}
892 Do not choose a new output directory and do not install another filesystem skill.
893
894 Expected tree:
895 - project/index.html
896 - project/style.css
897 - project/script.js
898 - project/assets/images
899 - project/assets/audio
900 - project/assets/video
901
902 Allowed operations (path-level only, no content reads):
903 - `test -f` each of the three authored files; report MISSING_FILE
904 with the specific path for any missing one.
905 - `ls` the three asset directories; missing dirs are warnings, not
906 failures. `mkdir -p` to create any missing asset directory.
907 - Normalize relative links at the path level only — do not open
908 files to check link targets.
909
910 Hard prohibitions:
911 - Do NOT run `cat`, `head`, `tail`, `sed`, `grep`, `wc`, `diff`,
912 or any content-inspection command on index.html, style.css, or
913 script.js. Trust the webpage_generation output as the source of
914 truth.
915 - Do not regenerate, rewrite, or patch authored file content. If a
916 file is missing, report it; do not synthesize a replacement.
917 - Skip any packaging step (zip/tar) — the project tree is the
918 deliverable.
919
920 Output: a 1-paragraph summary stating either VALIDATED (all three
921 authored files exist and asset directories are present) or one of
922 MISSING_FILE / MISSING_ASSETS with the specific path. Stop within
923 three shell commands.
924
925 Webpage write output:
926 {{ outputs.webpage_write | truncate(2500) }}
927
928 Media bind/validate output:
929 {{ outputs.media_bind_validate | truncate(3500) }}
930
931 - id: publish_webpage
932 kind: tool_call
933 tool: publish_artifact
934 tool_allowlist: [publish_artifact]
935 depends_on: [quick_validate]
936 tool_args:
937 path: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project/index.html"
938 mime: "text/html"
939 bundle: "directory"
940 bundle_root: "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project"
941
942 - id: delivery_guide
943 kind: llm_chat
944 depends_on: [quick_validate, publish_webpage]
945 with:
946 system: |
947 You are the Delivery Guide stage for AwesomeWebpageMetaSkill.
948 Produce concise user-facing run and edit instructions.
949 task: |
950 {{ inputs.language_instruction }}
951
952 Write a delivery guide containing:
953 - output project path from config.awesome_webpage.output_dir: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '/awesome-webpage-output') }}/{{ outputs.project_slug | slugify }}/project
954 - how to open project/index.html locally
955 - how to replace images, audio, and video assets
956 - how to edit content in index.html, style.css, and script.js
957 - what was validated, including the deterministic media bind gate
958 - confirm that the complete project was published as one webpage
959 bundle; do not invent or print an artifact URL
960 - any CONFIG_NEEDED items if config values were missing
961 - do not claim a requested media modality is available unless the
962 media bind report says it is present and referenced
963
964 Validation report:
965 {{ outputs.quick_validate | truncate(5000) }}
966
967 Media bind report:
968 {{ outputs.media_bind_validate | truncate(5000) }}
969
970 Webpage bundle publication:
971 {{ outputs.publish_webpage | truncate(1500) }}
972---
973
974# AwesomeWebpageMetaSkill
975
976Build a local multimedia webpage project from a topic, audience, language,
977style, and media preference request.
978
979Pipeline:
980
9811. Requirement Framing
9822. Deep Research
9833. Page Outline (section structure + media intents; no filenames)
9844. Media Acquisition (search / AIGC; producers emit `*_READY:` lines)
9855. Media Assets Collect (deterministic path + existence check)
9866. Webpage Source Generation
9877. Deterministic Webpage Write
9888. Deterministic Media Bind + Validation
9899. Local Validation
99010. Publish Complete Webpage Bundle
99111. Delivery Guide
992
993The current code-owned media capability candidate is OpenRouter. Its volatile
994credential, endpoint, and proxy are leased from ordinary Provider Settings only
995after the explicit media-send approval gate; they never enter this plan or a
996persisted run. Non-secret media model defaults, output directory, and media
997strategy remain configuration-owned, and individual steps must not invent them.