@sylphx/flow 2.4.0 → 2.4.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 2.4.2 (2025-12-10)
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - **mcp:** wrap npx/npm commands with cmd /c on Windows ([e37b126](https://github.com/SylphxAI/flow/commit/e37b1262c4002341df32de92f84ccdc1bf359d4b))
8
+
9
+ ## 2.4.1 (2025-12-10)
10
+
11
+ ### 🐛 Bug Fixes
12
+
13
+ - **backup:** handle Windows symlink permission error ([8f1337f](https://github.com/SylphxAI/flow/commit/8f1337f6d1381ecceebc8751460a6cf710a62978))
14
+
3
15
  ## 2.4.0 (2025-12-09)
4
16
 
5
17
  ### ✨ Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "One CLI to rule them all. Unified orchestration layer for Claude Code, OpenCode, Cursor and all AI development tools. Auto-detection, auto-installation, auto-upgrade.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -128,12 +128,26 @@ export class BackupManager {
128
128
 
129
129
  await fs.writeFile(path.join(backupPath, 'manifest.json'), JSON.stringify(manifest, null, 2));
130
130
 
131
- // Create symlink to latest
131
+ // Create symlink to latest (with fallback for Windows)
132
132
  const latestLink = paths.latestBackup;
133
133
  if (existsSync(latestLink)) {
134
134
  await fs.unlink(latestLink);
135
135
  }
136
- await fs.symlink(sessionId, latestLink);
136
+ try {
137
+ await fs.symlink(sessionId, latestLink);
138
+ } catch (symlinkError: unknown) {
139
+ // Windows without admin/Developer Mode can't create symlinks
140
+ // Fall back to writing session ID to a file
141
+ if (
142
+ symlinkError instanceof Error &&
143
+ 'code' in symlinkError &&
144
+ symlinkError.code === 'EPERM'
145
+ ) {
146
+ await fs.writeFile(latestLink, sessionId, 'utf-8');
147
+ } else {
148
+ throw symlinkError;
149
+ }
150
+ }
137
151
 
138
152
  spinner.succeed(`Backup created: ${sessionId}`);
139
153
 
@@ -5,6 +5,17 @@
5
5
 
6
6
  import type { MCPServerConfigUnion } from '../../types.js';
7
7
 
8
+ // ============================================================================
9
+ // Platform Detection
10
+ // ============================================================================
11
+
12
+ const isWindows = process.platform === 'win32';
13
+
14
+ /**
15
+ * Commands that require cmd /c wrapper on Windows
16
+ */
17
+ const WINDOWS_WRAPPED_COMMANDS = ['npx', 'npm', 'pnpm', 'yarn', 'bun', 'node'];
18
+
8
19
  // ============================================================================
9
20
  // Types
10
21
  // ============================================================================
@@ -36,6 +47,38 @@ export interface RemoteConfig {
36
47
  headers?: Record<string, string>;
37
48
  }
38
49
 
50
+ // ============================================================================
51
+ // Windows Command Wrapper
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Wrap command for Windows if needed
56
+ * On Windows, npx/npm/etc. need to be executed via cmd /c
57
+ */
58
+ const wrapCommandForWindows = (
59
+ command: string,
60
+ args?: string[]
61
+ ): { command: string; args: string[] } => {
62
+ if (!isWindows) {
63
+ return { command, args: args || [] };
64
+ }
65
+
66
+ // Check if command needs wrapping
67
+ const needsWrapper = WINDOWS_WRAPPED_COMMANDS.some(
68
+ (cmd) => command === cmd || command.endsWith(`\\${cmd}`) || command.endsWith(`/${cmd}`)
69
+ );
70
+
71
+ if (needsWrapper) {
72
+ // Wrap with cmd /c
73
+ return {
74
+ command: 'cmd',
75
+ args: ['/c', command, ...(args || [])],
76
+ };
77
+ }
78
+
79
+ return { command, args: args || [] };
80
+ };
81
+
39
82
  // ============================================================================
40
83
  // Pure Transform Functions
41
84
  // ============================================================================
@@ -51,13 +94,16 @@ export const stdioToLocal = (config: StdioConfig): LocalConfig => ({
51
94
 
52
95
  /**
53
96
  * Convert local format to stdio format (OpenCode → Claude Code)
97
+ * On Windows, wraps npx/npm/etc. with cmd /c
54
98
  */
55
99
  export const localToStdio = (config: LocalConfig): StdioConfig => {
56
100
  const [command, ...args] = config.command;
101
+ const wrapped = wrapCommandForWindows(command, args);
102
+
57
103
  return {
58
104
  type: 'stdio',
59
- command,
60
- ...(args.length > 0 && { args }),
105
+ command: wrapped.command,
106
+ ...(wrapped.args.length > 0 && { args: wrapped.args }),
61
107
  ...(config.environment && { env: config.environment }),
62
108
  };
63
109
  };
@@ -82,13 +128,18 @@ export const remoteToHttp = (config: RemoteConfig): HttpConfig => ({
82
128
 
83
129
  /**
84
130
  * Normalize stdio config (ensure consistent structure)
131
+ * On Windows, wraps npx/npm/etc. with cmd /c
85
132
  */
86
- export const normalizeStdio = (config: StdioConfig): StdioConfig => ({
87
- type: 'stdio',
88
- command: config.command,
89
- ...(config.args && config.args.length > 0 && { args: config.args }),
90
- ...(config.env && { env: config.env }),
91
- });
133
+ export const normalizeStdio = (config: StdioConfig): StdioConfig => {
134
+ const wrapped = wrapCommandForWindows(config.command, config.args);
135
+
136
+ return {
137
+ type: 'stdio',
138
+ command: wrapped.command,
139
+ ...(wrapped.args.length > 0 && { args: wrapped.args }),
140
+ ...(config.env && { env: config.env }),
141
+ };
142
+ };
92
143
 
93
144
  /**
94
145
  * Normalize http config (ensure consistent structure)