@sylphx/flow 2.1.3 → 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 (66) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +44 -0
  3. package/package.json +79 -73
  4. package/src/commands/flow/execute-v2.ts +37 -29
  5. package/src/commands/flow/prompt.ts +5 -3
  6. package/src/commands/flow/types.ts +0 -2
  7. package/src/commands/flow-command.ts +20 -13
  8. package/src/commands/hook-command.ts +1 -3
  9. package/src/commands/settings-command.ts +36 -33
  10. package/src/config/ai-config.ts +60 -41
  11. package/src/core/agent-loader.ts +11 -6
  12. package/src/core/attach-manager.ts +92 -84
  13. package/src/core/backup-manager.ts +35 -29
  14. package/src/core/cleanup-handler.ts +11 -8
  15. package/src/core/error-handling.ts +23 -30
  16. package/src/core/flow-executor.ts +58 -76
  17. package/src/core/formatting/bytes.ts +2 -4
  18. package/src/core/functional/async.ts +5 -4
  19. package/src/core/functional/error-handler.ts +2 -2
  20. package/src/core/git-stash-manager.ts +21 -10
  21. package/src/core/installers/file-installer.ts +0 -1
  22. package/src/core/installers/mcp-installer.ts +0 -1
  23. package/src/core/project-manager.ts +24 -18
  24. package/src/core/secrets-manager.ts +54 -73
  25. package/src/core/session-manager.ts +20 -22
  26. package/src/core/state-detector.ts +139 -80
  27. package/src/core/template-loader.ts +13 -31
  28. package/src/core/upgrade-manager.ts +122 -69
  29. package/src/index.ts +8 -5
  30. package/src/services/auto-upgrade.ts +1 -1
  31. package/src/services/config-service.ts +41 -29
  32. package/src/services/global-config.ts +2 -2
  33. package/src/services/target-installer.ts +9 -7
  34. package/src/targets/claude-code.ts +24 -12
  35. package/src/targets/opencode.ts +17 -6
  36. package/src/types/cli.types.ts +2 -2
  37. package/src/types/provider.types.ts +1 -7
  38. package/src/types/session.types.ts +11 -11
  39. package/src/types/target.types.ts +3 -1
  40. package/src/types/todo.types.ts +1 -1
  41. package/src/types.ts +1 -1
  42. package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
  43. package/src/utils/agent-enhancer.ts +4 -4
  44. package/src/utils/config/paths.ts +3 -1
  45. package/src/utils/config/target-utils.ts +2 -2
  46. package/src/utils/display/banner.ts +2 -2
  47. package/src/utils/display/notifications.ts +58 -45
  48. package/src/utils/display/status.ts +29 -12
  49. package/src/utils/files/file-operations.ts +1 -1
  50. package/src/utils/files/sync-utils.ts +38 -41
  51. package/src/utils/index.ts +19 -27
  52. package/src/utils/package-manager-detector.ts +15 -5
  53. package/src/utils/security/security.ts +8 -4
  54. package/src/utils/target-selection.ts +5 -2
  55. package/src/utils/version.ts +4 -2
  56. package/src/commands/flow-orchestrator.ts +0 -328
  57. package/src/commands/init-command.ts +0 -92
  58. package/src/commands/init-core.ts +0 -331
  59. package/src/core/agent-manager.ts +0 -174
  60. package/src/core/loop-controller.ts +0 -200
  61. package/src/core/rule-loader.ts +0 -147
  62. package/src/core/rule-manager.ts +0 -240
  63. package/src/services/claude-config-service.ts +0 -252
  64. package/src/services/first-run-setup.ts +0 -220
  65. package/src/services/smart-config-service.ts +0 -269
  66. package/src/types/api.types.ts +0 -9
@@ -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,7 +12,7 @@
12
12
 
13
13
  import fs from 'node:fs/promises';
14
14
  import path from 'node:path';
15
- import { getOutputStylesDir, getRulesDir, getAgentsDir } from './config/paths.js';
15
+ import { getAgentsDir, getOutputStylesDir, getRulesDir } from './config/paths.js';
16
16
  import { yamlUtils } from './config/target-utils.js';
17
17
  import { CLIError } from './error-handler.js';
18
18
 
