ccmanager 1.0.0 → 1.1.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/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  CCManager is a TUI application for managing multiple AI coding assistant sessions (Claude Code, Gemini CLI) across Git worktrees.
4
4
 
5
- https://github.com/user-attachments/assets/a6d80e73-dc06-4ef8-849d-e3857f6c7024
5
+
6
+
7
+ https://github.com/user-attachments/assets/15914a88-e288-4ac9-94d5-8127f2e19dbf
8
+
9
+
6
10
 
7
11
  ## Features
8
12
 
@@ -52,13 +56,6 @@ $ npm start
52
56
  $ npx ccmanager
53
57
  ```
54
58
 
55
- ## Environment Variables
56
-
57
- ### CCMANAGER_CLAUDE_ARGS
58
-
59
- ⚠️ **Deprecated in v0.1.9**: `CCMANAGER_CLAUDE_ARGS` is no longer supported. Please use the [Command Configuration](#command-configuration) feature instead.
60
-
61
-
62
59
  ## Keyboard Shortcuts
63
60
 
64
61
  ### Default Shortcuts
@@ -71,7 +68,7 @@ $ npx ccmanager
71
68
  You can customize keyboard shortcuts in two ways:
72
69
 
73
70
  1. **Through the UI**: Select "Configuration" → "Configure Shortcuts" from the main menu
74
- 2. **Configuration file**: Edit `~/.config/ccmanager/config.json` (or legacy `~/.config/ccmanager/shortcuts.json`)
71
+ 2. **Configuration file**: Edit `~/.config/ccmanager/config.json`
75
72
 
76
73
  Example configuration:
77
74
  ```json
@@ -87,17 +84,6 @@ Example configuration:
87
84
  }
88
85
  }
89
86
  }
