[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6J8P7GRB8AHKQEFDNVP41E3":3,"skill-same:01M6J8P7GRB8AHKQEFDNVP41E3":98},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":54,"commitSha":55,"skillDirectory":56,"forksCount":57,"repositoryHtmlUrl":58,"repositoryHomepageUrl":6,"ownerHtmlUrl":59,"ownerBlogUrl":60,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6J8P7GRB8AHKQEFDNVP41E3","web-reader",null,"Implement web page content extraction capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to scrape web pages, extract article content, retrieve page metadata, or build applications that process web content. Supports automatic content extraction with title, HTML, and publication time retrieval.","sgomez-dev\u002Fclaude-skills","sgomez-dev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F81492002?v=4","01M6J7TFP3PR3PTGBAG3N9Z007","Santiago Gómez de la Torre Romero","01M6J7TFT704JY40NG1YE3V5JQ",1,"MIT","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"web","web reader","reader","build","implement","scrape web","with","web content","pages","article","web-dev","time","dev","html","use","capabilities","page","article content","scrape","applications","2026-11-10 10:12:30",false,"---\nname: web-reader\ndescription: Implement web page content extraction capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to scrape web pages, extract article content, retrieve page metadata, or build applications that process web content. Supports automatic content extraction with title, HTML, and publication time retrieval.\nlicense: MIT\n---\n\n# Web Reader Skill\n\nThis skill guides the implementation of web page reading and content extraction functionality using the z-ai-web-dev-sdk package, enabling applications to fetch and process web page content programmatically.\n\n## Skills Path\n\n**Skill Location**: `{project_path}\u002Fskills\u002Fweb-reader`\n\nThis skill is located at the above path in your project.\n\n**Reference Scripts**: Example test scripts are available in the `{Skill Location}\u002Fscripts\u002F` directory for quick testing and reference. See `{Skill Location}\u002Fscripts\u002Fweb-reader.ts` for a working example.\n\n## Overview\n\nWeb Reader allows you to build applications that can extract content from web pages, retrieve article metadata, and process HTML content. The API automatically handles content extraction, providing clean, structured data from any web URL.\n\n**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.\n\n## Prerequisites\n\nThe z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.\n\n## CLI Usage (For Simple Tasks)\n\nFor simple web page content extraction, you can use the z-ai CLI instead of writing code. This is ideal for quick content scraping, testing URLs, or simple automation tasks.\n\n### Basic Page Reading\n\n```bash\n# Extract content from a web page\nz-ai function --name \"page_reader\" --args '{\"url\": \"https:\u002F\u002Fexample.com\"}'\n\n# Using short options\nz-ai function -n page_reader -a '{\"url\": \"https:\u002F\u002Fwww.example.com\u002Farticle\"}'\n```\n\n### Save Page Content\n\n```bash\n# Save extracted content to JSON file\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fnews.example.com\u002Farticle\"}' \\\n  -o page_content.json\n\n# Extract and save blog post\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fblog.example.com\u002Fpost\u002F123\"}' \\\n  -o blog_post.json\n```\n\n### Common Use Cases\n\n```bash\n# Extract news article\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fnews.site.com\u002Fbreaking-news\"}' \\\n  -o news.json\n\n# Read documentation page\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fdocs.example.com\u002Fgetting-started\"}' \\\n  -o docs.json\n\n# Scrape blog content\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Ftechblog.com\u002Fai-trends-2024\"}' \\\n  -o blog.json\n\n# Extract research article\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fresearch.org\u002Fpapers\u002Fquantum-computing\"}' \\\n  -o research.json\n```\n\n### CLI Parameters\n\n- `--name, -n`: **Required** - Function name (use \"page_reader\")\n- `--args, -a`: **Required** - JSON arguments object with:\n  - `url` (string, required): The URL of the web page to read\n- `--output, -o \u003Cpath>`: Optional - Output file path (JSON format)\n\n### Response Structure\n\nThe CLI returns a JSON object containing:\n- `title`: Page title\n- `html`: Main content HTML\n- `text`: Plain text content\n- `publish_time`: Publication timestamp (if available)\n- `url`: Original URL\n- `metadata`: Additional page metadata\n\n### Example Response\n\n```json\n{\n  \"title\": \"Introduction to Machine Learning\",\n  \"html\": \"\u003Carticle>\u003Ch1>Introduction to Machine Learning\u003C\u002Fh1>\u003Cp>Machine learning is...\u003C\u002Fp>\u003C\u002Farticle>\",\n  \"text\": \"Introduction to Machine Learning\\n\\nMachine learning is...\",\n  \"publish_time\": \"2024-01-15T10:30:00Z\",\n  \"url\": \"https:\u002F\u002Fexample.com\u002Fml-intro\",\n  \"metadata\": {\n    \"author\": \"John Doe\",\n    \"description\": \"A comprehensive guide to ML\"\n  }\n}\n```\n\n### Processing Multiple URLs\n\n```bash\n# Create a simple script to process multiple URLs\nfor url in \\\n  \"https:\u002F\u002Fsite1.com\u002Farticle1\" \\\n  \"https:\u002F\u002Fsite2.com\u002Farticle2\" \\\n  \"https:\u002F\u002Fsite3.com\u002Farticle3\"\ndo\n  filename=$(echo $url | md5sum | cut -d' ' -f1)\n  z-ai function -n page_reader -a \"{\\\"url\\\": \\\"$url\\\"}\" -o \"${filename}.json\"\ndone\n```\n\n### When to Use CLI vs SDK\n\n**Use CLI for:**\n- Quick content extraction\n- Testing URL accessibility\n- Simple web scraping tasks\n- One-off content retrieval\n\n**Use SDK for:**\n- Batch URL processing with custom logic\n- Integration with web applications\n- Complex content processing pipelines\n- Production applications with error handling\n\n## How It Works\n\nThe Web Reader uses the `page_reader` function to:\n1. Fetch the web page content\n2. Extract main article content and metadata\n3. Parse and clean the HTML\n4. Return structured data including title, content, and publication time\n\n## Basic Web Reading Implementation\n\n### Simple Page Reading\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readWebPage(url) {\n  try {\n    const zai = await ZAI.create();\n\n    const result = await zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    console.log('Title:', result.data.title);\n    console.log('URL:', result.data.url);\n    console.log('Published:', result.data.publishedTime);\n    console.log('HTML Content:', result.data.html);\n    console.log('Tokens Used:', result.data.usage.tokens);\n\n    return result.data;\n  } catch (error) {\n    console.error('Page reading failed:', error.message);\n    throw error;\n  }\n}\n\n\u002F\u002F Usage\nconst pageData = await readWebPage('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Page title:', pageData.title);\n```\n\n### Extract Article Text Only\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function extractArticleText(url) {\n  const zai = await ZAI.create();\n\n  const result = await zai.functions.invoke('page_reader', {\n    url: url\n  });\n\n  \u002F\u002F Convert HTML to plain text (basic approach)\n  const plainText = result.data.html\n    .replace(\u002F\u003C[^>]*>\u002Fg, ' ')\n    .replace(\u002F\\s+\u002Fg, ' ')\n    .trim();\n\n  return {\n    title: result.data.title,\n    text: plainText,\n    url: result.data.url,\n    publishedTime: result.data.publishedTime\n  };\n}\n\n\u002F\u002F Usage\nconst article = await extractArticleText('https:\u002F\u002Fnews.example.com\u002Fstory');\nconsole.log(article.title);\nconsole.log(article.text.substring(0, 200) + '...');\n```\n\n### Read Multiple Pages\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readMultiplePages(urls) {\n  const zai = await ZAI.create();\n  const results = [];\n\n  for (const url of urls) {\n    try {\n      const result = await zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      results.push({\n        url: url,\n        success: true,\n        data: result.data\n      });\n    } catch (error) {\n      results.push({\n        url: url,\n        success: false,\n        error: error.message\n      });\n    }\n  }\n\n  return results;\n}\n\n\u002F\u002F Usage\nconst urls = [\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2',\n  'https:\u002F\u002Fexample.com\u002Farticle3'\n];\n\nconst pages = await readMultiplePages(urls);\npages.forEach(page => {\n  if (page.success) {\n    console.log(`✓ ${page.data.title}`);\n  } else {\n    console.log(`✗ ${page.url}: ${page.error}`);\n  }\n});\n```\n\n## Advanced Use Cases\n\n### Web Content Analyzer\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass WebContentAnalyzer {\n  constructor() {\n    this.cache = new Map();\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url, useCache = true) {\n    \u002F\u002F Check cache\n    if (useCache && this.cache.has(url)) {\n      console.log('Returning cached result for:', url);\n      return this.cache.get(url);\n    }\n\n    \u002F\u002F Fetch fresh content\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Cache the result\n    if (useCache) {\n      this.cache.set(url, result.data);\n    }\n\n    return result.data;\n  }\n\n  async getPageMetadata(url) {\n    const data = await this.readPage(url);\n\n    return {\n      title: data.title,\n      url: data.url,\n      publishedTime: data.publishedTime,\n      contentLength: data.html.length,\n      wordCount: this.estimateWordCount(data.html)\n    };\n  }\n\n  estimateWordCount(html) {\n    const text = html.replace(\u002F\u003C[^>]*>\u002Fg, ' ');\n    const words = text.split(\u002F\\s+\u002F).filter(word => word.length > 0);\n    return words.length;\n  }\n\n  async comparePages(url1, url2) {\n    const [page1, page2] = await Promise.all([\n      this.readPage(url1),\n      this.readPage(url2)\n    ]);\n\n    return {\n      page1: {\n        title: page1.title,\n        wordCount: this.estimateWordCount(page1.html),\n        published: page1.publishedTime\n      },\n      page2: {\n        title: page2.title,\n        wordCount: this.estimateWordCount(page2.html),\n        published: page2.publishedTime\n      }\n    };\n  }\n\n  clearCache() {\n    this.cache.clear();\n  }\n}\n\n\u002F\u002F Usage\nconst analyzer = new WebContentAnalyzer();\nawait analyzer.initialize();\n\nconst metadata = await analyzer.getPageMetadata('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Article Metadata:', metadata);\n\nconst comparison = await analyzer.comparePages(\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2'\n);\nconsole.log('Comparison:', comparison);\n```\n\n### RSS Feed Reader\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass FeedReader {\n  constructor() {\n    this.articles = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async fetchArticlesFromUrls(urls) {\n    const articles = [];\n\n    for (const url of urls) {\n      try {\n        const result = await this.zai.functions.invoke('page_reader', {\n          url: url\n        });\n\n        articles.push({\n          title: result.data.title,\n          url: result.data.url,\n          publishedTime: result.data.publishedTime,\n          content: result.data.html,\n          fetchedAt: new Date().toISOString()\n        });\n\n        console.log(`Fetched: ${result.data.title}`);\n      } catch (error) {\n        console.error(`Failed to fetch ${url}:`, error.message);\n      }\n    }\n\n    this.articles = articles;\n    return articles;\n  }\n\n  getRecentArticles(limit = 10) {\n    return this.articles\n      .sort((a, b) => {\n        const dateA = new Date(a.publishedTime || a.fetchedAt);\n        const dateB = new Date(b.publishedTime || b.fetchedAt);\n        return dateB - dateA;\n      })\n      .slice(0, limit);\n  }\n\n  searchArticles(keyword) {\n    return this.articles.filter(article => {\n      const searchText = `${article.title} ${article.content}`.toLowerCase();\n      return searchText.includes(keyword.toLowerCase());\n    });\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new FeedReader();\nawait reader.initialize();\n\nconst feedUrls = [\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2',\n  'https:\u002F\u002Fexample.com\u002Farticle3'\n];\n\nawait reader.fetchArticlesFromUrls(feedUrls);\nconst recent = reader.getRecentArticles(5);\nconsole.log('Recent articles:', recent.map(a => a.title));\n```\n\n### Content Aggregator\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function aggregateContent(urls, options = {}) {\n  const zai = await ZAI.create();\n  const aggregated = {\n    sources: [],\n    totalWords: 0,\n    aggregatedAt: new Date().toISOString()\n  };\n\n  for (const url of urls) {\n    try {\n      const result = await zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      const text = result.data.html.replace(\u002F\u003C[^>]*>\u002Fg, ' ');\n      const wordCount = text.split(\u002F\\s+\u002F).filter(w => w.length > 0).length;\n\n      aggregated.sources.push({\n        title: result.data.title,\n        url: result.data.url,\n        publishedTime: result.data.publishedTime,\n        wordCount: wordCount,\n        excerpt: text.substring(0, 200).trim() + '...'\n      });\n\n      aggregated.totalWords += wordCount;\n\n      if (options.delay) {\n        await new Promise(resolve => setTimeout(resolve, options.delay));\n      }\n    } catch (error) {\n      console.error(`Failed to fetch ${url}:`, error.message);\n    }\n  }\n\n  return aggregated;\n}\n\n\u002F\u002F Usage\nconst sources = [\n  'https:\u002F\u002Fexample.com\u002Fnews1',\n  'https:\u002F\u002Fexample.com\u002Fnews2',\n  'https:\u002F\u002Fexample.com\u002Fnews3'\n];\n\nconst aggregated = await aggregateContent(sources, { delay: 1000 });\nconsole.log(`Aggregated ${aggregated.sources.length} sources`);\nconsole.log(`Total words: ${aggregated.totalWords}`);\n```\n\n### Web Scraping Pipeline\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass ScrapingPipeline {\n  constructor() {\n    this.processors = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  addProcessor(name, processorFn) {\n    this.processors.push({ name, fn: processorFn });\n  }\n\n  async scrape(url) {\n    \u002F\u002F Fetch the page\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    let data = {\n      raw: result.data,\n      processed: {}\n    };\n\n    \u002F\u002F Run through processors\n    for (const processor of this.processors) {\n      try {\n        data.processed[processor.name] = await processor.fn(data.raw);\n        console.log(`✓ Processed with ${processor.name}`);\n      } catch (error) {\n        console.error(`✗ Failed ${processor.name}:`, error.message);\n        data.processed[processor.name] = null;\n      }\n    }\n\n    return data;\n  }\n}\n\n\u002F\u002F Processor functions\nfunction extractLinks(pageData) {\n  const linkRegex = \u002Fhref=[\"'](https?:\\\u002F\\\u002F[^\"']+)[\"']\u002Fg;\n  const links = [];\n  let match;\n\n  while ((match = linkRegex.exec(pageData.html)) !== null) {\n    links.push(match[1]);\n  }\n\n  return [...new Set(links)]; \u002F\u002F Remove duplicates\n}\n\nfunction extractImages(pageData) {\n  const imgRegex = \u002Fsrc=[\"'](https?:\\\u002F\\\u002F[^\"']+\\.(jpg|jpeg|png|gif|webp))[\"']\u002Fgi;\n  const images = [];\n  let match;\n\n  while ((match = imgRegex.exec(pageData.html)) !== null) {\n    images.push(match[1]);\n  }\n\n  return [...new Set(images)];\n}\n\nfunction extractPlainText(pageData) {\n  return pageData.html\n    .replace(\u002F\u003Cscript[^>]*>[\\s\\S]*?\u003C\\\u002Fscript>\u002Fgi, '')\n    .replace(\u002F\u003Cstyle[^>]*>[\\s\\S]*?\u003C\\\u002Fstyle>\u002Fgi, '')\n    .replace(\u002F\u003C[^>]*>\u002Fg, ' ')\n    .replace(\u002F\\s+\u002Fg, ' ')\n    .trim();\n}\n\n\u002F\u002F Usage\nconst pipeline = new ScrapingPipeline();\nawait pipeline.initialize();\n\npipeline.addProcessor('links', extractLinks);\npipeline.addProcessor('images', extractImages);\npipeline.addProcessor('plainText', extractPlainText);\n\nconst result = await pipeline.scrape('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Links found:', result.processed.links.length);\nconsole.log('Images found:', result.processed.images.length);\nconsole.log('Text length:', result.processed.plainText.length);\n```\n\n## Response Format\n\n### Successful Response\n\n```typescript\n{\n  code: 200,\n  status: 200,\n  data: {\n    title: \"Article Title\",\n    url: \"https:\u002F\u002Fexample.com\u002Farticle\",\n    html: \"\u003Cdiv>Article content...\u003C\u002Fdiv>\",\n    publishedTime: \"2025-01-15T10:30:00Z\",\n    usage: {\n      tokens: 1500\n    }\n  },\n  meta: {\n    usage: {\n      tokens: 1500\n    }\n  }\n}\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `code` | number | Response status code |\n| `status` | number | HTTP status code |\n| `data.title` | string | Page title |\n| `data.url` | string | Page URL |\n| `data.html` | string | Extracted HTML content |\n| `data.publishedTime` | string | Publication date (optional) |\n| `data.usage.tokens` | number | Tokens used for processing |\n| `meta.usage.tokens` | number | Total tokens used |\n\n## Best Practices\n\n### 1. Error Handling\n\n```javascript\nasync function safeReadPage(url) {\n  try {\n    const zai = await ZAI.create();\n\n    \u002F\u002F Validate URL\n    if (!url || !url.startsWith('http')) {\n      throw new Error('Invalid URL format');\n    }\n\n    const result = await zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Check response status\n    if (result.code !== 200) {\n      throw new Error(`Failed to fetch page: ${result.code}`);\n    }\n\n    \u002F\u002F Verify essential data\n    if (!result.data.html || !result.data.title) {\n      throw new Error('Incomplete page data received');\n    }\n\n    return {\n      success: true,\n      data: result.data\n    };\n  } catch (error) {\n    console.error('Page reading error:', error);\n    return {\n      success: false,\n      error: error.message\n    };\n  }\n}\n```\n\n### 2. Rate Limiting\n\n```javascript\nclass RateLimitedReader {\n  constructor(requestsPerMinute = 10) {\n    this.requestsPerMinute = requestsPerMinute;\n    this.requestTimes = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url) {\n    await this.waitForRateLimit();\n\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    this.requestTimes.push(Date.now());\n    return result.data;\n  }\n\n  async waitForRateLimit() {\n    const now = Date.now();\n    const oneMinuteAgo = now - 60000;\n\n    \u002F\u002F Remove old timestamps\n    this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);\n\n    \u002F\u002F Check if we need to wait\n    if (this.requestTimes.length >= this.requestsPerMinute) {\n      const oldestRequest = this.requestTimes[0];\n      const waitTime = 60000 - (now - oldestRequest);\n\n      if (waitTime > 0) {\n        console.log(`Rate limit reached. Waiting ${waitTime}ms...`);\n        await new Promise(resolve => setTimeout(resolve, waitTime));\n      }\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new RateLimitedReader(10); \u002F\u002F 10 requests per minute\nawait reader.initialize();\n\nconst urls = ['https:\u002F\u002Fexample.com\u002F1', 'https:\u002F\u002Fexample.com\u002F2'];\nfor (const url of urls) {\n  const data = await reader.readPage(url);\n  console.log('Fetched:', data.title);\n}\n```\n\n### 3. Caching Strategy\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass CachedWebReader {\n  constructor(cacheDuration = 3600000) { \u002F\u002F 1 hour default\n    this.cache = new Map();\n    this.cacheDuration = cacheDuration;\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url, forceRefresh = false) {\n    const cacheKey = url;\n    const cached = this.cache.get(cacheKey);\n\n    \u002F\u002F Return cached if valid and not forcing refresh\n    if (cached && !forceRefresh) {\n      const age = Date.now() - cached.timestamp;\n      if (age \u003C this.cacheDuration) {\n        console.log('Returning cached content for:', url);\n        return cached.data;\n      }\n    }\n\n    \u002F\u002F Fetch fresh content\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Update cache\n    this.cache.set(cacheKey, {\n      data: result.data,\n      timestamp: Date.now()\n    });\n\n    return result.data;\n  }\n\n  clearCache() {\n    this.cache.clear();\n  }\n\n  getCacheStats() {\n    return {\n      size: this.cache.size,\n      entries: Array.from(this.cache.keys())\n    };\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new CachedWebReader(3600000); \u002F\u002F 1 hour cache\nawait reader.initialize();\n\nconst data1 = await reader.readPage('https:\u002F\u002Fexample.com'); \u002F\u002F Fresh fetch\nconst data2 = await reader.readPage('https:\u002F\u002Fexample.com'); \u002F\u002F From cache\nconst data3 = await reader.readPage('https:\u002F\u002Fexample.com', true); \u002F\u002F Force refresh\n```\n\n### 4. Parallel Processing\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readPagesInParallel(urls, concurrency = 3) {\n  const zai = await ZAI.create();\n  const results = [];\n  \n  \u002F\u002F Process in batches\n  for (let i = 0; i \u003C urls.length; i += concurrency) {\n    const batch = urls.slice(i, i + concurrency);\n    \n    const batchResults = await Promise.allSettled(\n      batch.map(url =>\n        zai.functions.invoke('page_reader', { url })\n          .then(result => ({\n            url: url,\n            success: true,\n            data: result.data\n          }))\n          .catch(error => ({\n            url: url,\n            success: false,\n            error: error.message\n          }))\n      )\n    );\n\n    results.push(...batchResults.map(r => r.value));\n    console.log(`Completed batch ${Math.floor(i \u002F concurrency) + 1}`);\n  }\n\n  return results;\n}\n\n\u002F\u002F Usage\nconst urls = [\n  'https:\u002F\u002Fexample.com\u002F1',\n  'https:\u002F\u002Fexample.com\u002F2',\n  'https:\u002F\u002Fexample.com\u002F3',\n  'https:\u002F\u002Fexample.com\u002F4',\n  'https:\u002F\u002Fexample.com\u002F5'\n];\n\nconst results = await readPagesInParallel(urls, 2); \u002F\u002F 2 concurrent requests\nresults.forEach(result => {\n  if (result.success) {\n    console.log(`✓ ${result.data.title}`);\n  } else {\n    console.log(`✗ ${result.url}: ${result.error}`);\n  }\n});\n```\n\n### 5. Content Processing\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass ContentProcessor {\n  static extractMainContent(html) {\n    \u002F\u002F Remove scripts, styles, and comments\n    let content = html\n      .replace(\u002F\u003Cscript[^>]*>[\\s\\S]*?\u003C\\\u002Fscript>\u002Fgi, '')\n      .replace(\u002F\u003Cstyle[^>]*>[\\s\\S]*?\u003C\\\u002Fstyle>\u002Fgi, '')\n      .replace(\u002F\u003C!--[\\s\\S]*?-->\u002Fg, '');\n\n    return content;\n  }\n\n  static htmlToPlainText(html) {\n    return html\n      .replace(\u002F\u003Cbr\\s*\\\u002F?>\u002Fgi, '\\n')\n      .replace(\u002F\u003C\\\u002Fp>\u002Fgi, '\\n\\n')\n      .replace(\u002F\u003C[^>]*>\u002Fg, '')\n      .replace(\u002F&nbsp;\u002Fg, ' ')\n      .replace(\u002F&amp;\u002Fg, '&')\n      .replace(\u002F&lt;\u002Fg, '\u003C')\n      .replace(\u002F&gt;\u002Fg, '>')\n      .replace(\u002F&quot;\u002Fg, '\"')\n      .replace(\u002F\\s+\u002Fg, ' ')\n      .trim();\n  }\n\n  static extractMetadata(html) {\n    const metadata = {};\n\n    \u002F\u002F Extract meta description\n    const descMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']description[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (descMatch) metadata.description = descMatch[1];\n\n    \u002F\u002F Extract keywords\n    const keywordsMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']keywords[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (keywordsMatch) metadata.keywords = keywordsMatch[1].split(',').map(k => k.trim());\n\n    \u002F\u002F Extract author\n    const authorMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']author[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (authorMatch) metadata.author = authorMatch[1];\n\n    return metadata;\n  }\n}\n\n\u002F\u002F Usage\nasync function processWebPage(url) {\n  const zai = await ZAI.create();\n  const result = await zai.functions.invoke('page_reader', { url });\n\n  return {\n    title: result.data.title,\n    url: result.data.url,\n    mainContent: ContentProcessor.extractMainContent(result.data.html),\n    plainText: ContentProcessor.htmlToPlainText(result.data.html),\n    metadata: ContentProcessor.extractMetadata(result.data.html),\n    publishedTime: result.data.publishedTime\n  };\n}\n\nconst processed = await processWebPage('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Processed content:', processed.title);\n```\n\n## Common Use Cases\n\n1. **News Aggregation**: Collect and aggregate news articles from multiple sources\n2. **Content Monitoring**: Track changes on specific web pages\n3. **Research Tools**: Extract information from academic or reference websites\n4. **Price Tracking**: Monitor product pages for price changes\n5. **SEO Analysis**: Extract page metadata and content for SEO purposes\n6. **Archive Creation**: Create local copies of web content\n7. **Content Curation**: Collect and organize web content by topic\n8. **Competitive Intelligence**: Monitor competitor websites for updates\n\n## Integration Examples\n\n### Express.js API Endpoint\n\n```javascript\nimport express from 'express';\nimport ZAI from 'z-ai-web-dev-sdk';\n\nconst app = express();\napp.use(express.json());\n\nlet zaiInstance;\n\nasync function initZAI() {\n  zaiInstance = await ZAI.create();\n}\n\napp.post('\u002Fapi\u002Fread-page', async (req, res) => {\n  try {\n    const { url } = req.body;\n\n    if (!url) {\n      return res.status(400).json({ \n        error: 'URL is required' \n      });\n    }\n\n    const result = await zaiInstance.functions.invoke('page_reader', {\n      url: url\n    });\n\n    res.json({\n      success: true,\n      data: {\n        title: result.data.title,\n        url: result.data.url,\n        content: result.data.html,\n        publishedTime: result.data.publishedTime,\n        tokensUsed: result.data.usage.tokens\n      }\n    });\n  } catch (error) {\n    res.status(500).json({\n      success: false,\n      error: error.message\n    });\n  }\n});\n\napp.post('\u002Fapi\u002Fread-multiple', async (req, res) => {\n  try {\n    const { urls } = req.body;\n\n    if (!urls || !Array.isArray(urls)) {\n      return res.status(400).json({ \n        error: 'URLs array is required' \n      });\n    }\n\n    const results = await Promise.allSettled(\n      urls.map(url =>\n        zaiInstance.functions.invoke('page_reader', { url })\n          .then(result => ({\n            url: url,\n            success: true,\n            data: result.data\n          }))\n          .catch(error => ({\n            url: url,\n            success: false,\n            error: error.message\n          }))\n      )\n    );\n\n    res.json({\n      success: true,\n      results: results.map(r => r.value)\n    });\n  } catch (error) {\n    res.status(500).json({\n      success: false,\n      error: error.message\n    });\n  }\n});\n\ninitZAI().then(() => {\n  app.listen(3000, () => {\n    console.log('Web reader API running on port 3000');\n  });\n});\n```\n\n### Scheduled Content Fetcher\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\nimport cron from 'node-cron';\n\nclass ScheduledFetcher {\n  constructor() {\n    this.urls = [];\n    this.results = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  addUrl(url, schedule) {\n    this.urls.push({ url, schedule });\n  }\n\n  async fetchContent(url) {\n    try {\n      const result = await this.zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      return {\n        url: url,\n        success: true,\n        title: result.data.title,\n        content: result.data.html,\n        fetchedAt: new Date().toISOString()\n      };\n    } catch (error) {\n      return {\n        url: url,\n        success: false,\n        error: error.message,\n        fetchedAt: new Date().toISOString()\n      };\n    }\n  }\n\n  startScheduledFetch(url, schedule) {\n    cron.schedule(schedule, async () => {\n      console.log(`Fetching ${url}...`);\n      const result = await this.fetchContent(url);\n      this.results.push(result);\n      \n      \u002F\u002F Keep only last 100 results\n      if (this.results.length > 100) {\n        this.results = this.results.slice(-100);\n      }\n      \n      console.log(`Fetched: ${result.success ? result.title : result.error}`);\n    });\n  }\n\n  start() {\n    for (const { url, schedule } of this.urls) {\n      this.startScheduledFetch(url, schedule);\n    }\n  }\n\n  getResults() {\n    return this.results;\n  }\n}\n\n\u002F\u002F Usage\nconst fetcher = new ScheduledFetcher();\nawait fetcher.initialize();\n\n\u002F\u002F Fetch every hour\nfetcher.addUrl('https:\u002F\u002Fexample.com\u002Fnews', '0 * * * *');\n\n\u002F\u002F Fetch every day at midnight\nfetcher.addUrl('https:\u002F\u002Fexample.com\u002Fdaily', '0 0 * * *');\n\nfetcher.start();\nconsole.log('Scheduled fetching started');\n```\n\n## Troubleshooting\n\n**Issue**: \"SDK must be used in backend\"\n- **Solution**: Ensure z-ai-web-dev-sdk is only imported and used in server-side code\n\n**Issue**: Failed to fetch page (404, 403, etc.)\n- **Solution**: Verify the URL is accessible and not behind authentication\u002Fpaywall\n\n**Issue**: Incomplete or missing content\n- **Solution**: Some pages may have dynamic content that requires JavaScript. The reader extracts static HTML content.\n\n**Issue**: High token usage\n- **Solution**: The token usage depends on page size. Consider caching frequently accessed pages.\n\n**Issue**: Slow response times\n- **Solution**: Implement caching, use parallel processing for multiple URLs, and consider rate limiting\n\n**Issue**: Empty HTML content\n- **Solution**: Check if the page requires authentication or has anti-scraping measures. Verify the URL is correct.\n\n## Performance Tips\n\n1. **Implement caching**: Cache frequently accessed pages to reduce API calls\n2. **Use parallel processing**: Fetch multiple pages concurrently (with rate limiting)\n3. **Process content efficiently**: Extract only needed information from HTML\n4. **Set timeouts**: Implement reasonable timeouts for page fetching\n5. **Monitor token usage**: Track usage to optimize costs\n6. **Batch operations**: Group multiple URL fetches when possible\n\n## Security Considerations\n\n- Validate all URLs before processing\n- Sanitize extracted HTML content before displaying\n- Implement rate limiting to prevent abuse\n- Never expose SDK credentials in client-side code\n- Be respectful of robots.txt and website terms of service\n- Handle user data according to privacy regulations\n- Implement proper error handling for failed requests\n\n## Remember\n\n- Always use z-ai-web-dev-sdk in backend code only\n- The SDK is already installed - import as shown in examples\n- Implement proper error handling for robust applications\n- Use caching to improve performance and reduce costs\n- Respect website terms of service and rate limits\n- Process HTML content carefully to extract meaningful data\n- Monitor token usage for cost optimization\n","# Web Reader Skill\n\nThis skill guides the implementation of web page reading and content extraction functionality using the z-ai-web-dev-sdk package, enabling applications to fetch and process web page content programmatically.\n\n## Skills Path\n\n**Skill Location**: `{project_path}\u002Fskills\u002Fweb-reader`\n\nThis skill is located at the above path in your project.\n\n**Reference Scripts**: Example test scripts are available in the `{Skill Location}\u002Fscripts\u002F` directory for quick testing and reference. See `{Skill Location}\u002Fscripts\u002Fweb-reader.ts` for a working example.\n\n## Overview\n\nWeb Reader allows you to build applications that can extract content from web pages, retrieve article metadata, and process HTML content. The API automatically handles content extraction, providing clean, structured data from any web URL.\n\n**IMPORTANT**: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.\n\n## Prerequisites\n\nThe z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.\n\n## CLI Usage (For Simple Tasks)\n\nFor simple web page content extraction, you can use the z-ai CLI instead of writing code. This is ideal for quick content scraping, testing URLs, or simple automation tasks.\n\n### Basic Page Reading\n\n```bash\n# Extract content from a web page\nz-ai function --name \"page_reader\" --args '{\"url\": \"https:\u002F\u002Fexample.com\"}'\n\n# Using short options\nz-ai function -n page_reader -a '{\"url\": \"https:\u002F\u002Fwww.example.com\u002Farticle\"}'\n```\n\n### Save Page Content\n\n```bash\n# Save extracted content to JSON file\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fnews.example.com\u002Farticle\"}' \\\n  -o page_content.json\n\n# Extract and save blog post\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fblog.example.com\u002Fpost\u002F123\"}' \\\n  -o blog_post.json\n```\n\n### Common Use Cases\n\n```bash\n# Extract news article\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fnews.site.com\u002Fbreaking-news\"}' \\\n  -o news.json\n\n# Read documentation page\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fdocs.example.com\u002Fgetting-started\"}' \\\n  -o docs.json\n\n# Scrape blog content\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Ftechblog.com\u002Fai-trends-2024\"}' \\\n  -o blog.json\n\n# Extract research article\nz-ai function \\\n  -n page_reader \\\n  -a '{\"url\": \"https:\u002F\u002Fresearch.org\u002Fpapers\u002Fquantum-computing\"}' \\\n  -o research.json\n```\n\n### CLI Parameters\n\n- `--name, -n`: **Required** - Function name (use \"page_reader\")\n- `--args, -a`: **Required** - JSON arguments object with:\n  - `url` (string, required): The URL of the web page to read\n- `--output, -o \u003Cpath>`: Optional - Output file path (JSON format)\n\n### Response Structure\n\nThe CLI returns a JSON object containing:\n- `title`: Page title\n- `html`: Main content HTML\n- `text`: Plain text content\n- `publish_time`: Publication timestamp (if available)\n- `url`: Original URL\n- `metadata`: Additional page metadata\n\n### Example Response\n\n```json\n{\n  \"title\": \"Introduction to Machine Learning\",\n  \"html\": \"\u003Carticle>\u003Ch1>Introduction to Machine Learning\u003C\u002Fh1>\u003Cp>Machine learning is...\u003C\u002Fp>\u003C\u002Farticle>\",\n  \"text\": \"Introduction to Machine Learning\\n\\nMachine learning is...\",\n  \"publish_time\": \"2024-01-15T10:30:00Z\",\n  \"url\": \"https:\u002F\u002Fexample.com\u002Fml-intro\",\n  \"metadata\": {\n    \"author\": \"John Doe\",\n    \"description\": \"A comprehensive guide to ML\"\n  }\n}\n```\n\n### Processing Multiple URLs\n\n```bash\n# Create a simple script to process multiple URLs\nfor url in \\\n  \"https:\u002F\u002Fsite1.com\u002Farticle1\" \\\n  \"https:\u002F\u002Fsite2.com\u002Farticle2\" \\\n  \"https:\u002F\u002Fsite3.com\u002Farticle3\"\ndo\n  filename=$(echo $url | md5sum | cut -d' ' -f1)\n  z-ai function -n page_reader -a \"{\\\"url\\\": \\\"$url\\\"}\" -o \"${filename}.json\"\ndone\n```\n\n### When to Use CLI vs SDK\n\n**Use CLI for:**\n- Quick content extraction\n- Testing URL accessibility\n- Simple web scraping tasks\n- One-off content retrieval\n\n**Use SDK for:**\n- Batch URL processing with custom logic\n- Integration with web applications\n- Complex content processing pipelines\n- Production applications with error handling\n\n## How It Works\n\nThe Web Reader uses the `page_reader` function to:\n1. Fetch the web page content\n2. Extract main article content and metadata\n3. Parse and clean the HTML\n4. Return structured data including title, content, and publication time\n\n## Basic Web Reading Implementation\n\n### Simple Page Reading\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readWebPage(url) {\n  try {\n    const zai = await ZAI.create();\n\n    const result = await zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    console.log('Title:', result.data.title);\n    console.log('URL:', result.data.url);\n    console.log('Published:', result.data.publishedTime);\n    console.log('HTML Content:', result.data.html);\n    console.log('Tokens Used:', result.data.usage.tokens);\n\n    return result.data;\n  } catch (error) {\n    console.error('Page reading failed:', error.message);\n    throw error;\n  }\n}\n\n\u002F\u002F Usage\nconst pageData = await readWebPage('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Page title:', pageData.title);\n```\n\n### Extract Article Text Only\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function extractArticleText(url) {\n  const zai = await ZAI.create();\n\n  const result = await zai.functions.invoke('page_reader', {\n    url: url\n  });\n\n  \u002F\u002F Convert HTML to plain text (basic approach)\n  const plainText = result.data.html\n    .replace(\u002F\u003C[^>]*>\u002Fg, ' ')\n    .replace(\u002F\\s+\u002Fg, ' ')\n    .trim();\n\n  return {\n    title: result.data.title,\n    text: plainText,\n    url: result.data.url,\n    publishedTime: result.data.publishedTime\n  };\n}\n\n\u002F\u002F Usage\nconst article = await extractArticleText('https:\u002F\u002Fnews.example.com\u002Fstory');\nconsole.log(article.title);\nconsole.log(article.text.substring(0, 200) + '...');\n```\n\n### Read Multiple Pages\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readMultiplePages(urls) {\n  const zai = await ZAI.create();\n  const results = [];\n\n  for (const url of urls) {\n    try {\n      const result = await zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      results.push({\n        url: url,\n        success: true,\n        data: result.data\n      });\n    } catch (error) {\n      results.push({\n        url: url,\n        success: false,\n        error: error.message\n      });\n    }\n  }\n\n  return results;\n}\n\n\u002F\u002F Usage\nconst urls = [\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2',\n  'https:\u002F\u002Fexample.com\u002Farticle3'\n];\n\nconst pages = await readMultiplePages(urls);\npages.forEach(page => {\n  if (page.success) {\n    console.log(`✓ ${page.data.title}`);\n  } else {\n    console.log(`✗ ${page.url}: ${page.error}`);\n  }\n});\n```\n\n## Advanced Use Cases\n\n### Web Content Analyzer\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass WebContentAnalyzer {\n  constructor() {\n    this.cache = new Map();\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url, useCache = true) {\n    \u002F\u002F Check cache\n    if (useCache && this.cache.has(url)) {\n      console.log('Returning cached result for:', url);\n      return this.cache.get(url);\n    }\n\n    \u002F\u002F Fetch fresh content\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Cache the result\n    if (useCache) {\n      this.cache.set(url, result.data);\n    }\n\n    return result.data;\n  }\n\n  async getPageMetadata(url) {\n    const data = await this.readPage(url);\n\n    return {\n      title: data.title,\n      url: data.url,\n      publishedTime: data.publishedTime,\n      contentLength: data.html.length,\n      wordCount: this.estimateWordCount(data.html)\n    };\n  }\n\n  estimateWordCount(html) {\n    const text = html.replace(\u002F\u003C[^>]*>\u002Fg, ' ');\n    const words = text.split(\u002F\\s+\u002F).filter(word => word.length > 0);\n    return words.length;\n  }\n\n  async comparePages(url1, url2) {\n    const [page1, page2] = await Promise.all([\n      this.readPage(url1),\n      this.readPage(url2)\n    ]);\n\n    return {\n      page1: {\n        title: page1.title,\n        wordCount: this.estimateWordCount(page1.html),\n        published: page1.publishedTime\n      },\n      page2: {\n        title: page2.title,\n        wordCount: this.estimateWordCount(page2.html),\n        published: page2.publishedTime\n      }\n    };\n  }\n\n  clearCache() {\n    this.cache.clear();\n  }\n}\n\n\u002F\u002F Usage\nconst analyzer = new WebContentAnalyzer();\nawait analyzer.initialize();\n\nconst metadata = await analyzer.getPageMetadata('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Article Metadata:', metadata);\n\nconst comparison = await analyzer.comparePages(\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2'\n);\nconsole.log('Comparison:', comparison);\n```\n\n### RSS Feed Reader\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass FeedReader {\n  constructor() {\n    this.articles = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async fetchArticlesFromUrls(urls) {\n    const articles = [];\n\n    for (const url of urls) {\n      try {\n        const result = await this.zai.functions.invoke('page_reader', {\n          url: url\n        });\n\n        articles.push({\n          title: result.data.title,\n          url: result.data.url,\n          publishedTime: result.data.publishedTime,\n          content: result.data.html,\n          fetchedAt: new Date().toISOString()\n        });\n\n        console.log(`Fetched: ${result.data.title}`);\n      } catch (error) {\n        console.error(`Failed to fetch ${url}:`, error.message);\n      }\n    }\n\n    this.articles = articles;\n    return articles;\n  }\n\n  getRecentArticles(limit = 10) {\n    return this.articles\n      .sort((a, b) => {\n        const dateA = new Date(a.publishedTime || a.fetchedAt);\n        const dateB = new Date(b.publishedTime || b.fetchedAt);\n        return dateB - dateA;\n      })\n      .slice(0, limit);\n  }\n\n  searchArticles(keyword) {\n    return this.articles.filter(article => {\n      const searchText = `${article.title} ${article.content}`.toLowerCase();\n      return searchText.includes(keyword.toLowerCase());\n    });\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new FeedReader();\nawait reader.initialize();\n\nconst feedUrls = [\n  'https:\u002F\u002Fexample.com\u002Farticle1',\n  'https:\u002F\u002Fexample.com\u002Farticle2',\n  'https:\u002F\u002Fexample.com\u002Farticle3'\n];\n\nawait reader.fetchArticlesFromUrls(feedUrls);\nconst recent = reader.getRecentArticles(5);\nconsole.log('Recent articles:', recent.map(a => a.title));\n```\n\n### Content Aggregator\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function aggregateContent(urls, options = {}) {\n  const zai = await ZAI.create();\n  const aggregated = {\n    sources: [],\n    totalWords: 0,\n    aggregatedAt: new Date().toISOString()\n  };\n\n  for (const url of urls) {\n    try {\n      const result = await zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      const text = result.data.html.replace(\u002F\u003C[^>]*>\u002Fg, ' ');\n      const wordCount = text.split(\u002F\\s+\u002F).filter(w => w.length > 0).length;\n\n      aggregated.sources.push({\n        title: result.data.title,\n        url: result.data.url,\n        publishedTime: result.data.publishedTime,\n        wordCount: wordCount,\n        excerpt: text.substring(0, 200).trim() + '...'\n      });\n\n      aggregated.totalWords += wordCount;\n\n      if (options.delay) {\n        await new Promise(resolve => setTimeout(resolve, options.delay));\n      }\n    } catch (error) {\n      console.error(`Failed to fetch ${url}:`, error.message);\n    }\n  }\n\n  return aggregated;\n}\n\n\u002F\u002F Usage\nconst sources = [\n  'https:\u002F\u002Fexample.com\u002Fnews1',\n  'https:\u002F\u002Fexample.com\u002Fnews2',\n  'https:\u002F\u002Fexample.com\u002Fnews3'\n];\n\nconst aggregated = await aggregateContent(sources, { delay: 1000 });\nconsole.log(`Aggregated ${aggregated.sources.length} sources`);\nconsole.log(`Total words: ${aggregated.totalWords}`);\n```\n\n### Web Scraping Pipeline\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass ScrapingPipeline {\n  constructor() {\n    this.processors = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  addProcessor(name, processorFn) {\n    this.processors.push({ name, fn: processorFn });\n  }\n\n  async scrape(url) {\n    \u002F\u002F Fetch the page\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    let data = {\n      raw: result.data,\n      processed: {}\n    };\n\n    \u002F\u002F Run through processors\n    for (const processor of this.processors) {\n      try {\n        data.processed[processor.name] = await processor.fn(data.raw);\n        console.log(`✓ Processed with ${processor.name}`);\n      } catch (error) {\n        console.error(`✗ Failed ${processor.name}:`, error.message);\n        data.processed[processor.name] = null;\n      }\n    }\n\n    return data;\n  }\n}\n\n\u002F\u002F Processor functions\nfunction extractLinks(pageData) {\n  const linkRegex = \u002Fhref=[\"'](https?:\\\u002F\\\u002F[^\"']+)[\"']\u002Fg;\n  const links = [];\n  let match;\n\n  while ((match = linkRegex.exec(pageData.html)) !== null) {\n    links.push(match[1]);\n  }\n\n  return [...new Set(links)]; \u002F\u002F Remove duplicates\n}\n\nfunction extractImages(pageData) {\n  const imgRegex = \u002Fsrc=[\"'](https?:\\\u002F\\\u002F[^\"']+\\.(jpg|jpeg|png|gif|webp))[\"']\u002Fgi;\n  const images = [];\n  let match;\n\n  while ((match = imgRegex.exec(pageData.html)) !== null) {\n    images.push(match[1]);\n  }\n\n  return [...new Set(images)];\n}\n\nfunction extractPlainText(pageData) {\n  return pageData.html\n    .replace(\u002F\u003Cscript[^>]*>[\\s\\S]*?\u003C\\\u002Fscript>\u002Fgi, '')\n    .replace(\u002F\u003Cstyle[^>]*>[\\s\\S]*?\u003C\\\u002Fstyle>\u002Fgi, '')\n    .replace(\u002F\u003C[^>]*>\u002Fg, ' ')\n    .replace(\u002F\\s+\u002Fg, ' ')\n    .trim();\n}\n\n\u002F\u002F Usage\nconst pipeline = new ScrapingPipeline();\nawait pipeline.initialize();\n\npipeline.addProcessor('links', extractLinks);\npipeline.addProcessor('images', extractImages);\npipeline.addProcessor('plainText', extractPlainText);\n\nconst result = await pipeline.scrape('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Links found:', result.processed.links.length);\nconsole.log('Images found:', result.processed.images.length);\nconsole.log('Text length:', result.processed.plainText.length);\n```\n\n## Response Format\n\n### Successful Response\n\n```typescript\n{\n  code: 200,\n  status: 200,\n  data: {\n    title: \"Article Title\",\n    url: \"https:\u002F\u002Fexample.com\u002Farticle\",\n    html: \"\u003Cdiv>Article content...\u003C\u002Fdiv>\",\n    publishedTime: \"2025-01-15T10:30:00Z\",\n    usage: {\n      tokens: 1500\n    }\n  },\n  meta: {\n    usage: {\n      tokens: 1500\n    }\n  }\n}\n```\n\n### Response Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `code` | number | Response status code |\n| `status` | number | HTTP status code |\n| `data.title` | string | Page title |\n| `data.url` | string | Page URL |\n| `data.html` | string | Extracted HTML content |\n| `data.publishedTime` | string | Publication date (optional) |\n| `data.usage.tokens` | number | Tokens used for processing |\n| `meta.usage.tokens` | number | Total tokens used |\n\n## Best Practices\n\n### 1. Error Handling\n\n```javascript\nasync function safeReadPage(url) {\n  try {\n    const zai = await ZAI.create();\n\n    \u002F\u002F Validate URL\n    if (!url || !url.startsWith('http')) {\n      throw new Error('Invalid URL format');\n    }\n\n    const result = await zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Check response status\n    if (result.code !== 200) {\n      throw new Error(`Failed to fetch page: ${result.code}`);\n    }\n\n    \u002F\u002F Verify essential data\n    if (!result.data.html || !result.data.title) {\n      throw new Error('Incomplete page data received');\n    }\n\n    return {\n      success: true,\n      data: result.data\n    };\n  } catch (error) {\n    console.error('Page reading error:', error);\n    return {\n      success: false,\n      error: error.message\n    };\n  }\n}\n```\n\n### 2. Rate Limiting\n\n```javascript\nclass RateLimitedReader {\n  constructor(requestsPerMinute = 10) {\n    this.requestsPerMinute = requestsPerMinute;\n    this.requestTimes = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url) {\n    await this.waitForRateLimit();\n\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    this.requestTimes.push(Date.now());\n    return result.data;\n  }\n\n  async waitForRateLimit() {\n    const now = Date.now();\n    const oneMinuteAgo = now - 60000;\n\n    \u002F\u002F Remove old timestamps\n    this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);\n\n    \u002F\u002F Check if we need to wait\n    if (this.requestTimes.length >= this.requestsPerMinute) {\n      const oldestRequest = this.requestTimes[0];\n      const waitTime = 60000 - (now - oldestRequest);\n\n      if (waitTime > 0) {\n        console.log(`Rate limit reached. Waiting ${waitTime}ms...`);\n        await new Promise(resolve => setTimeout(resolve, waitTime));\n      }\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new RateLimitedReader(10); \u002F\u002F 10 requests per minute\nawait reader.initialize();\n\nconst urls = ['https:\u002F\u002Fexample.com\u002F1', 'https:\u002F\u002Fexample.com\u002F2'];\nfor (const url of urls) {\n  const data = await reader.readPage(url);\n  console.log('Fetched:', data.title);\n}\n```\n\n### 3. Caching Strategy\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass CachedWebReader {\n  constructor(cacheDuration = 3600000) { \u002F\u002F 1 hour default\n    this.cache = new Map();\n    this.cacheDuration = cacheDuration;\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  async readPage(url, forceRefresh = false) {\n    const cacheKey = url;\n    const cached = this.cache.get(cacheKey);\n\n    \u002F\u002F Return cached if valid and not forcing refresh\n    if (cached && !forceRefresh) {\n      const age = Date.now() - cached.timestamp;\n      if (age \u003C this.cacheDuration) {\n        console.log('Returning cached content for:', url);\n        return cached.data;\n      }\n    }\n\n    \u002F\u002F Fetch fresh content\n    const result = await this.zai.functions.invoke('page_reader', {\n      url: url\n    });\n\n    \u002F\u002F Update cache\n    this.cache.set(cacheKey, {\n      data: result.data,\n      timestamp: Date.now()\n    });\n\n    return result.data;\n  }\n\n  clearCache() {\n    this.cache.clear();\n  }\n\n  getCacheStats() {\n    return {\n      size: this.cache.size,\n      entries: Array.from(this.cache.keys())\n    };\n  }\n}\n\n\u002F\u002F Usage\nconst reader = new CachedWebReader(3600000); \u002F\u002F 1 hour cache\nawait reader.initialize();\n\nconst data1 = await reader.readPage('https:\u002F\u002Fexample.com'); \u002F\u002F Fresh fetch\nconst data2 = await reader.readPage('https:\u002F\u002Fexample.com'); \u002F\u002F From cache\nconst data3 = await reader.readPage('https:\u002F\u002Fexample.com', true); \u002F\u002F Force refresh\n```\n\n### 4. Parallel Processing\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nasync function readPagesInParallel(urls, concurrency = 3) {\n  const zai = await ZAI.create();\n  const results = [];\n  \n  \u002F\u002F Process in batches\n  for (let i = 0; i \u003C urls.length; i += concurrency) {\n    const batch = urls.slice(i, i + concurrency);\n    \n    const batchResults = await Promise.allSettled(\n      batch.map(url =>\n        zai.functions.invoke('page_reader', { url })\n          .then(result => ({\n            url: url,\n            success: true,\n            data: result.data\n          }))\n          .catch(error => ({\n            url: url,\n            success: false,\n            error: error.message\n          }))\n      )\n    );\n\n    results.push(...batchResults.map(r => r.value));\n    console.log(`Completed batch ${Math.floor(i \u002F concurrency) + 1}`);\n  }\n\n  return results;\n}\n\n\u002F\u002F Usage\nconst urls = [\n  'https:\u002F\u002Fexample.com\u002F1',\n  'https:\u002F\u002Fexample.com\u002F2',\n  'https:\u002F\u002Fexample.com\u002F3',\n  'https:\u002F\u002Fexample.com\u002F4',\n  'https:\u002F\u002Fexample.com\u002F5'\n];\n\nconst results = await readPagesInParallel(urls, 2); \u002F\u002F 2 concurrent requests\nresults.forEach(result => {\n  if (result.success) {\n    console.log(`✓ ${result.data.title}`);\n  } else {\n    console.log(`✗ ${result.url}: ${result.error}`);\n  }\n});\n```\n\n### 5. Content Processing\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\n\nclass ContentProcessor {\n  static extractMainContent(html) {\n    \u002F\u002F Remove scripts, styles, and comments\n    let content = html\n      .replace(\u002F\u003Cscript[^>]*>[\\s\\S]*?\u003C\\\u002Fscript>\u002Fgi, '')\n      .replace(\u002F\u003Cstyle[^>]*>[\\s\\S]*?\u003C\\\u002Fstyle>\u002Fgi, '')\n      .replace(\u002F\u003C!--[\\s\\S]*?-->\u002Fg, '');\n\n    return content;\n  }\n\n  static htmlToPlainText(html) {\n    return html\n      .replace(\u002F\u003Cbr\\s*\\\u002F?>\u002Fgi, '\\n')\n      .replace(\u002F\u003C\\\u002Fp>\u002Fgi, '\\n\\n')\n      .replace(\u002F\u003C[^>]*>\u002Fg, '')\n      .replace(\u002F&nbsp;\u002Fg, ' ')\n      .replace(\u002F&amp;\u002Fg, '&')\n      .replace(\u002F&lt;\u002Fg, '\u003C')\n      .replace(\u002F&gt;\u002Fg, '>')\n      .replace(\u002F&quot;\u002Fg, '\"')\n      .replace(\u002F\\s+\u002Fg, ' ')\n      .trim();\n  }\n\n  static extractMetadata(html) {\n    const metadata = {};\n\n    \u002F\u002F Extract meta description\n    const descMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']description[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (descMatch) metadata.description = descMatch[1];\n\n    \u002F\u002F Extract keywords\n    const keywordsMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']keywords[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (keywordsMatch) metadata.keywords = keywordsMatch[1].split(',').map(k => k.trim());\n\n    \u002F\u002F Extract author\n    const authorMatch = html.match(\u002F\u003Cmeta\\s+name=[\"']author[\"']\\s+content=[\"']([^\"']+)[\"']\u002Fi);\n    if (authorMatch) metadata.author = authorMatch[1];\n\n    return metadata;\n  }\n}\n\n\u002F\u002F Usage\nasync function processWebPage(url) {\n  const zai = await ZAI.create();\n  const result = await zai.functions.invoke('page_reader', { url });\n\n  return {\n    title: result.data.title,\n    url: result.data.url,\n    mainContent: ContentProcessor.extractMainContent(result.data.html),\n    plainText: ContentProcessor.htmlToPlainText(result.data.html),\n    metadata: ContentProcessor.extractMetadata(result.data.html),\n    publishedTime: result.data.publishedTime\n  };\n}\n\nconst processed = await processWebPage('https:\u002F\u002Fexample.com\u002Farticle');\nconsole.log('Processed content:', processed.title);\n```\n\n## Common Use Cases\n\n1. **News Aggregation**: Collect and aggregate news articles from multiple sources\n2. **Content Monitoring**: Track changes on specific web pages\n3. **Research Tools**: Extract information from academic or reference websites\n4. **Price Tracking**: Monitor product pages for price changes\n5. **SEO Analysis**: Extract page metadata and content for SEO purposes\n6. **Archive Creation**: Create local copies of web content\n7. **Content Curation**: Collect and organize web content by topic\n8. **Competitive Intelligence**: Monitor competitor websites for updates\n\n## Integration Examples\n\n### Express.js API Endpoint\n\n```javascript\nimport express from 'express';\nimport ZAI from 'z-ai-web-dev-sdk';\n\nconst app = express();\napp.use(express.json());\n\nlet zaiInstance;\n\nasync function initZAI() {\n  zaiInstance = await ZAI.create();\n}\n\napp.post('\u002Fapi\u002Fread-page', async (req, res) => {\n  try {\n    const { url } = req.body;\n\n    if (!url) {\n      return res.status(400).json({ \n        error: 'URL is required' \n      });\n    }\n\n    const result = await zaiInstance.functions.invoke('page_reader', {\n      url: url\n    });\n\n    res.json({\n      success: true,\n      data: {\n        title: result.data.title,\n        url: result.data.url,\n        content: result.data.html,\n        publishedTime: result.data.publishedTime,\n        tokensUsed: result.data.usage.tokens\n      }\n    });\n  } catch (error) {\n    res.status(500).json({\n      success: false,\n      error: error.message\n    });\n  }\n});\n\napp.post('\u002Fapi\u002Fread-multiple', async (req, res) => {\n  try {\n    const { urls } = req.body;\n\n    if (!urls || !Array.isArray(urls)) {\n      return res.status(400).json({ \n        error: 'URLs array is required' \n      });\n    }\n\n    const results = await Promise.allSettled(\n      urls.map(url =>\n        zaiInstance.functions.invoke('page_reader', { url })\n          .then(result => ({\n            url: url,\n            success: true,\n            data: result.data\n          }))\n          .catch(error => ({\n            url: url,\n            success: false,\n            error: error.message\n          }))\n      )\n    );\n\n    res.json({\n      success: true,\n      results: results.map(r => r.value)\n    });\n  } catch (error) {\n    res.status(500).json({\n      success: false,\n      error: error.message\n    });\n  }\n});\n\ninitZAI().then(() => {\n  app.listen(3000, () => {\n    console.log('Web reader API running on port 3000');\n  });\n});\n```\n\n### Scheduled Content Fetcher\n\n```javascript\nimport ZAI from 'z-ai-web-dev-sdk';\nimport cron from 'node-cron';\n\nclass ScheduledFetcher {\n  constructor() {\n    this.urls = [];\n    this.results = [];\n  }\n\n  async initialize() {\n    this.zai = await ZAI.create();\n  }\n\n  addUrl(url, schedule) {\n    this.urls.push({ url, schedule });\n  }\n\n  async fetchContent(url) {\n    try {\n      const result = await this.zai.functions.invoke('page_reader', {\n        url: url\n      });\n\n      return {\n        url: url,\n        success: true,\n        title: result.data.title,\n        content: result.data.html,\n        fetchedAt: new Date().toISOString()\n      };\n    } catch (error) {\n      return {\n        url: url,\n        success: false,\n        error: error.message,\n        fetchedAt: new Date().toISOString()\n      };\n    }\n  }\n\n  startScheduledFetch(url, schedule) {\n    cron.schedule(schedule, async () => {\n      console.log(`Fetching ${url}...`);\n      const result = await this.fetchContent(url);\n      this.results.push(result);\n      \n      \u002F\u002F Keep only last 100 results\n      if (this.results.length > 100) {\n        this.results = this.results.slice(-100);\n      }\n      \n      console.log(`Fetched: ${result.success ? result.title : result.error}`);\n    });\n  }\n\n  start() {\n    for (const { url, schedule } of this.urls) {\n      this.startScheduledFetch(url, schedule);\n    }\n  }\n\n  getResults() {\n    return this.results;\n  }\n}\n\n\u002F\u002F Usage\nconst fetcher = new ScheduledFetcher();\nawait fetcher.initialize();\n\n\u002F\u002F Fetch every hour\nfetcher.addUrl('https:\u002F\u002Fexample.com\u002Fnews', '0 * * * *');\n\n\u002F\u002F Fetch every day at midnight\nfetcher.addUrl('https:\u002F\u002Fexample.com\u002Fdaily', '0 0 * * *');\n\nfetcher.start();\nconsole.log('Scheduled fetching started');\n```\n\n## Troubleshooting\n\n**Issue**: \"SDK must be used in backend\"\n- **Solution**: Ensure z-ai-web-dev-sdk is only imported and used in server-side code\n\n**Issue**: Failed to fetch page (404, 403, etc.)\n- **Solution**: Verify the URL is accessible and not behind authentication\u002Fpaywall\n\n**Issue**: Incomplete or missing content\n- **Solution**: Some pages may have dynamic content that requires JavaScript. The reader extracts static HTML content.\n\n**Issue**: High token usage\n- **Solution**: The token usage depends on page size. Consider caching frequently accessed pages.\n\n**Issue**: Slow response times\n- **Solution**: Implement caching, use parallel processing for multiple URLs, and consider rate limiting\n\n**Issue**: Empty HTML content\n- **Solution**: Check if the page requires authentication or has anti-scraping measures. Verify the URL is correct.\n\n## Performance Tips\n\n1. **Implement caching**: Cache frequently accessed pages to reduce API calls\n2. **Use parallel processing**: Fetch multiple pages concurrently (with rate limiting)\n3. **Process content efficiently**: Extract only needed information from HTML\n4. **Set timeouts**: Implement reasonable timeouts for page fetching\n5. **Monitor token usage**: Track usage to optimize costs\n6. **Batch operations**: Group multiple URL fetches when possible\n\n## Security Considerations\n\n- Validate all URLs before processing\n- Sanitize extracted HTML content before displaying\n- Implement rate limiting to prevent abuse\n- Never expose SDK credentials in client-side code\n- Be respectful of robots.txt and website terms of service\n- Handle user data according to privacy regulations\n- Implement proper error handling for failed requests\n\n## Remember\n\n- Always use z-ai-web-dev-sdk in backend code only\n- The SDK is already installed - import as shown in examples\n- Implement proper error handling for robust applications\n- Use caching to improve performance and reduce costs\n- Respect website terms of service and rate limits\n- Process HTML content carefully to extract meaningful data\n- Monitor token usage for cost optimization\n",{"name":5,"license":15,"description":7},"main","563fd9d0d01a7240bf22073090c9484e9070e8e8","external\u002Fweb-reader",0,"https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills","https:\u002F\u002Fgithub.com\u002Fsgomez-dev","https:\u002F\u002Fsgomez.dev","\"Volnuratus non victus\"","2026-10-01 18:25:47",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add sgomez-dev\u002Fclaude-skills --skill web-reader",{"tool":69,"command":70},"bunx","bunx skills add sgomez-dev\u002Fclaude-skills --skill web-reader",{"tool":72,"command":73},"pnpm","pnpm dlx skills add sgomez-dev\u002Fclaude-skills --skill web-reader","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002FSKILL.md",true,[77,82,86,92],{"path":78,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":80,"sourceHtmlUrl":81},"LICENSE","许可证",1100,"https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002FLICENSE",{"path":83,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":84,"sourceHtmlUrl":85},"LICENSE.txt",1080,"https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002FLICENSE.txt",{"path":87,"resourceType":88,"resourceTypeDesc":89,"sizeBytes":90,"sourceHtmlUrl":91},"scripts\u002Fweb-reader.ts","SCRIPT","脚本",739,"https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002Fscripts\u002Fweb-reader.ts",{"path":93,"resourceType":94,"resourceTypeDesc":95,"sizeBytes":96,"sourceHtmlUrl":97},"UPSTREAM.md","REFERENCE","参考文档",793,"https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fweb-reader\u002FUPSTREAM.md",[99,110,118,130,140,149],{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":49},"01M6J8Q0WEP76PV381RJDTRTV4","template","YOUR_DESCRIPTION_HERE (shown in command palette, keep under 80 chars)","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Ftemplate\u002FSKILL.md",[],[101,106,107,108,109],"command palette","command","palette","keep",{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":116,"publishedAt":49},"01M6J8PY4KNE7SQP5BD2X1YMHJ","multi","Line one continues here.   Second sentence.","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fsite\u002Ftests\u002Ffixtures\u002Frepo\u002Fexternal\u002Fmulti\u002FSKILL.md",[],[112,117],"line",{"skillNo":119,"name":120,"nameZh":6,"description":121,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":122,"categories":123,"keywords":124,"publishedAt":49},"01M6J8PVS7AY0AWP36JRQ7T8S6","ffmpeg","FFmpeg recipes for video production.","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fsite\u002Ftests\u002Ffixtures\u002Frepo\u002Fexternal\u002Fffmpeg\u002FSKILL.md",[],[120,125,126,127,128,129],"Production","Video","video production","recipes","for",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":134,"categories":135,"keywords":137,"publishedAt":49},"01M6J8PS6NDT2VY6Y72QW3NGGH","secret-skill","Private, never published.","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fsite\u002Ftests\u002Ffixtures\u002Frepo\u002Fexternal\u002F.local\u002Fsecret-skill\u002FSKILL.md",[136],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[138,139],"secret skill","never",{"skillNo":141,"name":142,"nameZh":6,"description":143,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":144,"categories":145,"keywords":146,"publishedAt":49},"01M6J8PQETD1FW6BWPPSWXFH5N","external-no-license","Has no license.","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fsite\u002Ftests\u002Ffixtures\u002Frepo\u002Fbroken\u002Fexternal-no-license\u002FSKILL.md",[],[147,148],"license","external no license",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":164,"publishedAt":49},"01M6J8PJ5XGMZZZZGECEDHN13J","write-swift","How to write modern Swift well — modeling with value types, Swift 6 data-race safety and approachable concurrency (@concurrent, main-actor-by-default, actors, task groups), protocols and generics (some vs any), API design, performance and ARC, Swift Testing, macros, and the modern language features agents don't know about yet. Use when writing, reviewing, or migrating Swift, or when a concurrency error, a hang, a data race, a retain cycle, or a performance problem needs fixing.","https:\u002F\u002Fgithub.com\u002Fsgomez-dev\u002Fclaude-skills\u002Fblob\u002Fmain\u002Fexternal\u002Fwrite-swift\u002FSKILL.md",[155,158,161],{"categoryNo":156,"categoryName":157,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":159,"categoryName":160,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":162,"categoryName":163,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000030","开发工程师",[165,166,167,168,169,170,171,172,173,174,175,176,54,177,178,179,180,181,182,183],"swift","write","write swift","features","retain","testing","types","error","how to","problem","modeling","cycle","default","how","api","concurrency","performance","writing","design"]