think-ai-auto 1.8.0

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 ADDED
@@ -0,0 +1,39 @@
1
+ # think-ai-auto
2
+
3
+ 一键配置 Claude Code / OpenCode / OpenClaw 的 API 密钥工具。
4
+
5
+ ## 使用方法
6
+
7
+ ```bash
8
+ npx think-ai-auto
9
+ ```
10
+
11
+ 或全局安装后使用:
12
+
13
+ ```bash
14
+ npm install -g think-ai-auto
15
+ think-ai-auto
16
+ ```
17
+
18
+ ## 功能
19
+
20
+ - 支持配置 **Claude Code**、**OpenCode**、**OpenClaw**
21
+ - 使用上下箭头选择工具
22
+ - 自动创建配置文件和目录
23
+ - 兼容 macOS / Windows / Linux
24
+
25
+ ## 配置说明
26
+
27
+ ### Claude Code
28
+ - 初始化文件: `~/.claude.json`
29
+ - 配置文件: `~/.claude/settings.json`
30
+
31
+ ### OpenCode
32
+ - 配置文件: `~/.config/opencode/opencode.json`
33
+
34
+ ### OpenClaw
35
+ - 配置文件: `~/.openclaw/openclaw.json`
36
+
37
+ ## API 地址
38
+
39
+ 所有工具统一使用: `https://www.thinkai.tv`
package/bin/index.js ADDED
@@ -0,0 +1,744 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { select, password } from '@inquirer/prompts';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import os from 'os';
7
+
8
+ const API_BASE_URL = 'https://www.thinkai.tv';
9
+
10
+ const colors = {
11
+ reset: '\x1b[0m',
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ blue: '\x1b[34m',
16
+ cyan: '\x1b[36m',
17
+ gray: '\x1b[90m',
18
+ bold: '\x1b[1m'
19
+ };
20
+
21
+ function colorize(text, color) {
22
+ return `${colors[color] || ''}${text}${colors.reset}`;
23
+ }
24
+
25
+ function getHomeDir() {
26
+ return os.homedir();
27
+ }
28
+
29
+ function ensureDir(dirPath) {
30
+ if (!fs.existsSync(dirPath)) {
31
+ fs.mkdirSync(dirPath, { recursive: true });
32
+ }
33
+ }
34
+
35
+ function getTimestamp() {
36
+ const now = new Date();
37
+ return now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
38
+ }
39
+
40
+ function backupFile(filePath) {
41
+ if (fs.existsSync(filePath)) {
42
+ const timestamp = getTimestamp();
43
+ const backupPath = `${filePath}.bak.${timestamp}`;
44
+ fs.copyFileSync(filePath, backupPath);
45
+ console.log(`📦 已备份原配置: ${backupPath}`);
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+
51
+ function readJsonFile(filePath) {
52
+ try {
53
+ if (fs.existsSync(filePath)) {
54
+ const content = fs.readFileSync(filePath, 'utf-8');
55
+ return JSON.parse(content);
56
+ }
57
+ } catch (e) {}
58
+ return {};
59
+ }
60
+
61
+ function writeJsonFile(filePath, data) {
62
+ ensureDir(path.dirname(filePath));
63
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
64
+ }
65
+
66
+ function deepMerge(target, source) {
67
+ const result = { ...target };
68
+ for (const key of Object.keys(source)) {
69
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
70
+ result[key] = deepMerge(result[key] || {}, source[key]);
71
+ } else {
72
+ result[key] = source[key];
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+
78
+ function configureClaudeCode(apiKey) {
79
+ const homeDir = getHomeDir();
80
+ const claudeJsonPath = path.join(homeDir, '.claude.json');
81
+ backupFile(claudeJsonPath);
82
+ const claudeJson = readJsonFile(claudeJsonPath);
83
+ claudeJson.hasCompletedOnboarding = true;
84
+ writeJsonFile(claudeJsonPath, claudeJson);
85
+ console.log(`✓ 已初始化: ${claudeJsonPath}`);
86
+
87
+ const settingsPath = path.join(homeDir, '.claude', 'settings.json');
88
+ backupFile(settingsPath);
89
+ const settings = readJsonFile(settingsPath);
90
+ settings.env = {
91
+ ...(settings.env || {}),
92
+ ANTHROPIC_BASE_URL: API_BASE_URL,
93
+ API_TIMEOUT_MS: 3000000,
94
+ ANTHROPIC_AUTH_TOKEN: apiKey,
95
+ ANTHROPIC_API_KEY: apiKey
96
+ };
97
+ writeJsonFile(settingsPath, settings);
98
+ console.log(`✓ 已配置: ${settingsPath}`);
99
+ console.log('\n🎉 Claude Code 配置完成!');
100
+ }
101
+
102
+ function readTomlValue(content, key) {
103
+ const match = content.match(new RegExp(`^${key}\\s*=\\s*"([^"]*)"`, 'm'));
104
+ return match ? match[1] : null;
105
+ }
106
+
107
+ function mergeTomlConfig(existing, name, baseUrl, apiKey) {
108
+ let content = existing || '';
109
+ const newSection = `[model_providers.thinkai]\nname = "${name}"\nbase_url = "${baseUrl}"\napi_key = "${apiKey}"`;
110
+
111
+ // 更新或插入顶层 model_provider
112
+ if (/^model_provider\s*=/m.test(content)) {
113
+ content = content.replace(/^model_provider\s*=.*/m, `model_provider = "thinkai"`);
114
+ } else {
115
+ content = `model_provider = "thinkai"\n` + content;
116
+ }
117
+
118
+ // 原地替换 [model_providers.thinkai] 节;不存在则追加
119
+ const sectionStart = content.indexOf('[model_providers.thinkai]');
120
+ if (sectionStart !== -1) {
121
+ const afterSection = content.slice(sectionStart + 1);
122
+ const nextSection = afterSection.match(/\n\[/);
123
+ const sectionEnd = nextSection ? sectionStart + 1 + nextSection.index + 1 : content.length;
124
+ content = content.slice(0, sectionStart) + newSection + '\n' + content.slice(sectionEnd);
125
+ } else {
126
+ content = content.trimEnd() + `\n\n${newSection}\n`;
127
+ }
128
+
129
+ return content;
130
+ }
131
+
132
+ function configureCodex(apiKey) {
133
+ const homeDir = getHomeDir();
134
+ const codexDir = path.join(homeDir, '.codex');
135
+ const configPath = path.join(codexDir, 'config.toml');
136
+ const authPath = path.join(codexDir, 'auth.json');
137
+
138
+ backupFile(configPath);
139
+ backupFile(authPath);
140
+
141
+ ensureDir(codexDir);
142
+
143
+ const existingToml = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
144
+ const merged = mergeTomlConfig(existingToml, 'thinkAI', `${API_BASE_URL}/v1`, apiKey);
145
+ fs.writeFileSync(configPath, merged, 'utf-8');
146
+ console.log(`✓ 已配置: ${configPath}`);
147
+
148
+ const existingAuth = readJsonFile(authPath);
149
+ writeJsonFile(authPath, { ...existingAuth, auth_mode: 'apikey', OPENAI_API_KEY: apiKey });
150
+ console.log(`✓ 已配置: ${authPath}`);
151
+ console.log('\n🎉 Codex 配置完成!');
152
+ }
153
+
154
+ function configureOpenCode(apiKey) {
155
+ const homeDir = getHomeDir();
156
+ const configPath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
157
+ backupFile(configPath);
158
+ const existing = readJsonFile(configPath);
159
+ const apiConfig = {
160
+ "$schema": "https://opencode.ai/config.json",
161
+ "provider": {
162
+ "anthropic": {
163
+ "name": "think AI(Anthropic)",
164
+ "options": {
165
+ "baseURL": `${API_BASE_URL}/v1`,
166
+ "apiKey": apiKey
167
+ }
168
+ },
169
+ "openai": {
170
+ "npm": "@ai-sdk/openai-compatible",
171
+ "name": "think AI(Openai)",
172
+ "options": {
173
+ "baseURL": `${API_BASE_URL}/v1`,
174
+ "apiKey": apiKey
175
+ }
176
+ }
177
+ }
178
+ };
179
+ const merged = deepMerge(existing, apiConfig);
180
+ writeJsonFile(configPath, merged);
181
+ console.log(`✓ 已配置: ${configPath}`);
182
+ console.log('\n🎉 OpenCode 配置完成!');
183
+ console.log('提示: 运行 opencode 后使用 /model 选择模型');
184
+ }
185
+
186
+ function configureOpenClaw(apiKey) {
187
+ const homeDir = getHomeDir();
188
+ const configPath = path.join(homeDir, '.openclaw', 'openclaw.json');
189
+ backupFile(configPath);
190
+ const existing = readJsonFile(configPath);
191
+ const modelClaude = [
192
+ { "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "reasoning": false, "input": ["text", "image"], "cost": { "input": 3, "output": 15, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 },
193
+ { "id": "claude-opus-4-6", "name": "Claude Opus 4.6", "reasoning": false, "input": ["text", "image"], "cost": { "input": 5, "output": 25, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 }
194
+ ];
195
+ const apiConfig = {
196
+ "models": {
197
+ "providers": {
198
+ "think-Anthropic": {
199
+ "baseUrl": API_BASE_URL,
200
+ "apiKey": apiKey,
201
+ "api": "anthropic-messages",
202
+ "models": modelClaude
203
+ },
204
+ "think-Openai": {
205
+ "baseUrl": `${API_BASE_URL}/v1`,
206
+ "apiKey": apiKey,
207
+ "api": "openai-completions",
208
+ "models": [
209
+ ...modelClaude,
210
+ { "id": "gpt-5.2", "name": "GPT-5.2", "reasoning": false, "input": ["text", "image"], "cost": { "input": 1.7, "output": 14, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 },
211
+ { "id": "gpt-5.3-codex", "name": "GPT-5.3-codex", "reasoning": false, "input": ["text", "image"], "cost": { "input": 1.7, "output": 14, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 },
212
+ { "id": "gpt-5.4", "name": "GPT-5.4", "reasoning": false, "input": ["text", "image"], "cost": { "input": 2.5, "output": 20, "cacheRead": 0, "cacheWrite": 0 }, "contextWindow": 200000, "maxTokens": 8192 }
213
+ ]
214
+ }
215
+ }
216
+ },
217
+ "agents": {
218
+ "defaults": {
219
+ "model": { "primary": "think-Anthropic/claude-sonnet-4-6" }
220
+ }
221
+ }
222
+ };
223
+ const merged = deepMerge(existing, apiConfig);
224
+ writeJsonFile(configPath, merged);
225
+ console.log(`✓ 已配置: ${configPath}`);
226
+ console.log('\n🎉 OpenClaw 配置完成!');
227
+ console.log('\n📖 使用提示:');
228
+ console.log(' /models think-Openai 查看 OpenAI 兼容模型');
229
+ console.log(' /models think-Anthropic 查看 Anthropic 模型');
230
+ console.log(' /model think-Anthropic/xxx 切换模型 (如 /model think-Anthropic/claude-opus-4-6)');
231
+ console.log('\n⚠️ 配置后需要重启 gateway 才能生效: openclaw gateway restart');
232
+ }
233
+
234
+ // ==================== 配置检测功能 ====================
235
+
236
+ function maskSecret(value) {
237
+ if (!value || value.length < 16) return '***';
238
+ return value.slice(0, 6) + '****' + value.slice(-6);
239
+ }
240
+
241
+ function fileExists(filePath) {
242
+ return fs.existsSync(filePath);
243
+ }
244
+
245
+ function printSection(title) {
246
+ console.log(`\n${colorize('━'.repeat(50), 'cyan')}`);
247
+ console.log(colorize(` ${title}`, 'bold'));
248
+ console.log(colorize('━'.repeat(50), 'cyan'));
249
+ }
250
+
251
+ function printConfigItem(label, value, source, isOverridden = false) {
252
+ const statusIcon = value ? colorize('✓', 'green') : colorize('✗', 'red');
253
+ const overrideTag = isOverridden ? colorize(' [已覆盖]', 'yellow') : '';
254
+ const sourceTag = source ? colorize(` (${source})`, 'gray') : '';
255
+ if (value) {
256
+ console.log(` ${statusIcon} ${label}: ${colorize(maskSecret(value), 'green')}${sourceTag}${overrideTag}`);
257
+ } else {
258
+ console.log(` ${statusIcon} ${label}: ${colorize('未设置', 'red')}${sourceTag}`);
259
+ }
260
+ }
261
+
262
+ function printFileStatus(label, filePath, exists) {
263
+ const icon = exists ? colorize('✓', 'green') : colorize('○', 'gray');
264
+ const status = exists ? colorize('存在', 'green') : colorize('不存在', 'gray');
265
+ console.log(` ${icon} ${label}`);
266
+ console.log(` ${colorize(filePath, 'gray')} - ${status}`);
267
+ }
268
+
269
+ function checkClaudeCode() {
270
+ printSection('Claude Code 配置检测');
271
+ const homeDir = getHomeDir();
272
+ const cwd = process.cwd();
273
+ const isHomeDir = path.resolve(cwd) === path.resolve(homeDir);
274
+
275
+ if (isHomeDir) {
276
+ console.log(colorize('\n⚠️ 当前工作目录为用户主目录,跳过项目级配置检测(与全局配置路径相同)', 'yellow'));
277
+ }
278
+
279
+ const globalSettingsPath = path.join(homeDir, '.claude', 'settings.json');
280
+ const projectSettingsPath = path.join(cwd, '.claude', 'settings.json');
281
+ const projectLocalSettingsPath = path.join(cwd, '.claude', 'settings.local.json');
282
+ const claudeJsonPath = path.join(homeDir, '.claude.json');
283
+
284
+ console.log(colorize('\n📁 配置文件状态:', 'blue'));
285
+ printFileStatus('全局配置', globalSettingsPath, fileExists(globalSettingsPath));
286
+ if (!isHomeDir) {
287
+ printFileStatus('项目配置', projectSettingsPath, fileExists(projectSettingsPath));
288
+ printFileStatus('项目本地配置', projectLocalSettingsPath, fileExists(projectLocalSettingsPath));
289
+ }
290
+ printFileStatus('全局状态文件', claudeJsonPath, fileExists(claudeJsonPath));
291
+
292
+ const globalSettings = readJsonFile(globalSettingsPath);
293
+ const projectSettings = isHomeDir ? {} : readJsonFile(projectSettingsPath);
294
+ const projectLocalSettings = isHomeDir ? {} : readJsonFile(projectLocalSettingsPath);
295
+
296
+ const envVars = {
297
+ ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL,
298
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
299
+ ANTHROPIC_AUTH_TOKEN: process.env.ANTHROPIC_AUTH_TOKEN,
300
+ ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL,
301
+ ANTHROPIC_SMALL_FAST_MODEL: process.env.ANTHROPIC_SMALL_FAST_MODEL
302
+ };
303
+
304
+ const globalEnv = globalSettings.env || {};
305
+ const projectEnv = projectSettings.env || {};
306
+ const projectLocalEnv = projectLocalSettings.env || {};
307
+
308
+ console.log(colorize('\n🔧 关键配置项 (按优先级合并后):', 'blue'));
309
+ const configKeys = ['ANTHROPIC_BASE_URL', 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'];
310
+
311
+ for (const key of configKeys) {
312
+ let effectiveValue = null;
313
+ let source = null;
314
+ let isOverridden = false;
315
+
316
+ if (envVars[key]) {
317
+ effectiveValue = envVars[key];
318
+ source = '环境变量';
319
+ isOverridden = !!(globalEnv[key] || projectEnv[key] || projectLocalEnv[key]);
320
+ } else if (projectLocalEnv[key]) {
321
+ effectiveValue = projectLocalEnv[key];
322
+ source = '项目本地配置';
323
+ isOverridden = !!(globalEnv[key] || projectEnv[key]);
324
+ } else if (projectEnv[key]) {
325
+ effectiveValue = projectEnv[key];
326
+ source = '项目配置';
327
+ isOverridden = !!globalEnv[key];
328
+ } else if (globalEnv[key]) {
329
+ effectiveValue = globalEnv[key];
330
+ source = '全局配置';
331
+ }
332
+ printConfigItem(key, effectiveValue, source, isOverridden);
333
+ }
334
+
335
+ console.log(colorize('\n📊 各级配置详情:', 'blue'));
336
+
337
+ console.log(colorize('\n [环境变量]', 'yellow'));
338
+ for (const key of configKeys) {
339
+ const val = envVars[key];
340
+ console.log(` ${key}: ${val ? colorize(maskSecret(val), 'green') : colorize('未设置', 'gray')}`);
341
+ }
342
+
343
+ if (!isHomeDir) {
344
+ console.log(colorize('\n [项目本地配置] .claude/settings.local.json', 'yellow'));
345
+ if (fileExists(projectLocalSettingsPath)) {
346
+ for (const key of configKeys) {
347
+ const val = projectLocalEnv[key];
348
+ console.log(` ${key}: ${val ? colorize(maskSecret(val), 'green') : colorize('未设置', 'gray')}`);
349
+ }
350
+ } else {
351
+ console.log(colorize(' (文件不存在)', 'gray'));
352
+ }
353
+
354
+ console.log(colorize('\n [项目配置] .claude/settings.json', 'yellow'));
355
+ if (fileExists(projectSettingsPath)) {
356
+ for (const key of configKeys) {
357
+ const val = projectEnv[key];
358
+ console.log(` ${key}: ${val ? colorize(maskSecret(val), 'green') : colorize('未设置', 'gray')}`);
359
+ }
360
+ } else {
361
+ console.log(colorize(' (文件不存在)', 'gray'));
362
+ }
363
+ }
364
+
365
+ console.log(colorize('\n [全局配置] ~/.claude/settings.json', 'yellow'));
366
+ if (fileExists(globalSettingsPath)) {
367
+ for (const key of configKeys) {
368
+ const val = globalEnv[key];
369
+ console.log(` ${key}: ${val ? colorize(maskSecret(val), 'green') : colorize('未设置', 'gray')}`);
370
+ }
371
+ } else {
372
+ console.log(colorize(' (文件不存在)', 'gray'));
373
+ }
374
+
375
+ console.log(colorize('\n🎯 think API 配置状态:', 'blue'));
376
+ const finalBaseUrl = envVars.ANTHROPIC_BASE_URL || projectLocalEnv.ANTHROPIC_BASE_URL || projectEnv.ANTHROPIC_BASE_URL || globalEnv.ANTHROPIC_BASE_URL;
377
+ const finalAuthToken = envVars.ANTHROPIC_AUTH_TOKEN || projectLocalEnv.ANTHROPIC_AUTH_TOKEN || projectEnv.ANTHROPIC_AUTH_TOKEN || globalEnv.ANTHROPIC_AUTH_TOKEN;
378
+
379
+ if (finalBaseUrl === API_BASE_URL && finalAuthToken) {
380
+ console.log(colorize(' ✓ think API 已正确配置', 'green'));
381
+ } else {
382
+ if (finalBaseUrl !== API_BASE_URL) {
383
+ console.log(colorize(` ✗ BASE_URL 不是 think (当前: ${finalBaseUrl || '未设置'})`, 'red'));
384
+ }
385
+ if (!finalAuthToken) {
386
+ console.log(colorize(' ✗ AUTH_TOKEN 未设置', 'red'));
387
+ }
388
+ }
389
+ }
390
+
391
+ function checkOpenCode() {
392
+ printSection('OpenCode 配置检测');
393
+ const homeDir = getHomeDir();
394
+ const cwd = process.cwd();
395
+ const isHomeDir = path.resolve(cwd) === path.resolve(homeDir);
396
+
397
+ if (isHomeDir) {
398
+ console.log(colorize('\n⚠️ 当前工作目录为用户主目录,跳过项目级配置检测', 'yellow'));
399
+ }
400
+
401
+ const globalConfigPath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
402
+ const projectConfigPath = path.join(cwd, 'opencode.json');
403
+
404
+ console.log(colorize('\n📁 配置文件状态:', 'blue'));
405
+ printFileStatus('全局配置', globalConfigPath, fileExists(globalConfigPath));
406
+ if (!isHomeDir) {
407
+ printFileStatus('项目配置', projectConfigPath, fileExists(projectConfigPath));
408
+ }
409
+
410
+ const globalConfig = readJsonFile(globalConfigPath);
411
+ const projectConfig = isHomeDir ? {} : readJsonFile(projectConfigPath);
412
+
413
+ const envVars = {
414
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
415
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY
416
+ };
417
+
418
+ console.log(colorize('\n🔧 Anthropic Provider 配置:', 'blue'));
419
+
420
+ const globalBaseUrl = globalConfig?.provider?.anthropic?.options?.baseURL;
421
+ const projectBaseUrl = projectConfig?.provider?.anthropic?.options?.baseURL;
422
+ const globalApiKey = globalConfig?.provider?.anthropic?.options?.apiKey;
423
+ const projectApiKey = projectConfig?.provider?.anthropic?.options?.apiKey;
424
+
425
+ const effectiveBaseUrl = projectBaseUrl || globalBaseUrl;
426
+ const effectiveApiKey = envVars.ANTHROPIC_API_KEY || projectApiKey || globalApiKey;
427
+ const baseUrlSource = projectBaseUrl ? '项目配置' : (globalBaseUrl ? '全局配置' : null);
428
+ const apiKeySource = envVars.ANTHROPIC_API_KEY ? '环境变量' : (projectApiKey ? '项目配置' : (globalApiKey ? '全局配置' : null));
429
+
430
+ printConfigItem('baseURL', effectiveBaseUrl, baseUrlSource, projectBaseUrl && globalBaseUrl);
431
+ printConfigItem('apiKey', effectiveApiKey, apiKeySource, (projectApiKey || envVars.ANTHROPIC_API_KEY) && globalApiKey);
432
+
433
+ console.log(colorize('\n📊 各级配置详情:', 'blue'));
434
+
435
+ console.log(colorize('\n [环境变量]', 'yellow'));
436
+ console.log(` ANTHROPIC_API_KEY: ${envVars.ANTHROPIC_API_KEY ? colorize(maskSecret(envVars.ANTHROPIC_API_KEY), 'green') : colorize('未设置', 'gray')}`);
437
+
438
+ if (!isHomeDir) {
439
+ console.log(colorize('\n [项目配置] ./opencode.json', 'yellow'));
440
+ if (fileExists(projectConfigPath)) {
441
+ console.log(` baseURL: ${projectBaseUrl ? colorize(projectBaseUrl, 'green') : colorize('未设置', 'gray')}`);
442
+ console.log(` apiKey: ${projectApiKey ? colorize(maskSecret(projectApiKey), 'green') : colorize('未设置', 'gray')}`);
443
+ } else {
444
+ console.log(colorize(' (文件不存在)', 'gray'));
445
+ }
446
+ }
447
+
448
+ console.log(colorize('\n [全局配置] ~/.config/opencode/opencode.json', 'yellow'));
449
+ console.log(` baseURL: ${globalBaseUrl ? colorize(globalBaseUrl, 'green') : colorize('未设置', 'gray')}`);
450
+ console.log(` apiKey: ${globalApiKey ? colorize(maskSecret(globalApiKey), 'green') : colorize('未设置', 'gray')}`);
451
+
452
+ console.log(colorize('\n🎯 think API 配置状态:', 'blue'));
453
+ if (effectiveBaseUrl === `${API_BASE_URL}/v1` && effectiveApiKey) {
454
+ console.log(colorize(' ✓ think API 已正确配置', 'green'));
455
+ } else {
456
+ if (effectiveBaseUrl !== `${API_BASE_URL}/v1`) {
457
+ console.log(colorize(` ✗ baseURL 不是 think (当前: ${effectiveBaseUrl || '未设置'})`, 'red'));
458
+ }
459
+ if (!effectiveApiKey) {
460
+ console.log(colorize(' ✗ apiKey 未设置', 'red'));
461
+ }
462
+ }
463
+ }
464
+
465
+ function checkOpenClaw() {
466
+ printSection('OpenClaw 配置检测');
467
+ const homeDir = getHomeDir();
468
+ const configPath = path.join(homeDir, '.openclaw', 'openclaw.json');
469
+
470
+ console.log(colorize('\n📁 配置文件状态:', 'blue'));
471
+ printFileStatus('全局配置', configPath, fileExists(configPath));
472
+
473
+ const config = readJsonFile(configPath);
474
+ const anthropicProvider = config?.models?.providers?.['think-Anthropic'];
475
+ const openaiProvider = config?.models?.providers?.['think-Openai'];
476
+
477
+ console.log(colorize('\n🔧 think-Anthropic Provider 配置:', 'blue'));
478
+ if (anthropicProvider) {
479
+ printConfigItem('baseUrl', anthropicProvider.baseUrl, '全局配置');
480
+ printConfigItem('apiKey', anthropicProvider.apiKey, '全局配置');
481
+ } else {
482
+ console.log(colorize(' ✗ think-Anthropic provider 未配置', 'red'));
483
+ }
484
+
485
+ console.log(colorize('\n🔧 think-Openai Provider 配置:', 'blue'));
486
+ if (openaiProvider) {
487
+ printConfigItem('baseUrl', openaiProvider.baseUrl, '全局配置');
488
+ printConfigItem('apiKey', openaiProvider.apiKey, '全局配置');
489
+ } else {
490
+ console.log(colorize(' ✗ think-Openai provider 未配置', 'red'));
491
+ }
492
+
493
+ console.log(colorize('\n🎯 think API 配置状态:', 'blue'));
494
+ if (anthropicProvider?.baseUrl === API_BASE_URL && anthropicProvider?.apiKey) {
495
+ console.log(colorize(' ✓ think-Anthropic 已正确配置', 'green'));
496
+ } else {
497
+ if (!anthropicProvider) {
498
+ console.log(colorize(' ✗ think-Anthropic provider 未配置', 'red'));
499
+ } else {
500
+ if (anthropicProvider.baseUrl !== API_BASE_URL) {
501
+ console.log(colorize(` ✗ think-Anthropic baseUrl 不正确 (当前: ${anthropicProvider.baseUrl || '未设置'})`, 'red'));
502
+ }
503
+ if (!anthropicProvider.apiKey) {
504
+ console.log(colorize(' ✗ think-Anthropic apiKey 未设置', 'red'));
505
+ }
506
+ }
507
+ }
508
+ if (openaiProvider?.baseUrl === `${API_BASE_URL}/v1` && openaiProvider?.apiKey) {
509
+ console.log(colorize(' ✓ think-Openai 已正确配置', 'green'));
510
+ } else {
511
+ if (!openaiProvider) {
512
+ console.log(colorize(' ✗ think-Openai provider 未配置', 'red'));
513
+ } else {
514
+ if (openaiProvider.baseUrl !== `${API_BASE_URL}/v1`) {
515
+ console.log(colorize(` ✗ think-Openai baseUrl 不正确 (当前: ${openaiProvider.baseUrl || '未设置'})`, 'red'));
516
+ }
517
+ if (!openaiProvider.apiKey) {
518
+ console.log(colorize(' ✗ think-Openai apiKey 未设置', 'red'));
519
+ }
520
+ }
521
+ }
522
+ }
523
+
524
+ function checkCodex() {
525
+ printSection('Codex 配置检测');
526
+ const homeDir = getHomeDir();
527
+ const cwd = process.cwd();
528
+ const isHomeDir = path.resolve(cwd) === path.resolve(homeDir);
529
+
530
+ if (isHomeDir) {
531
+ console.log(colorize('\n⚠️ 当前工作目录为用户主目录,跳过项目级配置检测(与全局配置路径相同)', 'yellow'));
532
+ }
533
+
534
+ const globalConfigPath = path.join(homeDir, '.codex', 'config.toml');
535
+ const globalAuthPath = path.join(homeDir, '.codex', 'auth.json');
536
+ const projectConfigPath = path.join(cwd, '.codex', 'config.toml');
537
+ const projectAuthPath = path.join(cwd, '.codex', 'auth.json');
538
+
539
+ console.log(colorize('\n📁 配置文件状态:', 'blue'));
540
+ printFileStatus('全局 config.toml', globalConfigPath, fileExists(globalConfigPath));
541
+ printFileStatus('全局 auth.json', globalAuthPath, fileExists(globalAuthPath));
542
+ if (!isHomeDir) {
543
+ printFileStatus('项目 config.toml', projectConfigPath, fileExists(projectConfigPath));
544
+ printFileStatus('项目 auth.json', projectAuthPath, fileExists(projectAuthPath));
545
+ }
546
+
547
+ const readToml = (p) => fileExists(p) ? fs.readFileSync(p, 'utf-8') : '';
548
+ const readAuth = (p) => readJsonFile(p);
549
+
550
+ const globalToml = readToml(globalConfigPath);
551
+ const projectToml = isHomeDir ? '' : readToml(projectConfigPath);
552
+ const globalAuth = readAuth(globalAuthPath);
553
+ const projectAuth = isHomeDir ? {} : readAuth(projectAuthPath);
554
+
555
+ const envBaseUrl = process.env.OPENAI_BASE_URL;
556
+ const envApiKey = process.env.OPENAI_API_KEY;
557
+
558
+ // 从 TOML 中提取值
559
+ const globalProvider = readTomlValue(globalToml, 'model_provider');
560
+ const globalBaseUrl = readTomlValue(globalToml, 'base_url');
561
+ const globalApiKeyToml = readTomlValue(globalToml, 'api_key');
562
+ const projectProvider = readTomlValue(projectToml, 'model_provider');
563
+ const projectBaseUrl = readTomlValue(projectToml, 'base_url');
564
+ const projectApiKeyToml = readTomlValue(projectToml, 'api_key');
565
+
566
+ const effectiveBaseUrl = envBaseUrl || projectBaseUrl || globalBaseUrl;
567
+ const effectiveApiKey = envApiKey || projectAuth.OPENAI_API_KEY || globalAuth.OPENAI_API_KEY || projectApiKeyToml || globalApiKeyToml;
568
+ const baseUrlSource = envBaseUrl ? '环境变量' : (projectBaseUrl ? '项目配置' : (globalBaseUrl ? '全局配置' : null));
569
+ const apiKeySource = envApiKey ? '环境变量' : (projectAuth.OPENAI_API_KEY ? '项目auth.json' : (globalAuth.OPENAI_API_KEY ? '全局auth.json' : (projectApiKeyToml ? '项目config.toml' : (globalApiKeyToml ? '全局config.toml' : null))));
570
+
571
+ console.log(colorize('\n🔧 关键配置项 (按优先级合并后):', 'blue'));
572
+ printConfigItem('model_provider', effectiveBaseUrl ? (projectProvider || globalProvider) : null, baseUrlSource ? '配置文件' : null);
573
+ printConfigItem('base_url', effectiveBaseUrl, baseUrlSource);
574
+ printConfigItem('OPENAI_API_KEY', effectiveApiKey, apiKeySource);
575
+
576
+ console.log(colorize('\n📊 各级配置详情:', 'blue'));
577
+
578
+ console.log(colorize('\n [环境变量]', 'yellow'));
579
+ console.log(` OPENAI_BASE_URL: ${envBaseUrl ? colorize(envBaseUrl, 'green') : colorize('未设置', 'gray')}`);
580
+ console.log(` OPENAI_API_KEY: ${envApiKey ? colorize(maskSecret(envApiKey), 'green') : colorize('未设置', 'gray')}`);
581
+
582
+ if (!isHomeDir) {
583
+ console.log(colorize('\n [项目配置] .codex/config.toml', 'yellow'));
584
+ if (fileExists(projectConfigPath)) {
585
+ console.log(` model_provider: ${projectProvider ? colorize(projectProvider, 'green') : colorize('未设置', 'gray')}`);
586
+ console.log(` base_url: ${projectBaseUrl ? colorize(projectBaseUrl, 'green') : colorize('未设置', 'gray')}`);
587
+ console.log(` api_key: ${projectApiKeyToml ? colorize(maskSecret(projectApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
588
+ } else {
589
+ console.log(colorize(' (文件不存在)', 'gray'));
590
+ }
591
+
592
+ console.log(colorize('\n [项目配置] .codex/auth.json', 'yellow'));
593
+ if (fileExists(projectAuthPath)) {
594
+ console.log(` auth_mode: ${projectAuth.auth_mode ? colorize(projectAuth.auth_mode, 'green') : colorize('未设置', 'gray')}`);
595
+ console.log(` OPENAI_API_KEY: ${projectAuth.OPENAI_API_KEY ? colorize(maskSecret(projectAuth.OPENAI_API_KEY), 'green') : colorize('未设置', 'gray')}`);
596
+ } else {
597
+ console.log(colorize(' (文件不存在)', 'gray'));
598
+ }
599
+ }
600
+
601
+ console.log(colorize('\n [全局配置] ~/.codex/config.toml', 'yellow'));
602
+ if (fileExists(globalConfigPath)) {
603
+ console.log(` model_provider: ${globalProvider ? colorize(globalProvider, 'green') : colorize('未设置', 'gray')}`);
604
+ console.log(` base_url: ${globalBaseUrl ? colorize(globalBaseUrl, 'green') : colorize('未设置', 'gray')}`);
605
+ console.log(` api_key: ${globalApiKeyToml ? colorize(maskSecret(globalApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
606
+ } else {
607
+ console.log(colorize(' (文件不存在)', 'gray'));
608
+ }
609
+
610
+ console.log(colorize('\n [全局配置] ~/.codex/auth.json', 'yellow'));
611
+ if (fileExists(globalAuthPath)) {
612
+ console.log(` auth_mode: ${globalAuth.auth_mode ? colorize(globalAuth.auth_mode, 'green') : colorize('未设置', 'gray')}`);
613
+ console.log(` OPENAI_API_KEY: ${globalAuth.OPENAI_API_KEY ? colorize(maskSecret(globalAuth.OPENAI_API_KEY), 'green') : colorize('未设置', 'gray')}`);
614
+ } else {
615
+ console.log(colorize(' (文件不存在)', 'gray'));
616
+ }
617
+
618
+ console.log(colorize('\n🎯 think API 配置状态:', 'blue'));
619
+ if (effectiveBaseUrl === `${API_BASE_URL}/v1` && effectiveApiKey) {
620
+ console.log(colorize(' ✓ think API 已正确配置', 'green'));
621
+ } else {
622
+ if (effectiveBaseUrl !== `${API_BASE_URL}/v1`) {
623
+ console.log(colorize(` ✗ base_url 不是 think (当前: ${effectiveBaseUrl || '未设置'})`, 'red'));
624
+ }
625
+ if (!effectiveApiKey) {
626
+ console.log(colorize(' ✗ OPENAI_API_KEY 未设置', 'red'));
627
+ }
628
+ }
629
+ }
630
+
631
+ async function runCheck() {
632
+ console.log('╔════════════════════════════════════════╗');
633
+ console.log('║ think AI 配置检测工具 ║');
634
+ console.log('║ 检测配置优先级和覆盖情况 ║');
635
+ console.log('╚════════════════════════════════════════╝');
636
+ console.log(colorize(`\n当前工作目录: ${process.cwd()}`, 'gray'));
637
+
638
+ const tool = await select({
639
+ message: '请选择要检测的工具:',
640
+ choices: [
641
+ { name: '全部检测', value: 'all' },
642
+ { name: 'Claude Code', value: 'claude-code' },
643
+ { name: 'OpenCode', value: 'opencode' },
644
+ { name: 'OpenClaw', value: 'openclaw' },
645
+ { name: 'Codex', value: 'codex' }
646
+ ],
647
+ default: 'all'
648
+ });
649
+
650
+ switch (tool) {
651
+ case 'all':
652
+ checkClaudeCode();
653
+ checkOpenCode();
654
+ checkOpenClaw();
655
+ checkCodex();
656
+ break;
657
+ case 'claude-code':
658
+ checkClaudeCode();
659
+ break;
660
+ case 'opencode':
661
+ checkOpenCode();
662
+ break;
663
+ case 'openclaw':
664
+ checkOpenClaw();
665
+ break;
666
+ case 'codex':
667
+ checkCodex();
668
+ break;
669
+ }
670
+
671
+ console.log(colorize('\n' + '━'.repeat(50), 'cyan'));
672
+ console.log(colorize('\n📖 配置优先级说明:', 'blue'));
673
+ console.log(colorize(' Claude Code: 环境变量 > 项目本地配置 > 项目配置 > 全局配置', 'gray'));
674
+ console.log(colorize(' OpenCode: 环境变量 > 项目配置 > 全局配置', 'gray'));
675
+ console.log(colorize(' OpenClaw: 全局配置 (暂无项目级配置支持)', 'gray'));
676
+ console.log(colorize(' Codex: 环境变量 > 项目配置(.codex/) > 全局配置(~/.codex/)', 'gray'));
677
+ console.log();
678
+ }
679
+
680
+ async function main() {
681
+ console.log('╔════════════════════════════════════════╗');
682
+ console.log('║ think AI 配置工具 ║');
683
+ console.log('║ API: https://www.thinkai.tv ║');
684
+ console.log('╚════════════════════════════════════════╝\n');
685
+
686
+ const tool = await select({
687
+ message: '请选择要配置的工具 (上下箭头选择):',
688
+ choices: [
689
+ { name: 'Claude Code', value: 'claude-code' },
690
+ { name: 'OpenCode', value: 'opencode' },
691
+ { name: 'OpenClaw', value: 'openclaw' },
692
+ { name: 'Codex', value: 'codex' }
693
+ ],
694
+ default: 'claude-code'
695
+ });
696
+
697
+ const apiKey = await password({
698
+ message: '请输入/粘贴您的 API 令牌:',
699
+ mask: '*',
700
+ validate: (input) => {
701
+ if (!input || input.trim().length === 0) {
702
+ return '令牌不能为空';
703
+ }
704
+ return true;
705
+ }
706
+ });
707
+
708
+ const trimmedKey = apiKey.trim();
709
+
710
+ switch (tool) {
711
+ case 'claude-code':
712
+ configureClaudeCode(trimmedKey);
713
+ break;
714
+ case 'opencode':
715
+ configureOpenCode(trimmedKey);
716
+ break;
717
+ case 'openclaw':
718
+ configureOpenClaw(trimmedKey);
719
+ break;
720
+ case 'codex':
721
+ configureCodex(trimmedKey);
722
+ break;
723
+ }
724
+ }
725
+
726
+ const args = process.argv.slice(2);
727
+
728
+ if (args.includes('check') || args.includes('--check') || args.includes('-c')) {
729
+ runCheck().catch((err) => {
730
+ if (err.name === 'ExitPromptError') {
731
+ console.log('\n已取消。');
732
+ } else {
733
+ console.error(err);
734
+ }
735
+ });
736
+ } else {
737
+ main().catch((err) => {
738
+ if (err.name === 'ExitPromptError') {
739
+ console.log('\n已取消配置。');
740
+ } else {
741
+ console.error(err);
742
+ }
743
+ });
744
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "think-ai-auto",
3
+ "version": "1.8.0",
4
+ "description": "一键配置 Claude Code / OpenCode / OpenClaw 的 API 密钥工具",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "think-ai-auto": "./bin/index.js"
9
+ },
10
+ "files": [
11
+ "bin/index.js",
12
+ "README.md",
13
+ "教程.txt"
14
+ ],
15
+ "scripts": {
16
+ "start": "node bin/index.js"
17
+ },
18
+ "keywords": [
19
+ "claude",
20
+ "opencode",
21
+ "openclaw",
22
+ "api",
23
+ "config",
24
+ "think"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@inquirer/prompts": "^8.2.0"
33
+ }
34
+ }
@@ -0,0 +1,117 @@
1
+ 接入Claude Code
2
+ 首先:初始化Claude Code
3
+ 如果之前未使用过,需要进行初始化,否则会强制要求登录账号。
4
+ 打开文件:.claude.json
5
+ ●macOS / Linux: ~/.claude.json
6
+ ●Windows: 用户目录\.claude.json
7
+ 如果不存在创建它,注意,点开头!
8
+ 如果有,请在文件的最后,}之前加入配置:
9
+ {
10
+ "hasCompletedOnboarding": true
11
+ }
12
+ 方法一:使用配置文件 (推荐)
13
+ Claude Code 支持通过 JSON 配置文件来持久化设置。这是最推荐的方式,因为它不需要每次都修改 Shell 配置,且支持项目级别的隔离。
14
+ 1. 配置文件位置
15
+ Claude Code 会按以下优先级读取配置(文件名通常为 settings.json):
16
+ ○macOS / Linux: ~/.claude/settings.json
17
+ ○Windows: 用户目录\.claude\settings.json
18
+ ○(例如 C:\Users\你的用户名\.claude\settings.json)
19
+ 2. 编辑配置内容
20
+ 请创建或编辑 settings.json 文件,填入以下内容。重点是利用 env 字段注入环境变量:
21
+ JSON
22
+ {
23
+ "env": {
24
+ "ANTHROPIC_BASE_URL": "https://www.thinkai.tv",
25
+ "API_TIMEOUT_MS": 3000000,
26
+ "ANTHROPIC_AUTH_TOKEN": "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
27
+ }
28
+ }
29
+
30
+
31
+ 接入OpenCode
32
+ 接入方式为 配置文件 + API Key。
33
+ 一、配置文件位置
34
+ OpenCode 通过配置文件加载 Provider 配置,配置文件名为:
35
+ opencode.json
36
+ 路径说明
37
+ macOS / Linux
38
+ ~/.config/opencode/opencode.json
39
+ Windows
40
+ C:\Users<用户名>.config\opencode\opencode.json
41
+ 如路径不存在,请手动创建目录与文件。
42
+
43
+ 二、配置 API Provider
44
+ 在 opencode.json 中写入以下内容,将 OpenCode 请求指向你的 API 地址。
45
+ 示例配置
46
+
47
+ {
48
+ "$schema": "https://opencode.ai/config.json",
49
+ "provider": {
50
+ "anthropic": {
51
+ "options": {
52
+ "baseURL": "https://www.thinkai.tv/v1"
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ 三、配置 Anthropic API Key
59
+ 执行以下命令,在 OpenCode 中录入 Anthropic 的 API Key:
60
+ opencode auth login
61
+ 在交互界面中依次选择并输入:
62
+ ●Select provider → Anthropic
63
+ ●Login method → Manually enter API Key
64
+ ●Enter your API key → 输入你平台发放的 API Key
65
+ 然后,/model 选择claude-4-5xxx
66
+ 接入OpenClaw
67
+ OpenClaw 配置文件教程(仅配置文件)
68
+ 1) 配置文件位置
69
+ ●macOS / Linux:~/.openclaw/openclaw.json
70
+ ●Windows:C:\Users\<你的用户名>\.openclaw\openclaw.json(用户目录下的 .openclaw)
71
+ 说明:OpenClaw 配置文件为 JSON(支持注释、尾逗号)。
72
+
73
+ 2) 配置文件示例(自定义 Provider + 设置默认模型)
74
+ 创建或编辑 openclaw.json,填入以下内容(将 apiKey 替换为你的密钥):
75
+ {
76
+ //...
77
+ "models": {
78
+ "providers": {
79
+ "think-Anthropic": {
80
+ "baseUrl": "https://www.thinkai.tv",
81
+ "apiKey": "${你的apikey}",
82
+ "api": "anthropic-messages",
83
+ "models": [
84
+ { "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 },
85
+ { "id": "claude-opus-4-6", "name": "Claude Opus 4.6", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 }
86
+ ]
87
+ },
88
+ "think-Openai": {
89
+ "baseUrl": "https://www.thinkai.tv/v1",
90
+ "apiKey": "${你的apikey}",
91
+ "api": "openai-completions",
92
+ "models": [
93
+ { "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 },
94
+ { "id": "claude-opus-4-6", "name": "Claude Opus 4.6", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 },
95
+ { "id": "gpt-5.2", "name": "GPT-5.2", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 },
96
+ { "id": "gpt-5.3-codex", "name": "GPT-5.3-codex", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 },
97
+ { "id": "gpt-5.4", "name": "GPT-5.4", "reasoning": false, "input": ["text", "image"], "contextWindow": 200000, "maxTokens": 8192 }
98
+ ]
99
+ }
100
+ }
101
+ },
102
+ "agents": {
103
+ "defaults": {
104
+ "model": {
105
+ "primary": "think-Anthropic/claude-sonnet-4-6"
106
+ }
107
+ }
108
+ },
109
+ //...
110
+ }
111
+
112
+ 使用提示:
113
+ /models think-Openai 查看 OpenAI 兼容模型
114
+ /models think-Anthropic 查看 Anthropic 模型
115
+ /model think-Anthropic/xxx 切换模型
116
+
117
+