ccmanager 3.7.2 → 3.7.3

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.
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from 'react';
3
3
  import { Box, Text, useInput, useStdout } from 'ink';
4
4
  import SelectInput from 'ink-select-input';
@@ -35,6 +35,8 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
35
35
  const [sessions, setSessions] = useState([]);
36
36
  const [items, setItems] = useState([]);
37
37
  const [recentProjects, setRecentProjects] = useState([]);
38
+ const [highlightedWorktreePath, setHighlightedWorktreePath] = useState(null);
39
+ const [autoApprovalToggleCounter, setAutoApprovalToggleCounter] = useState(0);
38
40
  const { stdout } = useStdout();
39
41
  const fixedRows = 6;
40
42
  // Use the search mode hook
@@ -137,7 +139,10 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
137
139
  : items;
138
140
  // Build menu items with proper alignment
139
141
  const menuItems = filteredItems.map((item, index) => {
140
- const label = assembleWorktreeLabel(item, columnPositions);
142
+ const baseLabel = assembleWorktreeLabel(item, columnPositions);
143
+ const aaDisabled = configReader.isAutoApprovalEnabled() &&
144
+ sessionManager.isAutoApprovalDisabledForWorktree(item.worktree.path);
145
+ const label = baseLabel + (aaDisabled ? ' [Auto Approval Off]' : '');
141
146
  // Only show numbers for worktrees (0-9) when not in search mode
142
147
  const numberPrefix = !isSearchMode && index < 10 ? `${index} ❯ ` : '❯ ';
143
148
  return {
@@ -255,6 +260,16 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
255
260
  }
256
261
  }
257
262
  setItems(menuItems);
263
+ // Ensure highlighted worktree path is valid for hotkey support
264
+ // (e.g., on initial render or when returning from a session view)
265
+ setHighlightedWorktreePath(prev => {
266
+ if (prev &&
267
+ menuItems.some(item => item.type === 'worktree' && item.value === prev)) {
268
+ return prev;
269
+ }
270
+ const first = menuItems.find(item => item.type === 'worktree');
271
+ return first && first.type === 'worktree' ? first.worktree.path : null;
272
+ });
258
273
  }, [
259
274
  worktrees,
260
275
  sessions,
@@ -264,6 +279,8 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
264
279
  recentProjects,
265
280
  searchQuery,
266
281
  isSearchMode,
282
+ autoApprovalToggleCounter,
283
+ sessionManager,
267
284
  ]);
268
285
  // Handle hotkeys
269
286
  useInput((input, _key) => {
@@ -309,6 +326,13 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
309
326
  return;
310
327
  }
311
328
  switch (keyPressed) {
329
+ case 'a':
330
+ // Toggle auto-approval for the currently highlighted worktree
331
+ if (configReader.isAutoApprovalEnabled() && highlightedWorktreePath) {
332
+ sessionManager.toggleAutoApprovalForWorktree(highlightedWorktreePath);
333
+ setAutoApprovalToggleCounter(c => c + 1);
334
+ }
335
+ break;
312
336
  case 'n':
313
337
  // Trigger new worktree action
314
338
  onSelectWorktree({
@@ -487,10 +511,15 @@ const Menu = ({ sessionManager, worktreeService, onSelectWorktree, onSelectRecen
487
511
  };
488
512
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "green", children: ["CCManager - Claude Code Worktree Manager v", version] }), projectName && (_jsx(Text, { bold: true, color: "green", children: projectName }))] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Select a worktree to start or resume a Claude Code session:" }) }), isSearchMode && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { children: "Search: " }), _jsx(TextInputWrapper, { value: searchQuery, onChange: setSearchQuery, focus: true, placeholder: "Type to filter worktrees..." })] })), isSearchMode && items.length === 0 ? (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "No worktrees match your search" }) })) : isSearchMode ? (
489
513
  // In search mode, show the items as a list without SelectInput
490
- _jsx(Box, { flexDirection: "column", children: items.slice(0, limit).map((item, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', item.label] }, item.value))) })) : (_jsx(SelectInput, { items: items, onSelect: item => handleSelect(item), isFocused: !error, initialIndex: selectedIndex, limit: limit })), (error || loadError) && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red", children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", error || loadError] }), _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to dismiss" })] }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Status: ", STATUS_ICONS.BUSY, " ", STATUS_LABELS.BUSY, ' ', STATUS_ICONS.WAITING, " ", STATUS_LABELS.WAITING, " ", STATUS_ICONS.IDLE, ' ', STATUS_LABELS.IDLE] }), _jsx(Text, { dimColor: true, children: isSearchMode
514
+ _jsx(Box, { flexDirection: "column", children: items.slice(0, limit).map((item, index) => (_jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', item.label] }, item.value))) })) : (_jsx(SelectInput, { items: items, onSelect: item => handleSelect(item), onHighlight: item => {
515
+ const menuItem = item;
516
+ if (menuItem.type === 'worktree') {
517
+ setHighlightedWorktreePath(menuItem.worktree.path);
518
+ }
519
+ }, isFocused: !error, initialIndex: selectedIndex, limit: limit })), (error || loadError) && (_jsx(Box, { marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "red", children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", error || loadError] }), _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to dismiss" })] }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["Status: ", STATUS_ICONS.BUSY, " ", STATUS_LABELS.BUSY, ' ', STATUS_ICONS.WAITING, " ", STATUS_LABELS.WAITING, " ", STATUS_ICONS.IDLE, ' ', STATUS_LABELS.IDLE, configReader.isAutoApprovalEnabled() && (_jsxs(_Fragment, { children: [' | ', _jsx(Text, { color: "green", children: "Auto Approval Enabled" })] }))] }), _jsx(Text, { dimColor: true, children: isSearchMode
491
520
  ? 'Search Mode: Type to filter, Enter to exit search, ESC to exit search'
492
521
  : searchQuery
493
- ? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
494
- : `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}` })] })] }));
522
+ ? `Filtered: "${searchQuery}" | ↑↓ Navigate Enter Select | /-Search ESC-Clear 0-9 Quick Select N-New M-Merge D-Delete ${configReader.isAutoApprovalEnabled() ? 'A-AutoApproval ' : ''}${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}`
523
+ : `Controls: ↑↓ Navigate Enter Select | Hotkeys: 0-9 Quick Select /-Search N-New M-Merge D-Delete ${configReader.isAutoApprovalEnabled() ? 'A-AutoApproval ' : ''}${multiProject ? 'C-Config' : 'P-ProjConfig C-GlobalConfig'} ${projectName ? 'B-Back' : 'Q-Quit'}` })] })] }));
495
524
  };
