yunyi-activator 1.0.0
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/dist/api.js +75 -0
- package/dist/claude-injector.js +227 -0
- package/dist/config.js +79 -0
- package/dist/index.js +281 -0
- package/package.json +39 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateApiKey = validateApiKey;
|
|
7
|
+
exports.getServiceEndpoint = getServiceEndpoint;
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const http_1 = __importDefault(require("http"));
|
|
10
|
+
const url_1 = require("url");
|
|
11
|
+
// 发起 HTTP 请求
|
|
12
|
+
function request(url, options) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const parsedUrl = new url_1.URL(url);
|
|
15
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
16
|
+
const lib = isHttps ? https_1.default : http_1.default;
|
|
17
|
+
const reqOptions = {
|
|
18
|
+
hostname: parsedUrl.hostname,
|
|
19
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
20
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
21
|
+
method: options.method || 'GET',
|
|
22
|
+
headers: options.headers || {},
|
|
23
|
+
};
|
|
24
|
+
const req = lib.request(reqOptions, (res) => {
|
|
25
|
+
let data = '';
|
|
26
|
+
res.on('data', (chunk) => {
|
|
27
|
+
data += chunk;
|
|
28
|
+
});
|
|
29
|
+
res.on('end', () => {
|
|
30
|
+
try {
|
|
31
|
+
const json = JSON.parse(data);
|
|
32
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
33
|
+
resolve(json);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
reject(new Error(json.message || json.error || `HTTP ${res.statusCode}`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
reject(new Error(`Invalid JSON response: ${data.substring(0, 100)}`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
req.on('error', (err) => {
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
req.setTimeout(10000, () => {
|
|
48
|
+
req.destroy();
|
|
49
|
+
reject(new Error('Request timeout'));
|
|
50
|
+
});
|
|
51
|
+
req.end();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// 验证 API Key 并获取信息
|
|
55
|
+
async function validateApiKey(serverUrl, apiKey) {
|
|
56
|
+
const url = `${serverUrl}/user/api/v1/me`;
|
|
57
|
+
return request(url, {
|
|
58
|
+
method: 'GET',
|
|
59
|
+
headers: {
|
|
60
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// 获取服务端点 URL
|
|
66
|
+
function getServiceEndpoint(serverUrl, serviceType) {
|
|
67
|
+
// 移除末尾斜杠
|
|
68
|
+
const baseUrl = serverUrl.replace(/\/+$/, '');
|
|
69
|
+
if (serviceType === 'claude') {
|
|
70
|
+
return `${baseUrl}/claude`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return `${baseUrl}/codex`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getClaudeCommandPath = getClaudeCommandPath;
|
|
7
|
+
exports.resolveSymlink = resolveSymlink;
|
|
8
|
+
exports.getClaudeCliJsPath = getClaudeCliJsPath;
|
|
9
|
+
exports.isInjected = isInjected;
|
|
10
|
+
exports.removeOldInjection = removeOldInjection;
|
|
11
|
+
exports.injectCode = injectCode;
|
|
12
|
+
exports.injectClaudeCli = injectClaudeCli;
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
// 注入标记
|
|
18
|
+
const MARKER_START = '// YUNYI_INJECTED_START';
|
|
19
|
+
const MARKER_END = '// YUNYI_INJECTED_END';
|
|
20
|
+
/**
|
|
21
|
+
* 获取 claude 命令路径
|
|
22
|
+
* @returns claude 命令的完整路径,未找到返回 null
|
|
23
|
+
*/
|
|
24
|
+
function getClaudeCommandPath() {
|
|
25
|
+
try {
|
|
26
|
+
const isWindows = os_1.default.platform() === 'win32';
|
|
27
|
+
const cmd = isWindows ? 'where claude' : 'which claude';
|
|
28
|
+
const result = (0, child_process_1.execSync)(cmd, { encoding: 'utf-8' }).trim();
|
|
29
|
+
// Windows 的 where 可能返回多行,取第一个
|
|
30
|
+
const firstLine = result.split('\n')[0].trim();
|
|
31
|
+
return firstLine || null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 解析符号链接获取真实路径
|
|
39
|
+
* @param linkPath 符号链接路径
|
|
40
|
+
* @returns 真实文件路径
|
|
41
|
+
*/
|
|
42
|
+
function resolveSymlink(linkPath) {
|
|
43
|
+
try {
|
|
44
|
+
// 使用 fs.realpathSync 解析符号链接
|
|
45
|
+
return fs_1.default.realpathSync(linkPath);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return linkPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 从 Windows .cmd 文件中解析 Node.js 脚本路径
|
|
53
|
+
* @param cmdPath .cmd 文件路径
|
|
54
|
+
* @returns cli.js 文件路径,未找到返回 null
|
|
55
|
+
*/
|
|
56
|
+
function parseWindowsCmdFile(cmdPath) {
|
|
57
|
+
try {
|
|
58
|
+
const content = fs_1.default.readFileSync(cmdPath, 'utf-8');
|
|
59
|
+
// Windows cmd 文件通常包含类似这样的内容:
|
|
60
|
+
// @"%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" %*
|
|
61
|
+
// 或者
|
|
62
|
+
// @node "%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" %*
|
|
63
|
+
// 尝试提取 cli.js 路径
|
|
64
|
+
const match = content.match(/@anthropic-ai[/\\]claude-code[/\\]cli\.js/i);
|
|
65
|
+
if (match) {
|
|
66
|
+
// 从 cmd 文件目录构建完整路径
|
|
67
|
+
const cmdDir = path_1.default.dirname(cmdPath);
|
|
68
|
+
return path_1.default.join(cmdDir, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
69
|
+
}
|
|
70
|
+
// 另一种模式:直接在 npm 全局目录
|
|
71
|
+
const npmDir = path_1.default.dirname(cmdPath);
|
|
72
|
+
const possiblePath = path_1.default.join(npmDir, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
73
|
+
if (fs_1.default.existsSync(possiblePath)) {
|
|
74
|
+
return possiblePath;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 获取 cli.js 文件路径
|
|
84
|
+
* @returns cli.js 完整路径,未找到返回 null
|
|
85
|
+
*/
|
|
86
|
+
function getClaudeCliJsPath() {
|
|
87
|
+
const commandPath = getClaudeCommandPath();
|
|
88
|
+
if (!commandPath) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const isWindows = os_1.default.platform() === 'win32';
|
|
92
|
+
if (isWindows) {
|
|
93
|
+
// Windows: claude.cmd 文件需要特殊处理
|
|
94
|
+
if (commandPath.endsWith('.cmd')) {
|
|
95
|
+
return parseWindowsCmdFile(commandPath);
|
|
96
|
+
}
|
|
97
|
+
// 如果是直接的 js 文件
|
|
98
|
+
if (commandPath.endsWith('.js')) {
|
|
99
|
+
return commandPath;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// macOS/Linux: 解析符号链接
|
|
103
|
+
const realPath = resolveSymlink(commandPath);
|
|
104
|
+
// 如果解析后就是 cli.js,直接返回
|
|
105
|
+
if (realPath.endsWith('cli.js')) {
|
|
106
|
+
return realPath;
|
|
107
|
+
}
|
|
108
|
+
// 尝试从 bin 目录推断 node_modules 位置
|
|
109
|
+
// /usr/local/bin/claude -> /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js
|
|
110
|
+
const possiblePaths = [
|
|
111
|
+
// 从 bin 目录向上查找 lib/node_modules
|
|
112
|
+
path_1.default.join(path_1.default.dirname(realPath), '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
113
|
+
// 直接在同级 node_modules
|
|
114
|
+
path_1.default.join(path_1.default.dirname(realPath), 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
115
|
+
// npm 全局目录
|
|
116
|
+
path_1.default.join(os_1.default.homedir(), '.npm-global', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
117
|
+
// nvm 目录 (常见于使用 nvm 的用户)
|
|
118
|
+
path_1.default.join(os_1.default.homedir(), '.nvm', 'versions', 'node', '*', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
119
|
+
];
|
|
120
|
+
for (const possiblePath of possiblePaths) {
|
|
121
|
+
// 处理通配符路径
|
|
122
|
+
if (possiblePath.includes('*')) {
|
|
123
|
+
try {
|
|
124
|
+
const baseDir = possiblePath.split('*')[0];
|
|
125
|
+
if (fs_1.default.existsSync(baseDir)) {
|
|
126
|
+
const dirs = fs_1.default.readdirSync(baseDir);
|
|
127
|
+
for (const dir of dirs) {
|
|
128
|
+
const fullPath = possiblePath.replace('*', dir);
|
|
129
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
130
|
+
return fullPath;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (fs_1.default.existsSync(possiblePath)) {
|
|
140
|
+
return possiblePath;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 检测 cli.js 是否已经注入
|
|
147
|
+
* @param content cli.js 文件内容
|
|
148
|
+
* @returns 是否已注入
|
|
149
|
+
*/
|
|
150
|
+
function isInjected(content) {
|
|
151
|
+
return content.includes(MARKER_START);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 移除旧的注入代码
|
|
155
|
+
* @param content cli.js 文件内容
|
|
156
|
+
* @returns 清理后的内容
|
|
157
|
+
*/
|
|
158
|
+
function removeOldInjection(content) {
|
|
159
|
+
const regex = new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}\\n?`, 'g');
|
|
160
|
+
return content.replace(regex, '');
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 生成注入代码
|
|
164
|
+
* @param baseUrl API 基础 URL
|
|
165
|
+
* @param apiKey API Key
|
|
166
|
+
* @returns 注入代码字符串
|
|
167
|
+
*/
|
|
168
|
+
function generateInjectionCode(baseUrl, apiKey) {
|
|
169
|
+
return `${MARKER_START}
|
|
170
|
+
process.env.ANTHROPIC_BASE_URL="${baseUrl}";
|
|
171
|
+
process.env.ANTHROPIC_AUTH_TOKEN="${apiKey}";
|
|
172
|
+
${MARKER_END}
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 查找注入位置(在注释后、代码前)
|
|
177
|
+
* @param lines 文件行数组
|
|
178
|
+
* @returns 注入位置索引
|
|
179
|
+
*/
|
|
180
|
+
function findInjectionIndex(lines) {
|
|
181
|
+
let insertIndex = 0;
|
|
182
|
+
for (let i = 0; i < lines.length; i++) {
|
|
183
|
+
const trimmed = lines[i].trim();
|
|
184
|
+
// 跳过 shebang、注释、空行
|
|
185
|
+
if (trimmed.startsWith('#!') ||
|
|
186
|
+
trimmed.startsWith('//') ||
|
|
187
|
+
trimmed === '') {
|
|
188
|
+
insertIndex = i + 1;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// 遇到代码行,停止
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return insertIndex;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 注入环境变量代码到 cli.js
|
|
199
|
+
* @param content cli.js 原始内容
|
|
200
|
+
* @param baseUrl API 基础 URL
|
|
201
|
+
* @param apiKey API Key
|
|
202
|
+
* @returns 注入后的内容
|
|
203
|
+
*/
|
|
204
|
+
function injectCode(content, baseUrl, apiKey) {
|
|
205
|
+
// 先移除旧注入
|
|
206
|
+
let cleanContent = removeOldInjection(content);
|
|
207
|
+
const injection = generateInjectionCode(baseUrl, apiKey);
|
|
208
|
+
const lines = cleanContent.split('\n');
|
|
209
|
+
const insertIndex = findInjectionIndex(lines);
|
|
210
|
+
lines.splice(insertIndex, 0, injection);
|
|
211
|
+
return lines.join('\n');
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 执行 CLI 注入
|
|
215
|
+
* @param cliJsPath cli.js 文件路径
|
|
216
|
+
* @param baseUrl API 基础 URL
|
|
217
|
+
* @param apiKey API Key
|
|
218
|
+
* @throws 文件读写错误
|
|
219
|
+
*/
|
|
220
|
+
function injectClaudeCli(cliJsPath, baseUrl, apiKey) {
|
|
221
|
+
// 读取原始内容
|
|
222
|
+
const originalContent = fs_1.default.readFileSync(cliJsPath, 'utf-8');
|
|
223
|
+
// 注入代码
|
|
224
|
+
const newContent = injectCode(originalContent, baseUrl, apiKey);
|
|
225
|
+
// 写回文件
|
|
226
|
+
fs_1.default.writeFileSync(cliJsPath, newContent, 'utf-8');
|
|
227
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SERVER_URL = void 0;
|
|
7
|
+
exports.getCodexConfigDir = getCodexConfigDir;
|
|
8
|
+
exports.getCodexConfigPath = getCodexConfigPath;
|
|
9
|
+
exports.getCodexAuthPath = getCodexAuthPath;
|
|
10
|
+
exports.ensureDir = ensureDir;
|
|
11
|
+
exports.loadCodexConfig = loadCodexConfig;
|
|
12
|
+
exports.saveCodexConfig = saveCodexConfig;
|
|
13
|
+
exports.loadCodexAuth = loadCodexAuth;
|
|
14
|
+
exports.saveCodexAuth = saveCodexAuth;
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
// ============================================
|
|
19
|
+
// 服务器配置 - 在此处配置您的服务器地址
|
|
20
|
+
// ============================================
|
|
21
|
+
exports.SERVER_URL = 'https://yunyi.cfd';
|
|
22
|
+
// ============================================
|
|
23
|
+
// Codex 配置文件路径
|
|
24
|
+
function getCodexConfigDir() {
|
|
25
|
+
return path_1.default.join(os_1.default.homedir(), '.codex');
|
|
26
|
+
}
|
|
27
|
+
function getCodexConfigPath() {
|
|
28
|
+
return path_1.default.join(getCodexConfigDir(), 'config.toml');
|
|
29
|
+
}
|
|
30
|
+
function getCodexAuthPath() {
|
|
31
|
+
return path_1.default.join(getCodexConfigDir(), 'auth.json');
|
|
32
|
+
}
|
|
33
|
+
// 确保目录存在
|
|
34
|
+
function ensureDir(dirPath) {
|
|
35
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
36
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 读取 Codex config.toml
|
|
40
|
+
function loadCodexConfig() {
|
|
41
|
+
const configPath = getCodexConfigPath();
|
|
42
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
43
|
+
try {
|
|
44
|
+
return fs_1.default.readFileSync(configPath, 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
// 保存 Codex config.toml
|
|
53
|
+
function saveCodexConfig(content) {
|
|
54
|
+
const configDir = getCodexConfigDir();
|
|
55
|
+
const configPath = getCodexConfigPath();
|
|
56
|
+
ensureDir(configDir);
|
|
57
|
+
fs_1.default.writeFileSync(configPath, content, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
// 读取 Codex auth.json
|
|
60
|
+
function loadCodexAuth() {
|
|
61
|
+
const authPath = getCodexAuthPath();
|
|
62
|
+
if (fs_1.default.existsSync(authPath)) {
|
|
63
|
+
try {
|
|
64
|
+
const content = fs_1.default.readFileSync(authPath, 'utf-8');
|
|
65
|
+
return JSON.parse(content);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
// 保存 Codex auth.json
|
|
74
|
+
function saveCodexAuth(auth) {
|
|
75
|
+
const configDir = getCodexConfigDir();
|
|
76
|
+
const authPath = getCodexAuthPath();
|
|
77
|
+
ensureDir(configDir);
|
|
78
|
+
fs_1.default.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf-8');
|
|
79
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const api_1 = require("./api");
|
|
12
|
+
const claude_injector_1 = require("./claude-injector");
|
|
13
|
+
const VERSION = '1.0.0';
|
|
14
|
+
function getServiceName(type) {
|
|
15
|
+
return type === 'claude' ? 'Claude Code' : type === 'codex' ? 'Codex' : type;
|
|
16
|
+
}
|
|
17
|
+
function getBillingName(type) {
|
|
18
|
+
switch (type) {
|
|
19
|
+
case 'duration':
|
|
20
|
+
return '按时长计费';
|
|
21
|
+
case 'quota':
|
|
22
|
+
return '按额度计费';
|
|
23
|
+
case 'count':
|
|
24
|
+
return '按次数计费';
|
|
25
|
+
default:
|
|
26
|
+
return type;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function formatCurrency(cents) {
|
|
30
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
31
|
+
}
|
|
32
|
+
function formatDate(dateStr) {
|
|
33
|
+
if (!dateStr)
|
|
34
|
+
return '永久有效';
|
|
35
|
+
const date = new Date(dateStr);
|
|
36
|
+
return date.toLocaleDateString('zh-CN', {
|
|
37
|
+
year: 'numeric',
|
|
38
|
+
month: '2-digit',
|
|
39
|
+
day: '2-digit',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function printBanner() {
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(chalk_1.default.cyan(' ╭─────────────────────────────────────╮'));
|
|
45
|
+
console.log(chalk_1.default.cyan(' │') +
|
|
46
|
+
chalk_1.default.white.bold(' 云驿激活器 v' + VERSION) +
|
|
47
|
+
chalk_1.default.cyan(' │'));
|
|
48
|
+
console.log(chalk_1.default.cyan(' │') +
|
|
49
|
+
chalk_1.default.gray(' Claude Code / Codex 配置工具') +
|
|
50
|
+
chalk_1.default.cyan(' │'));
|
|
51
|
+
console.log(chalk_1.default.cyan(' ╰─────────────────────────────────────╯'));
|
|
52
|
+
console.log();
|
|
53
|
+
}
|
|
54
|
+
function getProgressBar(percent) {
|
|
55
|
+
const width = 20;
|
|
56
|
+
const filled = Math.round((percent / 100) * width);
|
|
57
|
+
const empty = width - filled;
|
|
58
|
+
let barColor;
|
|
59
|
+
if (percent < 50)
|
|
60
|
+
barColor = chalk_1.default.green;
|
|
61
|
+
else if (percent < 80)
|
|
62
|
+
barColor = chalk_1.default.yellow;
|
|
63
|
+
else
|
|
64
|
+
barColor = chalk_1.default.red;
|
|
65
|
+
return (barColor('█'.repeat(filled)) +
|
|
66
|
+
chalk_1.default.gray('░'.repeat(empty)) +
|
|
67
|
+
` ${percent}%`);
|
|
68
|
+
}
|
|
69
|
+
function printAPIInfo(info) {
|
|
70
|
+
const statusColor = info.status === 'active' ? chalk_1.default.green : chalk_1.default.red;
|
|
71
|
+
const statusText = info.status === 'active'
|
|
72
|
+
? '正常'
|
|
73
|
+
: info.status === 'disabled'
|
|
74
|
+
? '已禁用'
|
|
75
|
+
: '已过期';
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk_1.default.white.bold(' API Key 信息'));
|
|
78
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(chalk_1.default.gray(' 密钥: ') + chalk_1.default.cyan(info.key_preview));
|
|
81
|
+
console.log(chalk_1.default.gray(' 服务类型: ') +
|
|
82
|
+
chalk_1.default.magenta(getServiceName(info.service_type)));
|
|
83
|
+
console.log(chalk_1.default.gray(' 状态: ') + statusColor(statusText));
|
|
84
|
+
console.log(chalk_1.default.gray(' 计费模式: ') +
|
|
85
|
+
chalk_1.default.white(getBillingName(info.billing_type)));
|
|
86
|
+
console.log();
|
|
87
|
+
console.log(chalk_1.default.white.bold(' 配额信息'));
|
|
88
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
89
|
+
if (info.billing_type === 'duration') {
|
|
90
|
+
const daily = info.quota.daily_quota || 0;
|
|
91
|
+
const spent = info.quota.daily_spent || 0;
|
|
92
|
+
const remaining = daily - spent;
|
|
93
|
+
const percent = daily > 0 ? Math.round((spent / daily) * 100) : 0;
|
|
94
|
+
console.log(chalk_1.default.gray(' 每日配额: ') + chalk_1.default.white(formatCurrency(daily)));
|
|
95
|
+
console.log(chalk_1.default.gray(' 今日已用: ') + chalk_1.default.yellow(formatCurrency(spent)));
|
|
96
|
+
console.log(chalk_1.default.gray(' 今日剩余: ') + chalk_1.default.green(formatCurrency(remaining)));
|
|
97
|
+
console.log(chalk_1.default.gray(' 使用进度: ') + getProgressBar(percent));
|
|
98
|
+
}
|
|
99
|
+
else if (info.billing_type === 'quota') {
|
|
100
|
+
const total = info.quota.total_quota || 0;
|
|
101
|
+
const remaining = info.quota.remaining_quota || 0;
|
|
102
|
+
const used = total - remaining;
|
|
103
|
+
const percent = total > 0 ? Math.round((used / total) * 100) : 0;
|
|
104
|
+
console.log(chalk_1.default.gray(' 总配额: ') + chalk_1.default.white(formatCurrency(total)));
|
|
105
|
+
console.log(chalk_1.default.gray(' 已使用: ') + chalk_1.default.yellow(formatCurrency(used)));
|
|
106
|
+
console.log(chalk_1.default.gray(' 剩余额度: ') + chalk_1.default.green(formatCurrency(remaining)));
|
|
107
|
+
console.log(chalk_1.default.gray(' 使用进度: ') + getProgressBar(percent));
|
|
108
|
+
}
|
|
109
|
+
else if (info.billing_type === 'count') {
|
|
110
|
+
const max = info.quota.max_requests || 0;
|
|
111
|
+
const count = info.quota.request_count || 0;
|
|
112
|
+
const remaining = max - count;
|
|
113
|
+
const percent = max > 0 ? Math.round((count / max) * 100) : 0;
|
|
114
|
+
console.log(chalk_1.default.gray(' 最大请求: ') + chalk_1.default.white(`${max} 次`));
|
|
115
|
+
console.log(chalk_1.default.gray(' 已请求: ') + chalk_1.default.yellow(`${count} 次`));
|
|
116
|
+
console.log(chalk_1.default.gray(' 剩余次数: ') + chalk_1.default.green(`${remaining} 次`));
|
|
117
|
+
console.log(chalk_1.default.gray(' 使用进度: ') + getProgressBar(percent));
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
console.log(chalk_1.default.white.bold(' 有效期'));
|
|
121
|
+
console.log(chalk_1.default.gray(' ─────────────────────────────────────'));
|
|
122
|
+
console.log(chalk_1.default.gray(' 创建时间: ') +
|
|
123
|
+
chalk_1.default.white(formatDate(info.timestamps.created_at)));
|
|
124
|
+
console.log(chalk_1.default.gray(' 到期时间: ') +
|
|
125
|
+
(info.timestamps.expires_at
|
|
126
|
+
? chalk_1.default.yellow(formatDate(info.timestamps.expires_at))
|
|
127
|
+
: chalk_1.default.green('永久有效')));
|
|
128
|
+
console.log();
|
|
129
|
+
}
|
|
130
|
+
// 配置 Claude Code - 通过注入 cli.js
|
|
131
|
+
function configureClaude(apiKey, endpoint) {
|
|
132
|
+
const cliJsPath = (0, claude_injector_1.getClaudeCliJsPath)();
|
|
133
|
+
if (!cliJsPath) {
|
|
134
|
+
throw new Error('未找到 Claude Code 安装\n' +
|
|
135
|
+
' 请先运行: npm install -g @anthropic-ai/claude-code');
|
|
136
|
+
}
|
|
137
|
+
// 注入环境变量到 cli.js
|
|
138
|
+
(0, claude_injector_1.injectClaudeCli)(cliJsPath, endpoint, apiKey);
|
|
139
|
+
return cliJsPath;
|
|
140
|
+
}
|
|
141
|
+
// 配置 Codex
|
|
142
|
+
// Codex 使用 config.toml 设置 model_provider 和 auth.json 存储 API key
|
|
143
|
+
function configureCodex(apiKey, endpoint) {
|
|
144
|
+
// 1. 更新 config.toml - 添加自定义 model_provider
|
|
145
|
+
let config = (0, config_1.loadCodexConfig)();
|
|
146
|
+
const providerName = 'yunyi';
|
|
147
|
+
const providerSection = `[model_providers.${providerName}]`;
|
|
148
|
+
// 删除所有已存在的 model_providers.yunyi 配置块
|
|
149
|
+
const lines = config.split('\n');
|
|
150
|
+
const newLines = [];
|
|
151
|
+
let skipSection = false;
|
|
152
|
+
let skipComment = false;
|
|
153
|
+
for (let i = 0; i < lines.length; i++) {
|
|
154
|
+
const line = lines[i];
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
// 检测到云驿配置注释,开始跳过
|
|
157
|
+
if (trimmed === '# 云驿 API 中转配置') {
|
|
158
|
+
skipComment = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// 检测到 yunyi provider section,开始跳过
|
|
162
|
+
if (trimmed === providerSection) {
|
|
163
|
+
skipSection = true;
|
|
164
|
+
skipComment = false;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
// 如果在跳过区域,遇到新的 section 则停止跳过
|
|
168
|
+
if (skipSection && trimmed.startsWith('[') && trimmed !== providerSection) {
|
|
169
|
+
skipSection = false;
|
|
170
|
+
}
|
|
171
|
+
// 跳过云驿相关配置
|
|
172
|
+
if (skipSection || skipComment) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
newLines.push(line);
|
|
176
|
+
}
|
|
177
|
+
config = newLines.join('\n');
|
|
178
|
+
// 处理 model_provider 配置
|
|
179
|
+
// 移除旧的 model_provider 行
|
|
180
|
+
config = config.replace(/^model_provider\s*=\s*"[^"]*"\s*\n?/gm, '');
|
|
181
|
+
// 清理多余的空行
|
|
182
|
+
config = config.replace(/\n{3,}/g, '\n\n').trim();
|
|
183
|
+
// 添加新的配置(严格按照要求的格式)
|
|
184
|
+
const newProvider = `
|
|
185
|
+
# 云驿 API 中转配置
|
|
186
|
+
${providerSection}
|
|
187
|
+
name = "yunyi"
|
|
188
|
+
base_url = "${endpoint}"
|
|
189
|
+
wire_api = "responses"
|
|
190
|
+
temp_env_key = "YUNYI_API_KEY"
|
|
191
|
+
requires_openai_auth = true
|
|
192
|
+
model = "gpt-5-codex"
|
|
193
|
+
`;
|
|
194
|
+
// 在开头添加 model_provider,在末尾添加 provider 配置
|
|
195
|
+
config = `model_provider = "${providerName}"\n` + config + '\n' + newProvider;
|
|
196
|
+
(0, config_1.saveCodexConfig)(config);
|
|
197
|
+
// 2. 更新 auth.json - 存储 API key(两个 key 都设置为相同值)
|
|
198
|
+
const auth = {
|
|
199
|
+
OPENAI_API_KEY: apiKey,
|
|
200
|
+
YUNYI_API_KEY: apiKey,
|
|
201
|
+
};
|
|
202
|
+
(0, config_1.saveCodexAuth)(auth);
|
|
203
|
+
return [(0, config_1.getCodexConfigPath)(), (0, config_1.getCodexAuthPath)()];
|
|
204
|
+
}
|
|
205
|
+
// 配置入口
|
|
206
|
+
async function configure(apiKey, info) {
|
|
207
|
+
const endpoint = (0, api_1.getServiceEndpoint)(config_1.SERVER_URL, info.service_type);
|
|
208
|
+
if (info.service_type === 'claude') {
|
|
209
|
+
return [configureClaude(apiKey, endpoint)];
|
|
210
|
+
}
|
|
211
|
+
else if (info.service_type === 'codex') {
|
|
212
|
+
return configureCodex(apiKey, endpoint);
|
|
213
|
+
}
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
async function main() {
|
|
217
|
+
printBanner();
|
|
218
|
+
const { apiKey } = await inquirer_1.default.prompt([
|
|
219
|
+
{
|
|
220
|
+
type: 'password',
|
|
221
|
+
name: 'apiKey',
|
|
222
|
+
message: '请输入 API Key:',
|
|
223
|
+
mask: '*',
|
|
224
|
+
validate: (input) => input?.trim().length > 0 || 'API Key 不能为空',
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
console.log();
|
|
228
|
+
const spinner = (0, ora_1.default)('正在验证 API Key...').start();
|
|
229
|
+
try {
|
|
230
|
+
const info = await (0, api_1.validateApiKey)(config_1.SERVER_URL, apiKey.trim());
|
|
231
|
+
spinner.succeed('验证成功');
|
|
232
|
+
printAPIInfo(info);
|
|
233
|
+
if (info.status !== 'active') {
|
|
234
|
+
console.log(chalk_1.default.yellow(` ⚠ API Key 状态异常: ${info.status}`));
|
|
235
|
+
console.log();
|
|
236
|
+
}
|
|
237
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
238
|
+
{
|
|
239
|
+
type: 'confirm',
|
|
240
|
+
name: 'confirm',
|
|
241
|
+
message: `确认激活 ${getServiceName(info.service_type)} 配置?`,
|
|
242
|
+
default: true,
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
if (!confirm) {
|
|
246
|
+
console.log();
|
|
247
|
+
console.log(chalk_1.default.gray(' 已取消'));
|
|
248
|
+
console.log();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const configSpinner = (0, ora_1.default)('正在写入配置...').start();
|
|
252
|
+
const configPaths = await configure(apiKey.trim(), info);
|
|
253
|
+
configSpinner.succeed('配置完成');
|
|
254
|
+
console.log();
|
|
255
|
+
console.log(chalk_1.default.green.bold(' ✓ ' + getServiceName(info.service_type) + ' 已激活'));
|
|
256
|
+
console.log();
|
|
257
|
+
console.log(chalk_1.default.gray(' 配置文件:'));
|
|
258
|
+
for (const path of configPaths) {
|
|
259
|
+
console.log(chalk_1.default.cyan(' ' + path));
|
|
260
|
+
}
|
|
261
|
+
console.log();
|
|
262
|
+
if (info.service_type === 'claude') {
|
|
263
|
+
console.log(chalk_1.default.gray(' 请重启 Claude Code 以应用新配置'));
|
|
264
|
+
}
|
|
265
|
+
else if (info.service_type === 'codex') {
|
|
266
|
+
console.log(chalk_1.default.gray(' 请重启 Codex CLI 或 IDE 扩展以应用新配置'));
|
|
267
|
+
}
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
spinner.fail('验证失败');
|
|
272
|
+
console.log();
|
|
273
|
+
console.log(chalk_1.default.red(' ✗ ' + (err instanceof Error ? err.message : '未知错误')));
|
|
274
|
+
console.log();
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
main().catch((err) => {
|
|
279
|
+
console.error(chalk_1.default.red('错误:'), err.message);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yunyi-activator",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Yunyi API Activator for Claude Code & Codex",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"yunyi": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"codex",
|
|
18
|
+
"api",
|
|
19
|
+
"yunyi"
|
|
20
|
+
],
|
|
21
|
+
"author": "Yunyi",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"chalk": "^4.1.2",
|
|
31
|
+
"inquirer": "^8.2.6",
|
|
32
|
+
"ora": "^5.4.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/inquirer": "^9.0.7",
|
|
36
|
+
"@types/node": "^20.10.0",
|
|
37
|
+
"typescript": "^5.3.3"
|
|
38
|
+
}
|
|
39
|
+
}
|