@sylphx/flow 2.15.0 โ 2.15.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
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @sylphx/flow
|
|
2
2
|
|
|
3
|
+
## 2.15.1 (2025-12-17)
|
|
4
|
+
|
|
5
|
+
### Improvements
|
|
6
|
+
|
|
7
|
+
- **cli**: Make CLI output truly minimal and modern
|
|
8
|
+
- Remove all verbose status messages (backup, restore, session recovery)
|
|
9
|
+
- Remove emoji icons (๐ง, โ) for cleaner output
|
|
10
|
+
- Remove "Attached" and "Running" status lines
|
|
11
|
+
- Silent git worktree operations
|
|
12
|
+
- Silent crash recovery on startup
|
|
13
|
+
- Only show header: `flow {version} โ {target}`
|
|
14
|
+
|
|
15
|
+
### โป๏ธ Refactoring
|
|
16
|
+
|
|
17
|
+
- **cli:** make output truly minimal and modern ([3315e41](https://github.com/SylphxAI/flow/commit/3315e41fa34839d7c866af1ebbe3b6f2d9e3ee71))
|
|
18
|
+
|
|
3
19
|
## 2.15.0 (2025-12-17)
|
|
4
20
|
|
|
5
21
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/flow",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.1",
|
|
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": {
|
|
@@ -242,12 +242,12 @@ export async function executeFlowV2(
|
|
|
242
242
|
const autoUpgrade = new AutoUpgrade(projectPath);
|
|
243
243
|
const upgradeResult = await autoUpgrade.runAutoUpgrade(selectedTargetId);
|
|
244
244
|
|
|
245
|
-
// Show upgrade
|
|
245
|
+
// Show upgrade notice (minimal - only if upgraded)
|
|
246
246
|
if (upgradeResult.flowUpgraded && upgradeResult.flowVersion) {
|
|
247
|
-
console.log(chalk.
|
|
247
|
+
console.log(chalk.dim(`โ flow ${upgradeResult.flowVersion.latest}`));
|
|
248
248
|
}
|
|
249
249
|
if (upgradeResult.targetUpgraded && upgradeResult.targetVersion) {
|
|
250
|
-
console.log(chalk.
|
|
250
|
+
console.log(chalk.dim(`โ ${targetName.toLowerCase()} ${upgradeResult.targetVersion.latest}`));
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// Create executor
|
|
@@ -263,15 +263,6 @@ export async function executeFlowV2(
|
|
|
263
263
|
merge: options.merge || false,
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
// Show attach summary (single line)
|
|
267
|
-
if (!attachResult.joined) {
|
|
268
|
-
console.log(
|
|
269
|
-
chalk.green(
|
|
270
|
-
` โ Attached ${attachResult.agents} agents, ${attachResult.commands} commands, ${attachResult.mcp} MCP`
|
|
271
|
-
)
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
266
|
const targetId = selectedTargetId;
|
|
276
267
|
|
|
277
268
|
// Provider selection (Claude Code only, silent unless prompting)
|
|
@@ -289,9 +280,6 @@ export async function executeFlowV2(
|
|
|
289
280
|
agent = enabledAgents.length > 0 ? enabledAgents[0] : 'coder';
|
|
290
281
|
}
|
|
291
282
|
|
|
292
|
-
// Show running agent
|
|
293
|
-
console.log(chalk.dim(`\n Running: ${agent}\n`));
|
|
294
|
-
|
|
295
283
|
// Load agent content
|
|
296
284
|
const enabledRules = await configService.getEnabledRules();
|
|
297
285
|
const enabledOutputStyles = await configService.getEnabledOutputStyles();
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import fs from 'node:fs/promises';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import ora from 'ora';
|
|
11
10
|
import type { Target } from '../types/target.types.js';
|
|
12
11
|
import type { ProjectManager } from './project-manager.js';
|
|
13
12
|
import { targetManager } from './target-manager.js';
|
|
@@ -96,72 +95,63 @@ export class BackupManager {
|
|
|
96
95
|
// Ensure backup directory exists
|
|
97
96
|
await fs.mkdir(backupPath, { recursive: true });
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
// Get target config directory
|
|
99
|
+
const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Use configDir from target config (e.g., '.claude', '.opencode')
|
|
108
|
-
const backupTargetDir = path.join(backupPath, target.config.configDir);
|
|
109
|
-
await this.copyDirectory(targetConfigDir, backupTargetDir);
|
|
110
|
-
}
|
|
101
|
+
// Backup entire target directory if it exists
|
|
102
|
+
if (existsSync(targetConfigDir)) {
|
|
103
|
+
// Use configDir from target config (e.g., '.claude', '.opencode')
|
|
104
|
+
const backupTargetDir = path.join(backupPath, target.config.configDir);
|
|
105
|
+
await this.copyDirectory(targetConfigDir, backupTargetDir);
|
|
106
|
+
}
|
|
111
107
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Create symlink to latest (with fallback for Windows)
|
|
132
|
-
const latestLink = paths.latestBackup;
|
|
133
|
-
if (existsSync(latestLink)) {
|
|
134
|
-
await fs.unlink(latestLink);
|
|
135
|
-
}
|
|
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
|
-
}
|
|
108
|
+
// Create manifest (store target ID as string for JSON serialization)
|
|
109
|
+
const manifest: BackupManifest = {
|
|
110
|
+
sessionId,
|
|
111
|
+
timestamp,
|
|
112
|
+
projectPath,
|
|
113
|
+
target: targetId,
|
|
114
|
+
backup: {
|
|
115
|
+
agents: { user: [], flow: [] },
|
|
116
|
+
commands: { user: [], flow: [] },
|
|
117
|
+
singleFiles: {},
|
|
118
|
+
},
|
|
119
|
+
secrets: {
|
|
120
|
+
mcpEnvExtracted: false,
|
|
121
|
+
storedAt: '',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
await fs.writeFile(path.join(backupPath, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
timestamp,
|
|
157
|
-
projectPath,
|
|
158
|
-
target: targetId,
|
|
159
|
-
backupPath,
|
|
160
|
-
};
|
|
161
|
-
} catch (error) {
|
|
162
|
-
spinner.fail('Backup failed');
|
|
163
|
-
throw error;
|
|
127
|
+
// Create symlink to latest (with fallback for Windows)
|
|
128
|
+
const latestLink = paths.latestBackup;
|
|
129
|
+
if (existsSync(latestLink)) {
|
|
130
|
+
await fs.unlink(latestLink);
|
|
164
131
|
}
|
|
132
|
+
try {
|
|
133
|
+
await fs.symlink(sessionId, latestLink);
|
|
134
|
+
} catch (symlinkError: unknown) {
|
|
135
|
+
// Windows without admin/Developer Mode can't create symlinks
|
|
136
|
+
// Fall back to writing session ID to a file
|
|
137
|
+
if (
|
|
138
|
+
symlinkError instanceof Error &&
|
|
139
|
+
'code' in symlinkError &&
|
|
140
|
+
symlinkError.code === 'EPERM'
|
|
141
|
+
) {
|
|
142
|
+
await fs.writeFile(latestLink, sessionId, 'utf-8');
|
|
143
|
+
} else {
|
|
144
|
+
throw symlinkError;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
sessionId,
|
|
150
|
+
timestamp,
|
|
151
|
+
projectPath,
|
|
152
|
+
target: targetId,
|
|
153
|
+
backupPath,
|
|
154
|
+
};
|
|
165
155
|
}
|
|
166
156
|
|
|
167
157
|
/**
|
|
@@ -175,38 +165,29 @@ export class BackupManager {
|
|
|
175
165
|
throw new Error(`Backup not found: ${sessionId}`);
|
|
176
166
|
}
|
|
177
167
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// Read manifest
|
|
182
|
-
const manifestPath = path.join(backupPath, 'manifest.json');
|
|
183
|
-
const manifest: BackupManifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
|
|
184
|
-
|
|
185
|
-
const projectPath = manifest.projectPath;
|
|
186
|
-
const targetId = manifest.target;
|
|
168
|
+
// Read manifest
|
|
169
|
+
const manifestPath = path.join(backupPath, 'manifest.json');
|
|
170
|
+
const manifest: BackupManifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
|
|
187
171
|
|
|
188
|
-
|
|
189
|
-
|
|
172
|
+
const projectPath = manifest.projectPath;
|
|
173
|
+
const targetId = manifest.target;
|
|
190
174
|
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
// Resolve target to get config
|
|
176
|
+
const target = this.resolveTarget(targetId);
|
|
193
177
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await fs.rm(targetConfigDir, { recursive: true, force: true });
|
|
197
|
-
}
|
|
178
|
+
// Get target config directory
|
|
179
|
+
const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
|
|
198
180
|
|
|
199
|
-
|
|
200
|
-
|
|
181
|
+
// Remove current target directory
|
|
182
|
+
if (existsSync(targetConfigDir)) {
|
|
183
|
+
await fs.rm(targetConfigDir, { recursive: true, force: true });
|
|
184
|
+
}
|
|
201
185
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
186
|
+
// Restore from backup using target config's configDir
|
|
187
|
+
const backupTargetDir = path.join(backupPath, target.config.configDir);
|
|
205
188
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
spinner.fail('Restore failed');
|
|
209
|
-
throw error;
|
|
189
|
+
if (existsSync(backupTargetDir)) {
|
|
190
|
+
await this.copyDirectory(backupTargetDir, targetConfigDir);
|
|
210
191
|
}
|
|
211
192
|
}
|
|
212
193
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Handles process signals and ensures backup restoration
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import chalk from 'chalk';
|
|
8
7
|
import type { BackupManager } from './backup-manager.js';
|
|
9
8
|
import type { ProjectManager } from './project-manager.js';
|
|
10
9
|
import type { SessionManager } from './session-manager.js';
|
|
@@ -43,30 +42,26 @@ export class CleanupHandler {
|
|
|
43
42
|
|
|
44
43
|
// SIGINT (Ctrl+C)
|
|
45
44
|
process.on('SIGINT', async () => {
|
|
46
|
-
console.log(chalk.yellow('\nโ ๏ธ Interrupted by user, cleaning up...'));
|
|
47
45
|
await this.onSignal('SIGINT');
|
|
48
46
|
process.exit(0);
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
// SIGTERM
|
|
52
50
|
process.on('SIGTERM', async () => {
|
|
53
|
-
console.log(chalk.yellow('\nโ ๏ธ Terminated, cleaning up...'));
|
|
54
51
|
await this.onSignal('SIGTERM');
|
|
55
52
|
process.exit(0);
|
|
56
53
|
});
|
|
57
54
|
|
|
58
55
|
// Uncaught exceptions
|
|
59
56
|
process.on('uncaughtException', async (error) => {
|
|
60
|
-
console.error(
|
|
61
|
-
console.error(error);
|
|
57
|
+
console.error('\nUncaught Exception:', error);
|
|
62
58
|
await this.onSignal('uncaughtException');
|
|
63
59
|
process.exit(1);
|
|
64
60
|
});
|
|
65
61
|
|
|
66
62
|
// Unhandled rejections
|
|
67
63
|
process.on('unhandledRejection', async (reason) => {
|
|
68
|
-
console.error(
|
|
69
|
-
console.error(reason);
|
|
64
|
+
console.error('\nUnhandled Rejection:', reason);
|
|
70
65
|
await this.onSignal('unhandledRejection');
|
|
71
66
|
process.exit(1);
|
|
72
67
|
});
|
|
@@ -97,6 +92,7 @@ export class CleanupHandler {
|
|
|
97
92
|
|
|
98
93
|
/**
|
|
99
94
|
* Signal-based cleanup (SIGINT, SIGTERM, etc.) with multi-session support
|
|
95
|
+
* Silent operation - no console output
|
|
100
96
|
*/
|
|
101
97
|
private async onSignal(_signal: string): Promise<void> {
|
|
102
98
|
if (!this.currentProjectHash) {
|
|
@@ -104,29 +100,22 @@ export class CleanupHandler {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
try {
|
|
107
|
-
console.log(chalk.cyan('๐งน Cleaning up...'));
|
|
108
|
-
|
|
109
103
|
const { shouldRestore, session } = await this.sessionManager.endSession(
|
|
110
104
|
this.currentProjectHash
|
|
111
105
|
);
|
|
112
106
|
|
|
113
107
|
if (shouldRestore && session) {
|
|
114
|
-
// Last session - restore environment
|
|
115
|
-
console.log(chalk.cyan(' Restoring environment...'));
|
|
116
108
|
await this.backupManager.restoreBackup(this.currentProjectHash, session.sessionId);
|
|
117
|
-
console.log(chalk.green('โ Environment restored'));
|
|
118
|
-
} else if (!shouldRestore && session) {
|
|
119
|
-
// Other sessions still running
|
|
120
|
-
console.log(chalk.yellow(` ${session.refCount} session(s) still running`));
|
|
121
109
|
}
|
|
122
|
-
} catch (
|
|
123
|
-
|
|
110
|
+
} catch (_error) {
|
|
111
|
+
// Silent fail
|
|
124
112
|
}
|
|
125
113
|
}
|
|
126
114
|
|
|
127
115
|
/**
|
|
128
116
|
* Recover on startup (for all projects)
|
|
129
117
|
* Checks for orphaned sessions from crashes
|
|
118
|
+
* Silent operation - no console output
|
|
130
119
|
*/
|
|
131
120
|
async recoverOnStartup(): Promise<void> {
|
|
132
121
|
const orphanedSessions = await this.sessionManager.detectOrphanedSessions();
|
|
@@ -135,21 +124,12 @@ export class CleanupHandler {
|
|
|
135
124
|
return;
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
console.log(chalk.cyan(`\n๐ง Recovering ${orphanedSessions.size} crashed session(s)...\n`));
|
|
139
|
-
|
|
140
127
|
for (const [projectHash, session] of orphanedSessions) {
|
|
141
|
-
console.log(chalk.dim(` Project: ${session.projectPath}`));
|
|
142
|
-
|
|
143
128
|
try {
|
|
144
|
-
// Restore backup
|
|
145
129
|
await this.backupManager.restoreBackup(projectHash, session.sessionId);
|
|
146
|
-
|
|
147
|
-
// Clean up session
|
|
148
130
|
await this.sessionManager.recoverSession(projectHash, session);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error(chalk.red(' โ Recovery failed:'), error);
|
|
131
|
+
} catch (_error) {
|
|
132
|
+
// Silent fail - don't interrupt startup
|
|
153
133
|
}
|
|
154
134
|
}
|
|
155
135
|
}
|
|
@@ -8,7 +8,6 @@ import { exec } from 'node:child_process';
|
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
|
-
import chalk from 'chalk';
|
|
12
11
|
|
|
13
12
|
const execAsync = promisify(exec);
|
|
14
13
|
|
|
@@ -93,14 +92,8 @@ export class GitStashManager {
|
|
|
93
92
|
// File might not exist or not tracked, skip it
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
|
-
|
|
97
|
-
if (this.skipWorktreeFiles.length > 0) {
|
|
98
|
-
console.log(
|
|
99
|
-
chalk.dim(` โ Hiding ${this.skipWorktreeFiles.length} settings file(s) from git\n`)
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
95
|
} catch (_error) {
|
|
103
|
-
|
|
96
|
+
// Silent fail
|
|
104
97
|
}
|
|
105
98
|
}
|
|
106
99
|
|
|
@@ -123,15 +116,9 @@ export class GitStashManager {
|
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
|
|
126
|
-
console.log(
|
|
127
|
-
chalk.dim(` โ Restored git tracking for ${this.skipWorktreeFiles.length} file(s)\n`)
|
|
128
|
-
);
|
|
129
119
|
this.skipWorktreeFiles = [];
|
|
130
|
-
} catch {
|
|
131
|
-
|
|
132
|
-
console.log(
|
|
133
|
-
chalk.yellow(' Run manually: git update-index --no-skip-worktree .claude/* .opencode/*\n')
|
|
134
|
-
);
|
|
120
|
+
} catch (_error) {
|
|
121
|
+
// Silent fail
|
|
135
122
|
}
|
|
136
123
|
}
|
|
137
124
|
|
|
@@ -59,8 +59,7 @@ async function loadRules(ruleNames?: string[]): Promise<string> {
|
|
|
59
59
|
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
60
60
|
sections.push(stripped);
|
|
61
61
|
} catch (_error) {
|
|
62
|
-
//
|
|
63
|
-
console.warn(`Warning: Rule file not found: ${ruleName}.md`);
|
|
62
|
+
// Silent - rule file not found, continue with other rules
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
|
|
@@ -89,7 +88,7 @@ async function loadOutputStyles(styleNames?: string[]): Promise<string> {
|
|
|
89
88
|
const stripped = await yamlUtils.stripFrontMatter(content);
|
|
90
89
|
sections.push(stripped);
|
|
91
90
|
} catch (_error) {
|
|
92
|
-
|
|
91
|
+
// Silent - output style file not found, continue
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
} else {
|