@kikkimo/claude-launcher 2.5.0 → 3.0.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.
@@ -14,7 +14,7 @@ module.exports = {
14
14
  launch_api: "使用第三方API啟動 Claude Code",
15
15
  launch_api_skip: "使用第三方API啟動 Claude Code(自動跳過權限詢問)",
16
16
  api_management: "第三方API管理",
17
- language_settings: "語言設定",
17
+ config_management: "設定管理",
18
18
  version_check: "版本更新檢查",
19
19
  exit: "退出"
20
20
  },
@@ -22,11 +22,26 @@ module.exports = {
22
22
  title: "第三方API管理",
23
23
  add_new: "新增第三方API",
24
24
  remove: "刪除API",
25
+ edit: "Edit API",
25
26
  switch: "切換活躍的API",
26
27
  statistics: "查看API統計",
27
28
  export: "匯出設定",
28
29
  import: "匯入設定",
29
30
  change_password: "修改密碼",
31
+ manual_upgrade: "手動升級模型",
32
+ back: "返回主選單"
33
+ },
34
+ config: {
35
+ title: "設定管理",
36
+ language: "語言設定",
37
+ auto_model_upgrade: "模型自動升級",
38
+ model_upgrade_notification: "模型升級提示",
39
+ telemetry: "Anthropic 遙測資料",
40
+ api_launch_mode: "第三方API啟動模式",
41
+ back: "返回主選單"
42
+ },
43
+ api_select: {
44
+ title: "選擇要啟動的API:",
30
45
  back: "返回主選單"
31
46
  },
32
47
  remove_api: {
@@ -204,6 +219,23 @@ module.exports = {
204
219
  remove_confirm: "要刪除的API:{0}",
205
220
  cannot_undo: "此操作無法復原!",
206
221
  removed_info: "已刪除:{0}"
222
+ },
223
+ edit: {
224
+ select_api: 'Select API to edit',
225
+ current_value: 'Current value: {0}',
226
+ new_value: 'New value: ',
227
+ success: '✅ {0} updated successfully',
228
+ cancelled: 'Edit cancelled',
229
+ back: 'Back',
230
+ field_name: 'Name',
231
+ field_provider: 'Provider',
232
+ field_base_url: 'Base URL',
233
+ field_model: 'Model',
234
+ name_required: 'Name cannot be empty when editing',
235
+ duplicate: 'This change would create a duplicate configuration',
236
+ provider_url_mismatch: 'Provider and URL may be inconsistent',
237
+ provider_url_mismatch_detail: 'Provider: {0} / URL suggests: {1}',
238
+ url_provider_hint: "URL matches provider '{0}' but current provider is '{1}'. Consider updating Provider field."
207
239
  }
208
240
  },
209
241
 
@@ -277,6 +309,10 @@ module.exports = {
277
309
  good: "良好",
278
310
  strong: "強",
279
311
  very_strong: "極強"
312
+ },
313
+ guard: {
314
+ delete: { header: '🗑️ Remove API — Password required to verify identity' },
315
+ edit: { header: '✏️ Edit API — Password required to verify identity' }
280
316
  }
281
317
  },
282
318
 
