@mfjjs/ruflo-setup 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.1.4](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.3...v0.1.4) (2026-03-11)
6
+
7
+
8
+ ### Features
9
+
10
+ * **hooks:** enhance hook status reporting with matched command and source info ([faca4f7](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/faca4f77a26581a2262548753fb15ca8ee9d4773))
11
+
12
+ ### [0.1.3](https://gitlab.mfj.local:8022/mario/setup-ruflo/compare/v0.1.2...v0.1.3) (2026-03-11)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **hooks:** update session start hook logic to prevent duplicate commands ([f81388e](https://gitlab.mfj.local:8022/mario/setup-ruflo/commit/f81388e46c4c91d72018e68365c4cc4c9831e778))
18
+
5
19
  ### [0.1.2](https://gitlab.mfj.local:8022/mario/setup-ruflo/compare/v0.1.1...v0.1.2) (2026-03-11)
6
20
 
7
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mfjjs/ruflo-setup",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Cross-platform setup CLI for Ruflo + Claude Flow projects",
5
5
  "type": "module",
6
6
  "bin": {
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 'npx ruflo@latest 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
- process.stdout.write(`Settings path: ${status.settingsPath}\n`);
67
- process.stdout.write(`Reason: ${status.reason}\n`);
68
- process.stdout.write(`Command: ${status.hookCommand}\n`);
69
- return status.installed ? 0 : 1;
70
- }
71
-
72
- if (subcommand === 'install') {
73
- const result = installGlobalCheckRufloHook({ packageRoot, dryRun: flags.dryRun });
74
- process.stdout.write(`${flags.dryRun ? '[DRY RUN] ' : ''}${result.inserted ? 'Hook installed' : 'Hook already present'}\n`);
75
- process.stdout.write(`Settings path: ${result.settingsPath}\n`);
76
- return 0;
77
- }
78
-
79
- process.stderr.write(`Unknown hooks subcommand: ${subcommand}\n`);
80
- return 1;
81
- }
82
-
83
- await runSetup({
84
- cwd,
85
- packageRoot,
86
- force: flags.force,
87
- dryRun: flags.dryRun,
88
- yes: flags.yes,
89
- noHooks: flags.noHooks,
90
- skipInit: flags.skipInit,
91
- verbose: flags.verbose
92
- });
93
-
94
- return 0;
95
- } catch (error) {
96
- process.stderr.write(`Error: ${error.message}\n`);
97
- return 1;
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 'npx ruflo@latest 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,106 +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
- 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
- }
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
+ }