@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.
- package/CHANGELOG.md +42 -0
- package/README.md +17 -10
- package/claude-launcher +614 -398
- package/docs/README-zh.md +17 -10
- package/lib/api-manager.js +136 -11
- package/lib/auth/password-input.js +8 -4
- package/lib/auth/password-validator.js +83 -48
- package/lib/i18n/index.js +4 -3
- package/lib/i18n/language-manager.js +4 -3
- package/lib/i18n/locales/de.js +89 -11
- package/lib/i18n/locales/en.js +89 -11
- package/lib/i18n/locales/es.js +89 -11
- package/lib/i18n/locales/fr.js +89 -11
- package/lib/i18n/locales/it.js +89 -11
- package/lib/i18n/locales/ja.js +89 -11
- package/lib/i18n/locales/ko.js +89 -11
- package/lib/i18n/locales/pt.js +89 -11
- package/lib/i18n/locales/ru.js +89 -11
- package/lib/i18n/locales/zh-TW.js +89 -11
- package/lib/i18n/locales/zh.js +89 -11
- package/lib/launcher.js +121 -93
- package/lib/ui/api-editor.js +210 -0
- package/lib/ui/interactive-table.js +216 -99
- package/lib/ui/menu.js +73 -62
- package/lib/ui/prompts.js +168 -139
- package/lib/ui/screen.js +125 -0
- package/lib/utils/stdin-manager.js +11 -9
- package/lib/utils/version-checker.js +63 -3
- package/package.json +2 -2
- package/docs/superpowers/plans/2026-03-31-update-models-and-auto-mode.md +0 -1414
- package/docs/superpowers/specs/2026-03-31-update-models-and-auto-mode-design.md +0 -187
|
@@ -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
|
-
|
|
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: "使用 ↑↓
|
|
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: "
|
|
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: '
|
|
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
|
};
|
package/lib/i18n/locales/zh.js
CHANGED
|
@@ -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
|
-
|
|
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: "使用 ↑↓
|
|
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: "
|
|
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: '
|
|
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
|
-
//
|
|
139
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
};
|