methodalgo-cli 1.0.1 → 1.0.3
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.
- package/README.md +13 -15
- package/package.json +1 -1
- package/src/commands/dashboard.js +23 -12
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
# 🚀 Methodalgo CLI
|
|
4
|
+
`methodalgo-cli` is an ultra-fast, professional L2 signals & tools terminal based on Node.js. Specially designed for traders and AI agents, it allows users to quickly obtain cryptocurrency market snapshots, news, and signals with deep optimization for LLM workflows.
|
|
4
5
|
|
|
5
6
|
[English](#english) | [中文](#中文)
|
|
7
|
+
|
|
8
|
+
[](https://opensource.org/licenses/ISC)
|
|
9
|
+

|
|
10
|
+
|
|
6
11
|
```text
|
|
7
12
|
▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄ ▄▄
|
|
8
13
|
████▄ ▄████ ██ ██ ██ ▄██▀▀██▄ ██
|
|
@@ -17,11 +22,7 @@
|
|
|
17
22
|
<a name="english"></a>
|
|
18
23
|
## English
|
|
19
24
|
|
|
20
|
-
[](https://opensource.org/licenses/ISC)
|
|
21
|
-

|
|
22
25
|
|
|
23
|
-
### Overview
|
|
24
|
-
`methodalgo-cli` is an ultra-fast, professional L2 signals & tools terminal based on Node.js. Specially designed for traders and AI agents, it allows users to quickly obtain cryptocurrency market snapshots, news, and signals with deep optimization for LLM workflows.
|
|
25
26
|
|
|
26
27
|
### 🚀 Highlights
|
|
27
28
|
|
|
@@ -34,13 +35,13 @@
|
|
|
34
35
|
|
|
35
36
|
### 🛠️ Installation
|
|
36
37
|
|
|
37
|
-
####
|
|
38
|
+
#### Recommended: NPM Global Install
|
|
38
39
|
This is the fastest and easiest way to upgrade. Ensure [Node.js](https://nodejs.org/) (v20+) is installed on your system:
|
|
39
40
|
```bash
|
|
40
41
|
npm install -g methodalgo-cli
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
####
|
|
44
|
+
#### Other Installation Methods
|
|
44
45
|
|
|
45
46
|
**1. Standalone Binary (No Node.js Required)**
|
|
46
47
|
Download the binary for your platform from [Releases](https://github.com/methodalgo/methodalgo-cli/releases). To make it globally accessible:
|
|
@@ -63,7 +64,7 @@ npm link # Link local command
|
|
|
63
64
|
#### 🖥️ Market Dashboard (`dashboard`)
|
|
64
65
|
Launch a real-time TUI (Terminal User Interface) dashboard for a global view of market insights, news, and signals.
|
|
65
66
|
|
|
66
|
-

|
|
67
68
|
|
|
68
69
|
* **Usage**: `methodalgo dashboard` (Alias: `top`)
|
|
69
70
|
* **Controls**: Use `TAB` to switch panels, `UP/DOWN` to scroll, and `ENTER` to view details.
|
|
@@ -147,13 +148,10 @@ Get multi-language crypto market news filtered by AI.
|
|
|
147
148
|
<a name="中文"></a>
|
|
148
149
|
## 中文
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-

|
|
152
|
-
|
|
153
|
-
### 项目概览
|
|
151
|
+
### 概览
|
|
154
152
|
`methodalgo-cli` 是一个基于 Node.js 开发的极速、专业的加密货币市场 L2 信号与工具终端。它专为交易者与 AI 代理设计,集成了市场快照、新闻与信号抓取,并针对 LLM 工作流进行了深度优化。
|
|
155
153
|
|
|
156
|
-
###
|
|
154
|
+
### 亮点
|
|
157
155
|
|
|
158
156
|
- ⚡ **极致速度**: 基于 Node SEA 打造,启动毫秒级。
|
|
159
157
|
- 🖼️ **终端绘图**: 深度适配 iTerm2,无需离开终端即可预览截图。
|
|
@@ -164,13 +162,13 @@ Get multi-language crypto market news filtered by AI.
|
|
|
164
162
|
|
|
165
163
|
### 🛠️ 安装指南
|
|
166
164
|
|
|
167
|
-
####
|
|
165
|
+
#### 推荐:NPM 全局安装
|
|
168
166
|
这是最快速、最易于升级的方式。确保您的系统已安装 [Node.js](https://nodejs.org/) (v20+):
|
|
169
167
|
```bash
|
|
170
168
|
npm install -g methodalgo-cli
|
|
171
169
|
```
|
|
172
170
|
|
|
173
|
-
####
|
|
171
|
+
#### 其他安装方式
|
|
174
172
|
|
|
175
173
|
**1. 独立二进制版 (无需 Node.js)**
|
|
176
174
|
从 [Releases](https://github.com/methodalgo/methodalgo-cli/releases) 下载对应平台的二进制文件。为了全局调用,建议:
|
|
@@ -194,7 +192,7 @@ npm link # 链接本地指令
|
|
|
194
192
|
#### 🖥️ 市场看板 (`dashboard`)
|
|
195
193
|
启动实时 TUI(终端用户界面)仪表盘,全局俯瞰市场洞察、新闻与信号。
|
|
196
194
|
|
|
197
|
-

|
|
198
196
|
|
|
199
197
|
* **用法**: `methodalgo dashboard` (别名: `top`)
|
|
200
198
|
* **操作**: 使用 `TAB` 切换面板,`UP/DOWN` 滚动列表,`ENTER` 弹出详情。
|
package/package.json
CHANGED
|
@@ -239,9 +239,18 @@ const Dashboard = () => {
|
|
|
239
239
|
const [statusInfo, setStatusInfo] = useState({ time: "", mem: "0", error: null });
|
|
240
240
|
const dataTimerRef = useRef(null);
|
|
241
241
|
const lastFetchRef = useRef(null); // 记录上次拉取时间,用于增量请求
|
|
242
|
+
const isFetchingRef = useRef(false); // 防止并发重入
|
|
243
|
+
const refreshRef = useRef(null); // 始终持有最新 refreshData 引用
|
|
242
244
|
const lang = getLang();
|
|
243
245
|
|
|
246
|
+
const scheduleRefresh = (delay = 60000) => {
|
|
247
|
+
if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
|
|
248
|
+
dataTimerRef.current = setTimeout(() => refreshRef.current?.(), delay);
|
|
249
|
+
};
|
|
250
|
+
|
|
244
251
|
const refreshData = async () => {
|
|
252
|
+
if (isFetchingRef.current) return; // 防止并发
|
|
253
|
+
isFetchingRef.current = true;
|
|
245
254
|
const memUsage = (process.memoryUsage().rss / 1024 / 1024).toFixed(1);
|
|
246
255
|
try {
|
|
247
256
|
const newsTypes = ["article", "breaking", "onchain", "report"];
|
|
@@ -262,7 +271,7 @@ const Dashboard = () => {
|
|
|
262
271
|
(r.status === "fulfilled" && r.value.data.status === false &&
|
|
263
272
|
(r.value.data.msg?.includes("auth") || r.value.data.msg?.includes("key")))
|
|
264
273
|
);
|
|
265
|
-
if (authFailed) { setAuthError(true); setLoading(false); if (dataTimerRef.current)
|
|
274
|
+
if (authFailed) { setAuthError(true); setLoading(false); if (dataTimerRef.current) clearTimeout(dataTimerRef.current); return; }
|
|
266
275
|
|
|
267
276
|
lastFetchRef.current = new Date().toISOString();
|
|
268
277
|
|
|
@@ -294,7 +303,8 @@ const Dashboard = () => {
|
|
|
294
303
|
return (res?.status === "fulfilled" && res.value.data.status) ? res.value.data.data : [];
|
|
295
304
|
};
|
|
296
305
|
|
|
297
|
-
const formatSig = (
|
|
306
|
+
const formatSig = (rawItem, overrideDir, type) => {
|
|
307
|
+
const item = { ...rawItem }; // 浅拷贝,避免直接修改原始数据
|
|
298
308
|
const sig = item.signals?.[0];
|
|
299
309
|
let breakPrice = sig?.details?.BreakPrice || sig?.breakPrice || sig?.break_price || item.breakPrice || item.break_price;
|
|
300
310
|
if (!breakPrice && sig?.fields) {
|
|
@@ -363,7 +373,8 @@ const Dashboard = () => {
|
|
|
363
373
|
};
|
|
364
374
|
|
|
365
375
|
const mergeAndSort = (items, existing, overrideDir, type, preferNew = false) => {
|
|
366
|
-
const
|
|
376
|
+
const existingIds = new Set(existing.map(e => e.id)); // 提前构建 Set,避免 O(n²)
|
|
377
|
+
const combined = preferNew ? [...items, ...existing] : [...items.filter(i => !existingIds.has(i.id)), ...existing];
|
|
367
378
|
const unique = Array.from(new Map(combined.map(item => [item.id, item])).values());
|
|
368
379
|
return unique.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).slice(0, 50).map(i => formatSig(i, overrideDir, type));
|
|
369
380
|
};
|
|
@@ -445,8 +456,7 @@ const Dashboard = () => {
|
|
|
445
456
|
return next;
|
|
446
457
|
});
|
|
447
458
|
setStatusInfo({ time: new Date().toLocaleTimeString(), mem: memUsage, error: null });
|
|
448
|
-
|
|
449
|
-
dataTimerRef.current = setTimeout(refreshData, 60000);
|
|
459
|
+
scheduleRefresh(60000);
|
|
450
460
|
} catch (error) {
|
|
451
461
|
if (error.status === 429) {
|
|
452
462
|
const secMatch = error.message.match(/(\d+)\s+seconds/);
|
|
@@ -455,21 +465,22 @@ const Dashboard = () => {
|
|
|
455
465
|
if (secMatch) delay = parseInt(secMatch[1]) * 1000 + 2000;
|
|
456
466
|
else if (minMatch) delay = parseInt(minMatch[1]) * 60000 + 2000;
|
|
457
467
|
setStatusInfo(prev => ({ ...prev, error: `Rate Limited (Retry in ${Math.round(delay / 1000)}s)` }));
|
|
458
|
-
|
|
459
|
-
dataTimerRef.current = setTimeout(refreshData, delay);
|
|
468
|
+
scheduleRefresh(delay);
|
|
460
469
|
} else {
|
|
461
470
|
setStatusInfo(prev => ({ ...prev, error: `Err: ${error.message.substring(0, 20)}` }));
|
|
462
|
-
|
|
463
|
-
dataTimerRef.current = setTimeout(refreshData, 60000);
|
|
471
|
+
scheduleRefresh(60000);
|
|
464
472
|
}
|
|
473
|
+
} finally {
|
|
474
|
+
isFetchingRef.current = false;
|
|
475
|
+
setLoading(false); // 确保任何路径退出都会重置 loading
|
|
465
476
|
}
|
|
466
|
-
setLoading(false);
|
|
467
477
|
};
|
|
478
|
+
refreshRef.current = refreshData; // 每次渲染后同步最新引用
|
|
468
479
|
|
|
469
480
|
useEffect(() => {
|
|
470
|
-
|
|
481
|
+
refreshRef.current(); // 挂载时首次拉取(ref 已在渲染体 L476 同步)
|
|
471
482
|
return () => { if (dataTimerRef.current) clearTimeout(dataTimerRef.current); };
|
|
472
|
-
}, []);
|
|
483
|
+
}, []); // 仅挂载执行一次,后续刷新由 scheduleRefresh + refreshRef 统一管理
|
|
473
484
|
|
|
474
485
|
useInput((input, key) => {
|
|
475
486
|
if (dialog) return; // 弹窗时由 DetailDialog 处理
|