@2api-store/activate 0.4.0 → 0.4.2
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/README.md +5 -5
- package/lib/cmds.mjs +125 -80
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,10 +37,10 @@ npx @2api-store/activate <subcommand> [options]
|
|
|
37
37
|
|--------|------|
|
|
38
38
|
| `restore` | 从备份恢复:扫描 `~/.codex/`、`~/.claude/`、`~/.gemini/` 下的 `.bak.*` 文件,选择一个恢复(覆盖当前) |
|
|
39
39
|
| `reset` | 恢复官方默认:删除 2api.store 写入的配置文件(`~/.codex/config.toml`、`~/.codex/auth.json`、`~/.claude/settings.json`、`~/.gemini/settings.json`),并尝试从最新备份恢复。**破坏性操作,需确认** |
|
|
40
|
-
| `
|
|
40
|
+
| `reconfig` | 重新配置:输入已有 `sk-` API Key → 重拉最新模板/模型 → 覆盖写入配置(写前备份)。同激活流程但不创建账号 |
|
|
41
41
|
| `refresh-groups` | 查看可用分组:`GET /cli/groups` → 彩色表格展示。只读,不改 key/配置 |
|
|
42
42
|
|
|
43
|
-
所有子命令支持 `--api-base <url>` 覆盖端点;`restore` / `reset` / `
|
|
43
|
+
所有子命令支持 `--api-base <url>` 覆盖端点;`restore` / `reset` / `reconfig` 支持 `--dry-run` 预演。
|
|
44
44
|
|
|
45
45
|
### restore — 从备份恢复
|
|
46
46
|
|
|
@@ -65,11 +65,11 @@ npx @2api-store/activate reset
|
|
|
65
65
|
- 删除后尝试从最新常规备份恢复(回到激活前的官方状态)
|
|
66
66
|
- 需要显式确认(默认 `N`,输入 `y` 才执行)
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### reconfig — 重新配置
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
npx @2api-store/activate
|
|
72
|
-
npx @2api-store/activate
|
|
71
|
+
npx @2api-store/activate reconfig
|
|
72
|
+
npx @2api-store/activate reconfig --api-base http://127.0.0.1:5566
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
- 输入现有 `sk-` API Key(不接受兑换码)
|
package/lib/cmds.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 子命令实现:restore / reset /
|
|
2
|
+
* 子命令实现:restore / reset / reconfig / refresh-groups
|
|
3
3
|
*
|
|
4
4
|
* 设计原则:
|
|
5
5
|
* - 每个子命令是独立 async 函数,接收 argv 数组(不含子命令名本身)
|
|
6
6
|
* - 返回退出码(0 成功 / 1 输入错误 / 2 权限 / 3 写入失败)
|
|
7
7
|
* - 自包含:解析自己的 argv + 走完整流程
|
|
8
|
-
* - 操作配置文件前都备份(reset 删前备份;
|
|
8
|
+
* - 操作配置文件前都备份(reset 删前备份;reconfig 写前备份)
|
|
9
9
|
* - 不复用 activate.mjs 的 main()(避免耦合,main 包含 redeem/CLI 多选/安装等复杂流程)
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -126,10 +126,10 @@ const SUB_HELP = `
|
|
|
126
126
|
子命令:
|
|
127
127
|
restore 从备份恢复(扫描 ~/.codex / ~/.claude / ~/.gemini 下的 .bak.* 文件)
|
|
128
128
|
reset 恢复官方默认(删 2api.store 写入的配置,并从最新备份恢复)
|
|
129
|
-
|
|
129
|
+
reconfig 重新配置(输入已有 sk- API Key → 重拉模板/模型 → 覆盖写入)
|
|
130
130
|
refresh-groups 刷新分组列表(只展示,不改 key/配置)
|
|
131
131
|
|
|
132
|
-
所有子命令都支持 --api-base <url> 覆盖端点,restore/reset/
|
|
132
|
+
所有子命令都支持 --api-base <url> 覆盖端点,restore/reset/reconfig 支持 --dry-run。
|
|
133
133
|
`.trim()
|
|
134
134
|
|
|
135
135
|
// ============================================================
|
|
@@ -162,56 +162,130 @@ async function cmdRestore(argv) {
|
|
|
162
162
|
return 0
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
ok(`找到 ${backups.length}
|
|
165
|
+
ok(`找到 ${backups.length} 个备份文件`)
|
|
166
|
+
|
|
167
|
+
// 按应用分组(根据 origPath 的父目录)
|
|
168
|
+
const APP_LABELS = {
|
|
169
|
+
'.codex': 'Codex CLI',
|
|
170
|
+
'.claude': 'Claude Code',
|
|
171
|
+
'.gemini': 'Gemini CLI',
|
|
172
|
+
}
|
|
173
|
+
function appLabel(origPath) {
|
|
174
|
+
const home = process.env.HOME || process.env.USERPROFILE || ''
|
|
175
|
+
const rel = path.relative(home, origPath).replace(/\\/g, '/')
|
|
176
|
+
const firstSeg = rel.split('/')[0]
|
|
177
|
+
return APP_LABELS[firstSeg] || firstSeg
|
|
178
|
+
}
|
|
179
|
+
function appDir(origPath) {
|
|
180
|
+
const home = process.env.HOME || process.env.USERPROFILE || ''
|
|
181
|
+
return path.dirname(origPath)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 按应用(目录)分组
|
|
185
|
+
const byApp = {}
|
|
186
|
+
for (const b of backups) {
|
|
187
|
+
const dir = appDir(b.origPath)
|
|
188
|
+
if (!byApp[dir]) byApp[dir] = []
|
|
189
|
+
byApp[dir].push(b)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const appDirs = Object.keys(byApp)
|
|
193
|
+
if (appDirs.length === 0) {
|
|
194
|
+
info('无可恢复的备份')
|
|
195
|
+
return 0
|
|
196
|
+
}
|
|
197
|
+
|
|
166
198
|
console.log()
|
|
199
|
+
step('选择要恢复的应用')
|
|
200
|
+
const appItems = appDirs.map((dir) => {
|
|
201
|
+
const label = appLabel(path.join(dir, 'dummy'))
|
|
202
|
+
const count = byApp[dir].length
|
|
203
|
+
return { label: `${chalk.cyan(label)}(${count} 个备份)`, dir }
|
|
204
|
+
})
|
|
205
|
+
const appIdx = await select('选择应用(回车=第一个)', appItems, { defaultIndex: 0 })
|
|
206
|
+
if (appIdx < 0 || appIdx >= appItems.length) {
|
|
207
|
+
fail('未选择应用')
|
|
208
|
+
return 1
|
|
209
|
+
}
|
|
210
|
+
const chosenDir = appItems[appIdx].dir
|
|
211
|
+
const chosenAppLabel = appLabel(path.join(chosenDir, 'dummy'))
|
|
212
|
+
|
|
213
|
+
// 该应用的备份按时间戳分组(截取到秒:T08-23-03)
|
|
214
|
+
const appBackups = byApp[chosenDir].sort((a, b) => b.mtime - a.mtime)
|
|
215
|
+
const byTime = {}
|
|
216
|
+
const timeOrder = []
|
|
217
|
+
for (const b of appBackups) {
|
|
218
|
+
// 从 bakPath 提取时间戳前缀(到秒)
|
|
219
|
+
const m = path.basename(b.bakPath).match(/\.bak\.(.+)$/)
|
|
220
|
+
const fullTs = m ? m[1] : ''
|
|
221
|
+
// 截取到秒:去毫秒后缀(最后 4 字符:-XXXZ)
|
|
222
|
+
const tsKey = fullTs.replace(/-\d{3}Z$/, 'Z')
|
|
223
|
+
if (!byTime[tsKey]) {
|
|
224
|
+
byTime[tsKey] = []
|
|
225
|
+
timeOrder.push(tsKey)
|
|
226
|
+
}
|
|
227
|
+
byTime[tsKey].push(b)
|
|
228
|
+
}
|
|
167
229
|
|
|
168
|
-
//
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
backup: b,
|
|
230
|
+
// 同时间组内按 origPath 去重(同 origPath 只保留 mtime 最大的)
|
|
231
|
+
for (const ts of timeOrder) {
|
|
232
|
+
const group = byTime[ts]
|
|
233
|
+
const byOrig = {}
|
|
234
|
+
for (const b of group) {
|
|
235
|
+
if (!byOrig[b.origPath] || b.mtime > byOrig[b.origPath].mtime) {
|
|
236
|
+
byOrig[b.origPath] = b
|
|
237
|
+
}
|
|
177
238
|
}
|
|
178
|
-
|
|
239
|
+
byTime[ts] = Object.values(byOrig).sort((a, b) => b.mtime - a.mtime)
|
|
240
|
+
}
|
|
179
241
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
242
|
+
console.log()
|
|
243
|
+
step(`选择恢复时间点(${chosenAppLabel})`)
|
|
244
|
+
const timeItems = timeOrder.map((ts) => {
|
|
245
|
+
const group = byTime[ts]
|
|
246
|
+
const files = group.map((b) => path.basename(b.origPath)).join(', ')
|
|
247
|
+
const tsDisplay = fmtTime(group[0].mtime)
|
|
248
|
+
return { label: `${chalk.dim(tsDisplay)}(${group.length} 文件:${files})`, ts, group }
|
|
249
|
+
})
|
|
250
|
+
const timeIdx = await select('选择时间点(回车=最新)', timeItems, { defaultIndex: 0 })
|
|
251
|
+
if (timeIdx < 0 || timeIdx >= timeItems.length) {
|
|
252
|
+
fail('未选择时间点')
|
|
184
253
|
return 1
|
|
185
254
|
}
|
|
186
|
-
const
|
|
255
|
+
const chosenGroup = timeItems[timeIdx].group
|
|
256
|
+
|
|
187
257
|
console.log()
|
|
188
|
-
info(
|
|
189
|
-
|
|
258
|
+
info(`将恢复 ${chosenAppLabel}(${chosenGroup.length} 个文件):`)
|
|
259
|
+
for (const b of chosenGroup) {
|
|
260
|
+
info(` ${path.basename(b.origPath)} ← ${path.basename(b.bakPath)}`)
|
|
261
|
+
}
|
|
190
262
|
|
|
191
263
|
if (args.dryRun) {
|
|
192
264
|
warn('[试运行] 不真实恢复')
|
|
193
265
|
return 0
|
|
194
266
|
}
|
|
195
267
|
|
|
196
|
-
//
|
|
197
|
-
|
|
268
|
+
// 恢复前安全备份当前文件(方便撤销)
|
|
269
|
+
let restored = 0
|
|
270
|
+
for (const b of chosenGroup) {
|
|
271
|
+
if (fs.existsSync(b.origPath)) {
|
|
272
|
+
try {
|
|
273
|
+
const safetyBak = backupFile(b.origPath)
|
|
274
|
+
if (safetyBak) info(`安全备份:${path.basename(b.origPath)} → ${path.basename(safetyBak)}`)
|
|
275
|
+
} catch {
|
|
276
|
+
// 安全备份失败不阻塞
|
|
277
|
+
}
|
|
278
|
+
}
|
|
198
279
|
try {
|
|
199
|
-
|
|
200
|
-
|
|
280
|
+
restoreBackup(b.bakPath, b.origPath)
|
|
281
|
+
ok(`已恢复:${b.origPath}`)
|
|
282
|
+
restored++
|
|
201
283
|
} catch (err) {
|
|
202
|
-
|
|
284
|
+
fail(`恢复失败:${b.origPath}(${err.message})`)
|
|
203
285
|
}
|
|
204
286
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
restoreBackup(chosen.bakPath, chosen.origPath)
|
|
208
|
-
ok(`已恢复:${chosen.origPath}`)
|
|
209
|
-
info('(原备份文件保留,可多次恢复)')
|
|
210
|
-
return 0
|
|
211
|
-
} catch (err) {
|
|
212
|
-
fail(`恢复失败:${err.message}`)
|
|
213
|
-
return 3
|
|
214
|
-
}
|
|
287
|
+
info(`共恢复 ${restored} 个文件`)
|
|
288
|
+
return restored > 0 ? 0 : 3
|
|
215
289
|
}
|
|
216
290
|
|
|
217
291
|
// ============================================================
|
|
@@ -359,13 +433,13 @@ async function cmdReset(argv) {
|
|
|
359
433
|
}
|
|
360
434
|
|
|
361
435
|
// ============================================================
|
|
362
|
-
//
|
|
436
|
+
// reconfig:重新配置
|
|
363
437
|
// ============================================================
|
|
364
438
|
|
|
365
|
-
async function
|
|
439
|
+
async function cmdReconfig(argv) {
|
|
366
440
|
const args = parseSubArgs(argv)
|
|
367
441
|
if (args.help) {
|
|
368
|
-
console.log(`npx @2api-store/activate
|
|
442
|
+
console.log(`npx @2api-store/activate reconfig [options]\n\n${SUB_HELP}`)
|
|
369
443
|
return 0
|
|
370
444
|
}
|
|
371
445
|
|
|
@@ -396,23 +470,17 @@ async function cmdReconfigure(argv) {
|
|
|
396
470
|
return 1
|
|
397
471
|
}
|
|
398
472
|
if (!apiKey.startsWith('sk-')) {
|
|
399
|
-
fail('
|
|
473
|
+
fail('reconfig 仅支持 API Key 模式(sk- 前缀)。如需使用兑换码,请运行默认激活流程:npx @2api-store/activate')
|
|
400
474
|
return 1
|
|
401
475
|
}
|
|
402
476
|
info('识别为:API Key 模式(不创建账号,仅重拉模板写入配置)')
|
|
403
477
|
console.log()
|
|
404
478
|
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
const platform = platformAnswer || null
|
|
408
|
-
if (platform) info(`平台过滤:${platform}`)
|
|
409
|
-
console.log()
|
|
410
|
-
|
|
411
|
-
// POST /cli/activate
|
|
412
|
-
step('请求激活服务(key 模式重拉模板)')
|
|
479
|
+
// POST /cli/activate — 全平台,不问平台限制
|
|
480
|
+
step('请求激活服务(全量重拉模板)')
|
|
413
481
|
let bundle
|
|
414
482
|
try {
|
|
415
|
-
bundle = await cliActivate({ credential: apiKey,
|
|
483
|
+
bundle = await cliActivate({ credential: apiKey, apiBase })
|
|
416
484
|
} catch (err) {
|
|
417
485
|
fail(`请求失败:${err.message}`)
|
|
418
486
|
if (err.statusCode) fail(` HTTP ${err.statusCode}`)
|
|
@@ -425,7 +493,7 @@ async function cmdReconfigure(argv) {
|
|
|
425
493
|
if (bundle.base_url) info(`base_url:${bundle.base_url}`)
|
|
426
494
|
console.log()
|
|
427
495
|
|
|
428
|
-
// 过滤配置
|
|
496
|
+
// 过滤配置 — 按模型平台推断 + OS 过滤(与默认激活流程一致)
|
|
429
497
|
const allowedPlatforms = new Set()
|
|
430
498
|
for (const m of bundle.models || []) {
|
|
431
499
|
const id = (typeof m === 'string' ? m : m?.id || '').toLowerCase()
|
|
@@ -434,12 +502,10 @@ async function cmdReconfigure(argv) {
|
|
|
434
502
|
else if (id.startsWith('gemini')) allowedPlatforms.add('gemini')
|
|
435
503
|
else if (id.startsWith('grok')) allowedPlatforms.add('grok')
|
|
436
504
|
}
|
|
437
|
-
if (platform) allowedPlatforms.add(platform)
|
|
438
505
|
|
|
439
506
|
const osConfigs = (bundle.configs || []).filter((c) => {
|
|
440
507
|
if (!c) return false
|
|
441
508
|
if (allowedPlatforms.size > 0 && !allowedPlatforms.has(c.platform)) return false
|
|
442
|
-
if (platform && c.platform !== platform) return false
|
|
443
509
|
if (env.os.isWindows) {
|
|
444
510
|
return c.os_type === 'windows' || c.os_type === 'all'
|
|
445
511
|
}
|
|
@@ -451,43 +517,22 @@ async function cmdReconfigure(argv) {
|
|
|
451
517
|
return 1
|
|
452
518
|
}
|
|
453
519
|
|
|
454
|
-
//
|
|
520
|
+
// reconfig: 全选所有配置,不问 CLI 选择
|
|
455
521
|
const seen = new Set()
|
|
456
|
-
const
|
|
522
|
+
const selectedItems = []
|
|
457
523
|
for (const c of osConfigs) {
|
|
458
524
|
const key = `${c.platform}/${c.client_type}`
|
|
459
525
|
if (seen.has(key)) continue
|
|
460
526
|
seen.add(key)
|
|
461
527
|
const meta = getCliMeta(c.client_type)
|
|
462
528
|
const displayName = c.display_name || c.displayName || meta?.name || `${c.platform} ${c.client_type}`
|
|
463
|
-
|
|
464
|
-
const primaryMark = isDefaultSelected(c.client_type) ? '' : '(独立 CLI)'
|
|
465
|
-
const nameColored = installed ? chalk.green(displayName) : chalk.gray(`${displayName} (未装)`)
|
|
466
|
-
cliItems.push({
|
|
467
|
-
label: `${nameColored} ${chalk.dim(`[${c.platform}/${c.client_type}]`)}${primaryMark}`,
|
|
529
|
+
selectedItems.push({
|
|
468
530
|
config: c,
|
|
469
531
|
clientType: c.client_type,
|
|
470
532
|
meta,
|
|
471
|
-
alreadyInstalled: !!installed,
|
|
472
533
|
})
|
|
473
534
|
}
|
|
474
|
-
|
|
475
|
-
const defaultSelected = cliItems
|
|
476
|
-
.map((item, i) => ({ item, i }))
|
|
477
|
-
.filter(({ item }) => isDefaultSelected(item.clientType))
|
|
478
|
-
.map(({ i }) => i)
|
|
479
|
-
|
|
480
|
-
step('选择要重配的 CLI')
|
|
481
|
-
const selectedIdxes = await multiSelect(
|
|
482
|
-
'选择 CLI(绿色=已装,灰色=未装;默认选主 CLI)',
|
|
483
|
-
cliItems,
|
|
484
|
-
{ defaultSelected },
|
|
485
|
-
)
|
|
486
|
-
if (selectedIdxes.length === 0) {
|
|
487
|
-
plain('未选择任何 CLI,退出。')
|
|
488
|
-
return 0
|
|
489
|
-
}
|
|
490
|
-
const selectedItems = selectedIdxes.map((i) => cliItems[i])
|
|
535
|
+
ok(`将覆盖 ${selectedItems.length} 个 CLI 配置`)
|
|
491
536
|
console.log()
|
|
492
537
|
|
|
493
538
|
// 选默认模型
|
|
@@ -613,7 +658,7 @@ async function cmdReconfigure(argv) {
|
|
|
613
658
|
if (firstPermissionError) {
|
|
614
659
|
console.log()
|
|
615
660
|
fail('部分文件权限不足,请按提示用 sudo / 管理员 PowerShell 重跑:')
|
|
616
|
-
fail(' sudo npx -y @2api-store/activate
|
|
661
|
+
fail(' sudo npx -y @2api-store/activate reconfig')
|
|
617
662
|
return 2
|
|
618
663
|
}
|
|
619
664
|
|
|
@@ -691,7 +736,7 @@ async function cmdRefreshGroups(argv) {
|
|
|
691
736
|
export const SUBCOMMANDS = {
|
|
692
737
|
restore: { handler: cmdRestore, description: '从备份恢复配置' },
|
|
693
738
|
reset: { handler: cmdReset, description: '恢复官方默认(删 2api.store 配置并从备份恢复)' },
|
|
694
|
-
|
|
739
|
+
reconfig: { handler: cmdReconfig, description: '用已有 API Key 重新拉取并写入配置' },
|
|
695
740
|
'refresh-groups': { handler: cmdRefreshGroups, description: '查看可用分组(不改配置)' },
|
|
696
741
|
}
|
|
697
742
|
|