hjworktree-cli 2.3.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.
Files changed (35) hide show
  1. package/.context-snapshots/context-snapshot-20260106-211500.md +85 -0
  2. package/README.md +18 -10
  3. package/dist/server/socketHandlers.d.ts.map +1 -1
  4. package/dist/server/socketHandlers.js +19 -18
  5. package/dist/server/socketHandlers.js.map +1 -1
  6. package/dist/shared/constants.d.ts +1 -3
  7. package/dist/shared/constants.d.ts.map +1 -1
  8. package/dist/shared/constants.js +1 -24
  9. package/dist/shared/constants.js.map +1 -1
  10. package/dist/shared/types/index.d.ts +3 -15
  11. package/dist/shared/types/index.d.ts.map +1 -1
  12. package/dist/shared/types/index.js +1 -1
  13. package/dist/shared/types/index.js.map +1 -1
  14. package/dist/web/assets/index-D-hASqdI.js +53 -0
  15. package/dist/web/assets/index-D-hASqdI.js.map +1 -0
  16. package/dist/web/assets/index-Dgl6wRHk.css +32 -0
  17. package/dist/web/index.html +2 -2
  18. package/package.json +1 -1
  19. package/server/socketHandlers.ts +24 -23
  20. package/shared/constants.ts +1 -27
  21. package/shared/types/index.ts +6 -21
  22. package/web/src/App.tsx +8 -6
  23. package/web/src/components/Layout/LeftNavBar.tsx +6 -17
  24. package/web/src/components/Modals/AddWorktreeModal.tsx +1 -21
  25. package/web/src/components/Steps/WorktreeStep.tsx +1 -8
  26. package/web/src/components/Terminal/SplitTerminalView.tsx +64 -0
  27. package/web/src/components/Terminal/TerminalPanel.tsx +3 -69
  28. package/web/src/components/Terminal/XTerminal.tsx +4 -6
  29. package/web/src/stores/useAppStore.ts +77 -35
  30. package/web/src/styles/global.css +127 -77
  31. package/dist/web/assets/index-CsixHL-D.css +0 -32
  32. package/dist/web/assets/index-De6xm4hO.js +0 -53
  33. package/dist/web/assets/index-De6xm4hO.js.map +0 -1
  34. package/web/src/components/Setup/AgentSelector.tsx +0 -27
  35. package/web/src/components/Steps/AgentStep.tsx +0 -20
@@ -1,19 +1,18 @@
1
- import React, { useEffect, useRef, useCallback } from 'react';
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 { AgentId, TerminalOutputData } from '../../../../shared/types/index';
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, agentType, isActive }: XTerminalProps) {
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, agentType, connected, emit, on, off, updateTerminalStatus]);
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, AgentId, AgentStatus, NavigationStep, StepStatus, ModalType, SessionGroup } from '../../../shared/types/index.js';
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 (new)
31
+ // Session management
35
32
  activeSessionId: string | null;
36
33
  selectedSessionIds: string[];
37
34
 
38
- // LNB state (new)
35
+ // LNB state
39
36
  isSetupCollapsed: boolean;
40
37
  collapsedBranches: string[];
41
38
 
42
- // Modal state (new)
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: AgentStatus) => void;
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 (new)
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 (new)
90
+ // LNB state
97
91
  toggleSetupCollapse: () => void;
98
92
  toggleBranchCollapse: (branchName: string) => void;
99
93
 
100
- // Modal (new)
94
+ // Modal
101
95
  openModal: (type: ModalType, data?: { sessionIds?: string[]; branchName?: string }) => void;
102
96
  closeModal: () => void;
103
97
 
