base_parts_ai 1.0.30
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/.claude/settings.local.json +26 -0
- package/.eslintrc.js +264 -0
- package/CLAUDE.md +93 -0
- package/NEXT.md +1 -0
- package/bin/jcc.js +133 -0
- package/lib/build_japp.js +187 -0
- package/lib/claude_utils.js +166 -0
- package/lib/setapi.js +125 -0
- package/lib/setkey.js +44 -0
- package/main.js +2 -0
- package/package.json +27 -0
- package/test_home/.claude/jcc.json +6 -0
- package/test_home/.claude/settings.json +25 -0
- package/test_home/.claude.json +4 -0
- package/test_jcc.js +334 -0
package/test_jcc.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* jcc 功能测试脚本
|
|
4
|
+
* 使用 JCC_TEST_HOME 隔离测试环境,不影响真实 Claude 配置
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
var os = require('os');
|
|
10
|
+
|
|
11
|
+
// ---- 设置测试 home 目录 ----
|
|
12
|
+
var testHome = path.join(__dirname, 'test_home');
|
|
13
|
+
process.env.JCC_TEST_HOME = testHome;
|
|
14
|
+
console.log('[Test] 使用测试 home:', testHome);
|
|
15
|
+
|
|
16
|
+
// 清理测试目录,保证每次全新测试
|
|
17
|
+
var testClaudeDir = path.join(testHome, '.claude');
|
|
18
|
+
if (fs.existsSync(testClaudeDir)) {
|
|
19
|
+
fs.rmSync(testClaudeDir, { recursive: true, force: true });
|
|
20
|
+
console.log('[Test] 已清理旧测试数据');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---- 引入被测模块 ----
|
|
24
|
+
// 清除 require 缓存,确保读到最新代码
|
|
25
|
+
Object.keys(require.cache).forEach(function (k) { delete require.cache[k]; });
|
|
26
|
+
|
|
27
|
+
var utils = require('./lib/claude_utils');
|
|
28
|
+
|
|
29
|
+
// ============================================================
|
|
30
|
+
// 测试 1:getBuildCfg 目录自动创建
|
|
31
|
+
// ============================================================
|
|
32
|
+
console.log('\n======= Test 1: 目录自动创建 =======');
|
|
33
|
+
(function () {
|
|
34
|
+
// 模拟 getBuildCfg 中的目录创建逻辑
|
|
35
|
+
var homeDir = process.env.JCC_TEST_HOME;
|
|
36
|
+
var claudeDir = path.join(homeDir, '.claude');
|
|
37
|
+
var settingsPath = path.join(claudeDir, 'settings.json');
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(claudeDir)) {
|
|
40
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
41
|
+
console.log('[Init] 已创建目录:', claudeDir);
|
|
42
|
+
}
|
|
43
|
+
if (!fs.existsSync(settingsPath)) {
|
|
44
|
+
fs.writeFileSync(settingsPath, '{}', 'utf8');
|
|
45
|
+
console.log('[Init] 已创建文件:', settingsPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var ok1 = fs.existsSync(claudeDir);
|
|
49
|
+
var ok2 = fs.existsSync(settingsPath);
|
|
50
|
+
console.log('✅ .claude 目录存在:', ok1);
|
|
51
|
+
console.log('✅ settings.json 存在:', ok2);
|
|
52
|
+
if (!ok1 || !ok2) { process.exit(1); }
|
|
53
|
+
})();
|
|
54
|
+
|
|
55
|
+
// ============================================================
|
|
56
|
+
// 测试 2:loadSettings 读取空文件
|
|
57
|
+
// ============================================================
|
|
58
|
+
console.log('\n======= Test 2: loadSettings 读取空文件 =======');
|
|
59
|
+
(function () {
|
|
60
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
61
|
+
var settings = utils.loadSettings(settingsPath);
|
|
62
|
+
console.log('settings:', JSON.stringify(settings));
|
|
63
|
+
console.log('✅ env 字段存在:', typeof settings.env === 'object');
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
// ============================================================
|
|
67
|
+
// 测试 3:setapi 逻辑(模拟选择渠道 jai_claude_128k + envObj 合并写入)
|
|
68
|
+
// ============================================================
|
|
69
|
+
console.log('\n======= Test 3: setapi 写入渠道配置(envObj 合并)=======');
|
|
70
|
+
(function () {
|
|
71
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
72
|
+
var jccJsonPath = path.join(testHome, '.claude', 'jcc.json');
|
|
73
|
+
var claudeJsonPath = path.join(testHome, '.claude.json');
|
|
74
|
+
|
|
75
|
+
// 选择第一个渠道 jai_claude_128k
|
|
76
|
+
var selected = utils.API_CHANNELS.find(function (c) { return c.id === 'jai_claude_128k'; });
|
|
77
|
+
var settings = utils.loadSettings(settingsPath);
|
|
78
|
+
|
|
79
|
+
// 写入渠道标识 __jid
|
|
80
|
+
settings.__jid = selected.id;
|
|
81
|
+
|
|
82
|
+
// 合并渠道 envObj 到 settings.env(模拟 setapi 的核心合并逻辑)
|
|
83
|
+
if (selected.envObj) {
|
|
84
|
+
Object.assign(settings.env, selected.envObj);
|
|
85
|
+
}
|
|
86
|
+
// 值为 null 或 "" 的 env 属性一律删除
|
|
87
|
+
Object.keys(settings.env).forEach(function (key) {
|
|
88
|
+
var val = settings.env[key];
|
|
89
|
+
if (val === null || val === '') { delete settings.env[key]; }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// 写入固定配置字段(hooks / language / autoUpdatesChannel)
|
|
93
|
+
settings.hooks = {
|
|
94
|
+
"SessionStart": [
|
|
95
|
+
{
|
|
96
|
+
"matcher": ".*",
|
|
97
|
+
"hooks": [{ "type": "command", "command": "curl -s -m 2 http://116.62.243.108:7180/pub/ai_tip?os=%OS%" }]
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
settings.language = '简体中文';
|
|
102
|
+
settings.autoUpdatesChannel = 'stable';
|
|
103
|
+
|
|
104
|
+
var newKey = 'sk-test-12345678';
|
|
105
|
+
settings.env.ANTHROPIC_API_KEY = newKey;
|
|
106
|
+
utils.writeJsonFile(settingsPath, settings);
|
|
107
|
+
|
|
108
|
+
// 缓存 Key 到 jcc.json
|
|
109
|
+
var jccJson = { keys: {} };
|
|
110
|
+
jccJson.keys[selected.id] = newKey;
|
|
111
|
+
utils.writeJsonFile(jccJsonPath, jccJson);
|
|
112
|
+
|
|
113
|
+
// 写入 claudeJsonPath(~/.claude.json)固定字段
|
|
114
|
+
var claudeJson = {};
|
|
115
|
+
claudeJson.autoUpdaterStatus = 'disabled';
|
|
116
|
+
claudeJson.hasCompletedOnboarding = true;
|
|
117
|
+
utils.writeJsonFile(claudeJsonPath, claudeJson);
|
|
118
|
+
|
|
119
|
+
// ---- 验证 ----
|
|
120
|
+
var s = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
121
|
+
var j = JSON.parse(fs.readFileSync(jccJsonPath, 'utf8'));
|
|
122
|
+
var cj = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
123
|
+
|
|
124
|
+
console.log('settings.__jid:', s.__jid);
|
|
125
|
+
console.log('settings.env.ANTHROPIC_BASE_URL:', s.env.ANTHROPIC_BASE_URL);
|
|
126
|
+
console.log('settings.env.ANTHROPIC_API_KEY:', s.env.ANTHROPIC_API_KEY);
|
|
127
|
+
console.log('settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL:', s.env.ANTHROPIC_DEFAULT_OPUS_MODEL);
|
|
128
|
+
console.log('settings.language:', s.language);
|
|
129
|
+
console.log('settings.autoUpdatesChannel:', s.autoUpdatesChannel);
|
|
130
|
+
console.log('settings.hooks 存在:', typeof s.hooks === 'object');
|
|
131
|
+
console.log('jcc.json keys.jai_claude_128k:', j.keys.jai_claude_128k);
|
|
132
|
+
console.log('.claude.json autoUpdaterStatus:', cj.autoUpdaterStatus);
|
|
133
|
+
console.log('.claude.json hasCompletedOnboarding:', cj.hasCompletedOnboarding);
|
|
134
|
+
|
|
135
|
+
// 断言
|
|
136
|
+
var ok = true;
|
|
137
|
+
ok = ok && (s.__jid === 'jai_claude_128k');
|
|
138
|
+
ok = ok && (s.env.ANTHROPIC_API_KEY === newKey);
|
|
139
|
+
ok = ok && (s.env.ANTHROPIC_BASE_URL === 'http://116.62.243.108:6030'); // jai_EnvObj 中的值
|
|
140
|
+
ok = ok && (s.env.ANTHROPIC_DEFAULT_OPUS_MODEL === 'cc_opus');
|
|
141
|
+
ok = ok && (s.language === '简体中文');
|
|
142
|
+
ok = ok && (s.autoUpdatesChannel === 'stable');
|
|
143
|
+
ok = ok && (typeof s.hooks === 'object');
|
|
144
|
+
ok = ok && (j.keys.jai_claude_128k === newKey);
|
|
145
|
+
ok = ok && (cj.autoUpdaterStatus === 'disabled');
|
|
146
|
+
ok = ok && (cj.hasCompletedOnboarding === true);
|
|
147
|
+
// DISABLE_AUTOUPDATER 来自 jpub_EnvObj,值为 "1",不能被删除
|
|
148
|
+
ok = ok && (s.env.DISABLE_AUTOUPDATER === '1');
|
|
149
|
+
// CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 对 jai_claude_128k 为 "60"
|
|
150
|
+
ok = ok && (s.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE === '60');
|
|
151
|
+
|
|
152
|
+
console.log('✅ Test 3 通过:', ok);
|
|
153
|
+
if (!ok) { process.exit(1); }
|
|
154
|
+
})();
|
|
155
|
+
|
|
156
|
+
// ============================================================
|
|
157
|
+
// 测试 4:getCurrentInfo 识别当前渠道
|
|
158
|
+
// ============================================================
|
|
159
|
+
console.log('\n======= Test 4: getCurrentInfo 识别渠道 =======');
|
|
160
|
+
(function () {
|
|
161
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
162
|
+
var settings = utils.loadSettings(settingsPath);
|
|
163
|
+
var info = utils.getCurrentInfo(settings);
|
|
164
|
+
console.log('识别到渠道:', info.channel ? info.channel.name : '未知');
|
|
165
|
+
console.log('✅ 渠道识别正确:', info.channel && info.channel.id === 'jai_claude_128k');
|
|
166
|
+
if (!info.channel || info.channel.id !== 'jai_claude_128k') { process.exit(1); }
|
|
167
|
+
})();
|
|
168
|
+
|
|
169
|
+
// ============================================================
|
|
170
|
+
// 测试 5:setkey 逻辑(模拟更新 Key)
|
|
171
|
+
// ============================================================
|
|
172
|
+
console.log('\n======= Test 5: setkey 更新 Key =======');
|
|
173
|
+
(function () {
|
|
174
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
175
|
+
var jccJsonPath = path.join(testHome, '.claude', 'jcc.json');
|
|
176
|
+
|
|
177
|
+
var settings = utils.loadSettings(settingsPath);
|
|
178
|
+
var jccJson = utils.readJsonFile(jccJsonPath, {});
|
|
179
|
+
var info = utils.getCurrentInfo(settings);
|
|
180
|
+
|
|
181
|
+
var newKey = 'sk-new-key-abcdefgh';
|
|
182
|
+
settings.env.ANTHROPIC_API_KEY = newKey;
|
|
183
|
+
utils.writeJsonFile(settingsPath, settings);
|
|
184
|
+
|
|
185
|
+
// 若渠道可识别,同步更新 jcc.json 缓存
|
|
186
|
+
if (info.channel) {
|
|
187
|
+
if (!jccJson.keys) { jccJson.keys = {}; }
|
|
188
|
+
jccJson.keys[info.channel.id] = newKey;
|
|
189
|
+
utils.writeJsonFile(jccJsonPath, jccJson);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var s = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
193
|
+
var j = JSON.parse(fs.readFileSync(jccJsonPath, 'utf8'));
|
|
194
|
+
console.log('新 Key:', s.env.ANTHROPIC_API_KEY);
|
|
195
|
+
console.log('jcc.json 缓存:', j.keys[info.channel.id]);
|
|
196
|
+
console.log('✅ Key 更新正确:', s.env.ANTHROPIC_API_KEY === newKey);
|
|
197
|
+
console.log('✅ jcc.json 同步正确:', j.keys[info.channel.id] === newKey);
|
|
198
|
+
if (s.env.ANTHROPIC_API_KEY !== newKey || j.keys[info.channel.id] !== newKey) { process.exit(1); }
|
|
199
|
+
})();
|
|
200
|
+
|
|
201
|
+
// ============================================================
|
|
202
|
+
// 测试 6:切换到另一渠道 jth_BestAiGate(envObj 完整替换)
|
|
203
|
+
// ============================================================
|
|
204
|
+
console.log('\n======= Test 6: 切换到 jth_BestAiGate 渠道 =======');
|
|
205
|
+
(function () {
|
|
206
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
207
|
+
var jccJsonPath = path.join(testHome, '.claude', 'jcc.json');
|
|
208
|
+
|
|
209
|
+
// 选择 jth_BestAiGate 渠道
|
|
210
|
+
var selected = utils.API_CHANNELS.find(function (c) { return c.id === 'jth_BestAiGate'; });
|
|
211
|
+
var settings = utils.loadSettings(settingsPath);
|
|
212
|
+
var jccJson = utils.readJsonFile(jccJsonPath, {});
|
|
213
|
+
|
|
214
|
+
settings.__jid = selected.id;
|
|
215
|
+
|
|
216
|
+
// 合并新渠道的 envObj
|
|
217
|
+
if (selected.envObj) {
|
|
218
|
+
Object.assign(settings.env, selected.envObj);
|
|
219
|
+
}
|
|
220
|
+
// 删除值为空的 env 字段
|
|
221
|
+
Object.keys(settings.env).forEach(function (key) {
|
|
222
|
+
var val = settings.env[key];
|
|
223
|
+
if (val === null || val === '') { delete settings.env[key]; }
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 模拟无缓存 Key,用户输入新 Key
|
|
227
|
+
var savedKey = (jccJson.keys && jccJson.keys[selected.id]) || '';
|
|
228
|
+
var inputKey = savedKey || 'sk-bestgate-key-xxxx';
|
|
229
|
+
settings.env.ANTHROPIC_API_KEY = inputKey;
|
|
230
|
+
if (!jccJson.keys) { jccJson.keys = {}; }
|
|
231
|
+
jccJson.keys[selected.id] = inputKey;
|
|
232
|
+
utils.writeJsonFile(jccJsonPath, jccJson);
|
|
233
|
+
utils.writeJsonFile(settingsPath, settings);
|
|
234
|
+
|
|
235
|
+
var s = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
236
|
+
var j = JSON.parse(fs.readFileSync(jccJsonPath, 'utf8'));
|
|
237
|
+
|
|
238
|
+
// jth_BestAiGate 的 baseUrl 应覆盖 jai 渠道的 baseUrl
|
|
239
|
+
console.log('settings.__jid:', s.__jid);
|
|
240
|
+
console.log('settings.env.ANTHROPIC_BASE_URL:', s.env.ANTHROPIC_BASE_URL);
|
|
241
|
+
console.log('jcc.json keys.jth_BestAiGate:', j.keys.jth_BestAiGate);
|
|
242
|
+
|
|
243
|
+
var ok = true;
|
|
244
|
+
ok = ok && (s.__jid === 'jth_BestAiGate');
|
|
245
|
+
ok = ok && (s.env.ANTHROPIC_BASE_URL === 'https://bestaigate.top');
|
|
246
|
+
ok = ok && (j.keys.jth_BestAiGate === inputKey);
|
|
247
|
+
// jai 专属字段 ANTHROPIC_DEFAULT_OPUS_MODEL 来自上一渠道,新渠道 envObj 未设置此字段
|
|
248
|
+
// 但因 Object.assign 是合并,且 jpub_EnvObj 中 ANTHROPIC_DEFAULT_OPUS_MODEL 为 "",会被删除
|
|
249
|
+
// 所以最终不应有该字段(被清空规则删除)
|
|
250
|
+
ok = ok && (!s.env.ANTHROPIC_DEFAULT_OPUS_MODEL);
|
|
251
|
+
|
|
252
|
+
console.log('✅ Test 6 通过:', ok);
|
|
253
|
+
if (!ok) { process.exit(1); }
|
|
254
|
+
})();
|
|
255
|
+
|
|
256
|
+
// ============================================================
|
|
257
|
+
// 测试 7:API_CHANNELS 列表结构验证(含 ccVersion / envObj)
|
|
258
|
+
// ============================================================
|
|
259
|
+
console.log('\n======= Test 7: API_CHANNELS 结构验证 =======');
|
|
260
|
+
(function () {
|
|
261
|
+
var channels = utils.API_CHANNELS;
|
|
262
|
+
console.log('渠道数量:', channels.length);
|
|
263
|
+
|
|
264
|
+
var allOk = true;
|
|
265
|
+
channels.forEach(function (c) {
|
|
266
|
+
var hasId = typeof c.id === 'string' && c.id.length > 0;
|
|
267
|
+
var hasName = typeof c.name === 'string' && c.name.length > 0;
|
|
268
|
+
var hasDesc = typeof c.description === 'string';
|
|
269
|
+
var hasCcVer = typeof c.ccVersion === 'string' && c.ccVersion.length > 0;
|
|
270
|
+
var hasEnvObj = c.envObj && typeof c.envObj === 'object';
|
|
271
|
+
var hasApiKey = 'ANTHROPIC_API_KEY' in c.envObj; // jpub_EnvObj 中存在该字段
|
|
272
|
+
|
|
273
|
+
var ok = hasId && hasName && hasDesc && hasCcVer && hasEnvObj && hasApiKey;
|
|
274
|
+
console.log(' 渠道 [' + c.id + ']: id✅ name✅ ccVersion=' + c.ccVersion
|
|
275
|
+
+ ' envObj✅ ' + (ok ? '✅' : '❌'));
|
|
276
|
+
if (!ok) { allOk = false; }
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// 验证必须包含的渠道
|
|
280
|
+
var ids = channels.map(function (c) { return c.id; });
|
|
281
|
+
var mustHave = ['jai_claude_128k', 'jai_low_cost', 'jth_BestAiGate', 'jth_pincc_1M'];
|
|
282
|
+
mustHave.forEach(function (id) {
|
|
283
|
+
var exist = ids.indexOf(id) !== -1;
|
|
284
|
+
console.log(' 渠道 ' + id + ' 存在:', exist);
|
|
285
|
+
if (!exist) { allOk = false; }
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
console.log('✅ Test 7 通过:', allOk);
|
|
289
|
+
if (!allOk) { process.exit(1); }
|
|
290
|
+
})();
|
|
291
|
+
|
|
292
|
+
// ============================================================
|
|
293
|
+
// 测试 8:maskKey 脱敏
|
|
294
|
+
// ============================================================
|
|
295
|
+
console.log('\n======= Test 8: maskKey 脱敏显示 =======');
|
|
296
|
+
(function () {
|
|
297
|
+
console.log(utils.maskKey('')); // (未设置)
|
|
298
|
+
console.log(utils.maskKey('sk-ab')); // ****
|
|
299
|
+
console.log(utils.maskKey('sk-ant-12345678abcd')); // sk-a****abcd
|
|
300
|
+
console.log('✅ 脱敏正常');
|
|
301
|
+
})();
|
|
302
|
+
|
|
303
|
+
// ============================================================
|
|
304
|
+
// 测试 9:jai_low_cost 渠道 envObj 验证(GLM 模型)
|
|
305
|
+
// ============================================================
|
|
306
|
+
console.log('\n======= Test 9: jai_low_cost envObj 验证 =======');
|
|
307
|
+
(function () {
|
|
308
|
+
var ch = utils.API_CHANNELS.find(function (c) { return c.id === 'jai_low_cost'; });
|
|
309
|
+
console.log('ANTHROPIC_DEFAULT_OPUS_MODEL:', ch.envObj.ANTHROPIC_DEFAULT_OPUS_MODEL);
|
|
310
|
+
console.log('ANTHROPIC_DEFAULT_HAIKU_MODEL:', ch.envObj.ANTHROPIC_DEFAULT_HAIKU_MODEL);
|
|
311
|
+
var ok = ch.envObj.ANTHROPIC_DEFAULT_OPUS_MODEL === 'glm-5'
|
|
312
|
+
&& ch.envObj.ANTHROPIC_DEFAULT_HAIKU_MODEL === 'glm-4.7'
|
|
313
|
+
&& ch.envObj.ANTHROPIC_BASE_URL === 'http://116.62.243.108:6030';
|
|
314
|
+
console.log('✅ Test 9 通过:', ok);
|
|
315
|
+
if (!ok) { process.exit(1); }
|
|
316
|
+
})();
|
|
317
|
+
|
|
318
|
+
// ============================================================
|
|
319
|
+
// 测试 10:printCurrentInfo 不抛异常(smoke test)
|
|
320
|
+
// ============================================================
|
|
321
|
+
console.log('\n======= Test 10: printCurrentInfo smoke test =======');
|
|
322
|
+
(function () {
|
|
323
|
+
var settingsPath = path.join(testHome, '.claude', 'settings.json');
|
|
324
|
+
var settings = utils.loadSettings(settingsPath);
|
|
325
|
+
try {
|
|
326
|
+
utils.printCurrentInfo(settings);
|
|
327
|
+
console.log('✅ printCurrentInfo 无异常');
|
|
328
|
+
} catch (e) {
|
|
329
|
+
console.error('❌ printCurrentInfo 抛出异常:', e.message);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
})();
|
|
333
|
+
|
|
334
|
+
console.log('\n======= 所有测试完成 =======');
|