diff --git a/cli-manifest.json b/cli-manifest.json new file mode 100644 index 000000000..92edddbfa --- /dev/null +++ b/cli-manifest.json @@ -0,0 +1,7473 @@ +[ + { + "site": "36kr", + "name": "article", + "description": "获取36氪文章正文内容", + "domain": "www.36kr.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Article ID or full 36kr article URL" + } + ], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "36kr/article.js", + "sourceFile": "36kr\\article.ts" + }, + { + "site": "36kr", + "name": "hot", + "description": "36氪热榜 — trending articles (renqi/zonghe/shoucang/catalog)", + "domain": "www.36kr.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of items (max 50)" + }, + { + "name": "type", + "type": "string", + "default": "catalog", + "required": false, + "help": "List type: renqi (人气), zonghe (综合), shoucang (收藏), catalog (热门资讯)" + } + ], + "columns": [ + "rank", + "title", + "url" + ], + "type": "ts", + "modulePath": "36kr/hot.js", + "sourceFile": "36kr\\hot.ts" + }, + { + "site": "36kr", + "name": "news", + "description": "Latest tech/startup news from 36kr (36氪)", + "domain": "www.36kr.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of articles (max 50)" + } + ], + "columns": [ + "rank", + "title", + "summary", + "date", + "url" + ], + "type": "ts", + "modulePath": "36kr/news.js", + "sourceFile": "36kr\\news.ts" + }, + { + "site": "36kr", + "name": "search", + "description": "搜索36氪文章", + "domain": "www.36kr.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword (e.g. \"AI\", \"OpenAI\")" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results (max 50)" + } + ], + "columns": [ + "rank", + "title", + "date", + "url" + ], + "type": "ts", + "modulePath": "36kr/search.js", + "sourceFile": "36kr\\search.ts" + }, + { + "site": "antigravity", + "name": "dump", + "description": "Dump the DOM to help AI understand the UI", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "htmlFile", + "snapFile" + ], + "type": "ts", + "modulePath": "antigravity/dump.js", + "sourceFile": "antigravity\\dump.ts" + }, + { + "site": "antigravity", + "name": "extract-code", + "description": "Extract multi-line code blocks from the current Antigravity conversation", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "code" + ], + "type": "ts", + "modulePath": "antigravity/extract-code.js", + "sourceFile": "antigravity\\extract-code.ts" + }, + { + "site": "antigravity", + "name": "model", + "description": "Switch the active LLM model in Antigravity", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "name", + "type": "str", + "required": true, + "positional": true, + "help": "Target model name (e.g. claude, gemini, o1)" + } + ], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "antigravity/model.js", + "sourceFile": "antigravity\\model.ts" + }, + { + "site": "antigravity", + "name": "new", + "description": "Start a new conversation / clear context in Antigravity", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "status" + ], + "type": "ts", + "modulePath": "antigravity/new.js", + "sourceFile": "antigravity\\new.ts" + }, + { + "site": "antigravity", + "name": "read", + "description": "Read the latest chat messages from Antigravity AI", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "last", + "type": "str", + "required": false, + "help": "Number of recent messages to read (not fully implemented due to generic structure, currently returns full history text or latest chunk)" + } + ], + "columns": [ + "role", + "content" + ], + "type": "ts", + "modulePath": "antigravity/read.js", + "sourceFile": "antigravity\\read.ts" + }, + { + "site": "antigravity", + "name": "send", + "description": "Send a message to Antigravity AI via the internal Lexical editor", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "message", + "type": "str", + "required": true, + "positional": true, + "help": "The message text to send" + } + ], + "columns": [ + "Status", + "Message" + ], + "type": "ts", + "modulePath": "antigravity/send.js", + "sourceFile": "antigravity\\send.ts" + }, + { + "site": "antigravity", + "name": "status", + "description": "Check Antigravity CDP connection and get current page state", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "status", + "url", + "title" + ], + "type": "ts", + "modulePath": "antigravity/status.js", + "sourceFile": "antigravity\\status.ts" + }, + { + "site": "antigravity", + "name": "watch", + "description": "Stream new chat messages from Antigravity in real-time", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [], + "timeout": 86400, + "type": "ts", + "modulePath": "antigravity/watch.js", + "sourceFile": "antigravity\\watch.ts" + }, + { + "site": "apple-podcasts", + "name": "top", + "description": "Top podcasts chart on Apple Podcasts", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of podcasts (max 100)" + }, + { + "name": "country", + "type": "str", + "default": "us", + "required": false, + "help": "Country code (e.g. us, cn, gb, jp)" + } + ], + "columns": [ + "rank", + "title", + "author", + "id" + ], + "type": "ts", + "modulePath": "apple-podcasts/top.js", + "sourceFile": "apple-podcasts\\top.ts" + }, + { + "site": "band", + "name": "bands", + "description": "List all Bands you belong to", + "domain": "www.band.us", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "band_no", + "name", + "members" + ], + "type": "ts", + "modulePath": "band/bands.js", + "sourceFile": "band\\bands.ts" + }, + { + "site": "band", + "name": "mentions", + "description": "Show Band notifications where you are @mentioned", + "domain": "www.band.us", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "filter", + "type": "str", + "default": "mentioned", + "required": false, + "help": "Filter: mentioned (default) | all | post | comment", + "choices": [ + "mentioned", + "all", + "post", + "comment" + ] + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Max results" + }, + { + "name": "unread", + "type": "bool", + "default": false, + "required": false, + "help": "Show only unread notifications" + } + ], + "columns": [ + "time", + "band", + "type", + "from", + "text", + "url" + ], + "type": "ts", + "modulePath": "band/mentions.js", + "sourceFile": "band\\mentions.ts" + }, + { + "site": "band", + "name": "post", + "description": "Export full content of a post including comments", + "domain": "www.band.us", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "band_no", + "type": "int", + "required": true, + "positional": true, + "help": "Band number" + }, + { + "name": "post_no", + "type": "int", + "required": true, + "positional": true, + "help": "Post number" + }, + { + "name": "output", + "type": "str", + "default": "", + "required": false, + "help": "Directory to save attached photos" + }, + { + "name": "comments", + "type": "bool", + "default": true, + "required": false, + "help": "Include comments (default: true)" + } + ], + "columns": [ + "type", + "author", + "date", + "text" + ], + "type": "ts", + "modulePath": "band/post.js", + "sourceFile": "band\\post.ts", + "navigateBefore": false + }, + { + "site": "band", + "name": "posts", + "description": "List posts from a Band", + "domain": "www.band.us", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "band_no", + "type": "int", + "required": true, + "positional": true, + "help": "Band number (get it from: band bands)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Max results" + } + ], + "columns": [ + "date", + "author", + "content", + "comments", + "url" + ], + "type": "ts", + "modulePath": "band/posts.js", + "sourceFile": "band\\posts.ts", + "navigateBefore": false + }, + { + "site": "barchart", + "name": "flow", + "description": "Barchart unusual options activity / options flow", + "domain": "www.barchart.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "type", + "type": "str", + "default": "all", + "required": false, + "help": "Filter: all, call, or put", + "choices": [ + "all", + "call", + "put" + ] + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "symbol", + "type", + "strike", + "expiration", + "last", + "volume", + "openInterest", + "volOiRatio", + "iv" + ], + "type": "ts", + "modulePath": "barchart/flow.js", + "sourceFile": "barchart\\flow.ts" + }, + { + "site": "barchart", + "name": "greeks", + "description": "Barchart options greeks overview (IV, delta, gamma, theta, vega)", + "domain": "www.barchart.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "symbol", + "type": "str", + "required": true, + "positional": true, + "help": "Stock ticker (e.g. AAPL)" + }, + { + "name": "expiration", + "type": "str", + "required": false, + "help": "Expiration date (YYYY-MM-DD). Defaults to the nearest available expiration." + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of near-the-money strikes per type" + } + ], + "columns": [ + "type", + "strike", + "last", + "iv", + "delta", + "gamma", + "theta", + "vega", + "rho", + "volume", + "openInterest", + "expiration" + ], + "type": "ts", + "modulePath": "barchart/greeks.js", + "sourceFile": "barchart\\greeks.ts" + }, + { + "site": "barchart", + "name": "options", + "description": "Barchart options chain with greeks, IV, volume, and open interest", + "domain": "www.barchart.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "symbol", + "type": "str", + "required": true, + "positional": true, + "help": "Stock ticker (e.g. AAPL)" + }, + { + "name": "type", + "type": "str", + "default": "Call", + "required": false, + "help": "Option type: Call or Put", + "choices": [ + "Call", + "Put" + ] + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Max number of strikes to return" + } + ], + "columns": [ + "strike", + "bid", + "ask", + "last", + "change", + "volume", + "openInterest", + "iv", + "delta", + "gamma", + "theta", + "vega", + "expiration" + ], + "type": "ts", + "modulePath": "barchart/options.js", + "sourceFile": "barchart\\options.ts" + }, + { + "site": "barchart", + "name": "quote", + "description": "Barchart stock quote with price, volume, and key metrics", + "domain": "www.barchart.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "symbol", + "type": "str", + "required": true, + "positional": true, + "help": "Stock ticker (e.g. AAPL, MSFT, TSLA)" + } + ], + "columns": [ + "symbol", + "name", + "price", + "change", + "changePct", + "open", + "high", + "low", + "prevClose", + "volume", + "avgVolume", + "marketCap", + "peRatio", + "eps" + ], + "type": "ts", + "modulePath": "barchart/quote.js", + "sourceFile": "barchart\\quote.ts" + }, + { + "site": "bbc", + "name": "news", + "description": "BBC News headlines (RSS)", + "domain": "www.bbc.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of headlines (max 50)" + } + ], + "columns": [ + "rank", + "title", + "description", + "url" + ], + "type": "ts", + "modulePath": "bbc/news.js", + "sourceFile": "bbc\\news.ts" + }, + { + "site": "bilibili", + "name": "hot", + "description": "B站热门视频", + "domain": "www.bilibili.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of videos" + } + ], + "columns": [ + "rank", + "title", + "author", + "play", + "danmaku" + ], + "type": "ts", + "modulePath": "bilibili/hot.js", + "sourceFile": "bilibili\\hot.ts" + }, + { + "site": "bluesky", + "name": "feeds", + "description": "Popular Bluesky feed generators", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of feeds" + } + ], + "columns": [ + "rank", + "name", + "likes", + "creator", + "description" + ], + "type": "ts", + "modulePath": "bluesky/feeds.js", + "sourceFile": "bluesky\\feeds.ts" + }, + { + "site": "bluesky", + "name": "followers", + "description": "List followers of a Bluesky user", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "handle", + "type": "str", + "required": true, + "positional": true, + "help": "Bluesky handle" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of followers" + } + ], + "columns": [ + "rank", + "handle", + "name", + "description" + ], + "type": "ts", + "modulePath": "bluesky/followers.js", + "sourceFile": "bluesky\\followers.ts" + }, + { + "site": "bluesky", + "name": "following", + "description": "List accounts a Bluesky user is following", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "handle", + "type": "str", + "required": true, + "positional": true, + "help": "Bluesky handle" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of accounts" + } + ], + "columns": [ + "rank", + "handle", + "name", + "description" + ], + "type": "ts", + "modulePath": "bluesky/following.js", + "sourceFile": "bluesky\\following.ts" + }, + { + "site": "bluesky", + "name": "profile", + "description": "Get Bluesky user profile info", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "handle", + "type": "str", + "required": true, + "positional": true, + "help": "Bluesky handle (e.g. bsky.app, jay.bsky.team)" + } + ], + "columns": [ + "handle", + "name", + "followers", + "following", + "posts", + "description" + ], + "type": "ts", + "modulePath": "bluesky/profile.js", + "sourceFile": "bluesky\\profile.ts" + }, + { + "site": "bluesky", + "name": "search", + "description": "Search Bluesky users", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "handle", + "name", + "followers", + "description" + ], + "type": "ts", + "modulePath": "bluesky/search.js", + "sourceFile": "bluesky\\search.ts" + }, + { + "site": "bluesky", + "name": "starter-packs", + "description": "Get starter packs created by a Bluesky user", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "handle", + "type": "str", + "required": true, + "positional": true, + "help": "Bluesky handle" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of starter packs" + } + ], + "columns": [ + "rank", + "name", + "description", + "members", + "joins" + ], + "type": "ts", + "modulePath": "bluesky/starter-packs.js", + "sourceFile": "bluesky\\starter-packs.ts" + }, + { + "site": "bluesky", + "name": "thread", + "description": "Get a Bluesky post thread with replies", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "uri", + "type": "str", + "required": true, + "positional": true, + "help": "Post AT URI (at://did:.../app.bsky.feed.post/...) or bsky.app URL" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of replies" + } + ], + "columns": [ + "author", + "text", + "likes", + "reposts", + "replies_count" + ], + "type": "ts", + "modulePath": "bluesky/thread.js", + "sourceFile": "bluesky\\thread.ts" + }, + { + "site": "bluesky", + "name": "trending", + "description": "Trending topics on Bluesky", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of topics" + } + ], + "columns": [ + "rank", + "topic", + "link" + ], + "type": "ts", + "modulePath": "bluesky/trending.js", + "sourceFile": "bluesky\\trending.ts" + }, + { + "site": "bluesky", + "name": "user", + "description": "Get recent posts from a Bluesky user", + "domain": "public.api.bsky.app", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "handle", + "type": "str", + "required": true, + "positional": true, + "help": "Bluesky handle (e.g. bsky.app)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "rank", + "text", + "likes", + "reposts", + "replies" + ], + "type": "ts", + "modulePath": "bluesky/user.js", + "sourceFile": "bluesky\\user.ts" + }, + { + "site": "chatgpt", + "name": "new", + "description": "Open a new chat in ChatGPT Desktop App", + "domain": "localhost", + "strategy": "public", + "browser": false, + "args": [], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "chatgpt/new.js", + "sourceFile": "chatgpt\\new.ts" + }, + { + "site": "chatgpt", + "name": "status", + "description": "Check if ChatGPT Desktop App is running natively on macOS", + "domain": "localhost", + "strategy": "public", + "browser": false, + "args": [], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "chatgpt/status.js", + "sourceFile": "chatgpt\\status.ts" + }, + { + "site": "chatwise", + "name": "ask", + "description": "Send a prompt and wait for the AI response (send + wait + read)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Prompt to send" + }, + { + "name": "timeout", + "type": "str", + "default": "30", + "required": false, + "help": "Max seconds to wait (default: 30)" + } + ], + "columns": [ + "Role", + "Text" + ], + "type": "ts", + "modulePath": "chatwise/ask.js", + "sourceFile": "chatwise\\ask.ts" + }, + { + "site": "chatwise", + "name": "export", + "description": "Export the current ChatWise conversation to a Markdown file", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "output", + "type": "str", + "required": false, + "help": "Output file (default: /tmp/chatwise-export.md)" + } + ], + "columns": [ + "Status", + "File", + "Messages" + ], + "type": "ts", + "modulePath": "chatwise/export.js", + "sourceFile": "chatwise\\export.ts" + }, + { + "site": "chatwise", + "name": "history", + "description": "List conversation history in ChatWise sidebar", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Title" + ], + "type": "ts", + "modulePath": "chatwise/history.js", + "sourceFile": "chatwise\\history.ts" + }, + { + "site": "chatwise", + "name": "model", + "description": "Get or switch the active AI model in ChatWise", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "model-name", + "type": "str", + "required": false, + "positional": true, + "help": "Model to switch to (e.g. gpt-4, claude-3)" + } + ], + "columns": [ + "Status", + "Model" + ], + "type": "ts", + "modulePath": "chatwise/model.js", + "sourceFile": "chatwise\\model.ts" + }, + { + "site": "chatwise", + "name": "read", + "description": "Read the current ChatWise conversation history", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Content" + ], + "type": "ts", + "modulePath": "chatwise/read.js", + "sourceFile": "chatwise\\read.ts" + }, + { + "site": "chatwise", + "name": "send", + "description": "Send a message to the active ChatWise conversation", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Message to send" + } + ], + "columns": [ + "Status", + "InjectedText" + ], + "type": "ts", + "modulePath": "chatwise/send.js", + "sourceFile": "chatwise\\send.ts" + }, + { + "site": "codex", + "name": "ask", + "description": "Send a prompt and wait for the AI response (send + wait + read)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Prompt to send" + }, + { + "name": "timeout", + "type": "str", + "default": "60", + "required": false, + "help": "Max seconds to wait for response (default: 60)" + } + ], + "columns": [ + "Role", + "Text" + ], + "type": "ts", + "modulePath": "codex/ask.js", + "sourceFile": "codex\\ask.ts" + }, + { + "site": "codex", + "name": "export", + "description": "Export the current Codex conversation to a Markdown file", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "output", + "type": "str", + "required": false, + "help": "Output file (default: /tmp/codex-export.md)" + } + ], + "columns": [ + "Status", + "File", + "Messages" + ], + "type": "ts", + "modulePath": "codex/export.js", + "sourceFile": "codex\\export.ts" + }, + { + "site": "codex", + "name": "extract-diff", + "description": "Extract visual code review diff patches from Codex", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "File", + "Diff" + ], + "type": "ts", + "modulePath": "codex/extract-diff.js", + "sourceFile": "codex\\extract-diff.ts" + }, + { + "site": "codex", + "name": "history", + "description": "List recent conversation threads in Codex", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Title" + ], + "type": "ts", + "modulePath": "codex/history.js", + "sourceFile": "codex\\history.ts" + }, + { + "site": "codex", + "name": "model", + "description": "Get or switch the currently active AI model in Codex Desktop", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "model-name", + "type": "str", + "required": false, + "positional": true, + "help": "The ID of the model to switch to (e.g. gpt-4)" + } + ], + "columns": [ + "Status", + "Model" + ], + "type": "ts", + "modulePath": "codex/model.js", + "sourceFile": "codex\\model.ts" + }, + { + "site": "codex", + "name": "read", + "description": "Read the contents of the current Codex conversation thread", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Content" + ], + "type": "ts", + "modulePath": "codex/read.js", + "sourceFile": "codex\\read.ts" + }, + { + "site": "codex", + "name": "send", + "description": "Send text/commands to the Codex AI composer", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Text, command (e.g. /review), or skill (e.g. $imagegen)" + } + ], + "columns": [ + "Status", + "InjectedText" + ], + "type": "ts", + "modulePath": "codex/send.js", + "sourceFile": "codex\\send.ts" + }, + { + "site": "ctrip", + "name": "search", + "description": "搜索携程目的地、景区和酒店联想结果", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword (city or attraction)" + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "name", + "type", + "score", + "price", + "url" + ], + "type": "ts", + "modulePath": "ctrip/search.js", + "sourceFile": "ctrip\\search.ts" + }, + { + "site": "cursor", + "name": "ask", + "description": "Send a prompt and wait for the AI response (send + wait + read)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Prompt to send" + }, + { + "name": "timeout", + "type": "str", + "default": "30", + "required": false, + "help": "Max seconds to wait for response (default: 30)" + } + ], + "columns": [ + "Role", + "Text" + ], + "type": "ts", + "modulePath": "cursor/ask.js", + "sourceFile": "cursor\\ask.ts" + }, + { + "site": "cursor", + "name": "composer", + "description": "Send a prompt directly into Cursor Composer (Cmd+I shortcut)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Text to send into Composer" + } + ], + "columns": [ + "Status", + "InjectedText" + ], + "type": "ts", + "modulePath": "cursor/composer.js", + "sourceFile": "cursor\\composer.ts" + }, + { + "site": "cursor", + "name": "export", + "description": "Export the current cursor conversation to a Markdown file", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "output", + "type": "str", + "required": false, + "help": "Output file (default: /tmp/cursor-export.md)" + } + ], + "columns": [ + "Status", + "File", + "Messages" + ], + "type": "ts", + "modulePath": "cursor/export.js", + "sourceFile": "cursor\\export.ts" + }, + { + "site": "cursor", + "name": "extract-code", + "description": "Extract multi-line code blocks from the current Cursor conversation", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Code" + ], + "type": "ts", + "modulePath": "cursor/extract-code.js", + "sourceFile": "cursor\\extract-code.ts" + }, + { + "site": "cursor", + "name": "history", + "description": "List recent chat sessions from the Cursor sidebar", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Title" + ], + "type": "ts", + "modulePath": "cursor/history.js", + "sourceFile": "cursor\\history.ts" + }, + { + "site": "cursor", + "name": "model", + "description": "Get or switch the currently active AI model in Cursor", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "model-name", + "type": "str", + "required": false, + "positional": true, + "help": "The ID of the model to switch to (e.g. claude-3.5-sonnet)" + } + ], + "columns": [ + "Status", + "Model" + ], + "type": "ts", + "modulePath": "cursor/model.js", + "sourceFile": "cursor\\model.ts" + }, + { + "site": "cursor", + "name": "read", + "description": "Read the current Cursor chat/composer conversation history", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Role", + "Text" + ], + "type": "ts", + "modulePath": "cursor/read.js", + "sourceFile": "cursor\\read.ts" + }, + { + "site": "cursor", + "name": "send", + "description": "Send a prompt directly into Cursor Composer/Chat", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Text to send into Cursor" + } + ], + "columns": [ + "Status", + "InjectedText" + ], + "type": "ts", + "modulePath": "cursor/send.js", + "sourceFile": "cursor\\send.ts" + }, + { + "site": "devto", + "name": "tag", + "description": "Latest DEV.to articles for a specific tag", + "domain": "dev.to", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "tag", + "type": "str", + "required": true, + "positional": true, + "help": "Tag name (e.g. javascript, python, webdev)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of articles" + } + ], + "columns": [ + "rank", + "title", + "author", + "reactions", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "devto/tag.js", + "sourceFile": "devto\\tag.ts" + }, + { + "site": "devto", + "name": "top", + "description": "Top DEV.to articles of the day", + "domain": "dev.to", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of articles" + } + ], + "columns": [ + "rank", + "title", + "author", + "reactions", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "devto/top.js", + "sourceFile": "devto\\top.ts" + }, + { + "site": "devto", + "name": "user", + "description": "Recent DEV.to articles from a specific user", + "domain": "dev.to", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "DEV.to username (e.g. ben, thepracticaldev)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of articles" + } + ], + "columns": [ + "rank", + "title", + "reactions", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "devto/user.js", + "sourceFile": "devto\\user.ts" + }, + { + "site": "dictionary", + "name": "examples", + "description": "Read real-world example sentences utilizing the word", + "domain": "api.dictionaryapi.dev", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "word", + "type": "string", + "required": true, + "positional": true, + "help": "Word to get example sentences for" + } + ], + "columns": [ + "word", + "example" + ], + "type": "ts", + "modulePath": "dictionary/examples.js", + "sourceFile": "dictionary\\examples.ts" + }, + { + "site": "dictionary", + "name": "search", + "description": "Search the Free Dictionary API for definitions, parts of speech, and pronunciations.", + "domain": "api.dictionaryapi.dev", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "word", + "type": "string", + "required": true, + "positional": true, + "help": "Word to define (e.g., serendipity)" + } + ], + "columns": [ + "word", + "phonetic", + "type", + "definition" + ], + "type": "ts", + "modulePath": "dictionary/search.js", + "sourceFile": "dictionary\\search.ts" + }, + { + "site": "dictionary", + "name": "synonyms", + "description": "Find synonyms for a specific word", + "domain": "api.dictionaryapi.dev", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "word", + "type": "string", + "required": true, + "positional": true, + "help": "Word to find synonyms for (e.g., serendipity)" + } + ], + "columns": [ + "word", + "synonyms" + ], + "type": "ts", + "modulePath": "dictionary/synonyms.js", + "sourceFile": "dictionary\\synonyms.ts" + }, + { + "site": "discord-app", + "name": "channels", + "description": "List channels in the current Discord server", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Channel", + "Type" + ], + "type": "ts", + "modulePath": "discord-app/channels.js", + "sourceFile": "discord-app\\channels.ts" + }, + { + "site": "discord-app", + "name": "members", + "description": "List online members in the current Discord channel", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Name", + "Status" + ], + "type": "ts", + "modulePath": "discord-app/members.js", + "sourceFile": "discord-app\\members.ts" + }, + { + "site": "discord-app", + "name": "read", + "description": "Read recent messages from the active Discord channel", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "count", + "type": "str", + "default": "20", + "required": false, + "help": "Number of messages to read (default: 20)" + } + ], + "columns": [ + "Author", + "Time", + "Message" + ], + "type": "ts", + "modulePath": "discord-app/read.js", + "sourceFile": "discord-app\\read.ts" + }, + { + "site": "discord-app", + "name": "search", + "description": "Search messages in the current Discord server/channel (Cmd+F)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + } + ], + "columns": [ + "Index", + "Author", + "Message" + ], + "type": "ts", + "modulePath": "discord-app/search.js", + "sourceFile": "discord-app\\search.ts" + }, + { + "site": "discord-app", + "name": "send", + "description": "Send a message in the active Discord channel", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Message to send" + } + ], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "discord-app/send.js", + "sourceFile": "discord-app\\send.ts" + }, + { + "site": "discord-app", + "name": "servers", + "description": "List all Discord servers (guilds) in the sidebar", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Server" + ], + "type": "ts", + "modulePath": "discord-app/servers.js", + "sourceFile": "discord-app\\servers.ts" + }, + { + "site": "discord-app", + "name": "status", + "description": "Check active CDP connection to Discord Desktop", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Status", + "Url", + "Title" + ], + "type": "ts", + "modulePath": "discord-app/status.js", + "sourceFile": "discord-app\\status.ts" + }, + { + "site": "douban", + "name": "subject", + "description": "获取电影详情", + "domain": "movie.douban.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "电影 ID" + } + ], + "columns": [ + "id", + "title", + "originalTitle", + "year", + "rating", + "ratingCount", + "genres", + "directors", + "casts", + "country", + "duration", + "summary", + "url" + ], + "type": "ts", + "modulePath": "douban/subject.js", + "sourceFile": "douban\\subject.ts" + }, + { + "site": "douban", + "name": "top250", + "description": "豆瓣电影 Top250", + "domain": "movie.douban.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 250, + "required": false, + "help": "返回结果数量" + } + ], + "columns": [ + "rank", + "id", + "title", + "rating", + "url" + ], + "type": "ts", + "modulePath": "douban/top250.js", + "sourceFile": "douban\\top250.ts" + }, + { + "site": "doubao-app", + "name": "dump", + "description": "Dump Doubao desktop app DOM and snapshot to /tmp for debugging", + "domain": "doubao-app", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Status", + "File" + ], + "type": "ts", + "modulePath": "doubao-app/dump.js", + "sourceFile": "doubao-app\\dump.ts" + }, + { + "site": "doubao-app", + "name": "screenshot", + "description": "Capture a screenshot of the Doubao desktop app window", + "domain": "doubao-app", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "output", + "type": "str", + "required": false, + "help": "Output file path (default: /tmp/doubao-screenshot.png)" + } + ], + "columns": [ + "Status", + "File" + ], + "type": "ts", + "modulePath": "doubao-app/screenshot.js", + "sourceFile": "doubao-app\\screenshot.ts" + }, + { + "site": "doubao-app", + "name": "status", + "description": "Check CDP connection to Doubao desktop app", + "domain": "doubao-app", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Status", + "Url", + "Title" + ], + "type": "ts", + "modulePath": "doubao-app/status.js", + "sourceFile": "doubao-app\\status.ts" + }, + { + "site": "douyin", + "name": "draft", + "description": "上传视频并保存为草稿", + "domain": "creator.douyin.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "video", + "type": "str", + "required": true, + "positional": true, + "help": "视频文件路径" + }, + { + "name": "title", + "type": "str", + "required": true, + "help": "视频标题(≤30字)" + }, + { + "name": "caption", + "type": "str", + "default": "", + "required": false, + "help": "正文内容(≤1000字,支持 #话题)" + }, + { + "name": "cover", + "type": "str", + "default": "", + "required": false, + "help": "封面图片路径" + }, + { + "name": "visibility", + "type": "str", + "default": "public", + "required": false, + "help": "", + "choices": [ + "public", + "friends", + "private" + ] + } + ], + "columns": [ + "status", + "draft_id" + ], + "type": "ts", + "modulePath": "douyin/draft.js", + "sourceFile": "douyin\\draft.ts", + "navigateBefore": false + }, + { + "site": "facebook", + "name": "add-friend", + "description": "Send a friend request on Facebook", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Facebook username or profile URL" + } + ], + "columns": [ + "status", + "username" + ], + "type": "ts", + "modulePath": "facebook/add-friend.js", + "sourceFile": "facebook\\add-friend.ts" + }, + { + "site": "facebook", + "name": "events", + "description": "Browse Facebook event categories", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "Number of categories" + } + ], + "columns": [ + "index", + "name" + ], + "type": "ts", + "modulePath": "facebook/events.js", + "sourceFile": "facebook\\events.ts" + }, + { + "site": "facebook", + "name": "feed", + "description": "Get your Facebook news feed", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "index", + "author", + "content", + "likes", + "comments", + "shares" + ], + "type": "ts", + "modulePath": "facebook/feed.js", + "sourceFile": "facebook\\feed.ts" + }, + { + "site": "facebook", + "name": "friends", + "description": "Get Facebook friend suggestions", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of friend suggestions" + } + ], + "columns": [ + "index", + "name", + "mutual" + ], + "type": "ts", + "modulePath": "facebook/friends.js", + "sourceFile": "facebook\\friends.ts" + }, + { + "site": "facebook", + "name": "groups", + "description": "List your Facebook groups", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of groups" + } + ], + "columns": [ + "index", + "name", + "last_post", + "url" + ], + "type": "ts", + "modulePath": "facebook/groups.js", + "sourceFile": "facebook\\groups.ts" + }, + { + "site": "facebook", + "name": "join-group", + "description": "Join a Facebook group", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "group", + "type": "str", + "required": true, + "positional": true, + "help": "Group ID or URL path (e.g. '1876150192925481' or group name)" + } + ], + "columns": [ + "status", + "group" + ], + "type": "ts", + "modulePath": "facebook/join-group.js", + "sourceFile": "facebook\\join-group.ts" + }, + { + "site": "facebook", + "name": "memories", + "description": "Get your Facebook memories (On This Day)", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of memories" + } + ], + "columns": [ + "index", + "source", + "content", + "time" + ], + "type": "ts", + "modulePath": "facebook/memories.js", + "sourceFile": "facebook\\memories.ts" + }, + { + "site": "facebook", + "name": "notifications", + "description": "Get recent Facebook notifications", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "Number of notifications" + } + ], + "columns": [ + "index", + "text", + "time" + ], + "type": "ts", + "modulePath": "facebook/notifications.js", + "sourceFile": "facebook\\notifications.ts" + }, + { + "site": "facebook", + "name": "profile", + "description": "Get Facebook user/page profile info", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Facebook username or page name" + } + ], + "columns": [ + "name", + "username", + "friends", + "followers", + "url" + ], + "type": "ts", + "modulePath": "facebook/profile.js", + "sourceFile": "facebook\\profile.ts" + }, + { + "site": "facebook", + "name": "search", + "description": "Search Facebook for people, pages, or posts", + "domain": "www.facebook.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "index", + "title", + "text", + "url" + ], + "type": "ts", + "modulePath": "facebook/search.js", + "sourceFile": "facebook\\search.ts" + }, + { + "site": "gitee", + "name": "search", + "description": "Search repositories on Gitee", + "domain": "gitee.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results (max 50)" + } + ], + "columns": [ + "rank", + "name", + "language", + "stars", + "description", + "url" + ], + "type": "ts", + "modulePath": "gitee/search.js", + "sourceFile": "gitee\\search.ts" + }, + { + "site": "gitee", + "name": "trending", + "description": "Recommended open-source projects on Gitee Explore", + "domain": "gitee.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of projects (max 50)" + } + ], + "columns": [ + "name", + "description", + "stars", + "url" + ], + "type": "ts", + "modulePath": "gitee/trending.js", + "sourceFile": "gitee\\trending.ts" + }, + { + "site": "gitee", + "name": "user", + "description": "Show a Gitee user profile panel", + "domain": "gitee.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Gitee username" + } + ], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "gitee/user.js", + "sourceFile": "gitee\\user.ts" + }, + { + "site": "google", + "name": "search", + "description": "Search Google", + "domain": "google.com", + "strategy": "public", + "browser": true, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results (1-100)" + }, + { + "name": "lang", + "type": "str", + "default": "en", + "required": false, + "help": "Language short code (e.g. en, zh)" + } + ], + "columns": [ + "type", + "title", + "url", + "snippet" + ], + "type": "ts", + "modulePath": "google/search.js", + "sourceFile": "google\\search.ts" + }, + { + "site": "google", + "name": "suggest", + "description": "Get Google search suggestions", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "lang", + "type": "str", + "default": "zh-CN", + "required": false, + "help": "Language code" + } + ], + "columns": [ + "suggestion" + ], + "type": "ts", + "modulePath": "google/suggest.js", + "sourceFile": "google\\suggest.ts" + }, + { + "site": "grok", + "name": "ask", + "description": "Send a message to Grok and get response", + "domain": "grok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "prompt", + "type": "string", + "required": true, + "positional": true, + "help": "Prompt to send to Grok" + }, + { + "name": "timeout", + "type": "int", + "default": 120, + "required": false, + "help": "Max seconds to wait for response (default: 120)" + }, + { + "name": "new", + "type": "boolean", + "default": false, + "required": false, + "help": "Start a new chat before sending (default: false)" + }, + { + "name": "web", + "type": "boolean", + "default": false, + "required": false, + "help": "Use the explicit grok.com consumer web flow (default: false)" + } + ], + "columns": [ + "response" + ], + "type": "ts", + "modulePath": "grok/ask.js", + "sourceFile": "grok\\ask.ts" + }, + { + "site": "hackernews", + "name": "ask", + "description": "Hacker News Ask HN posts", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments" + ], + "type": "ts", + "modulePath": "hackernews/ask.js", + "sourceFile": "hackernews\\ask.ts" + }, + { + "site": "hackernews", + "name": "best", + "description": "Hacker News best stories", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments" + ], + "type": "ts", + "modulePath": "hackernews/best.js", + "sourceFile": "hackernews\\best.ts" + }, + { + "site": "hackernews", + "name": "jobs", + "description": "Hacker News job postings", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of job postings" + } + ], + "columns": [ + "rank", + "title", + "author", + "url" + ], + "type": "ts", + "modulePath": "hackernews/jobs.js", + "sourceFile": "hackernews\\jobs.ts" + }, + { + "site": "hackernews", + "name": "new", + "description": "Hacker News newest stories", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments" + ], + "type": "ts", + "modulePath": "hackernews/new.js", + "sourceFile": "hackernews\\new.ts" + }, + { + "site": "hackernews", + "name": "search", + "description": "Search Hacker News stories", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results" + }, + { + "name": "sort", + "type": "str", + "default": "relevance", + "required": false, + "help": "Sort by relevance or date", + "choices": [ + "relevance", + "date" + ] + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments", + "url" + ], + "type": "ts", + "modulePath": "hackernews/search.js", + "sourceFile": "hackernews\\search.ts" + }, + { + "site": "hackernews", + "name": "show", + "description": "Hacker News Show HN posts", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments" + ], + "type": "ts", + "modulePath": "hackernews/show.js", + "sourceFile": "hackernews\\show.ts" + }, + { + "site": "hackernews", + "name": "top", + "description": "Hacker News top stories", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments" + ], + "type": "ts", + "modulePath": "hackernews/top.js", + "sourceFile": "hackernews\\top.ts" + }, + { + "site": "hackernews", + "name": "user", + "description": "Hacker News user profile", + "domain": "news.ycombinator.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "HN username" + } + ], + "columns": [ + "username", + "karma", + "created", + "about" + ], + "type": "ts", + "modulePath": "hackernews/user.js", + "sourceFile": "hackernews\\user.ts" + }, + { + "site": "hf", + "name": "top", + "description": "Top upvoted Hugging Face papers", + "domain": "huggingface.co", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of papers" + }, + { + "name": "all", + "type": "bool", + "default": false, + "required": false, + "help": "Return all papers (ignore limit)" + }, + { + "name": "date", + "type": "str", + "required": false, + "help": "Date (YYYY-MM-DD), defaults to most recent" + }, + { + "name": "period", + "type": "str", + "default": "daily", + "required": false, + "help": "Time period: daily, weekly, or monthly", + "choices": [ + "daily", + "weekly", + "monthly" + ] + } + ], + "type": "ts", + "modulePath": "hf/top.js", + "sourceFile": "hf\\top.ts" + }, + { + "site": "hupu", + "name": "hot", + "description": "虎扑热门帖子", + "domain": "bbs.hupu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of hot posts" + } + ], + "columns": [ + "rank", + "title", + "url" + ], + "type": "ts", + "modulePath": "hupu/hot.js", + "sourceFile": "hupu\\hot.ts" + }, + { + "site": "instagram", + "name": "comment", + "description": "Comment on an Instagram post", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username of the post author" + }, + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Comment text" + }, + { + "name": "index", + "type": "int", + "default": 1, + "required": false, + "help": "Post index (1 = most recent)" + } + ], + "columns": [ + "status", + "user", + "text" + ], + "type": "ts", + "modulePath": "instagram/comment.js", + "sourceFile": "instagram\\comment.ts" + }, + { + "site": "instagram", + "name": "download", + "description": "Download images and videos from Instagram posts and reels", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram post / reel / tv URL" + }, + { + "name": "path", + "type": "str", + "default": "C:\\Users\\Administrator\\Downloads\\Instagram", + "required": false, + "help": "Download directory" + } + ], + "type": "ts", + "modulePath": "instagram/download.js", + "sourceFile": "instagram\\download.ts", + "navigateBefore": false + }, + { + "site": "instagram", + "name": "explore", + "description": "Instagram explore/discover trending posts", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "rank", + "user", + "caption", + "likes", + "comments", + "type" + ], + "type": "ts", + "modulePath": "instagram/explore.js", + "sourceFile": "instagram\\explore.ts" + }, + { + "site": "instagram", + "name": "follow", + "description": "Follow an Instagram user", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username to follow" + } + ], + "columns": [ + "status", + "username" + ], + "type": "ts", + "modulePath": "instagram/follow.js", + "sourceFile": "instagram\\follow.ts" + }, + { + "site": "instagram", + "name": "followers", + "description": "List followers of an Instagram user", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of followers" + } + ], + "columns": [ + "rank", + "username", + "name", + "verified", + "private" + ], + "type": "ts", + "modulePath": "instagram/followers.js", + "sourceFile": "instagram\\followers.ts" + }, + { + "site": "instagram", + "name": "following", + "description": "List accounts an Instagram user is following", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of accounts" + } + ], + "columns": [ + "rank", + "username", + "name", + "verified", + "private" + ], + "type": "ts", + "modulePath": "instagram/following.js", + "sourceFile": "instagram\\following.ts" + }, + { + "site": "instagram", + "name": "like", + "description": "Like an Instagram post", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username of the post author" + }, + { + "name": "index", + "type": "int", + "default": 1, + "required": false, + "help": "Post index (1 = most recent)" + } + ], + "columns": [ + "status", + "user", + "post" + ], + "type": "ts", + "modulePath": "instagram/like.js", + "sourceFile": "instagram\\like.ts" + }, + { + "site": "instagram", + "name": "note", + "description": "Publish a text Instagram note", + "domain": "www.instagram.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "content", + "type": "str", + "required": true, + "positional": true, + "help": "Note text (max 60 characters)" + } + ], + "columns": [ + "status", + "detail", + "noteId" + ], + "timeout": 120, + "type": "ts", + "modulePath": "instagram/note.js", + "sourceFile": "instagram\\note.ts" + }, + { + "site": "instagram", + "name": "profile", + "description": "Get Instagram user profile info", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username" + } + ], + "columns": [ + "username", + "name", + "followers", + "following", + "posts", + "verified", + "bio" + ], + "type": "ts", + "modulePath": "instagram/profile.js", + "sourceFile": "instagram\\profile.ts" + }, + { + "site": "instagram", + "name": "save", + "description": "Save (bookmark) an Instagram post", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username of the post author" + }, + { + "name": "index", + "type": "int", + "default": 1, + "required": false, + "help": "Post index (1 = most recent)" + } + ], + "columns": [ + "status", + "user", + "post" + ], + "type": "ts", + "modulePath": "instagram/save.js", + "sourceFile": "instagram\\save.ts" + }, + { + "site": "instagram", + "name": "saved", + "description": "Get your saved Instagram posts", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of saved posts" + } + ], + "columns": [ + "index", + "user", + "caption", + "likes", + "comments", + "type" + ], + "type": "ts", + "modulePath": "instagram/saved.js", + "sourceFile": "instagram\\saved.ts" + }, + { + "site": "instagram", + "name": "search", + "description": "Search Instagram users", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "username", + "name", + "verified", + "private", + "url" + ], + "type": "ts", + "modulePath": "instagram/search.js", + "sourceFile": "instagram\\search.ts" + }, + { + "site": "instagram", + "name": "unfollow", + "description": "Unfollow an Instagram user", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username to unfollow" + } + ], + "columns": [ + "status", + "username" + ], + "type": "ts", + "modulePath": "instagram/unfollow.js", + "sourceFile": "instagram\\unfollow.ts" + }, + { + "site": "instagram", + "name": "unlike", + "description": "Unlike an Instagram post", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username of the post author" + }, + { + "name": "index", + "type": "int", + "default": 1, + "required": false, + "help": "Post index (1 = most recent)" + } + ], + "columns": [ + "status", + "user", + "post" + ], + "type": "ts", + "modulePath": "instagram/unlike.js", + "sourceFile": "instagram\\unlike.ts" + }, + { + "site": "instagram", + "name": "unsave", + "description": "Unsave (remove bookmark) an Instagram post", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username of the post author" + }, + { + "name": "index", + "type": "int", + "default": 1, + "required": false, + "help": "Post index (1 = most recent)" + } + ], + "columns": [ + "status", + "user", + "post" + ], + "type": "ts", + "modulePath": "instagram/unsave.js", + "sourceFile": "instagram\\unsave.ts" + }, + { + "site": "instagram", + "name": "user", + "description": "Get recent posts from an Instagram user", + "domain": "www.instagram.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Instagram username" + }, + { + "name": "limit", + "type": "int", + "default": 12, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "index", + "caption", + "likes", + "comments", + "type", + "date" + ], + "type": "ts", + "modulePath": "instagram/user.js", + "sourceFile": "instagram\\user.ts" + }, + { + "site": "jd", + "name": "cart", + "description": "查看京东购物车", + "domain": "cart.jd.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "index", + "title", + "price", + "quantity", + "sku" + ], + "type": "ts", + "modulePath": "jd/cart.js", + "sourceFile": "jd\\cart.ts", + "navigateBefore": false + }, + { + "site": "jd", + "name": "item", + "description": "京东商品详情(价格、店铺、规格参数、AVIF 图片)", + "domain": "item.jd.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "sku", + "type": "str", + "required": true, + "positional": true, + "help": "商品 SKU ID(如 100291143898)" + }, + { + "name": "images", + "type": "int", + "default": 10, + "required": false, + "help": "AVIF 图片数量上限(默认10)" + } + ], + "columns": [ + "title", + "price", + "shop", + "specs", + "avifImages" + ], + "type": "ts", + "modulePath": "jd/item.js", + "sourceFile": "jd\\item.ts" + }, + { + "site": "jike", + "name": "comment", + "description": "评论即刻帖子", + "domain": "web.okjike.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "id", + "type": "string", + "required": true, + "positional": true, + "help": "帖子 ID" + }, + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "评论内容" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "jike/comment.js", + "sourceFile": "jike\\comment.ts" + }, + { + "site": "jike", + "name": "create", + "description": "发布即刻动态", + "domain": "web.okjike.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "动态正文内容" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "jike/create.js", + "sourceFile": "jike\\create.ts" + }, + { + "site": "jike", + "name": "like", + "description": "点赞即刻帖子", + "domain": "web.okjike.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "id", + "type": "string", + "required": true, + "positional": true, + "help": "帖子 ID" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "jike/like.js", + "sourceFile": "jike\\like.ts" + }, + { + "site": "jike", + "name": "notifications", + "description": "即刻通知", + "domain": "web.okjike.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "" + } + ], + "columns": [ + "type", + "user", + "content", + "time" + ], + "type": "ts", + "modulePath": "jike/notifications.js", + "sourceFile": "jike\\notifications.ts" + }, + { + "site": "jike", + "name": "post", + "description": "即刻帖子详情及评论", + "domain": "m.okjike.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "string", + "required": true, + "positional": true, + "help": "Post ID (from post URL)" + } + ], + "columns": [ + "type", + "author", + "content", + "likes", + "time" + ], + "type": "ts", + "modulePath": "jike/post.js", + "sourceFile": "jike\\post.ts" + }, + { + "site": "jike", + "name": "repost", + "description": "转发即刻帖子", + "domain": "web.okjike.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "id", + "type": "string", + "required": true, + "positional": true, + "help": "帖子 ID" + }, + { + "name": "text", + "type": "string", + "required": false, + "positional": true, + "help": "转发附言(可选)" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "jike/repost.js", + "sourceFile": "jike\\repost.ts" + }, + { + "site": "jike", + "name": "topic", + "description": "即刻话题/圈子帖子", + "domain": "m.okjike.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "string", + "required": true, + "positional": true, + "help": "Topic ID (from topic URL, e.g. 553870e8e4b0cafb0a1bef68)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "content", + "author", + "likes", + "comments", + "time", + "url" + ], + "type": "ts", + "modulePath": "jike/topic.js", + "sourceFile": "jike\\topic.ts" + }, + { + "site": "jike", + "name": "user", + "description": "即刻用户动态", + "domain": "m.okjike.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "Username from profile URL (e.g. wenhao1996)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "content", + "type", + "likes", + "comments", + "time", + "url" + ], + "type": "ts", + "modulePath": "jike/user.js", + "sourceFile": "jike\\user.ts" + }, + { + "site": "jimeng", + "name": "generate", + "description": "即梦AI 文生图 — 输入 prompt 生成图片", + "domain": "jimeng.jianying.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "prompt", + "type": "string", + "required": true, + "positional": true, + "help": "图片描述 prompt" + }, + { + "name": "model", + "type": "string", + "default": "high_aes_general_v50", + "required": false, + "help": "模型: high_aes_general_v50 (5.0 Lite), high_aes_general_v42 (4.6), high_aes_general_v40 (4.0)" + }, + { + "name": "wait", + "type": "int", + "default": 40, + "required": false, + "help": "等待生成完成的秒数" + } + ], + "columns": [ + "status", + "prompt", + "image_count", + "image_urls" + ], + "type": "ts", + "modulePath": "jimeng/generate.js", + "sourceFile": "jimeng\\generate.ts" + }, + { + "site": "jimeng", + "name": "history", + "description": "即梦AI 查看最近生成的作品", + "domain": "jimeng.jianying.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 5, + "required": false, + "help": "" + } + ], + "columns": [ + "prompt", + "model", + "status", + "image_url", + "created_at" + ], + "type": "ts", + "modulePath": "jimeng/history.js", + "sourceFile": "jimeng\\history.ts" + }, + { + "site": "jimeng", + "name": "new", + "description": "即梦AI 新建会话(workspace)", + "domain": "jimeng.jianying.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "workspace_id", + "workspace_url" + ], + "type": "ts", + "modulePath": "jimeng/new.js", + "sourceFile": "jimeng\\new.ts" + }, + { + "site": "jimeng", + "name": "workspaces", + "description": "即梦AI 查看所有工作区(会话窗口)", + "domain": "jimeng.jianying.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "workspace_id", + "name", + "is_pinned", + "updated_at" + ], + "type": "ts", + "modulePath": "jimeng/workspaces.js", + "sourceFile": "jimeng\\workspaces.ts" + }, + { + "site": "linkedin", + "name": "search", + "description": "Search LinkedIn jobs", + "domain": "www.linkedin.com", + "strategy": "header", + "browser": true, + "args": [ + { + "name": "query", + "type": "string", + "required": true, + "positional": true, + "help": "Job search keywords" + }, + { + "name": "location", + "type": "string", + "required": false, + "help": "Location text such as San Francisco Bay Area" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of jobs to return (max 100)" + }, + { + "name": "start", + "type": "int", + "default": 0, + "required": false, + "help": "Result offset for pagination" + }, + { + "name": "details", + "type": "bool", + "default": false, + "required": false, + "help": "Include full job description and apply URL (slower)" + }, + { + "name": "company", + "type": "string", + "required": false, + "help": "Comma-separated company names or LinkedIn company IDs" + }, + { + "name": "experience-level", + "type": "string", + "required": false, + "help": "Comma-separated: internship, entry, associate, mid-senior, director, executive" + }, + { + "name": "job-type", + "type": "string", + "required": false, + "help": "Comma-separated: full-time, part-time, contract, temporary, volunteer, internship, other" + }, + { + "name": "date-posted", + "type": "string", + "required": false, + "help": "One of: any, month, week, 24h" + }, + { + "name": "remote", + "type": "string", + "required": false, + "help": "Comma-separated: on-site, hybrid, remote" + } + ], + "columns": [ + "rank", + "title", + "company", + "location", + "listed", + "salary", + "url" + ], + "type": "ts", + "modulePath": "linkedin/search.js", + "sourceFile": "linkedin\\search.ts" + }, + { + "site": "linkedin", + "name": "timeline", + "description": "Read LinkedIn home timeline posts", + "domain": "www.linkedin.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts to return (max 100)" + } + ], + "columns": [ + "rank", + "author", + "author_url", + "headline", + "text", + "posted_at", + "reactions", + "comments", + "url" + ], + "type": "ts", + "modulePath": "linkedin/timeline.js", + "sourceFile": "linkedin\\timeline.ts" + }, + { + "site": "linux-do", + "name": "feed", + "description": "linux.do 话题列表(需登录;支持全站、标签、分类)", + "domain": "linux.do", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "view", + "type": "str", + "default": "latest", + "required": false, + "help": "View type", + "choices": [ + "latest", + "hot", + "top" + ] + }, + { + "name": "tag", + "type": "str", + "required": false, + "help": "Tag name, slug, or id" + }, + { + "name": "category", + "type": "str", + "required": false, + "help": "Category name, slug, id, or parent/name path" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of items (per_page)" + }, + { + "name": "order", + "type": "str", + "default": "default", + "required": false, + "help": "Sort order", + "choices": [ + "default", + "created", + "activity", + "views", + "posts", + "category", + "likes", + "op_likes", + "posters" + ] + }, + { + "name": "ascending", + "type": "boolean", + "default": false, + "required": false, + "help": "Sort ascending (default: desc)" + }, + { + "name": "period", + "type": "str", + "required": false, + "help": "Time period (only for --view top)", + "choices": [ + "all", + "daily", + "weekly", + "monthly", + "quarterly", + "yearly" + ] + } + ], + "columns": [ + "title", + "replies", + "created", + "likes", + "views", + "url" + ], + "type": "ts", + "modulePath": "linux-do/feed.js", + "sourceFile": "linux-do\\feed.ts" + }, + { + "site": "linux-do", + "name": "topic-content", + "description": "Get the main topic body as Markdown", + "domain": "linux.do", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "int", + "required": true, + "positional": true, + "help": "Topic ID" + } + ], + "columns": [ + "content" + ], + "type": "ts", + "modulePath": "linux-do/topic-content.js", + "sourceFile": "linux-do\\topic-content.ts" + }, + { + "site": "lobsters", + "name": "active", + "description": "Lobste.rs most active discussions", + "domain": "lobste.rs", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "lobsters/active.js", + "sourceFile": "lobsters\\active.ts" + }, + { + "site": "lobsters", + "name": "hot", + "description": "Lobste.rs hottest stories", + "domain": "lobste.rs", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "lobsters/hot.js", + "sourceFile": "lobsters\\hot.ts" + }, + { + "site": "lobsters", + "name": "newest", + "description": "Lobste.rs newest stories", + "domain": "lobste.rs", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "lobsters/newest.js", + "sourceFile": "lobsters\\newest.ts" + }, + { + "site": "lobsters", + "name": "tag", + "description": "Lobste.rs stories by tag", + "domain": "lobste.rs", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "tag", + "type": "str", + "required": true, + "positional": true, + "help": "Tag name (e.g. programming, rust, security, ai)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of stories" + } + ], + "columns": [ + "rank", + "title", + "score", + "author", + "comments", + "tags" + ], + "type": "ts", + "modulePath": "lobsters/tag.js", + "sourceFile": "lobsters\\tag.ts" + }, + { + "site": "notion", + "name": "export", + "description": "Export the current Notion page as Markdown", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "output", + "type": "str", + "required": false, + "help": "Output file (default: /tmp/notion-export.md)" + } + ], + "columns": [ + "Status", + "File" + ], + "type": "ts", + "modulePath": "notion/export.js", + "sourceFile": "notion\\export.ts" + }, + { + "site": "notion", + "name": "favorites", + "description": "List pages from the Notion Favorites section in the sidebar", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Title", + "Icon" + ], + "type": "ts", + "modulePath": "notion/favorites.js", + "sourceFile": "notion\\favorites.ts" + }, + { + "site": "notion", + "name": "new", + "description": "Create a new page in Notion", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "title", + "type": "str", + "required": false, + "positional": true, + "help": "Page title (optional)" + } + ], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "notion/new.js", + "sourceFile": "notion\\new.ts" + }, + { + "site": "notion", + "name": "read", + "description": "Read the content of the currently open Notion page", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Title", + "Content" + ], + "type": "ts", + "modulePath": "notion/read.js", + "sourceFile": "notion\\read.ts" + }, + { + "site": "notion", + "name": "search", + "description": "Search pages and databases in Notion via Quick Find (Cmd+P)", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + } + ], + "columns": [ + "Index", + "Title" + ], + "type": "ts", + "modulePath": "notion/search.js", + "sourceFile": "notion\\search.ts" + }, + { + "site": "notion", + "name": "sidebar", + "description": "List pages and databases from the Notion sidebar", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Index", + "Title" + ], + "type": "ts", + "modulePath": "notion/sidebar.js", + "sourceFile": "notion\\sidebar.ts" + }, + { + "site": "notion", + "name": "status", + "description": "Check active CDP connection to Notion Desktop", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [], + "columns": [ + "Status", + "Url", + "Title" + ], + "type": "ts", + "modulePath": "notion/status.js", + "sourceFile": "notion\\status.ts" + }, + { + "site": "notion", + "name": "write", + "description": "Append text content to the currently open Notion page", + "domain": "localhost", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Text to append to the page" + } + ], + "columns": [ + "Status" + ], + "type": "ts", + "modulePath": "notion/write.js", + "sourceFile": "notion\\write.ts" + }, + { + "site": "pixiv", + "name": "detail", + "description": "View illustration details (tags, stats, URLs)", + "domain": "www.pixiv.net", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Illustration ID" + } + ], + "columns": [ + "illust_id", + "title", + "author", + "type", + "pages", + "bookmarks", + "likes", + "views", + "tags", + "created", + "url" + ], + "type": "ts", + "modulePath": "pixiv/detail.js", + "sourceFile": "pixiv\\detail.ts" + }, + { + "site": "pixiv", + "name": "ranking", + "description": "Pixiv illustration rankings (daily/weekly/monthly)", + "domain": "www.pixiv.net", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "mode", + "type": "str", + "default": "daily", + "required": false, + "help": "Ranking mode", + "choices": [ + "daily", + "weekly", + "monthly", + "rookie", + "original", + "male", + "female", + "daily_r18", + "weekly_r18" + ] + }, + { + "name": "page", + "type": "int", + "default": 1, + "required": false, + "help": "Page number" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "title", + "author", + "illust_id", + "pages", + "bookmarks" + ], + "type": "ts", + "modulePath": "pixiv/ranking.js", + "sourceFile": "pixiv\\ranking.ts" + }, + { + "site": "pixiv", + "name": "user", + "description": "View Pixiv artist profile", + "domain": "www.pixiv.net", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "uid", + "type": "str", + "required": true, + "positional": true, + "help": "Pixiv user ID" + } + ], + "columns": [ + "user_id", + "name", + "premium", + "following", + "illusts", + "manga", + "novels", + "comment" + ], + "type": "ts", + "modulePath": "pixiv/user.js", + "sourceFile": "pixiv\\user.ts" + }, + { + "site": "reddit", + "name": "comment", + "description": "Post a comment on a Reddit post", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "post-id", + "type": "string", + "required": true, + "positional": true, + "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)" + }, + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "Comment text" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "reddit/comment.js", + "sourceFile": "reddit\\comment.ts" + }, + { + "site": "reddit", + "name": "frontpage", + "description": "Reddit Frontpage / r/all", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "subreddit", + "author", + "upvotes", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/frontpage.js", + "sourceFile": "reddit\\frontpage.ts" + }, + { + "site": "reddit", + "name": "hot", + "description": "Reddit 热门帖子", + "domain": "www.reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "subreddit", + "type": "str", + "default": "", + "required": false, + "help": "Subreddit name (e.g. programming). Empty for frontpage" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of posts" + } + ], + "columns": [ + "rank", + "title", + "subreddit", + "score", + "comments" + ], + "type": "ts", + "modulePath": "reddit/hot.js", + "sourceFile": "reddit\\hot.ts" + }, + { + "site": "reddit", + "name": "popular", + "description": "Reddit Popular posts (/r/popular)", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "" + } + ], + "columns": [ + "rank", + "title", + "subreddit", + "score", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/popular.js", + "sourceFile": "reddit\\popular.ts" + }, + { + "site": "reddit", + "name": "read", + "description": "Read a Reddit post and its comments", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "post-id", + "type": "str", + "required": true, + "positional": true, + "help": "Post ID (e.g. 1abc123) or full URL" + }, + { + "name": "sort", + "type": "str", + "default": "best", + "required": false, + "help": "Comment sort: best, top, new, controversial, old, qa" + }, + { + "name": "limit", + "type": "int", + "default": 25, + "required": false, + "help": "Number of top-level comments" + }, + { + "name": "depth", + "type": "int", + "default": 2, + "required": false, + "help": "Max reply depth (1=no replies, 2=one level of replies, etc.)" + }, + { + "name": "replies", + "type": "int", + "default": 5, + "required": false, + "help": "Max replies shown per comment at each level (sorted by score)" + }, + { + "name": "max-length", + "type": "int", + "default": 2000, + "required": false, + "help": "Max characters per comment body (min 100)" + } + ], + "columns": [ + "type", + "author", + "score", + "text" + ], + "type": "ts", + "modulePath": "reddit/read.js", + "sourceFile": "reddit\\read.ts" + }, + { + "site": "reddit", + "name": "save", + "description": "Save or unsave a Reddit post", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "post-id", + "type": "string", + "required": true, + "positional": true, + "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)" + }, + { + "name": "undo", + "type": "boolean", + "default": false, + "required": false, + "help": "Unsave instead of save" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "reddit/save.js", + "sourceFile": "reddit\\save.ts" + }, + { + "site": "reddit", + "name": "saved", + "description": "Browse your saved Reddit posts", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "subreddit", + "score", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/saved.js", + "sourceFile": "reddit\\saved.ts" + }, + { + "site": "reddit", + "name": "search", + "description": "Search Reddit Posts", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "subreddit", + "type": "string", + "default": "", + "required": false, + "help": "Search within a specific subreddit" + }, + { + "name": "sort", + "type": "string", + "default": "relevance", + "required": false, + "help": "Sort order: relevance, hot, top, new, comments" + }, + { + "name": "time", + "type": "string", + "default": "all", + "required": false, + "help": "Time filter: hour, day, week, month, year, all" + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "subreddit", + "author", + "score", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/search.js", + "sourceFile": "reddit\\search.ts" + }, + { + "site": "reddit", + "name": "subreddit", + "description": "Get posts from a specific Subreddit", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "name", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "sort", + "type": "string", + "default": "hot", + "required": false, + "help": "Sorting method: hot, new, top, rising, controversial" + }, + { + "name": "time", + "type": "string", + "default": "all", + "required": false, + "help": "Time filter for top/controversial: hour, day, week, month, year, all" + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "author", + "upvotes", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/subreddit.js", + "sourceFile": "reddit\\subreddit.ts" + }, + { + "site": "reddit", + "name": "subscribe", + "description": "Subscribe or unsubscribe to a subreddit", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "subreddit", + "type": "string", + "required": true, + "positional": true, + "help": "Subreddit name (e.g. python)" + }, + { + "name": "undo", + "type": "boolean", + "default": false, + "required": false, + "help": "Unsubscribe instead of subscribe" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "reddit/subscribe.js", + "sourceFile": "reddit\\subscribe.ts" + }, + { + "site": "reddit", + "name": "upvote", + "description": "Upvote or downvote a Reddit post", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "post-id", + "type": "string", + "required": true, + "positional": true, + "help": "Post ID (e.g. 1abc123) or fullname (t3_xxx)" + }, + { + "name": "direction", + "type": "string", + "default": "up", + "required": false, + "help": "Vote direction: up, down, none" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "reddit/upvote.js", + "sourceFile": "reddit\\upvote.ts" + }, + { + "site": "reddit", + "name": "upvoted", + "description": "Browse your upvoted Reddit posts", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "subreddit", + "score", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/upvoted.js", + "sourceFile": "reddit\\upvoted.ts" + }, + { + "site": "reddit", + "name": "user", + "description": "View a Reddit user profile", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "" + } + ], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "reddit/user.js", + "sourceFile": "reddit\\user.ts" + }, + { + "site": "reddit", + "name": "user-comments", + "description": "View a Reddit user's comment history", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "subreddit", + "score", + "body", + "url" + ], + "type": "ts", + "modulePath": "reddit/user-comments.js", + "sourceFile": "reddit\\user-comments.ts" + }, + { + "site": "reddit", + "name": "user-posts", + "description": "View a Reddit user's submitted posts", + "domain": "reddit.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "title", + "subreddit", + "score", + "comments", + "url" + ], + "type": "ts", + "modulePath": "reddit/user-posts.js", + "sourceFile": "reddit\\user-posts.ts" + }, + { + "site": "reuters", + "name": "search", + "description": "Reuters 路透社新闻搜索", + "domain": "www.reuters.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results (max 40)" + } + ], + "columns": [ + "rank", + "title", + "date", + "section", + "url" + ], + "type": "ts", + "modulePath": "reuters/search.js", + "sourceFile": "reuters\\search.ts" + }, + { + "site": "sinablog", + "name": "search", + "description": "搜索新浪博客文章(通过新浪搜索)", + "domain": "blog.sina.com.cn", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "搜索关键词" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "返回的文章数量" + } + ], + "columns": [ + "rank", + "title", + "author", + "date", + "description", + "url" + ], + "type": "ts", + "modulePath": "sinablog/search.js", + "sourceFile": "sinablog\\search.ts" + }, + { + "site": "sinafinance", + "name": "news", + "description": "新浪财经 7x24 小时实时快讯", + "domain": "app.cj.sina.com.cn", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Max results (max 50)" + }, + { + "name": "type", + "type": "int", + "default": 0, + "required": false, + "help": "News type: 0=全部 1=A股 2=宏观 3=公司 4=数据 5=市场 6=国际 7=观点 8=央行 9=其它" + } + ], + "columns": [ + "id", + "time", + "content", + "views" + ], + "type": "ts", + "modulePath": "sinafinance/news.js", + "sourceFile": "sinafinance\\news.ts" + }, + { + "site": "sinafinance", + "name": "rolling-news", + "description": "新浪财经滚动新闻", + "domain": "finance.sina.com.cn/roll", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "column", + "title", + "date", + "url" + ], + "type": "ts", + "modulePath": "sinafinance/rolling-news.js", + "sourceFile": "sinafinance\\rolling-news.ts" + }, + { + "site": "sinafinance", + "name": "stock", + "description": "新浪财经行情(A股/港股/美股)", + "domain": "suggest3.sinajs.cn,hq.sinajs.cn", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "key", + "type": "string", + "required": true, + "positional": true, + "help": "Stock name or code (e.g. 贵州茅台, 腾讯控股, AAPL)" + }, + { + "name": "market", + "type": "string", + "default": "auto", + "required": false, + "help": "Market: cn, hk, us, auto (default: auto searches cn → hk → us)" + } + ], + "columns": [ + "Symbol", + "Name", + "Price", + "Change", + "ChangePercent", + "Open", + "High", + "Low", + "Volume", + "MarketCap" + ], + "type": "ts", + "modulePath": "sinafinance/stock.js", + "sourceFile": "sinafinance\\stock.ts" + }, + { + "site": "sinafinance", + "name": "stock-rank", + "description": "新浪财经热搜榜", + "domain": "finance.sina.cn", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "market", + "type": "string", + "default": "cn", + "required": false, + "help": "Market: cn (A股), hk (港股), us (美股), wh (外汇), ft (期货)", + "choices": [ + "cn", + "hk", + "us", + "wh", + "ft" + ] + } + ], + "columns": [ + "rank", + "name", + "symbol", + "market", + "price", + "change", + "url" + ], + "type": "ts", + "modulePath": "sinafinance/stock-rank.js", + "sourceFile": "sinafinance\\stock-rank.ts", + "navigateBefore": false + }, + { + "site": "smzdm", + "name": "search", + "description": "什么值得买搜索好价", + "domain": "www.smzdm.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "title", + "price", + "mall", + "comments", + "url" + ], + "type": "ts", + "modulePath": "smzdm/search.js", + "sourceFile": "smzdm\\search.ts" + }, + { + "site": "stackoverflow", + "name": "bounties", + "description": "Active bounties on Stack Overflow", + "domain": "stackoverflow.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Max number of results" + } + ], + "columns": [ + "bounty", + "title", + "score", + "answers", + "url" + ], + "type": "ts", + "modulePath": "stackoverflow/bounties.js", + "sourceFile": "stackoverflow\\bounties.ts" + }, + { + "site": "stackoverflow", + "name": "hot", + "description": "Hot Stack Overflow questions", + "domain": "stackoverflow.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Max number of results" + } + ], + "columns": [ + "title", + "score", + "answers", + "url" + ], + "type": "ts", + "modulePath": "stackoverflow/hot.js", + "sourceFile": "stackoverflow\\hot.ts" + }, + { + "site": "stackoverflow", + "name": "search", + "description": "Search Stack Overflow questions", + "domain": "stackoverflow.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "query", + "type": "string", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Max number of results" + } + ], + "columns": [ + "title", + "score", + "answers", + "url" + ], + "type": "ts", + "modulePath": "stackoverflow/search.js", + "sourceFile": "stackoverflow\\search.ts" + }, + { + "site": "stackoverflow", + "name": "unanswered", + "description": "Top voted unanswered questions on Stack Overflow", + "domain": "stackoverflow.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Max number of results" + } + ], + "columns": [ + "title", + "score", + "answers", + "url" + ], + "type": "ts", + "modulePath": "stackoverflow/unanswered.js", + "sourceFile": "stackoverflow\\unanswered.ts" + }, + { + "site": "steam", + "name": "top-sellers", + "description": "Steam top selling games", + "domain": "store.steampowered.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of games" + } + ], + "columns": [ + "rank", + "name", + "price", + "discount", + "url" + ], + "type": "ts", + "modulePath": "steam/top-sellers.js", + "sourceFile": "steam\\top-sellers.ts" + }, + { + "site": "substack", + "name": "search", + "description": "搜索 Substack 文章和 Newsletter", + "domain": "substack.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "搜索关键词" + }, + { + "name": "type", + "type": "str", + "default": "posts", + "required": false, + "help": "搜索类型(posts=文章, publications=Newsletter)", + "choices": [ + "posts", + "publications" + ] + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "返回结果数量" + } + ], + "columns": [ + "rank", + "title", + "author", + "date", + "description", + "url" + ], + "type": "ts", + "modulePath": "substack/search.js", + "sourceFile": "substack\\search.ts" + }, + { + "site": "tiktok", + "name": "comment", + "description": "Comment on a TikTok video", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok video URL" + }, + { + "name": "text", + "type": "str", + "required": true, + "positional": true, + "help": "Comment text" + } + ], + "columns": [ + "status", + "url", + "text" + ], + "type": "ts", + "modulePath": "tiktok/comment.js", + "sourceFile": "tiktok\\comment.ts" + }, + { + "site": "tiktok", + "name": "explore", + "description": "Get trending TikTok videos from explore page", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of videos" + } + ], + "columns": [ + "rank", + "author", + "views", + "url" + ], + "type": "ts", + "modulePath": "tiktok/explore.js", + "sourceFile": "tiktok\\explore.ts" + }, + { + "site": "tiktok", + "name": "follow", + "description": "Follow a TikTok user", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok username (without @)" + } + ], + "columns": [ + "status", + "username" + ], + "type": "ts", + "modulePath": "tiktok/follow.js", + "sourceFile": "tiktok\\follow.ts" + }, + { + "site": "tiktok", + "name": "following", + "description": "List accounts you follow on TikTok", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of accounts" + } + ], + "columns": [ + "index", + "username", + "name" + ], + "type": "ts", + "modulePath": "tiktok/following.js", + "sourceFile": "tiktok\\following.ts" + }, + { + "site": "tiktok", + "name": "friends", + "description": "Get TikTok friend suggestions", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of suggestions" + } + ], + "columns": [ + "index", + "username", + "name" + ], + "type": "ts", + "modulePath": "tiktok/friends.js", + "sourceFile": "tiktok\\friends.ts" + }, + { + "site": "tiktok", + "name": "like", + "description": "Like a TikTok video", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok video URL" + } + ], + "columns": [ + "status", + "likes", + "url" + ], + "type": "ts", + "modulePath": "tiktok/like.js", + "sourceFile": "tiktok\\like.ts" + }, + { + "site": "tiktok", + "name": "live", + "description": "Browse live streams on TikTok", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of streams" + } + ], + "columns": [ + "index", + "streamer", + "viewers", + "url" + ], + "type": "ts", + "modulePath": "tiktok/live.js", + "sourceFile": "tiktok\\live.ts" + }, + { + "site": "tiktok", + "name": "notifications", + "description": "Get TikTok notifications (likes, comments, mentions, followers)", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "Number of notifications" + }, + { + "name": "type", + "type": "str", + "default": "all", + "required": false, + "help": "Notification type", + "choices": [ + "all", + "likes", + "comments", + "mentions", + "followers" + ] + } + ], + "columns": [ + "index", + "text" + ], + "type": "ts", + "modulePath": "tiktok/notifications.js", + "sourceFile": "tiktok\\notifications.ts" + }, + { + "site": "tiktok", + "name": "profile", + "description": "Get TikTok user profile info", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok username (without @)" + } + ], + "columns": [ + "username", + "name", + "followers", + "following", + "likes", + "videos", + "verified", + "bio" + ], + "type": "ts", + "modulePath": "tiktok/profile.js", + "sourceFile": "tiktok\\profile.ts" + }, + { + "site": "tiktok", + "name": "save", + "description": "Add a TikTok video to Favorites", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok video URL" + } + ], + "columns": [ + "status", + "url" + ], + "type": "ts", + "modulePath": "tiktok/save.js", + "sourceFile": "tiktok\\save.ts" + }, + { + "site": "tiktok", + "name": "search", + "description": "Search TikTok videos", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "desc", + "author", + "url", + "plays", + "likes", + "comments", + "shares" + ], + "type": "ts", + "modulePath": "tiktok/search.js", + "sourceFile": "tiktok\\search.ts" + }, + { + "site": "tiktok", + "name": "unfollow", + "description": "Unfollow a TikTok user", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok username (without @)" + } + ], + "columns": [ + "status", + "username" + ], + "type": "ts", + "modulePath": "tiktok/unfollow.js", + "sourceFile": "tiktok\\unfollow.ts" + }, + { + "site": "tiktok", + "name": "unlike", + "description": "Unlike a TikTok video", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok video URL" + } + ], + "columns": [ + "status", + "likes", + "url" + ], + "type": "ts", + "modulePath": "tiktok/unlike.js", + "sourceFile": "tiktok\\unlike.ts" + }, + { + "site": "tiktok", + "name": "unsave", + "description": "Remove a TikTok video from Favorites", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok video URL" + } + ], + "columns": [ + "status", + "url" + ], + "type": "ts", + "modulePath": "tiktok/unsave.js", + "sourceFile": "tiktok\\unsave.ts" + }, + { + "site": "tiktok", + "name": "user", + "description": "Get recent videos from a TikTok user", + "domain": "www.tiktok.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "TikTok username (without @)" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of videos" + } + ], + "columns": [ + "index", + "views", + "url" + ], + "type": "ts", + "modulePath": "tiktok/user.js", + "sourceFile": "tiktok\\user.ts" + }, + { + "site": "twitter", + "name": "accept", + "description": "Auto-accept DM requests containing specific keywords", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "query", + "type": "string", + "required": true, + "positional": true, + "help": "Keywords to match (comma-separated for OR, e.g. \"群,微信\")" + }, + { + "name": "max", + "type": "int", + "default": 20, + "required": false, + "help": "Maximum number of requests to accept (default: 20)" + } + ], + "columns": [ + "index", + "status", + "user", + "message" + ], + "timeout": 600, + "type": "ts", + "modulePath": "twitter/accept.js", + "sourceFile": "twitter\\accept.ts" + }, + { + "site": "twitter", + "name": "block", + "description": "Block a Twitter user", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "Twitter screen name (without @)" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/block.js", + "sourceFile": "twitter\\block.ts" + }, + { + "site": "twitter", + "name": "bookmark", + "description": "Bookmark a tweet", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "Tweet URL to bookmark" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/bookmark.js", + "sourceFile": "twitter\\bookmark.ts" + }, + { + "site": "twitter", + "name": "bookmarks", + "description": "Fetch Twitter/X bookmarks", + "domain": "x.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "" + } + ], + "columns": [ + "author", + "text", + "likes", + "url" + ], + "type": "ts", + "modulePath": "twitter/bookmarks.js", + "sourceFile": "twitter\\bookmarks.ts" + }, + { + "site": "twitter", + "name": "delete", + "description": "Delete a specific tweet by URL", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "The URL of the tweet to delete" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/delete.js", + "sourceFile": "twitter\\delete.ts" + }, + { + "site": "twitter", + "name": "download", + "description": "下载 Twitter/X 媒体(图片和视频)", + "domain": "x.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "username", + "type": "str", + "required": false, + "positional": true, + "help": "Twitter username (downloads from media tab)" + }, + { + "name": "tweet-url", + "type": "str", + "required": false, + "help": "Single tweet URL to download" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of tweets to scan" + }, + { + "name": "output", + "type": "str", + "default": "./twitter-downloads", + "required": false, + "help": "Output directory" + } + ], + "columns": [ + "index", + "type", + "status", + "size" + ], + "type": "ts", + "modulePath": "twitter/download.js", + "sourceFile": "twitter\\download.ts" + }, + { + "site": "twitter", + "name": "follow", + "description": "Follow a Twitter user", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "Twitter screen name (without @)" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/follow.js", + "sourceFile": "twitter\\follow.ts" + }, + { + "site": "twitter", + "name": "followers", + "description": "Get accounts following a Twitter/X user", + "domain": "x.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "user", + "type": "string", + "required": false, + "positional": true, + "help": "" + }, + { + "name": "limit", + "type": "int", + "default": 50, + "required": false, + "help": "" + } + ], + "columns": [ + "screen_name", + "name", + "bio", + "followers" + ], + "type": "ts", + "modulePath": "twitter/followers.js", + "sourceFile": "twitter\\followers.ts" + }, + { + "site": "twitter", + "name": "following", + "description": "Get accounts a Twitter/X user is following", + "domain": "x.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "user", + "type": "string", + "required": false, + "positional": true, + "help": "" + }, + { + "name": "limit", + "type": "int", + "default": 50, + "required": false, + "help": "" + } + ], + "columns": [ + "screen_name", + "name", + "bio", + "followers" + ], + "type": "ts", + "modulePath": "twitter/following.js", + "sourceFile": "twitter\\following.ts" + }, + { + "site": "twitter", + "name": "hide-reply", + "description": "Hide a reply on your tweet (useful for hiding bot/spam replies)", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "The URL of the reply tweet to hide" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/hide-reply.js", + "sourceFile": "twitter\\hide-reply.ts" + }, + { + "site": "twitter", + "name": "like", + "description": "Like a specific tweet", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "The URL of the tweet to like" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/like.js", + "sourceFile": "twitter\\like.ts" + }, + { + "site": "twitter", + "name": "notifications", + "description": "Get Twitter/X notifications", + "domain": "x.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "" + } + ], + "columns": [ + "id", + "action", + "author", + "text", + "url" + ], + "type": "ts", + "modulePath": "twitter/notifications.js", + "sourceFile": "twitter\\notifications.ts" + }, + { + "site": "twitter", + "name": "post", + "description": "Post a new tweet/thread", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "The text content of the tweet" + }, + { + "name": "images", + "type": "string", + "required": false, + "help": "Image paths, comma-separated, max 4 (jpg/png/gif/webp)" + } + ], + "columns": [ + "status", + "message", + "text" + ], + "type": "ts", + "modulePath": "twitter/post.js", + "sourceFile": "twitter\\post.ts" + }, + { + "site": "twitter", + "name": "reply", + "description": "Reply to a specific tweet, optionally with a local or remote image", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "The URL of the tweet to reply to" + }, + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "The text content of your reply" + }, + { + "name": "image", + "type": "str", + "required": false, + "help": "Optional local image path to attach to the reply" + }, + { + "name": "image-url", + "type": "str", + "required": false, + "help": "Optional remote image URL to download and attach to the reply" + } + ], + "columns": [ + "status", + "message", + "text" + ], + "type": "ts", + "modulePath": "twitter/reply.js", + "sourceFile": "twitter\\reply.ts" + }, + { + "site": "twitter", + "name": "reply-dm", + "description": "Send a message to recent DM conversations", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "text", + "type": "string", + "required": true, + "positional": true, + "help": "Message text to send (e.g. \"我的微信 wxkabi\")" + }, + { + "name": "max", + "type": "int", + "default": 20, + "required": false, + "help": "Maximum number of conversations to reply to (default: 20)" + }, + { + "name": "skip-replied", + "type": "boolean", + "default": true, + "required": false, + "help": "Skip conversations where you already sent the same text (default: true)" + } + ], + "columns": [ + "index", + "status", + "user", + "message" + ], + "timeout": 600, + "type": "ts", + "modulePath": "twitter/reply-dm.js", + "sourceFile": "twitter\\reply-dm.ts" + }, + { + "site": "twitter", + "name": "search", + "description": "Search Twitter/X for tweets", + "domain": "x.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "query", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "filter", + "type": "string", + "default": "top", + "required": false, + "help": "", + "choices": [ + "top", + "live" + ] + }, + { + "name": "limit", + "type": "int", + "default": 15, + "required": false, + "help": "" + } + ], + "columns": [ + "id", + "author", + "text", + "created_at", + "likes", + "views", + "url" + ], + "type": "ts", + "modulePath": "twitter/search.js", + "sourceFile": "twitter\\search.ts" + }, + { + "site": "twitter", + "name": "thread", + "description": "Get a tweet thread (original + all replies)", + "domain": "x.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "tweet-id", + "type": "string", + "required": true, + "positional": true, + "help": "" + }, + { + "name": "limit", + "type": "int", + "default": 50, + "required": false, + "help": "" + } + ], + "columns": [ + "id", + "author", + "text", + "likes", + "retweets", + "url" + ], + "type": "ts", + "modulePath": "twitter/thread.js", + "sourceFile": "twitter\\thread.ts" + }, + { + "site": "twitter", + "name": "trending", + "description": "Twitter/X trending topics", + "domain": "x.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of trends to show" + } + ], + "columns": [ + "rank", + "topic", + "tweets", + "category" + ], + "type": "ts", + "modulePath": "twitter/trending.js", + "sourceFile": "twitter\\trending.ts" + }, + { + "site": "twitter", + "name": "unblock", + "description": "Unblock a Twitter user", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "Twitter screen name (without @)" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/unblock.js", + "sourceFile": "twitter\\unblock.ts" + }, + { + "site": "twitter", + "name": "unbookmark", + "description": "Remove a tweet from bookmarks", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "url", + "type": "string", + "required": true, + "positional": true, + "help": "Tweet URL to unbookmark" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/unbookmark.js", + "sourceFile": "twitter\\unbookmark.ts" + }, + { + "site": "twitter", + "name": "unfollow", + "description": "Unfollow a Twitter user", + "domain": "x.com", + "strategy": "ui", + "browser": true, + "args": [ + { + "name": "username", + "type": "string", + "required": true, + "positional": true, + "help": "Twitter screen name (without @)" + } + ], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "twitter/unfollow.js", + "sourceFile": "twitter\\unfollow.ts" + }, + { + "site": "v2ex", + "name": "daily", + "description": "V2EX 每日签到并领取铜币", + "domain": "www.v2ex.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "status", + "message" + ], + "type": "ts", + "modulePath": "v2ex/daily.js", + "sourceFile": "v2ex\\daily.ts" + }, + { + "site": "v2ex", + "name": "hot", + "description": "V2EX 热门话题", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of topics" + } + ], + "columns": [ + "id", + "rank", + "title", + "node", + "replies", + "url" + ], + "type": "ts", + "modulePath": "v2ex/hot.js", + "sourceFile": "v2ex\\hot.ts" + }, + { + "site": "v2ex", + "name": "latest", + "description": "V2EX 最新话题", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of topics" + } + ], + "columns": [ + "id", + "rank", + "title", + "node", + "replies", + "url" + ], + "type": "ts", + "modulePath": "v2ex/latest.js", + "sourceFile": "v2ex\\latest.ts" + }, + { + "site": "v2ex", + "name": "me", + "description": "V2EX 获取个人资料 (余额/未读提醒)", + "domain": "www.v2ex.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "username", + "balance", + "unread_notifications", + "daily_reward_ready" + ], + "type": "ts", + "modulePath": "v2ex/me.js", + "sourceFile": "v2ex\\me.ts" + }, + { + "site": "v2ex", + "name": "member", + "description": "V2EX 用户资料", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username" + } + ], + "columns": [ + "username", + "tagline", + "website", + "github", + "twitter", + "location" + ], + "type": "ts", + "modulePath": "v2ex/member.js", + "sourceFile": "v2ex\\member.ts" + }, + { + "site": "v2ex", + "name": "node", + "description": "V2EX 节点话题列表", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "name", + "type": "str", + "required": true, + "positional": true, + "help": "Node name (e.g. python, javascript, apple)" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of topics (API returns max 20)" + } + ], + "columns": [ + "rank", + "title", + "author", + "replies", + "url" + ], + "type": "ts", + "modulePath": "v2ex/node.js", + "sourceFile": "v2ex\\node.ts" + }, + { + "site": "v2ex", + "name": "nodes", + "description": "V2EX 所有节点列表", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "limit", + "type": "int", + "default": 30, + "required": false, + "help": "Number of nodes" + } + ], + "columns": [ + "rank", + "name", + "title", + "topics", + "stars" + ], + "type": "ts", + "modulePath": "v2ex/nodes.js", + "sourceFile": "v2ex\\nodes.ts" + }, + { + "site": "v2ex", + "name": "notifications", + "description": "V2EX 获取提醒 (回复/由于)", + "domain": "www.v2ex.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of notifications" + } + ], + "columns": [ + "type", + "content", + "time" + ], + "type": "ts", + "modulePath": "v2ex/notifications.js", + "sourceFile": "v2ex\\notifications.ts" + }, + { + "site": "v2ex", + "name": "replies", + "description": "V2EX 主题回复列表", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Topic ID" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of replies" + } + ], + "columns": [ + "floor", + "author", + "content" + ], + "type": "ts", + "modulePath": "v2ex/replies.js", + "sourceFile": "v2ex\\replies.ts" + }, + { + "site": "v2ex", + "name": "topic", + "description": "V2EX 主题详情和回复", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Topic ID" + } + ], + "columns": [ + "id", + "title", + "content", + "member", + "created", + "node", + "replies", + "url" + ], + "type": "ts", + "modulePath": "v2ex/topic.js", + "sourceFile": "v2ex\\topic.ts" + }, + { + "site": "v2ex", + "name": "user", + "description": "V2EX 用户发帖列表", + "domain": "www.v2ex.com", + "strategy": "public", + "browser": false, + "args": [ + { + "name": "username", + "type": "str", + "required": true, + "positional": true, + "help": "Username" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of topics (API returns max 20)" + } + ], + "columns": [ + "rank", + "title", + "node", + "replies", + "url" + ], + "type": "ts", + "modulePath": "v2ex/user.js", + "sourceFile": "v2ex\\user.ts" + }, + { + "site": "web", + "name": "read", + "description": "Fetch any web page and export as Markdown", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "help": "Any web page URL" + }, + { + "name": "output", + "type": "str", + "default": "./web-articles", + "required": false, + "help": "Output directory" + }, + { + "name": "download-images", + "type": "boolean", + "default": true, + "required": false, + "help": "Download images locally" + }, + { + "name": "wait", + "type": "int", + "default": 3, + "required": false, + "help": "Seconds to wait after page load" + } + ], + "columns": [ + "title", + "author", + "publish_time", + "status", + "size" + ], + "type": "ts", + "modulePath": "web/read.js", + "sourceFile": "web\\read.ts", + "navigateBefore": false + }, + { + "site": "weibo", + "name": "comments", + "description": "Get comments on a Weibo post", + "domain": "weibo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Post ID (numeric idstr)" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of comments (max 50)" + } + ], + "columns": [ + "rank", + "author", + "text", + "likes", + "replies", + "time" + ], + "type": "ts", + "modulePath": "weibo/comments.js", + "sourceFile": "weibo\\comments.ts" + }, + { + "site": "weibo", + "name": "hot", + "description": "微博热搜", + "domain": "weibo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 30, + "required": false, + "help": "Number of items (max 50)" + } + ], + "columns": [ + "rank", + "word", + "hot_value", + "category", + "label", + "url" + ], + "type": "ts", + "modulePath": "weibo/hot.js", + "sourceFile": "weibo\\hot.ts" + }, + { + "site": "weibo", + "name": "post", + "description": "Get a single Weibo post", + "domain": "weibo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Post ID (numeric idstr or mblogid from URL)" + } + ], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "weibo/post.js", + "sourceFile": "weibo\\post.ts" + }, + { + "site": "weibo", + "name": "search", + "description": "搜索微博", + "domain": "weibo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "keyword", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results (max 50)" + } + ], + "columns": [ + "rank", + "title", + "author", + "time", + "url" + ], + "type": "ts", + "modulePath": "weibo/search.js", + "sourceFile": "weibo\\search.ts" + }, + { + "site": "weibo", + "name": "user", + "description": "Get Weibo user profile", + "domain": "weibo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "User ID (numeric uid) or screen name" + } + ], + "columns": [ + "screen_name", + "uid", + "followers", + "following", + "statuses", + "verified", + "description", + "location", + "url" + ], + "type": "ts", + "modulePath": "weibo/user.js", + "sourceFile": "weibo\\user.ts" + }, + { + "site": "weixin", + "name": "download", + "description": "下载微信公众号文章为 Markdown 格式", + "domain": "mp.weixin.qq.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "help": "WeChat article URL (mp.weixin.qq.com/s/xxx)" + }, + { + "name": "output", + "type": "str", + "default": "./weixin-articles", + "required": false, + "help": "Output directory" + }, + { + "name": "download-images", + "type": "boolean", + "default": true, + "required": false, + "help": "Download images locally" + } + ], + "columns": [ + "title", + "author", + "publish_time", + "status", + "size" + ], + "type": "ts", + "modulePath": "weixin/download.js", + "sourceFile": "weixin\\download.ts" + }, + { + "site": "xianyu", + "name": "search", + "description": "搜索闲鱼商品", + "domain": "www.goofish.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results to return" + } + ], + "columns": [ + "item_id", + "rank", + "title", + "price", + "condition", + "brand", + "location", + "badge", + "url" + ], + "type": "ts", + "modulePath": "xianyu/search.js", + "sourceFile": "xianyu\\search.ts", + "navigateBefore": false + }, + { + "site": "xiaoe", + "name": "catalog", + "description": "小鹅通课程目录(支持普通课程、专栏、大专栏)", + "domain": "h5.xet.citv.cn", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "课程页面 URL" + } + ], + "columns": [ + "ch", + "chapter", + "no", + "title", + "type", + "resource_id", + "status" + ], + "type": "ts", + "modulePath": "xiaoe/catalog.js", + "sourceFile": "xiaoe\\catalog.ts" + }, + { + "site": "xiaoe", + "name": "content", + "description": "提取小鹅通图文页面内容为文本", + "domain": "h5.xet.citv.cn", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "页面 URL" + } + ], + "columns": [ + "title", + "content_length", + "image_count" + ], + "type": "ts", + "modulePath": "xiaoe/content.js", + "sourceFile": "xiaoe\\content.ts" + }, + { + "site": "xiaoe", + "name": "courses", + "description": "列出已购小鹅通课程(含 URL 和店铺名)", + "domain": "study.xiaoe-tech.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "title", + "shop", + "url" + ], + "type": "ts", + "modulePath": "xiaoe/courses.js", + "sourceFile": "xiaoe\\courses.ts" + }, + { + "site": "xiaoe", + "name": "detail", + "description": "小鹅通课程详情(名称、价格、学员数、店铺)", + "domain": "h5.xet.citv.cn", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "课程页面 URL" + } + ], + "columns": [ + "name", + "price", + "original_price", + "user_count", + "shop_name" + ], + "type": "ts", + "modulePath": "xiaoe/detail.js", + "sourceFile": "xiaoe\\detail.ts" + }, + { + "site": "xiaoe", + "name": "play-url", + "description": "小鹅通视频/音频/直播回放 M3U8 播放地址", + "domain": "h5.xet.citv.cn", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "positional": true, + "help": "小节页面 URL" + } + ], + "columns": [ + "title", + "resource_id", + "m3u8_url", + "duration_sec", + "method" + ], + "type": "ts", + "modulePath": "xiaoe/play-url.js", + "sourceFile": "xiaoe\\play-url.ts" + }, + { + "site": "xiaohongshu", + "name": "creator-note-detail", + "description": "小红书单篇笔记详情页数据 (笔记信息 + 核心/互动数据 + 观看来源 + 观众画像 + 趋势数据)", + "domain": "creator.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "note-id", + "type": "string", + "required": true, + "positional": true, + "help": "Note ID (from creator-notes or note-detail page URL)" + } + ], + "columns": [ + "section", + "metric", + "value", + "extra" + ], + "type": "ts", + "modulePath": "xiaohongshu/creator-note-detail.js", + "sourceFile": "xiaohongshu\\creator-note-detail.ts" + }, + { + "site": "xiaohongshu", + "name": "creator-notes", + "description": "小红书创作者笔记列表 + 每篇数据 (标题/日期/观看/点赞/收藏/评论)", + "domain": "creator.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of notes to return" + } + ], + "columns": [ + "rank", + "id", + "title", + "date", + "views", + "likes", + "collects", + "comments", + "url" + ], + "type": "ts", + "modulePath": "xiaohongshu/creator-notes.js", + "sourceFile": "xiaohongshu\\creator-notes.ts" + }, + { + "site": "xiaohongshu", + "name": "creator-profile", + "description": "小红书创作者账号信息 (粉丝/关注/获赞/成长等级)", + "domain": "creator.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "xiaohongshu/creator-profile.js", + "sourceFile": "xiaohongshu\\creator-profile.ts" + }, + { + "site": "xiaohongshu", + "name": "creator-stats", + "description": "小红书创作者数据总览 (观看/点赞/收藏/评论/分享/涨粉,含每日趋势)", + "domain": "creator.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "period", + "type": "string", + "default": "seven", + "required": false, + "help": "Stats period: seven or thirty", + "choices": [ + "seven", + "thirty" + ] + } + ], + "columns": [ + "metric", + "total", + "trend" + ], + "type": "ts", + "modulePath": "xiaohongshu/creator-stats.js", + "sourceFile": "xiaohongshu\\creator-stats.ts" + }, + { + "site": "xiaohongshu", + "name": "feed", + "description": "小红书首页推荐 Feed (via Pinia Store Action)", + "domain": "www.xiaohongshu.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of items to return" + } + ], + "columns": [ + "title", + "author", + "likes", + "type", + "url" + ], + "type": "ts", + "modulePath": "xiaohongshu/feed.js", + "sourceFile": "xiaohongshu\\feed.ts" + }, + { + "site": "xiaohongshu", + "name": "notifications", + "description": "小红书通知 (mentions/likes/connections)", + "domain": "www.xiaohongshu.com", + "strategy": "intercept", + "browser": true, + "args": [ + { + "name": "type", + "type": "str", + "default": "mentions", + "required": false, + "help": "Notification type: mentions, likes, or connections" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of notifications to return" + } + ], + "columns": [ + "rank", + "user", + "action", + "content", + "note", + "time" + ], + "type": "ts", + "modulePath": "xiaohongshu/notifications.js", + "sourceFile": "xiaohongshu\\notifications.ts" + }, + { + "site": "xiaohongshu", + "name": "publish", + "description": "小红书发布图文笔记 (creator center UI automation)", + "domain": "creator.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "title", + "type": "str", + "required": true, + "help": "笔记标题 (最多20字)" + }, + { + "name": "content", + "type": "str", + "required": true, + "positional": true, + "help": "笔记正文" + }, + { + "name": "images", + "type": "str", + "required": true, + "help": "图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)" + }, + { + "name": "topics", + "type": "str", + "required": false, + "help": "话题标签,逗号分隔,不含 # 号" + }, + { + "name": "draft", + "type": "bool", + "default": false, + "required": false, + "help": "保存为草稿,不直接发布" + } + ], + "columns": [ + "status", + "detail" + ], + "type": "ts", + "modulePath": "xiaohongshu/publish.js", + "sourceFile": "xiaohongshu\\publish.ts" + }, + { + "site": "xiaohongshu", + "name": "search", + "description": "搜索小红书笔记", + "domain": "www.xiaohongshu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search keyword" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "title", + "author", + "likes", + "published_at", + "url" + ], + "type": "ts", + "modulePath": "xiaohongshu/search.js", + "sourceFile": "xiaohongshu\\search.ts" + }, + { + "site": "xueqiu", + "name": "comments", + "description": "获取单只股票的讨论动态", + "domain": "xueqiu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "symbol", + "type": "str", + "required": true, + "positional": true, + "help": "Stock symbol, e.g. SH600519, AAPL, or 00700" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of discussion posts to return" + } + ], + "columns": [ + "author", + "text", + "likes", + "replies", + "retweets", + "created_at", + "url" + ], + "type": "ts", + "modulePath": "xueqiu/comments.js", + "sourceFile": "xueqiu\\comments.ts", + "navigateBefore": false + }, + { + "site": "yahoo-finance", + "name": "quote", + "description": "Yahoo Finance 股票行情", + "domain": "finance.yahoo.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "symbol", + "type": "str", + "required": true, + "positional": true, + "help": "Stock ticker (e.g. AAPL, MSFT, TSLA)" + } + ], + "columns": [ + "symbol", + "name", + "price", + "change", + "changePercent", + "open", + "high", + "low", + "volume", + "marketCap" + ], + "type": "ts", + "modulePath": "yahoo-finance/quote.js", + "sourceFile": "yahoo-finance\\quote.ts" + }, + { + "site": "youtube", + "name": "channel", + "description": "Get YouTube channel info and recent videos", + "domain": "www.youtube.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Channel ID (UCxxxx) or handle (@name)" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Max recent videos (max 30)" + } + ], + "columns": [ + "field", + "value" + ], + "type": "ts", + "modulePath": "youtube/channel.js", + "sourceFile": "youtube\\channel.ts" + }, + { + "site": "youtube", + "name": "search", + "description": "Search YouTube videos", + "domain": "www.youtube.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Max results (max 50)" + }, + { + "name": "type", + "type": "str", + "default": "", + "required": false, + "help": "Filter type: shorts, video, channel, playlist" + }, + { + "name": "upload", + "type": "str", + "default": "", + "required": false, + "help": "Upload date: hour, today, week, month, year" + }, + { + "name": "sort", + "type": "str", + "default": "", + "required": false, + "help": "Sort by: relevance, date, views, rating" + } + ], + "columns": [ + "rank", + "title", + "channel", + "views", + "duration", + "published", + "url" + ], + "type": "ts", + "modulePath": "youtube/search.js", + "sourceFile": "youtube\\search.ts" + }, + { + "site": "zhihu", + "name": "download", + "description": "导出知乎文章为 Markdown 格式", + "domain": "zhuanlan.zhihu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "url", + "type": "str", + "required": true, + "help": "Article URL (zhuanlan.zhihu.com/p/xxx)" + }, + { + "name": "output", + "type": "str", + "default": "./zhihu-articles", + "required": false, + "help": "Output directory" + }, + { + "name": "download-images", + "type": "boolean", + "default": false, + "required": false, + "help": "Download images locally" + } + ], + "columns": [ + "title", + "author", + "publish_time", + "status", + "size" + ], + "type": "ts", + "modulePath": "zhihu/download.js", + "sourceFile": "zhihu\\download.ts" + }, + { + "site": "zhihu", + "name": "hot", + "description": "知乎热榜", + "domain": "www.zhihu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "limit", + "type": "int", + "default": 20, + "required": false, + "help": "Number of items to return" + } + ], + "columns": [ + "rank", + "title", + "heat", + "answers" + ], + "type": "ts", + "modulePath": "zhihu/hot.js", + "sourceFile": "zhihu\\hot.ts" + }, + { + "site": "zhihu", + "name": "question", + "description": "知乎问题详情和回答", + "domain": "www.zhihu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "id", + "type": "str", + "required": true, + "positional": true, + "help": "Question ID (numeric)" + }, + { + "name": "limit", + "type": "int", + "default": 5, + "required": false, + "help": "Number of answers" + } + ], + "columns": [ + "rank", + "author", + "votes", + "content" + ], + "type": "ts", + "modulePath": "zhihu/question.js", + "sourceFile": "zhihu\\question.ts" + }, + { + "site": "zhihu", + "name": "search", + "description": "知乎搜索", + "domain": "www.zhihu.com", + "strategy": "cookie", + "browser": true, + "args": [ + { + "name": "query", + "type": "str", + "required": true, + "positional": true, + "help": "Search query" + }, + { + "name": "limit", + "type": "int", + "default": 10, + "required": false, + "help": "Number of results" + } + ], + "columns": [ + "rank", + "title", + "type", + "author", + "votes", + "url" + ], + "type": "ts", + "modulePath": "zhihu/search.js", + "sourceFile": "zhihu\\search.ts" + } +] \ No newline at end of file diff --git a/extension/dist/background.js b/extension/dist/background.js index bd1caa221..328a7f799 100644 --- a/extension/dist/background.js +++ b/extension/dist/background.js @@ -1,981 +1,1984 @@ -const DAEMON_PORT = 19825; -const DAEMON_HOST = "localhost"; -const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`; -const DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`; -const WS_RECONNECT_BASE_DELAY = 2e3; -const WS_RECONNECT_MAX_DELAY = 5e3; - -const attached = /* @__PURE__ */ new Set(); -const networkCaptures = /* @__PURE__ */ new Map(); +//#region src/protocol.ts +/** Default daemon port */ +var DAEMON_PORT = 19825; +var DAEMON_HOST = "localhost"; +var DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`; +/** Lightweight health-check endpoint — probed before each WebSocket attempt. */ +var DAEMON_PING_URL = `http://${DAEMON_HOST}:${DAEMON_PORT}/ping`; +/** Base reconnect delay for extension WebSocket (ms) */ +var WS_RECONNECT_BASE_DELAY = 2e3; +/** Max reconnect delay (ms) — kept short since daemon is long-lived */ +var WS_RECONNECT_MAX_DELAY = 5e3; +//#endregion +//#region src/cdp.ts +/** +* CDP execution via chrome.debugger API. +* +* chrome.debugger only needs the "debugger" permission — no host_permissions. +* It can attach to any http/https tab. Avoid chrome:// and chrome-extension:// +* tabs (resolveTabId in background.ts filters them). +*/ +var attached = /* @__PURE__ */ new Set(); +var networkCaptures = /* @__PURE__ */ new Map(); +/** Check if a URL can be attached via CDP — only allow http(s) and blank pages. */ function isDebuggableUrl$1(url) { - if (!url) return true; - return url.startsWith("http://") || url.startsWith("https://") || url === "about:blank" || url.startsWith("data:"); + if (!url) return true; + return url.startsWith("http://") || url.startsWith("https://") || url === "about:blank" || url.startsWith("data:"); } async function ensureAttached(tabId, aggressiveRetry = false) { - try { - const tab = await chrome.tabs.get(tabId); - if (!isDebuggableUrl$1(tab.url)) { - attached.delete(tabId); - throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`); - } - } catch (e) { - if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e; - attached.delete(tabId); - throw new Error(`Tab ${tabId} no longer exists`); - } - if (attached.has(tabId)) { - try { - await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", { - expression: "1", - returnByValue: true - }); - return; - } catch { - attached.delete(tabId); - } - } - const MAX_ATTACH_RETRIES = aggressiveRetry ? 5 : 2; - const RETRY_DELAY_MS = aggressiveRetry ? 1500 : 500; - let lastError = ""; - for (let attempt = 1; attempt <= MAX_ATTACH_RETRIES; attempt++) { - try { - try { - await chrome.debugger.detach({ tabId }); - } catch { - } - await chrome.debugger.attach({ tabId }, "1.3"); - lastError = ""; - break; - } catch (e) { - lastError = e instanceof Error ? e.message : String(e); - if (attempt < MAX_ATTACH_RETRIES) { - console.warn(`[opencli] attach attempt ${attempt}/${MAX_ATTACH_RETRIES} failed: ${lastError}, retrying in ${RETRY_DELAY_MS}ms...`); - await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); - try { - const tab = await chrome.tabs.get(tabId); - if (!isDebuggableUrl$1(tab.url)) { - lastError = `Tab URL changed to ${tab.url} during retry`; - break; - } - } catch { - lastError = `Tab ${tabId} no longer exists`; - break; - } - } - } - } - if (lastError) { - let finalUrl = "unknown"; - let finalWindowId = "unknown"; - try { - const tab = await chrome.tabs.get(tabId); - finalUrl = tab.url ?? "undefined"; - finalWindowId = String(tab.windowId); - } catch { - } - console.warn(`[opencli] attach failed for tab ${tabId}: url=${finalUrl}, windowId=${finalWindowId}, error=${lastError}`); - const hint = lastError.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : ""; - throw new Error(`attach failed: ${lastError}${hint}`); - } - attached.add(tabId); - try { - await chrome.debugger.sendCommand({ tabId }, "Runtime.enable"); - } catch { - } + try { + const tab = await chrome.tabs.get(tabId); + if (!isDebuggableUrl$1(tab.url)) { + attached.delete(tabId); + throw new Error(`Cannot debug tab ${tabId}: URL is ${tab.url ?? "unknown"}`); + } + } catch (e) { + if (e instanceof Error && e.message.startsWith("Cannot debug tab")) throw e; + attached.delete(tabId); + throw new Error(`Tab ${tabId} no longer exists`); + } + if (attached.has(tabId)) try { + await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", { + expression: "1", + returnByValue: true + }); + return; + } catch { + attached.delete(tabId); + } + const MAX_ATTACH_RETRIES = aggressiveRetry ? 5 : 2; + const RETRY_DELAY_MS = aggressiveRetry ? 1500 : 500; + let lastError = ""; + for (let attempt = 1; attempt <= MAX_ATTACH_RETRIES; attempt++) try { + try { + await chrome.debugger.detach({ tabId }); + } catch {} + await chrome.debugger.attach({ tabId }, "1.3"); + lastError = ""; + break; + } catch (e) { + lastError = e instanceof Error ? e.message : String(e); + if (attempt < MAX_ATTACH_RETRIES) { + console.warn(`[opencli] attach attempt ${attempt}/${MAX_ATTACH_RETRIES} failed: ${lastError}, retrying in ${RETRY_DELAY_MS}ms...`); + await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); + try { + const tab = await chrome.tabs.get(tabId); + if (!isDebuggableUrl$1(tab.url)) { + lastError = `Tab URL changed to ${tab.url} during retry`; + break; + } + } catch { + lastError = `Tab ${tabId} no longer exists`; + } + } + } + if (lastError) { + let finalUrl = "unknown"; + let finalWindowId = "unknown"; + try { + const tab = await chrome.tabs.get(tabId); + finalUrl = tab.url ?? "undefined"; + finalWindowId = String(tab.windowId); + } catch {} + console.warn(`[opencli] attach failed for tab ${tabId}: url=${finalUrl}, windowId=${finalWindowId}, error=${lastError}`); + const hint = lastError.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : ""; + throw new Error(`attach failed: ${lastError}${hint}`); + } + attached.add(tabId); + try { + await chrome.debugger.sendCommand({ tabId }, "Runtime.enable"); + } catch {} } async function evaluate(tabId, expression, aggressiveRetry = false) { - const MAX_EVAL_RETRIES = aggressiveRetry ? 3 : 2; - for (let attempt = 1; attempt <= MAX_EVAL_RETRIES; attempt++) { - try { - await ensureAttached(tabId, aggressiveRetry); - const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", { - expression, - returnByValue: true, - awaitPromise: true - }); - if (result.exceptionDetails) { - const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error"; - throw new Error(errMsg); - } - return result.result?.value; - } catch (e) { - const msg = e instanceof Error ? e.message : String(e); - const isNavigateError = msg.includes("Inspected target navigated") || msg.includes("Target closed"); - const isAttachError = isNavigateError || msg.includes("attach failed") || msg.includes("Debugger is not attached") || msg.includes("chrome-extension://"); - if (isAttachError && attempt < MAX_EVAL_RETRIES) { - attached.delete(tabId); - const retryMs = isNavigateError ? 200 : 500; - await new Promise((resolve) => setTimeout(resolve, retryMs)); - continue; - } - throw e; - } - } - throw new Error("evaluate: max retries exhausted"); + const MAX_EVAL_RETRIES = aggressiveRetry ? 3 : 2; + for (let attempt = 1; attempt <= MAX_EVAL_RETRIES; attempt++) try { + await ensureAttached(tabId, aggressiveRetry); + const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", { + expression, + returnByValue: true, + awaitPromise: true + }); + if (result.exceptionDetails) { + const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error"; + throw new Error(errMsg); + } + return result.result?.value; + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + const isNavigateError = msg.includes("Inspected target navigated") || msg.includes("Target closed"); + if ((isNavigateError || msg.includes("attach failed") || msg.includes("Debugger is not attached") || msg.includes("chrome-extension://")) && attempt < MAX_EVAL_RETRIES) { + attached.delete(tabId); + const retryMs = isNavigateError ? 200 : 500; + await new Promise((resolve) => setTimeout(resolve, retryMs)); + continue; + } + throw e; + } + throw new Error("evaluate: max retries exhausted"); } -const evaluateAsync = evaluate; +var evaluateAsync = evaluate; +/** +* Capture a screenshot via CDP Page.captureScreenshot. +* Returns base64-encoded image data. +*/ async function screenshot(tabId, options = {}) { - await ensureAttached(tabId); - const format = options.format ?? "png"; - if (options.fullPage) { - const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics"); - const size = metrics.cssContentSize || metrics.contentSize; - if (size) { - await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", { - mobile: false, - width: Math.ceil(size.width), - height: Math.ceil(size.height), - deviceScaleFactor: 1 - }); - } - } - try { - const params = { format }; - if (format === "jpeg" && options.quality !== void 0) { - params.quality = Math.max(0, Math.min(100, options.quality)); - } - const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params); - return result.data; - } finally { - if (options.fullPage) { - await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => { - }); - } - } + await ensureAttached(tabId); + const format = options.format ?? "png"; + if (options.fullPage) { + const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics"); + const size = metrics.cssContentSize || metrics.contentSize; + if (size) await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", { + mobile: false, + width: Math.ceil(size.width), + height: Math.ceil(size.height), + deviceScaleFactor: 1 + }); + } + try { + const params = { format }; + if (format === "jpeg" && options.quality !== void 0) params.quality = Math.max(0, Math.min(100, options.quality)); + return (await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params)).data; + } finally { + if (options.fullPage) await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {}); + } } +/** +* Set local file paths on a file input element via CDP DOM.setFileInputFiles. +* This bypasses the need to send large base64 payloads through the message channel — +* Chrome reads the files directly from the local filesystem. +* +* @param tabId - Target tab ID +* @param files - Array of absolute local file paths +* @param selector - CSS selector to find the file input (optional, defaults to first file input) +*/ async function setFileInputFiles(tabId, files, selector) { - await ensureAttached(tabId); - await chrome.debugger.sendCommand({ tabId }, "DOM.enable"); - const doc = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument"); - const query = selector || 'input[type="file"]'; - const result = await chrome.debugger.sendCommand({ tabId }, "DOM.querySelector", { - nodeId: doc.root.nodeId, - selector: query - }); - if (!result.nodeId) { - throw new Error(`No element found matching selector: ${query}`); - } - await chrome.debugger.sendCommand({ tabId }, "DOM.setFileInputFiles", { - files, - nodeId: result.nodeId - }); + await ensureAttached(tabId); + await chrome.debugger.sendCommand({ tabId }, "DOM.enable"); + const doc = await chrome.debugger.sendCommand({ tabId }, "DOM.getDocument"); + const query = selector || "input[type=\"file\"]"; + const result = await chrome.debugger.sendCommand({ tabId }, "DOM.querySelector", { + nodeId: doc.root.nodeId, + selector: query + }); + if (!result.nodeId) throw new Error(`No element found matching selector: ${query}`); + await chrome.debugger.sendCommand({ tabId }, "DOM.setFileInputFiles", { + files, + nodeId: result.nodeId + }); } async function insertText(tabId, text) { - await ensureAttached(tabId); - await chrome.debugger.sendCommand({ tabId }, "Input.insertText", { text }); + await ensureAttached(tabId); + await chrome.debugger.sendCommand({ tabId }, "Input.insertText", { text }); } function normalizeCapturePatterns(pattern) { - return String(pattern || "").split("|").map((part) => part.trim()).filter(Boolean); + return String(pattern || "").split("|").map((part) => part.trim()).filter(Boolean); } function shouldCaptureUrl(url, patterns) { - if (!url) return false; - if (!patterns.length) return true; - return patterns.some((pattern) => url.includes(pattern)); + if (!url) return false; + if (!patterns.length) return true; + return patterns.some((pattern) => url.includes(pattern)); } function normalizeHeaders(headers) { - if (!headers || typeof headers !== "object") return {}; - const out = {}; - for (const [key, value] of Object.entries(headers)) { - out[String(key)] = String(value); - } - return out; + if (!headers || typeof headers !== "object") return {}; + const out = {}; + for (const [key, value] of Object.entries(headers)) out[String(key)] = String(value); + return out; } function getOrCreateNetworkCaptureEntry(tabId, requestId, fallback) { - const state = networkCaptures.get(tabId); - if (!state) return null; - const existingIndex = state.requestToIndex.get(requestId); - if (existingIndex !== void 0) { - return state.entries[existingIndex] || null; - } - const url = fallback?.url || ""; - if (!shouldCaptureUrl(url, state.patterns)) return null; - const entry = { - kind: "cdp", - url, - method: fallback?.method || "GET", - requestHeaders: fallback?.requestHeaders || {}, - timestamp: Date.now() - }; - state.entries.push(entry); - state.requestToIndex.set(requestId, state.entries.length - 1); - return entry; + const state = networkCaptures.get(tabId); + if (!state) return null; + const existingIndex = state.requestToIndex.get(requestId); + if (existingIndex !== void 0) return state.entries[existingIndex] || null; + const url = fallback?.url || ""; + if (!shouldCaptureUrl(url, state.patterns)) return null; + const entry = { + kind: "cdp", + url, + method: fallback?.method || "GET", + requestHeaders: fallback?.requestHeaders || {}, + timestamp: Date.now() + }; + state.entries.push(entry); + state.requestToIndex.set(requestId, state.entries.length - 1); + return entry; } async function startNetworkCapture(tabId, pattern) { - await ensureAttached(tabId); - await chrome.debugger.sendCommand({ tabId }, "Network.enable"); - networkCaptures.set(tabId, { - patterns: normalizeCapturePatterns(pattern), - entries: [], - requestToIndex: /* @__PURE__ */ new Map() - }); + await ensureAttached(tabId); + await chrome.debugger.sendCommand({ tabId }, "Network.enable"); + networkCaptures.set(tabId, { + patterns: normalizeCapturePatterns(pattern), + entries: [], + requestToIndex: /* @__PURE__ */ new Map() + }); } async function readNetworkCapture(tabId) { - const state = networkCaptures.get(tabId); - if (!state) return []; - const entries = state.entries.slice(); - state.entries = []; - state.requestToIndex.clear(); - return entries; + const state = networkCaptures.get(tabId); + if (!state) return []; + const entries = state.entries.slice(); + state.entries = []; + state.requestToIndex.clear(); + return entries; } async function detach(tabId) { - if (!attached.has(tabId)) return; - attached.delete(tabId); - networkCaptures.delete(tabId); - try { - await chrome.debugger.detach({ tabId }); - } catch { - } + if (!attached.has(tabId)) return; + attached.delete(tabId); + networkCaptures.delete(tabId); + try { + await chrome.debugger.detach({ tabId }); + } catch {} } function registerListeners() { - chrome.tabs.onRemoved.addListener((tabId) => { - attached.delete(tabId); - networkCaptures.delete(tabId); - }); - chrome.debugger.onDetach.addListener((source) => { - if (source.tabId) { - attached.delete(source.tabId); - networkCaptures.delete(source.tabId); + chrome.tabs.onRemoved.addListener((tabId) => { + attached.delete(tabId); + networkCaptures.delete(tabId); + }); + chrome.debugger.onDetach.addListener((source) => { + if (source.tabId) { + attached.delete(source.tabId); + networkCaptures.delete(source.tabId); + } + }); + chrome.tabs.onUpdated.addListener(async (tabId, info) => { + if (info.url && !isDebuggableUrl$1(info.url)) await detach(tabId); + }); + chrome.debugger.onEvent.addListener(async (source, method, params) => { + const tabId = source.tabId; + if (!tabId) return; + const state = networkCaptures.get(tabId); + if (!state) return; + if (method === "Network.requestWillBeSent") { + const requestId = String(params?.requestId || ""); + const request = params?.request; + const entry = getOrCreateNetworkCaptureEntry(tabId, requestId, { + url: request?.url, + method: request?.method, + requestHeaders: normalizeHeaders(request?.headers) + }); + if (!entry) return; + entry.requestBodyKind = request?.hasPostData ? "string" : "empty"; + entry.requestBodyPreview = String(request?.postData || "").slice(0, 4e3); + try { + const postData = await chrome.debugger.sendCommand({ tabId }, "Network.getRequestPostData", { requestId }); + if (postData?.postData) { + entry.requestBodyKind = "string"; + entry.requestBodyPreview = postData.postData.slice(0, 4e3); + } + } catch {} + return; + } + if (method === "Network.responseReceived") { + const requestId = String(params?.requestId || ""); + const response = params?.response; + const entry = getOrCreateNetworkCaptureEntry(tabId, requestId, { url: response?.url }); + if (!entry) return; + entry.responseStatus = response?.status; + entry.responseContentType = response?.mimeType || ""; + entry.responseHeaders = normalizeHeaders(response?.headers); + return; + } + if (method === "Network.loadingFinished") { + const requestId = String(params?.requestId || ""); + const stateEntryIndex = state.requestToIndex.get(requestId); + if (stateEntryIndex === void 0) return; + const entry = state.entries[stateEntryIndex]; + if (!entry) return; + try { + const body = await chrome.debugger.sendCommand({ tabId }, "Network.getResponseBody", { requestId }); + if (typeof body?.body === "string") entry.responsePreview = body.base64Encoded ? `base64:${body.body.slice(0, 4e3)}` : body.body.slice(0, 4e3); + } catch {} + } + }); +} +//#endregion +//#region src/identity.ts +/** +* Page identity mapping — targetId ↔ tabId. +* +* targetId is the cross-layer page identity (CDP target UUID). +* tabId is an internal Chrome Tabs API routing detail — never exposed outside the extension. +* +* Lifecycle: +* - Cache populated lazily via chrome.debugger.getTargets() +* - Evicted on tab close (chrome.tabs.onRemoved) +* - Miss triggers full refresh; refresh miss → hard error (no guessing) +*/ +var targetToTab = /* @__PURE__ */ new Map(); +var tabToTarget = /* @__PURE__ */ new Map(); +/** +* Resolve targetId for a given tabId. +* Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets(). +* Throws if no targetId can be found (page may have been destroyed). +*/ +async function resolveTargetId(tabId) { + const cached = tabToTarget.get(tabId); + if (cached) return cached; + await refreshMappings(); + const result = tabToTarget.get(tabId); + if (!result) throw new Error(`No targetId for tab ${tabId} — page may have been closed`); + return result; +} +/** +* Resolve tabId for a given targetId. +* Returns cached value if available; on miss, refreshes from chrome.debugger.getTargets(). +* Throws if no tabId can be found — never falls back to guessing. +*/ +async function resolveTabId$1(targetId) { + const cached = targetToTab.get(targetId); + if (cached !== void 0) return cached; + await refreshMappings(); + const result = targetToTab.get(targetId); + if (result === void 0) throw new Error(`Page not found: ${targetId} — stale page identity`); + return result; +} +/** +* Remove mappings for a closed tab. +* Called from chrome.tabs.onRemoved listener. +*/ +function evictTab(tabId) { + const targetId = tabToTarget.get(tabId); + if (targetId) targetToTab.delete(targetId); + tabToTarget.delete(tabId); +} +/** +* Full refresh of targetId ↔ tabId mappings from chrome.debugger.getTargets(). +*/ +async function refreshMappings() { + const targets = await chrome.debugger.getTargets(); + targetToTab.clear(); + tabToTarget.clear(); + for (const t of targets) if (t.type === "page" && t.tabId !== void 0) { + targetToTab.set(t.id, t.tabId); + tabToTarget.set(t.tabId, t.id); + } +} +//#endregion +//#region ../src/browser/dom-snapshot.ts +/** +* Generate JavaScript code that, when evaluated in a page context via CDP +* Runtime.evaluate, returns a pruned DOM snapshot string optimised for LLMs. +* +* The snapshot output format: +* [42] +* |scroll|
opencli command.