@sylphx/flow 2.1.2 → 2.1.4
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 +23 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +39 -30
- package/src/commands/flow/index.ts +2 -4
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -9
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings-command.ts +36 -33
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach-manager.ts +92 -84
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +2 -2
- package/src/services/target-installer.ts +9 -7
- package/src/targets/claude-code.ts +28 -15
- package/src/targets/opencode.ts +17 -6
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +111 -3
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +5 -2
- package/src/utils/version.ts +4 -2
- package/src/commands/flow/execute.ts +0 -453
- package/src/commands/flow/setup.ts +0 -312
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/commands/run-command.ts +0 -126
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
|
@@ -7,10 +7,10 @@ import { installToDirectory } from '../core/installers/file-installer.js';
|
|
|
7
7
|
import { createMCPInstaller } from '../core/installers/mcp-installer.js';
|
|
8
8
|
import type { AgentMetadata } from '../types/target-config.types.js';
|
|
9
9
|
import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
|
|
10
|
-
import { CLIError } from '../utils/error-handler.js';
|
|
11
10
|
import { getAgentsDir, getSlashCommandsDir } from '../utils/config/paths.js';
|
|
12
|
-
import { sanitize } from '../utils/security/security.js';
|
|
13
11
|
import { fileUtils, generateHelpText, pathUtils, yamlUtils } from '../utils/config/target-utils.js';
|
|
12
|
+
import { CLIError } from '../utils/error-handler.js';
|
|
13
|
+
import { sanitize } from '../utils/security/security.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Claude Code target - composition approach with all original functionality
|
|
@@ -24,6 +24,7 @@ export const claudeCodeTarget: Target = {
|
|
|
24
24
|
isDefault: false,
|
|
25
25
|
|
|
26
26
|
config: {
|
|
27
|
+
configDir: '.claude',
|
|
27
28
|
agentDir: '.claude/agents',
|
|
28
29
|
agentExtension: '.md',
|
|
29
30
|
agentFormat: 'yaml-frontmatter',
|
|
@@ -218,8 +219,12 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
218
219
|
if (options.dryRun) {
|
|
219
220
|
// Build the command for display
|
|
220
221
|
const dryRunArgs = ['claude', '--dangerously-skip-permissions'];
|
|
221
|
-
if (options.print)
|
|
222
|
-
|
|
222
|
+
if (options.print) {
|
|
223
|
+
dryRunArgs.push('-p');
|
|
224
|
+
}
|
|
225
|
+
if (options.continue) {
|
|
226
|
+
dryRunArgs.push('-c');
|
|
227
|
+
}
|
|
223
228
|
dryRunArgs.push('--system-prompt', '"<agent content>"');
|
|
224
229
|
if (sanitizedUserPrompt.trim() !== '') {
|
|
225
230
|
dryRunArgs.push(`"${sanitizedUserPrompt}"`);
|
|
@@ -297,14 +302,18 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
297
302
|
reject(error);
|
|
298
303
|
});
|
|
299
304
|
});
|
|
300
|
-
} catch (error:
|
|
301
|
-
|
|
305
|
+
} catch (error: unknown) {
|
|
306
|
+
const err = error as NodeJS.ErrnoException & { code?: string | number };
|
|
307
|
+
if (err.code === 'ENOENT') {
|
|
302
308
|
throw new CLIError('Claude Code not found. Please install it first.', 'CLAUDE_NOT_FOUND');
|
|
303
309
|
}
|
|
304
|
-
if (
|
|
305
|
-
throw new CLIError(`Claude Code exited with code ${
|
|
310
|
+
if (err.code) {
|
|
311
|
+
throw new CLIError(`Claude Code exited with code ${err.code}`, 'CLAUDE_ERROR');
|
|
306
312
|
}
|
|
307
|
-
throw new CLIError(
|
|
313
|
+
throw new CLIError(
|
|
314
|
+
`Failed to execute Claude Code: ${(error as Error).message}`,
|
|
315
|
+
'CLAUDE_ERROR'
|
|
316
|
+
);
|
|
308
317
|
}
|
|
309
318
|
},
|
|
310
319
|
|
|
@@ -333,8 +342,9 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
333
342
|
try {
|
|
334
343
|
const content = await fsPromises.readFile(settingsPath, 'utf8');
|
|
335
344
|
settings = JSON.parse(content);
|
|
336
|
-
} catch (error:
|
|
337
|
-
|
|
345
|
+
} catch (error: unknown) {
|
|
346
|
+
const err = error as NodeJS.ErrnoException;
|
|
347
|
+
if (err.code !== 'ENOENT') {
|
|
338
348
|
throw error;
|
|
339
349
|
}
|
|
340
350
|
// File doesn't exist, will create new
|
|
@@ -376,7 +386,9 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
376
386
|
* Configure session and prompt hooks for system information display
|
|
377
387
|
*/
|
|
378
388
|
async setupHooks(cwd: string, _options: CommonOptions): Promise<SetupResult> {
|
|
379
|
-
const { processSettings, generateHookCommands } = await import(
|
|
389
|
+
const { processSettings, generateHookCommands } = await import(
|
|
390
|
+
'./functional/claude-code-logic.js'
|
|
391
|
+
);
|
|
380
392
|
const { pathExists, createDirectory, readFile, writeFile } = await import(
|
|
381
393
|
'../composables/functional/useFileSystem.js'
|
|
382
394
|
);
|
|
@@ -428,7 +440,8 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
428
440
|
|
|
429
441
|
/**
|
|
430
442
|
* Setup agents for Claude Code
|
|
431
|
-
* Install agents to .claude/agents/ directory with rules
|
|
443
|
+
* Install agents to .claude/agents/ directory with rules appended
|
|
444
|
+
* Output styles are applied dynamically at runtime based on user settings
|
|
432
445
|
*/
|
|
433
446
|
async setupAgents(cwd: string, options: CommonOptions): Promise<SetupResult> {
|
|
434
447
|
const { enhanceAgentContent } = await import('../utils/agent-enhancer.js');
|
|
@@ -445,8 +458,8 @@ Please begin your response with a comprehensive summary of all the instructions
|
|
|
445
458
|
// Transform agent content (converts to Claude Code format, strips unsupported fields)
|
|
446
459
|
const transformed = await this.transformAgentContent(content, undefined, sourcePath);
|
|
447
460
|
|
|
448
|
-
// Enhance with rules
|
|
449
|
-
const enhanced = await enhanceAgentContent(transformed, rules);
|
|
461
|
+
// Enhance with rules only (output styles are applied dynamically at runtime)
|
|
462
|
+
const enhanced = await enhanceAgentContent(transformed, rules, []);
|
|
450
463
|
|
|
451
464
|
return enhanced;
|
|
452
465
|
},
|
package/src/targets/opencode.ts
CHANGED
|
@@ -3,14 +3,14 @@ import path from 'node:path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { getRulesPath, ruleFileExists } from '../config/rules.js';
|
|
5
5
|
import { MCP_SERVER_REGISTRY } from '../config/servers.js';
|
|
6
|
-
import {
|
|
6
|
+
import { installFile, installToDirectory } from '../core/installers/file-installer.js';
|
|
7
7
|
import { createMCPInstaller } from '../core/installers/mcp-installer.js';
|
|
8
8
|
import type { AgentMetadata } from '../types/target-config.types.js';
|
|
9
9
|
import type { CommonOptions, MCPServerConfigUnion, SetupResult, Target } from '../types.js';
|
|
10
10
|
import { getAgentsDir, getOutputStylesDir, getSlashCommandsDir } from '../utils/config/paths.js';
|
|
11
|
-
import { secretUtils } from '../utils/security/secret-utils.js';
|
|
12
11
|
import { fileUtils, generateHelpText, yamlUtils } from '../utils/config/target-utils.js';
|
|
13
12
|
import { CLIError } from '../utils/error-handler.js';
|
|
13
|
+
import { secretUtils } from '../utils/security/secret-utils.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* OpenCode target - composition approach with all original functionality
|
|
@@ -24,6 +24,7 @@ export const opencodeTarget: Target = {
|
|
|
24
24
|
isDefault: true,
|
|
25
25
|
|
|
26
26
|
config: {
|
|
27
|
+
configDir: '.opencode',
|
|
27
28
|
agentDir: '.opencode/agent',
|
|
28
29
|
agentExtension: '.md',
|
|
29
30
|
agentFormat: 'yaml-frontmatter',
|
|
@@ -63,7 +64,12 @@ export const opencodeTarget: Target = {
|
|
|
63
64
|
|
|
64
65
|
// If additional metadata is provided, merge it (but exclude unsupported fields)
|
|
65
66
|
if (metadata) {
|
|
66
|
-
const {
|
|
67
|
+
const {
|
|
68
|
+
name: additionalName,
|
|
69
|
+
mode: additionalMode,
|
|
70
|
+
rules: additionalRules,
|
|
71
|
+
...additionalCleanMetadata
|
|
72
|
+
} = metadata;
|
|
67
73
|
const mergedMetadata = { ...cleanMetadata, ...additionalCleanMetadata };
|
|
68
74
|
return yamlUtils.addFrontMatter(baseContent, mergedMetadata);
|
|
69
75
|
}
|
|
@@ -397,9 +403,15 @@ export const opencodeTarget: Target = {
|
|
|
397
403
|
* Execute OpenCode CLI
|
|
398
404
|
*/
|
|
399
405
|
async executeCommand(
|
|
400
|
-
|
|
406
|
+
_systemPrompt: string,
|
|
401
407
|
userPrompt: string,
|
|
402
|
-
options: {
|
|
408
|
+
options: {
|
|
409
|
+
verbose?: boolean;
|
|
410
|
+
dryRun?: boolean;
|
|
411
|
+
print?: boolean;
|
|
412
|
+
continue?: boolean;
|
|
413
|
+
agent?: string;
|
|
414
|
+
} = {}
|
|
403
415
|
): Promise<void> {
|
|
404
416
|
if (options.dryRun) {
|
|
405
417
|
// Build the command for display
|
|
@@ -492,7 +504,6 @@ export const opencodeTarget: Target = {
|
|
|
492
504
|
}
|
|
493
505
|
});
|
|
494
506
|
});
|
|
495
|
-
|
|
496
507
|
} catch (error) {
|
|
497
508
|
if (error instanceof Error) {
|
|
498
509
|
throw new CLIError(`Failed to execute OpenCode: ${error.message}`, 'OPENCODE_ERROR');
|
package/src/types/cli.types.ts
CHANGED
|
@@ -82,6 +82,6 @@ export interface RunCommandOptions {
|
|
|
82
82
|
agent?: string;
|
|
83
83
|
agentFile?: string;
|
|
84
84
|
prompt?: string;
|
|
85
|
-
print?: boolean;
|
|
86
|
-
continue?: boolean;
|
|
85
|
+
print?: boolean; // Headless print mode
|
|
86
|
+
continue?: boolean; // Continue previous conversation
|
|
87
87
|
}
|
|
@@ -10,13 +10,7 @@
|
|
|
10
10
|
* Provider IDs
|
|
11
11
|
* All supported AI providers
|
|
12
12
|
*/
|
|
13
|
-
export type ProviderId =
|
|
14
|
-
| 'anthropic'
|
|
15
|
-
| 'openai'
|
|
16
|
-
| 'google'
|
|
17
|
-
| 'openrouter'
|
|
18
|
-
| 'claude-code'
|
|
19
|
-
| 'zai';
|
|
13
|
+
export type ProviderId = 'anthropic' | 'openai' | 'google' | 'openrouter' | 'claude-code' | 'zai';
|
|
20
14
|
|
|
21
15
|
/**
|
|
22
16
|
* Provider configuration value
|
|
@@ -49,7 +49,7 @@ export type MessagePart =
|
|
|
49
49
|
| {
|
|
50
50
|
type: 'error';
|
|
51
51
|
error: string;
|
|
52
|
-
status: 'completed';
|
|
52
|
+
status: 'completed'; // Errors are immediately completed
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -94,8 +94,8 @@ export interface TokenUsage {
|
|
|
94
94
|
* - content: Shown in UI AND sent to LLM
|
|
95
95
|
*/
|
|
96
96
|
export interface MessageMetadata {
|
|
97
|
-
cpu?: string;
|
|
98
|
-
memory?: string;
|
|
97
|
+
cpu?: string; // CPU usage at creation time (e.g., "45.3% (8 cores)")
|
|
98
|
+
memory?: string; // Memory usage at creation time (e.g., "4.2GB/16.0GB")
|
|
99
99
|
// Future: add more fields as needed (sessionId, requestId, modelVersion, etc.)
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -136,14 +136,14 @@ export interface MessageMetadata {
|
|
|
136
136
|
*/
|
|
137
137
|
export interface SessionMessage {
|
|
138
138
|
role: 'user' | 'assistant';
|
|
139
|
-
content: MessagePart[];
|
|
139
|
+
content: MessagePart[]; // UI display (without system status)
|
|
140
140
|
timestamp: number;
|
|
141
|
-
status?: 'active' | 'completed' | 'error' | 'abort';
|
|
142
|
-
metadata?: MessageMetadata;
|
|
143
|
-
todoSnapshot?: Todo[];
|
|
141
|
+
status?: 'active' | 'completed' | 'error' | 'abort'; // Message lifecycle state (default: 'completed')
|
|
142
|
+
metadata?: MessageMetadata; // System info for LLM context (not shown in UI)
|
|
143
|
+
todoSnapshot?: Todo[]; // Full todo state at message creation time (for rewind + LLM context)
|
|
144
144
|
attachments?: FileAttachment[];
|
|
145
|
-
usage?: TokenUsage;
|
|
146
|
-
finishReason?: string;
|
|
145
|
+
usage?: TokenUsage; // For UI/monitoring, not sent to LLM
|
|
146
|
+
finishReason?: string; // For flow control (stop/tool-calls/length/error), not sent to LLM
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
@@ -203,8 +203,8 @@ export interface Session {
|
|
|
203
203
|
provider: ProviderId;
|
|
204
204
|
model: string;
|
|
205
205
|
messages: SessionMessage[];
|
|
206
|
-
todos: Todo[];
|
|
207
|
-
nextTodoId: number;
|
|
206
|
+
todos: Todo[]; // Per-session todo list (not global!)
|
|
207
|
+
nextTodoId: number; // Next todo ID for this session (starts at 1)
|
|
208
208
|
|
|
209
209
|
// Note: Streaming state derived from message.status, not stored here
|
|
210
210
|
// To check if streaming: messages.some(m => m.status === 'active')
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { CommonOptions, SetupResult } from './common.types.js';
|
|
7
|
-
import type {
|
|
7
|
+
import type { MCPServerConfigUnion } from './mcp.types.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Target-specific configuration
|
|
11
11
|
* Defines how agents, configs, and other artifacts are structured for each target
|
|
12
12
|
*/
|
|
13
13
|
export interface TargetConfig {
|
|
14
|
+
/** Base configuration directory (e.g., '.claude', '.opencode') */
|
|
15
|
+
configDir: string;
|
|
14
16
|
/** Directory where agents are installed */
|
|
15
17
|
agentDir: string;
|
|
16
18
|
/** File extension for agent files */
|
package/src/types/todo.types.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface Todo {
|
|
|
10
10
|
content: string;
|
|
11
11
|
status: TodoStatus;
|
|
12
12
|
activeForm: string; // Present continuous form (e.g., "Building feature X")
|
|
13
|
-
ordering: number;
|
|
13
|
+
ordering: number; // For custom ordering (higher = earlier in list)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface TodoUpdate {
|
package/src/types.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
5
|
import {
|
|
3
|
-
detectPackageManagerFromUserAgent,
|
|
4
|
-
detectPackageManagerFromLockFiles,
|
|
5
6
|
detectPackageManager,
|
|
7
|
+
detectPackageManagerFromLockFiles,
|
|
8
|
+
detectPackageManagerFromUserAgent,
|
|
6
9
|
getPackageManagerInfo,
|
|
7
10
|
getUpgradeCommand,
|
|
8
11
|
} from '../package-manager-detector';
|
|
9
|
-
import fs from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import os from 'node:os';
|
|
12
12
|
|
|
13
13
|
describe('Package Manager Detection', () => {
|
|
14
14
|
const originalEnv = process.env;
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from 'node:fs/promises';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
import { getOutputStylesDir, getRulesDir } from './config/paths.js';
|
|
15
|
+
import { getAgentsDir, getOutputStylesDir, getRulesDir } from './config/paths.js';
|
|
16
16
|
import { yamlUtils } from './config/target-utils.js';
|
|
17
|
+
import { CLIError } from './error-handler.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Load and combine rules and output styles
|
|
@@ -57,7 +58,7 @@ async function loadRules(ruleNames?: string[]): Promise<string> {
|
|
|
57
58
|
// Strip YAML front matter
|
|
58
59
|
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
59
60
|
sections.push(stripped);
|
|
60
|
-
} catch (
|
|
61
|
+
} catch (_error) {
|
|
61
62
|
// Log warning if rule file not found, but continue with other rules
|
|
62
63
|
console.warn(`Warning: Rule file not found: ${ruleName}.md`);
|
|
63
64
|
}
|
|
@@ -87,7 +88,7 @@ async function loadOutputStyles(styleNames?: string[]): Promise<string> {
|
|
|
87
88
|
const content = await fs.readFile(filePath, 'utf8');
|
|
88
89
|
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
89
90
|
sections.push(stripped);
|
|
90
|
-
} catch (
|
|
91
|
+
} catch (_error) {
|
|
91
92
|
console.warn(`Warning: Output style file not found: ${styleName}.md`);
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -130,3 +131,110 @@ export async function enhanceAgentContent(
|
|
|
130
131
|
|
|
131
132
|
return `${agentContent}\n\n---\n\n# Rules and Output Styles\n\n${rulesAndStyles}`;
|
|
132
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Filter rules: intersection of agent's required rules and globally enabled rules
|
|
137
|
+
* @param agentRules - Rules defined in agent frontmatter
|
|
138
|
+
* @param enabledRules - Globally enabled rules from user settings
|
|
139
|
+
* @returns Intersection of both arrays (rules that are both required by agent AND enabled globally)
|
|
140
|
+
*/
|
|
141
|
+
function filterRules(agentRules?: string[], enabledRules?: string[]): string[] | undefined {
|
|
142
|
+
// If agent doesn't define rules, return undefined (will use default)
|
|
143
|
+
if (!agentRules || agentRules.length === 0) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If no global filter, use all agent rules
|
|
148
|
+
if (!enabledRules || enabledRules.length === 0) {
|
|
149
|
+
return agentRules;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Return intersection: rules that are both in agent's list AND globally enabled
|
|
153
|
+
const filtered = agentRules.filter((rule) => enabledRules.includes(rule));
|
|
154
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Load agent content from various locations
|
|
159
|
+
* @param agentName - Name of the agent (without .md extension)
|
|
160
|
+
* @param agentFilePath - Optional specific file path to load from
|
|
161
|
+
* @param enabledRules - Globally enabled rules (filtered with agent's frontmatter rules)
|
|
162
|
+
* @param enabledOutputStyles - Optional array of enabled output style names
|
|
163
|
+
*/
|
|
164
|
+
export async function loadAgentContent(
|
|
165
|
+
agentName: string,
|
|
166
|
+
agentFilePath?: string,
|
|
167
|
+
enabledRules?: string[],
|
|
168
|
+
enabledOutputStyles?: string[]
|
|
169
|
+
): Promise<string> {
|
|
170
|
+
try {
|
|
171
|
+
// If specific file path provided, load from there
|
|
172
|
+
if (agentFilePath) {
|
|
173
|
+
const content = await fs.readFile(path.resolve(agentFilePath), 'utf-8');
|
|
174
|
+
// Extract rules from agent frontmatter
|
|
175
|
+
const { metadata } = await yamlUtils.extractFrontMatter(content);
|
|
176
|
+
const agentRules = metadata.rules as string[] | undefined;
|
|
177
|
+
// Filter: intersection of agent's rules and globally enabled rules
|
|
178
|
+
const rulesToLoad = filterRules(agentRules, enabledRules);
|
|
179
|
+
// Enhance with filtered rules and enabled output styles
|
|
180
|
+
return await enhanceAgentContent(content, rulesToLoad, enabledOutputStyles);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// First try to load from .claude/agents/ directory (processed agents with rules already included)
|
|
184
|
+
const claudeAgentPath = path.join(process.cwd(), '.claude', 'agents', `${agentName}.md`);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const content = await fs.readFile(claudeAgentPath, 'utf-8');
|
|
188
|
+
// Enhance with enabled output styles (rules are already included in the file)
|
|
189
|
+
return await enhanceAgentContent(content, [], enabledOutputStyles);
|
|
190
|
+
} catch (_error) {
|
|
191
|
+
// Try to load from local agents/ directory (user-defined agents)
|
|
192
|
+
const localAgentPath = path.join(process.cwd(), 'agents', `${agentName}.md`);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const content = await fs.readFile(localAgentPath, 'utf-8');
|
|
196
|
+
// Extract rules from agent frontmatter
|
|
197
|
+
const { metadata } = await yamlUtils.extractFrontMatter(content);
|
|
198
|
+
const agentRules = metadata.rules as string[] | undefined;
|
|
199
|
+
// Filter: intersection of agent's rules and globally enabled rules
|
|
200
|
+
const rulesToLoad = filterRules(agentRules, enabledRules);
|
|
201
|
+
// Enhance with filtered rules and enabled output styles
|
|
202
|
+
return await enhanceAgentContent(content, rulesToLoad, enabledOutputStyles);
|
|
203
|
+
} catch (_error2) {
|
|
204
|
+
// Try to load from the package's agents directory
|
|
205
|
+
const packageAgentsDir = getAgentsDir();
|
|
206
|
+
const packageAgentPath = path.join(packageAgentsDir, `${agentName}.md`);
|
|
207
|
+
|
|
208
|
+
const content = await fs.readFile(packageAgentPath, 'utf-8');
|
|
209
|
+
// Extract rules from agent frontmatter
|
|
210
|
+
const { metadata } = await yamlUtils.extractFrontMatter(content);
|
|
211
|
+
const agentRules = metadata.rules as string[] | undefined;
|
|
212
|
+
// Filter: intersection of agent's rules and globally enabled rules
|
|
213
|
+
const rulesToLoad = filterRules(agentRules, enabledRules);
|
|
214
|
+
// Enhance with filtered rules and enabled output styles
|
|
215
|
+
return await enhanceAgentContent(content, rulesToLoad, enabledOutputStyles);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (_error) {
|
|
219
|
+
throw new CLIError(
|
|
220
|
+
`Agent '${agentName}' not found${agentFilePath ? ` at ${agentFilePath}` : ''}`,
|
|
221
|
+
'AGENT_NOT_FOUND'
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract agent instructions from agent content (strip YAML front matter)
|
|
228
|
+
*/
|
|
229
|
+
export function extractAgentInstructions(agentContent: string): string {
|
|
230
|
+
// Extract content after YAML front matter
|
|
231
|
+
const yamlFrontMatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
|
232
|
+
const match = agentContent.match(yamlFrontMatterRegex);
|
|
233
|
+
|
|
234
|
+
if (match) {
|
|
235
|
+
return agentContent.substring(match[0].length).trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If no front matter, return the full content
|
|
239
|
+
return agentContent.trim();
|
|
240
|
+
}
|
|
@@ -39,7 +39,9 @@ export function findPackageRoot(context?: string): string {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const parentDir = path.dirname(currentDir);
|
|
42
|
-
if (parentDir === currentDir)
|
|
42
|
+
if (parentDir === currentDir) {
|
|
43
|
+
break; // reached filesystem root
|
|
44
|
+
}
|
|
43
45
|
currentDir = parentDir;
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -288,7 +288,7 @@ export const pathUtils = {
|
|
|
288
288
|
// Try to extract from content title
|
|
289
289
|
const titleMatch = content.match(/^#\s+(.+?)(?:\s+Agent)?$/m);
|
|
290
290
|
if (titleMatch) {
|
|
291
|
-
const title = titleMatch[1]
|
|
291
|
+
const title = titleMatch[1]?.trim().toLowerCase();
|
|
292
292
|
const kebabTitle = title.replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
293
293
|
return kebabTitle + (kebabTitle.includes('agent') ? '' : '-agent');
|
|
294
294
|
}
|
|
@@ -319,7 +319,7 @@ export const pathUtils = {
|
|
|
319
319
|
extractDescription(content: string): string {
|
|
320
320
|
const firstParagraph = content.match(/^#\s+.+?\n\n(.+?)(?:\n\n|\n#|$)/s);
|
|
321
321
|
if (firstParagraph) {
|
|
322
|
-
return firstParagraph[1]
|
|
322
|
+
return firstParagraph[1]?.trim().replace(/\n+/g, ' ');
|
|
323
323
|
}
|
|
324
324
|
return 'Development agent for specialized tasks';
|
|
325
325
|
},
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Welcome messages and branding
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import chalk from 'chalk';
|
|
7
6
|
import boxen from 'boxen';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Display welcome banner
|
|
@@ -13,7 +13,7 @@ export function showWelcome(): void {
|
|
|
13
13
|
console.log(
|
|
14
14
|
boxen(
|
|
15
15
|
`${chalk.cyan.bold('Sylphx Flow')} ${chalk.dim('- AI-Powered Development Framework')}\n` +
|
|
16
|
-
|
|
16
|
+
`${chalk.dim('Auto-initialization • Smart upgrades • One-click launch')}`,
|
|
17
17
|
{
|
|
18
18
|
padding: 1,
|
|
19
19
|
margin: { bottom: 1 },
|