ccmanager 3.3.2 → 3.5.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 +11 -5
- package/dist/components/App.js +17 -3
- package/dist/components/App.test.js +5 -5
- package/dist/components/Configuration.d.ts +2 -0
- package/dist/components/Configuration.js +6 -2
- package/dist/components/ConfigureCommand.js +34 -11
- package/dist/components/ConfigureOther.js +18 -4
- package/dist/components/ConfigureOther.test.js +48 -12
- package/dist/components/ConfigureShortcuts.js +27 -85
- package/dist/components/ConfigureStatusHooks.js +19 -4
- package/dist/components/ConfigureStatusHooks.test.js +46 -12
- package/dist/components/ConfigureWorktree.js +18 -4
- package/dist/components/ConfigureWorktreeHooks.js +19 -4
- package/dist/components/ConfigureWorktreeHooks.test.js +49 -14
- package/dist/components/Menu.js +72 -14
- package/dist/components/Menu.recent-projects.test.js +2 -0
- package/dist/components/Menu.test.js +2 -0
- package/dist/components/NewWorktree.js +2 -2
- package/dist/components/NewWorktree.test.js +6 -6
- package/dist/components/PresetSelector.js +2 -2
- package/dist/constants/statusIcons.d.ts +4 -1
- package/dist/constants/statusIcons.js +10 -1
- package/dist/constants/statusIcons.test.js +42 -0
- package/dist/contexts/ConfigEditorContext.d.ts +21 -0
- package/dist/contexts/ConfigEditorContext.js +25 -0
- package/dist/services/autoApprovalVerifier.js +3 -3
- package/dist/services/autoApprovalVerifier.test.js +2 -2
- package/dist/services/config/configEditor.d.ts +46 -0
- package/dist/services/{configurationManager.effect.test.js → config/configEditor.effect.test.js} +46 -49
- package/dist/services/config/configEditor.js +101 -0
- package/dist/services/{configurationManager.selectPresetOnStart.test.js → config/configEditor.selectPresetOnStart.test.js} +27 -19
- package/dist/services/config/configEditor.test.d.ts +1 -0
- package/dist/services/{configurationManager.test.js → config/configEditor.test.js} +60 -132
- package/dist/services/config/configReader.d.ts +28 -0
- package/dist/services/config/configReader.js +95 -0
- package/dist/services/config/configReader.multiProject.test.d.ts +1 -0
- package/dist/services/config/configReader.multiProject.test.js +136 -0
- package/dist/services/config/globalConfigManager.d.ts +30 -0
- package/dist/services/config/globalConfigManager.js +216 -0
- package/dist/services/config/index.d.ts +13 -0
- package/dist/services/config/index.js +13 -0
- package/dist/services/config/projectConfigManager.d.ts +41 -0
- package/dist/services/config/projectConfigManager.js +181 -0
- package/dist/services/config/projectConfigManager.test.d.ts +1 -0
- package/dist/services/config/projectConfigManager.test.js +105 -0
- package/dist/services/config/testUtils.d.ts +81 -0
- package/dist/services/config/testUtils.js +351 -0
- package/dist/services/sessionManager.autoApproval.test.js +9 -6
- package/dist/services/sessionManager.d.ts +2 -0
- package/dist/services/sessionManager.effect.test.js +27 -18
- package/dist/services/sessionManager.js +43 -40
- package/dist/services/sessionManager.statePersistence.test.js +5 -4
- package/dist/services/sessionManager.test.js +71 -49
- package/dist/services/shortcutManager.d.ts +0 -1
- package/dist/services/shortcutManager.js +5 -16
- package/dist/services/shortcutManager.test.js +2 -2
- package/dist/services/stateDetector/base.d.ts +1 -0
- package/dist/services/stateDetector/claude.d.ts +1 -0
- package/dist/services/stateDetector/claude.js +8 -0
- package/dist/services/stateDetector/claude.test.js +102 -0
- package/dist/services/stateDetector/cline.d.ts +1 -0
- package/dist/services/stateDetector/cline.js +3 -0
- package/dist/services/stateDetector/codex.d.ts +1 -0
- package/dist/services/stateDetector/codex.js +3 -0
- package/dist/services/stateDetector/cursor.d.ts +1 -0
- package/dist/services/stateDetector/cursor.js +3 -0
- package/dist/services/stateDetector/gemini.d.ts +1 -0
- package/dist/services/stateDetector/gemini.js +3 -0
- package/dist/services/stateDetector/github-copilot.d.ts +1 -0
- package/dist/services/stateDetector/github-copilot.js +3 -0
- package/dist/services/stateDetector/opencode.d.ts +1 -0
- package/dist/services/stateDetector/opencode.js +3 -0
- package/dist/services/stateDetector/types.d.ts +1 -0
- package/dist/services/worktreeService.d.ts +12 -0
- package/dist/services/worktreeService.js +24 -4
- package/dist/services/worktreeService.sort.test.js +105 -109
- package/dist/services/worktreeService.test.js +5 -5
- package/dist/types/index.d.ts +47 -7
- package/dist/utils/gitUtils.d.ts +8 -0
- package/dist/utils/gitUtils.js +32 -0
- package/dist/utils/hookExecutor.js +2 -2
- package/dist/utils/hookExecutor.test.js +13 -12
- package/dist/utils/mutex.d.ts +1 -0
- package/dist/utils/mutex.js +1 -0
- package/dist/utils/worktreeUtils.js +3 -2
- package/dist/utils/worktreeUtils.test.js +2 -1
- package/package.json +7 -7
- package/dist/services/configurationManager.d.ts +0 -121
- package/dist/services/configurationManager.js +0 -597
- /package/dist/{services/configurationManager.effect.test.d.ts → constants/statusIcons.test.d.ts} +0 -0
- /package/dist/services/{configurationManager.selectPresetOnStart.test.d.ts → config/configEditor.effect.test.d.ts} +0 -0
- /package/dist/services/{configurationManager.test.d.ts → config/configEditor.selectPresetOnStart.test.d.ts} +0 -0
|
@@ -1,597 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'os';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
-
import { Effect, Either } from 'effect';
|
|
5
|
-
import { DEFAULT_SHORTCUTS, } from '../types/index.js';
|
|
6
|
-
import { FileSystemError, ConfigError, ValidationError, } from '../types/errors.js';
|
|
7
|
-
export class ConfigurationManager {
|
|
8
|
-
constructor() {
|
|
9
|
-
Object.defineProperty(this, "configPath", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
configurable: true,
|
|
12
|
-
writable: true,
|
|
13
|
-
value: void 0
|
|
14
|
-
});
|
|
15
|
-
Object.defineProperty(this, "legacyShortcutsPath", {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: void 0
|
|
20
|
-
});
|
|
21
|
-
Object.defineProperty(this, "configDir", {
|
|
22
|
-
enumerable: true,
|
|
23
|
-
configurable: true,
|
|
24
|
-
writable: true,
|
|
25
|
-
value: void 0
|
|
26
|
-
});
|
|
27
|
-
Object.defineProperty(this, "config", {
|
|
28
|
-
enumerable: true,
|
|
29
|
-
configurable: true,
|
|
30
|
-
writable: true,
|
|
31
|
-
value: {}
|
|
32
|
-
});
|
|
33
|
-
Object.defineProperty(this, "worktreeLastOpened", {
|
|
34
|
-
enumerable: true,
|
|
35
|
-
configurable: true,
|
|
36
|
-
writable: true,
|
|
37
|
-
value: new Map()
|
|
38
|
-
});
|
|
39
|
-
// Determine config directory based on platform
|
|
40
|
-
const homeDir = homedir();
|
|
41
|
-
this.configDir =
|
|
42
|
-
process.platform === 'win32'
|
|
43
|
-
? join(process.env['APPDATA'] || join(homeDir, 'AppData', 'Roaming'), 'ccmanager')
|
|
44
|
-
: join(homeDir, '.config', 'ccmanager');
|
|
45
|
-
// Ensure config directory exists
|
|
46
|
-
if (!existsSync(this.configDir)) {
|
|
47
|
-
mkdirSync(this.configDir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
this.configPath = join(this.configDir, 'config.json');
|
|
50
|
-
this.legacyShortcutsPath = join(this.configDir, 'shortcuts.json');
|
|
51
|
-
this.loadConfig();
|
|
52
|
-
}
|
|
53
|
-
loadConfig() {
|
|
54
|
-
// Try to load the new config file
|
|
55
|
-
if (existsSync(this.configPath)) {
|
|
56
|
-
try {
|
|
57
|
-
const configData = readFileSync(this.configPath, 'utf-8');
|
|
58
|
-
this.config = JSON.parse(configData);
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
console.error('Failed to load configuration:', error);
|
|
62
|
-
this.config = {};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
// If new config doesn't exist, check for legacy shortcuts.json
|
|
67
|
-
this.migrateLegacyShortcuts();
|
|
68
|
-
}
|
|
69
|
-
// Check if shortcuts need to be loaded from legacy file
|
|
70
|
-
// This handles the case where config.json exists but doesn't have shortcuts
|
|
71
|
-
if (!this.config.shortcuts && existsSync(this.legacyShortcutsPath)) {
|
|
72
|
-
this.migrateLegacyShortcuts();
|
|
73
|
-
}
|
|
74
|
-
// Ensure default values
|
|
75
|
-
if (!this.config.shortcuts) {
|
|
76
|
-
this.config.shortcuts = DEFAULT_SHORTCUTS;
|
|
77
|
-
}
|
|
78
|
-
if (!this.config.statusHooks) {
|
|
79
|
-
this.config.statusHooks = {};
|
|
80
|
-
}
|
|
81
|
-
if (!this.config.worktreeHooks) {
|
|
82
|
-
this.config.worktreeHooks = {};
|
|
83
|
-
}
|
|
84
|
-
if (!this.config.worktree) {
|
|
85
|
-
this.config.worktree = {
|
|
86
|
-
autoDirectory: false,
|
|
87
|
-
copySessionData: true,
|
|
88
|
-
sortByLastSession: false,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
if (!Object.prototype.hasOwnProperty.call(this.config.worktree, 'copySessionData')) {
|
|
92
|
-
this.config.worktree.copySessionData = true;
|
|
93
|
-
}
|
|
94
|
-
if (!Object.prototype.hasOwnProperty.call(this.config.worktree, 'sortByLastSession')) {
|
|
95
|
-
this.config.worktree.sortByLastSession = false;
|
|
96
|
-
}
|
|
97
|
-
if (!this.config.command) {
|
|
98
|
-
this.config.command = {
|
|
99
|
-
command: 'claude',
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
if (!this.config.autoApproval) {
|
|
103
|
-
this.config.autoApproval = {
|
|
104
|
-
enabled: false,
|
|
105
|
-
timeout: 30,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'enabled')) {
|
|
110
|
-
this.config.autoApproval.enabled = false;
|
|
111
|
-
}
|
|
112
|
-
if (!Object.prototype.hasOwnProperty.call(this.config.autoApproval, 'timeout')) {
|
|
113
|
-
this.config.autoApproval.timeout = 30;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
// Migrate legacy command config to presets if needed
|
|
117
|
-
this.migrateLegacyCommandToPresets();
|
|
118
|
-
}
|
|
119
|
-
migrateLegacyShortcuts() {
|
|
120
|
-
if (existsSync(this.legacyShortcutsPath)) {
|
|
121
|
-
try {
|
|
122
|
-
const shortcutsData = readFileSync(this.legacyShortcutsPath, 'utf-8');
|
|
123
|
-
const shortcuts = JSON.parse(shortcutsData);
|
|
124
|
-
// Validate that it's a valid shortcuts config
|
|
125
|
-
if (shortcuts && typeof shortcuts === 'object') {
|
|
126
|
-
this.config.shortcuts = shortcuts;
|
|
127
|
-
// Save to new config format
|
|
128
|
-
this.saveConfig();
|
|
129
|
-
console.log('Migrated shortcuts from legacy shortcuts.json to config.json');
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
console.error('Failed to migrate legacy shortcuts:', error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
saveConfig() {
|
|
138
|
-
try {
|
|
139
|
-
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
console.error('Failed to save configuration:', error);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
getShortcuts() {
|
|
146
|
-
return this.config.shortcuts || DEFAULT_SHORTCUTS;
|
|
147
|
-
}
|
|
148
|
-
setShortcuts(shortcuts) {
|
|
149
|
-
this.config.shortcuts = shortcuts;
|
|
150
|
-
this.saveConfig();
|
|
151
|
-
}
|
|
152
|
-
getStatusHooks() {
|
|
153
|
-
return this.config.statusHooks || {};
|
|
154
|
-
}
|
|
155
|
-
setStatusHooks(hooks) {
|
|
156
|
-
this.config.statusHooks = hooks;
|
|
157
|
-
this.saveConfig();
|
|
158
|
-
}
|
|
159
|
-
getWorktreeHooks() {
|
|
160
|
-
return this.config.worktreeHooks || {};
|
|
161
|
-
}
|
|
162
|
-
setWorktreeHooks(hooks) {
|
|
163
|
-
this.config.worktreeHooks = hooks;
|
|
164
|
-
this.saveConfig();
|
|
165
|
-
}
|
|
166
|
-
getConfiguration() {
|
|
167
|
-
return this.config;
|
|
168
|
-
}
|
|
169
|
-
setConfiguration(config) {
|
|
170
|
-
this.config = config;
|
|
171
|
-
this.saveConfig();
|
|
172
|
-
}
|
|
173
|
-
getWorktreeConfig() {
|
|
174
|
-
return (this.config.worktree || {
|
|
175
|
-
autoDirectory: false,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
setWorktreeConfig(worktreeConfig) {
|
|
179
|
-
this.config.worktree = worktreeConfig;
|
|
180
|
-
this.saveConfig();
|
|
181
|
-
}
|
|
182
|
-
getAutoApprovalConfig() {
|
|
183
|
-
const config = this.config.autoApproval || {
|
|
184
|
-
enabled: false,
|
|
185
|
-
};
|
|
186
|
-
// Default timeout to 30 seconds if not set
|
|
187
|
-
return {
|
|
188
|
-
...config,
|
|
189
|
-
timeout: config.timeout ?? 30,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
setAutoApprovalConfig(autoApproval) {
|
|
193
|
-
this.config.autoApproval = autoApproval;
|
|
194
|
-
this.saveConfig();
|
|
195
|
-
}
|
|
196
|
-
setAutoApprovalEnabled(enabled) {
|
|
197
|
-
const currentConfig = this.getAutoApprovalConfig();
|
|
198
|
-
this.setAutoApprovalConfig({ ...currentConfig, enabled });
|
|
199
|
-
}
|
|
200
|
-
setAutoApprovalTimeout(timeout) {
|
|
201
|
-
const currentConfig = this.getAutoApprovalConfig();
|
|
202
|
-
this.setAutoApprovalConfig({ ...currentConfig, timeout });
|
|
203
|
-
}
|
|
204
|
-
getCommandConfig() {
|
|
205
|
-
// For backward compatibility, return the default preset as CommandConfig
|
|
206
|
-
const defaultPreset = this.getDefaultPreset();
|
|
207
|
-
return {
|
|
208
|
-
command: defaultPreset.command,
|
|
209
|
-
args: defaultPreset.args,
|
|
210
|
-
fallbackArgs: defaultPreset.fallbackArgs,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
setCommandConfig(commandConfig) {
|
|
214
|
-
this.config.command = commandConfig;
|
|
215
|
-
// Also update the default preset for backward compatibility
|
|
216
|
-
if (this.config.commandPresets) {
|
|
217
|
-
const defaultPreset = this.config.commandPresets.presets.find(p => p.id === this.config.commandPresets.defaultPresetId);
|
|
218
|
-
if (defaultPreset) {
|
|
219
|
-
defaultPreset.command = commandConfig.command;
|
|
220
|
-
defaultPreset.args = commandConfig.args;
|
|
221
|
-
defaultPreset.fallbackArgs = commandConfig.fallbackArgs;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
this.saveConfig();
|
|
225
|
-
}
|
|
226
|
-
migrateLegacyCommandToPresets() {
|
|
227
|
-
// Only migrate if we have legacy command config but no presets
|
|
228
|
-
if (this.config.command && !this.config.commandPresets) {
|
|
229
|
-
const defaultPreset = {
|
|
230
|
-
id: '1',
|
|
231
|
-
name: 'Main',
|
|
232
|
-
command: this.config.command.command,
|
|
233
|
-
args: this.config.command.args,
|
|
234
|
-
fallbackArgs: this.config.command.fallbackArgs,
|
|
235
|
-
};
|
|
236
|
-
this.config.commandPresets = {
|
|
237
|
-
presets: [defaultPreset],
|
|
238
|
-
defaultPresetId: '1',
|
|
239
|
-
};
|
|
240
|
-
this.saveConfig();
|
|
241
|
-
}
|
|
242
|
-
// Ensure default presets if none exist
|
|
243
|
-
if (!this.config.commandPresets) {
|
|
244
|
-
this.config.commandPresets = {
|
|
245
|
-
presets: [
|
|
246
|
-
{
|
|
247
|
-
id: '1',
|
|
248
|
-
name: 'Main',
|
|
249
|
-
command: 'claude',
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
defaultPresetId: '1',
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
getCommandPresets() {
|
|
257
|
-
if (!this.config.commandPresets) {
|
|
258
|
-
this.migrateLegacyCommandToPresets();
|
|
259
|
-
}
|
|
260
|
-
return this.config.commandPresets;
|
|
261
|
-
}
|
|
262
|
-
setCommandPresets(presets) {
|
|
263
|
-
this.config.commandPresets = presets;
|
|
264
|
-
this.saveConfig();
|
|
265
|
-
}
|
|
266
|
-
getDefaultPreset() {
|
|
267
|
-
const presets = this.getCommandPresets();
|
|
268
|
-
const defaultPreset = presets.presets.find(p => p.id === presets.defaultPresetId);
|
|
269
|
-
// If default preset not found, return the first one
|
|
270
|
-
return defaultPreset || presets.presets[0];
|
|
271
|
-
}
|
|
272
|
-
getPresetById(id) {
|
|
273
|
-
const presets = this.getCommandPresets();
|
|
274
|
-
return presets.presets.find(p => p.id === id);
|
|
275
|
-
}
|
|
276
|
-
addPreset(preset) {
|
|
277
|
-
const presets = this.getCommandPresets();
|
|
278
|
-
// Replace if exists, otherwise add
|
|
279
|
-
const existingIndex = presets.presets.findIndex(p => p.id === preset.id);
|
|
280
|
-
if (existingIndex >= 0) {
|
|
281
|
-
presets.presets[existingIndex] = preset;
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
presets.presets.push(preset);
|
|
285
|
-
}
|
|
286
|
-
this.setCommandPresets(presets);
|
|
287
|
-
}
|
|
288
|
-
deletePreset(id) {
|
|
289
|
-
const presets = this.getCommandPresets();
|
|
290
|
-
// Don't delete if it's the last preset
|
|
291
|
-
if (presets.presets.length <= 1) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
// Remove the preset
|
|
295
|
-
presets.presets = presets.presets.filter(p => p.id !== id);
|
|
296
|
-
// Update default if needed
|
|
297
|
-
if (presets.defaultPresetId === id && presets.presets.length > 0) {
|
|
298
|
-
presets.defaultPresetId = presets.presets[0].id;
|
|
299
|
-
}
|
|
300
|
-
this.setCommandPresets(presets);
|
|
301
|
-
}
|
|
302
|
-
setDefaultPreset(id) {
|
|
303
|
-
const presets = this.getCommandPresets();
|
|
304
|
-
// Only update if preset exists
|
|
305
|
-
if (presets.presets.some(p => p.id === id)) {
|
|
306
|
-
presets.defaultPresetId = id;
|
|
307
|
-
this.setCommandPresets(presets);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
getSelectPresetOnStart() {
|
|
311
|
-
const presets = this.getCommandPresets();
|
|
312
|
-
return presets.selectPresetOnStart ?? false;
|
|
313
|
-
}
|
|
314
|
-
setSelectPresetOnStart(enabled) {
|
|
315
|
-
const presets = this.getCommandPresets();
|
|
316
|
-
presets.selectPresetOnStart = enabled;
|
|
317
|
-
this.setCommandPresets(presets);
|
|
318
|
-
}
|
|
319
|
-
getWorktreeLastOpened() {
|
|
320
|
-
return Object.fromEntries(this.worktreeLastOpened);
|
|
321
|
-
}
|
|
322
|
-
setWorktreeLastOpened(worktreePath, timestamp) {
|
|
323
|
-
this.worktreeLastOpened.set(worktreePath, timestamp);
|
|
324
|
-
}
|
|
325
|
-
getWorktreeLastOpenedTime(worktreePath) {
|
|
326
|
-
return this.worktreeLastOpened.get(worktreePath);
|
|
327
|
-
}
|
|
328
|
-
// Effect-based methods for type-safe error handling
|
|
329
|
-
/**
|
|
330
|
-
* Load configuration from file with Effect-based error handling
|
|
331
|
-
*
|
|
332
|
-
* @returns {Effect.Effect<ConfigurationData, FileSystemError | ConfigError, never>} Configuration data on success, errors on failure
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* ```typescript
|
|
336
|
-
* const result = await Effect.runPromise(
|
|
337
|
-
* configManager.loadConfigEffect()
|
|
338
|
-
* );
|
|
339
|
-
* ```
|
|
340
|
-
*/
|
|
341
|
-
loadConfigEffect() {
|
|
342
|
-
return Effect.try({
|
|
343
|
-
try: () => {
|
|
344
|
-
// Try to load the new config file
|
|
345
|
-
if (existsSync(this.configPath)) {
|
|
346
|
-
const configData = readFileSync(this.configPath, 'utf-8');
|
|
347
|
-
const parsedConfig = JSON.parse(configData);
|
|
348
|
-
return this.applyDefaults(parsedConfig);
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
// If new config doesn't exist, check for legacy shortcuts.json
|
|
352
|
-
const migratedConfig = this.migrateLegacyShortcutsSync();
|
|
353
|
-
return this.applyDefaults(migratedConfig || {});
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
catch: (error) => {
|
|
357
|
-
// Determine error type
|
|
358
|
-
if (error instanceof SyntaxError) {
|
|
359
|
-
return new ConfigError({
|
|
360
|
-
configPath: this.configPath,
|
|
361
|
-
reason: 'parse',
|
|
362
|
-
details: String(error),
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return new FileSystemError({
|
|
366
|
-
operation: 'read',
|
|
367
|
-
path: this.configPath,
|
|
368
|
-
cause: String(error),
|
|
369
|
-
});
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Save configuration to file with Effect-based error handling
|
|
375
|
-
*
|
|
376
|
-
* @returns {Effect.Effect<void, FileSystemError, never>} Void on success, FileSystemError on write failure
|
|
377
|
-
*
|
|
378
|
-
* @example
|
|
379
|
-
* ```typescript
|
|
380
|
-
* await Effect.runPromise(
|
|
381
|
-
* configManager.saveConfigEffect(config)
|
|
382
|
-
* );
|
|
383
|
-
* ```
|
|
384
|
-
*/
|
|
385
|
-
saveConfigEffect(config) {
|
|
386
|
-
return Effect.try({
|
|
387
|
-
try: () => {
|
|
388
|
-
this.config = config;
|
|
389
|
-
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
390
|
-
},
|
|
391
|
-
catch: (error) => {
|
|
392
|
-
return new FileSystemError({
|
|
393
|
-
operation: 'write',
|
|
394
|
-
path: this.configPath,
|
|
395
|
-
cause: String(error),
|
|
396
|
-
});
|
|
397
|
-
},
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Validate configuration structure
|
|
402
|
-
* Synchronous validation using Either
|
|
403
|
-
*/
|
|
404
|
-
validateConfig(config) {
|
|
405
|
-
if (!config || typeof config !== 'object') {
|
|
406
|
-
return Either.left(new ValidationError({
|
|
407
|
-
field: 'config',
|
|
408
|
-
constraint: 'must be a valid configuration object',
|
|
409
|
-
receivedValue: config,
|
|
410
|
-
}));
|
|
411
|
-
}
|
|
412
|
-
// Validate shortcuts field if present
|
|
413
|
-
const configObj = config;
|
|
414
|
-
if (configObj['shortcuts'] !== undefined &&
|
|
415
|
-
(typeof configObj['shortcuts'] !== 'object' ||
|
|
416
|
-
configObj['shortcuts'] === null)) {
|
|
417
|
-
return Either.left(new ValidationError({
|
|
418
|
-
field: 'config',
|
|
419
|
-
constraint: 'shortcuts must be a valid object',
|
|
420
|
-
receivedValue: config,
|
|
421
|
-
}));
|
|
422
|
-
}
|
|
423
|
-
// Additional validation could go here
|
|
424
|
-
return Either.right(config);
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Get preset by ID with Either-based error handling
|
|
428
|
-
* Synchronous lookup using Either
|
|
429
|
-
*/
|
|
430
|
-
getPresetByIdEffect(id) {
|
|
431
|
-
const presets = this.getCommandPresets();
|
|
432
|
-
const preset = presets.presets.find(p => p.id === id);
|
|
433
|
-
if (!preset) {
|
|
434
|
-
return Either.left(new ValidationError({
|
|
435
|
-
field: 'presetId',
|
|
436
|
-
constraint: 'Preset not found',
|
|
437
|
-
receivedValue: id,
|
|
438
|
-
}));
|
|
439
|
-
}
|
|
440
|
-
return Either.right(preset);
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Set shortcuts with Effect-based error handling
|
|
444
|
-
*
|
|
445
|
-
* @returns {Effect.Effect<void, FileSystemError, never>} Void on success, FileSystemError on save failure
|
|
446
|
-
*
|
|
447
|
-
* @example
|
|
448
|
-
* ```typescript
|
|
449
|
-
* await Effect.runPromise(
|
|
450
|
-
* configManager.setShortcutsEffect(shortcuts)
|
|
451
|
-
* );
|
|
452
|
-
* ```
|
|
453
|
-
*/
|
|
454
|
-
setShortcutsEffect(shortcuts) {
|
|
455
|
-
this.config.shortcuts = shortcuts;
|
|
456
|
-
return this.saveConfigEffect(this.config);
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Set command presets with Effect-based error handling
|
|
460
|
-
*/
|
|
461
|
-
setCommandPresetsEffect(presets) {
|
|
462
|
-
this.config.commandPresets = presets;
|
|
463
|
-
return this.saveConfigEffect(this.config);
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Add or update preset with Effect-based error handling
|
|
467
|
-
*/
|
|
468
|
-
addPresetEffect(preset) {
|
|
469
|
-
const presets = this.getCommandPresets();
|
|
470
|
-
// Replace if exists, otherwise add
|
|
471
|
-
const existingIndex = presets.presets.findIndex(p => p.id === preset.id);
|
|
472
|
-
if (existingIndex >= 0) {
|
|
473
|
-
presets.presets[existingIndex] = preset;
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
presets.presets.push(preset);
|
|
477
|
-
}
|
|
478
|
-
return this.setCommandPresetsEffect(presets);
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Delete preset with Effect-based error handling
|
|
482
|
-
*/
|
|
483
|
-
deletePresetEffect(id) {
|
|
484
|
-
const presets = this.getCommandPresets();
|
|
485
|
-
// Don't delete if it's the last preset
|
|
486
|
-
if (presets.presets.length <= 1) {
|
|
487
|
-
return Effect.fail(new ValidationError({
|
|
488
|
-
field: 'presetId',
|
|
489
|
-
constraint: 'Cannot delete last preset',
|
|
490
|
-
receivedValue: id,
|
|
491
|
-
}));
|
|
492
|
-
}
|
|
493
|
-
// Remove the preset
|
|
494
|
-
presets.presets = presets.presets.filter(p => p.id !== id);
|
|
495
|
-
// Update default if needed
|
|
496
|
-
if (presets.defaultPresetId === id && presets.presets.length > 0) {
|
|
497
|
-
presets.defaultPresetId = presets.presets[0].id;
|
|
498
|
-
}
|
|
499
|
-
return this.setCommandPresetsEffect(presets);
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Set default preset with Effect-based error handling
|
|
503
|
-
*/
|
|
504
|
-
setDefaultPresetEffect(id) {
|
|
505
|
-
const presets = this.getCommandPresets();
|
|
506
|
-
// Only update if preset exists
|
|
507
|
-
if (!presets.presets.some(p => p.id === id)) {
|
|
508
|
-
return Effect.fail(new ValidationError({
|
|
509
|
-
field: 'presetId',
|
|
510
|
-
constraint: 'Preset not found',
|
|
511
|
-
receivedValue: id,
|
|
512
|
-
}));
|
|
513
|
-
}
|
|
514
|
-
presets.defaultPresetId = id;
|
|
515
|
-
return this.setCommandPresetsEffect(presets);
|
|
516
|
-
}
|
|
517
|
-
// Helper methods
|
|
518
|
-
/**
|
|
519
|
-
* Apply default values to configuration
|
|
520
|
-
*/
|
|
521
|
-
applyDefaults(config) {
|
|
522
|
-
// Ensure default values
|
|
523
|
-
if (!config.shortcuts) {
|
|
524
|
-
config.shortcuts = DEFAULT_SHORTCUTS;
|
|
525
|
-
}
|
|
526
|
-
if (!config.statusHooks) {
|
|
527
|
-
config.statusHooks = {};
|
|
528
|
-
}
|
|
529
|
-
if (!config.worktreeHooks) {
|
|
530
|
-
config.worktreeHooks = {};
|
|
531
|
-
}
|
|
532
|
-
if (!config.worktree) {
|
|
533
|
-
config.worktree = {
|
|
534
|
-
autoDirectory: false,
|
|
535
|
-
copySessionData: true,
|
|
536
|
-
sortByLastSession: false,
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
if (!Object.prototype.hasOwnProperty.call(config.worktree, 'copySessionData')) {
|
|
540
|
-
config.worktree.copySessionData = true;
|
|
541
|
-
}
|
|
542
|
-
if (!Object.prototype.hasOwnProperty.call(config.worktree, 'sortByLastSession')) {
|
|
543
|
-
config.worktree.sortByLastSession = false;
|
|
544
|
-
}
|
|
545
|
-
if (!config.command) {
|
|
546
|
-
config.command = {
|
|
547
|
-
command: 'claude',
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
if (!config.autoApproval) {
|
|
551
|
-
config.autoApproval = {
|
|
552
|
-
enabled: false,
|
|
553
|
-
timeout: 30,
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
if (!Object.prototype.hasOwnProperty.call(config.autoApproval, 'enabled')) {
|
|
558
|
-
config.autoApproval.enabled = false;
|
|
559
|
-
}
|
|
560
|
-
if (!Object.prototype.hasOwnProperty.call(config.autoApproval, 'timeout')) {
|
|
561
|
-
config.autoApproval.timeout = 30;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
return config;
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Synchronous legacy shortcuts migration helper
|
|
568
|
-
*/
|
|
569
|
-
migrateLegacyShortcutsSync() {
|
|
570
|
-
if (existsSync(this.legacyShortcutsPath)) {
|
|
571
|
-
try {
|
|
572
|
-
const shortcutsData = readFileSync(this.legacyShortcutsPath, 'utf-8');
|
|
573
|
-
const shortcuts = JSON.parse(shortcutsData);
|
|
574
|
-
// Validate that it's a valid shortcuts config
|
|
575
|
-
if (shortcuts && typeof shortcuts === 'object') {
|
|
576
|
-
const config = { shortcuts };
|
|
577
|
-
// Save to new config format
|
|
578
|
-
this.config = config;
|
|
579
|
-
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
580
|
-
console.log('Migrated shortcuts from legacy shortcuts.json to config.json');
|
|
581
|
-
return config;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
catch (error) {
|
|
585
|
-
console.error('Failed to migrate legacy shortcuts:', error);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Get whether auto-approval is enabled
|
|
592
|
-
*/
|
|
593
|
-
isAutoApprovalEnabled() {
|
|
594
|
-
return this.config.autoApproval?.enabled ?? false;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
export const configurationManager = new ConfigurationManager();
|
/package/dist/{services/configurationManager.effect.test.d.ts → constants/statusIcons.test.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|