@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.
- package/lib/memory-backend-agentdb.js +12 -13
- package/lib/memory-store.js +1 -1
- package/lib/replace-ruflo.js +70 -2
- package/package.json +3 -3
- package/tests/smoke.sh +26 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// AgentDB-backed memory adapter. Wraps @myflo
|
|
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
|
|
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
|
|
29
|
-
const memoryPkgUrl = import.meta.resolve('@myflo
|
|
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
|
|
43
|
-
//
|
|
44
|
-
//
|
|
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
|
|
47
|
+
({ SqlJsBackend } = await import('@fuzeelogik/myflo-memory'));
|
|
49
48
|
} catch (err) {
|
|
50
49
|
throw new Error(
|
|
51
|
-
`agentdb backend requires @myflo
|
|
52
|
-
`
|
|
53
|
-
`
|
|
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
|
|
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 = {} }) {
|
package/lib/memory-store.js
CHANGED
|
@@ -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
|
|
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.
|
package/lib/replace-ruflo.js
CHANGED
|
@@ -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,
|
|
4
|
-
//
|
|
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
|
|
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
|
-
"
|
|
18
|
-
"@myflo
|
|
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
|