Tmux
trpc-group/trpc-agent-go
Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
Implement web page content extraction capabilities using the z-ai-web-dev-sdk.
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jjyaoao/HelloAgents web-reader --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/web-reader .claude/skills/web-reader && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .claude/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-readerType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jjyaoao/HelloAgents web-reader --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/web-reader .agents/skills/web-reader && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .agents/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jjyaoao/HelloAgents web-reader --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/web-reader .cursor/skills/web-reader && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .cursor/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/jjyaoao/HelloAgents.git --path skills/web-reader--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jjyaoao/HelloAgents web-reader --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/web-reader .gemini/skills/web-reader && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .gemini/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jjyaoao/HelloAgents web-readerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/web-reader .github/skills/web-reader && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .github/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jjyaoao/HelloAgents --skill web-reader -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jjyaoao/HelloAgents web-reader --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jjyaoao/HelloAgents.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/web-reader .opencode/skills/web-reader && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "web-reader" agent skill from https://github.com/jjyaoao/HelloAgents/tree/main/skills/web-reader into .opencode/skills/web-reader/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web-reader", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
web-readerImplement web page content extraction capabilities using the z-ai-web-dev-sdk.
Web Reader is an agent skill from jjyaoao/HelloAgents. 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.
Its SKILL.md is about 7.1k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including scripts (for example `scripts/web-reader.ts`).
It sits in Data & Analytics, covering Web scraping. It works with Zhipu GLM. The repository describes itself as: A agent framework based on the tutorial hello-agents. The licence is MIT.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit c597a7c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/ (TypeScript), which the agent can run.
From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
techblog.comresearch.orgsite1.comsite2.comsite3.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Web Reader loads about 7.1k tokens when it runs. Until then it costs about 83 tokens; SKILL.md has 794 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from jjyaoao/HelloAgents at commit c597a7c, republished under its MIT licence (© jjyaoao). 794 words, ~7,089 tokens.
.claude/skills/web-reader/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.This 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.
Skill Location: {project_path}/skills/web-reader
This skill is located at the above path in your project.
Reference Scripts: Example test scripts are available in the {Skill Location}/scripts/ directory for quick testing and reference. See {Skill Location}/scripts/web-reader.ts for a working example.
Web 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.
IMPORTANT: z-ai-web-dev-sdk MUST be used in backend code only. Never use it in client-side code.
The z-ai-web-dev-sdk package is already installed. Import it as shown in the examples below.
For 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.
# Extract content from a web page
z-ai function --name "page_reader" --args '{"url": "https://example.com"}'
# Using short options
z-ai function -n page_reader -a '{"url": "https://www.example.com/article"}'# Save extracted content to JSON file
z-ai function \
-n page_reader \
-a '{"url": "https://news.example.com/article"}' \
-o page_content.json
# Extract and save blog post
z-ai function \
-n page_reader \
-a '{"url": "https://blog.example.com/post/123"}' \
-o blog_post.json# Extract news article
z-ai function \
-n page_reader \
-a '{"url": "https://news.site.com/breaking-news"}' \
-o news.json
# Read documentation page
z-ai function \
-n page_reader \
-a '{"url": "https://docs.example.com/getting-started"}' \
-o docs.json
# Scrape blog content
z-ai function \
-n page_reader \
-a '{"url": "https://techblog.com/ai-trends-2024"}' \
-o blog.json
# Extract research article
z-ai function \
-n page_reader \
-a '{"url": "https://research.org/papers/quantum-computing"}' \
-o research.json--name, -n: Required - Function name (use "page_reader")--args, -a: Required - JSON arguments object with:url (string, required): The URL of the web page to read--output, -o <path>: Optional - Output file path (JSON format)The CLI returns a JSON object containing:
title: Page titlehtml: Main content HTMLtext: Plain text contentpublish_time: Publication timestamp (if available)url: Original URLmetadata: Additional page metadata{
"title": "Introduction to Machine Learning",
"html": "<article><h1>Introduction to Machine Learning</h1><p>Machine learning is...</p></article>",
"text": "Introduction to Machine Learning\n\nMachine learning is...",
"publish_time": "2024-01-15T10:30:00Z",
"url": "https://example.com/ml-intro",
"metadata": {
"author": "John Doe",
"description": "A comprehensive guide to ML"
}
}# Create a simple script to process multiple URLs
for url in \
"https://site1.com/article1" \
"https://site2.com/article2" \
"https://site3.com/article3"
do
filename=$(echo $url | md5sum | cut -d' ' -f1)
z-ai function -n page_reader -a "{\"url\": \"$url\"}" -o "${filename}.json"
doneUse CLI for:
Use SDK for:
The Web Reader uses the page_reader function to:
import ZAI from 'z-ai-web-dev-sdk';
async function readWebPage(url) {
try {
const zai = await ZAI.create();
const result = await zai.functions.invoke('page_reader', {
url: url
});
console.log('Title:', result.data.title);
console.log('URL:', result.data.url);
console.log('Published:', result.data.publishedTime);
console.log('HTML Content:', result.data.html);
console.log('Tokens Used:', result.data.usage.tokens);
return result.data;
} catch (error) {
console.error('Page reading failed:', error.message);
throw error;
}
}
// Usage
const pageData = await readWebPage('https://example.com/article');
console.log('Page title:', pageData.title);import ZAI from 'z-ai-web-dev-sdk';
async function extractArticleText(url) {
const zai = await ZAI.create();
const result = await zai.functions.invoke('page_reader', {
url: url
});
// Convert HTML to plain text (basic approach)
const plainText = result.data.html
.replace(/<[^>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
return {
title: result.data.title,
text: plainText,
url: result.data.url,
publishedTime: result.data.publishedTime
};
}
// Usage
const article = await extractArticleText('https://news.example.com/story');
console.log(article.title);
console.log(article.text.substring(0, 200) + '...');import ZAI from 'z-ai-web-dev-sdk';
async function readMultiplePages(urls) {
const zai = await ZAI.create();
const results = [];
for (const url of urls) {
try {
const result = await zai.functions.invoke('page_reader', {
url: url
});
results.push({
url: url,
success: true,
data: result.data
});
} catch (error) {
results.push({
url: url,
success: false,
error: error.message
});
}
}
return results;
}
// Usage
const urls = [
'https://example.com/article1',
'https://example.com/article2',
'https://example.com/article3'
];
const pages = await readMultiplePages(urls);
pages.forEach(page => {
if (page.success) {
console.log(`✓ ${page.data.title}`);
} else {
console.log(`✗ ${page.url}: ${page.error}`);
}
});import ZAI from 'z-ai-web-dev-sdk';
class WebContentAnalyzer {
constructor() {
this.cache = new Map();
}
async initialize() {
this.zai = await ZAI.create();
}
async readPage(url, useCache = true) {
// Check cache
if (useCache && this.cache.has(url)) {
console.log('Returning cached result for:', url);
return this.cache.get(url);
}
// Fetch fresh content
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
// Cache the result
if (useCache) {
this.cache.set(url, result.data);
}
return result.data;
}
async getPageMetadata(url) {
const data = await this.readPage(url);
return {
title: data.title,
url: data.url,
publishedTime: data.publishedTime,
contentLength: data.html.length,
wordCount: this.estimateWordCount(data.html)
};
}
estimateWordCount(html) {
const text = html.replace(/<[^>]*>/g, ' ');
const words = text.split(/\s+/).filter(word => word.length > 0);
return words.length;
}
async comparePages(url1, url2) {
const [page1, page2] = await Promise.all([
this.readPage(url1),
this.readPage(url2)
]);
return {
page1: {
title: page1.title,
wordCount: this.estimateWordCount(page1.html),
published: page1.publishedTime
},
page2: {
title: page2.title,
wordCount: this.estimateWordCount(page2.html),
published: page2.publishedTime
}
};
}
clearCache() {
this.cache.clear();
}
}
// Usage
const analyzer = new WebContentAnalyzer();
await analyzer.initialize();
const metadata = await analyzer.getPageMetadata('https://example.com/article');
console.log('Article Metadata:', metadata);
const comparison = await analyzer.comparePages(
'https://example.com/article1',
'https://example.com/article2'
);
console.log('Comparison:', comparison);import ZAI from 'z-ai-web-dev-sdk';
class FeedReader {
constructor() {
this.articles = [];
}
async initialize() {
this.zai = await ZAI.create();
}
async fetchArticlesFromUrls(urls) {
const articles = [];
for (const url of urls) {
try {
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
articles.push({
title: result.data.title,
url: result.data.url,
publishedTime: result.data.publishedTime,
content: result.data.html,
fetchedAt: new Date().toISOString()
});
console.log(`Fetched: ${result.data.title}`);
} catch (error) {
console.error(`Failed to fetch ${url}:`, error.message);
}
}
this.articles = articles;
return articles;
}
getRecentArticles(limit = 10) {
return this.articles
.sort((a, b) => {
const dateA = new Date(a.publishedTime || a.fetchedAt);
const dateB = new Date(b.publishedTime || b.fetchedAt);
return dateB - dateA;
})
.slice(0, limit);
}
searchArticles(keyword) {
return this.articles.filter(article => {
const searchText = `${article.title} ${article.content}`.toLowerCase();
return searchText.includes(keyword.toLowerCase());
});
}
}
// Usage
const reader = new FeedReader();
await reader.initialize();
const feedUrls = [
'https://example.com/article1',
'https://example.com/article2',
'https://example.com/article3'
];
await reader.fetchArticlesFromUrls(feedUrls);
const recent = reader.getRecentArticles(5);
console.log('Recent articles:', recent.map(a => a.title));import ZAI from 'z-ai-web-dev-sdk';
async function aggregateContent(urls, options = {}) {
const zai = await ZAI.create();
const aggregated = {
sources: [],
totalWords: 0,
aggregatedAt: new Date().toISOString()
};
for (const url of urls) {
try {
const result = await zai.functions.invoke('page_reader', {
url: url
});
const text = result.data.html.replace(/<[^>]*>/g, ' ');
const wordCount = text.split(/\s+/).filter(w => w.length > 0).length;
aggregated.sources.push({
title: result.data.title,
url: result.data.url,
publishedTime: result.data.publishedTime,
wordCount: wordCount,
excerpt: text.substring(0, 200).trim() + '...'
});
aggregated.totalWords += wordCount;
if (options.delay) {
await new Promise(resolve => setTimeout(resolve, options.delay));
}
} catch (error) {
console.error(`Failed to fetch ${url}:`, error.message);
}
}
return aggregated;
}
// Usage
const sources = [
'https://example.com/news1',
'https://example.com/news2',
'https://example.com/news3'
];
const aggregated = await aggregateContent(sources, { delay: 1000 });
console.log(`Aggregated ${aggregated.sources.length} sources`);
console.log(`Total words: ${aggregated.totalWords}`);import ZAI from 'z-ai-web-dev-sdk';
class ScrapingPipeline {
constructor() {
this.processors = [];
}
async initialize() {
this.zai = await ZAI.create();
}
addProcessor(name, processorFn) {
this.processors.push({ name, fn: processorFn });
}
async scrape(url) {
// Fetch the page
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
let data = {
raw: result.data,
processed: {}
};
// Run through processors
for (const processor of this.processors) {
try {
data.processed[processor.name] = await processor.fn(data.raw);
console.log(`✓ Processed with ${processor.name}`);
} catch (error) {
console.error(`✗ Failed ${processor.name}:`, error.message);
data.processed[processor.name] = null;
}
}
return data;
}
}
// Processor functions
function extractLinks(pageData) {
const linkRegex = /href=["'](https?:\/\/[^"']+)["']/g;
const links = [];
let match;
while ((match = linkRegex.exec(pageData.html)) !== null) {
links.push(match[1]);
}
return [...new Set(links)]; // Remove duplicates
}
function extractImages(pageData) {
const imgRegex = /src=["'](https?:\/\/[^"']+\.(jpg|jpeg|png|gif|webp))["']/gi;
const images = [];
let match;
while ((match = imgRegex.exec(pageData.html)) !== null) {
images.push(match[1]);
}
return [...new Set(images)];
}
function extractPlainText(pageData) {
return pageData.html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]*>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
// Usage
const pipeline = new ScrapingPipeline();
await pipeline.initialize();
pipeline.addProcessor('links', extractLinks);
pipeline.addProcessor('images', extractImages);
pipeline.addProcessor('plainText', extractPlainText);
const result = await pipeline.scrape('https://example.com/article');
console.log('Links found:', result.processed.links.length);
console.log('Images found:', result.processed.images.length);
console.log('Text length:', result.processed.plainText.length);{
code: 200,
status: 200,
data: {
title: "Article Title",
url: "https://example.com/article",
html: "<div>Article content...</div>",
publishedTime: "2025-01-15T10:30:00Z",
usage: {
tokens: 1500
}
},
meta: {
usage: {
tokens: 1500
}
}
}| Field | Type | Description |
|---|---|---|
code | number | Response status code |
status | number | HTTP status code |
data.title | string | Page title |
data.url | string | Page URL |
data.html | string | Extracted HTML content |
data.publishedTime | string | Publication date (optional) |
data.usage.tokens | number | Tokens used for processing |
meta.usage.tokens | number | Total tokens used |
async function safeReadPage(url) {
try {
const zai = await ZAI.create();
// Validate URL
if (!url || !url.startsWith('http')) {
throw new Error('Invalid URL format');
}
const result = await zai.functions.invoke('page_reader', {
url: url
});
// Check response status
if (result.code !== 200) {
throw new Error(`Failed to fetch page: ${result.code}`);
}
// Verify essential data
if (!result.data.html || !result.data.title) {
throw new Error('Incomplete page data received');
}
return {
success: true,
data: result.data
};
} catch (error) {
console.error('Page reading error:', error);
return {
success: false,
error: error.message
};
}
}class RateLimitedReader {
constructor(requestsPerMinute = 10) {
this.requestsPerMinute = requestsPerMinute;
this.requestTimes = [];
}
async initialize() {
this.zai = await ZAI.create();
}
async readPage(url) {
await this.waitForRateLimit();
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
this.requestTimes.push(Date.now());
return result.data;
}
async waitForRateLimit() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove old timestamps
this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);
// Check if we need to wait
if (this.requestTimes.length >= this.requestsPerMinute) {
const oldestRequest = this.requestTimes[0];
const waitTime = 60000 - (now - oldestRequest);
if (waitTime > 0) {
console.log(`Rate limit reached. Waiting ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}
}
// Usage
const reader = new RateLimitedReader(10); // 10 requests per minute
await reader.initialize();
const urls = ['https://example.com/1', 'https://example.com/2'];
for (const url of urls) {
const data = await reader.readPage(url);
console.log('Fetched:', data.title);
}import ZAI from 'z-ai-web-dev-sdk';
class CachedWebReader {
constructor(cacheDuration = 3600000) { // 1 hour default
this.cache = new Map();
this.cacheDuration = cacheDuration;
}
async initialize() {
this.zai = await ZAI.create();
}
async readPage(url, forceRefresh = false) {
const cacheKey = url;
const cached = this.cache.get(cacheKey);
// Return cached if valid and not forcing refresh
if (cached && !forceRefresh) {
const age = Date.now() - cached.timestamp;
if (age < this.cacheDuration) {
console.log('Returning cached content for:', url);
return cached.data;
}
}
// Fetch fresh content
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
// Update cache
this.cache.set(cacheKey, {
data: result.data,
timestamp: Date.now()
});
return result.data;
}
clearCache() {
this.cache.clear();
}
getCacheStats() {
return {
size: this.cache.size,
entries: Array.from(this.cache.keys())
};
}
}
// Usage
const reader = new CachedWebReader(3600000); // 1 hour cache
await reader.initialize();
const data1 = await reader.readPage('https://example.com'); // Fresh fetch
const data2 = await reader.readPage('https://example.com'); // From cache
const data3 = await reader.readPage('https://example.com', true); // Force refreshimport ZAI from 'z-ai-web-dev-sdk';
async function readPagesInParallel(urls, concurrency = 3) {
const zai = await ZAI.create();
const results = [];
// Process in batches
for (let i = 0; i < urls.length; i += concurrency) {
const batch = urls.slice(i, i + concurrency);
const batchResults = await Promise.allSettled(
batch.map(url =>
zai.functions.invoke('page_reader', { url })
.then(result => ({
url: url,
success: true,
data: result.data
}))
.catch(error => ({
url: url,
success: false,
error: error.message
}))
)
);
results.push(...batchResults.map(r => r.value));
console.log(`Completed batch ${Math.floor(i / concurrency) + 1}`);
}
return results;
}
// Usage
const urls = [
'https://example.com/1',
'https://example.com/2',
'https://example.com/3',
'https://example.com/4',
'https://example.com/5'
];
const results = await readPagesInParallel(urls, 2); // 2 concurrent requests
results.forEach(result => {
if (result.success) {
console.log(`✓ ${result.data.title}`);
} else {
console.log(`✗ ${result.url}: ${result.error}`);
}
});import ZAI from 'z-ai-web-dev-sdk';
class ContentProcessor {
static extractMainContent(html) {
// Remove scripts, styles, and comments
let content = html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<!--[\s\S]*?-->/g, '');
return content;
}
static htmlToPlainText(html) {
return html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>/gi, '\n\n')
.replace(/<[^>]*>/g, '')
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/\s+/g, ' ')
.trim();
}
static extractMetadata(html) {
const metadata = {};
// Extract meta description
const descMatch = html.match(/<meta\s+name=["']description["']\s+content=["']([^"']+)["']/i);
if (descMatch) metadata.description = descMatch[1];
// Extract keywords
const keywordsMatch = html.match(/<meta\s+name=["']keywords["']\s+content=["']([^"']+)["']/i);
if (keywordsMatch) metadata.keywords = keywordsMatch[1].split(',').map(k => k.trim());
// Extract author
const authorMatch = html.match(/<meta\s+name=["']author["']\s+content=["']([^"']+)["']/i);
if (authorMatch) metadata.author = authorMatch[1];
return metadata;
}
}
// Usage
async function processWebPage(url) {
const zai = await ZAI.create();
const result = await zai.functions.invoke('page_reader', { url });
return {
title: result.data.title,
url: result.data.url,
mainContent: ContentProcessor.extractMainContent(result.data.html),
plainText: ContentProcessor.htmlToPlainText(result.data.html),
metadata: ContentProcessor.extractMetadata(result.data.html),
publishedTime: result.data.publishedTime
};
}
const processed = await processWebPage('https://example.com/article');
console.log('Processed content:', processed.title);import express from 'express';
import ZAI from 'z-ai-web-dev-sdk';
const app = express();
app.use(express.json());
let zaiInstance;
async function initZAI() {
zaiInstance = await ZAI.create();
}
app.post('/api/read-page', async (req, res) => {
try {
const { url } = req.body;
if (!url) {
return res.status(400).json({
error: 'URL is required'
});
}
const result = await zaiInstance.functions.invoke('page_reader', {
url: url
});
res.json({
success: true,
data: {
title: result.data.title,
url: result.data.url,
content: result.data.html,
publishedTime: result.data.publishedTime,
tokensUsed: result.data.usage.tokens
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
app.post('/api/read-multiple', async (req, res) => {
try {
const { urls } = req.body;
if (!urls || !Array.isArray(urls)) {
return res.status(400).json({
error: 'URLs array is required'
});
}
const results = await Promise.allSettled(
urls.map(url =>
zaiInstance.functions.invoke('page_reader', { url })
.then(result => ({
url: url,
success: true,
data: result.data
}))
.catch(error => ({
url: url,
success: false,
error: error.message
}))
)
);
res.json({
success: true,
results: results.map(r => r.value)
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
initZAI().then(() => {
app.listen(3000, () => {
console.log('Web reader API running on port 3000');
});
});import ZAI from 'z-ai-web-dev-sdk';
import cron from 'node-cron';
class ScheduledFetcher {
constructor() {
this.urls = [];
this.results = [];
}
async initialize() {
this.zai = await ZAI.create();
}
addUrl(url, schedule) {
this.urls.push({ url, schedule });
}
async fetchContent(url) {
try {
const result = await this.zai.functions.invoke('page_reader', {
url: url
});
return {
url: url,
success: true,
title: result.data.title,
content: result.data.html,
fetchedAt: new Date().toISOString()
};
} catch (error) {
return {
url: url,
success: false,
error: error.message,
fetchedAt: new Date().toISOString()
};
}
}
startScheduledFetch(url, schedule) {
cron.schedule(schedule, async () => {
console.log(`Fetching ${url}...`);
const result = await this.fetchContent(url);
this.results.push(result);
// Keep only last 100 results
if (this.results.length > 100) {
this.results = this.results.slice(-100);
}
console.log(`Fetched: ${result.success ? result.title : result.error}`);
});
}
start() {
for (const { url, schedule } of this.urls) {
this.startScheduledFetch(url, schedule);
}
}
getResults() {
return this.results;
}
}
// Usage
const fetcher = new ScheduledFetcher();
await fetcher.initialize();
// Fetch every hour
fetcher.addUrl('https://example.com/news', '0 * * * *');
// Fetch every day at midnight
fetcher.addUrl('https://example.com/daily', '0 0 * * *');
fetcher.start();
console.log('Scheduled fetching started');Issue: "SDK must be used in backend"
Issue: Failed to fetch page (404, 403, etc.)
Issue: Incomplete or missing content
Issue: High token usage
Issue: Slow response times
Issue: Empty HTML content
© jjyaoao, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 2 other files (scripts) in skills/web-reader of jjyaoao/HelloAgents.
Open the folder on GitHubat commit c597a7c
We found 1 copy of this SKILL.md (exact, near-identical or edited) in other folders, from 1 other GitHub owner. This page covers the copy in jjyaoao/HelloAgents, which our catalogue first saw on October 7, 2026.
Web Reader next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Web Reader this skilljjyaoao/HelloAgents | 3.2k | 1 repos | ~7.1k | Automated safety check: Pass | MIT | |
| Tmuxtrpc-group/trpc-agent-go | 1.8k | 24 repos | ~868 | Automated safety check: Pass | Apache-2.0 | |
| Ketch1broseidon/ketch | 697 | 1 repos | ~3.9k | Automated safety check: Pass | MIT | |
| Crawl4AI Web Scrapingsmallnest/goclaw | 598 | 1 repos | ~2.5k | Automated safety check: Pass | MIT | |
| Boss Zhipin Scrapereatmoreduck/boss-zhipin-scraper | 1.5k | — | ~2.6k | Automated safety check: Pass | MIT | |
| Axyusukebe/ax | 719 | 1 repos | ~918 | Automated safety check: Pass | MIT |
trpc-group/trpc-agent-go
Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
1broseidon/ketch
Research skill for ketch — a fast stateless CLI for web search, OSS code search, curated library docs, page scraping, and site crawling; an optional MCP server exists for operators who want it, but…
smallnest/goclaw
Scrapes sites, handles JavaScript-heavy pages and extracts structured data with Crawl4AI, through its crwl CLI or Python SDK, including schema-based extraction without an LLM.
eatmoreduck/boss-zhipin-scraper
Scrape BOSS直聘 (job listing site) via Chrome CDP. An agent skill from eatmoreduck/boss-zhipin-scraper.
yusukebe/ax
Use the ax CLI instead of curl + throwaway parsing scripts whenever you fetch a URL, explore an unknown web page, or extract structured data from HTML.
Anakin-Inc/anakin
Scrape any website into clean markdown or structured JSON. An agent skill from Anakin-Inc/anakin.
jjyaoao/HelloAgents
Implement AI image generation capabilities using the z-ai-web-dev-sdk.
jjyaoao/HelloAgents
Implement specialized video understanding capabilities using the z-ai-web-dev-sdk.
jjyaoao/HelloAgents
Implement web search capabilities using the z-ai-web-dev-sdk.
jjyaoao/HelloAgents
Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction.
jjyaoao/HelloAgents
The PRIMARY tool for Spring Festival gift analysis and social interaction generation.
jjyaoao/HelloAgents
Transform UI style requirements into production-ready frontend code with systematic design tokens, accessibility compliance, and creative execution.
Works with
Categories
Implement web page content extraction capabilities using the z-ai-web-dev-sdk. Web Reader is an agent skill from jjyaoao/HelloAgents. Implement web page content extraction capabilities using the z-ai-web-dev-sdk.
Web Reader fits situations like: the user needs to scrape web pages; extract article content; retrieve page metadata; build applications that process web content.
Run `npx skills add jjyaoao/HelloAgents --skill web-reader -a claude-code`. Or copy the skill folder (skills/web-reader in jjyaoao/HelloAgents) into .claude/skills/web-reader in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jjyaoao/HelloAgents --skill web-reader -a codex`. Or copy the skill folder (skills/web-reader in jjyaoao/HelloAgents) into .agents/skills/web-reader in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jjyaoao/HelloAgents --skill web-reader -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/web-reader, .gemini/skills/web-reader, .github/skills/web-reader and .opencode/skills/web-reader in your project.
Going by SKILL.md and its folder, Web Reader needs TypeScript for the scripts in its folder. Our summary lists: Node.js.
SKILL.md names 5 domains. In commands or code: techblog.com, research.org, site1.com, site2.com and site3.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Web Reader is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.1k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Web Reader: Tmux (trpc-group/trpc-agent-go, 1.8k stars), Ketch (1broseidon/ketch, 697 stars), Crawl4AI Web Scraping (smallnest/goclaw, 598 stars) and Boss Zhipin Scraper (eatmoreduck/boss-zhipin-scraper, 1.5k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jjyaoao (a GitHub user) maintains it in jjyaoao/HelloAgents, which has 3,167 GitHub stars. The repository holds 11 skills in this directory. The repository was last updated on October 6, 2026.
Source: jjyaoao/HelloAgents on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.