104
- // Single worktree creation (new)
105
- createSingleWorktree: (branch: string, agentType: AgentId) => Promise<void>;
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, selectedAgent, worktreeCount, terminals } = get();
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
- terminals: [...state.terminals, terminal]
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, agentType) => {
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, agentType }),
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 = `${worktree.name}-${Date.now()}`;
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, selectedAgent, setLoading, setError, setStep, addTerminal } = get();
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 = `${worktree.name}-${Date.now()}`;
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', 'agent', 'worktree'],
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
+ }
@@ -1,32 +0,0 @@
1
- /**
2
- * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
- * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
- * https://github.com/chjj/term.js
5
- * @license MIT
6
- *
7
- * Permission is hereby granted, free of charge, to any person obtaining a copy
8
- * of this software and associated documentation files (the "Software"), to deal
9
- * in the Software without restriction, including without limitation the rights
10
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- * copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions:
13
- *
14
- * The above copyright notice and this permission notice shall be included in
15
- * all copies or substantial portions of the Software.
16
- *
17
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- * THE SOFTWARE.
24
- *
25
- * Originally forked from (with the author's permission):
26
- * Fabrice Bellard's javascript vt100 for jslinux:
27
- * http://bellard.org/jslinux/
28
- * Copyright (c) 2011 Fabrice Bellard
29
- * The original design remains. The terminal itself
30
- * has been extended to include xterm CSI codes, among
31
- * other features.
32
- */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: #0d1117;--bg-secondary: #161b22;--bg-tertiary: #21262d;--border-color: #30363d;--text-primary: #c9d1d9;--text-secondary: #8b949e;--text-muted: #6e7681;--accent-blue: #58a6ff;--accent-green: #3fb950;--accent-yellow: #d29922;--accent-red: #f85149;--accent-purple: #a371f7}html,body,#root{height:100%;width:100%}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background-color:var(--bg-primary);color:var(--text-primary);line-height:1.5}.app{display:flex;flex-direction:column;height:100vh;overflow:hidden}.header{display:flex;align-items:center;justify-content:space-between;padding:12px 24px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color)}.header h1{font-size:18px;font-weight:600;color:var(--text-primary)}.header .project-info{display:flex;align-items:center;gap:12px;color:var(--text-secondary);font-size:13px}.header .project-info .branch{display:flex;align-items:center;gap:4px;color:var(--accent-green)}.main-content{flex:1;display:flex;flex-direction:column;overflow:hidden}.setup-panel{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:32px}.setup-panel h2{font-size:24px;font-weight:600;margin-bottom:8px}.setup-panel p{color:var(--text-secondary);font-size:14px}.setup-section{width:100%;max-width:500px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:20px}.setup-section label{display:block;font-size:14px;font-weight:500;margin-bottom:8px;color:var(--text-primary)}.setup-section select,.setup-section input{width:100%;padding:10px 12px;border:1px solid var(--border-color);border-radius:6px;background-color:var(--bg-tertiary);color:var(--text-primary);font-size:14px;font-family:inherit}.setup-section select:focus,.setup-section input:focus{outline:none;border-color:var(--accent-blue)}.setup-section .counter{display:flex;align-items:center;gap:16px}.setup-section .counter button{width:40px;height:40px;border:1px solid var(--border-color);border-radius:6px;background-color:var(--bg-tertiary);color:var(--text-primary);font-size:20px;cursor:pointer;transition:all .2s}.setup-section .counter button:hover{background-color:var(--bg-primary);border-color:var(--accent-blue)}.setup-section .counter button:disabled{opacity:.5;cursor:not-allowed}.setup-section .counter span{font-size:24px;font-weight:600;min-width:60px;text-align:center}.agent-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.agent-card{padding:16px;border:2px solid var(--border-color);border-radius:8px;background-color:var(--bg-tertiary);cursor:pointer;transition:all .2s}.agent-card:hover{border-color:var(--accent-blue)}.agent-card.selected{border-color:var(--accent-blue);background-color:#58a6ff1a}.agent-card h4{font-size:14px;font-weight:600;margin-bottom:4px}.agent-card p{font-size:12px;color:var(--text-muted)}.execute-button{padding:14px 48px;background-color:var(--accent-green);color:var(--bg-primary);border:none;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer;transition:all .2s}.execute-button:hover{filter:brightness(1.1)}.execute-button:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.terminal-panel{display:flex;flex-direction:column;height:100%;overflow:hidden}.terminal-tabs{display:flex;align-items:center;gap:4px;padding:8px 16px;background-color:var(--bg-secondary);border-bottom:1px solid var(--border-color);overflow-x:auto}.terminal-tab{display:flex;align-items:center;gap:8px;padding:8px 16px;background-color:transparent;border:1px solid transparent;border-radius:6px;color:var(--text-secondary);font-size:13px;cursor:pointer;transition:all .2s;white-space:nowrap}.terminal-tab:hover{background-color:var(--bg-tertiary)}.terminal-tab.active{background-color:var(--bg-tertiary);border-color:var(--border-color);color:var(--text-primary)}.terminal-tab .status{width:8px;height:8px;border-radius:50%}.terminal-tab .status.running{background-color:var(--accent-green)}.terminal-tab .status.initializing{background-color:var(--accent-yellow)}.terminal-tab .status.stopped{background-color:var(--text-muted)}.terminal-tab .status.error{background-color:var(--accent-red)}.terminal-tab .close-btn{padding:2px 4px;background:transparent;border:none;color:var(--text-muted);cursor:pointer;border-radius:4px;line-height:1}.terminal-tab .close-btn:hover{background-color:var(--bg-primary);color:var(--accent-red)}.terminal-actions{display:flex;align-items:center;gap:8px;margin-left:auto;padding-left:16px}.terminal-actions button{padding:6px 12px;background-color:transparent;border:1px solid var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:12px;cursor:pointer;transition:all .2s}.terminal-actions button:hover{background-color:var(--bg-tertiary);color:var(--text-primary)}.terminal-actions button.danger:hover{border-color:var(--accent-red);color:var(--accent-red)}.terminal-container{flex:1;position:relative;overflow:hidden}.terminal-wrapper{position:absolute;top:0;left:0;right:0;bottom:0;padding:8px}.terminal-wrapper.hidden{visibility:hidden}.terminal{height:100%;width:100%}.loading-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#0d1117e6;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;z-index:1000}.loading-spinner{width:48px;height:48px;border:3px solid var(--border-color);border-top-color:var(--accent-blue);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.loading-overlay p{color:var(--text-secondary);font-size:14px}.connection-status{position:fixed;bottom:16px;right:16px;display:flex;align-items:center;gap:8px;padding:8px 12px;background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:6px;font-size:12px;color:var(--text-secondary)}.connection-status .dot{width:8px;height:8px;border-radius:50%}.connection-status .dot.connected{background-color:var(--accent-green)}.connection-status .dot.disconnected{background-color:var(--accent-red)}.error-banner{padding:12px 16px;background-color:#f851491a;border:1px solid var(--accent-red);border-radius:6px;color:var(--accent-red);font-size:14px;text-align:center}.main-layout{display:flex;flex:1;overflow:hidden}.main-content-area{flex:1;display:flex;flex-direction:column;overflow:hidden}.main-content-area.with-lnb{margin-left:0}.main-content-area.full-width{width:100%}.left-nav-bar{width:260px;background-color:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;flex-shrink:0}.lnb-header{padding:20px;border-bottom:1px solid var(--border-color);font-size:14px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px}.lnb-steps{padding:16px;display:flex;flex-direction:column;gap:8px}.lnb-step{display:flex;align-items:center;gap:12px;padding:14px 16px;background:transparent;border:1px solid transparent;border-radius:8px;text-align:left;cursor:pointer;transition:all .2s ease;width:100%;font-family:inherit}.lnb-step:hover:not(:disabled){background-color:var(--bg-tertiary)}.lnb-step:disabled{cursor:not-allowed;opacity:.5}.step-number{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;flex-shrink:0;transition:all .2s ease}.lnb-step.pending .step-number{background-color:var(--bg-tertiary);color:var(--text-muted);border:2px solid var(--border-color)}.lnb-step.pending .step-label{color:var(--text-muted)}.lnb-step.current{background-color:#58a6ff1a;border-color:var(--accent-blue)}.lnb-step.current .step-number{background-color:var(--accent-blue);color:var(--bg-primary)}.lnb-step.current .step-label{color:var(--text-primary);font-weight:500}.lnb-step.completed .step-number{background-color:var(--accent-green);color:var(--bg-primary)}.lnb-step.completed .step-label{color:var(--text-primary)}.step-label{font-size:14px;flex:1}.step-container{flex:1;display:flex;flex-direction:column;padding:40px;max-width:600px;margin:0 auto;width:100%}.step-header{text-align:center;margin-bottom:32px}.step-header h2{font-size:24px;font-weight:600;margin-bottom:8px;color:var(--text-primary)}.step-header p{color:var(--text-secondary);font-size:14px}.step-content{flex:1;display:flex;flex-direction:column;gap:24px}.step-navigation{display:flex;justify-content:space-between;padding-top:32px;margin-top:auto;border-top:1px solid var(--border-color)}.nav-button{padding:12px 32px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:all .2s ease;font-family:inherit}.nav-button.prev{background:transparent;border:1px solid var(--border-color);color:var(--text-secondary)}.nav-button.prev:hover:not(:disabled){background-color:var(--bg-tertiary);color:var(--text-primary)}.nav-button.prev:disabled{opacity:.3;cursor:not-allowed}.nav-button.next{background-color:var(--accent-blue);border:none;color:#fff}.nav-button.next:hover:not(:disabled){filter:brightness(1.1)}.nav-button.next:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.execution-summary{background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;padding:20px}.execution-summary h4{font-size:14px;font-weight:600;margin-bottom:16px;color:var(--text-primary);padding-bottom:12px;border-bottom:1px solid var(--border-color)}.summary-item{display:flex;justify-content:space-between;align-items:center;padding:8px 0}.summary-label{font-size:13px;color:var(--text-secondary)}.summary-value{font-size:13px;font-weight:500;color:var(--text-primary)}.left-nav-bar{width:280px;background-color:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}.lnb-section{border-bottom:1px solid var(--border-color)}.lnb-section-header{display:flex;align-items:center;justify-content:space-between;padding:16px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .2s}.lnb-section-header:hover{background-color:var(--bg-tertiary)}.section-title{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.collapse-icon{font-size:14px;color:var(--text-muted)}.sessions-section{flex:1;display:flex;flex-direction:column;min-height:0}.session-count{font-size:11px;background-color:var(--bg-tertiary);color:var(--text-secondary);padding:2px 8px;border-radius:10px;margin-left:8px}.session-list{flex:1;overflow-y:auto;padding:8px}.session-group{margin-bottom:8px}.session-group-header{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;border-radius:6px;transition:background-color .2s}.session-group-header:hover{background-color:var(--bg-tertiary)}.group-collapse-icon{font-size:10px;color:var(--text-muted)}.group-name{font-size:12px;font-weight:500;color:var(--text-secondary);flex:1}.group-count{font-size:11px;color:var(--text-muted)}.session-group-items{padding-left:12px}.session-item{display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;border-radius:6px;margin:2px 0;border:1px solid transparent;transition:all .2s}.session-item:hover{background-color:var(--bg-tertiary)}.session-item.active{background-color:#58a6ff1a;border-color:var(--accent-blue)}.session-item.selected{background-color:#58a6ff0d}.session-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--accent-blue)}.status-indicator{width:8px;height:8px;border-radius:50%;flex-shrink:0}.session-name{flex:1;font-size:12px;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-agent{font-size:10px;font-weight:600;color:var(--text-muted);background-color:var(--bg-tertiary);padding:2px 6px;border-radius:4px}.session-close{padding:2px 6px;background:transparent;border:none;color:var(--text-muted);cursor:pointer;border-radius:4px;font-size:14px;line-height:1;opacity:0;transition:all .2s}.session-item:hover .session-close{opacity:1}.session-close:hover{background-color:var(--bg-primary);color:var(--accent-red)}.add-session-btn{margin:8px 16px 16px;padding:10px;background-color:transparent;border:1px dashed var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:12px;cursor:pointer;transition:all .2s}.add-session-btn:hover{border-color:var(--accent-blue);color:var(--accent-blue);background-color:#58a6ff0d}.bulk-actions-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;background-color:var(--bg-tertiary);border-top:1px solid var(--border-color)}.selected-count{font-size:12px;color:var(--text-secondary);flex:1}.bulk-delete-btn{padding:6px 12px;background-color:var(--accent-red);border:none;border-radius:4px;color:#fff;font-size:11px;font-weight:500;cursor:pointer;transition:filter .2s}.bulk-delete-btn:hover{filter:brightness(1.1)}.bulk-cancel-btn{padding:6px 12px;background-color:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);font-size:11px;cursor:pointer;transition:all .2s}.bulk-cancel-btn:hover{background-color:var(--bg-primary);color:var(--text-primary)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#000000b3;display:flex;align-items:center;justify-content:center;z-index:1000}.modal-content{background-color:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;width:100%;max-width:440px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column}.modal-confirm{max-width:400px}.modal-header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--border-color)}.modal-header h2{font-size:16px;font-weight:600;color:var(--text-primary)}.modal-close{padding:4px 8px;background:transparent;border:none;color:var(--text-muted);font-size:20px;cursor:pointer;border-radius:4px;line-height:1}.modal-close:hover{background-color:var(--bg-tertiary);color:var(--text-primary)}.modal-body{padding:24px;overflow-y:auto}.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--border-color)}.form-group{margin-bottom:20px}.form-group:last-child{margin-bottom:0}.form-group label{display:block;font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:8px}.form-group select{width:100%;padding:10px 12px;background-color:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-size:14px;font-family:inherit}.form-group select:focus{outline:none;border-color:var(--accent-blue)}.agent-options{display:flex;gap:8px}.agent-option{flex:1;padding:12px;background-color:var(--bg-tertiary);border:2px solid var(--border-color);border-radius:6px;cursor:pointer;transition:all .2s;text-align:center}.agent-option:hover{border-color:var(--accent-blue)}.agent-option.selected{border-color:var(--accent-blue);background-color:#58a6ff1a}.agent-option .agent-name{font-size:12px;font-weight:500;color:var(--text-primary)}.btn-primary{padding:10px 20px;background-color:var(--accent-blue);border:none;border-radius:6px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:filter .2s;font-family:inherit}.btn-primary:hover:not(:disabled){filter:brightness(1.1)}.btn-primary:disabled{background-color:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed}.btn-secondary{padding:10px 20px;background-color:transparent;border:1px solid var(--border-color);border-radius:6px;color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:all .2s;font-family:inherit}.btn-secondary:hover:not(:disabled){background-color:var(--bg-tertiary);color:var(--text-primary)}.btn-danger{padding:10px 20px;background-color:var(--accent-red);border:none;border-radius:6px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:filter .2s;font-family:inherit}.btn-danger:hover:not(:disabled){filter:brightness(1.1)}.btn-danger:disabled{opacity:.5;cursor:not-allowed}.confirm-message{font-size:14px;color:var(--text-primary);margin-bottom:16px}.delete-list{list-style:none;padding:12px;background-color:var(--bg-tertiary);border-radius:6px;margin-bottom:16px;max-height:150px;overflow-y:auto}.delete-list li{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:13px;color:var(--text-secondary)}.delete-list .status{width:6px;height:6px;border-radius:50%}.warning-text{font-size:12px;color:var(--accent-yellow);padding:12px;background-color:#d299221a;border-radius:6px}.terminal-panel.empty{display:flex;align-items:center;justify-content:center}.empty-message{text-align:center;color:var(--text-muted)}.empty-message p{margin:8px 0}.empty-message p:first-child{font-size:16px;color:var(--text-secondary)}