ccmanager 3.12.1 → 3.12.2

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.
@@ -7,11 +7,12 @@ const PresetSelector = ({ onSelect, onCancel, }) => {
7
7
  const presetsConfig = configReader.getCommandPresets();
8
8
  const [presets] = useState(presetsConfig.presets);
9
9
  const defaultPresetId = presetsConfig.defaultPresetId;
10
- const selectItems = presets.map(preset => {
10
+ const selectItems = presets.map((preset, index) => {
11
11
  const isDefault = preset.id === defaultPresetId;
12
12
  const args = preset.args?.join(' ') || '';
13
13
  const fallback = preset.fallbackArgs?.join(' ') || '';
14
- let label = preset.name;
14
+ const numberPrefix = index < 9 ? `[${index + 1}] ` : '';
15
+ let label = numberPrefix + preset.name;
15
16
  if (isDefault)
16
17
  label += ' (default)';
17
18
  label += `\n Command: ${preset.command}`;
@@ -39,8 +40,17 @@ const PresetSelector = ({ onSelect, onCancel, }) => {
39
40
  useInput((input, key) => {
40
41
  if (key.escape) {
41
42
  onCancel();
43
+ return;
44
+ }
45
+ // Number keys 1-9: immediate launch
46
+ if (/^[1-9]$/.test(input)) {
47
+ const idx = parseInt(input) - 1;
48
+ if (idx < presets.length && presets[idx]) {
49
+ onSelect(presets[idx].id);
50
+ }
51
+ return;
42
52
  }
43
53
  });
44
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Select Command Preset" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a preset to start the session with" }) }), _jsx(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: initialIndex >= 0 ? initialIndex : 0 }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press \u2191\u2193 to navigate, Enter to select, ESC to cancel" }) })] }));
54
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "green", children: "Select Command Preset" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a preset to start the session with" }) }), _jsx(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: initialIndex >= 0 ? initialIndex : 0 }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191\u2193 Navigate 1-9 Quick Select Enter Select ESC Cancel" }) })] }));
45
55
  };
46
56
  export default PresetSelector;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink-testing-library';
