@mfjjs/ruflo-setup 0.1.9 β†’ 0.2.1

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,28 @@
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.1](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.2.0...v0.2.1) (2026-03-14)
6
+
7
+
8
+ ### Features
9
+
10
+ * **docs:** update usage section by moving status command first ([e98f473](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/e98f473f25f53031d8cf08cf8d6bd3bfedcae949))
11
+
12
+ ## [0.2.0](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.9...v0.2.0) (2026-03-14)
13
+
14
+
15
+ ### ⚠ BREAKING CHANGES
16
+
17
+ * **cli:** The CLI now includes a new command which may affect existing scripts that rely on the previous command structure.
18
+
19
+ ### Features
20
+
21
+ * **cli:** add 'status' command to check feature status ([d551664](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/d551664619635e7efbbc9a9810266ecfe2212d1c))
22
+ * **docs:** add detailed agents and skills sections to Ruflo benefit documentation ([c66d212](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/c66d212f4ee25390ab139e43131ecb548b7bcb5c))
23
+ * **docs:** add link to ruflo-benefits.md and renamed to plural ([2820aeb](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/2820aeb5cebbfad0ac7b568291aa55a49076c6c3))
24
+ * **docs:** update Ruflo benefit documentation with agents and skills sections ([8feb883](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/8feb8836c88ec5096a907eb4c842b0b3b4c55b4c))
25
+ * **status:** enhance directory checks to display count of agents and skills ([3ade22b](https://gitlab.mfj.local:8022/mario/ruflo-setup/commit/3ade22b10ba1840f405b8f239f2a723f6d4089d7))
26
+
5
27
  ### [0.1.9](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.8...v0.1.9) (2026-03-13)
6
28
 
7
29
  ### [0.1.8](https://gitlab.mfj.local:8022/mario/ruflo-setup/compare/v0.1.7...v0.1.8) (2026-03-13)
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Cross-platform npm CLI to bootstrap a project with Ruflo on Windows and Linux.
4
4
 
5
+ > **Deep dive:** [`docs/ruflo-benefits.md`](docs/ruflo-benefits.md) is a comprehensive reference (~900 lines) covering every feature layer, the MinCut/RuVector/RVF internals, all 80+ agents, 33 skills, 65 slash commands, and how agents and skills are invoked. Not a quick read.
6
+
5
7
  ## πŸ“˜ What this project is
6
8
 
7
9
  `@mfjjs/ruflo-setup` implements the setup with a Node-based CLI command:
@@ -58,6 +60,15 @@ You only need to do this once in each folder. Just run the command and you’re
58
60
 
59
61
  ## πŸš€ Usage
60
62
 
63
+ ### Status
64
+ Check whether all Ruflo feature layers (0–8) are enabled in the current project:
65
+
66
+ ```bash
67
+ ruflo-setup status
68
+ ```
69
+
70
+ This prints a layer-by-layer report showing which features are active β€” prerequisites, global packages, optional WASM/ML packages, MCP servers, tool groups, environment variables, project scaffolding, and the Docker chat UI stack.
71
+
61
72
  ### Bootstrap
62
73
  Use this once if you want Claude Code to expose the `/ruflo-setup` command globally.
63
74
 
@@ -96,6 +107,7 @@ ruflo-setup hooks status
96
107
  - `bin/ruflo-setup.js`: executable entry file the shell runs
97
108
  - `src/cli.js`: command router and argument handling
98
109
  - `src/setup.js`: setup workflow (`init`, `.mcp.json`, template copy)
110
+ - `src/status.js`: layer-by-layer feature status checker (Layers 0–8)
99
111
  - `src/hooks.js`: global `check-ruflo` hook install/status
100
112
  - `src/utils.js`: reusable filesystem and argument helpers
101
113
  - `templates/CLAUDE.md`: bundled template copied into target project
@@ -130,6 +142,8 @@ Flow:
130
142
  - copies `templates/CLAUDE.md`
131
143
  - installs global SessionStart hook (unless skipped)
132
144
 
145
+ When called as `ruflo-setup status`, step 5 dispatches to `src/status.js` which checks all layers (0–8) and prints a feature status report.
146
+
133
147
  ## πŸ› οΈ Local development with pnpm
134
148
 
135
149
  From this repository root (`setup-ruflo/`):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mfjjs/ruflo-setup",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Cross-platform setup CLI for Ruflo + Claude Flow projects",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -18,6 +18,7 @@ function printHelp() {
18
18
 
19
19
  Usage:
20
20
  ruflo-setup [options]
21
+ ruflo-setup status
21
22
  ruflo-setup hooks install [options]
22
23
  ruflo-setup hooks status
23
24
 
@@ -32,6 +33,7 @@ Options:
32
33
 
33
34
  Examples:
34
35
  ruflo-setup
36
+ ruflo-setup status
35
37
  ruflo-setup --dry-run --skip-init
36
38
  ruflo-setup hooks status
37
39
  ruflo-setup hooks install --dry-run
@@ -58,6 +60,12 @@ export async function runCli(argv, cwd) {
58
60
  const packageRoot = packageRootFromModule();
59
61
  const flags = parseArgs(argv);
60
62
 
63
+ if (flags.command === 'status') {
64
+ const { runStatus } = await import('./status.js');
65
+ await runStatus({ cwd, packageRoot });
66
+ return 0;
67
+ }
68
+
61
69
  if (flags.command === 'hooks') {
62
70
  const subcommand = argv[1] || 'status';
63
71
  if (subcommand === 'status') {
package/src/status.js ADDED
@@ -0,0 +1,303 @@
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 { createRequire } from 'node:module';
6
+ import { readJsonSafe } from './utils.js';
7
+ import { getGlobalHookStatus } from './hooks.js';
8
+
9
+ const require = createRequire(import.meta.url);
10
+ const OK = '[OK]';
11
+ const MISS = '[--]';
12
+ const ERR = '[!!]';
13
+ const IS_WIN = process.platform === 'win32';
14
+
15
+ function spawn(cmd, args) {
16
+ try {
17
+ return spawnSync(cmd, args, {
18
+ stdio: ['ignore', 'pipe', 'ignore'],
19
+ encoding: 'utf8',
20
+ shell: IS_WIN
21
+ });
22
+ } catch {
23
+ return { status: 1, stdout: '' };
24
+ }
25
+ }
26
+
27
+ function dirExists(p) {
28
+ try {
29
+ return fs.statSync(p).isDirectory();
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function fileExists(p) {
36
+ try {
37
+ return fs.statSync(p).isFile();
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ // Flatten npm/pnpm --json list result into a name->version map (1 level deep).
44
+ function buildPkgMap(jsonText) {
45
+ const map = {};
46
+ try {
47
+ const parsed = JSON.parse(jsonText || '{}');
48
+ // npm list -g returns an array with one element, pnpm returns an object
49
+ const root = Array.isArray(parsed) ? parsed[0] : parsed;
50
+ const deps = root?.dependencies ?? {};
51
+ for (const [name, info] of Object.entries(deps)) {
52
+ map[name] = info?.version ?? true;
53
+ for (const [nname, ninfo] of Object.entries(info?.dependencies ?? {})) {
54
+ if (!(nname in map)) map[nname] = ninfo?.version ?? true;
55
+ }
56
+ }
57
+ } catch {
58
+ // ignore parse errors
59
+ }
60
+ return map;
61
+ }
62
+
63
+ // Tries npm list -g first, falls back to pnpm list -g.
64
+ function getGlobalPkgMap() {
65
+ const npmRes = spawn('npm', ['list', '-g', '--depth=1', '--json']);
66
+ if (npmRes.status === 0 && npmRes.stdout) {
67
+ const m = buildPkgMap(npmRes.stdout);
68
+ if (Object.keys(m).length > 0) return m;
69
+ }
70
+ const pnpmRes = spawn('pnpm', ['list', '-g', '--depth=1', '--json']);
71
+ if (pnpmRes.status === 0 && pnpmRes.stdout) {
72
+ return buildPkgMap(pnpmRes.stdout);
73
+ }
74
+ return {};
75
+ }
76
+
77
+ // Each checkLayer* returns { lines: string[], ok: number, total: number }
78
+
79
+ function checkLayer0() {
80
+ const lines = [];
81
+ let ok = 0;
82
+ const nodeMajor = parseInt(process.version.slice(1), 10);
83
+ if (nodeMajor >= 20) { lines.push(` ${OK} Node.js ${process.version} (>=20 required)`); ok += 1; }
84
+ else { lines.push(` ${ERR} Node.js ${process.version} (>=20 required β€” upgrade Node.js)`); }
85
+
86
+ const pnpmRes = spawn('pnpm', ['--version']);
87
+ if (pnpmRes.status === 0 && pnpmRes.stdout.trim()) {
88
+ lines.push(` ${OK} pnpm ${pnpmRes.stdout.trim()}`); ok += 1;
89
+ } else {
90
+ lines.push(` ${MISS} pnpm (install: npm install -g pnpm)`);
91
+ }
92
+
93
+ const claudeRes = spawn('claude', ['--version']);
94
+ if (claudeRes.status === 0) {
95
+ const ver = (claudeRes.stdout || '').trim();
96
+ lines.push(` ${OK} Claude Code CLI${ver ? ` ${ver}` : ''}`); ok += 1;
97
+ } else {
98
+ lines.push(` ${MISS} Claude Code CLI (install: npm install -g @anthropic-ai/claude-code)`);
99
+ }
100
+
101
+ if (process.env.ANTHROPIC_API_KEY) {
102
+ lines.push(` ${OK} ANTHROPIC_API_KEY set`); ok += 1;
103
+ } else {
104
+ lines.push(` ${ERR} ANTHROPIC_API_KEY not set (required for LLM calls)`);
105
+ }
106
+
107
+ return { lines, ok, total: 4 };
108
+ }
109
+
110
+ function checkLayer1(pkgMap) {
111
+ const lines = [];
112
+ let ok = 0;
113
+ for (const name of ['ruflo', '@mfjjs/ruflo-setup']) {
114
+ const ver = pkgMap[name];
115
+ if (ver) { lines.push(` ${OK} ${name}${typeof ver === 'string' ? `@${ver}` : ''}`); ok += 1; }
116
+ else { lines.push(` ${MISS} ${name} (install: npm install -g ${name})`); }
117
+ }
118
+ return { lines, ok, total: 2 };
119
+ }
120
+
121
+ function checkLayer2(pkgMap) {
122
+ const lines = [];
123
+ let ok = 0;
124
+ const ATTENTION_WIN_NOTE = '(Windows: requires Windows 11 SDK for NAPI; WASM fallback available)';
125
+ const pkgs = [
126
+ '@claude-flow/memory', '@ruvector/attention', '@claude-flow/aidefence', 'agentic-flow',
127
+ '@ruvector/sona', '@ruvector/router', '@ruvector/learning-wasm',
128
+ '@claude-flow/embeddings', '@claude-flow/guidance', '@claude-flow/codex'
129
+ ];
130
+ for (const name of pkgs) {
131
+ const ver = pkgMap[name];
132
+ if (ver) { lines.push(` ${OK} ${name}${typeof ver === 'string' ? `@${ver}` : ''}`); ok += 1; }
133
+ else {
134
+ const note = (name === '@ruvector/attention' && IS_WIN) ? ` ${ATTENTION_WIN_NOTE}` : '';
135
+ lines.push(` ${MISS} ${name}${note}`);
136
+ }
137
+ }
138
+ return { lines, ok, total: pkgs.length };
139
+ }
140
+
141
+ function checkLayer3(mcpJson) {
142
+ const lines = [];
143
+ let ok = 0;
144
+ const servers = mcpJson?.mcpServers ?? {};
145
+
146
+ if (servers['claude-flow']) {
147
+ const args = servers['claude-flow']?.args ?? [];
148
+ const pkgArg = args.find((a) => typeof a === 'string' && a.includes('@claude-flow/cli')) ?? '@claude-flow/cli@latest';
149
+ lines.push(` ${OK} claude-flow (${pkgArg})`); ok += 1;
150
+ } else {
151
+ lines.push(` ${MISS} claude-flow (run ruflo-setup to configure)`);
152
+ }
153
+
154
+ if (servers['ruv-swarm']) { lines.push(` ${OK} ruv-swarm (optional)`); ok += 1; }
155
+ else { lines.push(` ${MISS} ruv-swarm (optional)`); }
156
+
157
+ if (servers['flow-nexus']) { lines.push(` ${OK} flow-nexus (optional)`); ok += 1; }
158
+ else { lines.push(` ${MISS} flow-nexus (optional β€” needs Cognitum.One account)`); }
159
+
160
+ return { lines, ok, total: 3 };
161
+ }
162
+
163
+ function checkLayer4(mcpJson) {
164
+ const lines = [];
165
+ let ok = 0;
166
+ const cfEnv = mcpJson?.mcpServers?.['claude-flow']?.env ?? {};
167
+ const resolve = (k) => { const v = cfEnv[k] ?? process.env[k]; return v === undefined ? null : String(v).toLowerCase(); };
168
+
169
+ for (const g of ['INTELLIGENCE', 'AGENTS', 'MEMORY', 'DEVTOOLS']) {
170
+ if (resolve(`MCP_GROUP_${g}`) === 'false') { lines.push(` ${MISS} ${g} (disabled via MCP_GROUP_${g}=false)`); }
171
+ else { lines.push(` ${OK} ${g} (default on)`); ok += 1; }
172
+ }
173
+ for (const g of ['SECURITY', 'BROWSER', 'NEURAL', 'AGENTIC_FLOW']) {
174
+ if (resolve(`MCP_GROUP_${g}`) === 'true') { lines.push(` ${OK} ${g} (enabled)`); ok += 1; }
175
+ else { lines.push(` ${MISS} ${g} (set MCP_GROUP_${g}=true in .mcp.json env)`); }
176
+ }
177
+
178
+ return { lines, ok, total: 8 };
179
+ }
180
+
181
+ function checkLayer5() {
182
+ const lines = [];
183
+ let ok = 0;
184
+ const checks = [
185
+ { key: 'ANTHROPIC_API_KEY', req: true, note: '' },
186
+ { key: 'OPENAI_API_KEY', req: false, note: '(optional β€” enables GPT + Codex)' },
187
+ { key: 'GOOGLE_API_KEY', req: false, note: '(optional β€” enables Gemini)' },
188
+ { key: 'OPENROUTER_API_KEY', req: false, note: '(optional β€” multi-provider proxy)' }
189
+ ];
190
+ for (const { key, req, note } of checks) {
191
+ if (process.env[key]) { lines.push(` ${OK} ${key}`); ok += 1; }
192
+ else if (req) { lines.push(` ${ERR} ${key} not set (required for LLM calls)`); }
193
+ else { lines.push(` ${MISS} ${key} ${note}`); }
194
+ }
195
+ return { lines, ok, total: checks.length };
196
+ }
197
+
198
+ function checkLayer6(packageRoot) {
199
+ const lines = [];
200
+ let ok = 0;
201
+ const homeDir = os.homedir();
202
+
203
+ try {
204
+ const hs = getGlobalHookStatus({ packageRoot });
205
+ const sp = hs.settingsPath ?? path.join(homeDir, '.claude', 'settings.json');
206
+ if (hs.installed) { lines.push(` ${OK} SessionStart hook (${sp})`); ok += 1; }
207
+ else { lines.push(` ${MISS} SessionStart hook (${sp})`); }
208
+ } catch {
209
+ lines.push(` ${MISS} SessionStart hook (could not read ~/.claude/settings.json)`);
210
+ }
211
+
212
+ const commandFile = path.join(homeDir, '.claude', 'commands', 'ruflo-setup.md');
213
+ if (fileExists(commandFile)) { lines.push(` ${OK} /ruflo-setup command (${commandFile})`); ok += 1; }
214
+ else { lines.push(` ${MISS} /ruflo-setup command (${commandFile})`); }
215
+
216
+ return { lines, ok, total: 2 };
217
+ }
218
+
219
+ function checkLayer7(cwd) {
220
+ const lines = [];
221
+ let ok = 0;
222
+ const files = ['.mcp.json', 'CLAUDE.md', path.join('.claude', 'settings.json')];
223
+ const dirs = [
224
+ { rel: path.join('.claude', 'agents'), hint: 'run: ruflo init --full' },
225
+ { rel: path.join('.claude', 'skills'), hint: null },
226
+ { rel: path.join('.claude', 'commands'), hint: null },
227
+ { rel: '.claude-flow', hint: null }
228
+ ];
229
+
230
+ for (const rel of files) {
231
+ if (fileExists(path.join(cwd, rel))) { lines.push(` ${OK} ${rel}`); ok += 1; }
232
+ else { lines.push(` ${MISS} ${rel}`); }
233
+ }
234
+ for (const { rel, hint } of dirs) {
235
+ const disp = `${rel}/`.replace(/\\/g, '/');
236
+ const full = path.join(cwd, rel);
237
+ if (dirExists(full)) {
238
+ let count = '';
239
+ if (rel.endsWith('agents') || rel.endsWith('skills')) {
240
+ try {
241
+ const n = fs.readdirSync(full).length;
242
+ const label = rel.endsWith('agents') ? 'agents' : 'skills';
243
+ count = ` (${n} ${label})`;
244
+ } catch { /* ignore */ }
245
+ }
246
+ lines.push(` ${OK} ${disp}${count}`); ok += 1;
247
+ } else {
248
+ lines.push(` ${MISS} ${disp}${hint ? ` (${hint})` : ''}`);
249
+ }
250
+ }
251
+
252
+ return { lines, ok, total: files.length + dirs.length };
253
+ }
254
+
255
+ function checkLayer8() {
256
+ const res = spawn('docker', ['--version']);
257
+ if (res.status === 0 && res.stdout.trim()) {
258
+ return { lines: [` ${OK} Docker ${res.stdout.trim()}`], ok: 1, total: 1 };
259
+ }
260
+ return { lines: [` ${MISS} Docker not detected (optional β€” needed for ruvocal chat UI)`], ok: 0, total: 1 };
261
+ }
262
+
263
+ export async function runStatus({ cwd, packageRoot }) {
264
+ try {
265
+ const { version } = require('../package.json');
266
+ const mcpJson = readJsonSafe(path.join(cwd, '.mcp.json'), {});
267
+ const pkgMap = getGlobalPkgMap();
268
+
269
+ const layers = [
270
+ { title: 'Layer 0: Prerequisites', result: checkLayer0() },
271
+ { title: 'Layer 1: Global npm Packages', result: checkLayer1(pkgMap) },
272
+ { title: 'Layer 2: Optional Packages (WASM/ML) β€” enables AI features', result: checkLayer2(pkgMap) },
273
+ { title: 'Layer 3: MCP Servers (.mcp.json)', result: checkLayer3(mcpJson) },
274
+ { title: 'Layer 4: MCP Tool Groups', result: checkLayer4(mcpJson) },
275
+ { title: 'Layer 5: Environment Variables', result: checkLayer5() },
276
+ { title: 'Layer 6: Claude Code Hooks', result: checkLayer6(packageRoot) },
277
+ { title: 'Layer 7: Project Scaffolding', result: checkLayer7(cwd) },
278
+ { title: 'Layer 8: Docker Chat UI (optional)', result: checkLayer8() }
279
+ ];
280
+
281
+ let totalOk = 0;
282
+ let totalChecks = 0;
283
+
284
+ process.stdout.write(`\nRuflo Feature Status (ruflo-setup v${version})\n`);
285
+ process.stdout.write(`Target: ${cwd}\n`);
286
+
287
+ for (const { title, result } of layers) {
288
+ process.stdout.write(`\n${title}\n`);
289
+ for (const line of result.lines) process.stdout.write(`${line}\n`);
290
+ totalOk += result.ok;
291
+ totalChecks += result.total;
292
+ }
293
+
294
+ process.stdout.write(`\nSummary: ${totalOk}/${totalChecks} features enabled\n`);
295
+ process.stdout.write(`For agents, skills, and slash commands reference: docs/ruflo-benefit.md\n`);
296
+
297
+ const hasRequiredMissing = parseInt(process.version.slice(1), 10) < 20 || !process.env.ANTHROPIC_API_KEY;
298
+ if (hasRequiredMissing) process.stdout.write(`Run 'ruflo-setup' to configure missing required features.\n`);
299
+ process.stdout.write('\n');
300
+ } catch (error) {
301
+ process.stderr.write(`status error: ${error.message}\n`);
302
+ }
303
+ }
package/src/utils.js CHANGED
@@ -90,7 +90,11 @@ export function toPlatformMcpConfig(platform) {
90
90
  CLAUDE_FLOW_HOOKS_ENABLED: 'true',
91
91
  CLAUDE_FLOW_TOPOLOGY: 'hierarchical-mesh',
92
92
  CLAUDE_FLOW_MAX_AGENTS: '15',
93
- CLAUDE_FLOW_MEMORY_BACKEND: 'hybrid'
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'
94
98
  },
95
99
  autoStart: false
96
100
  },