@2api-store/activate 0.5.2 → 0.6.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/activate.mjs +2 -0
- package/dist/lib/api.mjs +1 -0
- package/dist/lib/backup.mjs +1 -0
- package/dist/lib/cmds.mjs +1 -0
- package/dist/lib/detect.mjs +1 -0
- package/dist/lib/device.mjs +1 -0
- package/dist/lib/install.mjs +1 -0
- package/dist/lib/platforms.mjs +1 -0
- package/dist/lib/pow.mjs +1 -0
- package/dist/lib/prompt.mjs +1 -0
- package/dist/lib/render.mjs +1 -0
- package/dist/lib/ui.mjs +1 -0
- package/dist/lib/write.mjs +1 -0
- package/dist/package.json +5 -0
- package/package.json +10 -5
- package/activate.mjs +0 -540
- package/lib/api.mjs +0 -184
- package/lib/backup.mjs +0 -109
- package/lib/cmds.mjs +0 -546
- package/lib/detect.mjs +0 -122
- package/lib/install.mjs +0 -110
- package/lib/platforms.mjs +0 -123
- package/lib/prompt.mjs +0 -160
- package/lib/render.mjs +0 -94
- package/lib/ui.mjs +0 -90
- package/lib/write.mjs +0 -57
package/activate.mjs
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// 抑制 node DeprecationWarning(DEP0190 spawnSync shell 等)+ 透传子进程
|
|
3
|
-
process.removeAllListeners('warning')
|
|
4
|
-
process.env.NODE_NO_WARNINGS = '1'
|
|
5
|
-
/**
|
|
6
|
-
* @2api-store/activate — TokenShop CLI 一键激活(v0.3.0+)
|
|
7
|
-
*
|
|
8
|
-
* 入口(argv[2] 分发):
|
|
9
|
-
* - 无子命令 / 选项(--help / --version / --dry-run / --platform / --api-base)
|
|
10
|
-
* → 走默认激活流程 main()
|
|
11
|
-
*
|
|
12
|
-
* 默认激活主流程(薄编排,逻辑在各 lib 模块):
|
|
13
|
-
* 1. banner + 检测环境 + 列已装/未装 CLI
|
|
14
|
-
* 2. 输入凭据(sk- → API Key;否则兑换码 + email)
|
|
15
|
-
* 3. POST /api/v1/cli/activate → 配置包
|
|
16
|
-
* 4. 多选 CLI(主 CLI 默认选;opencode 默认不选;未装的标 ✗)
|
|
17
|
-
* 5. 未装 CLI → ensureInstalled(询问确认 → npm i -g)
|
|
18
|
-
* 6. 选默认模型
|
|
19
|
-
* 7. 备份 → 渲染 → 写入(每个文件彩色 ok/fail)
|
|
20
|
-
* 8. 验证 API Key(GET /v1/models)
|
|
21
|
-
* 9. 报告 + 打印启动/使用指南(每个激活 CLI 的 startCmd + usage)
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { detectAll } from './lib/detect.mjs'
|
|
25
|
-
import { cliActivate, listGroups, verifyApiKey } from './lib/api.mjs'
|
|
26
|
-
import { renderContent, expandPath, listModels } from './lib/render.mjs'
|
|
27
|
-
import { backupFile } from './lib/backup.mjs'
|
|
28
|
-
import { writeFileWithPermission, PermissionDeniedError } from './lib/write.mjs'
|
|
29
|
-
import {
|
|
30
|
-
ask,
|
|
31
|
-
askSecret,
|
|
32
|
-
multiSelect,
|
|
33
|
-
select,
|
|
34
|
-
confirm,
|
|
35
|
-
closePrompt,
|
|
36
|
-
} from './lib/prompt.mjs'
|
|
37
|
-
import { banner, ok, fail, warn, info, step, guide, plain, chalk } from './lib/ui.mjs'
|
|
38
|
-
import { getCliMeta, isDefaultSelected } from './lib/platforms.mjs'
|
|
39
|
-
import { ensureInstalled } from './lib/install.mjs'
|
|
40
|
-
import { SUBCOMMANDS, SUBCOMMAND_NAMES, SUB_HELP } from './lib/cmds.mjs'
|
|
41
|
-
|
|
42
|
-
import { createRequire } from 'node:module'
|
|
43
|
-
const VERSION = createRequire(import.meta.url)('./package.json').version
|
|
44
|
-
const DEFAULT_API_BASE = 'https://2api.store'
|
|
45
|
-
|
|
46
|
-
// ============================================================
|
|
47
|
-
// 参数解析 / 帮助 / 凭据分流
|
|
48
|
-
// ============================================================
|
|
49
|
-
|
|
50
|
-
function parseArgs(argv) {
|
|
51
|
-
const args = { dryRun: false, platform: null, _: [] }
|
|
52
|
-
for (let i = 0; i < argv.length; i++) {
|
|
53
|
-
const a = argv[i]
|
|
54
|
-
if (a === '--dry-run' || a === '-n') {
|
|
55
|
-
args.dryRun = true
|
|
56
|
-
} else if (a === '--platform' || a === '-p') {
|
|
57
|
-
args.platform = argv[++i]
|
|
58
|
-
} else if (a === '--api-base') {
|
|
59
|
-
args.apiBase = argv[++i]
|
|
60
|
-
} else if (a === '--help' || a === '-h') {
|
|
61
|
-
args.help = true
|
|
62
|
-
} else if (a === '--version' || a === '-v') {
|
|
63
|
-
args.version = true
|
|
64
|
-
} else if (a.startsWith('--') && a.includes('=')) {
|
|
65
|
-
const [k, v] = a.slice(2).split(/=(.*)/, 2)
|
|
66
|
-
args[k] = v
|
|
67
|
-
} else if (a.startsWith('--')) {
|
|
68
|
-
args[a.replace(/^--/, '')] = argv[++i]
|
|
69
|
-
} else {
|
|
70
|
-
args._.push(a)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return args
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function printHelp() {
|
|
77
|
-
console.log(`
|
|
78
|
-
@2api-store/activate v${VERSION} — TokenShop CLI 一键激活
|
|
79
|
-
|
|
80
|
-
用法:
|
|
81
|
-
npx @2api-store/activate [options] 默认激活流程
|
|
82
|
-
npx @2api-store/activate <subcommand> [options] 运行子命令
|
|
83
|
-
|
|
84
|
-
选项(默认激活流程):
|
|
85
|
-
--dry-run, -n 不真写配置/不真装 CLI,只展示将做什么
|
|
86
|
-
--platform <name> 只激活指定平台(openai/anthropic/gemini/grok/antigravity)
|
|
87
|
-
--api-base <url> 覆盖 API 端点(默认 $ACTIVATE_API_BASE 或 ${DEFAULT_API_BASE})
|
|
88
|
-
-h, --help 显示帮助
|
|
89
|
-
-v, --version 显示版本
|
|
90
|
-
|
|
91
|
-
${SUB_HELP}
|
|
92
|
-
|
|
93
|
-
环境变量:
|
|
94
|
-
ACTIVATE_API_BASE 覆盖 API 端点(同 --api-base)
|
|
95
|
-
|
|
96
|
-
示例:
|
|
97
|
-
npx @2api-store/activate # 默认激活
|
|
98
|
-
npx @2api-store/activate --dry-run # 试运行
|
|
99
|
-
npx @2api-store/activate --platform openai # 只激活 openai
|
|
100
|
-
npx @2api-store/activate restore # 从备份恢复
|
|
101
|
-
npx @2api-store/activate reset # 恢复官方默认
|
|
102
|
-
npx @2api-store/activate refresh-groups # 查看可用分组
|
|
103
|
-
ACTIVATE_API_BASE=http://127.0.0.1:5566 npx @2api-store/activate
|
|
104
|
-
|
|
105
|
-
退出码:
|
|
106
|
-
0 成功
|
|
107
|
-
1 输入错误 / 网络失败 / 致命错误
|
|
108
|
-
2 部分文件权限不足(请按提示用 sudo / 管理员 PowerShell 重跑)
|
|
109
|
-
3 其他写入失败
|
|
110
|
-
`.trim())
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 凭据格式分流:sk- → key;16-64 字符 [a-zA-Z0-9] → redeem
|
|
115
|
-
*/
|
|
116
|
-
function classifyCredential(cred) {
|
|
117
|
-
if (!cred) return 'empty'
|
|
118
|
-
if (cred.startsWith('sk-')) return 'key'
|
|
119
|
-
if (/^[a-zA-Z0-9]{16,64}$/.test(cred)) return 'redeem'
|
|
120
|
-
return 'unknown'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 从模型 ID 列表推断平台集合(脚本兜底,与端点 inferPlatforms 对应)
|
|
125
|
-
*/
|
|
126
|
-
function inferModelPlatforms(models) {
|
|
127
|
-
const set = new Set()
|
|
128
|
-
for (const m of models || []) {
|
|
129
|
-
const id = (typeof m === 'string' ? m : m?.id || '').toLowerCase()
|
|
130
|
-
if (/^(gpt|o1|o3|codex|glm|minimax|deepseek)/.test(id)) set.add('openai')
|
|
131
|
-
else if (id.startsWith('claude')) set.add('anthropic')
|
|
132
|
-
else if (id.startsWith('gemini')) set.add('gemini')
|
|
133
|
-
else if (id.startsWith('grok')) set.add('grok')
|
|
134
|
-
}
|
|
135
|
-
return [...set]
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================================
|
|
139
|
-
// 主流程
|
|
140
|
-
// ============================================================
|
|
141
|
-
|
|
142
|
-
async function main() {
|
|
143
|
-
// 注意:必须在 parseArgs 之前判断,因为子命令有自己的 argv 解析(--api-base / --dry-run)
|
|
144
|
-
const firstArg = process.argv[2]
|
|
145
|
-
if (firstArg && !firstArg.startsWith('-') && SUBCOMMAND_NAMES.has(firstArg)) {
|
|
146
|
-
const subRest = process.argv.slice(3)
|
|
147
|
-
const entry = SUBCOMMANDS[firstArg]
|
|
148
|
-
try {
|
|
149
|
-
const code = await entry.handler(subRest)
|
|
150
|
-
return code || 0
|
|
151
|
-
} catch (err) {
|
|
152
|
-
fail(`子命令 ${firstArg} 异常:${err?.message || err}`)
|
|
153
|
-
if (err?.stack) console.error(err.stack)
|
|
154
|
-
return 1
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const args = parseArgs(process.argv.slice(2))
|
|
159
|
-
if (args.help) {
|
|
160
|
-
printHelp()
|
|
161
|
-
return 0
|
|
162
|
-
}
|
|
163
|
-
if (args.version) {
|
|
164
|
-
console.log(VERSION)
|
|
165
|
-
return 0
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// === 1. banner + 环境检测 ===
|
|
169
|
-
banner()
|
|
170
|
-
const env = detectAll()
|
|
171
|
-
const osLabel = env.os.isMac
|
|
172
|
-
? 'macOS'
|
|
173
|
-
: env.os.isLinux
|
|
174
|
-
? 'Linux'
|
|
175
|
-
: env.os.isWindows
|
|
176
|
-
? 'Windows'
|
|
177
|
-
: env.os.platform
|
|
178
|
-
info(`环境:${osLabel} · node v${env.node.version}`)
|
|
179
|
-
if (!env.node.supported) {
|
|
180
|
-
fail(`需要 node >= 18,当前 ${env.node.version}`)
|
|
181
|
-
return 1
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 列出已装/未装 CLI(彩色)
|
|
185
|
-
const installedNames = Object.keys(env.installed)
|
|
186
|
-
const installedList = installedNames.map((n) => {
|
|
187
|
-
const v = env.installed[n]?.version
|
|
188
|
-
return v ? `${n}@${v}` : n
|
|
189
|
-
})
|
|
190
|
-
if (installedNames.length > 0) {
|
|
191
|
-
ok(`已检测到 CLI:${installedList.join(', ')}`)
|
|
192
|
-
} else {
|
|
193
|
-
info('未检测到 codex/claude/gemini/grok/opencode(可在后续步骤按需自动安装)')
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const apiBase = args.apiBase || process.env.ACTIVATE_API_BASE || DEFAULT_API_BASE
|
|
197
|
-
info(`API 端点:${apiBase}`)
|
|
198
|
-
if (args.dryRun) warn('【试运行模式 --dry-run】不会真写文件、不会真装 CLI')
|
|
199
|
-
if (args.platform) info(`平台过滤:${args.platform}`)
|
|
200
|
-
console.log()
|
|
201
|
-
|
|
202
|
-
// === 2. 输入凭据 ===
|
|
203
|
-
const credential = (await askSecret('请输入 API Key(sk-...)或兑换码')).trim()
|
|
204
|
-
if (!credential) {
|
|
205
|
-
fail('未输入凭据')
|
|
206
|
-
return 1
|
|
207
|
-
}
|
|
208
|
-
const kind = classifyCredential(credential)
|
|
209
|
-
if (kind === 'unknown') {
|
|
210
|
-
fail('凭据格式无法识别(应为 sk- 前缀的 API Key 或兑换码)')
|
|
211
|
-
return 1
|
|
212
|
-
}
|
|
213
|
-
info(`识别为:${kind === 'key' ? 'API Key' : '兑换码'}模式`)
|
|
214
|
-
|
|
215
|
-
// === 3. 兑换码 → 邮箱 + 选组 ===
|
|
216
|
-
let email = ''
|
|
217
|
-
let groupId = null
|
|
218
|
-
if (kind === 'redeem') {
|
|
219
|
-
email = (await ask('请输入邮箱(用于兑换码绑定 / 新账号创建)')).trim()
|
|
220
|
-
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
221
|
-
fail('邮箱格式不正确')
|
|
222
|
-
return 1
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 选组:兑换码模式都走选组流程(balance 类型码用 group_id 绑定 + 加余额 + 建 key;
|
|
226
|
-
// api_key 类型码若码已绑组则后端忽略传入的 group_id,但 CLI 选组无害,统一流程)
|
|
227
|
-
step('获取可用分组')
|
|
228
|
-
const groups = await listGroups({ apiBase })
|
|
229
|
-
if (groups.length === 0) {
|
|
230
|
-
fail('未查询到可用分组。请联系管理员开通分组后再激活。')
|
|
231
|
-
return 1
|
|
232
|
-
}
|
|
233
|
-
const groupItems = groups.map((g) => ({ label: `${g.name}`, value: g.id }))
|
|
234
|
-
const groupIdx = await select('选择绑定的分组', groupItems, { defaultIndex: 0 })
|
|
235
|
-
groupId = groups[groupIdx]?.id
|
|
236
|
-
if (!groupId) {
|
|
237
|
-
fail('未选择分组')
|
|
238
|
-
return 1
|
|
239
|
-
}
|
|
240
|
-
ok(`已选分组:${groups[groupIdx].name}(ID ${groupId})`)
|
|
241
|
-
console.log()
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// === 4. POST /cli/activate ===
|
|
245
|
-
step('请求激活服务')
|
|
246
|
-
let bundle
|
|
247
|
-
try {
|
|
248
|
-
bundle = await cliActivate({ credential, email, platform: args.platform, groupId, apiBase })
|
|
249
|
-
} catch (err) {
|
|
250
|
-
fail(`激活失败:${err.message}`)
|
|
251
|
-
if (err.statusCode) fail(` HTTP ${err.statusCode}`)
|
|
252
|
-
return 1
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
ok(`模式:${bundle.mode}${bundle.user?.is_new ? '(新用户已创建)' : ''}`)
|
|
256
|
-
if (bundle.user?.is_new && bundle.user?.email) {
|
|
257
|
-
info(`新账号:${bundle.user.email}`)
|
|
258
|
-
if (bundle.user.message) info(bundle.user.message)
|
|
259
|
-
}
|
|
260
|
-
console.log()
|
|
261
|
-
|
|
262
|
-
// === 5. 过滤配置(os_type + 平台) + 多选 CLI(主 CLI 默认选,opencode 默认不选)===
|
|
263
|
-
const allowedPlatforms = inferModelPlatforms(bundle.models || [])
|
|
264
|
-
if (args.platform && !allowedPlatforms.includes(args.platform)) {
|
|
265
|
-
allowedPlatforms.push(args.platform)
|
|
266
|
-
}
|
|
267
|
-
const osConfigs = (bundle.configs || []).filter((c) => {
|
|
268
|
-
if (!c) return false
|
|
269
|
-
if (allowedPlatforms.length > 0 && !allowedPlatforms.includes(c.platform)) return false
|
|
270
|
-
if (args.platform && c.platform !== args.platform) return false
|
|
271
|
-
if (env.os.isWindows) {
|
|
272
|
-
return c.os_type === 'windows' || c.os_type === 'all'
|
|
273
|
-
}
|
|
274
|
-
return c.os_type === 'unix' || c.os_type === 'all'
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
if (osConfigs.length === 0) {
|
|
278
|
-
fail('服务端未返回适配当前系统的配置模板')
|
|
279
|
-
return 1
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// dedupe by platform + client_type,构建多选项
|
|
283
|
-
const seen = new Set()
|
|
284
|
-
const cliItems = []
|
|
285
|
-
for (const c of osConfigs) {
|
|
286
|
-
const key = `${c.platform}/${c.client_type}`
|
|
287
|
-
if (seen.has(key)) continue
|
|
288
|
-
seen.add(key)
|
|
289
|
-
const meta = getCliMeta(c.client_type)
|
|
290
|
-
const displayName = c.display_name || c.displayName || meta?.name || `${c.platform} ${c.client_type}`
|
|
291
|
-
const installed = env.installed[c.client_type]?.path || (meta && env.installed[meta.detectBin]?.path)
|
|
292
|
-
const primaryMark = isDefaultSelected(c.client_type) ? '' : '(独立 CLI)'
|
|
293
|
-
const nameColored = installed ? chalk.green(displayName) : chalk.gray(`${displayName} (未装)`)
|
|
294
|
-
cliItems.push({
|
|
295
|
-
label: `${nameColored} ${chalk.dim(`[${c.platform}/${c.client_type}]`)}${primaryMark}`,
|
|
296
|
-
config: c,
|
|
297
|
-
clientType: c.client_type,
|
|
298
|
-
meta,
|
|
299
|
-
alreadyInstalled: !!installed,
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// 默认选中:主 CLI(isPrimary=true)。dry-run 也用此默认(不因 dry-run 改默认)
|
|
304
|
-
const defaultSelected = cliItems
|
|
305
|
-
.map((item, i) => ({ item, i }))
|
|
306
|
-
.filter(({ item }) => isDefaultSelected(item.clientType))
|
|
307
|
-
.map(({ i }) => i)
|
|
308
|
-
|
|
309
|
-
step('选择 CLI')
|
|
310
|
-
const selectedIdxes = await multiSelect(
|
|
311
|
-
'选择要激活的 CLI(绿色=已装,灰色=未装;默认选主 CLI,独立 CLI 默认不选)',
|
|
312
|
-
cliItems,
|
|
313
|
-
{ defaultSelected },
|
|
314
|
-
)
|
|
315
|
-
if (selectedIdxes.length === 0) {
|
|
316
|
-
plain('未选择任何 CLI,退出。')
|
|
317
|
-
return 0
|
|
318
|
-
}
|
|
319
|
-
const selectedItems = selectedIdxes.map((i) => cliItems[i])
|
|
320
|
-
console.log()
|
|
321
|
-
|
|
322
|
-
// === 6. 未装 CLI → 询问确认 → npm i -g(dry-run 跳过真装)===
|
|
323
|
-
if (!args.dryRun) {
|
|
324
|
-
step('检查 CLI 安装状态')
|
|
325
|
-
for (const item of selectedItems) {
|
|
326
|
-
if (item.alreadyInstalled) {
|
|
327
|
-
const ver = env.installed[item.clientType]?.version || env.installed[item.meta?.detectBin]?.version
|
|
328
|
-
ok(`${item.meta?.name || item.clientType} 已安装${ver ? `(v${ver})` : ''}`)
|
|
329
|
-
continue
|
|
330
|
-
}
|
|
331
|
-
if (!item.meta || !item.meta.installPackage) {
|
|
332
|
-
// 未知 CLI(如 grok 无官方 npm 包)→ 只提示不装
|
|
333
|
-
warn(`${item.clientType} 无可自动安装的 npm 包,请手动安装后重跑(配置仍会写入)`)
|
|
334
|
-
continue
|
|
335
|
-
}
|
|
336
|
-
info(`未检测到 ${item.meta.name}(命令 ${item.meta.detectBin})`)
|
|
337
|
-
const r = await ensureInstalled(item.meta, { dryRun: args.dryRun })
|
|
338
|
-
if (r.installed) {
|
|
339
|
-
ok(`${item.meta.name} 安装成功${r.version ? `(v${r.version})` : ''}`)
|
|
340
|
-
} else if (r.skipped && r.reason === 'user-declined') {
|
|
341
|
-
warn(`已跳过 ${item.meta.name} 安装(配置仍会写入,但请手动安装后使用)`)
|
|
342
|
-
} else if (r.skipped) {
|
|
343
|
-
info(`已跳过 ${item.meta.name} 安装(${r.reason})`)
|
|
344
|
-
} else {
|
|
345
|
-
fail(`${item.meta.name} 安装失败:${r.error || '未知错误'}(配置仍会写入,但请手动安装后使用)`)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
console.log()
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// === 7. 选默认模型 ===
|
|
352
|
-
const modelItems = listModels(bundle.models || [])
|
|
353
|
-
let defaultModelId = ''
|
|
354
|
-
if (modelItems.length > 0) {
|
|
355
|
-
step('选择默认模型(最新优先)')
|
|
356
|
-
const TOP_N = 10
|
|
357
|
-
const topItems = modelItems.slice(0, TOP_N)
|
|
358
|
-
const hasMore = modelItems.length > TOP_N
|
|
359
|
-
const selectItems = [
|
|
360
|
-
...topItems.map((m) => ({ label: `${m.label} (${m.id})` })),
|
|
361
|
-
...(hasMore ? [{ label: chalk.dim('其他(手动输入模型 ID)') }] : []),
|
|
362
|
-
]
|
|
363
|
-
const idx = await select('选择默认模型', selectItems, { defaultIndex: 0 })
|
|
364
|
-
if (hasMore && idx === topItems.length) {
|
|
365
|
-
// 手动输入
|
|
366
|
-
defaultModelId = (await ask('输入模型 ID(如 gpt-5.6-terra)')).trim()
|
|
367
|
-
if (!defaultModelId) defaultModelId = topItems[0].id
|
|
368
|
-
} else {
|
|
369
|
-
defaultModelId = topItems[idx].id
|
|
370
|
-
}
|
|
371
|
-
ok(`已选默认模型:${defaultModelId}`)
|
|
372
|
-
} else {
|
|
373
|
-
warn('服务端未返回模型列表,{{defaultModel}} 将留空')
|
|
374
|
-
}
|
|
375
|
-
console.log()
|
|
376
|
-
|
|
377
|
-
// 渲染值
|
|
378
|
-
const baseUrl = bundle.base_url || ''
|
|
379
|
-
const baseUrlBeta = bundle.base_url_beta || baseUrl
|
|
380
|
-
const renderValues = {
|
|
381
|
-
apiKey: bundle.api_key || '',
|
|
382
|
-
baseUrl,
|
|
383
|
-
apiBase: baseUrl,
|
|
384
|
-
apiBaseBeta: baseUrlBeta,
|
|
385
|
-
antigravityApiBase: `${baseUrl}/antigravity`,
|
|
386
|
-
antigravityApiBaseBeta: `${baseUrlBeta}/antigravity`,
|
|
387
|
-
defaultModel: defaultModelId,
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// === 8. 每个选中 CLI 的每个 file:备份 → 渲染 → 写入 ===
|
|
391
|
-
step('备份并写入配置')
|
|
392
|
-
const report = []
|
|
393
|
-
let firstPermissionError = null
|
|
394
|
-
|
|
395
|
-
for (const item of selectedItems) {
|
|
396
|
-
const displayName =
|
|
397
|
-
item.config.display_name || item.config.displayName || item.meta?.name || item.clientType
|
|
398
|
-
plain(`── ${displayName} ──`)
|
|
399
|
-
const files = item.config.files || []
|
|
400
|
-
for (const f of files) {
|
|
401
|
-
const rawPath = f.path || ''
|
|
402
|
-
const isShellOnly = rawPath === 'Terminal' || rawPath === 'PowerShell'
|
|
403
|
-
const targetPath = isShellOnly
|
|
404
|
-
? rawPath
|
|
405
|
-
: expandPath(rawPath, { isWindows: env.os.isWindows })
|
|
406
|
-
const rendered = renderContent(f.content || '', renderValues)
|
|
407
|
-
|
|
408
|
-
if (isShellOnly) {
|
|
409
|
-
plain(' [shell 命令,请手动执行]')
|
|
410
|
-
plain(rendered.split('\n').map((l) => ' ' + l).join('\n'))
|
|
411
|
-
report.push({ cli: displayName, path: rawPath, action: 'shell-only' })
|
|
412
|
-
continue
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
plain(` 目标:${targetPath}`)
|
|
416
|
-
|
|
417
|
-
if (args.dryRun) {
|
|
418
|
-
warn(' [试运行] 不真实写入')
|
|
419
|
-
const lines = rendered.split('\n')
|
|
420
|
-
const preview = lines.slice(0, 8).join('\n')
|
|
421
|
-
plain(' 内容预览:')
|
|
422
|
-
plain(preview.split('\n').map((l) => ' ' + l).join('\n'))
|
|
423
|
-
if (lines.length > 8) plain(` ...(共 ${lines.length} 行)`)
|
|
424
|
-
report.push({ cli: displayName, path: targetPath, action: 'dry-run' })
|
|
425
|
-
console.log()
|
|
426
|
-
continue
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// 备份
|
|
430
|
-
let backupPath = null
|
|
431
|
-
try {
|
|
432
|
-
backupPath = backupFile(targetPath)
|
|
433
|
-
if (backupPath) plain(` 备份:${backupPath}`)
|
|
434
|
-
} catch (err) {
|
|
435
|
-
fail(`备份失败:${err.message}(跳过此文件避免无备份覆盖)`)
|
|
436
|
-
report.push({ cli: displayName, path: targetPath, action: 'backup-failed', error: err.message })
|
|
437
|
-
continue
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// 写入
|
|
441
|
-
try {
|
|
442
|
-
writeFileWithPermission(targetPath, rendered, { isWindows: env.os.isWindows })
|
|
443
|
-
ok('已写入')
|
|
444
|
-
report.push({ cli: displayName, path: targetPath, action: 'written', backup: backupPath })
|
|
445
|
-
} catch (err) {
|
|
446
|
-
if (err instanceof PermissionDeniedError) {
|
|
447
|
-
if (!firstPermissionError) firstPermissionError = err
|
|
448
|
-
fail(err.message)
|
|
449
|
-
report.push({ cli: displayName, path: targetPath, action: 'permission-denied', error: err.message })
|
|
450
|
-
} else {
|
|
451
|
-
fail(`写入失败:${err.message}`)
|
|
452
|
-
report.push({ cli: displayName, path: targetPath, action: 'failed', error: err.message })
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
console.log()
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
closePrompt()
|
|
460
|
-
|
|
461
|
-
// === 9. 验证 API Key ===
|
|
462
|
-
if (!args.dryRun && bundle.api_key && bundle.base_url) {
|
|
463
|
-
step('验证 API Key')
|
|
464
|
-
info('请求 GET /v1/models…')
|
|
465
|
-
const v = await verifyApiKey({ apiKey: bundle.api_key, baseUrl: bundle.base_url })
|
|
466
|
-
if (v.ok) {
|
|
467
|
-
ok('API Key 验证通过(GET /v1/models → 200)')
|
|
468
|
-
} else {
|
|
469
|
-
warn(`验证未通过:${v.error || `HTTP ${v.statusCode}`}(配置已写入,请手动检查)`)
|
|
470
|
-
}
|
|
471
|
-
console.log()
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// === 10. 报告 ===
|
|
475
|
-
step('激活报告')
|
|
476
|
-
const written = report.filter((r) => r.action === 'written')
|
|
477
|
-
const failed = report.filter(
|
|
478
|
-
(r) => r.action === 'failed' || r.action === 'permission-denied' || r.action === 'backup-failed',
|
|
479
|
-
)
|
|
480
|
-
const shellOnly = report.filter((r) => r.action === 'shell-only')
|
|
481
|
-
const dryRunItems = report.filter((r) => r.action === 'dry-run')
|
|
482
|
-
|
|
483
|
-
if (args.dryRun) {
|
|
484
|
-
plain(`[试运行] 将写入 ${dryRunItems.length} 个文件(未真实写入)`)
|
|
485
|
-
} else {
|
|
486
|
-
ok(`已写入 ${written.length} 个文件`)
|
|
487
|
-
}
|
|
488
|
-
if (shellOnly.length > 0) {
|
|
489
|
-
info(`需手动执行的 shell 命令:${shellOnly.length} 项(见上方输出)`)
|
|
490
|
-
}
|
|
491
|
-
if (failed.length > 0) {
|
|
492
|
-
warn(`失败 ${failed.length} 项:`)
|
|
493
|
-
failed.forEach((f) => warn(` - ${f.cli} ${f.path}`))
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const backups = report.filter((r) => r.backup).map((r) => r.backup)
|
|
497
|
-
if (backups.length > 0) {
|
|
498
|
-
info('备份位置:')
|
|
499
|
-
backups.forEach((b) => plain(` - ${b}`))
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (bundle.user?.is_new) {
|
|
503
|
-
console.log()
|
|
504
|
-
info(`📧 新账号 ${bundle.user.email || ''} 已创建,请查收邮件获取初始密码,登录后尽快修改。`)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// === 11. 启动/使用指南(每个激活 CLI)===
|
|
508
|
-
if (!args.dryRun) {
|
|
509
|
-
const guideItems = selectedItems
|
|
510
|
-
.map((item) => item.meta)
|
|
511
|
-
.filter(Boolean)
|
|
512
|
-
.map((meta) => ({
|
|
513
|
-
name: meta.name,
|
|
514
|
-
startCmd: meta.startCmd,
|
|
515
|
-
usage: meta.usage,
|
|
516
|
-
}))
|
|
517
|
-
guide(guideItems)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (firstPermissionError) {
|
|
521
|
-
console.log()
|
|
522
|
-
fail('部分文件权限不足,请按提示用 sudo / 管理员 PowerShell 重跑:')
|
|
523
|
-
fail(' sudo npx -y @2api-store/activate')
|
|
524
|
-
return 2
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return failed.length > 0 ? 3 : 0
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
main()
|
|
531
|
-
.then((code) => {
|
|
532
|
-
closePrompt()
|
|
533
|
-
process.exit(code || 0)
|
|
534
|
-
})
|
|
535
|
-
.catch((err) => {
|
|
536
|
-
fail(`未捕获错误:${err?.message || err}`)
|
|
537
|
-
if (err?.stack) console.error(err.stack)
|
|
538
|
-
closePrompt()
|
|
539
|
-
process.exit(1)
|
|
540
|
-
})
|