ccmanager 1.0.0 → 1.1.1

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
 
@@ -41,23 +45,28 @@ Following Claude Code's philosophy, CCManager keeps things minimal and intuitive
41
45
  ## Install
42
46
 
43
47
  ```bash
44
- $ npm install
45
- $ npm run build
46
- $ npm start
48
+ npm install -g ccmanager
47
49
  ```
48
50
 
49
- ## Usage
51
+ Or for local development:
50
52
 
51
53
  ```bash
52
- $ npx ccmanager
54
+ npm install
55
+ npm run build
56
+ npm start
53
57
  ```
54
58
 
55
- ## Environment Variables
59
+ ## Usage
56
60
 
57
- ### CCMANAGER_CLAUDE_ARGS
61
+ ```bash
62
+ ccmanager
63
+ ```
58
64
 
59
- ⚠️ **Deprecated in v0.1.9**: `CCMANAGER_CLAUDE_ARGS` is no longer supported. Please use the [Command Configuration](#command-configuration) feature instead.
65
+ Or run without installing:
60
66
 
67
+ ```bash
68
+ npx ccmanager
69
+ ```
61
70
 
62
71
  ## Keyboard Shortcuts
63
72
 
@@ -71,7 +80,7 @@ $ npx ccmanager
71
80
  You can customize keyboard shortcuts in two ways:
72
81
 
73
82
  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`)
83
+ 2. **Configuration file**: Edit `~/.config/ccmanager/config.json`
75
84
 
76
85
  Example configuration:
77
86
  ```json
@@ -87,17 +96,6 @@ Example configuration:
87
96
  }
88
97
  }
89
98
  }
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
99
  ```
102
100
 
103
101
  Note: Shortcuts from `shortcuts.json` will be automatically migrated to `config.json` on first use.
@@ -167,7 +165,33 @@ Status hooks allow you to:
167
165
  - Trigger automations based on session activity
168
166
  - Integrate with notification systems like [noti](https://github.com/variadico/noti)
169
167
 
170
- For detailed setup instructions, see [docs/state-hooks.md](docs/state-hooks.md).
168
+ For detailed setup instructions, see [docs/state-hooks.md](docs/status-hooks.md).
169
+
170
+ ## Automatic Worktree Directory Generation
171
+
172
+ CCManager can automatically generate worktree directory paths based on branch names, streamlining the worktree creation process.
173
+
174
+ - **Auto-generate paths**: No need to manually specify directories
175
+ - **Customizable patterns**: Use placeholders like `{branch}` in your pattern
176
+ - **Smart sanitization**: Branch names are automatically made filesystem-safe
177
+
178
+ For detailed configuration and examples, see [docs/worktree-auto-directory.md](docs/worktree-auto-directory.md).
179
+
180
+ ## Git Worktree Configuration
181
+
182
+ CCManager can display enhanced git status information for each worktree when Git's worktree configuration extension is enabled.
183
+
184
+ ```bash
185
+ # Enable enhanced status tracking
186
+ git config extensions.worktreeConfig true
187
+ ```
188
+
189
+ With this enabled, you'll see:
190
+ - **File changes**: `+10 -5` (additions/deletions)
191
+ - **Commit tracking**: `↑3 ↓1` (ahead/behind parent branch)
192
+ - **Parent branch context**: Shows which branch the worktree was created from
193
+
194
+ For complete setup instructions and troubleshooting, see [docs/git-worktree-config.md](docs/git-worktree-config.md).
171
195
 
172
196
  ## Development
173
197
 
@@ -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.1",
4
4
  "description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
5
5
  "license": "MIT",
6
6
  "author": "Kodai Kabasawa",