ltcraft-ai-auto 1.8.0 → 1.11.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 CHANGED
@@ -36,4 +36,4 @@ ltcraft-ai-auto
36
36
 
37
37
  ## API 地址
38
38
 
39
- 所有工具统一使用: `https://ai.ltcraft.cn`
39
+ 所有工具统一使用: `https://ai.lt4net.org`
package/bin/index.js CHANGED
@@ -5,7 +5,7 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import os from 'os';
7
7
 
8
- const API_BASE_URL = 'https://ai.ltcraft.cn';
8
+ const API_BASE_URL = 'https://ai.lt4net.org';
9
9
 
10
10
  const colors = {
11
11
  reset: '\x1b[0m',
@@ -104,9 +104,9 @@ function readTomlValue(content, key) {
104
104
  return match ? match[1] : null;
105
105
  }
106
106
 
107
- function mergeTomlConfig(existing, name, baseUrl, apiKey) {
107
+ function mergeTomlConfig(existing, name, baseUrl) {
108
108
  let content = existing || '';
109
- const newSection = `[model_providers.ltcraftai]\nname = "${name}"\nbase_url = "${baseUrl}"\napi_key = "${apiKey}"`;
109
+ const newSection = `[model_providers.ltcraftai]\nname = "${name}"\nbase_url = "${baseUrl}"`;
110
110
 
111
111
  // 更新或插入顶层 model_provider
112
112
  if (/^model_provider\s*=/m.test(content)) {
@@ -141,7 +141,7 @@ function configureCodex(apiKey) {
141
141
  ensureDir(codexDir);
142
142
 
143
143
  const existingToml = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
144
- const merged = mergeTomlConfig(existingToml, 'LTCraftAI', `${API_BASE_URL}/v1`, apiKey);
144
+ const merged = mergeTomlConfig(existingToml, 'LTCraftAI', `${API_BASE_URL}/v1`);
145
145
  fs.writeFileSync(configPath, merged, 'utf-8');
146
146
  console.log(`✓ 已配置: ${configPath}`);
147
147
 
@@ -151,7 +151,7 @@ function configureCodex(apiKey) {
151
151
  console.log('\n🎉 Codex 配置完成!');
152
152
  }
153
153
 
154
- function configureOpenCode(apiKey) {
154
+ function configureOpenCode(anthropicKey, openaiKey) {
155
155
  const homeDir = getHomeDir();
156
156
  const configPath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
157
157
  backupFile(configPath);
@@ -163,7 +163,7 @@ function configureOpenCode(apiKey) {
163
163
  "name": "LTCraft AI(Anthropic)",
164
164
  "options": {
165
165
  "baseURL": `${API_BASE_URL}/v1`,
166
- "apiKey": apiKey
166
+ "apiKey": anthropicKey
167
167
  }
168
168
  },
169
169
  "openai": {
@@ -171,7 +171,7 @@ function configureOpenCode(apiKey) {
171
171
  "name": "LTCraft AI(Openai)",
172
172
  "options": {
173
173
  "baseURL": `${API_BASE_URL}/v1`,
174
- "apiKey": apiKey
174
+ "apiKey": openaiKey
175
175
  }
176
176
  }
177
177
  }
@@ -183,7 +183,7 @@ function configureOpenCode(apiKey) {
183
183
  console.log('提示: 运行 opencode 后使用 /model 选择模型');
184
184
  }
185
185
 
186
- function configureOpenClaw(apiKey) {
186
+ function configureOpenClaw(anthropicKey, openaiKey) {
187
187
  const homeDir = getHomeDir();
188
188
  const configPath = path.join(homeDir, '.openclaw', 'openclaw.json');
189
189
  backupFile(configPath);
@@ -197,13 +197,13 @@ function configureOpenClaw(apiKey) {
197
197
  "providers": {
198
198
  "LTCraft-Anthropic": {
199
199
  "baseUrl": API_BASE_URL,
200
- "apiKey": apiKey,
200
+ "apiKey": anthropicKey,
201
201
  "api": "anthropic-messages",
202
202
  "models": modelClaude
203
203
  },
204
204
  "LTCraft-Openai": {
205
205
  "baseUrl": `${API_BASE_URL}/v1`,
206
- "apiKey": apiKey,
206
+ "apiKey": openaiKey,
207
207
  "api": "openai-completions",
208
208
  "models": [
209
209
  ...modelClaude,
@@ -231,6 +231,156 @@ function configureOpenClaw(apiKey) {
231
231
  console.log('\n⚠️ 配置后需要重启 gateway 才能生效: openclaw gateway restart');
232
232
  }
233
233
 
234
+ function escapeYamlSingleQuoted(value) {
235
+ return String(value).replace(/'/g, "''");
236
+ }
237
+
238
+ function buildHermesOpenaiBlock(indent, openaiKey) {
239
+ const i1 = indent;
240
+ const i2 = i1 + ' ';
241
+ const i3 = i2 + ' ';
242
+ const i4 = i3 + ' ';
243
+ return [
244
+ `${i1}LTCraft-Openai:`,
245
+ `${i2}api_mode: chat_completions`,
246
+ `${i2}base_url: ${API_BASE_URL}/v1`,
247
+ `${i2}api_key: '${escapeYamlSingleQuoted(openaiKey)}'`,
248
+ `${i2}models:`,
249
+ `${i3}gpt-5.5:`,
250
+ `${i4}context_length: 560000`,
251
+ `${i3}gpt-5.4:`,
252
+ `${i4}context_length: 560000`,
253
+ `${i3}gpt-5.4-mini:`,
254
+ `${i4}context_length: 128000`,
255
+ `${i3}gpt-5.3-codex:`,
256
+ `${i4}context_length: 256000`,
257
+ `${i3}gpt-5.2-codex:`,
258
+ `${i4}context_length: 256000`,
259
+ ];
260
+ }
261
+
262
+ function buildHermesAnthropicBlock(indent, anthropicKey) {
263
+ const i1 = indent;
264
+ const i2 = i1 + ' ';
265
+ const i3 = i2 + ' ';
266
+ const i4 = i3 + ' ';
267
+ return [
268
+ `${i1}LTCraft-Anthropic:`,
269
+ `${i2}api_mode: anthropic_messages`,
270
+ `${i2}base_url: ${API_BASE_URL}/`,
271
+ `${i2}api_key: '${escapeYamlSingleQuoted(anthropicKey)}'`,
272
+ `${i2}models:`,
273
+ `${i3}claude-sonnet-4-5:`,
274
+ `${i4}context_length: 180000`,
275
+ `${i3}claude-sonnet-4-6:`,
276
+ `${i4}context_length: 960000`,
277
+ `${i3}claude-opus-4-6:`,
278
+ `${i4}context_length: 960000`,
279
+ `${i3}claude-opus-4-7:`,
280
+ `${i4}context_length: 960000`,
281
+ ];
282
+ }
283
+
284
+ // 在 providers 顶层映射下 upsert 一个子节点;保留其他 provider 与顶层字段不变
285
+ function upsertHermesProvider(content, providerName, getBlockLines) {
286
+ const lines = content.split('\n');
287
+
288
+ // 找 providers: 顶级(无缩进)行
289
+ const providersIdx = lines.findIndex(l => /^providers\s*:\s*$/.test(l));
290
+
291
+ if (providersIdx === -1) {
292
+ // 没有 providers 块,追加一个
293
+ const newLines = [`providers:`, ...getBlockLines(' ')];
294
+ let result = content;
295
+ if (result.length > 0 && !result.endsWith('\n')) result += '\n';
296
+ result += newLines.join('\n') + '\n';
297
+ return result;
298
+ }
299
+
300
+ // 计算 providers 块结束(下一个顶级非空行)
301
+ let providersEnd = lines.length;
302
+ for (let i = providersIdx + 1; i < lines.length; i++) {
303
+ const l = lines[i];
304
+ if (l.length === 0) continue;
305
+ if (/^\s/.test(l)) continue;
306
+ providersEnd = i;
307
+ break;
308
+ }
309
+
310
+ // 推导 provider 子块的缩进(取 providers 块内第一个有缩进的行)
311
+ let indent = ' ';
312
+ for (let i = providersIdx + 1; i < providersEnd; i++) {
313
+ const m = lines[i].match(/^(\s+)\S/);
314
+ if (m) { indent = m[1]; break; }
315
+ }
316
+
317
+ const block = getBlockLines(indent);
318
+
319
+ // 在 providers 块内找指定子 provider
320
+ const headerRe = new RegExp(`^${indent}${providerName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:`);
321
+ let providerStart = -1;
322
+ for (let i = providersIdx + 1; i < providersEnd; i++) {
323
+ if (headerRe.test(lines[i])) { providerStart = i; break; }
324
+ }
325
+
326
+ if (providerStart === -1) {
327
+ // 追加到 providers 块末尾(在 providersEnd 之前;跳过尾部空行)
328
+ let insertAt = providersEnd;
329
+ while (insertAt > providersIdx + 1 && lines[insertAt - 1].trim() === '') insertAt--;
330
+ lines.splice(insertAt, 0, ...block);
331
+ return lines.join('\n');
332
+ }
333
+
334
+ // 计算该子块结束:下一个缩进 <= indent 的非空行
335
+ let providerEnd = providersEnd;
336
+ for (let i = providerStart + 1; i < providersEnd; i++) {
337
+ const l = lines[i];
338
+ if (l.length === 0) continue;
339
+ const m = l.match(/^(\s*)/);
340
+ if (m[1].length <= indent.length && /\S/.test(l)) {
341
+ providerEnd = i;
342
+ break;
343
+ }
344
+ }
345
+
346
+ lines.splice(providerStart, providerEnd - providerStart, ...block);
347
+ return lines.join('\n');
348
+ }
349
+
350
+ function configureHermes(anthropicKey, openaiKey) {
351
+ const homeDir = getHomeDir();
352
+ const configPath = path.join(homeDir, '.hermes', 'config.yaml');
353
+ backupFile(configPath);
354
+ ensureDir(path.dirname(configPath));
355
+
356
+ let content;
357
+ if (fs.existsSync(configPath)) {
358
+ content = fs.readFileSync(configPath, 'utf-8');
359
+ content = upsertHermesProvider(content, 'LTCraft-Openai', (indent) => buildHermesOpenaiBlock(indent, openaiKey));
360
+ content = upsertHermesProvider(content, 'LTCraft-Anthropic', (indent) => buildHermesAnthropicBlock(indent, anthropicKey));
361
+ if (!content.endsWith('\n')) content += '\n';
362
+ } else {
363
+ // 新建:写入完整模板(含 model 默认)
364
+ const lines = [
365
+ 'model:',
366
+ ' default: claude-sonnet-4-6',
367
+ ' provider: custom:ltcraft-anthropic',
368
+ 'providers:',
369
+ ...buildHermesOpenaiBlock(' ', openaiKey),
370
+ ...buildHermesAnthropicBlock(' ', anthropicKey),
371
+ ];
372
+ content = lines.join('\n') + '\n';
373
+ }
374
+
375
+ fs.writeFileSync(configPath, content, 'utf-8');
376
+ console.log(`✓ 已配置: ${configPath}`);
377
+ console.log('\n🎉 Hermes 配置完成!');
378
+ console.log('\n📖 切换模型命令:');
379
+ console.log(' /model 模型ID --provider LTCraft-Anthropic/Openai --global');
380
+ console.log(' 示例: /model claude-sonnet-4-6 --provider LTCraft-Anthropic --global');
381
+ console.log('\n⚠️ 配置后需要重启 gateway 才能生效: hermes gateway restart');
382
+ }
383
+
234
384
  // ==================== 配置检测功能 ====================
235
385
 
236
386
  function maskSecret(value) {
@@ -558,15 +708,13 @@ function checkCodex() {
558
708
  // 从 TOML 中提取值
559
709
  const globalProvider = readTomlValue(globalToml, 'model_provider');
560
710
  const globalBaseUrl = readTomlValue(globalToml, 'base_url');
561
- const globalApiKeyToml = readTomlValue(globalToml, 'api_key');
562
711
  const projectProvider = readTomlValue(projectToml, 'model_provider');
563
712
  const projectBaseUrl = readTomlValue(projectToml, 'base_url');
564
- const projectApiKeyToml = readTomlValue(projectToml, 'api_key');
565
713
 
566
714
  const effectiveBaseUrl = envBaseUrl || projectBaseUrl || globalBaseUrl;
567
- const effectiveApiKey = envApiKey || projectAuth.OPENAI_API_KEY || globalAuth.OPENAI_API_KEY || projectApiKeyToml || globalApiKeyToml;
715
+ const effectiveApiKey = envApiKey || projectAuth.OPENAI_API_KEY || globalAuth.OPENAI_API_KEY;
568
716
  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))));
717
+ const apiKeySource = envApiKey ? '环境变量' : (projectAuth.OPENAI_API_KEY ? '项目auth.json' : (globalAuth.OPENAI_API_KEY ? '全局auth.json' : null));
570
718
 
571
719
  console.log(colorize('\n🔧 关键配置项 (按优先级合并后):', 'blue'));
572
720
  printConfigItem('model_provider', effectiveBaseUrl ? (projectProvider || globalProvider) : null, baseUrlSource ? '配置文件' : null);
@@ -584,7 +732,6 @@ function checkCodex() {
584
732
  if (fileExists(projectConfigPath)) {
585
733
  console.log(` model_provider: ${projectProvider ? colorize(projectProvider, 'green') : colorize('未设置', 'gray')}`);
586
734
  console.log(` base_url: ${projectBaseUrl ? colorize(projectBaseUrl, 'green') : colorize('未设置', 'gray')}`);
587
- console.log(` api_key: ${projectApiKeyToml ? colorize(maskSecret(projectApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
588
735
  } else {
589
736
  console.log(colorize(' (文件不存在)', 'gray'));
590
737
  }
@@ -602,7 +749,6 @@ function checkCodex() {
602
749
  if (fileExists(globalConfigPath)) {
603
750
  console.log(` model_provider: ${globalProvider ? colorize(globalProvider, 'green') : colorize('未设置', 'gray')}`);
604
751
  console.log(` base_url: ${globalBaseUrl ? colorize(globalBaseUrl, 'green') : colorize('未设置', 'gray')}`);
605
- console.log(` api_key: ${globalApiKeyToml ? colorize(maskSecret(globalApiKeyToml), 'green') : colorize('未设置', 'gray')}`);
606
752
  } else {
607
753
  console.log(colorize(' (文件不存在)', 'gray'));
608
754
  }
@@ -680,7 +826,7 @@ async function runCheck() {
680
826
  async function main() {
681
827
  console.log('╔════════════════════════════════════════╗');
682
828
  console.log('║ LTCraft AI 配置工具 ║');
683
- console.log('║ API: https://ai.ltcraft.cn ║');
829
+ console.log('║ API: https://ai.lt4net.org ║');
684
830
  console.log('╚════════════════════════════════════════╝\n');
685
831
 
686
832
  const tool = await select({
@@ -689,37 +835,55 @@ async function main() {
689
835
  { name: 'Claude Code', value: 'claude-code' },
690
836
  { name: 'OpenCode', value: 'opencode' },
691
837
  { name: 'OpenClaw', value: 'openclaw' },
838
+ { name: 'Hermes', value: 'hermes' },
692
839
  { name: 'Codex', value: 'codex' }
693
840
  ],
694
841
  default: 'claude-code'
695
842
  });
696
843
 
697
- const apiKey = await password({
698
- message: '请输入/粘贴您的 API 令牌:',
699
- mask: '*',
700
- validate: (input) => {
701
- if (!input || input.trim().length === 0) {
702
- return '令牌不能为空';
844
+ const askKey = async (message) => {
845
+ const value = await password({
846
+ message,
847
+ mask: '*',
848
+ validate: (input) => {
849
+ if (!input || input.trim().length === 0) {
850
+ return '令牌不能为空';
851
+ }
852
+ return true;
703
853
  }
704
- return true;
705
- }
706
- });
707
-
708
- const trimmedKey = apiKey.trim();
854
+ });
855
+ return value.trim();
856
+ };
709
857
 
710
858
  switch (tool) {
711
- case 'claude-code':
712
- configureClaudeCode(trimmedKey);
859
+ case 'claude-code': {
860
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
861
+ configureClaudeCode(anthropicKey);
713
862
  break;
714
- case 'opencode':
715
- configureOpenCode(trimmedKey);
863
+ }
864
+ case 'codex': {
865
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
866
+ configureCodex(openaiKey);
716
867
  break;
717
- case 'openclaw':
718
- configureOpenClaw(trimmedKey);
868
+ }
869
+ case 'opencode': {
870
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
871
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
872
+ configureOpenCode(anthropicKey, openaiKey);
719
873
  break;
720
- case 'codex':
721
- configureCodex(trimmedKey);
874
+ }
875
+ case 'openclaw': {
876
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
877
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
878
+ configureOpenClaw(anthropicKey, openaiKey);
722
879
  break;
880
+ }
881
+ case 'hermes': {
882
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
883
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
884
+ configureHermes(anthropicKey, openaiKey);
885
+ break;
886
+ }
723
887
  }
724
888
  }
725
889
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ltcraft-ai-auto",
3
- "version": "1.8.0",
4
- "description": "一键配置 Claude Code / OpenCode / OpenClaw 的 API 密钥工具",
3
+ "version": "1.11.0",
4
+ "description": "一键配置 Claude Code / OpenCode / OpenClaw / Hermes / Codex 的 API 密钥工具",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -21,7 +21,7 @@ Claude Code 会按以下优先级读取配置(文件名通常为 settings.json
21
21
  JSON
22
22
  {
23
23
  "env": {
24
- "ANTHROPIC_BASE_URL": "https://ai.ltcraft.cn",
24
+ "ANTHROPIC_BASE_URL": "https://ai.lt4net.org",
25
25
  "API_TIMEOUT_MS": 3000000,
26
26
  "ANTHROPIC_AUTH_TOKEN": "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
27
27
  }
@@ -49,7 +49,7 @@ C:\Users<用户名>.config\opencode\opencode.json
49
49
  "provider": {
50
50
  "anthropic": {
51
51
  "options": {
52
- "baseURL": "https://ai.ltcraft.cn/v1"
52
+ "baseURL": "https://ai.lt4net.org/v1"
53
53
  }
54
54
  }
55
55
  }
@@ -77,7 +77,7 @@ OpenClaw 配置文件教程(仅配置文件)
77
77
  "models": {
78
78
  "providers": {
79
79
  "LTCraft-Anthropic": {
80
- "baseUrl": "https://ai.ltcraft.cn",
80
+ "baseUrl": "https://ai.lt4net.org",
81
81
  "apiKey": "${你的apikey}",
82
82
  "api": "anthropic-messages",
83
83
  "models": [
@@ -86,7 +86,7 @@ OpenClaw 配置文件教程(仅配置文件)
86
86
  ]
87
87
  },
88
88
  "LTCraft-Openai": {
89
- "baseUrl": "https://ai.ltcraft.cn/v1",
89
+ "baseUrl": "https://ai.lt4net.org/v1",
90
90
  "apiKey": "${你的apikey}",
91
91
  "api": "openai-completions",
92
92
  "models": [