Initial commit with translated description

This commit is contained in:
2026-03-29 10:22:43 +08:00
commit fe7efbf39e
17 changed files with 609 additions and 0 deletions

149
SKILL.md Normal file
View File

@@ -0,0 +1,149 @@
---
name: Crypto & Stock Market Data (Node.js)
description: "无需API密钥的免费层级。专业级加密货币和股票市场数据集成。"
---
# Crypto & Stock Market Data Skill (Node.js)
A comprehensive suite of tools for retrieving real-time and historical cryptocurrency and stock market data. This skill interfaces with a dedicated market data server to provide high-performance, authenticated access to global financial statistics. Built entirely on Node.js standard libraries — no `npm install` required.
## Key Capabilities
| Category | Description |
| :--- | :--- |
| **Real-time Prices** | Fetch current valuations, market caps, 24h volumes, and price changes for crypto & stocks. |
| **Market Discovery** | Track trending assets and top-performing coins by market capitalization. |
| **Smart Search** | Quickly find coin IDs or stock tickers by searching names or symbols. |
| **Deep Details** | Access exhaustive asset information, from community links to company profiles. |
| **Precise Charts** | Retrieve OHLC candlestick data and historical price/volume time-series. |
| **Global Metrics** | Monitor total market capitalization and public company treasury holdings. |
## Tool Reference
| Script Name | Primary Function | Command Example |
| :--- | :--- | :--- |
| `get_crypto_price.js` | Multi-coin price fetch | `node scripts/get_crypto_price.js bitcoin` |
| `get_stock_quote.js` | Real-time stock quotes | `node scripts/get_stock_quote.js AAPL MSFT` |
| `get_company_profile.js` | Company overview | `node scripts/get_company_profile.js AAPL` |
| `search_stocks.js` | Find stock tickers | `node scripts/search_stocks.js apple` |
| `get_trending_coins.js` | 24h trending assets | `node scripts/get_trending_coins.js` |
| `get_top_coins.js` | Market leaderboards | `node scripts/get_top_coins.js --per_page=20` |
| `search_coins.js` | Asset discovery | `node scripts/search_coins.js solana` |
| `get_coin_details.js` | Comprehensive metadata | `node scripts/get_coin_details.js ethereum` |
| `get_coin_ohlc_chart.js` | Candlestick data | `node scripts/get_coin_ohlc_chart.js bitcoin` |
| `get_coin_historical_chart.js` | Time-series data | `node scripts/get_coin_historical_chart.js bitcoin` |
| `get_global_market_data.js` | Macro market stats | `node scripts/get_global_market_data.js` |
| `get_public_companies_holdings.js` | Treasury holdings | `node scripts/get_public_companies_holdings.js bitcoin` |
| `get_supported_currencies.js` | Valuation options | `node scripts/get_supported_currencies.js` |
---
## Usage Details
### 1. `get_crypto_price.js`
Fetch real-time pricing and basic market metrics for one or more cryptocurrencies.
**Syntax:**
```bash
node scripts/get_crypto_price.js <coin_id_1> [coin_id_2] ... [--currency=usd]
```
**Parameters:**
- `coin_id`: The unique identifier for the coin (e.g., `bitcoin`, `solana`).
- `--currency`: The target currency for valuation (default: `usd`).
**Example:**
```bash
node scripts/get_crypto_price.js bitcoin ethereum cardano --currency=jpy
```
---
### 2. `get_top_coins.js`
Retrieve a list of the top cryptocurrencies ranked by market capitalization.
**Syntax:**
```bash
node scripts/get_top_coins.js [--currency=usd] [--per_page=10] [--page=1] [--order=market_cap_desc]
```
**Parameters:**
- `--currency`: Valuation currency (default: `usd`).
- `--per_page`: Number of results (1-250, default: `10`).
- `--order`: Sorting logic (e.g., `market_cap_desc`, `volume_desc`).
---
### 3. `get_coin_ohlc_chart.js`
Get Open, High, Low, Close (candlestick) data for technical analysis.
**Syntax:**
```bash
node scripts/get_coin_ohlc_chart.js <coin_id> [--currency=usd] [--days=7]
```
**Allowed `days` values:** `1, 7, 14, 30, 90, 180, 365`.
| Range | Resolution |
| :--- | :--- |
| 1-2 Days | 30 Minute intervals |
| 3-30 Days | 4 Hour intervals |
| 31+ Days | 4 Day intervals |
---
### 4. `get_coin_historical_chart.js`
Retrieve detailed historical time-series data for price, market cap, and volume.
**Syntax:**
```bash
node scripts/get_coin_historical_chart.js <coin_id> [--currency=usd] [--days=30]
```
---
### 5. `get_stock_quote.js`
Fetch real-time stock prices for one or more ticker symbols.
**Syntax:**
```bash
node scripts/get_stock_quote.js <SYMBOL_1> [SYMBOL_2] ...
```
---
### 6. `get_company_profile.js`
Get a comprehensive company profile, including description, industry, and CEO.
**Syntax:**
```bash
node scripts/get_company_profile.js <SYMBOL>
```
---
## Important Guidelines
### Cryptos: Use IDs | Stocks: Use Tickers
- **Cryptocurrencies**: Always use **Coin IDs** (slugs) instead of ticker symbols (e.g., `bitcoin`, `BTC`).
- **Stocks**: Always use **Ticker Symbols** (e.g., `AAPL`, `Apple`).
Use `search_coins.js` if you are unsure of the correct ID.
### Authentication
Authentication is handled **automatically** by the internal `api_client.js`. Here is how it works simply:
- **Endpoint**: `GET https://api.igent.net/api/token`
- **Mechanism**:
1. **Automatic Retrieval**: The first time you use a tool, it asks the server for a temporary session token.
2. **Local Storage**: This token is stored in a hidden `.token` file locally so it can be reused for subsequent requests.
3. **Automatic Headers**: The client automatically includes this token in every request to prove you are authorized.
4. **Auto-Refresh**: If a token expires, the client automatically fetches a new one without you needing to do anything.
No manual API keys or configuration are required.
### Rate Limiting
While the system is robust, please avoid burst requests (more than 30 per minute) to maintain service stability for all users.
### Agent Integration
This skill is fully compatible with OpenClaw and other agents using the **AgentSkills** standard. Execute scripts directly from the `scripts/` directory.

