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 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
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
9
+ ![Node Version](https://img.shields.io/badge/node-%3E%3D20.0.0-green.svg)
10
+
6
11
  ```text
7
12
  ▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄ ▄▄
8
13
  ████▄ ▄████ ██ ██ ██ ▄██▀▀██▄ ██
@@ -17,11 +22,7 @@
17
22
  <a name="english"></a>
18
23
  ## English
19
24
 
20
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
21
- ![Node Version](https://img.shields.io/badge/node-%3E%3D20.0.0-green.svg)
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
- #### 🌟 Recommended: NPM Global Install
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
- #### 📦 Other Installation Methods
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
- ![Dashboard Preview](./screenshot/dashboard.png)
67
+ ![Dashboard Preview](./screenshot/dashboard.jpg)
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
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
151
- ![Node Version](https://img.shields.io/badge/node-%3E%3D20.0.0-green.svg)
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
- #### 🌟 推荐方式:NPM 全局安装
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
- ![Dashboard Preview](./screenshot/dashboard.png)
195
+ ![Dashboard Preview](./screenshot/dashboard.jpg)
198
196
 
199
197
  * **用法**: `methodalgo dashboard` (别名: `top`)
200
198
  * **操作**: 使用 `TAB` 切换面板,`UP/DOWN` 滚动列表,`ENTER` 弹出详情。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "methodalgo-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -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) clearInterval(dataTimerRef.current); return; }
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 = (item, overrideDir, type) => {
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 combined = preferNew ? [...items, ...existing] : [...items.filter(i => !new Set(existing.map(e => e.id)).has(i.id)), ...existing];
376
+ const existingIds = new Set(existing.map(e => e.id)); // 提前构建 Set,避免 O()
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
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
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
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
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
- if (dataTimerRef.current) clearTimeout(dataTimerRef.current);
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
- refreshData();
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 处理