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.
Files changed (36) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.context-snapshots/context-snapshot-20260106-180000.md +108 -0
  3. package/.context-snapshots/context-snapshot-20260106-235500.md +108 -0
  4. package/README.md +134 -0
  5. package/dist/server/routes/api.d.ts.map +1 -1
  6. package/dist/server/routes/api.js +45 -1
  7. package/dist/server/routes/api.js.map +1 -1
  8. package/dist/server/services/worktreeService.d.ts +23 -0
  9. package/dist/server/services/worktreeService.d.ts.map +1 -1
  10. package/dist/server/services/worktreeService.js +156 -14
  11. package/dist/server/services/worktreeService.js.map +1 -1
  12. package/dist/server/socketHandlers.d.ts.map +1 -1
  13. package/dist/server/socketHandlers.js +74 -1
  14. package/dist/server/socketHandlers.js.map +1 -1
  15. package/dist/shared/types/index.d.ts +36 -0
  16. package/dist/shared/types/index.d.ts.map +1 -1
  17. package/dist/web/assets/{index-C61yAbey.css → index-CsixHL-D.css} +1 -1
  18. package/dist/web/assets/index-D8dr9mJa.js +53 -0
  19. package/dist/web/assets/index-D8dr9mJa.js.map +1 -0
  20. package/dist/web/index.html +2 -2
  21. package/package.json +1 -1
  22. package/server/routes/api.ts +51 -2
  23. package/server/services/worktreeService.ts +181 -13
  24. package/server/socketHandlers.ts +91 -2
  25. package/shared/types/index.ts +45 -0
  26. package/web/src/App.tsx +3 -0
  27. package/web/src/components/Layout/LeftNavBar.tsx +290 -26
  28. package/web/src/components/Layout/MainLayout.tsx +2 -6
  29. package/web/src/components/Modals/AddWorktreeModal.tsx +87 -0
  30. package/web/src/components/Modals/ConfirmDeleteModal.tsx +114 -0
  31. package/web/src/components/Modals/ModalContainer.tsx +21 -0
  32. package/web/src/components/Terminal/TerminalPanel.tsx +32 -37
  33. package/web/src/stores/useAppStore.ts +200 -3
  34. package/web/src/styles/global.css +522 -0
  35. package/dist/web/assets/index-WEdVUKxb.js +0 -53
  36. 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');