@jackwener/opencli 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/cli-manifest.json +386 -1
  4. package/clis/binance/asks.js +21 -0
  5. package/clis/binance/commands.test.js +70 -0
  6. package/clis/binance/depth.js +21 -0
  7. package/clis/binance/gainers.js +22 -0
  8. package/clis/binance/klines.js +21 -0
  9. package/clis/binance/losers.js +22 -0
  10. package/clis/binance/pairs.js +21 -0
  11. package/clis/binance/price.js +18 -0
  12. package/clis/binance/prices.js +19 -0
  13. package/clis/binance/ticker.js +21 -0
  14. package/clis/binance/top.js +21 -0
  15. package/clis/binance/trades.js +20 -0
  16. package/clis/twitter/lists-parser.js +77 -0
  17. package/clis/twitter/lists.d.ts +5 -0
  18. package/clis/twitter/lists.js +62 -0
  19. package/clis/twitter/lists.test.js +50 -0
  20. package/clis/weibo/feed.js +18 -5
  21. package/clis/zsxq/topic.js +5 -3
  22. package/clis/zsxq/topic.test.js +4 -3
  23. package/clis/zsxq/utils.js +1 -1
  24. package/dist/src/cli.js +108 -0
  25. package/dist/src/discovery.d.ts +5 -2
  26. package/dist/src/discovery.js +7 -35
  27. package/dist/src/engine.test.js +29 -1
  28. package/dist/src/main.js +6 -5
  29. package/package.json +3 -3
  30. package/scripts/fetch-adapters.js +59 -28
  31. package/dist/src/clis/binance/asks.d.ts +0 -1
  32. package/dist/src/clis/binance/asks.js +0 -20
  33. package/dist/src/clis/binance/commands.test.d.ts +0 -3
  34. package/dist/src/clis/binance/commands.test.js +0 -58
  35. package/dist/src/clis/binance/depth.d.ts +0 -1
  36. package/dist/src/clis/binance/depth.js +0 -20
  37. package/dist/src/clis/binance/gainers.d.ts +0 -1
  38. package/dist/src/clis/binance/gainers.js +0 -21
  39. package/dist/src/clis/binance/klines.d.ts +0 -1
  40. package/dist/src/clis/binance/klines.js +0 -20
  41. package/dist/src/clis/binance/losers.d.ts +0 -1
  42. package/dist/src/clis/binance/losers.js +0 -21
  43. package/dist/src/clis/binance/pairs.d.ts +0 -1
  44. package/dist/src/clis/binance/pairs.js +0 -20
  45. package/dist/src/clis/binance/price.d.ts +0 -1
  46. package/dist/src/clis/binance/price.js +0 -17
  47. package/dist/src/clis/binance/prices.d.ts +0 -1
  48. package/dist/src/clis/binance/prices.js +0 -18
  49. package/dist/src/clis/binance/ticker.d.ts +0 -1
  50. package/dist/src/clis/binance/ticker.js +0 -20
  51. package/dist/src/clis/binance/top.d.ts +0 -1
  52. package/dist/src/clis/binance/top.js +0 -20
  53. package/dist/src/clis/binance/trades.d.ts +0 -1
  54. package/dist/src/clis/binance/trades.js +0 -19
