feat(skill): add script-driven full skill template generation

Add fetch-skill-templates.ts script that clones anthropics/skills repo
and generates complete directory trees (scripts, references, assets)
for all 16 skills with base64 encoding for binary files, replacing
the previous single-SKILL.md-only approach. Generated files are
lazy-loaded per skill on user click.
This commit is contained in:
yyh
2026-01-30 14:57:04 +08:00
parent acc8671c28
commit 038b03fa8e
24 changed files with 2317 additions and 40 deletions

View File

@ -1,15 +1,74 @@
'use client'
import { memo, useState } from 'react'
import type { SkillTemplateSummary } from './templates/types'
import { memo, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useBatchUpload } from '@/service/use-app-asset'
import { useSkillTreeUpdateEmitter } from '../hooks/use-skill-tree-collaboration'
import CategoryTabs from './category-tabs'
import SectionHeader from './section-header'
import TemplateCard from './template-card'
import TemplateSearch from './template-search'
import { SKILL_TEMPLATES } from './templates/registry'
import { buildUploadDataFromTemplate } from './templates/template-to-upload'
const SkillTemplatesSection = () => {
const { t } = useTranslation('workflow')
const [activeCategory, setActiveCategory] = useState('all')
const [searchValue, setSearchValue] = useState('')
const [loadingId, setLoadingId] = useState<string | null>(null)
const appDetail = useAppStore(s => s.appDetail)
const appId = appDetail?.id || ''
const storeApi = useWorkflowStore()
const batchUpload = useBatchUpload()
const emitTreeUpdate = useSkillTreeUpdateEmitter()
const handleUse = useCallback(async (summary: SkillTemplateSummary) => {
const entry = SKILL_TEMPLATES.find(e => e.id === summary.id)
if (!entry || !appId)
return
setLoadingId(summary.id)
storeApi.getState().setUploadStatus('uploading')
storeApi.getState().setUploadProgress({ uploaded: 0, total: 1, failed: 0 })
try {
const children = await entry.loadContent()
const uploadData = await buildUploadDataFromTemplate(summary.name, children)
await batchUpload.mutateAsync({
appId,
tree: uploadData.tree,
files: uploadData.files,
parentId: null,
onProgress: (uploaded, total) => {
storeApi.getState().setUploadProgress({ uploaded, total, failed: 0 })
},
})
storeApi.getState().setUploadStatus('success')
emitTreeUpdate()
}
catch {
storeApi.getState().setUploadStatus('partial_error')
}
finally {
setLoadingId(null)
}
}, [appId, batchUpload, storeApi, emitTreeUpdate])
const filtered = SKILL_TEMPLATES.filter((entry) => {
if (searchValue) {
const q = searchValue.toLowerCase()
return entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q)
}
if (activeCategory !== 'all')
return entry.tags?.some(tag => tag.toLowerCase() === activeCategory.toLowerCase())
return true
})
return (
<section className="flex flex-col gap-3 px-6 py-2">
@ -27,11 +86,18 @@ const SkillTemplatesSection = () => {
onChange={setSearchValue}
/>
</div>
<div className="flex min-h-[200px] items-center justify-center rounded-xl border border-dashed border-divider-regular bg-background-section-burn">
<span className="system-sm-regular text-text-quaternary">
{t('skill.startTab.templatesComingSoon')}
</span>
<div className="grid grid-cols-3 gap-3">
{filtered.map(entry => (
<TemplateCard
key={entry.id}
template={entry}
onUse={handleUse}
/>
))}
</div>
{loadingId && (
<div className="pointer-events-none fixed inset-0 z-50" />
)}
</section>
)
}

View File