6
_meta.json Normal file
View File

@@ -0,0 +1,6 @@
{
"ownerId": "kn7514q7g1cz1cjgh8aryp988980qjsk",
"slug": "crypto-market-data",
"version": "1.0.2",
"publishedAt": 1770945803111
}

8
package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"name": "crypto-market-data-skill-node",
"version": "1.0.0",
"description": "Crypto & Stock Market Data Skill (Node.js) — No external dependencies",
"private": true,
"scripts": {},
"dependencies": {}
}

106
scripts/api_client.js Normal file
View File

@@ -0,0 +1,106 @@
#!/usr/bin/env node
const https = require('https');
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const BASE_URL = process.env.API_BASE_URL || 'https://api.igent.net/api';
const TOKEN_FILE = path.join(__dirname, '.token');
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36';
function _loadToken() {
if (!fs.existsSync(TOKEN_FILE)) return null;
try {
const data = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf-8'));
const expiresAtStr = data.expires_at;
if (!expiresAtStr) return null;
const expiresAt = new Date(expiresAtStr);
if (Date.now() >= expiresAt.getTime()) return null;
return data.token || null;
} catch (e) {
console.error(`Warning: Failed to load token file: ${e.message}`);
return null;
}
}
function _httpRequest(reqUrl, headers = {}) {
return new Promise((resolve, reject) => {
const parsed = new URL(reqUrl);
const mod = parsed.protocol === 'https:' ? https : http;
const options = {
hostname: parsed.hostname,
port: parsed.port,
path: parsed.pathname + parsed.search,
method: 'GET',
headers: { 'User-Agent': USER_AGENT, ...headers },
};
const req = mod.request(options, (res) => {
let body = '';
res.on('data', (chunk) => (body += chunk));
res.on('end', () => resolve({ status: res.statusCode, body }));
});
req.on('error', reject);
req.end();
});
}
async function _fetchNewToken() {
const reqUrl = `${BASE_URL}/token`;
const { status, body } = await _httpRequest(reqUrl);
if (status === 200) {
const data = JSON.parse(body);
fs.writeFileSync(TOKEN_FILE, JSON.stringify(data));
return data.token;
}
throw new Error(`Failed to fetch token. Status: ${status}`);
}
async function getToken() {
const token = _loadToken();
if (token) return token;
return _fetchNewToken();
}
async function get(endpoint, params) {
let token;
try {
token = await getToken();
} catch (e) {
return { error: e.message };
}
const parsed = new URL(`${BASE_URL}${endpoint}`);
if (params) {
for (const [key, value] of Object.entries(params)) {
parsed.searchParams.set(key, value);
}
}
const headers = {
accept: 'application/json',
'X-API-Token': token,
};
try {
const { status, body } = await _httpRequest(parsed.toString(), headers);
if (status === 200) {
return JSON.parse(body);
}
try {
const errorJson = JSON.parse(body);
return { error: `HTTP Error ${status}: ${errorJson.error || 'Unknown'}` };
} catch {
return { error: `HTTP Error ${status}` };
}
} catch (e) {
return { error: `Request error: ${e.message}` };
}
}
module.exports = { get, getToken };

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getCoinDetails(coinId) {
return apiClient.get(`/crypto/coins/${coinId}`);
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_coin_details.js <coin_id>',
example: 'node scripts/get_coin_details.js bitcoin',
}, null, 2));
process.exit(1);
}
const result = await getCoinDetails(args[0]);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getCoinHistoricalChart(coinId, vsCurrency = 'usd', days = '7', precision = null) {
const params = { vs_currency: vsCurrency, days, interval: 'daily' };
if (precision) params.precision = precision;
return apiClient.get(`/crypto/coins/${coinId}/market_chart`, params);
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_coin_historical_chart.js <coin_id> [--currency=usd] [--days=7] [--precision=2]',
example: 'node scripts/get_coin_historical_chart.js bitcoin --currency=usd --days=30',
}, null, 2));
process.exit(1);
}
const coinId = args[0];
let vsCurrency = 'usd';
let days = '7';
let precision = null;
for (const arg of args.slice(1)) {
if (arg.startsWith('--currency=')) vsCurrency = arg.split('=')[1];
else if (arg.startsWith('--days=')) days = arg.split('=')[1];
else if (arg.startsWith('--precision=')) precision = arg.split('=')[1];
}
const result = await getCoinHistoricalChart(coinId, vsCurrency, days, precision);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
const VALID_DAYS = ['1', '7', '14', '30', '90', '180', '365'];
async function getCoinOhlcChart(coinId, vsCurrency = 'usd', days = '7', precision = null) {
if (!VALID_DAYS.includes(days)) {
return { error: `days must be one of: ${VALID_DAYS.join(', ')}` };
}
const params = { vs_currency: vsCurrency, days };
if (precision) params.precision = precision;
return apiClient.get(`/crypto/coins/${coinId}/ohlc`, params);
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_coin_ohlc_chart.js <coin_id> [--currency=usd] [--days=7] [--precision=2]',
example: 'node scripts/get_coin_ohlc_chart.js bitcoin --currency=usd --days=30',
valid_days: VALID_DAYS,
}, null, 2));
process.exit(1);
}
const coinId = args[0];
let vsCurrency = 'usd';
let days = '7';
let precision = null;
for (const arg of args.slice(1)) {
if (arg.startsWith('--currency=')) vsCurrency = arg.split('=')[1];
else if (arg.startsWith('--days=')) days = arg.split('=')[1];
else if (arg.startsWith('--precision=')) precision = arg.split('=')[1];
}
const result = await getCoinOhlcChart(coinId, vsCurrency, days, precision);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getCompanyProfile(symbol) {
return apiClient.get('/stock/profile', { symbol: symbol.toUpperCase() });
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_company_profile.js <SYMBOL>',
example: 'node scripts/get_company_profile.js AAPL',
}, null, 2));
process.exit(1);
}
const result = await getCompanyProfile(args[0]);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getCryptoPrice(coinIds, currency = 'usd') {
const params = {
ids: coinIds.join(','),
vs_currencies: currency,
include_market_cap: 'true',
include_24hr_vol: 'true',
include_24hr_change: 'true',
include_last_updated_at: 'true',
};
return apiClient.get('/crypto/prices', params);
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_crypto_price.js <coin_id_1> [coin_id_2] ... [--currency=usd]',
example: 'node scripts/get_crypto_price.js bitcoin ethereum --currency=usd',
}, null, 2));
process.exit(1);
}
let currency = 'usd';
const coinIds = [];
for (const arg of args) {
if (arg.startsWith('--currency=')) {
currency = arg.split('=')[1];
} else {
coinIds.push(arg);
}
}
if (coinIds.length === 0) coinIds.push('bitcoin');
const result = await getCryptoPrice(coinIds, currency);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getGlobalMarketData() {
return apiClient.get('/crypto/global');
}
async function main() {
const result = await getGlobalMarketData();
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getPublicCompaniesHoldings(coinId) {
if (!['bitcoin', 'ethereum'].includes(coinId)) {
return { error: "coin_id must be either 'bitcoin' or 'ethereum'" };
}
return apiClient.get('/crypto/companies/holdings', { coin_id: coinId });
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_public_companies_holdings.js <coin_id>',
example: 'node scripts/get_public_companies_holdings.js bitcoin',
note: "coin_id must be either 'bitcoin' or 'ethereum'",
}, null, 2));
process.exit(1);
}
const result = await getPublicCompaniesHoldings(args[0].toLowerCase());
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getStockQuote(symbols) {
const symbolsStr = symbols.map((s) => s.toUpperCase()).join(',');
return apiClient.get('/stock/quote', { symbols: symbolsStr });
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/get_stock_quote.js <SYMBOL_1> [SYMBOL_2] ...',
example: 'node scripts/get_stock_quote.js AAPL MSFT GOOG',
}, null, 2));
process.exit(1);
}
const result = await getStockQuote(args);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getSupportedCurrencies() {
return apiClient.get('/crypto/supported-currencies');
}
async function main() {
const result = await getSupportedCurrencies();
console.log(JSON.stringify(result, null, 2));
}
main();