@@ -58,7 +58,7 @@ async function loadRules(ruleNames?: string[]): Promise<string> {
58
58
  // Strip YAML front matter
59
59
  const stripped = await yamlUtils.stripFrontMatter(content);
60
60
  sections.push(stripped);
61
- } catch (error) {
61
+ } catch (_error) {
62
62
  // Log warning if rule file not found, but continue with other rules
63
63
  console.warn(`Warning: Rule file not found: ${ruleName}.md`);
64
64
  }
@@ -88,7 +88,7 @@ async function loadOutputStyles(styleNames?: string[]): Promise<string> {
88
88
  const content = await fs.readFile(filePath, 'utf8');
89
89
  const stripped = await yamlUtils.stripFrontMatter(content);
90
90
  sections.push(stripped);
91
- } catch (error) {
91
+ } catch (_error) {
92
92
  console.warn(`Warning: Output style file not found: ${styleName}.md`);
93
93
  }
94
94
  }
@@ -150,7 +150,7 @@ function filterRules(agentRules?: string[], enabledRules?: string[]): string[] |
150
150
  }
151
151
 
152
152
  // Return intersection: rules that are both in agent's list AND globally enabled
153
- const filtered = agentRules.filter(rule => enabledRules.includes(rule));
153
+ const filtered = agentRules.filter((rule) => enabledRules.includes(rule));
154
154
  return filtered.length > 0 ? filtered : undefined;
155
155
  }
156
156
 
@@ -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 },
@@ -6,10 +6,14 @@
6
6
  import { playNotificationSound } from './audio-player.js';
7
7
 
8
8
  // Terminal notification with sound
