cf-memory-mcp 3.44.0 → 3.46.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/bin/cf-memory-mcp.js +191 -5
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -3969,6 +3969,14 @@ function parseCliArgs(rest) {
|
|
|
3969
3969
|
flags.card = true;
|
|
3970
3970
|
} else if (a === '--short') {
|
|
3971
3971
|
flags.short = true;
|
|
3972
|
+
} else if (a === '--age') {
|
|
3973
|
+
flags.age = true;
|
|
3974
|
+
} else if (a === '--quality') {
|
|
3975
|
+
flags.quality = true;
|
|
3976
|
+
} else if (a === '--goal-only') {
|
|
3977
|
+
flags.goal_only = true;
|
|
3978
|
+
} else if (a === '--status-only') {
|
|
3979
|
+
flags.status_only = true;
|
|
3972
3980
|
} else if (a === '--older-than') {
|
|
3973
3981
|
// Accept "7d" / "30d" / "12h" / raw number (days).
|
|
3974
3982
|
const raw = rest[++i] || '';
|
|
@@ -4131,6 +4139,38 @@ async function runResumeCli() {
|
|
|
4131
4139
|
process.exit(0);
|
|
4132
4140
|
}
|
|
4133
4141
|
|
|
4142
|
+
// --goal-only: print just the goal (single line).
|
|
4143
|
+
if (flags.goal_only) {
|
|
4144
|
+
const goal = payload.resume_handoff?.handoff?.goal;
|
|
4145
|
+
if (!goal) process.exit(3);
|
|
4146
|
+
process.stdout.write(goal + '\n');
|
|
4147
|
+
process.exit(0);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
// --status-only: print just the status string.
|
|
4151
|
+
if (flags.status_only) {
|
|
4152
|
+
const status = payload.resume_handoff?.handoff?.status;
|
|
4153
|
+
if (!status) process.exit(3);
|
|
4154
|
+
process.stdout.write(status + '\n');
|
|
4155
|
+
process.exit(0);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// --age: print just handoff_age_minutes (single integer).
|
|
4159
|
+
if (flags.age) {
|
|
4160
|
+
const ageMin = payload.resume_handoff?.handoff_age_minutes;
|
|
4161
|
+
if (typeof ageMin !== 'number') process.exit(3);
|
|
4162
|
+
process.stdout.write(String(ageMin) + '\n');
|
|
4163
|
+
process.exit(0);
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
// --quality: print just quality_score (single float).
|
|
4167
|
+
if (flags.quality) {
|
|
4168
|
+
const q = payload.resume_handoff?.quality_score;
|
|
4169
|
+
if (typeof q !== 'number') process.exit(3);
|
|
4170
|
+
process.stdout.write(String(q) + '\n');
|
|
4171
|
+
process.exit(0);
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4134
4174
|
// --short: single line for tmux/status bars. "goal · status"
|
|
4135
4175
|
// truncated to terminal-friendly length.
|
|
4136
4176
|
if (flags.short) {
|
|
@@ -4923,6 +4963,10 @@ const PER_COMMAND_HELP = {
|
|
|
4923
4963
|
--chain Walk parent_session_id back; show the thread history.
|
|
4924
4964
|
--validate Check that files_touched + code_anchors still exist locally.
|
|
4925
4965
|
--raw Print just the raw handoff JSON (no envelope metadata).
|
|
4966
|
+
--age Print handoff_age_minutes (single integer).
|
|
4967
|
+
--quality Print quality_score (single float, 0-1).
|
|
4968
|
+
--goal-only Print just the goal text (single line).
|
|
4969
|
+
--status-only Print just the status string.
|
|
4926
4970
|
--json, -j Full bootstrap payload as JSON.
|
|
4927
4971
|
Exit codes: 0 = found, 3 = no handoff / no data, 4 = --validate found missing files.`,
|
|
4928
4972
|
list: `cf-memory-mcp list [--status S] [--since ISO] [--repo PATH] [--limit N] [--json]
|
|
@@ -4950,14 +4994,18 @@ const PER_COMMAND_HELP = {
|
|
|
4950
4994
|
--all Delete ALL cf-memory disk caches.
|
|
4951
4995
|
--json, -j Emit a JSON list of removed paths.`,
|
|
4952
4996
|
export: `cf-memory-mcp export <session-id-or-prefix> [--md path]
|
|
4997
|
+
cf-memory-mcp export --all
|
|
4953
4998
|
Print a handoff as a JSON bundle to stdout (or write to file). For
|
|
4954
|
-
backup or cross-machine sync.
|
|
4999
|
+
backup or cross-machine sync. With --all, streams ALL handoffs as
|
|
5000
|
+
NDJSON (one bundle per line) suitable for piping to a file.
|
|
4955
5001
|
<session-id> Full UUID or short prefix (>=8 chars).
|
|
4956
|
-
--md <path> Write the JSON to a file
|
|
4957
|
-
|
|
5002
|
+
--md <path> Write the JSON to a file (single-handoff mode).
|
|
5003
|
+
--all Stream all handoffs as NDJSON.`,
|
|
5004
|
+
import: `cf-memory-mcp import [<file>|-] [--all] [--json]
|
|
4958
5005
|
Restore a handoff bundle into a NEW session (keep_open). Cross-machine
|
|
4959
5006
|
sync. Use "-" to read from stdin.
|
|
4960
5007
|
<file> Bundle file path; "-" for stdin.
|
|
5008
|
+
--all Input is NDJSON (one bundle per line). Imports each.
|
|
4961
5009
|
--json, -j Emit a JSON status object.`,
|
|
4962
5010
|
doctor: `cf-memory-mcp doctor [--json]
|
|
4963
5011
|
Diagnose common setup issues. Checks API key, git repo/branch, disk-
|
|
@@ -5131,12 +5179,91 @@ async function runExportCli() {
|
|
|
5131
5179
|
}
|
|
5132
5180
|
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
5133
5181
|
const sessionArg = positional[0];
|
|
5134
|
-
|
|
5135
|
-
|
|
5182
|
+
const exportAll = process.argv.includes('--all');
|
|
5183
|
+
if (!sessionArg && !exportAll) {
|
|
5184
|
+
console.error('Usage:');
|
|
5185
|
+
console.error(' cf-memory-mcp export <session-id-or-prefix> # single handoff bundle');
|
|
5186
|
+
console.error(' cf-memory-mcp export --all # all handoffs as NDJSON');
|
|
5136
5187
|
process.exit(1);
|
|
5137
5188
|
}
|
|
5138
5189
|
const server = new CFMemoryMCP();
|
|
5139
5190
|
server.logDebug = () => {};
|
|
5191
|
+
|
|
5192
|
+
// --all: stream every handoff as NDJSON (one bundle per line).
|
|
5193
|
+
if (exportAll) {
|
|
5194
|
+
// EPIPE handler: when piping to `head` etc., downstream closes
|
|
5195
|
+
// stdout while we're still writing. Exit cleanly instead of
|
|
5196
|
+
// dumping a Node stack trace.
|
|
5197
|
+
process.stdout.on('error', (err) => {
|
|
5198
|
+
if (err.code === 'EPIPE') process.exit(0);
|
|
5199
|
+
throw err;
|
|
5200
|
+
});
|
|
5201
|
+
try {
|
|
5202
|
+
// Page through all the user's handoffs via get_context_bootstrap
|
|
5203
|
+
// with recent_handoff_limit=50, sorted by recency. Use `since`
|
|
5204
|
+
// to walk back in time without dupes — query for entries before
|
|
5205
|
+
// the oldest one in the previous batch.
|
|
5206
|
+
const seen = new Set();
|
|
5207
|
+
let cursor = null; // ISO timestamp; entries with ended_at|started_at >= cursor are returned
|
|
5208
|
+
let totalEmitted = 0;
|
|
5209
|
+
for (let page = 0; page < 100; page++) {
|
|
5210
|
+
const args = { resume: true, recent_handoff_limit: 50 };
|
|
5211
|
+
const meta = server.getRepoMetadata();
|
|
5212
|
+
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
5213
|
+
if (cursor) args.since = cursor;
|
|
5214
|
+
const res = await server.makeRequest({
|
|
5215
|
+
jsonrpc: '2.0', id: `cli-export-all-${page}-${Date.now()}`,
|
|
5216
|
+
method: 'tools/call',
|
|
5217
|
+
params: { name: 'get_context_bootstrap', arguments: args },
|
|
5218
|
+
});
|
|
5219
|
+
const text = res?.result?.content?.[0]?.text;
|
|
5220
|
+
const payload = JSON.parse(text || '{}');
|
|
5221
|
+
const handoffs = payload.recent_handoffs || [];
|
|
5222
|
+
if (handoffs.length === 0) break;
|
|
5223
|
+
let newCount = 0;
|
|
5224
|
+
let oldestSeen = null;
|
|
5225
|
+
for (const h of handoffs) {
|
|
5226
|
+
if (seen.has(h.session_id)) continue;
|
|
5227
|
+
seen.add(h.session_id);
|
|
5228
|
+
// Fetch the full handoff (not just the summary).
|
|
5229
|
+
const fullRes = await server.makeRequest({
|
|
5230
|
+
jsonrpc: '2.0', id: `cli-export-all-detail-${Date.now()}`,
|
|
5231
|
+
method: 'tools/call',
|
|
5232
|
+
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: h.session_id } },
|
|
5233
|
+
});
|
|
5234
|
+
const fullText = fullRes?.result?.content?.[0]?.text;
|
|
5235
|
+
const fullPayload = JSON.parse(fullText || '{}');
|
|
5236
|
+
const envelope = fullPayload.resume_handoff;
|
|
5237
|
+
if (!envelope) continue;
|
|
5238
|
+
const bundle = {
|
|
5239
|
+
kind: 'cf-memory-handoff',
|
|
5240
|
+
version: 1,
|
|
5241
|
+
exported_at: new Date().toISOString(),
|
|
5242
|
+
session_id: envelope.session_id,
|
|
5243
|
+
started_at: envelope.started_at,
|
|
5244
|
+
ended_at: envelope.ended_at,
|
|
5245
|
+
handoff: envelope.handoff,
|
|
5246
|
+
};
|
|
5247
|
+
process.stdout.write(JSON.stringify(bundle) + '\n');
|
|
5248
|
+
totalEmitted++;
|
|
5249
|
+
newCount++;
|
|
5250
|
+
const t = envelope.ended_at || envelope.started_at;
|
|
5251
|
+
if (!oldestSeen || t > oldestSeen) oldestSeen = t;
|
|
5252
|
+
}
|
|
5253
|
+
// No new entries → we've exhausted the timeline.
|
|
5254
|
+
if (newCount === 0) break;
|
|
5255
|
+
// Step the cursor forward (next page returns entries from
|
|
5256
|
+
// after the oldest we saw).
|
|
5257
|
+
cursor = oldestSeen;
|
|
5258
|
+
}
|
|
5259
|
+
process.stderr.write(`Exported ${totalEmitted} handoffs.\n`);
|
|
5260
|
+
process.exit(totalEmitted === 0 ? 3 : 0);
|
|
5261
|
+
} catch (err) {
|
|
5262
|
+
console.error('export --all failed:', err.message);
|
|
5263
|
+
process.exit(1);
|
|
5264
|
+
}
|
|
5265
|
+
return;
|
|
5266
|
+
}
|
|
5140
5267
|
try {
|
|
5141
5268
|
const args = { resume: true, session_id_hint: sessionArg };
|
|
5142
5269
|
const response = await server.makeRequest({
|
|
@@ -5182,6 +5309,7 @@ async function runImportCli() {
|
|
|
5182
5309
|
process.exit(1);
|
|
5183
5310
|
}
|
|
5184
5311
|
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
5312
|
+
const bulk = process.argv.includes('--all');
|
|
5185
5313
|
const sourcePath = positional[0];
|
|
5186
5314
|
let raw;
|
|
5187
5315
|
try {
|
|
@@ -5195,6 +5323,64 @@ async function runImportCli() {
|
|
|
5195
5323
|
console.error('import: cannot read input:', err.message);
|
|
5196
5324
|
process.exit(1);
|
|
5197
5325
|
}
|
|
5326
|
+
|
|
5327
|
+
// Bulk: parse NDJSON (one bundle per line) and import each.
|
|
5328
|
+
if (bulk) {
|
|
5329
|
+
const lines = raw.split('\n').filter(l => l.trim());
|
|
5330
|
+
const server = new CFMemoryMCP();
|
|
5331
|
+
server.logDebug = () => {};
|
|
5332
|
+
const results = { imported: 0, failed: 0, errors: [] };
|
|
5333
|
+
const meta = server.getRepoMetadata();
|
|
5334
|
+
for (const line of lines) {
|
|
5335
|
+
let bundle;
|
|
5336
|
+
try { bundle = JSON.parse(line); } catch (err) {
|
|
5337
|
+
results.failed++;
|
|
5338
|
+
results.errors.push(`line skip: not valid JSON`);
|
|
5339
|
+
continue;
|
|
5340
|
+
}
|
|
5341
|
+
if (bundle.kind !== 'cf-memory-handoff' || !bundle.handoff) {
|
|
5342
|
+
results.failed++;
|
|
5343
|
+
results.errors.push(`line skip: not a cf-memory-handoff bundle`);
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
try {
|
|
5347
|
+
const startArgs = { context: 'main' };
|
|
5348
|
+
if (meta.repo_path) startArgs.repo_path = meta.repo_path;
|
|
5349
|
+
if (meta.branch) startArgs.branch = meta.branch;
|
|
5350
|
+
const startRes = await server.makeRequest({
|
|
5351
|
+
jsonrpc: '2.0', id: `cli-import-all-start-${Date.now()}`,
|
|
5352
|
+
method: 'tools/call', params: { name: 'start_session', arguments: startArgs },
|
|
5353
|
+
});
|
|
5354
|
+
const sp = JSON.parse(startRes?.result?.content?.[0]?.text || '{}');
|
|
5355
|
+
const newId = sp.session_id;
|
|
5356
|
+
if (!newId) { results.failed++; continue; }
|
|
5357
|
+
const handoff = {
|
|
5358
|
+
...bundle.handoff,
|
|
5359
|
+
parent_session_id: bundle.session_id,
|
|
5360
|
+
notes: (bundle.handoff.notes || '') + `\n\n[bulk-imported from ${bundle.session_id}]`,
|
|
5361
|
+
};
|
|
5362
|
+
await server.makeRequest({
|
|
5363
|
+
jsonrpc: '2.0', id: `cli-import-all-end-${Date.now()}`,
|
|
5364
|
+
method: 'tools/call', params: { name: 'end_session', arguments: {
|
|
5365
|
+
session_id: newId, keep_open: true, handoff,
|
|
5366
|
+
} },
|
|
5367
|
+
});
|
|
5368
|
+
results.imported++;
|
|
5369
|
+
} catch (err) {
|
|
5370
|
+
results.failed++;
|
|
5371
|
+
results.errors.push(`${bundle.session_id}: ${err.message}`);
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
if (flags.json) {
|
|
5375
|
+
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
5376
|
+
} else {
|
|
5377
|
+
process.stderr.write(`Imported ${results.imported} handoffs.`);
|
|
5378
|
+
if (results.failed > 0) process.stderr.write(` Failed: ${results.failed}.`);
|
|
5379
|
+
process.stderr.write('\n');
|
|
5380
|
+
}
|
|
5381
|
+
process.exit(results.imported > 0 ? 0 : 3);
|
|
5382
|
+
}
|
|
5383
|
+
|
|
5198
5384
|
let bundle;
|
|
5199
5385
|
try { bundle = JSON.parse(raw); } catch (err) {
|
|
5200
5386
|
console.error('import: input is not valid JSON:', err.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.46.0",
|
|
4
4
|
"description": "Cloudflare-hosted MCP server for code indexing, retrieval, and assistant memory with a direct remote MCP endpoint and local stdio bridge.",
|
|
5
5
|
"main": "bin/cf-memory-mcp.js",
|
|
6
6
|
"bin": {
|