
Opencli Adapter Author
Author new OpenCLI adapters with the fixed cli({ site, name, args, columns }) layout and current typed-error conventions.
Overview
OpenCLI Adapter Author is an agent skill for the Build phase that shows how to structure OpenCLI adapters with cli(), args, columns, and modern typed-error handling.
Install
npx skills add https://github.com/jackwener/opencli --skill opencli-adapter-authorWhat is this skill?
- Fixed three-part adapter layout: declaration, args, and handler func via cli({...})
- Uses live eastmoney convertible.js as structural reference (grandfathered errors called out)
- Documents Strategy, domain, browser flag, columns, and sort/limit args patterns
- Points new adapters to typed-errors.md instead of legacy CliError/HTTP_ERROR copies
- Registry import path from @jackwener/opencli/registry and errors package
- Three-part adapter structure: declaration, args, func
- Grandfathered baseline referenced: scripts/typed-error-lint-baseline.json (2026-05)
Adoption & trust: 8.2k installs on skills.sh; 23.8k GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
You want a new OpenCLI command but lack the enforced file shape, registry imports, and up-to-date error patterns.
Who is it for?
Indie builders extending jackwener/opencli with new site integrations who learn best from a concrete convertible.js skeleton.
Skip if: Users who only consume existing CLIs without authoring adapters, or projects not using @jackwener/opencli/registry.
When should I use this skill?
User is creating or editing an OpenCLI adapter file and needs the cli({ site, name, args, columns }) contract and current error-handling rules.
What do I get? / Deliverables
You ship an adapter that matches OpenCLI registry contracts and passes current typed-error expectations instead of copying legacy CliError snippets.
- New adapter module registered via cli({...})
- Args schema and column mapping for CLI output
Recommended Skills
Journey fit
Adapter authoring happens when you extend your CLI product surface during Build, not during validate-only prototyping unless you already commit to OpenCLI. Each adapter is a typed integration to an external domain (e.g. eastmoney)—classic build-time API/CLI wiring work.
How it compares
Authoring guide for OpenCLI integration modules—not a general REST client generator or MCP tool server.
Common Questions / FAQ
Who is opencli-adapter-author for?
Developers building or maintaining OpenCLI adapters who need the declarative cli({}) pattern, args schema, and columns map aligned with the registry.
When should I use opencli-adapter-author?
During Build integrations when adding a new site command, refactoring adapter layout, or fixing lint failures around typed errors and limit validation.
Is opencli-adapter-author safe to install?
It guides code you write that may call external HTTP APIs—review Security Audits on this page and avoid copying grandfathered error/limit patterns into new files.
SKILL.md
READMESKILL.md - Opencli Adapter Author
# Adapter Template 一份 adapter 就是一次 `cli({...})` 调用。文件结构固定,三段:declaration、args、func。 拿 `clis/eastmoney/convertible.js` 当活例子,对照拆解。 --- ## 活例子:convertible.js > **注意(2026-05 起)**:下面这份 `convertible.js` 的 limit clamp 和 `CliError('HTTP_ERROR' / 'NO_DATA')` 是 grandfathered 写法(在 [`scripts/typed-error-lint-baseline.json`](../../../scripts/typed-error-lint-baseline.json) 里)。结构布局(cli 声明 / args / columns / map)仍然是好范本,但 **error 处理 + limit 校验请按下文 §3 + [`typed-errors.md`](./typed-errors.md) 写**。新写 adapter 抄这个文件别连 `Math.max(1, Math.min(...))` 和 `CliError(...)` 一起抄过去。 ```javascript // eastmoney convertible — on-market convertible bond listing. import { cli, Strategy } from '@jackwener/opencli/registry'; import { CliError } from '@jackwener/opencli/errors'; const SORTS = { change: { fid: 'f3', order: 'desc' }, drop: { fid: 'f3', order: 'asc' }, turnover: { fid: 'f6', order: 'desc' }, price: { fid: 'f2', order: 'desc' }, premium: { fid: 'f237', order: 'desc' }, value: { fid: 'f236', order: 'desc' }, ytm: { fid: 'f239', order: 'desc' }, }; cli({ site: 'eastmoney', name: 'convertible', description: '可转债行情列表(默认按成交额排序)', domain: 'push2.eastmoney.com', strategy: Strategy.PUBLIC, browser: false, args: [ { name: 'sort', type: 'string', default: 'turnover', help: '排序:turnover / change / drop / price / premium' }, { name: 'limit', type: 'int', default: 20, help: '返回数量 (max 100)' }, ], columns: ['rank', 'bondCode', 'bondName', 'bondPrice', 'bondChangePct', 'stockCode', 'stockName', 'stockPrice', 'stockChangePct', 'convPrice', 'convValue', 'convPremiumPct', 'remainingYears', 'ytm', 'listDate'], func: async (args) => { const sortKey = String(args.sort ?? 'turnover').toLowerCase(); const sort = SORTS[sortKey]; if (!sort) throw new CliError('INVALID_ARGUMENT', `Unknown sort "${sortKey}". Valid: ${Object.keys(SORTS).join(', ')}`); const limit = Math.max(1, Math.min(Number(args.limit) || 20, 100)); const url = new URL('https://push2.eastmoney.com/api/qt/clist/get'); url.searchParams.set('pn', '1'); url.searchParams.set('pz', String(limit)); url.searchParams.set('po', sort.order === 'desc' ? '1' : '0'); url.searchParams.set('np', '1'); url.searchParams.set('fltt', '2'); url.searchParams.set('invt', '2'); url.searchParams.set('fid', sort.fid); url.searchParams.set('fs', 'b:MK0354'); url.searchParams.set('fields', 'f12,f14,f2,f3,f6,f229,f230,f232,f234,f235,f236,f237,f238,f239,f243'); url.searchParams.set('ut', 'bd1d9ddb04089700cf9c27f6f7426281'); const resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }); if (!resp.ok) throw new CliError('HTTP_ERROR', `convertible failed: HTTP ${resp.status}`); const data = await resp.json(); const diff = Array.isArray(data?.data?.diff) ? data.data.diff : []; if (diff.length === 0) throw new CliError('NO_DATA', 'eastmoney returned no convertible data'); return diff.slice(0, limit).map((it, i) => ({ rank: i + 1, bondCode: it.f12, bondName: it.f14, bondPrice: it.f2, bondChangePct: it.f3, stockCode: it.f232, stockName: it.f234, stockPrice: it.f229, stockChangePct: it.f230, convPrice: it.f235, convValue: it.f236, convPremiumPct: it.f237, remainingYears: it.f238, ytm: it.f239, listDate: String(it.f243 ?? ''), })); }, }); ``` --- ## 三段解剖 ### 1. Declaration — 标头 ```javascript cli({ site: 'eastmoney', // 第一级命名空间,目录名一致 name: 'convertible', // 第二级,CLI 上的子命令 description: '...', // 一句话,出现在 `opencli list` 和 `opencli <site> -h` domain: 'push2.eastmoney.com', // 主要请求域名(诊断面板用) strategy: Strategy.PUBLIC, // PUBLIC / COOKIE / INTERCEPT / UI browser: false, // PUBLIC 几乎总是 false;COOKIE/INTERCEPT/UI 一律 true ... }); ``` ### 2. Args & Columns ```javascript args: [ { name: 'sort', type: 's