@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 +14 -0
- package/README.md +6 -4
- package/package.json +1 -1
- package/src/setup.js +414 -380
- package/src/utils.js +120 -120
- package/templates/ruflo-setup.md +92 -90
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.
|
|
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
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
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
logLine(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
logLine(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
'ruflo-setup'
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
logLine(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
logLine('');
|
|
352
|
-
logLine('
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
logLine('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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' : '
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const makeArgs = (pkg, extraArgs) => {
|
|
79
|
-
return [...
|
|
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
|
+
}
|
package/templates/ruflo-setup.md
CHANGED
|
@@ -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.
|
|
42
|
-
4.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
pnpm add -g
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
|
|
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)
|