@fuzeelogik/myflo 1.0.0 → 1.0.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.
@@ -39,18 +39,18 @@ async function getBackend() {
39
39
  if (_backend) return _backend;
40
40
  if (!existsSync(FLO_HOME)) await mkdir(FLO_HOME, { recursive: true });
41
41
  // Dynamic import keeps the heavy module out of the load path for the JSONL
42
- // codepath. @myflo/memory is an optionalDependencywhen missing (e.g. a
43
- // bare `npm install myflo` without the optional extras), throw a clear error
44
- // so the caller knows to either install the optional dep or stick with the
45
- // jsonl backend.
42
+ // codepath. @myflo/memory is not yet published to npm the agentdb backend
43
+ // only works when myflo is run from the monorepo (pnpm workspace resolves
44
+ // the package locally). For standalone `npx @fuzeelogik/myflo` installs,
45
+ // stick with the default jsonl backend.
46
46
  let SqlJsBackend;
47
47
  try {
48
48
  ({ SqlJsBackend } = await import('@myflo/memory'));
49
49
  } catch (err) {
50
50
  throw new Error(
51
- `agentdb backend requires @myflo/memory (optional dependency). ` +
52
- `Install it: npm i @myflo/memory. ` +
53
- `Or stick with the default jsonl backend (unset FLO_MEMORY_BACKEND). ` +
51
+ `agentdb backend requires @myflo/memory, which is not yet published to npm. ` +
52
+ `It currently resolves only inside the myflo monorepo. ` +
53
+ `Stick with the default jsonl backend (unset FLO_MEMORY_BACKEND). ` +
54
54
  `Underlying error: ${err.message}`
55
55
  );
56
56
  }
@@ -1,12 +1,15 @@
1
1
  // `flo replace ruflo` — cutover script.
2
2
  // Removes ruflo / claude-flow entries from ~/.claude/mcp.json and project
3
- // .claude/settings.json mcpServers blocks, leaving only flo. Idempotent.
4
- // Backs up both files before writing.
3
+ // .claude/settings.json mcpServers blocks, AND shells out to
4
+ // `claude mcp remove <name>` for entries the Claude CLI itself manages
5
+ // (e.g. ones registered via `claude mcp add` that don't live in those files).
6
+ // Idempotent. Backs up json files before writing.
5
7
 
6
8
  import { readFile, writeFile, copyFile, mkdir } from 'node:fs/promises';
7
9
  import { existsSync } from 'node:fs';
8
10
  import { homedir } from 'node:os';
9
11
  import { join } from 'node:path';
12
+ import { spawn } from 'node:child_process';
10
13
 
11
14
  const USER_MCP = join(homedir(), '.claude', 'mcp.json');
12
15
  const PROJECT_SETTINGS = join(process.cwd(), '.claude', 'settings.json');
@@ -57,11 +60,72 @@ export async function replaceCommand(args) {
57
60
  console.log(`✓ ${label}: removed [${result.removed.join(', ')}] from ${path} (backup: ${backup})`);
58
61
  }
59
62
 
63
+ // Phase 2: ask Claude CLI itself to remove any ruflo/claude-flow servers it
64
+ // tracks. This catches entries registered via `claude mcp add` that don't
65
+ // live in mcp.json or settings.json (e.g. user/local scope state the CLI
66
+ // keeps elsewhere). The flo replace ruflo command was previously a no-op
67
+ // for users in this state — see #1 on the v1.0.1 premortem.
68
+ const claudePath = await whichClaudeCli();
69
+ if (!claudePath) {
70
+ console.log('');
71
+ console.log('- claude CLI not on PATH — skipping `claude mcp remove` step.');
72
+ console.log(' (Files were cleaned above; that is usually enough.)');
73
+ } else {
74
+ console.log('');
75
+ console.log(`Running \`claude mcp remove\` for each known ruflo name...`);
76
+ for (const name of RUFLO_KEYS) {
77
+ if (opts.dryRun) {
78
+ console.log(`# would run: claude mcp remove "${name}"`);
79
+ continue;
80
+ }
81
+ const res = await runClaudeMcpRemove(claudePath, name);
82
+ if (res.removed) {
83
+ console.log(`✓ claude mcp remove "${name}" — removed from ${res.scope || 'config'}`);
84
+ } else if (res.notFound) {
85
+ console.log(`= claude mcp remove "${name}" — not registered, skipping`);
86
+ } else {
87
+ console.log(`× claude mcp remove "${name}" — failed: ${res.error}`);
88
+ }
89
+ }
90
+ }
91
+
60
92
  console.log('');
61
93
  console.log('Done. Restart Claude Code to pick up the change.');
62
94
  console.log("If you need to roll back, the .flo-bak.* file is your snapshot.");
63
95
  }
64
96
 
