ccmanager 0.2.0 → 1.0.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 +26 -4
- package/dist/components/App.js +35 -1
- package/dist/components/ConfigureCommand.js +455 -124
- package/dist/components/PresetSelector.d.ts +7 -0
- package/dist/components/PresetSelector.js +52 -0
- package/dist/services/configurationManager.d.ts +11 -1
- package/dist/services/configurationManager.js +111 -3
- package/dist/services/configurationManager.selectPresetOnStart.test.d.ts +1 -0
- package/dist/services/configurationManager.selectPresetOnStart.test.js +103 -0
- package/dist/services/configurationManager.test.d.ts +1 -0
- package/dist/services/configurationManager.test.js +313 -0
- package/dist/services/sessionManager.d.ts +2 -4
- package/dist/services/sessionManager.js +78 -30
- package/dist/services/sessionManager.test.js +103 -0
- package/dist/services/stateDetector.d.ts +16 -0
- package/dist/services/stateDetector.js +67 -0
- package/dist/services/stateDetector.test.d.ts +1 -0
- package/dist/services/stateDetector.test.js +242 -0
- package/dist/types/index.d.ts +16 -0
- package/package.json +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import { configurationManager } from '../services/configurationManager.js';
|
|
5
|
+
const PresetSelector = ({ onSelect, onCancel, }) => {
|
|
6
|
+
const presetsConfig = configurationManager.getCommandPresets();
|
|
7
|
+
const [presets] = useState(presetsConfig.presets);
|
|
8
|
+
const defaultPresetId = presetsConfig.defaultPresetId;
|
|
9
|
+
const selectItems = presets.map(preset => {
|
|
10
|
+
const isDefault = preset.id === defaultPresetId;
|
|
11
|
+
const args = preset.args?.join(' ') || '';
|
|
12
|
+
const fallback = preset.fallbackArgs?.join(' ') || '';
|
|
13
|
+
let label = preset.name;
|
|
14
|
+
if (isDefault)
|
|
15
|
+
label += ' (default)';
|
|
16
|
+
label += `\n Command: ${preset.command}`;
|
|
17
|
+
if (args)
|
|
18
|
+
label += `\n Args: ${args}`;
|
|
19
|
+
if (fallback)
|
|
20
|
+
label += `\n Fallback: ${fallback}`;
|
|
21
|
+
return {
|
|
22
|
+
label,
|
|
23
|
+
value: preset.id,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
// Add cancel option
|
|
27
|
+
selectItems.push({ label: '← Cancel', value: 'cancel' });
|
|
28
|
+
const handleSelectItem = (item) => {
|
|
29
|
+
if (item.value === 'cancel') {
|
|
30
|
+
onCancel();
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
onSelect(item.value);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
// Find initial index based on default preset
|
|
37
|
+
const initialIndex = selectItems.findIndex(item => item.value === defaultPresetId);
|
|
38
|
+
useInput((input, key) => {
|
|
39
|
+
if (key.escape) {
|
|
40
|
+
onCancel();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
44
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
45
|
+
React.createElement(Text, { bold: true, color: "green" }, "Select Command Preset")),
|
|
46
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
47
|
+
React.createElement(Text, { dimColor: true }, "Choose a preset to start the session with")),
|
|
48
|
+
React.createElement(SelectInput, { items: selectItems, onSelect: handleSelectItem, initialIndex: initialIndex >= 0 ? initialIndex : 0 }),
|
|
49
|
+
React.createElement(Box, { marginTop: 1 },
|
|
50
|
+
React.createElement(Text, { dimColor: true }, "Press \u2191\u2193 to navigate, Enter to select, ESC to cancel"))));
|
|
51
|
+
};
|
|
52
|
+
export default PresetSelector;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigurationData, StatusHookConfig, ShortcutConfig, WorktreeConfig, CommandConfig } from '../types/index.js';
|
|
1
|
+
import { ConfigurationData, StatusHookConfig, ShortcutConfig, WorktreeConfig, CommandConfig, CommandPreset, CommandPresetsConfig } from '../types/index.js';
|
|
2
2
|
export declare class ConfigurationManager {
|
|
3
3
|
private configPath;
|
|
4
4
|
private legacyShortcutsPath;
|
|
@@ -17,5 +17,15 @@ export declare class ConfigurationManager {
|
|
|
17
17
|
setWorktreeConfig(worktreeConfig: WorktreeConfig): void;
|
|
18
18
|
getCommandConfig(): CommandConfig;
|
|
19
19
|
setCommandConfig(commandConfig: CommandConfig): void;
|
|
20
|
+
private migrateLegacyCommandToPresets;
|
|
21
|
+
getCommandPresets(): CommandPresetsConfig;
|
|
22
|
+
setCommandPresets(presets: CommandPresetsConfig): void;
|
|
23
|
+
getDefaultPreset(): CommandPreset;
|
|
24
|
+
getPresetById(id: string): CommandPreset | undefined;
|
|
25
|
+
addPreset(preset: CommandPreset): void;
|
|
26
|
+
deletePreset(id: string): void;
|
|
27
|
+
setDefaultPreset(id: string): void;
|
|
28
|
+
getSelectPresetOnStart(): boolean;
|
|
29
|
+
setSelectPresetOnStart(enabled: boolean): void;
|
|
20
30
|
}
|
|
21
31
|
export declare const configurationManager: ConfigurationManager;
|
|
@@ -73,6 +73,8 @@ export class ConfigurationManager {
|
|
|
73
73
|
command: 'claude',
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
// Migrate legacy command config to presets if needed
|
|
77
|
+
this.migrateLegacyCommandToPresets();
|
|
76
78
|
}
|
|
77
79
|
migrateLegacyShortcuts() {
|
|
78
80
|
if (existsSync(this.legacyShortcutsPath)) {
|
|
@@ -131,13 +133,119 @@ export class ConfigurationManager {
|
|
|
131
133
|
this.saveConfig();
|
|
132
134
|
}
|
|
133
135
|
getCommandConfig() {
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
// For backward compatibility, return the default preset as CommandConfig
|
|
137
|
+
const defaultPreset = this.getDefaultPreset();
|
|
138
|
+
return {
|
|
139
|
+
command: defaultPreset.command,
|
|
140
|
+
args: defaultPreset.args,
|
|
141
|
+
fallbackArgs: defaultPreset.fallbackArgs,
|
|
142
|
+
};
|
|
137
143
|
}
|
|
138
144
|
setCommandConfig(commandConfig) {
|
|
139
145
|
this.config.command = commandConfig;
|
|
146
|
+
// Also update the default preset for backward compatibility
|
|
147
|
+
if (this.config.commandPresets) {
|
|
148
|
+
const defaultPreset = this.config.commandPresets.presets.find(p => p.id === this.config.commandPresets.defaultPresetId);
|
|
149
|
+
if (defaultPreset) {
|
|
150
|
+
defaultPreset.command = commandConfig.command;
|
|
151
|
+
defaultPreset.args = commandConfig.args;
|
|
152
|
+
defaultPreset.fallbackArgs = commandConfig.fallbackArgs;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.saveConfig();
|
|
156
|
+
}
|
|
157
|
+
migrateLegacyCommandToPresets() {
|
|
158
|
+
// Only migrate if we have legacy command config but no presets
|
|
159
|
+
if (this.config.command && !this.config.commandPresets) {
|
|
160
|
+
const defaultPreset = {
|
|
161
|
+
id: '1',
|
|
162
|
+
name: 'Main',
|
|
163
|
+
command: this.config.command.command,
|
|
164
|
+
args: this.config.command.args,
|
|
165
|
+
fallbackArgs: this.config.command.fallbackArgs,
|
|
166
|
+
};
|
|
167
|
+
this.config.commandPresets = {
|
|
168
|
+
presets: [defaultPreset],
|
|
169
|
+
defaultPresetId: '1',
|
|
170
|
+
};
|
|
171
|
+
this.saveConfig();
|
|
172
|
+
}
|
|
173
|
+
// Ensure default presets if none exist
|
|
174
|
+
if (!this.config.commandPresets) {
|
|
175
|
+
this.config.commandPresets = {
|
|
176
|
+
presets: [
|
|
177
|
+
{
|
|
178
|
+
id: '1',
|
|
179
|
+
name: 'Main',
|
|
180
|
+
command: 'claude',
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
defaultPresetId: '1',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
getCommandPresets() {
|
|
188
|
+
if (!this.config.commandPresets) {
|
|
189
|
+
this.migrateLegacyCommandToPresets();
|
|
190
|
+
}
|
|
191
|
+
return this.config.commandPresets;
|
|
192
|
+
}
|
|
193
|
+
setCommandPresets(presets) {
|
|
194
|
+
this.config.commandPresets = presets;
|
|
140
195
|
this.saveConfig();
|
|
141
196
|
}
|
|
197
|
+
getDefaultPreset() {
|
|
198
|
+
const presets = this.getCommandPresets();
|
|
199
|
+
const defaultPreset = presets.presets.find(p => p.id === presets.defaultPresetId);
|
|
200
|
+
// If default preset not found, return the first one
|
|
201
|
+
return defaultPreset || presets.presets[0];
|
|
202
|
+
}
|
|
203
|
+
getPresetById(id) {
|
|
204
|
+
const presets = this.getCommandPresets();
|
|
205
|
+
return presets.presets.find(p => p.id === id);
|
|
206
|
+
}
|
|
207
|
+
addPreset(preset) {
|
|
208
|
+
const presets = this.getCommandPresets();
|
|
209
|
+
// Replace if exists, otherwise add
|
|
210
|
+
const existingIndex = presets.presets.findIndex(p => p.id === preset.id);
|
|
211
|
+
if (existingIndex >= 0) {
|
|
212
|
+
presets.presets[existingIndex] = preset;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
presets.presets.push(preset);
|
|
216
|
+
}
|
|
217
|
+
this.setCommandPresets(presets);
|
|
218
|
+
}
|
|
219
|
+
deletePreset(id) {
|
|
220
|
+
const presets = this.getCommandPresets();
|
|
221
|
+
// Don't delete if it's the last preset
|
|
222
|
+
if (presets.presets.length <= 1) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Remove the preset
|
|
226
|
+
presets.presets = presets.presets.filter(p => p.id !== id);
|
|
227
|
+
// Update default if needed
|
|
228
|
+
if (presets.defaultPresetId === id && presets.presets.length > 0) {
|
|
229
|
+
presets.defaultPresetId = presets.presets[0].id;
|
|
230
|
+
}
|
|
231
|
+
this.setCommandPresets(presets);
|
|
232
|
+
}
|
|
233
|
+
setDefaultPreset(id) {
|
|
234
|
+
const presets = this.getCommandPresets();
|
|
235
|
+
// Only update if preset exists
|
|
236
|
+
if (presets.presets.some(p => p.id === id)) {
|
|
237
|
+
presets.defaultPresetId = id;
|
|
238
|
+
this.setCommandPresets(presets);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
getSelectPresetOnStart() {
|
|
242
|
+
const presets = this.getCommandPresets();
|
|
243
|
+
return presets.selectPresetOnStart ?? false;
|
|
244
|
+
}
|
|
245
|
+
setSelectPresetOnStart(enabled) {
|
|
246
|
+
const presets = this.getCommandPresets();
|
|
247
|
+
presets.selectPresetOnStart = enabled;
|
|
248
|
+
this.setCommandPresets(presets);
|
|
249
|
+
}
|
|
142
250
|
}
|
|
143
251
|
export const configurationManager = new ConfigurationManager();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { ConfigurationManager } from './configurationManager.js';
|
|
4
|
+
// Mock fs module
|
|
5
|
+
vi.mock('fs', () => ({
|
|
6
|
+
existsSync: vi.fn(),
|
|
7
|
+
mkdirSync: vi.fn(),
|
|
8
|
+
readFileSync: vi.fn(),
|
|
9
|
+
writeFileSync: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
// Mock os module
|
|
12
|
+
vi.mock('os', () => ({
|
|
13
|
+
homedir: vi.fn(() => '/home/test'),
|
|
14
|
+
}));
|
|
15
|
+
describe('ConfigurationManager - selectPresetOnStart', () => {
|
|
16
|
+
let configManager;
|
|
17
|
+
let mockConfigData;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// Reset all mocks
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
// Default mock config data
|
|
22
|
+
mockConfigData = {
|
|
23
|
+
shortcuts: {
|
|
24
|
+
returnToMenu: { ctrl: true, key: 'e' },
|
|
25
|
+
cancel: { key: 'escape' },
|
|
26
|
+
},
|
|
27
|
+
commandPresets: {
|
|
28
|
+
presets: [
|
|
29
|
+
{
|
|
30
|
+
id: '1',
|
|
31
|
+
name: 'Main',
|
|
32
|
+
command: 'claude',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: '2',
|
|
36
|
+
name: 'Development',
|
|
37
|
+
command: 'claude',
|
|
38
|
+
args: ['--resume'],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
defaultPresetId: '1',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
// Mock file system operations
|
|
45
|
+
existsSync.mockImplementation((path) => {
|
|
46
|
+
return path.includes('config.json');
|
|
47
|
+
});
|
|
48
|
+
readFileSync.mockImplementation(() => {
|
|
49
|
+
return JSON.stringify(mockConfigData);
|
|
50
|
+
});
|
|
51
|
+
mkdirSync.mockImplementation(() => { });
|
|
52
|
+
writeFileSync.mockImplementation(() => { });
|
|
53
|
+
// Create new instance for each test
|
|
54
|
+
configManager = new ConfigurationManager();
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.resetAllMocks();
|
|
58
|
+
});
|
|
59
|
+
describe('getSelectPresetOnStart', () => {
|
|
60
|
+
it('should return false by default', () => {
|
|
61
|
+
const result = configManager.getSelectPresetOnStart();
|
|
62
|
+
expect(result).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
it('should return true when configured', () => {
|
|
65
|
+
mockConfigData.commandPresets.selectPresetOnStart = true;
|
|
66
|
+
configManager = new ConfigurationManager();
|
|
67
|
+
const result = configManager.getSelectPresetOnStart();
|
|
68
|
+
expect(result).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
it('should return false when explicitly set to false', () => {
|
|
71
|
+
mockConfigData.commandPresets.selectPresetOnStart = false;
|
|
72
|
+
configManager = new ConfigurationManager();
|
|
73
|
+
const result = configManager.getSelectPresetOnStart();
|
|
74
|
+
expect(result).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('setSelectPresetOnStart', () => {
|
|
78
|
+
it('should set selectPresetOnStart to true', () => {
|
|
79
|
+
configManager.setSelectPresetOnStart(true);
|
|
80
|
+
const result = configManager.getSelectPresetOnStart();
|
|
81
|
+
expect(result).toBe(true);
|
|
82
|
+
// Verify that config was saved
|
|
83
|
+
expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), expect.stringContaining('"selectPresetOnStart": true'));
|
|
84
|
+
});
|
|
85
|
+
it('should set selectPresetOnStart to false', () => {
|
|
86
|
+
// First set to true
|
|
87
|
+
configManager.setSelectPresetOnStart(true);
|
|
88
|
+
// Then set to false
|
|
89
|
+
configManager.setSelectPresetOnStart(false);
|
|
90
|
+
const result = configManager.getSelectPresetOnStart();
|
|
91
|
+
expect(result).toBe(false);
|
|
92
|
+
// Verify that config was saved
|
|
93
|
+
expect(writeFileSync).toHaveBeenLastCalledWith(expect.stringContaining('config.json'), expect.stringContaining('"selectPresetOnStart": false'));
|
|
94
|
+
});
|
|
95
|
+
it('should preserve other preset configuration when setting selectPresetOnStart', () => {
|
|
96
|
+
configManager.setSelectPresetOnStart(true);
|
|
97
|
+
const presets = configManager.getCommandPresets();
|
|
98
|
+
expect(presets.presets).toHaveLength(2);
|
|
99
|
+
expect(presets.defaultPresetId).toBe('1');
|
|
100
|
+
expect(presets.selectPresetOnStart).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { ConfigurationManager } from './configurationManager.js';
|
|
4
|
+
// Mock fs module
|
|
5
|
+
vi.mock('fs', () => ({
|
|
6
|
+
existsSync: vi.fn(),
|
|
7
|
+
mkdirSync: vi.fn(),
|
|
8
|
+
readFileSync: vi.fn(),
|
|
9
|
+
writeFileSync: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
// Mock os module
|
|
12
|
+
vi.mock('os', () => ({
|
|
13
|
+
homedir: vi.fn(() => '/home/test'),
|
|
14
|
+
}));
|
|
15
|
+
describe('ConfigurationManager - Command Presets', () => {
|
|
16
|
+
let configManager;
|
|
17
|
+
let mockConfigData;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// Reset all mocks
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
// Default mock config data
|
|
22
|
+
mockConfigData = {
|
|
23
|
+
shortcuts: {
|
|
24
|
+
returnToMenu: { ctrl: true, key: 'e' },
|
|
25
|
+
cancel: { key: 'escape' },
|
|
26
|
+
},
|
|
27
|
+
command: {
|
|
28
|
+
command: 'claude',
|
|
29
|
+
args: ['--existing'],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
// Mock file system operations
|
|
33
|
+
existsSync.mockImplementation((path) => {
|
|
34
|
+
return path.includes('config.json');
|
|
35
|
+
});
|
|
36
|
+
readFileSync.mockImplementation(() => {
|
|
37
|
+
return JSON.stringify(mockConfigData);
|
|
38
|
+
});
|
|
39
|
+
mkdirSync.mockImplementation(() => { });
|
|
40
|
+
writeFileSync.mockImplementation(() => { });
|
|
41
|
+
// Create new instance for each test
|
|
42
|
+
configManager = new ConfigurationManager();
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.resetAllMocks();
|
|
46
|
+
});
|
|
47
|
+
describe('getCommandPresets', () => {
|
|
48
|
+
it('should return default presets when no presets are configured', () => {
|
|
49
|
+
// Remove command config for this test
|
|
50
|
+
delete mockConfigData.command;
|
|
51
|
+
configManager = new ConfigurationManager();
|
|
52
|
+
const presets = configManager.getCommandPresets();
|
|
53
|
+
expect(presets).toBeDefined();
|
|
54
|
+
expect(presets.presets).toHaveLength(1);
|
|
55
|
+
expect(presets.presets[0]).toEqual({
|
|
56
|
+
id: '1',
|
|
57
|
+
name: 'Main',
|
|
58
|
+
command: 'claude',
|
|
59
|
+
});
|
|
60
|
+
expect(presets.defaultPresetId).toBe('1');
|
|
61
|
+
});
|
|
62
|
+
it('should return configured presets', () => {
|
|
63
|
+
mockConfigData.commandPresets = {
|
|
64
|
+
presets: [
|
|
65
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
66
|
+
{ id: '2', name: 'Development', command: 'claude', args: ['--resume'] },
|
|
67
|
+
],
|
|
68
|
+
defaultPresetId: '2',
|
|
69
|
+
};
|
|
70
|
+
configManager = new ConfigurationManager();
|
|
71
|
+
const presets = configManager.getCommandPresets();
|
|
72
|
+
expect(presets.presets).toHaveLength(2);
|
|
73
|
+
expect(presets.defaultPresetId).toBe('2');
|
|
74
|
+
});
|
|
75
|
+
it('should migrate legacy command config to presets on first access', () => {
|
|
76
|
+
// Config has legacy command but no presets
|
|
77
|
+
mockConfigData.command = {
|
|
78
|
+
command: 'claude',
|
|
79
|
+
args: ['--resume'],
|
|
80
|
+
fallbackArgs: ['--no-mcp'],
|
|
81
|
+
};
|
|
82
|
+
delete mockConfigData.commandPresets;
|
|
83
|
+
configManager = new ConfigurationManager();
|
|
84
|
+
const presets = configManager.getCommandPresets();
|
|
85
|
+
expect(presets.presets).toHaveLength(1);
|
|
86
|
+
expect(presets.presets[0]).toEqual({
|
|
87
|
+
id: '1',
|
|
88
|
+
name: 'Main',
|
|
89
|
+
command: 'claude',
|
|
90
|
+
args: ['--resume'],
|
|
91
|
+
fallbackArgs: ['--no-mcp'],
|
|
92
|
+
});
|
|
93
|
+
expect(presets.defaultPresetId).toBe('1');
|
|
94
|
+
// Verify that writeFileSync was called to save the migration
|
|
95
|
+
expect(writeFileSync).toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('setCommandPresets', () => {
|
|
99
|
+
it('should save new presets configuration', () => {
|
|
100
|
+
const newPresets = {
|
|
101
|
+
presets: [
|
|
102
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
103
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
104
|
+
],
|
|
105
|
+
defaultPresetId: '2',
|
|
106
|
+
};
|
|
107
|
+
configManager.setCommandPresets(newPresets);
|
|
108
|
+
expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('config.json'), expect.stringContaining('commandPresets'));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('getDefaultPreset', () => {
|
|
112
|
+
it('should return the default preset', () => {
|
|
113
|
+
mockConfigData.commandPresets = {
|
|
114
|
+
presets: [
|
|
115
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
116
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
117
|
+
],
|
|
118
|
+
defaultPresetId: '2',
|
|
119
|
+
};
|
|
120
|
+
configManager = new ConfigurationManager();
|
|
121
|
+
const defaultPreset = configManager.getDefaultPreset();
|
|
122
|
+
expect(defaultPreset).toEqual({
|
|
123
|
+
id: '2',
|
|
124
|
+
name: 'Custom',
|
|
125
|
+
command: 'claude',
|
|
126
|
+
args: ['--custom'],
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
it('should return first preset if defaultPresetId is invalid', () => {
|
|
130
|
+
mockConfigData.commandPresets = {
|
|
131
|
+
presets: [
|
|
132
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
133
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
134
|
+
],
|
|
135
|
+
defaultPresetId: 'invalid',
|
|
136
|
+
};
|
|
137
|
+
configManager = new ConfigurationManager();
|
|
138
|
+
const defaultPreset = configManager.getDefaultPreset();
|
|
139
|
+
expect(defaultPreset).toEqual({
|
|
140
|
+
id: '1',
|
|
141
|
+
name: 'Main',
|
|
142
|
+
command: 'claude',
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('getPresetById', () => {
|
|
147
|
+
it('should return preset by id', () => {
|
|
148
|
+
mockConfigData.commandPresets = {
|
|
149
|
+
presets: [
|
|
150
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
151
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
152
|
+
],
|
|
153
|
+
defaultPresetId: '1',
|
|
154
|
+
};
|
|
155
|
+
configManager = new ConfigurationManager();
|
|
156
|
+
const preset = configManager.getPresetById('2');
|
|
157
|
+
expect(preset).toEqual({
|
|
158
|
+
id: '2',
|
|
159
|
+
name: 'Custom',
|
|
160
|
+
command: 'claude',
|
|
161
|
+
args: ['--custom'],
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
it('should return undefined for non-existent id', () => {
|
|
165
|
+
mockConfigData.commandPresets = {
|
|
166
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
167
|
+
defaultPresetId: '1',
|
|
168
|
+
};
|
|
169
|
+
configManager = new ConfigurationManager();
|
|
170
|
+
const preset = configManager.getPresetById('999');
|
|
171
|
+
expect(preset).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe('addPreset', () => {
|
|
175
|
+
it('should add a new preset', () => {
|
|
176
|
+
mockConfigData.commandPresets = {
|
|
177
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
178
|
+
defaultPresetId: '1',
|
|
179
|
+
};
|
|
180
|
+
configManager = new ConfigurationManager();
|
|
181
|
+
const newPreset = {
|
|
182
|
+
id: '2',
|
|
183
|
+
name: 'New Preset',
|
|
184
|
+
command: 'claude',
|
|
185
|
+
args: ['--new'],
|
|
186
|
+
};
|
|
187
|
+
configManager.addPreset(newPreset);
|
|
188
|
+
const presets = configManager.getCommandPresets();
|
|
189
|
+
expect(presets.presets).toHaveLength(2);
|
|
190
|
+
expect(presets.presets[1]).toEqual(newPreset);
|
|
191
|
+
});
|
|
192
|
+
it('should replace preset with same id', () => {
|
|
193
|
+
mockConfigData.commandPresets = {
|
|
194
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
195
|
+
defaultPresetId: '1',
|
|
196
|
+
};
|
|
197
|
+
configManager = new ConfigurationManager();
|
|
198
|
+
const updatedPreset = {
|
|
199
|
+
id: '1',
|
|
200
|
+
name: 'Updated Default',
|
|
201
|
+
command: 'claude',
|
|
202
|
+
args: ['--updated'],
|
|
203
|
+
};
|
|
204
|
+
configManager.addPreset(updatedPreset);
|
|
205
|
+
const presets = configManager.getCommandPresets();
|
|
206
|
+
expect(presets.presets).toHaveLength(1);
|
|
207
|
+
expect(presets.presets[0]).toEqual(updatedPreset);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
describe('deletePreset', () => {
|
|
211
|
+
it('should delete preset by id', () => {
|
|
212
|
+
mockConfigData.commandPresets = {
|
|
213
|
+
presets: [
|
|
214
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
215
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
216
|
+
],
|
|
217
|
+
defaultPresetId: '1',
|
|
218
|
+
};
|
|
219
|
+
configManager = new ConfigurationManager();
|
|
220
|
+
configManager.deletePreset('2');
|
|
221
|
+
const presets = configManager.getCommandPresets();
|
|
222
|
+
expect(presets.presets).toHaveLength(1);
|
|
223
|
+
expect(presets.presets[0].id).toBe('1');
|
|
224
|
+
});
|
|
225
|
+
it('should not delete the last preset', () => {
|
|
226
|
+
mockConfigData.commandPresets = {
|
|
227
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
228
|
+
defaultPresetId: '1',
|
|
229
|
+
};
|
|
230
|
+
configManager = new ConfigurationManager();
|
|
231
|
+
configManager.deletePreset('1');
|
|
232
|
+
const presets = configManager.getCommandPresets();
|
|
233
|
+
expect(presets.presets).toHaveLength(1);
|
|
234
|
+
});
|
|
235
|
+
it('should update defaultPresetId if default preset is deleted', () => {
|
|
236
|
+
mockConfigData.commandPresets = {
|
|
237
|
+
presets: [
|
|
238
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
239
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
240
|
+
],
|
|
241
|
+
defaultPresetId: '2',
|
|
242
|
+
};
|
|
243
|
+
configManager = new ConfigurationManager();
|
|
244
|
+
configManager.deletePreset('2');
|
|
245
|
+
const presets = configManager.getCommandPresets();
|
|
246
|
+
expect(presets.defaultPresetId).toBe('1');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('setDefaultPreset', () => {
|
|
250
|
+
it('should update default preset id', () => {
|
|
251
|
+
mockConfigData.commandPresets = {
|
|
252
|
+
presets: [
|
|
253
|
+
{ id: '1', name: 'Main', command: 'claude' },
|
|
254
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
255
|
+
],
|
|
256
|
+
defaultPresetId: '1',
|
|
257
|
+
};
|
|
258
|
+
configManager = new ConfigurationManager();
|
|
259
|
+
configManager.setDefaultPreset('2');
|
|
260
|
+
const presets = configManager.getCommandPresets();
|
|
261
|
+
expect(presets.defaultPresetId).toBe('2');
|
|
262
|
+
});
|
|
263
|
+
it('should not update if preset id does not exist', () => {
|
|
264
|
+
mockConfigData.commandPresets = {
|
|
265
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
266
|
+
defaultPresetId: '1',
|
|
267
|
+
};
|
|
268
|
+
configManager = new ConfigurationManager();
|
|
269
|
+
configManager.setDefaultPreset('999');
|
|
270
|
+
const presets = configManager.getCommandPresets();
|
|
271
|
+
expect(presets.defaultPresetId).toBe('1');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
describe('backward compatibility', () => {
|
|
275
|
+
it('should maintain getCommandConfig for backward compatibility', () => {
|
|
276
|
+
mockConfigData.commandPresets = {
|
|
277
|
+
presets: [
|
|
278
|
+
{ id: '1', name: 'Main', command: 'claude', args: ['--resume'] },
|
|
279
|
+
{ id: '2', name: 'Custom', command: 'claude', args: ['--custom'] },
|
|
280
|
+
],
|
|
281
|
+
defaultPresetId: '1',
|
|
282
|
+
};
|
|
283
|
+
configManager = new ConfigurationManager();
|
|
284
|
+
const commandConfig = configManager.getCommandConfig();
|
|
285
|
+
// Should return the default preset as CommandConfig
|
|
286
|
+
expect(commandConfig).toEqual({
|
|
287
|
+
command: 'claude',
|
|
288
|
+
args: ['--resume'],
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
it('should update default preset when setCommandConfig is called', () => {
|
|
292
|
+
mockConfigData.commandPresets = {
|
|
293
|
+
presets: [{ id: '1', name: 'Main', command: 'claude' }],
|
|
294
|
+
defaultPresetId: '1',
|
|
295
|
+
};
|
|
296
|
+
configManager = new ConfigurationManager();
|
|
297
|
+
const newConfig = {
|
|
298
|
+
command: 'claude',
|
|
299
|
+
args: ['--new-args'],
|
|
300
|
+
fallbackArgs: ['--new-fallback'],
|
|
301
|
+
};
|
|
302
|
+
configManager.setCommandConfig(newConfig);
|
|
303
|
+
const presets = configManager.getCommandPresets();
|
|
304
|
+
expect(presets.presets[0]).toEqual({
|
|
305
|
+
id: '1',
|
|
306
|
+
name: 'Main',
|
|
307
|
+
command: 'claude',
|
|
308
|
+
args: ['--new-args'],
|
|
309
|
+
fallbackArgs: ['--new-fallback'],
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { Session, SessionManager as ISessionManager, SessionState } from '../types/index.js';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
|
-
import pkg from '@xterm/headless';
|
|
4
|
-
declare const Terminal: typeof pkg.Terminal;
|
|
5
3
|
export declare class SessionManager extends EventEmitter implements ISessionManager {
|
|
6
4
|
sessions: Map<string, Session>;
|
|
7
5
|
private waitingWithBottomBorder;
|
|
8
6
|
private busyTimers;
|
|
9
7
|
private spawn;
|
|
10
|
-
detectTerminalState(
|
|
8
|
+
detectTerminalState(session: Session): SessionState;
|
|
11
9
|
constructor();
|
|
12
10
|
createSession(worktreePath: string): Promise<Session>;
|
|
11
|
+
createSessionWithPreset(worktreePath: string, presetId?: string): Promise<Session>;
|
|
13
12
|
private setupDataHandler;
|
|
14
13
|
private setupExitHandler;
|
|
15
14
|
private setupBackgroundHandler;
|
|
@@ -21,4 +20,3 @@ export declare class SessionManager extends EventEmitter implements ISessionMana
|
|
|
21
20
|
private executeStatusHook;
|
|
22
21
|
destroy(): void;
|
|
23
22
|
}
|
|
24
|
-
export {};
|