@fuzeelogik/myflo 1.0.1 → 1.1.0

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.
@@ -1,4 +1,4 @@
1
- // AgentDB-backed memory adapter. Wraps @myflo/memory's UnifiedMemoryService
1
+ // AgentDB-backed memory adapter. Wraps @fuzeelogik/myflo-memory's UnifiedMemoryService
2
2
  // + SqlJsBackend to expose the same API as memory-store.js's JSONL backend:
3
3
  // storeEntry, searchEntries, listEntries, getEntry, deleteEntry,
4
4
  // listNamespaces, namespaceStats
@@ -21,12 +21,12 @@ let _backend = null;
21
21
  async function locateSqlJsWasm() {
22
22
  // sql.js ships its WASM as a file. By default it tries to fetch from
23
23
  // sql.js.org which fails offline. Point it at the bundled file.
24
- // sql.js is a transitive dep of @myflo/memory, so we resolve via that
24
+ // sql.js is a transitive dep of @fuzeelogik/myflo-memory, so we resolve via that
25
25
  // package's location (createRequire from the memory module URL).
26
26
  const { createRequire } = await import('node:module');
27
27
  try {
28
- // Resolve @myflo/memory's package.json to find its location
29
- const memoryPkgUrl = import.meta.resolve('@myflo/memory/package.json');
28
+ // Resolve @fuzeelogik/myflo-memory's package.json to find its location
29
+ const memoryPkgUrl = import.meta.resolve('@fuzeelogik/myflo-memory/package.json');
30
30
  const req = createRequire(memoryPkgUrl);
31
31
  const sqlJsMain = req.resolve('sql.js');
32
32
  return sqlJsMain.replace(/sql-wasm\.js$/, 'sql-wasm.wasm');
@@ -39,18 +39,17 @@ 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 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.
42
+ // codepath. @fuzeelogik/myflo-memory is an optionalDependency npm installs
43
+ // it by default but doesn't fail the install if e.g. better-sqlite3 native
44
+ // compile fails. If it's missing entirely, fall back gracefully.
46
45
  let SqlJsBackend;
47
46
  try {
48
- ({ SqlJsBackend } = await import('@myflo/memory'));
47
+ ({ SqlJsBackend } = await import('@fuzeelogik/myflo-memory'));
49
48
  } catch (err) {
50
49
  throw new Error(
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). ` +
50
+ `agentdb backend requires @fuzeelogik/myflo-memory to be installed. ` +
51
+ `Install it: npm install @fuzeelogik/myflo-memory. ` +
52
+ `Or stick with the default jsonl backend (unset FLO_MEMORY_BACKEND). ` +
54
53
  `Underlying error: ${err.message}`
55
54
  );
56
55
  }
@@ -71,7 +70,7 @@ function newId() {
71
70
  return `${Date.now()}-${randomBytes(3).toString('hex')}`;
72
71
  }
73
72
 
74
- // Map flo-shape entry to @myflo/memory MemoryEntry. SqlJsBackend binds every
73
+ // Map flo-shape entry to @fuzeelogik/myflo-memory MemoryEntry. SqlJsBackend binds every
75
74
  // field via positional ?-params, so undefined is fatal — we provide every
76
75
  // expected field with a sensible default.
77
76
  function toBackendEntry({ namespace = 'default', key, value, tags = [], metadata = {} }) {
@@ -1,7 +1,7 @@
1
1
  // Memory store with two backends:
2
2
  // - jsonl (default) — file-backed JSONL per namespace under ~/.flo/memory/<ns>.jsonl
3
3
  // Pure JS, no native deps, BM25 ranking. Zero install cost.
4
- // - agentdb — SQLite-backed via @myflo/memory's SqlJsBackend. FTS5
4
+ // - agentdb — SQLite-backed via @fuzeelogik/myflo-memory's SqlJsBackend. FTS5
5
5
  // keyword search, optional vector embeddings, scales further.
6
6
  //
7
7
  // Select via FLO_MEMORY_BACKEND env var or { backend } option on each call.
@@ -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.1",
3
+ "version": "1.1.0",
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,8 +14,8 @@
14
14
  "README.md"
15
15
  ],
16
16
  "dependencies": {},
17
- "devDependencies": {
18
- "@myflo/memory": "workspace:*"
17
+ "optionalDependencies": {
18
+ "@fuzeelogik/myflo-memory": "^1.0.0"
19
19
  },
20
20
  "engines": {
21
21
  "node": ">=20.0.0"
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