@mfjjs/ruflo-setup 0.1.0
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/README.md +120 -0
- package/bin/ruflo-setup.js +5 -0
- package/claude-hooks/check-ruflo.cjs +28 -0
- package/package.json +34 -0
- package/src/cli.js +85 -0
- package/src/hooks.js +106 -0
- package/src/setup.js +180 -0
- package/src/utils.js +119 -0
- package/templates/CLAUDE.md +99 -0
- package/templates/ruflo-setup.md +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @mfjjs/ruflo-setup
|
|
2
|
+
|
|
3
|
+
Cross-platform npm CLI to bootstrap a project with Ruflo on Windows and Linux.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
`@mfjjs/ruflo-setup` replaces the old PowerShell-only bootstrap script with a Node-based CLI command:
|
|
8
|
+
|
|
9
|
+
- Package name: `@mfjjs/ruflo-setup`
|
|
10
|
+
- Command name: `ruflo-setup`
|
|
11
|
+
- Platform support: Windows and Linux (plus macOS by default)
|
|
12
|
+
|
|
13
|
+
## Project structure
|
|
14
|
+
|
|
15
|
+
- `package.json`: npm metadata, scripts, and `bin` mapping
|
|
16
|
+
- `bin/ruflo-setup.js`: executable entry file the shell runs
|
|
17
|
+
- `src/cli.js`: command router and argument handling
|
|
18
|
+
- `src/setup.js`: setup workflow (`init`, `.mcp.json`, template copy)
|
|
19
|
+
- `src/hooks.js`: global `check-ruflo` hook install/status
|
|
20
|
+
- `src/utils.js`: reusable filesystem and argument helpers
|
|
21
|
+
- `templates/CLAUDE.md`: bundled template copied into target project
|
|
22
|
+
- `claude-hooks/check-ruflo.cjs`: SessionStart hook payload
|
|
23
|
+
- `tests/cli.test.mjs`: smoke tests for CLI behavior
|
|
24
|
+
|
|
25
|
+
## What the command line calls
|
|
26
|
+
|
|
27
|
+
After install/link, `ruflo-setup` resolves to your package `bin` entry:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"bin": {
|
|
32
|
+
"ruflo-setup": "./bin/ruflo-setup.js"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The shell shim launches `bin/ruflo-setup.js`, which imports `src/cli.js`, which dispatches to setup or hook subcommands.
|
|
38
|
+
|
|
39
|
+
## How the CLI entry point works
|
|
40
|
+
|
|
41
|
+
Flow:
|
|
42
|
+
|
|
43
|
+
1. `ruflo-setup` is invoked.
|
|
44
|
+
2. npm/pnpm command shim runs `bin/ruflo-setup.js`.
|
|
45
|
+
3. `bin/ruflo-setup.js` forwards args to `runCli(...)`.
|
|
46
|
+
4. `src/cli.js` parses command and flags.
|
|
47
|
+
5. `src/setup.js` runs setup steps:
|
|
48
|
+
- optional `npx ruflo@latest init --full`
|
|
49
|
+
- writes platform-aware `.mcp.json`
|
|
50
|
+
- copies `templates/CLAUDE.md`
|
|
51
|
+
- installs global SessionStart hook (unless skipped)
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# full setup
|
|
57
|
+
ruflo-setup
|
|
58
|
+
|
|
59
|
+
# non-interactive
|
|
60
|
+
ruflo-setup --yes
|
|
61
|
+
|
|
62
|
+
# preview only
|
|
63
|
+
ruflo-setup --dry-run --skip-init
|
|
64
|
+
|
|
65
|
+
# skip global hook install
|
|
66
|
+
ruflo-setup --no-hooks
|
|
67
|
+
|
|
68
|
+
# hook operations
|
|
69
|
+
ruflo-setup hooks install
|
|
70
|
+
ruflo-setup hooks status
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Local development with pnpm
|
|
74
|
+
|
|
75
|
+
From this repository root (`setup-ruflo/`):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pnpm install
|
|
79
|
+
pnpm test
|
|
80
|
+
pnpm run test:cli
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Link locally so command works everywhere
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# from setup-ruflo/
|
|
87
|
+
pnpm link --global
|
|
88
|
+
|
|
89
|
+
# now use from any folder
|
|
90
|
+
ruflo-setup --dry-run --skip-init
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This is the fast edit loop: change files in `src/`, rerun `ruflo-setup`, and behavior updates immediately without reinstall.
|
|
94
|
+
|
|
95
|
+
## Simulate a real install (deploy-style testing)
|
|
96
|
+
|
|
97
|
+
Create a tarball and install it into a clean test location.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# from setup-ruflo/
|
|
101
|
+
pnpm pack
|
|
102
|
+
|
|
103
|
+
# then in a clean temp folder
|
|
104
|
+
pnpm add -g ./mfjjs-ruflo-setup-0.1.0.tgz
|
|
105
|
+
ruflo-setup --dry-run --skip-init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This tests exactly what users get from a package install.
|
|
109
|
+
|
|
110
|
+
## Global hook behavior
|
|
111
|
+
|
|
112
|
+
`ruflo-setup` installs a global Claude SessionStart command hook that runs:
|
|
113
|
+
|
|
114
|
+
- `claude-hooks/check-ruflo.cjs`
|
|
115
|
+
|
|
116
|
+
It merges into existing global settings instead of replacing them, and creates a backup of the settings file before writing.
|
|
117
|
+
|
|
118
|
+
## Legacy note
|
|
119
|
+
|
|
120
|
+
`setup-ruflo.ps1` remains in the repository as historical reference, but the npm CLI is now the primary path.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* check-ruflo.cjs
|
|
4
|
+
* Global Claude Code SessionStart hook.
|
|
5
|
+
* Checks if the current project has Ruflo configured.
|
|
6
|
+
* If not, prints a notice that Claude sees as a <system-reminder>.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const mcpJson = path.join(cwd, '.mcp.json');
|
|
16
|
+
const claudeSettings = path.join(cwd, '.claude', 'settings.json');
|
|
17
|
+
|
|
18
|
+
const isConfigured = fs.existsSync(mcpJson) || fs.existsSync(claudeSettings);
|
|
19
|
+
|
|
20
|
+
if (!isConfigured) {
|
|
21
|
+
const msg =
|
|
22
|
+
'[RUFLO] This project is not configured with Ruflo + Claude Flow. ' +
|
|
23
|
+
'IMPORTANT: Before responding to anything else, tell the user this project is not configured with Ruflo + Claude Flow ' +
|
|
24
|
+
'and offer to set it up via /ruflo-setup.\n';
|
|
25
|
+
process.stdout.write(msg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.exit(0);
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mfjjs/ruflo-setup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-platform setup CLI for Ruflo + Claude Flow projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ruflo-setup": "./bin/ruflo-setup.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates",
|
|
13
|
+
"claude-hooks"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20.0.0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ruflo",
|
|
20
|
+
"claude-flow",
|
|
21
|
+
"cli",
|
|
22
|
+
"setup"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"lint": "node --check ./src/cli.js && node --check ./src/setup.js && node --check ./src/hooks.js && node --check ./src/utils.js && node --check ./bin/ruflo-setup.js",
|
|
30
|
+
"test": "node --test tests/cli.test.mjs",
|
|
31
|
+
"test:cli": "node ./bin/ruflo-setup.js --dry-run --skip-init --no-hooks --yes",
|
|
32
|
+
"pack:dry": "pnpm pack --dry-run"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { parseArgs } from './utils.js';
|
|
4
|
+
import { runSetup } from './setup.js';
|
|
5
|
+
import { getGlobalHookStatus, installGlobalCheckRufloHook } from './hooks.js';
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
process.stdout.write(`
|
|
9
|
+
@mfjjs/ruflo-setup
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
ruflo-setup [options]
|
|
13
|
+
ruflo-setup hooks install [options]
|
|
14
|
+
ruflo-setup hooks status
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--force, -f Overwrite existing config without prompt
|
|
18
|
+
--dry-run Show actions without making changes
|
|
19
|
+
--yes, -y Non-interactive yes for prompts
|
|
20
|
+
--no-hooks Skip global hook installation during setup
|
|
21
|
+
--skip-init Skip 'npx ruflo@latest init --full'
|
|
22
|
+
--verbose, -v Extra output
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
ruflo-setup
|
|
26
|
+
ruflo-setup --dry-run --skip-init
|
|
27
|
+
ruflo-setup hooks status
|
|
28
|
+
ruflo-setup hooks install --dry-run
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function packageRootFromModule() {
|
|
33
|
+
const filename = fileURLToPath(import.meta.url);
|
|
34
|
+
return path.join(path.dirname(filename), '..');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runCli(argv, cwd) {
|
|
38
|
+
try {
|
|
39
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
40
|
+
printHelp();
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const packageRoot = packageRootFromModule();
|
|
45
|
+
const flags = parseArgs(argv);
|
|
46
|
+
|
|
47
|
+
if (flags.command === 'hooks') {
|
|
48
|
+
const subcommand = argv[1] || 'status';
|
|
49
|
+
if (subcommand === 'status') {
|
|
50
|
+
const status = getGlobalHookStatus({ packageRoot });
|
|
51
|
+
process.stdout.write(`Hook installed: ${status.installed ? 'yes' : 'no'}\n`);
|
|
52
|
+
process.stdout.write(`Settings path: ${status.settingsPath}\n`);
|
|
53
|
+
process.stdout.write(`Reason: ${status.reason}\n`);
|
|
54
|
+
process.stdout.write(`Command: ${status.hookCommand}\n`);
|
|
55
|
+
return status.installed ? 0 : 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (subcommand === 'install') {
|
|
59
|
+
const result = installGlobalCheckRufloHook({ packageRoot, dryRun: flags.dryRun });
|
|
60
|
+
process.stdout.write(`${flags.dryRun ? '[DRY RUN] ' : ''}${result.inserted ? 'Hook installed' : 'Hook already present'}\n`);
|
|
61
|
+
process.stdout.write(`Settings path: ${result.settingsPath}\n`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stderr.write(`Unknown hooks subcommand: ${subcommand}\n`);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await runSetup({
|
|
70
|
+
cwd,
|
|
71
|
+
packageRoot,
|
|
72
|
+
force: flags.force,
|
|
73
|
+
dryRun: flags.dryRun,
|
|
74
|
+
yes: flags.yes,
|
|
75
|
+
noHooks: flags.noHooks,
|
|
76
|
+
skipInit: flags.skipInit,
|
|
77
|
+
verbose: flags.verbose
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return 0;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/hooks.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { readJsonSafe, writeJson } from './utils.js';
|
|
5
|
+
|
|
6
|
+
function defaultGlobalClaudeSettingsPath() {
|
|
7
|
+
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function ensureSessionStartHook(settings, hookCommand) {
|
|
11
|
+
const next = settings;
|
|
12
|
+
if (!next.hooks || typeof next.hooks !== 'object') {
|
|
13
|
+
next.hooks = {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!Array.isArray(next.hooks.SessionStart)) {
|
|
17
|
+
next.hooks.SessionStart = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const sessionStart = next.hooks.SessionStart;
|
|
21
|
+
if (sessionStart.length === 0) {
|
|
22
|
+
sessionStart.push({ hooks: [] });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const firstGroup = sessionStart[0];
|
|
26
|
+
if (!Array.isArray(firstGroup.hooks)) {
|
|
27
|
+
firstGroup.hooks = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const alreadyExists = firstGroup.hooks.some((h) => h && h.type === 'command' && h.command === hookCommand);
|
|
31
|
+
if (!alreadyExists) {
|
|
32
|
+
firstGroup.hooks.unshift({
|
|
33
|
+
type: 'command',
|
|
34
|
+
command: hookCommand,
|
|
35
|
+
timeout: 5000
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return !alreadyExists;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function installGlobalCheckRufloHook({
|
|
43
|
+
packageRoot,
|
|
44
|
+
dryRun = false,
|
|
45
|
+
globalSettingsPath
|
|
46
|
+
}) {
|
|
47
|
+
const resolvedSettingsPath = globalSettingsPath || process.env.CLAUDE_SETTINGS_PATH || defaultGlobalClaudeSettingsPath();
|
|
48
|
+
const hookScriptPath = path.join(packageRoot, 'claude-hooks', 'check-ruflo.cjs');
|
|
49
|
+
const hookCommand = `node "${hookScriptPath}"`;
|
|
50
|
+
|
|
51
|
+
const exists = fs.existsSync(resolvedSettingsPath);
|
|
52
|
+
const settings = readJsonSafe(resolvedSettingsPath, {});
|
|
53
|
+
|
|
54
|
+
const inserted = ensureSessionStartHook(settings, hookCommand);
|
|
55
|
+
|
|
56
|
+
if (!dryRun) {
|
|
57
|
+
if (exists) {
|
|
58
|
+
const backupPath = `${resolvedSettingsPath}.bak`;
|
|
59
|
+
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
60
|
+
fs.copyFileSync(resolvedSettingsPath, backupPath);
|
|
61
|
+
}
|
|
62
|
+
writeJson(resolvedSettingsPath, settings);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
settingsPath: resolvedSettingsPath,
|
|
67
|
+
hookCommand,
|
|
68
|
+
inserted,
|
|
69
|
+
existed: exists
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getGlobalHookStatus({ packageRoot, globalSettingsPath }) {
|
|
74
|
+
const resolvedSettingsPath = globalSettingsPath || process.env.CLAUDE_SETTINGS_PATH || defaultGlobalClaudeSettingsPath();
|
|
75
|
+
const hookScriptPath = path.join(packageRoot, 'claude-hooks', 'check-ruflo.cjs');
|
|
76
|
+
const hookCommand = `node "${hookScriptPath}"`;
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(resolvedSettingsPath)) {
|
|
79
|
+
return {
|
|
80
|
+
installed: false,
|
|
81
|
+
reason: 'global settings file does not exist',
|
|
82
|
+
settingsPath: resolvedSettingsPath,
|
|
83
|
+
hookCommand
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const settings = readJsonSafe(resolvedSettingsPath, {});
|
|
88
|
+
const sessionStart = settings?.hooks?.SessionStart;
|
|
89
|
+
if (!Array.isArray(sessionStart)) {
|
|
90
|
+
return {
|
|
91
|
+
installed: false,
|
|
92
|
+
reason: 'SessionStart hooks are missing',
|
|
93
|
+
settingsPath: resolvedSettingsPath,
|
|
94
|
+
hookCommand
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const found = sessionStart.some((group) => Array.isArray(group?.hooks) && group.hooks.some((hook) => hook?.type === 'command' && hook?.command === hookCommand));
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
installed: found,
|
|
102
|
+
reason: found ? 'hook found' : 'hook command not found in SessionStart hooks',
|
|
103
|
+
settingsPath: resolvedSettingsPath,
|
|
104
|
+
hookCommand
|
|
105
|
+
};
|
|
106
|
+
}
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
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 runNpxInit({ force, cwd, dryRun }) {
|
|
13
|
+
const args = ['ruflo@latest', 'init', '--full'];
|
|
14
|
+
if (force) {
|
|
15
|
+
args.push('--force');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (dryRun) {
|
|
19
|
+
logLine(` [DRY RUN] Would run: npx ${args.join(' ')}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = spawnSync('npx', args, {
|
|
24
|
+
cwd,
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
shell: process.platform === 'win32'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (result.status !== 0) {
|
|
30
|
+
throw new Error(`ruflo init failed with exit code ${result.status}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeMcpJson({ cwd, dryRun }) {
|
|
35
|
+
const mcpPath = path.join(cwd, '.mcp.json');
|
|
36
|
+
const mcpConfig = toPlatformMcpConfig(process.platform);
|
|
37
|
+
|
|
38
|
+
if (dryRun) {
|
|
39
|
+
const action = pathExists(mcpPath) ? 'overwrite' : 'write';
|
|
40
|
+
logLine(` [DRY RUN] Would ${action}: ${mcpPath}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
writeJson(mcpPath, mcpConfig);
|
|
45
|
+
logLine(' .mcp.json written for this platform.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function copyTemplateClaude({ cwd, force, dryRun, templatePath, yes }) {
|
|
49
|
+
const destination = path.join(cwd, 'CLAUDE.md');
|
|
50
|
+
|
|
51
|
+
if (pathExists(destination) && !force && !dryRun && !yes) {
|
|
52
|
+
logLine(' WARNING: CLAUDE.md already exists.');
|
|
53
|
+
const shouldOverwrite = await confirm(' Overwrite with template? [y/N] ');
|
|
54
|
+
if (!shouldOverwrite) {
|
|
55
|
+
logLine(' Skipped CLAUDE.md (kept existing).');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (dryRun) {
|
|
61
|
+
if (pathExists(destination)) {
|
|
62
|
+
logLine(` [DRY RUN] Would overwrite: ${destination}`);
|
|
63
|
+
} else {
|
|
64
|
+
logLine(` [DRY RUN] Would copy CLAUDE.md to: ${destination}`);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
copyFileSync(templatePath, destination);
|
|
70
|
+
if (pathExists(destination)) {
|
|
71
|
+
logLine(' CLAUDE.md copied from template.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function installGlobalCommand({ packageRoot, dryRun }) {
|
|
76
|
+
const src = path.join(packageRoot, 'templates', 'ruflo-setup.md');
|
|
77
|
+
const dest = path.join(os.homedir(), '.claude', 'commands', 'ruflo-setup.md');
|
|
78
|
+
|
|
79
|
+
if (dryRun) {
|
|
80
|
+
logLine(` [DRY RUN] Would write: ${dest}`);
|
|
81
|
+
return { dest };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
copyFileSync(src, dest);
|
|
85
|
+
return { dest };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isAlreadyConfigured(cwd) {
|
|
89
|
+
return pathExists(path.join(cwd, '.mcp.json')) || pathExists(path.join(cwd, '.claude', 'settings.json'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function runSetup({
|
|
93
|
+
cwd,
|
|
94
|
+
packageRoot,
|
|
95
|
+
force = false,
|
|
96
|
+
dryRun = false,
|
|
97
|
+
skipInit = false,
|
|
98
|
+
noHooks = false,
|
|
99
|
+
yes = false,
|
|
100
|
+
verbose = false
|
|
101
|
+
}) {
|
|
102
|
+
const templateClaude = path.join(packageRoot, 'templates', 'CLAUDE.md');
|
|
103
|
+
|
|
104
|
+
if (!fs.existsSync(templateClaude)) {
|
|
105
|
+
throw new Error(`Template CLAUDE.md not found at: ${templateClaude}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
logLine('');
|
|
109
|
+
logLine('Ruflo Setup (npm CLI)');
|
|
110
|
+
logLine(`Target directory: ${cwd}`);
|
|
111
|
+
if (dryRun) {
|
|
112
|
+
logLine('[DRY RUN - no changes will be made]');
|
|
113
|
+
}
|
|
114
|
+
logLine('');
|
|
115
|
+
|
|
116
|
+
if (isAlreadyConfigured(cwd) && !force && !yes) {
|
|
117
|
+
logLine('WARNING: This project already has Ruflo configuration.');
|
|
118
|
+
const shouldOverwrite = await confirm('Overwrite existing configuration? [y/N] ');
|
|
119
|
+
if (!shouldOverwrite) {
|
|
120
|
+
logLine('Aborted. No changes made.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!skipInit) {
|
|
126
|
+
logLine('Step 1: Running npx ruflo@latest init --full ...');
|
|
127
|
+
runNpxInit({ force, cwd, dryRun });
|
|
128
|
+
if (!dryRun) {
|
|
129
|
+
logLine(' ruflo init completed.');
|
|
130
|
+
}
|
|
131
|
+
logLine('');
|
|
132
|
+
} else {
|
|
133
|
+
logLine('Step 1: Skipped ruflo init (--skip-init).');
|
|
134
|
+
logLine('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
logLine('Step 2: Writing platform-aware .mcp.json ...');
|
|
138
|
+
writeMcpJson({ cwd, dryRun });
|
|
139
|
+
logLine('');
|
|
140
|
+
|
|
141
|
+
logLine('Step 3: Copying template CLAUDE.md ...');
|
|
142
|
+
await copyTemplateClaude({ cwd, force, dryRun, templatePath: templateClaude, yes });
|
|
143
|
+
logLine('');
|
|
144
|
+
|
|
145
|
+
if (!noHooks) {
|
|
146
|
+
logLine('Step 4: Installing global SessionStart check-ruflo hook ...');
|
|
147
|
+
const hookResult = installGlobalCheckRufloHook({ packageRoot, dryRun });
|
|
148
|
+
if (hookResult.inserted) {
|
|
149
|
+
logLine(` Hook installed in: ${hookResult.settingsPath}`);
|
|
150
|
+
} else {
|
|
151
|
+
logLine(` Hook already present in: ${hookResult.settingsPath}`);
|
|
152
|
+
}
|
|
153
|
+
if (verbose) {
|
|
154
|
+
logLine(` Hook command: ${hookResult.hookCommand}`);
|
|
155
|
+
}
|
|
156
|
+
logLine('');
|
|
157
|
+
} else {
|
|
158
|
+
logLine('Step 4: Skipped hook installation (--no-hooks).');
|
|
159
|
+
logLine('');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
logLine('Step 5: Installing global /ruflo-setup command ...');
|
|
163
|
+
const commandResult = installGlobalCommand({ packageRoot, dryRun });
|
|
164
|
+
if (!dryRun) {
|
|
165
|
+
logLine(` Command installed at: ${commandResult.dest}`);
|
|
166
|
+
}
|
|
167
|
+
logLine('');
|
|
168
|
+
|
|
169
|
+
if (dryRun) {
|
|
170
|
+
logLine('Dry run complete. No changes were made.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logLine('Setup complete!');
|
|
175
|
+
logLine('');
|
|
176
|
+
logLine('Next steps:');
|
|
177
|
+
logLine(' 1. Edit CLAUDE.md for project-specific Build & Test commands');
|
|
178
|
+
logLine(' 2. Run: claude');
|
|
179
|
+
logLine(' 3. Verify hooks: ruflo-setup hooks status');
|
|
180
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
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' || item === '-v') 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 = ['-y'];
|
|
77
|
+
|
|
78
|
+
const makeArgs = (pkg, extraArgs) => {
|
|
79
|
+
if (isWindows) {
|
|
80
|
+
return ['/c', 'npx', ...npxArgs, pkg, ...extraArgs];
|
|
81
|
+
}
|
|
82
|
+
return [...npxArgs, pkg, ...extraArgs];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
mcpServers: {
|
|
87
|
+
'claude-flow': {
|
|
88
|
+
command,
|
|
89
|
+
args: makeArgs('@claude-flow/cli@latest', ['mcp', 'start']),
|
|
90
|
+
env: {
|
|
91
|
+
npm_config_update_notifier: 'false',
|
|
92
|
+
CLAUDE_FLOW_MODE: 'v3',
|
|
93
|
+
CLAUDE_FLOW_HOOKS_ENABLED: 'true',
|
|
94
|
+
CLAUDE_FLOW_TOPOLOGY: 'hierarchical-mesh',
|
|
95
|
+
CLAUDE_FLOW_MAX_AGENTS: '15',
|
|
96
|
+
CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid'
|
|
97
|
+
},
|
|
98
|
+
autoStart: false
|
|
99
|
+
},
|
|
100
|
+
'ruv-swarm': {
|
|
101
|
+
command,
|
|
102
|
+
args: makeArgs('ruv-swarm', ['mcp', 'start']),
|
|
103
|
+
env: {
|
|
104
|
+
npm_config_update_notifier: 'false'
|
|
105
|
+
},
|
|
106
|
+
optional: true
|
|
107
|
+
},
|
|
108
|
+
'flow-nexus': {
|
|
109
|
+
command,
|
|
110
|
+
args: makeArgs('flow-nexus@latest', ['mcp', 'start']),
|
|
111
|
+
env: {
|
|
112
|
+
npm_config_update_notifier: 'false'
|
|
113
|
+
},
|
|
114
|
+
optional: true,
|
|
115
|
+
requiresAuth: true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Claude Code Configuration - Claude Flow V3
|
|
2
|
+
|
|
3
|
+
## Behavioral Rules (Always Enforced)
|
|
4
|
+
|
|
5
|
+
- Do what has been asked; nothing more, nothing less
|
|
6
|
+
- NEVER create files unless they're absolutely necessary for achieving your goal
|
|
7
|
+
- ALWAYS prefer editing an existing file to creating a new one
|
|
8
|
+
- NEVER proactively create documentation files (*.md) or README files unless explicitly requested
|
|
9
|
+
- NEVER save working files, text/mds, or tests to the root folder
|
|
10
|
+
- Never continuously check status after spawning a swarm — wait for results
|
|
11
|
+
- ALWAYS read a file before editing it
|
|
12
|
+
- NEVER commit secrets, credentials, or .env files
|
|
13
|
+
|
|
14
|
+
## File Organization
|
|
15
|
+
|
|
16
|
+
- NEVER save to root folder — use the directories below
|
|
17
|
+
- Use `/src` for source code files
|
|
18
|
+
- Use `/tests` for test files
|
|
19
|
+
- Use `/docs` for documentation and markdown files
|
|
20
|
+
- Use `/config` for configuration files
|
|
21
|
+
- Use `/scripts` for utility scripts
|
|
22
|
+
- Use `/examples` for example code
|
|
23
|
+
|
|
24
|
+
## Project Architecture
|
|
25
|
+
|
|
26
|
+
- Follow Domain-Driven Design with bounded contexts
|
|
27
|
+
- Keep files under 500 lines
|
|
28
|
+
- Use typed interfaces for all public APIs
|
|
29
|
+
- Prefer TDD London School (mock-first) for new code
|
|
30
|
+
- Use event sourcing for state changes
|
|
31
|
+
- Ensure input validation at system boundaries
|
|
32
|
+
|
|
33
|
+
### Project Config
|
|
34
|
+
|
|
35
|
+
- **Topology**: hierarchical-mesh
|
|
36
|
+
- **Max Agents**: 15
|
|
37
|
+
- **Memory**: hybrid
|
|
38
|
+
- **HNSW**: Enabled
|
|
39
|
+
- **Neural**: Enabled
|
|
40
|
+
|
|
41
|
+
## Build & Test
|
|
42
|
+
|
|
43
|
+
<!-- TODO: Replace with this project's actual build/test/lint commands -->
|
|
44
|
+
```bash
|
|
45
|
+
# Build
|
|
46
|
+
npm run build
|
|
47
|
+
|
|
48
|
+
# Test
|
|
49
|
+
npm test
|
|
50
|
+
|
|
51
|
+
# Lint
|
|
52
|
+
npm run lint
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- ALWAYS run tests after making code changes
|
|
56
|
+
- ALWAYS verify build succeeds before committing
|
|
57
|
+
|
|
58
|
+
## Security Rules
|
|
59
|
+
|
|
60
|
+
- NEVER hardcode API keys, secrets, or credentials in source files
|
|
61
|
+
- NEVER commit .env files or any file containing secrets
|
|
62
|
+
- Always validate user input at system boundaries
|
|
63
|
+
- Always sanitize file paths to prevent directory traversal
|
|
64
|
+
- Run `npx ruflo@latest security scan` after security-related changes
|
|
65
|
+
|
|
66
|
+
## Concurrency: 1 MESSAGE = ALL RELATED OPERATIONS
|
|
67
|
+
|
|
68
|
+
- All operations MUST be concurrent/parallel in a single message
|
|
69
|
+
- Use Claude Code's Task tool for spawning agents, not just MCP
|
|
70
|
+
- ALWAYS batch ALL todos in ONE TodoWrite call (5-10+ minimum)
|
|
71
|
+
- ALWAYS spawn ALL agents in ONE message with full instructions via Task tool
|
|
72
|
+
- ALWAYS batch ALL file reads/writes/edits in ONE message
|
|
73
|
+
- ALWAYS batch ALL Bash commands in ONE message
|
|
74
|
+
|
|
75
|
+
## Swarm Orchestration
|
|
76
|
+
|
|
77
|
+
- MUST initialize the swarm using CLI tools when starting complex tasks
|
|
78
|
+
- MUST spawn concurrent agents using Claude Code's Task tool
|
|
79
|
+
- Never use CLI tools alone for execution — Task tool agents do the actual work
|
|
80
|
+
- MUST call CLI tools AND Task tool in ONE message for complex work
|
|
81
|
+
|
|
82
|
+
## Quick Setup
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx ruflo@latest init --full
|
|
86
|
+
npx ruflo@latest daemon start
|
|
87
|
+
npx ruflo@latest doctor --fix
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Claude Code vs CLI Tools
|
|
91
|
+
|
|
92
|
+
- Claude Code's Task tool handles ALL execution: agents, file ops, code generation, git
|
|
93
|
+
- CLI tools handle coordination via Bash: swarm init, memory, hooks, routing
|
|
94
|
+
- NEVER use CLI tools as a substitute for Task tool agents
|
|
95
|
+
|
|
96
|
+
## Support
|
|
97
|
+
|
|
98
|
+
- Documentation: https://github.com/ruvnet/claude-flow
|
|
99
|
+
- Issues: https://github.com/ruvnet/claude-flow/issues
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# /ruflo-setup
|
|
2
|
+
|
|
3
|
+
Set up Ruflo + Claude Flow V3 in the current project directory.
|
|
4
|
+
|
|
5
|
+
## What this does
|
|
6
|
+
|
|
7
|
+
Runs `npx @mfjjs/ruflo-setup` which:
|
|
8
|
+
|
|
9
|
+
1. Runs `npx ruflo@latest init --full` to install:
|
|
10
|
+
- `.claude/settings.json` with hooks, permissions, and Claude Flow config
|
|
11
|
+
- `.claude/helpers/` — hook-handler, statusline, auto-memory scripts
|
|
12
|
+
- `.claude/agents/` — 120+ agent definitions
|
|
13
|
+
- `.claude/skills/` — 30+ skill definitions
|
|
14
|
+
- `.claude/commands/` — slash commands
|
|
15
|
+
2. Writes a platform-aware `.mcp.json` (MCP server registration for claude-flow, ruv-swarm, flow-nexus)
|
|
16
|
+
3. Copies a template `CLAUDE.md` for the project
|
|
17
|
+
4. Installs a global `SessionStart` hook in `~/.claude/settings.json` that warns when Ruflo is not configured
|
|
18
|
+
|
|
19
|
+
## Instructions for Claude
|
|
20
|
+
|
|
21
|
+
When the user runs /ruflo-setup:
|
|
22
|
+
|
|
23
|
+
1. Confirm the current working directory with the user
|
|
24
|
+
2. Check if `.mcp.json` already exists — if so, warn and ask before overwriting
|
|
25
|
+
3. Run the setup CLI:
|
|
26
|
+
```bash
|
|
27
|
+
npx @mfjjs/ruflo-setup
|
|
28
|
+
```
|
|
29
|
+
4. Report what was installed and remind the user to restart Claude Code to load the new MCP servers
|