@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
|
@@ -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;
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import type { Target } from '../../types.js';
|
|
5
4
|
import { MCP_SERVER_REGISTRY } from '../../config/servers.js';
|
|
6
|
-
import {
|
|
5
|
+
import type { Target } from '../../types.js';
|
|
6
|
+
import { getAgentsDir, getRulesDir, getSlashCommandsDir } from '../config/paths.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Scan directory for .md files and return basenames
|
|
10
10
|
*/
|
|
11
11
|
function scanTemplateDir(dir: string): string[] {
|
|
12
|
-
if (!fs.existsSync(dir))
|
|
12
|
+
if (!fs.existsSync(dir)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
try {
|
|
15
|
-
return fs
|
|
17
|
+
return fs
|
|
18
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
16
19
|
.filter((f) => f.isFile() && f.name.endsWith('.md'))
|
|
17
20
|
.map((f) => f.name);
|
|
18
21
|
} catch {
|
|
@@ -31,9 +34,9 @@ const FLOW_RULES = scanTemplateDir(getRulesDir());
|
|
|
31
34
|
* Categorized files for sync
|
|
32
35
|
*/
|
|
33
36
|
interface CategorizedFiles {
|
|
34
|
-
inFlow: string[];
|
|
35
|
-
unknown: string[];
|
|
36
|
-
missing: string[];
|
|
37
|
+
inFlow: string[]; // Files that exist locally and in Flow templates (will reinstall)
|
|
38
|
+
unknown: string[]; // Files not in Flow templates (custom or removed)
|
|
39
|
+
missing: string[]; // Flow templates that don't exist locally (will install)
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -48,8 +51,8 @@ interface SyncManifest {
|
|
|
48
51
|
notInRegistry: string[];
|
|
49
52
|
};
|
|
50
53
|
hooks: {
|
|
51
|
-
inConfig: string[];
|
|
52
|
-
orphaned: string[];
|
|
54
|
+
inConfig: string[]; // Hooks from config (will be synced)
|
|
55
|
+
orphaned: string[]; // Hooks that exist locally but not in config (ask user)
|
|
53
56
|
};
|
|
54
57
|
preserve: string[];
|
|
55
58
|
}
|
|
@@ -151,14 +154,14 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
|
|
|
151
154
|
const installedServers = Object.keys(mcpConfig.mcpServers);
|
|
152
155
|
const registryServers = Object.keys(MCP_SERVER_REGISTRY);
|
|
153
156
|
|
|
154
|
-
manifest.mcpServers.inRegistry = installedServers.filter(id =>
|
|
157
|
+
manifest.mcpServers.inRegistry = installedServers.filter((id) =>
|
|
155
158
|
registryServers.includes(id)
|
|
156
159
|
);
|
|
157
|
-
manifest.mcpServers.notInRegistry = installedServers.filter(
|
|
158
|
-
!registryServers.includes(id)
|
|
160
|
+
manifest.mcpServers.notInRegistry = installedServers.filter(
|
|
161
|
+
(id) => !registryServers.includes(id)
|
|
159
162
|
);
|
|
160
163
|
}
|
|
161
|
-
} catch (
|
|
164
|
+
} catch (_error) {
|
|
162
165
|
console.warn(chalk.yellow('⚠ Failed to read .mcp.json'));
|
|
163
166
|
}
|
|
164
167
|
}
|
|
@@ -178,25 +181,21 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
|
|
|
178
181
|
// In the future, this could be read from Flow config
|
|
179
182
|
const expectedHookTypes = ['Notification'];
|
|
180
183
|
|
|
181
|
-
manifest.hooks.inConfig = existingHookTypes.filter(type =>
|
|
184
|
+
manifest.hooks.inConfig = existingHookTypes.filter((type) =>
|
|
182
185
|
expectedHookTypes.includes(type)
|
|
183
186
|
);
|
|
184
|
-
manifest.hooks.orphaned = existingHookTypes.filter(
|
|
185
|
-
!expectedHookTypes.includes(type)
|
|
187
|
+
manifest.hooks.orphaned = existingHookTypes.filter(
|
|
188
|
+
(type) => !expectedHookTypes.includes(type)
|
|
186
189
|
);
|
|
187
190
|
}
|
|
188
|
-
} catch (
|
|
191
|
+
} catch (_error) {
|
|
189
192
|
console.warn(chalk.yellow('⚠ Failed to read settings.json'));
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
// Files to preserve
|
|
195
|
-
manifest.preserve = [
|
|
196
|
-
'.sylphx-flow/',
|
|
197
|
-
'.secrets/',
|
|
198
|
-
target.config.configFile || '',
|
|
199
|
-
]
|
|
198
|
+
manifest.preserve = ['.sylphx-flow/', '.secrets/', target.config.configFile || '']
|
|
200
199
|
.filter(Boolean)
|
|
201
200
|
.map((p) => path.join(cwd, p));
|
|
202
201
|
|
|
@@ -379,7 +378,9 @@ export interface SelectedToRemove {
|
|
|
379
378
|
/**
|
|
380
379
|
* Select unknown files to remove
|
|
381
380
|
*/
|
|
382
|
-
export async function selectUnknownFilesToRemove(
|
|
381
|
+
export async function selectUnknownFilesToRemove(
|
|
382
|
+
manifest: SyncManifest
|
|
383
|
+
): Promise<SelectedToRemove> {
|
|
383
384
|
const unknownFiles: Array<{ name: string; value: string; type: string }> = [];
|
|
384
385
|
|
|
385
386
|
// Collect all unknown files
|
|
@@ -463,10 +464,7 @@ export async function selectUnknownFilesToRemove(manifest: SyncManifest): Promis
|
|
|
463
464
|
/**
|
|
464
465
|
* Show final summary before execution
|
|
465
466
|
*/
|
|
466
|
-
export function showFinalSummary(
|
|
467
|
-
manifest: SyncManifest,
|
|
468
|
-
selectedUnknowns: SelectedToRemove
|
|
469
|
-
): void {
|
|
467
|
+
export function showFinalSummary(manifest: SyncManifest, selectedUnknowns: SelectedToRemove): void {
|
|
470
468
|
console.log(chalk.cyan.bold('\n━━━ 📋 Final Summary\n'));
|
|
471
469
|
|
|
472
470
|
// Will delete + reinstall
|
|
@@ -490,7 +488,10 @@ export function showFinalSummary(
|
|
|
490
488
|
}
|
|
491
489
|
|
|
492
490
|
// Will remove (selected unknowns)
|
|
493
|
-
const totalToRemove =
|
|
491
|
+
const totalToRemove =
|
|
492
|
+
selectedUnknowns.files.length +
|
|
493
|
+
selectedUnknowns.mcpServers.length +
|
|
494
|
+
selectedUnknowns.hooks.length;
|
|
494
495
|
if (totalToRemove > 0) {
|
|
495
496
|
console.log(chalk.red('Remove (selected):\n'));
|
|
496
497
|
selectedUnknowns.files.forEach((file) => {
|
|
@@ -506,7 +507,11 @@ export function showFinalSummary(
|
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
// Will preserve
|
|
509
|
-
const allSelected = [
|
|
510
|
+
const allSelected = [
|
|
511
|
+
...selectedUnknowns.files,
|
|
512
|
+
...selectedUnknowns.mcpServers,
|
|
513
|
+
...selectedUnknowns.hooks,
|
|
514
|
+
];
|
|
510
515
|
const preservedUnknowns = [
|
|
511
516
|
...manifest.agents.unknown,
|
|
512
517
|
...manifest.slashCommands.unknown,
|
|
@@ -618,14 +623,10 @@ export async function removeMCPServers(cwd: string, serversToRemove: string[]):
|
|
|
618
623
|
}
|
|
619
624
|
|
|
620
625
|
// Write back
|
|
621
|
-
await fs.promises.writeFile(
|
|
622
|
-
mcpPath,
|
|
623
|
-
JSON.stringify(mcpConfig, null, 2) + '\n',
|
|
624
|
-
'utf-8'
|
|
625
|
-
);
|
|
626
|
+
await fs.promises.writeFile(mcpPath, `${JSON.stringify(mcpConfig, null, 2)}\n`, 'utf-8');
|
|
626
627
|
|
|
627
628
|
return removedCount;
|
|
628
|
-
} catch (
|
|
629
|
+
} catch (_error) {
|
|
629
630
|
console.warn(chalk.yellow('⚠ Failed to update .mcp.json'));
|
|
630
631
|
return 0;
|
|
631
632
|
}
|
|
@@ -659,14 +660,10 @@ export async function removeHooks(cwd: string, hooksToRemove: string[]): Promise
|
|
|
659
660
|
}
|
|
660
661
|
|
|
661
662
|
// Write back
|
|
662
|
-
await fs.promises.writeFile(
|
|
663
|
-
settingsPath,
|
|
664
|
-
JSON.stringify(settings, null, 2) + '\n',
|
|
665
|
-
'utf-8'
|
|
666
|
-
);
|
|
663
|
+
await fs.promises.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf-8');
|
|
667
664
|
|
|
668
665
|
return removedCount;
|
|
669
|
-
} catch (
|
|
666
|
+
} catch (_error) {
|
|
670
667
|
console.warn(chalk.yellow('⚠ Failed to update settings.json'));
|
|
671
668
|
return 0;
|
|
672
669
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -3,57 +3,49 @@
|
|
|
3
3
|
* Feature-based organization for better modularity
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// SHARED UTILITIES
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export * from '../shared/index.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// AGENTS
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export * from './agent-enhancer.js';
|
|
14
|
+
export * from './config/mcp-config.js';
|
|
15
|
+
export * from './config/paths.js';
|
|
6
16
|
// ============================================================================
|
|
7
17
|
// CONFIG & SETTINGS
|
|
8
18
|
// ============================================================================
|
|
9
19
|
export * from './config/settings.js';
|
|
10
|
-
export * from './config/mcp-config.js';
|
|
11
20
|
export * from './config/target-config.js';
|
|
12
21
|
export * from './config/target-utils.js';
|
|
13
|
-
export * from './config/paths.js';
|
|
14
|
-
|
|
15
22
|
// ============================================================================
|
|
16
23
|
// DISPLAY & OUTPUT
|
|
17
24
|
// ============================================================================
|
|
18
25
|
export * from './display/banner.js';
|
|
19
|
-
export * from './display/status.js';
|
|
20
26
|
export * from './display/cli-output.js';
|
|
21
27
|
export * from './display/logger.js';
|
|
22
28
|
export * from './display/notifications.js';
|
|
23
|
-
|
|
29
|
+
export * from './display/status.js';
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// ERROR HANDLING
|
|
32
|
+
// ============================================================================
|
|
33
|
+
export * from './error-handler.js';
|
|
24
34
|
// ============================================================================
|
|
25
35
|
// FILES & SYNC
|
|
26
36
|
// ============================================================================
|
|
27
37
|
export * from './files/file-operations.js';
|
|
28
38
|
export * from './files/sync-utils.js';
|
|
29
|
-
|
|
30
39
|
// ============================================================================
|
|
31
|
-
//
|
|
40
|
+
// FUNCTIONAL PROGRAMMING
|
|
32
41
|
// ============================================================================
|
|
33
|
-
export * from './
|
|
42
|
+
export * from './functional.js';
|
|
34
43
|
export * from './security/secret-utils.js';
|
|
35
|
-
|
|
36
44
|
// ============================================================================
|
|
37
|
-
//
|
|
45
|
+
// SECURITY
|
|
38
46
|
// ============================================================================
|
|
39
|
-
export * from './
|
|
40
|
-
|
|
47
|
+
export * from './security/security.js';
|
|
41
48
|
// ============================================================================
|
|
42
49
|
// VERSIONING
|
|
43
50
|
// ============================================================================
|
|
44
51
|
export * from './version.js';
|
|
45
|
-
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// AGENTS
|
|
48
|
-
// ============================================================================
|
|
49
|
-
export * from './agent-enhancer.js';
|
|
50
|
-
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// FUNCTIONAL PROGRAMMING
|
|
53
|
-
// ============================================================================
|
|
54
|
-
export * from './functional.js';
|
|
55
|
-
|
|
56
|
-
// ============================================================================
|
|
57
|
-
// SHARED UTILITIES
|
|
58
|
-
// ============================================================================
|
|
59
|
-
export * from '../shared/index.js';
|
|
@@ -27,10 +27,18 @@ export function detectPackageManagerFromUserAgent(): PackageManager | null {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (userAgent.includes('bun'))
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (userAgent.includes('
|
|
30
|
+
if (userAgent.includes('bun')) {
|
|
31
|
+
return 'bun';
|
|
32
|
+
}
|
|
33
|
+
if (userAgent.includes('pnpm')) {
|
|
34
|
+
return 'pnpm';
|
|
35
|
+
}
|
|
36
|
+
if (userAgent.includes('yarn')) {
|
|
37
|
+
return 'yarn';
|
|
38
|
+
}
|
|
39
|
+
if (userAgent.includes('npm')) {
|
|
40
|
+
return 'npm';
|
|
41
|
+
}
|
|
34
42
|
|
|
35
43
|
return null;
|
|
36
44
|
}
|
|
@@ -38,7 +46,9 @@ export function detectPackageManagerFromUserAgent(): PackageManager | null {
|
|
|
38
46
|
/**
|
|
39
47
|
* Detect package manager from lock files in directory
|
|
40
48
|
*/
|
|
41
|
-
export function detectPackageManagerFromLockFiles(
|
|
49
|
+
export function detectPackageManagerFromLockFiles(
|
|
50
|
+
dir: string = process.cwd()
|
|
51
|
+
): PackageManager | null {
|
|
42
52
|
const lockFiles: Record<PackageManager, string[]> = {
|
|
43
53
|
bun: ['bun.lockb', 'bun.lock'],
|
|
44
54
|
pnpm: ['pnpm-lock.yaml'],
|
|
@@ -209,11 +209,15 @@ export const commandSecurity = {
|
|
|
209
209
|
|
|
210
210
|
try {
|
|
211
211
|
return await execFileAsync(command, validatedArgs, secureOptions);
|
|
212
|
-
} catch (error:
|
|
212
|
+
} catch (error: unknown) {
|
|
213
213
|
// Sanitize error message to prevent information disclosure
|
|
214
|
-
const
|
|
215
|
-
sanitizedError
|
|
216
|
-
|
|
214
|
+
const err = error as NodeJS.ErrnoException & { signal?: string };
|
|
215
|
+
const sanitizedError = new Error(`Command execution failed: ${command}`) as Error & {
|
|
216
|
+
code?: string;
|
|
217
|
+
signal?: string;
|
|
218
|
+
};
|
|
219
|
+
sanitizedError.code = err.code;
|
|
220
|
+
sanitizedError.signal = err.signal;
|
|
217
221
|
throw sanitizedError;
|
|
218
222
|
}
|
|
219
223
|
},
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import inquirer from 'inquirer';
|
|
8
|
-
import { TargetInstaller } from '../services/target-installer.js';
|
|
8
|
+
import type { TargetInstaller } from '../services/target-installer.js';
|
|
9
9
|
import { handlePromptError } from './prompt-helpers.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -51,7 +51,10 @@ export function buildAvailableTargets(installedTargets: string[]): TargetChoice[
|
|
|
51
51
|
* @param context - Context where choice is displayed (affects status message)
|
|
52
52
|
* @returns Formatted string with target name and installation status
|
|
53
53
|
*/
|
|
54
|
-
export function formatTargetChoice(
|
|
54
|
+
export function formatTargetChoice(
|
|
55
|
+
target: TargetChoice,
|
|
56
|
+
context: 'execution' | 'settings'
|
|
57
|
+
): string {
|
|
55
58
|
const status = target.installed
|
|
56
59
|
? chalk.green(' ✓ installed')
|
|
57
60
|
: context === 'execution'
|
package/src/utils/version.ts
CHANGED
|
@@ -34,9 +34,11 @@ export function isVersionOutdated(current: string, latest: string): boolean {
|
|
|
34
34
|
/**
|
|
35
35
|
* Parse version string into components
|
|
36
36
|
*/
|
|
37
|
-
export function parseVersion(
|
|
37
|
+
export function parseVersion(
|
|
38
|
+
version: string
|
|
39
|
+
): { major: number; minor: number; patch: number } | null {
|
|
38
40
|
const parts = version.split('.').map(Number);
|
|
39
|
-
if (parts.length < 3 || parts.some(isNaN)) {
|
|
41
|
+
if (parts.length < 3 || parts.some(Number.isNaN)) {
|
|
40
42
|
return null;
|
|
41
43
|
}
|
|
42
44
|
return {
|