@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.0",
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 status (only if something was upgraded)
245
+ // Show upgrade notice (minimal - only if upgraded)
246
246
  if (upgradeResult.flowUpgraded && upgradeResult.flowVersion) {
247
- console.log(chalk.cyan(` โ†‘ Flow ${upgradeResult.flowVersion.latest} (next run)`));
247
+ console.log(chalk.dim(`โ†‘ flow ${upgradeResult.flowVersion.latest}`));
248
248
  }
249
249
  if (upgradeResult.targetUpgraded && upgradeResult.targetVersion) {
250
- console.log(chalk.cyan(` โ†‘ ${targetName} ${upgradeResult.targetVersion.latest}`));
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
- const spinner = ora('Creating backup...').start();
98
+ // Get target config directory
99
+ const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
100
100
 
101
- try {
102
- // Get target config directory
103
- const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
104
-
105
- // Backup entire target directory if it exists
106
- if (existsSync(targetConfigDir)) {
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
- // Create manifest (store target ID as string for JSON serialization)
113
- const manifest: BackupManifest = {
114
- sessionId,
115
- timestamp,
116
- projectPath,
117
- target: targetId,
118
- backup: {
119
- agents: { user: [], flow: [] },
120
- commands: { user: [], flow: [] },
121
- singleFiles: {},
122
- },
123
- secrets: {
124
- mcpEnvExtracted: false,
125
- storedAt: '',
126
- },
127
- };
128
-
129
- await fs.writeFile(path.join(backupPath, 'manifest.json'), JSON.stringify(manifest, null, 2));
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
- spinner.succeed(`Backup created: ${sessionId}`);
153
-
154
- return {
155
- sessionId,
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
- const spinner = ora('Restoring backup...').start();
179
-
180
- try {
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
- // Resolve target to get config
189
- const target = this.resolveTarget(targetId);
172
+ const projectPath = manifest.projectPath;
173
+ const targetId = manifest.target;
190
174
 
191
- // Get target config directory
192
- const targetConfigDir = this.projectManager.getTargetConfigDir(projectPath, target);
175
+ // Resolve target to get config
176
+ const target = this.resolveTarget(targetId);
193
177
 
194
- // Remove current target directory
195
- if (existsSync(targetConfigDir)) {
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
- // Restore from backup using target config's configDir
200
- const backupTargetDir = path.join(backupPath, target.config.configDir);
181
+ // Remove current target directory
182
+ if (existsSync(targetConfigDir)) {
183
+ await fs.rm(targetConfigDir, { recursive: true, force: true });
184
+ }
201
185
 
202
- if (existsSync(backupTargetDir)) {
203
- await this.copyDirectory(backupTargetDir, targetConfigDir);
204
- }
186
+ // Restore from backup using target config's configDir
187
+ const backupTargetDir = path.join(backupPath, target.config.configDir);
205
188
 
206
- spinner.succeed('Backup restored');
207
- } catch (error) {
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(chalk.red('\nโœ— Uncaught Exception:'));
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(chalk.red('\nโœ— Unhandled Rejection:'));
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 (error) {
123
- console.error(chalk.red('โœ— Cleanup failed:'), error);
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
- console.log(chalk.green(' โœ“ Environment restored\n'));
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
- console.log(chalk.yellow(' โš ๏ธ Could not hide settings from git\n'));
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
- console.log(chalk.yellow(' โš ๏ธ Could not restore git tracking'));
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
- // Log warning if rule file not found, but continue with other rules
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
- console.warn(`Warning: Output style file not found: ${styleName}.md`);
91
+ // Silent - output style file not found, continue
93
92
  }
94
93
  }
95
94
  } else {