@@ -324,15 +360,22 @@ module.exports = {
324
360
 
325
361
  // 導航和介面
326
362
  navigation: {
327
- use_arrows: "使用 ↑↓ 方向鍵導航,Enter鍵選擇,連按兩次 Ctrl+C 退出",
328
- use_arrows_esc: "使用 ↑↓ 導航,Enter鍵{0},ESC鍵返回主選單",
363
+ use_arrows: "使用 ↑↓ 方向鍵導航,回車/空格鍵選擇,連按兩次 Ctrl+C 退出",
364
+ use_arrows_esc: "使用 ↑↓ 導航,Enter鍵{0},ESC鍵取消",
365
+ use_arrows_page_esc: "←→ Page {0}/{1}, ↑↓ to navigate, Enter to {2}, ESC to cancel",
329
366
  use_number_keys: "使用數字鍵選擇:",
330
367
  currently_active: "目前活躍的API",
331
368
  select_action: "選擇一個動作:",
332
369
  no_options: "無可用選項",
333
370
  enter_choice: "輸入您的選擇({0},或任意其他鍵返回主選單):",
334
371
  arrow_keys_not_available: "方向鍵不可用。輸入選擇編號 (1-{0}):",
335
- enter_choice_prompt: "[>] 輸入您的選擇 (1-2,或任意其他鍵返回主選單):"
372
+ enter_choice_prompt: "[>] 輸入您的選擇 (1-2,或任意其他鍵返回主選單):",
373
+ action: {
374
+ edit: 'edit',
375
+ remove: 'remove',
376
+ switch: 'switch',
377
+ select: 'select'
378
+ }
336
379
  },
337
380
 
338
381
  // 啟動過程
@@ -573,19 +616,15 @@ module.exports = {
573
616
  model_upgrade: {
574
617
  notification: "模型升級可用:{0} → {1}",
575
618
  notification_api: "API:{0}",
576
- notification_hint: "前往「第三方API管理 > 模型升級設定」進行升級",
619
+ notification_hint: "自動升級:「設定管理」/ 手動升級:「第三方API管理 > 手動升級模型」",
577
620
  auto_upgraded: "模型已自動升級:{0} → {1}",
578
621
 
579
- settings_title: "模型升級設定",
580
622
  current_config: "目前設定",
581
623
  auto_upgrade_label: "自動使用最新模型",
582
624
  auto_upgrade_on: "開啟",
583
625
  auto_upgrade_off: "關閉",
584
626
 
585
- menu_toggle_auto_on: "自動升級 [● 開啟]",
586
- menu_toggle_auto_off: "自動升級 [○ 關閉]",
587
627
  menu_manual_upgrade: "手動一鍵升級所有模型",
588
- menu_back: "返回",
589
628
 
590
629
  manual_title: "模型升級檢查",
591
630
  manual_checking: "正在檢查 {0} 個 API 設定...",
@@ -602,8 +641,47 @@ module.exports = {
602
641
  manual_stats_skipped: "已跳過:{0} 個({1} 個已是最新,{2} 個無升級資訊)"
603
642
  },
604
643
  hints: {
605
- auto_mode_info: '自動模式:目前支援 Team 方案。Enterprise/API 方案逐步推出中。啟動後按 Shift+Tab 切換。',
644
+ auto_mode_info: '啟動後按 Shift+Tab 可切換到自動執行模式',
606
645
  active_api_info: '目前啟用:{0} / {1}',
607
- no_active_api: '未設定啟用的API,請前往「API管理」新增。'
646
+ no_active_api: '未設定啟用的API,請前往「API管理」新增。',
647
+ direct_mode_desc: '直接啟動模式,使用啟用的API直接啟動',
648
+ direct_mode_api_info: 'API: {0} | 提供商: {1}',
649
+ direct_mode_api_detail: '模型: {0} | 最後使用: {1}',
650
+ direct_mode_change: '啟動模式可在「設定管理」中切換',
651
+ direct_mode_no_active: '直接啟動模式,但目前沒有啟用的API',
652
+ direct_mode_no_active_detail: '已設定 {0} 個API,請在「第三方API管理」中選擇啟用',
653
+ select_mode_desc: '選擇模式,啟動前將從API列表中選擇',
654
+ select_mode_change: '啟動模式可在「設定管理」中切換',
655
+ select_mode_api_count: '已設定 {0} 個API,目前啟用: {1}',
656
+ select_mode_active_none: '無',
657
+ no_api_configured: '未設定第三方API,請先在「第三方API管理」中新增',
658
+ api_management_info: '已設定 {0} 個API,目前啟用: {1}',
659
+ config_summary: '語言: {0} | 啟動模式: {1} | 遙測: {2}',
660
+ edit_password_required: '🔒 Password verification required to edit API configuration',
661
+ remove_password_required: '🔒 Password verification required to remove API',
662
+ export_password_required: '🔒 Password verification required to export configuration',
663
+ import_password_required: '🔒 Password verification required to import configuration',
664
+ config: {
665
+ language: '切換介面顯示語言,目前: {0}',
666
+ auto_upgrade: '自動偵測並升級第三方API的模型版本',
667
+ upgrade_notification: '在主選單頂部顯示模型可升級的通知',
668
+ telemetry: '關閉後啟動時注入 DISABLE_TELEMETRY=1,建議關閉',
669
+ launch_mode: '直接模式: 使用啟用API啟動 / 選擇模式: 啟動前從列表選擇'
670
+ },
671
+ api_select: {
672
+ info: 'API: {0}',
673
+ detail: '提供商: {0} | 模型: {1}',
674
+ usage: '使用次數: {0} | 最後使用: {1}'
675
+ }
676
+ },
677
+
678
+ config: {
679
+ values: {
680
+ on: '開啟',
681
+ off: '關閉',
682
+ direct_mode: '直接模式',
683
+ select_mode: '選擇模式',
684
+ recommended_off: '關閉 (推薦)'
685
+ }
608
686
  }
609
687
  };
@@ -14,7 +14,7 @@ module.exports = {
14
14
  launch_api: "使用第三方API启动 Claude Code",
15
15
  launch_api_skip: "使用第三方API启动 Claude Code(自动跳过权限询问)",
16
16
  api_management: "第三方API管理",
17
- language_settings: "语言设置",
17
+ config_management: "配置管理",
18
18
  version_check: "版本更新检查",
19
19
  exit: "退出"
20
20
  },
@@ -22,11 +22,26 @@ module.exports = {
22
22
  title: "第三方API管理",
23
23
  add_new: "添加新的第三方API",
24
24
  remove: "删除API",
25
+ edit: "编辑API",
25
26
  switch: "切换激活的API",
26
27
  statistics: "查看API统计",
27
28
  export: "导出配置",
28
29
  import: "导入配置",
29
30
  change_password: "修改密码",
31
+ manual_upgrade: "手动升级模型",
32
+ back: "返回主菜单"
33
+ },
34
+ config: {
35
+ title: "配置管理",
36
+ language: "语言设置",
37
+ auto_model_upgrade: "模型自动升级",
38
+ model_upgrade_notification: "模型升级提示",
39
+ telemetry: "Anthropic 遥测数据",
40
+ api_launch_mode: "第三方API启动模式",
41
+ back: "返回主菜单"
42
+ },
43
+ api_select: {
44
+ title: "选择要启动的API:",
30
45
  back: "返回主菜单"
31
46
  },
32
47
  remove_api: {
@@ -204,6 +219,23 @@ module.exports = {
204
219
  remove_confirm: "要删除的API:{0}",
205
220
  cannot_undo: "此操作无法撤销!",
206
221
  removed_info: "已删除:{0}"
222
+ },
223
+ edit: {
224
+ select_api: '选择要编辑的 API',
225
+ current_value: '当前值:{0}',
226
+ new_value: '新值:',
227
+ success: '✅ {0} 更新成功',
228
+ cancelled: '编辑已取消',
229
+ back: '返回',
230
+ field_name: '名称',
231
+ field_provider: '供应商',
232
+ field_base_url: 'Base URL',
233
+ field_model: '模型',
234
+ name_required: '编辑时名称不能为空',
235
+ duplicate: '此更改将创建重复的配置',
236
+ provider_url_mismatch: '供应商和 URL 可能不一致',
237
+ provider_url_mismatch_detail: '供应商:{0} / URL 建议:{1}',
238
+ url_provider_hint: "URL 匹配供应商 '{0}',但当前供应商为 '{1}'。建议更新供应商字段。"
207
239
  }
208
240
  },
209
241
 
@@ -277,6 +309,10 @@ module.exports = {
277
309
  good: "良好",
278
310
  strong: "强",
279
311
  very_strong: "极强"
312
+ },
313
+ guard: {
314
+ delete: { header: '🗑️ 删除 API — 需要密码验证身份' },
315
+ edit: { header: '✏️ 编辑 API — 需要密码验证身份' }
280
316
  }
281
317
  },
282
318
 
@@ -324,15 +360,22 @@ module.exports = {
324
360
 
325
361
  // 导航和界面
326
362
  navigation: {
327
- use_arrows: "使用 ↑↓ 方向键导航,回车键选择,连击两次 Ctrl+C 退出",
328
- use_arrows_esc: "使用 ↑↓ 导航,回车键{0},ESC键返回主菜单",
363
+ use_arrows: "使用 ↑↓ 方向键导航,回车/空格键选择,连击两次 Ctrl+C 退出",
364
+ use_arrows_esc: "使用 ↑↓ 导航,回车键{0},ESC键取消",
365
+ use_arrows_page_esc: "←→ 第{0}/{1}页,↑↓ 导航,回车键{2},ESC键取消",
329
366
  use_number_keys: "使用数字键选择:",
330
367
  currently_active: "当前激活的API",
331
368
  select_action: "选择一个操作:",
332
369
  no_options: "无可用选项",
333
370
  enter_choice: "输入您的选择({0},或任意其他键返回主菜单):",
334
371
  arrow_keys_not_available: "方向键不可用。输入选择编号 (1-{0}):",
335
- enter_choice_prompt: "[>] 输入您的选择 (1-2,或任意其他键返回主菜单): "
372
+ enter_choice_prompt: "[>] 输入您的选择 (1-2,或任意其他键返回主菜单): ",
373
+ action: {
374
+ edit: '编辑',
375
+ remove: '删除',
376
+ switch: '切换',
377
+ select: '选择'
378
+ }
336
379
  },
337
380
 
338
381
  // 启动过程
@@ -573,19 +616,15 @@ module.exports = {
573
616
  model_upgrade: {
574
617
  notification: "模型升级可用: {0} → {1}",
575
618
  notification_api: "API: {0}",
576
- notification_hint: "前往「第三方API管理 > 模型升级设置」进行升级",
619
+ notification_hint: "自动升级:「配置管理」/ 手动升级:「第三方API管理 > 手动升级模型」",
577
620
  auto_upgraded: "模型已自动升级: {0} → {1}",
578
621
 
579
- settings_title: "模型升级设置",
580
622
  current_config: "当前配置",
581
623
  auto_upgrade_label: "自动使用最新模型",
582
624
  auto_upgrade_on: "开启",
583
625
  auto_upgrade_off: "关闭",
584
626
 
585
- menu_toggle_auto_on: "自动升级 [● 开启]",
586
- menu_toggle_auto_off: "自动升级 [○ 关闭]",
587
627
  menu_manual_upgrade: "手动一键升级所有模型",
588
- menu_back: "返回",
589
628
 
590
629
  manual_title: "模型升级检查",
591
630
  manual_checking: "正在检查 {0} 个 API 配置...",
@@ -602,8 +641,47 @@ module.exports = {
602
641
  manual_stats_skipped: "已跳过: {0} 个 ({1} 个已是最新, {2} 个无升级信息)"
603
642
  },
604
643
  hints: {
605
- auto_mode_info: '自动模式:目前支持 Team 计划。Enterprise/API 计划逐步推出中。启动后按 Shift+Tab 切换。',
644
+ auto_mode_info: '启动后按 Shift+Tab 可切换到自动执行模式',
606
645
  active_api_info: '当前激活:{0} / {1}',
607
- no_active_api: '未配置激活的API,请前往"API管理"添加。'
646
+ no_active_api: '未配置激活的API,请前往"API管理"添加。',
647
+ direct_mode_desc: '直接启动模式,使用激活的API直接启动',
648
+ direct_mode_api_info: 'API: {0} | 提供商: {1}',
649
+ direct_mode_api_detail: '模型: {0} | 最后使用: {1}',
650
+ direct_mode_change: '启动模式可在「配置管理」中切换',
651
+ direct_mode_no_active: '直接启动模式,但当前没有激活的API',
652
+ direct_mode_no_active_detail: '已配置 {0} 个API,请在「第三方API管理」中选择激活',
653
+ select_mode_desc: '选择模式,启动前将从API列表中选择',
654
+ select_mode_change: '启动模式可在「配置管理」中切换',
655
+ select_mode_api_count: '已配置 {0} 个API,当前激活: {1}',
656
+ select_mode_active_none: '无',
657
+ no_api_configured: '未配置第三方API,请先在「第三方API管理」中添加',
658
+ api_management_info: '已配置 {0} 个API,当前激活: {1}',
659
+ config_summary: '语言: {0} | 启动模式: {1} | 遥测: {2}',
660
+ edit_password_required: '🔒 编辑API配置需要密码验证',
661
+ remove_password_required: '🔒 删除API需要密码验证',
662
+ export_password_required: '🔒 导出配置需要密码验证',
663
+ import_password_required: '🔒 导入配置需要密码验证',
664
+ config: {
665
+ language: '切换界面显示语言,当前: {0}',
666
+ auto_upgrade: '自动检测并升级第三方API的模型版本',
667
+ upgrade_notification: '在主菜单顶部显示模型可升级的通知',
668
+ telemetry: '关闭后启动时注入 DISABLE_TELEMETRY=1,建议关闭',
669
+ launch_mode: '直接模式: 使用激活API启动 / 选择模式: 启动前从列表选择'
670
+ },
671
+ api_select: {
672
+ info: 'API: {0}',
673
+ detail: '提供商: {0} | 模型: {1}',
674
+ usage: '使用次数: {0} | 最后使用: {1}'
675
+ }
676
+ },
677
+
678
+ config: {
679
+ values: {
680
+ on: '开启',
681
+ off: '关闭',
682
+ direct_mode: '直接模式',
683
+ select_mode: '选择模式',
684
+ recommended_off: '关闭 (推荐)'
685
+ }
608
686
  }
609
687
  };
package/lib/launcher.js CHANGED
@@ -7,11 +7,108 @@ const colors = require('./ui/colors');
7
7
  const i18n = require('./i18n');
8
8
  const { getProvider } = require('./presets/providers');
9
9
  const stdinManager = require('./utils/stdin-manager');
10
+ const { loadConfigSync } = require('./utils/version-checker');
11
+
12
+ // Module-level flag for console handoff state
13
+ let consoleRelinquished = false;
14
+
15
+ /**
16
+ * Detach stdin and suspend stdinManager so the child process owns the terminal
17
+ */
18
+ function relinquishConsoleToChild() {
19
+ if (consoleRelinquished) return;
20
+ consoleRelinquished = true;
21
+ try {
22
+ if (process.stdin.isTTY) {
23
+ process.stdin.setRawMode(false);
24
+ }
25
+ } catch (_) {}
26
+
27
+ // Detach only current scope listeners to avoid affecting other modules
28
+ if (stdinManager.activeScope && typeof stdinManager.activeScope.detach === 'function') {
29
+ stdinManager.activeScope.detach();
30
+ }
31
+
32
+ // Suspend stdin manager so no new listeners are attached while Claude is running
33
+ if (typeof stdinManager.suspend === 'function') {
34
+ stdinManager.suspend();
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Restore stdinManager after child process exits
40
+ */
41
+ function restoreConsoleForLauncher() {
42
+ if (!consoleRelinquished) return;
43
+ consoleRelinquished = false;
44
+ if (typeof stdinManager.resume === 'function') {
45
+ stdinManager.resume();
46
+ }
47
+ stdinManager.enableCtrlC();
48
+ }
49
+
50
+ /**
51
+ * Handle launch failures with optional rollback and user prompt
52
+ */
53
+ function handleLaunchFailure(message, opts = {}) {
54
+ if (opts.afterHandover) {
55
+ restoreConsoleForLauncher();
56
+ } else {
57
+ stdinManager.enableCtrlC();
58
+ }
59
+
60
+ // Rollback launch statistics if callback provided — pass error message for lastError
61
+ if (typeof opts.rollbackFn === 'function') {
62
+ try { opts.rollbackFn(message); } catch (_) {}
63
+ }
64
+
65
+ console.log(colors.red + '[x] ' + message + colors.reset);
66
+ console.log(colors.gray + i18n.tSync('ui.general.press_key_return_menu') + colors.reset);
67
+
68
+ if (process.stdin.isTTY) {
69
+ try {
70
+ process.stdin.setRawMode(true);
71
+ process.stdin.resume();
72
+ } catch (_) {
73
+ // Ignore setup failures
74
+ }
75
+
76
+ // Set timeout to prevent infinite hanging
77
+ const timeoutId = setTimeout(() => {
78
+ try {
79
+ process.stdin.setRawMode(false);
80
+ } catch (_) {
81
+ // Ignore cleanup failures
82
+ }
83
+ process.exit(1);
84
+ }, 60000); // 60 second timeout
85
+
86
+ process.stdin.once('data', () => {
87
+ clearTimeout(timeoutId);
88
+ try {
89
+ process.stdin.setRawMode(false);
90
+ } catch (_) {
91
+ // Ignore cleanup failures
92
+ }
93
+ // Exit after user acknowledges the error
94
+ process.exit(1);
95
+ });
96
+ } else {
97
+ // For non-TTY environments, exit immediately
98
+ process.exit(1);
99
+ }
100
+ }
10
101
 
11
102
  /**
12
103
  * Launch Claude Code with specified environment variables
13
104
  */
14
- function launchClaude(command, envVars = {}, disableAuthTokens = false) {
105
+ function launchClaude(command, envVars = {}, disableAuthTokens = false, opts = {}) {
106
+ // Inject telemetry control from config
107
+ const launcherConfig = loadConfigSync();
108
+ if (launcherConfig.disableTelemetry) {
109
+ envVars.DISABLE_TELEMETRY = '1';
110
+ }
111
+
15
112
  // Disable Ctrl+C monitoring before launching Claude Code
16
113
  // This allows Ctrl+C to be handled exclusively by Claude Code process
17
114
  stdinManager.disableCtrlC();
@@ -49,81 +146,6 @@ function launchClaude(command, envVars = {}, disableAuthTokens = false) {
49
146
  const args = command.split(' ');
50
147
  const cmd = args.shift();
51
148
 
52
- let consoleRelinquished = false;
53
- const relinquishConsoleToChild = () => {
54
- if (consoleRelinquished) return;
55
- consoleRelinquished = true;
56
- // Do the minimal changes: switch to cooked mode and detach current scope, but do not pause stdin nor swallow signals
57
- try {
58
- if (process.stdin.isTTY) {
59
- process.stdin.setRawMode(false);
60
- }
61
- } catch (_) {}
62
-
63
- // Detach only current scope listeners to avoid affecting other modules
64
- if (stdinManager.activeScope && typeof stdinManager.activeScope.detach === 'function') {
65
- stdinManager.activeScope.detach();
66
- }
67
-
68
- // Suspend stdin manager so no new listeners are attached while Claude is running
69
- if (typeof stdinManager.suspend === 'function') {
70
- stdinManager.suspend();
71
- }
72
- };
73
-
74
- const restoreConsoleForLauncher = () => {
75
- if (!consoleRelinquished) return;
76
- consoleRelinquished = false;
77
- if (typeof stdinManager.resume === 'function') {
78
- stdinManager.resume();
79
- }
80
- stdinManager.enableCtrlC();
81
- };
82
-
83
- const handleLaunchFailure = (message, opts = {}) => {
84
- if (opts.afterHandover) {
85
- restoreConsoleForLauncher();
86
- } else {
87
- stdinManager.enableCtrlC();
88
- }
89
-
90
- console.log(colors.red + '[x] ' + message + colors.reset);
91
- console.log(colors.gray + i18n.tSync('ui.general.press_key_return_menu') + colors.reset);
92
-
93
- if (process.stdin.isTTY) {
94
- try {
95
- process.stdin.setRawMode(true);
96
- process.stdin.resume();
97
- } catch (_) {
98
- // Ignore setup failures
99
- }
100
-
101
- // Set timeout to prevent infinite hanging
102
- const timeoutId = setTimeout(() => {
103
- try {
104
- process.stdin.setRawMode(false);
105
- } catch (_) {
106
- // Ignore cleanup failures
107
- }
108
- process.exit(1);
109
- }, 60000); // 60 second timeout
110
-
111
- process.stdin.once('data', () => {
112
- clearTimeout(timeoutId);
113
- try {
114
- process.stdin.setRawMode(false);
115
- } catch (_) {
116
- // Ignore cleanup failures
117
- }
118
- // Exit after user acknowledges the error
119
- process.exit(1);
120
- });
121
- } else {
122
- // For non-TTY environments, exit immediately
123
- process.exit(1);
124
- }
125
- };
126
-
127
149
  try {
128
150
  // Clean up terminal state before launching Claude
129
151
  if (process.stdin.isTTY) {
@@ -135,15 +157,8 @@ function launchClaude(command, envVars = {}, disableAuthTokens = false) {
135
157
  }
136
158
  }
137
159
 
138
- // Note: stdin listener management is handled by relinquishConsoleToChild()
139
- // using stdinManager.activeScope.detach() and stdinManager.suspend().
140
- // This ensures only the current scope's listeners are detached while
141
- // preserving any listeners from other modules.
142
- //
143
- // Note: Do NOT remove global SIGINT/SIGTERM handlers here.
144
- // The existing handlers already check stdinManager.isSuspended() and
145
- // will properly ignore signals during child process execution.
146
- // Removing all handlers would break other modules and degrade reliability.
160
+ // Relinquish console before spawn so the child inherits a clean terminal
161
+ relinquishConsoleToChild();
147
162
 
148
163
  // Launch Claude in current terminal, inherit stdio
149
164
  const child = spawn(cmd, args, {
@@ -153,19 +168,22 @@ function launchClaude(command, envVars = {}, disableAuthTokens = false) {
153
168
  shell: true
154
169
  });
155
170
 
156
- relinquishConsoleToChild();
157
-
158
171
  child.on('close', (code) => {
159
172
  restoreConsoleForLauncher();
160
173
  process.exit(code || 0);
161
174
  });
162
175
 
163
176
  child.on('error', (error) => {
164
- handleLaunchFailure('Error running Claude: ' + error.message, { afterHandover: true });
177
+ handleLaunchFailure('Error running Claude: ' + error.message, {
178
+ afterHandover: true,
179
+ rollbackFn: opts.rollbackFn
180
+ });
165
181
  });
166
182
 
167
183
  } catch (error) {
168
- handleLaunchFailure('Error launching Claude Code: ' + error.message);
184
+ handleLaunchFailure('Error launching Claude Code: ' + error.message, {
185
+ rollbackFn: opts.rollbackFn
186
+ });
169
187
  }
170
188
  }
171
189
 
@@ -231,12 +249,21 @@ function getProviderEnvVars(api) {
231
249
  /**
232
250
  * Launch Claude with third-party API configuration
233
251
  */
234
- function launchClaudeWithApi(api, skipPermissions = false) {
252
+ function launchClaudeWithApi(api, skipPermissions = false, opts = {}) {
235
253
  const command = skipPermissions
236
254
  ? 'claude --dangerously-skip-permissions'
237
255
  : 'claude';
238
256
 
239
- const envVars = getProviderEnvVars(api);
257
+ let envVars;
258
+ try {
259
+ envVars = getProviderEnvVars(api);
260
+ } catch (error) {
261
+ handleLaunchFailure('Failed to prepare API environment: ' + error.message, {
262
+ afterHandover: true,
263
+ rollbackFn: opts.rollbackFn
264
+ });
265
+ return;
266
+ }
240
267
 
241
268
  console.log('');
242
269
  console.log(colors.bright + colors.orange + '🔗 ' + i18n.tSync('launch.using_third_party_api') + colors.reset);
@@ -281,7 +308,7 @@ function launchClaudeWithApi(api, skipPermissions = false) {
281
308
 
282
309
  console.log('');
283
310
 
284
- launchClaude(command, envVars, true);
311
+ launchClaude(command, envVars, true, { rollbackFn: opts.rollbackFn });
285
312
  }
286
313
 
287
314
  /**
@@ -365,5 +392,6 @@ module.exports = {
365
392
  launchClaudeAutoMode,
366
393
  launchClaudeWithApi,
367
394
  getProviderEnvVars,
368
- testApiConnection
395
+ testApiConnection,
396
+ handleLaunchFailure
369
397
  };