
Aicoin Market
- 58 installs
- 51 repo stars
- Updated June 9, 2026
- aicoincom/aicoin-skills
aicoin-market is a Claude Code skill that queries crypto prices, derivatives data, news and signals across 200+ exchanges via the AiCoin Open API v3.
About
aicoin-market is a Claude Code skill for crypto market data from the AiCoin Open API v3. A developer runs a single node script to query prices, K-lines, funding rates, open interest, long/short ratios, whale orders, liquidation maps, news, airdrops, treasuries and market signals across 200+ exchanges. A catalog command lists all 183 endpoints and their parameters so the agent looks them up rather than guessing.
- Crypto market data from AiCoin Open API v3 across 200+ exchanges
- One script fronts 183 endpoints: prices, K-lines, funding rates, liquidations, news, airdrops
- Live catalog command lists every available endpoint and its parameters
Aicoin Market by the numbers
- 58 all-time installs (skills.sh)
- Ranked #578 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
aicoin-market capabilities & compatibility
Built-in free key works with rate limits; an AiCoin key raises limits and unlocks paid tiers.
- Capabilities
- crypto market data · derivatives data · crypto news · funding rate lookup
- Use cases
- trading · data analysis · research
- Runs
- Runs locally
- Pricing
- Freemium
What aicoin-market says it does
Crypto market data from the [AiCoin Open API v3](https://www.aicoin.com/opendata) — prices, K-lines, derivatives, news, signals, airdrops, treasuries and more across 200+ exchanges.
node scripts/aicoin.mjs catalog # 看全部 183 个接口(按分组)
npx skills add https://github.com/aicoincom/aicoin-skills --skill aicoin-marketAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 58 |
|---|---|
| repo stars | ★ 51 |
| Last updated | June 9, 2026 |
| Repository | aicoincom/aicoin-skills ↗ |
What it does
Query real-time crypto prices, derivatives data, news and signals across 200+ exchanges from Claude Code.
Who is it for?
Pulling real-time crypto prices, K-lines, funding rates, liquidations, news and signals.
Skip if: Hyperliquid whale analytics (use aicoin-hyperliquid), CEX order placement (use aicoin-trading) or DEX swaps (use aicoin-onchain).
When should I use this skill?
The user asks for crypto prices, K-lines, funding rates, open interest, liquidation maps, news, airdrops or market signals.
What you get
Real-time prices, derivatives data, news and signals from a unified 183-endpoint API.
- real-time prices and K-lines
- derivatives and liquidation data
- news, airdrops and signals
By the numbers
- 183 API endpoints
- data across 200+ exchanges
Files
AiCoin Market
Crypto market data from the AiCoin Open API v3 — prices, K-lines, derivatives, news, signals, airdrops, treasuries and more across 200+ exchanges.
从 SKILL.md 所在目录运行脚本。CoinClaw 三引擎容器(OpenClaw / Hermes / Claude Code)自动注入 skill 路径,cd 到 skill 目录即可。怎么用
一个命令调所有接口:
node scripts/aicoin.mjs <接口> '<JSON 参数>'<接口> 就是 /api/v3/ 后面那段路径,例如 market/ticker、coins/tickers。
- 返回统一信封
{ ok, data, error, meta }—— 先看 `ok`。ok:false时看error.code/error.message,里面写清楚了哪里错。 - 不确定有哪些接口、参数怎么填 →
node scripts/aicoin.mjs catalog [分组]。catalog 是线上实时的接口菜单,永远准。先查它再调。 - 想知道当前 key 能用哪些接口 →
node scripts/aicoin.mjs key。
node scripts/aicoin.mjs catalog # 看全部 183 个接口(按分组)
node scripts/aicoin.mjs catalog derivatives # 看 derivatives 分组每个接口的参数
node scripts/aicoin.mjs market/ticker '{"coin_key":"bitcoin","market":"binance"}'常用接口速查
| 想查什么 | 接口 + 例子 |
|---|---|
| 币价 / 涨跌 / 市值 / 净流入 | coins/tickers '{"coin_key":"bitcoin,ethereum"}' —— degree_24h_usd / degree_7day_usd 是 24h / 7 天涨跌幅(%) |
| 单交易对实时行情 | market/ticker '{"coin_key":"bitcoin","market":"binance"}' |
| K 线 | market/klines '{"coin_key":"bitcoin","market":"binance","interval":"1h","limit":100}' |
| 搜币种 / 查某币在哪些交易所 | coins/search '{"query":"PEPE"}' —— 每条结果带 db_keys,列出该币跨交易所的全部交易对 |
| 币种详情 / 简介 | coins/detail '{"coin_key":"bitcoin"}' |
| 全部币种 / 全部交易所 | coins '{"limit":100}' / markets |
| 某交易所全部交易对行情 | market/tickers '{"market":"binance"}' |
| 交易对列表 | pairs '{"market":"binance"}' |
| 热门赛道币 | markets/hot-coins '{"tab_key":"defi"}' |
| 资金费率 | derivatives/funding-rates '{"coin_key":"bitcoin","market":"binance"}' —— 返回 8h OHLC 序列,close 是当期结算费率(小数,×100 得百分比) |
| 多空比 | 单交易对历史序列 derivatives/long-short-ratio '{"coin_key":"bitcoin","market":"binance"}';全市场当前汇总 derivatives/long-short-ratio/summary(不分币种,别当成某个币的) |
| 合约持仓量排名 | derivatives/open-interest/ranking |
| 清算地图 / 清算汇总 | derivatives/liquidations/map '{"coin_key":"bitcoin","market":"binance","window":"24h"}' / derivatives/liquidations/summary |
| 大单 / 大单成交 | market/big-orders '{"coin_key":"bitcoin","market":"binance"}' / market/aggregate-trades '{"coin_key":"bitcoin","market":"binance"}' |
| 订单簿深度 | market/orderbook/latest-depth '{"coin_key":"bitcoin","market":"binance"}' |
| 资讯文章 / 快讯 | content/articles / content/newsflashes / 行业 content/newsflashes/industry |
| 搜快讯 | content/newsflashes/search '{"query":"bitcoin"}' |
| 推特/X | content/social/x/posts/latest / 搜 content/social/x/posts/search '{"query":"bitcoin"}' |
| 空投项目(有哪些值得做) | drop-radar/projects —— 项目最全;详情 drop-radar/projects/detail '{"project_id":"..."}' |
| 交易所空投 / 空投日历 | airdrops '{"source":"all"}'(交易所活动,可能为空)/ airdrops/calendar '{"year":2026,"month":5}' |
| 上市公司持币(国库) | treasuries/summary '{"coin_key":"bitcoin"}' / 实体 treasuries/entities '{"coin_key":"bitcoin"}' |
| 灰度持仓 | institutions/grayscale/holdings |
| 加密概念股 / 全球股指 | equities/crypto-exposure/quotes / macro/stock-indices |
| 指数(恐慌贪婪等) | indexes / indexes/ticker '{"index_key":"i:fgi:alternative"}' |
| 异动信号 / 预警 | signals/changes / signals/alerts |
| AI 选币推荐 | coins/recommendations '{"coin_keys":["bitcoin"]}' |
其他接口都在 catalog 里 —— 查不到想要的就先跑 catalog,别猜路径。
几条要记住的
1. 不编数据。 永远跑脚本拿真实数据。data 为空 / ok:false 就如实告诉用户,不要编解释、不要编价格。 2. 加密数据只用这个脚本,不要用 web_search / web_fetch / curl / 浏览器去拼。 3. `coin_key` 还是 `coin`? 普通接口用 coin_key —— AiCoin 币种 slug,小写(bitcoin、ethereum);hyperliquid/* 接口用 coin —— 交易符号(BTC、HYPE)。每个参数照 catalog 里的 desc / example 填。 4. 详情接口的 id 先从列表接口拿:快讯/文章详情先 content/newsflashes、content/articles 取 id;空投/项目详情先 airdrops、drop-radar/projects 取 project_id。 5. `ok:false` + HTTP 403 = 当前套餐不覆盖这个接口,别重试,告诉用户去 https://www.aicoin.com/opendata 升级。200 + 空 data 是"此条件下没数据",不是出错。 6. 时间用 Unix 毫秒(start_time / end_time),分页用 limit / offset。 7. 用用户的语言回复(中文提问就全程中文)。
API Key
内置一个免费 key,开箱即用,够查行情、K 线、币种、新闻这些。资金费率、大单、清算、HL 鲸鱼、国库等需要付费套餐 —— 某接口返回 403 就是套餐不够。
用自己的 key —— 推荐 set-key 命令(会先验证再写入 .env,禁止手编 .env):
node scripts/aicoin.mjs set-key <key_id> <secret>
# 或直接把 AiCoin 后台 JSON 整段喂进来,脚本认字段名:
node scripts/aicoin.mjs set-key '{"api_key":"<id>","access_key":"<secret>"}'⚠️ AiCoin 后台字段名反直觉:JSON 里的 api_key 其实是公开 ID(对应 AICOIN_ACCESS_KEY_ID),access_key 才是 SECRET(对应 AICOIN_ACCESS_SECRET)。脚本帮你按这个映射存,不用人脑反向。
环境变量名(写 .env 或 export 用):
AICOIN_ACCESS_KEY_ID=... # = AiCoin 后台的 api_key
AICOIN_ACCESS_SECRET=... # = AiCoin 后台的 access_key安全说明:AiCoin API Key 只用于获取市场数据,无法交易、无法读取你在交易所的任何信息。所有密钥只存在本地 .env,不上传任何服务器。CoinClaw 用户在 web UI 的 EnvSection 里配置。
跨 skill
| 需求 | 用哪个 skill |
|---|---|
| Hyperliquid 鲸鱼 / 聪明钱 / 链上大资金 | aicoin-hyperliquid |
| 交易所下单 / 平仓 / 查余额 | aicoin-trading |
| Freqtrade 策略 / 回测 / 部署 | aicoin-freqtrade |
| 链上 DEX swap / 钱包 / token | aicoin-onchain |
#!/usr/bin/env node
// AiCoin Open Data v3 API client — HMAC-SHA1 signed, header auth.
// One unified envelope {ok, data, error, meta}; see catalog for all endpoints.
import { createHmac, randomBytes } from 'node:crypto';
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// ── .env auto-load (OpenClaw exec may not inject env into child processes) ──
const ENV_FILES = [
resolve(process.cwd(), '.env'),
resolve(process.env.HOME || '', '.openclaw', 'workspace', '.env'),
resolve(process.env.HOME || '', '.openclaw', '.env'),
resolve(process.env.HOME || '', '.hermes', '.env'),
resolve(process.env.HOME || '', '.workbuddy', '.env'),
];
for (const file of ENV_FILES) {
if (!existsSync(file)) continue;
try {
for (const line of readFileSync(file, 'utf-8').split('\n')) {
const t = line.trim();
if (!t || t.startsWith('#')) continue;
const eq = t.indexOf('=');
if (eq < 1) continue;
const k = t.slice(0, eq).trim();
let v = t.slice(eq + 1).trim();
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
if (!process.env[k]) process.env[k] = v;
}
} catch { /* ignore unreadable .env */ }
}
const defaults = JSON.parse(readFileSync(resolve(__dirname, 'defaults.json'), 'utf-8'));
export const BASE = process.env.AICOIN_BASE_URL || 'https://open.aicoin.com';
export const KEY = process.env.AICOIN_ACCESS_KEY_ID || defaults.accessKeyId;
const SECRET = process.env.AICOIN_ACCESS_SECRET || defaults.accessSecret;
export const USING_OWN_KEY = !!(process.env.AICOIN_ACCESS_KEY_ID && process.env.AICOIN_ACCESS_SECRET);
// HMAC-SHA1(signStr, secret) → hex → base64. The 4 values ride in X-Aic-* headers.
function authHeaders(keyId = KEY, secret = SECRET) {
const nonce = randomBytes(8).toString('hex');
const ts = Math.floor(Date.now() / 1000).toString();
const signStr = `AccessKeyId=${keyId}&SignatureNonce=${nonce}&Timestamp=${ts}`;
const hex = createHmac('sha1', secret).update(signStr).digest('hex');
return {
'X-Aic-AccessKey-Id': keyId,
'X-Aic-Signature-Nonce': nonce,
'X-Aic-Timestamp': ts,
'X-Aic-Signature': Buffer.from(hex).toString('base64'),
};
}
// Normalize a user-supplied endpoint to a full /api/v3/... path.
// "market/ticker" / "/market/ticker" / "/api/v3/market/ticker" → "/api/v3/market/ticker"
export function normalizePath(ep) {
let p = String(ep || '').trim().replace(/^https?:\/\/[^/]+/, '');
if (p.startsWith('/api/v3/') || p === '/api/v3') return p;
p = p.replace(/^\/?(api\/v3\/?)?/, '');
return '/api/v3/' + p;
}
// endpoints.json — a bundled catalog snapshot. Drives GET/POST selection and
// offline `catalog`. Live catalog is still the source of truth (see fetchCatalog).
let _snapshot = null;
export function snapshotEndpoints() {
if (_snapshot) return _snapshot;
try {
const j = JSON.parse(readFileSync(resolve(__dirname, 'endpoints.json'), 'utf-8'));
_snapshot = j.endpoints || [];
} catch { _snapshot = []; }
return _snapshot;
}
// Pull the live catalog. Falls back to the bundled snapshot when offline.
export async function fetchCatalog() {
try {
const { httpStatus, body } = await request('GET', '/api/v3/_catalog');
if (httpStatus === 200 && body?.data?.endpoints) return { endpoints: body.data.endpoints, live: true };
} catch { /* fall through to snapshot */ }
return { endpoints: snapshotEndpoints(), live: false };
}
// Core request. Returns { httpStatus, body }; body is the parsed envelope.
export async function request(method, path, params = {}) {
const full = normalizePath(path);
const m = (method || 'GET').toUpperCase();
const headers = authHeaders();
let url = `${BASE}${full}`;
const init = { method: m, headers, signal: AbortSignal.timeout(30000) };
if (m === 'GET' || m === 'DELETE') {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params || {})) {
if (v === undefined || v === null || v === '') continue;
qs.set(k, Array.isArray(v) ? v.join(',') : String(v));
}
const s = qs.toString();
if (s) url += `?${s}`;
} else {
headers['Content-Type'] = 'application/json';
init.body = JSON.stringify(params || {});
}
const res = await fetch(url, init);
const text = await res.text();
let body;
try { body = JSON.parse(text); } catch { body = { ok: false, error: { code: 'bad_response', message: text.slice(0, 300) } }; }
// v3 business endpoints answer with {ok,...}. Auth / quota errors from the
// gateway are still legacy-shaped ({success:false,errorCode,error}); fold them
// into the same envelope so callers only ever branch on `ok`.
if (body && typeof body === 'object' && typeof body.ok !== 'boolean' && (res.status >= 400 || body.success === false)) {
body = {
ok: false,
data: null,
error: {
code: body.errorCode != null ? String(body.errorCode) : String(res.status),
message: body.error || body.message || body.msg || `HTTP ${res.status}`,
},
meta: {},
};
}
return { httpStatus: res.status, body };
}
// Resolve which HTTP method an endpoint uses, from snapshot then live catalog.
export async function resolveMethod(path) {
const full = normalizePath(path);
let hit = snapshotEndpoints().find(e => e.path === full);
if (!hit) {
const { endpoints } = await fetchCatalog();
hit = endpoints.find(e => e.path === full);
}
return hit ? { method: hit.method, spec: hit } : null;
}
// Persist a new key pair to the workspace .env (validates before writing).
export async function saveKey(keyId, secret) {
const headers = authHeaders(keyId, secret);
const res = await fetch(`${BASE}/api/v3/coins/tickers?coin_key=bitcoin`, { headers, signal: AbortSignal.timeout(15000) });
if (res.status === 401 || res.status === 403) return { ok: false, error: `key 验证失败 (HTTP ${res.status})` };
if (!res.ok) return { ok: false, error: `验证请求失败 (HTTP ${res.status})` };
const target = ENV_FILES.find(existsSync) || ENV_FILES[0];
let lines = existsSync(target) ? readFileSync(target, 'utf-8').split('\n') : [];
const set = (k, v) => {
const i = lines.findIndex(l => l.trim().startsWith(k + '='));
if (i >= 0) lines[i] = `${k}=${v}`; else lines.push(`${k}=${v}`);
};
set('AICOIN_ACCESS_KEY_ID', keyId);
set('AICOIN_ACCESS_SECRET', secret);
writeFileSync(target, lines.join('\n'));
return { ok: true, file: target };
}
{
"comment": "Public free-tier AiCoin API key. IP rate-limited. Users can replace with their own key via env vars.",
"accessKeyId": "ronJ8uI0Yj2soAfGVs5H1YALUIINbE22",
"accessSecret": "CWHZcH2us1CLSE7grroR1TpS0Z1JxTwU"
}
{
"name": "aicoin-market",
"version": "4.0.0",
"private": true,
"type": "module"
}
#!/usr/bin/env node
// AiCoin Open Data v3 — one entry point for every endpoint.
//
// node scripts/aicoin.mjs <endpoint> ['<json params>'] call any v3 endpoint
// node scripts/aicoin.mjs catalog [group|endpoint] list endpoints (the live API menu)
// node scripts/aicoin.mjs key show API key status + access probe
// node scripts/aicoin.mjs set-key <id> <secret> validate & save a new key to .env
//
// Endpoint = the path after /api/v3/ , e.g. market/ticker , hyperliquid/whales/open-positions
// Every call prints the v3 envelope {ok, data, error, meta}. Check `ok` first.
import { request, resolveMethod, fetchCatalog, saveKey, KEY, USING_OWN_KEY } from '../lib/client.mjs';
const out = (o) => console.log(JSON.stringify(o, null, 2));
const groupOf = (p) => p.replace(/^\/api\/v3\//, '').split('/')[0] || '_catalog';
const rel = (p) => p.replace(/^\/api\/v3\//, '');
const HINTS = {
401: 'HTTP 401 — 签名或鉴权失败,检查 API key 是否正确。',
403: 'HTTP 403 — 当前 key 没有此接口权限(套餐不够或 key 无效)。不要重试;告诉用户去 https://www.aicoin.com/opendata 升级套餐。',
404: 'HTTP 404 — 资源不存在,检查 id / 参数是否对。',
429: 'HTTP 429 — 触发限流,等 30-60 秒再试,或把多个查询合并成一次。',
500: 'HTTP 500 — 服务端/上游故障,可隔 1-2 分钟重试;持续失败请联系 service@aicoin.com。',
501: 'HTTP 501 — 该接口尚未实现(数据源未接通),换其他接口。',
502: 'HTTP 502 — 网关临时故障,隔 1-2 分钟重试。',
503: 'HTTP 503 — 服务暂时不可用,稍后重试。',
504: 'HTTP 504 — 网关超时,稍后重试。',
};
async function callEndpoint(endpoint, rawParams) {
let params = {};
if (rawParams) {
try { params = JSON.parse(rawParams); }
catch { return out({ ok: false, error: { code: 'bad_params', message: `参数不是合法 JSON: ${rawParams}` }, _hint: "参数要用 JSON 对象,例: '{\"coin_key\":\"bitcoin\",\"market\":\"binance\"}'" }); }
}
const resolved = await resolveMethod(endpoint);
if (!resolved) {
return out({ ok: false, error: { code: 'unknown_endpoint', message: `未知接口: ${endpoint}` }, _hint: '跑 `node scripts/aicoin.mjs catalog` 看全部接口。' });
}
let res;
try { res = await request(resolved.method, endpoint, params); }
catch (e) { return out({ ok: false, error: { code: 'network', message: e.message }, _hint: '网络/超时错误,稍后重试。' }); }
const body = (res.body && typeof res.body === 'object') ? res.body : { raw: res.body };
if (res.httpStatus !== 200 && HINTS[res.httpStatus]) body._hint = HINTS[res.httpStatus];
out(body);
}
async function showCatalog(filter) {
const { endpoints, live } = await fetchCatalog();
endpoints.sort((a, b) => a.path.localeCompare(b.path));
if (filter) {
// Group match wins over a same-named bare endpoint (e.g. `indexes` is both
// the group and the path /api/v3/indexes) — the group view is more useful.
const inGroup = endpoints.filter((e) => groupOf(e.path) === filter);
if (inGroup.length) {
const lines = [`# ${filter} (${inGroup.length} 个接口) 来源: ${live ? '线上' : '本地快照'}\n`];
for (const e of inGroup) {
lines.push(`${e.method} ${rel(e.path)} — ${e.summary || ''}`);
for (const p of e.params || []) {
const bits = [p.in, p.required ? '必填' : '可选', p.type];
if (p.enum) bits.push('枚举:' + p.enum.join('/'));
if (p.example) bits.push('例:' + p.example);
lines.push(` ${p.name} (${bits.filter(Boolean).join(', ')}) ${p.desc || ''}`);
}
lines.push('');
}
return console.log(lines.join('\n'));
}
// Not a group — treat the filter as a single endpoint path.
const exact = endpoints.find((e) => rel(e.path) === filter || e.path === filter);
if (exact) return out({ source: live ? 'live' : 'snapshot', endpoint: exact });
return out({ ok: false, error: { code: 'no_match', message: `没有 "${filter}" 分组或接口` }, _hint: '不带参数跑 catalog 看全部分组。' });
}
// Full table of contents — grouped, paths + summaries (no params).
const groups = {};
for (const e of endpoints) (groups[groupOf(e.path)] ||= []).push(e);
const lines = [
`# AiCoin v3 接口清单 — ${endpoints.length} 个,${Object.keys(groups).length} 个分组 (来源: ${live ? '线上' : '本地快照'})`,
`# 看某分组的参数: node scripts/aicoin.mjs catalog <分组名>`,
`# 调用: node scripts/aicoin.mjs <接口> '<JSON 参数>'\n`,
];
for (const [g, es] of Object.entries(groups)) {
lines.push(`### ${g} (${es.length})`);
for (const e of es) lines.push(` ${e.method.padEnd(4)} ${rel(e.path).padEnd(46)} ${e.summary || ''}`);
lines.push('');
}
console.log(lines.join('\n'));
}
async function showKey() {
const probes = [
['coins/tickers', { coin_key: 'bitcoin' }],
['derivatives/funding-rates', { coin_key: 'bitcoin', market: 'binance' }],
['market/big-orders', { coin_key: 'bitcoin', market: 'binance' }],
['hyperliquid/whales/open-positions', { coin: 'BTC' }],
['treasuries/summary', { coin_key: 'bitcoin' }],
];
const access = [];
for (const [ep, params] of probes) {
try {
const { httpStatus, body } = await request('GET', ep, params);
access.push({ endpoint: ep, http: httpStatus, ok: body?.ok === true });
} catch (e) { access.push({ endpoint: ep, error: e.message }); }
}
out({
key_id: KEY ? KEY.slice(0, 6) + '…' : null,
source: USING_OWN_KEY ? '用户自己的 key (.env)' : '内置免费 key',
access,
note: '某个接口 http=403 表示当前套餐不覆盖它;要更多权限去 https://www.aicoin.com/opendata。',
});
}
const USAGE = `AiCoin Open Data v3
node scripts/aicoin.mjs <接口> '<JSON参数>' 调任意 v3 接口,例:
node scripts/aicoin.mjs market/ticker '{"coin_key":"bitcoin","market":"binance"}'
node scripts/aicoin.mjs coins/tickers '{"coin_key":"bitcoin,ethereum"}'
node scripts/aicoin.mjs hyperliquid/whales/open-positions '{"coin":"BTC"}'
node scripts/aicoin.mjs catalog [分组] 看接口清单(不确定接口/参数时先跑这个)
node scripts/aicoin.mjs key 看 key 状态 + 权限探测
node scripts/aicoin.mjs set-key <id> <secret> 校验并保存新 key`;
const [cmd, ...rest] = process.argv.slice(2);
(async () => {
if (!cmd || cmd === 'help' || cmd === '-h' || cmd === '--help') return console.log(USAGE);
if (cmd === 'catalog') return showCatalog(rest[0]);
if (cmd === 'key') return showKey();
if (cmd === 'set-key') {
let id, secret;
const raw = rest.join(' ').trim();
if (raw.startsWith('{')) {
// JSON 模式:兼容 AiCoin 后台直接拷下来的 {"api_key","access_key"}
// 注意 AiCoin 后台命名反直觉 —— `api_key` 是公开 ID,`access_key` 才是 SECRET。
// 也兼容 {"access_key_id","access_secret"} 等更直白的命名。
try {
const j = JSON.parse(raw);
id = j.access_key_id || j.accessKeyId || j.key_id || j.api_key || j.key;
secret = j.access_secret || j.accessSecret || j.secret_key || j.secret || j.access_key;
} catch {
return out({ ok: false, error: { code: 'bad_json', message: '参数不是合法 JSON' } });
}
} else if (rest.length >= 2) {
id = rest[0];
secret = rest[1];
}
if (!id || !secret) {
return out({ ok: false, error: { code: 'bad_args', message: "用法: set-key <key_id> <secret> 或 set-key '<json>'(JSON 字段名兼容 api_key/access_key、access_key_id/access_secret 等;AiCoin 后台 api_key 是 ID、access_key 是 SECRET)" } });
}
const r = await saveKey(id, secret);
return out(r.ok ? { ok: true, message: `key 已保存到 ${r.file}` } : { ok: false, error: { code: 'invalid_key', message: r.error } });
}
return callEndpoint(cmd, rest.join(' ').trim() || null);
})().catch((e) => { out({ ok: false, error: { code: 'fatal', message: e.message } }); process.exit(1); });
Related skills
FAQ
How many endpoints does aicoin-market expose?
The catalog command lists 183 endpoints across grouped categories, kept live so it is always accurate.
Should the agent invent prices?
No. The docs say always run the script for real data and never invent prices or use web_search for crypto data.