@2api-store/activate 0.1.0 → 0.2.1
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 +23 -2
- package/lib/prompt.mjs +62 -144
- package/package.json +5 -2
package/activate.mjs
CHANGED
|
@@ -110,6 +110,23 @@ function classifyCredential(cred) {
|
|
|
110
110
|
return 'unknown'
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* 从模型 ID 列表推断平台集合(与端点 inferPlatforms 对应,脚本兜底过滤)。
|
|
115
|
+
* 即使生产端点尚未部署过滤(0.314.0),脚本也按 key 分组实际可用模型平台过滤 configs,
|
|
116
|
+
* 避免给 codex key 列出 gemini/claude 等。
|
|
117
|
+
*/
|
|
118
|
+
function inferModelPlatforms(models) {
|
|
119
|
+
const set = new Set()
|
|
120
|
+
for (const m of models || []) {
|
|
121
|
+
const id = (typeof m === 'string' ? m : m?.id || '').toLowerCase()
|
|
122
|
+
if (/^(gpt|o1|o3|codex|glm|minimax|deepseek)/.test(id)) set.add('openai')
|
|
123
|
+
else if (id.startsWith('claude')) set.add('anthropic')
|
|
124
|
+
else if (id.startsWith('gemini')) set.add('gemini')
|
|
125
|
+
else if (id.startsWith('grok')) set.add('grok')
|
|
126
|
+
}
|
|
127
|
+
return [...set]
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
async function main() {
|
|
114
131
|
const args = parseArgs(process.argv.slice(2))
|
|
115
132
|
if (args.help) {
|
|
@@ -192,10 +209,14 @@ async function main() {
|
|
|
192
209
|
}
|
|
193
210
|
console.log()
|
|
194
211
|
|
|
195
|
-
// === 5. 按 os_type
|
|
212
|
+
// === 5. 按 os_type + key 分组平台过滤、展示可用 CLI ===
|
|
213
|
+
const allowedPlatforms = inferModelPlatforms(bundle.models || [])
|
|
214
|
+
if (args.platform) allowedPlatforms.push(args.platform)
|
|
196
215
|
let osConfigs = (bundle.configs || []).filter((c) => {
|
|
197
216
|
if (!c) return false
|
|
198
|
-
//
|
|
217
|
+
// 按 key 分组可用模型平台过滤(兜底,与端点 inferPlatforms 一致;codex key 只列 openai)
|
|
218
|
+
if (allowedPlatforms.length > 0 && !allowedPlatforms.includes(c.platform)) return false
|
|
219
|
+
// 客户端 --platform 二次过滤(防御性)
|
|
199
220
|
if (args.platform && c.platform !== args.platform) return false
|
|
200
221
|
if (env.os.isWindows) {
|
|
201
222
|
return c.os_type === 'windows' || c.os_type === 'all'
|
package/lib/prompt.mjs
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 提示输入模块(TUI 版,学 Hermes Agent 交互)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 避免 HTTP/异步操作期间 readline 把数据"吸走"导致后续 question 拿不到输入。
|
|
4
|
+
* TTY(交互式):用 @inquirer/prompts —— select 上下+Enter、checkbox 上下+空格+Enter、password 掩码。
|
|
5
|
+
* 用户要的「上下选择、空格选中/取消」即 checkbox 的标准行为。
|
|
6
|
+
* 非 TTY(管道/测试):退化为 readline 行输入(序号选择),保证 CI/测试/脚本可用。
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import readline from 'node:readline'
|
|
11
10
|
|
|
11
|
+
// @inquirer/prompts 动态导入(仅 TTY 用;非 TTY 不加载避免无关报错)
|
|
12
|
+
let _inquirer = null
|
|
13
|
+
async function getInquirer() {
|
|
14
|
+
if (!_inquirer) _inquirer = await import('@inquirer/prompts')
|
|
15
|
+
return _inquirer
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
let _rl = null
|
|
13
|
-
let _lineQueue = null
|
|
19
|
+
let _lineQueue = null
|
|
14
20
|
let _lineQueuePromise = null
|
|
15
21
|
|
|
16
22
|
function isInteractive() {
|
|
17
23
|
return process.stdin.isTTY === true
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
* 非交互模式:把 stdin 全量读入,按 \n 切成行队列。
|
|
22
|
-
* 后续 ask 从队列取;队列空时返回 undefined(视作空字符串)。
|
|
23
|
-
*/
|
|
26
|
+
// 非交互模式:把 stdin 全量读入按行缓冲,从队列消费(避免 readline 在异步期间吸走后续行)
|
|
24
27
|
function ensureLineQueue() {
|
|
25
28
|
if (_lineQueue) return _lineQueuePromise
|
|
26
29
|
_lineQueue = []
|
|
@@ -31,15 +34,11 @@ function ensureLineQueue() {
|
|
|
31
34
|
data += c
|
|
32
35
|
})
|
|
33
36
|
process.stdin.on('end', () => {
|
|
34
|
-
// 按 \n 切,去掉末尾空行
|
|
35
37
|
const lines = data.split(/\r?\n/)
|
|
36
|
-
// 如果末尾有 \n,split 会产生一个空字符串元素;保留中间空行(用户回车默认)
|
|
37
|
-
// 去掉最后一个空元素(来自末尾 \n)
|
|
38
38
|
if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop()
|
|
39
39
|
_lineQueue.push(...lines)
|
|
40
40
|
resolve()
|
|
41
41
|
})
|
|
42
|
-
// 如果 stdin 已结束(极端情况),也 resolve
|
|
43
42
|
if (process.stdin.destroyed) resolve()
|
|
44
43
|
})
|
|
45
44
|
return _lineQueuePromise
|
|
@@ -47,173 +46,95 @@ function ensureLineQueue() {
|
|
|
47
46
|
|
|
48
47
|
function getRl() {
|
|
49
48
|
if (!_rl || _rl.closed) {
|
|
50
|
-
_rl = readline.createInterface({
|
|
51
|
-
input: process.stdin,
|
|
52
|
-
output: process.stdout,
|
|
53
|
-
})
|
|
49
|
+
_rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
54
50
|
}
|
|
55
51
|
return _rl
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
/**
|
|
59
|
-
* 关闭提示接口(退出前调用)
|
|
60
|
-
*/
|
|
61
54
|
export function closePrompt() {
|
|
62
|
-
if (_rl && !_rl.closed)
|
|
63
|
-
_rl.close()
|
|
64
|
-
}
|
|
55
|
+
if (_rl && !_rl.closed) _rl.close()
|
|
65
56
|
_rl = null
|
|
66
57
|
}
|
|
67
58
|
|
|
68
|
-
/**
|
|
69
|
-
* 普通文本输入
|
|
70
|
-
* @param {string} question
|
|
71
|
-
* @param {{defaultValue?: string}} [opts]
|
|
72
|
-
* @returns {Promise<string>}
|
|
73
|
-
*/
|
|
59
|
+
/** 普通文本输入 */
|
|
74
60
|
export async function ask(question, { defaultValue } = {}) {
|
|
75
61
|
const suffix = defaultValue ? ` [${defaultValue}]` : ''
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await
|
|
79
|
-
const line = _lineQueue.shift() ?? ''
|
|
80
|
-
// 模拟 prompt 输出(可读性)
|
|
81
|
-
process.stdout.write(`${question}${suffix}: ${line}\n`)
|
|
82
|
-
const v = line.trim()
|
|
62
|
+
if (isInteractive()) {
|
|
63
|
+
const inq = await getInquirer()
|
|
64
|
+
const v = await inq.input({ message: `${question}${suffix}`, default: defaultValue ?? '' })
|
|
83
65
|
return v || (defaultValue ?? '')
|
|
84
66
|
}
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
resolve(v || (defaultValue ?? ''))
|
|
91
|
-
})
|
|
92
|
-
})
|
|
67
|
+
await ensureLineQueue()
|
|
68
|
+
const line = _lineQueue.shift() ?? ''
|
|
69
|
+
process.stdout.write(`${question}${suffix}: ${line}\n`)
|
|
70
|
+
const v = line.trim()
|
|
71
|
+
return v || (defaultValue ?? '')
|
|
93
72
|
}
|
|
94
73
|
|
|
95
|
-
/**
|
|
96
|
-
* 隐藏输入(API Key / 敏感凭据)
|
|
97
|
-
* 非 TTY 退化为 ask(可见输入),便于测试。
|
|
98
|
-
* @param {string} question
|
|
99
|
-
* @returns {Promise<string>}
|
|
100
|
-
*/
|
|
74
|
+
/** 隐藏输入(API Key / 敏感凭据),TTY 掩码,非 TTY 退化为可见 */
|
|
101
75
|
export async function askSecret(question) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
76
|
+
if (isInteractive()) {
|
|
77
|
+
const inq = await getInquirer()
|
|
78
|
+
return inq.password({ message: question, mask: '*' })
|
|
105
79
|
}
|
|
106
|
-
|
|
107
|
-
if (_rl && !_rl.closed) _rl.close()
|
|
108
|
-
_rl = null
|
|
109
|
-
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
process.stdout.write(`${question}: `)
|
|
112
|
-
let result = ''
|
|
113
|
-
let resolved = false
|
|
114
|
-
|
|
115
|
-
const cleanup = () => {
|
|
116
|
-
if (resolved) return
|
|
117
|
-
resolved = true
|
|
118
|
-
process.stdin.removeListener('data', onData)
|
|
119
|
-
try {
|
|
120
|
-
process.stdin.setRawMode(false)
|
|
121
|
-
} catch {
|
|
122
|
-
// ignore
|
|
123
|
-
}
|
|
124
|
-
process.stdin.pause()
|
|
125
|
-
process.stdout.write('\n')
|
|
126
|
-
resolve(result)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const onData = (buf) => {
|
|
130
|
-
for (let i = 0; i < buf.length; i++) {
|
|
131
|
-
const byte = buf[i]
|
|
132
|
-
if (byte === 13 || byte === 10) {
|
|
133
|
-
// Enter
|
|
134
|
-
cleanup()
|
|
135
|
-
return
|
|
136
|
-
} else if (byte === 3 || byte === 4) {
|
|
137
|
-
// Ctrl+C / Ctrl+D
|
|
138
|
-
cleanup()
|
|
139
|
-
return
|
|
140
|
-
} else if (byte === 127 || byte === 8) {
|
|
141
|
-
// Backspace / Ctrl+H
|
|
142
|
-
if (result.length > 0) {
|
|
143
|
-
result = result.slice(0, -1)
|
|
144
|
-
process.stdout.write('\b \b')
|
|
145
|
-
}
|
|
146
|
-
} else if (byte >= 32 && byte < 127) {
|
|
147
|
-
result += String.fromCharCode(byte)
|
|
148
|
-
process.stdout.write('*')
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
process.stdin.setRawMode(true)
|
|
155
|
-
} catch {
|
|
156
|
-
// 无法 raw mode — 退化为可见输入
|
|
157
|
-
process.stdout.write('\n')
|
|
158
|
-
resolve('')
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
process.stdin.resume()
|
|
162
|
-
process.stdin.on('data', onData)
|
|
163
|
-
})
|
|
80
|
+
return ask(question)
|
|
164
81
|
}
|
|
165
82
|
|
|
166
83
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* - 输入 a/all → 全选
|
|
170
|
-
* - 输入 n/none → 全不选
|
|
171
|
-
* - 输入 1,3 5 → 选 [0, 2, 4]
|
|
172
|
-
*
|
|
173
|
-
* @param {string} question
|
|
174
|
-
* @param {Array<{label: string}>} items
|
|
175
|
-
* @param {{defaultSelected?: number[]}} [opts]
|
|
84
|
+
* 多选(TUI:上下导航 + 空格选中/取消 + 回车确认)。
|
|
85
|
+
* 非 TTY:退化为序号输入(逗号/空格分隔)。
|
|
176
86
|
* @returns {Promise<number[]>} 选中的索引数组
|
|
177
87
|
*/
|
|
178
88
|
export async function multiSelect(question, items, { defaultSelected = [] } = {}) {
|
|
179
89
|
if (items.length === 0) return []
|
|
90
|
+
if (isInteractive()) {
|
|
91
|
+
const inq = await getInquirer()
|
|
92
|
+
const choices = items.map((item, i) => ({
|
|
93
|
+
name: item.label,
|
|
94
|
+
value: i,
|
|
95
|
+
checked: defaultSelected.includes(i),
|
|
96
|
+
}))
|
|
97
|
+
const selected = await inq.checkbox({
|
|
98
|
+
message: question,
|
|
99
|
+
choices,
|
|
100
|
+
loop: false,
|
|
101
|
+
required: false,
|
|
102
|
+
})
|
|
103
|
+
return selected
|
|
104
|
+
}
|
|
105
|
+
// 非 TTY fallback:序号输入
|
|
180
106
|
console.log(question)
|
|
181
107
|
items.forEach((item, i) => {
|
|
182
108
|
const selected = defaultSelected.includes(i)
|
|
183
109
|
console.log(` [${selected ? 'x' : ' '}] ${i + 1}. ${item.label}`)
|
|
184
110
|
})
|
|
185
111
|
console.log(' (输入序号,逗号/空格分隔;回车=默认;a=全选;n=全不选)')
|
|
186
|
-
|
|
187
112
|
const defaultStr = defaultSelected.map((i) => i + 1).join(',')
|
|
188
113
|
const answer = await ask('选择', { defaultValue: defaultStr })
|
|
189
114
|
const trimmed = answer.trim().toLowerCase()
|
|
190
|
-
|
|
191
|
-
if (trimmed === '
|
|
192
|
-
return items.map((_, i) => i)
|
|
193
|
-
}
|
|
194
|
-
if (trimmed === 'n' || trimmed === 'none') {
|
|
195
|
-
return []
|
|
196
|
-
}
|
|
197
|
-
|
|
115
|
+
if (trimmed === 'a' || trimmed === 'all') return items.map((_, i) => i)
|
|
116
|
+
if (trimmed === 'n' || trimmed === 'none') return []
|
|
198
117
|
const nums = answer
|
|
199
118
|
.split(/[,\s]+/)
|
|
200
119
|
.map((s) => parseInt(s, 10))
|
|
201
120
|
.filter((n) => !isNaN(n) && n >= 1 && n <= items.length)
|
|
202
121
|
.map((n) => n - 1)
|
|
203
|
-
// 去重
|
|
204
122
|
return [...new Set(nums)]
|
|
205
123
|
}
|
|
206
124
|
|
|
207
125
|
/**
|
|
208
|
-
*
|
|
209
|
-
* @param {string} question
|
|
210
|
-
* @param {Array<{label: string}>} items
|
|
211
|
-
* @param {{defaultIndex?: number}} [opts]
|
|
126
|
+
* 单选(TUI:上下导航 + 回车确认)。
|
|
212
127
|
* @returns {Promise<number>} 选中的索引(items.length===1 时直接返回 0)
|
|
213
128
|
*/
|
|
214
129
|
export async function select(question, items, { defaultIndex = 0 } = {}) {
|
|
215
130
|
if (items.length === 0) return -1
|
|
216
131
|
if (items.length === 1) return 0
|
|
132
|
+
if (isInteractive()) {
|
|
133
|
+
const inq = await getInquirer()
|
|
134
|
+
const choices = items.map((item, i) => ({ name: item.label, value: i }))
|
|
135
|
+
return inq.select({ message: question, choices, default: defaultIndex, loop: false })
|
|
136
|
+
}
|
|
137
|
+
// 非 TTY fallback
|
|
217
138
|
console.log(question)
|
|
218
139
|
items.forEach((item, i) => {
|
|
219
140
|
const marker = i === defaultIndex ? ' (默认)' : ''
|
|
@@ -225,17 +146,14 @@ export async function select(question, items, { defaultIndex = 0 } = {}) {
|
|
|
225
146
|
return n - 1
|
|
226
147
|
}
|
|
227
148
|
|
|
228
|
-
/**
|
|
229
|
-
* 是非确认
|
|
230
|
-
* @param {string} question
|
|
231
|
-
* @param {{defaultValue?: boolean}} [opts]
|
|
232
|
-
* @returns {Promise<boolean>}
|
|
233
|
-
*/
|
|
149
|
+
/** 是非确认(TUI:y/n;非 TTY:输入) */
|
|
234
150
|
export async function confirm(question, { defaultValue = true } = {}) {
|
|
151
|
+
if (isInteractive()) {
|
|
152
|
+
const inq = await getInquirer()
|
|
153
|
+
return inq.confirm({ message: question, default: defaultValue })
|
|
154
|
+
}
|
|
235
155
|
const hint = defaultValue ? 'Y/n' : 'y/N'
|
|
236
|
-
const answer = await ask(`${question} (${hint})`, {
|
|
237
|
-
defaultValue: defaultValue ? 'y' : 'n',
|
|
238
|
-
})
|
|
156
|
+
const answer = await ask(`${question} (${hint})`, { defaultValue: defaultValue ? 'y' : 'n' })
|
|
239
157
|
const v = answer.trim().toLowerCase()
|
|
240
158
|
if (!v) return defaultValue
|
|
241
159
|
return v === 'y' || v === 'yes'
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2api-store/activate",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "TokenShop CLI 一键激活:配置本地 codex/claude/gemini/grok/opencode 指向 2api.store 中转",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"activate": "activate.mjs"
|
|
7
|
+
"activate": "./activate.mjs"
|
|
8
8
|
},
|
|
9
9
|
"main": "./activate.mjs",
|
|
10
10
|
"engines": {
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"opencode"
|
|
32
32
|
],
|
|
33
33
|
"author": "TokenShop",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@inquirer/prompts": "^7.0.0"
|
|
36
|
+
},
|
|
34
37
|
"license": "MIT",
|
|
35
38
|
"publishConfig": {
|
|
36
39
|
"access": "public"
|