@mfjjs/ruflo-setup 0.1.9 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mfjjs/ruflo-setup",
3
- "version": "0.1.9",
3
+ "version": "0.2.2",
4
4
  "description": "Cross-platform setup CLI for Ruflo + Claude Flow projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "src",
12
12
  "templates",
13
13
  "CHANGELOG.md",
14
+ "docs/ruflo-benefits.md",
14
15
  "claude-hooks"
15
16
  ],
16
17
  "engines": {
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
  },