fogact 1.1.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/LICENSE +21 -0
- package/README.md +244 -0
- package/README.zh-CN.md +244 -0
- package/bin/cli.js +9 -0
- package/bin/web-server.js +1434 -0
- package/config/upstream.example.json +14 -0
- package/frontend/activate.html +249 -0
- package/frontend/admin/admin-panel-v2.js +1899 -0
- package/frontend/admin/index.html +705 -0
- package/frontend/assets/market-ui.css +1876 -0
- package/frontend/color-test.html +136 -0
- package/frontend/index.html +191 -0
- package/frontend/user/assets/AnnouncementDetail-Dvxmwz0A.js +12 -0
- package/frontend/user/assets/Announcements-CS1tF2mx.js +11 -0
- package/frontend/user/assets/CardBind-CsCxihhP.js +21 -0
- package/frontend/user/assets/CardContent.vue_vue_type_script_setup_true_lang-D2L-uqSl.js +1 -0
- package/frontend/user/assets/CardDescription.vue_vue_type_script_setup_true_lang-D-v5Pl7F.js +1 -0
- package/frontend/user/assets/CardTitle.vue_vue_type_script_setup_true_lang-a0CCN6D5.js +1 -0
- package/frontend/user/assets/Dashboard-rPsmltm5.js +51 -0
- package/frontend/user/assets/DashboardLayout-BUCWGlXC.css +1 -0
- package/frontend/user/assets/DashboardLayout-DDkxHYFj.js +80 -0
- package/frontend/user/assets/Input.vue_vue_type_script_setup_true_lang-B0SyPmYb.js +6 -0
- package/frontend/user/assets/Label.vue_vue_type_script_setup_true_lang-CxYORSgN.js +1 -0
- package/frontend/user/assets/Progress.vue_vue_type_script_setup_true_lang-2_QbPsEQ.js +1 -0
- package/frontend/user/assets/QuotaPack-B_tJ7Psm.js +6 -0
- package/frontend/user/assets/Renewal-BSDhDmwv.js +6 -0
- package/frontend/user/assets/ScrollArea.vue_vue_type_script_setup_true_lang-DMYwcfpz.js +1 -0
- package/frontend/user/assets/Separator.vue_vue_type_script_setup_true_lang-Ckg8EXj_.js +1 -0
- package/frontend/user/assets/Settings-CBdAa3lw.js +11 -0
- package/frontend/user/assets/TooltipTrigger.vue_vue_type_script_setup_true_lang-DtSBjzGo.js +16 -0
- package/frontend/user/assets/Welcome-7IfzEli4.css +1 -0
- package/frontend/user/assets/Welcome-Dtfp6oER.js +1 -0
- package/frontend/user/assets/_plugin-vue_export-helper-5cjT4u0R.js +16 -0
- package/frontend/user/assets/activity-wYWtyqTJ.js +6 -0
- package/frontend/user/assets/announcement-35mOnjRL.js +16 -0
- package/frontend/user/assets/calendar-BFNuCata.js +6 -0
- package/frontend/user/assets/chart-vendor-CULJE59K.js +37 -0
- package/frontend/user/assets/chevron-down-kDbuU1Py.js +6 -0
- package/frontend/user/assets/chevron-right-BayASIm0.js +6 -0
- package/frontend/user/assets/eye-CY62vip0.js +6 -0
- package/frontend/user/assets/gauge-C5NQ-mV8.js +6 -0
- package/frontend/user/assets/index-B8QSyYhS.css +1 -0
- package/frontend/user/assets/index-Da98HOxL.js +91 -0
- package/frontend/user/assets/link-2-DT5R5nGO.js +6 -0
- package/frontend/user/assets/package-rUbExUEn.js +6 -0
- package/frontend/user/assets/plus-CQc6C8wG.js +11 -0
- package/frontend/user/assets/refresh-cw-Y9hCloPL.js +6 -0
- package/frontend/user/assets/useUserPageRefresh-BYZvpNR9.js +1 -0
- package/frontend/user/assets/zap-l5zbZqrM.js +11 -0
- package/frontend/user/index.html +67 -0
- package/install.sh +402 -0
- package/lib/commands/activate.js +144 -0
- package/lib/commands/restore.js +102 -0
- package/lib/commands/test.js +40 -0
- package/lib/config/claude.js +81 -0
- package/lib/config/codex.js +164 -0
- package/lib/config/upstream.js +79 -0
- package/lib/index.js +164 -0
- package/lib/platforms/claude-code.js +35 -0
- package/lib/platforms/codex-cli.js +35 -0
- package/lib/platforms/editor-codex.js +138 -0
- package/lib/platforms/index.js +32 -0
- package/lib/platforms/openclaw.js +118 -0
- package/lib/platforms/opencode.js +89 -0
- package/lib/services/activation-orchestrator.js +666 -0
- package/lib/services/backup-service.js +162 -0
- package/lib/services/cliproxy-api.js +174 -0
- package/lib/services/database.js +461 -0
- package/lib/services/newapi.js +97 -0
- package/lib/services/node-service.js +49 -0
- package/lib/utils/json-file.js +33 -0
- package/package.json +53 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const DATA_DIR = path.join(__dirname, "..", "..", "data");
|
|
7
|
+
const USERS_FILE = path.join(DATA_DIR, "users.json");
|
|
8
|
+
const CODES_FILE = path.join(DATA_DIR, "codes.json");
|
|
9
|
+
|
|
10
|
+
// 确保数据目录存在
|
|
11
|
+
function ensureDataDir() {
|
|
12
|
+
if (!fs.existsSync(DATA_DIR)) {
|
|
13
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 读取 JSON 文件
|
|
18
|
+
function readJsonFile(filePath, defaultValue = []) {
|
|
19
|
+
try {
|
|
20
|
+
if (!fs.existsSync(filePath)) {
|
|
21
|
+
return defaultValue;
|
|
22
|
+
}
|
|
23
|
+
const data = fs.readFileSync(filePath, "utf8");
|
|
24
|
+
return JSON.parse(data);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(`读取文件失败 ${filePath}:`, err.message);
|
|
27
|
+
return defaultValue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 写入 JSON 文件
|
|
32
|
+
function writeJsonFile(filePath, data) {
|
|
33
|
+
try {
|
|
34
|
+
ensureDataDir();
|
|
35
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
36
|
+
return true;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`写入文件失败 ${filePath}:`, err.message);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 用户数据库操作
|
|
44
|
+
const userDb = {
|
|
45
|
+
getAll() {
|
|
46
|
+
return readJsonFile(USERS_FILE, []);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
getById(id) {
|
|
50
|
+
const users = this.getAll();
|
|
51
|
+
return users.find((u) => u.id === id);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
getByUsername(username) {
|
|
55
|
+
const users = this.getAll();
|
|
56
|
+
return users.find((u) => u.username === username);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
create(userData) {
|
|
60
|
+
const users = this.getAll();
|
|
61
|
+
const newUser = {
|
|
62
|
+
id: Date.now().toString(),
|
|
63
|
+
username: userData.username,
|
|
64
|
+
email: userData.email,
|
|
65
|
+
service: userData.service || "Claude Code",
|
|
66
|
+
status: userData.status || "待激活",
|
|
67
|
+
registeredAt: new Date().toISOString(),
|
|
68
|
+
...userData,
|
|
69
|
+
};
|
|
70
|
+
users.push(newUser);
|
|
71
|
+
writeJsonFile(USERS_FILE, users);
|
|
72
|
+
return newUser;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
update(id, updates) {
|
|
76
|
+
const users = this.getAll();
|
|
77
|
+
const index = users.findIndex((u) => u.id === id);
|
|
78
|
+
if (index === -1) return null;
|
|
79
|
+
|
|
80
|
+
users[index] = { ...users[index], ...updates, id };
|
|
81
|
+
writeJsonFile(USERS_FILE, users);
|
|
82
|
+
return users[index];
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
delete(id) {
|
|
86
|
+
const users = this.getAll();
|
|
87
|
+
const filtered = users.filter((u) => u.id !== id);
|
|
88
|
+
if (filtered.length === users.length) return false;
|
|
89
|
+
|
|
90
|
+
writeJsonFile(USERS_FILE, filtered);
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
search(query) {
|
|
95
|
+
const users = this.getAll();
|
|
96
|
+
if (!query) return users;
|
|
97
|
+
|
|
98
|
+
const lowerQuery = query.toLowerCase();
|
|
99
|
+
return users.filter(
|
|
100
|
+
(u) =>
|
|
101
|
+
u.username.toLowerCase().includes(lowerQuery) ||
|
|
102
|
+
u.email.toLowerCase().includes(lowerQuery) ||
|
|
103
|
+
u.id.includes(lowerQuery)
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
filterByStatus(status) {
|
|
108
|
+
const users = this.getAll();
|
|
109
|
+
if (!status || status === "all") return users;
|
|
110
|
+
return users.filter((u) => u.status === status);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// 激活码数据库操作
|
|
115
|
+
const codeDb = {
|
|
116
|
+
getAll() {
|
|
117
|
+
return readJsonFile(CODES_FILE, []);
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
getById(id) {
|
|
121
|
+
const codes = this.getAll();
|
|
122
|
+
return codes.find((c) => c.id === id);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
getByCode(code) {
|
|
126
|
+
const codes = this.getAll();
|
|
127
|
+
return codes.find((c) => c.code === code);
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
create(codeData) {
|
|
131
|
+
const codes = this.getAll();
|
|
132
|
+
const newCode = {
|
|
133
|
+
id: Date.now().toString(),
|
|
134
|
+
code: codeData.code || this.generateCode(),
|
|
135
|
+
name: codeData.name || `Code-${Date.now()}`,
|
|
136
|
+
service: codeData.service || "Claude Code",
|
|
137
|
+
category: codeData.category || "标准运营",
|
|
138
|
+
status: codeData.status || "未使用",
|
|
139
|
+
enabled: codeData.enabled !== undefined ? codeData.enabled : true,
|
|
140
|
+
usedBy: codeData.usedBy || null,
|
|
141
|
+
createdAt: new Date().toISOString(),
|
|
142
|
+
expiresAt: codeData.expiresAt || this.getDefaultExpiry(),
|
|
143
|
+
lastUsedAt: codeData.lastUsedAt || null,
|
|
144
|
+
batch: codeData.batch || null,
|
|
145
|
+
// 配额管理
|
|
146
|
+
quota: {
|
|
147
|
+
total: codeData.quota?.total || 100000,
|
|
148
|
+
used: codeData.quota?.used || 0,
|
|
149
|
+
dailyLimit: codeData.quota?.dailyLimit || 5000,
|
|
150
|
+
dailyUsed: codeData.quota?.dailyUsed || 0,
|
|
151
|
+
periodDays: codeData.quota?.periodDays || 30,
|
|
152
|
+
periodLimit: codeData.quota?.periodLimit || 50000,
|
|
153
|
+
},
|
|
154
|
+
// 计费设置
|
|
155
|
+
billing: {
|
|
156
|
+
mode: codeData.billing?.mode || "预付费",
|
|
157
|
+
balance: codeData.billing?.balance || 1000,
|
|
158
|
+
},
|
|
159
|
+
// 服务设置
|
|
160
|
+
serviceConfig: {
|
|
161
|
+
providerGroup: codeData.serviceConfig?.providerGroup || "全球一线节点",
|
|
162
|
+
routingStrategy: codeData.serviceConfig?.routingStrategy || "延迟优化",
|
|
163
|
+
autoFailover: codeData.serviceConfig?.autoFailover !== undefined ? codeData.serviceConfig.autoFailover : true,
|
|
164
|
+
},
|
|
165
|
+
// 有效期设置
|
|
166
|
+
validity: {
|
|
167
|
+
type: codeData.validity?.type || "固定天数",
|
|
168
|
+
days: codeData.validity?.days || 90,
|
|
169
|
+
activationCountdown: codeData.validity?.activationCountdown || 24,
|
|
170
|
+
},
|
|
171
|
+
// 风控管理
|
|
172
|
+
riskControl: {
|
|
173
|
+
maxDevices: codeData.riskControl?.maxDevices || 3,
|
|
174
|
+
maxConcurrent: codeData.riskControl?.maxConcurrent || 5,
|
|
175
|
+
geoLock: codeData.riskControl?.geoLock || ["CN"],
|
|
176
|
+
ipBinding: codeData.riskControl?.ipBinding || false,
|
|
177
|
+
},
|
|
178
|
+
// 技术参数
|
|
179
|
+
technical: {
|
|
180
|
+
region: codeData.technical?.region || "华东 1 (杭州)",
|
|
181
|
+
instanceId: codeData.technical?.instanceId || `ins-${Math.random().toString(36).substring(2, 10)}`,
|
|
182
|
+
},
|
|
183
|
+
...codeData,
|
|
184
|
+
};
|
|
185
|
+
codes.push(newCode);
|
|
186
|
+
writeJsonFile(CODES_FILE, codes);
|
|
187
|
+
return newCode;
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
update(id, updates) {
|
|
191
|
+
const codes = this.getAll();
|
|
192
|
+
const index = codes.findIndex((c) => c.id === id);
|
|
193
|
+
if (index === -1) return null;
|
|
194
|
+
|
|
195
|
+
codes[index] = { ...codes[index], ...updates, id };
|
|
196
|
+
writeJsonFile(CODES_FILE, codes);
|
|
197
|
+
return codes[index];
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
delete(id) {
|
|
201
|
+
const codes = this.getAll();
|
|
202
|
+
const filtered = codes.filter((c) => c.id !== id);
|
|
203
|
+
if (filtered.length === codes.length) return false;
|
|
204
|
+
|
|
205
|
+
writeJsonFile(CODES_FILE, filtered);
|
|
206
|
+
return true;
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
search(query) {
|
|
210
|
+
const codes = this.getAll();
|
|
211
|
+
if (!query) return codes;
|
|
212
|
+
|
|
213
|
+
const lowerQuery = query.toLowerCase();
|
|
214
|
+
return codes.filter(
|
|
215
|
+
(c) =>
|
|
216
|
+
c.code.toLowerCase().includes(lowerQuery) ||
|
|
217
|
+
(c.usedBy && c.usedBy.toLowerCase().includes(lowerQuery)) ||
|
|
218
|
+
c.id.includes(lowerQuery)
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
filterByStatus(status) {
|
|
223
|
+
const codes = this.getAll();
|
|
224
|
+
if (!status || status === "all") return codes;
|
|
225
|
+
return codes.filter((c) => c.status === status);
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
filterByService(service) {
|
|
229
|
+
const codes = this.getAll();
|
|
230
|
+
if (!service || service === "all") return codes;
|
|
231
|
+
return codes.filter((c) => c.service === service);
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
generateCode() {
|
|
235
|
+
// 格式: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
|
236
|
+
// 示例: VM2E8GPT-BBGN-P7MX-NQP7-SRY62GT3XU8D
|
|
237
|
+
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
238
|
+
|
|
239
|
+
const segments = [8, 4, 4, 4, 12];
|
|
240
|
+
const parts = [];
|
|
241
|
+
|
|
242
|
+
for (let i = 0; i < segments.length; i++) {
|
|
243
|
+
let segment = "";
|
|
244
|
+
for (let j = 0; j < segments[i]; j++) {
|
|
245
|
+
segment += chars[Math.floor(Math.random() * chars.length)];
|
|
246
|
+
}
|
|
247
|
+
parts.push(segment);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return parts.join("-");
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
getDefaultExpiry() {
|
|
254
|
+
const date = new Date();
|
|
255
|
+
date.setFullYear(date.getFullYear() + 1); // 1年后过期
|
|
256
|
+
return date.toISOString();
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// 初始化示例数据
|
|
261
|
+
function initializeSampleData() {
|
|
262
|
+
ensureDataDir();
|
|
263
|
+
|
|
264
|
+
// 初始化用户数据
|
|
265
|
+
if (!fs.existsSync(USERS_FILE)) {
|
|
266
|
+
const sampleUsers = [
|
|
267
|
+
{
|
|
268
|
+
id: "1001",
|
|
269
|
+
username: "user_001",
|
|
270
|
+
email: "user001@example.com",
|
|
271
|
+
service: "Claude Code",
|
|
272
|
+
status: "活跃",
|
|
273
|
+
registeredAt: "2026-04-01T00:00:00.000Z",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "1002",
|
|
277
|
+
username: "user_002",
|
|
278
|
+
email: "user002@example.com",
|
|
279
|
+
service: "Codex",
|
|
280
|
+
status: "活跃",
|
|
281
|
+
registeredAt: "2026-04-02T00:00:00.000Z",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "1003",
|
|
285
|
+
username: "user_003",
|
|
286
|
+
email: "user003@example.com",
|
|
287
|
+
service: "Claude Code",
|
|
288
|
+
status: "待激活",
|
|
289
|
+
registeredAt: "2026-04-03T00:00:00.000Z",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "1004",
|
|
293
|
+
username: "user_004",
|
|
294
|
+
email: "user004@example.com",
|
|
295
|
+
service: "Codex",
|
|
296
|
+
status: "已禁用",
|
|
297
|
+
registeredAt: "2026-04-04T00:00:00.000Z",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "1005",
|
|
301
|
+
username: "user_005",
|
|
302
|
+
email: "user005@example.com",
|
|
303
|
+
service: "Claude Code",
|
|
304
|
+
status: "活跃",
|
|
305
|
+
registeredAt: "2026-04-05T00:00:00.000Z",
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
writeJsonFile(USERS_FILE, sampleUsers);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 初始化激活码数据
|
|
312
|
+
if (!fs.existsSync(CODES_FILE)) {
|
|
313
|
+
const sampleCodes = [
|
|
314
|
+
{
|
|
315
|
+
id: "2001",
|
|
316
|
+
code: "CP-9821-XQ-001",
|
|
317
|
+
name: "Alpha Node Premium",
|
|
318
|
+
service: "Claude Code",
|
|
319
|
+
category: "高级版",
|
|
320
|
+
status: "已使用",
|
|
321
|
+
enabled: true,
|
|
322
|
+
usedBy: "user_001",
|
|
323
|
+
createdAt: "2026-03-15T00:00:00.000Z",
|
|
324
|
+
expiresAt: "2026-08-30T00:00:00.000Z",
|
|
325
|
+
lastUsedAt: "2026-04-06T14:20:00.000Z",
|
|
326
|
+
batch: "B2309-X",
|
|
327
|
+
quota: {
|
|
328
|
+
total: 80000,
|
|
329
|
+
used: 67600,
|
|
330
|
+
dailyLimit: 1200,
|
|
331
|
+
dailyUsed: 1200,
|
|
332
|
+
periodDays: 30,
|
|
333
|
+
periodLimit: 50000,
|
|
334
|
+
},
|
|
335
|
+
billing: {
|
|
336
|
+
mode: "预付费",
|
|
337
|
+
balance: 850,
|
|
338
|
+
},
|
|
339
|
+
serviceConfig: {
|
|
340
|
+
providerGroup: "全球一线节点",
|
|
341
|
+
routingStrategy: "延迟优化",
|
|
342
|
+
autoFailover: true,
|
|
343
|
+
},
|
|
344
|
+
validity: {
|
|
345
|
+
type: "固定天数",
|
|
346
|
+
days: 142,
|
|
347
|
+
activationCountdown: 24,
|
|
348
|
+
},
|
|
349
|
+
riskControl: {
|
|
350
|
+
maxDevices: 3,
|
|
351
|
+
maxConcurrent: 5,
|
|
352
|
+
geoLock: ["CN"],
|
|
353
|
+
ipBinding: false,
|
|
354
|
+
},
|
|
355
|
+
technical: {
|
|
356
|
+
region: "华东 1 (杭州)",
|
|
357
|
+
instanceId: "ins-88219-xa",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: "2002",
|
|
362
|
+
code: "CP-1104-LT-992",
|
|
363
|
+
name: "Beta Storage Instance",
|
|
364
|
+
service: "Codex",
|
|
365
|
+
category: "企业版",
|
|
366
|
+
status: "未使用",
|
|
367
|
+
enabled: false,
|
|
368
|
+
usedBy: null,
|
|
369
|
+
createdAt: "2026-04-01T00:00:00.000Z",
|
|
370
|
+
expiresAt: "2026-04-06T00:00:00.000Z",
|
|
371
|
+
lastUsedAt: "2023-10-01T09:12:00.000Z",
|
|
372
|
+
batch: "B2310-Z",
|
|
373
|
+
quota: {
|
|
374
|
+
total: 100000,
|
|
375
|
+
used: 0,
|
|
376
|
+
dailyLimit: 5000,
|
|
377
|
+
dailyUsed: 0,
|
|
378
|
+
periodDays: 30,
|
|
379
|
+
periodLimit: 50000,
|
|
380
|
+
},
|
|
381
|
+
billing: {
|
|
382
|
+
mode: "预付费",
|
|
383
|
+
balance: 0,
|
|
384
|
+
},
|
|
385
|
+
serviceConfig: {
|
|
386
|
+
providerGroup: "全球一线节点",
|
|
387
|
+
routingStrategy: "延迟优化",
|
|
388
|
+
autoFailover: true,
|
|
389
|
+
},
|
|
390
|
+
validity: {
|
|
391
|
+
type: "固定天数",
|
|
392
|
+
days: 0,
|
|
393
|
+
activationCountdown: 24,
|
|
394
|
+
},
|
|
395
|
+
riskControl: {
|
|
396
|
+
maxDevices: 3,
|
|
397
|
+
maxConcurrent: 5,
|
|
398
|
+
geoLock: ["CN"],
|
|
399
|
+
ipBinding: false,
|
|
400
|
+
},
|
|
401
|
+
technical: {
|
|
402
|
+
region: "华东 1 (杭州)",
|
|
403
|
+
instanceId: "ins-11049-lt",
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "2003",
|
|
408
|
+
code: "VIP-992-CLAUDE",
|
|
409
|
+
name: "VIP Claude Premium",
|
|
410
|
+
service: "Claude Code",
|
|
411
|
+
category: "高级版",
|
|
412
|
+
status: "已使用",
|
|
413
|
+
enabled: true,
|
|
414
|
+
usedBy: "user_002",
|
|
415
|
+
createdAt: "2026-03-20T00:00:00.000Z",
|
|
416
|
+
expiresAt: "2027-03-20T00:00:00.000Z",
|
|
417
|
+
lastUsedAt: "2026-04-06T10:30:00.000Z",
|
|
418
|
+
batch: "B2309-X",
|
|
419
|
+
quota: {
|
|
420
|
+
total: 500000,
|
|
421
|
+
used: 440000,
|
|
422
|
+
dailyLimit: 10000,
|
|
423
|
+
dailyUsed: 8500,
|
|
424
|
+
periodDays: 30,
|
|
425
|
+
periodLimit: 200000,
|
|
426
|
+
},
|
|
427
|
+
billing: {
|
|
428
|
+
mode: "预付费",
|
|
429
|
+
balance: 412.50,
|
|
430
|
+
},
|
|
431
|
+
serviceConfig: {
|
|
432
|
+
providerGroup: "全球一线节点",
|
|
433
|
+
routingStrategy: "延迟优化",
|
|
434
|
+
autoFailover: true,
|
|
435
|
+
},
|
|
436
|
+
validity: {
|
|
437
|
+
type: "固定天数",
|
|
438
|
+
days: 348,
|
|
439
|
+
activationCountdown: 24,
|
|
440
|
+
},
|
|
441
|
+
riskControl: {
|
|
442
|
+
maxDevices: 5,
|
|
443
|
+
maxConcurrent: 10,
|
|
444
|
+
geoLock: ["CN"],
|
|
445
|
+
ipBinding: false,
|
|
446
|
+
},
|
|
447
|
+
technical: {
|
|
448
|
+
region: "华东 1 (杭州)",
|
|
449
|
+
instanceId: "ins-vip992",
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
];
|
|
453
|
+
writeJsonFile(CODES_FILE, sampleCodes);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = {
|
|
458
|
+
userDb,
|
|
459
|
+
codeDb,
|
|
460
|
+
initializeSampleData,
|
|
461
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const http = require("http");
|
|
4
|
+
const https = require("https");
|
|
5
|
+
|
|
6
|
+
function buildModelsUrl(baseUrl) {
|
|
7
|
+
const normalized = String(baseUrl || "").replace(/\/+$/, "");
|
|
8
|
+
if (!normalized) {
|
|
9
|
+
throw new Error("NewAPI baseUrl is required");
|
|
10
|
+
}
|
|
11
|
+
return normalized.endsWith("/v1") ? `${normalized}/models` : `${normalized}/v1/models`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requestJson(urlString, options = {}) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const url = new URL(urlString);
|
|
17
|
+
const isHttps = url.protocol === "https:";
|
|
18
|
+
const client = isHttps ? https : http;
|
|
19
|
+
const timeoutMs = options.timeoutMs || 10000;
|
|
20
|
+
|
|
21
|
+
const req = client.request(
|
|
22
|
+
{
|
|
23
|
+
hostname: url.hostname,
|
|
24
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
25
|
+
path: `${url.pathname}${url.search}`,
|
|
26
|
+
method: options.method || "GET",
|
|
27
|
+
headers: options.headers || {},
|
|
28
|
+
},
|
|
29
|
+
(res) => {
|
|
30
|
+
let body = "";
|
|
31
|
+
res.on("data", (chunk) => {
|
|
32
|
+
body += chunk.toString();
|
|
33
|
+
});
|
|
34
|
+
res.on("end", () => {
|
|
35
|
+
let data = body;
|
|
36
|
+
try {
|
|
37
|
+
data = body ? JSON.parse(body) : null;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Keep raw response body.
|
|
40
|
+
}
|
|
41
|
+
resolve({ status: res.statusCode, data });
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
req.setTimeout(timeoutMs, () => {
|
|
47
|
+
req.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
|
|
48
|
+
});
|
|
49
|
+
req.on("error", reject);
|
|
50
|
+
req.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function verifyNewApiKey(config, apiKey) {
|
|
55
|
+
const modelsUrl = buildModelsUrl(config.baseUrl);
|
|
56
|
+
const response = await requestJson(modelsUrl, {
|
|
57
|
+
timeoutMs: config.timeoutMs,
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${apiKey}`,
|
|
60
|
+
Accept: "application/json",
|
|
61
|
+
"User-Agent": "fogact/1.1.3",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (response.status >= 200 && response.status < 300) {
|
|
66
|
+
return {
|
|
67
|
+
valid: true,
|
|
68
|
+
status: response.status,
|
|
69
|
+
modelsUrl,
|
|
70
|
+
models: Array.isArray(response.data && response.data.data) ? response.data.data : [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
valid: false,
|
|
76
|
+
status: response.status,
|
|
77
|
+
modelsUrl,
|
|
78
|
+
error: typeof response.data === "object" && response.data
|
|
79
|
+
? response.data.error?.message || response.data.message || JSON.stringify(response.data)
|
|
80
|
+
: String(response.data || `HTTP ${response.status}`),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function maskKey(apiKey) {
|
|
85
|
+
const value = String(apiKey || "");
|
|
86
|
+
if (value.length <= 10) {
|
|
87
|
+
return value ? "***" : "";
|
|
88
|
+
}
|
|
89
|
+
return `${value.slice(0, 6)}...${value.slice(-4)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
buildModelsUrl,
|
|
94
|
+
maskKey,
|
|
95
|
+
requestJson,
|
|
96
|
+
verifyNewApiKey,
|
|
97
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { testNode } = require("./cliproxy-api");
|
|
4
|
+
|
|
5
|
+
async function testNodes(nodes) {
|
|
6
|
+
const results = [];
|
|
7
|
+
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
const result = await testNode(node.url);
|
|
10
|
+
results.push({
|
|
11
|
+
...node,
|
|
12
|
+
...result,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return results;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function selectBestNode(testResults) {
|
|
20
|
+
const available = testResults.filter((r) => r.available);
|
|
21
|
+
|
|
22
|
+
if (available.length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
available.sort((a, b) => a.latency - b.latency);
|
|
27
|
+
|
|
28
|
+
return available[0];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatNodeResults(results) {
|
|
32
|
+
const lines = [];
|
|
33
|
+
|
|
34
|
+
for (const result of results) {
|
|
35
|
+
const status = result.available ? "✓" : "✗";
|
|
36
|
+
const latency = result.available ? `${result.latency}ms` : "N/A";
|
|
37
|
+
const name = result.name || result.url;
|
|
38
|
+
|
|
39
|
+
lines.push(` ${status} ${name} - ${latency}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return lines.join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
testNodes,
|
|
47
|
+
selectBestNode,
|
|
48
|
+
formatNodeResults,
|
|
49
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
function ensureDir(dirPath) {
|
|
7
|
+
if (!fs.existsSync(dirPath)) {
|
|
8
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readJsonFile(filePath, fallback = {}) {
|
|
13
|
+
if (!fs.existsSync(filePath)) {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeJsonFile(filePath, value) {
|
|
25
|
+
ensureDir(path.dirname(filePath));
|
|
26
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
ensureDir,
|
|
31
|
+
readJsonFile,
|
|
32
|
+
writeJsonFile,
|
|
33
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fogact",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "FogAct multi-platform activator — Activate Codex, Claude Code, OpenCode and OpenClaw",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"fogact",
|
|
7
|
+
"cliproxy",
|
|
8
|
+
"fogidc",
|
|
9
|
+
"claude",
|
|
10
|
+
"codex",
|
|
11
|
+
"cli-proxy-api",
|
|
12
|
+
"activator"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://github.com/FogMaly/cliproxy-activator",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/FogMaly/cliproxy-activator/issues"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"fogact": "bin/cli.js",
|
|
21
|
+
"fogact-web": "bin/web-server.js",
|
|
22
|
+
"cliproxy-activator": "bin/cli.js",
|
|
23
|
+
"cliproxy-web": "bin/web-server.js",
|
|
24
|
+
"fogidc-activator": "bin/cli.js",
|
|
25
|
+
"fogidc-web": "bin/web-server.js"
|
|
26
|
+
},
|
|
27
|
+
"main": "./lib/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/",
|
|
30
|
+
"lib/",
|
|
31
|
+
"frontend/",
|
|
32
|
+
"config/upstream.example.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"install.sh"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=16.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"commander": "^12.0.0",
|
|
41
|
+
"prompts": "^2.4.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"start": "node bin/cli.js",
|
|
45
|
+
"web": "PORT=34020 node bin/web-server.js",
|
|
46
|
+
"test": "node test/test-activation.js",
|
|
47
|
+
"demo": "./scripts/demo.sh"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/FogMaly/cliproxy-activator.git"
|
|
52
|
+
}
|
|
53
|
+
}
|