@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.
Files changed (70) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +44 -0
  3. package/package.json +79 -73
  4. package/src/commands/flow/execute-v2.ts +39 -30
  5. package/src/commands/flow/index.ts +2 -4
  6. package/src/commands/flow/prompt.ts +5 -3
  7. package/src/commands/flow/types.ts +0 -9
  8. package/src/commands/flow-command.ts +20 -13
  9. package/src/commands/hook-command.ts +1 -3
  10. package/src/commands/settings-command.ts +36 -33
  11. package/src/config/ai-config.ts +60 -41
  12. package/src/core/agent-loader.ts +11 -6
  13. package/src/core/attach-manager.ts +92 -84
  14. package/src/core/backup-manager.ts +35 -29
  15. package/src/core/cleanup-handler.ts +11 -8
  16. package/src/core/error-handling.ts +23 -30
  17. package/src/core/flow-executor.ts +58 -76
  18. package/src/core/formatting/bytes.ts +2 -4
  19. package/src/core/functional/async.ts +5 -4
  20. package/src/core/functional/error-handler.ts +2 -2
  21. package/src/core/git-stash-manager.ts +21 -10
  22. package/src/core/installers/file-installer.ts +0 -1
  23. package/src/core/installers/mcp-installer.ts +0 -1
  24. package/src/core/project-manager.ts +24 -18
  25. package/src/core/secrets-manager.ts +54 -73
  26. package/src/core/session-manager.ts +20 -22
  27. package/src/core/state-detector.ts +139 -80
  28. package/src/core/template-loader.ts +13 -31
  29. package/src/core/upgrade-manager.ts +122 -69
  30. package/src/index.ts +8 -5
  31. package/src/services/auto-upgrade.ts +1 -1
  32. package/src/services/config-service.ts +41 -29
  33. package/src/services/global-config.ts +2 -2
  34. package/src/services/target-installer.ts +9 -7
  35. package/src/targets/claude-code.ts +28 -15
  36. package/src/targets/opencode.ts +17 -6
  37. package/src/types/cli.types.ts +2 -2
  38. package/src/types/provider.types.ts +1 -7
  39. package/src/types/session.types.ts +11 -11
  40. package/src/types/target.types.ts +3 -1
  41. package/src/types/todo.types.ts +1 -1
  42. package/src/types.ts +1 -1
  43. package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
  44. package/src/utils/agent-enhancer.ts +111 -3
  45. package/src/utils/config/paths.ts +3 -1
  46. package/src/utils/config/target-utils.ts +2 -2
  47. package/src/utils/display/banner.ts +2 -2
  48. package/src/utils/display/notifications.ts +58 -45
  49. package/src/utils/display/status.ts +29 -12
  50. package/src/utils/files/file-operations.ts +1 -1
  51. package/src/utils/files/sync-utils.ts +38 -41
  52. package/src/utils/index.ts +19 -27
  53. package/src/utils/package-manager-detector.ts +15 -5
  54. package/src/utils/security/security.ts +8 -4
  55. package/src/utils/target-selection.ts +5 -2
  56. package/src/utils/version.ts +4 -2
  57. package/src/commands/flow/execute.ts +0 -453
  58. package/src/commands/flow/setup.ts +0 -312
  59. package/src/commands/flow-orchestrator.ts +0 -328
  60. package/src/commands/init-command.ts +0 -92
  61. package/src/commands/init-core.ts +0 -331
  62. package/src/commands/run-command.ts +0 -126
  63. package/src/core/agent-manager.ts +0 -174
  64. package/src/core/loop-controller.ts +0 -200
  65. package/src/core/rule-loader.ts +0 -147
  66. package/src/core/rule-manager.ts +0 -240
  67. package/src/services/claude-config-service.ts +0 -252
  68. package/src/services/first-run-setup.ts +0 -220
  69. package/src/services/smart-config-service.ts +0 -269
  70. 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) dryRunArgs.push('-p');
222
- if (options.continue) dryRunArgs.push('-c');
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: any) {
301
- if (error.code === 'ENOENT') {
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 (error.code) {
305
- throw new CLIError(`Claude Code exited with code ${error.code}`, 'CLAUDE_ERROR');
310
+ if (err.code) {
311
+ throw new CLIError(`Claude Code exited with code ${err.code}`, 'CLAUDE_ERROR');
306
312
  }
307
- throw new CLIError(`Failed to execute Claude Code: ${error.message}`, 'CLAUDE_ERROR');
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: any) {
337
- if (error.code !== 'ENOENT') {
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('./functional/claude-code-logic.js');
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 and output styles appended
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 and output styles
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
  },
@@ -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 { installToDirectory, installFile } from '../core/installers/file-installer.js';
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 { name: additionalName, mode: additionalMode, rules: additionalRules, ...additionalCleanMetadata } = metadata;
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
- systemPrompt: string,
406
+ _systemPrompt: string,
401
407
  userPrompt: string,
402
- options: { verbose?: boolean; dryRun?: boolean; print?: boolean; continue?: boolean; agent?: string } = {}
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');
@@ -82,6 +82,6 @@ export interface RunCommandOptions {
82
82
  agent?: string;
83
83
  agentFile?: string;
84
84
  prompt?: string;
85
- print?: boolean; // Headless print mode
86
- continue?: boolean; // Continue previous conversation
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'; // Errors are immediately 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; // 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")
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[]; // UI display (without system status)
139
+ content: MessagePart[]; // UI display (without system status)
140
140
  timestamp: number;
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)
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; // For UI/monitoring, not sent to LLM
146
- finishReason?: string; // For flow control (stop/tool-calls/length/error), not sent to LLM
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[]; // Per-session todo list (not global!)
207
- nextTodoId: number; // Next todo ID for this session (starts at 1)
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 { MCPServerConfigFlags, MCPServerConfigUnion } from './mcp.types.js';
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 */
@@ -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; // For custom ordering (higher = earlier in list)
13
+ ordering: number; // For custom ordering (higher = earlier in list)
14
14
  }
15
15
 
16
16
  export interface TodoUpdate {
package/src/types.ts CHANGED
@@ -12,9 +12,9 @@
12
12
 
13
13
  // Re-export all types for backward compatibility
14
14
  export type {
15
+ CommandConfig,
15
16
  CommandHandler,
16
17
  CommandOptions,
17
- CommandConfig,
18
18
  RunCommandOptions,
19
19
  } from './types/cli.types.js';
20
20
  export type {
@@ -1,14 +1,14 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
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 (error) {
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 (error) {
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) break; // reached filesystem root
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]!.trim().toLowerCase();
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]!.trim().replace(/\n+/g, ' ');
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
- `${chalk.dim('Auto-initialization • Smart upgrades • One-click launch')}`,
16
+ `${chalk.dim('Auto-initialization • Smart upgrades • One-click launch')}`,
17
17
  {
18
18
  padding: 1,
19
19
  margin: { bottom: 1 },