ltcraft-ai-auto 1.5.3 → 1.6.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.
Files changed (2) hide show
  1. package/bin/index.js +164 -2
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -99,6 +99,51 @@ function configureClaudeCode(apiKey) {
99
99
  console.log('\n🎉 Claude Code 配置完成!');
100
100
  }
101
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
+
110
+ // 更新或插入顶层 model_provider
111
+ if (/^model_provider\s*=/m.test(content)) {
112
+ content = content.replace(/^model_provider\s*=.*/m, `model_provider = "ltcraftai"`);
113
+ } else {
114
+ content = `model_provider = "ltcraftai"\n` + content;
115
+ }
116
+
117
+ // 移除已有的 [model_providers.ltcraftai] 节(到下一个节或文件末尾)
118
+ content = content.replace(/\[model_providers\.ltcraftai\][\s\S]*?(?=\n\[|\s*$)/, '');
119
+
120
+ // 追加新节
121
+ content = content.trimEnd() + `\n\n[model_providers.ltcraftai]\nname = "${name}"\nbase_url = "${baseUrl}"\napi_key = "${apiKey}"\n`;
122
+ return content;
123
+ }
124
+
125
+ function configureCodex(apiKey) {
126
+ const homeDir = getHomeDir();
127
+ const codexDir = path.join(homeDir, '.codex');
128
+ const configPath = path.join(codexDir, 'config.toml');
129
+ const authPath = path.join(codexDir, 'auth.json');
130
+
131
+ backupFile(configPath);
132
+ backupFile(authPath);
133
+
134
+ ensureDir(codexDir);
135
+
136
+ const existingToml = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
137
+ const merged = mergeTomlConfig(existingToml, 'LTCraftAI', `${API_BASE_URL}/v1`, apiKey);
138
+ fs.writeFileSync(configPath, merged, 'utf-8');
139
+ console.log(`✓ 已配置: ${configPath}`);
140
+
141
+ const existingAuth = readJsonFile(authPath);
142
+ writeJsonFile(authPath, { ...existingAuth, auth_mode: 'apikey', OPENAI_API_KEY: apiKey });
143
+ console.log(`✓ 已配置: ${authPath}`);
144
+ console.log('\n🎉 Codex 配置完成!');
145
+ }
146
+
102
147
  function configureOpenCode(apiKey) {
103
148
  const homeDir = getHomeDir();
104
149
  const configPath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
@@ -433,6 +478,113 @@ function checkOpenClaw() {
433
478
  }
434
479
  }
435
480
 
