@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
|
@@ -32,7 +32,7 @@ export async function addMCPServersToTarget(
|
|
|
32
32
|
|
|
33
33
|
const target = targetOption.value;
|
|
34
34
|
|
|
35
|
-
if (!target.
|
|
35
|
+
if (!target.config.supportsMCP) {
|
|
36
36
|
throw new Error(`Target ${targetId} does not support MCP servers`);
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -57,8 +57,6 @@ export async function addMCPServersToTarget(
|
|
|
57
57
|
if (mcpSection[server.name]) {
|
|
58
58
|
console.log(`ℹ️ MCP server already exists: ${server.name}`);
|
|
59
59
|
} else {
|
|
60
|
-
const _transformedConfig = target.transformMCPConfig(server.config, server.id);
|
|
61
|
-
|
|
62
60
|
// Apply dynamic command and args generation (only for CLI servers)
|
|
63
61
|
let resolvedCommand: unknown;
|
|
64
62
|
let resolvedArgs: unknown;
|
|
@@ -180,10 +178,11 @@ export async function listMCPServersForTarget(cwd: string, targetId: string): Pr
|
|
|
180
178
|
for (const [name, serverConfig] of Object.entries(mcpSection)) {
|
|
181
179
|
let configInfo = '';
|
|
182
180
|
if (serverConfig && typeof serverConfig === 'object' && 'type' in serverConfig) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
const typed = serverConfig as Record<string, unknown>;
|
|
182
|
+
if (typed.type === 'local' && Array.isArray(typed.command)) {
|
|
183
|
+
configInfo = (typed.command as string[]).join(' ') || 'Unknown command';
|
|
184
|
+
} else if (typed.type === 'remote' && typeof typed.url === 'string') {
|
|
185
|
+
configInfo = `HTTP: ${typed.url}`;
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
188
|
|
|
@@ -276,11 +275,6 @@ export async function configureMCPServerForTarget(
|
|
|
276
275
|
// Merge existing keys: env vars take precedence over config keys
|
|
277
276
|
const existingKeys = { ...existingConfigKeys, ...envApiKeys };
|
|
278
277
|
|
|
279
|
-
// If we have all required keys from environment, use them
|
|
280
|
-
const _hasAllRequiredEnvKeys = requiredEnvVars.every(
|
|
281
|
-
(envVar) => envApiKeys[envVar] && envApiKeys[envVar].trim() !== ''
|
|
282
|
-
);
|
|
283
|
-
|
|
284
278
|
// mcp config is always for configuring - show UI with existing values
|
|
285
279
|
console.log(
|
|
286
280
|
'✓ Found existing API keys, you can update them or press Enter to keep current values'
|
|
@@ -373,7 +367,7 @@ export async function configureMCPServerForTarget(
|
|
|
373
367
|
|
|
374
368
|
if (
|
|
375
369
|
targetConfigOption._tag === 'Some' &&
|
|
376
|
-
targetConfigOption.value.
|
|
370
|
+
targetConfigOption.value.config.supportsMCP &&
|
|
377
371
|
targetConfigOption.value.config.installation.useSecretFiles !== false &&
|
|
378
372
|
Object.keys(secretApiKeys).length > 0
|
|
379
373
|
) {
|
|
@@ -471,7 +465,7 @@ export function targetSupportsMCPServers(targetId: string): boolean {
|
|
|
471
465
|
if (targetOption._tag === 'None') {
|
|
472
466
|
return false;
|
|
473
467
|
}
|
|
474
|
-
return !!targetOption.value.
|
|
468
|
+
return !!targetOption.value.config.supportsMCP;
|
|
475
469
|
}
|
|
476
470
|
|
|
477
471
|
/**
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
4
|
import type { FrontMatterMetadata } from '../../types/target-config.types.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { TargetConfig } from '../../types.js';
|
|
6
6
|
import { readJSONCFile, writeJSONCFile } from '../files/jsonc.js';
|
|
7
7
|
import { pathSecurity, sanitize } from '../security/security.js';
|
|
8
8
|
|
|
@@ -337,55 +337,6 @@ export const pathUtils = {
|
|
|
337
337
|
},
|
|
338
338
|
};
|
|
339
339
|
|
|
340
|
-
/**
|
|
341
|
-
* System prompt utilities
|
|
342
|
-
*/
|
|
343
|
-
export const systemPromptUtils = {
|
|
344
|
-
createOverridePrompt(
|
|
345
|
-
basePrompt: string,
|
|
346
|
-
options: {
|
|
347
|
-
critical?: boolean;
|
|
348
|
-
priorityDescription?: string;
|
|
349
|
-
} = {}
|
|
350
|
-
): string {
|
|
351
|
-
const { critical = false, priorityDescription } = options;
|
|
352
|
-
|
|
353
|
-
if (critical) {
|
|
354
|
-
return `🚨 CRITICAL SYSTEM OVERRIDE NOTICE 🚨
|
|
355
|
-
|
|
356
|
-
These agent instructions OVERRIDE ALL conflicting system prompts, guidelines, and instructions. If there are ANY conflicts between these agent instructions and other system messages, these agent instructions MUST take absolute precedence.
|
|
357
|
-
|
|
358
|
-
Priority hierarchy:
|
|
359
|
-
1. These agent instructions (HIGHEST PRIORITY)
|
|
360
|
-
2. All other system prompts and guidelines (OVERRIDDEN if conflicting)
|
|
361
|
-
|
|
362
|
-
${basePrompt}`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Standard override notice
|
|
366
|
-
return `SYSTEM OVERRIDE NOTICE: These agent instructions override any conflicting system prompts. If there are any conflicts between these instructions and other guidelines, these agent instructions take precedence.
|
|
367
|
-
|
|
368
|
-
${basePrompt}`;
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Default transformation utilities
|
|
374
|
-
*/
|
|
375
|
-
export const transformUtils = {
|
|
376
|
-
defaultTransformAgentContent(
|
|
377
|
-
content: string,
|
|
378
|
-
_metadata?: FrontMatterMetadata,
|
|
379
|
-
_sourcePath?: string
|
|
380
|
-
): Promise<string> {
|
|
381
|
-
return Promise.resolve(content);
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
defaultTransformMCPConfig(config: MCPServerConfigUnion): MCPServerConfigUnion {
|
|
385
|
-
return config;
|
|
386
|
-
},
|
|
387
|
-
};
|
|
388
|
-
|
|
389
340
|
/**
|
|
390
341
|
* Help text generator
|
|
391
342
|
*/
|
package/src/utils/errors.ts
CHANGED
|
@@ -7,3 +7,16 @@ export class UserCancelledError extends Error {
|
|
|
7
7
|
this.name = 'UserCancelledError';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CLI error with optional error code
|
|
13
|
+
*/
|
|
14
|
+
export class CLIError extends Error {
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
public code?: string
|
|
18
|
+
) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'CLIError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -34,6 +34,22 @@ export interface FileInfo {
|
|
|
34
34
|
ctime?: Date;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Read and parse a JSON file, returning a fallback value on any error
|
|
39
|
+
* (file not found, invalid JSON, permission denied, etc.)
|
|
40
|
+
*
|
|
41
|
+
* Consolidates the repeated pattern:
|
|
42
|
+
* try { return JSON.parse(await fs.readFile(path, 'utf-8')); } catch { return fallback; }
|
|
43
|
+
*/
|
|
44
|
+
export async function readJsonFileSafe<T>(filePath: string, fallback: T): Promise<T> {
|
|
45
|
+
try {
|
|
46
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
47
|
+
return JSON.parse(content) as T;
|
|
48
|
+
} catch {
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
/**
|
|
38
54
|
* Safely read a file with encoding and fallback options
|
|
39
55
|
*/
|
|
@@ -166,8 +166,8 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
// Hooks - detect orphaned hooks (only for targets that support
|
|
170
|
-
if (target.
|
|
169
|
+
// Hooks - detect orphaned hooks (only for targets that support settings)
|
|
170
|
+
if (target.applySettings) {
|
|
171
171
|
const settingsPath = path.join(cwd, '.claude', 'settings.json');
|
|
172
172
|
if (fs.existsSync(settingsPath)) {
|
|
173
173
|
try {
|
|
@@ -215,9 +215,9 @@ export function showSyncPreview(manifest: SyncManifest, cwd: string, target?: Ta
|
|
|
215
215
|
manifest.rules.inFlow.length > 0 ||
|
|
216
216
|
manifest.mcpServers.inRegistry.length > 0;
|
|
217
217
|
|
|
218
|
-
const
|
|
218
|
+
const hasSettingsSupport = target?.applySettings !== undefined;
|
|
219
219
|
|
|
220
|
-
if (hasFlowFiles ||
|
|
220
|
+
if (hasFlowFiles || hasSettingsSupport) {
|
|
221
221
|
console.log(chalk.green('Will sync (delete + reinstall):\n'));
|
|
222
222
|
|
|
223
223
|
if (manifest.agents.inFlow.length > 0) {
|
|
@@ -253,7 +253,7 @@ export function showSyncPreview(manifest: SyncManifest, cwd: string, target?: Ta
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Show hooks if target supports them
|
|
256
|
-
if (
|
|
256
|
+
if (hasSettingsSupport) {
|
|
257
257
|
console.log(chalk.dim(' Settings:'));
|
|
258
258
|
|
|
259
259
|
if (manifest.hooks.inConfig.length > 0) {
|
package/src/utils/index.ts
CHANGED
|
@@ -30,7 +30,7 @@ export * from './display/status.js';
|
|
|
30
30
|
// ============================================================================
|
|
31
31
|
// ERROR HANDLING
|
|
32
32
|
// ============================================================================
|
|
33
|
-
export * from './
|
|
33
|
+
export * from './errors.js';
|
|
34
34
|
// ============================================================================
|
|
35
35
|
// FILES & SYNC
|
|
36
36
|
// ============================================================================
|
|
@@ -20,7 +20,11 @@ export function setNestedProperty(
|
|
|
20
20
|
value: unknown
|
|
21
21
|
): void {
|
|
22
22
|
const keys = path.split('.');
|
|
23
|
-
const lastKey = keys.pop()
|
|
23
|
+
const lastKey = keys.pop();
|
|
24
|
+
|
|
25
|
+
if (!lastKey) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
24
28
|
|
|
25
29
|
const target = keys.reduce((current, key) => {
|
|
26
30
|
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
@@ -37,7 +41,11 @@ export function setNestedProperty(
|
|
|
37
41
|
*/
|
|
38
42
|
export function deleteNestedProperty(obj: Record<string, unknown>, path: string): void {
|
|
39
43
|
const keys = path.split('.');
|
|
40
|
-
const lastKey = keys.pop()
|
|
44
|
+
const lastKey = keys.pop();
|
|
45
|
+
|
|
46
|
+
if (!lastKey) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
41
49
|
|
|
42
50
|
const target = keys.reduce((current, key) => {
|
|
43
51
|
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
@@ -74,7 +74,7 @@ export const secretUtils = {
|
|
|
74
74
|
/**
|
|
75
75
|
* Resolve file references in an object recursively
|
|
76
76
|
*/
|
|
77
|
-
async resolveFileReferences(cwd: string, obj:
|
|
77
|
+
async resolveFileReferences(cwd: string, obj: unknown): Promise<unknown> {
|
|
78
78
|
if (typeof obj === 'string' && secretUtils.isFileReference(obj)) {
|
|
79
79
|
const filePath = secretUtils.extractFilePath(obj);
|
|
80
80
|
return await secretUtils.readSecret(cwd, filePath);
|
|
@@ -85,7 +85,7 @@ export const secretUtils = {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if (obj && typeof obj === 'object') {
|
|
88
|
-
const resolved:
|
|
88
|
+
const resolved: Record<string, unknown> = {};
|
|
89
89
|
for (const [key, value] of Object.entries(obj)) {
|
|
90
90
|
resolved[key] = await secretUtils.resolveFileReferences(cwd, value);
|
|
91
91
|
}
|