@pikecode/api-key-manager 1.0.31 → 1.0.33
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 +8 -2
- package/bin/akm.js +5 -3
- package/package.json +1 -1
- package/src/commands/add.js +19 -33
- package/src/commands/backup.js +2 -2
- package/src/commands/current.js +14 -12
- package/src/commands/edit.js +51 -7
- package/src/commands/list.js +11 -9
- package/src/commands/remove.js +2 -2
- package/src/commands/switch.js +46 -73
- package/src/config.js +84 -23
- package/src/utils/codex-launcher.js +1 -42
- package/src/utils/env-launcher.js +7 -43
- package/src/utils/env-utils.js +52 -0
- package/src/utils/error-handler.js +1 -1
- package/src/utils/launch-args.js +96 -0
- package/src/utils/provider-status-checker.js +87 -26
- package/src/utils/secrets.js +22 -0
- package/src/utils/update-checker.js +8 -2
- package/src/utils/validator.js +3 -68
- package/src/utils/storage.js +0 -55
|
@@ -1,28 +1,94 @@
|
|
|
1
1
|
const { Anthropic, APIConnectionTimeoutError, APIConnectionError, APIError } = require('@anthropic-ai/sdk');
|
|
2
2
|
|
|
3
|
+
let authTokenEnvLock = Promise.resolve();
|
|
4
|
+
|
|
5
|
+
// 状态缓存
|
|
6
|
+
const statusCache = new Map();
|
|
7
|
+
const DEFAULT_CACHE_TTL = 30000; // 30秒缓存
|
|
8
|
+
|
|
3
9
|
class ProviderStatusChecker {
|
|
4
10
|
constructor(options = {}) {
|
|
5
11
|
this.timeout = options.timeout ?? 5000;
|
|
6
12
|
this.testMessage = options.testMessage ?? '你好';
|
|
7
13
|
this.maxTokens = options.maxTokens ?? 32;
|
|
8
14
|
this.defaultModel = 'claude-haiku-4-5-20251001';
|
|
15
|
+
this.cacheTTL = options.cacheTTL ?? DEFAULT_CACHE_TTL;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_getCacheKey(provider) {
|
|
19
|
+
// 基于关键配置生成缓存键
|
|
20
|
+
return `${provider.name}:${provider.authToken}:${provider.baseUrl}:${provider.authMode}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_getCachedStatus(provider) {
|
|
24
|
+
const key = this._getCacheKey(provider);
|
|
25
|
+
const cached = statusCache.get(key);
|
|
26
|
+
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
|
|
27
|
+
return cached.status;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_setCachedStatus(provider, status) {
|
|
33
|
+
const key = this._getCacheKey(provider);
|
|
34
|
+
statusCache.set(key, {
|
|
35
|
+
status,
|
|
36
|
+
timestamp: Date.now()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
clearCache() {
|
|
41
|
+
statusCache.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async _withAuthTokenEnv(authToken, operation) {
|
|
45
|
+
const previous = authTokenEnvLock;
|
|
46
|
+
let release;
|
|
47
|
+
authTokenEnvLock = new Promise((resolve) => {
|
|
48
|
+
release = resolve;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await previous;
|
|
52
|
+
|
|
53
|
+
const original = process.env.ANTHROPIC_AUTH_TOKEN;
|
|
54
|
+
try {
|
|
55
|
+
process.env.ANTHROPIC_AUTH_TOKEN = authToken;
|
|
56
|
+
return await operation();
|
|
57
|
+
} finally {
|
|
58
|
+
if (original !== undefined) {
|
|
59
|
+
process.env.ANTHROPIC_AUTH_TOKEN = original;
|
|
60
|
+
} else {
|
|
61
|
+
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
|
62
|
+
}
|
|
63
|
+
release();
|
|
64
|
+
}
|
|
9
65
|
}
|
|
10
66
|
|
|
11
|
-
async check(provider) {
|
|
67
|
+
async check(provider, options = {}) {
|
|
12
68
|
if (!provider) {
|
|
13
69
|
return this._result('unknown', '未找到配置', null);
|
|
14
70
|
}
|
|
15
71
|
|
|
72
|
+
// 检查缓存(除非明确跳过)
|
|
73
|
+
if (!options.skipCache) {
|
|
74
|
+
const cached = this._getCachedStatus(provider);
|
|
75
|
+
if (cached) {
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
16
80
|
if (provider.ideName === 'codex') {
|
|
17
|
-
|
|
81
|
+
const result = await this._checkCodex(provider);
|
|
82
|
+
this._setCachedStatus(provider, result);
|
|
83
|
+
return result;
|
|
18
84
|
}
|
|
19
85
|
|
|
20
86
|
if (provider.authMode === 'oauth_token') {
|
|
21
87
|
return this._result('unknown', '暂不支持 OAuth 令牌检测', null);
|
|
22
88
|
}
|
|
23
89
|
|
|
24
|
-
// auth_token
|
|
25
|
-
//
|
|
90
|
+
// auth_token 模式可留空 baseUrl(使用官方默认 API)
|
|
91
|
+
// api_key 模式需要 baseUrl(用于自定义端点/代理)
|
|
26
92
|
if (provider.authMode === 'auth_token' && !provider.baseUrl) {
|
|
27
93
|
// 对于官方 Anthropic API 的 auth_token 模式,不需要 baseUrl
|
|
28
94
|
// 直接使用官方 API
|
|
@@ -36,19 +102,12 @@ class ProviderStatusChecker {
|
|
|
36
102
|
|
|
37
103
|
const model = this._resolveModel(provider);
|
|
38
104
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// 保存原始环境变量
|
|
45
|
-
const originalEnv = {};
|
|
46
|
-
if (provider.authMode === 'auth_token') {
|
|
47
|
-
originalEnv.ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN;
|
|
48
|
-
process.env.ANTHROPIC_AUTH_TOKEN = provider.authToken;
|
|
49
|
-
}
|
|
105
|
+
const performCheck = async () => {
|
|
106
|
+
const client = this._createClient(provider);
|
|
107
|
+
if (!client) {
|
|
108
|
+
return this._result('unknown', '认证模式不受支持', null);
|
|
109
|
+
}
|
|
50
110
|
|
|
51
|
-
try {
|
|
52
111
|
const start = process.hrtime.bigint();
|
|
53
112
|
const response = await client.messages.create({
|
|
54
113
|
model,
|
|
@@ -68,18 +127,20 @@ class ProviderStatusChecker {
|
|
|
68
127
|
}
|
|
69
128
|
|
|
70
129
|
return this._result('online', `可用 ${latency.toFixed(0)}ms`, latency);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
let result;
|
|
133
|
+
if (provider.authMode === 'auth_token') {
|
|
134
|
+
result = await this._withAuthTokenEnv(provider.authToken, performCheck);
|
|
135
|
+
} else {
|
|
136
|
+
result = await performCheck();
|
|
80
137
|
}
|
|
138
|
+
this._setCachedStatus(provider, result);
|
|
139
|
+
return result;
|
|
81
140
|
} catch (error) {
|
|
82
|
-
|
|
141
|
+
const result = this._handleError(error);
|
|
142
|
+
this._setCachedStatus(provider, result);
|
|
143
|
+
return result;
|
|
83
144
|
}
|
|
84
145
|
}
|
|
85
146
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function maskToken(token) {
|
|
2
|
+
if (!token || typeof token !== 'string') {
|
|
3
|
+
return token;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const value = token.trim();
|
|
7
|
+
if (value.length < 10) {
|
|
8
|
+
return '***';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return value.substring(0, 8) + '***' + value.substring(value.length - 4);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function maybeMaskToken(token, showToken = false) {
|
|
15
|
+
if (!token) {
|
|
16
|
+
return token;
|
|
17
|
+
}
|
|
18
|
+
return showToken ? token : maskToken(token);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { maskToken, maybeMaskToken };
|
|
22
|
+
|
|
@@ -105,8 +105,14 @@ async function checkForUpdates({ packageName, currentVersion }) {
|
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
console.log(chalk.green('更新成功,正在重启...'));
|
|
108
|
-
const
|
|
109
|
-
|
|
108
|
+
const scriptPath = process.argv[1];
|
|
109
|
+
const restartCommand = scriptPath ? process.execPath : packageName;
|
|
110
|
+
const restartArgs = scriptPath
|
|
111
|
+
? [scriptPath, ...process.argv.slice(2)]
|
|
112
|
+
: process.argv.slice(2);
|
|
113
|
+
|
|
114
|
+
const child = spawn(restartCommand, restartArgs, {
|
|
115
|
+
shell: process.platform === 'win32',
|
|
110
116
|
stdio: 'inherit',
|
|
111
117
|
});
|
|
112
118
|
child.on('close', (code) => {
|
package/src/utils/validator.js
CHANGED
|
@@ -117,75 +117,10 @@ const validator = {
|
|
|
117
117
|
return null;
|
|
118
118
|
},
|
|
119
119
|
|
|
120
|
-
validateLaunchArgs(args) {
|
|
121
|
-
if (!Array.isArray(args)) {
|
|
122
|
-
return '启动参数必须是数组';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const validArgs = [
|
|
126
|
-
'--dangerously-skip-permissions',
|
|
127
|
-
'--no-confirm',
|
|
128
|
-
'--allow-all',
|
|
129
|
-
'--auto-approve',
|
|
130
|
-
'--yes',
|
|
131
|
-
'--force'
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
for (const arg of args) {
|
|
135
|
-
if (!validArgs.includes(arg)) {
|
|
136
|
-
return `无效的启动参数: ${arg}`;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return null;
|
|
141
|
-
},
|
|
142
|
-
|
|
143
120
|
getAvailableLaunchArgs() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
name: '--continue',
|
|
147
|
-
label: '继续上次对话',
|
|
148
|
-
description: '恢复上次的对话记录',
|
|
149
|
-
checked: false
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
name: '--dangerously-skip-permissions',
|
|
153
|
-
label: '最高权限',
|
|
154
|
-
description: '仅限沙盒环境使用',
|
|
155
|
-
checked: false
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: '--no-confirm',
|
|
159
|
-
label: '直接执行操作',
|
|
160
|
-
description: '跳过确认提示',
|
|
161
|
-
checked: false
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: '--allow-all',
|
|
165
|
-
label: '允许全部操作',
|
|
166
|
-
description: '移除安全限制',
|
|
167
|
-
checked: false
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
name: '--auto-approve',
|
|
171
|
-
label: '自动批准请求',
|
|
172
|
-
description: '无需人工同意',
|
|
173
|
-
checked: false
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: '--yes',
|
|
177
|
-
label: '默认回答 yes',
|
|
178
|
-
description: '自动同意所有询问',
|
|
179
|
-
checked: false
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: '--force',
|
|
183
|
-
label: '强制执行',
|
|
184
|
-
description: '忽略可能的警告',
|
|
185
|
-
checked: false
|
|
186
|
-
}
|
|
187
|
-
];
|
|
121
|
+
const { getClaudeLaunchArgs } = require('./launch-args');
|
|
122
|
+
return getClaudeLaunchArgs();
|
|
188
123
|
}
|
|
189
124
|
};
|
|
190
125
|
|
|
191
|
-
module.exports = { validator };
|
|
126
|
+
module.exports = { validator };
|
package/src/utils/storage.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
class Storage {
|
|
6
|
-
static async ensureConfigDir() {
|
|
7
|
-
const configDir = path.join(os.homedir(), '.akm-config');
|
|
8
|
-
await fs.ensureDir(configDir);
|
|
9
|
-
return configDir;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
static async readConfig() {
|
|
13
|
-
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
14
|
-
try {
|
|
15
|
-
if (await fs.pathExists(configPath)) {
|
|
16
|
-
return await fs.readJSON(configPath);
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
19
|
-
} catch (error) {
|
|
20
|
-
throw new Error(`读取配置文件失败: ${error.message}`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static async writeConfig(config) {
|
|
25
|
-
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
26
|
-
try {
|
|
27
|
-
await fs.writeJSON(configPath, config, { spaces: 2 });
|
|
28
|
-
return true;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
throw new Error(`写入配置文件失败: ${error.message}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
static async backupConfig() {
|
|
35
|
-
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
36
|
-
const backupPath = path.join(os.homedir(), '.akm-config.backup.json');
|
|
37
|
-
|
|
38
|
-
if (await fs.pathExists(configPath)) {
|
|
39
|
-
await fs.copy(configPath, backupPath);
|
|
40
|
-
return backupPath;
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
static async restoreConfig(backupPath) {
|
|
46
|
-
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
47
|
-
if (await fs.pathExists(backupPath)) {
|
|
48
|
-
await fs.copy(backupPath, configPath);
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = { Storage };
|