@2api-store/activate 0.3.6 → 0.3.7

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/activate.mjs CHANGED
@@ -18,7 +18,7 @@ process.env.NODE_NO_WARNINGS = '1'
18
18
  */
19
19
 
20
20
  import { detectAll } from './lib/detect.mjs'
21
- import { cliActivate, verifyApiKey } from './lib/api.mjs'
21
+ import { cliActivate, listGroups, verifyApiKey } from './lib/api.mjs'
22
22
  import { renderContent, expandPath, listModels } from './lib/render.mjs'
23
23
  import { backupFile } from './lib/backup.mjs'
24
24
  import { writeFileWithPermission, PermissionDeniedError } from './lib/write.mjs'
@@ -186,21 +186,40 @@ async function main() {
186
186
  }
187
187
  info(`识别为:${kind === 'key' ? 'API Key' : '兑换码'}模式`)
188
188
 
189
- // === 3. 兑换码 → 邮箱 ===
189
+ // === 3. 兑换码 → 邮箱 + 选组 ===
190
190
  let email = ''
191
+ let groupId = null
191
192
  if (kind === 'redeem') {
192
193
  email = (await ask('请输入邮箱(用于兑换码绑定 / 新账号创建)')).trim()
193
194
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
194
195
  fail('邮箱格式不正确')
195
196
  return 1
196
197
  }
198
+
199
+ // 选组:兑换码模式都走选组流程(balance 类型码用 group_id 绑定 + 加余额 + 建 key;
200
+ // api_key 类型码若码已绑组则后端忽略传入的 group_id,但 CLI 选组无害,统一流程)
201
+ step('获取可用分组')
202
+ const groups = await listGroups({ apiBase })
203
+ if (groups.length === 0) {
204
+ fail('未查询到可用分组。请联系管理员开通分组后再激活。')
205
+ return 1
206
+ }
207
+ const groupItems = groups.map((g) => ({ label: `${g.name}`, value: g.id }))
208
+ const groupIdx = await select('选择绑定的分组', groupItems, { defaultIndex: 0 })
209
+ groupId = groups[groupIdx]?.id
210
+ if (!groupId) {
211
+ fail('未选择分组')
212
+ return 1
213
+ }
214
+ ok(`已选分组:${groups[groupIdx].name}(ID ${groupId})`)
215
+ console.log()
197
216
  }
198
217
 
199
218
  // === 4. POST /cli/activate ===
200
219
  step('请求激活服务')
201
220
  let bundle
202
221
  try {
203
- bundle = await cliActivate({ credential, email, platform: args.platform, apiBase })
222
+ bundle = await cliActivate({ credential, email, platform: args.platform, groupId, apiBase })
204
223
  } catch (err) {
205
224
  fail(`激活失败:${err.message}`)
206
225
  if (err.statusCode) fail(` HTTP ${err.statusCode}`)
package/lib/api.mjs CHANGED
@@ -6,10 +6,10 @@ const DEFAULT_TIMEOUT_MS = 30000
6
6
 
7
7
  /**
8
8
  * 发送 POST /api/v1/cli/activate 请求
9
- * @param {{credential: string, email?: string, platform?: string, apiBase: string}} opts
9
+ * @param {{credential: string, email?: string, platform?: string, group_id?: number, apiBase: string}} opts
10
10
  * @returns {Promise<object>} 解析后的 JSON 响应
11
11
  */
12
- export function cliActivate({ credential, email, platform, apiBase }) {
12
+ export function cliActivate({ credential, email, platform, groupId, apiBase }) {
13
13
  return new Promise((resolve, reject) => {
14
14
  let url
15
15
  try {
@@ -24,6 +24,7 @@ export function cliActivate({ credential, email, platform, apiBase }) {
24
24
  credential,
25
25
  ...(email ? { email } : {}),
26
26
  ...(platform ? { platform } : {}),
27
+ ...(groupId ? { group_id: groupId } : {}),
27
28
  }
28
29
  const body = JSON.stringify(payload)
29
30
 
@@ -76,6 +77,69 @@ export function cliActivate({ credential, email, platform, apiBase }) {
76
77
  })
77
78
  }
78
79
 
80
+ /**
81
+ * 获取可用分组列表:GET /api/v1/cli/groups
82
+ * 兑换码模式(balance 类型码无 group_id)CLI 选组用。
83
+ * @param {{apiBase: string}} opts
84
+ * @returns {Promise<Array<{id: number, name: string}>>} 分组列表(精简,仅 id+name)
85
+ */
86
+ export function listGroups({ apiBase }) {
87
+ return new Promise((resolve, reject) => {
88
+ let url
89
+ try {
90
+ url = new URL('/api/v1/cli/groups', apiBase)
91
+ } catch (err) {
92
+ reject(new Error(`无效的 apiBase:${apiBase}(${err.message})`))
93
+ return
94
+ }
95
+
96
+ const lib = url.protocol === 'https:' ? https : http
97
+ const req = lib.request(
98
+ url,
99
+ {
100
+ method: 'GET',
101
+ headers: {
102
+ 'User-Agent': USER_AGENT,
103
+ Accept: 'application/json',
104
+ },
105
+ timeout: DEFAULT_TIMEOUT_MS,
106
+ },
107
+ (res) => {
108
+ const chunks = []
109
+ res.on('data', (c) => chunks.push(c))
110
+ res.on('end', () => {
111
+ const text = Buffer.concat(chunks).toString('utf8')
112
+ let json
113
+ try {
114
+ json = text ? JSON.parse(text) : {}
115
+ } catch {
116
+ // 响应非 JSON,退化为空列表(CLI 兜底)
117
+ resolve([])
118
+ return
119
+ }
120
+ if (res.statusCode >= 200 && res.statusCode < 300) {
121
+ // 后端返回 {groups: [{id,name}]};兼容直接返回数组的形式
122
+ const arr = Array.isArray(json) ? json : json?.groups
123
+ resolve(Array.isArray(arr) ? arr : [])
124
+ } else {
125
+ // 失败退化:返回空列表(不阻塞激活主流程,让用户看到「无组可选」提示)
126
+ resolve([])
127
+ }
128
+ })
129
+ },
130
+ )
131
+
132
+ req.on('timeout', () => {
133
+ req.destroy(new Error(`请求超时(${DEFAULT_TIMEOUT_MS / 1000}s)— ${url.href}`))
134
+ })
135
+ req.on('error', (err) => {
136
+ // 网络错误同样退化为空列表(CLI 兜底)
137
+ resolve([])
138
+ })
139
+ req.end()
140
+ })
141
+ }
142
+
79
143
  /**
80
144
  * 验证 API Key:GET {baseUrl}/v1/models(Bearer api_key),期望 200
81
145
  * @param {{apiKey: string, baseUrl: string}} opts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2api-store/activate",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "TokenShop CLI 一键激活:配置本地 codex/claude/gemini/grok/opencode 指向 2api.store 中转(彩色输出 + 自动安装 CLI)",
5
5
  "type": "module",
6
6
  "bin": {