@sylphx/flow 2.0.0 → 2.1.1
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 +117 -0
- package/UPGRADE.md +25 -14
- package/package.json +10 -6
- package/src/commands/flow/execute-v2.ts +161 -67
- package/src/commands/settings-command.ts +22 -15
- package/src/config/ai-config.ts +2 -69
- package/src/config/targets.ts +0 -11
- package/src/core/attach-manager.ts +26 -22
- package/src/core/flow-executor.ts +53 -14
- package/src/core/installers/file-installer.ts +0 -57
- package/src/core/installers/mcp-installer.ts +0 -33
- package/src/index.ts +2 -2
- package/src/services/auto-upgrade.ts +248 -0
- package/src/services/global-config.ts +1 -1
- package/src/services/target-installer.ts +254 -0
- package/src/targets/claude-code.ts +5 -7
- package/src/targets/opencode.ts +6 -26
- package/src/utils/prompt-helpers.ts +48 -0
- package/src/utils/target-selection.ts +169 -0
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { existsSync } from 'node:fs';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
10
11
|
import chalk from 'chalk';
|
|
11
12
|
import { ProjectManager } from './project-manager.js';
|
|
12
13
|
import type { BackupManifest } from './backup-manager.js';
|
|
@@ -52,6 +53,18 @@ export class AttachManager {
|
|
|
52
53
|
this.configService = new GlobalConfigService();
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Calculate SHA256 hash of file content
|
|
58
|
+
*/
|
|
59
|
+
private async calculateFileHash(filePath: string): Promise<string> {
|
|
60
|
+
try {
|
|
61
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
62
|
+
return createHash('sha256').update(content).digest('hex');
|
|
63
|
+
} catch {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
/**
|
|
56
69
|
* Get target-specific directory names
|
|
57
70
|
*/
|
|
@@ -369,7 +382,7 @@ ${rules}
|
|
|
369
382
|
// Track in manifest
|
|
370
383
|
manifest.backup.config = {
|
|
371
384
|
path: configPath,
|
|
372
|
-
hash:
|
|
385
|
+
hash: await this.calculateFileHash(configPath),
|
|
373
386
|
mcpServersCount: Object.keys(config.mcp.servers).length,
|
|
374
387
|
};
|
|
375
388
|
}
|
|
@@ -407,7 +420,9 @@ ${rules}
|
|
|
407
420
|
}
|
|
408
421
|
|
|
409
422
|
/**
|
|
410
|
-
* Attach single files (
|
|
423
|
+
* Attach single files (output styles like silent.md)
|
|
424
|
+
* NOTE: These files are placed in the target config directory (.claude/ or .opencode/),
|
|
425
|
+
* NOT in the project root directory.
|
|
411
426
|
*/
|
|
412
427
|
private async attachSingleFiles(
|
|
413
428
|
projectPath: string,
|
|
@@ -415,33 +430,22 @@ ${rules}
|
|
|
415
430
|
result: AttachResult,
|
|
416
431
|
manifest: BackupManifest
|
|
417
432
|
): Promise<void> {
|
|
433
|
+
// Get target from manifest to determine correct directory
|
|
434
|
+
const target = manifest.target;
|
|
435
|
+
const targetDir = this.projectManager.getTargetConfigDir(projectPath, target);
|
|
436
|
+
|
|
418
437
|
for (const file of singleFiles) {
|
|
419
|
-
|
|
438
|
+
// Write to target config directory, not project root
|
|
439
|
+
const filePath = path.join(targetDir, file.path);
|
|
420
440
|
const existed = existsSync(filePath);
|
|
421
441
|
|
|
422
442
|
if (existed) {
|
|
423
|
-
// User has file,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// Check if already appended
|
|
427
|
-
if (userContent.includes('<!-- Sylphx Flow Enhancement -->')) {
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const merged = `${userContent}
|
|
432
|
-
|
|
433
|
-
---
|
|
434
|
-
|
|
435
|
-
**Sylphx Flow Enhancement:**
|
|
436
|
-
|
|
437
|
-
${file.content}
|
|
438
|
-
`;
|
|
439
|
-
|
|
440
|
-
await fs.writeFile(filePath, merged);
|
|
443
|
+
// User has file, overwrite with Flow content (backed up already)
|
|
444
|
+
await fs.writeFile(filePath, file.content);
|
|
441
445
|
|
|
442
446
|
manifest.backup.singleFiles[file.path] = {
|
|
443
447
|
existed: true,
|
|
444
|
-
originalSize:
|
|
448
|
+
originalSize: (await fs.readFile(filePath, 'utf-8')).length,
|
|
445
449
|
flowContentAdded: true,
|
|
446
450
|
};
|
|
447
451
|
} else {
|
|
@@ -169,6 +169,7 @@ export class FlowExecutor {
|
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
171
|
* Clear user settings in replace mode
|
|
172
|
+
* This ensures a clean slate for Flow's configuration
|
|
172
173
|
*/
|
|
173
174
|
private async clearUserSettings(
|
|
174
175
|
projectPath: string,
|
|
@@ -188,7 +189,7 @@ export class FlowExecutor {
|
|
|
188
189
|
? { agents: 'agents', commands: 'commands' }
|
|
189
190
|
: { agents: 'agent', commands: 'command' };
|
|
190
191
|
|
|
191
|
-
// Clear agents directory
|
|
192
|
+
// 1. Clear agents directory (including AGENTS.md rules file)
|
|
192
193
|
const agentsDir = path.join(targetDir, dirs.agents);
|
|
193
194
|
if (existsSync(agentsDir)) {
|
|
194
195
|
const files = await fs.readdir(agentsDir);
|
|
@@ -197,7 +198,7 @@ export class FlowExecutor {
|
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
// Clear commands directory
|
|
201
|
+
// 2. Clear commands directory
|
|
201
202
|
const commandsDir = path.join(targetDir, dirs.commands);
|
|
202
203
|
if (existsSync(commandsDir)) {
|
|
203
204
|
const files = await fs.readdir(commandsDir);
|
|
@@ -206,31 +207,69 @@ export class FlowExecutor {
|
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
// Clear
|
|
210
|
+
// 3. Clear hooks directory
|
|
211
|
+
const hooksDir = path.join(targetDir, 'hooks');
|
|
212
|
+
if (existsSync(hooksDir)) {
|
|
213
|
+
const files = await fs.readdir(hooksDir);
|
|
214
|
+
for (const file of files) {
|
|
215
|
+
await fs.unlink(path.join(hooksDir, file));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 4. Clear MCP configuration completely
|
|
210
220
|
const configPath = target === 'claude-code'
|
|
211
221
|
? path.join(targetDir, 'settings.json')
|
|
212
222
|
: path.join(targetDir, '.mcp.json');
|
|
213
223
|
|
|
214
224
|
if (existsSync(configPath)) {
|
|
215
|
-
// Clear MCP servers section only, keep other settings
|
|
216
|
-
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
217
225
|
if (target === 'claude-code') {
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
// For Claude Code, clear entire MCP section to remove all user config
|
|
227
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
228
|
+
if (config.mcp) {
|
|
229
|
+
// Remove entire MCP configuration, not just servers
|
|
230
|
+
delete config.mcp;
|
|
220
231
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
221
232
|
}
|
|
222
233
|
} else {
|
|
223
|
-
// For
|
|
234
|
+
// For OpenCode, clear the entire .mcp.json file
|
|
224
235
|
await fs.writeFile(configPath, JSON.stringify({ servers: {} }, null, 2));
|
|
225
236
|
}
|
|
226
237
|
}
|
|
227
238
|
|
|
228
|
-
// Clear
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
await fs.unlink(
|
|
239
|
+
// 5. Clear AGENTS.md rules file (for OpenCode)
|
|
240
|
+
// Claude Code AGENTS.md is already handled in agents directory
|
|
241
|
+
if (target === 'opencode') {
|
|
242
|
+
const rulesPath = path.join(targetDir, 'AGENTS.md');
|
|
243
|
+
if (existsSync(rulesPath)) {
|
|
244
|
+
await fs.unlink(rulesPath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 6. Clear single files (output styles like silent.md)
|
|
249
|
+
// These are now in the target directory, not project root
|
|
250
|
+
const singleFiles = ['silent.md']; // Add other known single files here
|
|
251
|
+
for (const fileName of singleFiles) {
|
|
252
|
+
const filePath = path.join(targetDir, fileName);
|
|
253
|
+
if (existsSync(filePath)) {
|
|
254
|
+
await fs.unlink(filePath);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 7. Clean up any Flow-created files in project root (legacy bug cleanup)
|
|
259
|
+
// This handles files that were incorrectly created in project root
|
|
260
|
+
const legacySingleFiles = ['silent.md'];
|
|
261
|
+
for (const fileName of legacySingleFiles) {
|
|
262
|
+
const filePath = path.join(projectPath, fileName);
|
|
263
|
+
if (existsSync(filePath)) {
|
|
264
|
+
// Only delete if it looks like a Flow-created file
|
|
265
|
+
try {
|
|
266
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
267
|
+
if (content.includes('Sylphx Flow') || content.includes('Silent Execution Style')) {
|
|
268
|
+
await fs.unlink(filePath);
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore errors - file might not be readable
|
|
272
|
+
}
|
|
234
273
|
}
|
|
235
274
|
}
|
|
236
275
|
}
|
|
@@ -257,60 +257,3 @@ export async function installFile(
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
/**
|
|
261
|
-
* File installer interface for backward compatibility
|
|
262
|
-
*/
|
|
263
|
-
export interface FileInstaller {
|
|
264
|
-
installToDirectory(
|
|
265
|
-
sourceDir: string,
|
|
266
|
-
targetDir: string,
|
|
267
|
-
transform: FileTransformFn,
|
|
268
|
-
options?: InstallOptions
|
|
269
|
-
): Promise<ProcessResult[]>;
|
|
270
|
-
appendToFile(
|
|
271
|
-
sourceDir: string,
|
|
272
|
-
targetFile: string,
|
|
273
|
-
transform: FileTransformFn,
|
|
274
|
-
options?: InstallOptions
|
|
275
|
-
): Promise<void>;
|
|
276
|
-
installFile(
|
|
277
|
-
sourceFile: string,
|
|
278
|
-
targetFile: string,
|
|
279
|
-
transform: FileTransformFn,
|
|
280
|
-
options?: InstallOptions
|
|
281
|
-
): Promise<void>;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Composable file installer
|
|
286
|
-
* Handles copying files from source to destination with optional transformation
|
|
287
|
-
* @deprecated Use standalone functions (installToDirectory, appendToFile, installFile) for new code
|
|
288
|
-
*/
|
|
289
|
-
export class FileInstaller {
|
|
290
|
-
async installToDirectory(
|
|
291
|
-
sourceDir: string,
|
|
292
|
-
targetDir: string,
|
|
293
|
-
transform: FileTransformFn,
|
|
294
|
-
options: InstallOptions = {}
|
|
295
|
-
): Promise<ProcessResult[]> {
|
|
296
|
-
return installToDirectory(sourceDir, targetDir, transform, options);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async appendToFile(
|
|
300
|
-
sourceDir: string,
|
|
301
|
-
targetFile: string,
|
|
302
|
-
transform: FileTransformFn,
|
|
303
|
-
options: InstallOptions = {}
|
|
304
|
-
): Promise<void> {
|
|
305
|
-
return appendToFile(sourceDir, targetFile, transform, options);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async installFile(
|
|
309
|
-
sourceFile: string,
|
|
310
|
-
targetFile: string,
|
|
311
|
-
transform: FileTransformFn,
|
|
312
|
-
options: InstallOptions = {}
|
|
313
|
-
): Promise<void> {
|
|
314
|
-
return installFile(sourceFile, targetFile, transform, options);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
@@ -178,36 +178,3 @@ export function createMCPInstaller(target: Target): MCPInstaller {
|
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/**
|
|
182
|
-
* @deprecated Use createMCPInstaller() for new code
|
|
183
|
-
*/
|
|
184
|
-
export class MCPInstaller {
|
|
185
|
-
private installer: ReturnType<typeof createMCPInstaller>;
|
|
186
|
-
|
|
187
|
-
constructor(target: Target) {
|
|
188
|
-
this.installer = createMCPInstaller(target);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async selectServers(options: { quiet?: boolean } = {}): Promise<MCPServerID[]> {
|
|
192
|
-
return this.installer.selectServers(options);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async configureServers(
|
|
196
|
-
selectedServers: MCPServerID[],
|
|
197
|
-
options: { quiet?: boolean } = {}
|
|
198
|
-
): Promise<Record<MCPServerID, Record<string, string>>> {
|
|
199
|
-
return this.installer.configureServers(selectedServers, options);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async installServers(
|
|
203
|
-
selectedServers: MCPServerID[],
|
|
204
|
-
serverConfigsMap: Record<MCPServerID, Record<string, string>>,
|
|
205
|
-
options: { quiet?: boolean } = {}
|
|
206
|
-
): Promise<void> {
|
|
207
|
-
return this.installer.installServers(selectedServers, serverConfigsMap, options);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async setupMCP(options: { quiet?: boolean; dryRun?: boolean } = {}): Promise<MCPInstallResult> {
|
|
211
|
-
return this.installer.setupMCP(options);
|
|
212
|
-
}
|
|
213
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Upgrade Service
|
|
3
|
+
* Automatically checks and upgrades Flow and target CLI before each execution
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import { detectPackageManager, getUpgradeCommand } from '../utils/package-manager-detector.js';
|
|
13
|
+
import { TargetInstaller } from './target-installer.js';
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
export interface UpgradeStatus {
|
|
18
|
+
flowNeedsUpgrade: boolean;
|
|
19
|
+
targetNeedsUpgrade: boolean;
|
|
20
|
+
flowVersion: { current: string; latest: string } | null;
|
|
21
|
+
targetVersion: { current: string; latest: string } | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AutoUpgradeOptions {
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
skipFlow?: boolean;
|
|
27
|
+
skipTarget?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AutoUpgrade {
|
|
31
|
+
private projectPath: string;
|
|
32
|
+
private options: AutoUpgradeOptions;
|
|
33
|
+
private targetInstaller: TargetInstaller;
|
|
34
|
+
|
|
35
|
+
constructor(projectPath: string = process.cwd(), options: AutoUpgradeOptions = {}) {
|
|
36
|
+
this.projectPath = projectPath;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.targetInstaller = new TargetInstaller(projectPath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check for available upgrades for Flow and target CLI
|
|
43
|
+
* @param targetId - Optional target CLI ID to check for upgrades
|
|
44
|
+
* @returns Upgrade status indicating what needs upgrading
|
|
45
|
+
*/
|
|
46
|
+
async checkForUpgrades(targetId?: string): Promise<UpgradeStatus> {
|
|
47
|
+
const [flowVersion, targetVersion] = await Promise.all([
|
|
48
|
+
this.options.skipFlow ? null : this.checkFlowVersion(),
|
|
49
|
+
this.options.skipTarget || !targetId ? null : this.checkTargetVersion(targetId),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
flowNeedsUpgrade: !!flowVersion,
|
|
54
|
+
targetNeedsUpgrade: !!targetVersion,
|
|
55
|
+
flowVersion,
|
|
56
|
+
targetVersion,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check Flow version
|
|
62
|
+
*/
|
|
63
|
+
private async checkFlowVersion(): Promise<{ current: string; latest: string } | null> {
|
|
64
|
+
try {
|
|
65
|
+
// Get current version from package.json
|
|
66
|
+
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
67
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
68
|
+
const currentVersion = packageJson.version;
|
|
69
|
+
|
|
70
|
+
// Get latest version from npm
|
|
71
|
+
const { stdout } = await execAsync('npm view @sylphx/flow version');
|
|
72
|
+
const latestVersion = stdout.trim();
|
|
73
|
+
|
|
74
|
+
if (currentVersion !== latestVersion) {
|
|
75
|
+
return { current: currentVersion, latest: latestVersion };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check target CLI version
|
|
86
|
+
*/
|
|
87
|
+
private async checkTargetVersion(
|
|
88
|
+
targetId: string
|
|
89
|
+
): Promise<{ current: string; latest: string } | null> {
|
|
90
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
91
|
+
if (!installation) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Get current version
|
|
97
|
+
const { stdout: currentOutput } = await execAsync(installation.checkCommand);
|
|
98
|
+
const currentMatch = currentOutput.match(/v?(\d+\.\d+\.\d+)/);
|
|
99
|
+
if (!currentMatch) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const currentVersion = currentMatch[1];
|
|
103
|
+
|
|
104
|
+
// Get latest version from npm
|
|
105
|
+
const { stdout: latestOutput } = await execAsync(`npm view ${installation.package} version`);
|
|
106
|
+
const latestVersion = latestOutput.trim();
|
|
107
|
+
|
|
108
|
+
if (currentVersion !== latestVersion) {
|
|
109
|
+
return { current: currentVersion, latest: latestVersion };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Upgrade Flow to latest version using detected package manager
|
|
120
|
+
* @returns True if upgrade successful, false otherwise
|
|
121
|
+
*/
|
|
122
|
+
async upgradeFlow(): Promise<boolean> {
|
|
123
|
+
const packageManager = detectPackageManager(this.projectPath);
|
|
124
|
+
const spinner = ora('Upgrading Flow...').start();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const upgradeCmd = getUpgradeCommand('@sylphx/flow', packageManager);
|
|
128
|
+
await execAsync(upgradeCmd);
|
|
129
|
+
|
|
130
|
+
spinner.succeed(chalk.green('✓ Flow upgraded to latest version'));
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
spinner.fail(chalk.red('✗ Flow upgrade failed'));
|
|
134
|
+
|
|
135
|
+
if (this.options.verbose) {
|
|
136
|
+
console.error(error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Upgrade target CLI to latest version
|
|
145
|
+
* Tries built-in upgrade command first, falls back to package manager
|
|
146
|
+
* @param targetId - Target CLI ID to upgrade
|
|
147
|
+
* @returns True if upgrade successful, false otherwise
|
|
148
|
+
*/
|
|
149
|
+
async upgradeTarget(targetId: string): Promise<boolean> {
|
|
150
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
151
|
+
if (!installation) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const packageManager = detectPackageManager(this.projectPath);
|
|
156
|
+
const spinner = ora(`Upgrading ${installation.name}...`).start();
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// For Claude Code, use built-in update command if available
|
|
160
|
+
if (targetId === 'claude-code') {
|
|
161
|
+
try {
|
|
162
|
+
await execAsync('claude update');
|
|
163
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
164
|
+
return true;
|
|
165
|
+
} catch {
|
|
166
|
+
// Fall back to npm upgrade
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// For OpenCode, use built-in upgrade command if available
|
|
171
|
+
if (targetId === 'opencode') {
|
|
172
|
+
try {
|
|
173
|
+
await execAsync('opencode upgrade');
|
|
174
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
// Fall back to npm upgrade
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Fall back to npm/bun/pnpm/yarn upgrade
|
|
182
|
+
const upgradeCmd = getUpgradeCommand(installation.package, packageManager);
|
|
183
|
+
await execAsync(upgradeCmd);
|
|
184
|
+
|
|
185
|
+
spinner.succeed(chalk.green(`✓ ${installation.name} upgraded`));
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
spinner.fail(chalk.red(`✗ ${installation.name} upgrade failed`));
|
|
189
|
+
|
|
190
|
+
if (this.options.verbose) {
|
|
191
|
+
console.error(error);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Run auto-upgrade check and upgrade if needed
|
|
200
|
+
* Shows upgrade status and performs upgrades automatically
|
|
201
|
+
* @param targetId - Optional target CLI ID to check and upgrade
|
|
202
|
+
*/
|
|
203
|
+
async runAutoUpgrade(targetId?: string): Promise<void> {
|
|
204
|
+
console.log(chalk.cyan('🔄 Checking for updates...\n'));
|
|
205
|
+
|
|
206
|
+
const status = await this.checkForUpgrades(targetId);
|
|
207
|
+
|
|
208
|
+
// Show upgrade status
|
|
209
|
+
if (status.flowNeedsUpgrade && status.flowVersion) {
|
|
210
|
+
console.log(
|
|
211
|
+
chalk.yellow(
|
|
212
|
+
`📦 Flow update available: ${status.flowVersion.current} → ${status.flowVersion.latest}`
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
} else if (!this.options.skipFlow) {
|
|
216
|
+
console.log(chalk.green('✓ Flow is up to date'));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (status.targetNeedsUpgrade && status.targetVersion && targetId) {
|
|
220
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
221
|
+
console.log(
|
|
222
|
+
chalk.yellow(
|
|
223
|
+
`📦 ${installation?.name} update available: ${status.targetVersion.current} → ${status.targetVersion.latest}`
|
|
224
|
+
)
|
|
225
|
+
);
|
|
226
|
+
} else if (!this.options.skipTarget && targetId) {
|
|
227
|
+
const installation = this.targetInstaller.getInstallationInfo(targetId);
|
|
228
|
+
console.log(chalk.green(`✓ ${installation?.name} is up to date`));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Perform upgrades if needed
|
|
232
|
+
if (status.flowNeedsUpgrade || status.targetNeedsUpgrade) {
|
|
233
|
+
console.log(chalk.cyan('\n📦 Installing updates...\n'));
|
|
234
|
+
|
|
235
|
+
if (status.flowNeedsUpgrade) {
|
|
236
|
+
await this.upgradeFlow();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (status.targetNeedsUpgrade && targetId) {
|
|
240
|
+
await this.upgradeTarget(targetId);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(chalk.green('\n✓ All tools upgraded\n'));
|
|
244
|
+
} else {
|
|
245
|
+
console.log();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -10,7 +10,7 @@ import { existsSync } from 'node:fs';
|
|
|
10
10
|
|
|
11
11
|
export interface GlobalSettings {
|
|
12
12
|
version: string;
|
|
13
|
-
defaultTarget?: 'claude-code' | 'opencode';
|
|
13
|
+
defaultTarget?: 'claude-code' | 'opencode' | 'cursor' | 'ask-every-time';
|
|
14
14
|
defaultAgent?: string; // Default agent to use (e.g., 'coder', 'writer', 'reviewer', 'orchestrator')
|
|
15
15
|
firstRun: boolean;
|
|
16
16
|
lastUpdated: string;
|