fogact 1.2.5 → 1.2.7

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.
@@ -7,6 +7,7 @@ const DATA_DIR = path.join(__dirname, "..", "..", "data");
7
7
  const USERS_FILE = path.join(DATA_DIR, "users.json");
8
8
  const CODES_FILE = path.join(DATA_DIR, "codes.json");
9
9
  const USAGE_FILE = path.join(DATA_DIR, "usage.json");
10
+ const CARD_MERGES_FILE = path.join(DATA_DIR, "card-merges.json");
10
11
 
11
12
  // 确保数据目录存在
12
13
  function ensureDataDir() {
@@ -136,6 +137,11 @@ const codeDb = {
136
137
  name: codeData.name || `Code-${Date.now()}`,
137
138
  service: codeData.service || "Claude Code",
138
139
  category: codeData.category || "标准运营",
140
+ subServiceType: codeData.subServiceType || codeData.category || "标准运营",
141
+ billingType: codeData.billingType || codeData.quota?.billingType || codeData.quota?.type || "quota",
142
+ cycleType: codeData.cycleType || codeData.quota?.cycleType || codeData.quota?.type || "fixed",
143
+ quotaUnit: codeData.quotaUnit || codeData.quota?.unit || "tokens",
144
+ resetTimezone: codeData.resetTimezone || codeData.quota?.resetTimezone || "Asia/Shanghai",
139
145
  status: codeData.status || "未使用",
140
146
  enabled: codeData.enabled !== undefined ? codeData.enabled : true,
141
147
  usedBy: codeData.usedBy || null,
@@ -148,9 +154,14 @@ const codeDb = {
148
154
  total: codeData.quota?.total || 100000,
149
155
  used: codeData.quota?.used || 0,
150
156
  dailyLimit: codeData.quota?.dailyLimit || 5000,
157
+ dailyQuota: codeData.quota?.dailyQuota || codeData.quota?.dailyLimit || 5000,
151
158
  dailyUsed: codeData.quota?.dailyUsed || 0,
152
159
  periodDays: codeData.quota?.periodDays || 30,
153
160
  periodLimit: codeData.quota?.periodLimit || 50000,
161
+ billingType: codeData.billingType || codeData.quota?.billingType || codeData.quota?.type || "quota",
162
+ cycleType: codeData.cycleType || codeData.quota?.cycleType || codeData.quota?.type || "fixed",
163
+ unit: codeData.quotaUnit || codeData.quota?.unit || "tokens",
164
+ resetTimezone: codeData.resetTimezone || codeData.quota?.resetTimezone || "Asia/Shanghai",
154
165
  },
155
166
  // 计费设置
156
167
  billing: {
@@ -303,6 +314,52 @@ const usageDb = {
303
314
  },
304
315
  };
305
316
 
317
+ // 叠卡合并记录数据库操作
318
+ const cardMergeDb = {
319
+ getAll() {
320
+ return readJsonFile(CARD_MERGES_FILE, []);
321
+ },
322
+
323
+ create(mergeData) {
324
+ const items = this.getAll();
325
+ const now = new Date().toISOString();
326
+ const newItem = {
327
+ id: mergeData.id || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
328
+ parentCodeId: mergeData.parentCodeId,
329
+ parentCode: mergeData.parentCode,
330
+ childCodeId: mergeData.childCodeId,
331
+ childCode: mergeData.childCode,
332
+ serviceType: mergeData.serviceType,
333
+ subServiceType: mergeData.subServiceType || "",
334
+ billingType: mergeData.billingType,
335
+ cycleType: mergeData.cycleType,
336
+ quotaUnit: mergeData.quotaUnit,
337
+ timezone: mergeData.timezone || "Asia/Shanghai",
338
+ mergeMode: mergeData.mergeMode,
339
+ parentDailyQuota: Number(mergeData.parentDailyQuota || 0),
340
+ childDailyQuota: Number(mergeData.childDailyQuota || 0),
341
+ childDays: Number(mergeData.childDays || 0),
342
+ childTotalValue: Number(mergeData.childTotalValue || 0),
343
+ addedDays: Number(mergeData.addedDays || 0),
344
+ addedQuota: Number(mergeData.addedQuota || 0),
345
+ oldExpiresAt: mergeData.oldExpiresAt || null,
346
+ newExpiresAt: mergeData.newExpiresAt || null,
347
+ createdAt: mergeData.createdAt || now,
348
+ };
349
+ items.push(newItem);
350
+ writeJsonFile(CARD_MERGES_FILE, items);
351
+ return newItem;
352
+ },
353
+
354
+ getByParent(parentCodeId) {
355
+ return this.getAll().filter((item) => item.parentCodeId === parentCodeId);
356
+ },
357
+
358
+ getByChild(childCodeId) {
359
+ return this.getAll().filter((item) => item.childCodeId === childCodeId);
360
+ },
361
+ };
362
+
306
363
  // 初始化示例数据
307
364
  function initializeSampleData() {
308
365
  ensureDataDir();
@@ -504,5 +561,6 @@ module.exports = {
504
561
  userDb,
505
562
  codeDb,
506
563
  usageDb,
564
+ cardMergeDb,
507
565
  initializeSampleData,
508
566
  };
@@ -1,38 +1,122 @@
1
1
  "use strict";
2
2
 
3
+ const { execFile } = require("child_process");
4
+ const net = require("net");
3
5
  const { testNode } = require("./fogact-api");
4
6
 
7
+ const PROBE_TIMEOUT_MS = 5000;
8
+
9
+ function parseNodeUrl(nodeUrl) {
10
+ const url = new URL(nodeUrl);
11
+ return {
12
+ hostname: url.hostname,
13
+ port: Number(url.port || (url.protocol === "https:" ? 443 : 80)),
14
+ protocol: url.protocol,
15
+ };
16
+ }
17
+
18
+ function execPing(hostname) {
19
+ return new Promise((resolve) => {
20
+ const start = Date.now();
21
+ const child = execFile("ping", ["-c", "1", "-W", "2", hostname], { timeout: 3000 }, (error, stdout) => {
22
+ if (error) {
23
+ resolve({ ok: false, latency: -1, error: error.message });
24
+ return;
25
+ }
26
+ const match = String(stdout).match(/time[=<]([0-9.]+)\s*ms/i);
27
+ resolve({ ok: true, latency: match ? Math.round(Number(match[1])) : Date.now() - start });
28
+ });
29
+ child.on("error", (error) => resolve({ ok: false, latency: -1, error: error.message }));
30
+ });
31
+ }
32
+
33
+ function testTcp(hostname, port, timeout = PROBE_TIMEOUT_MS) {
34
+ return new Promise((resolve) => {
35
+ const start = Date.now();
36
+ const socket = net.createConnection({ host: hostname, port });
37
+ let settled = false;
38
+
39
+ const done = (result) => {
40
+ if (settled) return;
41
+ settled = true;
42
+ socket.destroy();
43
+ resolve(result);
44
+ };
45
+
46
+ socket.setTimeout(timeout);
47
+ socket.once("connect", () => done({ ok: true, latency: Date.now() - start }));
48
+ socket.once("timeout", () => done({ ok: false, latency: -1, error: "TCP timeout" }));
49
+ socket.once("error", (error) => done({ ok: false, latency: -1, error: error.message }));
50
+ });
51
+ }
52
+
53
+ async function probeNode(node) {
54
+ const { hostname, port } = parseNodeUrl(node.url);
55
+ const [ping, tcp, http] = await Promise.all([
56
+ execPing(hostname),
57
+ testTcp(hostname, port),
58
+ testNode(node.url),
59
+ ]);
60
+
61
+ return {
62
+ ping,
63
+ tcp,
64
+ http: {
65
+ ok: Boolean(http.available),
66
+ latency: Number(http.latency || -1),
67
+ error: http.error,
68
+ },
69
+ };
70
+ }
71
+
72
+ function getSuccessfulLatencies(probes) {
73
+ return probes.flatMap((probe) => [probe.ping, probe.tcp, probe.http])
74
+ .filter((entry) => entry && entry.ok && Number(entry.latency) >= 0)
75
+ .map((entry) => Number(entry.latency));
76
+ }
77
+
78
+ function summarizeProbeResults(probes) {
79
+ const last = probes[probes.length - 1] || {};
80
+ const latencies = getSuccessfulLatencies(probes);
81
+ const avgLatency = latencies.length
82
+ ? Math.round(latencies.reduce((sum, value) => sum + value, 0) / latencies.length)
83
+ : -1;
84
+ const latencyStdDev = latencies.length
85
+ ? Math.round(Math.sqrt(latencies.reduce((sum, value) => sum + Math.pow(value - avgLatency, 2), 0) / latencies.length))
86
+ : 0;
87
+ const successRate = probes.length
88
+ ? probes.filter((probe) => probe.tcp?.ok && probe.http?.ok).length / probes.length
89
+ : 0;
90
+ const available = probes.some((probe) => probe.tcp?.ok && probe.http?.ok);
91
+
92
+ return {
93
+ available,
94
+ reachable: available,
95
+ latency: avgLatency,
96
+ avgLatency,
97
+ latencyStdDev,
98
+ successRate,
99
+ ping: last.ping || { ok: false, latency: -1 },
100
+ tcp: last.tcp || { ok: false, latency: -1 },
101
+ http: last.http || { ok: false, latency: -1 },
102
+ error: available ? undefined : last.http?.error || last.tcp?.error || last.ping?.error || "节点不可达",
103
+ };
104
+ }
105
+
5
106
  async function testNodes(nodes, onProgress = null) {
6
107
  const results = [];
7
108
 
8
109
  for (const node of nodes) {
9
110
  const probes = [];
10
111
  for (let index = 0; index < 3; index += 1) {
11
- const result = await testNode(node.url);
12
- probes.push(result);
112
+ probes.push(await probeNode(node));
13
113
  }
14
114
 
15
- const availableProbes = probes.filter((probe) => probe.available);
16
- const latencies = availableProbes.map((probe) => probe.latency);
17
- const avgLatency = latencies.length
18
- ? Math.round(latencies.reduce((sum, value) => sum + value, 0) / latencies.length)
19
- : -1;
20
- const latencyStdDev = latencies.length
21
- ? Math.round(Math.sqrt(latencies.reduce((sum, value) => sum + Math.pow(value - avgLatency, 2), 0) / latencies.length))
22
- : 0;
23
- const successRate = availableProbes.length / probes.length;
24
- const available = successRate > 0 && avgLatency >= 0;
25
-
115
+ const summary = summarizeProbeResults(probes);
26
116
  results.push({
27
117
  ...node,
28
- available,
29
- reachable: available,
30
- latency: avgLatency,
31
- avgLatency,
32
- latencyStdDev,
33
- successRate,
34
- score: scoreNode({ available, avgLatency, latencyStdDev, successRate }),
35
- error: available ? undefined : probes.find((probe) => probe.error)?.error || "节点不可达",
118
+ ...summary,
119
+ score: scoreNode(summary),
36
120
  });
37
121
 
38
122
  if (onProgress) onProgress(node, results.length, nodes.length);
@@ -46,7 +130,9 @@ function scoreNode(result) {
46
130
  const latencyScore = Math.max(0, 100 - Math.round(result.avgLatency / 4));
47
131
  const stabilityScore = Math.max(0, 100 - result.latencyStdDev * 2);
48
132
  const reliabilityScore = Math.round((result.successRate || 0) * 100);
49
- return Math.round(latencyScore * 0.7 + stabilityScore * 0.2 + reliabilityScore * 0.1);
133
+ const pingBonus = result.ping?.ok ? 5 : 0;
134
+ const tcpBonus = result.tcp?.ok ? 5 : 0;
135
+ return Math.min(100, Math.round(latencyScore * 0.6 + stabilityScore * 0.2 + reliabilityScore * 0.2 + pingBonus + tcpBonus));
50
136
  }
51
137
 
52
138
  function getResultLatency(result) {
@@ -63,6 +149,8 @@ function getResultScore(result) {
63
149
  avgLatency: getResultLatency(result),
64
150
  latencyStdDev: result.latencyStdDev || 0,
65
151
  successRate: result.successRate || (result.available ? 1 : 0),
152
+ ping: result.ping,
153
+ tcp: result.tcp,
66
154
  });
67
155
  }
68
156
 
@@ -77,13 +165,6 @@ function sortNodeResults(results) {
77
165
  return [...results].sort((left, right) => getResultScore(right) - getResultScore(left) || getResultLatency(left) - getResultLatency(right));
78
166
  }
79
167
 
80
- function latencyLevel(latency) {
81
- if (latency <= 50) return "优秀";
82
- if (latency <= 100) return "良好";
83
- if (latency <= 300) return "一般";
84
- return "较慢";
85
- }
86
-
87
168
  function stabilityLabel(stdDev) {
88
169
  if (stdDev <= 5) return "稳定";
89
170
  if (stdDev <= 15) return "良好";
@@ -97,22 +178,21 @@ function padCell(value, width) {
97
178
  return `${text}${" ".repeat(Math.max(0, width - displayWidth))}`;
98
179
  }
99
180
 
181
+ function formatProbe(entry) {
182
+ if (!entry?.ok) return "--";
183
+ return `${entry.latency}ms`;
184
+ }
185
+
100
186
  function formatNodeLine(result, best) {
101
187
  const mark = result.available ? "✓" : "✗";
102
188
  const name = padCell(result.name || result.url || "FogAct", 12);
103
- const service = padCell(result.serviceLabel || result.region || "Global", 9);
104
189
 
105
190
  if (!result.available) {
106
- return ` ${mark} ${name} ${service} -- -- 不可达 --`;
191
+ return ` ${mark} ${name} ping:${formatProbe(result.ping)} tcp:${formatProbe(result.tcp)} http:${formatProbe(result.http)} 不可达`;
107
192
  }
108
193
 
109
- const latency = padCell(`${result.avgLatency}ms`, 8);
110
- const jitter = padCell(`±${result.latencyStdDev}ms`, 7);
111
- const stability = padCell(stabilityLabel(result.latencyStdDev), 6);
112
- const score = padCell(`${getResultScore(result)}分`, 5);
113
- const level = padCell(latencyLevel(result.avgLatency), 4);
114
194
  const bestMark = best && best === result ? " ★ 最优" : "";
115
- return ` ${mark} ${name} ${service} ${latency} ${jitter} ${stability} ${score} ${level}${bestMark}`;
195
+ return ` ${mark} ${name} ping:${formatProbe(result.ping)} tcp:${formatProbe(result.tcp)} http:${formatProbe(result.http)} ${result.avgLatency}ms (±${result.latencyStdDev}ms) ${stabilityLabel(result.latencyStdDev)} ${getResultScore(result)}分${bestMark}`;
116
196
  }
117
197
 
118
198
  function formatNodeResults(results, options = {}) {
@@ -123,21 +203,16 @@ function formatNodeResults(results, options = {}) {
123
203
  const title = options.title || "节点测试结果";
124
204
 
125
205
  lines.push(` ${title}`);
126
- lines.push(" ────────────────────────────────────────────────────────────");
127
- lines.push(" 状态 节点 服务 延迟 波动 稳定性 评分 等级");
128
- lines.push(" ────────────────────────────────────────────────────────────");
206
+ lines.push(" ───────────────────────────────────────────────────");
207
+ lines.push("");
129
208
 
130
209
  for (const result of sorted) {
131
210
  lines.push(formatNodeLine(result, best));
132
211
  }
133
212
 
134
- lines.push(" ────────────────────────────────────────────────────────────");
213
+ lines.push("");
214
+ lines.push(" ───────────────────────────────────────────────────");
135
215
  lines.push(` 测试完成,共 ${results.length} 个节点,${availableCount} 个可用`);
136
- if (best) {
137
- lines.push(` 推荐节点: ${best.name || best.url} · ${best.avgLatency}ms · ${getResultScore(best)}分`);
138
- } else {
139
- lines.push(" 暂无可用节点,请稍后重试或联系管理员");
140
- }
141
216
  return lines.join("\n");
142
217
  }
143
218
 
@@ -146,4 +221,6 @@ module.exports = {
146
221
  selectBestNode,
147
222
  formatNodeResults,
148
223
  sortNodeResults,
224
+ probeNode,
225
+ testTcp,
149
226
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fogact",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "FogAct activation helper for Claude Code and Codex",
5
5
  "keywords": [
6
6
  "fogact",