3
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
4
+ // Hoist mocks to avoid top-level variable access in vi.mock factories
5
+ const { capturedHandlers } = vi.hoisted(() => {
6
+ const capturedHandlers = {
7
+ inputHandler: null,
8
+ };
9
+ return { capturedHandlers };
10
+ });
11
+ // Mock ink to avoid stdin issues and capture useInput callbacks
12
+ vi.mock('ink', async () => {
13
+ const actual = await vi.importActual('ink');
14
+ return {
15
+ ...actual,
16
+ useInput: vi.fn((handler) => {
17
+ capturedHandlers.inputHandler = handler;
18
+ }),
19
+ };
20
+ });
21
+ // Mock SelectInput
22
+ vi.mock('ink-select-input', async () => {
23
+ const React = await vi.importActual('react');
24
+ const { Text, Box } = await vi.importActual('ink');
25
+ return {
26
+ default: ({ items, onSelect: _onSelect, initialIndex = 0, }) => {
27
+ return React.createElement(Box, { flexDirection: 'column' }, items.map((item, index) => React.createElement(Text, { key: index }, `${index === initialIndex ? '❯ ' : ' '}${item.label}`)));
28
+ },
29
+ };
30
+ });
31
+ // Mock configReader
32
+ vi.mock('../services/config/configReader.js', () => ({
33
+ configReader: {
34
+ getCommandPresets: vi.fn().mockReturnValue({
35
+ presets: [
36
+ { id: 'preset-1', name: 'Claude', command: 'claude' },
37
+ { id: 'preset-2', name: 'Gemini', command: 'gemini' },
38
+ { id: 'preset-3', name: 'Cursor', command: 'cursor' },
39
+ ],
40
+ defaultPresetId: 'preset-1',
41
+ selectPresetOnStart: true,
42
+ }),
43
+ },
44
+ }));
45
+ import PresetSelector from './PresetSelector.js';
46
+ const makeKey = (overrides = {}) => ({
47
+ upArrow: false,
48
+ downArrow: false,
49
+ leftArrow: false,
50
+ rightArrow: false,
51
+ pageDown: false,
52
+ pageUp: false,
53
+ home: false,
54
+ end: false,
55
+ return: false,
56
+ escape: false,
57
+ ctrl: false,
58
+ shift: false,
59
+ tab: false,
60
+ backspace: false,
61
+ delete: false,
62
+ meta: false,
63
+ ...overrides,
64
+ });
65
+ describe('PresetSelector component', () => {
66
+ let onSelect;
67
+ let onCancel;
68
+ beforeEach(() => {
69
+ onSelect = vi.fn();
70
+ onCancel = vi.fn();
71
+ capturedHandlers.inputHandler = null;
72
+ });
73
+ afterEach(() => {
74
+ vi.clearAllMocks();
75
+ });
76
+ it('renders preset list with number prefixes and default label', () => {
77
+ const { lastFrame } = render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
78
+ const output = lastFrame();
79
+ expect(output).toContain('[1]');
80
+ expect(output).toContain('[2]');
81
+ expect(output).toContain('[3]');
82
+ expect(output).toContain('(default)');
83
+ expect(output).toContain('← Cancel');
84
+ });
85
+ it('pressing 1 calls onSelect with first preset id immediately', () => {
86
+ render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
87
+ expect(capturedHandlers.inputHandler).not.toBeNull();
88
+ capturedHandlers.inputHandler('1', makeKey());
89
+ expect(onSelect).toHaveBeenCalledWith('preset-1');
90
+ expect(onCancel).not.toHaveBeenCalled();
91
+ });
92
+ it('pressing 2 calls onSelect with second preset id immediately', () => {
93
+ render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
94
+ capturedHandlers.inputHandler('2', makeKey());
95
+ expect(onSelect).toHaveBeenCalledWith('preset-2');
96
+ });
97
+ it('pressing 3 calls onSelect with third preset id immediately', () => {
98
+ render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
99
+ capturedHandlers.inputHandler('3', makeKey());
100
+ expect(onSelect).toHaveBeenCalledWith('preset-3');
101
+ });
102
+ it('pressing a number beyond preset count does nothing', () => {
103
+ render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
104
+ capturedHandlers.inputHandler('9', makeKey());
105
+ expect(onSelect).not.toHaveBeenCalled();
106
+ expect(onCancel).not.toHaveBeenCalled();
107
+ });
108
+ it('pressing ESC calls onCancel', () => {
109
+ render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
110
+ capturedHandlers.inputHandler('', makeKey({ escape: true }));
111
+ expect(onCancel).toHaveBeenCalled();
112
+ expect(onSelect).not.toHaveBeenCalled();
113
+ });
114
+ it('displays title and subtitle', () => {
115
+ const { lastFrame } = render(_jsx(PresetSelector, { onSelect: onSelect, onCancel: onCancel }));
116
+ const output = lastFrame();
117
+ expect(output).toContain('Select Command Preset');
118
+ expect(output).toContain('Choose a preset to start the session with');
119
+ });
120
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccmanager",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
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.12.1",
45
- "@kodaikabasawa/ccmanager-darwin-x64": "3.12.1",
46
- "@kodaikabasawa/ccmanager-linux-arm64": "3.12.1",
47
- "@kodaikabasawa/ccmanager-linux-x64": "3.12.1",
48
- "@kodaikabasawa/ccmanager-win32-x64": "3.12.1"
44
+ "@kodaikabasawa/ccmanager-darwin-arm64": "3.12.2",
45
+ "@kodaikabasawa/ccmanager-darwin-x64": "3.12.2",
46
+ "@kodaikabasawa/ccmanager-linux-arm64": "3.12.2",
47
+ "@kodaikabasawa/ccmanager-linux-x64": "3.12.2",
48
+ "@kodaikabasawa/ccmanager-win32-x64": "3.12.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@eslint/js": "^9.28.0",