diff --git a/cmd/desktop/app.go b/cmd/desktop/app.go index c2bf7a7..a1fe4c3 100644 --- a/cmd/desktop/app.go +++ b/cmd/desktop/app.go @@ -505,8 +505,8 @@ func (a *App) SendUpdateNotification(title, message string) error { func (a *App) DetectTerminals() string { return a.terminal.DetectTerminals() } func (a *App) GetTerminalConfig() string { return a.terminal.GetTerminalConfig() } -func (a *App) SaveTerminalConfig(selectedTerminal string, projectDirs []string) error { - return a.terminal.SaveTerminalConfig(selectedTerminal, projectDirs) +func (a *App) SaveTerminalConfig(selectedTerminal string, projectDirs []string, claudeCommand string) error { + return a.terminal.SaveTerminalConfig(selectedTerminal, projectDirs, claudeCommand) } func (a *App) AddProjectDir(dir string) error { return a.terminal.AddProjectDir(dir) } func (a *App) RemoveProjectDir(dir string) error { return a.terminal.RemoveProjectDir(dir) } diff --git a/cmd/desktop/frontend/src/i18n/en.js b/cmd/desktop/frontend/src/i18n/en.js index db2c9bd..5eba077 100644 --- a/cmd/desktop/frontend/src/i18n/en.js +++ b/cmd/desktop/frontend/src/i18n/en.js @@ -446,6 +446,8 @@ export default { confirmDelete: 'Are you sure you want to delete this project directory?', addDirFailed: 'Failed to add directory', dirExists: 'Directory already exists', + launcherCommand: 'Launcher Command', + launcherCommandHelp: 'Custom CLI command to launch Claude Code, defaults to claude (e.g., hapi)', // Error messages errors: { directory_already_exists: 'Directory already exists' diff --git a/cmd/desktop/frontend/src/i18n/zh-CN.js b/cmd/desktop/frontend/src/i18n/zh-CN.js index f3fbca0..fced0a1 100644 --- a/cmd/desktop/frontend/src/i18n/zh-CN.js +++ b/cmd/desktop/frontend/src/i18n/zh-CN.js @@ -446,6 +446,8 @@ export default { confirmDelete: '确定要删除此项目目录吗?', addDirFailed: '添加目录失败', dirExists: '目录已存在', + launcherCommand: '启动命令', + launcherCommandHelp: '自定义 Claude Code 启动命令,默认为 claude(例如:hapi)', // 错误消息 errors: { directory_already_exists: '目录已存在' diff --git a/cmd/desktop/frontend/src/modules/terminal.js b/cmd/desktop/frontend/src/modules/terminal.js index 7cd3433..10cc44f 100644 --- a/cmd/desktop/frontend/src/modules/terminal.js +++ b/cmd/desktop/frontend/src/modules/terminal.js @@ -24,6 +24,7 @@ export function initTerminal() { window.showTerminalModal = showTerminalModal; window.closeTerminalModal = closeTerminalModal; window.onTerminalChange = onTerminalChange; + window.onClaudeCommandChange = onClaudeCommandChange; window.addProjectDir = addProjectDir; window.removeProjectDir = removeProjectDir; window.launchTerminal = launchTerminal; @@ -87,6 +88,11 @@ async function loadTerminalConfig() { if (select && terminalConfig.selectedTerminal) { select.value = terminalConfig.selectedTerminal; } + // Update claudeCommand input + const cmdInput = document.getElementById('claudeCommandInput'); + if (cmdInput) { + cmdInput.value = terminalConfig.claudeCommand || ''; + } } catch (err) { console.error('Failed to load terminal config:', err); } @@ -105,12 +111,22 @@ async function onTerminalChange() { const select = document.getElementById('terminalSelect'); terminalConfig.selectedTerminal = select.value; try { - await SaveTerminalConfig(terminalConfig.selectedTerminal, terminalConfig.projectDirs); + await SaveTerminalConfig(terminalConfig.selectedTerminal, terminalConfig.projectDirs, terminalConfig.claudeCommand || ''); } catch (err) { console.error('Failed to save terminal config:', err); } } +async function onClaudeCommandChange() { + const cmdInput = document.getElementById('claudeCommandInput'); + terminalConfig.claudeCommand = cmdInput ? cmdInput.value.trim() : ''; + try { + await SaveTerminalConfig(terminalConfig.selectedTerminal, terminalConfig.projectDirs, terminalConfig.claudeCommand); + } catch (err) { + console.error('Failed to save claude command:', err); + } +} + function renderProjectDirs() { const container = document.getElementById('projectDirList'); if (!container) return; diff --git a/cmd/desktop/frontend/src/modules/ui.js b/cmd/desktop/frontend/src/modules/ui.js index 6ae70f9..1cfa63c 100644 --- a/cmd/desktop/frontend/src/modules/ui.js +++ b/cmd/desktop/frontend/src/modules/ui.js @@ -393,6 +393,12 @@ export function initUI() { ${t('terminal.selectTerminalHelp')} +
+ + + ${t('terminal.launcherCommandHelp')} +
${t('terminal.projectDirsHelp')} diff --git a/cmd/desktop/frontend/wailsjs/go/main/App.js b/cmd/desktop/frontend/wailsjs/go/main/App.js index f546a9f..111233b 100755 --- a/cmd/desktop/frontend/wailsjs/go/main/App.js +++ b/cmd/desktop/frontend/wailsjs/go/main/App.js @@ -282,8 +282,8 @@ export function SaveSettings(arg1) { return window['go']['main']['App']['SaveSettings'](arg1); } -export function SaveTerminalConfig(arg1, arg2) { - return window['go']['main']['App']['SaveTerminalConfig'](arg1, arg2); +export function SaveTerminalConfig(arg1, arg2, arg3) { + return window['go']['main']['App']['SaveTerminalConfig'](arg1, arg2, arg3); } export function SelectDirectory() { diff --git a/internal/config/config.go b/internal/config/config.go index 29c601f..d99bda0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -64,6 +64,7 @@ type UpdateConfig struct { type TerminalConfig struct { SelectedTerminal string `json:"selectedTerminal"` // Selected terminal ID ProjectDirs []string `json:"projectDirs"` // Project directories + ClaudeCommand string `json:"claudeCommand"` // Custom launcher command, defaults to "claude" } // ProxyConfig represents HTTP proxy configuration @@ -357,6 +358,7 @@ func (c *Config) GetTerminal() *TerminalConfig { return &TerminalConfig{ SelectedTerminal: "cmd", ProjectDirs: []string{}, + ClaudeCommand: "", } } return c.Terminal @@ -600,6 +602,7 @@ func LoadFromStorage(storage StorageAdapter) (*Config, error) { config.Terminal = &TerminalConfig{ SelectedTerminal: "cmd", ProjectDirs: []string{}, + ClaudeCommand: "", } if selectedTerminal, err := storage.GetConfig("terminal_selected"); err == nil && selectedTerminal != "" { config.Terminal.SelectedTerminal = selectedTerminal @@ -610,6 +613,9 @@ func LoadFromStorage(storage StorageAdapter) (*Config, error) { config.Terminal.ProjectDirs = dirs } } + if claudeCmd, err := storage.GetConfig("terminal_claudeCommand"); err == nil { + config.Terminal.ClaudeCommand = claudeCmd + } // Load Proxy config if proxyURL, err := storage.GetConfig("proxy_url"); err == nil && proxyURL != "" { @@ -733,6 +739,7 @@ func (c *Config) SaveToStorage(storage StorageAdapter) error { if dirsJSON, err := json.Marshal(c.Terminal.ProjectDirs); err == nil { storage.SetConfig("terminal_projectDirs", string(dirsJSON)) } + storage.SetConfig("terminal_claudeCommand", c.Terminal.ClaudeCommand) } // Save Proxy config diff --git a/internal/service/terminal.go b/internal/service/terminal.go index e03a21e..8d6e60a 100644 --- a/internal/service/terminal.go +++ b/internal/service/terminal.go @@ -37,10 +37,11 @@ func (t *TerminalService) GetTerminalConfig() string { } // SaveTerminalConfig saves the terminal configuration -func (t *TerminalService) SaveTerminalConfig(selectedTerminal string, projectDirs []string) error { +func (t *TerminalService) SaveTerminalConfig(selectedTerminal string, projectDirs []string, claudeCommand string) error { terminalCfg := &config.TerminalConfig{ SelectedTerminal: selectedTerminal, ProjectDirs: projectDirs, + ClaudeCommand: claudeCommand, } t.config.UpdateTerminal(terminalCfg) @@ -51,7 +52,7 @@ func (t *TerminalService) SaveTerminalConfig(selectedTerminal string, projectDir } } - logger.Info("Terminal config saved: terminal=%s, dirs=%d", selectedTerminal, len(projectDirs)) + logger.Info("Terminal config saved: terminal=%s, dirs=%d, claudeCommand=%s", selectedTerminal, len(projectDirs), claudeCommand) return nil } @@ -107,9 +108,10 @@ func (t *TerminalService) LaunchTerminal(dir string) error { if terminalID == "" { terminalID = "cmd" } + customCmd := terminalCfg.ClaudeCommand - logger.Info("Launching terminal: %s in %s", terminalID, dir) - return terminal.LaunchTerminal(terminalID, dir) + logger.Info("Launching terminal: %s in %s (cmd=%s)", terminalID, dir, customCmd) + return terminal.LaunchTerminalWithCustomCmd(terminalID, dir, customCmd) } // GetSessions returns all sessions for a project directory @@ -183,13 +185,14 @@ func (t *TerminalService) LaunchSessionTerminal(dir, sessionID string) error { if terminalID == "" { terminalID = "cmd" } + customCmd := terminalCfg.ClaudeCommand if sessionID != "" { - logger.Info("Launching terminal with session: %s in %s", sessionID, dir) + logger.Info("Launching terminal with session: %s in %s (cmd=%s)", sessionID, dir, customCmd) } else { - logger.Info("Launching new terminal in %s", dir) + logger.Info("Launching new terminal in %s (cmd=%s)", dir, customCmd) } - return terminal.LaunchTerminalWithSession(terminalID, dir, sessionID) + return terminal.LaunchSessionTerminalWithCustomCmd(terminalID, dir, sessionID, customCmd) } // LaunchCodexTerminal launches a terminal with Codex diff --git a/internal/terminal/launcher.go b/internal/terminal/launcher.go index 829dec6..756deeb 100644 --- a/internal/terminal/launcher.go +++ b/internal/terminal/launcher.go @@ -16,7 +16,18 @@ func LaunchTerminal(terminalID, dir string) error { // LaunchTerminalWithSession launches a terminal with optional session resume func LaunchTerminalWithSession(terminalID, dir, sessionID string) error { - cliCmd := getClaudeCommand(sessionID) + cliCmd := getClaudeCommand(sessionID, "") + return launchTerminalWithCli(terminalID, dir, cliCmd) +} + +// LaunchTerminalWithCustomCmd launches a terminal with a custom CLI command +func LaunchTerminalWithCustomCmd(terminalID, dir, customCmd string) error { + return LaunchSessionTerminalWithCustomCmd(terminalID, dir, "", customCmd) +} + +// LaunchSessionTerminalWithCustomCmd launches a terminal with a custom CLI command and optional session +func LaunchSessionTerminalWithCustomCmd(terminalID, dir, sessionID, customCmd string) error { + cliCmd := getClaudeCommand(sessionID, customCmd) return launchTerminalWithCli(terminalID, dir, cliCmd) } @@ -89,10 +100,14 @@ func getShellType(shell string) shellType { // getClaudeCommand returns the claude command with optional session resume // On macOS, prepends npm initialization to handle lazy-loaded Node environments (nvm, fnm, etc.) -func getClaudeCommand(sessionID string) string { - cmd := "claude" +func getClaudeCommand(sessionID, customCmd string) string { + base := "claude" + if customCmd != "" { + base = customCmd + } + cmd := base if sessionID != "" { - cmd = fmt.Sprintf("claude -r %s", shellEscape(sessionID)) + cmd = fmt.Sprintf("%s -r %s", base, shellEscape(sessionID)) } if runtime.GOOS == "darwin" { // Trigger npm lazy-loading for nvm/fnm environments @@ -239,7 +254,7 @@ func buildThirdPartyTerminalCommand(shell, dir, claudeCmd string) string { } func buildLaunchCommand(termInfo TerminalInfo, dir, sessionID string) *exec.Cmd { - claudeCmd := getClaudeCommand(sessionID) + claudeCmd := getClaudeCommand(sessionID, "") return buildLaunchCommandWithCli(termInfo, dir, claudeCmd) }