9
- export function sendTerminalNotification(title: string, message: string, options?: {
10
- sound?: boolean;
11
- duration?: number;
12
- }) {
9
+ export function sendTerminalNotification(
10
+ _title: string,
11
+ _message: string,
12
+ options?: {
13
+ sound?: boolean;
14
+ duration?: number;
15
+ }
16
+ ) {
13
17
  const { sound = true, duration = 3000 } = options || {};
14
18
 
15
19
  // Play system sound using cross-platform audio player
@@ -30,40 +34,45 @@ export function sendTerminalNotification(title: string, message: string, options
30
34
  }
31
35
 
32
36
  // OS-level notification using system APIs
33
- export async function sendOSNotification(title: string, message: string, options?: {
34
- icon?: string;
35
- urgency?: 'low' | 'normal' | 'critical';
36
- sound?: boolean;
37
- timeout?: number;
38
- }) {
37
+ export async function sendOSNotification(
38
+ title: string,
39
+ message: string,
40
+ options?: {
41
+ icon?: string;
42
+ urgency?: 'low' | 'normal' | 'critical';
43
+ sound?: boolean;
44
+ timeout?: number;
45
+ }
46
+ ) {
39
47
  const {
40
48
  icon = '🌀', // Flow-themed spiral emoji for Sylphx Flow notifications
41
49
  urgency = 'normal',
42
50
  sound = true,
43
- timeout = 5000
51
+ timeout = 5000,
44
52
  } = options || {};
45
-
53
+
46
54
  try {
47
55
  if (process.platform === 'darwin') {
48
56
  // macOS: use osascript with simplified approach
49
- const { spawn } = require('child_process');
50
-
57
+ const { spawn } = require('node:child_process');
58
+
51
59
  await new Promise<void>((resolve, reject) => {
52
60
  // Simple notification without complex escaping
53
61
  const args = [
54
- '-e', `display notification "${message.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`
62
+ '-e',
63
+ `display notification "${message.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`,
55
64
  ];
56
-
57
- const proc = spawn('osascript', args, {
65
+
66
+ const proc = spawn('osascript', args, {
58
67
  stdio: 'pipe',
59
- env: { ...process.env, PATH: '/usr/bin:/bin:/usr/sbin:/sbin' }
68
+ env: { ...process.env, PATH: '/usr/bin:/bin:/usr/sbin:/sbin' },
60
69
  });
61
-
70
+
62
71
  let stderr = '';
63
72
  proc.stderr?.on('data', (data) => {
64
73
  stderr += data.toString();
65
74
  });
66
-
75
+
67
76
  proc.on('exit', (code) => {
68
77
  if (code === 0) {
69
78
  // Play sound separately if needed using cross-platform audio player
@@ -79,28 +88,32 @@ export async function sendOSNotification(title: string, message: string, options
79
88
  });
80
89
  proc.on('error', reject);
81
90
  });
82
-
83
91
  } else if (process.platform === 'linux') {
84
92
  // Linux: use notify-send
85
- const { spawn } = require('child_process');
93
+ const { spawn } = require('node:child_process');
86
94
  await new Promise<void>((resolve, reject) => {
87
95
  const proc = spawn('notify-send', [
88
- '--urgency', urgency,
89
- '--icon', icon,
90
- '--expire-time', timeout.toString(),
96
+ '--urgency',
97
+ urgency,
98
+ '--icon',
99
+ icon,
100
+ '--expire-time',
101
+ timeout.toString(),
91
102
  title,
92
- message
103
+ message,
93
104
  ]);
94
105
  proc.on('exit', (code) => {
95
- if (code === 0) resolve();
96
- else reject(new Error(`notify-send failed with code ${code}`));
106
+ if (code === 0) {
107
+ resolve();
108
+ } else {
109
+ reject(new Error(`notify-send failed with code ${code}`));
110
+ }
97
111
  });
98
112
  proc.on('error', reject);
99
113
  });
100
-
101
114
  } else if (process.platform === 'win32') {
102
115
  // Windows: use PowerShell toast notifications
103
- const { spawn } = require('child_process');
116
+ const { spawn } = require('node:child_process');
104
117
  const powershellScript = `
105
118
  Add-Type -AssemblyName System.Windows.Forms
106
119
  $notification = New-Object System.Windows.Forms.NotifyIcon
@@ -113,12 +126,15 @@ export async function sendOSNotification(title: string, message: string, options
113
126
  Start-Sleep -Milliseconds ${timeout + 1000}
114
127
  $notification.Dispose()
115
128
  `;
116
-
129
+
117
130
  await new Promise<void>((resolve, reject) => {
118
131
  const proc = spawn('powershell', ['-Command', powershellScript]);
119
132
  proc.on('exit', (code) => {
120
- if (code === 0) resolve();
121
- else reject(new Error(`PowerShell failed with code ${code}`));
133
+ if (code === 0) {
134
+ resolve();
135
+ } else {
136
+ reject(new Error(`PowerShell failed with code ${code}`));
137
+ }
122
138
  });
123
139
  proc.on('error', reject);
124
140
  });
@@ -132,24 +148,20 @@ export async function sendOSNotification(title: string, message: string, options
132
148
 
133
149
  // Combined notification - sends both terminal and OS notifications
134
150
  export function sendNotification(
135
- title: string,
136
- message: string,
151
+ title: string,
152
+ message: string,
137
153
  options?: {
138
154
  osNotification?: boolean;
139
155
  terminalNotification?: boolean;
140
156
  sound?: boolean;
141
157
  }
142
158
  ) {
143
- const {
144
- osNotification = true,
145
- terminalNotification = true,
146
- sound = true
147
- } = options || {};
148
-
159
+ const { osNotification = true, terminalNotification = true, sound = true } = options || {};
160
+
149
161
  if (terminalNotification) {
150
162
  sendTerminalNotification(title, message, { sound });
151
163
  }
152
-
164
+
153
165
  if (osNotification) {
154
166
  sendOSNotification(title, message, { sound });
155
167
  }
@@ -163,7 +175,8 @@ export async function checkNotificationSupport(): Promise<{
163
175
  }> {
164
176
  return {
165
177
  terminalSupported: true, // Always supported
166
- osSupported: process.platform === 'darwin' || process.platform === 'linux' || process.platform === 'win32',
167
- platform: process.platform
178
+ osSupported:
179
+ process.platform === 'darwin' || process.platform === 'linux' || process.platform === 'win32',
180
+ platform: process.platform,
168
181
  };
169
- }
182
+ }
@@ -13,9 +13,7 @@ import { isVersionOutdated } from '../version.js';
13
13
  export async function showStatus(state: ProjectState): Promise<void> {
14
14
  console.log(chalk.cyan.bold('📊 Project Status\n'));
15
15
 
16
- if (!state.initialized) {
17
- console.log(' ' + chalk.yellow('⚠ Not initialized'));
18
- } else {
16
+ if (state.initialized) {
19
17
  console.log(` ${chalk.green('✓')} Initialized (Flow v${state.version || 'unknown'})`);
20
18
 
21
19
  if (state.target) {
@@ -26,21 +24,38 @@ export async function showStatus(state: ProjectState): Promise<void> {
26
24
  // Component status
27
25
  const components = state.components;
28
26
  console.log(`\n ${chalk.cyan('Components:')}`);
29
- console.log(` Agents: ${components.agents.installed ? chalk.green(`✓ ${components.agents.count}`) : chalk.red('✗')}`);
30
- console.log(` Rules: ${components.rules.installed ? chalk.green(`✓ ${components.rules.count}`) : chalk.red('✗')}`);
27
+ console.log(
28
+ ` Agents: ${components.agents.installed ? chalk.green(`✓ ${components.agents.count}`) : chalk.red('✗')}`
29
+ );
30
+ console.log(
31
+ ` Rules: ${components.rules.installed ? chalk.green(`✓ ${components.rules.count}`) : chalk.red('✗')}`
32
+ );
31
33
  console.log(` Hooks: ${components.hooks.installed ? chalk.green('✓') : chalk.red('✗')}`);
32
- console.log(` MCP: ${components.mcp.installed ? chalk.green(`✓ ${components.mcp.serverCount} servers`) : chalk.red('✗')}`);
33
- console.log(` Output styles: ${components.outputStyles.installed ? chalk.green('✓') : chalk.red('✗')}`);
34
- console.log(` Slash commands: ${components.slashCommands.installed ? chalk.green(`✓ ${components.slashCommands.count}`) : chalk.red('✗')}`);
34
+ console.log(
35
+ ` MCP: ${components.mcp.installed ? chalk.green(`✓ ${components.mcp.serverCount} servers`) : chalk.red('✗')}`
36
+ );
37
+ console.log(
38
+ ` Output styles: ${components.outputStyles.installed ? chalk.green('✓') : chalk.red('✗')}`
39
+ );
40
+ console.log(
41
+ ` Slash commands: ${components.slashCommands.installed ? chalk.green(`✓ ${components.slashCommands.count}`) : chalk.red('✗')}`
42
+ );
35
43
 
36
44
  // Outdated warnings
37
45
  if (state.outdated) {
38
- console.log(`\n ${chalk.yellow('⚠')} Flow version outdated: ${state.version} → ${state.latestVersion}`);
46
+ console.log(
47
+ `\n ${chalk.yellow('⚠')} Flow version outdated: ${state.version} → ${state.latestVersion}`
48
+ );
39
49
  }
40
50
 
41
- if (state.targetVersion && state.targetLatestVersion &&
42
- isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
43
- console.log(` ${chalk.yellow('⚠')} ${state.target} update available: v${state.targetVersion} → v${state.targetLatestVersion}`);
51
+ if (
52
+ state.targetVersion &&
53
+ state.targetLatestVersion &&
54
+ isVersionOutdated(state.targetVersion, state.targetLatestVersion)
55
+ ) {
56
+ console.log(
57
+ ` ${chalk.yellow('⚠')} ${state.target} update available: v${state.targetVersion} → v${state.targetLatestVersion}`
58
+ );
44
59
  }
45
60
 
46
61
  if (state.lastUpdated) {
@@ -49,6 +64,8 @@ export async function showStatus(state: ProjectState): Promise<void> {
49
64
  console.log(`\n ${chalk.yellow('⚠')} Last updated: ${days} days ago`);
50
65
  }
51
66
  }
67
+ } else {
68
+ console.log(` ${chalk.yellow('⚠ Not initialized')}`);
52
69
  }
53
70
 
54
71
  console.log('');
@@ -5,8 +5,8 @@
5
5
 
6
6
  import fs from 'node:fs/promises';
7
7
  import path from 'node:path';
8
- import { pathSecurity } from '../security/security.js';
9
8
  import { formatFileSize as formatFileSizeCore } from '../../core/formatting/bytes.js';
9
+ import { pathSecurity } from '../security/security.js';
10
10
 
11
11
  export interface FileReadOptions {
12
12
  encoding?: BufferEncoding;