hjworktree-cli 2.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/.context-snapshots/context-snapshot-20260106-110353.md +66 -0
- package/.context-snapshots/context-snapshot-20260106-110441.md +66 -0
- package/.context-snapshots/context-snapshot-20260106-220000.md +99 -0
- package/AGENTS.md +29 -0
- package/CLAUDE.md +88 -0
- package/bin/cli.js +85 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +64 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/api.d.ts +3 -0
- package/dist/server/routes/api.d.ts.map +1 -0
- package/dist/server/routes/api.js +101 -0
- package/dist/server/routes/api.js.map +1 -0
- package/dist/server/services/gitService.d.ts +13 -0
- package/dist/server/services/gitService.d.ts.map +1 -0
- package/dist/server/services/gitService.js +84 -0
- package/dist/server/services/gitService.js.map +1 -0
- package/dist/server/services/worktreeService.d.ts +17 -0
- package/dist/server/services/worktreeService.d.ts.map +1 -0
- package/dist/server/services/worktreeService.js +161 -0
- package/dist/server/services/worktreeService.js.map +1 -0
- package/dist/server/socketHandlers.d.ts +4 -0
- package/dist/server/socketHandlers.d.ts.map +1 -0
- package/dist/server/socketHandlers.js +118 -0
- package/dist/server/socketHandlers.js.map +1 -0
- package/dist/shared/constants.d.ts +10 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +31 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/types/index.d.ts +67 -0
- package/dist/shared/types/index.d.ts.map +1 -0
- package/dist/shared/types/index.js +3 -0
- package/dist/shared/types/index.js.map +1 -0
- package/dist/web/assets/index-C61yAbey.css +32 -0
- package/dist/web/assets/index-WEdVUKxb.js +53 -0
- package/dist/web/assets/index-WEdVUKxb.js.map +1 -0
- package/dist/web/index.html +16 -0
- package/package.json +63 -0
- package/server/index.ts +75 -0
- package/server/routes/api.ts +108 -0
- package/server/services/gitService.ts +91 -0
- package/server/services/worktreeService.ts +181 -0
- package/server/socketHandlers.ts +157 -0
- package/shared/constants.ts +35 -0
- package/shared/types/index.ts +92 -0
- package/tsconfig.json +20 -0
- package/web/index.html +15 -0
- package/web/src/App.tsx +65 -0
- package/web/src/components/Layout/Header.tsx +29 -0
- package/web/src/components/Layout/LeftNavBar.tsx +67 -0
- package/web/src/components/Layout/MainLayout.tsx +23 -0
- package/web/src/components/Layout/StepContainer.tsx +71 -0
- package/web/src/components/Setup/AgentSelector.tsx +27 -0
- package/web/src/components/Setup/BranchSelector.tsx +28 -0
- package/web/src/components/Setup/SetupPanel.tsx +32 -0
- package/web/src/components/Setup/WorktreeCountSelector.tsx +30 -0
- package/web/src/components/Steps/AgentStep.tsx +20 -0
- package/web/src/components/Steps/BranchStep.tsx +20 -0
- package/web/src/components/Steps/WorktreeStep.tsx +41 -0
- package/web/src/components/Terminal/TerminalPanel.tsx +113 -0
- package/web/src/components/Terminal/XTerminal.tsx +203 -0
- package/web/src/hooks/useSocket.ts +80 -0
- package/web/src/main.tsx +10 -0
- package/web/src/stores/useAppStore.ts +348 -0
- package/web/src/styles/global.css +695 -0
- package/web/tsconfig.json +23 -0
- package/web/vite.config.ts +32 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AgentType } from './types/index.js';
|
|
2
|
+
|
|
3
|
+
export const AI_AGENTS: AgentType[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'codex',
|
|
6
|
+
name: 'Codex CLI',
|
|
7
|
+
command: 'codex',
|
|
8
|
+
installCommand: 'npm install -g @openai/codex',
|
|
9
|
+
description: 'OpenAI Codex CLI - AI-powered coding assistant'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'claude',
|
|
13
|
+
name: 'Claude Code',
|
|
14
|
+
command: 'claude',
|
|
15
|
+
installCommand: 'npm install -g @anthropic-ai/claude-code',
|
|
16
|
+
description: 'Anthropic Claude Code - Advanced AI coding assistant'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'gemini',
|
|
20
|
+
name: 'Gemini CLI',
|
|
21
|
+
command: 'gemini',
|
|
22
|
+
installCommand: 'npm install -g @google/gemini-cli',
|
|
23
|
+
description: 'Google Gemini CLI - Multi-modal AI assistant'
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const MAX_PARALLEL_COUNT = 10;
|
|
28
|
+
export const MIN_PARALLEL_COUNT = 1;
|
|
29
|
+
export const DEFAULT_PARALLEL_COUNT = 3;
|
|
30
|
+
|
|
31
|
+
export const BRANCH_POLL_INTERVAL = 5000; // 5 seconds
|
|
32
|
+
|
|
33
|
+
export const APP_NAME = 'hjWorktree CLI';
|
|
34
|
+
export const APP_VERSION = '2.0.0';
|
|
35
|
+
export const DEFAULT_PORT = 3847;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Navigation step types (4-step wizard)
|
|
2
|
+
export type NavigationStep = 'branch' | 'agent' | 'worktree' | 'running';
|
|
3
|
+
|
|
4
|
+
// Step status for LNB display
|
|
5
|
+
export type StepStatus = 'pending' | 'current' | 'completed';
|
|
6
|
+
|
|
7
|
+
// Step configuration
|
|
8
|
+
export interface StepConfig {
|
|
9
|
+
id: NavigationStep;
|
|
10
|
+
number: number;
|
|
11
|
+
label: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Step order constant
|
|
16
|
+
export const STEP_ORDER: NavigationStep[] = ['branch', 'agent', 'worktree', 'running'];
|
|
17
|
+
|
|
18
|
+
// Agent types
|
|
19
|
+
export type AgentId = 'codex' | 'claude' | 'gemini';
|
|
20
|
+
|
|
21
|
+
export interface AgentType {
|
|
22
|
+
id: AgentId;
|
|
23
|
+
name: string;
|
|
24
|
+
command: string;
|
|
25
|
+
installCommand: string;
|
|
26
|
+
description: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Branch types
|
|
30
|
+
export interface Branch {
|
|
31
|
+
name: string;
|
|
32
|
+
isCurrent: boolean;
|
|
33
|
+
isRemote: boolean;
|
|
34
|
+
lastCommit?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Running agent types
|
|
38
|
+
export type AgentStatus = 'initializing' | 'installing' | 'running' | 'stopped' | 'error';
|
|
39
|
+
|
|
40
|
+
export interface TerminalInfo {
|
|
41
|
+
sessionId: string;
|
|
42
|
+
worktreePath: string;
|
|
43
|
+
worktreeName: string;
|
|
44
|
+
branchName: string;
|
|
45
|
+
agentType: AgentId;
|
|
46
|
+
status: AgentStatus;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Worktree types
|
|
50
|
+
export interface Worktree {
|
|
51
|
+
path: string;
|
|
52
|
+
branch: string;
|
|
53
|
+
name: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Socket.IO event types
|
|
57
|
+
export interface TerminalCreateData {
|
|
58
|
+
sessionId: string;
|
|
59
|
+
worktreePath: string;
|
|
60
|
+
agentType: AgentId;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TerminalOutputData {
|
|
64
|
+
sessionId: string;
|
|
65
|
+
data: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TerminalInputData {
|
|
69
|
+
sessionId: string;
|
|
70
|
+
data: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface TerminalResizeData {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
cols: number;
|
|
76
|
+
rows: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface TerminalKillData {
|
|
80
|
+
sessionId: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// API response types
|
|
84
|
+
export interface CreateWorktreesRequest {
|
|
85
|
+
branch: string;
|
|
86
|
+
count: number;
|
|
87
|
+
agentType: AgentId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CreateWorktreesResponse {
|
|
91
|
+
worktrees: Worktree[];
|
|
92
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"outDir": "./dist",
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"isolatedModules": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["shared/**/*", "server/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "web", "src"]
|
|
20
|
+
}
|
package/web/index.html
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>hjWorktree CLI</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
package/web/src/App.tsx
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { useAppStore } from './stores/useAppStore.js';
|
|
3
|
+
import { useSocket } from './hooks/useSocket.js';
|
|
4
|
+
import Header from './components/Layout/Header.js';
|
|
5
|
+
import MainLayout from './components/Layout/MainLayout.js';
|
|
6
|
+
import BranchStep from './components/Steps/BranchStep.js';
|
|
7
|
+
import AgentStep from './components/Steps/AgentStep.js';
|
|
8
|
+
import WorktreeStep from './components/Steps/WorktreeStep.js';
|
|
9
|
+
import TerminalPanel from './components/Terminal/TerminalPanel.js';
|
|
10
|
+
|
|
11
|
+
function App() {
|
|
12
|
+
const { step, isLoading, loadingMessage, error } = useAppStore();
|
|
13
|
+
const { connected } = useSocket();
|
|
14
|
+
|
|
15
|
+
// Fetch initial data
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
useAppStore.getState().fetchProjectInfo();
|
|
18
|
+
useAppStore.getState().fetchBranches();
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
const renderStepContent = () => {
|
|
22
|
+
switch (step) {
|
|
23
|
+
case 'branch':
|
|
24
|
+
return <BranchStep />;
|
|
25
|
+
case 'agent':
|
|
26
|
+
return <AgentStep />;
|
|
27
|
+
case 'worktree':
|
|
28
|
+
return <WorktreeStep />;
|
|
29
|
+
case 'running':
|
|
30
|
+
return <TerminalPanel />;
|
|
31
|
+
default:
|
|
32
|
+
return <BranchStep />;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="app">
|
|
38
|
+
<Header />
|
|
39
|
+
|
|
40
|
+
<MainLayout>
|
|
41
|
+
{renderStepContent()}
|
|
42
|
+
</MainLayout>
|
|
43
|
+
|
|
44
|
+
{isLoading && (
|
|
45
|
+
<div className="loading-overlay">
|
|
46
|
+
<div className="loading-spinner" />
|
|
47
|
+
<p>{loadingMessage || 'Loading...'}</p>
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{error && (
|
|
52
|
+
<div className="error-banner">
|
|
53
|
+
{error}
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<div className="connection-status">
|
|
58
|
+
<span className={`dot ${connected ? 'connected' : 'disconnected'}`} />
|
|
59
|
+
{connected ? 'Connected' : 'Disconnected'}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default App;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore';
|
|
3
|
+
import { APP_NAME, APP_VERSION } from '../../../../shared/constants';
|
|
4
|
+
|
|
5
|
+
function Header() {
|
|
6
|
+
const projectInfo = useAppStore((state) => state.projectInfo);
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<header className="header">
|
|
10
|
+
<h1>{APP_NAME}</h1>
|
|
11
|
+
<div className="project-info">
|
|
12
|
+
{projectInfo && (
|
|
13
|
+
<>
|
|
14
|
+
<span className="branch">
|
|
15
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
16
|
+
<path d="M11.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5zm-2.25.75a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25zM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5zM3.5 3.25a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0z"/>
|
|
17
|
+
</svg>
|
|
18
|
+
{projectInfo.currentBranch || 'Unknown'}
|
|
19
|
+
</span>
|
|
20
|
+
<span>{projectInfo.cwd}</span>
|
|
21
|
+
</>
|
|
22
|
+
)}
|
|
23
|
+
<span>v{APP_VERSION}</span>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default Header;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
3
|
+
import type { NavigationStep, StepStatus } from '../../../../shared/types/index.js';
|
|
4
|
+
|
|
5
|
+
interface StepItemProps {
|
|
6
|
+
step: NavigationStep;
|
|
7
|
+
number: number;
|
|
8
|
+
label: string;
|
|
9
|
+
status: StepStatus;
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const STEP_CONFIG = [
|
|
15
|
+
{ id: 'branch' as const, number: 1, label: 'Branch Selection' },
|
|
16
|
+
{ id: 'agent' as const, number: 2, label: 'Agent Selection' },
|
|
17
|
+
{ id: 'worktree' as const, number: 3, label: 'Worktree Count' },
|
|
18
|
+
{ id: 'running' as const, number: 4, label: 'Running' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function StepItem({ number, label, status, onClick, disabled }: StepItemProps) {
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
className={`lnb-step ${status}`}
|
|
25
|
+
onClick={onClick}
|
|
26
|
+
disabled={disabled}
|
|
27
|
+
type="button"
|
|
28
|
+
>
|
|
29
|
+
<span className="step-number">
|
|
30
|
+
{status === 'completed' ? '✓' : number}
|
|
31
|
+
</span>
|
|
32
|
+
<span className="step-label">{label}</span>
|
|
33
|
+
</button>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function LeftNavBar() {
|
|
38
|
+
const { step, goToStep, canNavigateTo, getStepStatus } = useAppStore();
|
|
39
|
+
|
|
40
|
+
// Don't show LNB on running step
|
|
41
|
+
if (step === 'running') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<nav className="left-nav-bar">
|
|
47
|
+
<div className="lnb-header">
|
|
48
|
+
<span>Setup Steps</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="lnb-steps">
|
|
51
|
+
{STEP_CONFIG.map((config) => (
|
|
52
|
+
<StepItem
|
|
53
|
+
key={config.id}
|
|
54
|
+
step={config.id}
|
|
55
|
+
number={config.number}
|
|
56
|
+
label={config.label}
|
|
57
|
+
status={getStepStatus(config.id)}
|
|
58
|
+
onClick={() => goToStep(config.id)}
|
|
59
|
+
disabled={!canNavigateTo(config.id)}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</nav>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default LeftNavBar;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import LeftNavBar from './LeftNavBar.js';
|
|
3
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
4
|
+
|
|
5
|
+
interface MainLayoutProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function MainLayout({ children }: MainLayoutProps) {
|
|
10
|
+
const step = useAppStore((state) => state.step);
|
|
11
|
+
const showLNB = step !== 'running';
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className="main-layout">
|
|
15
|
+
{showLNB && <LeftNavBar />}
|
|
16
|
+
<div className={`main-content-area ${showLNB ? 'with-lnb' : 'full-width'}`}>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default MainLayout;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
3
|
+
|
|
4
|
+
interface StepContainerProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
showNavButtons?: boolean;
|
|
9
|
+
nextLabel?: string;
|
|
10
|
+
onNext?: () => void;
|
|
11
|
+
nextDisabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function StepContainer({
|
|
15
|
+
children,
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
showNavButtons = true,
|
|
19
|
+
nextLabel = 'Next',
|
|
20
|
+
onNext,
|
|
21
|
+
nextDisabled = false,
|
|
22
|
+
}: StepContainerProps) {
|
|
23
|
+
const { step, goToNext, goToPrev, isStepComplete } = useAppStore();
|
|
24
|
+
|
|
25
|
+
const isFirstStep = step === 'branch';
|
|
26
|
+
const canProceed = isStepComplete(step);
|
|
27
|
+
|
|
28
|
+
const handleNext = () => {
|
|
29
|
+
if (onNext) {
|
|
30
|
+
onNext();
|
|
31
|
+
} else {
|
|
32
|
+
goToNext();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="step-container">
|
|
38
|
+
<div className="step-header">
|
|
39
|
+
<h2>{title}</h2>
|
|
40
|
+
{description && <p>{description}</p>}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="step-content">
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{showNavButtons && (
|
|
48
|
+
<div className="step-navigation">
|
|
49
|
+
<button
|
|
50
|
+
className="nav-button prev"
|
|
51
|
+
onClick={goToPrev}
|
|
52
|
+
disabled={isFirstStep}
|
|
53
|
+
type="button"
|
|
54
|
+
>
|
|
55
|
+
Previous
|
|
56
|
+
</button>
|
|
57
|
+
<button
|
|
58
|
+
className="nav-button next"
|
|
59
|
+
onClick={handleNext}
|
|
60
|
+
disabled={nextDisabled || !canProceed}
|
|
61
|
+
type="button"
|
|
62
|
+
>
|
|
63
|
+
{nextLabel}
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default StepContainer;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore';
|
|
3
|
+
import { AI_AGENTS } from '../../../../shared/constants';
|
|
4
|
+
|
|
5
|
+
function AgentSelector() {
|
|
6
|
+
const { selectedAgent, setSelectedAgent } = useAppStore();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="setup-section">
|
|
10
|
+
<label>Select AI Agent</label>
|
|
11
|
+
<div className="agent-cards">
|
|
12
|
+
{AI_AGENTS.map((agent) => (
|
|
13
|
+
<div
|
|
14
|
+
key={agent.id}
|
|
15
|
+
className={`agent-card ${selectedAgent === agent.id ? 'selected' : ''}`}
|
|
16
|
+
onClick={() => setSelectedAgent(agent.id)}
|
|
17
|
+
>
|
|
18
|
+
<h4>{agent.name}</h4>
|
|
19
|
+
<p>{agent.description}</p>
|
|
20
|
+
</div>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default AgentSelector;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore';
|
|
3
|
+
|
|
4
|
+
function BranchSelector() {
|
|
5
|
+
const { branches, selectedBranch, setSelectedBranch } = useAppStore();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="setup-section">
|
|
9
|
+
<label htmlFor="branch-select">Select Branch</label>
|
|
10
|
+
<select
|
|
11
|
+
id="branch-select"
|
|
12
|
+
value={selectedBranch || ''}
|
|
13
|
+
onChange={(e) => setSelectedBranch(e.target.value || null)}
|
|
14
|
+
>
|
|
15
|
+
<option value="">-- Select a branch --</option>
|
|
16
|
+
{branches.map((branch) => (
|
|
17
|
+
<option key={branch.name} value={branch.name}>
|
|
18
|
+
{branch.name}
|
|
19
|
+
{branch.isCurrent ? ' (current)' : ''}
|
|
20
|
+
{branch.isRemote ? ' (remote)' : ''}
|
|
21
|
+
</option>
|
|
22
|
+
))}
|
|
23
|
+
</select>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default BranchSelector;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore';
|
|
3
|
+
import BranchSelector from './BranchSelector';
|
|
4
|
+
import AgentSelector from './AgentSelector';
|
|
5
|
+
import WorktreeCountSelector from './WorktreeCountSelector';
|
|
6
|
+
|
|
7
|
+
function SetupPanel() {
|
|
8
|
+
const { selectedBranch, isLoading, execute } = useAppStore();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="setup-panel">
|
|
12
|
+
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
|
|
13
|
+
<h2>Configure Parallel Agents</h2>
|
|
14
|
+
<p>Set up multiple AI coding agents in separate git worktrees</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<BranchSelector />
|
|
18
|
+
<AgentSelector />
|
|
19
|
+
<WorktreeCountSelector />
|
|
20
|
+
|
|
21
|
+
<button
|
|
22
|
+
className="execute-button"
|
|
23
|
+
onClick={execute}
|
|
24
|
+
disabled={!selectedBranch || isLoading}
|
|
25
|
+
>
|
|
26
|
+
{isLoading ? 'Creating...' : 'Execute'}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default SetupPanel;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore';
|
|
3
|
+
import { MIN_PARALLEL_COUNT, MAX_PARALLEL_COUNT } from '../../../../shared/constants';
|
|
4
|
+
|
|
5
|
+
function WorktreeCountSelector() {
|
|
6
|
+
const { worktreeCount, incrementWorktreeCount, decrementWorktreeCount } = useAppStore();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className="setup-section">
|
|
10
|
+
<label>Number of Worktrees</label>
|
|
11
|
+
<div className="counter">
|
|
12
|
+
<button
|
|
13
|
+
onClick={decrementWorktreeCount}
|
|
14
|
+
disabled={worktreeCount <= MIN_PARALLEL_COUNT}
|
|
15
|
+
>
|
|
16
|
+
-
|
|
17
|
+
</button>
|
|
18
|
+
<span>{worktreeCount}</span>
|
|
19
|
+
<button
|
|
20
|
+
onClick={incrementWorktreeCount}
|
|
21
|
+
disabled={worktreeCount >= MAX_PARALLEL_COUNT}
|
|
22
|
+
>
|
|
23
|
+
+
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default WorktreeCountSelector;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import StepContainer from '../Layout/StepContainer.js';
|
|
3
|
+
import AgentSelector from '../Setup/AgentSelector.js';
|
|
4
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
5
|
+
|
|
6
|
+
function AgentStep() {
|
|
7
|
+
const selectedAgent = useAppStore((state) => state.selectedAgent);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<StepContainer
|
|
11
|
+
title="Select AI Agent"
|
|
12
|
+
description="Choose which AI coding agent to run in each worktree"
|
|
13
|
+
nextDisabled={!selectedAgent}
|
|
14
|
+
>
|
|
15
|
+
<AgentSelector />
|
|
16
|
+
</StepContainer>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default AgentStep;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import StepContainer from '../Layout/StepContainer.js';
|
|
3
|
+
import BranchSelector from '../Setup/BranchSelector.js';
|
|
4
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
5
|
+
|
|
6
|
+
function BranchStep() {
|
|
7
|
+
const selectedBranch = useAppStore((state) => state.selectedBranch);
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<StepContainer
|
|
11
|
+
title="Select Branch"
|
|
12
|
+
description="Choose the Git branch to create worktrees from"
|
|
13
|
+
nextDisabled={!selectedBranch}
|
|
14
|
+
>
|
|
15
|
+
<BranchSelector />
|
|
16
|
+
</StepContainer>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default BranchStep;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import StepContainer from '../Layout/StepContainer.js';
|
|
3
|
+
import WorktreeCountSelector from '../Setup/WorktreeCountSelector.js';
|
|
4
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
5
|
+
import { AI_AGENTS } from '../../../../shared/constants.js';
|
|
6
|
+
|
|
7
|
+
function WorktreeStep() {
|
|
8
|
+
const { execute, isLoading, selectedBranch, selectedAgent, worktreeCount } = useAppStore();
|
|
9
|
+
|
|
10
|
+
const agentName = AI_AGENTS.find(a => a.id === selectedAgent)?.name || selectedAgent;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<StepContainer
|
|
14
|
+
title="Configure Worktrees"
|
|
15
|
+
description="Set the number of parallel worktrees to create"
|
|
16
|
+
nextLabel={isLoading ? 'Creating...' : 'Execute'}
|
|
17
|
+
onNext={execute}
|
|
18
|
+
nextDisabled={!selectedBranch || isLoading || worktreeCount < 1}
|
|
19
|
+
>
|
|
20
|
+
<WorktreeCountSelector />
|
|
21
|
+
|
|
22
|
+
<div className="execution-summary">
|
|
23
|
+
<h4>Summary</h4>
|
|
24
|
+
<div className="summary-item">
|
|
25
|
+
<span className="summary-label">Branch:</span>
|
|
26
|
+
<span className="summary-value">{selectedBranch}</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="summary-item">
|
|
29
|
+
<span className="summary-label">Agent:</span>
|
|
30
|
+
<span className="summary-value">{agentName}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="summary-item">
|
|
33
|
+
<span className="summary-label">Worktrees:</span>
|
|
34
|
+
<span className="summary-value">{worktreeCount}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</StepContainer>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default WorktreeStep;
|