ccmanager 0.2.0 → 0.2.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/dist/components/App.js +35 -1
- package/dist/components/ConfigureCommand.js +367 -121
- 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 +1 -0
- package/dist/services/sessionManager.js +69 -0
- package/dist/services/sessionManager.test.js +103 -0
- package/dist/types/index.d.ts +13 -0
- package/package.json +1 -1
|
@@ -108,6 +108,75 @@ export class SessionManager extends EventEmitter {
|
|
|
108
108
|
this.emit('sessionCreated', session);
|
|
109
109
|
return session;
|
|
110
110
|
}
|
|
111
|
+
async createSessionWithPreset(worktreePath, presetId) {
|
|
112
|
+
// Check if session already exists
|
|
113
|
+
const existing = this.sessions.get(worktreePath);
|
|
114
|
+
if (existing) {
|
|
115
|
+
return existing;
|
|
116
|
+
}
|
|
117
|
+
const id = `session-${Date.now()}-${Math.random()
|
|
118
|
+
.toString(36)
|
|
119
|
+
.substr(2, 9)}`;
|
|
120
|
+
// Get preset configuration
|
|
121
|
+
let preset = presetId ? configurationManager.getPresetById(presetId) : null;
|
|
122
|
+
if (!preset) {
|
|
123
|
+
preset = configurationManager.getDefaultPreset();
|
|
124
|
+
}
|
|
125
|
+
const command = preset.command;
|
|
126
|
+
const args = preset.args || [];
|
|
127
|
+
const commandConfig = {
|
|
128
|
+
command: preset.command,
|
|
129
|
+
args: preset.args,
|
|
130
|
+
fallbackArgs: preset.fallbackArgs,
|
|
131
|
+
};
|
|
132
|
+
// Try to spawn the process
|
|
133
|
+
let ptyProcess;
|
|
134
|
+
let isPrimaryCommand = true;
|
|
135
|
+
try {
|
|
136
|
+
ptyProcess = await this.spawn(command, args, worktreePath);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
// If primary command fails and we have fallback args, try them
|
|
140
|
+
if (preset.fallbackArgs) {
|
|
141
|
+
try {
|
|
142
|
+
ptyProcess = await this.spawn(command, preset.fallbackArgs, worktreePath);
|
|
143
|
+
isPrimaryCommand = false;
|
|
144
|
+
}
|
|
145
|
+
catch (_fallbackError) {
|
|
146
|
+
// Both attempts failed, throw the original error
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// No fallback args, throw the error
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Create virtual terminal for state detection
|
|
156
|
+
const terminal = new Terminal({
|
|
157
|
+
cols: process.stdout.columns || 80,
|
|
158
|
+
rows: process.stdout.rows || 24,
|
|
159
|
+
allowProposedApi: true,
|
|
160
|
+
});
|
|
161
|
+
const session = {
|
|
162
|
+
id,
|
|
163
|
+
worktreePath,
|
|
164
|
+
process: ptyProcess,
|
|
165
|
+
state: 'busy', // Session starts as busy when created
|
|
166
|
+
output: [],
|
|
167
|
+
outputHistory: [],
|
|
168
|
+
lastActivity: new Date(),
|
|
169
|
+
isActive: false,
|
|
170
|
+
terminal,
|
|
171
|
+
isPrimaryCommand,
|
|
172
|
+
commandConfig,
|
|
173
|
+
};
|
|
174
|
+
// Set up persistent background data handler for state detection
|
|
175
|
+
this.setupBackgroundHandler(session);
|
|
176
|
+
this.sessions.set(worktreePath, session);
|
|
177
|
+
this.emit('sessionCreated', session);
|
|
178
|
+
return session;
|
|
179
|
+
}
|
|
111
180
|
setupDataHandler(session) {
|
|
112
181
|
// This handler always runs for all data
|
|
113
182
|
session.process.onData((data) => {
|
|
@@ -10,6 +10,8 @@ vi.mock('./configurationManager.js', () => ({
|
|
|
10
10
|
configurationManager: {
|
|
11
11
|
getCommandConfig: vi.fn(),
|
|
12
12
|
getStatusHooks: vi.fn(() => ({})),
|
|
13
|
+
getDefaultPreset: vi.fn(),
|
|
14
|
+
getPresetById: vi.fn(),
|
|
13
15
|
},
|
|
14
16
|
}));
|
|
15
17
|
// Mock Terminal
|
|
@@ -257,4 +259,105 @@ describe('SessionManager', () => {
|
|
|
257
259
|
expect(sessionManager.getSession('/test/worktree')).toBeUndefined();
|
|
258
260
|
});
|
|
259
261
|
});
|
|
262
|
+
describe('createSession with presets', () => {
|
|
263
|
+
it('should use default preset when no preset ID specified', async () => {
|
|
264
|
+
// Setup mock preset
|
|
265
|
+
vi.mocked(configurationManager.getDefaultPreset).mockReturnValue({
|
|
266
|
+
id: '1',
|
|
267
|
+
name: 'Main',
|
|
268
|
+
command: 'claude',
|
|
269
|
+
args: ['--preset-arg'],
|
|
270
|
+
});
|
|
271
|
+
// Setup spawn mock
|
|
272
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
273
|
+
// Create session with preset
|
|
274
|
+
await sessionManager.createSessionWithPreset('/test/worktree');
|
|
275
|
+
// Verify spawn was called with preset config
|
|
276
|
+
expect(spawn).toHaveBeenCalledWith('claude', ['--preset-arg'], {
|
|
277
|
+
name: 'xterm-color',
|
|
278
|
+
cols: expect.any(Number),
|
|
279
|
+
rows: expect.any(Number),
|
|
280
|
+
cwd: '/test/worktree',
|
|
281
|
+
env: process.env,
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
it('should use specific preset when ID provided', async () => {
|
|
285
|
+
// Setup mock preset
|
|
286
|
+
vi.mocked(configurationManager.getPresetById).mockReturnValue({
|
|
287
|
+
id: '2',
|
|
288
|
+
name: 'Development',
|
|
289
|
+
command: 'claude',
|
|
290
|
+
args: ['--resume', '--dev'],
|
|
291
|
+
fallbackArgs: ['--no-mcp'],
|
|
292
|
+
});
|
|
293
|
+
// Setup spawn mock
|
|
294
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
295
|
+
// Create session with specific preset
|
|
296
|
+
await sessionManager.createSessionWithPreset('/test/worktree', '2');
|
|
297
|
+
// Verify getPresetById was called with correct ID
|
|
298
|
+
expect(configurationManager.getPresetById).toHaveBeenCalledWith('2');
|
|
299
|
+
// Verify spawn was called with preset config
|
|
300
|
+
expect(spawn).toHaveBeenCalledWith('claude', ['--resume', '--dev'], {
|
|
301
|
+
name: 'xterm-color',
|
|
302
|
+
cols: expect.any(Number),
|
|
303
|
+
rows: expect.any(Number),
|
|
304
|
+
cwd: '/test/worktree',
|
|
305
|
+
env: process.env,
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
it('should fall back to default preset if specified preset not found', async () => {
|
|
309
|
+
// Setup mocks
|
|
310
|
+
vi.mocked(configurationManager.getPresetById).mockReturnValue(undefined);
|
|
311
|
+
vi.mocked(configurationManager.getDefaultPreset).mockReturnValue({
|
|
312
|
+
id: '1',
|
|
313
|
+
name: 'Main',
|
|
314
|
+
command: 'claude',
|
|
315
|
+
});
|
|
316
|
+
// Setup spawn mock
|
|
317
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
318
|
+
// Create session with non-existent preset
|
|
319
|
+
await sessionManager.createSessionWithPreset('/test/worktree', 'invalid');
|
|
320
|
+
// Verify fallback to default preset
|
|
321
|
+
expect(configurationManager.getDefaultPreset).toHaveBeenCalled();
|
|
322
|
+
expect(spawn).toHaveBeenCalledWith('claude', [], expect.any(Object));
|
|
323
|
+
});
|
|
324
|
+
it('should try fallback args with preset if main command fails', async () => {
|
|
325
|
+
// Setup mock preset with fallback
|
|
326
|
+
vi.mocked(configurationManager.getDefaultPreset).mockReturnValue({
|
|
327
|
+
id: '1',
|
|
328
|
+
name: 'Main',
|
|
329
|
+
command: 'claude',
|
|
330
|
+
args: ['--bad-flag'],
|
|
331
|
+
fallbackArgs: ['--good-flag'],
|
|
332
|
+
});
|
|
333
|
+
// Mock spawn to fail first, succeed second
|
|
334
|
+
let callCount = 0;
|
|
335
|
+
vi.mocked(spawn).mockImplementation(() => {
|
|
336
|
+
callCount++;
|
|
337
|
+
if (callCount === 1) {
|
|
338
|
+
throw new Error('Command failed');
|
|
339
|
+
}
|
|
340
|
+
return mockPty;
|
|
341
|
+
});
|
|
342
|
+
// Create session
|
|
343
|
+
await sessionManager.createSessionWithPreset('/test/worktree');
|
|
344
|
+
// Verify both attempts were made
|
|
345
|
+
expect(spawn).toHaveBeenCalledTimes(2);
|
|
346
|
+
expect(spawn).toHaveBeenNthCalledWith(1, 'claude', ['--bad-flag'], expect.any(Object));
|
|
347
|
+
expect(spawn).toHaveBeenNthCalledWith(2, 'claude', ['--good-flag'], expect.any(Object));
|
|
348
|
+
});
|
|
349
|
+
it('should maintain backward compatibility with createSession', async () => {
|
|
350
|
+
// Setup legacy config
|
|
351
|
+
vi.mocked(configurationManager.getCommandConfig).mockReturnValue({
|
|
352
|
+
command: 'claude',
|
|
353
|
+
args: ['--legacy'],
|
|
354
|
+
});
|
|
355
|
+
// Setup spawn mock
|
|
356
|
+
vi.mocked(spawn).mockReturnValue(mockPty);
|
|
357
|
+
// Create session using legacy method
|
|
358
|
+
await sessionManager.createSession('/test/worktree');
|
|
359
|
+
// Verify legacy method still works
|
|
360
|
+
expect(spawn).toHaveBeenCalledWith('claude', ['--legacy'], expect.any(Object));
|
|
361
|
+
});
|
|
362
|
+
});
|
|
260
363
|
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -61,9 +61,22 @@ export interface CommandConfig {
|
|
|
61
61
|
args?: string[];
|
|
62
62
|
fallbackArgs?: string[];
|
|
63
63
|
}
|
|
64
|
+
export interface CommandPreset {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
command: string;
|
|
68
|
+
args?: string[];
|
|
69
|
+
fallbackArgs?: string[];
|
|
70
|
+
}
|
|
71
|
+
export interface CommandPresetsConfig {
|
|
72
|
+
presets: CommandPreset[];
|
|
73
|
+
defaultPresetId: string;
|
|
74
|
+
selectPresetOnStart?: boolean;
|
|
75
|
+
}
|
|
64
76
|
export interface ConfigurationData {
|
|
65
77
|
shortcuts?: ShortcutConfig;
|
|
66
78
|
statusHooks?: StatusHookConfig;
|
|
67
79
|
worktree?: WorktreeConfig;
|
|
68
80
|
command?: CommandConfig;
|
|
81
|
+
commandPresets?: CommandPresetsConfig;
|
|
69
82
|
}
|