90
-
91
- // shortcuts.json (legacy format, still supported)
92
- {
93
- "returnToMenu": {
94
- "ctrl": true,
95
- "key": "r"
96
- },
97
- "cancel": {
98
- "key": "escape"
99
- }
100
- }
101
87
  ```
102
88
 
103
89
  Note: Shortcuts from `shortcuts.json` will be automatically migrated to `config.json` on first use.
@@ -167,7 +153,33 @@ Status hooks allow you to:
167
153
  - Trigger automations based on session activity
168
154
  - Integrate with notification systems like [noti](https://github.com/variadico/noti)
169
155
 
170
- For detailed setup instructions, see [docs/state-hooks.md](docs/state-hooks.md).
156
+ For detailed setup instructions, see [docs/state-hooks.md](docs/status-hooks.md).
157
+
158
+ ## Automatic Worktree Directory Generation
159
+
160
+ CCManager can automatically generate worktree directory paths based on branch names, streamlining the worktree creation process.
161
+
162
+ - **Auto-generate paths**: No need to manually specify directories
163
+ - **Customizable patterns**: Use placeholders like `{branch}` in your pattern
164
+ - **Smart sanitization**: Branch names are automatically made filesystem-safe
165
+
166
+ For detailed configuration and examples, see [docs/worktree-auto-directory.md](docs/worktree-auto-directory.md).
167
+
168
+ ## Git Worktree Configuration
169
+
170
+ CCManager can display enhanced git status information for each worktree when Git's worktree configuration extension is enabled.
171
+
172
+ ```bash
173
+ # Enable enhanced status tracking
174
+ git config extensions.worktreeConfig true
175
+ ```
176
+
177
+ With this enabled, you'll see:
178
+ - **File changes**: `+10 -5` (additions/deletions)
179
+ - **Commit tracking**: `↑3 ↓1` (ahead/behind parent branch)
180
+ - **Parent branch context**: Shows which branch the worktree was created from
181
+
182
+ For complete setup instructions and troubleshooting, see [docs/git-worktree-config.md](docs/git-worktree-config.md).
171
183
 
172
184
  ## Development
173
185
 
@@ -6,7 +6,14 @@ import { configurationManager } from '../services/configurationManager.js';
6
6
  import { shortcutManager } from '../services/shortcutManager.js';
7
7
  const formatDetectionStrategy = (strategy) => {
8
8
  const value = strategy || 'claude';
9
- return value === 'gemini' ? 'Gemini' : 'Claude';
9
+ switch (value) {
10
+ case 'gemini':
11
+ return 'Gemini';
12
+ case 'codex':
13
+ return 'Codex';
14
+ default:
15
+ return 'Claude';
16
+ }
10
17
  };
11
18
  const ConfigureCommand = ({ onComplete }) => {
12
19
  const presetsConfig = configurationManager.getCommandPresets();
@@ -260,6 +267,7 @@ const ConfigureCommand = ({ onComplete }) => {
260
267
  const strategyItems = [
261
268
  { label: 'Claude', value: 'claude' },
262
269
  { label: 'Gemini', value: 'gemini' },
270
+ { label: 'Codex', value: 'codex' },
263
271
  ];
264
272
  const currentStrategy = preset.detectionStrategy || 'claude';
265
273
  const initialIndex = strategyItems.findIndex(item => item.value === currentStrategy);
@@ -308,6 +316,7 @@ const ConfigureCommand = ({ onComplete }) => {
308
316
  const strategyItems = [
309
317
  { label: 'Claude', value: 'claude' },
310
318
  { label: 'Gemini', value: 'gemini' },
319
+ { label: 'Codex', value: 'codex' },
311
320
  ];
312
321
  return (React.createElement(Box, { flexDirection: "column" },
313
322
  React.createElement(Box, { marginBottom: 1 },
@@ -14,3 +14,6 @@ export declare class ClaudeStateDetector extends BaseStateDetector {
14
14
  export declare class GeminiStateDetector extends BaseStateDetector {
15
15
  detectState(terminal: Terminal): SessionState;
16
16
  }
17
+ export declare class CodexStateDetector extends BaseStateDetector {
18
+ detectState(terminal: Terminal): SessionState;
19
+ }
@@ -4,6 +4,8 @@ export function createStateDetector(strategy = 'claude') {
4
4
  return new ClaudeStateDetector();
5
5
  case 'gemini':
6
6
  return new GeminiStateDetector();
7
+ case 'codex':
8
+ return new CodexStateDetector();
7
9
  default:
8
10
  return new ClaudeStateDetector();
9
11
  }
@@ -65,3 +67,21 @@ export class GeminiStateDetector extends BaseStateDetector {
65
67
  return 'idle';
66
68
  }
67
69
  }
70
+ export class CodexStateDetector extends BaseStateDetector {
71
+ detectState(terminal) {
72
+ const content = this.getTerminalContent(terminal);
73
+ const lowerContent = content.toLowerCase();
74
+ // Check for waiting prompts
75
+ if (content.includes('│Allow') ||
76
+ content.includes('[y/N]') ||
77
+ content.includes('Press any key')) {
78
+ return 'waiting_input';
79
+ }
80
+ // Check for busy state
81
+ if (lowerContent.includes('press esc')) {
82
+ return 'busy';
83
+ }
84
+ // Otherwise idle
85
+ return 'idle';
86
+ }
87
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
- import { ClaudeStateDetector, GeminiStateDetector } from './stateDetector.js';
2
+ import { ClaudeStateDetector, GeminiStateDetector, CodexStateDetector, } from './stateDetector.js';
3
3
  describe('ClaudeStateDetector', () => {
4
4
  let detector;
5
5
  let terminal;
@@ -240,3 +240,94 @@ describe('GeminiStateDetector', () => {
240
240
  });
241
241
  });
242
242
  });
243
+ describe('CodexStateDetector', () => {
244
+ let detector;
245
+ let terminal;
246
+ const createMockTerminal = (lines) => {
247
+ const buffer = {
248
+ length: lines.length,
249
+ active: {
250
+ length: lines.length,
251
+ getLine: (index) => {
252
+ if (index >= 0 && index < lines.length) {
253
+ return {
254
+ translateToString: () => lines[index],
255
+ };
256
+ }
257
+ return null;
258
+ },
259
+ },
260
+ };
261
+ return { buffer };
262
+ };
263
+ beforeEach(() => {
264
+ detector = new CodexStateDetector();
265
+ });
266
+ it('should detect waiting_input state for │Allow pattern', () => {
267
+ // Arrange
268
+ terminal = createMockTerminal(['Some output', '│Allow execution?', '│ > ']);
269
+ // Act
270
+ const state = detector.detectState(terminal);
271
+ // Assert
272
+ expect(state).toBe('waiting_input');
273
+ });
274
+ it('should detect waiting_input state for [y/N] pattern', () => {
275
+ // Arrange
276
+ terminal = createMockTerminal(['Some output', 'Continue? [y/N]', '> ']);
277
+ // Act
278
+ const state = detector.detectState(terminal);
279
+ // Assert
280
+ expect(state).toBe('waiting_input');
281
+ });
282
+ it('should detect waiting_input state for Press any key pattern', () => {
283
+ // Arrange
284
+ terminal = createMockTerminal([
285
+ 'Some output',
286
+ 'Press any key to continue...',
287
+ ]);
288
+ // Act
289
+ const state = detector.detectState(terminal);
290
+ // Assert
291
+ expect(state).toBe('waiting_input');
292
+ });
293
+ it('should detect busy state for press esc pattern', () => {
294
+ // Arrange
295
+ terminal = createMockTerminal([
296
+ 'Processing...',
297
+ 'press esc to cancel',
298
+ 'Working...',
299
+ ]);
300
+ // Act
301
+ const state = detector.detectState(terminal);
302
+ // Assert
303
+ expect(state).toBe('busy');
304
+ });
305
+ it('should detect busy state for PRESS ESC (uppercase)', () => {
306
+ // Arrange
307
+ terminal = createMockTerminal([
308
+ 'Processing...',
309
+ 'PRESS ESC to stop',
310
+ 'Working...',
311
+ ]);
312
+ // Act
313
+ const state = detector.detectState(terminal);
314
+ // Assert
315
+ expect(state).toBe('busy');
316
+ });
317
+ it('should detect idle state when no patterns match', () => {
318
+ // Arrange
319
+ terminal = createMockTerminal(['Normal output', 'Some message', 'Ready']);
320
+ // Act
321
+ const state = detector.detectState(terminal);
322
+ // Assert
323
+ expect(state).toBe('idle');
324
+ });
325
+ it('should prioritize waiting_input over busy', () => {
326
+ // Arrange
327
+ terminal = createMockTerminal(['press esc to cancel', '[y/N]']);
328
+ // Act
329
+ const state = detector.detectState(terminal);
330
+ // Assert
331
+ expect(state).toBe('waiting_input');
332
+ });
333
+ });
@@ -3,7 +3,7 @@ import type pkg from '@xterm/headless';
3
3
  import { GitStatus } from '../utils/gitStatus.js';
4
4
  export type Terminal = InstanceType<typeof pkg.Terminal>;
5
5
  export type SessionState = 'idle' | 'busy' | 'waiting_input';
6
- export type StateDetectionStrategy = 'claude' | 'gemini';
6
+ export type StateDetectionStrategy = 'claude' | 'gemini' | 'codex';
7
7
  export interface Worktree {
8
8
  path: string;
9
9
  branch?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",