@sylphx/flow 2.15.1 → 2.15.3

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,18 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 2.15.3 (2025-12-17)
4
+
5
+ ### ⚡️ Performance
6
+
7
+ - **auto-upgrade:** simplify - no TTL, always background check ([5621a21](https://github.com/SylphxAI/flow/commit/5621a21ab2a3eeb54f3fecadd32c19269bd22312))
8
+ - **auto-upgrade:** cache target current version too ([229a400](https://github.com/SylphxAI/flow/commit/229a4002bde6da3540c014eb39e8a941e072a4db))
9
+
10
+ ## 2.15.2 (2025-12-17)
11
+
12
+ ### ⚡️ Performance
13
+
14
+ - **auto-upgrade:** non-blocking version check with cache ([0550e44](https://github.com/SylphxAI/flow/commit/0550e44ea9b471ae07b2ea13c196354a7a32a605))
15
+
3
16
  ## 2.15.1 (2025-12-17)
4
17
 
5
18
  ### Improvements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "2.15.1",
3
+ "version": "2.15.3",
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": {
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Auto-Upgrade Service
3
- * Automatically checks and upgrades Flow and target CLI before each execution
3
+ * Non-blocking version check with cache
4
+ * Checks in background, shows result on next run
4
5
  */
5
6
 
6
7
  import { exec } from 'node:child_process';
8
+ import { existsSync } from 'node:fs';
7
9
  import fs from 'node:fs/promises';
10
+ import os from 'node:os';
8
11
  import path from 'node:path';
9
12
  import { fileURLToPath } from 'node:url';
10
13
  import { promisify } from 'node:util';
@@ -16,6 +19,15 @@ import { TargetInstaller } from './target-installer.js';
16
19
  const __filename = fileURLToPath(import.meta.url);
17
20
  const __dirname = path.dirname(__filename);
18
21
 
22
+ // Version info file (stores last background check result)
23
+ const VERSION_FILE = path.join(os.homedir(), '.sylphx-flow', 'versions.json');
24
+
25
+ interface VersionInfo {
26
+ flowLatest?: string;
27
+ targetLatest?: Record<string, string>;
28
+ targetCurrent?: Record<string, string>;
29
+ }
30
+
19
31
  const execAsync = promisify(exec);
20
32
 
21
33
  export interface UpgradeStatus {
@@ -43,80 +55,152 @@ export class AutoUpgrade {
43
55
  }
44
56
 
45
57
  /**
46
- * Check for available upgrades for Flow and target CLI
47
- * @param targetId - Optional target CLI ID to check for upgrades
48
- * @returns Upgrade status indicating what needs upgrading
58
+ * Read version info from last background check
49
59
  */
50
- async checkForUpgrades(targetId?: string): Promise<UpgradeStatus> {
51
- const [flowVersion, targetVersion] = await Promise.all([
52
- this.options.skipFlow ? null : this.checkFlowVersion(),
53
- this.options.skipTarget || !targetId ? null : this.checkTargetVersion(targetId),
54
- ]);
60
+ private async readVersionInfo(): Promise<VersionInfo | null> {
61
+ try {
62
+ if (!existsSync(VERSION_FILE)) {
63
+ return null;
64
+ }
65
+ const data = await fs.readFile(VERSION_FILE, 'utf-8');
66
+ return JSON.parse(data);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
55
71
 
56
- return {
57
- flowNeedsUpgrade: !!flowVersion,
58
- targetNeedsUpgrade: !!targetVersion,
59
- flowVersion,
60
- targetVersion,
61
- };
72
+ /**
73
+ * Write version info
74
+ */
75
+ private async writeVersionInfo(info: VersionInfo): Promise<void> {
76
+ try {
77
+ const dir = path.dirname(VERSION_FILE);
78
+ await fs.mkdir(dir, { recursive: true });
79
+ await fs.writeFile(VERSION_FILE, JSON.stringify(info, null, 2));
80
+ } catch {
81
+ // Silent fail
82
+ }
62
83
  }
63
84
 
64
85
  /**
65
- * Check Flow version
86
+ * Get current Flow version from package.json (instant, local file)
66
87
  */
67
- private async checkFlowVersion(): Promise<{ current: string; latest: string } | null> {
88
+ private async getCurrentFlowVersion(): Promise<string> {
68
89
  try {
69
- // Get current version from package.json
70
90
  const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
71
91
  const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
72
- const currentVersion = packageJson.version;
73
-
74
- // Get latest version from npm
75
- const { stdout } = await execAsync('npm view @sylphx/flow version');
76
- const latestVersion = stdout.trim();
77
-
78
- if (currentVersion !== latestVersion) {
79
- return { current: currentVersion, latest: latestVersion };
80
- }
81
-
82
- return null;
92
+ return packageJson.version;
83
93
  } catch {
84
- return null;
94
+ return 'unknown';
85
95
  }
86
96
  }
87
97
 
88
98
  /**
89
- * Check target CLI version
99
+ * Check for available upgrades (instant, reads from last background check)
100
+ * Background check runs every time for fresh data next run
90
101
  */
91
- private async checkTargetVersion(
92
- targetId: string
93
- ): Promise<{ current: string; latest: string } | null> {
94
- const installation = this.targetInstaller.getInstallationInfo(targetId);
95
- if (!installation) {
96
- return null;
102
+ async checkForUpgrades(targetId?: string): Promise<UpgradeStatus> {
103
+ const info = await this.readVersionInfo();
104
+ const currentVersion = await this.getCurrentFlowVersion();
105
+
106
+ // Trigger background check for next run (non-blocking, every time)
107
+ this.checkInBackground(targetId);
108
+
109
+ // No previous check = no upgrade info yet
110
+ if (!info) {
111
+ return {
112
+ flowNeedsUpgrade: false,
113
+ targetNeedsUpgrade: false,
114
+ flowVersion: null,
115
+ targetVersion: null,
116
+ };
97
117
  }
98
118
 
99
- try {
100
- // Get current version
101
- const { stdout: currentOutput } = await execAsync(installation.checkCommand);
102
- const currentMatch = currentOutput.match(/v?(\d+\.\d+\.\d+)/);
103
- if (!currentMatch) {
104
- return null;
119
+ // Check if Flow needs upgrade
120
+ const flowVersion =
121
+ info.flowLatest && info.flowLatest !== currentVersion
122
+ ? { current: currentVersion, latest: info.flowLatest }
123
+ : null;
124
+
125
+ // Check if target needs upgrade
126
+ let targetVersion: { current: string; latest: string } | null = null;
127
+ if (targetId && info.targetLatest?.[targetId] && info.targetCurrent?.[targetId]) {
128
+ const current = info.targetCurrent[targetId];
129
+ const latest = info.targetLatest[targetId];
130
+ if (current !== latest) {
131
+ targetVersion = { current, latest };
105
132
  }
106
- const currentVersion = currentMatch[1];
133
+ }
107
134
 
108
- // Get latest version from npm
109
- const { stdout: latestOutput } = await execAsync(`npm view ${installation.package} version`);
110
- const latestVersion = latestOutput.trim();
135
+ return {
136
+ flowNeedsUpgrade: !!flowVersion,
137
+ targetNeedsUpgrade: !!targetVersion,
138
+ flowVersion,
139
+ targetVersion,
140
+ };
141
+ }
111
142
 
112
- if (currentVersion !== latestVersion) {
113
- return { current: currentVersion, latest: latestVersion };
114
- }
143
+ /**
144
+ * Check versions in background (non-blocking)
145
+ * Runs every time, updates info for next run
146
+ */
147
+ private checkInBackground(targetId?: string): void {
148
+ // Fire and forget - don't await
149
+ this.performBackgroundCheck(targetId).catch(() => {
150
+ // Silent fail
151
+ });
152
+ }
115
153
 
116
- return null;
154
+ /**
155
+ * Perform the actual version check (called in background)
156
+ */
157
+ private async performBackgroundCheck(targetId?: string): Promise<void> {
158
+ const oldInfo = await this.readVersionInfo();
159
+
160
+ const newInfo: VersionInfo = {
161
+ targetLatest: oldInfo?.targetLatest || {},
162
+ targetCurrent: oldInfo?.targetCurrent || {},
163
+ };
164
+
165
+ // Check Flow version from npm (with timeout)
166
+ try {
167
+ const { stdout } = await execAsync('npm view @sylphx/flow version', { timeout: 5000 });
168
+ newInfo.flowLatest = stdout.trim();
117
169
  } catch {
118
- return null;
170
+ // Keep old value if check fails
171
+ newInfo.flowLatest = oldInfo?.flowLatest;
172
+ }
173
+
174
+ // Check target version from npm and local (with timeout)
175
+ if (targetId) {
176
+ const installation = this.targetInstaller.getInstallationInfo(targetId);
177
+ if (installation) {
178
+ // Check latest version from npm
179
+ try {
180
+ const { stdout } = await execAsync(`npm view ${installation.package} version`, {
181
+ timeout: 5000,
182
+ });
183
+ newInfo.targetLatest = newInfo.targetLatest || {};
184
+ newInfo.targetLatest[targetId] = stdout.trim();
185
+ } catch {
186
+ // Keep old value
187
+ }
188
+
189
+ // Check current installed version (local command)
190
+ try {
191
+ const { stdout } = await execAsync(installation.checkCommand, { timeout: 5000 });
192
+ const match = stdout.match(/v?(\d+\.\d+\.\d+)/);
193
+ if (match) {
194
+ newInfo.targetCurrent = newInfo.targetCurrent || {};
195
+ newInfo.targetCurrent[targetId] = match[1];
196
+ }
197
+ } catch {
198
+ // Keep old value
199
+ }
200
+ }
119
201
  }
202
+
203
+ await this.writeVersionInfo(newInfo);
120
204
  }
121
205
 
122
206
  /**