hjworktree-cli 2.0.0 → 2.2.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 +10 -0
- package/.context-snapshots/context-snapshot-20260106-180000.md +108 -0
- package/.context-snapshots/context-snapshot-20260106-235500.md +108 -0
- package/README.md +134 -0
- package/dist/server/routes/api.d.ts.map +1 -1
- package/dist/server/routes/api.js +45 -1
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/services/worktreeService.d.ts +23 -0
- package/dist/server/services/worktreeService.d.ts.map +1 -1
- package/dist/server/services/worktreeService.js +156 -14
- package/dist/server/services/worktreeService.js.map +1 -1
- package/dist/server/socketHandlers.d.ts.map +1 -1
- package/dist/server/socketHandlers.js +74 -1
- package/dist/server/socketHandlers.js.map +1 -1
- package/dist/shared/types/index.d.ts +36 -0
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/web/assets/{index-C61yAbey.css → index-CsixHL-D.css} +1 -1
- package/dist/web/assets/index-D8dr9mJa.js +53 -0
- package/dist/web/assets/index-D8dr9mJa.js.map +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/server/routes/api.ts +51 -2
- package/server/services/worktreeService.ts +181 -13
- package/server/socketHandlers.ts +91 -2
- package/shared/types/index.ts +45 -0
- package/web/src/App.tsx +3 -0
- package/web/src/components/Layout/LeftNavBar.tsx +290 -26
- package/web/src/components/Layout/MainLayout.tsx +2 -6
- package/web/src/components/Modals/AddWorktreeModal.tsx +87 -0
- package/web/src/components/Modals/ConfirmDeleteModal.tsx +114 -0
- package/web/src/components/Modals/ModalContainer.tsx +21 -0
- package/web/src/components/Terminal/TerminalPanel.tsx +32 -37
- package/web/src/stores/useAppStore.ts +200 -3
- package/web/src/styles/global.css +522 -0
- package/dist/web/assets/index-WEdVUKxb.js +0 -53
- package/dist/web/assets/index-WEdVUKxb.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
-
import type { Branch, TerminalInfo, AgentId, AgentStatus, NavigationStep, StepStatus } from '../../../shared/types/index.js';
|
|
2
|
+
import type { Branch, TerminalInfo, AgentId, AgentStatus, 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
|
|
|
@@ -31,6 +31,18 @@ interface AppState {
|
|
|
31
31
|
terminals: TerminalInfo[];
|
|
32
32
|
activeTerminalIndex: number;
|
|
33
33
|
|
|
34
|
+
// Session management (new)
|
|
35
|
+
activeSessionId: string | null;
|
|
36
|
+
selectedSessionIds: string[];
|
|
37
|
+
|
|
38
|
+
// LNB state (new)
|
|
39
|
+
isSetupCollapsed: boolean;
|
|
40
|
+
collapsedBranches: string[];
|
|
41
|
+
|
|
42
|
+
// Modal state (new)
|
|
43
|
+
modalType: ModalType;
|
|
44
|
+
modalData: { sessionIds?: string[]; branchName?: string } | null;
|
|
45
|
+
|
|
34
46
|
// Loading states
|
|
35
47
|
isLoading: boolean;
|
|
36
48
|
loadingMessage: string;
|
|
@@ -73,6 +85,25 @@ interface AppActions {
|
|
|
73
85
|
setActiveTerminal: (index: number) => void;
|
|
74
86
|
clearAllTerminals: () => void;
|
|
75
87
|
|
|
88
|
+
// Session management (new)
|
|
89
|
+
setActiveSession: (sessionId: string | null) => void;
|
|
90
|
+
toggleSessionSelection: (sessionId: string) => void;
|
|
91
|
+
selectAllSessions: () => void;
|
|
92
|
+
clearSessionSelection: () => void;
|
|
93
|
+
removeSelectedSessions: () => Promise<void>;
|
|
94
|
+
getSessionGroups: () => SessionGroup[];
|
|
95
|
+
|
|
96
|
+
// LNB state (new)
|
|
97
|
+
toggleSetupCollapse: () => void;
|
|
98
|
+
toggleBranchCollapse: (branchName: string) => void;
|
|
99
|
+
|
|
100
|
+
// Modal (new)
|
|
101
|
+
openModal: (type: ModalType, data?: { sessionIds?: string[]; branchName?: string }) => void;
|
|
102
|
+
closeModal: () => void;
|
|
103
|
+
|
|
104
|
+
// Single worktree creation (new)
|
|
105
|
+
createSingleWorktree: (branch: string, agentType: AgentId) => Promise<void>;
|
|
106
|
+
|
|
76
107
|
// Execute
|
|
77
108
|
execute: () => Promise<void>;
|
|
78
109
|
|
|
@@ -98,6 +129,12 @@ const initialState: AppState = {
|
|
|
98
129
|
worktreeCount: DEFAULT_PARALLEL_COUNT,
|
|
99
130
|
terminals: [],
|
|
100
131
|
activeTerminalIndex: 0,
|
|
132
|
+
activeSessionId: null,
|
|
133
|
+
selectedSessionIds: [],
|
|
134
|
+
isSetupCollapsed: false,
|
|
135
|
+
collapsedBranches: [],
|
|
136
|
+
modalType: null,
|
|
137
|
+
modalData: null,
|
|
101
138
|
isLoading: false,
|
|
102
139
|
loadingMessage: '',
|
|
103
140
|
error: null,
|
|
@@ -277,7 +314,164 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
277
314
|
|
|
278
315
|
setActiveTerminal: (index) => set({ activeTerminalIndex: index }),
|
|
279
316
|
|
|
280
|
-
clearAllTerminals: () => set({ terminals: [], activeTerminalIndex: 0 }),
|
|
317
|
+
clearAllTerminals: () => set({ terminals: [], activeTerminalIndex: 0, activeSessionId: null, selectedSessionIds: [] }),
|
|
318
|
+
|
|
319
|
+
// Session management
|
|
320
|
+
setActiveSession: (sessionId) => {
|
|
321
|
+
const { terminals } = get();
|
|
322
|
+
const index = terminals.findIndex(t => t.sessionId === sessionId);
|
|
323
|
+
set({
|
|
324
|
+
activeSessionId: sessionId,
|
|
325
|
+
activeTerminalIndex: index >= 0 ? index : 0,
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
toggleSessionSelection: (sessionId) => set((state) => {
|
|
330
|
+
const selectedIds = [...state.selectedSessionIds];
|
|
331
|
+
const index = selectedIds.indexOf(sessionId);
|
|
332
|
+
if (index >= 0) {
|
|
333
|
+
selectedIds.splice(index, 1);
|
|
334
|
+
} else {
|
|
335
|
+
selectedIds.push(sessionId);
|
|
336
|
+
}
|
|
337
|
+
return { selectedSessionIds: selectedIds };
|
|
338
|
+
}),
|
|
339
|
+
|
|
340
|
+
selectAllSessions: () => set((state) => ({
|
|
341
|
+
selectedSessionIds: state.terminals.map(t => t.sessionId),
|
|
342
|
+
})),
|
|
343
|
+
|
|
344
|
+
clearSessionSelection: () => set({ selectedSessionIds: [] }),
|
|
345
|
+
|
|
346
|
+
removeSelectedSessions: async () => {
|
|
347
|
+
const { selectedSessionIds, terminals } = get();
|
|
348
|
+
if (selectedSessionIds.length === 0) return;
|
|
349
|
+
|
|
350
|
+
const names = terminals
|
|
351
|
+
.filter(t => selectedSessionIds.includes(t.sessionId))
|
|
352
|
+
.map(t => t.worktreeName);
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const response = await fetch('/api/worktrees/batch', {
|
|
356
|
+
method: 'DELETE',
|
|
357
|
+
headers: { 'Content-Type': 'application/json' },
|
|
358
|
+
body: JSON.stringify({ names }),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
if (response.ok) {
|
|
362
|
+
set((state) => {
|
|
363
|
+
const remainingTerminals = state.terminals.filter(
|
|
364
|
+
t => !selectedSessionIds.includes(t.sessionId)
|
|
365
|
+
);
|
|
366
|
+
return {
|
|
367
|
+
terminals: remainingTerminals,
|
|
368
|
+
selectedSessionIds: [],
|
|
369
|
+
activeTerminalIndex: Math.min(state.activeTerminalIndex, Math.max(0, remainingTerminals.length - 1)),
|
|
370
|
+
activeSessionId: remainingTerminals.length > 0 ? remainingTerminals[0].sessionId : null,
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error('Failed to remove sessions:', error);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
getSessionGroups: () => {
|
|
380
|
+
const { terminals, collapsedBranches } = get();
|
|
381
|
+
const groupMap = new Map<string, TerminalInfo[]>();
|
|
382
|
+
|
|
383
|
+
for (const terminal of terminals) {
|
|
384
|
+
// Extract base branch name from worktree branch (e.g., "main-project-1" -> "main")
|
|
385
|
+
const baseBranch = terminal.branchName.replace(/-project-\d+$/, '');
|
|
386
|
+
if (!groupMap.has(baseBranch)) {
|
|
387
|
+
groupMap.set(baseBranch, []);
|
|
388
|
+
}
|
|
389
|
+
groupMap.get(baseBranch)!.push(terminal);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const groups: SessionGroup[] = [];
|
|
393
|
+
for (const [branchName, sessions] of groupMap) {
|
|
394
|
+
groups.push({
|
|
395
|
+
branchName,
|
|
396
|
+
sessions: sessions.map(s => ({
|
|
397
|
+
...s,
|
|
398
|
+
id: s.sessionId,
|
|
399
|
+
createdAt: Date.now(),
|
|
400
|
+
})),
|
|
401
|
+
isCollapsed: collapsedBranches.includes(branchName),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return groups.sort((a, b) => a.branchName.localeCompare(b.branchName));
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
// LNB state
|
|
409
|
+
toggleSetupCollapse: () => set((state) => ({
|
|
410
|
+
isSetupCollapsed: !state.isSetupCollapsed,
|
|
411
|
+
})),
|
|
412
|
+
|
|
413
|
+
toggleBranchCollapse: (branchName) => set((state) => {
|
|
414
|
+
const collapsed = [...state.collapsedBranches];
|
|
415
|
+
const index = collapsed.indexOf(branchName);
|
|
416
|
+
if (index >= 0) {
|
|
417
|
+
collapsed.splice(index, 1);
|
|
418
|
+
} else {
|
|
419
|
+
collapsed.push(branchName);
|
|
420
|
+
}
|
|
421
|
+
return { collapsedBranches: collapsed };
|
|
422
|
+
}),
|
|
423
|
+
|
|
424
|
+
// Modal
|
|
425
|
+
openModal: (type, data) => set({ modalType: type, modalData: data || null }),
|
|
426
|
+
closeModal: () => set({ modalType: null, modalData: null }),
|
|
427
|
+
|
|
428
|
+
// Single worktree creation
|
|
429
|
+
createSingleWorktree: async (branch, agentType) => {
|
|
430
|
+
const { setLoading, setError, addTerminal, setActiveSession } = get();
|
|
431
|
+
|
|
432
|
+
setLoading(true, 'Creating worktree...');
|
|
433
|
+
setError(null);
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const response = await fetch('/api/worktrees/single', {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
headers: { 'Content-Type': 'application/json' },
|
|
439
|
+
body: JSON.stringify({ branch, agentType }),
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
const data = await response.json();
|
|
444
|
+
throw new Error(data.error || 'Failed to create worktree');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const { worktree } = await response.json();
|
|
448
|
+
const sessionId = `${worktree.name}-${Date.now()}`;
|
|
449
|
+
|
|
450
|
+
addTerminal({
|
|
451
|
+
sessionId,
|
|
452
|
+
worktreePath: worktree.path,
|
|
453
|
+
worktreeName: worktree.name,
|
|
454
|
+
branchName: worktree.branch,
|
|
455
|
+
agentType,
|
|
456
|
+
status: 'initializing',
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Auto-switch to the new session
|
|
460
|
+
setActiveSession(sessionId);
|
|
461
|
+
|
|
462
|
+
// Auto-collapse setup and switch to running
|
|
463
|
+
set({
|
|
464
|
+
step: 'running',
|
|
465
|
+
isSetupCollapsed: true,
|
|
466
|
+
modalType: null,
|
|
467
|
+
modalData: null,
|
|
468
|
+
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
setError(error instanceof Error ? error.message : 'Unknown error');
|
|
471
|
+
} finally {
|
|
472
|
+
setLoading(false);
|
|
473
|
+
}
|
|
474
|
+
},
|
|
281
475
|
|
|
282
476
|
// Execute
|
|
283
477
|
execute: async () => {
|
|
@@ -323,9 +517,12 @@ export const useAppStore = create<AppStore>((set, get) => ({
|
|
|
323
517
|
}
|
|
324
518
|
|
|
325
519
|
// Mark all setup steps as complete and transition to running
|
|
520
|
+
const { terminals: updatedTerminals } = get();
|
|
326
521
|
set({
|
|
327
522
|
step: 'running',
|
|
328
|
-
completedSteps: ['branch', 'agent', 'worktree']
|
|
523
|
+
completedSteps: ['branch', 'agent', 'worktree'],
|
|
524
|
+
isSetupCollapsed: true,
|
|
525
|
+
activeSessionId: updatedTerminals.length > 0 ? updatedTerminals[0].sessionId : null,
|
|
329
526
|
});
|
|
330
527
|
} catch (error) {
|
|
331
528
|
setError(error instanceof Error ? error.message : 'Unknown error');
|