{"url_pattern":"^https?://news\\.ycombinator\\.com(/.*)?$","site_name":"hackernews","allowed_domains":["example.com","hacker-news.firebaseio.com","hn.algolia.com","news.ycombinator.com"],"tools":[{"name":"hackernews_top","description":"获取 Hacker News 当前热门帖子","inputSchema":{"type":"object","properties":{"count":{"type":"string","description":"Number of posts (default: 20, max: 50)"}},"required":null},"handler":"(params) => {\n  const run = async function(args) {\n\n      const count = Math.min(parseInt(args.count) || 20, 50);\n      // HN Firebase API is public, no auth needed\n      const idsResp = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json');\n      if (!idsResp.ok) return {error: 'HTTP ' + idsResp.status};\n      const ids = await idsResp.json();\n      const topIds = ids.slice(0, count);\n\n      const items = await Promise.all(topIds.map(async id => {\n        const resp = await fetch('https://hacker-news.firebaseio.com/v0/item/' + id + '.json');\n        return await resp.json();\n      }));\n\n      return {\n        count: items.length,\n        posts: items.map((item, i) => ({\n          rank: i + 1, id: item.id, title: item.title,\n          url: item.url || null,\n          hn_url: 'https://news.ycombinator.com/item?id=' + item.id,\n          author: item.by, score: item.score,\n          comments: item.descendants || 0, time: item.time\n        }))\n      };\n  };\n  return run(params || {});\n}"},{"name":"hackernews_new","description":"Hacker News newest stories","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Number of stories","default":20}},"required":null},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://hacker-news.firebaseio.com/v0/newstories.json\");\n      data = __wbResp.data;\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(Math.min((args.limit ? args.limit : 20) + 10, 50)) || 0);\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"id\": item,\n      }));\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      const next = [];\n      for (let index = 0; index < source.length; index += 1) {\n        const item = source[index];\n        const __wbResp = await __wbFetch(`https://hacker-news.firebaseio.com/v0/item/${item.id}.json`);\n        next.push(__wbResp.data);\n      }\n      data = next;\n    }\n    data = (Array.isArray(data) ? data : (data == null ? [] : [data])).filter((item, index) => Boolean(item.title && !item.deleted && !item.dead));\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"score\": item.score,\n        \"author\": item.by,\n        \"comments\": item.descendants,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"hackernews_ask","description":"Hacker News Ask HN posts","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Number of stories","default":20}},"required":null},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://hacker-news.firebaseio.com/v0/askstories.json\");\n      data = __wbResp.data;\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(Math.min((args.limit ? args.limit : 20) + 10, 50)) || 0);\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"id\": item,\n      }));\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      const next = [];\n      for (let index = 0; index < source.length; index += 1) {\n        const item = source[index];\n        const __wbResp = await __wbFetch(`https://hacker-news.firebaseio.com/v0/item/${item.id}.json`);\n        next.push(__wbResp.data);\n      }\n      data = next;\n    }\n    data = (Array.isArray(data) ? data : (data == null ? [] : [data])).filter((item, index) => Boolean(item.title && !item.deleted && !item.dead));\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"score\": item.score,\n        \"author\": item.by,\n        \"comments\": item.descendants,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"hackernews_jobs","description":"Hacker News job postings","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Number of job postings","default":20}},"required":null},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://hacker-news.firebaseio.com/v0/jobstories.json\");\n      data = __wbResp.data;\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(Math.min((args.limit ? args.limit : 20) + 10, 50)) || 0);\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"id\": item,\n      }));\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      const next = [];\n      for (let index = 0; index < source.length; index += 1) {\n        const item = source[index];\n        const __wbResp = await __wbFetch(`https://hacker-news.firebaseio.com/v0/item/${item.id}.json`);\n        next.push(__wbResp.data);\n      }\n      data = next;\n    }\n    data = (Array.isArray(data) ? data : (data == null ? [] : [data])).filter((item, index) => Boolean(item.title && !item.deleted && !item.dead));\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"author\": item.by,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"hackernews_search","description":"Search Hacker News stories","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"limit":{"type":"number","description":"Number of results","default":20},"sort":{"type":"string","description":"Sort by relevance or date","default":"relevance","enum":["relevance","date"]}},"required":["query"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20, \"sort\": \"relevance\"}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(`https://hn.algolia.com/api/v1/${args.sort === 'date' ? 'search_by_date' : 'search'}`);\n      data = __wbResp.data;\n    }\n    data = __wbGet(data, \"hits\");\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"score\": item.points,\n        \"author\": item.author,\n        \"comments\": item.num_comments,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"hackernews_thread","description":"获取 Hacker News 帖子的评论树","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"HN item ID or URL"}},"required":["id"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      if (!args.id) return {error: 'Missing argument: id'};\n      let itemId = args.id;\n      const urlMatch = itemId.match(/id=(\\d+)/);\n      if (urlMatch) itemId = urlMatch[1];\n\n      const resp = await fetch('https://hacker-news.firebaseio.com/v0/item/' + itemId + '.json');\n      if (!resp.ok) return {error: 'HTTP ' + resp.status};\n      const item = await resp.json();\n      if (!item) return {error: 'Item not found', hint: 'Check the ID: ' + itemId};\n\n      // Fetch comment tree (max 2 levels deep for performance)\n      async function fetchComments(ids, depth) {\n        if (!ids || ids.length === 0 || depth > 2) return [];\n        const comments = await Promise.all(ids.slice(0, 30).map(async id => {\n          const r = await fetch('https://hacker-news.firebaseio.com/v0/item/' + id + '.json');\n          const c = await r.json();\n          if (!c || c.deleted || c.dead) return null;\n          return {\n            id: c.id, author: c.by, text: c.text, time: c.time, depth,\n            replies: await fetchComments(c.kids, depth + 1)\n          };\n        }));\n        return comments.filter(Boolean);\n      }\n\n      const comments = await fetchComments(item.kids, 0);\n      return {\n        post: {id: item.id, title: item.title, url: item.url || null, hn_url: 'https://news.ycombinator.com/item?id=' + item.id, author: item.by, score: item.score, comments_count: item.descendants || 0, time: item.time, text: item.text},\n        comments\n      };\n  };\n  return run(params || {});\n}"},{"name":"hackernews_user","description":"Hacker News user profile","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":"HN username"}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(`https://hacker-news.firebaseio.com/v0/user/${args.username}.json`);\n      data = __wbResp.data;\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"username\": item.id,\n        \"karma\": item.karma,\n        \"created\": item.created ? new Date(item.created * 1000).toISOString().slice(0, 10) : '',\n        \"about\": item.about,\n      }));\n    }\n    return data;\n  })();\n}"}]}