481
+ function checkCodex() {
482
+ printSection('Codex 配置检测');
483
+ const homeDir = getHomeDir();
484
+ const cwd = process.cwd();
485
+ const isHomeDir = path.resolve(cwd) === path.resolve(homeDir);
486
+
487
+ if (isHomeDir) {
488
+ console.log(colorize('\n⚠️ 当前工作目录为用户主目录,跳过项目级配置检测(与全局配置路径相同)', 'yellow'));
489
+ }
490
+
491
+ const globalConfigPath = path.join(homeDir, '.codex', 'config.toml');
492
+ const globalAuthPath = path.join(homeDir, '.codex', 'auth.json');
493
+ const projectConfigPath = path.join(cwd, '.codex', 'config.toml');
494
+ const projectAuthPath = path.join(cwd, '.codex', 'auth.json');
495
+
496
+ console.log(colorize('\n📁 配置文件状态:', 'blue'));
497
+ printFileStatus('全局 config.toml', globalConfigPath, fileExists(globalConfigPath));
498
+ printFileStatus('全局 auth.json', globalAuthPath, fileExists(globalAuthPath));
499
+ if (!isHomeDir) {
500
+ printFileStatus('项目 config.toml', projectConfigPath, fileExists(projectConfigPath));
501
+ printFileStatus('项目 auth.json', projectAuthPath, fileExists(projectAuthPath));
502
+ }
503
+
504
+ const readToml = (p) => fileExists(p) ? fs.readFileSync(p, 'utf-8') : '';
505
+ const readAuth = (p) => readJsonFile(p);
506
+
507
+ const globalToml = readToml(globalConfigPath);
508
+ const projectToml = isHomeDir ? '' : readToml(projectConfigPath);
509
+ const globalAuth = readAuth(globalAuthPath);
510
+ const projectAuth = isHomeDir ? {} : readAuth(projectAuthPath);
511
+
512
+ const envBaseUrl = process.env.OPENAI_BASE_URL;
513
+ const envApiKey = process.env.OPENAI_API_KEY;
514
+
515
+ // 从 TOML 中提取值
516
+ const globalProvider = readTomlValue(globalToml, 'model_provider');
517
+ const globalBaseUrl = readTomlValue(globalToml, 'base_url');
518
+ const globalApiKeyToml = readTomlValue(globalToml, 'api_key');
519
+ const projectProvider = readTomlValue(projectToml, 'model_provider');
520
+ const projectBaseUrl = readTomlValue(projectToml, 'base_url');
521
+ const projectApiKeyToml = readTomlValue(projectToml, 'api_key');
522
+
523
+ const effectiveBaseUrl = envBaseUrl || projectBaseUrl || globalBaseUrl;
524
+ const effectiveApiKey = envApiKey || projectAuth.OPENAI_API_KEY || globalAuth.OPENAI_API_KEY || projectApiKeyToml || globalApiKeyToml;
525
+ const baseUrlSource = envBaseUrl ? '环境变量' : (projectBaseUrl ? '项目配置' : (globalBaseUrl ? '全局配置' : null));
526
+ const apiKeySource = envApiKey ? '环境变量' : (projectAuth.OPENAI_API_KEY ? '项目auth.json' : (globalAuth.OPENAI_API_KEY ? '全局auth.json' : (projectApiKeyToml ? '项目config.toml' : (globalApiKeyToml ? '全局config.toml' : null))));
527
+
528
+ console.log(colorize('\n🔧 关键配置项 (按优先级合并后):', 'blue'));
529
+ printConfigItem('model_provider', effectiveBaseUrl ? (projectProvider || globalProvider) : null, baseUrlSource ? '配置文件' : null);
530
+ printConfigItem('base_url', effectiveBaseUrl, baseUrlSource);
531
+ printConfigItem('OPENAI_API_KEY', effectiveApiKey, apiKeySource);
532
+
533
+ console.log(colorize('\n📊 各级配置详情:', 'blue'));
534
+
535
+ console.log(colorize('\n [环境变量]', 'yellow'));
536
+ console.log(` OPENAI_BASE_URL: ${envBaseUrl ? colorize(envBaseUrl, 'green') : colorize('未设置', 'gray')}`);
537
+ console.log(` OPENAI_API_KEY: ${envApiKey ? colorize(maskSecret(envApiKey), 'green') : colorize('未设置', 'gray')}`);
538
+
539
+ if (!isHomeDir) {
540
+ console.log(colorize('\n [项目配置] .codex/config.toml', 'yellow'));
541
+ if (fileExists(projectConfigPath)) {
542
+ console.log(` model_provider: ${projectProvider ? colorize(projectProvider, 'green') : colorize('未设置', 'gray')}`);
543
+ console.log(` base_url: ${projectBaseUrl ? colorize(projectBaseUrl, 'green') : colorize('未设置', 'gray')}`);
544
+ console.log(` api_key: ${projectApiKeyToml ? colorize(maskSecret(projectApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
545
+ } else {
546
+ console.log(colorize(' (文件不存在)', 'gray'));
547
+ }
548
+
549
+ console.log(colorize('\n [项目配置] .codex/auth.json', 'yellow'));
550
+ if (fileExists(projectAuthPath)) {
551
+ console.log(` auth_mode: ${projectAuth.auth_mode ? colorize(projectAuth.auth_mode, 'green') : colorize('未设置', 'gray')}`);
552
+ console.log(` OPENAI_API_KEY: ${projectAuth.OPENAI_API_KEY ? colorize(maskSecret(projectAuth.OPENAI_API_KEY), 'green') : colorize('未设置', 'gray')}`);
553
+ } else {
554
+ console.log(colorize(' (文件不存在)', 'gray'));
555
+ }
556
+ }
557
+
558
+ console.log(colorize('\n [全局配置] ~/.codex/config.toml', 'yellow'));
559
+ if (fileExists(globalConfigPath)) {
560
+ console.log(` model_provider: ${globalProvider ? colorize(globalProvider, 'green') : colorize('未设置', 'gray')}`);
561
+ console.log(` base_url: ${globalBaseUrl ? colorize(globalBaseUrl, 'green') : colorize('未设置', 'gray')}`);
562
+ console.log(` api_key: ${globalApiKeyToml ? colorize(maskSecret(globalApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
563
+ } else {
564
+ console.log(colorize(' (文件不存在)', 'gray'));
565
+ }
566
+
567
+ console.log(colorize('\n [全局配置] ~/.codex/auth.json', 'yellow'));
568
+ if (fileExists(globalAuthPath)) {
569
+ console.log(` auth_mode: ${globalAuth.auth_mode ? colorize(globalAuth.auth_mode, 'green') : colorize('未设置', 'gray')}`);
570
+ console.log(` OPENAI_API_KEY: ${globalAuth.OPENAI_API_KEY ? colorize(maskSecret(globalAuth.OPENAI_API_KEY), 'green') : colorize('未设置', 'gray')}`);
571
+ } else {
572
+ console.log(colorize(' (文件不存在)', 'gray'));
573
+ }
574
+
575
+ console.log(colorize('\n🎯 LTCraft API 配置状态:', 'blue'));
576
+ if (effectiveBaseUrl === `${API_BASE_URL}/v1` && effectiveApiKey) {
577
+ console.log(colorize(' ✓ LTCraft API 已正确配置', 'green'));
578
+ } else {
579
+ if (effectiveBaseUrl !== `${API_BASE_URL}/v1`) {
580
+ console.log(colorize(` ✗ base_url 不是 LTCraft (当前: ${effectiveBaseUrl || '未设置'})`, 'red'));
581
+ }
582
+ if (!effectiveApiKey) {
583
+ console.log(colorize(' ✗ OPENAI_API_KEY 未设置', 'red'));
584
+ }
585
+ }
586
+ }
587
+
436
588
  async function runCheck() {
437
589
  console.log('╔════════════════════════════════════════╗');
438
590
  console.log('║ LTCraft AI 配置检测工具 ║');
@@ -446,7 +598,8 @@ async function runCheck() {
446
598
  { name: '全部检测', value: 'all' },
447
599
  { name: 'Claude Code', value: 'claude-code' },
448
600
  { name: 'OpenCode', value: 'opencode' },
449
- { name: 'OpenClaw', value: 'openclaw' }
601
+ { name: 'OpenClaw', value: 'openclaw' },
602
+ { name: 'Codex', value: 'codex' }
450
603
  ],
451
604
  default: 'all'
452
605
  });
@@ -456,6 +609,7 @@ async function runCheck() {
456
609
  checkClaudeCode();
457
610
  checkOpenCode();
458
611
  checkOpenClaw();
612
+ checkCodex();
459
613
  break;
460
614
  case 'claude-code':
461
615
  checkClaudeCode();
@@ -466,6 +620,9 @@ async function runCheck() {
466
620
  case 'openclaw':
467
621
  checkOpenClaw();
468
622
  break;
623
+ case 'codex':
624
+ checkCodex();
625
+ break;
469
626
  }
470
627
 
471
628
  console.log(colorize('\n' + '━'.repeat(50), 'cyan'));
@@ -473,6 +630,7 @@ async function runCheck() {
473
630
  console.log(colorize(' Claude Code: 环境变量 > 项目本地配置 > 项目配置 > 全局配置', 'gray'));
474
631
  console.log(colorize(' OpenCode: 环境变量 > 项目配置 > 全局配置', 'gray'));
475
632
  console.log(colorize(' OpenClaw: 全局配置 (暂无项目级配置支持)', 'gray'));
633
+ console.log(colorize(' Codex: 环境变量 > 项目配置(.codex/) > 全局配置(~/.codex/)', 'gray'));
476
634
  console.log();
477
635
  }
478
636
 
@@ -487,7 +645,8 @@ async function main() {
487
645
  choices: [
488
646
  { name: 'Claude Code', value: 'claude-code' },
489
647
  { name: 'OpenCode', value: 'opencode' },
490
- { name: 'OpenClaw', value: 'openclaw' }
648
+ { name: 'OpenClaw', value: 'openclaw' },
649
+ { name: 'Codex', value: 'codex' }
491
650
  ],
492
651
  default: 'claude-code'
493
652
  });
@@ -515,6 +674,9 @@ async function main() {
515
674
  case 'openclaw':
516
675
  configureOpenClaw(trimmedKey);
517
676
  break;
677
+ case 'codex':
678
+ configureCodex(trimmedKey);
679
+ break;
518
680
  }
519
681
  }
520
682
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ltcraft-ai-auto",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "description": "一键配置 Claude Code / OpenCode / OpenClaw 的 API 密钥工具",
5
5
  "type": "module",
6
6
  "main": "index.js",