31
scripts/get_top_coins.js Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getTopCoins(vsCurrency = 'usd', perPage = 10, page = 1, order = 'market_cap_desc') {
const params = {
vs_currency: vsCurrency,
per_page: String(perPage),
page: String(page),
order,
};
return apiClient.get('/crypto/coins/markets', params);
}
async function main() {
let vsCurrency = 'usd';
let perPage = 10;
let page = 1;
let order = 'market_cap_desc';
for (const arg of process.argv.slice(2)) {
if (arg.startsWith('--currency=')) vsCurrency = arg.split('=')[1];
else if (arg.startsWith('--per_page=')) perPage = parseInt(arg.split('=')[1], 10);
else if (arg.startsWith('--page=')) page = parseInt(arg.split('=')[1], 10);
else if (arg.startsWith('--order=')) order = arg.split('=')[1];
}
const result = await getTopCoins(vsCurrency, perPage, page, order);
console.log(JSON.stringify(result, null, 2));
}
main();

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function getTrendingCoins() {
return apiClient.get('/crypto/search/trending');
}
async function main() {
const result = await getTrendingCoins();
console.log(JSON.stringify(result, null, 2));
}
main();

23
scripts/search_coins.js Normal file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function searchCoins(query) {
return apiClient.get('/crypto/search', { query });
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/search_coins.js <query>',
example: 'node scripts/search_coins.js bitcoin',
}, null, 2));
process.exit(1);
}
const query = args.join(' ');
const result = await searchCoins(query);
console.log(JSON.stringify(result, null, 2));
}
main();

34
scripts/search_stocks.js Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env node
const apiClient = require('./api_client');
async function searchStocks(query, limit = 10) {
return apiClient.get('/stock/search', { query, limit: String(limit) });
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log(JSON.stringify({
usage: 'node scripts/search_stocks.js <query> [--limit=10]',
example: 'node scripts/search_stocks.js apple --limit=5',
}, null, 2));
process.exit(1);
}
let limit = 10;
const queryParts = [];
for (const arg of args) {
if (arg.startsWith('--limit=')) {
const val = parseInt(arg.split('=')[1], 10);
if (!isNaN(val)) limit = val;
} else {
queryParts.push(arg);
}
}
const result = await searchStocks(queryParts.join(' '), limit);
console.log(JSON.stringify(result, null, 2));
}
main();