496
525
  export default Menu;
@@ -84,6 +84,8 @@ describe('Menu - Recent Projects', () => {
84
84
  createSessionWithPreset: vi.fn(),
85
85
  createSessionWithDevcontainer: vi.fn(),
86
86
  destroy: vi.fn(),
87
+ isAutoApprovalDisabledForWorktree: vi.fn().mockReturnValue(false),
88
+ toggleAutoApprovalForWorktree: vi.fn().mockReturnValue(false),
87
89
  };
88
90
  mockWorktreeService = {
89
91
  getWorktreesEffect: vi.fn().mockReturnValue(Effect.succeed([
@@ -14,6 +14,7 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
14
14
  sessions: Map<string, Session>;
15
15
  private waitingWithBottomBorder;
16
16
  private busyTimers;
17
+ private autoApprovalDisabledWorktrees;
17
18
  private spawn;
18
19
  detectTerminalState(session: Session): SessionState;
19
20
  detectBackgroundTask(session: Session): number;
@@ -66,6 +67,8 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
66
67
  getSession(worktreePath: string): Session | undefined;
67
68
  setSessionActive(worktreePath: string, active: boolean): void;
68
69
  cancelAutoApproval(worktreePath: string, reason?: string): void;
70
+ toggleAutoApprovalForWorktree(worktreePath: string): boolean;
71
+ isAutoApprovalDisabledForWorktree(worktreePath: string): boolean;
69
72
  destroySession(worktreePath: string): void;
70
73
  /**
71
74
  * Terminate session and cleanup resources using Effect-based error handling
@@ -22,6 +22,7 @@ export class SessionManager extends EventEmitter {
22
22
  sessions;
23
23
  waitingWithBottomBorder = new Map();
24
24
  busyTimers = new Map();
25
+ autoApprovalDisabledWorktrees = new Set();
25
26
  async spawn(command, args, worktreePath) {
26
27
  const spawnOptions = {
27
28
  name: 'xterm-256color',
@@ -38,7 +39,8 @@ export class SessionManager extends EventEmitter {
38
39
  // If auto-approval is enabled and state is waiting_input, convert to pending_auto_approval
39
40
  if (detectedState === 'waiting_input' &&
40
41
  configReader.isAutoApprovalEnabled() &&
41
- !stateData.autoApprovalFailed) {
42
+ !stateData.autoApprovalFailed &&
43
+ !this.autoApprovalDisabledWorktrees.has(session.worktreePath)) {
42
44
  return 'pending_auto_approval';
43
45
  }
44
46
  return detectedState;
@@ -483,6 +485,20 @@ export class SessionManager extends EventEmitter {
483
485
  }));
484
486
  }
485
487
  }
488
+ toggleAutoApprovalForWorktree(worktreePath) {
489
+ if (this.autoApprovalDisabledWorktrees.has(worktreePath)) {
490
+ this.autoApprovalDisabledWorktrees.delete(worktreePath);
491
+ return false;
492
+ }
493
+ else {
494
+ this.autoApprovalDisabledWorktrees.add(worktreePath);
495
+ this.cancelAutoApproval(worktreePath, 'Auto-approval disabled for worktree');
496
+ return true;
497
+ }
498
+ }
499
+ isAutoApprovalDisabledForWorktree(worktreePath) {
500
+ return this.autoApprovalDisabledWorktrees.has(worktreePath);
501
+ }
486
502
  destroySession(worktreePath) {
487
503
  const session = this.sessions.get(worktreePath);
488
504
  if (session) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "3.7.2",
3
+ "version": "3.7.3",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",
@@ -41,11 +41,11 @@
41
41
  "bin"
42
42
  ],
43
43
  "optionalDependencies": {
44
- "@kodaikabasawa/ccmanager-darwin-arm64": "3.7.2",
45
- "@kodaikabasawa/ccmanager-darwin-x64": "3.7.2",
46
- "@kodaikabasawa/ccmanager-linux-arm64": "3.7.2",
47
- "@kodaikabasawa/ccmanager-linux-x64": "3.7.2",
48
- "@kodaikabasawa/ccmanager-win32-x64": "3.7.2"
44
+ "@kodaikabasawa/ccmanager-darwin-arm64": "3.7.3",
45
+ "@kodaikabasawa/ccmanager-darwin-x64": "3.7.3",
46
+ "@kodaikabasawa/ccmanager-linux-arm64": "3.7.3",
47
+ "@kodaikabasawa/ccmanager-linux-x64": "3.7.3",
48
+ "@kodaikabasawa/ccmanager-win32-x64": "3.7.3"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.28.0",