@sylphx/flow 3.18.0 → 3.19.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/CHANGELOG.md +66 -0
- package/package.json +1 -3
- package/src/commands/flow/execute-v2.ts +126 -128
- package/src/commands/flow-command.ts +52 -42
- package/src/config/index.ts +0 -20
- package/src/config/targets.ts +1 -1
- package/src/core/__tests__/backup-restore.test.ts +1 -1
- package/src/core/__tests__/cleanup-handler.test.ts +292 -0
- package/src/core/__tests__/git-stash-manager.test.ts +246 -0
- package/src/core/__tests__/secrets-manager.test.ts +126 -0
- package/src/core/__tests__/session-cleanup.test.ts +147 -0
- package/src/core/agent-loader.ts +2 -2
- package/src/core/attach-manager.ts +12 -78
- package/src/core/backup-manager.ts +8 -20
- package/src/core/cleanup-handler.ts +187 -11
- package/src/core/flow-executor.ts +139 -126
- package/src/core/functional/index.ts +0 -11
- package/src/core/git-stash-manager.ts +50 -68
- package/src/core/index.ts +1 -1
- package/src/core/project-manager.ts +26 -43
- package/src/core/secrets-manager.ts +15 -18
- package/src/core/session-manager.ts +32 -41
- package/src/core/state-detector.ts +4 -15
- package/src/core/target-manager.ts +6 -3
- package/src/core/target-resolver.ts +14 -9
- package/src/core/template-loader.ts +7 -33
- package/src/core/upgrade-manager.ts +5 -16
- package/src/index.ts +7 -36
- package/src/services/auto-upgrade.ts +6 -14
- package/src/services/config-service.ts +7 -23
- package/src/services/index.ts +1 -1
- package/src/targets/claude-code.ts +24 -109
- package/src/targets/functional/claude-code-logic.ts +47 -103
- package/src/targets/opencode.ts +63 -197
- package/src/targets/shared/mcp-transforms.ts +20 -43
- package/src/targets/shared/target-operations.ts +1 -54
- package/src/types/agent.types.ts +5 -3
- package/src/types/mcp.types.ts +38 -1
- package/src/types/target.types.ts +4 -24
- package/src/types.ts +4 -0
- package/src/utils/agent-enhancer.ts +1 -1
- package/src/utils/config/target-config.ts +8 -14
- package/src/utils/config/target-utils.ts +1 -50
- package/src/utils/errors.ts +13 -0
- package/src/utils/files/file-operations.ts +16 -0
- package/src/utils/files/sync-utils.ts +5 -5
- package/src/utils/index.ts +1 -1
- package/src/utils/object-utils.ts +10 -2
- package/src/utils/security/secret-utils.ts +2 -2
- package/src/core/error-handling.ts +0 -512
- package/src/core/functional/async.ts +0 -101
- package/src/core/functional/either.ts +0 -109
- package/src/core/functional/error-handler.ts +0 -135
- package/src/core/functional/pipe.ts +0 -189
- package/src/core/functional/validation.ts +0 -138
- package/src/types/mcp-config.types.ts +0 -448
- package/src/utils/error-handler.ts +0 -53
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getProjectSettingsFile,
|
|
12
12
|
USER_SETTINGS_FILE,
|
|
13
13
|
} from '../config/constants.js';
|
|
14
|
+
import { readJsonFileSafe } from '../utils/files/file-operations.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* User configuration (sensitive data, saved to home directory)
|
|
@@ -102,13 +103,8 @@ export class ConfigService {
|
|
|
102
103
|
/**
|
|
103
104
|
* Load user global settings (mainly for API keys)
|
|
104
105
|
*/
|
|
105
|
-
static
|
|
106
|
-
|
|
107
|
-
const content = await fs.readFile(USER_SETTINGS_FILE, 'utf-8');
|
|
108
|
-
return JSON.parse(content);
|
|
109
|
-
} catch {
|
|
110
|
-
return {};
|
|
111
|
-
}
|
|
106
|
+
static loadHomeSettings(): Promise<UserSettings> {
|
|
107
|
+
return readJsonFileSafe<UserSettings>(USER_SETTINGS_FILE, {});
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
/**
|
|
@@ -154,14 +150,8 @@ export class ConfigService {
|
|
|
154
150
|
/**
|
|
155
151
|
* Load project-level settings
|
|
156
152
|
*/
|
|
157
|
-
static
|
|
158
|
-
|
|
159
|
-
const configPath = getProjectSettingsFile(cwd);
|
|
160
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
161
|
-
return JSON.parse(content);
|
|
162
|
-
} catch {
|
|
163
|
-
return {};
|
|
164
|
-
}
|
|
153
|
+
static loadProjectSettings(cwd: string = process.cwd()): Promise<ProjectSettings> {
|
|
154
|
+
return readJsonFileSafe<ProjectSettings>(getProjectSettingsFile(cwd), {});
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
/**
|
|
@@ -186,14 +176,8 @@ export class ConfigService {
|
|
|
186
176
|
/**
|
|
187
177
|
* Load project-local settings (overrides everything)
|
|
188
178
|
*/
|
|
189
|
-
static
|
|
190
|
-
|
|
191
|
-
const configPath = getProjectLocalSettingsFile(cwd);
|
|
192
|
-
const content = await fs.readFile(configPath, 'utf-8');
|
|
193
|
-
return JSON.parse(content);
|
|
194
|
-
} catch {
|
|
195
|
-
return {};
|
|
196
|
-
}
|
|
179
|
+
static loadLocalSettings(cwd: string = process.cwd()): Promise<RuntimeChoices> {
|
|
180
|
+
return readJsonFileSafe<RuntimeChoices>(getProjectLocalSettingsFile(cwd), {});
|
|
197
181
|
}
|
|
198
182
|
|
|
199
183
|
/**
|
package/src/services/index.ts
CHANGED
|
@@ -2,11 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import fsPromises from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { installToDirectory } from '../core/installers/file-installer.js';
|
|
6
|
-
import { createMCPInstaller } from '../core/installers/mcp-installer.js';
|
|
7
5
|
import type { AgentMetadata, FrontMatterMetadata } from '../types/target-config.types.js';
|
|
8
6
|
import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
|
|
9
|
-
import { getAgentsDir } from '../utils/config/paths.js';
|
|
10
7
|
import {
|
|
11
8
|
type ConfigData,
|
|
12
9
|
fileUtils,
|
|
@@ -14,11 +11,11 @@ import {
|
|
|
14
11
|
pathUtils,
|
|
15
12
|
yamlUtils,
|
|
16
13
|
} from '../utils/config/target-utils.js';
|
|
17
|
-
import { CLIError } from '../utils/
|
|
14
|
+
import { CLIError } from '../utils/errors.js';
|
|
18
15
|
import { sanitize } from '../utils/security/security.js';
|
|
16
|
+
import { DEFAULT_CLAUDE_CODE_ENV } from './functional/claude-code-logic.js';
|
|
19
17
|
import {
|
|
20
18
|
detectTargetConfig,
|
|
21
|
-
setupSlashCommandsTo,
|
|
22
19
|
stripFrontMatter,
|
|
23
20
|
transformMCPConfig as transformMCP,
|
|
24
21
|
} from './shared/index.js';
|
|
@@ -40,6 +37,11 @@ interface ProcessExitError extends Error {
|
|
|
40
37
|
code: number | null;
|
|
41
38
|
}
|
|
42
39
|
|
|
40
|
+
/** Type guard for Node.js errors with errno/code properties */
|
|
41
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
42
|
+
return error instanceof Error && 'code' in error;
|
|
43
|
+
}
|
|
44
|
+
|
|
43
45
|
/**
|
|
44
46
|
* Claude Code target - composition approach with all original functionality
|
|
45
47
|
*/
|
|
@@ -70,6 +72,7 @@ export const claudeCodeTarget: Target = {
|
|
|
70
72
|
createConfigFile: true,
|
|
71
73
|
useSecretFiles: false,
|
|
72
74
|
},
|
|
75
|
+
supportsMCP: true,
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
/**
|
|
@@ -271,7 +274,7 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
271
274
|
const child = spawn('claude', args, {
|
|
272
275
|
stdio: 'inherit',
|
|
273
276
|
shell: false,
|
|
274
|
-
env: process.env,
|
|
277
|
+
env: { ...process.env, ...DEFAULT_CLAUDE_CODE_ENV },
|
|
275
278
|
});
|
|
276
279
|
|
|
277
280
|
child.on('spawn', () => {
|
|
@@ -298,17 +301,19 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
298
301
|
});
|
|
299
302
|
});
|
|
300
303
|
} catch (error: unknown) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
if (isNodeError(error)) {
|
|
305
|
+
if (error.code === 'ENOENT') {
|
|
306
|
+
throw new CLIError('Claude Code not found. Please install it first.', 'CLAUDE_NOT_FOUND');
|
|
307
|
+
}
|
|
308
|
+
if (error.code !== undefined) {
|
|
309
|
+
throw new CLIError(`Claude Code exited with code ${error.code}`, 'CLAUDE_ERROR');
|
|
310
|
+
}
|
|
311
|
+
throw new CLIError(`Failed to execute Claude Code: ${error.message}`, 'CLAUDE_ERROR');
|
|
304
312
|
}
|
|
305
|
-
if (
|
|
306
|
-
throw new CLIError(`
|
|
313
|
+
if (error instanceof Error) {
|
|
314
|
+
throw new CLIError(`Failed to execute Claude Code: ${error.message}`, 'CLAUDE_ERROR');
|
|
307
315
|
}
|
|
308
|
-
throw new CLIError(
|
|
309
|
-
`Failed to execute Claude Code: ${(error as Error).message}`,
|
|
310
|
-
'CLAUDE_ERROR'
|
|
311
|
-
);
|
|
316
|
+
throw new CLIError(`Failed to execute Claude Code: ${String(error)}`, 'CLAUDE_ERROR');
|
|
312
317
|
}
|
|
313
318
|
},
|
|
314
319
|
|
|
@@ -333,8 +338,7 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
333
338
|
const content = await fsPromises.readFile(settingsPath, 'utf8');
|
|
334
339
|
settings = JSON.parse(content);
|
|
335
340
|
} catch (error: unknown) {
|
|
336
|
-
|
|
337
|
-
if (err.code !== 'ENOENT') {
|
|
341
|
+
if (!isNodeError(error) || error.code !== 'ENOENT') {
|
|
338
342
|
throw error;
|
|
339
343
|
}
|
|
340
344
|
// File doesn't exist, will create new
|
|
@@ -370,10 +374,10 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
370
374
|
transformRulesContent: stripFrontMatter,
|
|
371
375
|
|
|
372
376
|
/**
|
|
373
|
-
*
|
|
374
|
-
*
|
|
377
|
+
* Apply Claude Code settings (attribution, hooks, env, thinking mode)
|
|
378
|
+
* Merges Flow defaults into .claude/settings.json, preserving user settings
|
|
375
379
|
*/
|
|
376
|
-
async
|
|
380
|
+
async applySettings(cwd: string, _options: CommonOptions): Promise<SetupResult> {
|
|
377
381
|
const { processSettings, generateHookCommands } = await import(
|
|
378
382
|
'./functional/claude-code-logic.js'
|
|
379
383
|
);
|
|
@@ -425,95 +429,6 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
425
429
|
message: 'Configured notification hook',
|
|
426
430
|
};
|
|
427
431
|
},
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Setup agents for Claude Code
|
|
431
|
-
* Install agents to .claude/agents/ directory with rules appended
|
|
432
|
-
* Output styles are applied dynamically at runtime based on user settings
|
|
433
|
-
*/
|
|
434
|
-
async setupAgents(cwd: string, options: CommonOptions): Promise<SetupResult> {
|
|
435
|
-
const { enhanceAgentContent } = await import('../utils/agent-enhancer.js');
|
|
436
|
-
const agentsDir = path.join(cwd, this.config.agentDir);
|
|
437
|
-
|
|
438
|
-
const results = await installToDirectory(
|
|
439
|
-
getAgentsDir(),
|
|
440
|
-
agentsDir,
|
|
441
|
-
async (content, sourcePath) => {
|
|
442
|
-
// Extract rules from ORIGINAL content before transformation
|
|
443
|
-
const { metadata } = await yamlUtils.extractFrontMatter(content);
|
|
444
|
-
const rules = metadata.rules as string[] | undefined;
|
|
445
|
-
|
|
446
|
-
// Transform agent content (converts to Claude Code format, strips unsupported fields)
|
|
447
|
-
const transformed = await this.transformAgentContent(content, undefined, sourcePath);
|
|
448
|
-
|
|
449
|
-
// Enhance with rules only (output styles are applied dynamically at runtime)
|
|
450
|
-
const enhanced = await enhanceAgentContent(transformed, rules, []);
|
|
451
|
-
|
|
452
|
-
return enhanced;
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
...options,
|
|
456
|
-
showProgress: false, // UI handled by init-command
|
|
457
|
-
}
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
return { count: results.length };
|
|
461
|
-
},
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Setup output styles for Claude Code
|
|
465
|
-
* Output styles are appended to each agent file
|
|
466
|
-
*/
|
|
467
|
-
async setupOutputStyles(_cwd: string, _options: CommonOptions): Promise<SetupResult> {
|
|
468
|
-
// Output styles are appended to each agent file during setupAgents
|
|
469
|
-
// No separate installation needed
|
|
470
|
-
return {
|
|
471
|
-
count: 0,
|
|
472
|
-
message: 'Output styles included in agent files',
|
|
473
|
-
};
|
|
474
|
-
},
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Setup rules for Claude Code
|
|
478
|
-
* Rules are appended to each agent file
|
|
479
|
-
*/
|
|
480
|
-
async setupRules(_cwd: string, _options: CommonOptions): Promise<SetupResult> {
|
|
481
|
-
// Rules are appended to each agent file during setupAgents
|
|
482
|
-
// No separate CLAUDE.md file needed
|
|
483
|
-
return {
|
|
484
|
-
count: 0,
|
|
485
|
-
message: 'Rules included in agent files',
|
|
486
|
-
};
|
|
487
|
-
},
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Setup MCP servers for Claude Code
|
|
491
|
-
* Select, configure, install, and approve MCP servers
|
|
492
|
-
*/
|
|
493
|
-
async setupMCP(cwd: string, options: CommonOptions): Promise<SetupResult> {
|
|
494
|
-
const installer = createMCPInstaller(this);
|
|
495
|
-
const result = await installer.setupMCP({ ...options, quiet: true });
|
|
496
|
-
|
|
497
|
-
// Approve servers in Claude Code settings
|
|
498
|
-
if (result.selectedServers.length > 0 && !options.dryRun) {
|
|
499
|
-
if (this.approveMCPServers) {
|
|
500
|
-
await this.approveMCPServers(cwd, result.selectedServers);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return { count: result.selectedServers.length };
|
|
505
|
-
},
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Setup slash commands for Claude Code
|
|
509
|
-
* Install slash command templates to .claude/commands/ directory
|
|
510
|
-
*/
|
|
511
|
-
async setupSlashCommands(cwd: string, options: CommonOptions): Promise<SetupResult> {
|
|
512
|
-
if (!this.config.slashCommandsDir) {
|
|
513
|
-
return { count: 0 };
|
|
514
|
-
}
|
|
515
|
-
return setupSlashCommandsTo(path.join(cwd, this.config.slashCommandsDir), undefined, options);
|
|
516
|
-
},
|
|
517
432
|
};
|
|
518
433
|
|
|
519
434
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { ConfigError } from '../../core/functional/error-types.js';
|
|
13
13
|
import { configError } from '../../core/functional/error-types.js';
|
|
14
14
|
import type { Result } from '../../core/functional/result.js';
|
|
15
|
-
import {
|
|
15
|
+
import { success, tryCatch } from '../../core/functional/result.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Claude Code settings structure
|
|
@@ -44,14 +44,19 @@ export interface ClaudeCodeSettings {
|
|
|
44
44
|
[key: string]: unknown;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Default environment variables injected into the Claude Code process
|
|
49
|
+
*/
|
|
50
|
+
export const DEFAULT_CLAUDE_CODE_ENV: Record<string, string> = {
|
|
51
|
+
CLAUDE_CODE_MAX_OUTPUT_TOKENS: '128000',
|
|
52
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
53
|
+
};
|
|
54
|
+
|
|
47
55
|
/**
|
|
48
56
|
* Default Claude Code settings for optimal experience
|
|
49
57
|
*/
|
|
50
|
-
|
|
51
|
-
env:
|
|
52
|
-
CLAUDE_CODE_MAX_OUTPUT_TOKENS: '128000',
|
|
53
|
-
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
54
|
-
},
|
|
58
|
+
const DEFAULT_CLAUDE_CODE_SETTINGS: Partial<ClaudeCodeSettings> = {
|
|
59
|
+
env: DEFAULT_CLAUDE_CODE_ENV,
|
|
55
60
|
attribution: {
|
|
56
61
|
commit: '',
|
|
57
62
|
pr: '',
|
|
@@ -77,32 +82,20 @@ export const generateHookCommands = async (targetId: string): Promise<HookConfig
|
|
|
77
82
|
* Default hook commands (fallback)
|
|
78
83
|
* Simplified to only include notification hook
|
|
79
84
|
*/
|
|
80
|
-
|
|
85
|
+
const DEFAULT_HOOKS: HookConfig = {
|
|
81
86
|
notificationCommand: 'sylphx-flow hook --type notification --target claude-code',
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
/**
|
|
85
|
-
*
|
|
86
|
-
*/
|
|
87
|
-
export const parseSettings = (content: string): Result<ClaudeCodeSettings, ConfigError> => {
|
|
88
|
-
return tryCatch(
|
|
89
|
-
() => JSON.parse(content) as ClaudeCodeSettings,
|
|
90
|
-
(error) =>
|
|
91
|
-
configError('Failed to parse Claude Code settings', {
|
|
92
|
-
cause: error instanceof Error ? error : undefined,
|
|
93
|
-
})
|
|
94
|
-
);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Build hook configuration (pure)
|
|
90
|
+
* Process settings: parse existing or create new, merge hooks, serialize (pure)
|
|
99
91
|
*/
|
|
100
|
-
export const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
export const processSettings = (
|
|
93
|
+
existingContent: string | null,
|
|
94
|
+
hookConfig: HookConfig = DEFAULT_HOOKS
|
|
95
|
+
): Result<string, ConfigError> => {
|
|
96
|
+
const notificationCommand = hookConfig.notificationCommand || DEFAULT_HOOKS.notificationCommand!;
|
|
104
97
|
|
|
105
|
-
|
|
98
|
+
const hookConfiguration: ClaudeCodeSettings['hooks'] = {
|
|
106
99
|
Notification: [
|
|
107
100
|
{
|
|
108
101
|
matcher: '',
|
|
@@ -115,98 +108,49 @@ export const buildHookConfiguration = (
|
|
|
115
108
|
},
|
|
116
109
|
],
|
|
117
110
|
};
|
|
118
|
-
};
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
export const mergeSettings = (
|
|
125
|
-
existingSettings: ClaudeCodeSettings,
|
|
126
|
-
hookConfig: HookConfig = DEFAULT_HOOKS
|
|
127
|
-
): ClaudeCodeSettings => {
|
|
128
|
-
const newHooks = buildHookConfiguration(hookConfig);
|
|
112
|
+
const createNewSettings = (): ClaudeCodeSettings => ({
|
|
113
|
+
...DEFAULT_CLAUDE_CODE_SETTINGS,
|
|
114
|
+
hooks: hookConfiguration,
|
|
115
|
+
});
|
|
129
116
|
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
const serialize = (settings: ClaudeCodeSettings): string => JSON.stringify(settings, null, 2);
|
|
118
|
+
|
|
119
|
+
if (existingContent === null || existingContent.trim() === '') {
|
|
120
|
+
return success(serialize(createNewSettings()));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Parse existing settings
|
|
124
|
+
const parseResult = tryCatch(
|
|
125
|
+
() => JSON.parse(existingContent) as ClaudeCodeSettings,
|
|
126
|
+
(error) =>
|
|
127
|
+
configError('Failed to parse Claude Code settings', {
|
|
128
|
+
cause: error instanceof Error ? error : undefined,
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (parseResult._tag === 'Failure') {
|
|
133
|
+
return success(serialize(createNewSettings()));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Merge with existing
|
|
137
|
+
const existingSettings = parseResult.value;
|
|
138
|
+
const merged: ClaudeCodeSettings = {
|
|
132
139
|
...DEFAULT_CLAUDE_CODE_SETTINGS,
|
|
133
140
|
...existingSettings,
|
|
134
|
-
// Deep merge env variables
|
|
135
141
|
env: {
|
|
136
142
|
...DEFAULT_CLAUDE_CODE_SETTINGS.env,
|
|
137
143
|
...(existingSettings.env || {}),
|
|
138
144
|
},
|
|
139
|
-
// Deep merge attribution
|
|
140
145
|
attribution: {
|
|
141
146
|
...DEFAULT_CLAUDE_CODE_SETTINGS.attribution,
|
|
142
147
|
...(existingSettings.attribution || {}),
|
|
143
148
|
},
|
|
144
|
-
// Merge hooks
|
|
145
149
|
hooks: {
|
|
146
150
|
...(existingSettings.hooks || {}),
|
|
147
|
-
...
|
|
151
|
+
...hookConfiguration,
|
|
148
152
|
},
|
|
149
153
|
};
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Create settings with defaults and hooks (pure)
|
|
154
|
-
* Includes optimal Claude Code settings for extended output, thinking mode, and clean attribution
|
|
155
|
-
*/
|
|
156
|
-
export const createSettings = (hookConfig: HookConfig = DEFAULT_HOOKS): ClaudeCodeSettings => {
|
|
157
|
-
return {
|
|
158
|
-
...DEFAULT_CLAUDE_CODE_SETTINGS,
|
|
159
|
-
hooks: buildHookConfiguration(hookConfig),
|
|
160
|
-
};
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Serialize settings to JSON (pure)
|
|
165
|
-
*/
|
|
166
|
-
export const serializeSettings = (settings: ClaudeCodeSettings): string => {
|
|
167
|
-
return JSON.stringify(settings, null, 2);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get success message (pure)
|
|
172
|
-
*/
|
|
173
|
-
export const getSuccessMessage = (): string => {
|
|
174
|
-
return 'Claude Code hook configured: Notification';
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Process settings: parse existing or create new, merge hooks, serialize (pure)
|
|
179
|
-
*/
|
|
180
|
-
export const processSettings = (
|
|
181
|
-
existingContent: string | null,
|
|
182
|
-
hookConfig: HookConfig = DEFAULT_HOOKS
|
|
183
|
-
): Result<string, ConfigError> => {
|
|
184
|
-
if (existingContent === null || existingContent.trim() === '') {
|
|
185
|
-
// No existing settings, create new
|
|
186
|
-
const settings = createSettings(hookConfig);
|
|
187
|
-
return success(serializeSettings(settings));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Parse existing settings
|
|
191
|
-
const parseResult = parseSettings(existingContent);
|
|
192
|
-
if (parseResult._tag === 'Failure') {
|
|
193
|
-
// If parsing fails, create new settings
|
|
194
|
-
const settings = createSettings(hookConfig);
|
|
195
|
-
return success(serializeSettings(settings));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Merge with existing
|
|
199
|
-
const merged = mergeSettings(parseResult.value, hookConfig);
|
|
200
|
-
return success(serializeSettings(merged));
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Validate hook configuration (pure)
|
|
205
|
-
*/
|
|
206
|
-
export const validateHookConfig = (config: HookConfig): Result<HookConfig, ConfigError> => {
|
|
207
|
-
if (config.notificationCommand !== undefined && config.notificationCommand.trim() === '') {
|
|
208
|
-
return failure(configError('Notification command cannot be empty'));
|
|
209
|
-
}
|
|
210
154
|
|
|
211
|
-
return success(
|
|
155
|
+
return success(serialize(merged));
|
|
212
156
|
};
|