@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.
- package/CHANGELOG.md +12 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +37 -29
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -2
- 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 +24 -12
- 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 +4 -4
- 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-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- 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
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,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from 'node:fs/promises';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
import { getOutputStylesDir, getRulesDir
|
|
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 (
|
|
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 (
|
|
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)
|
|
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 },
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
import { playNotificationSound } from './audio-player.js';
|
|
7
7
|
|
|
8
8
|
// Terminal notification with sound
|
|
9
|
-
export function sendTerminalNotification(
|
|
10
|
-
|
|
11
|
-
|
|
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(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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',
|
|
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',
|
|
89
|
-
|
|
90
|
-
'--
|
|
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)
|
|
96
|
-
|
|
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)
|
|
121
|
-
|
|
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
|
-
|
|
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:
|
|
167
|
-
|
|
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 (
|
|
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(
|
|
30
|
-
|
|
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(
|
|
33
|
-
|
|
34
|
-
|
|
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(
|
|
46
|
+
console.log(
|
|
47
|
+
`\n ${chalk.yellow('⚠')} Flow version outdated: ${state.version} → ${state.latestVersion}`
|
|
48
|
+
);
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
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;
|