package/dist/src/main.js CHANGED
@@ -45,14 +45,15 @@ if (argv[0] === 'completion' && argv.length >= 2) {
45
45
  // Fast path: --get-completions — read from manifest, skip discovery
46
46
  const getCompIdx = process.argv.indexOf('--get-completions');
47
47
  if (getCompIdx !== -1) {
48
- // Only require manifest for directories that actually exist.
49
- // If user clis dir doesn't exist, there are no user adapters to miss.
48
+ // Only include manifests that actually exist on disk.
49
+ // With sparse override, the user clis dir may exist but have no manifest.
50
50
  const manifestPaths = [getCliManifestPath(BUILTIN_CLIS)];
51
+ const userManifest = getCliManifestPath(USER_CLIS);
51
52
  try {
52
- fs.accessSync(USER_CLIS);
53
- manifestPaths.push(getCliManifestPath(USER_CLIS));
53
+ fs.accessSync(userManifest);
54
+ manifestPaths.push(userManifest);
54
55
  }
55
- catch { /* no user dir */ }
56
+ catch { /* no user manifest */ }
56
57
  if (hasAllManifests(manifestPaths)) {
57
58
  const rest = process.argv.slice(getCompIdx + 1);
58
59
  let cursor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -52,8 +52,8 @@
52
52
  "typecheck": "tsc --noEmit",
53
53
  "prepare": "[ -d src ] && npm run build || true",
54
54
  "prepublishOnly": "npm run build",
55
- "test": "vitest run --project unit --project extension",
56
- "test:bun": "bun vitest run --project unit --project extension",
55
+ "test": "vitest run --project unit --project extension --project adapter",
56
+ "test:bun": "bun vitest run --project unit --project extension --project adapter",
57
57
  "test:adapter": "vitest run --project adapter",
58
58
  "test:all": "vitest run",
59
59
  "test:e2e": "vitest run --project e2e",
@@ -1,21 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Copy official CLI adapters from the installed package to ~/.opencli/clis/.
4
+ * Sparse adapter sync: keeps ~/.opencli/clis/ clean by removing stale overrides.
5
5
  *
6
- * Update strategy (file-level granularity via adapter-manifest.json):
7
- * - Official files (in new manifest) are unconditionally overwritten
8
- * - Removed official files (in old manifest but not new) are cleaned up
9
- * - User-created files (never in any manifest) are preserved
10
- * - Skips if already installed at the same version
6
+ * Strategy (hash-based, site-level granularity):
7
+ * - When an official site has upstream changes: DELETE the local override
8
+ * (do NOT copy new version runtime falls back to package baseline)
9
+ * - When an official site has no changes: leave local override intact
10
+ * - User-created custom sites (not in package): always preserved
11
+ * - Skips entirely if already synced at the same version
12
+ *
13
+ * ~/.opencli/clis/ is a sparse override layer, not a full copy.
14
+ * Only eject-ed or user-modified sites appear here.
11
15
  *
12
16
  * Only runs on global install (npm install -g) or explicit OPENCLI_FETCH=1.
13
- * No network calls — copies directly from clis/ in the installed package.
17
+ * No network calls — reads hashes from clis/ in the installed package.
14
18
  *
15
19
  * This is an ESM script (package.json type: module). No TypeScript, no src/ imports.
16
20
  */
17
21
 
18
- import { existsSync, mkdirSync, rmSync, cpSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
22
+ import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
23
+ import { createHash } from 'node:crypto';
19
24
  import { join, resolve, dirname } from 'node:path';
20
25
  import { homedir } from 'node:os';
21
26
 
@@ -38,7 +43,14 @@ function getPackageVersion() {
38
43
  }
39
44
 
40
45
  /**
41
- * Read existing manifest. Returns { version, files } or null.
46
+ * Compute SHA-256 hash of file content.
47
+ */
48
+ function fileHash(filePath) {
49
+ return createHash('sha256').update(readFileSync(filePath)).digest('hex');
50
+ }
51
+
52
+ /**
53
+ * Read existing manifest. Returns { version, files, hashes } or null.
42
54
  */
43
55
  function readManifest() {
44
56
  try {
@@ -101,30 +113,48 @@ export function fetchAdapters() {
101
113
 
102
114
  const newOfficialFiles = new Set(walkFiles(BUILTIN_CLIS));
103
115
  const oldOfficialFiles = new Set(oldManifest?.files ?? []);
116
+ const oldHashes = oldManifest?.hashes ?? {};
104
117
  mkdirSync(USER_CLIS_DIR, { recursive: true });
105
118
 
106
- // 1. Copy official files (unconditionally overwrite)
107
- let copied = 0;
119
+ // 1. Compute new hashes and detect which sites have changes
120
+ const newHashes = {};
121
+ const siteFiles = new Map(); // site -> [relPath, ...]
108
122
  for (const relPath of newOfficialFiles) {
109
123
  const src = join(BUILTIN_CLIS, relPath);
110
- const dst = join(USER_CLIS_DIR, relPath);
111
- mkdirSync(dirname(dst), { recursive: true });
112
- cpSync(src, dst, { force: true });
113
- copied++;
124
+ const srcHash = fileHash(src);
125
+ newHashes[relPath] = srcHash;
126
+
127
+ const site = relPath.split('/')[0];
128
+ if (!siteFiles.has(site)) siteFiles.set(site, []);
129
+ siteFiles.get(site).push(relPath);
114
130
  }
115
131
 
116
- // 2. Remove files that were official but are no longer (upstream deleted)
117
- let removed = 0;
132
+ // Determine which sites have any changed/new/removed files
133
+ const changedSites = new Set();
134
+ for (const [site, files] of siteFiles) {
135
+ for (const relPath of files) {
136
+ if (oldHashes[relPath] !== newHashes[relPath]) {
137
+ changedSites.add(site);
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ // Also mark sites that had files removed
118
143
  for (const relPath of oldOfficialFiles) {
119
144
  if (!newOfficialFiles.has(relPath)) {
120
- const dst = join(USER_CLIS_DIR, relPath);
121
- try {
122
- unlinkSync(dst);
123
- pruneEmptyDirs(dst, USER_CLIS_DIR);
124
- removed++;
125
- } catch {
126
- // File may not exist locally
127
- }
145
+ changedSites.add(relPath.split('/')[0]);
146
+ }
147
+ }
148
+
149
+ // 2. Sparse cleanup: for changed/removed official sites, delete local overrides.
150
+ // Do NOT copy new versions — runtime falls back to package baseline.
151
+ // Only eject-ed sites live in ~/.opencli/clis/.
152
+ let cleared = 0;
153
+ for (const site of changedSites) {
154
+ const siteDir = join(USER_CLIS_DIR, site);
155
+ if (existsSync(siteDir)) {
156
+ rmSync(siteDir, { recursive: true, force: true });
157
+ cleared++;
128
158
  }
129
159
  }
130
160
 
@@ -206,15 +236,16 @@ export function fetchAdapters() {
206
236
  log(`Cleaned up${legacyCleaned > 0 ? ` ${legacyCleaned} legacy shim files` : ''}${tmpCleaned > 0 ? `${legacyCleaned > 0 ? ',' : ''} ${tmpCleaned} stale tmp files` : ''}`);
207
237
  }
208
238
 
209
- // 6. Write updated manifest
239
+ // 6. Write updated manifest (with per-file hashes for smart sync)
210
240
  writeFileSync(MANIFEST_PATH, JSON.stringify({
211
241
  version: currentVersion,
212
242
  files: [...newOfficialFiles].sort(),
243
+ hashes: newHashes,
213
244
  updatedAt: new Date().toISOString(),
214
245
  }, null, 2));
215
246
 
216
- log(`Installed ${copied} adapter files to ${USER_CLIS_DIR}` +
217
- (removed > 0 ? `, removed ${removed} deprecated files` : ''));
247
+ log(`Synced adapters: ${cleared} local override(s) cleared` +
248
+ (tsCleaned > 0 ? `, ${tsCleaned} stale .ts files removed` : ''));
218
249
  }
219
250
 
220
251
  function main() {
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'asks',
5
- description: 'Order book ask prices for a trading pair',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
11
- { name: 'limit', type: 'int', default: 10, help: 'Number of price levels (5, 10, 20, 50, 100)' },
12
- ],
13
- columns: ['rank', 'ask_price', 'ask_qty'],
14
- pipeline: [
15
- { fetch: { url: 'https://data-api.binance.vision/api/v3/depth?symbol=${{ args.symbol }}&limit=${{ args.limit }}' } },
16
- { select: 'asks' },
17
- { map: { rank: '${{ index + 1 }}', ask_price: '${{ item.0 }}', ask_qty: '${{ item.1 }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1,3 +0,0 @@
1
- import './top.js';
2
- import './gainers.js';
3
- import './pairs.js';
@@ -1,58 +0,0 @@
1
- import { getRegistry } from '@jackwener/opencli/registry';
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
3
- import { executePipeline } from '../../pipeline/index.js';
4
- // Import all binance adapters to register them
5
- import './top.js';
6
- import './gainers.js';
7
- import './pairs.js';
8
- function loadPipeline(name) {
9
- const cmd = getRegistry().get(`binance/${name}`);
10
- if (!cmd?.pipeline)
11
- throw new Error(`Command binance/${name} not found or has no pipeline`);
12
- return cmd.pipeline;
13
- }
14
- function mockJsonOnce(payload) {
15
- vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
16
- ok: true,
17
- status: 200,
18
- statusText: 'OK',
19
- json: vi.fn().mockResolvedValue(payload),
20
- }));
21
- }
22
- afterEach(() => {
23
- vi.unstubAllGlobals();
24
- vi.restoreAllMocks();
25
- });
26
- describe('binance adapters', () => {
27
- it('sorts top pairs by numeric quote volume', async () => {
28
- mockJsonOnce([
29
- { symbol: 'SMALL', lastPrice: '1', priceChangePercent: '1.2', highPrice: '1', lowPrice: '1', quoteVolume: '9.9' },
30
- { symbol: 'LARGE', lastPrice: '2', priceChangePercent: '2.3', highPrice: '2', lowPrice: '2', quoteVolume: '100.0' },
31
- { symbol: 'MID', lastPrice: '3', priceChangePercent: '3.4', highPrice: '3', lowPrice: '3', quoteVolume: '11.0' },
32
- ]);
33
- const result = await executePipeline(null, loadPipeline('top'), { args: { limit: 3 } });
34
- expect(result.map((item) => item.symbol)).toEqual(['LARGE', 'MID', 'SMALL']);
35
- expect(result.map((item) => item.rank)).toEqual([1, 2, 3]);
36
- });
37
- it('sorts gainers by numeric percent change', async () => {
38
- mockJsonOnce([
39
- { symbol: 'TEN', lastPrice: '1', priceChangePercent: '10.0', quoteVolume: '100' },
40
- { symbol: 'NINE', lastPrice: '1', priceChangePercent: '9.5', quoteVolume: '100' },
41
- { symbol: 'HUNDRED', lastPrice: '1', priceChangePercent: '100.0', quoteVolume: '100' },
42
- ]);
43
- const result = await executePipeline(null, loadPipeline('gainers'), { args: { limit: 3 } });
44
- expect(result.map((item) => item.symbol)).toEqual(['HUNDRED', 'TEN', 'NINE']);
45
- });
46
- it('keeps only TRADING pairs', async () => {
47
- mockJsonOnce({
48
- symbols: [
49
- { symbol: 'BTCUSDT', baseAsset: 'BTC', quoteAsset: 'USDT', status: 'TRADING' },
50
- { symbol: 'OLDPAIR', baseAsset: 'OLD', quoteAsset: 'USDT', status: 'BREAK' },
51
- ],
52
- });
53
- const result = await executePipeline(null, loadPipeline('pairs'), { args: { limit: 10 } });
54
- expect(result).toEqual([
55
- { symbol: 'BTCUSDT', base: 'BTC', quote: 'USDT', status: 'TRADING' },
56
- ]);
57
- });
58
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'depth',
5
- description: 'Order book bid prices for a trading pair',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
11
- { name: 'limit', type: 'int', default: 10, help: 'Number of price levels (5, 10, 20, 50, 100)' },
12
- ],
13
- columns: ['rank', 'bid_price', 'bid_qty'],
14
- pipeline: [
15
- { fetch: { url: 'https://data-api.binance.vision/api/v3/depth?symbol=${{ args.symbol }}&limit=${{ args.limit }}' } },
16
- { select: 'bids' },
17
- { map: { rank: '${{ index + 1 }}', bid_price: '${{ item.0 }}', bid_qty: '${{ item.1 }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'gainers',
5
- description: 'Top gaining trading pairs by 24h price change',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 10, help: 'Number of trading pairs' },
11
- ],
12
- columns: ['rank', 'symbol', 'price', 'change_24h', 'volume'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/24hr' } },
15
- { filter: 'item.priceChangePercent' },
16
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', volume: '${{ item.quoteVolume }}', sort_change: '${{ Number(item.priceChangePercent) }}' } },
17
- { sort: { by: 'sort_change', order: 'desc' } },
18
- { map: { rank: '${{ index + 1 }}', symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', volume: '${{ item.quoteVolume }}' } },
19
- { limit: '${{ args.limit }}' },
20
- ],
21
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'klines',
5
- description: 'Candlestick/kline data for a trading pair',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
11
- { name: 'interval', type: 'str', default: '1d', help: 'Kline interval (1m, 5m, 15m, 1h, 4h, 1d, 1w, 1M)' },
12
- { name: 'limit', type: 'int', default: 10, help: 'Number of klines (max 1000)' },
13
- ],
14
- columns: ['open', 'high', 'low', 'close', 'volume'],
15
- pipeline: [
16
- { fetch: { url: 'https://data-api.binance.vision/api/v3/klines?symbol=${{ args.symbol }}&interval=${{ args.interval }}&limit=${{ args.limit }}' } },
17
- { map: { open: '${{ item.1 }}', high: '${{ item.2 }}', low: '${{ item.3 }}', close: '${{ item.4 }}', volume: '${{ item.5 }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'losers',
5
- description: 'Top losing trading pairs by 24h price change',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 10, help: 'Number of trading pairs' },
11
- ],
12
- columns: ['rank', 'symbol', 'price', 'change_24h', 'volume'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/24hr' } },
15
- { filter: 'item.priceChangePercent' },
16
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', volume: '${{ item.quoteVolume }}', sort_change: '${{ Number(item.priceChangePercent) }}' } },
17
- { sort: { by: 'sort_change' } },
18
- { map: { rank: '${{ index + 1 }}', symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', volume: '${{ item.quoteVolume }}' } },
19
- { limit: '${{ args.limit }}' },
20
- ],
21
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'pairs',
5
- description: 'List active trading pairs on Binance',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 20, help: 'Number of trading pairs' },
11
- ],
12
- columns: ['symbol', 'base', 'quote', 'status'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/exchangeInfo' } },
15
- { select: 'symbols' },
16
- { filter: 'item.status === \'TRADING\'' },
17
- { map: { symbol: '${{ item.symbol }}', base: '${{ item.baseAsset }}', quote: '${{ item.quoteAsset }}', status: '${{ item.status }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'price',
5
- description: 'Quick price check for a trading pair',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
11
- ],
12
- columns: ['symbol', 'price', 'change', 'change_pct', 'high', 'low', 'volume', 'quote_volume', 'trades'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/24hr?symbol=${{ args.symbol }}' } },
15
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change: '${{ item.priceChange }}', change_pct: '${{ item.priceChangePercent }}', high: '${{ item.highPrice }}', low: '${{ item.lowPrice }}', volume: '${{ item.volume }}', quote_volume: '${{ item.quoteVolume }}', trades: '${{ item.count }}' } },
16
- ],
17
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,18 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'prices',
5
- description: 'Latest prices for all trading pairs',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 20, help: 'Number of prices' },
11
- ],
12
- columns: ['rank', 'symbol', 'price'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/price' } },
15
- { map: { rank: '${{ index + 1 }}', symbol: '${{ item.symbol }}', price: '${{ item.price }}' } },
16
- { limit: '${{ args.limit }}' },
17
- ],
18
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'ticker',
5
- description: '24h ticker statistics for top trading pairs by volume',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 20, help: 'Number of tickers' },
11
- ],
12
- columns: ['symbol', 'price', 'change_pct', 'high', 'low', 'volume', 'quote_vol', 'trades'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/24hr' } },
15
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_pct: '${{ item.priceChangePercent }}', high: '${{ item.highPrice }}', low: '${{ item.lowPrice }}', volume: '${{ item.volume }}', quote_vol: '${{ item.quoteVolume }}', trades: '${{ item.count }}', sort_volume: '${{ Number(item.quoteVolume) }}' } },
16
- { sort: { by: 'sort_volume', order: 'desc' } },
17
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_pct: '${{ item.priceChangePercent }}', high: '${{ item.highPrice }}', low: '${{ item.lowPrice }}', volume: '${{ item.volume }}', quote_vol: '${{ item.quoteVolume }}', trades: '${{ item.count }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'top',
5
- description: 'Top trading pairs by 24h volume on Binance',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'limit', type: 'int', default: 20, help: 'Number of trading pairs' },
11
- ],
12
- columns: ['rank', 'symbol', 'price', 'change_24h', 'high', 'low', 'volume'],
13
- pipeline: [
14
- { fetch: { url: 'https://data-api.binance.vision/api/v3/ticker/24hr' } },
15
- { map: { symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', high: '${{ item.highPrice }}', low: '${{ item.lowPrice }}', volume: '${{ item.quoteVolume }}', sort_volume: '${{ Number(item.quoteVolume) }}' } },
16
- { sort: { by: 'sort_volume', order: 'desc' } },
17
- { map: { rank: '${{ index + 1 }}', symbol: '${{ item.symbol }}', price: '${{ item.lastPrice }}', change_24h: '${{ item.priceChangePercent }}', high: '${{ item.highPrice }}', low: '${{ item.lowPrice }}', volume: '${{ item.quoteVolume }}' } },
18
- { limit: '${{ args.limit }}' },
19
- ],
20
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- import { cli, Strategy } from '@jackwener/opencli/registry';
2
- cli({
3
- site: 'binance',
4
- name: 'trades',
5
- description: 'Recent trades for a trading pair',
6
- domain: 'data-api.binance.vision',
7
- strategy: Strategy.PUBLIC,
8
- browser: false,
9
- args: [
10
- { name: 'symbol', type: 'str', required: true, positional: true, help: 'Trading pair symbol (e.g. BTCUSDT, ETHUSDT)' },
11
- { name: 'limit', type: 'int', default: 20, help: 'Number of trades (max 1000)' },
12
- ],
13
- columns: ['id', 'price', 'qty', 'quote_qty', 'buyer_maker'],
14
- pipeline: [
15
- { fetch: { url: 'https://data-api.binance.vision/api/v3/trades?symbol=${{ args.symbol }}&limit=${{ args.limit }}' } },
16
- { map: { id: '${{ item.id }}', price: '${{ item.price }}', qty: '${{ item.qty }}', quote_qty: '${{ item.quoteQty }}', buyer_maker: '${{ item.isBuyerMaker }}' } },
17
- { limit: '${{ args.limit }}' },
18
- ],
19
- });