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,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const prompts = require("prompts");
|
|
4
|
+
const { listBackups, restoreBackup, clearBackups } = require("../services/backup-service");
|
|
5
|
+
|
|
6
|
+
async function runRestoreCommand(options = {}) {
|
|
7
|
+
console.log("");
|
|
8
|
+
console.log("=== Restore Backup ===");
|
|
9
|
+
console.log("");
|
|
10
|
+
|
|
11
|
+
// Step 1: Select service
|
|
12
|
+
let service = options.service;
|
|
13
|
+
if (!service) {
|
|
14
|
+
const response = await prompts({
|
|
15
|
+
type: "select",
|
|
16
|
+
name: "service",
|
|
17
|
+
message: "Select service",
|
|
18
|
+
choices: [
|
|
19
|
+
{ title: "Claude Code", value: "claude" },
|
|
20
|
+
{ title: "Codex", value: "codex" },
|
|
21
|
+
{ title: "All services", value: null },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (response.service === undefined) {
|
|
26
|
+
console.log("Restore cancelled.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
service = response.service;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Step 2: List backups
|
|
34
|
+
const backups = listBackups(service);
|
|
35
|
+
|
|
36
|
+
if (backups.length === 0) {
|
|
37
|
+
console.log("No backups found.");
|
|
38
|
+
console.log("");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Found ${backups.length} backup(s):`);
|
|
43
|
+
console.log("");
|
|
44
|
+
|
|
45
|
+
// Step 3: Select backup or clear all
|
|
46
|
+
const choices = backups.map((backup, index) => ({
|
|
47
|
+
title: `${backup.service} - ${new Date(backup.timestamp).toLocaleString()}`,
|
|
48
|
+
value: backup.path,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
choices.push({ title: "Clear all backups", value: "__clear__" });
|
|
52
|
+
|
|
53
|
+
const response = await prompts({
|
|
54
|
+
type: "select",
|
|
55
|
+
name: "backup",
|
|
56
|
+
message: "Select backup to restore",
|
|
57
|
+
choices,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!response.backup) {
|
|
61
|
+
console.log("Restore cancelled.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Step 4: Handle clear all
|
|
66
|
+
if (response.backup === "__clear__") {
|
|
67
|
+
const confirm = await prompts({
|
|
68
|
+
type: "confirm",
|
|
69
|
+
name: "value",
|
|
70
|
+
message: "Are you sure you want to clear all backups?",
|
|
71
|
+
initial: false,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!confirm.value) {
|
|
75
|
+
console.log("Clear cancelled.");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const count = clearBackups(service);
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log(`✓ Cleared ${count} backup(s)`);
|
|
82
|
+
console.log("");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Step 5: Restore backup
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log("Restoring backup...");
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const restoredPath = restoreBackup(response.backup);
|
|
92
|
+
console.log(`✓ Backup restored: ${restoredPath}`);
|
|
93
|
+
console.log("");
|
|
94
|
+
console.log("Please restart your application to apply changes.");
|
|
95
|
+
console.log("");
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.log(`✗ Restore failed: ${err.message}`);
|
|
98
|
+
console.log("");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { runRestoreCommand };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { getNodes } = require("../services/cliproxy-api");
|
|
4
|
+
const { testNodes, formatNodeResults } = require("../services/node-service");
|
|
5
|
+
|
|
6
|
+
async function runTestCommand() {
|
|
7
|
+
console.log("");
|
|
8
|
+
console.log("=== Node Testing ===");
|
|
9
|
+
console.log("");
|
|
10
|
+
|
|
11
|
+
// Test Claude nodes
|
|
12
|
+
console.log("Testing Claude Code nodes...");
|
|
13
|
+
const claudeNodes = await getNodes("claude");
|
|
14
|
+
|
|
15
|
+
if (claudeNodes.length > 0) {
|
|
16
|
+
const claudeResults = await testNodes(claudeNodes);
|
|
17
|
+
console.log("");
|
|
18
|
+
console.log(formatNodeResults(claudeResults));
|
|
19
|
+
} else {
|
|
20
|
+
console.log(" No Claude nodes available");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("");
|
|
24
|
+
|
|
25
|
+
// Test Codex nodes
|
|
26
|
+
console.log("Testing Codex nodes...");
|
|
27
|
+
const codexNodes = await getNodes("codex");
|
|
28
|
+
|
|
29
|
+
if (codexNodes.length > 0) {
|
|
30
|
+
const codexResults = await testNodes(codexNodes);
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(formatNodeResults(codexResults));
|
|
33
|
+
} else {
|
|
34
|
+
console.log(" No Codex nodes available");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { runTestCommand };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
function getClaudeDir() {
|
|
8
|
+
return path.join(os.homedir(), ".claude");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getClaudeSettingsPath() {
|
|
12
|
+
return path.join(getClaudeDir(), "settings.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getClaudeStatePath() {
|
|
16
|
+
return path.join(os.homedir(), ".claude.json");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getClaudeConfigPath() {
|
|
20
|
+
return getClaudeSettingsPath();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ensureDir(dirPath) {
|
|
24
|
+
if (!fs.existsSync(dirPath)) {
|
|
25
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readJsonFile(filePath, fallback = {}) {
|
|
30
|
+
if (!fs.existsSync(filePath)) {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeJsonFile(filePath, value) {
|
|
42
|
+
ensureDir(path.dirname(filePath));
|
|
43
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf8");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readClaudeConfig() {
|
|
47
|
+
return readJsonFile(getClaudeSettingsPath(), {});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeClaudeConfig(apiKey, baseUrl) {
|
|
51
|
+
const settingsPath = getClaudeSettingsPath();
|
|
52
|
+
const statePath = getClaudeStatePath();
|
|
53
|
+
const settings = readJsonFile(settingsPath, {});
|
|
54
|
+
const { ANTHROPIC_API_KEY, ...existingEnv } = settings.env || {};
|
|
55
|
+
|
|
56
|
+
writeJsonFile(settingsPath, {
|
|
57
|
+
...settings,
|
|
58
|
+
env: {
|
|
59
|
+
...existingEnv,
|
|
60
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
61
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const state = readJsonFile(statePath, {});
|
|
66
|
+
writeJsonFile(statePath, {
|
|
67
|
+
...state,
|
|
68
|
+
hasCompletedOnboarding: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return settingsPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
getClaudeDir,
|
|
76
|
+
getClaudeConfigPath,
|
|
77
|
+
getClaudeSettingsPath,
|
|
78
|
+
getClaudeStatePath,
|
|
79
|
+
readClaudeConfig,
|
|
80
|
+
writeClaudeConfig,
|
|
81
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const YUNYI_PROVIDER = "yunyi";
|
|
8
|
+
const YUNYI_MODEL = "gpt-5.3-codex";
|
|
9
|
+
const BLOCK_START = "# >>> yunyi activator codex >>>";
|
|
10
|
+
const BLOCK_END = "# <<< yunyi activator codex <<<";
|
|
11
|
+
|
|
12
|
+
function getCodexDir() {
|
|
13
|
+
return path.join(os.homedir(), ".codex");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getCodexConfigPath() {
|
|
17
|
+
return path.join(getCodexDir(), "config.toml");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCodexAuthPath() {
|
|
21
|
+
return path.join(getCodexDir(), "auth.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureDir(dirPath) {
|
|
25
|
+
if (!fs.existsSync(dirPath)) {
|
|
26
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readCodexConfig() {
|
|
31
|
+
const configPath = getCodexConfigPath();
|
|
32
|
+
if (!fs.existsSync(configPath)) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(configPath, "utf8");
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readCodexAuth() {
|
|
44
|
+
const authPath = getCodexAuthPath();
|
|
45
|
+
if (!fs.existsSync(authPath)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(fs.readFileSync(authPath, "utf8"));
|
|
51
|
+
} catch (err) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stripYunyiBlock(content) {
|
|
57
|
+
const lines = String(content || "").split(/\r?\n/);
|
|
58
|
+
const kept = [];
|
|
59
|
+
let inBlock = false;
|
|
60
|
+
let currentSection = null;
|
|
61
|
+
let inYunyiProvider = false;
|
|
62
|
+
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (trimmed === BLOCK_START) {
|
|
66
|
+
inBlock = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (trimmed === BLOCK_END) {
|
|
70
|
+
inBlock = false;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (inBlock) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const section = trimmed.match(/^\[([^\]]+)\]$/);
|
|
78
|
+
if (section) {
|
|
79
|
+
currentSection = section[1].trim();
|
|
80
|
+
inYunyiProvider = currentSection.toLowerCase().startsWith("model_providers.yunyi");
|
|
81
|
+
if (inYunyiProvider) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
kept.push(line);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (inYunyiProvider) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const isRootYunyiSetting =
|
|
93
|
+
!currentSection &&
|
|
94
|
+
(
|
|
95
|
+
trimmed === "# 云驿 API 中转配置" ||
|
|
96
|
+
/^#?\s*model_provider\s*=/.test(trimmed) ||
|
|
97
|
+
/^#?\s*model\s*=/.test(trimmed) ||
|
|
98
|
+
/^#?\s*model_reasoning_effort\s*=/.test(trimmed) ||
|
|
99
|
+
/^#?\s*disable_response_storage\s*=/.test(trimmed) ||
|
|
100
|
+
/^#?\s*preferred_auth_method\s*=/.test(trimmed)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!isRootYunyiSetting) {
|
|
104
|
+
kept.push(line);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildCodexConfig(existingContent, baseUrl, apiKey) {
|
|
112
|
+
const cleaned = stripYunyiBlock(existingContent);
|
|
113
|
+
const yunyiConfig = [
|
|
114
|
+
BLOCK_START,
|
|
115
|
+
`model_provider = "${YUNYI_PROVIDER}"`,
|
|
116
|
+
`model = "${YUNYI_MODEL}"`,
|
|
117
|
+
'model_reasoning_effort = "high"',
|
|
118
|
+
"disable_response_storage = true",
|
|
119
|
+
'preferred_auth_method = "apikey"',
|
|
120
|
+
"",
|
|
121
|
+
`[model_providers.${YUNYI_PROVIDER}]`,
|
|
122
|
+
`name = "${YUNYI_PROVIDER}"`,
|
|
123
|
+
`base_url = "${baseUrl}"`,
|
|
124
|
+
'wire_api = "responses"',
|
|
125
|
+
`experimental_bearer_token = "${apiKey}"`,
|
|
126
|
+
"requires_openai_auth = true",
|
|
127
|
+
BLOCK_END,
|
|
128
|
+
].join("\n");
|
|
129
|
+
|
|
130
|
+
const result = cleaned ? `${yunyiConfig}\n\n${cleaned}` : yunyiConfig;
|
|
131
|
+
return result.endsWith("\n") ? result : `${result}\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeCodexConfig(apiKey, baseUrl) {
|
|
135
|
+
const configPath = getCodexConfigPath();
|
|
136
|
+
const authPath = getCodexAuthPath();
|
|
137
|
+
ensureDir(getCodexDir());
|
|
138
|
+
|
|
139
|
+
const config = buildCodexConfig(readCodexConfig(), baseUrl, apiKey);
|
|
140
|
+
fs.writeFileSync(configPath, config, "utf8");
|
|
141
|
+
|
|
142
|
+
const { YUNYI_API_KEY, ...auth } = readCodexAuth();
|
|
143
|
+
fs.writeFileSync(
|
|
144
|
+
authPath,
|
|
145
|
+
JSON.stringify({ ...auth, auth_mode: "apikey", OPENAI_API_KEY: apiKey }, null, 2),
|
|
146
|
+
"utf8"
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return configPath;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
BLOCK_START,
|
|
154
|
+
BLOCK_END,
|
|
155
|
+
YUNYI_MODEL,
|
|
156
|
+
getCodexDir,
|
|
157
|
+
getCodexConfigPath,
|
|
158
|
+
getCodexAuthPath,
|
|
159
|
+
readCodexConfig,
|
|
160
|
+
readCodexAuth,
|
|
161
|
+
stripYunyiBlock,
|
|
162
|
+
buildCodexConfig,
|
|
163
|
+
writeCodexConfig,
|
|
164
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const PROJECT_ROOT = path.join(__dirname, "..", "..");
|
|
7
|
+
const DEFAULT_CONFIG_PATH = path.join(PROJECT_ROOT, "config", "upstream.json");
|
|
8
|
+
const EXAMPLE_CONFIG_PATH = path.join(PROJECT_ROOT, "config", "upstream.example.json");
|
|
9
|
+
|
|
10
|
+
function readJsonFile(filePath) {
|
|
11
|
+
if (!fs.existsSync(filePath)) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
throw new Error(`Invalid upstream config JSON: ${filePath}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function trimTrailingSlash(value) {
|
|
23
|
+
return String(value || "").trim().replace(/\/+$/, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadUpstreamConfig(options = {}) {
|
|
27
|
+
const configPath = options.configPath || process.env.CLIPROXY_UPSTREAM_CONFIG || DEFAULT_CONFIG_PATH;
|
|
28
|
+
const fileConfig = readJsonFile(configPath);
|
|
29
|
+
const baseUrl = trimTrailingSlash(
|
|
30
|
+
process.env.NEWAPI_BASE_URL ||
|
|
31
|
+
process.env.UPSTREAM_BASE_URL ||
|
|
32
|
+
fileConfig.baseUrl ||
|
|
33
|
+
fileConfig.url
|
|
34
|
+
);
|
|
35
|
+
const apiKey = String(
|
|
36
|
+
process.env.NEWAPI_API_KEY ||
|
|
37
|
+
process.env.UPSTREAM_API_KEY ||
|
|
38
|
+
fileConfig.apiKey ||
|
|
39
|
+
fileConfig.key ||
|
|
40
|
+
""
|
|
41
|
+
).trim();
|
|
42
|
+
const timeoutMs = parseInt(
|
|
43
|
+
process.env.NEWAPI_TIMEOUT_MS || fileConfig.timeoutMs || "10000",
|
|
44
|
+
10
|
|
45
|
+
) || 10000;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
provider: fileConfig.provider || "newapi",
|
|
49
|
+
baseUrl,
|
|
50
|
+
apiKey,
|
|
51
|
+
services: fileConfig.services || {},
|
|
52
|
+
timeoutMs,
|
|
53
|
+
configPath,
|
|
54
|
+
configured: Boolean(baseUrl && apiKey),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getServiceBaseUrl(config, service) {
|
|
59
|
+
const serviceConfig = (config.services && config.services[service]) || {};
|
|
60
|
+
return trimTrailingSlash(serviceConfig.baseUrl || config.baseUrl);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function requireUpstreamConfig(options = {}) {
|
|
64
|
+
const config = loadUpstreamConfig(options);
|
|
65
|
+
if (!config.configured) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Upstream NewAPI config is incomplete. Copy ${path.relative(PROJECT_ROOT, EXAMPLE_CONFIG_PATH)} to ${path.relative(PROJECT_ROOT, DEFAULT_CONFIG_PATH)} and set baseUrl/apiKey, or use NEWAPI_BASE_URL and NEWAPI_API_KEY.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
DEFAULT_CONFIG_PATH,
|
|
75
|
+
EXAMPLE_CONFIG_PATH,
|
|
76
|
+
getServiceBaseUrl,
|
|
77
|
+
loadUpstreamConfig,
|
|
78
|
+
requireUpstreamConfig,
|
|
79
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Command } = require("commander");
|
|
4
|
+
const prompts = require("prompts");
|
|
5
|
+
const packageJson = require("../package.json");
|
|
6
|
+
const { runActivateCommand } = require("./commands/activate");
|
|
7
|
+
const { runTestCommand } = require("./commands/test");
|
|
8
|
+
const { runRestoreCommand } = require("./commands/restore");
|
|
9
|
+
const { runActivationWizard } = require("./services/activation-orchestrator");
|
|
10
|
+
|
|
11
|
+
function printBanner() {
|
|
12
|
+
console.log("FogAct 多平台激活器");
|
|
13
|
+
console.log("一键激活 Codex / Claude / OpenCode / OpenClaw");
|
|
14
|
+
console.log("");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function runInteractiveMenu() {
|
|
18
|
+
await runActivationWizard();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runToolsMenu() {
|
|
22
|
+
printBanner();
|
|
23
|
+
|
|
24
|
+
const response = await prompts(
|
|
25
|
+
{
|
|
26
|
+
type: "select",
|
|
27
|
+
name: "action",
|
|
28
|
+
message: "请选择操作",
|
|
29
|
+
choices: [
|
|
30
|
+
{ title: "多平台激活", value: "activate" },
|
|
31
|
+
{ title: "测试节点", value: "test" },
|
|
32
|
+
{ title: "恢复备份", value: "restore" },
|
|
33
|
+
{ title: "启动 Web UI", value: "web" },
|
|
34
|
+
{ title: "退出", value: "exit" },
|
|
35
|
+
],
|
|
36
|
+
initial: 0,
|
|
37
|
+
},
|
|
38
|
+
{ onCancel: () => false }
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
switch (response.action) {
|
|
42
|
+
case "activate":
|
|
43
|
+
await runActivationWizard();
|
|
44
|
+
break;
|
|
45
|
+
case "test":
|
|
46
|
+
await runTestCommand();
|
|
47
|
+
break;
|
|
48
|
+
case "restore":
|
|
49
|
+
await runRestoreCommand();
|
|
50
|
+
break;
|
|
51
|
+
case "web":
|
|
52
|
+
runWebServer();
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
console.log("再见。");
|
|
56
|
+
console.log("");
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runWebServer() {
|
|
62
|
+
require("../bin/web-server");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildProgram() {
|
|
66
|
+
const program = new Command();
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.name("fogact")
|
|
70
|
+
.description(packageJson.description)
|
|
71
|
+
.version(packageJson.version)
|
|
72
|
+
.addHelpText(
|
|
73
|
+
"after",
|
|
74
|
+
[
|
|
75
|
+
"",
|
|
76
|
+
"Examples:",
|
|
77
|
+
" npx fogact",
|
|
78
|
+
" fogact",
|
|
79
|
+
" fogact activate --service codex --yes --all",
|
|
80
|
+
" fogact activate --service claude --api-key sk-... --yes",
|
|
81
|
+
" fogact activate --code K1DHPY3P-4B2W-F1A4-DC4P-Y74TCQZXPNYT",
|
|
82
|
+
" fogact test",
|
|
83
|
+
" fogact restore --service claude",
|
|
84
|
+
].join("\n")
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
program
|
|
88
|
+
.command("activate")
|
|
89
|
+
.description("Open the multi-platform activation flow")
|
|
90
|
+
.option("-s, --service <service>", "target service: claude or codex")
|
|
91
|
+
.option("-k, --api-key <apiKey>", "NewAPI key; defaults to config/upstream.json or NEWAPI_API_KEY")
|
|
92
|
+
.option("-y, --yes", "auto-confirm activation plan")
|
|
93
|
+
.option("--auto", "alias for --yes")
|
|
94
|
+
.option("--all", "configure optional platforms even when their config files do not exist")
|
|
95
|
+
.option("--platforms <ids>", "comma-separated platform ids to activate")
|
|
96
|
+
.option("--skip-verify", "skip upstream /v1/models key verification")
|
|
97
|
+
.option("--upstream-config <path>", "path to upstream config JSON")
|
|
98
|
+
.option("-c, --code <code>", "activation / redeem code")
|
|
99
|
+
.option("--legacy", "use legacy activation-code node switching flow")
|
|
100
|
+
.option("--no-redeem", "do not mark activation code as redeemed after writing config")
|
|
101
|
+
.action(runActivateCommand);
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command("wizard")
|
|
105
|
+
.description("Open FogIDC-style activation wizard")
|
|
106
|
+
.option("-s, --service <service>", "target service: claude or codex")
|
|
107
|
+
.option("-k, --api-key <apiKey>", "NewAPI key")
|
|
108
|
+
.option("-c, --code <code>", "activation / redeem code")
|
|
109
|
+
.option("--platforms <ids>", "comma-separated platform ids to activate")
|
|
110
|
+
.option("--all", "select all configurable platforms")
|
|
111
|
+
.option("--yes", "auto-confirm activation plan")
|
|
112
|
+
.option("--skip-verify", "skip upstream /v1/models key verification")
|
|
113
|
+
.option("--upstream-config <path>", "path to upstream config JSON")
|
|
114
|
+
.option("--no-redeem", "do not mark activation code as redeemed after writing config")
|
|
115
|
+
.action(runActivationWizard);
|
|
116
|
+
|
|
117
|
+
program
|
|
118
|
+
.command("test")
|
|
119
|
+
.description("Test CLIProxy nodes")
|
|
120
|
+
.action(runTestCommand);
|
|
121
|
+
|
|
122
|
+
program
|
|
123
|
+
.command("restore")
|
|
124
|
+
.description("Restore a previous backup")
|
|
125
|
+
.option("-s, --service <service>", "target service: claude or codex")
|
|
126
|
+
.action(runRestoreCommand);
|
|
127
|
+
|
|
128
|
+
program
|
|
129
|
+
.command("web")
|
|
130
|
+
.description("Start the local Web UI")
|
|
131
|
+
.action(runWebServer);
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command("interactive")
|
|
135
|
+
.description("Open the activation wizard")
|
|
136
|
+
.action(runInteractiveMenu);
|
|
137
|
+
|
|
138
|
+
program
|
|
139
|
+
.command("menu")
|
|
140
|
+
.description("Open tools menu")
|
|
141
|
+
.action(runToolsMenu);
|
|
142
|
+
|
|
143
|
+
return program;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function runCli(argv = process.argv) {
|
|
147
|
+
const args = argv.slice(2);
|
|
148
|
+
|
|
149
|
+
if (args.length === 0) {
|
|
150
|
+
await runToolsMenu();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const program = buildProgram();
|
|
155
|
+
await program.parseAsync(argv);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
buildProgram,
|
|
160
|
+
runCli,
|
|
161
|
+
runInteractiveMenu,
|
|
162
|
+
runToolsMenu,
|
|
163
|
+
runWebServer,
|
|
164
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const { getServiceBaseUrl } = require("../config/upstream");
|
|
5
|
+
const {
|
|
6
|
+
getClaudeDir,
|
|
7
|
+
getClaudeSettingsPath,
|
|
8
|
+
getClaudeStatePath,
|
|
9
|
+
writeClaudeConfig,
|
|
10
|
+
} = require("../config/claude");
|
|
11
|
+
|
|
12
|
+
function createClaudeCodePlatform() {
|
|
13
|
+
return {
|
|
14
|
+
id: "claude-code",
|
|
15
|
+
name: "Claude Code",
|
|
16
|
+
services: ["claude"],
|
|
17
|
+
required: true,
|
|
18
|
+
detect() {
|
|
19
|
+
return {
|
|
20
|
+
installed: fs.existsSync(getClaudeDir()) || fs.existsSync(getClaudeSettingsPath()),
|
|
21
|
+
paths: [getClaudeSettingsPath(), getClaudeStatePath()],
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
activate(context) {
|
|
25
|
+
const baseUrl = getServiceBaseUrl(context.upstream, "claude");
|
|
26
|
+
const settingsPath = writeClaudeConfig(context.apiKey, baseUrl);
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
files: [settingsPath, getClaudeStatePath()],
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { createClaudeCodePlatform };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const { getServiceBaseUrl } = require("../config/upstream");
|
|
5
|
+
const {
|
|
6
|
+
getCodexDir,
|
|
7
|
+
getCodexConfigPath,
|
|
8
|
+
getCodexAuthPath,
|
|
9
|
+
writeCodexConfig,
|
|
10
|
+
} = require("../config/codex");
|
|
11
|
+
|
|
12
|
+
function createCodexCliPlatform() {
|
|
13
|
+
return {
|
|
14
|
+
id: "codex-cli",
|
|
15
|
+
name: "Codex CLI",
|
|
16
|
+
services: ["codex"],
|
|
17
|
+
required: true,
|
|
18
|
+
detect() {
|
|
19
|
+
return {
|
|
20
|
+
installed: fs.existsSync(getCodexDir()) || fs.existsSync(getCodexConfigPath()) || fs.existsSync(getCodexAuthPath()),
|
|
21
|
+
paths: [getCodexConfigPath(), getCodexAuthPath()],
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
activate(context) {
|
|
25
|
+
const baseUrl = getServiceBaseUrl(context.upstream, "codex");
|
|
26
|
+
const configPath = writeCodexConfig(context.apiKey, baseUrl);
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
files: [configPath, getCodexAuthPath()],
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { createCodexCliPlatform };
|