/ autoresearch / save-tasks.json
save-tasks.json
  1  [
  2    {
  3      "name": "httpbin-get",
  4      "site": "test-httpbin",
  5      "command": "get",
  6      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-httpbin',\n  name: 'get',\n  description: 'httpbin echo test',\n  domain: 'httpbin.org',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [],\n  columns: ['origin', 'url'],\n  func: async () => {\n    const res = await fetch('https://httpbin.org/get');\n    const d = await res.json();\n    return [{ origin: d.origin, url: d.url }];\n  },\n});\n",
  7      "judge": {
  8        "type": "arrayMinLength",
  9        "minLength": 1
 10      },
 11      "note": "Simplest possible: httpbin echo, single row"
 12    },
 13    {
 14      "name": "jsonplaceholder-posts",
 15      "site": "test-jsonplaceholder",
 16      "command": "posts",
 17      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'posts',\n  description: 'JSONPlaceholder posts',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of posts' },\n  ],\n  columns: ['id', 'title'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/posts');\n    const posts = await res.json();\n    return posts.slice(0, limit).map((p: any) => ({ id: p.id, title: p.title }));\n  },\n});\n",
 18      "judge": {
 19        "type": "arrayMinLength",
 20        "minLength": 3
 21      }
 22    },
 23    {
 24      "name": "jsonplaceholder-users",
 25      "site": "test-jsonplaceholder",
 26      "command": "users",
 27      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'users',\n  description: 'JSONPlaceholder users',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of users' },\n  ],\n  columns: ['id', 'name', 'email'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/users');\n    const users = await res.json();\n    return users.slice(0, limit).map((u: any) => ({ id: u.id, name: u.name, email: u.email }));\n  },\n});\n",
 28      "judge": {
 29        "type": "arrayMinLength",
 30        "minLength": 3
 31      }
 32    },
 33    {
 34      "name": "hn-top",
 35      "site": "test-hn",
 36      "command": "top",
 37      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'top',\n  description: 'HackerNews top stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['rank', 'title', 'score'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/topstories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, score: item.score,\n    }));\n  },\n});\n",
 38      "judge": {
 39        "type": "arrayMinLength",
 40        "minLength": 3
 41      }
 42    },
 43    {
 44      "name": "hn-ask",
 45      "site": "test-hn",
 46      "command": "ask",
 47      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'ask',\n  description: 'HackerNews Ask HN stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['rank', 'title', 'score'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/askstories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, score: item.score,\n    }));\n  },\n});\n",
 48      "judge": {
 49        "type": "arrayMinLength",
 50        "minLength": 3
 51      }
 52    },
 53    {
 54      "name": "wiki-summary",
 55      "site": "test-wiki",
 56      "command": "summary",
 57      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-wiki',\n  name: 'summary',\n  description: 'Wikipedia article summary',\n  domain: 'en.wikipedia.org',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'title', type: 'string', default: 'JavaScript', positional: true, help: 'Article title' },\n  ],\n  columns: ['title', 'extract'],\n  func: async (_page, kwargs) => {\n    const title = encodeURIComponent(kwargs.title);\n    const res = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${title}`);\n    const d = await res.json();\n    return [{ title: d.title, extract: d.extract?.slice(0, 200) }];\n  },\n});\n",
 58      "judge": {
 59        "type": "contains",
 60        "value": "programming language"
 61      }
 62    },
 63    {
 64      "name": "lobsters-hot",
 65      "site": "test-lobsters",
 66      "command": "hot",
 67      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-lobsters',\n  name: 'hot',\n  description: 'Lobsters hottest stories',\n  domain: 'lobste.rs',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['title', 'score', 'url'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://lobste.rs/hottest.json');\n    const stories = await res.json();\n    return stories.slice(0, limit).map((s: any) => ({\n      title: s.title, score: s.score, url: s.short_id_url,\n    }));\n  },\n});\n",
 68      "judge": {
 69        "type": "arrayMinLength",
 70        "minLength": 3
 71      }
 72    },
 73    {
 74      "name": "devto-top",
 75      "site": "test-devto",
 76      "command": "top",
 77      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-devto',\n  name: 'top',\n  description: 'DEV.to top articles',\n  domain: 'dev.to',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of articles' },\n  ],\n  columns: ['title', 'user', 'reactions'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://dev.to/api/articles?per_page=' + limit);\n    const articles = await res.json();\n    return articles.map((a: any) => ({\n      title: a.title, user: a.user?.username, reactions: a.positive_reactions_count,\n    }));\n  },\n});\n",
 78      "judge": {
 79        "type": "arrayMinLength",
 80        "minLength": 3
 81      }
 82    },
 83    {
 84      "name": "zhihu-hot-with-top-answer",
 85      "site": "test-zhihu",
 86      "command": "hot-detail",
 87      "adapterFile": "save-adapters/zhihu-hot-detail.ts",
 88      "judge": {
 89        "type": "arrayMinLength",
 90        "minLength": 3
 91      },
 92      "note": "6-step chain: navigate → fetch hot list API → parse big-int IDs → loop N items → fetch answer API per question → strip HTML → merge"
 93    },
 94    {
 95      "name": "zhihu-search-with-question-stats",
 96      "site": "test-zhihu",
 97      "command": "search-detail",
 98      "adapterFile": "save-adapters/zhihu-search-detail.ts",
 99      "judge": {
100        "type": "arrayMinLength",
101        "minLength": 3
102      },
103      "note": "7-step chain: navigate → search API → filter by type → extract question IDs → fetch question detail per result → merge stats → format"
104    },
105    {
106      "name": "xhs-search-scroll-extract",
107      "site": "test-xhs",
108      "command": "search-full",
109      "adapterFile": "save-adapters/xhs-search-full.ts",
110      "judge": {
111        "type": "arrayMinLength",
112        "minLength": 3
113      },
114      "note": "6-step chain: navigate → MutationObserver wait → scroll 3x → DOM extract with URL dedup → slice + format"
115    },
116    {
117      "name": "xhs-note-with-comments",
118      "site": "test-xhs",
119      "command": "note-comments",
120      "adapterFile": "save-adapters/xhs-note-comments.ts",
121      "judge": {
122        "type": "arrayMinLength",
123        "minLength": 1
124      },
125      "note": "7-step chain: navigate → wait → extract note meta → scroll container 3x → extract comments DOM → merge note+comments → unified output"
126    },
127    {
128      "name": "zhihu-question-with-related",
129      "site": "test-zhihu",
130      "command": "question-full",
131      "adapterFile": "save-adapters/zhihu-question-full.ts",
132      "judge": {
133        "type": "arrayMinLength",
134        "minLength": 2
135      },
136      "note": "8-step chain: navigate → wait → fetch question detail → fetch answers → strip HTML → fetch related questions → merge 3 layers → format"
137    },
138    {
139      "name": "xhs-explore-scroll-dedupe",
140      "site": "test-xhs",
141      "command": "explore-deep",
142      "adapterFile": "save-adapters/xhs-explore-deep.ts",
143      "judge": {
144        "type": "arrayMinLength",
145        "minLength": 3
146      },
147      "note": "8-step chain: navigate → MutationObserver wait → adaptive scroll → DOM extract with dedup → parse likes → sort desc → slice → format"
148    },
149    {
150      "name": "hn-new",
151      "site": "test-hn",
152      "command": "new",
153      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'new',\n  description: 'HackerNews newest stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['rank', 'title', 'score'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/newstories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, score: item.score ?? 0,\n    }));\n  },\n});\n",
154      "judge": {
155        "type": "arrayMinLength",
156        "minLength": 3
157      },
158      "note": "PUBLIC strategy: HackerNews new stories using same Firebase API as hn-top/hn-ask"
159    },
160    {
161      "name": "jsonplaceholder-todos",
162      "site": "test-jsonplaceholder",
163      "command": "todos",
164      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'todos',\n  description: 'JSONPlaceholder todos',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of todos' },\n  ],\n  columns: ['id', 'title', 'completed'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/todos');\n    const todos = await res.json();\n    return todos.slice(0, limit).map((t: any) => ({ id: t.id, title: t.title, completed: t.completed }));\n  },\n});\n",
165      "judge": {
166        "type": "arrayMinLength",
167        "minLength": 3
168      },
169      "note": "PUBLIC strategy: JSONPlaceholder todos — same base domain as posts/users, different endpoint"
170    },
171    {
172      "name": "hn-show",
173      "site": "test-hn",
174      "command": "show",
175      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'show',\n  description: 'HackerNews Show HN stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['rank', 'title', 'score'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/showstories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, score: item.score ?? 0,\n    }));\n  },\n});\n",
176      "judge": {
177        "type": "arrayMinLength",
178        "minLength": 3
179      },
180      "note": "PUBLIC strategy: HackerNews show stories using same Firebase API as hn-top/hn-ask/hn-new"
181    },
182    {
183      "name": "jsonplaceholder-comments",
184      "site": "test-jsonplaceholder",
185      "command": "comments",
186      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'comments',\n  description: 'JSONPlaceholder comments',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of comments' },\n  ],\n  columns: ['id', 'name', 'email'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/comments');\n    const comments = await res.json();\n    return comments.slice(0, limit).map((c: any) => ({ id: c.id, name: c.name, email: c.email }));\n  },\n});\n",
187      "judge": {
188        "type": "arrayMinLength",
189        "minLength": 3
190      },
191      "note": "PUBLIC strategy: JSONPlaceholder comments — same base domain as posts/users/todos, different endpoint"
192    },
193    {
194      "name": "jsonplaceholder-albums",
195      "site": "test-jsonplaceholder",
196      "command": "albums",
197      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'albums',\n  description: 'JSONPlaceholder albums',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of albums' },\n  ],\n  columns: ['id', 'userId', 'title'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/albums');\n    const albums = await res.json();\n    return albums.slice(0, limit).map((a: any) => ({ id: a.id, userId: a.userId, title: a.title }));\n  },\n});\n",
198      "judge": {
199        "type": "arrayMinLength",
200        "minLength": 3
201      },
202      "note": "PUBLIC strategy: JSONPlaceholder albums — same base domain as posts/users/todos/comments, different endpoint"
203    },
204    {
205      "name": "jsonplaceholder-photos",
206      "site": "test-jsonplaceholder",
207      "command": "photos",
208      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-jsonplaceholder',\n  name: 'photos',\n  description: 'JSONPlaceholder photos',\n  domain: 'jsonplaceholder.typicode.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of photos' },\n  ],\n  columns: ['id', 'albumId', 'title'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://jsonplaceholder.typicode.com/photos');\n    const photos = await res.json();\n    return photos.slice(0, limit).map((p: any) => ({ id: p.id, albumId: p.albumId, title: p.title }));\n  },\n});\n",
209      "judge": {
210        "type": "arrayMinLength",
211        "minLength": 3
212      },
213      "note": "PUBLIC strategy: JSONPlaceholder photos — same base domain as posts/users/todos/comments/albums, different endpoint"
214    },
215    {
216      "name": "hn-best",
217      "site": "test-hn",
218      "command": "best",
219      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'best',\n  description: 'HackerNews best stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of stories' },\n  ],\n  columns: ['rank', 'title', 'score'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/beststories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, score: item.score ?? 0,\n    }));\n  },\n});\n",
220      "judge": {
221        "type": "arrayMinLength",
222        "minLength": 3
223      },
224      "note": "PUBLIC strategy: HackerNews best stories using same Firebase API as hn-top/hn-ask/hn-new/hn-show"
225    },
226    {
227      "name": "hn-jobs",
228      "site": "test-hn",
229      "command": "jobs",
230      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-hn',\n  name: 'jobs',\n  description: 'HackerNews job stories',\n  domain: 'news.ycombinator.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of jobs' },\n  ],\n  columns: ['rank', 'title', 'url'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch('https://hacker-news.firebaseio.com/v0/jobstories.json');\n    const ids = await res.json();\n    const items = await Promise.all(ids.slice(0, limit).map(async (id: number) => {\n      const r = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`);\n      return r.json();\n    }));\n    return items.map((item: any, i: number) => ({\n      rank: i + 1, title: item.title, url: item.url ?? '',\n    }));\n  },\n});\n",
231      "judge": {
232        "type": "arrayMinLength",
233        "minLength": 3
234      },
235      "note": "PUBLIC strategy: HackerNews job listings using same Firebase API as other HN adapters"
236    },
237    {
238      "name": "restcountries-list",
239      "site": "test-restcountries",
240      "command": "list",
241      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-restcountries',\n  name: 'list',\n  description: 'REST Countries list',\n  domain: 'restcountries.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of countries' },\n  ],\n  columns: ['name', 'capital', 'region'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch('https://restcountries.com/v3.1/all?fields=name,capital,region');\n    const countries = await res.json();\n    return countries.slice(0, limit).map((c: any) => ({\n      name: c.name?.common ?? '',\n      capital: c.capital?.[0] ?? '',\n      region: c.region ?? '',\n    }));\n  },\n});\n",
242      "judge": {
243        "type": "arrayMinLength",
244        "minLength": 3
245      },
246      "note": "PUBLIC strategy: REST Countries API — stable, no-auth, returns 250 countries with name/capital/region"
247    },
248    {
249      "name": "nager-holidays",
250      "site": "test-nager",
251      "command": "holidays",
252      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-nager',\n  name: 'holidays',\n  description: 'US public holidays for current year',\n  domain: 'date.nager.at',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of holidays' },\n  ],\n  columns: ['date', 'name', 'type'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const year = new Date().getFullYear();\n    const res = await fetch(`https://date.nager.at/api/v3/PublicHolidays/${year}/US`);\n    const holidays = await res.json();\n    return holidays.slice(0, limit).map((h: any) => ({\n      date: h.date,\n      name: h.name,\n      type: (h.types || []).join(','),\n    }));\n  },\n});\n",
253      "judge": {
254        "type": "arrayMinLength",
255        "minLength": 3
256      },
257      "note": "PUBLIC strategy: Nager public holidays API — stable, no-auth, returns US federal holidays by year"
258    },
259    {
260      "name": "catfact-list",
261      "site": "test-catfact",
262      "command": "facts",
263      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-catfact',\n  name: 'facts',\n  description: 'Random cat facts',\n  domain: 'catfact.ninja',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of facts' },\n  ],\n  columns: ['fact', 'length'],\n  func: async (_page, kwargs) => {\n    const limit = kwargs.limit ?? 5;\n    const res = await fetch(`https://catfact.ninja/facts?limit=${limit}`);\n    const d = await res.json();\n    return d.data.map((item: any) => ({\n      fact: item.fact.slice(0, 100),\n      length: item.length,\n    }));\n  },\n});\n",
264      "judge": {
265        "type": "arrayMinLength",
266        "minLength": 3
267      },
268      "note": "PUBLIC strategy: catfact.ninja facts API — stable, no-auth, returns random cat facts"
269    },
270    {
271      "name": "opentdb-trivia",
272      "site": "test-opentdb",
273      "command": "easy",
274      "adapter": "import { cli, Strategy } from '@jackwener/opencli/registry';\n\ncli({\n  site: 'test-opentdb',\n  name: 'easy',\n  description: 'Easy trivia questions from Open Trivia DB',\n  domain: 'opentdb.com',\n  strategy: Strategy.PUBLIC,\n  browser: false,\n  args: [\n    { name: 'limit', type: 'int', default: 5, help: 'Number of questions' },\n  ],\n  columns: ['category', 'question', 'answer'],\n  func: async (_page, kwargs) => {\n    const limit = Math.min(kwargs.limit ?? 5, 10);\n    const res = await fetch(`https://opentdb.com/api.php?amount=${limit}&difficulty=easy&type=multiple`);\n    const d = await res.json();\n    return d.results.map((q: any) => ({\n      category: q.category,\n      question: q.question.replace(/"/g, '\"').replace(/'/g, \"'\").slice(0, 80),\n      answer: q.correct_answer,\n    }));\n  },\n});\n",
275      "judge": {
276        "type": "arrayMinLength",
277        "minLength": 3
278      },
279      "note": "PUBLIC strategy: Open Trivia DB API — stable, no-auth, returns trivia questions with correct answers"
280    }
281  ]