97
+ async function whichClaudeCli() {
98
+ return new Promise((resolve) => {
99
+ const p = spawn('which', ['claude'], { stdio: ['ignore', 'pipe', 'ignore'] });
100
+ let out = '';
101
+ p.stdout.on('data', (b) => { out += b.toString(); });
102
+ p.on('close', (code) => resolve(code === 0 ? out.trim() : null));
103
+ p.on('error', () => resolve(null));
104
+ });
105
+ }
106
+
107
+ async function runClaudeMcpRemove(claudePath, name) {
108
+ return new Promise((resolve) => {
109
+ const p = spawn(claudePath, ['mcp', 'remove', name], { stdio: ['ignore', 'pipe', 'pipe'] });
110
+ let stdout = '';
111
+ let stderr = '';
112
+ p.stdout.on('data', (b) => { stdout += b.toString(); });
113
+ p.stderr.on('data', (b) => { stderr += b.toString(); });
114
+ p.on('close', (code) => {
115
+ const combined = (stdout + stderr).toLowerCase();
116
+ if (code === 0) {
117
+ const scopeMatch = stdout.match(/from\s+(\w+)\s+config/i);
118
+ resolve({ removed: true, scope: scopeMatch?.[1] || null });
119
+ } else if (/no.*server.*found|not.*found|does not exist/.test(combined)) {
120
+ resolve({ notFound: true });
121
+ } else {
122
+ resolve({ error: (stderr || stdout).trim().slice(0, 200) || `exit ${code}` });
123
+ }
124
+ });
125
+ p.on('error', (err) => resolve({ error: err.message }));
126
+ });
127
+ }
128
+
65
129
  function removeRufloFrom(obj) {
66
130
  const removed = [];
67
131
  // mcpServers block
@@ -115,6 +179,10 @@ Specifically removes:
115
179
 
116
180
  Both files are backed up to <path>.flo-bak.<ts> before being rewritten.
117
181
 
182
+ Also runs \`claude mcp remove ruflo\` and \`claude mcp remove claude-flow\`
183
+ to catch entries the Claude CLI tracks outside those files (registered via
184
+ \`claude mcp add\`). Silently skipped if the claude CLI isn't on PATH.
185
+
118
186
  Idempotent. Re-running on a clean config is a no-op.
119
187
 
120
188
  This complements 'flo migrate', which only ADDED the flo entry. Run that first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzeelogik/myflo",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "myflo — local-first developer workbench. CLI + MCP server + Next.js dashboard. Forked from ruflo for the runtime substrate; flo CLI on top is ours.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "README.md"
15
15
  ],
16
16
  "dependencies": {},
17
- "optionalDependencies": {
17
+ "devDependencies": {
18
18
  "@myflo/memory": "workspace:*"
19
19
  },
20
20
  "engines": {
package/tests/smoke.sh CHANGED
@@ -387,6 +387,32 @@ for line in sys.stdin:
387
387
  if [ "$TOOLCOUNT" = "22" ]; then echo " PASS mcp tools/list count (22)"; PASS=$((PASS+1))
388
388
  else echo " FAIL mcp tools/list count (got $TOOLCOUNT)"; FAIL=$((FAIL+1)); fi
389
389
 
390
+ # `flo replace ruflo` against a fixture project — file rewrites + graceful
391
+ # skip when claude CLI is unavailable (PATH hidden to avoid mutating real config).
392
+ REPLACE_DIR="$TMP/replace-ruflo-test"
393
+ mkdir -p "$REPLACE_DIR/.claude"
394
+ cat > "$REPLACE_DIR/.claude/settings.json" <<'JSON'
395
+ {
396
+ "mcpServers": {
397
+ "ruflo": {"command": "npx", "args": ["-y", "ruflo@latest", "mcp", "start"]},
398
+ "flo": {"command": "node", "args": ["/x.js"]}
399
+ },
400
+ "enabledMcpjsonServers": ["ruflo", "flo"],
401
+ "permissions": {"allow": ["mcp__ruflo__foo", "Bash(ls:*)", "mcp__claude-flow__bar"]}
402
+ }
403
+ JSON
404
+ NODE_BIN=$(which node)
405
+ REPLACE_OUT=$(cd "$REPLACE_DIR" && PATH="/usr/bin:/bin" $NODE_BIN "$REPO_ROOT/apps/cli/bin/flo.js" replace ruflo 2>&1)
406
+ if echo "$REPLACE_OUT" | grep -q "claude CLI not on PATH"; then
407
+ if python3 -c "import json; d=json.load(open('$REPLACE_DIR/.claude/settings.json')); assert list(d['mcpServers'].keys())==['flo'] and d['enabledMcpjsonServers']==['flo'] and d['permissions']['allow']==['Bash(ls:*)']" 2>/dev/null; then
408
+ echo " PASS replace ruflo (file rewrite + graceful claude-cli skip)"; PASS=$((PASS+1))
409
+ else
410
+ echo " FAIL replace ruflo: file content unexpected"; FAIL=$((FAIL+1))
411
+ fi
412
+ else
413
+ echo " FAIL replace ruflo: expected 'claude CLI not on PATH' message"; FAIL=$((FAIL+1))
414
+ fi
415
+
390
416
  echo "--------------"
391
417
  echo "$PASS passed, $FAIL failed."
392
418
  if [ "$FAIL" -gt 0 ]; then exit 1; fi