hjworktree-cli 2.2.0 → 2.4.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/.claude/settings.local.json +11 -2
- package/.context-snapshots/context-snapshot-20260106-211500.md +85 -0
- package/README.md +26 -10
- package/dist/server/socketHandlers.d.ts.map +1 -1
- package/dist/server/socketHandlers.js +58 -23
- package/dist/server/socketHandlers.js.map +1 -1
- package/dist/shared/constants.d.ts +1 -3
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +1 -24
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/types/index.d.ts +3 -15
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/index.js.map +1 -1
- package/dist/web/assets/index-D-hASqdI.js +53 -0
- package/dist/web/assets/index-D-hASqdI.js.map +1 -0
- package/dist/web/assets/index-Dgl6wRHk.css +32 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- package/scripts/fix-pty-permissions.js +89 -0
- package/server/socketHandlers.ts +66 -28
- package/shared/constants.ts +1 -27
- package/shared/types/index.ts +6 -21
- package/web/src/App.tsx +8 -6
- package/web/src/components/Layout/LeftNavBar.tsx +6 -17
- package/web/src/components/Modals/AddWorktreeModal.tsx +1 -21
- package/web/src/components/Steps/WorktreeStep.tsx +1 -8
- package/web/src/components/Terminal/SplitTerminalView.tsx +64 -0
- package/web/src/components/Terminal/TerminalPanel.tsx +3 -69
- package/web/src/components/Terminal/XTerminal.tsx +4 -6
- package/web/src/stores/useAppStore.ts +77 -35
- package/web/src/styles/global.css +127 -77
- package/dist/web/assets/index-CsixHL-D.css +0 -32
- package/dist/web/assets/index-D8dr9mJa.js +0 -53
- package/dist/web/assets/index-D8dr9mJa.js.map +0 -1
- package/web/src/components/Setup/AgentSelector.tsx +0 -27
- package/web/src/components/Steps/AgentStep.tsx +0 -20
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { useAppStore } from '../../stores/useAppStore.js';
|
|
3
|
+
import XTerminal from './XTerminal.js';
|
|
4
|
+
import type { TerminalInfo } from '../../../../shared/types/index.js';
|
|
5
|
+
|
|
6
|
+
function SplitTerminalView() {
|
|
7
|
+
const {
|
|
8
|
+
terminals,
|
|
9
|
+
activeSessionId,
|
|
10
|
+
setActiveSession,
|
|
11
|
+
} = useAppStore();
|
|
12
|
+
|
|
13
|
+
// Handle single click to activate
|
|
14
|
+
const handleClick = useCallback((sessionId: string) => {
|
|
15
|
+
setActiveSession(sessionId);
|
|
16
|
+
}, [setActiveSession]);
|
|
17
|
+
|
|
18
|
+
// Render a terminal pane
|
|
19
|
+
const renderPane = (terminal: TerminalInfo) => {
|
|
20
|
+
const isActive = terminal.sessionId === activeSessionId;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
key={terminal.sessionId}
|
|
25
|
+
className={`terminal-pane ${isActive ? 'active' : ''}`}
|
|
26
|
+
onClick={() => handleClick(terminal.sessionId)}
|
|
27
|
+
>
|
|
28
|
+
<div className="pane-header">
|
|
29
|
+
<span className={`pane-status ${terminal.status}`} />
|
|
30
|
+
<span className="pane-title">{terminal.worktreeName}</span>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="pane-terminal">
|
|
33
|
+
<XTerminal
|
|
34
|
+
sessionId={terminal.sessionId}
|
|
35
|
+
worktreePath={terminal.worktreePath}
|
|
36
|
+
isActive={isActive}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Render split grid view
|
|
44
|
+
if (terminals.length === 0) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="terminal-grid-container">
|
|
47
|
+
<div className="empty-grid-message">
|
|
48
|
+
<p>No active sessions</p>
|
|
49
|
+
<p>Use the Setup wizard or click "+ Add Session" in the sidebar</p>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="terminal-grid-container scrollable">
|
|
57
|
+
<div className="terminal-grid cols-2">
|
|
58
|
+
{terminals.map(terminal => renderPane(terminal))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default SplitTerminalView;
|
|
@@ -1,52 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { useAppStore } from '../../stores/useAppStore.js';
|
|
3
|
-
import
|
|
4
|
-
import XTerminal from './XTerminal.js';
|
|
3
|
+
import SplitTerminalView from './SplitTerminalView.js';
|
|
5
4
|
|
|
6
5
|
function TerminalPanel() {
|
|
7
6
|
const {
|
|
8
7
|
terminals,
|
|
9
|
-
activeTerminalIndex,
|
|
10
|
-
activeSessionId,
|
|
11
|
-
setActiveSession,
|
|
12
|
-
removeTerminal,
|
|
13
|
-
clearAllTerminals,
|
|
14
8
|
openModal,
|
|
15
9
|
} = useAppStore();
|
|
16
|
-
const { emit } = useSocket();
|
|
17
|
-
|
|
18
|
-
const handleCloseTerminal = async (sessionId: string, e: React.MouseEvent) => {
|
|
19
|
-
e.stopPropagation();
|
|
20
|
-
|
|
21
|
-
// Kill the terminal via socket
|
|
22
|
-
emit('terminal:kill', { sessionId });
|
|
23
|
-
|
|
24
|
-
// Find the worktree name to delete
|
|
25
|
-
const terminal = terminals.find(t => t.sessionId === sessionId);
|
|
26
|
-
if (terminal) {
|
|
27
|
-
try {
|
|
28
|
-
await fetch(`/api/worktrees/${terminal.worktreeName}`, {
|
|
29
|
-
method: 'DELETE',
|
|
30
|
-
});
|
|
31
|
-
} catch (error) {
|
|
32
|
-
console.error('Failed to delete worktree:', error);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
removeTerminal(sessionId);
|
|
37
|
-
};
|
|
38
10
|
|
|
39
11
|
const handleCloseAll = () => {
|
|
40
12
|
const sessionIds = terminals.map(t => t.sessionId);
|
|
41
13
|
openModal('confirmDelete', { sessionIds });
|
|
42
14
|
};
|
|
43
15
|
|
|
44
|
-
// Find active index based on sessionId
|
|
45
|
-
const currentActiveIndex = activeSessionId
|
|
46
|
-
? terminals.findIndex(t => t.sessionId === activeSessionId)
|
|
47
|
-
: activeTerminalIndex;
|
|
48
|
-
const effectiveActiveIndex = currentActiveIndex >= 0 ? currentActiveIndex : 0;
|
|
49
|
-
|
|
50
16
|
if (terminals.length === 0) {
|
|
51
17
|
return (
|
|
52
18
|
<div className="terminal-panel empty">
|
|
@@ -60,25 +26,7 @@ function TerminalPanel() {
|
|
|
60
26
|
|
|
61
27
|
return (
|
|
62
28
|
<div className="terminal-panel">
|
|
63
|
-
<div className="terminal-
|
|
64
|
-
{terminals.map((terminal, index) => (
|
|
65
|
-
<button
|
|
66
|
-
key={terminal.sessionId}
|
|
67
|
-
className={`terminal-tab ${terminal.sessionId === activeSessionId || index === effectiveActiveIndex ? 'active' : ''}`}
|
|
68
|
-
onClick={() => setActiveSession(terminal.sessionId)}
|
|
69
|
-
>
|
|
70
|
-
<span className={`status ${terminal.status}`} />
|
|
71
|
-
<span>{terminal.worktreeName}</span>
|
|
72
|
-
<button
|
|
73
|
-
className="close-btn"
|
|
74
|
-
onClick={(e) => handleCloseTerminal(terminal.sessionId, e)}
|
|
75
|
-
title="Close terminal"
|
|
76
|
-
>
|
|
77
|
-
×
|
|
78
|
-
</button>
|
|
79
|
-
</button>
|
|
80
|
-
))}
|
|
81
|
-
|
|
29
|
+
<div className="terminal-header">
|
|
82
30
|
<div className="terminal-actions">
|
|
83
31
|
<button className="danger" onClick={handleCloseAll}>
|
|
84
32
|
Close All
|
|
@@ -86,21 +34,7 @@ function TerminalPanel() {
|
|
|
86
34
|
</div>
|
|
87
35
|
</div>
|
|
88
36
|
|
|
89
|
-
<
|
|
90
|
-
{terminals.map((terminal, index) => (
|
|
91
|
-
<div
|
|
92
|
-
key={terminal.sessionId}
|
|
93
|
-
className={`terminal-wrapper ${index !== effectiveActiveIndex ? 'hidden' : ''}`}
|
|
94
|
-
>
|
|
95
|
-
<XTerminal
|
|
96
|
-
sessionId={terminal.sessionId}
|
|
97
|
-
worktreePath={terminal.worktreePath}
|
|
98
|
-
agentType={terminal.agentType}
|
|
99
|
-
isActive={index === effectiveActiveIndex}
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
102
|
-
))}
|
|
103
|
-
</div>
|
|
37
|
+
<SplitTerminalView />
|
|
104
38
|
</div>
|
|
105
39
|
);
|
|
106
40
|
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import React, { useEffect, useRef
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
2
|
import { Terminal } from '@xterm/xterm';
|
|
3
3
|
import { FitAddon } from '@xterm/addon-fit';
|
|
4
4
|
import { useSocket } from '../../hooks/useSocket';
|
|
5
5
|
import { useAppStore } from '../../stores/useAppStore';
|
|
6
|
-
import type {
|
|
6
|
+
import type { TerminalOutputData } from '../../../../shared/types/index';
|
|
7
7
|
import '@xterm/xterm/css/xterm.css';
|
|
8
8
|
|
|
9
9
|
interface XTerminalProps {
|
|
10
10
|
sessionId: string;
|
|
11
11
|
worktreePath: string;
|
|
12
|
-
agentType: AgentId;
|
|
13
12
|
isActive: boolean;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
function XTerminal({ sessionId, worktreePath,
|
|
15
|
+
function XTerminal({ sessionId, worktreePath, isActive }: XTerminalProps) {
|
|
17
16
|
const terminalRef = useRef<HTMLDivElement>(null);
|
|
18
17
|
const termRef = useRef<Terminal | null>(null);
|
|
19
18
|
const fitAddonRef = useRef<FitAddon | null>(null);
|
|
@@ -129,7 +128,6 @@ function XTerminal({ sessionId, worktreePath, agentType, isActive }: XTerminalPr
|
|
|
129
128
|
emit('terminal:create', {
|
|
130
129
|
sessionId,
|
|
131
130
|
worktreePath,
|
|
132
|
-
agentType,
|
|
133
131
|
});
|
|
134
132
|
|
|
135
133
|
// Send initial resize
|
|
@@ -148,7 +146,7 @@ function XTerminal({ sessionId, worktreePath, agentType, isActive }: XTerminalPr
|
|
|
148
146
|
off('terminal:error', handleError);
|
|
149
147
|
onDataDisposable.dispose();
|
|
150
148
|
};
|
|
151
|
-
}, [sessionId, worktreePath,
|
|
149
|
+
}, [sessionId, worktreePath, connected, emit, on, off, updateTerminalStatus]);
|
|
152
150
|
|
|
153
151
|
// Handle resize
|
|
154
152
|
useEffect(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
-
import type { Branch, TerminalInfo,
|
|
2
|
+
import type { Branch, TerminalInfo, TerminalStatus, NavigationStep, StepStatus, ModalType, SessionGroup } from '../../../shared/types/index.js';
|
|
3
3
|
import { STEP_ORDER } from '../../../shared/types/index.js';
|
|
4
4
|
import { DEFAULT_PARALLEL_COUNT, MIN_PARALLEL_COUNT, MAX_PARALLEL_COUNT } from '../../../shared/constants.js';
|
|
5
5
|
|
|
@@ -21,9 +21,6 @@ interface AppState {
|
|
|
21
21
|
branches: Branch[];
|
|
22
22
|
selectedBranch: string | null;
|
|
23
23
|
|
|
24
|
-
// Agent selection
|
|
25
|
-
selectedAgent: AgentId;
|
|
26
|
-
|
|
27
24
|
// Worktree count
|
|
28
25
|
worktreeCount: number;
|
|
29
26
|
|
|
@@ -31,15 +28,15 @@ interface AppState {
|
|
|
31
28
|
terminals: TerminalInfo[];
|
|
32
29
|
activeTerminalIndex: number;
|
|
33
30
|
|
|
34
|
-
// Session management
|
|
31
|
+
// Session management
|
|
35
32
|
activeSessionId: string | null;
|
|
36
33
|
selectedSessionIds: string[];
|
|
37
34
|
|
|
38
|
-
// LNB state
|
|
35
|
+
// LNB state
|
|
39
36
|
isSetupCollapsed: boolean;
|
|
40
37
|
collapsedBranches: string[];
|
|
41
38
|
|
|
42
|
-
// Modal state
|
|
39
|
+
// Modal state
|
|
43
40
|
modalType: ModalType;
|
|
44
41
|
modalData: { sessionIds?: string[]; branchName?: string } | null;
|
|
45
42
|
|
|
@@ -70,9 +67,6 @@ interface AppActions {
|
|
|
70
67
|
setSelectedBranch: (branch: string | null) => void;
|
|
71
68
|
fetchBranches: () => Promise<void>;
|
|
72
69
|
|
|
73
|
-
// Agent selection
|
|
74
|
-
setSelectedAgent: (agent: AgentId) => void;
|
|
75
|
-
|
|
76
70
|
// Worktree count
|
|
77
71
|
setWorktreeCount: (count: number) => void;
|
|
78
72
|
incrementWorktreeCount: () => void;
|
|
@@ -80,12 +74,12 @@ interface AppActions {
|
|
|
80
74
|
|
|
81
75
|
// Terminals
|
|
82
76
|
addTerminal: (terminal: TerminalInfo) => void;
|
|
83
|
-
updateTerminalStatus: (sessionId: string, status:
|
|
77
|
+
updateTerminalStatus: (sessionId: string, status: TerminalStatus) => void;
|
|
84
78
|
removeTerminal: (sessionId: string) => void;
|
|
85
79
|
setActiveTerminal: (index: number) => void;
|
|
86
80
|
clearAllTerminals: () => void;
|
|
87
81
|
|
|
88
|
-
// Session management
|
|
82
|
+
// Session management
|
|
89
83
|
setActiveSession: (sessionId: string | null) => void;
|
|
90
84
|
toggleSessionSelection: (sessionId: string) => void;
|
|
91
85
|
selectAllSessions: () => void;
|
|
@@ -93,16 +87,19 @@ interface AppActions {
|
|
|
93
87
|
removeSelectedSessions: () => Promise<void>;
|
|
94
88
|
getSessionGroups: () => SessionGroup[];
|
|
95
89
|
|
|
96
|
-
// LNB state
|
|
90
|
+
// LNB state
|
|
97
91
|
toggleSetupCollapse: () => void;
|
|
98
92
|
toggleBranchCollapse: (branchName: string) => void;
|
|
99
93
|
|
|
100
|
-
// Modal
|
|
94
|
+
// Modal
|
|
101
95
|
openModal: (type: ModalType, data?: { sessionIds?: string[]; branchName?: string }) => void;
|
|
102
96
|
closeModal: () => void;
|
|
103
97
|
|
|
104
|
-
// Single worktree creation
|
|
105
|
-
createSingleWorktree: (branch: string
|
|
98
|
+
// Single worktree creation
|
|
99
|
+
createSingleWorktree: (branch: string) => Promise<void>;
|
|
100
|
+
|
|
101
|
+
// Restore existing worktrees on page load
|
|
102
|
+
restoreExistingWorktrees: () => Promise<void>;
|
|
106
103
|
|
|
107
104
|
// Execute
|
|
108
105
|
execute: () => Promise<void>;
|
|
@@ -125,7 +122,6 @@ const initialState: AppState = {
|
|
|
125
122
|
projectInfo: null,
|
|
126
123
|
branches: [],
|
|
127
124
|
selectedBranch: null,
|
|
128
|
-
selectedAgent: 'claude',
|
|
129
125
|
worktreeCount: DEFAULT_PARALLEL_COUNT,
|
|
130
126
|
terminals: [],
|
|
131
127
|
activeTerminalIndex: 0,
|
|
@@ -206,13 +202,11 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
206
202
|
},
|
|
207
203
|
|
|
208
204
|
isStepComplete: (checkStep) => {
|
|
209
|
-
const { selectedBranch,
|
|
205
|
+
const { selectedBranch, worktreeCount, terminals } = get();
|
|
210
206
|
|
|
211
207
|
switch (checkStep) {
|
|
212
208
|
case 'branch':
|
|
213
209
|
return selectedBranch !== null;
|
|
214
|
-
case 'agent':
|
|
215
|
-
return selectedAgent !== null;
|
|
216
210
|
case 'worktree':
|
|
217
211
|
return worktreeCount >= MIN_PARALLEL_COUNT;
|
|
218
212
|
case 'running':
|
|
@@ -268,9 +262,6 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
268
262
|
}
|
|
269
263
|
},
|
|
270
264
|
|
|
271
|
-
// Agent selection
|
|
272
|
-
setSelectedAgent: (agent) => set({ selectedAgent: agent }),
|
|
273
|
-
|
|
274
265
|
// Worktree count
|
|
275
266
|
setWorktreeCount: (count) => set({
|
|
276
267
|
worktreeCount: Math.max(MIN_PARALLEL_COUNT, Math.min(MAX_PARALLEL_COUNT, count))
|
|
@@ -291,9 +282,13 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
291
282
|
},
|
|
292
283
|
|
|
293
284
|
// Terminals
|
|
294
|
-
addTerminal: (terminal) => set((state) =>
|
|
295
|
-
|
|
296
|
-
|
|
285
|
+
addTerminal: (terminal) => set((state) => {
|
|
286
|
+
// 이미 같은 sessionId가 있으면 무시
|
|
287
|
+
if (state.terminals.some(t => t.sessionId === terminal.sessionId)) {
|
|
288
|
+
return state;
|
|
289
|
+
}
|
|
290
|
+
return { terminals: [...state.terminals, terminal] };
|
|
291
|
+
}),
|
|
297
292
|
|
|
298
293
|
updateTerminalStatus: (sessionId, status) => set((state) => ({
|
|
299
294
|
terminals: state.terminals.map((t) =>
|
|
@@ -426,7 +421,7 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
426
421
|
closeModal: () => set({ modalType: null, modalData: null }),
|
|
427
422
|
|
|
428
423
|
// Single worktree creation
|
|
429
|
-
createSingleWorktree: async (branch
|
|
424
|
+
createSingleWorktree: async (branch) => {
|
|
430
425
|
const { setLoading, setError, addTerminal, setActiveSession } = get();
|
|
431
426
|
|
|
432
427
|
setLoading(true, 'Creating worktree...');
|
|
@@ -436,7 +431,7 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
436
431
|
const response = await fetch('/api/worktrees/single', {
|
|
437
432
|
method: 'POST',
|
|
438
433
|
headers: { 'Content-Type': 'application/json' },
|
|
439
|
-
body: JSON.stringify({ branch
|
|
434
|
+
body: JSON.stringify({ branch }),
|
|
440
435
|
});
|
|
441
436
|
|
|
442
437
|
if (!response.ok) {
|
|
@@ -445,14 +440,13 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
445
440
|
}
|
|
446
441
|
|
|
447
442
|
const { worktree } = await response.json();
|
|
448
|
-
const sessionId =
|
|
443
|
+
const sessionId = worktree.name;
|
|
449
444
|
|
|
450
445
|
addTerminal({
|
|
451
446
|
sessionId,
|
|
452
447
|
worktreePath: worktree.path,
|
|
453
448
|
worktreeName: worktree.name,
|
|
454
449
|
branchName: worktree.branch,
|
|
455
|
-
agentType,
|
|
456
450
|
status: 'initializing',
|
|
457
451
|
});
|
|
458
452
|
|
|
@@ -473,9 +467,59 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
473
467
|
}
|
|
474
468
|
},
|
|
475
469
|
|
|
470
|
+
// Restore existing worktrees on page load
|
|
471
|
+
restoreExistingWorktrees: async () => {
|
|
472
|
+
const { terminals } = get();
|
|
473
|
+
|
|
474
|
+
// 이미 터미널이 복구되어 있으면 실행하지 않음
|
|
475
|
+
if (terminals.length > 0) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
const response = await fetch('/api/worktrees');
|
|
481
|
+
if (!response.ok) return;
|
|
482
|
+
|
|
483
|
+
const worktrees = await response.json();
|
|
484
|
+
|
|
485
|
+
// Filter to only project worktrees (exclude main worktree)
|
|
486
|
+
const projectWorktrees = worktrees.filter(
|
|
487
|
+
(wt: { isMainWorktree: boolean; name: string }) =>
|
|
488
|
+
!wt.isMainWorktree && wt.name.includes('-project-')
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (projectWorktrees.length === 0) return;
|
|
492
|
+
|
|
493
|
+
const { addTerminal } = get();
|
|
494
|
+
|
|
495
|
+
// Restore terminals for each project worktree
|
|
496
|
+
for (const wt of projectWorktrees) {
|
|
497
|
+
addTerminal({
|
|
498
|
+
sessionId: wt.name,
|
|
499
|
+
worktreePath: wt.path,
|
|
500
|
+
worktreeName: wt.name,
|
|
501
|
+
branchName: wt.branch,
|
|
502
|
+
status: 'running',
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Transition to running state
|
|
507
|
+
set({
|
|
508
|
+
step: 'running',
|
|
509
|
+
completedSteps: ['branch', 'worktree'],
|
|
510
|
+
isSetupCollapsed: true,
|
|
511
|
+
activeSessionId: projectWorktrees[0].name,
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
console.log(`Restored ${projectWorktrees.length} worktree sessions`);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error('Failed to restore worktrees:', error);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
|
|
476
520
|
// Execute
|
|
477
521
|
execute: async () => {
|
|
478
|
-
const { selectedBranch, worktreeCount,
|
|
522
|
+
const { selectedBranch, worktreeCount, setLoading, setError, addTerminal } = get();
|
|
479
523
|
|
|
480
524
|
if (!selectedBranch) {
|
|
481
525
|
setError('Please select a branch');
|
|
@@ -492,7 +536,6 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
492
536
|
body: JSON.stringify({
|
|
493
537
|
branch: selectedBranch,
|
|
494
538
|
count: worktreeCount,
|
|
495
|
-
agentType: selectedAgent,
|
|
496
539
|
}),
|
|
497
540
|
});
|
|
498
541
|
|
|
@@ -505,13 +548,12 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
505
548
|
|
|
506
549
|
// Create terminal info for each worktree
|
|
507
550
|
for (const worktree of worktrees) {
|
|
508
|
-
const sessionId =
|
|
551
|
+
const sessionId = worktree.name;
|
|
509
552
|
addTerminal({
|
|
510
553
|
sessionId,
|
|
511
554
|
worktreePath: worktree.path,
|
|
512
555
|
worktreeName: worktree.name,
|
|
513
556
|
branchName: worktree.branch,
|
|
514
|
-
agentType: selectedAgent,
|
|
515
557
|
status: 'initializing',
|
|
516
558
|
});
|
|
517
559
|
}
|
|
@@ -520,7 +562,7 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
520
562
|
const { terminals: updatedTerminals } = get();
|
|
521
563
|
set({
|
|
522
564
|
step: 'running',
|
|
523
|
-
completedSteps: ['branch', '
|
|
565
|
+
completedSteps: ['branch', 'worktree'],
|
|
524
566
|
isSetupCollapsed: true,
|
|
525
567
|
activeSessionId: updatedTerminals.length > 0 ? updatedTerminals[0].sessionId : null,
|
|
526
568
|
});
|
|
@@ -169,42 +169,6 @@ body {
|
|
|
169
169
|
text-align: center;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
/* Agent cards */
|
|
173
|
-
.agent-cards {
|
|
174
|
-
display: grid;
|
|
175
|
-
grid-template-columns: repeat(3, 1fr);
|
|
176
|
-
gap: 12px;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.agent-card {
|
|
180
|
-
padding: 16px;
|
|
181
|
-
border: 2px solid var(--border-color);
|
|
182
|
-
border-radius: 8px;
|
|
183
|
-
background-color: var(--bg-tertiary);
|
|
184
|
-
cursor: pointer;
|
|
185
|
-
transition: all 0.2s;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.agent-card:hover {
|
|
189
|
-
border-color: var(--accent-blue);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.agent-card.selected {
|
|
193
|
-
border-color: var(--accent-blue);
|
|
194
|
-
background-color: rgba(88, 166, 255, 0.1);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.agent-card h4 {
|
|
198
|
-
font-size: 14px;
|
|
199
|
-
font-weight: 600;
|
|
200
|
-
margin-bottom: 4px;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.agent-card p {
|
|
204
|
-
font-size: 12px;
|
|
205
|
-
color: var(--text-muted);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
172
|
/* Execute button */
|
|
209
173
|
.execute-button {
|
|
210
174
|
padding: 14px 48px;
|
|
@@ -852,15 +816,6 @@ body {
|
|
|
852
816
|
white-space: nowrap;
|
|
853
817
|
}
|
|
854
818
|
|
|
855
|
-
.session-agent {
|
|
856
|
-
font-size: 10px;
|
|
857
|
-
font-weight: 600;
|
|
858
|
-
color: var(--text-muted);
|
|
859
|
-
background-color: var(--bg-tertiary);
|
|
860
|
-
padding: 2px 6px;
|
|
861
|
-
border-radius: 4px;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
819
|
.session-close {
|
|
865
820
|
padding: 2px 6px;
|
|
866
821
|
background: transparent;
|
|
@@ -1059,38 +1014,6 @@ body {
|
|
|
1059
1014
|
border-color: var(--accent-blue);
|
|
1060
1015
|
}
|
|
1061
1016
|
|
|
1062
|
-
/* Agent Options in Modal */
|
|
1063
|
-
.agent-options {
|
|
1064
|
-
display: flex;
|
|
1065
|
-
gap: 8px;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
.agent-option {
|
|
1069
|
-
flex: 1;
|
|
1070
|
-
padding: 12px;
|
|
1071
|
-
background-color: var(--bg-tertiary);
|
|
1072
|
-
border: 2px solid var(--border-color);
|
|
1073
|
-
border-radius: 6px;
|
|
1074
|
-
cursor: pointer;
|
|
1075
|
-
transition: all 0.2s;
|
|
1076
|
-
text-align: center;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
.agent-option:hover {
|
|
1080
|
-
border-color: var(--accent-blue);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
.agent-option.selected {
|
|
1084
|
-
border-color: var(--accent-blue);
|
|
1085
|
-
background-color: rgba(88, 166, 255, 0.1);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
.agent-option .agent-name {
|
|
1089
|
-
font-size: 12px;
|
|
1090
|
-
font-weight: 500;
|
|
1091
|
-
color: var(--text-primary);
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
1017
|
/* Modal Buttons */
|
|
1095
1018
|
.btn-primary {
|
|
1096
1019
|
padding: 10px 20px;
|
|
@@ -1215,3 +1138,130 @@ body {
|
|
|
1215
1138
|
font-size: 16px;
|
|
1216
1139
|
color: var(--text-secondary);
|
|
1217
1140
|
}
|
|
1141
|
+
|
|
1142
|
+
/* ========================================
|
|
1143
|
+
Split Terminal View Styles
|
|
1144
|
+
======================================== */
|
|
1145
|
+
|
|
1146
|
+
/* Terminal Header for Split View */
|
|
1147
|
+
.terminal-header {
|
|
1148
|
+
display: flex;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
gap: 12px;
|
|
1151
|
+
padding: 8px 16px;
|
|
1152
|
+
background-color: var(--bg-secondary);
|
|
1153
|
+
border-bottom: 1px solid var(--border-color);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/* Split Terminal Grid Container */
|
|
1157
|
+
.terminal-grid-container {
|
|
1158
|
+
flex: 1;
|
|
1159
|
+
display: flex;
|
|
1160
|
+
flex-direction: column;
|
|
1161
|
+
overflow: hidden;
|
|
1162
|
+
padding: 8px;
|
|
1163
|
+
background-color: var(--bg-primary);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.terminal-grid-container.scrollable {
|
|
1167
|
+
overflow-y: auto;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/* Terminal Grid */
|
|
1171
|
+
.terminal-grid {
|
|
1172
|
+
flex: 1;
|
|
1173
|
+
display: grid;
|
|
1174
|
+
gap: 8px;
|
|
1175
|
+
min-height: 0;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/* Grid layout - fixed 2 columns */
|
|
1179
|
+
.terminal-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
1180
|
+
|
|
1181
|
+
/* Scrollable grid for many sessions */
|
|
1182
|
+
.terminal-grid-container.scrollable .terminal-grid {
|
|
1183
|
+
grid-auto-rows: minmax(350px, 1fr);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/* Terminal Pane */
|
|
1187
|
+
.terminal-pane {
|
|
1188
|
+
position: relative;
|
|
1189
|
+
display: flex;
|
|
1190
|
+
flex-direction: column;
|
|
1191
|
+
border: 2px solid var(--border-color);
|
|
1192
|
+
border-radius: 8px;
|
|
1193
|
+
overflow: hidden;
|
|
1194
|
+
background-color: var(--bg-primary);
|
|
1195
|
+
min-height: 300px;
|
|
1196
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
.terminal-pane:hover {
|
|
1200
|
+
border-color: var(--text-muted);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.terminal-pane.active {
|
|
1204
|
+
border-color: var(--accent-green);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
/* Pane Header */
|
|
1209
|
+
.pane-header {
|
|
1210
|
+
display: flex;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
gap: 8px;
|
|
1213
|
+
padding: 6px 10px;
|
|
1214
|
+
background-color: var(--bg-secondary);
|
|
1215
|
+
border-bottom: 1px solid var(--border-color);
|
|
1216
|
+
min-height: 32px;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.pane-status {
|
|
1220
|
+
width: 8px;
|
|
1221
|
+
height: 8px;
|
|
1222
|
+
border-radius: 50%;
|
|
1223
|
+
flex-shrink: 0;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.pane-status.running { background-color: var(--accent-green); }
|
|
1227
|
+
.pane-status.initializing { background-color: var(--accent-yellow); }
|
|
1228
|
+
.pane-status.stopped { background-color: var(--text-muted); }
|
|
1229
|
+
.pane-status.error { background-color: var(--accent-red); }
|
|
1230
|
+
|
|
1231
|
+
.pane-title {
|
|
1232
|
+
flex: 1;
|
|
1233
|
+
font-size: 11px;
|
|
1234
|
+
font-weight: 500;
|
|
1235
|
+
color: var(--text-primary);
|
|
1236
|
+
white-space: nowrap;
|
|
1237
|
+
overflow: hidden;
|
|
1238
|
+
text-overflow: ellipsis;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/* Pane Terminal Container */
|
|
1242
|
+
.pane-terminal {
|
|
1243
|
+
flex: 1;
|
|
1244
|
+
position: relative;
|
|
1245
|
+
overflow: hidden;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.pane-terminal .terminal {
|
|
1249
|
+
height: 100%;
|
|
1250
|
+
width: 100%;
|
|
1251
|
+
padding: 4px;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/* Empty Grid Message */
|
|
1255
|
+
.empty-grid-message {
|
|
1256
|
+
display: flex;
|
|
1257
|
+
flex-direction: column;
|
|
1258
|
+
align-items: center;
|
|
1259
|
+
justify-content: center;
|
|
1260
|
+
height: 100%;
|
|
1261
|
+
color: var(--text-muted);
|
|
1262
|
+
gap: 16px;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.empty-grid-message p {
|
|
1266
|
+
font-size: 14px;
|
|
1267
|
+
}
|