@2api-store/activate 0.3.5 → 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 +22 -3
- package/lib/api.mjs +68 -3
- package/lib/platforms.mjs +3 -31
- package/package.json +1 -1
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
|
|
|
@@ -56,7 +57,8 @@ export function cliActivate({ credential, email, platform, apiBase }) {
|
|
|
56
57
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
57
58
|
resolve(json)
|
|
58
59
|
} else {
|
|
59
|
-
const
|
|
60
|
+
const errObj = json?.error
|
|
61
|
+
const msg = (errObj && typeof errObj === 'object' ? errObj.message : errObj) || json?.message || `HTTP ${res.statusCode}`
|
|
60
62
|
const err = new Error(msg)
|
|
61
63
|
err.statusCode = res.statusCode
|
|
62
64
|
err.body = json
|
|
@@ -75,6 +77,69 @@ export function cliActivate({ credential, email, platform, apiBase }) {
|
|
|
75
77
|
})
|
|
76
78
|
}
|
|
77
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
|
+
|
|
78
143
|
/**
|
|
79
144
|
* 验证 API Key:GET {baseUrl}/v1/models(Bearer api_key),期望 200
|
|
80
145
|
* @param {{apiKey: string, baseUrl: string}} opts
|
package/lib/platforms.mjs
CHANGED
|
@@ -62,21 +62,7 @@ export const CLI_META = {
|
|
|
62
62
|
startCmd: 'claude',
|
|
63
63
|
detectBin: 'claude',
|
|
64
64
|
configFiles: ['~/.claude/', '~/.claude.json'],
|
|
65
|
-
usage:
|
|
66
|
-
'常用命令:',
|
|
67
|
-
' claude 交互模式',
|
|
68
|
-
' claude "你的问题" 带初始问题启动',
|
|
69
|
-
' claude -p "问题" 非交互模式(输出后退出,适合脚本)',
|
|
70
|
-
' claude -c 继续最近会话',
|
|
71
|
-
' claude -r 恢复历史会话(选择器)',
|
|
72
|
-
' claude --model sonnet 指定模型(sonnet/opus/haiku/fable)',
|
|
73
|
-
' claude mcp 管理 MCP 服务器',
|
|
74
|
-
' claude doctor 诊断安装/配置',
|
|
75
|
-
' claude update 更新到最新版',
|
|
76
|
-
'权限模式(Shift+Tab 切换):default / acceptEdits / plan / auto / bypassPermissions',
|
|
77
|
-
' claude --dangerously-skip-permissions 跳过所有权限提示(=bypassPermissions,谨慎用)',
|
|
78
|
-
'项目根放 CLAUDE.md 让 Claude 了解你的代码库',
|
|
79
|
-
].join('\n'),
|
|
65
|
+
usage: '运行 `claude` 开始;`claude --help` 查看选项;在项目根放 CLAUDE.md 让 Claude 了解你的代码库',
|
|
80
66
|
isPrimary: true,
|
|
81
67
|
},
|
|
82
68
|
'gemini-cli': {
|
|
@@ -86,15 +72,7 @@ export const CLI_META = {
|
|
|
86
72
|
startCmd: 'gemini',
|
|
87
73
|
detectBin: 'gemini',
|
|
88
74
|
configFiles: ['~/.gemini/settings.json'],
|
|
89
|
-
usage:
|
|
90
|
-
'常用命令:',
|
|
91
|
-
' gemini 交互模式',
|
|
92
|
-
' gemini "你的问题" 带初始问题启动',
|
|
93
|
-
' gemini -p "问题" 非交互模式(输出后退出)',
|
|
94
|
-
' gemini --resume 恢复历史会话',
|
|
95
|
-
' gemini --help 查看所有选项',
|
|
96
|
-
'项目根放 GEMINI.md 让 Gemini 了解你的代码库',
|
|
97
|
-
].join('\n'),
|
|
75
|
+
usage: '运行 `gemini` 开始;`gemini --help` 查看选项;在项目根放 GEMINI.md 让 Gemini 了解你的代码库',
|
|
98
76
|
isPrimary: true,
|
|
99
77
|
},
|
|
100
78
|
opencode: {
|
|
@@ -105,13 +83,7 @@ export const CLI_META = {
|
|
|
105
83
|
startCmd: 'opencode',
|
|
106
84
|
detectBin: 'opencode',
|
|
107
85
|
configFiles: ['~/.config/opencode/opencode.json', 'opencode.json'],
|
|
108
|
-
usage:
|
|
109
|
-
'常用命令:',
|
|
110
|
-
' opencode 交互模式',
|
|
111
|
-
' opencode "你的问题" 带初始问题启动',
|
|
112
|
-
' opencode --help 查看所有选项',
|
|
113
|
-
'项目根放 AGENTS.md 让 OpenCode 了解你的代码库',
|
|
114
|
-
].join('\n'),
|
|
86
|
+
usage: '运行 `opencode` 开始;`opencode --help` 查看选项;在项目根放 AGENTS.md 让 OpenCode 了解你的代码库',
|
|
115
87
|
isPrimary: false,
|
|
116
88
|
},
|
|
117
89
|
}
|