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
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
let glob = require("glob")
|
|
2
|
+
let archiver = require('archiver');
|
|
3
|
+
let jutils = require('base_parts');
|
|
4
|
+
|
|
5
|
+
let fs = require('fs');
|
|
6
|
+
let os = require('os');
|
|
7
|
+
let path = require('path');
|
|
8
|
+
var AdmZip = require('adm-zip');
|
|
9
|
+
|
|
10
|
+
module.exports = async (cmd, buildCfg) => {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
(async () => {
|
|
13
|
+
// 待删除列表
|
|
14
|
+
var deleteList = [];
|
|
15
|
+
|
|
16
|
+
// 读取sh脚本
|
|
17
|
+
var shDataStr = fs.readFileSync(buildCfg.rootPath + "/jbuild_data/repair.sh", "utf8");
|
|
18
|
+
|
|
19
|
+
// 建立压缩文件
|
|
20
|
+
console.log(`build file to ${buildCfg.outpath}`);
|
|
21
|
+
let output = fs.createWriteStream(buildCfg.outpath);
|
|
22
|
+
let archive = archiver('zip', {
|
|
23
|
+
zlib: { level: 9 }
|
|
24
|
+
});
|
|
25
|
+
output.on('close', function () {
|
|
26
|
+
// 清理
|
|
27
|
+
for (const iterator of deleteList) {
|
|
28
|
+
fs.unlinkSync(iterator);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 完成
|
|
32
|
+
console.log(archive.pointer() + ' total bytes');
|
|
33
|
+
console.log(`build success:${buildCfg.outpath}`);
|
|
34
|
+
resolve();
|
|
35
|
+
});
|
|
36
|
+
output.on('end', function () {
|
|
37
|
+
});
|
|
38
|
+
archive.on('warning', function (err) {
|
|
39
|
+
if (err.code === 'ENOENT') {
|
|
40
|
+
} else {
|
|
41
|
+
reject(err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
archive.on('error', function (err) {
|
|
45
|
+
reject(err);
|
|
46
|
+
});
|
|
47
|
+
archive.pipe(output);
|
|
48
|
+
|
|
49
|
+
// 添加应用
|
|
50
|
+
if (!cmd.noapp) {
|
|
51
|
+
|
|
52
|
+
// 修改版本号
|
|
53
|
+
try {
|
|
54
|
+
console.log(`change dist ver ...`);
|
|
55
|
+
var repFile = `${buildCfg.rootPath}\\dist\\index.html`;
|
|
56
|
+
var indexStr = fs.readFileSync(repFile, "utf8");
|
|
57
|
+
indexStr = indexStr.replace(/#J_APPNAME#/g, buildCfg.appName);
|
|
58
|
+
indexStr = indexStr.replace(/#J_DATE#/g, buildCfg.dateStr);
|
|
59
|
+
|
|
60
|
+
// 读取H5版本
|
|
61
|
+
try {
|
|
62
|
+
var bInfoStr = fs.readFileSync(`${buildCfg.rootPath}/jbuild_data/info.txt`, "utf8");
|
|
63
|
+
var bInfoObj = JSON.parse(bInfoStr);
|
|
64
|
+
if (bInfoObj.version) {
|
|
65
|
+
indexStr = indexStr.replace(/#J_H5_VER#/g, bInfoObj.version);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.log(`no h5Ver!`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 保存
|
|
72
|
+
fs.writeFileSync(repFile, indexStr);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 添加用户文件
|
|
77
|
+
let pathSub = "dist";
|
|
78
|
+
console.log(`scaning ${pathSub} ...`);
|
|
79
|
+
let fileList = glob(`${pathSub}/**/*`, {
|
|
80
|
+
dot: true,
|
|
81
|
+
sync: true,
|
|
82
|
+
nodir: true,
|
|
83
|
+
cwd: buildCfg.rootPath,
|
|
84
|
+
ignore: []
|
|
85
|
+
});
|
|
86
|
+
if (fileList.length === 0) {
|
|
87
|
+
console.error("no dist files!");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(`add [${fileList.length}] files ...`);
|
|
91
|
+
for (let index = 0; index < fileList.length; index++) {
|
|
92
|
+
let element = fileList[index].substr(pathSub.length + 1);
|
|
93
|
+
|
|
94
|
+
// 添加文件
|
|
95
|
+
archive.file(`${buildCfg.rootPath}/${pathSub}/${element}`, { name: "update_a20/h5app/" + element });
|
|
96
|
+
}
|
|
97
|
+
// 清空应用目录
|
|
98
|
+
shDataStr = shDataStr.replace(/#del_app#/g, "");
|
|
99
|
+
// 复制应用文件
|
|
100
|
+
shDataStr = shDataStr.replace(/#copy_app#/g, "");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 添加框架二进制
|
|
104
|
+
if (!cmd.nobin) {
|
|
105
|
+
let pathSub = "jbuild_data/bin";
|
|
106
|
+
console.log(`scaning ${pathSub} ...`);
|
|
107
|
+
let fileList = glob(`${pathSub}/**/*`, {
|
|
108
|
+
dot: true,
|
|
109
|
+
sync: true,
|
|
110
|
+
nodir: true,
|
|
111
|
+
cwd: buildCfg.rootPath,
|
|
112
|
+
ignore: []
|
|
113
|
+
});
|
|
114
|
+
console.log(`add [${fileList.length}] files ...`);
|
|
115
|
+
for (let index = 0; index < fileList.length; index++) {
|
|
116
|
+
let element = fileList[index].substr(pathSub.length + 1);
|
|
117
|
+
|
|
118
|
+
// 添加文件
|
|
119
|
+
archive.file(`${buildCfg.rootPath}/${pathSub}/${element}`, { name: "update_a20/app/bin/" + element });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 添加框架apk
|
|
123
|
+
archive.file(`${buildCfg.rootPath}/jbuild_data/app-debug.apk`, { name: "update_a20/app/app-debug.apk" });
|
|
124
|
+
|
|
125
|
+
// 清空模型文件
|
|
126
|
+
shDataStr = shDataStr.replace(/#del_bin#/g, "");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 添加后台服务
|
|
130
|
+
if (!cmd.noweb) {
|
|
131
|
+
console.log(`add device_webapi files ...`);
|
|
132
|
+
// 载入zip
|
|
133
|
+
var myZip = new AdmZip(`${buildCfg.rootPath}/jbuild_data/device_webapi.zip`);
|
|
134
|
+
|
|
135
|
+
// 循环添加
|
|
136
|
+
var zipEntries = myZip.getEntries();
|
|
137
|
+
zipEntries.forEach(function (zipEntry) {
|
|
138
|
+
let tmpFile = path.resolve(`${buildCfg.rootPath}/jbuild_data/${zipEntry.entryName}.tmp`);
|
|
139
|
+
fs.writeFileSync(tmpFile, zipEntry.getData());
|
|
140
|
+
deleteList.push(tmpFile);
|
|
141
|
+
archive.file(tmpFile, { name: "update_a20/" + zipEntry.entryName });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 复制web
|
|
145
|
+
shDataStr = shDataStr.replace(/#copy_web#/g, "");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 恢复出厂设置
|
|
149
|
+
if (cmd.reset) {
|
|
150
|
+
shDataStr = shDataStr.replace(/#rest#/g, "");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 添加sh
|
|
154
|
+
try {
|
|
155
|
+
let tmpFile = path.resolve(`${buildCfg.rootPath}/jbuild_data/repair.sh.tmp`);
|
|
156
|
+
fs.writeFileSync(tmpFile, shDataStr);
|
|
157
|
+
deleteList.push(tmpFile);
|
|
158
|
+
archive.file(tmpFile, { name: "update_a20/repair.sh" });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 添加设备信息
|
|
163
|
+
try {
|
|
164
|
+
var pkgInfo = os.networkInterfaces();
|
|
165
|
+
pkgInfo.buildTime = jutils.getNowDate();
|
|
166
|
+
|
|
167
|
+
let tmpFile = path.resolve(`${buildCfg.rootPath}/jbuild_data/build_info.txt`);
|
|
168
|
+
fs.writeFileSync(tmpFile, JSON.stringify(pkgInfo, null, 4));
|
|
169
|
+
deleteList.push(tmpFile);
|
|
170
|
+
archive.file(tmpFile, { name: "update_a20/build_info.txt" });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 开始压缩
|
|
175
|
+
archive.finalize();
|
|
176
|
+
})().catch((err) => {
|
|
177
|
+
console.error(err.message);
|
|
178
|
+
console.error("build error,please run [jh5 update] first.");
|
|
179
|
+
try {
|
|
180
|
+
jutils.jfile_utils.deleteFile(buildCfg.outpath);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude 配置工具函数
|
|
5
|
+
* 供 setapi.js / setkey.js 共享使用
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var path = require('path');
|
|
10
|
+
|
|
11
|
+
const jpub_EnvObj = {
|
|
12
|
+
"ANTHROPIC_API_KEY": "",
|
|
13
|
+
"ANTHROPIC_BASE_URL": "",
|
|
14
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "",
|
|
15
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "",
|
|
16
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "",
|
|
17
|
+
"ANTHROPIC_MODEL": "",
|
|
18
|
+
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "",
|
|
19
|
+
"API_TIMEOUT_MS": "100000",
|
|
20
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
21
|
+
"DISABLE_AUTOUPDATER": "1",
|
|
22
|
+
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const jai_EnvObj = {
|
|
26
|
+
"ANTHROPIC_BASE_URL": "http://116.62.243.108:6030",
|
|
27
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "cc_opus",
|
|
28
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "cc_sonnet",
|
|
29
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "cc_haiku",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// AI 渠道列表(写死,后续可按需扩展差异化字段)
|
|
34
|
+
// ============================================================
|
|
35
|
+
var API_CHANNELS = [
|
|
36
|
+
{
|
|
37
|
+
id: 'jai_claude_128k',
|
|
38
|
+
name: 'JAI Claude 128K',
|
|
39
|
+
description: '自己搭建的网关,Claude模型(128K上下文,省钱)',
|
|
40
|
+
ccVersion: '2.1.22',
|
|
41
|
+
envObj: Object.assign({}, jpub_EnvObj, jai_EnvObj, {
|
|
42
|
+
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "60",
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'jai_low_cost',
|
|
47
|
+
name: 'JAI 低成本模型',
|
|
48
|
+
description: '目前为GLM-5',
|
|
49
|
+
ccVersion: '2.1.22',
|
|
50
|
+
envObj: Object.assign({}, jpub_EnvObj, jai_EnvObj, {
|
|
51
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-5",
|
|
52
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-5",
|
|
53
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.7",
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'jth_BestAiGate',
|
|
58
|
+
name: 'Best Ai Gate',
|
|
59
|
+
description: '比较便宜',
|
|
60
|
+
ccVersion: '2.1.22',
|
|
61
|
+
envObj: Object.assign({}, jpub_EnvObj, {
|
|
62
|
+
"ANTHROPIC_BASE_URL": "https://bestaigate.top",
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'jth_pincc_1M',
|
|
67
|
+
name: 'Pincc 1M',
|
|
68
|
+
description: 'sub2api作者网关(1M上下文,越聊越贵)',
|
|
69
|
+
ccVersion: '2.1.76',
|
|
70
|
+
envObj: Object.assign({}, jpub_EnvObj, {
|
|
71
|
+
"ANTHROPIC_BASE_URL": "https://v2-as.pincc.ai",
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 读取 JSON 文件,出错返回默认值
|
|
78
|
+
* @param {string} filePath 文件路径
|
|
79
|
+
* @param {*} defaultVal 出错时的默认值
|
|
80
|
+
* @returns {*}
|
|
81
|
+
*/
|
|
82
|
+
function readJsonFile(filePath, defaultVal) {
|
|
83
|
+
try {
|
|
84
|
+
var content = fs.readFileSync(filePath, 'utf8');
|
|
85
|
+
return JSON.parse(content);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return defaultVal;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 写入 JSON 文件(自动创建目录)
|
|
93
|
+
* @param {string} filePath 文件路径
|
|
94
|
+
* @param {*} data 要写入的对象
|
|
95
|
+
*/
|
|
96
|
+
function writeJsonFile(filePath, data) {
|
|
97
|
+
var dir = path.dirname(filePath);
|
|
98
|
+
if (!fs.existsSync(dir)) {
|
|
99
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 4), 'utf8');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 读取 settings.json,确保 env 字段存在
|
|
106
|
+
* @param {string} settingsPath settings.json 路径
|
|
107
|
+
* @returns {object}
|
|
108
|
+
*/
|
|
109
|
+
function loadSettings(settingsPath) {
|
|
110
|
+
var settings = readJsonFile(settingsPath, {});
|
|
111
|
+
if (!settings.env) {
|
|
112
|
+
settings.env = {};
|
|
113
|
+
}
|
|
114
|
+
return settings;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 根据 settings 中的 __jid 字段匹配当前渠道
|
|
119
|
+
* @param {object} settings Claude settings.json 对象
|
|
120
|
+
* @returns {{ channel: object|null, key: string, jid: string }}
|
|
121
|
+
*/
|
|
122
|
+
function getCurrentInfo(settings) {
|
|
123
|
+
// 当前渠道 id,存储在 settings 顶层 __jid 字段
|
|
124
|
+
var currentJid = settings.__jid || '';
|
|
125
|
+
var currentKey = (settings.env && settings.env.ANTHROPIC_API_KEY) || '';
|
|
126
|
+
// 按 id 匹配渠道
|
|
127
|
+
var channel = currentJid ? (API_CHANNELS.find(function (c) { return c.id === currentJid; }) || null) : null;
|
|
128
|
+
return { channel: channel, key: currentKey, jid: currentJid };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 对 API Key 做脱敏显示(前4后4,中间****)
|
|
133
|
+
* @param {string} key
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function maskKey(key) {
|
|
137
|
+
if (!key) return '(未设置)';
|
|
138
|
+
if (key.length > 8) return key.slice(0, 4) + '****' + key.slice(-4);
|
|
139
|
+
return '****';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 打印当前渠道和Key状态
|
|
144
|
+
* @param {object} settings
|
|
145
|
+
*/
|
|
146
|
+
function printCurrentInfo(settings) {
|
|
147
|
+
var info = getCurrentInfo(settings);
|
|
148
|
+
console.log('\n====== 当前 Claude 配置 ======');
|
|
149
|
+
if (info.channel) {
|
|
150
|
+
console.log('当前渠道: ' + info.channel.name + ' (' + info.channel.id + ')');
|
|
151
|
+
} else {
|
|
152
|
+
console.log('当前渠道: 未知/自定义 (' + (info.jid || '未设置') + ')');
|
|
153
|
+
}
|
|
154
|
+
console.log('当前 Key: ' + maskKey(info.key));
|
|
155
|
+
console.log('==============================\n');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
API_CHANNELS,
|
|
160
|
+
readJsonFile,
|
|
161
|
+
writeJsonFile,
|
|
162
|
+
loadSettings,
|
|
163
|
+
getCurrentInfo,
|
|
164
|
+
maskKey,
|
|
165
|
+
printCurrentInfo,
|
|
166
|
+
};
|
package/lib/setapi.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setapi 命令:交互式选择 AI 渠道
|
|
5
|
+
* 将选中的渠道 id 写入 ~/.claude/settings.json 的 __jid 字段
|
|
6
|
+
* 渠道专属动作(如 baseUrl 等)由后续扩展的 if 分支单独处理
|
|
7
|
+
* 若渠道无缓存 Key,则提示用户输入并保存到 ~/.claude/jcc.json
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// inquirer v9+ 拆分为独立包,使用 @inquirer/prompts 的具名函数
|
|
11
|
+
var { select, input } = require('@inquirer/prompts');
|
|
12
|
+
var utils = require('./claude_utils');
|
|
13
|
+
|
|
14
|
+
module.exports = async function (cmd, buildCfg) {
|
|
15
|
+
var settings = utils.loadSettings(buildCfg.claudeSettingsPath);
|
|
16
|
+
var jccJson = utils.readJsonFile(buildCfg.jccJsonPath, {});
|
|
17
|
+
|
|
18
|
+
// 显示当前配置状态
|
|
19
|
+
utils.printCurrentInfo(settings);
|
|
20
|
+
|
|
21
|
+
// 构建渠道选择列表
|
|
22
|
+
var choices = utils.API_CHANNELS.map(function (c) {
|
|
23
|
+
return {
|
|
24
|
+
name: c.name + ' — ' + c.description,
|
|
25
|
+
value: c.id,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 弹出渠道选择列表
|
|
30
|
+
var channelId = await select({
|
|
31
|
+
message: '请选择 AI 渠道:',
|
|
32
|
+
choices: choices,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 找到选中的渠道配置
|
|
36
|
+
var selected = utils.API_CHANNELS.find(function (c) { return c.id === channelId; });
|
|
37
|
+
|
|
38
|
+
// 将选中渠道 id 写入 settings.__jid,作为当前渠道标识
|
|
39
|
+
settings.__jid = selected.id;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// 强制合入 claudeJsonPath(~/.claude.json)的固定字段
|
|
43
|
+
// 禁用自动更新、标记已完成引导,避免每次启动弹窗
|
|
44
|
+
var claudeJson = utils.readJsonFile(buildCfg.claudeJsonPath, {});
|
|
45
|
+
claudeJson.autoUpdaterStatus = 'disabled';
|
|
46
|
+
claudeJson.hasCompletedOnboarding = true;
|
|
47
|
+
utils.writeJsonFile(buildCfg.claudeJsonPath, claudeJson);
|
|
48
|
+
|
|
49
|
+
// 强制合入 claudeSettingsPath(~/.claude/settings.json)的固定字段
|
|
50
|
+
// 设置 SessionStart Hook(上报提示)、界面语言、更新渠道
|
|
51
|
+
settings.hooks = {
|
|
52
|
+
"SessionStart": [
|
|
53
|
+
{
|
|
54
|
+
"matcher": ".*",
|
|
55
|
+
"hooks": [{ "type": "command", "command": "curl -s -m 2 http://116.62.243.108:7180/pub/ai_tip?os=%OS%" }]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
settings.language = '简体中文';
|
|
60
|
+
settings.autoUpdatesChannel = 'stable';
|
|
61
|
+
|
|
62
|
+
// 将选中渠道的 envObj 合并到 settings.env
|
|
63
|
+
// 合并后值为 null 或 "" 的 env 属性一律删除(避免写入空配置干扰 Claude)
|
|
64
|
+
if (selected.envObj) {
|
|
65
|
+
Object.assign(settings.env, selected.envObj);
|
|
66
|
+
}
|
|
67
|
+
Object.keys(settings.env).forEach(function (key) {
|
|
68
|
+
var val = settings.env[key];
|
|
69
|
+
if (val === null || val === '') {
|
|
70
|
+
delete settings.env[key];
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 查询该渠道是否有缓存的 Key
|
|
75
|
+
var savedKey = (jccJson.keys && jccJson.keys[selected.id]) || '';
|
|
76
|
+
if (savedKey) {
|
|
77
|
+
// 已有缓存 Key,直接切换
|
|
78
|
+
settings.env.ANTHROPIC_API_KEY = savedKey;
|
|
79
|
+
utils.writeJsonFile(buildCfg.claudeSettingsPath, settings);
|
|
80
|
+
console.log('✅ 已切换到渠道: ' + selected.name);
|
|
81
|
+
console.log(' Key: ' + utils.maskKey(savedKey) + ' (来自本地缓存)');
|
|
82
|
+
} else {
|
|
83
|
+
// 无缓存 Key,提示用户输入
|
|
84
|
+
console.log('⚠️ 渠道 [' + selected.name + '] 尚未设置 Key,请输入:');
|
|
85
|
+
var newKey = await input({
|
|
86
|
+
message: '请输入 ANTHROPIC_API_KEY:',
|
|
87
|
+
validate: function (v) {
|
|
88
|
+
return v.trim().length > 0 ? true : 'Key 不能为空';
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
newKey = newKey.trim();
|
|
92
|
+
|
|
93
|
+
// 写入 settings.json
|
|
94
|
+
settings.env.ANTHROPIC_API_KEY = newKey;
|
|
95
|
+
utils.writeJsonFile(buildCfg.claudeSettingsPath, settings);
|
|
96
|
+
|
|
97
|
+
// 缓存到 jcc.json
|
|
98
|
+
if (!jccJson.keys) { jccJson.keys = {}; }
|
|
99
|
+
jccJson.keys[selected.id] = newKey;
|
|
100
|
+
utils.writeJsonFile(buildCfg.jccJsonPath, jccJson);
|
|
101
|
+
|
|
102
|
+
console.log('✅ 已切换到渠道: ' + selected.name + ',Key 已保存到本地缓存。');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 检测当前 Claude 版本是否与渠道要求一致,不一致则从 OSS 下载 tgz 安装
|
|
106
|
+
var requiredVer = selected.ccVersion || '';
|
|
107
|
+
var currentVer = buildCfg.claudeVersion || '';
|
|
108
|
+
if (requiredVer && requiredVer !== currentVer) {
|
|
109
|
+
console.log('\n⚠️ Claude 版本不匹配:当前 ' + (currentVer || '未知') + ',渠道要求 ' + requiredVer);
|
|
110
|
+
// 拼接 OSS tgz 下载地址,文件名规则:claude-code-{version}.tgz
|
|
111
|
+
var tgzUrl = 'https://jdwfiles.oss-cn-hangzhou.aliyuncs.com/npm_pkg/claude-code-' + requiredVer + '.tgz';
|
|
112
|
+
console.log('🔄 正在从 OSS 下载并安装: ' + tgzUrl);
|
|
113
|
+
try {
|
|
114
|
+
// 直接将 tgz URL 传给 npm install -g,npm 支持从 URL 安装 tgz 包
|
|
115
|
+
// --force 覆盖已有版本,stdio: 'inherit' 让 npm 进度实时打印到终端
|
|
116
|
+
execSync('npm install -g ' + tgzUrl + ' --force', { stdio: 'inherit' });
|
|
117
|
+
console.log('✅ Claude 已安装到 ' + requiredVer);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error('❌ 版本安装失败: ' + e.message);
|
|
120
|
+
}
|
|
121
|
+
} else if (requiredVer) {
|
|
122
|
+
console.log('✅ Claude 版本匹配: ' + currentVer);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
package/lib/setkey.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setkey 命令:设置当前渠道的 API Key
|
|
5
|
+
* 修改 ~/.claude/settings.json 中的 ANTHROPIC_API_KEY
|
|
6
|
+
* 同时将 Key 缓存到 ~/.claude/jcc.json 对应渠道下
|
|
7
|
+
* 当前渠道通过 settings.__jid 字段识别
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// inquirer v9+ 拆分为独立包,使用 @inquirer/prompts 的具名函数
|
|
11
|
+
var { input } = require('@inquirer/prompts');
|
|
12
|
+
var utils = require('./claude_utils');
|
|
13
|
+
|
|
14
|
+
module.exports = async function (cmd, buildCfg) {
|
|
15
|
+
var settings = utils.loadSettings(buildCfg.claudeSettingsPath);
|
|
16
|
+
var jccJson = utils.readJsonFile(buildCfg.jccJsonPath, {});
|
|
17
|
+
|
|
18
|
+
// 显示当前配置状态
|
|
19
|
+
utils.printCurrentInfo(settings);
|
|
20
|
+
|
|
21
|
+
// 提示用户输入新 Key
|
|
22
|
+
var newKey = await input({
|
|
23
|
+
message: '请输入新的 ANTHROPIC_API_KEY:',
|
|
24
|
+
validate: function (v) {
|
|
25
|
+
return v.trim().length > 0 ? true : 'Key 不能为空';
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
newKey = newKey.trim();
|
|
29
|
+
|
|
30
|
+
// 写入 settings.json
|
|
31
|
+
settings.env.ANTHROPIC_API_KEY = newKey;
|
|
32
|
+
utils.writeJsonFile(buildCfg.claudeSettingsPath, settings);
|
|
33
|
+
|
|
34
|
+
// 若当前渠道可识别(__jid 有效),同步缓存到 jcc.json
|
|
35
|
+
var info = utils.getCurrentInfo(settings);
|
|
36
|
+
if (info.channel) {
|
|
37
|
+
if (!jccJson.keys) { jccJson.keys = {}; }
|
|
38
|
+
jccJson.keys[info.channel.id] = newKey;
|
|
39
|
+
utils.writeJsonFile(buildCfg.jccJsonPath, jccJson);
|
|
40
|
+
console.log('✅ Key 已更新,并缓存到渠道 [' + info.channel.name + '] 的本地记录。');
|
|
41
|
+
} else {
|
|
42
|
+
console.log('✅ Key 已更新到 settings.json。(当前渠道未在列表中,Key 未缓存到 jcc.json)');
|
|
43
|
+
}
|
|
44
|
+
};
|
package/main.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "base_parts_ai",
|
|
3
|
+
"version": "1.0.30",
|
|
4
|
+
"description": "jaskle base_parts_ai",
|
|
5
|
+
"main": "./main.js",
|
|
6
|
+
"registry": true,
|
|
7
|
+
"directories": {},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node ./test_jcc.js"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"jcc": "./bin/jcc.js"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">= 0.8.0"
|
|
16
|
+
},
|
|
17
|
+
"author": "jaskle",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"base_parts": "^2.*",
|
|
21
|
+
"commander": "^14.0.3",
|
|
22
|
+
"inquirer": "^13.3.2"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"base_parts_types": "^2.*"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"ANTHROPIC_BASE_URL": "https://bestaigate.top",
|
|
4
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
5
|
+
"DISABLE_AUTOUPDATER": "1",
|
|
6
|
+
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
|
|
7
|
+
"ANTHROPIC_API_KEY": "sk-bestgate-key-xxxx"
|
|
8
|
+
},
|
|
9
|
+
"__jid": "jth_BestAiGate",
|
|
10
|
+
"hooks": {
|
|
11
|
+
"SessionStart": [
|
|
12
|
+
{
|
|
13
|
+
"matcher": ".*",
|
|
14
|
+
"hooks": [
|
|
15
|
+
{
|
|
16
|
+
"type": "command",
|
|
17
|
+
"command": "curl -s -m 2 http://116.62.243.108:7180/pub/ai_tip?os=%OS%"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"language": "简体中文",
|
|
24
|
+
"autoUpdatesChannel": "stable"
|
|
25
|
+
}
|