commit fe7efbf39e1b62f23495472e6db379853c30ac4a Author: zlei9 Date: Sun Mar 29 10:22:43 2026 +0800 Initial commit with translated description diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..ff39b6a --- /dev/null +++ b/SKILL.md @@ -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_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 [--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 [--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_2] ... +``` + +--- + +### 6. `get_company_profile.js` +Get a comprehensive company profile, including description, industry, and CEO. + +**Syntax:** +```bash +node scripts/get_company_profile.js +``` + +--- + +## 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. diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..edfc013 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn7514q7g1cz1cjgh8aryp988980qjsk", + "slug": "crypto-market-data", + "version": "1.0.2", + "publishedAt": 1770945803111 +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7a6e2dc --- /dev/null +++ b/package.json @@ -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": {} +} diff --git a/scripts/api_client.js b/scripts/api_client.js new file mode 100644 index 0000000..c45e665 --- /dev/null +++ b/scripts/api_client.js @@ -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 }; diff --git a/scripts/get_coin_details.js b/scripts/get_coin_details.js new file mode 100644 index 0000000..2b44de3 --- /dev/null +++ b/scripts/get_coin_details.js @@ -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 ', + 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(); diff --git a/scripts/get_coin_historical_chart.js b/scripts/get_coin_historical_chart.js new file mode 100644 index 0000000..f8c43c0 --- /dev/null +++ b/scripts/get_coin_historical_chart.js @@ -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 [--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(); diff --git a/scripts/get_coin_ohlc_chart.js b/scripts/get_coin_ohlc_chart.js new file mode 100644 index 0000000..f3e8ee9 --- /dev/null +++ b/scripts/get_coin_ohlc_chart.js @@ -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 [--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(); diff --git a/scripts/get_company_profile.js b/scripts/get_company_profile.js new file mode 100644 index 0000000..ebbcad2 --- /dev/null +++ b/scripts/get_company_profile.js @@ -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 ', + 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(); diff --git a/scripts/get_crypto_price.js b/scripts/get_crypto_price.js new file mode 100644 index 0000000..53f6b95 --- /dev/null +++ b/scripts/get_crypto_price.js @@ -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_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(); diff --git a/scripts/get_global_market_data.js b/scripts/get_global_market_data.js new file mode 100644 index 0000000..001fa39 --- /dev/null +++ b/scripts/get_global_market_data.js @@ -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(); diff --git a/scripts/get_public_companies_holdings.js b/scripts/get_public_companies_holdings.js new file mode 100644 index 0000000..cf1ecc1 --- /dev/null +++ b/scripts/get_public_companies_holdings.js @@ -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 ', + 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(); diff --git a/scripts/get_stock_quote.js b/scripts/get_stock_quote.js new file mode 100644 index 0000000..30ce859 --- /dev/null +++ b/scripts/get_stock_quote.js @@ -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_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(); diff --git a/scripts/get_supported_currencies.js b/scripts/get_supported_currencies.js new file mode 100644 index 0000000..618908b --- /dev/null +++ b/scripts/get_supported_currencies.js @@ -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(); diff --git a/scripts/get_top_coins.js b/scripts/get_top_coins.js new file mode 100644 index 0000000..5dbf919 --- /dev/null +++ b/scripts/get_top_coins.js @@ -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(); diff --git a/scripts/get_trending_coins.js b/scripts/get_trending_coins.js new file mode 100644 index 0000000..6acc7b8 --- /dev/null +++ b/scripts/get_trending_coins.js @@ -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(); diff --git a/scripts/search_coins.js b/scripts/search_coins.js new file mode 100644 index 0000000..a35fa5b --- /dev/null +++ b/scripts/search_coins.js @@ -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 ', + 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(); diff --git a/scripts/search_stocks.js b/scripts/search_stocks.js new file mode 100644 index 0000000..a90f0f6 --- /dev/null +++ b/scripts/search_stocks.js @@ -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 [--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();