@ -1,29 +1,20 @@
'use client'
import type { SkillTemplateNode, SkillTemplateWithMetadata } from './templates/types'
import type { SkillTemplateSummary } from './templates/types'
import { RiAddLine } from '@remixicon/react'
import { memo, useMemo } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import Badge from '@/app/components/base/badge'
import Button from '@/app/components/base/button'
function countFiles(nodes: SkillTemplateNode[]): number {
return nodes.reduce((count, node) => {
if (node.node_type === 'file')
return count + 1
return count + countFiles(node.children)
}, 0)
}
type TemplateCardProps = {
template: SkillTemplateWithMetadata
onUse: (template: SkillTemplateWithMetadata) => void
template: SkillTemplateSummary
onUse: (template: SkillTemplateSummary) => void
}
const TemplateCard = ({ template, onUse }: TemplateCardProps) => {
const { t } = useTranslation('workflow')
const fileCount = useMemo(() => countFiles(template.children), [template.children])
return (
<div className="group flex h-full flex-col overflow-hidden rounded-xl border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg transition-colors hover:bg-components-panel-on-panel-item-bg-hover">
@ -38,7 +29,7 @@ const TemplateCard = ({ template, onUse }: TemplateCardProps) => {
{template.name}
</span>
<span className="system-xs-regular text-text-tertiary">
{t('skill.startTab.filesIncluded', { count: fileCount })}
{t('skill.startTab.filesIncluded', { count: template.fileCount })}
</span>
</div>
</div>

View File

@ -0,0 +1,150 @@
// AUTO-GENERATED — DO NOT EDIT
// Source: https://github.com/anthropics/skills
import type { SkillTemplateEntry } from './types'
export const SKILL_TEMPLATES: SkillTemplateEntry[] = [
{
id: 'algorithmic-art',
name: 'Algorithmic Art',
description: 'Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists\' work to avoid copyright violations.',
fileCount: 3,
icon: '✨',
tags: ['Creative', 'Development'],
loadContent: () => import('./skills/algorithmic-art').then(m => m.default),
},
{
id: 'brand-guidelines',
name: 'Brand Guidelines',
description: 'Applies Anthropic\'s official brand colors and typography to any sort of artifact that may benefit from having Anthropic\'s look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.',
fileCount: 1,
icon: '🏷️',
tags: ['Design', 'Productivity'],
loadContent: () => import('./skills/brand-guidelines').then(m => m.default),
},
{
id: 'canvas-design',
name: 'Canvas Design',
description: 'Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists\' work to avoid copyright violations.',
fileCount: 82,
icon: '🖼️',
tags: ['Design', 'Creative'],
loadContent: () => import('./skills/canvas-design').then(m => m.default),
},
{
id: 'doc-coauthoring',
name: 'Doc Co-authoring',
description: 'Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.',
fileCount: 1,
icon: '📋',
tags: ['Productivity'],
loadContent: () => import('./skills/doc-coauthoring').then(m => m.default),
},
{
id: 'docx',
name: 'DOCX',
description: 'Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks',
fileCount: 58,
icon: '📝',
tags: ['Document', 'Productivity'],
loadContent: () => import('./skills/docx').then(m => m.default),
},
{
id: 'frontend-design',
name: 'Frontend Design',
description: 'Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.',
fileCount: 1,
icon: '🎨',
tags: ['Development', 'Design'],
loadContent: () => import('./skills/frontend-design').then(m => m.default),
},
{
id: 'internal-comms',
name: 'Internal Comms',
description: 'A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).',
fileCount: 5,
icon: '💬',
tags: ['Productivity'],
loadContent: () => import('./skills/internal-comms').then(m => m.default),
},
{
id: 'mcp-builder',
name: 'MCP Builder',
description: 'Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).',
fileCount: 9,
icon: '🔌',
tags: ['Development'],
loadContent: () => import('./skills/mcp-builder').then(m => m.default),
},
{
id: 'pdf',
name: 'PDF',
description: 'Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.',
fileCount: 11,
icon: '📄',
tags: ['Document', 'Productivity'],
loadContent: () => import('./skills/pdf').then(m => m.default),
},
{
id: 'pptx',
name: 'PPTX',
description: 'Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks',
fileCount: 55,
icon: '📊',
tags: ['Document', 'Productivity'],
loadContent: () => import('./skills/pptx').then(m => m.default),
},
{
id: 'skill-creator',
name: 'Skill Creator',
description: 'Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude\'s capabilities with specialized knowledge, workflows, or tool integrations.',
fileCount: 6,
icon: '🛠️',
tags: ['Development'],
loadContent: () => import('./skills/skill-creator').then(m => m.default),
},
{
id: 'slack-gif-creator',
name: 'Slack GIF Creator',
description: 'Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack.',
fileCount: 6,
icon: '🎬',
tags: ['Creative', 'Productivity'],
loadContent: () => import('./skills/slack-gif-creator').then(m => m.default),
},
{
id: 'theme-factory',
name: 'Theme Factory',
description: 'Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.',
fileCount: 12,
icon: '🎭',
tags: ['Design'],
loadContent: () => import('./skills/theme-factory').then(m => m.default),
},
{
id: 'web-artifacts-builder',
name: 'Web Artifacts Builder',
description: 'Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.',
fileCount: 4,
icon: '🌐',
tags: ['Development', 'Design'],
loadContent: () => import('./skills/web-artifacts-builder').then(m => m.default),
},
{
id: 'webapp-testing',
name: 'Webapp Testing',
description: 'Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.',
fileCount: 5,
icon: '🧪',
tags: ['Development', 'Testing'],
loadContent: () => import('./skills/webapp-testing').then(m => m.default),
},
{
id: 'xlsx',
name: 'XLSX',
description: 'Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas',
fileCount: 2,
icon: '📈',
tags: ['Document', 'Analysis'],
loadContent: () => import('./skills/xlsx').then(m => m.default),
},
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
// AUTO-GENERATED — DO NOT EDIT
// Source: https://github.com/anthropics/skills
import type { SkillTemplateNode } from '../types'
const children: SkillTemplateNode[] = [
{
"name": "SKILL.md",
"node_type": "file",
"content": "---\nname: brand-guidelines\ndescription: Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.\nlicense: Complete terms in LICENSE.txt\n---\n\n# Anthropic Brand Styling\n\n## Overview\n\nTo access Anthropic's official brand identity and style resources, use this skill.\n\n**Keywords**: branding, corporate identity, visual identity, post-processing, styling, brand colors, typography, Anthropic brand, visual formatting, visual design\n\n## Brand Guidelines\n\n### Colors\n\n**Main Colors:**\n\n- Dark: `#141413` - Primary text and dark backgrounds\n- Light: `#faf9f5` - Light backgrounds and text on dark\n- Mid Gray: `#b0aea5` - Secondary elements\n- Light Gray: `#e8e6dc` - Subtle backgrounds\n\n**Accent Colors:**\n\n- Orange: `#d97757` - Primary accent\n- Blue: `#6a9bcc` - Secondary accent\n- Green: `#788c5d` - Tertiary accent\n\n### Typography\n\n- **Headings**: Poppins (with Arial fallback)\n- **Body Text**: Lora (with Georgia fallback)\n- **Note**: Fonts should be pre-installed in your environment for best results\n\n## Features\n\n### Smart Font Application\n\n- Applies Poppins font to headings (24pt and larger)\n- Applies Lora font to body text\n- Automatically falls back to Arial/Georgia if custom fonts unavailable\n- Preserves readability across all systems\n\n### Text Styling\n\n- Headings (24pt+): Poppins font\n- Body text: Lora font\n- Smart color selection based on background\n- Preserves text hierarchy and formatting\n\n### Shape and Accent Colors\n\n- Non-text shapes use accent colors\n- Cycles through orange, blue, and green accents\n- Maintains visual interest while staying on-brand\n\n## Technical Details\n\n### Font Management\n\n- Uses system-installed Poppins and Lora fonts when available\n- Provides automatic fallback to Arial (headings) and Georgia (body)\n- No font installation required - works with existing system fonts\n- For best results, pre-install Poppins and Lora fonts in your environment\n\n### Color Application\n\n- Uses RGB color values for precise brand matching\n- Applied via python-pptx's RGBColor class\n- Maintains color fidelity across different systems\n"
}
]
export default children

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
// AUTO-GENERATED — DO NOT EDIT
// Source: https://github.com/anthropics/skills
import type { SkillTemplateNode } from '../types'
const children: SkillTemplateNode[] = [
{
"name": "SKILL.md",
"node_type": "file",
"content": "---\nname: frontend-design\ndescription: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.\nlicense: Complete terms in LICENSE.txt\n---\n\nThis skill guides creation of distinctive, production-grade frontend interfaces that avoid generic \"AI slop\" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.\n\nThe user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.\n\n## Design Thinking\n\nBefore coding, understand the context and commit to a BOLD aesthetic direction:\n- **Purpose**: What problem does this interface solve? Who uses it?\n- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.\n- **Constraints**: Technical requirements (framework, performance, accessibility).\n- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?\n\n**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.\n\nThen implement working code (HTML/CSS/JS, React, Vue, etc.) that is:\n- Production-grade and functional\n- Visually striking and memorable\n- Cohesive with a clear aesthetic point-of-view\n- Meticulously refined in every detail\n\n## Frontend Aesthetics Guidelines\n\nFocus on:\n- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.\n- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.\n- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.\n- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.\n- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.\n\nNEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.\n\nInterpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.\n\n**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.\n\nRemember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.\n"
}
]
export default children

View File

@ -0,0 +1,39 @@
// AUTO-GENERATED — DO NOT EDIT
// Source: https://github.com/anthropics/skills
import type { SkillTemplateNode } from '../types'
const children: SkillTemplateNode[] = [
{
"name": "SKILL.md",
"node_type": "file",
"content": "---\nname: internal-comms\ndescription: A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).\nlicense: Complete terms in LICENSE.txt\n---\n\n## When to use this skill\nTo write internal communications, use this skill for:\n- 3P updates (Progress, Plans, Problems)\n- Company newsletters\n- FAQ responses\n- Status reports\n- Leadership updates\n- Project updates\n- Incident reports\n\n## How to use this skill\n\nTo write any internal communication:\n\n1. **Identify the communication type** from the request\n2. **Load the appropriate guideline file** from the `examples/` directory:\n - `examples/3p-updates.md` - For Progress/Plans/Problems team updates\n - `examples/company-newsletter.md` - For company-wide newsletters\n - `examples/faq-answers.md` - For answering frequently asked questions\n - `examples/general-comms.md` - For anything else that doesn't explicitly match one of the above\n3. **Follow the specific instructions** in that file for formatting, tone, and content gathering\n\nIf the communication type doesn't match any existing guideline, ask for clarification or more context about the desired format.\n\n## Keywords\n3P updates, company newsletter, company comms, weekly update, faqs, common questions, updates, internal comms\n"
},
{
"name": "examples",
"node_type": "folder",
"children": [
{
"name": "3p-updates.md",
"node_type": "file",
"content": "## Instructions\nYou are being asked to write a 3P update. 3P updates stand for \"Progress, Plans, Problems.\" The main audience is for executives, leadership, other teammates, etc. They're meant to be very succinct and to-the-point: think something you can read in 30-60sec or less. They're also for people with some, but not a lot of context on what the team does.\n\n3Ps can cover a team of any size, ranging all the way up to the entire company. The bigger the team, the less granular the tasks should be. For example, \"mobile team\" might have \"shipped feature\" or \"fixed bugs,\" whereas the company might have really meaty 3Ps, like \"hired 20 new people\" or \"closed 10 new deals.\" \n\nThey represent the work of the team across a time period, almost always one week. They include three sections:\n1) Progress: what the team has accomplished over the next time period. Focus mainly on things shipped, milestones achieved, tasks created, etc.\n2) Plans: what the team plans to do over the next time period. Focus on what things are top-of-mind, really high priority, etc. for the team.\n3) Problems: anything that is slowing the team down. This could be things like too few people, bugs or blockers that are preventing the team from moving forward, some deal that fell through, etc.\n\nBefore writing them, make sure that you know the team name. If it's not specified, you can ask explicitly what the team name you're writing for is.\n\n\n## Tools Available\nWhenever possible, try to pull from available sources to get the information you need:\n- Slack: posts from team members with their updates - ideally look for posts in large channels with lots of reactions\n- Google Drive: docs written from critical team members with lots of views\n- Email: emails with lots of responses of lots of content that seems relevant\n- Calendar: non-recurring meetings that have a lot of importance, like product reviews, etc.\n\n\nTry to gather as much context as you can, focusing on the things that covered the time period you're writing for:\n- Progress: anything between a week ago and today\n- Plans: anything from today to the next week\n- Problems: anything between a week ago and today\n\n\nIf you don't have access, you can ask the user for things they want to cover. They might also include these things to you directly, in which case you're mostly just formatting for this particular format.\n\n## Workflow\n\n1. **Clarify scope**: Confirm the team name and time period (usually past week for Progress/Problems, next\nweek for Plans)\n2. **Gather information**: Use available tools or ask the user directly\n3. **Draft the update**: Follow the strict formatting guidelines\n4. **Review**: Ensure it's concise (30-60 seconds to read) and data-driven\n\n## Formatting\n\nThe format is always the same, very strict formatting. Never use any formatting other than this. Pick an emoji that is fun and captures the vibe of the team and update.\n\n[pick an emoji] [Team Name] (Dates Covered, usually a week)\nProgress: [1-3 sentences of content]\nPlans: [1-3 sentences of content]\nProblems: [1-3 sentences of content]\n\nEach section should be no more than 1-3 sentences: clear, to the point. It should be data-driven, and generally include metrics where possible. The tone should be very matter-of-fact, not super prose-heavy."
},
{
"name": "company-newsletter.md",
"node_type": "file",
"content": "## Instructions\nYou are being asked to write a company-wide newsletter update. You are meant to summarize the past week/month of a company in the form of a newsletter that the entire company will read. It should be maybe ~20-25 bullet points long. It will be sent via Slack and email, so make it consumable for that.\n\nIdeally it includes the following attributes:\n- Lots of links: pulling documents from Google Drive that are very relevant, linking to prominent Slack messages in announce channels and from executives, perhgaps referencing emails that went company-wide, highlighting significant things that have happened in the company.\n- Short and to-the-point: each bullet should probably be no longer than ~1-2 sentences\n- Use the \"we\" tense, as you are part of the company. Many of the bullets should say \"we did this\" or \"we did that\"\n\n## Tools to use\nIf you have access to the following tools, please try to use them. If not, you can also let the user know directly that their responses would be better if they gave them access.\n\n- Slack: look for messages in channels with lots of people, with lots of reactions or lots of responses within the thread\n- Email: look for things from executives that discuss company-wide announcements\n- Calendar: if there were meetings with large attendee lists, particularly things like All-Hands meetings, big company announcements, etc. If there were documents attached to those meetings, those are great links to include.\n- Documents: if there were new docs published in the last week or two that got a lot of attention, you can link them. These should be things like company-wide vision docs, plans for the upcoming quarter or half, things authored by critical executives, etc.\n- External press: if you see references to articles or press we've received over the past week, that could be really cool too.\n\nIf you don't have access to any of these things, you can ask the user for things they want to cover. In this case, you'll mostly just be polishing up and fitting to this format more directly.\n\n## Sections\nThe company is pretty big: 1000+ people. There are a variety of different teams and initiatives going on across the company. To make sure the update works well, try breaking it into sections of similar things. You might break into clusters like {product development, go to market, finance} or {recruiting, execution, vision}, or {external news, internal news} etc. Try to make sure the different areas of the company are highlighted well.\n\n## Prioritization\nFocus on:\n- Company-wide impact (not team-specific details)\n- Announcements from leadership\n- Major milestones and achievements\n- Information that affects most employees\n- External recognition or press\n\nAvoid:\n- Overly granular team updates (save those for 3Ps)\n- Information only relevant to small groups\n- Duplicate information already communicated\n\n## Example Formats\n\n:megaphone: Company Announcements\n- Announcement 1\n- Announcement 2\n- Announcement 3\n\n:dart: Progress on Priorities\n- Area 1\n - Sub-area 1\n - Sub-area 2\n - Sub-area 3\n- Area 2\n - Sub-area 1\n - Sub-area 2\n - Sub-area 3\n- Area 3\n - Sub-area 1\n - Sub-area 2\n - Sub-area 3\n\n:pillar: Leadership Updates\n- Post 1\n- Post 2\n- Post 3\n\n:thread: Social Updates\n- Update 1\n- Update 2\n- Update 3\n"
},
{
"name": "faq-answers.md",
"node_type": "file",
"content": "## Instructions\nYou are an assistant for answering questions that are being asked across the company. Every week, there are lots of questions that get asked across the company, and your goal is to try to summarize what those questions are. We want our company to be well-informed and on the same page, so your job is to produce a set of frequently asked questions that our employees are asking and attempt to answer them. Your singular job is to do two things:\n\n- Find questions that are big sources of confusion for lots of employees at the company, generally about things that affect a large portion of the employee base\n- Attempt to give a nice summarized answer to that question in order to minimize confusion.\n\nSome examples of areas that may be interesting to folks: recent corporate events (fundraising, new executives, etc.), upcoming launches, hiring progress, changes to vision or focus, etc.\n\n\n## Tools Available\nYou should use the company's available tools, where communication and work happens. For most companies, it looks something like this:\n- Slack: questions being asked across the company - it could be questions in response to posts with lots of responses, questions being asked with lots of reactions or thumbs up to show support, or anything else to show that a large number of employees want to ask the same things\n- Email: emails with FAQs written directly in them can be a good source as well\n- Documents: docs in places like Google Drive, linked on calendar events, etc. can also be a good source of FAQs, either directly added or inferred based on the contents of the doc\n\n## Formatting\nThe formatting should be pretty basic:\n\n- *Question*: [insert question - 1 sentence]\n- *Answer*: [insert answer - 1-2 sentence]\n\n## Guidance\nMake sure you're being holistic in your questions. Don't focus too much on just the user in question or the team they are a part of, but try to capture the entire company. Try to be as holistic as you can in reading all the tools available, producing responses that are relevant to all at the company.\n\n## Answer Guidelines\n- Base answers on official company communications when possible\n- If information is uncertain, indicate that clearly\n- Link to authoritative sources (docs, announcements, emails)\n- Keep tone professional but approachable\n- Flag if a question requires executive input or official response"
},
{
"name": "general-comms.md",
"node_type": "file",
"content": " ## Instructions\n You are being asked to write internal company communication that doesn't fit into the standard formats (3P\n updates, newsletters, or FAQs).\n\n Before proceeding:\n 1. Ask the user about their target audience\n 2. Understand the communication's purpose\n 3. Clarify the desired tone (formal, casual, urgent, informational)\n 4. Confirm any specific formatting requirements\n\n Use these general principles:\n - Be clear and concise\n - Use active voice\n - Put the most important information first\n - Include relevant links and references\n - Match the company's communication style"
}
]
}
]
export default children

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,45 @@
// AUTO-GENERATED — DO NOT EDIT
// Source: https://github.com/anthropics/skills
import type { SkillTemplateNode } from '../types'
const children: SkillTemplateNode[] = [
{
"name": "SKILL.md",
"node_type": "file",
"content": "---\nname: webapp-testing\ndescription: Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.\nlicense: Complete terms in LICENSE.txt\n---\n\n# Web Application Testing\n\nTo test local web applications, write native Python Playwright scripts.\n\n**Helper Scripts Available**:\n- `scripts/with_server.py` - Manages server lifecycle (supports multiple servers)\n\n**Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary. These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.\n\n## Decision Tree: Choosing Your Approach\n\n```\nUser task → Is it static HTML?\n ├─ Yes → Read HTML file directly to identify selectors\n │ ├─ Success → Write Playwright script using selectors\n │ └─ Fails/Incomplete → Treat as dynamic (below)\n │\n └─ No (dynamic webapp) → Is the server already running?\n ├─ No → Run: python scripts/with_server.py --help\n │ Then use the helper + write simplified Playwright script\n │\n └─ Yes → Reconnaissance-then-action:\n 1. Navigate and wait for networkidle\n 2. Take screenshot or inspect DOM\n 3. Identify selectors from rendered state\n 4. Execute actions with discovered selectors\n```\n\n## Example: Using with_server.py\n\nTo start a server, run `--help` first, then use the helper:\n\n**Single server:**\n```bash\npython scripts/with_server.py --server \"npm run dev\" --port 5173 -- python your_automation.py\n```\n\n**Multiple servers (e.g., backend + frontend):**\n```bash\npython scripts/with_server.py \\\n --server \"cd backend && python server.py\" --port 3000 \\\n --server \"cd frontend && npm run dev\" --port 5173 \\\n -- python your_automation.py\n```\n\nTo create an automation script, include only Playwright logic (servers are managed automatically):\n```python\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode\n page = browser.new_page()\n page.goto('http://localhost:5173') # Server already running and ready\n page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute\n # ... your automation logic\n browser.close()\n```\n\n## Reconnaissance-Then-Action Pattern\n\n1. **Inspect rendered DOM**:\n ```python\n page.screenshot(path='/tmp/inspect.png', full_page=True)\n content = page.content()\n page.locator('button').all()\n ```\n\n2. **Identify selectors** from inspection results\n\n3. **Execute actions** using discovered selectors\n\n## Common Pitfall\n\n❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps\n✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection\n\n## Best Practices\n\n- **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. \n- Use `sync_playwright()` for synchronous scripts\n- Always close the browser when done\n- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs\n- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()`\n\n## Reference Files\n\n- **examples/** - Examples showing common patterns:\n - `element_discovery.py` - Discovering buttons, links, and inputs on a page\n - `static_html_automation.py` - Using file:// URLs for local HTML\n - `console_logging.py` - Capturing console logs during automation"
},
{
"name": "examples",
"node_type": "folder",
"children": [
{
"name": "console_logging.py",
"node_type": "file",
"content": "from playwright.sync_api import sync_playwright\n\n# Example: Capturing console logs during browser automation\n\nurl = 'http://localhost:5173' # Replace with your URL\n\nconsole_logs = []\n\nwith sync_playwright() as p:\n browser = p.chromium.launch(headless=True)\n page = browser.new_page(viewport={'width': 1920, 'height': 1080})\n\n # Set up console log capture\n def handle_console_message(msg):\n console_logs.append(f\"[{msg.type}] {msg.text}\")\n print(f\"Console: [{msg.type}] {msg.text}\")\n\n page.on(\"console\", handle_console_message)\n\n # Navigate to page\n page.goto(url)\n page.wait_for_load_state('networkidle')\n\n # Interact with the page (triggers console logs)\n page.click('text=Dashboard')\n page.wait_for_timeout(1000)\n\n browser.close()\n\n# Save console logs to file\nwith open('/mnt/user-data/outputs/console.log', 'w') as f:\n f.write('\\n'.join(console_logs))\n\nprint(f\"\\nCaptured {len(console_logs)} console messages\")\nprint(f\"Logs saved to: /mnt/user-data/outputs/console.log\")"
},
{
"name": "element_discovery.py",
"node_type": "file",
"content": "from playwright.sync_api import sync_playwright\n\n# Example: Discovering buttons and other elements on a page\n\nwith sync_playwright() as p:\n browser = p.chromium.launch(headless=True)\n page = browser.new_page()\n\n # Navigate to page and wait for it to fully load\n page.goto('http://localhost:5173')\n page.wait_for_load_state('networkidle')\n\n # Discover all buttons on the page\n buttons = page.locator('button').all()\n print(f\"Found {len(buttons)} buttons:\")\n for i, button in enumerate(buttons):\n text = button.inner_text() if button.is_visible() else \"[hidden]\"\n print(f\" [{i}] {text}\")\n\n # Discover links\n links = page.locator('a[href]').all()\n print(f\"\\nFound {len(links)} links:\")\n for link in links[:5]: # Show first 5\n text = link.inner_text().strip()\n href = link.get_attribute('href')\n print(f\" - {text} -> {href}\")\n\n # Discover input fields\n inputs = page.locator('input, textarea, select').all()\n print(f\"\\nFound {len(inputs)} input fields:\")\n for input_elem in inputs:\n name = input_elem.get_attribute('name') or input_elem.get_attribute('id') or \"[unnamed]\"\n input_type = input_elem.get_attribute('type') or 'text'\n print(f\" - {name} ({input_type})\")\n\n # Take screenshot for visual reference\n page.screenshot(path='/tmp/page_discovery.png', full_page=True)\n print(\"\\nScreenshot saved to /tmp/page_discovery.png\")\n\n browser.close()"
},
{
"name": "static_html_automation.py",
"node_type": "file",
"content": "from playwright.sync_api import sync_playwright\nimport os\n\n# Example: Automating interaction with static HTML files using file:// URLs\n\nhtml_file_path = os.path.abspath('path/to/your/file.html')\nfile_url = f'file://{html_file_path}'\n\nwith sync_playwright() as p:\n browser = p.chromium.launch(headless=True)\n page = browser.new_page(viewport={'width': 1920, 'height': 1080})\n\n # Navigate to local HTML file\n page.goto(file_url)\n\n # Take screenshot\n page.screenshot(path='/mnt/user-data/outputs/static_page.png', full_page=True)\n\n # Interact with elements\n page.click('text=Click Me')\n page.fill('#name', 'John Doe')\n page.fill('#email', 'john@example.com')\n\n # Submit form\n page.click('button[type=\"submit\"]')\n page.wait_for_timeout(500)\n\n # Take final screenshot\n page.screenshot(path='/mnt/user-data/outputs/after_submit.png', full_page=True)\n\n browser.close()\n\nprint(\"Static HTML automation completed!\")"
}
]
},
{
"name": "scripts",
"node_type": "folder",
"children": [
{
"name": "with_server.py",
"node_type": "file",
"content": "#!/usr/bin/env python3\n\"\"\"\nStart one or more servers, wait for them to be ready, run a command, then clean up.\n\nUsage:\n # Single server\n python scripts/with_server.py --server \"npm run dev\" --port 5173 -- python automation.py\n python scripts/with_server.py --server \"npm start\" --port 3000 -- python test.py\n\n # Multiple servers\n python scripts/with_server.py \\\n --server \"cd backend && python server.py\" --port 3000 \\\n --server \"cd frontend && npm run dev\" --port 5173 \\\n -- python test.py\n\"\"\"\n\nimport subprocess\nimport socket\nimport time\nimport sys\nimport argparse\n\ndef is_server_ready(port, timeout=30):\n \"\"\"Wait for server to be ready by polling the port.\"\"\"\n start_time = time.time()\n while time.time() - start_time < timeout:\n try:\n with socket.create_connection(('localhost', port), timeout=1):\n return True\n except (socket.error, ConnectionRefusedError):\n time.sleep(0.5)\n return False\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Run command with one or more servers')\n parser.add_argument('--server', action='append', dest='servers', required=True, help='Server command (can be repeated)')\n parser.add_argument('--port', action='append', dest='ports', type=int, required=True, help='Port for each server (must match --server count)')\n parser.add_argument('--timeout', type=int, default=30, help='Timeout in seconds per server (default: 30)')\n parser.add_argument('command', nargs=argparse.REMAINDER, help='Command to run after server(s) ready')\n\n args = parser.parse_args()\n\n # Remove the '--' separator if present\n if args.command and args.command[0] == '--':\n args.command = args.command[1:]\n\n if not args.command:\n print(\"Error: No command specified to run\")\n sys.exit(1)\n\n # Parse server configurations\n if len(args.servers) != len(args.ports):\n print(\"Error: Number of --server and --port arguments must match\")\n sys.exit(1)\n\n servers = []\n for cmd, port in zip(args.servers, args.ports):\n servers.append({'cmd': cmd, 'port': port})\n\n server_processes = []\n\n try:\n # Start all servers\n for i, server in enumerate(servers):\n print(f\"Starting server {i+1}/{len(servers)}: {server['cmd']}\")\n\n # Use shell=True to support commands with cd and &&\n process = subprocess.Popen(\n server['cmd'],\n shell=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE\n )\n server_processes.append(process)\n\n # Wait for this server to be ready\n print(f\"Waiting for server on port {server['port']}...\")\n if not is_server_ready(server['port'], timeout=args.timeout):\n raise RuntimeError(f\"Server failed to start on port {server['port']} within {args.timeout}s\")\n\n print(f\"Server ready on port {server['port']}\")\n\n print(f\"\\nAll {len(servers)} server(s) ready\")\n\n # Run the command\n print(f\"Running: {' '.join(args.command)}\\n\")\n result = subprocess.run(args.command)\n sys.exit(result.returncode)\n\n finally:\n # Clean up all servers\n print(f\"\\nStopping {len(server_processes)} server(s)...\")\n for i, process in enumerate(server_processes):\n try:\n process.terminate()\n process.wait(timeout=5)\n except subprocess.TimeoutExpired:\n process.kill()\n process.wait()\n print(f\"Server {i+1} stopped\")\n print(\"All servers stopped\")\n\n\nif __name__ == '__main__':\n main()"
}
]
}
]
export default children

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import type { SkillTemplate, SkillTemplateNode } from './types'
import type { SkillTemplateNode } from './types'
import type { BatchUploadNodeInput } from '@/types/app-asset'
import { prepareSkillUploadFile } from '../../utils/skill-upload-utils'
@ -8,7 +8,8 @@ type TemplateUploadData = {
}
export async function buildUploadDataFromTemplate(
template: SkillTemplate,
name: string,
children: SkillTemplateNode[],
): Promise<TemplateUploadData> {
const files = new Map<string, File>()
@ -19,23 +20,34 @@ export async function buildUploadDataFromTemplate(
const currentPath = pathPrefix ? `${pathPrefix}/${node.name}` : node.name
if (node.node_type === 'folder') {
const children = await Promise.all(
const converted = await Promise.all(
node.children.map(child => convertNode(child, currentPath)),
)
return { name: node.name, node_type: 'folder', children }
return { name: node.name, node_type: 'folder', children: converted }
}
const raw = new File([node.content], node.name, { type: 'text/plain' })
let fileData: BlobPart
if (node.encoding === 'base64') {
const binary = atob(node.content)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++)
bytes[i] = binary.charCodeAt(i)
fileData = bytes
}
else {
fileData = node.content
}
const raw = new File([fileData], node.name)
const prepared = await prepareSkillUploadFile(raw)
files.set(currentPath, prepared)
return { name: node.name, node_type: 'file', size: prepared.size }
}
const rootFolder: BatchUploadNodeInput = {
name: template.name,
name,
node_type: 'folder',
children: await Promise.all(
template.children.map(child => convertNode(child, template.name)),
children.map(child => convertNode(child, name)),
),
}

View File

@ -4,6 +4,7 @@ export type SkillTemplateFileNode = {
name: string
node_type: Extract<AssetNodeType, 'file'>
content: string
encoding?: 'base64'
}
export type SkillTemplateFolderNode = {
@ -14,24 +15,18 @@ export type SkillTemplateFolderNode = {
export type SkillTemplateNode = SkillTemplateFileNode | SkillTemplateFolderNode
export type SkillTemplateFrontmatter = {
name: string
description: string
}
export type SkillTemplate = {
export type SkillTemplateSummary = {
id: string
name: string
description: string
children: SkillTemplateNode[]
}
export type SkillTemplateMetadata = {
tags?: string[]
fileCount: number
icon?: string
tags?: string[]
}
export type SkillTemplateWithMetadata = SkillTemplate & SkillTemplateMetadata
export type SkillTemplateEntry = SkillTemplateSummary & {
loadContent: () => Promise<SkillTemplateNode[]>
}
export type SkillTemplateTag = {
id: string