@mfjjs/ruflo-setup 0.1.3 → 0.1.5
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 +15 -0
- package/README.md +26 -5
- package/package.json +1 -1
- package/src/cli.js +104 -99
- package/src/hooks.js +198 -109
- package/src/setup.js +72 -9
- package/src/utils.js +3 -6
- package/templates/ruflo-setup.md +29 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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.1.5](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.4...v0.1.5) (2026-03-11)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **cli:** update commands to use pnpm instead of npx for installation and initialization ([5d7e482](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/5d7e482beaae9b3adfdf2a4c6ea478e8cf6774cb))
|
|
11
|
+
* **setup:** enhance pnpm installation instructions and ensure availability checks ([b89cccc](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/b89cccc978b7e439c6544195c7d480bb95ef07af))
|
|
12
|
+
|
|
13
|
+
### [0.1.4](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.3...v0.1.4) (2026-03-11)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **hooks:** enhance hook status reporting with matched command and source info ([faca4f7](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/faca4f77a26581a2262548753fb15ca8ee9d4773))
|
|
19
|
+
|
|
5
20
|
### [0.1.3](https://gitlab.mfj.local:8022/mario/setup-ruflo/compare/v0.1.2...v0.1.3) (2026-03-11)
|
|
6
21
|
|
|
7
22
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,31 @@ Cross-platform npm CLI to bootstrap a project with Ruflo on Windows and Linux.
|
|
|
10
10
|
- Command name: `ruflo-setup`
|
|
11
11
|
- Platform support: Windows and Linux (plus macOS by default)
|
|
12
12
|
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js 20+
|
|
16
|
+
- pnpm available on PATH
|
|
17
|
+
|
|
18
|
+
Quickest pnpm install by platform:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Windows (recommended)
|
|
22
|
+
winget install -e --id pnpm.pnpm
|
|
23
|
+
|
|
24
|
+
# macOS (recommended)
|
|
25
|
+
brew install pnpm
|
|
26
|
+
|
|
27
|
+
# Linux (recommended)
|
|
28
|
+
curl -fsSL https://get.pnpm.io/install.sh | sh -
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Alternative (all platforms with recent Node.js):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
corepack enable
|
|
35
|
+
corepack prepare pnpm@latest --activate
|
|
36
|
+
```
|
|
37
|
+
|
|
13
38
|
## Project structure
|
|
14
39
|
|
|
15
40
|
- `package.json`: npm metadata, scripts, and `bin` mapping
|
|
@@ -45,7 +70,7 @@ Flow:
|
|
|
45
70
|
3. `bin/ruflo-setup.js` forwards args to `runCli(...)`.
|
|
46
71
|
4. `src/cli.js` parses command and flags.
|
|
47
72
|
5. `src/setup.js` runs setup steps:
|
|
48
|
-
- optional `
|
|
73
|
+
- optional `pnpm add -g ruflo@latest` then `ruflo init --full`
|
|
49
74
|
- writes platform-aware `.mcp.json`
|
|
50
75
|
- copies `templates/CLAUDE.md`
|
|
51
76
|
- installs global SessionStart hook (unless skipped)
|
|
@@ -114,7 +139,3 @@ This tests exactly what users get from a package install.
|
|
|
114
139
|
- `claude-hooks/check-ruflo.cjs`
|
|
115
140
|
|
|
116
141
|
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.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,99 +1,104 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { parseArgs } from './utils.js';
|
|
5
|
-
import { runSetup } from './setup.js';
|
|
6
|
-
import { getGlobalHookStatus, installGlobalCheckRufloHook } from './hooks.js';
|
|
7
|
-
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
10
|
-
function printVersion() {
|
|
11
|
-
const { version } = require('../package.json');
|
|
12
|
-
process.stdout.write(`${version}\n`);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function printHelp() {
|
|
16
|
-
process.stdout.write(`
|
|
17
|
-
@mfjjs/ruflo-setup
|
|
18
|
-
|
|
19
|
-
Usage:
|
|
20
|
-
ruflo-setup [options]
|
|
21
|
-
ruflo-setup hooks install [options]
|
|
22
|
-
ruflo-setup hooks status
|
|
23
|
-
|
|
24
|
-
Options:
|
|
25
|
-
--force, -f Overwrite existing config without prompt
|
|
26
|
-
--dry-run Show actions without making changes
|
|
27
|
-
--yes, -y Non-interactive yes for prompts
|
|
28
|
-
--no-hooks Skip global hook installation during setup
|
|
29
|
-
--skip-init Skip '
|
|
30
|
-
--version, -v Print version and exit
|
|
31
|
-
--verbose Extra output
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
ruflo-setup
|
|
35
|
-
ruflo-setup --dry-run --skip-init
|
|
36
|
-
ruflo-setup hooks status
|
|
37
|
-
ruflo-setup hooks install --dry-run
|
|
38
|
-
`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function packageRootFromModule() {
|
|
42
|
-
const filename = fileURLToPath(import.meta.url);
|
|
43
|
-
return path.join(path.dirname(filename), '..');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function runCli(argv, cwd) {
|
|
47
|
-
try {
|
|
48
|
-
if (argv.includes('--version') || argv.includes('-v')) {
|
|
49
|
-
printVersion();
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (argv.includes('--help') || argv.includes('-h')) {
|
|
54
|
-
printHelp();
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const packageRoot = packageRootFromModule();
|
|
59
|
-
const flags = parseArgs(argv);
|
|
60
|
-
|
|
61
|
-
if (flags.command === 'hooks') {
|
|
62
|
-
const subcommand = argv[1] || 'status';
|
|
63
|
-
if (subcommand === 'status') {
|
|
64
|
-
const status = getGlobalHookStatus({ packageRoot });
|
|
65
|
-
process.stdout.write(`Hook installed: ${status.installed ? 'yes' : 'no'}\n`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { parseArgs } from './utils.js';
|
|
5
|
+
import { runSetup } from './setup.js';
|
|
6
|
+
import { getGlobalHookStatus, installGlobalCheckRufloHook } from './hooks.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
function printVersion() {
|
|
11
|
+
const { version } = require('../package.json');
|
|
12
|
+
process.stdout.write(`${version}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printHelp() {
|
|
16
|
+
process.stdout.write(`
|
|
17
|
+
@mfjjs/ruflo-setup
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
ruflo-setup [options]
|
|
21
|
+
ruflo-setup hooks install [options]
|
|
22
|
+
ruflo-setup hooks status
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--force, -f Overwrite existing config without prompt
|
|
26
|
+
--dry-run Show actions without making changes
|
|
27
|
+
--yes, -y Non-interactive yes for prompts
|
|
28
|
+
--no-hooks Skip global hook installation during setup
|
|
29
|
+
--skip-init Skip 'pnpm add -g ruflo@latest && ruflo init --full'
|
|
30
|
+
--version, -v Print version and exit
|
|
31
|
+
--verbose Extra output
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
ruflo-setup
|
|
35
|
+
ruflo-setup --dry-run --skip-init
|
|
36
|
+
ruflo-setup hooks status
|
|
37
|
+
ruflo-setup hooks install --dry-run
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function packageRootFromModule() {
|
|
42
|
+
const filename = fileURLToPath(import.meta.url);
|
|
43
|
+
return path.join(path.dirname(filename), '..');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runCli(argv, cwd) {
|
|
47
|
+
try {
|
|
48
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
49
|
+
printVersion();
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
54
|
+
printHelp();
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const packageRoot = packageRootFromModule();
|
|
59
|
+
const flags = parseArgs(argv);
|
|
60
|
+
|
|
61
|
+
if (flags.command === 'hooks') {
|
|
62
|
+
const subcommand = argv[1] || 'status';
|
|
63
|
+
if (subcommand === 'status') {
|
|
64
|
+
const status = getGlobalHookStatus({ packageRoot });
|
|
65
|
+
process.stdout.write(`Hook installed: ${status.installed ? 'yes' : 'no'}\n`);
|
|
66
|
+
if (status.matchedHookPointingTo) {
|
|
67
|
+
process.stdout.write(`${status.matchedHookPointingTo}\n`);
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(`Settings path: ${status.settingsPath}\n`);
|
|
70
|
+
process.stdout.write(`Reason: ${status.reason}\n`);
|
|
71
|
+
if (status.matchedHookCommand) {
|
|
72
|
+
process.stdout.write(`Matched command: ${status.matchedHookCommand}\n`);
|
|
73
|
+
}
|
|
74
|
+
return status.installed ? 0 : 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (subcommand === 'install') {
|
|
78
|
+
const result = installGlobalCheckRufloHook({ packageRoot, dryRun: flags.dryRun });
|
|
79
|
+
process.stdout.write(`${flags.dryRun ? '[DRY RUN] ' : ''}${result.inserted ? 'Hook installed' : 'Hook already present'}\n`);
|
|
80
|
+
process.stdout.write(`Settings path: ${result.settingsPath}\n`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
process.stderr.write(`Unknown hooks subcommand: ${subcommand}\n`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await runSetup({
|
|
89
|
+
cwd,
|
|
90
|
+
packageRoot,
|
|
91
|
+
force: flags.force,
|
|
92
|
+
dryRun: flags.dryRun,
|
|
93
|
+
yes: flags.yes,
|
|
94
|
+
noHooks: flags.noHooks,
|
|
95
|
+
skipInit: flags.skipInit,
|
|
96
|
+
verbose: flags.verbose
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return 0;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/hooks.js
CHANGED
|
@@ -1,109 +1,198 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
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
|
+
const CHECK_RUFLO_HOOK_FILENAME = 'check-ruflo.cjs';
|
|
7
|
+
|
|
8
|
+
function defaultGlobalClaudeSettingsPath() {
|
|
9
|
+
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function hasCheckRufloHookFilename(command) {
|
|
13
|
+
if (typeof command !== 'string') return false;
|
|
14
|
+
return /check-ruflo\.cjs(?=["'\s]|$)/i.test(command);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractHookScriptPath(command) {
|
|
18
|
+
if (typeof command !== 'string') return null;
|
|
19
|
+
const quoted = command.match(/["']([^"']*check-ruflo\.cjs)["']/i);
|
|
20
|
+
if (quoted) return quoted[1];
|
|
21
|
+
const bare = command.match(/([^\s"']*check-ruflo\.cjs)/i);
|
|
22
|
+
return bare ? bare[1] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function inferHookSourceInfo(command, packageRoot) {
|
|
26
|
+
const scriptPath = extractHookScriptPath(command);
|
|
27
|
+
const normalizedScriptPath = scriptPath ? scriptPath.replace(/\\/g, '/').toLowerCase() : '';
|
|
28
|
+
const normalizedPath = scriptPath ? scriptPath.replace(/\\/g, '/') : '';
|
|
29
|
+
const normalizedPackageRoot = packageRoot ? packageRoot.replace(/\\/g, '/').toLowerCase() : '';
|
|
30
|
+
|
|
31
|
+
let source = 'unknown';
|
|
32
|
+
let packageRef = null;
|
|
33
|
+
let storeType = null;
|
|
34
|
+
|
|
35
|
+
if (normalizedScriptPath.includes('/_npx/')) {
|
|
36
|
+
source = 'npm/npx cache install';
|
|
37
|
+
storeType = 'npm/npx cache';
|
|
38
|
+
const m = normalizedPath.match(/_npx\/[^/]+\/node_modules\/((?:@[^/]+\/)?[^/]+)\/claude-hooks/i);
|
|
39
|
+
if (m) packageRef = m[1];
|
|
40
|
+
} else if (normalizedScriptPath.includes('/.pnpm/')) {
|
|
41
|
+
source = 'pnpm store install';
|
|
42
|
+
// scoped package: @scope+pkg@version
|
|
43
|
+
const pnpmScoped = normalizedPath.match(/\/\.pnpm\/((@[^/+]+)\+([^/@]+)@([^/]+))\//i);
|
|
44
|
+
if (pnpmScoped) {
|
|
45
|
+
packageRef = `${pnpmScoped[2]}/${pnpmScoped[3]}@${pnpmScoped[4]}`;
|
|
46
|
+
} else {
|
|
47
|
+
// unscoped: pkg@version
|
|
48
|
+
const pnpmUnscoped = normalizedPath.match(/\/\.pnpm\/([^/@+]+)@([^/]+)\//i);
|
|
49
|
+
if (pnpmUnscoped) packageRef = `${pnpmUnscoped[1]}@${pnpmUnscoped[2]}`;
|
|
50
|
+
}
|
|
51
|
+
storeType = normalizedScriptPath.includes('/pnpm/global/') ? 'pnpm global store' : 'pnpm store';
|
|
52
|
+
} else if (normalizedScriptPath.includes('/node_modules/')) {
|
|
53
|
+
source = 'node_modules install';
|
|
54
|
+
storeType = 'npm global';
|
|
55
|
+
const m = normalizedPath.match(/node_modules\/((?:@[^/]+\/)?[^/]+)\/claude-hooks/i);
|
|
56
|
+
if (m) packageRef = m[1];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isCurrentPackagePath = Boolean(
|
|
60
|
+
normalizedScriptPath && normalizedPackageRoot && normalizedScriptPath.startsWith(normalizedPackageRoot)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const versionMatch = command.match(/@mfjjs[+/\\]ruflo-setup@(\d+\.\d+\.\d+(?:[-+][^\\/"'\s]+)?)/i);
|
|
64
|
+
|
|
65
|
+
const pointingTo = packageRef && storeType
|
|
66
|
+
? `hook pointing to ${packageRef} from ${storeType}`
|
|
67
|
+
: packageRef
|
|
68
|
+
? `hook pointing to ${packageRef}`
|
|
69
|
+
: null;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
scriptPath,
|
|
73
|
+
source,
|
|
74
|
+
isCurrentPackagePath,
|
|
75
|
+
inferredVersion: versionMatch ? versionMatch[1] : null,
|
|
76
|
+
packageRef,
|
|
77
|
+
pointingTo
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function findCheckRufloHook(sessionStart) {
|
|
82
|
+
if (!Array.isArray(sessionStart)) return null;
|
|
83
|
+
for (let gi = 0; gi < sessionStart.length; gi += 1) {
|
|
84
|
+
const group = sessionStart[gi];
|
|
85
|
+
if (!Array.isArray(group?.hooks)) continue;
|
|
86
|
+
for (let hi = 0; hi < group.hooks.length; hi += 1) {
|
|
87
|
+
const hook = group.hooks[hi];
|
|
88
|
+
if (hasCheckRufloHookFilename(hook?.command)) {
|
|
89
|
+
return { hook, groupIndex: gi, hookIndex: hi, command: hook.command };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ensureSessionStartHook(settings, hookCommand) {
|
|
97
|
+
const next = settings;
|
|
98
|
+
if (!next.hooks || typeof next.hooks !== 'object') {
|
|
99
|
+
next.hooks = {};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!Array.isArray(next.hooks.SessionStart)) {
|
|
103
|
+
next.hooks.SessionStart = [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const sessionStart = next.hooks.SessionStart;
|
|
107
|
+
if (sessionStart.length === 0) {
|
|
108
|
+
sessionStart.push({ hooks: [] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const firstGroup = sessionStart[0];
|
|
112
|
+
if (!Array.isArray(firstGroup.hooks)) {
|
|
113
|
+
firstGroup.hooks = [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const newHook = { type: 'command', command: hookCommand, timeout: 5000 };
|
|
117
|
+
const existingIndex = firstGroup.hooks.findIndex((h) => hasCheckRufloHookFilename(h?.command));
|
|
118
|
+
|
|
119
|
+
if (existingIndex !== -1) {
|
|
120
|
+
const unchanged = firstGroup.hooks[existingIndex].command === hookCommand;
|
|
121
|
+
firstGroup.hooks[existingIndex] = newHook;
|
|
122
|
+
return unchanged ? false : true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
firstGroup.hooks.unshift(newHook);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function installGlobalCheckRufloHook({
|
|
130
|
+
packageRoot,
|
|
131
|
+
dryRun = false,
|
|
132
|
+
globalSettingsPath
|
|
133
|
+
}) {
|
|
134
|
+
const resolvedSettingsPath = globalSettingsPath || process.env.CLAUDE_SETTINGS_PATH || defaultGlobalClaudeSettingsPath();
|
|
135
|
+
const hookScriptPath = path.join(packageRoot, 'claude-hooks', 'check-ruflo.cjs');
|
|
136
|
+
const hookCommand = `node "${hookScriptPath}"`;
|
|
137
|
+
|
|
138
|
+
const exists = fs.existsSync(resolvedSettingsPath);
|
|
139
|
+
const settings = readJsonSafe(resolvedSettingsPath, {});
|
|
140
|
+
|
|
141
|
+
const inserted = ensureSessionStartHook(settings, hookCommand);
|
|
142
|
+
|
|
143
|
+
if (!dryRun) {
|
|
144
|
+
if (exists) {
|
|
145
|
+
const backupPath = `${resolvedSettingsPath}.bak`;
|
|
146
|
+
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
147
|
+
fs.copyFileSync(resolvedSettingsPath, backupPath);
|
|
148
|
+
}
|
|
149
|
+
writeJson(resolvedSettingsPath, settings);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
settingsPath: resolvedSettingsPath,
|
|
154
|
+
hookCommand,
|
|
155
|
+
inserted,
|
|
156
|
+
existed: exists
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getGlobalHookStatus({ packageRoot, globalSettingsPath }) {
|
|
161
|
+
const resolvedSettingsPath = globalSettingsPath || process.env.CLAUDE_SETTINGS_PATH || defaultGlobalClaudeSettingsPath();
|
|
162
|
+
const hookScriptPath = path.join(packageRoot, 'claude-hooks', 'check-ruflo.cjs');
|
|
163
|
+
const hookCommand = `node "${hookScriptPath}"`;
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(resolvedSettingsPath)) {
|
|
166
|
+
return {
|
|
167
|
+
installed: false,
|
|
168
|
+
reason: 'global settings file does not exist',
|
|
169
|
+
settingsPath: resolvedSettingsPath,
|
|
170
|
+
hookCommand
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const settings = readJsonSafe(resolvedSettingsPath, {});
|
|
175
|
+
const sessionStart = settings?.hooks?.SessionStart;
|
|
176
|
+
if (!Array.isArray(sessionStart)) {
|
|
177
|
+
return {
|
|
178
|
+
installed: false,
|
|
179
|
+
reason: 'SessionStart hooks are missing',
|
|
180
|
+
settingsPath: resolvedSettingsPath,
|
|
181
|
+
hookCommand
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const foundHook = findCheckRufloHook(sessionStart);
|
|
186
|
+
const found = Boolean(foundHook);
|
|
187
|
+
const sourceInfo = foundHook ? inferHookSourceInfo(foundHook.command, packageRoot) : null;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
installed: found,
|
|
191
|
+
reason: found ? `hook found by filename ${CHECK_RUFLO_HOOK_FILENAME}` : `hook command with filename ${CHECK_RUFLO_HOOK_FILENAME} not found in SessionStart hooks`,
|
|
192
|
+
settingsPath: resolvedSettingsPath,
|
|
193
|
+
hookCommand,
|
|
194
|
+
matchedHookCommand: foundHook?.command || null,
|
|
195
|
+
matchedHookSource: sourceInfo?.source || null,
|
|
196
|
+
matchedHookPointingTo: sourceInfo?.pointingTo || null
|
|
197
|
+
};
|
|
198
|
+
}
|
package/src/setup.js
CHANGED
|
@@ -8,25 +8,88 @@ function logLine(message) {
|
|
|
8
8
|
process.stdout.write(`${message}\n`);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
|
|
11
|
+
function getPnpmInstallSuggestions(platform) {
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
return [
|
|
14
|
+
'winget install -e --id pnpm.pnpm',
|
|
15
|
+
'corepack enable && corepack prepare pnpm@latest --activate',
|
|
16
|
+
'npm install -g pnpm'
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (platform === 'darwin') {
|
|
21
|
+
return [
|
|
22
|
+
'brew install pnpm',
|
|
23
|
+
'corepack enable && corepack prepare pnpm@latest --activate',
|
|
24
|
+
'npm install -g pnpm'
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return [
|
|
29
|
+
'curl -fsSL https://get.pnpm.io/install.sh | sh -',
|
|
30
|
+
'corepack enable && corepack prepare pnpm@latest --activate',
|
|
31
|
+
'npm install -g pnpm'
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensurePnpmAvailable() {
|
|
36
|
+
const check = spawnSync('pnpm', ['--version'], {
|
|
37
|
+
stdio: 'ignore',
|
|
38
|
+
shell: process.platform === 'win32'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (check.status === 0 && !check.error) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const platformLabel = process.platform === 'win32'
|
|
46
|
+
? 'Windows'
|
|
47
|
+
: process.platform === 'darwin'
|
|
48
|
+
? 'macOS'
|
|
49
|
+
: 'Linux';
|
|
50
|
+
const suggestions = getPnpmInstallSuggestions(process.platform)
|
|
51
|
+
.map((command) => ` - ${command}`)
|
|
52
|
+
.join('\n');
|
|
53
|
+
|
|
54
|
+
throw new Error(
|
|
55
|
+
`pnpm is required but was not found in PATH.\n` +
|
|
56
|
+
`Install pnpm, then re-run ruflo-setup.\n` +
|
|
57
|
+
`Quick install options for ${platformLabel}:\n${suggestions}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runPnpmInit({ force, cwd, dryRun }) {
|
|
62
|
+
const initArgs = ['init', '--full'];
|
|
13
63
|
if (force) {
|
|
14
|
-
|
|
64
|
+
initArgs.push('--force');
|
|
15
65
|
}
|
|
16
66
|
|
|
17
67
|
if (dryRun) {
|
|
18
|
-
logLine(` [DRY RUN] Would run:
|
|
68
|
+
logLine(` [DRY RUN] Would run: pnpm add -g ruflo@latest`);
|
|
69
|
+
logLine(` [DRY RUN] Would run: ruflo ${initArgs.join(' ')}`);
|
|
19
70
|
return;
|
|
20
71
|
}
|
|
21
72
|
|
|
22
|
-
|
|
73
|
+
ensurePnpmAvailable();
|
|
74
|
+
|
|
75
|
+
const install = spawnSync('pnpm', ['add', '-g', 'ruflo@latest'], {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: 'inherit',
|
|
78
|
+
shell: process.platform === 'win32'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (install.status !== 0) {
|
|
82
|
+
throw new Error(`pnpm add -g ruflo@latest failed with exit code ${install.status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const run = spawnSync('ruflo', initArgs, {
|
|
23
86
|
cwd,
|
|
24
87
|
stdio: 'inherit',
|
|
25
88
|
shell: process.platform === 'win32'
|
|
26
89
|
});
|
|
27
90
|
|
|
28
|
-
if (
|
|
29
|
-
throw new Error(`ruflo init failed with exit code ${
|
|
91
|
+
if (run.status !== 0) {
|
|
92
|
+
throw new Error(`ruflo init failed with exit code ${run.status}`);
|
|
30
93
|
}
|
|
31
94
|
}
|
|
32
95
|
|
|
@@ -89,8 +152,8 @@ export async function runSetup({
|
|
|
89
152
|
}
|
|
90
153
|
|
|
91
154
|
if (!skipInit) {
|
|
92
|
-
logLine('Step 1: Running
|
|
93
|
-
|
|
155
|
+
logLine('Step 1: Running pnpm add -g ruflo@latest && ruflo init --full ...');
|
|
156
|
+
runPnpmInit({ force, cwd, dryRun });
|
|
94
157
|
if (!dryRun) {
|
|
95
158
|
logLine(' ruflo init completed.');
|
|
96
159
|
}
|
package/src/utils.js
CHANGED
|
@@ -72,14 +72,11 @@ export function parseArgs(argv) {
|
|
|
72
72
|
|
|
73
73
|
export function toPlatformMcpConfig(platform) {
|
|
74
74
|
const isWindows = platform === 'win32';
|
|
75
|
-
const command = isWindows ? 'cmd' : '
|
|
76
|
-
const
|
|
75
|
+
const command = isWindows ? 'cmd' : 'pnpm';
|
|
76
|
+
const pnpmArgs = isWindows ? ['/c', 'pnpm', 'dlx'] : ['dlx'];
|
|
77
77
|
|
|
78
78
|
const makeArgs = (pkg, extraArgs) => {
|
|
79
|
-
|
|
80
|
-
return ['/c', 'npx', ...npxArgs, pkg, ...extraArgs];
|
|
81
|
-
}
|
|
82
|
-
return [...npxArgs, pkg, ...extraArgs];
|
|
79
|
+
return [...pnpmArgs, pkg, ...extraArgs];
|
|
83
80
|
};
|
|
84
81
|
|
|
85
82
|
return {
|
package/templates/ruflo-setup.md
CHANGED
|
@@ -2,11 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Set up Ruflo + Claude Flow V3 in the current project directory.
|
|
4
4
|
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 20+
|
|
8
|
+
- pnpm 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
|
+
|
|
5
30
|
## What this does
|
|
6
31
|
|
|
7
|
-
Runs `
|
|
32
|
+
Runs `pnpm add -g @mfjjs/ruflo-setup` then `ruflo-setup` which:
|
|
8
33
|
|
|
9
|
-
1. Runs `
|
|
34
|
+
1. Runs `pnpm add -g ruflo@latest` then `ruflo init --full` to install:
|
|
10
35
|
- `.claude/settings.json` with hooks, permissions, and Claude Flow config
|
|
11
36
|
- `.claude/helpers/` — hook-handler, statusline, auto-memory scripts
|
|
12
37
|
- `.claude/agents/` — 120+ agent definitions
|
|
@@ -23,6 +48,7 @@ When the user runs /ruflo-setup:
|
|
|
23
48
|
2. Check if `.mcp.json` already exists — if so, warn and ask before overwriting
|
|
24
49
|
3. Run the setup CLI:
|
|
25
50
|
```bash
|
|
26
|
-
|
|
51
|
+
pnpm add -g @mfjjs/ruflo-setup
|
|
52
|
+
ruflo-setup
|
|
27
53
|
```
|
|
28
54
|
4. Report what was installed and remind the user to restart Claude Code to load the new MCP servers
|