mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: 增加对 Windows CLI 工具的支持,允许使用 cmd 作为首选 shell,并改进错误处理
This commit is contained in:
@@ -29,7 +29,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/RadioGroup';
|
||||
|
||||
export type CliTool = 'claude' | 'gemini' | 'qwen' | 'codex' | 'opencode';
|
||||
export type LaunchMode = 'default' | 'yolo';
|
||||
export type ShellKind = 'bash' | 'pwsh';
|
||||
export type ShellKind = 'bash' | 'pwsh' | 'cmd';
|
||||
|
||||
export interface CliSessionConfig {
|
||||
tool: CliTool;
|
||||
@@ -69,7 +69,10 @@ export function CliConfigModal({
|
||||
const [tool, setTool] = React.useState<CliTool>('gemini');
|
||||
const [model, setModel] = React.useState<string | undefined>(MODEL_OPTIONS.gemini[0]);
|
||||
const [launchMode, setLaunchMode] = React.useState<LaunchMode>('yolo');
|
||||
const [preferredShell, setPreferredShell] = React.useState<ShellKind>('bash');
|
||||
// Default to 'cmd' on Windows for better compatibility with npm CLI tools (.cmd files)
|
||||
const [preferredShell, setPreferredShell] = React.useState<ShellKind>(
|
||||
typeof navigator !== 'undefined' && navigator.platform.toLowerCase().includes('win') ? 'cmd' : 'bash'
|
||||
);
|
||||
const [workingDir, setWorkingDir] = React.useState<string>(defaultWorkingDir ?? '');
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
@@ -216,8 +219,9 @@ export function CliConfigModal({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="bash">bash</SelectItem>
|
||||
<SelectItem value="pwsh">pwsh</SelectItem>
|
||||
<SelectItem value="cmd">cmd (推荐 Windows)</SelectItem>
|
||||
<SelectItem value="bash">bash (Git Bash/WSL)</SelectItem>
|
||||
<SelectItem value="pwsh">pwsh (PowerShell)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
import { useIssues, useIssueQueue } from '@/hooks/useIssues';
|
||||
import { useTerminalGridStore, selectTerminalGridFocusedPaneId } from '@/stores/terminalGridStore';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import { toast } from '@/stores/notificationStore';
|
||||
import { CliConfigModal, type CliSessionConfig } from './CliConfigModal';
|
||||
|
||||
// ========== Types ==========
|
||||
@@ -79,6 +80,7 @@ const LAYOUT_PRESETS = [
|
||||
];
|
||||
|
||||
type LaunchMode = 'default' | 'yolo';
|
||||
type ShellKind = 'bash' | 'pwsh' | 'cmd';
|
||||
|
||||
const CLI_TOOLS = ['claude', 'gemini', 'qwen', 'codex', 'opencode'] as const;
|
||||
type CliTool = (typeof CLI_TOOLS)[number];
|
||||
@@ -124,6 +126,9 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [selectedTool, setSelectedTool] = useState<CliTool>('gemini');
|
||||
const [launchMode, setLaunchMode] = useState<LaunchMode>('yolo');
|
||||
const [selectedShell, setSelectedShell] = useState<ShellKind>(
|
||||
typeof navigator !== 'undefined' && navigator.platform.toLowerCase().includes('win') ? 'cmd' : 'bash'
|
||||
);
|
||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||
|
||||
// Helper to get or create a focused pane
|
||||
@@ -140,18 +145,29 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const targetPaneId = getOrCreateFocusedPane();
|
||||
if (!targetPaneId) return;
|
||||
if (!targetPaneId) {
|
||||
toast.error('无法创建会话', '未能获取或创建窗格');
|
||||
return;
|
||||
}
|
||||
|
||||
await createSessionAndAssign(targetPaneId, {
|
||||
workingDir: projectPath,
|
||||
preferredShell: 'bash',
|
||||
preferredShell: selectedShell,
|
||||
tool: selectedTool,
|
||||
launchMode,
|
||||
}, projectPath);
|
||||
} catch (error: unknown) {
|
||||
// Handle both Error instances and ApiError-like objects
|
||||
const message = error instanceof Error
|
||||
? error.message
|
||||
: (error as { message?: string })?.message
|
||||
? (error as { message: string }).message
|
||||
: String(error);
|
||||
toast.error(`CLI 会话创建失败 (${selectedTool})`, message);
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
}, [projectPath, createSessionAndAssign, selectedTool, launchMode, getOrCreateFocusedPane]);
|
||||
}, [projectPath, createSessionAndAssign, selectedTool, selectedShell, launchMode, getOrCreateFocusedPane]);
|
||||
|
||||
const handleConfigure = useCallback(() => {
|
||||
setIsConfigOpen(true);
|
||||
@@ -164,7 +180,7 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
|
||||
const targetPaneId = getOrCreateFocusedPane();
|
||||
if (!targetPaneId) throw new Error('Failed to create pane');
|
||||
|
||||
const created = await createSessionAndAssign(
|
||||
await createSessionAndAssign(
|
||||
targetPaneId,
|
||||
{
|
||||
workingDir: config.workingDir || projectPath,
|
||||
@@ -175,8 +191,15 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
|
||||
},
|
||||
projectPath
|
||||
);
|
||||
|
||||
if (!created?.session?.sessionKey) throw new Error('createSessionAndAssign failed');
|
||||
} catch (error: unknown) {
|
||||
// Handle both Error instances and ApiError-like objects
|
||||
const message = error instanceof Error
|
||||
? error.message
|
||||
: (error as { message?: string })?.message
|
||||
? (error as { message: string }).message
|
||||
: String(error);
|
||||
toast.error(`CLI 会话创建失败 (${config.tool})`, message);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsCreating(false);
|
||||
}
|
||||
@@ -249,6 +272,31 @@ export function DashboardToolbar({ activePanel, onTogglePanel, isFileSidebarOpen
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="gap-2">
|
||||
<span>{formatMessage({ id: 'terminalDashboard.toolbar.shell' })}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{selectedShell === 'cmd' ? 'cmd' : selectedShell === 'pwsh' ? 'pwsh' : 'bash'}
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={selectedShell}
|
||||
onValueChange={(v) => setSelectedShell(v as ShellKind)}
|
||||
>
|
||||
<DropdownMenuRadioItem value="cmd">
|
||||
cmd {formatMessage({ id: 'terminalDashboard.toolbar.shellCmdDesc' })}
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="bash">
|
||||
bash (Git Bash/WSL)
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="pwsh">
|
||||
pwsh (PowerShell)
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={handleQuickCreate}
|
||||
|
||||
@@ -152,7 +152,9 @@ async function fetchApi<T>(
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
try {
|
||||
const body = await response.json();
|
||||
// Check both 'message' and 'error' fields for error message
|
||||
if (body.message) error.message = body.message;
|
||||
else if (body.error) error.message = body.error;
|
||||
if (body.code) error.code = body.code;
|
||||
} catch (parseError) {
|
||||
// Silently ignore JSON parse errors for non-JSON responses
|
||||
@@ -6344,7 +6346,8 @@ export interface CreateCliSessionInput {
|
||||
workingDir?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
preferredShell?: 'bash' | 'pwsh';
|
||||
/** Shell to use for spawning CLI tools on Windows. */
|
||||
preferredShell?: 'bash' | 'pwsh' | 'cmd';
|
||||
tool?: string;
|
||||
model?: string;
|
||||
resumeKey?: string;
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
"mode": "Mode",
|
||||
"modeDefault": "Default",
|
||||
"modeYolo": "Yolo",
|
||||
"shell": "Shell",
|
||||
"shellCmdDesc": "(Recommended for Windows)",
|
||||
"quickCreate": "Quick Create",
|
||||
"configure": "Configure...",
|
||||
"fullscreen": "Fullscreen",
|
||||
|
||||
@@ -21,7 +21,14 @@
|
||||
"toolbar": {
|
||||
"refresh": "刷新",
|
||||
"clearAll": "清空所有",
|
||||
"settings": "设置"
|
||||
"settings": "设置",
|
||||
"back": "返回",
|
||||
"addExecution": "添加",
|
||||
"running": "运行中",
|
||||
"executions": "执行",
|
||||
"executionsList": "最近执行",
|
||||
"fullscreen": "全屏",
|
||||
"exitFullscreen": "退出全屏"
|
||||
},
|
||||
"emptyState": {
|
||||
"title": "暂无 CLI 执行",
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
"mode": "模式",
|
||||
"modeDefault": "默认",
|
||||
"modeYolo": "Yolo",
|
||||
"shell": "Shell",
|
||||
"shellCmdDesc": "(推荐 Windows)",
|
||||
"quickCreate": "快速创建",
|
||||
"configure": "配置...",
|
||||
"fullscreen": "全屏",
|
||||
|
||||
@@ -341,9 +341,16 @@ export const useTerminalGridStore = create<TerminalGridStore>()(
|
||||
);
|
||||
|
||||
return { paneId: newPaneId, session };
|
||||
} catch (error) {
|
||||
console.error('Failed to create CLI session:', error);
|
||||
return null;
|
||||
} catch (error: unknown) {
|
||||
// Handle both Error instances and ApiError objects
|
||||
const errorMsg = error instanceof Error
|
||||
? error.message
|
||||
: (error as { message?: string })?.message
|
||||
? (error as { message: string }).message
|
||||
: String(error);
|
||||
console.error('Failed to create CLI session:', errorMsg, { config, projectPath, rawError: error });
|
||||
// Re-throw with meaningful message so UI can display it
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user