ltcraft-ai-auto 1.9.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.
Files changed (2) hide show
  1. package/bin/index.js +193 -25
  2. package/package.json +2 -2
package/bin/index.js CHANGED
@@ -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) {
@@ -685,37 +835,55 @@ async function main() {
685
835
  { name: 'Claude Code', value: 'claude-code' },
686
836
  { name: 'OpenCode', value: 'opencode' },
687
837
  { name: 'OpenClaw', value: 'openclaw' },
838
+ { name: 'Hermes', value: 'hermes' },
688
839
  { name: 'Codex', value: 'codex' }
689
840
  ],
690
841
  default: 'claude-code'
691
842
  });
692
843
 
693
- const apiKey = await password({
694
- message: '请输入/粘贴您的 API 令牌:',
695
- mask: '*',
696
- validate: (input) => {
697
- if (!input || input.trim().length === 0) {
698
- 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;
699
853
  }
700
- return true;
701
- }
702
- });
703
-
704
- const trimmedKey = apiKey.trim();
854
+ });
855
+ return value.trim();
856
+ };
705
857
 
706
858
  switch (tool) {
707
- case 'claude-code':
708
- configureClaudeCode(trimmedKey);
859
+ case 'claude-code': {
860
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
861
+ configureClaudeCode(anthropicKey);
709
862
  break;
710
- case 'opencode':
711
- configureOpenCode(trimmedKey);
863
+ }
864
+ case 'codex': {
865
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
866
+ configureCodex(openaiKey);
712
867
  break;
713
- case 'openclaw':
714
- configureOpenClaw(trimmedKey);
868
+ }
869
+ case 'opencode': {
870
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
871
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
872
+ configureOpenCode(anthropicKey, openaiKey);
715
873
  break;
716
- case 'codex':
717
- configureCodex(trimmedKey);
874
+ }
875
+ case 'openclaw': {
876
+ const anthropicKey = await askKey('请输入/粘贴您的 Anthropic API 令牌:');
877
+ const openaiKey = await askKey('请输入/粘贴您的 OpenAI API 令牌:');
878
+ configureOpenClaw(anthropicKey, openaiKey);
718
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
+ }
719
887
  }
720
888
  }
721
889
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ltcraft-ai-auto",
3
- "version": "1.9.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": {