claude-flow 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/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Claude-Flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Config } from '../utils/types.ts';
|
|
6
|
+
import { deepMerge, safeParseJSON } from '../utils/helpers.ts';
|
|
7
|
+
import { ConfigError, ValidationError } from '../utils/errors.ts';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default configuration values
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_CONFIG: Config = {
|
|
13
|
+
orchestrator: {
|
|
14
|
+
maxConcurrentAgents: 10,
|
|
15
|
+
taskQueueSize: 100,
|
|
16
|
+
healthCheckInterval: 30000, // 30 seconds
|
|
17
|
+
shutdownTimeout: 30000, // 30 seconds
|
|
18
|
+
},
|
|
19
|
+
terminal: {
|
|
20
|
+
type: 'auto',
|
|
21
|
+
poolSize: 5,
|
|
22
|
+
recycleAfter: 10, // recycle after 10 uses
|
|
23
|
+
healthCheckInterval: 60000, // 1 minute
|
|
24
|
+
commandTimeout: 300000, // 5 minutes
|
|
25
|
+
},
|
|
26
|
+
memory: {
|
|
27
|
+
backend: 'hybrid',
|
|
28
|
+
cacheSizeMB: 100,
|
|
29
|
+
syncInterval: 5000, // 5 seconds
|
|
30
|
+
conflictResolution: 'crdt',
|
|
31
|
+
retentionDays: 30,
|
|
32
|
+
},
|
|
33
|
+
coordination: {
|
|
34
|
+
maxRetries: 3,
|
|
35
|
+
retryDelay: 1000, // 1 second
|
|
36
|
+
deadlockDetection: true,
|
|
37
|
+
resourceTimeout: 60000, // 1 minute
|
|
38
|
+
messageTimeout: 30000, // 30 seconds
|
|
39
|
+
},
|
|
40
|
+
mcp: {
|
|
41
|
+
transport: 'stdio',
|
|
42
|
+
port: 3000,
|
|
43
|
+
tlsEnabled: false,
|
|
44
|
+
},
|
|
45
|
+
logging: {
|
|
46
|
+
level: 'info',
|
|
47
|
+
format: 'json',
|
|
48
|
+
destination: 'console',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Configuration manager
|
|
54
|
+
*/
|
|
55
|
+
export class ConfigManager {
|
|
56
|
+
private static instance: ConfigManager;
|
|
57
|
+
private config: Config;
|
|
58
|
+
private configPath?: string;
|
|
59
|
+
private profiles: Map<string, Partial<Config>> = new Map();
|
|
60
|
+
private currentProfile?: string;
|
|
61
|
+
private userConfigDir: string;
|
|
62
|
+
|
|
63
|
+
private constructor() {
|
|
64
|
+
this.config = deepClone(DEFAULT_CONFIG);
|
|
65
|
+
this.userConfigDir = this.getUserConfigDir();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gets the singleton instance
|
|
70
|
+
*/
|
|
71
|
+
static getInstance(): ConfigManager {
|
|
72
|
+
if (!ConfigManager.instance) {
|
|
73
|
+
ConfigManager.instance = new ConfigManager();
|
|
74
|
+
}
|
|
75
|
+
return ConfigManager.instance;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Loads configuration from various sources
|
|
80
|
+
*/
|
|
81
|
+
async load(configPath?: string): Promise<Config> {
|
|
82
|
+
if (configPath !== undefined) {
|
|
83
|
+
this.configPath = configPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Start with defaults
|
|
87
|
+
let config = deepClone(DEFAULT_CONFIG);
|
|
88
|
+
|
|
89
|
+
// Load from file if specified
|
|
90
|
+
if (configPath) {
|
|
91
|
+
const fileConfig = await this.loadFromFile(configPath);
|
|
92
|
+
config = deepMergeConfig(config, fileConfig);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load from environment variables
|
|
96
|
+
const envConfig = this.loadFromEnv();
|
|
97
|
+
config = deepMergeConfig(config, envConfig);
|
|
98
|
+
|
|
99
|
+
// Validate the final configuration
|
|
100
|
+
this.validate(config);
|
|
101
|
+
|
|
102
|
+
this.config = config;
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Gets the current configuration
|
|
108
|
+
*/
|
|
109
|
+
get(): Config {
|
|
110
|
+
return deepClone(this.config);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Updates configuration values
|
|
115
|
+
*/
|
|
116
|
+
update(updates: Partial<Config>): Config {
|
|
117
|
+
this.config = deepMergeConfig(this.config, updates);
|
|
118
|
+
this.validate(this.config);
|
|
119
|
+
return this.get();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Loads default configuration
|
|
124
|
+
*/
|
|
125
|
+
loadDefault(): void {
|
|
126
|
+
this.config = deepClone(DEFAULT_CONFIG);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Saves configuration to file
|
|
131
|
+
*/
|
|
132
|
+
async save(path?: string): Promise<void> {
|
|
133
|
+
const savePath = path || this.configPath;
|
|
134
|
+
if (!savePath) {
|
|
135
|
+
throw new ConfigError('No configuration file path specified');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const content = JSON.stringify(this.config, null, 2);
|
|
139
|
+
await Deno.writeTextFile(savePath, content);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gets user configuration directory
|
|
144
|
+
*/
|
|
145
|
+
private getUserConfigDir(): string {
|
|
146
|
+
const home = Deno.env.get('HOME') || Deno.env.get('USERPROFILE') || '/tmp';
|
|
147
|
+
return `${home}/.claude-flow`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Creates user config directory if it doesn't exist
|
|
152
|
+
*/
|
|
153
|
+
private async ensureUserConfigDir(): Promise<void> {
|
|
154
|
+
try {
|
|
155
|
+
await Deno.mkdir(this.userConfigDir, { recursive: true });
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (!(error instanceof Deno.errors.AlreadyExists)) {
|
|
158
|
+
throw new ConfigError(`Failed to create config directory: ${(error as Error).message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Loads all profiles from the profiles directory
|
|
165
|
+
*/
|
|
166
|
+
async loadProfiles(): Promise<void> {
|
|
167
|
+
const profilesDir = `${this.userConfigDir}/profiles`;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
for await (const entry of Deno.readDir(profilesDir)) {
|
|
171
|
+
if (entry.isFile && entry.name.endsWith('.json')) {
|
|
172
|
+
const profileName = entry.name.replace('.json', '');
|
|
173
|
+
const profilePath = `${profilesDir}/${entry.name}`;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const content = await Deno.readTextFile(profilePath);
|
|
177
|
+
const profileConfig = safeParseJSON<Partial<Config>>(content);
|
|
178
|
+
|
|
179
|
+
if (profileConfig) {
|
|
180
|
+
this.profiles.set(profileName, profileConfig);
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.warn(`Failed to load profile ${profileName}: ${(error as Error).message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// Profiles directory doesn't exist - this is okay
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Applies a named profile
|
|
194
|
+
*/
|
|
195
|
+
async applyProfile(profileName: string): Promise<void> {
|
|
196
|
+
await this.loadProfiles();
|
|
197
|
+
|
|
198
|
+
const profile = this.profiles.get(profileName);
|
|
199
|
+
if (!profile) {
|
|
200
|
+
throw new ConfigError(`Profile '${profileName}' not found`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.config = deepMergeConfig(this.config, profile);
|
|
204
|
+
this.currentProfile = profileName;
|
|
205
|
+
this.validate(this.config);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Saves current configuration as a profile
|
|
210
|
+
*/
|
|
211
|
+
async saveProfile(profileName: string, config?: Partial<Config>): Promise<void> {
|
|
212
|
+
await this.ensureUserConfigDir();
|
|
213
|
+
|
|
214
|
+
const profilesDir = `${this.userConfigDir}/profiles`;
|
|
215
|
+
await Deno.mkdir(profilesDir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
const profileConfig = config || this.config;
|
|
218
|
+
const profilePath = `${profilesDir}/${profileName}.json`;
|
|
219
|
+
|
|
220
|
+
const content = JSON.stringify(profileConfig, null, 2);
|
|
221
|
+
await Deno.writeTextFile(profilePath, content);
|
|
222
|
+
|
|
223
|
+
this.profiles.set(profileName, profileConfig);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Deletes a profile
|
|
228
|
+
*/
|
|
229
|
+
async deleteProfile(profileName: string): Promise<void> {
|
|
230
|
+
const profilePath = `${this.userConfigDir}/profiles/${profileName}.json`;
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await Deno.remove(profilePath);
|
|
234
|
+
this.profiles.delete(profileName);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error instanceof Deno.errors.NotFound) {
|
|
237
|
+
throw new ConfigError(`Profile '${profileName}' not found`);
|
|
238
|
+
}
|
|
239
|
+
throw new ConfigError(`Failed to delete profile: ${(error as Error).message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Lists all available profiles
|
|
245
|
+
*/
|
|
246
|
+
async listProfiles(): Promise<string[]> {
|
|
247
|
+
await this.loadProfiles();
|
|
248
|
+
return Array.from(this.profiles.keys());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Gets a specific profile configuration
|
|
253
|
+
*/
|
|
254
|
+
async getProfile(profileName: string): Promise<Partial<Config> | undefined> {
|
|
255
|
+
await this.loadProfiles();
|
|
256
|
+
return this.profiles.get(profileName);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Gets the current active profile name
|
|
261
|
+
*/
|
|
262
|
+
getCurrentProfile(): string | undefined {
|
|
263
|
+
return this.currentProfile;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Sets a configuration value by path
|
|
268
|
+
*/
|
|
269
|
+
set(path: string, value: any): void {
|
|
270
|
+
const keys = path.split('.');
|
|
271
|
+
let current: any = this.config;
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
274
|
+
const key = keys[i];
|
|
275
|
+
if (!(key in current)) {
|
|
276
|
+
current[key] = {};
|
|
277
|
+
}
|
|
278
|
+
current = current[key];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
current[keys[keys.length - 1]] = value;
|
|
282
|
+
this.validate(this.config);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Gets a configuration value by path
|
|
287
|
+
*/
|
|
288
|
+
getValue(path: string): any {
|
|
289
|
+
const keys = path.split('.');
|
|
290
|
+
let current: any = this.config;
|
|
291
|
+
|
|
292
|
+
for (const key of keys) {
|
|
293
|
+
if (current && typeof current === 'object' && key in current) {
|
|
294
|
+
current = current[key];
|
|
295
|
+
} else {
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return current;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Resets configuration to defaults
|
|
305
|
+
*/
|
|
306
|
+
reset(): void {
|
|
307
|
+
this.config = deepClone(DEFAULT_CONFIG);
|
|
308
|
+
delete this.currentProfile;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Gets configuration schema for validation
|
|
313
|
+
*/
|
|
314
|
+
getSchema(): any {
|
|
315
|
+
return {
|
|
316
|
+
orchestrator: {
|
|
317
|
+
maxConcurrentAgents: { type: 'number', min: 1, max: 100 },
|
|
318
|
+
taskQueueSize: { type: 'number', min: 1, max: 10000 },
|
|
319
|
+
healthCheckInterval: { type: 'number', min: 1000, max: 300000 },
|
|
320
|
+
shutdownTimeout: { type: 'number', min: 1000, max: 300000 },
|
|
321
|
+
},
|
|
322
|
+
terminal: {
|
|
323
|
+
type: { type: 'string', values: ['auto', 'vscode', 'native'] },
|
|
324
|
+
poolSize: { type: 'number', min: 1, max: 50 },
|
|
325
|
+
recycleAfter: { type: 'number', min: 1, max: 1000 },
|
|
326
|
+
healthCheckInterval: { type: 'number', min: 1000, max: 3600000 },
|
|
327
|
+
commandTimeout: { type: 'number', min: 1000, max: 3600000 },
|
|
328
|
+
},
|
|
329
|
+
memory: {
|
|
330
|
+
backend: { type: 'string', values: ['sqlite', 'markdown', 'hybrid'] },
|
|
331
|
+
cacheSizeMB: { type: 'number', min: 1, max: 10000 },
|
|
332
|
+
syncInterval: { type: 'number', min: 1000, max: 300000 },
|
|
333
|
+
conflictResolution: { type: 'string', values: ['crdt', 'timestamp', 'manual'] },
|
|
334
|
+
retentionDays: { type: 'number', min: 1, max: 3650 },
|
|
335
|
+
},
|
|
336
|
+
coordination: {
|
|
337
|
+
maxRetries: { type: 'number', min: 0, max: 100 },
|
|
338
|
+
retryDelay: { type: 'number', min: 100, max: 60000 },
|
|
339
|
+
deadlockDetection: { type: 'boolean' },
|
|
340
|
+
resourceTimeout: { type: 'number', min: 1000, max: 3600000 },
|
|
341
|
+
messageTimeout: { type: 'number', min: 1000, max: 300000 },
|
|
342
|
+
},
|
|
343
|
+
mcp: {
|
|
344
|
+
transport: { type: 'string', values: ['stdio', 'http', 'websocket'] },
|
|
345
|
+
port: { type: 'number', min: 1, max: 65535 },
|
|
346
|
+
tlsEnabled: { type: 'boolean' },
|
|
347
|
+
},
|
|
348
|
+
logging: {
|
|
349
|
+
level: { type: 'string', values: ['debug', 'info', 'warn', 'error'] },
|
|
350
|
+
format: { type: 'string', values: ['json', 'text'] },
|
|
351
|
+
destination: { type: 'string', values: ['console', 'file'] },
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Validates a value against schema
|
|
358
|
+
*/
|
|
359
|
+
private validateValue(value: any, schema: any, path: string): void {
|
|
360
|
+
if (schema.type === 'number') {
|
|
361
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
362
|
+
throw new ValidationError(`${path}: must be a number`);
|
|
363
|
+
}
|
|
364
|
+
if (schema.min !== undefined && value < schema.min) {
|
|
365
|
+
throw new ValidationError(`${path}: must be at least ${schema.min}`);
|
|
366
|
+
}
|
|
367
|
+
if (schema.max !== undefined && value > schema.max) {
|
|
368
|
+
throw new ValidationError(`${path}: must be at most ${schema.max}`);
|
|
369
|
+
}
|
|
370
|
+
} else if (schema.type === 'string') {
|
|
371
|
+
if (typeof value !== 'string') {
|
|
372
|
+
throw new ValidationError(`${path}: must be a string`);
|
|
373
|
+
}
|
|
374
|
+
if (schema.values && !schema.values.includes(value)) {
|
|
375
|
+
throw new ValidationError(`${path}: must be one of [${schema.values.join(', ')}]`);
|
|
376
|
+
}
|
|
377
|
+
} else if (schema.type === 'boolean') {
|
|
378
|
+
if (typeof value !== 'boolean') {
|
|
379
|
+
throw new ValidationError(`${path}: must be a boolean`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Gets configuration diff between current and default
|
|
386
|
+
*/
|
|
387
|
+
getDiff(): any {
|
|
388
|
+
const defaultConfig = DEFAULT_CONFIG;
|
|
389
|
+
const diff: any = {};
|
|
390
|
+
|
|
391
|
+
const findDifferences = (current: any, defaults: any, path: string = '') => {
|
|
392
|
+
for (const key in current) {
|
|
393
|
+
const currentValue = current[key];
|
|
394
|
+
const defaultValue = defaults[key];
|
|
395
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
396
|
+
|
|
397
|
+
if (typeof currentValue === 'object' && currentValue !== null && !Array.isArray(currentValue)) {
|
|
398
|
+
if (typeof defaultValue === 'object' && defaultValue !== null) {
|
|
399
|
+
const nestedDiff = {};
|
|
400
|
+
findDifferences(currentValue, defaultValue, fullPath);
|
|
401
|
+
if (Object.keys(nestedDiff).length > 0) {
|
|
402
|
+
if (!path) {
|
|
403
|
+
diff[key] = nestedDiff;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
} else if (currentValue !== defaultValue) {
|
|
408
|
+
const pathParts = fullPath.split('.');
|
|
409
|
+
let target = diff;
|
|
410
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
411
|
+
if (!target[pathParts[i]]) {
|
|
412
|
+
target[pathParts[i]] = {};
|
|
413
|
+
}
|
|
414
|
+
target = target[pathParts[i]];
|
|
415
|
+
}
|
|
416
|
+
target[pathParts[pathParts.length - 1]] = currentValue;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
findDifferences(this.config, defaultConfig);
|
|
422
|
+
return diff;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Exports configuration with metadata
|
|
427
|
+
*/
|
|
428
|
+
export(): any {
|
|
429
|
+
return {
|
|
430
|
+
version: '1.0.0',
|
|
431
|
+
exported: new Date().toISOString(),
|
|
432
|
+
profile: this.currentProfile,
|
|
433
|
+
config: this.config,
|
|
434
|
+
diff: this.getDiff(),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Imports configuration from export
|
|
440
|
+
*/
|
|
441
|
+
import(data: any): void {
|
|
442
|
+
if (!data.config) {
|
|
443
|
+
throw new ConfigError('Invalid configuration export format');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
this.validate(data.config);
|
|
447
|
+
this.config = data.config;
|
|
448
|
+
this.currentProfile = data.profile;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Loads configuration from file
|
|
453
|
+
*/
|
|
454
|
+
private async loadFromFile(path: string): Promise<Partial<Config>> {
|
|
455
|
+
try {
|
|
456
|
+
const content = await Deno.readTextFile(path);
|
|
457
|
+
const config = safeParseJSON<Partial<Config>>(content);
|
|
458
|
+
|
|
459
|
+
if (!config) {
|
|
460
|
+
throw new ConfigError(`Invalid JSON in configuration file: ${path}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return config;
|
|
464
|
+
} catch (error) {
|
|
465
|
+
if (error instanceof Deno.errors.NotFound) {
|
|
466
|
+
// File doesn't exist, use defaults
|
|
467
|
+
return {};
|
|
468
|
+
}
|
|
469
|
+
throw new ConfigError(`Failed to load configuration from ${path}: ${(error as Error).message}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Loads configuration from environment variables
|
|
475
|
+
*/
|
|
476
|
+
private loadFromEnv(): Partial<Config> {
|
|
477
|
+
const config: Partial<Config> = {};
|
|
478
|
+
|
|
479
|
+
// Orchestrator settings
|
|
480
|
+
const maxAgents = Deno.env.get('CLAUDE_FLOW_MAX_AGENTS');
|
|
481
|
+
if (maxAgents) {
|
|
482
|
+
if (!config.orchestrator) {
|
|
483
|
+
config.orchestrator = {} as any;
|
|
484
|
+
}
|
|
485
|
+
config.orchestrator = {
|
|
486
|
+
...DEFAULT_CONFIG.orchestrator,
|
|
487
|
+
...config.orchestrator,
|
|
488
|
+
maxConcurrentAgents: parseInt(maxAgents, 10),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Terminal settings
|
|
493
|
+
const terminalType = Deno.env.get('CLAUDE_FLOW_TERMINAL_TYPE');
|
|
494
|
+
if (terminalType === 'vscode' || terminalType === 'native' || terminalType === 'auto') {
|
|
495
|
+
config.terminal = {
|
|
496
|
+
...DEFAULT_CONFIG.terminal,
|
|
497
|
+
...config.terminal,
|
|
498
|
+
type: terminalType,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Memory settings
|
|
503
|
+
const memoryBackend = Deno.env.get('CLAUDE_FLOW_MEMORY_BACKEND');
|
|
504
|
+
if (memoryBackend === 'sqlite' || memoryBackend === 'markdown' || memoryBackend === 'hybrid') {
|
|
505
|
+
config.memory = {
|
|
506
|
+
...DEFAULT_CONFIG.memory,
|
|
507
|
+
...config.memory,
|
|
508
|
+
backend: memoryBackend,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// MCP settings
|
|
513
|
+
const mcpTransport = Deno.env.get('CLAUDE_FLOW_MCP_TRANSPORT');
|
|
514
|
+
if (mcpTransport === 'stdio' || mcpTransport === 'http' || mcpTransport === 'websocket') {
|
|
515
|
+
config.mcp = {
|
|
516
|
+
...DEFAULT_CONFIG.mcp,
|
|
517
|
+
...config.mcp,
|
|
518
|
+
transport: mcpTransport,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const mcpPort = Deno.env.get('CLAUDE_FLOW_MCP_PORT');
|
|
523
|
+
if (mcpPort) {
|
|
524
|
+
config.mcp = {
|
|
525
|
+
...DEFAULT_CONFIG.mcp,
|
|
526
|
+
...config.mcp,
|
|
527
|
+
port: parseInt(mcpPort, 10),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Logging settings
|
|
532
|
+
const logLevel = Deno.env.get('CLAUDE_FLOW_LOG_LEVEL');
|
|
533
|
+
if (logLevel === 'debug' || logLevel === 'info' || logLevel === 'warn' || logLevel === 'error') {
|
|
534
|
+
config.logging = {
|
|
535
|
+
...DEFAULT_CONFIG.logging,
|
|
536
|
+
...config.logging,
|
|
537
|
+
level: logLevel,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return config;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Validates configuration
|
|
546
|
+
*/
|
|
547
|
+
private validate(config: Config): void {
|
|
548
|
+
// Orchestrator validation
|
|
549
|
+
if (config.orchestrator.maxConcurrentAgents < 1) {
|
|
550
|
+
throw new ValidationError('maxConcurrentAgents must be at least 1');
|
|
551
|
+
}
|
|
552
|
+
if (config.orchestrator.taskQueueSize < 1) {
|
|
553
|
+
throw new ValidationError('taskQueueSize must be at least 1');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Terminal validation
|
|
557
|
+
if (config.terminal.poolSize < 1) {
|
|
558
|
+
throw new ValidationError('terminal poolSize must be at least 1');
|
|
559
|
+
}
|
|
560
|
+
if (config.terminal.recycleAfter < 1) {
|
|
561
|
+
throw new ValidationError('terminal recycleAfter must be at least 1');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Memory validation
|
|
565
|
+
if (config.memory.cacheSizeMB < 1) {
|
|
566
|
+
throw new ValidationError('memory cacheSizeMB must be at least 1');
|
|
567
|
+
}
|
|
568
|
+
if (config.memory.retentionDays < 1) {
|
|
569
|
+
throw new ValidationError('memory retentionDays must be at least 1');
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Coordination validation
|
|
573
|
+
if (config.coordination.maxRetries < 0) {
|
|
574
|
+
throw new ValidationError('coordination maxRetries cannot be negative');
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// MCP validation
|
|
578
|
+
if (config.mcp.transport === 'http' || config.mcp.transport === 'websocket') {
|
|
579
|
+
if (!config.mcp.port || config.mcp.port < 1 || config.mcp.port > 65535) {
|
|
580
|
+
throw new ValidationError('Invalid MCP port number');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Export singleton instance
|
|
587
|
+
export const configManager = ConfigManager.getInstance();
|
|
588
|
+
|
|
589
|
+
// Helper function to load configuration
|
|
590
|
+
export async function loadConfig(path?: string): Promise<Config> {
|
|
591
|
+
return await configManager.load(path);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function deepClone<T>(obj: T): T {
|
|
595
|
+
return JSON.parse(JSON.stringify(obj));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Custom deepMerge for Config type
|
|
599
|
+
function deepMergeConfig(target: Config, ...sources: Partial<Config>[]): Config {
|
|
600
|
+
const result = deepClone(target);
|
|
601
|
+
|
|
602
|
+
for (const source of sources) {
|
|
603
|
+
if (!source) continue;
|
|
604
|
+
|
|
605
|
+
// Merge each section
|
|
606
|
+
if (source.orchestrator) {
|
|
607
|
+
result.orchestrator = { ...result.orchestrator, ...source.orchestrator };
|
|
608
|
+
}
|
|
609
|
+
if (source.terminal) {
|
|
610
|
+
result.terminal = { ...result.terminal, ...source.terminal };
|
|
611
|
+
}
|
|
612
|
+
if (source.memory) {
|
|
613
|
+
result.memory = { ...result.memory, ...source.memory };
|
|
614
|
+
}
|
|
615
|
+
if (source.coordination) {
|
|
616
|
+
result.coordination = { ...result.coordination, ...source.coordination };
|
|
617
|
+
}
|
|
618
|
+
if (source.mcp) {
|
|
619
|
+
result.mcp = { ...result.mcp, ...source.mcp };
|
|
620
|
+
}
|
|
621
|
+
if (source.logging) {
|
|
622
|
+
result.logging = { ...result.logging, ...source.logging };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return result;
|
|
627
|
+
}
|