Agent Skills
› opensquilla/opensquilla
› AwesomeWebpageMetaSkill
AwesomeWebpageMetaSkill
GitHub根据用户主题、受众等偏好,构建本地多媒体网页项目。涵盖需求梳理、深度研究、大纲规划、媒体获取与绑定、源码生成及本地验证全流程,最终交付使用指南。
Trigger Scenarios
需要基于特定主题和风格生成包含多媒体内容的本地网页项目
请求自动化处理从调研到媒体素材收集及网页打包的完整工作流
Install
npx skills add opensquilla/opensquilla --skill AwesomeWebpageMetaSkill -g -y
SKILL.md
Frontmatter
{
"kind": "meta",
"name": "AwesomeWebpageMetaSkill",
"always": false,
"config": {
"awesome_webpage": {
"provider": "openrouter",
"openrouter": {
"models": {
"page_generation": "moonshotai\/kimi-k2.6",
"audio_generation": "openai\/gpt-audio-mini",
"image_generation": "google\/gemini-3-pro-image-preview",
"video_generation": "bytedance\/seedance-2.0-fast"
},
"api_key": null,
"base_url": "https:\/\/openrouter.ai\/api\/v1",
"api_key_env": "OPENROUTER_API_KEY"
},
"output_dir": "{{ inputs.workspace_dir }}\/awesome-webpage-output",
"clawhub_skills": {
"filesystem": {
"url": "https:\/\/clawhub.ai\/gtrusler\/clawdbot-filesystem",
"skill": "filesystem"
},
"web_search": {
"url": "https:\/\/clawhub.ai\/billyutw\/web-search",
"skill": "web-search"
},
"audio_generation": {
"url": "https:\/\/clawhub.ai\/skills\/audio-cog",
"skill": "audio-cog",
"opensquilla_compatibility": "openrouter-config-first"
},
"image_generation": {
"url": "https:\/\/clawhub.ai\/skills\/nano-banana-pro-openrouter",
"notes": "The image_aigc step runs this deterministic skill_exec adapter.\nIt does not invoke a prompt-building skill as an agent. No\nGEMINI_API_KEY required; only OPENROUTER_API_KEY.\n",
"skill": "nano-banana-pro-openrouter",
"opensquilla_compatibility": "deterministic-skill-exec"
},
"video_generation": {
"url": "openrouter-configured-video-generation",
"skill": "openrouter-video-generator"
},
"webpage_generation": {
"url": "https:\/\/clawhub.ai\/jhauga\/html-coder",
"notes": "Invoked by the webpage_generation step as the HTML\/CSS\/JS authoring\nskill. The meta-skill task constrains it to source JSON only;\npackaging, validation, repair, and media generation remain separate\nsteps.\n",
"skill": "html-coder",
"opensquilla_compatibility": "scoped-agent"
}
},
"media_strategy": {
"licensing": "prefer_reusable_or_user_supplied",
"aigc_policy": "search_images_direct_generate_audio_video",
"search_first": true,
"target_assets": {
"audio": 1,
"video": 1,
"images": 6
},
"search_modalities": [
"images"
],
"confirmation_steps": [
"ask_images",
"ask_audio",
"ask_video",
"ask_style"
],
"default_modalities": [
"text",
"images",
"audio",
"video"
],
"placeholder_policy": "visible_replacement_slot_when_generation_unavailable",
"allow_remote_embeds": false,
"direct_aigc_modalities": [
"audio",
"video"
],
"aigc_fallback_when_search_empty": true
}
}
},
"metadata": {
"opensquilla": {
"risk": "high",
"requires": {
"config": [
"awesome_webpage.provider",
"awesome_webpage.openrouter.api_key",
"awesome_webpage.openrouter.models.page_generation",
"awesome_webpage.openrouter.models.image_generation",
"awesome_webpage.openrouter.models.audio_generation",
"awesome_webpage.openrouter.models.video_generation",
"awesome_webpage.output_dir",
"awesome_webpage.media_strategy"
]
},
"capabilities": [
"network-read",
"network-write",
"filesystem-read",
"filesystem-write",
"command-exec"
]
}
},
"triggers": [
"create awesome webpage",
"build multimedia webpage project",
"生成图文音视频网页",
"做一个多媒体网页项目",
"AwesomeWebpageMetaSkill"
],
"provenance": {
"origin": "opensquilla-original",
"license": "Apache-2.0",
"maintained_by": "OpenSquilla"
},
"composition": {
"steps": [
{
"id": "requirement_framing",
"kind": "llm_chat",
"with": {
"task": "{{ inputs.language_instruction }}\n\nExtract and normalize the user's webpage request.\n\nRequired fields:\n- topic\n- target_audience\n- output_language\n- visual_style\n- media_preferences: infer the user's requested images, audio, video, remote embeds, and local-only needs\n- configured_output_dir: use the resolved config below; only mark CONFIG_NEEDED if it is empty\n- constraints and risks\n\nDo not invent provider names, model ids, API keys, output paths, or media strategy values.\nThey must come from the `awesome_webpage` config.\nA missing OpenRouter model value means CONFIG_NEEDED for that generator; it does not mean\nthe user does not want that media modality. Do not mark audio or video as unwanted only\nbecause its configured model is null.\n\nResolved awesome_webpage config for this run:\nprovider: openrouter\nopenrouter.api_key_env: OPENROUTER_API_KEY\nopenrouter.models.page_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('page_generation', 'moonshotai\/kimi-k2.6') }}\nopenrouter.models.image_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('image_generation', 'google\/gemini-3-pro-image-preview') }}\nopenrouter.models.audio_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('audio_generation', 'openai\/gpt-audio-mini') }}\nopenrouter.models.video_generation: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('video_generation', 'bytedance\/seedance-2.0-fast') }}\noutput_dir: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '\/awesome-webpage-output') }}\nThis resolved output_dir is configured and must not be reported as CONFIG_NEEDED.\nmedia_strategy.search_first: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_first', true) }}\nmedia_strategy.search_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_modalities', ['images']) | join(', ') }}\nmedia_strategy.direct_aigc_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('direct_aigc_modalities', ['audio', 'video']) | join(', ') }}\nmedia_strategy.aigc_fallback_when_search_empty: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('aigc_fallback_when_search_empty', true) }}\nmedia_strategy.allow_remote_embeds: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('allow_remote_embeds', false) }}\n\nUser request:\n{{ inputs.user_message | xml_escape | truncate(1600) }}\n",
"system": "You are the Requirement Framing stage for AwesomeWebpageMetaSkill.\nProduce a compact, structured brief. Do not call tools.\n"
}
},
{
"id": "project_slug",
"kind": "llm_chat",
"with": {
"task": "Output ONLY a single slug derived from the topic in the framed\nrequirements below. Constraints:\n- lowercase ASCII letters, digits, and hyphens only\n- no spaces, no path separators, no file extension, no quotes\n- max 40 characters\n- topic-derived, so different topics produce different slugs\n\nExamples (do NOT reuse — derive from the actual topic):\n- \"海洋塑料污染\" → ocean-plastic-pollution\n- \"Quantum computing intro\" → quantum-computing-intro\n- \"我们公司年会回顾\" → company-annual-recap\n\nIf you cannot derive a meaningful slug, output `webpage`.\n\nOutput: a single line containing just the slug. No prefix, no\nexplanation, no backticks, no quotes.\n\nFramed requirements:\n{{ outputs.requirement_framing | truncate(1500) }}\n",
"system": "You produce a single filesystem-safe slug identifying this webpage\nproject. Do not call tools.\n"
},
"depends_on": [
"requirement_framing"
]
},
{
"id": "ask_images",
"kind": "user_input",
"clarify": {
"mode": "chat",
"intro": "先逐项确认网页媒体配置,再继续研究、搜索素材和生成项目。\n默认需要图片;后续会先搜索,搜不到合适素材才生成。\n",
"fields": [
{
"name": "include_images",
"type": "enum",
"prompt": "是否需要图片?",
"choices": [
"YES",
"NO"
],
"default": "YES",
"prompt_en": "Do you want images?",
"prompt_zh": "是否需要图片?"
}
],
"intro_en": "I will confirm the webpage media settings before research, asset search,\nand project generation. Images are included by default; I will search\nfirst and generate only when suitable assets are unavailable.\n",
"intro_zh": "先逐项确认网页媒体配置,再继续研究、搜索素材和生成项目。\n默认需要图片;后续会先搜索,搜不到合适素材才生成。\n",
"nl_extract": true,
"timeout_hours": 24,
"cancel_keywords": [
"取消",
"算了",
"cancel",
"stop",
"abort"
]
},
"depends_on": [
"requirement_framing"
]
},
{
"id": "ask_audio",
"kind": "user_input",
"clarify": {
"mode": "chat",
"intro": "已记录图片选择。现在确认音频。\n默认需要音频;音频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。\n",
"fields": [
{
"name": "include_audio",
"type": "enum",
"prompt": "是否需要音频?",
"choices": [
"YES",
"NO"
],
"default": "YES",
"prompt_en": "Do you want audio?",
"prompt_zh": "是否需要音频?"
}
],
"intro_en": "I recorded the image choice. Now I need to confirm audio.\nAudio is included by default; it is generated from the OpenRouter\nconfiguration or represented as a replaceable slot.\n",
"intro_zh": "已记录图片选择。现在确认音频。\n默认需要音频;音频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。\n",
"nl_extract": true,
"timeout_hours": 24,
"cancel_keywords": [
"取消",
"算了",
"cancel",
"stop",
"abort"
]
},
"depends_on": [
"ask_images"
]
},
{
"id": "ask_video",
"kind": "user_input",
"clarify": {
"mode": "chat",
"intro": "已记录音频选择。现在确认视频。\n默认需要视频;视频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。\n",
"fields": [
{
"name": "include_video",
"type": "enum",
"prompt": "是否需要视频?",
"choices": [
"YES",
"NO"
],
"default": "YES",
"prompt_en": "Do you want video?",
"prompt_zh": "是否需要视频?"
}
],
"intro_en": "I recorded the audio choice. Now I need to confirm video.\nVideo is included by default; it is generated from the OpenRouter\nconfiguration or represented as a replaceable slot.\n",
"intro_zh": "已记录音频选择。现在确认视频。\n默认需要视频;视频不走素材搜索,会直接按 OpenRouter 配置生成或给出可替换位置。\n",
"nl_extract": true,
"timeout_hours": 24,
"cancel_keywords": [
"取消",
"算了",
"cancel",
"stop",
"abort"
]
},
"depends_on": [
"ask_audio"
]
},
{
"id": "ask_style",
"kind": "user_input",
"clarify": {
"mode": "chat",
"intro": "已记录视频选择。最后确认网页整体风格,然后开始研究、规划和素材获取。\n",
"fields": [
{
"name": "visual_style",
"type": "string",
"prompt": "网页整体风格是什么?例如:科技感、纪录片风、极简、儿童科普、商业发布会风。",
"max_chars": 500,
"prompt_en": "What overall style should the webpage use? For example: futuristic, documentary, minimal, kids science, or product launch.",
"prompt_zh": "网页整体风格是什么?例如:科技感、纪录片风、极简、儿童科普、商业发布会风。"
}
],
"intro_en": "I recorded the video choice. Last, confirm the overall visual style;\nthen I will start research, planning, and asset collection.\n",
"intro_zh": "已记录视频选择。最后确认网页整体风格,然后开始研究、规划和素材获取。\n",
"nl_extract": true,
"timeout_hours": 24,
"cancel_keywords": [
"取消",
"算了",
"cancel",
"stop",
"abort"
]
},
"depends_on": [
"ask_video"
]
},
{
"id": "deep_research",
"kind": "agent",
"with": {
"question": "Research the topic for a multimedia webpage project.\n\nSingle bounded round only. Produce a short cited brief with 3-5\npage anchors. Do not run multi-round investigation.\n\nRequirement framing:\n{{ outputs.requirement_framing | truncate(3000) }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n\nOriginal request:\n{{ inputs.user_message | xml_escape | truncate(1000) }}\n"
},
"skill": "awesome-webpage-research",
"depends_on": [
"requirement_framing",
"ask_images",
"ask_audio",
"ask_video",
"ask_style"
]
},
{
"id": "page_outline",
"kind": "llm_chat",
"with": {
"task": "{{ inputs.language_instruction }}\n\nOutput the webpage OUTLINE — section structure + media intents\nonly. Downstream media steps read this to decide WHAT to fetch\nor generate; the deterministic `media_assets_collect` step later\nturns producer output into concrete browser-relative asset paths.\n\nRequired sections:\n\n1. Page hierarchy\n - Ordered list of sections: section_id, title, narrative\n purpose, 1-2 sentence summary.\n - Overall narrative arc (one paragraph).\n\n2. Visual style summary (one paragraph)\n\n3. Media intent list — one entry per desired asset, with:\n - slot_id: short kebab-case key, unique within outline\n (e.g. `hero-ocean`, `foodchain-flow`, `narration-intro`).\n - modality: image | audio | video\n - placement: which section_id it belongs to + role\n (e.g. \"hero background\", \"section illustration\",\n \"section narration\").\n - subject: one-sentence concrete description of what the\n asset should depict \/ convey.\n - prompt_hint: 1-2 sentences of stylistic \/ compositional\n guidance for AIGC steps.\n - search_keywords: 3-6 short keywords for image search\n (only meaningful for image slots).\n - load_bearing: true | false. `true` means the section\n structurally relies on this asset (e.g. hero video). If\n the asset later fails to produce, the final media binding\n gate reports it instead of hiding the missing modality.\n `false` means the section degrades gracefully to\n text-only.\n\n HARD: do NOT specify filenames or paths. slot_id is a\n semantic key; the producer step (image_download,\n image_aigc, audio_aigc, video_aigc) decides the on-disk\n filename and the deterministic media binding step performs\n final src\/path checks.\n\n4. Local file plan: project\/index.html, project\/style.css,\n project\/script.js.\n\n5. Accessibility plan: alt-text strategy, ARIA roles,\n keyboard navigation, reduced-motion handling.\n\nHard rules:\n- No filenames. No paths. No `.jpg` \/ `.mp3` \/ `.mp4`.\n- If the user said NO to a modality, do not include any media\n intents of that modality.\n- Include only as many media intents as the section narrative\n actually needs — do not pad to hit a target count.\n- Outline must remain coherent even if every media intent\n fails downstream (text + headings carry the page).\n\nRequirement framing:\n{{ outputs.requirement_framing | truncate(3000) }}\n\nResearch report:\n{{ outputs.deep_research | truncate(6000) }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n",
"system": "You are the Page Outline stage for AwesomeWebpageMetaSkill.\nProduce a structural outline + media intent list — NOT a final\nasset binding. Later steps will normalize media results into a\ncompact manifest and then bind actual files to sections. Do not\ncall tools.\n"
},
"depends_on": [
"requirement_framing",
"project_slug",
"ask_images",
"ask_audio",
"ask_video",
"ask_style",
"deep_research"
]
},
{
"id": "media_slots_normalize",
"kind": "tool_call",
"tool": "exec_command",
"tool_args": {
"stdin": "{\"page_outline\": {{ outputs.page_outline | tojson }},\n \"requirement_framing\": {{ outputs.requirement_framing | tojson }},\n \"include_image\": {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') | tojson }},\n \"include_audio\": {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') | tojson }},\n \"include_video\": {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') | tojson }},\n \"visual_style\": {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') | tojson }}}\n",
"command": "python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_slots_normalize\n",
"timeout": 20
},
"depends_on": [
"page_outline"
],
"tool_allowlist": [
"exec_command"
]
},
{
"id": "media_search",
"kind": "agent",
"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'])",
"with": {
"query": "Search only for reusable webpage image candidates for this plan.\nDo not search for audio or video; those modalities are generated directly\nthrough the configured OpenRouter models when the user requests them.\nPrefer assets that can be used locally or replaced cleanly. Return URLs, titles,\nimage type, likely license\/provenance, and download\/replacement notes.\n\nTool contract for this step:\n- Use the platform-provided `web_search` tool ONLY (provider is configured\n globally; you do not need to know which engine — just call the tool).\n- Do NOT run any local Python script (no `python scripts\/search.py`,\n no `duckduckgo-search`, no `pip install`).\n- Do NOT call `web_fetch`. Snippets from `web_search` are enough.\n- Issue at most 2 `web_search` calls, each using only the supported\n arguments `query` and `max_results=6`; include image intent in the\n query text itself, grouped by visual theme — do not search per asset.\n- Stop as soon as you have ~4-6 usable candidates total.\n- If results are empty, unusable, off-topic, or licensing is unclear,\n stop immediately and return:\n {\"status\":\"NO_USABLE_IMAGE_CANDIDATES\",\"candidates\":[]}\n- Otherwise return one JSON object with:\n candidates[] (title, url, source_domain, likely_license, suggested_alt).\n\nNormalized media slots (authoritative):\n{{ outputs.media_slots_normalize | truncate(3500) }}\n\nPage plan context:\n{{ outputs.page_outline | truncate(1200) }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n",
"format": "json"
},
"skill": "web-search",
"depends_on": [
"media_slots_normalize"
]
},
{
"id": "media_strategy",
"kind": "llm_classify",
"with": {
"text": "Decide whether searched image media is enough or whether image AIGC fallback is required.\n\nFast media policy:\n- Use the confirmed interactive choices as the source of truth.\n- Search is image-only and only runs when config.awesome_webpage.media_strategy.search_first\n is true and `images` is present in search_modalities.\n- When image search is disabled by config, ignore media_search output and choose\n NEEDS_AIGC_IMAGE for requested images so the direct image generator handles them.\n- Audio and video must not be evaluated through web search.\n- If include_images is NO, choose IMAGE_SEARCH_READY.\n- If include_images is YES, evaluate only image candidates from web-search.\n- Choose NEEDS_AIGC_IMAGE when image search is empty, unusable, off-topic,\n not downloadable\/localizable, or has unclear licensing.\n- Choose IMAGE_SEARCH_READY only when the search results provide enough usable\n local\/downloadable image assets for the requested page.\n- Audio and video are direct AIGC modalities. They are handled by audio_aigc\n and video_aigc based on user confirmation and OpenRouter config, not by this classifier.\n\nRules:\n- IMAGE_SEARCH_READY: image search results are sufficient, or images were not requested.\n- NEEDS_AIGC_IMAGE: image generation is needed.\n\nIf search is empty and config.awesome_webpage.media_strategy.aigc_fallback_when_search_empty is true,\nchoose NEEDS_AIGC_IMAGE. Do not choose a model here; model choice belongs to config.\n\nResolved media strategy:\nsearch_first: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_first', true) }}\nsearch_modalities: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('search_modalities', ['images']) | join(', ') }}\naigc_fallback_when_search_empty: {{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('aigc_fallback_when_search_empty', true) }}\n\nRequirement framing:\n{{ outputs.requirement_framing | truncate(2000) }}\n\nPage plan:\n{{ outputs.page_outline | truncate(2500) }}\n\nNormalized media slots:\n{{ outputs.media_slots_normalize | truncate(2000) }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n\nPrimary search:\n{{ outputs.media_search | truncate(3500) }}\n"
},
"depends_on": [
"media_search",
"media_slots_normalize"
],
"output_choices": [
"IMAGE_SEARCH_READY",
"NEEDS_AIGC_IMAGE"
]
},
{
"id": "image_download",
"kind": "skill_exec",
"when": "inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') == 'YES' and outputs.media_strategy == 'IMAGE_SEARCH_READY'",
"with": {
"payload": "{\"media_slots\": {{ outputs.media_slots_normalize | tojson }},\n \"media_search\": {{ outputs.media_search | tojson }}}\n",
"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",
"local_path_prefix": "project\/assets\/images"
},
"skill": "awesome-webpage-image-download",
"depends_on": [
"media_strategy",
"media_slots_normalize"
]
},
{
"id": "image_aigc",
"kind": "skill_exec",
"when": "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', '')))",
"with": {
"model": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('image_generation', 'google\/gemini-3-pro-image-preview') }}",
"api_key": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key', '') }}",
"payload": "{\n \"requirement_framing\": {{ outputs.requirement_framing | tojson }},\n \"media_slots\": {{ outputs.media_slots_normalize | tojson }},\n \"page_outline\": {{ outputs.page_outline | tojson }},\n \"image_download\": {{ outputs.get('image_download', '') | tojson }},\n \"include_images\": {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') | tojson }},\n \"visual_style\": {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') | tojson }}\n}\n",
"base_url": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('base_url', 'https:\/\/openrouter.ai\/api\/v1') }}",
"filename": "{{ outputs.project_slug | slugify }}.png",
"max_images": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('media_strategy', {}).get('target_assets', {}).get('images', 6) }}",
"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",
"resolution": "1K",
"api_key_env": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key_env', 'OPENROUTER_API_KEY') }}",
"local_path_prefix": "project\/assets\/images"
},
"skill": "nano-banana-pro-openrouter",
"depends_on": [
"media_strategy",
"image_download",
"media_slots_normalize"
]
},
{
"id": "audio_aigc",
"kind": "skill_exec",
"when": "inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') == 'YES'",
"with": {
"model": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('audio_generation', 'openai\/gpt-audio-mini') }}",
"voice": "cedar",
"api_key": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key', '') }}",
"payload": "{\n \"script\": {{ outputs.audio_script | tojson }},\n \"requirement_framing\": {{ outputs.requirement_framing | tojson }},\n \"media_slots\": {{ outputs.media_slots_normalize | tojson }}\n}\n",
"base_url": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('base_url', 'https:\/\/openrouter.ai\/api\/v1') }}",
"filename": "{{ outputs.project_slug | slugify }}-narration.wav",
"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",
"api_key_env": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key_env', 'OPENROUTER_API_KEY') }}"
},
"skill": "audio-cog",
"depends_on": [
"audio_script"
]
},
{
"id": "audio_script",
"kind": "llm_chat",
"when": "inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') == 'YES'",
"with": {
"task": "{{ inputs.language_instruction }}\n\nWrite the exact narration script that the audio model should speak.\nOutput spoken text only. No title, no markdown, no filename, no JSON,\nno labels, no stage directions, no \"我明白了\", no \"接下来\", no\n\"我将为你生成\", no assistant-style acknowledgement.\n\nRequirements:\n- Make it a polished webpage narration or guide, not a response to\n the user.\n- 45-75 seconds when spoken.\n- Match the requested language and style.\n- Use the audio slot placement\/subject from media slots when present.\n- It must stand alone as content a visitor hears on the page.\n\nRequirement framing:\n{{ outputs.requirement_framing | truncate(1800) }}\n\nPage outline:\n{{ outputs.page_outline | truncate(2600) }}\n\nNormalized media slots:\n{{ outputs.media_slots_normalize | truncate(2200) }}\n",
"system": "You write only final spoken narration text for webpage audio.\nDo not call tools. Do not acknowledge the request.\n"
},
"depends_on": [
"requirement_framing",
"page_outline",
"media_slots_normalize"
]
},
{
"id": "video_aigc",
"kind": "skill_exec",
"when": "inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') == 'YES'",
"with": {
"model": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('models', {}).get('video_generation', 'bytedance\/seedance-2.0-fast') }}",
"prompt": "Generate the short video asset requested by the page outline. Do not\nrun web search for video assets.\n\nRequirement framing:\n{{ outputs.requirement_framing | truncate(1800) }}\n\nPage plan:\n{{ outputs.page_outline | truncate(3000) }}\n\nConfirmed interactive choices:\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n",
"api_key": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key', '') }}",
"base_url": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('base_url', 'https:\/\/openrouter.ai\/api\/v1') }}",
"duration": "10",
"filename": "{{ outputs.project_slug | slugify }}-intro.mp4",
"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",
"api_key_env": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('openrouter', {}).get('api_key_env', 'OPENROUTER_API_KEY') }}",
"aspect_ratio": "16:9"
},
"skill": "openrouter-video-generator",
"depends_on": [
"page_outline"
]
},
{
"id": "media_assets_collect",
"kind": "tool_call",
"tool": "exec_command",
"tool_args": {
"env": {
"AUDIO_AIGC": "{{ outputs.get('audio_aigc', '') }}",
"IMAGE_AIGC": "{{ outputs.get('image_aigc', '') }}",
"VIDEO_AIGC": "{{ outputs.get('video_aigc', '') }}",
"PROJECT_ROOT": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '\/awesome-webpage-output') }}\/{{ outputs.project_slug | slugify }}\/project",
"IMAGE_DOWNLOAD": "{{ outputs.get('image_download', '') }}"
},
"command": "python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_assets_collect\n",
"timeout": 30
},
"depends_on": [
"image_download",
"image_aigc",
"audio_aigc",
"video_aigc"
],
"tool_allowlist": [
"exec_command"
]
},
{
"id": "webpage_generation",
"kind": "agent",
"with": {
"mode": "generate",
"task": "{{ inputs.language_instruction }}\n\nYou are the Webpage Source Authoring stage for AwesomeWebpageMetaSkill.\nProduce source text only. Do not call tools and do not write files.\nApply a professional HTML\/CSS quality standard: clear visual\nhierarchy, semantic HTML, restrained but distinctive typography and\ncolor, accessible media controls, responsive layout, and intentional\ncomposition rather than a thin placeholder page.\n\nGenerate a complete local webpage as strict JSON with exactly these keys:\n- `index_html`\n- `style_css`\n- `script_js`\n- `summary`\n\nOutput JSON only. Do not wrap it in Markdown fences. Ignore\nhtml-coder's default Markdown\/code-block output format for this\ninvocation; the final answer must be the strict JSON object only.\n\nScope:\n- Author only the contents for project\/index.html, project\/style.css,\n and project\/script.js.\n- Do not download, search, generate, copy, move, delete, package, validate, repair, normalize paths, or clean up assets.\n- Do not mention or choose filesystem paths. A deterministic later\n step writes the files to the exact project root.\n- Use only browser-relative `assets\/...` paths listed in\n `media_assets_collect.assets[]`.\n- Include available image, audio, and video assets in the authored\n page. A deterministic later step patches any asset the model misses.\n- Do not show \"pending\", \"to be added\", or \"replace this asset\"\n placeholders for requested media.\n- Place audio controls according to the audio slot placement in\n `media_slots_normalize.slots[]`; for narration\/guide audio, put\n the player near the intro\/hero or the referenced section, never as\n footer-only\/end-of-page content unless the slot explicitly says so.\n\nDesign-quality contract, adapted from html-coder\n(https:\/\/clawhub.ai\/jhauga\/html-coder):\n- Build a real, production-quality webpage, not a short landing-page\n stub. Represent the page outline in semantic\n HTML (`header`, `main`, `section`, `figure`, `footer`).\n- Use strong visual hierarchy, clear grid alignment, intentional\n whitespace, readable line lengths, and a balanced 2-3 color system.\n Avoid a one-note dark\/slate\/blue page unless the requested style\n specifically requires it.\n- Include accessible local media: `<audio controls>` for any\n audio asset, `<video>` for any video asset, and `<img>` with\n meaningful alt text\/captions for every image asset.\n- If `media_assets_collect` contains extra image assets, render a gallery,\n evidence wall, or visual sequence section so generated assets do\n not sit unused on disk.\n- Add keyboard-accessible controls where interaction exists, visible\n focus states, reduced-motion handling, and mobile layouts without\n overlapping text.\n- Do not show \"replace this asset\" placeholders when a real asset is\n present in `media_assets_collect`.\n\nMedia assets:\nThe authoritative asset facts are `media_assets_collect` below.\nUse only `media_assets_collect.assets[].src` values in generated\nHTML\/CSS\/JS. Those values are already `assets\/...` browser paths.\nDo not scan raw producer output and do not use raw\n`project\/assets\/...` local_path values as browser src paths.\n\nPage outline (narrative + section structure):\n{{ outputs.page_outline | truncate(1500) }}\n\nNormalized media slots (placement\/role contract):\n{{ outputs.media_slots_normalize | truncate(3500) }}\n\nMedia strategy:\n{{ outputs.media_strategy }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n\nMedia assets:\n{{ outputs.media_assets_collect | truncate(6000) }}\n"
},
"skill": "html-coder",
"depends_on": [
"requirement_framing",
"deep_research",
"page_outline",
"media_slots_normalize",
"media_assets_collect"
]
},
{
"id": "webpage_source_validate",
"kind": "tool_call",
"tool": "exec_command",
"tool_args": {
"stdin": "{{ outputs.webpage_generation | tojson }}",
"command": "python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.webpage_source_validate\n",
"timeout": 15
},
"depends_on": [
"webpage_generation"
],
"tool_allowlist": [
"exec_command"
]
},
{
"id": "webpage_generation_retry",
"kind": "llm_chat",
"when": "not outputs.get('webpage_generation', '').strip() or 'WEBPAGE_SOURCE_INVALID:' in outputs.get('webpage_source_validate', '')",
"with": {
"task": "{{ inputs.language_instruction }}\n\nGenerate strict JSON with exactly these keys:\n- `index_html`\n- `style_css`\n- `script_js`\n- `summary`\n\nOutput JSON only. Do not wrap it in Markdown fences.\n\nScope:\n- Author only project\/index.html, project\/style.css, and\n project\/script.js contents.\n- Do not call tools, write files, download assets, search, generate\n media, package, validate, repair, or normalize paths.\n- Use only `assets\/...` browser paths already present in\n media_assets_collect.assets[].src.\n- Do not invent pending audio\/video controls or \"to be added\"\n placeholders.\n- Place audio controls near the audio slot placement from\n media_slots_normalize, not as footer-only\/end-of-page content\n unless the slot explicitly says footer.\n- Keep the page production-quality: semantic sections, accessible\n local media, responsive layout, meaningful hierarchy, and no\n overlapping mobile text.\n\nPage outline:\n{{ outputs.page_outline | truncate(900) }}\n\nNormalized media slots:\n{{ outputs.media_slots_normalize | truncate(2200) }}\n\nMedia assets:\n{{ outputs.media_assets_collect | truncate(3500) }}\n\nConfirmed interactive choices:\nimages: {{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}\naudio: {{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}\nvideo: {{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}\nvisual_style: {{ inputs.get('collected', {}).get('ask_style', {}).get('visual_style', '') }}\n",
"system": "You are the fallback Webpage Source Authoring stage for\nAwesomeWebpageMetaSkill. The primary source authoring step returned\nempty or invalid output. Produce compact source JSON only.\n"
},
"depends_on": [
"webpage_generation",
"webpage_source_validate",
"media_slots_normalize"
]
},
{
"id": "webpage_write",
"kind": "tool_call",
"tool": "exec_command",
"tool_args": {
"env": {
"PROJECT_ROOT": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '\/awesome-webpage-output') }}\/{{ outputs.project_slug | slugify }}",
"WORKSPACE_DIR": "{{ inputs.workspace_dir }}"
},
"stdin": "{{ (outputs.get('webpage_generation_retry', '') or outputs.webpage_generation) | tojson }}",
"command": "python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.webpage_write\n",
"timeout": 30
},
"depends_on": [
"webpage_generation",
"webpage_source_validate",
"webpage_generation_retry"
],
"tool_allowlist": [
"exec_command"
]
},
{
"id": "media_bind_validate",
"kind": "tool_call",
"tool": "exec_command",
"tool_args": {
"env": {
"AUDIO_AIGC": "{{ outputs.get('audio_aigc', '') }}",
"IMAGE_AIGC": "{{ outputs.get('image_aigc', '') }}",
"VIDEO_AIGC": "{{ outputs.get('video_aigc', '') }}",
"PROJECT_ROOT": "{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '\/awesome-webpage-output') }}\/{{ outputs.project_slug | slugify }}",
"INCLUDE_AUDIO": "{{ inputs.get('collected', {}).get('ask_audio', {}).get('include_audio', 'YES') }}",
"INCLUDE_IMAGE": "{{ inputs.get('collected', {}).get('ask_images', {}).get('include_images', 'YES') }}",
"INCLUDE_VIDEO": "{{ inputs.get('collected', {}).get('ask_video', {}).get('include_video', 'YES') }}",
"IMAGE_DOWNLOAD": "{{ outputs.get('image_download', '') }}"
},
"command": "python -m opensquilla.skills.bundled.AwesomeWebpageMetaSkill.scripts.media_bind_validate\n",
"timeout": 30
},
"depends_on": [
"webpage_write",
"media_assets_collect"
],
"tool_allowlist": [
"exec_command"
]
},
{
"id": "quick_validate",
"kind": "agent",
"with": {
"task": "Quick path-level sanity pass over the generated local webpage project.\n\nFixed ClawHub skill URL: https:\/\/clawhub.ai\/gtrusler\/clawdbot-filesystem\nUse only the per-project root:\n{{ inputs.get('config', {}).get('awesome_webpage', {}).get('output_dir') or (inputs.workspace_dir ~ '\/awesome-webpage-output') }}\/{{ outputs.project_slug | slugify }}\nDo not choose a new output directory and do not install another filesystem skill.\n\nExpected tree:\n- project\/index.html\n- project\/style.css\n- project\/script.js\n- project\/assets\/images\n- project\/assets\/audio\n- project\/assets\/video\n\nAllowed operations (path-level only, no content reads):\n- `test -f` each of the three authored files; report MISSING_FILE\n with the specific path for any missing one.\n- `ls` the three asset directories; missing dirs are warnings, not\n failures. `mkdir -p` to create any missing asset directory.\n- Normalize relative links at the path level only — do not open\n files to check link targets.\n\nHard prohibitions:\n- Do NOT run `cat`, `head`, `tail`, `sed`, `grep`, `wc`, `diff`,\n or any content-inspection command on index.html, style.css, or\n script.js. Trust the webpage_generation output as the source of\n truth.\n- Do not regenerate, rewrite, or patch authored file content. If a\n file is missing, report it; do not synthesize a replacement.\n- Skip any packaging step (zip\/tar) — the project tree is the\n deliverable.\n\nOutput: a 1-paragraph summary stating either VALIDATED (all three\nauthored files exist and asset directories are present) or one of\nMISSING_FILE \/ MISSING_ASSETS with the specific path. Stop within\nthree shell commands.\n\nWebpage write output:\n{{ outputs.webpage_write | truncate(2500) }}\n\nMedia bind\/validate output:\n{{ outputs.media_bind_validate | truncate(3500) }}\n"
},
"skill": "filesystem",
"depends_on": [
"media_bind_validate"
]
},
{
"id": "delivery_guide",
"kind": "llm_chat",
"with": {
"task": "{{ inputs.language_instruction }}\n\nWrite a delivery guide containing:\n- 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\n- how to open project\/index.html locally\n- how to replace images, audio, and video assets\n- how to edit content in index.html, style.css, and script.js\n- what was validated, including the deterministic media bind gate\n- any CONFIG_NEEDED items if config values were missing\n- do not claim a requested media modality is available unless the\n media bind report says it is present and referenced\n\nValidation report:\n{{ outputs.quick_validate | truncate(5000) }}\n\nMedia bind report:\n{{ outputs.media_bind_validate | truncate(5000) }}",
"system": "You are the Delivery Guide stage for AwesomeWebpageMetaSkill.\nProduce concise user-facing run and edit instructions.\n"
},
"depends_on": [
"quick_validate"
]
}
]
},
"description": "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.",
"meta_priority": 65,
"final_text_mode": "step:delivery_guide",
"request_template": {
"fields": [
{
"name": "topic",
"label": "Topic",
"label_en": "Topic",
"label_zh": "网页主题"
},
{
"name": "target_audience",
"label": "Target audience",
"label_en": "Target audience",
"label_zh": "目标受众"
},
{
"name": "output_language",
"label": "Output language",
"label_en": "Output language",
"label_zh": "输出语言"
},
{
"name": "visual_style",
"label": "Visual style",
"label_en": "Visual style",
"label_zh": "视觉风格"
},
{
"name": "media_preferences",
"label": "Media preferences",
"label_en": "Media preferences",
"label_zh": "媒体偏好"
}
],
"outcome": "A packaged local multimedia webpage project with assets, validation notes, and usage guidance.",
"outcome_en": "A packaged local multimedia webpage project with assets, validation notes, and usage guidance.",
"outcome_zh": "打包好的本地多媒体网页项目,包含素材、验证说明和使用指南。"
}
}
AwesomeWebpageMetaSkill
Build a local multimedia webpage project from a topic, audience, language, style, and media preference request.
Pipeline:
- Requirement Framing
- Deep Research
- Page Outline (section structure + media intents; no filenames)
- Media Acquisition (search / AIGC; producers emit
*_READY:lines) - Media Assets Collect (deterministic path + existence check)
- Webpage Source Generation
- Deterministic Webpage Write
- Deterministic Media Bind + Validation
- Local Validation
- Delivery Guide
Provider, OpenRouter model, API key, output directory, and media strategy are configuration-owned. The meta-skill may pass the config contract through the DAG, but individual steps must not invent those values.
Version History
- 7f72a32 Current 2026-07-05 18:37


