@mfjjs/ruflo-setup 0.2.7 → 0.2.8

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
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.2.8](///compare/v0.2.7...v0.2.8) (2026-03-31)
6
+
7
+
8
+ ### Features
9
+
10
+ * **docs:** add related documentation b67343a
11
+ * **docs:** add RuFlo enabled session notes and usage checklist bd9d96a
12
+ * enhance setup process with .gitignore updates and cross-platform compatibility for .mcp.json 4b44558
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * update toPlatformMcpConfig to use npx instead of pnpm for cross-platform compatibility e7cf99f
18
+
5
19
  ### [0.2.7](///compare/v0.2.6...v0.2.7) (2026-03-20)
6
20
 
7
21
 
package/README.md CHANGED
@@ -74,8 +74,9 @@ ruflo-setup
74
74
  That's it for most users. The command will:
75
75
  1. Check for a newer version of itself and offer to update before proceeding
76
76
  2. Install `ruflo@latest` globally and run `ruflo init --full` to scaffold your project
77
- 3. Write a platform-aware `.mcp.json`
78
- 4. Install a global Claude Code `SessionStart` hook
77
+ 3. Write a platform-aware `.mcp.json` (MCP servers use `npx` for cross-platform compatibility)
78
+ 4. Add `.mcp.json` and `.claude/settings.json` to the project's `.gitignore`
79
+ 5. Install a global Claude Code `SessionStart` hook
79
80
 
80
81
  Additional options:
81
82
 
@@ -152,7 +153,7 @@ ruflo-setup cleanup --dry-run
152
153
  - `package.json`: npm metadata, scripts, and `bin` mapping
153
154
  - `bin/ruflo-setup.js`: executable entry file the shell runs
154
155
  - `src/cli.js`: command router and argument handling
155
- - `src/setup.js`: setup workflow (`init`, `.mcp.json`, template copy)
156
+ - `src/setup.js`: setup workflow (`init`, `.mcp.json`, `.gitignore`, template copy)
156
157
  - `src/status.js`: layer-by-layer feature status checker (Layers 0–8)
157
158
  - `src/hooks.js`: global `check-ruflo` hook install/status
158
159
  - `src/utils.js`: reusable filesystem and argument helpers
@@ -185,7 +186,8 @@ Flow:
185
186
  5. `src/setup.js` runs setup steps:
186
187
  - checks for a newer version of itself and prompts to update
187
188
  - optional `pnpm add -g ruflo@latest` then `ruflo init --full`
188
- - writes platform-aware `.mcp.json`
189
+ - writes platform-aware `.mcp.json` (MCP servers invoked via `npx` for cross-platform compatibility)
190
+ - adds `.mcp.json` and `.claude/settings.json` to the project's `.gitignore`
189
191
  - copies `templates/CLAUDE.md`
190
192
  - installs global SessionStart hook (unless skipped)
191
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mfjjs/ruflo-setup",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Cross-platform setup CLI for Ruflo + Claude Flow projects",
5
5
  "type": "module",
6
6
  "bin": {
package/src/setup.js CHANGED
@@ -1,380 +1,414 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import os from 'node:os';
4
- import { spawnSync } from 'node:child_process';
5
- import { pathExists, copyFileSync, confirm, toPlatformMcpConfig, writeJson } from './utils.js';
6
- import { installGlobalCheckRufloHook } from './hooks.js';
7
-
8
- function logLine(message) {
9
- process.stdout.write(`${message}\n`);
10
- }
11
-
12
- function getPnpmInstallSuggestions(platform) {
13
- if (platform === 'win32') {
14
- return [
15
- 'winget install -e --id pnpm.pnpm',
16
- 'corepack enable && corepack prepare pnpm@latest --activate',
17
- 'npm install -g pnpm'
18
- ];
19
- }
20
-
21
- if (platform === 'darwin') {
22
- return [
23
- 'brew install pnpm',
24
- 'corepack enable && corepack prepare pnpm@latest --activate',
25
- 'npm install -g pnpm'
26
- ];
27
- }
28
-
29
- return [
30
- 'curl -fsSL https://get.pnpm.io/install.sh | sh -',
31
- 'corepack enable && corepack prepare pnpm@latest --activate',
32
- 'npm install -g pnpm'
33
- ];
34
- }
35
-
36
- const MIN_PNPM_VERSION = '10.32.1';
37
-
38
- function parseSemver(str) {
39
- const [major = 0, minor = 0, patch = 0] = str.trim().split('.').map(Number);
40
- return { major, minor, patch };
41
- }
42
-
43
- function semverGte(a, b) {
44
- if (a.major !== b.major) return a.major > b.major;
45
- if (a.minor !== b.minor) return a.minor > b.minor;
46
- return a.patch >= b.patch;
47
- }
48
-
49
- function ensurePnpmAvailable() {
50
- const check = spawnSync('pnpm', ['--version'], {
51
- stdio: ['ignore', 'pipe', 'ignore'],
52
- shell: process.platform === 'win32'
53
- });
54
-
55
- if (check.status !== 0 || check.error) {
56
- const platformLabel = process.platform === 'win32'
57
- ? 'Windows'
58
- : process.platform === 'darwin'
59
- ? 'macOS'
60
- : 'Linux';
61
- const suggestions = getPnpmInstallSuggestions(process.platform)
62
- .map((command) => ` - ${command}`)
63
- .join('\n');
64
-
65
- throw new Error(
66
- `pnpm is required but was not found in PATH.\n` +
67
- `Install pnpm, then re-run ruflo-setup.\n` +
68
- `Quick install options for ${platformLabel}:\n${suggestions}`
69
- );
70
- }
71
-
72
- const version = (check.stdout || '').toString().trim();
73
- if (!semverGte(parseSemver(version), parseSemver(MIN_PNPM_VERSION))) {
74
- throw new Error(
75
- `pnpm ${MIN_PNPM_VERSION} or higher is required, but found ${version}.\n` +
76
- `Upgrade with: pnpm self-update`
77
- );
78
- }
79
- }
80
-
81
- function runPnpmInit({ force, cwd, dryRun }) {
82
- const initArgs = ['init', '--full'];
83
- if (force) {
84
- initArgs.push('--force');
85
- }
86
-
87
- if (dryRun) {
88
- logLine(` [DRY RUN] Would run: pnpm add -g ruflo@latest`);
89
- logLine(` [DRY RUN] Would run: pnpm approve-builds -g --all (if changes detected)`);
90
- logLine(` [DRY RUN] Would run: ruflo ${initArgs.join(' ')}`);
91
- return;
92
- }
93
-
94
- ensurePnpmAvailable();
95
-
96
- // Capture stdout to detect whether pnpm installed/updated anything.
97
- // Progress spinners go to stderr (still shown to user); stdout has the summary.
98
- const install = spawnSync('pnpm', ['add', '-g', 'ruflo@latest'], {
99
- cwd,
100
- stdio: ['inherit', 'pipe', 'inherit'],
101
- shell: process.platform === 'win32'
102
- });
103
-
104
- const installOutput = (install.stdout || '').toString();
105
- if (installOutput) {
106
- process.stdout.write(installOutput);
107
- }
108
-
109
- if (install.status !== 0) {
110
- throw new Error(`pnpm add -g ruflo@latest failed with exit code ${install.status}`);
111
- }
112
-
113
- // pnpm prints a "Packages:" summary line and "+ pkg version" lines when
114
- // something is actually installed or updated. When already up to date the
115
- // stdout is empty or contains only "Already up to date".
116
- const somethingChanged = /Packages:/i.test(installOutput) || /^\+\s/m.test(installOutput);
117
- if (somethingChanged) {
118
- logLine(' Changes detected — running pnpm approve-builds -g --all ...');
119
- const approve = spawnSync('pnpm', ['approve-builds', '-g', '--all'], {
120
- cwd,
121
- stdio: 'inherit',
122
- shell: process.platform === 'win32'
123
- });
124
- if (approve.status !== 0) {
125
- throw new Error(`pnpm approve-builds -g --all failed with exit code ${approve.status}`);
126
- }
127
- }
128
-
129
- const run = spawnSync('ruflo', initArgs, {
130
- cwd,
131
- stdio: 'inherit',
132
- shell: process.platform === 'win32'
133
- });
134
-
135
- if (run.status !== 0) {
136
- throw new Error(`ruflo init failed with exit code ${run.status}`);
137
- }
138
- }
139
-
140
- function writeMcpJson({ cwd, dryRun }) {
141
- const mcpPath = path.join(cwd, '.mcp.json');
142
- const mcpConfig = toPlatformMcpConfig(process.platform);
143
-
144
- if (dryRun) {
145
- const action = pathExists(mcpPath) ? 'overwrite' : 'write';
146
- logLine(` [DRY RUN] Would ${action}: ${mcpPath}`);
147
- return;
148
- }
149
-
150
- writeJson(mcpPath, mcpConfig);
151
- logLine(' .mcp.json written for this platform.');
152
- }
153
-
154
- function syncGlobalCommandTemplate({ packageRoot, dryRun }) {
155
- const src = path.join(packageRoot, 'templates', 'ruflo-setup.md');
156
- const dest = path.join(os.homedir(), '.claude', 'commands', 'ruflo-setup.md');
157
- const exists = pathExists(dest);
158
- const operation = exists ? 'update' : 'install';
159
- const srcContent = fs.readFileSync(src, 'utf8');
160
- const changed = !exists || fs.readFileSync(dest, 'utf8') !== srcContent;
161
-
162
- if (dryRun || !changed) {
163
- return { dest, changed, operation };
164
- }
165
-
166
- copyFileSync(src, dest);
167
- return { dest, changed, operation };
168
- }
169
-
170
- function isAlreadyConfigured(cwd) {
171
- return pathExists(path.join(cwd, '.mcp.json')) || pathExists(path.join(cwd, '.claude', 'settings.json'));
172
- }
173
-
174
- function getCurrentVersion(packageRoot) {
175
- try {
176
- const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
177
- return pkg.version || '0.0.0';
178
- } catch {
179
- return '0.0.0';
180
- }
181
- }
182
-
183
- function getLatestVersion() {
184
- try {
185
- const result = spawnSync('pnpm', ['view', '@mfjjs/ruflo-setup', 'version'], {
186
- stdio: ['ignore', 'pipe', 'ignore'],
187
- shell: process.platform === 'win32',
188
- timeout: 8000
189
- });
190
- if (result.status !== 0 || result.error) return null;
191
- return (result.stdout || '').toString().trim() || null;
192
- } catch {
193
- return null;
194
- }
195
- }
196
-
197
- export async function runSetup({
198
- cwd,
199
- packageRoot,
200
- force = false,
201
- dryRun = false,
202
- skipInit = false,
203
- noHooks = false,
204
- yes = false,
205
- verbose = false
206
- }) {
207
- logLine('');
208
- logLine('Ruflo Setup (npm CLI)');
209
- logLine(`Target directory: ${cwd}`);
210
- if (dryRun) {
211
- logLine('[DRY RUN - no changes will be made]');
212
- }
213
- logLine('');
214
-
215
- // Check if a newer version of ruflo-setup itself is available.
216
- if (!dryRun && !yes) {
217
- const currentVersion = getCurrentVersion(packageRoot);
218
- const latestVersion = getLatestVersion();
219
- if (latestVersion && !semverGte(parseSemver(currentVersion), parseSemver(latestVersion))) {
220
- logLine(`A newer version of ruflo-setup is available: ${latestVersion} (you have ${currentVersion}).`);
221
- logLine('It is best to always have the latest version before running setup.');
222
- const doUpdate = await confirm('Update @mfjjs/ruflo-setup now? [y/N] ');
223
- if (doUpdate) {
224
- runUpdate({ dryRun: false });
225
- logLine('');
226
- logLine('Please re-run ruflo-setup to continue with the updated version.');
227
- return;
228
- }
229
- logLine('');
230
- }
231
- }
232
-
233
- logLine('Preflight: Syncing global /ruflo-setup command template ...');
234
- const preflightCommandResult = syncGlobalCommandTemplate({ packageRoot, dryRun });
235
- if (preflightCommandResult.changed) {
236
- if (dryRun) {
237
- logLine(` [DRY RUN] Would ${preflightCommandResult.operation}: ${preflightCommandResult.dest}`);
238
- } else if (preflightCommandResult.operation === 'install') {
239
- logLine(` Installed command template at: ${preflightCommandResult.dest}`);
240
- } else {
241
- logLine(` Updated command template at: ${preflightCommandResult.dest}`);
242
- }
243
- } else {
244
- logLine(` Command template already up to date: ${preflightCommandResult.dest}`);
245
- }
246
- logLine('');
247
-
248
- if (isAlreadyConfigured(cwd) && !force && !yes) {
249
- logLine('WARNING: This project already has Ruflo configuration.');
250
- const shouldOverwrite = await confirm('Overwrite existing configuration? [y/N] ');
251
- if (!shouldOverwrite) {
252
- logLine('Aborted. No changes made.');
253
- return;
254
- }
255
- }
256
-
257
- if (!skipInit) {
258
- logLine('Step 1: Running pnpm add -g ruflo@latest && ruflo init --full ...');
259
- runPnpmInit({ force, cwd, dryRun });
260
- if (!dryRun) {
261
- logLine(' ruflo init completed.');
262
- }
263
- logLine('');
264
- } else {
265
- logLine('Step 1: Skipped ruflo init (--skip-init).');
266
- logLine('');
267
- }
268
-
269
- logLine('Step 2: Writing platform-aware .mcp.json ...');
270
- writeMcpJson({ cwd, dryRun });
271
- logLine('');
272
-
273
- if (!noHooks) {
274
- logLine('Step 3: Installing global SessionStart check-ruflo hook ...');
275
- const hookResult = installGlobalCheckRufloHook({ packageRoot, dryRun });
276
- if (hookResult.inserted) {
277
- logLine(` Hook installed in: ${hookResult.settingsPath}`);
278
- } else {
279
- logLine(` Hook already present in: ${hookResult.settingsPath}`);
280
- }
281
- if (verbose) {
282
- logLine(` Hook command: ${hookResult.hookCommand}`);
283
- }
284
- logLine('');
285
- } else {
286
- logLine('Step 3: Skipped hook installation (--no-hooks).');
287
- logLine('');
288
- }
289
-
290
- logLine('Step 4: Installing global /ruflo-setup command ...');
291
- if (dryRun) {
292
- if (preflightCommandResult.changed) {
293
- logLine(` [DRY RUN] Would ${preflightCommandResult.operation}: ${preflightCommandResult.dest}`);
294
- } else {
295
- logLine(` [DRY RUN] Command already up to date: ${preflightCommandResult.dest}`);
296
- }
297
- } else if (preflightCommandResult.changed) {
298
- if (preflightCommandResult.operation === 'install') {
299
- logLine(` Command installed at: ${preflightCommandResult.dest}`);
300
- } else {
301
- logLine(` Command updated at: ${preflightCommandResult.dest}`);
302
- }
303
- } else {
304
- logLine(` Command already up to date: ${preflightCommandResult.dest}`);
305
- }
306
- logLine('');
307
-
308
- if (dryRun) {
309
- logLine('Dry run complete. No changes were made.');
310
- return;
311
- }
312
-
313
- logLine('Setup complete!');
314
- logLine('');
315
- logLine('Next steps:');
316
- logLine(' 1. Edit CLAUDE.md for project-specific Build & Test commands');
317
- logLine(' 2. Run: claude');
318
- logLine(' 3. Verify hooks: ruflo-setup hooks status');
319
- }
320
-
321
- const CLEANUP_NPM_PACKAGES = [
322
- 'ruflo',
323
- '@mfjjs/ruflo-setup',
324
- 'ruflo-setup',
325
- 'claude-flow',
326
- '@claude-flow/cli',
327
- 'ruv-swarm'
328
- ];
329
-
330
- export function runCleanup({ dryRun = false } = {}) {
331
- logLine('');
332
- logLine('Ruflo Cleanup — removing from npm global registry');
333
- logLine(`Packages: ${CLEANUP_NPM_PACKAGES.join(', ')}`);
334
- logLine('');
335
-
336
- if (dryRun) {
337
- logLine(` [DRY RUN] Would run: npm uninstall -g ${CLEANUP_NPM_PACKAGES.join(' ')}`);
338
- logLine('');
339
- return;
340
- }
341
-
342
- const result = spawnSync('npm', ['uninstall', '-g', ...CLEANUP_NPM_PACKAGES], {
343
- stdio: 'inherit',
344
- shell: process.platform === 'win32'
345
- });
346
-
347
- if (result.status !== 0) {
348
- throw new Error(`npm uninstall -g failed with exit code ${result.status}`);
349
- }
350
-
351
- logLine('');
352
- logLine('Cleanup complete.');
353
- }
354
-
355
- export function runUpdate({ dryRun = false } = {}) {
356
- logLine('');
357
- logLine('Ruflo Setup Update');
358
- logLine('');
359
-
360
- if (dryRun) {
361
- logLine('[DRY RUN] Would run: pnpm add -g @mfjjs/ruflo-setup@latest');
362
- logLine('');
363
- return;
364
- }
365
-
366
- ensurePnpmAvailable();
367
-
368
- logLine('Updating @mfjjs/ruflo-setup to latest...');
369
- const result = spawnSync('pnpm', ['add', '-g', '@mfjjs/ruflo-setup@latest'], {
370
- stdio: 'inherit',
371
- shell: process.platform === 'win32'
372
- });
373
-
374
- if (result.status !== 0) {
375
- throw new Error(`pnpm add -g @mfjjs/ruflo-setup@latest failed with exit code ${result.status}`);
376
- }
377
-
378
- logLine('');
379
- logLine('Update complete. Re-run ruflo-setup to continue with the updated version.');
380
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { pathExists, copyFileSync, confirm, toPlatformMcpConfig, writeJson } from './utils.js';
6
+ import { installGlobalCheckRufloHook } from './hooks.js';
7
+
8
+ function logLine(message) {
9
+ process.stdout.write(`${message}\n`);
10
+ }
11
+
12
+ function getPnpmInstallSuggestions(platform) {
13
+ if (platform === 'win32') {
14
+ return [
15
+ 'winget install -e --id pnpm.pnpm',
16
+ 'corepack enable && corepack prepare pnpm@latest --activate',
17
+ 'npm install -g pnpm'
18
+ ];
19
+ }
20
+
21
+ if (platform === 'darwin') {
22
+ return [
23
+ 'brew install pnpm',
24
+ 'corepack enable && corepack prepare pnpm@latest --activate',
25
+ 'npm install -g pnpm'
26
+ ];
27
+ }
28
+
29
+ return [
30
+ 'curl -fsSL https://get.pnpm.io/install.sh | sh -',
31
+ 'corepack enable && corepack prepare pnpm@latest --activate',
32
+ 'npm install -g pnpm'
33
+ ];
34
+ }
35
+
36
+ const MIN_PNPM_VERSION = '10.32.1';
37
+
38
+ function parseSemver(str) {
39
+ const [major = 0, minor = 0, patch = 0] = str.trim().split('.').map(Number);
40
+ return { major, minor, patch };
41
+ }
42
+
43
+ function semverGte(a, b) {
44
+ if (a.major !== b.major) return a.major > b.major;
45
+ if (a.minor !== b.minor) return a.minor > b.minor;
46
+ return a.patch >= b.patch;
47
+ }
48
+
49
+ function ensurePnpmAvailable() {
50
+ const check = spawnSync('pnpm', ['--version'], {
51
+ stdio: ['ignore', 'pipe', 'ignore'],
52
+ shell: process.platform === 'win32'
53
+ });
54
+
55
+ if (check.status !== 0 || check.error) {
56
+ const platformLabel = process.platform === 'win32'
57
+ ? 'Windows'
58
+ : process.platform === 'darwin'
59
+ ? 'macOS'
60
+ : 'Linux';
61
+ const suggestions = getPnpmInstallSuggestions(process.platform)
62
+ .map((command) => ` - ${command}`)
63
+ .join('\n');
64
+
65
+ throw new Error(
66
+ `pnpm is required but was not found in PATH.\n` +
67
+ `Install pnpm, then re-run ruflo-setup.\n` +
68
+ `Quick install options for ${platformLabel}:\n${suggestions}`
69
+ );
70
+ }
71
+
72
+ const version = (check.stdout || '').toString().trim();
73
+ if (!semverGte(parseSemver(version), parseSemver(MIN_PNPM_VERSION))) {
74
+ throw new Error(
75
+ `pnpm ${MIN_PNPM_VERSION} or higher is required, but found ${version}.\n` +
76
+ `Upgrade with: pnpm self-update`
77
+ );
78
+ }
79
+ }
80
+
81
+ function runPnpmInit({ force, cwd, dryRun }) {
82
+ const initArgs = ['init', '--full'];
83
+ if (force) {
84
+ initArgs.push('--force');
85
+ }
86
+
87
+ if (dryRun) {
88
+ logLine(` [DRY RUN] Would run: pnpm add -g ruflo@latest`);
89
+ logLine(` [DRY RUN] Would run: pnpm approve-builds -g --all (if changes detected)`);
90
+ logLine(` [DRY RUN] Would run: ruflo ${initArgs.join(' ')}`);
91
+ return;
92
+ }
93
+
94
+ ensurePnpmAvailable();
95
+
96
+ // Capture stdout to detect whether pnpm installed/updated anything.
97
+ // Progress spinners go to stderr (still shown to user); stdout has the summary.
98
+ const install = spawnSync('pnpm', ['add', '-g', 'ruflo@latest'], {
99
+ cwd,
100
+ stdio: ['inherit', 'pipe', 'inherit'],
101
+ shell: process.platform === 'win32'
102
+ });
103
+
104
+ const installOutput = (install.stdout || '').toString();
105
+ if (installOutput) {
106
+ process.stdout.write(installOutput);
107
+ }
108
+
109
+ if (install.status !== 0) {
110
+ throw new Error(`pnpm add -g ruflo@latest failed with exit code ${install.status}`);
111
+ }
112
+
113
+ // pnpm prints a "Packages:" summary line and "+ pkg version" lines when
114
+ // something is actually installed or updated. When already up to date the
115
+ // stdout is empty or contains only "Already up to date".
116
+ const somethingChanged = /Packages:/i.test(installOutput) || /^\+\s/m.test(installOutput);
117
+ if (somethingChanged) {
118
+ logLine(' Changes detected — running pnpm approve-builds -g --all ...');
119
+ const approve = spawnSync('pnpm', ['approve-builds', '-g', '--all'], {
120
+ cwd,
121
+ stdio: 'inherit',
122
+ shell: process.platform === 'win32'
123
+ });
124
+ if (approve.status !== 0) {
125
+ throw new Error(`pnpm approve-builds -g --all failed with exit code ${approve.status}`);
126
+ }
127
+ }
128
+
129
+ const run = spawnSync('ruflo', initArgs, {
130
+ cwd,
131
+ stdio: 'inherit',
132
+ shell: process.platform === 'win32'
133
+ });
134
+
135
+ if (run.status !== 0) {
136
+ throw new Error(`ruflo init failed with exit code ${run.status}`);
137
+ }
138
+ }
139
+
140
+ function writeMcpJson({ cwd, dryRun }) {
141
+ const mcpPath = path.join(cwd, '.mcp.json');
142
+ const mcpConfig = toPlatformMcpConfig(process.platform);
143
+
144
+ if (dryRun) {
145
+ const action = pathExists(mcpPath) ? 'overwrite' : 'write';
146
+ logLine(` [DRY RUN] Would ${action}: ${mcpPath}`);
147
+ return;
148
+ }
149
+
150
+ writeJson(mcpPath, mcpConfig);
151
+ logLine(' .mcp.json written for this platform.');
152
+ }
153
+
154
+ function syncGlobalCommandTemplate({ packageRoot, dryRun }) {
155
+ const src = path.join(packageRoot, 'templates', 'ruflo-setup.md');
156
+ const dest = path.join(os.homedir(), '.claude', 'commands', 'ruflo-setup.md');
157
+ const exists = pathExists(dest);
158
+ const operation = exists ? 'update' : 'install';
159
+ const srcContent = fs.readFileSync(src, 'utf8');
160
+ const changed = !exists || fs.readFileSync(dest, 'utf8') !== srcContent;
161
+
162
+ if (dryRun || !changed) {
163
+ return { dest, changed, operation };
164
+ }
165
+
166
+ copyFileSync(src, dest);
167
+ return { dest, changed, operation };
168
+ }
169
+
170
+ function updateGitignore({ cwd, dryRun }) {
171
+ const gitignorePath = path.join(cwd, '.gitignore');
172
+ const entries = ['.mcp.json', '.claude/settings.json'];
173
+
174
+ if (dryRun) {
175
+ logLine(` [DRY RUN] Would ensure ${gitignorePath} contains: ${entries.join(', ')}`);
176
+ return;
177
+ }
178
+
179
+ let content = pathExists(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
180
+ const lines = content.split('\n');
181
+ const added = [];
182
+
183
+ for (const entry of entries) {
184
+ if (!lines.some((line) => line.trim() === entry)) {
185
+ added.push(entry);
186
+ }
187
+ }
188
+
189
+ if (added.length === 0) {
190
+ logLine(` .gitignore already contains required entries.`);
191
+ return;
192
+ }
193
+
194
+ const suffix = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
195
+ content = content + suffix + added.join('\n') + '\n';
196
+ fs.writeFileSync(gitignorePath, content, 'utf8');
197
+ logLine(` Added to .gitignore: ${added.join(', ')}`);
198
+ }
199
+
200
+ function isAlreadyConfigured(cwd) {
201
+ return pathExists(path.join(cwd, '.mcp.json')) || pathExists(path.join(cwd, '.claude', 'settings.json'));
202
+ }
203
+
204
+ function getCurrentVersion(packageRoot) {
205
+ try {
206
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
207
+ return pkg.version || '0.0.0';
208
+ } catch {
209
+ return '0.0.0';
210
+ }
211
+ }
212
+
213
+ function getLatestVersion() {
214
+ try {
215
+ const result = spawnSync('pnpm', ['view', '@mfjjs/ruflo-setup', 'version'], {
216
+ stdio: ['ignore', 'pipe', 'ignore'],
217
+ shell: process.platform === 'win32',
218
+ timeout: 8000
219
+ });
220
+ if (result.status !== 0 || result.error) return null;
221
+ return (result.stdout || '').toString().trim() || null;
222
+ } catch {
223
+ return null;
224
+ }
225
+ }
226
+
227
+ export async function runSetup({
228
+ cwd,
229
+ packageRoot,
230
+ force = false,
231
+ dryRun = false,
232
+ skipInit = false,
233
+ noHooks = false,
234
+ yes = false,
235
+ verbose = false
236
+ }) {
237
+ logLine('');
238
+ logLine('Ruflo Setup (npm CLI)');
239
+ logLine(`Target directory: ${cwd}`);
240
+ if (dryRun) {
241
+ logLine('[DRY RUN - no changes will be made]');
242
+ }
243
+ logLine('');
244
+
245
+ // Check if a newer version of ruflo-setup itself is available.
246
+ if (!dryRun && !yes) {
247
+ const currentVersion = getCurrentVersion(packageRoot);
248
+ const latestVersion = getLatestVersion();
249
+ if (latestVersion && !semverGte(parseSemver(currentVersion), parseSemver(latestVersion))) {
250
+ logLine(`A newer version of ruflo-setup is available: ${latestVersion} (you have ${currentVersion}).`);
251
+ logLine('It is best to always have the latest version before running setup.');
252
+ const doUpdate = await confirm('Update @mfjjs/ruflo-setup now? [y/N] ');
253
+ if (doUpdate) {
254
+ runUpdate({ dryRun: false });
255
+ logLine('');
256
+ logLine('Please re-run ruflo-setup to continue with the updated version.');
257
+ return;
258
+ }
259
+ logLine('');
260
+ }
261
+ }
262
+
263
+ logLine('Preflight: Syncing global /ruflo-setup command template ...');
264
+ const preflightCommandResult = syncGlobalCommandTemplate({ packageRoot, dryRun });
265
+ if (preflightCommandResult.changed) {
266
+ if (dryRun) {
267
+ logLine(` [DRY RUN] Would ${preflightCommandResult.operation}: ${preflightCommandResult.dest}`);
268
+ } else if (preflightCommandResult.operation === 'install') {
269
+ logLine(` Installed command template at: ${preflightCommandResult.dest}`);
270
+ } else {
271
+ logLine(` Updated command template at: ${preflightCommandResult.dest}`);
272
+ }
273
+ } else {
274
+ logLine(` Command template already up to date: ${preflightCommandResult.dest}`);
275
+ }
276
+ logLine('');
277
+
278
+ if (isAlreadyConfigured(cwd) && !force && !yes) {
279
+ logLine('WARNING: This project already has Ruflo configuration.');
280
+ const shouldOverwrite = await confirm('Overwrite existing configuration? [y/N] ');
281
+ if (!shouldOverwrite) {
282
+ logLine('Aborted. No changes made.');
283
+ return;
284
+ }
285
+ }
286
+
287
+ if (!skipInit) {
288
+ logLine('Step 1: Running pnpm add -g ruflo@latest && ruflo init --full ...');
289
+ runPnpmInit({ force, cwd, dryRun });
290
+ if (!dryRun) {
291
+ logLine(' ruflo init completed.');
292
+ }
293
+ logLine('');
294
+ } else {
295
+ logLine('Step 1: Skipped ruflo init (--skip-init).');
296
+ logLine('');
297
+ }
298
+
299
+ logLine('Step 2: Writing platform-aware .mcp.json ...');
300
+ writeMcpJson({ cwd, dryRun });
301
+ logLine('');
302
+
303
+ logLine('Step 3: Updating .gitignore ...');
304
+ updateGitignore({ cwd, dryRun });
305
+ logLine('');
306
+
307
+ if (!noHooks) {
308
+ logLine('Step 4: Installing global SessionStart check-ruflo hook ...');
309
+ const hookResult = installGlobalCheckRufloHook({ packageRoot, dryRun });
310
+ if (hookResult.inserted) {
311
+ logLine(` Hook installed in: ${hookResult.settingsPath}`);
312
+ } else {
313
+ logLine(` Hook already present in: ${hookResult.settingsPath}`);
314
+ }
315
+ if (verbose) {
316
+ logLine(` Hook command: ${hookResult.hookCommand}`);
317
+ }
318
+ logLine('');
319
+ } else {
320
+ logLine('Step 4: Skipped hook installation (--no-hooks).');
321
+ logLine('');
322
+ }
323
+
324
+ logLine('Step 5: Installing global /ruflo-setup command ...');
325
+ if (dryRun) {
326
+ if (preflightCommandResult.changed) {
327
+ logLine(` [DRY RUN] Would ${preflightCommandResult.operation}: ${preflightCommandResult.dest}`);
328
+ } else {
329
+ logLine(` [DRY RUN] Command already up to date: ${preflightCommandResult.dest}`);
330
+ }
331
+ } else if (preflightCommandResult.changed) {
332
+ if (preflightCommandResult.operation === 'install') {
333
+ logLine(` Command installed at: ${preflightCommandResult.dest}`);
334
+ } else {
335
+ logLine(` Command updated at: ${preflightCommandResult.dest}`);
336
+ }
337
+ } else {
338
+ logLine(` Command already up to date: ${preflightCommandResult.dest}`);
339
+ }
340
+ logLine('');
341
+
342
+ if (dryRun) {
343
+ logLine('Dry run complete. No changes were made.');
344
+ return;
345
+ }
346
+
347
+ logLine('Setup complete!');
348
+ logLine('');
349
+ logLine('Next steps:');
350
+ logLine(' 1. Edit CLAUDE.md for project-specific Build & Test commands');
351
+ logLine(' 2. Run: claude');
352
+ logLine(' 3. Verify hooks: ruflo-setup hooks status');
353
+ }
354
+
355
+ const CLEANUP_NPM_PACKAGES = [
356
+ 'ruflo',
357
+ '@mfjjs/ruflo-setup',
358
+ 'ruflo-setup',
359
+ 'claude-flow',
360
+ '@claude-flow/cli',
361
+ 'ruv-swarm'
362
+ ];
363
+
364
+ export function runCleanup({ dryRun = false } = {}) {
365
+ logLine('');
366
+ logLine('Ruflo Cleanup — removing from npm global registry');
367
+ logLine(`Packages: ${CLEANUP_NPM_PACKAGES.join(', ')}`);
368
+ logLine('');
369
+
370
+ if (dryRun) {
371
+ logLine(` [DRY RUN] Would run: npm uninstall -g ${CLEANUP_NPM_PACKAGES.join(' ')}`);
372
+ logLine('');
373
+ return;
374
+ }
375
+
376
+ const result = spawnSync('npm', ['uninstall', '-g', ...CLEANUP_NPM_PACKAGES], {
377
+ stdio: 'inherit',
378
+ shell: process.platform === 'win32'
379
+ });
380
+
381
+ if (result.status !== 0) {
382
+ throw new Error(`npm uninstall -g failed with exit code ${result.status}`);
383
+ }
384
+
385
+ logLine('');
386
+ logLine('Cleanup complete.');
387
+ }
388
+
389
+ export function runUpdate({ dryRun = false } = {}) {
390
+ logLine('');
391
+ logLine('Ruflo Setup Update');
392
+ logLine('');
393
+
394
+ if (dryRun) {
395
+ logLine('[DRY RUN] Would run: pnpm add -g @mfjjs/ruflo-setup@latest');
396
+ logLine('');
397
+ return;
398
+ }
399
+
400
+ ensurePnpmAvailable();
401
+
402
+ logLine('Updating @mfjjs/ruflo-setup to latest...');
403
+ const result = spawnSync('pnpm', ['add', '-g', '@mfjjs/ruflo-setup@latest'], {
404
+ stdio: 'inherit',
405
+ shell: process.platform === 'win32'
406
+ });
407
+
408
+ if (result.status !== 0) {
409
+ throw new Error(`pnpm add -g @mfjjs/ruflo-setup@latest failed with exit code ${result.status}`);
410
+ }
411
+
412
+ logLine('');
413
+ logLine('Update complete. Re-run ruflo-setup to continue with the updated version.');
414
+ }
package/src/utils.js CHANGED
@@ -1,120 +1,120 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import readline from 'node:readline';
4
-
5
- export function pathExists(filePath) {
6
- return fs.existsSync(filePath);
7
- }
8
-
9
- export function readJsonSafe(filePath, fallbackValue = {}) {
10
- if (!pathExists(filePath)) {
11
- return fallbackValue;
12
- }
13
- try {
14
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
15
- } catch {
16
- return fallbackValue;
17
- }
18
- }
19
-
20
- export function writeJson(filePath, value) {
21
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
22
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
23
- }
24
-
25
- export function copyFileSync(src, dest) {
26
- fs.mkdirSync(path.dirname(dest), { recursive: true });
27
- fs.copyFileSync(src, dest);
28
- }
29
-
30
- export async function confirm(question) {
31
- const rl = readline.createInterface({
32
- input: process.stdin,
33
- output: process.stdout
34
- });
35
-
36
- const answer = await new Promise((resolve) => {
37
- rl.question(question, resolve);
38
- });
39
-
40
- rl.close();
41
- return /^[Yy]$/.test((answer || '').trim());
42
- }
43
-
44
- export function parseArgs(argv) {
45
- const flags = {
46
- force: false,
47
- dryRun: false,
48
- yes: false,
49
- noHooks: false,
50
- skipInit: false,
51
- verbose: false,
52
- command: 'setup'
53
- };
54
-
55
- const positional = [];
56
- for (const item of argv) {
57
- if (item === '--force' || item === '-f') flags.force = true;
58
- else if (item === '--dry-run') flags.dryRun = true;
59
- else if (item === '--yes' || item === '-y') flags.yes = true;
60
- else if (item === '--no-hooks') flags.noHooks = true;
61
- else if (item === '--skip-init') flags.skipInit = true;
62
- else if (item === '--verbose') flags.verbose = true;
63
- else positional.push(item);
64
- }
65
-
66
- if (positional.length > 0) {
67
- flags.command = positional[0];
68
- }
69
-
70
- return flags;
71
- }
72
-
73
- export function toPlatformMcpConfig(platform) {
74
- const isWindows = platform === 'win32';
75
- const command = isWindows ? 'cmd' : 'pnpm';
76
- const pnpmArgs = isWindows ? ['/c', 'pnpm', 'dlx'] : ['dlx'];
77
-
78
- const makeArgs = (pkg, extraArgs) => {
79
- return [...pnpmArgs, pkg, ...extraArgs];
80
- };
81
-
82
- return {
83
- mcpServers: {
84
- 'claude-flow': {
85
- command,
86
- args: makeArgs('@claude-flow/cli@latest', ['mcp', 'start']),
87
- env: {
88
- npm_config_update_notifier: 'false',
89
- CLAUDE_FLOW_MODE: 'v3',
90
- CLAUDE_FLOW_HOOKS_ENABLED: 'true',
91
- CLAUDE_FLOW_TOPOLOGY: 'hierarchical-mesh',
92
- CLAUDE_FLOW_MAX_AGENTS: '15',
93
- CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid',
94
- MCP_GROUP_SECURITY: 'true',
95
- MCP_GROUP_BROWSER: 'true',
96
- MCP_GROUP_NEURAL: 'true',
97
- MCP_GROUP_AGENTIC_FLOW: 'true'
98
- },
99
- autoStart: false
100
- },
101
- 'ruv-swarm': {
102
- command,
103
- args: makeArgs('ruv-swarm', ['mcp', 'start']),
104
- env: {
105
- npm_config_update_notifier: 'false'
106
- },
107
- optional: true
108
- },
109
- 'flow-nexus': {
110
- command,
111
- args: makeArgs('flow-nexus@latest', ['mcp', 'start']),
112
- env: {
113
- npm_config_update_notifier: 'false'
114
- },
115
- optional: true,
116
- requiresAuth: true
117
- }
118
- }
119
- };
120
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import readline from 'node:readline';
4
+
5
+ export function pathExists(filePath) {
6
+ return fs.existsSync(filePath);
7
+ }
8
+
9
+ export function readJsonSafe(filePath, fallbackValue = {}) {
10
+ if (!pathExists(filePath)) {
11
+ return fallbackValue;
12
+ }
13
+ try {
14
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
15
+ } catch {
16
+ return fallbackValue;
17
+ }
18
+ }
19
+
20
+ export function writeJson(filePath, value) {
21
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
22
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
23
+ }
24
+
25
+ export function copyFileSync(src, dest) {
26
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
27
+ fs.copyFileSync(src, dest);
28
+ }
29
+
30
+ export async function confirm(question) {
31
+ const rl = readline.createInterface({
32
+ input: process.stdin,
33
+ output: process.stdout
34
+ });
35
+
36
+ const answer = await new Promise((resolve) => {
37
+ rl.question(question, resolve);
38
+ });
39
+
40
+ rl.close();
41
+ return /^[Yy]$/.test((answer || '').trim());
42
+ }
43
+
44
+ export function parseArgs(argv) {
45
+ const flags = {
46
+ force: false,
47
+ dryRun: false,
48
+ yes: false,
49
+ noHooks: false,
50
+ skipInit: false,
51
+ verbose: false,
52
+ command: 'setup'
53
+ };
54
+
55
+ const positional = [];
56
+ for (const item of argv) {
57
+ if (item === '--force' || item === '-f') flags.force = true;
58
+ else if (item === '--dry-run') flags.dryRun = true;
59
+ else if (item === '--yes' || item === '-y') flags.yes = true;
60
+ else if (item === '--no-hooks') flags.noHooks = true;
61
+ else if (item === '--skip-init') flags.skipInit = true;
62
+ else if (item === '--verbose') flags.verbose = true;
63
+ else positional.push(item);
64
+ }
65
+
66
+ if (positional.length > 0) {
67
+ flags.command = positional[0];
68
+ }
69
+
70
+ return flags;
71
+ }
72
+
73
+ export function toPlatformMcpConfig(platform) {
74
+ const isWindows = platform === 'win32';
75
+ const command = isWindows ? 'cmd' : 'npx';
76
+ const npxArgs = isWindows ? ['/c', 'npx', '-y'] : ['-y'];
77
+
78
+ const makeArgs = (pkg, extraArgs) => {
79
+ return [...npxArgs, pkg, ...extraArgs];
80
+ };
81
+
82
+ return {
83
+ mcpServers: {
84
+ 'claude-flow': {
85
+ command,
86
+ args: makeArgs('@claude-flow/cli@latest', ['mcp', 'start']),
87
+ env: {
88
+ npm_config_update_notifier: 'false',
89
+ CLAUDE_FLOW_MODE: 'v3',
90
+ CLAUDE_FLOW_HOOKS_ENABLED: 'true',
91
+ CLAUDE_FLOW_TOPOLOGY: 'hierarchical-mesh',
92
+ CLAUDE_FLOW_MAX_AGENTS: '15',
93
+ CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid',
94
+ MCP_GROUP_SECURITY: 'true',
95
+ MCP_GROUP_BROWSER: 'true',
96
+ MCP_GROUP_NEURAL: 'true',
97
+ MCP_GROUP_AGENTIC_FLOW: 'true'
98
+ },
99
+ autoStart: false
100
+ },
101
+ 'ruv-swarm': {
102
+ command,
103
+ args: makeArgs('ruv-swarm', ['mcp', 'start']),
104
+ env: {
105
+ npm_config_update_notifier: 'false'
106
+ },
107
+ optional: true
108
+ },
109
+ 'flow-nexus': {
110
+ command,
111
+ args: makeArgs('flow-nexus@latest', ['mcp', 'start']),
112
+ env: {
113
+ npm_config_update_notifier: 'false'
114
+ },
115
+ optional: true,
116
+ requiresAuth: true
117
+ }
118
+ }
119
+ };
120
+ }
@@ -1,90 +1,92 @@
1
- # /ruflo-setup
2
-
3
- Set up Ruflo + Claude Flow V3 in the current project directory.
4
-
5
- ## Requirements
6
-
7
- - Node.js 20+
8
- - pnpm 10.32.1+ installed and available on PATH
9
-
10
- Quickest pnpm install by platform:
11
-
12
- ```bash
13
- # Windows (recommended)
14
- winget install -e --id pnpm.pnpm
15
-
16
- # macOS (recommended)
17
- brew install pnpm
18
-
19
- # Linux (recommended)
20
- curl -fsSL https://get.pnpm.io/install.sh | sh -
21
- ```
22
-
23
- Alternative (all platforms with recent Node.js):
24
-
25
- ```bash
26
- corepack enable
27
- corepack prepare pnpm@latest --activate
28
- ```
29
-
30
- ## What this does
31
-
32
- Runs `pnpm add -g @mfjjs/ruflo-setup` then `ruflo-setup` which:
33
-
34
- 1. Runs `pnpm add -g ruflo@latest` then `ruflo init --full` to install:
35
- - `.claude/settings.json` with hooks, permissions, and Claude Flow config
36
- - `.claude/helpers/` — hook-handler, statusline, auto-memory scripts
37
- - `.claude/agents/` — 120+ agent definitions
38
- - `.claude/skills/` — 30+ skill definitions
39
- - `.claude/commands/` — slash commands
40
- 2. Writes a platform-aware `.mcp.json` (MCP server registration for claude-flow, ruv-swarm, flow-nexus)
41
- 3. Installs a global `SessionStart` hook in `~/.claude/settings.json` that warns when Ruflo is not configured
42
- 4. May refresh `~/.claude/commands/ruflo-setup.md` from the latest packaged template when differences are detected
43
-
44
- ## Options
45
-
46
- ### cleanup
47
-
48
- Removes all Ruflo-related packages from the **npm** global registry (does not touch pnpm globals).
49
-
50
- Packages removed: `ruflo`, `@mfjjs/ruflo-setup`, `ruflo-setup`, `claude-flow`, `@claude-flow/cli`, `ruv-swarm`
51
-
52
- ## Instructions for Claude
53
-
54
- When the user runs /ruflo-setup:
55
-
56
- ### Default (no arguments) — install
57
-
58
- 1. Confirm the current working directory with the user
59
- 2. Check if `.mcp.json` already exists if so, warn and ask before overwriting
60
- 3. Check pnpm version is at least 10.32.1:
61
- ```bash
62
- pnpm --version
63
- ```
64
- If the version is lower than 10.32.1, stop and tell the user to upgrade pnpm before continuing.
65
- 4. Run the setup CLI and capture output to detect whether pnpm modified anything:
66
- ```bash
67
- pnpm add -g @mfjjs/ruflo-setup
68
- pnpm add -g ruflo@latest 2>&1 | tee /tmp/ruflo-pnpm-add.log
69
- ```
70
- After the `pnpm add -g ruflo@latest` step, inspect the output. If pnpm installed or updated any packages (i.e. the output does NOT contain "Already up to date" or an equivalent no-change message), run:
71
- ```bash
72
- pnpm approve-builds -g --all
73
- ```
74
- Skip `approve-builds` if nothing changed.
75
- 5. Run the setup tool:
76
- ```bash
77
- ruflo-setup
78
- ```
79
- 6. Report what was installed and remind the user to restart Claude Code to load the new MCP servers
80
-
81
- ### cleanup
82
-
83
- When the user runs `/ruflo-setup cleanup`:
84
-
85
- 1. Warn the user that this will remove Ruflo packages from the **npm** global registry and ask for confirmation
86
- 2. On confirmation, run:
87
- ```bash
88
- ruflo-setup cleanup
89
- ```
90
- 3. Report which packages were removed and which were not found (not found is fine — it means they were already clean)
1
+ # /ruflo-setup
2
+
3
+ Set up Ruflo + Claude Flow V3 in the current project directory.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 20+
8
+ - pnpm 10.32.1+ installed and available on PATH
9
+
10
+ Quickest pnpm install by platform:
11
+
12
+ ```bash
13
+ # Windows (recommended)
14
+ winget install -e --id pnpm.pnpm
15
+
16
+ # macOS (recommended)
17
+ brew install pnpm
18
+
19
+ # Linux (recommended)
20
+ curl -fsSL https://get.pnpm.io/install.sh | sh -
21
+ ```
22
+
23
+ Alternative (all platforms with recent Node.js):
24
+
25
+ ```bash
26
+ corepack enable
27
+ corepack prepare pnpm@latest --activate
28
+ ```
29
+
30
+ ## What this does
31
+
32
+ Runs `pnpm add -g @mfjjs/ruflo-setup` then `ruflo-setup` which:
33
+
34
+ 1. Runs `pnpm add -g ruflo@latest` then `ruflo init --full` to install:
35
+ - `.claude/settings.json` with hooks, permissions, and Claude Flow config
36
+ - `.claude/helpers/` — hook-handler, statusline, auto-memory scripts
37
+ - `.claude/agents/` — 120+ agent definitions
38
+ - `.claude/skills/` — 30+ skill definitions
39
+ - `.claude/commands/` — slash commands
40
+ 2. Writes a platform-aware `.mcp.json` (MCP server registration for claude-flow, ruv-swarm, flow-nexus; servers invoked via `npx` for cross-platform compatibility)
41
+ 3. Adds `.mcp.json` and `.claude/settings.json` to the project's `.gitignore`
42
+ 4. Installs a global `SessionStart` hook in `~/.claude/settings.json` that warns when Ruflo is not configured
43
+ 5. May refresh `~/.claude/commands/ruflo-setup.md` from the latest packaged template when differences are detected
44
+
45
+ ## Options
46
+
47
+ ### cleanup
48
+
49
+ Removes all Ruflo-related packages from the **npm** global registry (does not touch pnpm globals).
50
+
51
+ Packages removed: `ruflo`, `@mfjjs/ruflo-setup`, `ruflo-setup`, `claude-flow`, `@claude-flow/cli`, `ruv-swarm`
52
+
53
+ ## Instructions for Claude
54
+
55
+ When the user runs /ruflo-setup:
56
+
57
+ ### Default (no arguments) — install
58
+
59
+ 1. Confirm the current working directory with the user
60
+ 2. Check if `.mcp.json` already exists if so, warn and ask before overwriting
61
+ 3. Check pnpm version is at least 10.32.1:
62
+ ```bash
63
+ pnpm --version
64
+ ```
65
+ If the version is lower than 10.32.1, stop and tell the user to upgrade pnpm before continuing.
66
+ 4. Run the setup CLI and capture output to detect whether pnpm modified anything:
67
+ ```bash
68
+ pnpm add -g @mfjjs/ruflo-setup
69
+ pnpm add -g ruflo@latest 2>&1 | tee /tmp/ruflo-pnpm-add.log
70
+ ```
71
+ After the `pnpm add -g ruflo@latest` step, inspect the output. If pnpm installed or updated any packages (i.e. the output does NOT contain "Already up to date" or an equivalent no-change message), run:
72
+ ```bash
73
+ pnpm approve-builds -g --all
74
+ ```
75
+ Skip `approve-builds` if nothing changed.
76
+ 5. Run the setup tool:
77
+ ```bash
78
+ ruflo-setup
79
+ ```
80
+ 6. Ensure `.mcp.json` and `.claude/settings.json` are in the project's `.gitignore` (ruflo-setup does this automatically, but verify if using `--skip-init`)
81
+ 7. Report what was installed and remind the user to restart Claude Code to load the new MCP servers
82
+
83
+ ### cleanup
84
+
85
+ When the user runs `/ruflo-setup cleanup`:
86
+
87
+ 1. Warn the user that this will remove Ruflo packages from the **npm** global registry and ask for confirmation
88
+ 2. On confirmation, run:
89
+ ```bash
90
+ ruflo-setup cleanup
91
+ ```
92
+ 3. Report which packages were removed and which were not found (not found is fine — it means they were already clean)