npx skills add ...
npx skills add marswaveai/skills --skill content-parser
Extract and parse content from URLs. Triggers on: user provides a URL to extract content from, another skill needs to parse source material, "parse this URL", "extract content", "解析链接", "提取内容".
npx skills add marswaveai/skills --skill content-parser
Extract and normalize content from URLs across supported platforms. Returns structured data including content body, metadata, and references. Useful as a preprocessing step for content generation skills or standalone content extraction.
shared/config-pattern.md before any interaction~/Downloads/ or .listenhub/ — save to the current working directoryFollow shared/config-pattern.md § API Key Check. If the key is missing, stop immediately.
Follow shared/config-pattern.md Step 0 (Zero-Question Boot).
If file doesn't exist — silently create with defaults and proceed:
Do NOT ask any setup questions. Proceed directly to the Interaction Flow.
If file exists — read config silently and proceed:
Only run when the user explicitly asks to reconfigure. Display current settings:
Then ask:
autoDownload: trueautoDownload: falseSave immediately:
Free text input. Ask the user:
What URL would you like to extract content from?
Ask if the user wants to configure extraction options:
If "Yes", ask follow-up questions:
Summarize:
Wait for explicit confirmation before calling the API.
Validate URL: Must be HTTP(S). Normalize if needed (see references/supported-platforms.md)
Build request body:
Omit options if user chose defaults.
Submit (foreground): POST /v1/content/extract → extract taskId
Tell the user extraction is in progress
Poll (background): Run the following exact bash command with run_in_background: true and timeout: 300000. Note: status field is .data.status (not processStatus), interval is 5s, values are processing/completed/failed:
When notified, download and present result:
If autoDownload is true, generate a slug from the extracted title (falling back to domain name if no title). Follow shared/config-pattern.md § Artifact Naming for slug generation and dedup.
{slug}.md to the current directory — full extracted content in markdown{slug}.json to the current directory — full raw API response dataPresent:
Show a preview of the extracted content (first ~500 chars)
Offer to use content in another skill (e.g. /podcast, /tts)
Estimated time: 10-30 seconds depending on content size and platform.
Environment variable: LISTENHUB_API_KEY (format: lh_sk_...)
Store in ~/.zshrc (macOS) or ~/.bashrc (Linux):
How to obtain: Visit https://listenhub.ai/settings/api-keys (Pro plan required).
Base URL: https://api.marswave.ai/openapi/v1
Required headers (every request):
The X-Source: skills header identifies requests as coming from Claude Code skills (CLI tool).
curl template:
For GET requests, omit -d and change -X POST to -X GET.
Security notes:
Create a content extraction task for a URL. Returns a taskId for polling.
Request body:
| Field | Required | Type | Description |
|---|---|---|---|
| source | Yes | object | Source to extract from |
| source.type | Yes | string | Must be "url" |
| source.uri | Yes | string | Valid HTTP(S) URL to extract content from |
| options | No | object | Extraction options |
| options.summarize | No | boolean | Whether to generate a summary |
| options.maxLength | No | integer | Maximum content length |
| options.twitter | No | object | Twitter/X specific options |
| options.twitter.count | No | integer | Number of tweets to fetch (1-100, default 20) |
Response:
Error codes:
| Code | Meaning |
|---|---|
| 29003 | Validation error ("source.uri" is required, "source.uri" must be a valid uri) |
| 21007 | Invalid API key |
Get extraction task status and results.
Path params:
| Param | Type | Description |
|---|---|---|
| taskId | string | 24-char hex task ID |
Response states:
failCode and messageResponse (processing):
Response (completed):
Response (failed):
Key fields:
| Field | Type | Description |
|---|---|---|
| status | string | processing, completed, or failed |
| data.data.content | string | Extracted text content |
| data.data.metadata | object | Page metadata (title, author, publishedAt) |
| data.data.references | array | Referenced URLs (array of strings) |
| credits | integer | Credits consumed |
| failCode | string | Error code (null on success) |
| message | string | Error message (null on success) |
Error codes:
| Code | Meaning |
|---|---|
| 29003 | Invalid taskId format |
| 25002 | Task not found |
5-second interval, 60 polls max. Run with run_in_background: true and timeout: 300000.
Two-step pattern:
taskId from the response.run_in_background: true. You will be notified automatically when it completes.The exact polling bash command is already specified in the Workflow section (Step 5).
HTTP status codes:
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Parse response body |
| 400 | Bad request | Check parameters |
| 401 | Invalid API key | Re-check LISTENHUB_API_KEY |
| 402 | Insufficient credits | Inform user to recharge |
| 403 | Forbidden | No permission for this resource |
| 429 | Rate limited | Exponential backoff, retry after delay |
| 500/502/503/504 | Server error | Retry up to 3 times |
Retry strategy:
Application error codes:
| Code | Meaning |
|---|---|
| 21007 | Invalid user API key |
| 25429 | Rate limited (application-level) |
User: "Parse this article: https://en.wikipedia.org/wiki/Topology"
Agent workflow:
https://en.wikipedia.org/wiki/TopologyUser: "Extract recent tweets from @elonmusk, get 50 tweets"
Agent workflow:
https://x.com/elonmusk{"twitter": {"count": 50}}CONFIG_PATH=".listenhub/content-parser/config.json"
[ ! -f "$CONFIG_PATH" ] && CONFIG_PATH="$HOME/.listenhub/content-parser/config.json"
CONFIG=$(cat "$CONFIG_PATH")当前配置 (content-parser):
自动下载:{是 / 否}NEW_CONFIG=$(echo "$CONFIG" | jq --argjson dl {true/false} '. + {"autoDownload": $dl}')
echo "$NEW_CONFIG" > "$CONFIG_PATH"
CONFIG=$(cat "$CONFIG_PATH")Question: "Do you want to configure extraction options?"
Options:
- "No, use defaults" — Extract with default settings
- "Yes, configure options" — Set summarize, maxLength, or Twitter tweet countReady to extract content:
URL: {url}
Options: {summarize: true, maxLength: 5000, twitter.count: 50} / default
Proceed?