{"id":"7e98a997-fa13-480f-a48f-1d2ec1a341a7","entityType":"agent","slug":"clawhub-hermiod99-vibe-agent-social-reader","name":"Agent Social Reader","canonicalUrl":"https://www.xpersona.co/agent/clawhub-hermiod99-vibe-agent-social-reader","canonicalPath":"/agent/clawhub-hermiod99-vibe-agent-social-reader","generatedAt":"2026-10-12T09:57:41.799Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":null},"description":"Empower your AI agent to read and summarize specific public web or social media links from  TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 979 downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s178kw7jyc12a88r6yr75atxa189wwvf:agent-social-reader","sourceUrl":"https://clawhub.ai/hermiod99-vibe/agent-social-reader","homepage":"https://clawhub.ai/hermiod99-vibe/skills/agent-social-reader","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/hermiod99-vibe/agent-social-reader","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/hermiod99-vibe/skills/agent-social-reader","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Agent Social Reader technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":null},"stars":null,"forks":null,"downloads":979,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"2.0.1","tractionLabel":"979 downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T23:07:55.943Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T23:07:56.015Z","lastCrawledAt":"2026-10-11T23:07:55.943Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T23:07:55.943Z","lastVerifiedAt":null,"highlights":[{"version":"2.0.1","createdAt":"2026-07-14T09:37:09.386Z","changelog":"**agent-social-reader 2.0.1** - Changed: Skill now only acts when the user explicitly asks to read, summarize, analyze, or save a specific public URL; no background detection or auto-triggering. - Clarified: Will only save, archive, or export content to Notion, Obsidian, or ima if the active user request includes that exact destination. - Security: Emphasized not to enumerate arbitrary environment variables or user home directories; only reference explicit variables and config paths. - User privacy: Local config file is used only if the user explicitly approves plaintext storage, and never for arbitrary credentials. - Temp media: Cleanup now always previews matching files before deletion, and only deletes after the user confirms. - Docs: Removed general-purpose triggers and shortened the skill summary to reflect strict \"user-initiated only\" operations. Removed the `skill-card.md` file.","fileCount":6,"zipByteSize":32470},{"version":"2.0.0","createdAt":"2026-07-14T08:31:13.888Z","changelog":"### 🏗️ Structural Change: Document Split v1 put everything in a single `SKILL.md` file. v2 reorganizes into three focused files: | File | Content | Purpose | | ----------------- | ------------------------------------------------------------ | -------------------- | | `SKILL.md` | Routing map, SOPs, config lookup, Save section summaries | Runtime reference | | `tool-details.md` | Tool-specific code snippets (API formats, fallback strategies, error handling) | Tool spec reference | | `save-scripts.md` | Complete runnable scripts for Notion, Obsidian, and ima | Copy-paste execution | **Save section handling:** - v1: Notion and Obsidian scripts were embedded directly in SKILL.md - v2: SKILL.md retains only the overview (trigger conditions, credentials, archive format, success feedback); full scripts moved to `save-scripts.md` - ima (new): SKILL.md covers the full three-scene flow; scripts in `save-scripts.md` --- ### 🆕 New Features #### 1. Save to ima (Tencent ima Knowledge Base) ima is Tencent's cloud knowledge base, accessed via OpenAPI. v2 adds it as a third archive destination alongside Notion and Obsidian. This includes all ima-related improvements from this release: - Three-scene detection logic (ima native skills / WorkBuddy / external Agent) - Configuration and write paths per scene - Standard archive format (consistent across all three destinations) - Native ima skills prioritized in routing map over OpenAPI - Env var to HTTP header/body mapping explicitly documented - COS upload example labeled as reference (official SDK recommended) **SKILL.md chapters:** - Three-scene detection logic (ima native skills / WorkBuddy / external Agent) - Configuration and write paths per scene - Standard archive format (consistent across all three destinations) **save-scripts.md scripts:** - `detect_ima_scene()` - runtime scene detection - `_get_ima_credentials()` - reads credentials from env / config files - `_get_ima_kb_id()` - KB ID retrieval (includes API query path) - `_ima_api_request()` - OpenAPI request wrapper - `save_to_ima()` - three-step upload (create_media → COS → add_knowledge) **New env vars:** `IMA_CLIENT_ID`, `IMA_API_KEY`, `IMA_KNOWLEDGE_BASE_ID` --- ### ✨ Improvements #### 2. Whisper Chunking: File Size Check Added - **Before:** 10-minute chunks with `-c copy`, which preserves original encoding and offers no size guarantee. - **After:** Cut into ~5-minute chunks first, then check each chunk's file size. Any chunk ≥ 24MB → re-cut at 3 minutes or lower the bitrate before uploading. #### 3. Obsidian Overwrite Protection - **Location:** `save-scripts.md` - **Added:** `unique_note_path()` — detects existing files with the same name and generates a non-conflicting filename (e.g., `slug-2.md`). #### 4. Config File Permission Guidance - **Added:** Reminder to set `chmod 600` / `chmod 700` on `~/.agent-social-reader/config.json` and `~/.config/ima/` after storing credentials. --- ### 🐛 Bug Fixes #### 5. Cleanup Command Errors on Empty Match - **Location:** `SKILL.md` § Managing Temp Media Files - **Problem:** `ls /tmp/asr_*` and `du -sh /tmp/asr_*` return non-zero exit codes when no files match. - **Fix:** Replaced with `find /tmp -maxdepth 1 -type f -name 'asr_*' -delete` — silently skips when no match. #### 6. `preferences.json` Contained Illegal JSON Comments - **Location:** `SKILL.md` § Persistence & Onboarding - **Problem:** Code block contained `//` comments; JSON does not allow comments. - **Fix:** Removed comments; moved valid options to body text. #### 7. Duplicate AgentLens Endpoints - **Location:** `tool-details.md` - **Problem:** Two endpoints were identical URLs, providing no actual fallback. - **Fix:** Removed the duplicate. #### 8. Missing `platform` Field in Notion Script - **Location:** `save-scripts.md` - **Problem:** SKILL.md declared the archive format includes `Platform`, but `save_to_notion()` did not include it. - **Fix:** Added `platform` parameter; body text now includes `Platform:` line.","fileCount":6,"zipByteSize":31457},{"version":"0.1.0","createdAt":"2026-07-09T10:14:16.225Z","changelog":"agent-social-reader 0.1.0 - Initial release supporting reading and summarizing content from 20+ major social and web platforms, including TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Accounts, and RSS/web pages. - Auto-summarization and archiving to Notion or Obsidian upon user confirmation. - Intelligent routing through free and fallback tools for each platform. - Strict rules for media handling, temp file cleanup, configuration management, and user privacy. - Supports user-driven onboarding for default tool and archiving features.","fileCount":3,"zipByteSize":14675}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s178kw7jyc12a88r6yr75atxa189wwvf:agent-social-reader","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s178kw7jyc12a88r6yr75atxa189wwvf:agent-social-reader` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/hermiod99-vibe/agent-social-reader before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-12T09:57:41.795Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-hermiod99-vibe-agent-social-reader/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":null},"readme":"Skill: Agent Social Reader\n\nOwner: hermiod99-vibe\n\nSummary: Empower your AI agent to read and summarize specific public web or social media links from  TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official\n\nTags: latest:2.0.1\n\nVersion history:\n\nv2.0.1 | 2026-07-14T09:37:09.386Z | user\n\n**agent-social-reader 2.0.1**\n\n- Changed: Skill now only acts when the user explicitly asks to read, summarize, analyze, or save a specific public URL; no background detection or auto-triggering.\n- Clarified: Will only save, archive, or export content to Notion, Obsidian, or ima if the active user request includes that exact destination.\n- Security: Emphasized not to enumerate arbitrary environment variables or user home directories; only reference explicit variables and config paths.\n- User privacy: Local config file is used only if the user explicitly approves plaintext storage, and never for arbitrary credentials.\n- Temp media: Cleanup now always previews matching files before deletion, and only deletes after the user confirms.\n- Docs: Removed general-purpose triggers and shortened the skill summary to reflect strict \"user-initiated only\" operations. Removed the `skill-card.md` file.\n\nv2.0.0 | 2026-07-14T08:31:13.888Z | user\n\n### 🏗️ Structural Change: Document Split\n\nv1 put everything in a single `SKILL.md` file. v2 reorganizes into three focused files:\n\n| File              | Content                                                      | Purpose              |\n| ----------------- | ------------------------------------------------------------ | -------------------- |\n| `SKILL.md`        | Routing map, SOPs, config lookup, Save section summaries     | Runtime reference    |\n| `tool-details.md` | Tool-specific code snippets (API formats, fallback strategies, error handling) | Tool spec reference  |\n| `save-scripts.md` | Complete runnable scripts for Notion, Obsidian, and ima      | Copy-paste execution |\n\n**Save section handling:**\n\n- v1: Notion and Obsidian scripts were embedded directly in SKILL.md\n- v2: SKILL.md retains only the overview (trigger conditions, credentials, archive format, success feedback); full scripts moved to `save-scripts.md`\n- ima (new): SKILL.md covers the full three-scene flow; scripts in `save-scripts.md`\n\n---\n\n### 🆕 New Features\n\n#### 1. Save to ima (Tencent ima Knowledge Base)\n\nima is Tencent's cloud knowledge base, accessed via OpenAPI. v2 adds it as a third archive destination alongside Notion and Obsidian.\n\nThis includes all ima-related improvements from this release:\n\n- Three-scene detection logic (ima native skills / WorkBuddy / external Agent)\n- Configuration and write paths per scene\n- Standard archive format (consistent across all three destinations)\n- Native ima skills prioritized in routing map over OpenAPI\n- Env var to HTTP header/body mapping explicitly documented\n- COS upload example labeled as reference (official SDK recommended)\n\n**SKILL.md chapters:**\n\n- Three-scene detection logic (ima native skills / WorkBuddy / external Agent)\n- Configuration and write paths per scene\n- Standard archive format (consistent across all three destinations)\n\n**save-scripts.md scripts:**\n\n- `detect_ima_scene()` - runtime scene detection\n- `_get_ima_credentials()` - reads credentials from env / config files\n- `_get_ima_kb_id()` - KB ID retrieval (includes API query path)\n- `_ima_api_request()` - OpenAPI request wrapper\n- `save_to_ima()` - three-step upload (create_media → COS → add_knowledge)\n\n**New env vars:** `IMA_CLIENT_ID`, `IMA_API_KEY`, `IMA_KNOWLEDGE_BASE_ID`\n\n---\n\n### ✨ Improvements\n\n#### 2. Whisper Chunking: File Size Check Added\n\n- **Before:** 10-minute chunks with `-c copy`, which preserves original encoding and offers no size guarantee.\n- **After:** Cut into ~5-minute chunks first, then check each chunk's file size. Any chunk ≥ 24MB → re-cut at 3 minutes or lower the bitrate before uploading.\n\n#### 3. Obsidian Overwrite Protection\n\n- **Location:** `save-scripts.md`\n- **Added:** `unique_note_path()` — detects existing files with the same name and generates a non-conflicting filename (e.g., `slug-2.md`).\n\n#### 4. Config File Permission Guidance\n\n- **Added:** Reminder to set `chmod 600` / `chmod 700` on `~/.agent-social-reader/config.json` and `~/.config/ima/` after storing credentials.\n\n---\n\n### 🐛 Bug Fixes\n\n#### 5. Cleanup Command Errors on Empty Match\n\n- **Location:** `SKILL.md` § Managing Temp Media Files\n- **Problem:** `ls /tmp/asr_*` and `du -sh /tmp/asr_*` return non-zero exit codes when no files match.\n- **Fix:** Replaced with `find /tmp -maxdepth 1 -type f -name 'asr_*' -delete` — silently skips when no match.\n\n#### 6. `preferences.json` Contained Illegal JSON Comments\n\n- **Location:** `SKILL.md` § Persistence & Onboarding\n- **Problem:** Code block contained `//` comments; JSON does not allow comments.\n- **Fix:** Removed comments; moved valid options to body text.\n\n#### 7. Duplicate AgentLens Endpoints\n\n- **Location:** `tool-details.md`\n- **Problem:** Two endpoints were identical URLs, providing no actual fallback.\n- **Fix:** Removed the duplicate.\n\n#### 8. Missing `platform` Field in Notion Script\n\n- **Location:** `save-scripts.md`\n- **Problem:** SKILL.md declared the archive format includes `Platform`, but `save_to_notion()` did not include it.\n- **Fix:** Added `platform` parameter; body text now includes `Platform:` line.\n\nv0.1.0 | 2026-07-09T10:14:16.225Z | auto\n\nagent-social-reader 0.1.0\n\n- Initial release supporting reading and summarizing content from 20+ major social and web platforms, including TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Accounts, and RSS/web pages.\n- Auto-summarization and archiving to Notion or Obsidian upon user confirmation.\n- Intelligent routing through free and fallback tools for each platform.\n- Strict rules for media handling, temp file cleanup, configuration management, and user privacy.\n- Supports user-driven onboarding for default tool and archiving features.\n\nArchive index:\n\nArchive v2.0.1: 6 files, 32470 bytes\n\nFiles: README.md (16264b), references/save-scripts.md (18727b), references/tool-details.md (24648b), skill-card.md (2911b), SKILL.md (21627b), _meta.json (138b)\n\nFile v2.0.1:SKILL.md\n\n---\nname: agent-social-reader\nversion: 2.0.1\ndescription: >\n Empower your AI agent to read and summarize specific public web or social media links from  TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Accounts, WeChat Channels, general public web pages, and RSS feeds. The skill can save a specific retrieved item to Notion, Obsidian, or ima only when the current user request explicitly asks to save/archive it or confirms that exact destination.\n Trigger when: (1) the user asks to read, summarize, analyze, or extract content from a specific public URL, (2) the user asks what a specific linked video/post/article says, (3) the user asks to subscribe to or inspect a specific RSS feed, or (4) the user asks to save the current retrieved item to Notion, Obsidian, or ima.\n Triggers: \"read this link\", \"summarize this URL\", \"what does this video say\", \"extract this post\", \"save this to Notion\", \"save this to Obsidian\", \"save this to ima\".\nmetadata:\n openclaw:\n homepage: \"https://github.com/hermiod99-vibe/Agent-Social-Reader\"\n requires:\n bins:\n - curl\n - python3\n optional_bins:\n - ffmpeg\n - yt-dlp\n envVars:\n - AGENT_LENS_API_KEY\n - OPENAI_API_KEY\n - NOTION_TOKEN\n - NOTION_DATABASE_ID\n - OBSIDIAN_VAULT_PATH\n - IMA_CLIENT_ID\n - IMA_API_KEY\n - IMA_KNOWLEDGE_BASE_ID\n---\n\n# Agent-Social-Reader — Skill Guide\n\n## ⚠️ Workspace Rules\n\n**CRITICAL: Never create or modify files directly inside the agent's default workspace unless the user explicitly asks for a workspace artifact.**\n- Use `/tmp/` for all temporary outputs and transient cache.\n- Prefer platform secret storage, existing authenticated connectors, or environment variables for secrets.\n- Use `~/.agent-social-reader/config.json` only after the user explicitly agrees to local plaintext configuration.\n- Never print, log, summarize, archive, or commit full API keys/tokens.\n- Only write to Notion, Obsidian, or ima after the current user request asks for that destination or confirms that exact destination for the current item.\n- Use this skill only when the user explicitly asks to read, summarize, analyze, or save a specific URL.\n\n### Configuration Lookup Order\n\nWhen a credential or path is needed, check in this order:\n\n1. Current runtime connectors or MCP tools already authenticated by the user.\n2. Environment variables: `AGENT_LENS_API_KEY`, `OPENAI_API_KEY`, `NOTION_TOKEN`, `NOTION_DATABASE_ID`, `OBSIDIAN_VAULT_PATH`, `IMA_CLIENT_ID`, `IMA_API_KEY`, `IMA_KNOWLEDGE_BASE_ID`.\n3. Local config file: `~/.agent-social-reader/config.json` only if the user previously approved local plaintext storage.\n4. Ask the user for the missing value, explain what it is used for, and ask whether it should be saved locally.\n\nDo not enumerate arbitrary environment variables or scan the user's home directory. Read only the explicit variables and config paths named in this document.\n\nUse this JSON shape for local config:\n\n```json\n{\n \"agentLensApiKey\": \"\",\n \"openAiApiKey\": \"\",\n \"notionToken\": \"\",\n \"notionDatabaseId\": \"\",\n \"obsidianVaultPath\": \"\",\n \"imaClientId\": \"\",\n \"imaApiKey\": \"\",\n \"imaKnowledgeBaseId\": \"\",\n \"imaScenario\": \"\"\n}\n```\n\n`imaScenario` records the current ima write path: `\"ima-native\"` / `\"workbuddy\"` / `\"external-api\"`.\n\n---\n\n## 🔧 Tool Routing Map\n\nEach platform has a **primary (free) tool** and an **optional fallback**. When the primary fails, try the next option in order. Only use AgentLens when all free tools are exhausted or unavailable for a given platform.\n\n| # | Platform | Primary (Free) | Fallback |\n|:--|:--|:--|:--|\n| 1 | General web pages | r.jina.ai | Runtime browser / Camoufox |\n| 2 | X / Twitter (public tweets) | FxTwitter API | AgentLens API |\n| 3 | YouTube (subtitles) | youtube-transcript-api | AgentLens API + Whisper |\n| 4 | WeChat Official Account | Camoufox | AgentLens API |\n| 5 | Weibo | r.jina.ai | AgentLens API |\n| 6 | 20+ Social platforms | AgentLens API | — |\n| 7 | Video summarization | Local Whisper | OpenAI Whisper API |\n| 8 | RSS feeds | feedparser | — |\n| 9 | Web search | Built-in runtime search | — |\n| 10 | Save to Notion | Native API write | — |\n| 11 | Save to Obsidian | File write (.md) | — |\n| 12 | Save to ima | Native ima skills if available | ima OpenAPI |\n\n**Routing principle: Exhaust free tools first. Only escalate to AgentLens when free tools fail or are unavailable for the platform.**\n\nFor detailed tool commands and code, see `references/tool-details.md`.\n\n---\n\n## ⚠️ Reading Rules\n\n**Trigger:** When user asks to \"read\" or \"summarize\" a link → AND the content is primarily visual (images or videos carry the main information, while text is secondary: hashtags, captions, short descriptions)\n\n**Rules:**\n\n1. When a tool returns media files (images or videos) alongside text → the media is the primary content\n2. **Images** → download CDN/media URLs as `/tmp/asr_{platform}_{timestamp}.jpg` → pass to Vision model → incorporate visual analysis into summary\n3. **Videos** → follow SOP C (Video Summarization)\n4. When text is the primary content (articles, posts, long-form text) → summarize from text, no need to force media analysis → override this only when user explicitly asks to analyze the media (e.g., \"analyze the images/videos\")\n5. Never ignore text entirely — media supplements text, text and media together form the complete picture\n\n**Note:** Different tools return media in different formats — check the actual response structure and extract media URLs accordingly.\n\n---\n\n## 🗂️ Managing Temp Media Files\n\n### Naming Convention\n\nAll media files created by this skill MUST use the `asr_` prefix. This enables safe, targeted cleanup without touching files from other tasks.\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Video | `/tmp/asr_` + `{platform}` + `_{timestamp}.mp4` | `/tmp/asr_douyin_20260703.mp4` |\n| Audio | `/tmp/asr_audio_` + `{timestamp}.wav` (or `.MP3`) | `/tmp/asr_audio_20260703.wav` |\n| Image | `/tmp/asr_` + `{platform}` + `_{timestamp}.jpg` (or `.png`, `.webp`) | `/tmp/asr_xhs_20260703.jpg` |\n\nThe timestamp is optional but recommended to avoid naming collisions.\n\n### Cleanup Trigger\n\nAfter any task that downloads media files, check both conditions:\n1. This is the **second or beyond** media download in this session, **OR**\n2. Total size of `/tmp/asr_*` files exceeds **~1GB**\n\nIf either is true → prompt user:\n> \"Temp media files are accumulating. Options: (A) Preview files / (B) Delete these temporary files now / (C) Keep for now\"\n\n- Always preview or describe matching files before deletion.\n- Delete only after the user confirms deletion for the current cleanup request.\n- After any cleanup → always confirm what was deleted (file count + bytes freed).\n\n### Cleanup Rules\n\n```\nCRITICAL:\n1. ONLY delete files under /tmp/\n2. ONLY delete files with the `asr_` prefix — never touch files without this prefix\n3. Files without `asr_` may belong to other tasks or processes — do not delete them\n4. If you cannot reliably identify which files were created by this skill, do not delete — ask the user instead\n```\n\n### Cleanup Preview\n\n```bash\n# Preview matching files only\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec ls -lh {} +\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec du -ch {} +\n```\n\nDo not run deletion commands until the user confirms deletion after preview.\n\n---\n\n## 📋 Standard Operating Procedures\n\n### SOP A: URL Read → Deliver\n\n```\nUser shares link\n → Route to correct tool (see Routing Map above)\n → Read content\n → Execute user's intent (summarize / analyze / download / save / etc.)\n → Deliver result\n → If this was a successful public social-media read and no preference has been asked in this runtime:\n    Ask the scoped preference question from § Persistence & Onboarding\n → Stop unless the current user request explicitly asks to save/archive the result\n\n --- AgentLens API Key Setup (inline, triggered when AgentLens is needed + no key) ---\n\n → IF the platform requires AgentLens AND no AGENT_LENS_API_KEY is found in any lookup:\n Tell user:\n \"This platform requires an AgentLens API key to read.\n It unlocks 20+ major social platforms.\n Get your free API key in 10 seconds at https://agentlensapi.io/pricing — 20 requests/month free; paid plans from $2.9/month, see pricing page.\n Once you paste it here, I'll handle the rest.\"\n\n User pastes key:\n → Use it for the current request only.\n → Ask: \"Would you like me to save this API key locally in plaintext config for future runs, or use it only for this session?\"\n → If user says yes → save to ~/.agent-social-reader/config.json and mention that it is a local plaintext config file\n → If user says no → do not persist it; use only for this session\n → Check: was there a pending URL from this conversation?\n    if YES:\n    → Execute the intended operation for that URL\n    → Deliver result\n    if NO:\n    → Stop\n```\n\n---\n\n### SOP B: Unsupported Platform / Self-Healing Fallback\n\n```\nUser shares link\n → Route to AgentLens API\n → API returns unsupported-platform status/code/message (for example Status 10032)\n → DO NOT stop — execute fallback:\n\n 1. Tell user:\n \"This platform isn't directly supported yet.\n I'll try a general web parser as fallback...\"\n\n 2. Fallback A — Jina Reader:\n    curl -L -s \"https://r.jina.ai/{url}\"\n    If returns valid content → summarize and deliver with note:\n    \"Recovered via general web parsing.\"\n\n 3. Fallback B — Runtime Web Search (if Jina also fails):\n    Use the current runtime's built-in web/search tool if available.\n    If no runtime search is available, tell the user search is not configured.\n\n 4. If all paths fail:\n    \"I've tried all available paths, but this link is blocked by anti-bot walls.\n    You may need to open it manually and share the content with me.\"\n```\n\n---\n\n### SOP C: Video Summarization\n\n**Trigger**: User's first instruction on a video link contains \"summarize\", \"what does this video say\", or similar intent + the media type is video.\n\n```\nUser shares video URL with summarization intent\n → Step 1: Attempt to get subtitle\n    if YouTube → try youtube-transcript-api\n    if subtitles found → summarize → done\n    if no subtitles → continue to Step 2\n    if non-YouTube → continue to Step 2\n\n → Step 2: Ask user for Whisper preference BEFORE downloading\n    Tell user: \"This video doesn't have subtitles, so I can't summarize the spoken content yet.\n    To do that, I need to transcribe the audio first. You have two options:\n\n    A) Local Whisper (free) — I'll walk you through installing faster-whisper.\n       You'll also need ffmpeg (already required).\n\n    B) OpenAI Whisper API (~$0.006/min) — you'll need an OpenAI API key.\n\n    Which would you prefer?\"\n    → IF user declines → deliver available metadata/text only; stop\n    → IF user chooses A or B → continue to Step 3\n\n → Step 3: Get download URL via AgentLens API\n    → Extract sourceUrl from downloadUrlList (prefer \"video\" type)\n    → If AgentLens is not configured, ask for AGENT_LENS_API_KEY or try subtitles/search-only fallback\n\n → Step 3b: Check for direct audio URL\n    → If downloadUrlList contains an \"audio\" type entry → prefer it over video\n    → Download the audio file directly as `/tmp/asr_audio_{timestamp}.MP3` → skip Step 4 and Step 5 → go straight to Step 6\n    → Audio is much smaller than video → faster download + no ffmpeg extraction needed\n\n → Step 4: Download video\n    curl -L --fail --max-time 120 -o /tmp/asr_{platform}_{timestamp}.mp4 \"{sourceUrl}\"\n    if curl fails + is YouTube → try yt-dlp\n    if curl fails + not YouTube → inform user download failed\n\n → Step 5: Extract audio\n    (Skip this step if audio was already obtained in Step 3b)\n    ffmpeg -y -i /tmp/asr_{platform}_{timestamp}.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/asr_audio_{timestamp}.wav\n\n → Step 6: Transcribe\n    Local Whisper: faster-whisper (CPU, free)\n    API Whisper: OpenAI Whisper API ($0.006/min)\n    Note: OpenAI Whisper API has a 25MB file size limit. For long videos,\n    pre-split into ~5-minute chunks, then check each chunk's file size:\n    any chunk ≥ 24MB → re-split at 3 minutes or lower the bitrate before uploading.\n\n → Step 7: Summarize transcript → deliver\n\n → After task complete: See § Managing Temp Media Files for cleanup\n```\n\nFor detailed download/transcribe commands, see `references/tool-details.md` §7.\n\n---\n\n## 💾 Save to Notion\n\n**Trigger**: User says \"save to Notion\" or confirms during onboarding.\n\n**Dependency Check first**: Check for existing Notion tools (notion-cli, mcporter-notion, custom Notion connector). If found, use that tool instead of the built-in script.\n\n**Built-in fallback**: See `references/save-scripts.md` §Notion.\n\n**Credentials**: If not yet stored, guide user to provide Notion Integration Token and either a Database ID or Page ID. Store locally only if the user approves. Tokens are never printed or committed.\n\n**Archive format** (markdown):\n```markdown\n# {title}\n\n**Source**: {source_url}\n**Platform**: {platform_name}\n\n## Summary\n{ai_summary}\n\n## Full Text\n{full_text}\n```\n\n**Limitation**: Notion API limits children to 100 blocks per request. If content exceeds 100 blocks, ask user whether to save summary only or use batched PATCH. Never silently truncate.\n\n**Success feedback**:\n> \"Successfully saved to your Notion reading list! Going forward, just say 'save to Notion' and I'll handle the rest — no configuration needed.\"\n\n---\n\n## 💾 Save to Obsidian\n\n**Trigger**: User says \"save to Obsidian\" or confirms during onboarding.\n\n**Dependency Check first**: Check for existing Obsidian integration tools. If found, use that tool instead.\n\n**Built-in fallback**: See `references/save-scripts.md` §Obsidian.\n\n**Credentials**: If vault path not yet stored, guide user to provide it. Store locally only if the user approves.\n\n**Archive format** (same markdown structure as Notion above).\n\n**Success feedback**:\n> \"Successfully saved to your Obsidian vault! Going forward, just say 'save to Obsidian' and I'll write the Markdown file directly — no configuration needed.\"\n\n---\n\n## 💾 Save to ima\n\n**Trigger**: User says \"save to ima\", \"存到 ima\", \"存到知识库\" or confirms during onboarding.\n\nima is Tencent's cloud knowledge base, accessed via OpenAPI (Token + target ID). It works across all channels — inside ima, via Feishu/Lark, Enterprise WeChat, or any third-party agent.\n\n### Step 1 — Detect Runtime Scene\n\nBefore saving, detect which environment you are running in:\n\n| Detection signal | Scene | Write path |\n|:----------------|:------|:-----------|\n| Runtime has `ima-knowledge` / `ima-note` native skills | **Scene 1: ima 内** | Call built-in ima skills directly |\n| Runtime has WorkBuddy-specific tools/connectors | **Scene 2: WorkBuddy** | ima OpenAPI |\n| Env vars `IMA_CLIENT_ID`/`IMA_API_KEY` exist but no WorkBuddy signal, or shell execution available | **Scene 3: Other Agent** | ima OpenAPI + manual config |\n\nIf no signal detected → ask user: \"你是在哪个平台使用本 skill？（ima / WorkBuddy / 其他 Agent）\"\n\nAfter detecting scene, save `imaScenario` to config and skip detection on subsequent runs.\n\n### Scene 1 — Running inside ima\n\nZero-config. Use built-in ima skills:\n\n| Intent | Action |\n|:-------|:-------|\n| Save to knowledge base | Call `ima-knowledge` skill → import content as Markdown |\n| Save as note | Call `ima-note` skill → create note with title + source + summary |\n| User wants download | `file_write` + `provide_file` → generate download link |\n\n**Steps**:\n1. Organize parsed content as Markdown (use standard archive format below)\n2. Call `ima-knowledge` or `ima-note` with the content\n3. Confirm success to user\n\n> ⚠️ ima's `import_urls` can also be used (ima crawls and snapshots the URL), but for social links (TikTok/Xiaohongshu/X) ima's crawler often fails due to anti-bot walls — resulting in a dead link with no content. **Prefer Markdown import** to ensure persistent content.\n\n### Scene 2 — Running in WorkBuddy\n\nOne-time credential setup required, then OpenAPI write. Full setup instructions → `references/tool-details.md` §10 (WorkBuddy setup).\n\nAfter setup, write via OpenAPI. Path selection:\n\n| Content type | Recommended way | Reason |\n|:-------------|:----------------|:-------|\n| Social links (TikTok/Xiaohongshu/X/etc.) | **Way B** (upload MD) ⭐ | ima crawler cannot bypass anti-bot walls on social platforms |\n| Regular web pages (blogs/news) | **Way A** (import_urls) | ima crawler can snapshot public webpages |\n| User wants AI summary saved | **Way B** (upload MD) ⭐ | Content persists in KB even if original link dies |\n\n### Scene 3 — Running in Other Agent (OpenClaw/Hermes/Claude Code/etc.)\n\nOne-time credential setup required, then OpenAPI write. Full setup instructions → `references/tool-details.md` §10 (External Agent setup).\n\n**Credentials** (three required):\n\n| Credential | Description | How to get |\n|:-----------|:-----------|:-----------|\n| `IMA_CLIENT_ID` | Client ID | ima.qq.com/agent-interface |\n| `IMA_API_KEY` | API Key | Same page (**shown only once — save immediately**) |\n| `IMA_KNOWLEDGE_BASE_ID` | Target KB ID | From `get_addable_knowledge_base_list` API |\n\n**Environment variable to HTTP mapping**:\n```\nIMA_CLIENT_ID        → HTTP header  ima-openapi-clientid\nIMA_API_KEY          → HTTP header  ima-openapi-apikey\nIMA_KNOWLEDGE_BASE_ID → request body field  knowledge_base_id / folder_id\n```\n\n**Credentials storage** (environment variables, recommended):\n```bash\nexport IMA_CLIENT_ID=\"your_client_id\"\nexport IMA_API_KEY=\"your_api_key\"\n```\nOr config files: `~/.config/ima/client_id` + `~/.config/ima/api_key`.\n\nWay selection: same as Scene 2 above. Full API details → `references/tool-details.md` §9.\n\n### Standard Archive Format\n\nAll three scenes use the same content format:\n\n```markdown\n# {title}\n\n**Source**: {source_url}\n**Platform**: {platform_name}\n\n## Summary\n{ai_summary}\n\n## Full Text\n{full_text}\n```\n\n### ima API Response\n\nima API responses follow the format described in `references/tool-details.md` §9.\n\n**Success feedback**:\n> \"Successfully saved to your ima knowledge base! Going forward, just say 'save to ima' and I'll handle the rest — no configuration needed.\"\n\n---\n\n## 🧠 Persistence & Onboarding\n\nAfter the first successful public social-media read in the current runtime, ask once:\n\n> \"Would you like me to remember that Agent-Social-Reader is your preferred workflow for public social-media links in this agent runtime? If you confirm, I will use it when you ask me to read or summarize a social post/video link. This does not apply to unrelated files, private content, general browsing, or saving/archiving.\"\n\nIf the user confirms, store only this scoped preference through the current runtime's approved memory mechanism when available:\n\n```json\n{\n \"preferredPublicSocialLinkReader\": \"agent-social-reader\"\n}\n```\n\nIf no approved memory mechanism is available, do not write a local preference file. Continue normally and let the user invoke the skill by asking to read or summarize a specific link.\n\nAfter a successful read, deliver the result and stop. If the user wants to save the current item, they can explicitly say \"save this to Notion\", \"save this to Obsidian\", or \"save this to ima\".\n\nIf the user asks to save, confirm the destination for that current item before writing:\n- Notion: database/page target\n- Obsidian: vault path and note filename\n- ima: scenario and target knowledge base when available\n\nSave only the current item the user asked to save.\n\n**Important**: Never write Markdown into JSON configuration files.\n\n---\n\n## 🩺 Diagnostics\n\n| Tool | Symptom | Fix |\n|:--|:--|:--|\n| r.jina.ai | Returns empty or login wall | Try Camoufox for JS pages; try AgentLens for social |\n| FxTwitter | NOT_FOUND or rate-limit | Fall back to AgentLens API |\n| youtube-transcript-api | `AttributeError: get_transcript` | Use `YouTubeTranscriptApi().fetch(...)` — 1.x API, not 0.x |\n| youtube-transcript-api | Blocked (403/429) or no subtitles | Fall back to AgentLens + Whisper (SOP C) |\n| Camoufox | Import error or crash | Run `pip install camoufox` |\n| Camoufox | Browser launch failure / missing libgbm | Run `playwright install-deps` |\n| Camoufox | `Browser.setDefaultViewport` protocol error | Treat as unavailable; try Jina Reader, AgentLens, or ask user for content |\n| AgentLens | Status 10032 | Trigger SOP B (self-healing fallback chain) |\n| AgentLens | TLS failure on `api.agentlensapi.io` | Use `https://agentlensapi.io/api/v1/fetch` on the main host |\n| AgentLens | `AUTH_FAILED` or HTTP 401 | Ask for a valid `AGENT_LENS_API_KEY`; do not retry |\n| AgentLens | Quota exhausted | Inform user; suggest upgrading plan |\n| feedparser | SSL EOF / network TLS failure | Retry later, try `curl -L` first |\n| Notion write | 403 Forbidden | Verify Integration Token has write access |\n| Notion write | Property schema mismatch | Ask for database property names |\n| Obsidian write | File not found | Confirm vault path exists and is accessible |\n| ffmpeg | \"command not found\" | Install: `apt install ffmpeg` (Linux), `brew install ffmpeg` (macOS) |\n| Whisper | API error or quota | Fall back to local Whisper if available |\n\n---\n\n## 📦 Installation\n\n```bash\n# One-line install — paste this to your AI agent:\n帮我安装 Agent-Social-Reader 技能包：https://github.com/hermiod99-vibe/Agent-Social-Reader\n```\n\nFile v2.0.1:README.md\n\n# 👁️ Agent-Social-Reader\n\n<p align=\"center\">\n <strong>Ask your agent to read a specific social link → AI summarizes → Save that item to Notion / Obsidian / ima when you request it<br/> No social login. No cookies. Lower account risk.<br/><br/>\nReads content from X, Instagram, YouTube, Xiaohongshu, and more — with optional per-item archiving to your personal knowledge base</strong>\n</p>\n<p align=\"center\">\n <a href=\"#-quick-overview\">Quick Overview</a> ·\n <a href=\"#-what-it-does\">What It Does</a> ·\n <a href=\"#-supported-platforms\">Supported Platforms</a> ·\n <a href=\"#-quick-start\">Quick Start</a> ·\n <a href=\"docs/README_cn.md\">中文</a>\n</p>\n\n\n---\n\n## 🎯 Quick Overview\n\nAs a knowledge worker, product manager, or heavy AI agent user, the moment you drop an X (Twitter), Reddit, WeChat article, or Xiaohongshu link into your AI, you hit walls: login barriers, anti-scraping blocks, IP bans, or HTML garbage. On top of that, you're stuck maintaining a separate tool for every single platform.\n\n**The problem this Skill solves:**\n\n❌ Before:\n\n```\nYou: drop a Twitter/Reddit/Xiaohongshu link into your agent\nAgent: \"I can't access that\" / returns garbled HTML\nYou: resort to manual copy-paste, screenshots, or give up\n```\n\n✅ Now:\n\n```\nYou: ask your agent to read a specific social media link\nAgent: reads it → summarizes it\nYou: optionally say \"save this to Notion / Obsidian / ima\"\n→ content neatly filed in your knowledge base\n```\n\n**Core value**:\n- ✅ **Complete loop**: Read → Summarize → Archive — end-to-end in one workflow\n- ✅ **Broad coverage**: 20+ global social platforms (TikTok, Instagram, X, YouTube, Reddit, Douyin, Xiaohongshu, Bilibili...)\n- ✅ **Lower account risk**: No need to log in to personal social accounts, no cookie required — reduces risk of suspension from cookie-based access\n- ✅ **Flexible cost**: Free tools first; optional paid enhancements from $2.90/month\n\n> 💡 **Design philosophy**: Use free tools wherever possible; reach for the most cost-effective paid option only when free tools won't do. You decide which tier to use.\n\n> ❤️ **Special thanks to** [Agent-Reach](https://github.com/Panniantong/Agent-Reach) by [@Panniantong](https://github.com/Panniantong). Agent-Reach proved that getting an AI agent to read web content was achievable — this project is a personal iteration on the same idea, with a tighter focus on social content reading and knowledge base archiving.\n\n---\n\n## ✨ What It Does\n\nAfter installing this Skill, your agent gains the following capabilities (no code required):\n\n| Content Source | Tool | Cost |\n| :--- | :--- | :--- |\n| General web pages | Jina Reader (r.jina.ai) | ✅ Free, no API key required |\n| Public X/Twitter posts | FxTwitter API | ✅ Free, no API key or cookie required |\n| YouTube (with subtitles) | youtube-transcript-api | ✅ Free, no API key required |\n| Weibo | Jina Reader (r.jina.ai) | ✅ Free, bypasses login wall |\n| WeChat public accounts | Camoufox | ✅ Free, headless browser handles JS rendering<br/>(falls back to AgentLens or prompts you to paste text if it fails) |\n| **20+ global social platforms** | AgentLens API | 🎁 20 free requests per month<br>💵 Then $2.90/month (200 requests) |\n| Video summarization (TikTok, YouTube and other videos without subtitles) | Whisper transcription + LLM | ✅ Free locally (requires setup)<br>💵 OpenAI API $0.006/minute |\n| RSS feeds | Python feedparser | ✅ Free |\n| Full-web semantic search | Agent runtime built-in search | ✅ Free (provided your current runtime supports it) |\n| Save to Notion upon request | Agent writes after confirmation | ✅ Free (your Notion Token) |\n| Save to Obsidian upon request | Agent writes after confirmation | ✅ Free (local path) |\n| Save to ima upon request | Agent writes after confirmation | ✅ Free (ima OpenAPI, accessible from WeChat/Feishu/WeCom) |\n\n---\n\n## 🌐 Supported Platforms\n\nVia **AgentLens API**, the following social media platforms are accessible and verified (if you spot errors or discover additional platforms, please let us know):\n\n| Global Platforms | Chinese Platforms |\n| :--------------- | :---------------- |\n| TikTok | Douyin |\n| Instagram | Xiaohongshu |\n| YouTube | Bilibili |\n| X (Twitter) | Weibo |\n| Facebook | Kuaishou |\n| Threads | Xigua Video |\n| Reddit | Zhihu (columns) |\n| LinkedIn | WeChat Official Account (articles) |\n| Twitch (clips) | WeChat Channels |\n| Pinterest | |\n| Bluesky | |\n| Snapchat | |\n| Kick (clips) | |\n| Lemon8 | |\n\n* **AgentLens reads**: main text content + images/video files (original media links)\n* **AgentLens does not read**: comments, timelines, X Lists, private account content, or reply/conversation threads on X and Reddit\n\n---\n\n## 🚀 Quick Start\n\n### Step 1: Install the Skill (10 seconds)\n\nCopy and paste this into whichever AI agent you're using (Claude Code, Cursor, Codex, OpenClaw, Hermes Agent, WorkBuddy, etc.):\n\n```\nInstall the Agent-Social-Reader Skill: https://github.com/hermiod99-vibe/Agent-Social-Reader\n```\n\nThat's it. Your agent handles everything else.\n\n---\n\n### Step 2: Start using it immediately (free right away)\n\nAfter installation, these features work at no cost:\n\n- \"Show me what's on this tweet\"\n- \"Summarize this WeChat article\"\n- \"What does this webpage say?\"\n- \"Subscribe to this RSS feed\"\n\n---\n\n### Step 3: Optional enhancements (as needed)\n\n#### 🔓 Unlock 20+ social platform reading (TikTok, Reddit, Instagram, Douyin, Xiaohongshu, Bilibili, etc.; X/Twitter single public posts, Weibo posts, WeChat Official Account articles use the free tool path first)\n\nThe first time you ask your agent to read from these platforms, it will prompt:\n\n> \"This platform requires an AgentLens API key to read. It unlocks 20+ major social platforms. Get your free API key in 10 seconds at https://agentlensapi.io/pricing — 20 requests/month free; paid plans from $2.90/month (200 requests), see pricing page. Once you paste it here, I'll handle the rest.\"\n\nFull pricing: [agentlensapi.io/pricing](https://agentlensapi.io/pricing)\n\n---\n\n#### 🎥 Unlock video summarization (TikTok, YouTube, and other videos without subtitles)\n\nTwo options:\n\n**Option A: Completely free (requires setup)**\n\nInstall Whisper locally:\n\n```bash\npip install faster-whisper\n# macOS: brew install ffmpeg | Ubuntu: sudo apt install ffmpeg\n```\n\nOnce installed, tell your agent:\n\n> \"I've installed local Whisper, running in CPU mode.\"\n\nIf your current agent supports preference memory and you confirm, your agent can remember your preference. Going forward, when it encounters a video without subtitles, it automatically follows: download → extract audio → transcribe locally → summarize.\n\n> ⚠️ **Hardware notes**: Small models (tiny/base) run on any regular computer; large models (large-v3) are recommended with a GPU. On first use, the model downloads automatically (75MB–3GB).\n\n**Option B: Paid & convenient (recommended for most users)**\n\nGet a Whisper API Key from the [OpenAI platform](https://platform.openai.com/api-keys) and tell your agent:\n\n> \"Here's my OpenAI API Key: [YOUR_KEY]\"\n\nIf your current agent supports preference memory and you confirm, your agent can remember your preference. Going forward, it automatically calls the API for any subtitle-free video.\n\n> 💡 **Low cost**: ~$0.006/minute. A 1-minute TikTok costs ~$0.006; a 10-minute YouTube video costs ~$0.06.\n\n---\n\n#### 📁 Connect your knowledge base (Notion / Obsidian / ima)\n\nThe first time you say \"save to Notion\", \"save to Obsidian\", or \"save to ima\", your agent walks you through a one-time setup:\n\n- **Notion**: requires an Integration Token and a Database ID or Page ID; use platform secret storage or environment variables when possible, and use local plaintext config only if you approve it\n  💡 The default save script assumes your Notion database has a Title property named **Name** and a URL property named **Source**. If your database uses different property names or you want to save to a single page instead of a database, let your agent know during the first save.\n- **Obsidian**: requires your local vault path\n- **ima**: requires Client ID + API Key (scan QR code at ima.qq.com/agent-interface); ima is Tencent's knowledge base product, accessible from WeChat, Feishu, and WeCom\n\n**ima — three access scenarios:**\n\n| Scenario | Description |\n|:---------|:-----------|\n| Running inside ima directly | Zero config; use built-in ima skills |\n| Running in WorkBuddy | One-time setup: Client ID + API Key |\n| Running in another Agent (OpenClaw / Claude Code etc.) | One-time setup: Client ID + API Key + Knowledge Base ID |\n\nFor social links (Douyin/Xiaohongshu/X etc.), Markdown upload is recommended — content persists even if the original link goes dead. For regular web pages, URL import works fine.\n\nOnce configured in the same runtime environment, setup is typically one-time only.\n\n---\n\n### Step 4: Use natural, explicit requests\n\nAfter setup, just ask for the specific action you want:\n\n- \"Show me what this tweet says\"\n- \"Summarize this Xiaohongshu post\"\n- \"Save this to Notion\"\n- \"Save this to ima\"\n\nAfter the first successful public social-media read, the agent may ask whether Agent-Social-Reader should be remembered as the preferred workflow for public social-media links in that runtime. This is scoped to social post/video reading and summarization; it does not apply to unrelated files, private content, general browsing, or saving.\n\nSaving happens only when you ask to save the current item.\n\n## 👍🏻 My Daily Workflow\n\n```\nI share a link → Agent reads → Agent summarizes → I say \"save to Notion\" → it's in Notion\n```\n\nContent saved to Notion/Obsidian/ima includes:\n\n- Original link\n- Full text\n- AI summary\n\nEasy to search and review later.\n\n---\n\n## 💰 Pricing (Fully Transparent)\n\nPer Skill marketplace guidelines, paid services require transparent pricing disclosure:\n\n### Completely free features\n\n- ✅ General web pages (r.jina.ai)\n- ✅ Single public X/Twitter posts (FxTwitter API)\n- ✅ YouTube videos with subtitles (youtube-transcript-api)\n- ✅ Weibo (r.jina.ai)\n- ✅ WeChat public accounts (Camoufox)\n- ✅ RSS feeds (feedparser)\n- ✅ Full-web search (Agent runtime built-in search)\n- ✅ Notion/Obsidian/ima archiving\n- ✅ Local Whisper video transcription (requires your own installation)\n\n### Optional paid features\n\n#### 📱 AgentLens API (20+ social platform reading)\n\n| Plan | Requests/month | Price |\n| :--- | :--- | :--- |\n| Free | 20 | $0 |\n| Basic | 200 | $2.90/month |\n| Ultra | 500 | $5.90/month |\n| Mega | 1,000 | $9.90/month |\n\nFull pricing: [agentlensapi.io/pricing](https://agentlensapi.io/pricing)\n\nWhy AgentLens (from my own experience):\n\n- One API key for 20+ platforms (vs one tool per platform)\n- No cookie required (vs risking account suspension)\n- No proxy required (vs maintaining your own IP pool)\n- Stable maintenance (vs tracking platform rule changes yourself)\n\nFor me, $2.90/month (200 requests, enough for a month) is excellent value for the peace of mind it brings.\n\n---\n\n#### 🎥 OpenAI Whisper API (video transcription)\n\n- **Price**: ~$0.006/minute\n- **Examples**:\n  - 1-minute TikTok: $0.006\n  - 10-minute YouTube: $0.06\n  - 30-minute podcast: $0.18\n\nFull pricing: [OpenAI Pricing — Whisper](https://platform.openai.com/docs/pricing)\n\n> 💡 If your machine has sufficient resources and you don't mind the setup, you can install Whisper locally (`pip install faster-whisper`) for zero cost.\n\n---\n\n## 💡 Design Philosophy\n\n**Focus on completing the \"read → summarize → archive\" loop.**\n\nWithin this loop:\n\n### Read layer (free-first)\n\n| Content source | Tool | Cost |\n| :--- | :--- | :--- |\n| General web pages | Jina Reader | ✅ Free |\n| Public X/Twitter posts | FxTwitter API | ✅ Free |\n| YouTube subtitles | youtube-transcript-api | ✅ Free |\n| Weibo | r.jina.ai | ✅ Free |\n| WeChat public accounts | Camoufox | ✅ Free |\n| RSS feeds | feedparser | ✅ Free |\n| 20+ social platforms | AgentLens API | 💵 Optional |\n\n🔐 **Core advantage**: No social account login / no cookie used.\nThe underlying layer never requires you to log into any personal social account, enter a password, or provide a cookie — so platforms cannot trace activity back to your personal account, reducing the risk of suspension from using personal account cookies.\n\n---\n\n### Summarize layer (LLM calls)\n\n| Content type | Method |\n| :--- | :--- |\n| Text, images | LLM summarizes directly |\n| YouTube with subtitles | Subtitle text → LLM summarizes |\n| Videos without subtitles | Audio transcription (✅ free locally, or 💵 optional paid API) → LLM summarizes |\n\n---\n\n### Archive layer (completely free)\n\nNotion, Obsidian, and ima are all free. Tell your agent \"save this to Notion\", \"save this to Obsidian\", or \"save this to ima\" for the current item. The agent walks you through one-time destination setup when needed, then confirms the target before writing.\n\nArchived content includes:\n\n- Original link\n- Full text\n- AI summary\n\nima is Tencent's knowledge base product: once configured, your archive is accessible across WeChat, Feishu, and WeCom. For social links, Markdown upload is recommended — content is saved directly and doesn't depend on the original link remaining accessible.\n\n---\n\n## 🔒 Security & Privacy\n\n- **Account safety (no login required)**: Many solutions require browser extensions or cookie exports to fetch content. This Skill never asks you to log into any personal social account or provide a cookie, which reduces the risk of suspension from cookie-based access.\n- **Credential handling**: Prefer your agent platform's secret storage or environment variables for API keys and tokens. Local plaintext config (`~/.agent-social-reader/config.json`) is used only if you explicitly approve that tradeoff.\n- **Service boundaries**: The agent calls only the service needed for the current request. AgentLens receives a URL only when AgentLens is needed for reading; OpenAI receives audio only if you choose OpenAI Whisper transcription; Notion or ima receive archive content only when you ask to save the current item. Obsidian writes to your local vault path.\n- **Per-item save boundary**: Each archive write requires an explicit current request or confirmation of the destination.\n- **Scoped reading preference**: If you ask the agent to remember Agent-Social-Reader as the preferred workflow, that preference is limited to public social-media link reading/summarization in the current runtime. It does not enable saving or unrelated browsing.\n\n---\n\n## 🛠️ Tech Stack\n\n<details>\n<summary>👀 Click to expand tech stack</summary>\n\n**Free open-source tools and parsers:**\n- [Jina Reader](https://jina.ai/reader) — general web page content extraction\n- [FxTwitter API](https://github.com/FixTweet/FxTwitter) — public X/Twitter post parsing\n- [youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api) — YouTube subtitle extraction\n- [yt-dlp](https://github.com/yt-dlp/yt-dlp) — video download fallback (used when curl fails to extract video for transcription)\n- [Jina Reader](https://r.jina.ai) — Weibo content extraction\n- [Camoufox](https://github.com/daijro/camoufox) — WeChat public account dynamic rendering and reading\n- Agent runtime built-in search — full-web semantic search (no extra dependencies)\n- Python `feedparser` — RSS feed parsing\n\n**Third-party optional APIs (user chooses as needed):**\n- [AgentLens API](https://agentlensapi.io) — unified entry for 20+ social media platform public content\n- [OpenAI Whisper API](https://platform.openai.com/docs/guides/speech-to-text) — cloud audio/video high-precision transcription\n\n**Local processing (completely free):**\n- [faster-whisper](https://github.com/SYSTRAN/faster-whisper) — locally running Whisper speech-to-text engine\n\n</details>\n\n---\n\n## 🙏 Final Words\n\nThis is the first time I've published a Skill publicly.\n\nIf something doesn't work, open an issue and I'll fix it right away.\n\nIf I find a new tool or solution, I'll add it right away.\n\nIf you want a platform added, submit a PR.\n\nIf this Skill helps streamline your agent's social media reading and archiving workflow, please give it a ⭐ **Star**!\n\n---\n\n**MIT License**\n\nFile v2.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn7dct92yfrktj1r1p84kvtdpn89xcqf\",\n  \"slug\": \"agent-social-reader\",\n  \"version\": \"2.0.1\",\n  \"publishedAt\": 1784021829386\n}\n\nFile v2.0.1:references/save-scripts.md\n\n# Save Scripts\n\nBuilt-in fallback scripts for archiving content to Notion and Obsidian. These are used when no native Notion/Obsidian tool is available in the current runtime.\n\nFor the ima OpenAPI (Scene 2 & 3), see `references/tool-details.md` §9.\n\nUse these scripts only after the current user request explicitly asks to save or archive the current item. Do not run them during read-only summarization. Confirm the target destination for the current item before writing.\n\nCredential helpers in this file read only the explicit environment variables and config paths named below. They must not enumerate arbitrary environment variables or scan the user's home directory. Prefer platform secret storage or environment variables; read local plaintext config only if the user previously approved local config storage.\n\n---\n\n## Save to ima (Scene 2 & 3 — OpenAPI)\n\n**When to use**: User says \"save to ima\" and no native ima skill is available (i.e., not running inside ima). Use this after completing the scene detection in SKILL.md.\n\nBefore writing, confirm the target ima scenario and knowledge base for the current item.\n\n### Scene Detection Function\n\n```python\nimport os\nfrom pathlib import Path\n\ndef detect_ima_scene():\n    \"\"\"\n    Detect which ima write path to use.\n    Returns: \"ima-native\" | \"workbuddy\" | \"external-api\" | None\n    \"\"\"\n    # Scene 1: ima native skills — check via runtime skill discovery\n    # (ima-knowledge and ima-note are skills, not shell commands;\n    # implementation varies by runtime: check <available_skills>, skill registry,\n    # or platform-specific tool list. If you can call use_skill(\"ima-knowledge\")\n    # and it succeeds → return \"ima-native\".)\n    # TODO: replace with actual runtime skill discovery when running inside ima.\n    # For now, fall through to check env vars / config files below.\n\n    # Scene 2: WorkBuddy — requires platform-specific signal, not just env vars.\n    # IMA_CLIENT_ID + IMA_API_KEY only mean OpenAPI is configured; they are present\n    # in ALL OpenAPI environments (WorkBuddy, OpenClaw, Claude Code, etc.).\n    # Use WorkBuddy-specific indicators: runtime tool/connector presence, or ask user.\n    # If you detect WorkBuddy-specific runtime tools or connectors → return \"workbuddy\"\n    # If only env vars are present but no WorkBuddy signal → return \"external-api\"\n    if os.environ.get(\"IMA_CLIENT_ID\") and os.environ.get(\"IMA_API_KEY\"):\n        # These env vars alone only prove OpenAPI is configured.\n        # Do NOT assume WorkBuddy without a platform-specific signal.\n        return \"external-api\"\n\n    # Scene 3: External agent — read only the documented config files\n    # after the user has approved local plaintext config storage.\n    config_dir = Path.home() / \".config\" / \"ima\"\n    if config_dir.exists():\n        client_id_file = config_dir / \"client_id\"\n        api_key_file = config_dir / \"api_key\"\n        if client_id_file.exists() and api_key_file.exists():\n            return \"external-api\"\n\n    # Also check OpenClaw skill config after user-approved local storage.\n    skill_config = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_config.exists():\n        try:\n            import json\n            with open(skill_config) as f:\n                cfg = json.load(f)\n                if cfg.get(\"imaClientId\") and cfg.get(\"imaApiKey\"):\n                    return cfg.get(\"imaScenario\", \"external-api\")\n        except Exception:\n            pass\n\n    return None  # Cannot detect — ask user\n```\n\n### Save to ima via OpenAPI (Way B — Upload Markdown)\n\nThree-step: `create_media` → COS upload → `add_knowledge`. Full API details in `references/tool-details.md` §9.\n\n```python\nimport os\nimport urllib.request\nimport urllib.error\nimport json\nimport time\nimport subprocess\nimport shutil\nimport re\nfrom pathlib import Path\n\n# ── helpers ────────────────────────────────────────────────────────────────────\n\ndef _slugify(title):\n    slug = re.sub(r\"[^\\w\\u4e00-\\u9fff.-]+\", \"-\", title.strip(), flags=re.UNICODE)\n    slug = slug.strip(\".-\")[:80]\n    return slug or \"untitled\"\n\ndef _get_ima_credentials():\n    \"\"\"Load ima credentials from env vars or config files.\"\"\"\n    client_id = os.environ.get(\"IMA_CLIENT_ID\")\n    api_key = os.environ.get(\"IMA_API_KEY\")\n    if client_id and api_key:\n        return client_id, api_key\n\n    # Fallback: read from documented config files only after user-approved\n    # local plaintext config storage.\n    cfg_dir = Path.home() / \".config\" / \"ima\"\n    client_id_file = cfg_dir / \"client_id\"\n    api_key_file = cfg_dir / \"api_key\"\n    if client_id_file.exists() and api_key_file.exists():\n        return (\n            client_id_file.read_text().strip(),\n            api_key_file.read_text().strip()\n        )\n\n    # Fallback: user-approved skill config.\n    skill_cfg = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_cfg.exists():\n        with open(skill_cfg) as f:\n            cfg = json.load(f)\n            return cfg.get(\"imaClientId\", \"\"), cfg.get(\"imaApiKey\", \"\")\n\n    raise ValueError(\"ima credentials not found. Set IMA_CLIENT_ID / IMA_API_KEY or configure ~/.config/ima/\")\n\ndef _get_ima_kb_id(client_id=None, api_key=None):\n    \"\"\"\n    Get the ima Knowledge Base ID to save content into.\n    Checks env var IMA_KNOWLEDGE_BASE_ID, then skill config, then queries the API.\n\n    Returns:\n        kb_id (str): The knowledge base ID\n\n    Raises:\n        ValueError: If no KB ID can be determined\n    \"\"\"\n    # 1. env var\n    kb_id = os.environ.get(\"IMA_KNOWLEDGE_BASE_ID\")\n    if kb_id:\n        return kb_id\n\n    # 2. user-approved skill config\n    skill_cfg = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_cfg.exists():\n        with open(skill_cfg) as f:\n            cfg = json.load(f)\n            kb_id = cfg.get(\"imaKnowledgeBaseId\", \"\")\n            if kb_id:\n                return kb_id\n\n    # 3. query API — return options so the agent can ask the user to choose\n    if client_id and api_key:\n        url = \"https://ima.qq.com/openapi/wiki/v1/get_addable_knowledge_base_list\"\n        body = json.dumps({}).encode(\"utf-8\")\n        try:\n            req = urllib.request.Request(\n                url,\n                data=body,\n                headers={\n                    \"ima-openapi-clientid\": client_id,\n                    \"ima-openapi-apikey\": api_key,\n                    \"Content-Type\": \"application/json\"\n                },\n                method=\"POST\"\n            )\n            with urllib.request.urlopen(req, timeout=30) as resp:\n                result = json.load(resp)\n        except Exception:\n            result = {}\n\n        if result.get(\"retcode\") == 0:\n            kb_list = result.get(\"data\", {}).get(\"addable_knowledge_base_list\", [])\n            if len(kb_list) == 1:\n                return kb_list[0].get(\"id\", \"\")\n            if len(kb_list) > 1:\n                options = [\n                    f\"{item.get('name', '(unnamed)')} ({item.get('id', '')})\"\n                    for item in kb_list\n                ]\n                raise ValueError(\n                    \"Multiple ima knowledge bases are available. Ask the user to choose one, \"\n                    \"then save its id as IMA_KNOWLEDGE_BASE_ID or imaKnowledgeBaseId. Options: \"\n                    + \"; \".join(options)\n                )\n\n    raise ValueError(\n        \"ima Knowledge Base ID not found. Set IMA_KNOWLEDGE_BASE_ID, \"\n        \"configure imaKnowledgeBaseId in ~/.agent-social-reader/config.json, \"\n        \"or ensure client_id + api_key can query get_addable_knowledge_base_list.\"\n    )\n\ndef _ima_api_request(endpoint, payload, client_id, api_key):\n    \"\"\"Make an ima OpenAPI request. Returns parsed JSON.\"\"\"\n    url = f\"https://ima.qq.com/openapi/wiki/v1/{endpoint}\"\n    body = json.dumps(payload).encode(\"utf-8\")\n    req = urllib.request.Request(\n        url,\n        data=body,\n        headers={\n            \"ima-openapi-clientid\": client_id,\n            \"ima-openapi-apikey\": api_key,\n            \"Content-Type\": \"application/json\"\n        },\n        method=\"POST\"\n    )\n    with urllib.request.urlopen(req, timeout=30) as resp:\n        result = json.load(resp)\n    if result.get(\"retcode\") != 0:\n        raise RuntimeError(f\"ima API error: {result.get('errmsg')} (code {result.get('retcode')})\")\n    return result\n\ndef _upload_to_ima_cos(tmp_path):\n    \"\"\"\n    Upload the markdown file to COS using ima-provided tooling.\n\n    Do not hand-roll the COS Authorization header here: Tencent COS REST signing\n    requires a full signature or a pre-signed URL, and ima's create_media response\n    should be uploaded with ima_cos_util / cos-upload.cjs or an official COS SDK.\n    A COS SDK implementation would need create_media's cos_credential fields\n    (bucket_name, region, cos_key, secret_id, secret_key, token).\n    \"\"\"\n    if shutil.which(\"ima_cos_util\"):\n        subprocess.run([\"ima_cos_util\", \"-f\", tmp_path], check=True, timeout=120)\n        return\n\n    cos_upload = shutil.which(\"cos-upload.cjs\")\n    if cos_upload:\n        subprocess.run([\"node\", cos_upload, \"-f\", tmp_path], check=True, timeout=120)\n        return\n\n    raise RuntimeError(\n        \"COS upload helper not found. Install/use ima_cos_util or the ima skills package \"\n        \"(cos-upload.cjs), or replace _upload_to_ima_cos with an official COS SDK upload \"\n        \"after verifying create_media's cos_credential format.\"\n    )\n\n# ── main save function ────────────────────────────────────────────────────────\n\ndef save_to_ima(title, source_url, platform, summary, full_text, kb_id, client_id, api_key):\n    \"\"\"\n    Save content to ima knowledge base via OpenAPI (Way B — upload Markdown).\n\n    Args:\n        title: Content title\n        source_url: Original URL\n        platform: Platform name\n        summary: AI-generated summary\n        full_text: Full article/post text\n        kb_id: Knowledge base ID\n        client_id: ima Client ID\n        api_key: ima API Key\n    \"\"\"\n    slug = _slugify(title)\n    markdown_body = (\n        f\"# {title}\\n\\n\"\n        f\"**Source**: {source_url}\\n\"\n        f\"**Platform**: {platform}\\n\\n\"\n        f\"## Summary\\n\\n{summary}\\n\\n\"\n        f\"## Full Text\\n\\n{full_text}\\n\"\n    )\n    byte_size = len(markdown_body.encode(\"utf-8\"))\n    timestamp = int(time.time())\n\n    # Write temp file for COS upload\n    tmp_path = f\"/tmp/asr_ima_{slug}.md\"\n    Path(tmp_path).write_text(markdown_body, encoding=\"utf-8\")\n\n    # Step 1: create_media\n    media_result = _ima_api_request(\n        \"create_media\",\n        {\n            \"file_name\": f\"{slug}.md\",\n            \"file_size\": byte_size,\n            \"content_type\": \"text/markdown\",\n            \"knowledge_base_id\": kb_id,\n            \"file_ext\": \"md\"\n        },\n        client_id,\n        api_key\n    )\n    media_id = media_result[\"data\"][\"media_id\"]\n    cos_cred = media_result[\"data\"][\"cos_credential\"]\n\n    # Step 2: COS upload\n    cos_key = cos_cred[\"cos_key\"]\n    _upload_to_ima_cos(tmp_path)\n\n    # Step 3: add_knowledge\n    _ima_api_request(\n        \"add_knowledge\",\n        {\n            \"media_type\": 7,\n            \"media_id\": media_id,\n            \"title\": title,\n            \"knowledge_base_id\": kb_id,\n            \"folder_id\": kb_id,\n            \"file_info\": {\n                \"cos_key\": cos_key,\n                \"file_size\": byte_size,\n                \"file_name\": f\"{slug}.md\",\n                \"last_modify_time\": timestamp\n            }\n        },\n        client_id,\n        api_key\n    )\n\n    # Cleanup temp file\n    Path(tmp_path).unlink(missing_ok=True)\n    return True\n```\n\n**Usage**:\n```python\nclient_id, api_key = _get_ima_credentials()\nkb_id = _get_ima_kb_id(client_id, api_key)\nsave_to_ima(\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    platform=\"Web\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text\",\n    kb_id=kb_id,\n    client_id=client_id,\n    api_key=api_key\n)\n```\n\n**Note**: For Windows PowerShell 5.1 environments, the request body must be explicitly encoded as UTF-8 bytes. See `references/tool-details.md` §10 for the PowerShell workaround.\n\n---\n\n## Save to Notion\n\n**When to use**: User says \"save to Notion\" and no native Notion tool (notion-cli, mcporter-notion, custom Notion connector) is available.\n\n**Dependencies**: urllib standard library only — no external packages required.\n\n```python\nimport urllib.request, urllib.error, json\n\ndef _paragraph_blocks(text, chunk_size=1900):\n    \"\"\"Split text into Notion paragraph blocks, respecting the 1900-char limit per block.\"\"\"\n    chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)] or [\"\"]\n    return [\n        {\n            \"object\": \"block\",\n            \"type\": \"paragraph\",\n            \"paragraph\": {\"rich_text\": [{\"type\": \"text\", \"text\": {\"content\": chunk}}]}\n        }\n        for chunk in chunks\n    ]\n\ndef save_to_notion(token, target_id, title, source_url, platform, summary, full_text=\"\", target_type=\"database\"):\n    \"\"\"\n    Archive content to Notion.\n\n    Args:\n        token: Notion Integration Token\n        target_id: Database ID (target_type=\"database\") or Page ID (target_type=\"page\")\n        title: Page title\n        source_url: Original URL\n        platform: Platform name (e.g. \"Twitter\", \"Instagram\", \"Web\")\n        summary: AI-generated summary\n        full_text: Full article/post text (optional)\n        target_type: \"database\" (default) or \"page\"\n    \"\"\"\n    body_text = f\"Source: {source_url}\\nPlatform: {platform}\\n\\nSummary:\\n{summary}\\n\\nFull text:\\n{full_text}\"\n    children = _paragraph_blocks(body_text)\n\n    if len(children) > 100:\n        raise ValueError(\n            f\"Notion content too long: {len(children)} blocks exceeds 100-block single-request limit. \"\n            \"Ask the user whether to save the summary only, or use batched PATCH append requests.\"\n        )\n\n    if target_type == \"database\":\n        url = \"https://api.notion.com/v1/pages\"\n        payload = {\n            \"parent\": {\"database_id\": target_id},\n            \"properties\": {\n                \"Name\": {\"title\": [{\"type\": \"text\", \"text\": {\"content\": title[:2000]}}]},\n                \"Source\": {\"url\": source_url}\n            },\n            \"children\": children\n        }\n        method = \"POST\"\n    else:\n        url = f\"https://api.notion.com/v1/blocks/{target_id}/children\"\n        payload = {\"children\": children}\n        method = \"PATCH\"\n\n    req = urllib.request.Request(\n        url,\n        data=json.dumps(payload).encode(),\n        headers={\n            \"Authorization\": f\"Bearer {token}\",\n            \"Notion-Version\": \"2022-06-28\",\n            \"Content-Type\": \"application/json\"\n        },\n        method=method\n    )\n    with urllib.request.urlopen(req) as resp:\n        return json.load(resp)\n```\n\n**Usage**:\n```python\nresult = save_to_notion(\n    token=\"secret_xxx\",\n    target_id=\"database_or_page_id\",\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    platform=\"Twitter\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text here\",\n    target_type=\"database\"  # or \"page\"\n)\n```\n\n**Notes**:\n- Default database schema assumed: title property `Name`, URL property `Source`. If different, ask user for the actual property names.\n- Notion API limits children to 100 blocks per single request. If content exceeds 100 blocks, raise ValueError and ask user.\n- Never silently truncate content.\n\n---\n\n## Save to Obsidian\n\n**When to use**: User says \"save to Obsidian\" and no native Obsidian integration tool is available.\n\nBefore writing, confirm the vault path and note title for the current item.\n\n**Dependencies**: pathlib + re standard library only — no external packages required.\n\n```python\nfrom pathlib import Path\nimport re\n\ndef slugify(title):\n    \"\"\"\n    Convert title to a safe filename slug.\n    - Replaces non-alphanumeric characters (except Chinese chars, dots, hyphens) with hyphens\n    - Strips leading/trailing hyphens and dots\n    - Truncates to 80 characters\n    - Returns 'untitled' if result is empty\n    \"\"\"\n    slug = re.sub(r\"[^\\w\\u4e00-\\u9fff.-]+\", \"-\", title.strip(), flags=re.UNICODE)\n    slug = slug.strip(\".-\")[:80]\n    return slug or \"untitled\"\n\ndef unique_note_path(vault, slug):\n    note_path = vault / f\"{slug}.md\"\n    if not note_path.exists():\n        return note_path\n    counter = 2\n    while True:\n        candidate = vault / f\"{slug}-{counter}.md\"\n        if not candidate.exists():\n            return candidate\n        counter += 1\n\ndef save_to_obsidian(vault_path, title, source_url, summary, full_text=\"\"):\n    \"\"\"\n    Write content as a Markdown note in an Obsidian vault.\n\n    Args:\n        vault_path: Absolute path to the Obsidian vault root\n        title: Note title (used for filename and H1)\n        source_url: Original URL\n        summary: AI-generated summary\n        full_text: Full article/post text (optional)\n\n    Returns:\n        Absolute path to the created note\n\n    Raises:\n        FileNotFoundError: vault_path does not exist or is not a directory\n        ValueError: Filename would escape vault directory (path traversal attempt)\n    \"\"\"\n    vault = Path(vault_path).expanduser().resolve()\n    if not vault.exists() or not vault.is_dir():\n        raise FileNotFoundError(f\"Obsidian vault path does not exist: {vault}\")\n\n    note_path = unique_note_path(vault, slugify(title))\n    body = (\n        f\"# {title}\\n\\n\"\n        f\"Source: {source_url}\\n\\n\"\n        f\"## Summary\\n\\n{summary}\\n\\n\"\n        f\"## Full Text\\n\\n{full_text}\\n\"\n    )\n\n    # Security: ensure path stays within vault (prevent path traversal)\n    resolved_path = note_path.resolve()\n    if not str(resolved_path).startswith(str(vault.resolve())):\n        raise ValueError(f\"Filename would escape vault: {note_path}\")\n\n    note_path.write_text(body, encoding=\"utf-8\")\n    return str(note_path)\n```\n\n**Usage**:\n```python\nnote_path = save_to_obsidian(\n    vault_path=\"/Users/user/Documents/my-vault\",\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text here\"\n)\nprint(f\"Saved to: {note_path}\")\n```\n\n**Notes**:\n- slugify supports Unicode (Chinese, Japanese, etc.) via `\\u4e00-\\u9fff` range\n- Existing notes are not overwritten; duplicate titles create `-2`, `-3`, etc. suffixes\n- Path traversal protection: filename is always relative to vault root; absolute paths in title are rejected\n- If vault_path contains symlinks, `resolve()` normalizes them — ensure the final path is within the vault\n\nFile v2.0.1:references/tool-details.md\n\n# Platform Tool Details\n\nWhen the Routing Map in SKILL.md routes to a specific tool, read this file for the exact commands, API parameters, and error handling.\n\n## Service Boundaries\n\nOnly call a service when the current user request requires that service. Do not send the same content to every listed service.\n\n- AgentLens receives a URL only when AgentLens is needed for the current read request.\n- OpenAI Whisper receives audio only if the user chooses API transcription.\n- Notion and ima receive archive content only when the user asks to save the current item and confirms the target destination.\n- Obsidian writes to the user's local vault path.\n- Credential examples read only the explicit variables and config paths shown here. Prefer platform secret storage or environment variables; use local plaintext files only after user approval.\n\n---\n\n## 1. General Web Pages — r.jina.ai\n\n**Tool**: r.jina.ai\n**API key**: None (free)\n**Command**:\n```bash\ncurl -L -s \"https://r.jina.ai/{url}\"\n```\nReturns clean Markdown. Works for most public web pages.\n\n**Note**: `{url}` must include the full `https://` prefix:\n```bash\ncurl -L -s \"https://r.jina.ai/https://example.com/article\"\n```\n\n**Fallback**: If Jina returns empty content, the page likely requires JS rendering. Try Camoufox or AgentLens.\n\n---\n\n## 2. X / Twitter — FxTwitter API\n\n**Tool**: FxTwitter API\n**API key**: None (free, no login or cookie required)\n**Code**:\n```python\nimport urllib.request, urllib.error, re, json\n\ndef fetch_tweet(url):\n    pattern = r'(?:x\\.com|twitter\\.com)/([a-zA-Z0-9_]{1,15})/status/(\\d+)'\n    match = re.search(pattern, url)\n    if not match:\n        return {\"error\": f\"Cannot parse tweet URL: {url}\"}\n    username, tweet_id = match.group(1), match.group(2)\n    api_url = f\"https://api.fxtwitter.com/{username}/status/{tweet_id}\"\n    req = urllib.request.Request(api_url, headers={\"User-Agent\": \"Mozilla/5.0\"})\n    try:\n        with urllib.request.urlopen(req, timeout=30) as resp:\n            data = json.loads(resp.read().decode())\n    except urllib.error.HTTPError as e:\n        return {\"error\": f\"FxTwitter HTTP {e.code}: could not fetch tweet (may be deleted or private)\"}\n    except Exception as e:\n        return {\"error\": f\"FxTwitter error: {e}\"}\n    if data.get(\"code\") != 200:\n        return {\"error\": f\"FxTwitter error: {data.get('message')}\"}\n    tweet = data[\"tweet\"]\n    return {\n        \"text\": tweet.get(\"text\", \"\"),\n        \"author\": tweet.get(\"author\", {}).get(\"name\", \"\"),\n        \"screen_name\": tweet.get(\"author\", {}).get(\"screen_name\", \"\"),\n        \"likes\": tweet.get(\"likes\", 0),\n        \"retweets\": tweet.get(\"retweets\", 0),\n        \"views\": tweet.get(\"views\", 0),\n        \"replies\": tweet.get(\"replies\", 0),\n        \"created_at\": tweet.get(\"created_at\", \"\"),\n        \"media\": tweet.get(\"media\", {}).get(\"all\", []),\n        \"quote\": tweet.get(\"quote\", {})\n    }\n```\n**Fallback**: AgentLens API (use when FxTwitter returns NOT_FOUND or rate-limit error)\n**Limitation**: Thread replies, user timelines, X Lists, and private accounts are not supported by any free tool. AgentLens does not support these either.\n\n---\n\n## 3. YouTube — youtube-transcript-api\n\n**Tool**: youtube-transcript-api\n**API key**: None (free)\n**Installation**: `pip install youtube-transcript-api`\n**Code**:\n```python\nimport re\n\ndef get_youtube_subtitle(video_url, lang=\"en\"):\n    from youtube_transcript_api import YouTubeTranscriptApi\n    patterns = [\n        r'(?:youtube\\.com/watch\\?.*?v=)([a-zA-Z0-9_-]{11})',\n        r'(?:youtu\\.be/)([a-zA-Z0-9_-]{11})',\n        r'(?:youtube\\.com/shorts/)([a-zA-Z0-9_-]{11})',\n    ]\n    video_id = None\n    for pat in patterns:\n        m = re.search(pat, video_url)\n        if m:\n            video_id = m.group(1)\n            break\n    if not video_id:\n        return {\"error\": \"Cannot parse YouTube URL\"}\n    # Language priority: requested lang first, then zh-Hans/zh/en, then any available\n    langs = [lang, \"zh-Hans\", \"zh\", \"en\"]\n    try:\n        # Try 0.x API\n        transcript_list = YouTubeTranscriptApi.get_transcript(video_id, languages=langs)\n        entries = [\n            {\"text\": entry[\"text\"], \"start\": entry[\"start\"], \"duration\": entry[\"duration\"]}\n            for entry in transcript_list\n        ]\n        text = \" \".join(e[\"text\"] for e in entries)\n        return {\"video_id\": video_id, \"language\": lang, \"transcript\": text, \"entries\": entries}\n    except Exception:\n        pass\n    # Fallback to 1.x API\n    try:\n        api = YouTubeTranscriptApi()\n        fetched = api.fetch(video_id, languages=langs)\n        entries = [\n            {\"text\": s.text, \"start\": s.start, \"duration\": s.duration}\n            for s in fetched\n        ]\n        text = \" \".join(e[\"text\"] for e in entries)\n        return {\"video_id\": video_id, \"language\": lang, \"transcript\": text, \"entries\": entries}\n    except Exception as e:\n        return {\"error\": f\"YouTube transcript unavailable: {e}. Try video summarization via SOP C.\"}\n```\n**Transcript source priority**: Creator-uploaded subtitles → auto-generated subtitles.\n**Fallback**: If blocked (403/429 from YouTube), fall back to AgentLens API + Whisper (see SOP C).\n**Limitation**: Subtitles must be enabled on the video. If fully disabled, use SOP C.\n\n---\n\n## 4. WeChat Official Account — Camoufox\n\n**Tool**: Camoufox (headless browser for JS-rendered content)\n**API key**: None (free)\n**Installation**: `pip install camoufox`\n**Compatibility note**: Camoufox may download a large browser bundle on first launch and can fail with Playwright protocol errors on some Python/browser-version combinations. Treat Camoufox as a best-effort renderer, not a guaranteed dependency.\n**Code**:\n```python\nimport asyncio\nfrom camoufox.async_api import AsyncCamoufox\n\nasync def read_wechat(url):\n    async with AsyncCamoufox(headless=True) as browser:\n        page = await browser.new_page()\n        try:\n            await page.goto(url, wait_until=\"networkidle\", timeout=30000)\n            await asyncio.sleep(3)\n            content = await page.locator(\"body\").inner_text()\n            return content\n        finally:\n            await page.close()\n\nprint(asyncio.run(read_wechat(\"https://mp.weixin.qq.com/s/ARTICLE_ID\"))[:5000])\n```\n**Fallback**: If Camoufox fails or is unavailable, try AgentLens API if configured. If all automated paths fail, ask the user to share copied text or screenshots.\n\n---\n\n## 5. Weibo — r.jina.ai\n\n**Tool**: r.jina.ai\n**API key**: None (free)\n**Command**:\n```bash\ncurl -L -s \"https://r.jina.ai/{weibo_url}\"\n```\nr.jina.ai handles Weibo's mobile pages and bypasses login walls in most cases.\n**Fallback**: AgentLens API.\n\n---\n\n## 6. AgentLens API — 20+ Social Platforms\n\n**Purpose**: Mandatory handling channel for all platforms not covered by free tools above.\n**Primary endpoint**: `POST https://agentlensapi.io/api/v1/fetch`\n**Avoid by default**: `https://api.agentlensapi.io/v1/fetch` because some local proxy/fake-IP environments can fail TLS for the API subdomain while the main host works.\n**Auth**: `Authorization: Bearer {AGENT_LENS_API_KEY}`\n\n**Boundary**: Send only the URL for the current read request. Do not include unrelated user files, saved archives, or credentials in the request body.\n\n#### Supported Platforms\n\n| Global Platforms | Chinese Platforms |\n|:---|:---|\n| TikTok | Douyin |\n| Instagram | Xiaohongshu |\n| YouTube | Bilibili |\n| X (Twitter) | Weibo |\n| Facebook | Kuaishou |\n| Threads | Xigua |\n| Reddit | Zhihu (columns) |\n| LinkedIn | WeChat Official Account (articles) |\n| Twitch (clips) | WeChat Channels |\n| Pinterest | |\n| Bluesky | |\n| Snapchat | |\n| Kick (clips) | |\n| Lemon8 | |\n\n**AgentLens reads**: main text content + media files (images/videos via `downloadUrlList`)\n**AgentLens does NOT read**: comment sections, X/Reddit thread conversations\n\n#### API Request\n```http\nPOST https://agentlensapi.io/api/v1/fetch\nAuthorization: Bearer {AGENT_LENS_API_KEY}\nContent-Type: application/json\n\n{ \"url\": \"https://...\" }\n```\n\n#### Response Fields\n```yaml\ndata.status: HTTP-style status code (200 = success)\ndata.message: Success message or descriptive error\ndata.data.platform: Platform identifier (e.g., \"youtube\", \"tiktok\")\ndata.data.name: Author / channel / username\ndata.data.title: Content title\ndata.data.description: Full text content\ndata.data.downloadUrlList[].type: \"video\" or \"pic\"\ndata.data.downloadUrlList[].sourceUrl: Direct media URL\ndata.data.subtitle: Transcript if available\n```\nSome error responses use a top-level envelope instead:\n```yaml\nsuccess: false\nerror.code: AUTH_FAILED\nerror.message: API Key invalid or disabled\n```\nParse both shapes.\n\n#### Core Retrieval Script\n```python\nimport urllib.request, urllib.error, json\n\nAGENTLENS_ENDPOINTS = [\n    \"https://agentlensapi.io/api/v1/fetch\",\n]\n\ndef fetch_social_content(url, api_key):\n    last_error = None\n    for endpoint in AGENTLENS_ENDPOINTS:\n        req = urllib.request.Request(\n            endpoint,\n            data=json.dumps({\"url\": url}).encode(),\n            headers={\n                \"Authorization\": f\"Bearer {api_key}\",\n                \"Content-Type\": \"application/json\"\n            },\n            method=\"POST\"\n        )\n        try:\n            with urllib.request.urlopen(req, timeout=60) as resp:\n                result = json.load(resp)\n        except urllib.error.HTTPError as e:\n            body = e.read().decode(\"utf-8\", \"replace\")\n            try:\n                result = json.loads(body)\n            except json.JSONDecodeError:\n                return {\"error\": f\"AgentLens HTTP {e.code}: {body[:300]}\"}\n        except Exception as e:\n            last_error = f\"{type(e).__name__}: {e}\"\n            continue\n\n        envelope = result.get(\"data\", result)\n        status = envelope.get(\"status\") or result.get(\"status\")\n        if str(status) == \"200\" or result.get(\"success\") is True:\n            content = envelope.get(\"data\", envelope)\n            return {\n                \"platform\": content.get(\"platform\"),\n                \"author\": content.get(\"name\"),\n                \"title\": content.get(\"title\"),\n                \"description\": content.get(\"description\"),\n                \"media\": content.get(\"downloadUrlList\", []),\n                \"subtitle\": content.get(\"subtitle\")\n            }\n\n        error = result.get(\"error\") or {}\n        code = error.get(\"code\") or envelope.get(\"code\") or status\n        message = error.get(\"message\") or envelope.get(\"message\") or result.get(\"message\") or \"Unknown error\"\n        status_str = str(status)\n        code_str = str(code).lower()\n        message_str = str(message).lower()\n\n        if status_str == \"10032\" or \"unsupported\" in code_str or \"unsupported\" in message_str or \"not supported\" in message_str:\n            return {\"error\": message, \"code\": code, \"status\": status, \"action\": \"trigger_sop_b\"}\n\n        return {\"error\": message, \"code\": code, \"status\": status}\n\n    return {\"error\": last_error or \"AgentLens request failed before receiving a response\"}\n```\n\n#### Error Handling\n- **Status 200**: Success — extract and summarize content.\n- **Status 10032 or unsupported-platform message/code**: Trigger SOP B immediately.\n- **AUTH_FAILED / HTTP 401**: Ask the user to provide or refresh `AGENT_LENS_API_KEY`; do not retry repeatedly.\n- **Other errors** (timeout, parse failure): Inform user the link could not be read at this time.\n\n---\n\n## 7. Video Summarization\n\n**Boundary**: Download or transcribe media only when the current user request requires spoken-content summarization or media analysis. Send audio to OpenAI Whisper only if the user chooses API transcription.\n\nSee **SOP C** for the complete flow. Tool details for Steps 3–6:\n\n**Download**:\n```bash\ncurl -L --fail --max-time 120 -o /tmp/asr_{platform}_{timestamp}.mp4 \"{sourceUrl}\"\n```\n\n**YouTube fallback**:\n```bash\nyt-dlp -f \"bv*+ba/best\" --merge-output-format mp4 --remux-video mp4 -o \"/tmp/asr_youtube_{timestamp}.mp4\" \"{url}\"\n```\n\n**Audio extraction**:\n```bash\nffmpeg -y -i /tmp/asr_{platform}_{timestamp}.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/asr_audio_{timestamp}.wav\n```\n> **Required**: ffmpeg must be installed. If missing: `apt install ffmpeg` (Linux), `brew install ffmpeg` (macOS), or download from ffmpeg.org (Windows).\n\n**Local Whisper** (free, CPU):\n```bash\npip install faster-whisper\n# Model auto-downloads on first use (tiny/base ≈75MB, large-v3 ≈3GB)\n```\n```python\nfrom faster_whisper import WhisperModel\n\nmodel = WhisperModel(\"base\", device=\"cpu\", compute_type=\"int8\")\nsegments, _ = model.transcribe(\"/tmp/asr_audio_{timestamp}.wav\")\ntranscript = \" \".join(segment.text for segment in segments)\n```\n\n**OpenAI Whisper API** ($0.006/min, 99+ languages):\n```python\nimport urllib.request, urllib.error, json\n\ndef transcribe_whisper(audio_path, api_key, model=\"whisper-1\"):\n    boundary = \"----WebKitFormBoundary7MA4YWxkTrZu0gW\"\n    with open(audio_path, \"rb\") as f:\n        audio_data = f.read()\n    body = (\n        f\"--{boundary}\\r\\n\"\n        f\"Content-Disposition: form-data; name=\\\"model\\\"\\r\\n\\r\\n{model}\\r\\n\"\n        f\"--{boundary}\\r\\n\"\n        f\"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"audio.wav\\\"\\r\\n\"\n        f\"Content-Type: audio/wav\\r\\n\\r\\n\"\n    ).encode('utf-8') + audio_data + f\"\\r\\n--{boundary}--\\r\\n\".encode('utf-8')\n    req = urllib.request.Request(\n        \"https://api.openai.com/v1/audio/transcriptions\",\n        data=body,\n        headers={\n            \"Authorization\": f\"Bearer {api_key}\",\n            \"Content-Type\": f\"multipart/form-data; boundary={boundary}\"\n        },\n        method=\"POST\"\n    )\n    with urllib.request.urlopen(req, timeout=120) as resp:\n        result = json.load(resp)\n        return result.get(\"text\", \"\")\n```\n\n**Note**: OpenAI Whisper API has a 25MB file size limit. For long videos, first normalize/compress the audio, then split into ~5-minute chunks and check each chunk's file size:\n```bash\nmkdir -p /tmp/asr_audio_chunks\n# Normalize to mono 16kHz MP3 at a controlled bitrate\nffmpeg -y -i /tmp/asr_audio_{timestamp}.wav -vn -ac 1 -ar 16000 -c:a libmp3lame -b:a 64k /tmp/asr_audio_{timestamp}.mp3\n# Cut into ~5-min chunks (safer than 10-min for the 25MB limit)\nffmpeg -y -i /tmp/asr_audio_{timestamp}.mp3 -f segment -segment_time 300 -c copy /tmp/asr_audio_chunks/chunk_%03d.mp3\n# Check chunk sizes; any ≥ 24MB → shorten to 3 min or re-encode at lower bitrate\nfind /tmp/asr_audio_chunks -name \"chunk_*.mp3\" -exec ls -lh {} \\;\n```\nIf multiple chunks were transcribed → concatenate all text parts in order before summarization.\n\n---\n\n## 8. RSS Feeds — feedparser\n\n**Tool**: feedparser\n**API key**: None (free)\n**Installation**: `pip install feedparser`\n**Code**:\n```python\nimport feedparser\n\ndef read_rss(feed_url, limit=10):\n    feed = feedparser.parse(feed_url)\n    return [\n        {\n            \"title\": entry.get(\"title\", \"\"),\n            \"link\": entry.get(\"link\", \"\"),\n            \"published\": entry.get(\"published\", \"\"),\n            \"summary\": entry.get(\"summary\", \"\"),\n        }\n        for entry in feed.entries[:limit]\n    ]\n\nfor item in read_rss(\"FEED_URL\"):\n    print(f\"{item['title']} - {item['link']}\")\n```\n\n---\n\n## 9. ima OpenAPI — Knowledge Base\n\nima is Tencent's cloud knowledge base, accessed via OpenAPI (Token + target ID). It works across all channels — inside ima, via Feishu/Lark, Enterprise WeChat, or any third-party agent.\n\n**Boundary**: Use ima only when the current user request asks to save the current item to ima and the target knowledge base is confirmed.\n\n### Service Info\n\n- **Base URL**: `https://ima.qq.com`\n- **Base Path**: `/openapi/wiki/v1`\n- **Auth Headers**:\n  - `ima-openapi-clientid`: Client ID\n  - `ima-openapi-apikey`: API Key\n  - `Content-Type`: `application/json`\n- **Env var mapping**:\n  - `IMA_CLIENT_ID` → header `ima-openapi-clientid`\n  - `IMA_API_KEY` → header `ima-openapi-apikey`\n  - `IMA_KNOWLEDGE_BASE_ID` → request body `knowledge_base_id` / `folder_id`\n\n### Response Format\n\nAll APIs return: `{\"retcode\": 0, \"errmsg\": \"成功\", \"data\": { }}`\n- `retcode=0` = success, extract from `data`\n- `retcode≠0` = failure, show `errmsg` to user\n\n### Error Codes\n\n| Code | Meaning | Action |\n|:-----|:--------|:-------|\n| 0 | Success | — |\n| 110001 | Invalid parameter | Check request |\n| 110021 | Rate limited | Retry after delay |\n| 110030 | No permission | Verify Client ID / API Key / KB ID |\n\n### File Size Limits\n\n| Type | Limit |\n|:-----|:------|\n| Markdown / TXT / Excel | ≤10MB |\n| Images | ≤30MB |\n| PDF / Word / PPT / Audio | ≤200MB |\n| Audio duration | ≤2 hours |\n\n### 9.1 Way A — import_urls (simplest)\n\nDirectly import URLs into ima knowledge base; ima parses the content itself. Use when user only wants to archive links.\n\n```bash\ncurl -X POST \"https://ima.qq.com/openapi/wiki/v1/import_urls\" \\\n -H \"ima-openapi-clientid: {IMA_CLIENT_ID}\" \\\n -H \"ima-openapi-apikey: {IMA_API_KEY}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n   \"knowledge_base_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"folder_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"urls\": [\"https://example.com/post\"]\n }'\n```\n- `urls`: 1–10 URLs per request\n- `folder_id` = `knowledge_base_id` for root directory\n- Returns `ret_code` per URL (0 = success) and `media_id`; the top-level envelope uses `retcode`\n\n### 9.2 Way B — Upload Markdown (recommended for AI summaries)\n\nThree-step: create_media → COS upload → add_knowledge.\n\n#### Step 1: create_media\n\n```bash\ncurl -X POST \"https://ima.qq.com/openapi/wiki/v1/create_media\" \\\n -H \"ima-openapi-clientid: {IMA_CLIENT_ID}\" \\\n -H \"ima-openapi-apikey: {IMA_API_KEY}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n   \"file_name\": \"{slug}.md\",\n   \"file_size\": {byte_size},\n   \"content_type\": \"text/markdown\",\n   \"knowledge_base_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"file_ext\": \"md\"\n }'\n```\nReturns `media_id` and `cos_credential` (含 token, secret_id, secret_key, bucket_name, region, cos_key).\n\n#### Step 2: COS Upload\n\n> ⚠️ **Reference / pending verification**: The curl Authorization header format below is documented for completeness. The actual upload method depends on what `create_media` returns — if it is a pre-signed URL, use that URL directly with the method indicated by the response. For a reliable copy-pasteable upload, use `ima_cos_util` or the official COS SDK (see below) instead of the raw curl below.\n\nWrite markdown to temp file, then upload:\n```bash\ncat > /tmp/asr_{slug}.md << 'EOF'\n{markdown_content}\nEOF\n\ncurl -X PUT \"https://{bucket}-{region}.myqcloud.com/{cos_key}\" \\\n -H \"Authorization: COS {secret_id}/{secret_key}/{token}\" \\\n -H \"Content-Type: text/markdown\" \\\n --data-binary @/tmp/asr_{slug}.md\n```\n\nOr use the built-in `ima_cos_util` if available:\n```bash\nima_cos_util -f /tmp/asr_{slug}.md\n```\n\n#### Step 3: add_knowledge\n\n```bash\ncurl -X POST \"https://ima.qq.com/openapi/wiki/v1/add_knowledge\" \\\n -H \"ima-openapi-clientid: {IMA_CLIENT_ID}\" \\\n -H \"ima-openapi-apikey: {IMA_API_KEY}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n   \"media_type\": 7,\n   \"media_id\": \"{media_id}\",\n   \"title\": \"{title}\",\n   \"knowledge_base_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"folder_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"file_info\": {\n     \"cos_key\": \"{cos_key}\",\n     \"file_size\": {byte_size},\n     \"file_name\": \"{slug}.md\",\n     \"last_modify_time\": {unix_timestamp}\n   }\n }'\n```\n- `media_type: 7` = Markdown\n- `folder_id` = `knowledge_base_id` for root directory\n\n### 9.3 get_addable_knowledge_base_list\n\nGet user's writable knowledge base list (for first-time setup):\n```bash\ncurl -X POST \"https://ima.qq.com/openapi/wiki/v1/get_addable_knowledge_base_list\" \\\n -H \"ima-openapi-clientid: {IMA_CLIENT_ID}\" \\\n -H \"ima-openapi-apikey: {IMA_API_KEY}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"cursor\": \"\", \"limit\": 20, \"knowledge_base_id\": \"{IMA_KNOWLEDGE_BASE_ID}\"}'\n```\nReturns `addable_knowledge_base_list` (含 id + name), `next_cursor`, `is_end`.\n\n### 9.4 check_repeated_names\n\nCheck if filename already exists in target folder:\n```bash\ncurl -X POST \"https://ima.qq.com/openapi/wiki/v1/check_repeated_names\" \\\n -H \"ima-openapi-clientid: {IMA_CLIENT_ID}\" \\\n -H \"ima-openapi-apikey: {IMA_API_KEY}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n   \"knowledge_base_id\": \"{IMA_KNOWLEDGE_BASE_ID}\",\n   \"params\": [{\"name\": \"{slug}.md\", \"media_type\": 7}]\n }'\n```\nReturns `is_repeated` (bool). If true, ask user whether to overwrite or rename.\n\n### 9.5 MediaType Reference\n\n| Value | Type |\n|:------|:-----|\n| 1 | PDF |\n| 2 | Web page |\n| 3 | Word |\n| 4 | PPT |\n| 5 | Excel |\n| 6 | WeChat Official Account article |\n| 7 | Markdown |\n| 9 | Image |\n| 11 | Note |\n| 13 | TXT |\n\n---\n\n## §10. ima Setup — Scene 2 (WorkBuddy) & Scene 3 (External Agent)\n\nThese sections document the one-time setup for the two OpenAPI-based ima write paths.\n\n### 10.1 Scene 2 — WorkBuddy Setup (One-time)\n\n**Prerequisite**: ima account + WorkBuddy account must be on the same WeChat ID.\n\n**Step 1 — Install ima skill**:\nWorkBuddy left menu → 「专家」→ search \"ima\" → install 「ima笔记」skill.\n\n**Step 2 — Get API credentials**:\nBrowser open `https://ima.qq.com/agent-interface` → WeChat scan → copy Client ID and API Key. ⚠️ API Key is shown only once — save immediately.\n\n**Step 3 — Configure credentials**:\nIn WorkBuddy chat, say: \"配置 IMA 凭证，Client ID 是 XXX，API Key 是 XXX\".\n\n**Step 4 — Verify connectivity**:\nSend \"帮我查看我的 IMA 知识库列表\" and confirm it returns the KB list.\n\nAfter setup, save `imaScenario: \"workbuddy\"` in `~/.agent-social-reader/config.json` only if the user approved local plaintext config storage.\n\n### 10.2 Scene 3 — External Agent Setup (One-time)\n\nApplicable to OpenClaw, Hermes Agent, Claude Code, ChatGPT/Codex, and other third-party agent platforms.\n\n**Step 1 — Get API credentials**:\nBrowser open `https://ima.qq.com/agent-interface` → WeChat scan → copy Client ID and API Key. ⚠️ API Key shown only once.\n\n**Step 2 — Store credentials**:\n\n**Option A — Environment variables (recommended)**:\n```bash\nexport IMA_CLIENT_ID=\"your_client_id\"\nexport IMA_API_KEY=\"your_api_key\"\n```\nPersistence varies by platform:\n- **Hermes Agent**: consider persisting to a .env file in the Hermes config directory (path may vary by version — check your Hermes installation for the correct location)\n- **Claude Code**: write to `~/.zshrc` or `~/.bashrc`\n- **OpenClaw**: write to skill config via `~/.agent-social-reader/config.json` only if the user approved local plaintext config storage\n- **ChatGPT/Codex**: add to Custom Instructions or environment config\n\n**Option B — Config files (all platforms, only after user approval for local plaintext storage)**:\n```bash\nmkdir -p ~/.config/ima\necho \"your_client_id\" > ~/.config/ima/client_id\necho \"your_api_key\" > ~/.config/ima/api_key\n```\nAgent checks env vars first, then falls back to `~/.config/ima/` only if the user approved local plaintext config storage.\n\n**Step 3 — Verify connectivity**:\n```bash\nIMA_CLIENT_ID=\"${IMA_CLIENT_ID:-$(cat ~/.config/ima/client_id 2>/dev/null)}\"\nIMA_API_KEY=\"${IMA_API_KEY:-$(cat ~/.config/ima/api_key 2>/dev/null)}\"\ncurl -s -X POST \"https://ima.qq.com/openapi/wiki/v1/get_addable_knowledge_base_list\" \\\n -H \"ima-openapi-clientid: $IMA_CLIENT_ID\" \\\n -H \"ima-openapi-apikey: $IMA_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"cursor\": \"\", \"limit\": 20}'\n```\nReturns the knowledge base list. Ask user to pick a KB and record its `id`.\n\n**Step 4 — Save KB ID**: If the user approved local plaintext config storage, store the chosen KB ID in `~/.agent-social-reader/config.json` as `imaKnowledgeBaseId`. Set `imaScenario: \"external-api\"`.\n\n**Optional — Install ima skills package** (includes `ima_api.cjs` and `cos-upload.cjs`):\n```bash\ncurl -o /tmp/ima-skills.zip \"https://app-dl.ima.qq.com/skills/ima-skills-1.1.7.zip\"  # version may change; check https://app-dl.ima.qq.com/skills/ for latest\ncd /tmp && unzip -o ima-skills.zip\n```\n\n### 10.3 PowerShell 5.1 — UTF-8 Encoding Fix\n\n**⚠️ Applies to**: Windows PowerShell 5.1 environments.\n\n**Problem**: All API request bodies must be explicitly converted to UTF-8 bytes, otherwise Chinese characters in content will corrupt.\n\n**Detection**: Check if `$PSVersionTable.PSVersion.Major -lt 7` (PowerShell 5.1 = version < 7).\n\n**Fix**: Encode body explicitly:\n```powershell\n$body = [System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json $payload))\n$headers = @{\n    \"ima-openapi-clientid\" = $clientId\n    \"ima-openapi-apikey\"   = $apiKey\n    \"Content-Type\"         = \"application/json; charset=utf-8\"\n}\nInvoke-RestMethod -Uri $url -Method POST -Headers $headers -Body $body\n```\nIn bash/shell environments this is automatic — no special handling needed.\n\nFile v2.0.1:skill-card.md\n\n## Description:\n\nAgent Social Reader helps an agent read, summarize, analyze, extract, and explicitly archive specific public web, social media, video, article, and RSS links across major platforms.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[hermiod99-vibe](https://clawhub.ai/user/hermiod99-vibe)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nExternal users, knowledge workers, product managers, and agent developers use this skill to retrieve and summarize specific public links from web and social platforms, then archive the current item to Notion, Obsidian, or ima only when explicitly requested.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: API keys and destination credentials could be exposed through chat, memory, shell profiles, Custom Instructions, or plaintext local configuration.\n\nMitigation: Prefer platform secret storage or environment variables, use plaintext local configuration only after explicit user approval, and never print or archive full tokens.\n\nRisk: Archive actions can send retrieved content to Notion or ima, or write files into an Obsidian vault.\n\nMitigation: Perform saves only when the current user request explicitly names the destination, and confirm the target database, knowledge base, or vault before writing.\n\nRisk: Bundled save scripts and optional install helpers may need hardening before use with important files or knowledge bases.\n\nMitigation: Review and harden the scripts before deployment, and avoid optional zip helpers unless their contents and source are verified.\n\nRisk: Temporary media cleanup could delete files if file matching is too broad.\n\nMitigation: Limit cleanup to /tmp files with the asr_ prefix, preview matching files first, and delete only after explicit user confirmation.\n\n## Reference(s):\n\n- [Agent Social Reader homepage](https://github.com/hermiod99-vibe/Agent-Social-Reader)\n- [Platform Tool Details](references/tool-details.md)\n- [Save Scripts](references/save-scripts.md)\n- [AgentLens API](https://agentlensapi.io)\n- [AgentLens pricing](https://agentlensapi.io/pricing)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, code, shell commands, configuration, API calls, guidance]\n\n**Output Format:** [Markdown and plain text with inline code blocks, API request examples, and optional archive file content]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May produce temporary media files under /tmp with the asr_ prefix and user-requested Markdown archives for Notion, Obsidian, or ima.]\n\n## Skill Version(s):\n\n2.0.1 (source: SKILL.md frontmatter and release evidence)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v2.0.0: 6 files, 31457 bytes\n\nFiles: README.md (15912b), references/save-scripts.md (17776b), references/tool-details.md (23185b), skill-card.md (2959b), SKILL.md (20767b), _meta.json (138b)\n\nFile v2.0.0:SKILL.md\n\n---\nname: agent-social-reader\nversion: 1.1.0\ndescription: >\n Empower your AI agent to read content from: TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Accounts, WeChat Channels — plus general web pages, RSS feeds, and web search. Automatically summarize and archive to Notion, Obsidian, or ima upon user confirmation.\n Trigger when: (1) user shares any web link or social URL, (2) user asks to read, summarize, or save content from a link, (3) user asks to search the web, (4) user asks to subscribe to or check an RSS feed, or (5) user asks to save to Notion, Obsidian, or ima.\n Triggers: \"save to notion\", \"save to obsidian\", \"save to ima\", \"read this\", \"summarize\", \"what's on this\", \"search for\", \"archive this\", \"add to my reading list\", \"what does this video say\".\nmetadata:\n openclaw:\n homepage: \"https://github.com/hermiod99-vibe/Agent-Social-Reader\"\n requires:\n bins:\n - curl\n - python3\n optional_bins:\n - ffmpeg\n - yt-dlp\n envVars:\n - AGENT_LENS_API_KEY\n - OPENAI_API_KEY\n - NOTION_TOKEN\n - NOTION_DATABASE_ID\n - OBSIDIAN_VAULT_PATH\n - IMA_CLIENT_ID\n - IMA_API_KEY\n - IMA_KNOWLEDGE_BASE_ID\n---\n\n# Agent-Social-Reader — Skill Guide\n\n## ⚠️ Workspace Rules\n\n**CRITICAL: Never create or modify files directly inside the agent's default workspace unless the user explicitly asks for a workspace artifact.**\n- Use `/tmp/` for all temporary outputs and transient cache.\n- Use `~/.agent-social-reader/config.json` for long-term local configuration.\n- Prefer environment variables for secrets. Never print, log, summarize, or commit full API keys/tokens.\n- Only write to Notion, Obsidian, or ima after the user asks for that destination or confirms a save prompt.\n\n### Configuration Lookup Order\n\nWhen a credential or path is needed, check in this order:\n\n1. Current runtime connectors or MCP tools already authenticated by the user.\n2. Environment variables: `AGENT_LENS_API_KEY`, `OPENAI_API_KEY`, `NOTION_TOKEN`, `NOTION_DATABASE_ID`, `OBSIDIAN_VAULT_PATH`, `IMA_CLIENT_ID`, `IMA_API_KEY`, `IMA_KNOWLEDGE_BASE_ID`.\n3. Local config file: `~/.agent-social-reader/config.json`.\n4. Ask the user for the missing value, explain what it is used for, and ask whether it should be saved locally.\n\nUse this JSON shape for local config:\n\n```json\n{\n \"agentLensApiKey\": \"\",\n \"openAiApiKey\": \"\",\n \"notionToken\": \"\",\n \"notionDatabaseId\": \"\",\n \"obsidianVaultPath\": \"\",\n \"imaClientId\": \"\",\n \"imaApiKey\": \"\",\n \"imaKnowledgeBaseId\": \"\",\n \"defaultArchiveTarget\": \"\",\n \"imaScenario\": \"\"\n}\n```\n\n`imaScenario` records the current ima write path: `\"ima-native\"` / `\"workbuddy\"` / `\"external-api\"`.\n\n---\n\n## 🔧 Tool Routing Map\n\nEach platform has a **primary (free) tool** and an **optional fallback**. When the primary fails, try the next option in order. Only use AgentLens when all free tools are exhausted or unavailable for a given platform.\n\n| # | Platform | Primary (Free) | Fallback |\n|:--|:--|:--|:--|\n| 1 | General web pages | r.jina.ai | Runtime browser / Camoufox |\n| 2 | X / Twitter (public tweets) | FxTwitter API | AgentLens API |\n| 3 | YouTube (subtitles) | youtube-transcript-api | AgentLens API + Whisper |\n| 4 | WeChat Official Account | Camoufox | AgentLens API |\n| 5 | Weibo | r.jina.ai | AgentLens API |\n| 6 | 20+ Social platforms | AgentLens API | — |\n| 7 | Video summarization | Local Whisper | OpenAI Whisper API |\n| 8 | RSS feeds | feedparser | — |\n| 9 | Web search | Built-in runtime search | — |\n| 10 | Save to Notion | Native API write | — |\n| 11 | Save to Obsidian | File write (.md) | — |\n| 12 | Save to ima | Native ima skills if available | ima OpenAPI |\n\n**Routing principle: Exhaust free tools first. Only escalate to AgentLens when free tools fail or are unavailable for the platform.**\n\nFor detailed tool commands and code, see `references/tool-details.md`.\n\n---\n\n## ⚠️ Reading Rules\n\n**Trigger:** When user asks to \"read\" or \"summarize\" a link → AND the content is primarily visual (images or videos carry the main information, while text is secondary: hashtags, captions, short descriptions)\n\n**Rules:**\n\n1. When a tool returns media files (images or videos) alongside text → the media is the primary content\n2. **Images** → download CDN/media URLs as `/tmp/asr_{platform}_{timestamp}.jpg` → pass to Vision model → incorporate visual analysis into summary\n3. **Videos** → follow SOP C (Video Summarization)\n4. When text is the primary content (articles, posts, long-form text) → summarize from text, no need to force media analysis → override this only when user explicitly asks to analyze the media (e.g., \"analyze the images/videos\")\n5. Never ignore text entirely — media supplements text, text and media together form the complete picture\n\n**Note:** Different tools return media in different formats — check the actual response structure and extract media URLs accordingly.\n\n---\n\n## 🗂️ Managing Temp Media Files\n\n### Naming Convention\n\nAll media files created by this skill MUST use the `asr_` prefix. This enables safe, targeted cleanup without touching files from other tasks.\n\n| Type | Pattern | Example |\n|------|---------|---------|\n| Video | `/tmp/asr_` + `{platform}` + `_{timestamp}.mp4` | `/tmp/asr_douyin_20260703.mp4` |\n| Audio | `/tmp/asr_audio_` + `{timestamp}.wav` (or `.MP3`) | `/tmp/asr_audio_20260703.wav` |\n| Image | `/tmp/asr_` + `{platform}` + `_{timestamp}.jpg` (or `.png`, `.webp`) | `/tmp/asr_xhs_20260703.jpg` |\n\nThe timestamp is optional but recommended to avoid naming collisions.\n\n### Cleanup Trigger\n\nAfter any task that downloads media files, check both conditions:\n1. This is the **second or beyond** media download in this session, **OR**\n2. Total size of `/tmp/asr_*` files exceeds **~1GB**\n\nIf either is true → prompt user:\n> \"Temp media files are accumulating. Options: (A) Delete now / (B) Auto-delete after each task / (C) Keep for now\"\n\n- If user chose **(B)** auto-delete previously → silently clean up without asking\n- After any cleanup → always confirm what was deleted (file count + bytes freed)\n\n### Cleanup Rules\n\n```\nCRITICAL:\n1. ONLY delete files under /tmp/\n2. ONLY delete files with the `asr_` prefix — never touch files without this prefix\n3. Files without `asr_` may belong to other tasks or processes — do not delete them\n4. If you cannot reliably identify which files were created by this skill, do not delete — ask the user instead\n```\n\n### Cleanup Command\n\n```bash\n# Preview — only runs if matching files exist\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec ls -lh {} +\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec du -ch {} +\n\n# Delete — only runs if find returns non-empty; safe against accidental glob expansion\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -delete\n```\n\n---\n\n## 📋 Standard Operating Procedures\n\n### SOP A: URL Read → Deliver\n\n```\nUser shares link\n → Route to correct tool (see Routing Map above)\n → Read content\n → Execute user's intent (summarize / analyze / download / save / etc.)\n → Deliver result\n → IF FIRST successful execution:\n    Display Memory Persistence prompt (see § Persistence & Onboarding)\n    Branch on user response\n ELSE (subsequent executions):\n    Stop — do NOT prompt about Notion/Obsidian/ima\n\n --- AgentLens API Key Setup (inline, triggered when AgentLens is needed + no key) ---\n\n → IF the platform requires AgentLens AND no AGENT_LENS_API_KEY is found in any lookup:\n Tell user:\n \"This platform requires an AgentLens API key to read.\n It unlocks 20+ major social platforms.\n Get your free API key in 10 seconds at https://agentlensapi.io/pricing — 20 requests/month free; paid plans from $2.9/month, see pricing page.\n Once you paste it here, I'll handle the rest.\"\n\n User pastes key:\n → Use it for the current request only.\n → Ask: \"Would you like me to save this API key locally for future runs?\"\n → If user says yes → save to ~/.agent-social-reader/config.json\n → If user says no → do not persist it; use only for this session\n → Check: was there a pending URL from this conversation?\n    if YES:\n    → Execute the intended operation for that URL\n    → Deliver result\n    → Trigger Memory Persistence prompt\n    if NO:\n    → Stop\n```\n\n---\n\n### SOP B: Unsupported Platform / Self-Healing Fallback\n\n```\nUser shares link\n → Route to AgentLens API\n → API returns unsupported-platform status/code/message (for example Status 10032)\n → DO NOT stop — execute fallback:\n\n 1. Tell user:\n \"This platform isn't directly supported yet.\n I'll try a general web parser as fallback...\"\n\n 2. Fallback A — Jina Reader:\n    curl -L -s \"https://r.jina.ai/{url}\"\n    If returns valid content → summarize and deliver with note:\n    \"Recovered via general web parsing.\"\n\n 3. Fallback B — Runtime Web Search (if Jina also fails):\n    Use the current runtime's built-in web/search tool if available.\n    If no runtime search is available, tell the user search is not configured.\n\n 4. If all paths fail:\n    \"I've tried all available paths, but this link is blocked by anti-bot walls.\n    You may need to open it manually and share the content with me.\"\n```\n\n---\n\n### SOP C: Video Summarization\n\n**Trigger**: User's first instruction on a video link contains \"summarize\", \"what does this video say\", or similar intent + the media type is video.\n\n```\nUser shares video URL with summarization intent\n → Step 1: Attempt to get subtitle\n    if YouTube → try youtube-transcript-api\n    if subtitles found → summarize → done\n    if no subtitles → continue to Step 2\n    if non-YouTube → continue to Step 2\n\n → Step 2: Ask user for Whisper preference BEFORE downloading\n    Tell user: \"This video doesn't have subtitles, so I can't summarize the spoken content yet.\n    To do that, I need to transcribe the audio first. You have two options:\n\n    A) Local Whisper (free) — I'll walk you through installing faster-whisper.\n       You'll also need ffmpeg (already required).\n\n    B) OpenAI Whisper API (~$0.006/min) — you'll need an OpenAI API key.\n\n    Which would you prefer?\"\n    → IF user declines → deliver available metadata/text only; stop\n    → IF user chooses A or B → continue to Step 3\n\n → Step 3: Get download URL via AgentLens API\n    → Extract sourceUrl from downloadUrlList (prefer \"video\" type)\n    → If AgentLens is not configured, ask for AGENT_LENS_API_KEY or try subtitles/search-only fallback\n\n → Step 3b: Check for direct audio URL\n    → If downloadUrlList contains an \"audio\" type entry → prefer it over video\n    → Download the audio file directly as `/tmp/asr_audio_{timestamp}.MP3` → skip Step 4 and Step 5 → go straight to Step 6\n    → Audio is much smaller than video → faster download + no ffmpeg extraction needed\n\n → Step 4: Download video\n    curl -L --fail --max-time 120 -o /tmp/asr_{platform}_{timestamp}.mp4 \"{sourceUrl}\"\n    if curl fails + is YouTube → try yt-dlp\n    if curl fails + not YouTube → inform user download failed\n\n → Step 5: Extract audio\n    (Skip this step if audio was already obtained in Step 3b)\n    ffmpeg -y -i /tmp/asr_{platform}_{timestamp}.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/asr_audio_{timestamp}.wav\n\n → Step 6: Transcribe\n    Local Whisper: faster-whisper (CPU, free)\n    API Whisper: OpenAI Whisper API ($0.006/min)\n    Note: OpenAI Whisper API has a 25MB file size limit. For long videos,\n    pre-split into ~5-minute chunks, then check each chunk's file size:\n    any chunk ≥ 24MB → re-split at 3 minutes or lower the bitrate before uploading.\n\n → Step 7: Summarize transcript → deliver\n\n → After task complete: See § Managing Temp Media Files for cleanup\n```\n\nFor detailed download/transcribe commands, see `references/tool-details.md` §7.\n\n---\n\n## 💾 Save to Notion\n\n**Trigger**: User says \"save to Notion\" or confirms during onboarding.\n\n**Dependency Check first**: Check for existing Notion tools (notion-cli, mcporter-notion, custom Notion connector). If found, use that tool instead of the built-in script.\n\n**Built-in fallback**: See `references/save-scripts.md` §Notion.\n\n**Credentials**: If not yet stored, guide user to provide Notion Integration Token and either a Database ID or Page ID. Store locally only if the user approves. Tokens are never printed or committed.\n\n**Archive format** (markdown):\n```markdown\n# {title}\n\n**Source**: {source_url}\n**Platform**: {platform_name}\n\n## Summary\n{ai_summary}\n\n## Full Text\n{full_text}\n```\n\n**Limitation**: Notion API limits children to 100 blocks per request. If content exceeds 100 blocks, ask user whether to save summary only or use batched PATCH. Never silently truncate.\n\n**Success feedback**:\n> \"Successfully saved to your Notion reading list! Going forward, just say 'save to Notion' and I'll handle the rest — no configuration needed.\"\n\n---\n\n## 💾 Save to Obsidian\n\n**Trigger**: User says \"save to Obsidian\" or confirms during onboarding.\n\n**Dependency Check first**: Check for existing Obsidian integration tools. If found, use that tool instead.\n\n**Built-in fallback**: See `references/save-scripts.md` §Obsidian.\n\n**Credentials**: If vault path not yet stored, guide user to provide it. Store locally only if the user approves.\n\n**Archive format** (same markdown structure as Notion above).\n\n**Success feedback**:\n> \"Successfully saved to your Obsidian vault! Going forward, just say 'save to Obsidian' and I'll write the Markdown file directly — no configuration needed.\"\n\n---\n\n## 💾 Save to ima\n\n**Trigger**: User says \"save to ima\", \"存到 ima\", \"存到知识库\" or confirms during onboarding.\n\nima is Tencent's cloud knowledge base, accessed via OpenAPI (Token + target ID). It works across all channels — inside ima, via Feishu/Lark, Enterprise WeChat, or any third-party agent.\n\n### Step 1 — Detect Runtime Scene\n\nBefore saving, detect which environment you are running in:\n\n| Detection signal | Scene | Write path |\n|:----------------|:------|:-----------|\n| Runtime has `ima-knowledge` / `ima-note` native skills | **Scene 1: ima 内** | Call built-in ima skills directly |\n| Runtime has WorkBuddy-specific tools/connectors | **Scene 2: WorkBuddy** | ima OpenAPI |\n| Env vars `IMA_CLIENT_ID`/`IMA_API_KEY` exist but no WorkBuddy signal, or shell execution available | **Scene 3: Other Agent** | ima OpenAPI + manual config |\n\nIf no signal detected → ask user: \"你是在哪个平台使用本 skill？（ima / WorkBuddy / 其他 Agent）\"\n\nAfter detecting scene, save `imaScenario` to config and skip detection on subsequent runs.\n\n### Scene 1 — Running inside ima\n\nZero-config. Use built-in ima skills:\n\n| Intent | Action |\n|:-------|:-------|\n| Save to knowledge base | Call `ima-knowledge` skill → import content as Markdown |\n| Save as note | Call `ima-note` skill → create note with title + source + summary |\n| User wants download | `file_write` + `provide_file` → generate download link |\n\n**Steps**:\n1. Organize parsed content as Markdown (use standard archive format below)\n2. Call `ima-knowledge` or `ima-note` with the content\n3. Confirm success to user\n\n> ⚠️ ima's `import_urls` can also be used (ima crawls and snapshots the URL), but for social links (TikTok/Xiaohongshu/X) ima's crawler often fails due to anti-bot walls — resulting in a dead link with no content. **Prefer Markdown import** to ensure persistent content.\n\n### Scene 2 — Running in WorkBuddy\n\nOne-time credential setup required, then OpenAPI write. Full setup instructions → `references/tool-details.md` §10 (WorkBuddy setup).\n\nAfter setup, write via OpenAPI. Path selection:\n\n| Content type | Recommended way | Reason |\n|:-------------|:----------------|:-------|\n| Social links (TikTok/Xiaohongshu/X/etc.) | **Way B** (upload MD) ⭐ | ima crawler cannot bypass anti-bot walls on social platforms |\n| Regular web pages (blogs/news) | **Way A** (import_urls) | ima crawler can snapshot public webpages |\n| User wants AI summary saved | **Way B** (upload MD) ⭐ | Content persists in KB even if original link dies |\n\n### Scene 3 — Running in Other Agent (OpenClaw/Hermes/Claude Code/etc.)\n\nOne-time credential setup required, then OpenAPI write. Full setup instructions → `references/tool-details.md` §10 (External Agent setup).\n\n**Credentials** (three required):\n\n| Credential | Description | How to get |\n|:-----------|:-----------|:-----------|\n| `IMA_CLIENT_ID` | Client ID | ima.qq.com/agent-interface |\n| `IMA_API_KEY` | API Key | Same page (**shown only once — save immediately**) |\n| `IMA_KNOWLEDGE_BASE_ID` | Target KB ID | From `get_addable_knowledge_base_list` API |\n\n**Environment variable to HTTP mapping**:\n```\nIMA_CLIENT_ID        → HTTP header  ima-openapi-clientid\nIMA_API_KEY          → HTTP header  ima-openapi-apikey\nIMA_KNOWLEDGE_BASE_ID → request body field  knowledge_base_id / folder_id\n```\n\n**Credentials storage** (environment variables, recommended):\n```bash\nexport IMA_CLIENT_ID=\"your_client_id\"\nexport IMA_API_KEY=\"your_api_key\"\n```\nOr config files: `~/.config/ima/client_id` + `~/.config/ima/api_key`.\n\nWay selection: same as Scene 2 above. Full API details → `references/tool-details.md` §9.\n\n### Standard Archive Format\n\nAll three scenes use the same content format:\n\n```markdown\n# {title}\n\n**Source**: {source_url}\n**Platform**: {platform_name}\n\n## Summary\n{ai_summary}\n\n## Full Text\n{full_text}\n```\n\n### ima API Response\n\nima API responses follow the format described in `references/tool-details.md` §9.\n\n**Success feedback**:\n> \"Successfully saved to your ima knowledge base! Going forward, just say 'save to ima' and I'll handle the rest — no configuration needed.\"\n\n---\n\n## 🧠 Persistence & Onboarding\n\nAfter the **first successful read** (SOP A), display:\n\n> \"Got the content! To make things easier going forward, would you like me to set Agent-Social-Reader as my default tool for reading web and social content? Just confirm and I'll lock this in.\n>\n> Also — as a built-in superpower, I can automatically archive future reads to Notion, Obsidian, or ima. Whenever you want to save something, just say 'save to Notion', 'save to Obsidian', or 'save to ima' and I'll walk you through the one-time setup.\"\n\n**If user approves default tool**, persist this preference only through the current runtime's approved memory mechanism. If no approved memory mechanism is available, write to `~/.agent-social-reader/preferences.json`:\n\n```json\n{\n \"useAgentSocialReaderByDefault\": true,\n \"defaultArchiveTarget\": \"notion\"\n}\n```\n\nValid values for `defaultArchiveTarget`: `\"notion\"`, `\"obsidian\"`, or `\"ima\"`.\n\n**Important**: Never write Markdown into JSON configuration files.\n\n**If user asks to save right now**, trigger the respective Save workflow immediately.\n\n**On subsequent executions**: Stop after delivering the result. Do NOT prompt about archiving unless the user explicitly requests it.\n\n---\n\n## 🩺 Diagnostics\n\n| Tool | Symptom | Fix |\n|:--|:--|:--|\n| r.jina.ai | Returns empty or login wall | Try Camoufox for JS pages; try AgentLens for social |\n| FxTwitter | NOT_FOUND or rate-limit | Fall back to AgentLens API |\n| youtube-transcript-api | `AttributeError: get_transcript` | Use `YouTubeTranscriptApi().fetch(...)` — 1.x API, not 0.x |\n| youtube-transcript-api | Blocked (403/429) or no subtitles | Fall back to AgentLens + Whisper (SOP C) |\n| Camoufox | Import error or crash | Run `pip install camoufox` |\n| Camoufox | Browser launch failure / missing libgbm | Run `playwright install-deps` |\n| Camoufox | `Browser.setDefaultViewport` protocol error | Treat as unavailable; try Jina Reader, AgentLens, or ask user for content |\n| AgentLens | Status 10032 | Trigger SOP B (self-healing fallback chain) |\n| AgentLens | TLS failure on `api.agentlensapi.io` | Use `https://agentlensapi.io/api/v1/fetch` on the main host |\n| AgentLens | `AUTH_FAILED` or HTTP 401 | Ask for a valid `AGENT_LENS_API_KEY`; do not retry |\n| AgentLens | Quota exhausted | Inform user; suggest upgrading plan |\n| feedparser | SSL EOF / network TLS failure | Retry later, try `curl -L` first |\n| Notion write | 403 Forbidden | Verify Integration Token has write access |\n| Notion write | Property schema mismatch | Ask for database property names |\n| Obsidian write | File not found | Confirm vault path exists and is accessible |\n| ffmpeg | \"command not found\" | Install: `apt install ffmpeg` (Linux), `brew install ffmpeg` (macOS) |\n| Whisper | API error or quota | Fall back to local Whisper if available |\n\n---\n\n## 📦 Installation\n\n```bash\n# One-line install — paste this to your AI agent:\n帮我安装 Agent-Social-Reader 技能包：https://github.com/hermiod99-vibe/Agent-Social-Reader\n```\n\nFile v2.0.0:README.md\n\n# 👁️ Agent-Social-Reader\n\n<p align=\"center\">\n <strong>Drop a social link → AI summarizes → Save it to Notion / Obsidian / ima<br/> No login. No cookies. No account risk.<br/><br/>\nReads content from X, Instagram, YouTube, Xiaohongshu, and more — archives to your personal knowledge base</strong>\n</p>\n<p align=\"center\">\n <a href=\"#-quick-overview\">Quick Overview</a> ·\n <a href=\"#-what-it-does\">What It Does</a> ·\n <a href=\"#-supported-platforms\">Supported Platforms</a> ·\n <a href=\"#-quick-start\">Quick Start</a> ·\n <a href=\"docs/README_cn.md\">中文</a>\n</p>\n\n\n---\n\n## 🎯 Quick Overview\n\nAs a knowledge worker, product manager, or heavy AI agent user, the moment you drop an X (Twitter), Reddit, WeChat article, or Xiaohongshu link into your AI, you hit walls: login barriers, anti-scraping blocks, IP bans, or HTML garbage. On top of that, you're stuck maintaining a separate tool for every single platform.\n\n**The problem this Skill solves:**\n\n❌ Before:\n\n```\nYou: drop a Twitter/Reddit/Xiaohongshu link into your agent\nAgent: \"I can't access that\" / returns garbled HTML\nYou: resort to manual copy-paste, screenshots, or give up\n```\n\n✅ Now:\n\n```\nYou: drop any social media link into your agent\nAgent: reads automatically → summarizes automatically\nYou: save it to Notion / Obsidian / ima\n→ content neatly filed in your knowledge base\n```\n\n**Core value**:\n- ✅ **Complete loop**: Read → Summarize → Archive — end-to-end in one workflow\n- ✅ **Broad coverage**: 20+ global social platforms (TikTok, Instagram, X, YouTube, Reddit, Douyin, Xiaohongshu, Bilibili...)\n- ✅ **Lower account risk**: No need to log in to personal social accounts, no cookie required — reduces risk of suspension from cookie-based access\n- ✅ **Flexible cost**: Free tools first; optional paid enhancements from $2.90/month\n\n> 💡 **Design philosophy**: Use free tools wherever possible; reach for the most cost-effective paid option only when free tools won't do. You decide which tier to use.\n\n> ❤️ **Special thanks to** [Agent-Reach](https://github.com/Panniantong/Agent-Reach) by [@Panniantong](https://github.com/Panniantong). Agent-Reach proved that getting an AI agent to read web content was achievable — this project is a personal iteration on the same idea, with a tighter focus on social content reading and knowledge base archiving.\n\n---\n\n## ✨ What It Does\n\nAfter installing this Skill, your agent gains the following capabilities (no code required):\n\n| Content Source | Tool | Cost |\n| :--- | :--- | :--- |\n| General web pages | Jina Reader (r.jina.ai) | ✅ Free, no API key required |\n| Public X/Twitter posts | FxTwitter API | ✅ Free, no API key or cookie required |\n| YouTube (with subtitles) | youtube-transcript-api | ✅ Free, no API key required |\n| Weibo | Jina Reader (r.jina.ai) | ✅ Free, bypasses login wall |\n| WeChat public accounts | Camoufox | ✅ Free, headless browser handles JS rendering<br/>(falls back to AgentLens or prompts you to paste text if it fails) |\n| **20+ global social platforms** | AgentLens API | 🎁 20 free requests per month<br>💵 Then $2.90/month (200 requests) |\n| Video summarization (TikTok, YouTube and other videos without subtitles) | Whisper transcription + LLM | ✅ Free locally (requires setup)<br>💵 OpenAI API $0.006/minute |\n| RSS feeds | Python feedparser | ✅ Free |\n| Full-web semantic search | Agent runtime built-in search | ✅ Free (provided your current runtime supports it) |\n| Save to Notion upon request | Agent writes after confirmation | ✅ Free (your Notion Token) |\n| Save to Obsidian upon request | Agent writes after confirmation | ✅ Free (local path) |\n| Save to ima upon request | Agent writes after confirmation | ✅ Free (ima OpenAPI, accessible from WeChat/Feishu/WeCom) |\n\n---\n\n## 🌐 Supported Platforms\n\nVia **AgentLens API**, the following social media platforms are accessible and verified (if you spot errors or discover additional platforms, please let us know):\n\n| Global Platforms | Chinese Platforms |\n| :--------------- | :---------------- |\n| TikTok | Douyin |\n| Instagram | Xiaohongshu |\n| YouTube | Bilibili |\n| X (Twitter) | Weibo |\n| Facebook | Kuaishou |\n| Threads | Xigua Video |\n| Reddit | Zhihu (columns) |\n| LinkedIn | WeChat Official Account (articles) |\n| Twitch (clips) | WeChat Channels |\n| Pinterest | |\n| Bluesky | |\n| Snapchat | |\n| Kick (clips) | |\n| Lemon8 | |\n\n* **AgentLens reads**: main text content + images/video files (original media links)\n* **AgentLens does not read**: comments, timelines, X Lists, private account content, or reply/conversation threads on X and Reddit\n\n---\n\n## 🚀 Quick Start\n\n### Step 1: Install the Skill (10 seconds)\n\nCopy and paste this into whichever AI agent you're using (Claude Code, Cursor, Codex, OpenClaw, Hermes Agent, WorkBuddy, etc.):\n\n```\nInstall the Agent-Social-Reader Skill: https://github.com/hermiod99-vibe/Agent-Social-Reader\n```\n\nThat's it. Your agent handles everything else.\n\n---\n\n### Step 2: Start using it immediately (free right away)\n\nAfter installation, these features work at no cost:\n\n- \"Show me what's on this tweet\"\n- \"Summarize this WeChat article\"\n- \"What does this webpage say?\"\n- \"Subscribe to this RSS feed\"\n\n---\n\n### Step 3: Optional enhancements (as needed)\n\n#### 🔓 Unlock 20+ social platform reading (TikTok, Reddit, Instagram, Douyin, Xiaohongshu, Bilibili, etc.; X/Twitter single public posts, Weibo posts, WeChat Official Account articles use the free tool path first)\n\nThe first time you ask your agent to read from these platforms, it will prompt:\n\n> \"This platform requires an AgentLens API key to read. It unlocks 20+ major social platforms. Get your free API key in 10 seconds at https://agentlensapi.io/pricing — 20 requests/month free; paid plans from $2.90/month (200 requests), see pricing page. Once you paste it here, I'll handle the rest.\"\n\nFull pricing: [agentlensapi.io/pricing](https://agentlensapi.io/pricing)\n\n---\n\n#### 🎥 Unlock video summarization (TikTok, YouTube, and other videos without subtitles)\n\nTwo options:\n\n**Option A: Completely free (requires setup)**\n\nInstall Whisper locally:\n\n```bash\npip install faster-whisper\n# macOS: brew install ffmpeg | Ubuntu: sudo apt install ffmpeg\n```\n\nOnce installed, tell your agent:\n\n> \"I've installed local Whisper, running in CPU mode.\"\n\nIf your current agent supports preference memory and you confirm, your agent can remember your preference. Going forward, when it encounters a video without subtitles, it automatically follows: download → extract audio → transcribe locally → summarize.\n\n> ⚠️ **Hardware notes**: Small models (tiny/base) run on any regular computer; large models (large-v3) are recommended with a GPU. On first use, the model downloads automatically (75MB–3GB).\n\n**Option B: Paid & convenient (recommended for most users)**\n\nGet a Whisper API Key from the [OpenAI platform](https://platform.openai.com/api-keys) and tell your agent:\n\n> \"Here's my OpenAI API Key: [YOUR_KEY]\"\n\nIf your current agent supports preference memory and you confirm, your agent can remember your preference. Going forward, it automatically calls the API for any subtitle-free video.\n\n> 💡 **Low cost**: ~$0.006/minute. A 1-minute TikTok costs ~$0.006; a 10-minute YouTube video costs ~$0.06.\n\n---\n\n#### 📁 Connect your knowledge base (Notion / Obsidian / ima)\n\nThe first time you say \"save to Notion\", \"save to Obsidian\", or \"save to ima\", your agent walks you through a one-time setup:\n\n- **Notion**: requires an Integration Token and a Database ID or Page ID (credentials stored locally; direct end-to-end connection)\n  💡 The default save script assumes your Notion database has a Title property named **Name** and a URL property named **Source**. If your database uses different property names or you want to save to a single page instead of a database, let your agent know during the first save.\n- **Obsidian**: requires your local vault path\n- **ima**: requires Client ID + API Key (scan QR code at ima.qq.com/agent-interface); ima is Tencent's knowledge base product, accessible from WeChat, Feishu, and WeCom\n\n**ima — three access scenarios:**\n\n| Scenario | Description |\n|:---------|:-----------|\n| Running inside ima directly | Zero config; use built-in ima skills |\n| Running in WorkBuddy | One-time setup: Client ID + API Key |\n| Running in another Agent (OpenClaw / Claude Code etc.) | One-time setup: Client ID + API Key + Knowledge Base ID |\n\nFor social links (Douyin/Xiaohongshu/X etc.), Markdown upload is recommended — content persists even if the original link goes dead. For regular web pages, URL import works fine.\n\nOnce configured in the same runtime environment, setup is typically one-time only.\n\n---\n\n### Step 4: One-time setup, automated going forward\n\nOnce you have configured everything and your agent successfully reads, summarizes, or saves its first piece of content, it will proactively ask:\n\n> \"Got everything sorted! To make things easier going forward, shall I set Agent-Social-Reader as my default tool for reading web and social content? If you confirm, I'll write this to long-term memory or your local preferences file. Then you can just drop me a link anytime and I'll handle the rest — no need to mention the Skill name each time.\"\n\nOnce you confirm, just speak naturally:\n\n- \"Show me what this tweet says\"\n- \"Save this to Notion\"\n- \"Save this to ima\"\n\nYour agent calls the right tool automatically. No need to specify the Skill name each time. Just say \"save it to Notion\" whenever you want.\n\n## 👍🏻 My Daily Workflow\n\n```\nI share a link → Agent reads → Agent summarizes → I say \"save to Notion\" → it's in Notion\n```\n\nContent saved to Notion/Obsidian/ima includes:\n\n- Original link\n- Full text\n- AI summary\n\nEasy to search and review later.\n\n---\n\n## 💰 Pricing (Fully Transparent)\n\nPer Skill marketplace guidelines, paid services require transparent pricing disclosure:\n\n### Completely free features\n\n- ✅ General web pages (r.jina.ai)\n- ✅ Single public X/Twitter posts (FxTwitter API)\n- ✅ YouTube videos with subtitles (youtube-transcript-api)\n- ✅ Weibo (r.jina.ai)\n- ✅ WeChat public accounts (Camoufox)\n- ✅ RSS feeds (feedparser)\n- ✅ Full-web search (Agent runtime built-in search)\n- ✅ Notion/Obsidian/ima archiving\n- ✅ Local Whisper video transcription (requires your own installation)\n\n### Optional paid features\n\n#### 📱 AgentLens API (20+ social platform reading)\n\n| Plan | Requests/month | Price |\n| :--- | :--- | :--- |\n| Free | 20 | $0 |\n| Basic | 200 | $2.90/month |\n| Ultra | 500 | $5.90/month |\n| Mega | 1,000 | $9.90/month |\n\nFull pricing: [agentlensapi.io/pricing](https://agentlensapi.io/pricing)\n\nWhy AgentLens (from my own experience):\n\n- One API key for 20+ platforms (vs one tool per platform)\n- No cookie required (vs risking account suspension)\n- No proxy required (vs maintaining your own IP pool)\n- Stable maintenance (vs tracking platform rule changes yourself)\n\nFor me, $2.90/month (200 requests, enough for a month) is excellent value for the peace of mind it brings.\n\n---\n\n#### 🎥 OpenAI Whisper API (video transcription)\n\n- **Price**: ~$0.006/minute\n- **Examples**:\n  - 1-minute TikTok: $0.006\n  - 10-minute YouTube: $0.06\n  - 30-minute podcast: $0.18\n\nFull pricing: [OpenAI Pricing — Whisper](https://platform.openai.com/docs/pricing)\n\n> 💡 If your machine has sufficient resources and you don't mind the setup, you can install Whisper locally (`pip install faster-whisper`) for zero cost.\n\n---\n\n## 💡 Design Philosophy\n\n**Focus on completing the \"read → summarize → archive\" loop.**\n\nWithin this loop:\n\n### Read layer (free-first)\n\n| Content source | Tool | Cost |\n| :--- | :--- | :--- |\n| General web pages | Jina Reader | ✅ Free |\n| Public X/Twitter posts | FxTwitter API | ✅ Free |\n| YouTube subtitles | youtube-transcript-api | ✅ Free |\n| Weibo | r.jina.ai | ✅ Free |\n| WeChat public accounts | Camoufox | ✅ Free |\n| RSS feeds | feedparser | ✅ Free |\n| 20+ social platforms | AgentLens API | 💵 Optional |\n\n🔐 **Core advantage**: No social account login / no cookie used.\nThe underlying layer never requires you to log into any personal social account, enter a password, or provide a cookie — so platforms cannot trace activity back to your personal account, reducing the risk of suspension from using personal account cookies.\n\n---\n\n### Summarize layer (LLM calls)\n\n| Content type | Method |\n| :--- | :--- |\n| Text, images | LLM summarizes directly |\n| YouTube with subtitles | Subtitle text → LLM summarizes |\n| Videos without subtitles | Audio transcription (✅ free locally, or 💵 optional paid API) → LLM summarizes |\n\n---\n\n### Archive layer (completely free)\n\nNotion, Obsidian, and ima are all free. Just tell your agent \"save to Notion\", \"save to Obsidian\", or \"save to ima\" — it walks you through one-time setup. After that, just ask again whenever you want to save, and it writes automatically with no need to reconfigure.\n\nArchived content includes:\n\n- Original link\n- Full text\n- AI summary\n\nima is Tencent's knowledge base product: once configured, your archive is accessible across WeChat, Feishu, and WeCom. For social links, Markdown upload is recommended — content is saved directly and doesn't depend on the original link remaining accessible.\n\n---\n\n## 🔒 Security & Privacy\n\n- **Account safety (no login required)**: Many solutions require browser extensions or cookie exports to fetch content. This Skill never asks you to log into any personal social account or provide a cookie, which reduces the risk of suspension from cookie-based access.\n- **End-to-end direct connection**: Whether it's your Notion Token, Obsidian local path, or ima Client ID + API Key — all credentials are strictly stored by your agent in a local file on your own machine (`~/.agent-social-reader/config.json` or environment variables). When making requests, your agent calls each service's API (Notion, OpenAI, AgentLens, ima, and any other configured service) directly from the current runtime environment — credentials are sent only to the corresponding service. Third-party services process data according to their own privacy policies.\n\n---\n\n## 🛠️ Tech Stack\n\n<details>\n<summary>👀 Click to expand tech stack</summary>\n\n**Free open-source tools and parsers:**\n- [Jina Reader](https://jina.ai/reader) — general web page content extraction\n- [FxTwitter API](https://github.com/FixTweet/FxTwitter) — public X/Twitter post parsing\n- [youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api) — YouTube subtitle extraction\n- [yt-dlp](https://github.com/yt-dlp/yt-dlp) — video download fallback (used when curl fails to extract video for transcription)\n- [Jina Reader](https://r.jina.ai) — Weibo content extraction\n- [Camoufox](https://github.com/daijro/camoufox) — WeChat public account dynamic rendering and reading\n- Agent runtime built-in search — full-web semantic search (no extra dependencies)\n- Python `feedparser` — RSS feed parsing\n\n**Third-party optional APIs (user chooses as needed):**\n- [AgentLens API](https://agentlensapi.io) — unified entry for 20+ social media platform public content\n- [OpenAI Whisper API](https://platform.openai.com/docs/guides/speech-to-text) — cloud audio/video high-precision transcription\n\n**Local processing (completely free):**\n- [faster-whisper](https://github.com/SYSTRAN/faster-whisper) — locally running Whisper speech-to-text engine\n\n</details>\n\n---\n\n## 🙏 Final Words\n\nThis is the first time I've published a Skill publicly.\n\nIf something doesn't work, open an issue and I'll fix it right away.\n\nIf I find a new tool or solution, I'll add it right away.\n\nIf you want a platform added, submit a PR.\n\nIf this Skill helps streamline your agent's social media reading and archiving workflow, please give it a ⭐ **Star**!\n\n---\n\n**MIT License**\n\nFile v2.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn7dct92yfrktj1r1p84kvtdpn89xcqf\",\n  \"slug\": \"agent-social-reader\",\n  \"version\": \"2.0.0\",\n  \"publishedAt\": 1784017873888\n}\n\nFile v2.0.0:references/save-scripts.md\n\n# Save Scripts\n\nBuilt-in fallback scripts for archiving content to Notion and Obsidian. These are used when no native Notion/Obsidian tool is available in the current runtime.\n\nFor the ima OpenAPI (Scene 2 & 3), see `references/tool-details.md` §9.\n\n---\n\n## Save to ima (Scene 2 & 3 — OpenAPI)\n\n**When to use**: User says \"save to ima\" and no native ima skill is available (i.e., not running inside ima). Use this after completing the scene detection in SKILL.md.\n\n### Scene Detection Function\n\n```python\nimport os\nfrom pathlib import Path\n\ndef detect_ima_scene():\n    \"\"\"\n    Detect which ima write path to use.\n    Returns: \"ima-native\" | \"workbuddy\" | \"external-api\" | None\n    \"\"\"\n    # Scene 1: ima native skills — check via runtime skill discovery\n    # (ima-knowledge and ima-note are skills, not shell commands;\n    # implementation varies by runtime: check <available_skills>, skill registry,\n    # or platform-specific tool list. If you can call use_skill(\"ima-knowledge\")\n    # and it succeeds → return \"ima-native\".)\n    # TODO: replace with actual runtime skill discovery when running inside ima.\n    # For now, fall through to check env vars / config files below.\n\n    # Scene 2: WorkBuddy — requires platform-specific signal, not just env vars.\n    # IMA_CLIENT_ID + IMA_API_KEY only mean OpenAPI is configured; they are present\n    # in ALL OpenAPI environments (WorkBuddy, OpenClaw, Claude Code, etc.).\n    # Use WorkBuddy-specific indicators: runtime tool/connector presence, or ask user.\n    # If you detect WorkBuddy-specific runtime tools or connectors → return \"workbuddy\"\n    # If only env vars are present but no WorkBuddy signal → return \"external-api\"\n    if os.environ.get(\"IMA_CLIENT_ID\") and os.environ.get(\"IMA_API_KEY\"):\n        # These env vars alone only prove OpenAPI is configured.\n        # Do NOT assume WorkBuddy without a platform-specific signal.\n        return \"external-api\"\n\n    # Scene 3: External agent — try to read config files\n    config_dir = Path.home() / \".config\" / \"ima\"\n    if config_dir.exists():\n        client_id_file = config_dir / \"client_id\"\n        api_key_file = config_dir / \"api_key\"\n        if client_id_file.exists() and api_key_file.exists():\n            return \"external-api\"\n\n    # Also check OpenClaw skill config\n    skill_config = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_config.exists():\n        try:\n            import json\n            with open(skill_config) as f:\n                cfg = json.load(f)\n                if cfg.get(\"imaClientId\") and cfg.get(\"imaApiKey\"):\n                    return cfg.get(\"imaScenario\", \"external-api\")\n        except Exception:\n            pass\n\n    return None  # Cannot detect — ask user\n```\n\n### Save to ima via OpenAPI (Way B — Upload Markdown)\n\nThree-step: `create_media` → COS upload → `add_knowledge`. Full API details in `references/tool-details.md` §9.\n\n```python\nimport os\nimport urllib.request\nimport urllib.error\nimport json\nimport time\nimport subprocess\nimport shutil\nimport re\nfrom pathlib import Path\n\n# ── helpers ────────────────────────────────────────────────────────────────────\n\ndef _slugify(title):\n    slug = re.sub(r\"[^\\w\\u4e00-\\u9fff.-]+\", \"-\", title.strip(), flags=re.UNICODE)\n    slug = slug.strip(\".-\")[:80]\n    return slug or \"untitled\"\n\ndef _get_ima_credentials():\n    \"\"\"Load ima credentials from env vars or config files.\"\"\"\n    client_id = os.environ.get(\"IMA_CLIENT_ID\")\n    api_key = os.environ.get(\"IMA_API_KEY\")\n    if client_id and api_key:\n        return client_id, api_key\n\n    # Fallback: read from config files\n    cfg_dir = Path.home() / \".config\" / \"ima\"\n    client_id_file = cfg_dir / \"client_id\"\n    api_key_file = cfg_dir / \"api_key\"\n    if client_id_file.exists() and api_key_file.exists():\n        return (\n            client_id_file.read_text().strip(),\n            api_key_file.read_text().strip()\n        )\n\n    # Fallback: skill config\n    skill_cfg = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_cfg.exists():\n        with open(skill_cfg) as f:\n            cfg = json.load(f)\n            return cfg.get(\"imaClientId\", \"\"), cfg.get(\"imaApiKey\", \"\")\n\n    raise ValueError(\"ima credentials not found. Set IMA_CLIENT_ID / IMA_API_KEY or configure ~/.config/ima/\")\n\ndef _get_ima_kb_id(client_id=None, api_key=None):\n    \"\"\"\n    Get the ima Knowledge Base ID to save content into.\n    Checks env var IMA_KNOWLEDGE_BASE_ID, then skill config, then queries the API.\n\n    Returns:\n        kb_id (str): The knowledge base ID\n\n    Raises:\n        ValueError: If no KB ID can be determined\n    \"\"\"\n    # 1. env var\n    kb_id = os.environ.get(\"IMA_KNOWLEDGE_BASE_ID\")\n    if kb_id:\n        return kb_id\n\n    # 2. skill config\n    skill_cfg = Path.home() / \".agent-social-reader\" / \"config.json\"\n    if skill_cfg.exists():\n        with open(skill_cfg) as f:\n            cfg = json.load(f)\n            kb_id = cfg.get(\"imaKnowledgeBaseId\", \"\")\n            if kb_id:\n                return kb_id\n\n    # 3. query API — return options so the agent can ask the user to choose\n    if client_id and api_key:\n        url = \"https://ima.qq.com/openapi/wiki/v1/get_addable_knowledge_base_list\"\n        body = json.dumps({}).encode(\"utf-8\")\n        try:\n            req = urllib.request.Request(\n                url,\n                data=body,\n                headers={\n                    \"ima-openapi-clientid\": client_id,\n                    \"ima-openapi-apikey\": api_key,\n                    \"Content-Type\": \"application/json\"\n                },\n                method=\"POST\"\n            )\n            with urllib.request.urlopen(req, timeout=30) as resp:\n                result = json.load(resp)\n        except Exception:\n            result = {}\n\n        if result.get(\"retcode\") == 0:\n            kb_list = result.get(\"data\", {}).get(\"addable_knowledge_base_list\", [])\n            if len(kb_list) == 1:\n                return kb_list[0].get(\"id\", \"\")\n            if len(kb_list) > 1:\n                options = [\n                    f\"{item.get('name', '(unnamed)')} ({item.get('id', '')})\"\n                    for item in kb_list\n                ]\n                raise ValueError(\n                    \"Multiple ima knowledge bases are available. Ask the user to choose one, \"\n                    \"then save its id as IMA_KNOWLEDGE_BASE_ID or imaKnowledgeBaseId. Options: \"\n                    + \"; \".join(options)\n                )\n\n    raise ValueError(\n        \"ima Knowledge Base ID not found. Set IMA_KNOWLEDGE_BASE_ID, \"\n        \"configure imaKnowledgeBaseId in ~/.agent-social-reader/config.json, \"\n        \"or ensure client_id + api_key can query get_addable_knowledge_base_list.\"\n    )\n\ndef _ima_api_request(endpoint, payload, client_id, api_key):\n    \"\"\"Make an ima OpenAPI request. Returns parsed JSON.\"\"\"\n    url = f\"https://ima.qq.com/openapi/wiki/v1/{endpoint}\"\n    body = json.dumps(payload).encode(\"utf-8\")\n    req = urllib.request.Request(\n        url,\n        data=body,\n        headers={\n            \"ima-openapi-clientid\": client_id,\n            \"ima-openapi-apikey\": api_key,\n            \"Content-Type\": \"application/json\"\n        },\n        method=\"POST\"\n    )\n    with urllib.request.urlopen(req, timeout=30) as resp:\n        result = json.load(resp)\n    if result.get(\"retcode\") != 0:\n        raise RuntimeError(f\"ima API error: {result.get('errmsg')} (code {result.get('retcode')})\")\n    return result\n\ndef _upload_to_ima_cos(tmp_path):\n    \"\"\"\n    Upload the markdown file to COS using ima-provided tooling.\n\n    Do not hand-roll the COS Authorization header here: Tencent COS REST signing\n    requires a full signature or a pre-signed URL, and ima's create_media response\n    should be uploaded with ima_cos_util / cos-upload.cjs or an official COS SDK.\n    A COS SDK implementation would need create_media's cos_credential fields\n    (bucket_name, region, cos_key, secret_id, secret_key, token).\n    \"\"\"\n    if shutil.which(\"ima_cos_util\"):\n        subprocess.run([\"ima_cos_util\", \"-f\", tmp_path], check=True, timeout=120)\n        return\n\n    cos_upload = shutil.which(\"cos-upload.cjs\")\n    if cos_upload:\n        subprocess.run([\"node\", cos_upload, \"-f\", tmp_path], check=True, timeout=120)\n        return\n\n    raise RuntimeError(\n        \"COS upload helper not found. Install/use ima_cos_util or the ima skills package \"\n        \"(cos-upload.cjs), or replace _upload_to_ima_cos with an official COS SDK upload \"\n        \"after verifying create_media's cos_credential format.\"\n    )\n\n# ── main save function ────────────────────────────────────────────────────────\n\ndef save_to_ima(title, source_url, platform, summary, full_text, kb_id, client_id, api_key):\n    \"\"\"\n    Save content to ima knowledge base via OpenAPI (Way B — upload Markdown).\n\n    Args:\n        title: Content title\n        source_url: Original URL\n        platform: Platform name\n        summary: AI-generated summary\n        full_text: Full article/post text\n        kb_id: Knowledge base ID\n        client_id: ima Client ID\n        api_key: ima API Key\n    \"\"\"\n    slug = _slugify(title)\n    markdown_body = (\n        f\"# {title}\\n\\n\"\n        f\"**Source**: {source_url}\\n\"\n        f\"**Platform**: {platform}\\n\\n\"\n        f\"## Summary\\n\\n{summary}\\n\\n\"\n        f\"## Full Text\\n\\n{full_text}\\n\"\n    )\n    byte_size = len(markdown_body.encode(\"utf-8\"))\n    timestamp = int(time.time())\n\n    # Write temp file for COS upload\n    tmp_path = f\"/tmp/asr_ima_{slug}.md\"\n    Path(tmp_path).write_text(markdown_body, encoding=\"utf-8\")\n\n    # Step 1: create_media\n    media_result = _ima_api_request(\n        \"create_media\",\n        {\n            \"file_name\": f\"{slug}.md\",\n            \"file_size\": byte_size,\n            \"content_type\": \"text/markdown\",\n            \"knowledge_base_id\": kb_id,\n            \"file_ext\": \"md\"\n        },\n        client_id,\n        api_key\n    )\n    media_id = media_result[\"data\"][\"media_id\"]\n    cos_cred = media_result[\"data\"][\"cos_credential\"]\n\n    # Step 2: COS upload\n    cos_key = cos_cred[\"cos_key\"]\n    _upload_to_ima_cos(tmp_path)\n\n    # Step 3: add_knowledge\n    _ima_api_request(\n        \"add_knowledge\",\n        {\n            \"media_type\": 7,\n            \"media_id\": media_id,\n            \"title\": title,\n            \"knowledge_base_id\": kb_id,\n            \"folder_id\": kb_id,\n            \"file_info\": {\n                \"cos_key\": cos_key,\n                \"file_size\": byte_size,\n                \"file_name\": f\"{slug}.md\",\n                \"last_modify_time\": timestamp\n            }\n        },\n        client_id,\n        api_key\n    )\n\n    # Cleanup temp file\n    Path(tmp_path).unlink(missing_ok=True)\n    return True\n```\n\n**Usage**:\n```python\nclient_id, api_key = _get_ima_credentials()\nkb_id = _get_ima_kb_id(client_id, api_key)\nsave_to_ima(\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    platform=\"Web\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text\",\n    kb_id=kb_id,\n    client_id=client_id,\n    api_key=api_key\n)\n```\n\n**Note**: For Windows PowerShell 5.1 environments, the request body must be explicitly encoded as UTF-8 bytes. See `references/tool-details.md` §10 for the PowerShell workaround.\n\n---\n\n## Save to Notion\n\n**When to use**: User says \"save to Notion\" and no native Notion tool (notion-cli, mcporter-notion, custom Notion connector) is available.\n\n**Dependencies**: urllib standard library only — no external packages required.\n\n```python\nimport urllib.request, urllib.error, json\n\ndef _paragraph_blocks(text, chunk_size=1900):\n    \"\"\"Split text into Notion paragraph blocks, respecting the 1900-char limit per block.\"\"\"\n    chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)] or [\"\"]\n    return [\n        {\n            \"object\": \"block\",\n            \"type\": \"paragraph\",\n            \"paragraph\": {\"rich_text\": [{\"type\": \"text\", \"text\": {\"content\": chunk}}]}\n        }\n        for chunk in chunks\n    ]\n\ndef save_to_notion(token, target_id, title, source_url, platform, summary, full_text=\"\", target_type=\"database\"):\n    \"\"\"\n    Archive content to Notion.\n\n    Args:\n        token: Notion Integration Token\n        target_id: Database ID (target_type=\"database\") or Page ID (target_type=\"page\")\n        title: Page title\n        source_url: Original URL\n        platform: Platform name (e.g. \"Twitter\", \"Instagram\", \"Web\")\n        summary: AI-generated summary\n        full_text: Full article/post text (optional)\n        target_type: \"database\" (default) or \"page\"\n    \"\"\"\n    body_text = f\"Source: {source_url}\\nPlatform: {platform}\\n\\nSummary:\\n{summary}\\n\\nFull text:\\n{full_text}\"\n    children = _paragraph_blocks(body_text)\n\n    if len(children) > 100:\n        raise ValueError(\n            f\"Notion content too long: {len(children)} blocks exceeds 100-block single-request limit. \"\n            \"Ask the user whether to save the summary only, or use batched PATCH append requests.\"\n        )\n\n    if target_type == \"database\":\n        url = \"https://api.notion.com/v1/pages\"\n        payload = {\n            \"parent\": {\"database_id\": target_id},\n            \"properties\": {\n                \"Name\": {\"title\": [{\"type\": \"text\", \"text\": {\"content\": title[:2000]}}]},\n                \"Source\": {\"url\": source_url}\n            },\n            \"children\": children\n        }\n        method = \"POST\"\n    else:\n        url = f\"https://api.notion.com/v1/blocks/{target_id}/children\"\n        payload = {\"children\": children}\n        method = \"PATCH\"\n\n    req = urllib.request.Request(\n        url,\n        data=json.dumps(payload).encode(),\n        headers={\n            \"Authorization\": f\"Bearer {token}\",\n            \"Notion-Version\": \"2022-06-28\",\n            \"Content-Type\": \"application/json\"\n        },\n        method=method\n    )\n    with urllib.request.urlopen(req) as resp:\n        return json.load(resp)\n```\n\n**Usage**:\n```python\nresult = save_to_notion(\n    token=\"secret_xxx\",\n    target_id=\"database_or_page_id\",\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    platform=\"Twitter\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text here\",\n    target_type=\"database\"  # or \"page\"\n)\n```\n\n**Notes**:\n- Default database schema assumed: title property `Name`, URL property `Source`. If different, ask user for the actual property names.\n- Notion API limits children to 100 blocks per single request. If content exceeds 100 blocks, raise ValueError and ask user.\n- Never silently truncate content.\n\n---\n\n## Save to Obsidian\n\n**When to use**: User says \"save to Obsidian\" and no native Obsidian integration tool is available.\n\n**Dependencies**: pathlib + re standard library only — no external packages required.\n\n```python\nfrom pathlib import Path\nimport re\n\ndef slugify(title):\n    \"\"\"\n    Convert title to a safe filename slug.\n    - Replaces non-alphanumeric characters (except Chinese chars, dots, hyphens) with hyphens\n    - Strips leading/trailing hyphens and dots\n    - Truncates to 80 characters\n    - Returns 'untitled' if result is empty\n    \"\"\"\n    slug = re.sub(r\"[^\\w\\u4e00-\\u9fff.-]+\", \"-\", title.strip(), flags=re.UNICODE)\n    slug = slug.strip(\".-\")[:80]\n    return slug or \"untitled\"\n\ndef unique_note_path(vault, slug):\n    note_path = vault / f\"{slug}.md\"\n    if not note_path.exists():\n        return note_path\n    counter = 2\n    while True:\n        candidate = vault / f\"{slug}-{counter}.md\"\n        if not candidate.exists():\n            return candidate\n        counter += 1\n\ndef save_to_obsidian(vault_path, title, source_url, summary, full_text=\"\"):\n    \"\"\"\n    Write content as a Markdown note in an Obsidian vault.\n\n    Args:\n        vault_path: Absolute path to the Obsidian vault root\n        title: Note title (used for filename and H1)\n        source_url: Original URL\n        summary: AI-generated summary\n        full_text: Full article/post text (optional)\n\n    Returns:\n        Absolute path to the created note\n\n    Raises:\n        FileNotFoundError: vault_path does not exist or is not a directory\n        ValueError: Filename would escape vault directory (path traversal attempt)\n    \"\"\"\n    vault = Path(vault_path).expanduser().resolve()\n    if not vault.exists() or not vault.is_dir():\n        raise FileNotFoundError(f\"Obsidian vault path does not exist: {vault}\")\n\n    note_path = unique_note_path(vault, slugify(title))\n    body = (\n        f\"# {title}\\n\\n\"\n        f\"Source: {source_url}\\n\\n\"\n        f\"## Summary\\n\\n{summary}\\n\\n\"\n        f\"## Full Text\\n\\n{full_text}\\n\"\n    )\n\n    # Security: ensure path stays within vault (prevent path traversal)\n    resolved_path = note_path.resolve()\n    if not str(resolved_path).startswith(str(vault.resolve())):\n        raise ValueError(f\"Filename would escape vault: {note_path}\")\n\n    note_path.write_text(body, encoding=\"utf-8\")\n    return str(note_path)\n```\n\n**Usage**:\n```python\nnote_path = save_to_obsidian(\n    vault_path=\"/Users/user/Documents/my-vault\",\n    title=\"Article Title\",\n    source_url=\"https://example.com/article\",\n    summary=\"AI summary here\",\n    full_text=\"Full article text here\"\n)\nprint(f\"Saved to: {note_path}\")\n```\n\n**Notes**:\n- slugify supports Unicode (Chinese, Japanese, etc.) via `\\u4e00-\\u9fff` range\n- Existing notes are not overwritten; duplicate titles create `-2`, `-3`, etc. suffixes\n- Path traversal protection: filename is always relative to vault root; absolute paths in title are rejected\n- If vault_path contains symlinks, `resolve()` normalizes them — ensure the final path is within the vault\n\nFile v2.0.0:references/tool-details.md\n\n# Platform Tool Details\n\nWhen the Routing Map in SKILL.md routes to a specific tool, read this file for the exact commands, API parameters, and error handling.\n\n---\n\n## 1. General Web Pages — r.jina.ai\n\n**Tool**: r.jina.ai\n**API key**: None (free)\n**Command**:\n```bash\ncurl -L -s \"https://r.jina.ai/{url}\"\n```\nReturns clean Markdown. Works for most public web pages.\n\n**Note**: `{url}` must include the full `https://` prefix:\n```bash\ncurl -L -s \"https://r.jina.ai/https://example.com/article\"\n```\n\n**Fallback**: If Jina returns empty content, the page likely requires JS rendering. Try Camoufox or AgentLens.\n\n---\n\n## 2. X / Twitter — FxTwitter API\n\n**Tool**: FxTwitter API\n**API key**: None (free, no login or cookie required)\n**Code**:\n```python\nimport urllib.request, urllib.error, re, json\n\ndef fetch_tweet(url):\n    pattern = r'(?:x\\.com|twitter\\.com)/([a-zA-Z0-9_]{1,15})/status/(\\d+)'\n    match = re.search(pattern, url)\n    if not match:\n        return {\"error\": f\"Cannot parse tweet URL: {url}\"}\n    username, tweet_id = match.group(1), match.group(2)\n    api_url = f\"https://api.fxtwitter.com/{username}/status/{tweet_id}\"\n    req = urllib.request.Request(api_url, headers={\"User-Agent\": \"Mozilla/5.0\"})\n    try:\n        with urllib.request.urlopen(req, timeout=30) as resp:\n            data = json.loads(resp.read().decode())\n    except urllib.error.HTTPError as e:\n        return {\"error\": f\"FxTwitter HTTP {e.code}: could not fetch tweet (may be deleted or private)\"}\n    except Exception as e:\n        return {\"error\": f\"FxTwitter error: {e}\"}\n    if data.get(\"code\") != 200:\n        return {\"error\": f\"FxTwitter error: {data.get('message')}\"}\n    tweet = data[\"tweet\"]\n    return {\n        \"text\": tweet.get(\"text\", \"\"),\n        \"author\": tweet.get(\"author\", {}).get(\"name\", \"\"),\n        \"screen_name\": tweet.get(\"author\", {}).get(\"screen_name\", \"\"),\n        \"likes\": tweet.get(\"likes\", 0),\n        \"retweets\": tweet.get(\"retweets\", 0),\n        \"views\": tweet.get(\"views\", 0),\n        \"replies\": tweet.get(\"replies\", 0),\n        \"created_at\": tweet.get(\"created_at\", \"\"),\n        \"media\": tweet.get(\"media\", {}).get(\"all\", []),\n        \"quote\": tweet.get(\"quote\", {})\n    }\n```\n**Fallback**: AgentLens API (use when FxTwitter returns NOT_FOUND or rate-limit error)\n**Limitation**: Thread replies, user timelines, X Lists, and private accounts are not supported by any free tool. AgentLens does not support these either.\n\n---\n\n## 3. YouTube — youtube-transcript-api\n\n**Tool**: youtube-transcript-api\n**API key**: None (free)\n**Installation**: `pip install youtube-transcript-api`\n**Code**:\n```python\nimport re\n\ndef get_youtube_subtitle(video_url, lang=\"en\"):\n    from youtube_transcript_api import YouTubeTranscriptApi\n    patterns = [\n        r'(?:youtube\\.com/watch\\?.*?v=)([a-zA-Z0-9_-]{11})',\n        r'(?:youtu\\.be/)([a-zA-Z0-9_-]{11})',\n        r'(?:youtube\\.com/shorts/)([a-zA-Z0-9_-]{11})',\n    ]\n    video_id = None\n    for pat in patterns:\n\n\nArchive v0.1.0: 3 files, 14675 bytes\n\nFiles: skill-card.md (2655b), SKILL.md (34617b), _meta.json (138b)","readmeExcerpt":"Skill: Agent Social Reader Owner: hermiod99-vibe Summary: Empower your AI agent to read and summarize specific public web or social media links from TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Tags: latest:2.0.1 Version history: v2.0.1 | 2026-07-14T09:37:0","codeSnippets":[],"executableExamples":[{"language":"json","snippet":"{\n \"agentLensApiKey\": \"\",\n \"openAiApiKey\": \"\",\n \"notionToken\": \"\",\n \"notionDatabaseId\": \"\",\n \"obsidianVaultPath\": \"\",\n \"imaClientId\": \"\",\n \"imaApiKey\": \"\",\n \"imaKnowledgeBaseId\": \"\",\n \"imaScenario\": \"\"\n}"},{"language":"text","snippet":"CRITICAL:\n1. ONLY delete files under /tmp/\n2. ONLY delete files with the `asr_` prefix — never touch files without this prefix\n3. Files without `asr_` may belong to other tasks or processes — do not delete them\n4. If you cannot reliably identify which files were created by this skill, do not delete — ask the user instead"},{"language":"bash","snippet":"# Preview matching files only\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec ls -lh {} +\nfind /tmp -maxdepth 1 -type f -name 'asr_*' -exec du -ch {} +"},{"language":"text","snippet":"User shares link\n → Route to correct tool (see Routing Map above)\n → Read content\n → Execute user's intent (summarize / analyze / download / save / etc.)\n → Deliver result\n → If this was a successful public social-media read and no preference has been asked in this runtime:\n    Ask the scoped preference question from § Persistence & Onboarding\n → Stop unless the current user request explicitly asks to save/archive the result\n\n --- AgentLens API Key Setup (inline, triggered when AgentLens is needed + no key) ---\n\n → IF the platform requires AgentLens AND no AGENT_LENS_API_KEY is found in any lookup:\n Tell user:\n \"This platform requires an AgentLens API key to read.\n It unlocks 20+ major social platforms.\n Get your free API key in 10 seconds at https://agentlensapi.io/pricing — 20 requests/month free; paid plans from $2.9/month, see pricing page.\n Once you paste it here, I'll handle the rest.\"\n\n User pastes key:\n → Use it for the current request only.\n → Ask: \"Would you like me to save this API key locally in plaintext config for future runs, or use it only for this session?\"\n → If user says yes → save to ~/.agent-social-reader/config.json and mention that it is a local plaintext config file\n → If user says no → do not persist it; use only for this session\n → Check: was there a pending URL from this conversation?\n    if YES:\n    → Execute the intended operation for that URL\n    → Deliver result\n    if NO:\n    → Stop"},{"language":"text","snippet":"User shares link\n → Route to AgentLens API\n → API returns unsupported-platform status/code/message (for example Status 10032)\n → DO NOT stop — execute fallback:\n\n 1. Tell user:\n \"This platform isn't directly supported yet.\n I'll try a general web parser as fallback...\"\n\n 2. Fallback A — Jina Reader:\n    curl -L -s \"https://r.jina.ai/{url}\"\n    If returns valid content → summarize and deliver with note:\n    \"Recovered via general web parsing.\"\n\n 3. Fallback B — Runtime Web Search (if Jina also fails):\n    Use the current runtime's built-in web/search tool if available.\n    If no runtime search is available, tell the user search is not configured.\n\n 4. If all paths fail:\n    \"I've tried all available paths, but this link is blocked by anti-bot walls.\n    You may need to open it manually and share the content with me.\""},{"language":"text","snippet":"User shares video URL with summarization intent\n → Step 1: Attempt to get subtitle\n    if YouTube → try youtube-transcript-api\n    if subtitles found → summarize → done\n    if no subtitles → continue to Step 2\n    if non-YouTube → continue to Step 2\n\n → Step 2: Ask user for Whisper preference BEFORE downloading\n    Tell user: \"This video doesn't have subtitles, so I can't summarize the spoken content yet.\n    To do that, I need to transcribe the audio first. You have two options:\n\n    A) Local Whisper (free) — I'll walk you through installing faster-whisper.\n       You'll also need ffmpeg (already required).\n\n    B) OpenAI Whisper API (~$0.006/min) — you'll need an OpenAI API key.\n\n    Which would you prefer?\"\n    → IF user declines → deliver available metadata/text only; stop\n    → IF user chooses A or B → continue to Step 3\n\n → Step 3: Get download URL via AgentLens API\n    → Extract sourceUrl from downloadUrlList (prefer \"video\" type)\n    → If AgentLens is not configured, ask for AGENT_LENS_API_KEY or try subtitles/search-only fallback\n\n → Step 3b: Check for direct audio URL\n    → If downloadUrlList contains an \"audio\" type entry → prefer it over video\n    → Download the audio file directly as `/tmp/asr_audio_{timestamp}.MP3` → skip Step 4 and Step 5 → go straight to Step 6\n    → Audio is much smaller than video → faster download + no ffmpeg extraction needed\n\n → Step 4: Download video\n    curl -L --fail --max-time 120 -o /tmp/asr_{platform}_{timestamp}.mp4 \"{sourceUrl}\"\n    if curl fails + is YouTube → try yt-dlp\n    if curl fails + not YouTube → inform user download failed\n\n → Step 5: Extract audio\n    (Skip this step if audio was already obtained in Step 3b)\n    ffmpeg -y -i /tmp/asr_{platform}_{timestamp}.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/asr_audio_{timestamp}.wav\n\n → Step 6: Transcribe\n    Local Whisper: faster-whisper (CPU, free)\n    API Whisper: OpenAI Whisper API ($0.006/min)\n    Note: OpenAI Whisper API has a 25MB file size limit. For long v"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: agent-social-reader\nversion: 2.0.1\ndescription: >\n Empower your AI agent to read and summarize specific public web or social media links from  TikTok, Instagram, X (Twitter), YouTube, Facebook, Reddit, LinkedIn, Threads, Pinterest, Bluesky, Twitch, Snapchat, Kick, Lemon8, Douyin, Xiaohongshu, Weibo, Bilibili, Kuaishou, Xigua, Zhihu, WeChat Official Accounts, WeChat Channels, general public web pages, and RSS feeds. The skill can save a specific retrieved item to Notion, Obsidian, or ima only when the current user request explicitly asks to save/archive it or confirms that exact destination.\n Trigger when: (1) the user asks to read, summarize, analyze, or extract content from a specific public URL, (2) the user asks what a specific linked video/post/article says, (3) the user asks to subscribe to or inspect a specific RSS feed, or (4) the user asks to save the current retrieved item to Notion, Obsidian, or ima.\n Triggers: \"read this link\", \"summarize this URL\", \"what does this video say\", \"extract this post\", \"save this to Notion\", \"save this to Obsidian\", \"save this to ima\".\nmetadata:\n openclaw:\n homepage: \"https://github.com/hermiod99-vibe/Agent-Social-Reader\"\n requires:\n bins:\n - curl\n - python3\n optional_bins:\n - ffmpeg\n - yt-dlp\n envVars:\n - AGENT_LENS_API_KEY\n - OPENAI_API_KEY\n - NOTION_TOKEN\n - NOTION_DATABASE_ID\n - OBSIDIAN_VAULT_PATH\n - IMA_CLIENT_ID\n - IMA_API_KEY\n - IMA_KNOWLEDGE_BASE_ID\n---\n\n# Agent-Social-Reader — Skill Guide\n\n## ⚠️ Workspace Rules\n\n**CRITICAL: Never create or modify files directly inside the agent's default workspace unless the user explicitly asks for a workspace artifact.**\n- Use `/tmp/` for all temporary outputs and transient cache.\n- Prefer platform secret storage, existing authenticated connectors, or environment variables for secrets.\n- Use `~/.agent-social-reader/config.json` only after the user explicitly agrees to local plaintext configuration.\n- Never print, log, summarize, archive, or commit full API keys/tokens.\n- Only write to Notion, Obsidian, or ima after the current user request asks for that destination or confirms that exact destination for the current item.\n- Use this skill only when the user explicitly asks to read, summarize, analyze, or save a specific URL.\n\n### Configuration Lookup Order\n\nWhen a credential or path is needed, check in this order:\n\n1. Current runtime connectors or MCP tools already authenticated by the user.\n2. Environment variables: `AGENT_LENS_API_KEY`, `OPENAI_API_KEY`, `NOTION_TOKEN`, `NOTION_DATABASE_ID`, `OBSIDIAN_VAULT_PATH`, `IMA_CLIENT_ID`, `IMA_API_KEY`, `IMA_KNOWLEDGE_BASE_ID`.\n3. Local config file: `~/.agent-social-reader/config.json` only if the user previously approved local plaintext storage.\n4. Ask the user for the missing value, explain what it is used for, and ask whether it should be saved locally.\n\nDo not enumerate arbitrary environment variables or scan the user's home directory. Read only the explicit variables and config paths named in this docume"},{"path":"README.md","content":"# 👁️ Agent-Social-Reader\n\n<p align=\"center\">\n <strong>Ask your agent to read a specific social link → AI summarizes → Save that item to Notion / Obsidian / ima when you request it<br/> No social login. No cookies. Lower account risk.<br/><br/>\nReads content from X, Instagram, YouTube, Xiaohongshu, and more — with optional per-item archiving to your personal knowledge base</strong>\n</p>\n<p align=\"center\">\n <a href=\"#-quick-overview\">Quick Overview</a> ·\n <a href=\"#-what-it-does\">What It Does</a> ·\n <a href=\"#-supported-platforms\">Supported Platforms</a> ·\n <a href=\"#-quick-start\">Quick Start</a> ·\n <a href=\"docs/README_cn.md\">中文</a>\n</p>\n\n\n---\n\n## 🎯 Quick Overview\n\nAs a knowledge worker, product manager, or heavy AI agent user, the moment you drop an X (Twitter), Reddit, WeChat article, or Xiaohongshu link into your AI, you hit walls: login barriers, anti-scraping blocks, IP bans, or HTML garbage. On top of that, you're stuck maintaining a separate tool for every single platform.\n\n**The problem this Skill solves:**\n\n❌ Before:\n\n```\nYou: drop a Twitter/Reddit/Xiaohongshu link into your agent\nAgent: \"I can't access that\" / returns garbled HTML\nYou: resort to manual copy-paste, screenshots, or give up\n```\n\n✅ Now:\n\n```\nYou: ask your agent to read a specific social media link\nAgent: reads it → summarizes it\nYou: optionally say \"save this to Notion / Obsidian / ima\"\n→ content neatly filed in your knowledge base\n```\n\n**Core value**:\n- ✅ **Complete loop**: Read → Summarize → Archive — end-to-end in one workflow\n- ✅ **Broad coverage**: 20+ global social platforms (TikTok, Instagram, X, YouTube, Reddit, Douyin, Xiaohongshu, Bilibili...)\n- ✅ **Lower account risk**: No need to log in to personal social accounts, no cookie required — reduces risk of suspension from cookie-based access\n- ✅ **Flexible cost**: Free tools first; optional paid enhancements from $2.90/month\n\n> 💡 **Design philosophy**: Use free tools wherever possible; reach for the most cost-effective paid option only when free tools won't do. You decide which tier to use.\n\n> ❤️ **Special thanks to** [Agent-Reach](https://github.com/Panniantong/Agent-Reach) by [@Panniantong](https://github.com/Panniantong). Agent-Reach proved that getting an AI agent to read web content was achievable — this project is a personal iteration on the same idea, with a tighter focus on social content reading and knowledge base archiving.\n\n---\n\n## ✨ What It Does\n\nAfter installing this Skill, your agent gains the following capabilities (no code required):\n\n| Content Source | Tool | Cost |\n| :--- | :--- | :--- |\n| General web pages | Jina Reader (r.jina.ai) | ✅ Free, no API key required |\n| Public X/Twitter posts | FxTwitter API | ✅ Free, no API key or cookie required |\n| YouTube (with subtitles) | youtube-transcript-api | ✅ Free, no API key required |\n| Weibo | Jina Reader (r.jina.ai) | ✅ Free, bypasses login wall |\n| WeChat public accounts | Camoufox | ✅ Free, headless browser handles JS rendering<br/>(falls back to Age"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7dct92yfrktj1r1p84kvtdpn89xcqf\",\n  \"slug\": \"agent-social-reader\",\n  \"version\": \"2.0.1\",\n  \"publishedAt\": 1784021829386\n}"},{"path":"references/save-scripts.md","content":"# Save Scripts\n\nBuilt-in fallback scripts for archiving content to Notion and Obsidian. These are used when no native Notion/Obsidian tool is available in the current runtime.\n\nFor the ima OpenAPI (Scene 2 & 3), see `references/tool-details.md` §9.\n\nUse these scripts only after the current user request explicitly asks to save or archive the current item. Do not run them during read-only summarization. Confirm the target destination for the current item before writing.\n\nCredential helpers in this file read only the explicit environment variables and config paths named below. They must not enumerate arbitrary environment variables or scan the user's home directory. Prefer platform secret storage or environment variables; read local plaintext config only if the user previously approved local config storage.\n\n---\n\n## Save to ima (Scene 2 & 3 — OpenAPI)\n\n**When to use**: User says \"save to ima\" and no native ima skill is available (i.e., not running inside ima). Use this after completing the scene detection in SKILL.md.\n\nBefore writing, confirm the target ima scenario and knowledge base for the current item.\n\n### Scene Detection Function\n\n```python\nimport os\nfrom pathlib import Path\n\ndef detect_ima_scene():\n    \"\"\"\n    Detect which ima write path to use.\n    Returns: \"ima-native\" | \"workbuddy\" | \"external-api\" | None\n    \"\"\"\n    # Scene 1: ima native skills — check via runtime skill discovery\n    # (ima-knowledge and ima-note are skills, not shell commands;\n    # implementation varies by runtime: check <available_skills>, skill registry,\n    # or platform-specific tool list. If you can call use_skill(\"ima-knowledge\")\n    # and it succeeds → return \"ima-native\".)\n    # TODO: replace with actual runtime skill discovery when running inside ima.\n    # For now, fall through to check env vars / config files below.\n\n    # Scene 2: WorkBuddy — requires platform-specific signal, not just env vars.\n    # IMA_CLIENT_ID + IMA_API_KEY only mean OpenAPI is configured; they are present\n    # in ALL OpenAPI environments (WorkBuddy, OpenClaw, Claude Code, etc.).\n    # Use WorkBuddy-specific indicators: runtime tool/connector presence, or ask user.\n    # If you detect WorkBuddy-specific runtime tools or connectors → return \"workbuddy\"\n    # If only env vars are present but no WorkBuddy signal → return \"external-api\"\n    if os.environ.get(\"IMA_CLIENT_ID\") and os.environ.get(\"IMA_API_KEY\"):\n        # These env vars alone only prove OpenAPI is configured.\n        # Do NOT assume WorkBuddy without a platform-specific signal.\n        return \"external-api\"\n\n    # Scene 3: External agent — read only the documented config files\n    # after the user has approved local plaintext config storage.\n    config_dir = Path.home() / \".config\" / \"ima\"\n    if config_dir.exists():\n        client_id_file = config_dir / \"client_id\"\n        api_key_file = config_dir / \"api_key\"\n        if client_id_file.exists() and api_key_file.exists():\n            return \"external-api\"\n\n    # Also check Op"},{"path":"references/tool-details.md","content":"# Platform Tool Details\n\nWhen the Routing Map in SKILL.md routes to a specific tool, read this file for the exact commands, API parameters, and error handling.\n\n## Service Boundaries\n\nOnly call a service when the current user request requires that service. Do not send the same content to every listed service.\n\n- AgentLens receives a URL only when AgentLens is needed for the current read request.\n- OpenAI Whisper receives audio only if the user chooses API transcription.\n- Notion and ima receive archive content only when the user asks to save the current item and confirms the target destination.\n- Obsidian writes to the user's local vault path.\n- Credential examples read only the explicit variables and config paths shown here. Prefer platform secret storage or environment variables; use local plaintext files only after user approval.\n\n---\n\n## 1. General Web Pages — r.jina.ai\n\n**Tool**: r.jina.ai\n**API key**: None (free)\n**Command**:\n```bash\ncurl -L -s \"https://r.jina.ai/{url}\"\n```\nReturns clean Markdown. Works for most public web pages.\n\n**Note**: `{url}` must include the full `https://` prefix:\n```bash\ncurl -L -s \"https://r.jina.ai/https://example.com/article\"\n```\n\n**Fallback**: If Jina returns empty content, the page likely requires JS rendering. Try Camoufox or AgentLens.\n\n---\n\n## 2. X / Twitter — FxTwitter API\n\n**Tool**: FxTwitter API\n**API key**: None (free, no login or cookie required)\n**Code**:\n```python\nimport urllib.request, urllib.error, re, json\n\ndef fetch_tweet(url):\n    pattern = r'(?:x\\.com|twitter\\.com)/([a-zA-Z0-9_]{1,15})/status/(\\d+)'\n    match = re.search(pattern, url)\n    if not match:\n        return {\"error\": f\"Cannot parse tweet URL: {url}\"}\n    username, tweet_id = match.group(1), match.group(2)\n    api_url = f\"https://api.fxtwitter.com/{username}/status/{tweet_id}\"\n    req = urllib.request.Request(api_url, headers={\"User-Agent\": \"Mozilla/5.0\"})\n    try:\n        with urllib.request.urlopen(req, timeout=30) as resp:\n            data = json.loads(resp.read().decode())\n    except urllib.error.HTTPError as e:\n        return {\"error\": f\"FxTwitter HTTP {e.code}: could not fetch tweet (may be deleted or private)\"}\n    except Exception as e:\n        return {\"error\": f\"FxTwitter error: {e}\"}\n    if data.get(\"code\") != 200:\n        return {\"error\": f\"FxTwitter error: {data.get('message')}\"}\n    tweet = data[\"tweet\"]\n    return {\n        \"text\": tweet.get(\"text\", \"\"),\n        \"author\": tweet.get(\"author\", {}).get(\"name\", \"\"),\n        \"screen_name\": tweet.get(\"author\", {}).get(\"screen_name\", \"\"),\n        \"likes\": tweet.get(\"likes\", 0),\n        \"retweets\": tweet.get(\"retweets\", 0),\n        \"views\": tweet.get(\"views\", 0),\n        \"replies\": tweet.get(\"replies\", 0),\n        \"created_at\": tweet.get(\"created_at\", \"\"),\n        \"media\": tweet.get(\"media\", {}).get(\"all\", []),\n        \"quote\": tweet.get(\"quote\", {})\n    }\n```\n**Fallback**: AgentLens API (use when FxTwitter returns NOT_FOUND or rate-limit error)\n**Limitation**: Thread replies, us"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":2327,"uniquenessScore":39,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T23:07:56.015Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-12T09:57:41.799Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}