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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/README.zh-CN.md +244 -0
  4. package/bin/cli.js +9 -0
  5. package/bin/web-server.js +1434 -0
  6. package/config/upstream.example.json +14 -0
  7. package/frontend/activate.html +249 -0
  8. package/frontend/admin/admin-panel-v2.js +1899 -0
  9. package/frontend/admin/index.html +705 -0
  10. package/frontend/assets/market-ui.css +1876 -0
  11. package/frontend/color-test.html +136 -0
  12. package/frontend/index.html +191 -0
  13. package/frontend/user/assets/AnnouncementDetail-Dvxmwz0A.js +12 -0
  14. package/frontend/user/assets/Announcements-CS1tF2mx.js +11 -0
  15. package/frontend/user/assets/CardBind-CsCxihhP.js +21 -0
  16. package/frontend/user/assets/CardContent.vue_vue_type_script_setup_true_lang-D2L-uqSl.js +1 -0
  17. package/frontend/user/assets/CardDescription.vue_vue_type_script_setup_true_lang-D-v5Pl7F.js +1 -0
  18. package/frontend/user/assets/CardTitle.vue_vue_type_script_setup_true_lang-a0CCN6D5.js +1 -0
  19. package/frontend/user/assets/Dashboard-rPsmltm5.js +51 -0
  20. package/frontend/user/assets/DashboardLayout-BUCWGlXC.css +1 -0
  21. package/frontend/user/assets/DashboardLayout-DDkxHYFj.js +80 -0
  22. package/frontend/user/assets/Input.vue_vue_type_script_setup_true_lang-B0SyPmYb.js +6 -0
  23. package/frontend/user/assets/Label.vue_vue_type_script_setup_true_lang-CxYORSgN.js +1 -0
  24. package/frontend/user/assets/Progress.vue_vue_type_script_setup_true_lang-2_QbPsEQ.js +1 -0
  25. package/frontend/user/assets/QuotaPack-B_tJ7Psm.js +6 -0
  26. package/frontend/user/assets/Renewal-BSDhDmwv.js +6 -0
  27. package/frontend/user/assets/ScrollArea.vue_vue_type_script_setup_true_lang-DMYwcfpz.js +1 -0
  28. package/frontend/user/assets/Separator.vue_vue_type_script_setup_true_lang-Ckg8EXj_.js +1 -0
  29. package/frontend/user/assets/Settings-CBdAa3lw.js +11 -0
  30. package/frontend/user/assets/TooltipTrigger.vue_vue_type_script_setup_true_lang-DtSBjzGo.js +16 -0
  31. package/frontend/user/assets/Welcome-7IfzEli4.css +1 -0
  32. package/frontend/user/assets/Welcome-Dtfp6oER.js +1 -0
  33. package/frontend/user/assets/_plugin-vue_export-helper-5cjT4u0R.js +16 -0
  34. package/frontend/user/assets/activity-wYWtyqTJ.js +6 -0
  35. package/frontend/user/assets/announcement-35mOnjRL.js +16 -0
  36. package/frontend/user/assets/calendar-BFNuCata.js +6 -0
  37. package/frontend/user/assets/chart-vendor-CULJE59K.js +37 -0
  38. package/frontend/user/assets/chevron-down-kDbuU1Py.js +6 -0
  39. package/frontend/user/assets/chevron-right-BayASIm0.js +6 -0
  40. package/frontend/user/assets/eye-CY62vip0.js +6 -0
  41. package/frontend/user/assets/gauge-C5NQ-mV8.js +6 -0
  42. package/frontend/user/assets/index-B8QSyYhS.css +1 -0
  43. package/frontend/user/assets/index-Da98HOxL.js +91 -0
  44. package/frontend/user/assets/link-2-DT5R5nGO.js +6 -0
  45. package/frontend/user/assets/package-rUbExUEn.js +6 -0
  46. package/frontend/user/assets/plus-CQc6C8wG.js +11 -0
  47. package/frontend/user/assets/refresh-cw-Y9hCloPL.js +6 -0
  48. package/frontend/user/assets/useUserPageRefresh-BYZvpNR9.js +1 -0
  49. package/frontend/user/assets/zap-l5zbZqrM.js +11 -0
  50. package/frontend/user/index.html +67 -0
  51. package/install.sh +402 -0
  52. package/lib/commands/activate.js +144 -0
  53. package/lib/commands/restore.js +102 -0
  54. package/lib/commands/test.js +40 -0
  55. package/lib/config/claude.js +81 -0
  56. package/lib/config/codex.js +164 -0
  57. package/lib/config/upstream.js +79 -0
  58. package/lib/index.js +164 -0
  59. package/lib/platforms/claude-code.js +35 -0
  60. package/lib/platforms/codex-cli.js +35 -0
  61. package/lib/platforms/editor-codex.js +138 -0
  62. package/lib/platforms/index.js +32 -0
  63. package/lib/platforms/openclaw.js +118 -0
  64. package/lib/platforms/opencode.js +89 -0
  65. package/lib/services/activation-orchestrator.js +666 -0
  66. package/lib/services/backup-service.js +162 -0
  67. package/lib/services/cliproxy-api.js +174 -0
  68. package/lib/services/database.js +461 -0
  69. package/lib/services/newapi.js +97 -0
  70. package/lib/services/node-service.js +49 -0
  71. package/lib/utils/json-file.js +33 -0
  72. 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 };