cf-memory-mcp 3.41.0 → 3.43.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 +175 -2
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -3843,7 +3843,7 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
|
3843
3843
|
|
|
3844
3844
|
// Global --help only when no subcommand is present. With a subcommand, fall
|
|
3845
3845
|
// through to the per-command help dispatch below.
|
|
3846
|
-
const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history']);
|
|
3846
|
+
const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history', 'link']);
|
|
3847
3847
|
const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
|
|
3848
3848
|
|
|
3849
3849
|
if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
|
|
@@ -3858,6 +3858,7 @@ Usage:
|
|
|
3858
3858
|
npx cf-memory-mcp list List recent handoffs for cwd (status-ranked)
|
|
3859
3859
|
npx cf-memory-mcp history List all handoffs for cwd, chronological
|
|
3860
3860
|
npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
|
|
3861
|
+
npx cf-memory-mcp link <child> --parent <id> Retroactively link a child session to a parent
|
|
3861
3862
|
npx cf-memory-mcp status Show bridge state + server resume availability
|
|
3862
3863
|
npx cf-memory-mcp clean Delete local disk cache for current cwd
|
|
3863
3864
|
npx cf-memory-mcp clean --all Delete ALL local disk caches
|
|
@@ -3890,6 +3891,42 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
|
|
|
3890
3891
|
process.exit(0);
|
|
3891
3892
|
}
|
|
3892
3893
|
|
|
3894
|
+
/**
|
|
3895
|
+
* Pipe a string to the platform clipboard. Returns true on success.
|
|
3896
|
+
* Detects the right command per platform; falls back gracefully when
|
|
3897
|
+
* none are installed. Bounded by a 2s timeout so a hung clipboard
|
|
3898
|
+
* helper can't lock the CLI.
|
|
3899
|
+
*/
|
|
3900
|
+
function copyToClipboard(text) {
|
|
3901
|
+
const { spawnSync } = require('child_process');
|
|
3902
|
+
let cmd, args;
|
|
3903
|
+
if (process.platform === 'darwin') {
|
|
3904
|
+
cmd = 'pbcopy'; args = [];
|
|
3905
|
+
} else if (process.platform === 'win32') {
|
|
3906
|
+
cmd = 'clip'; args = [];
|
|
3907
|
+
} else {
|
|
3908
|
+
// Linux: try xclip first, then wl-copy (Wayland).
|
|
3909
|
+
const which = spawnSync('which', ['xclip']);
|
|
3910
|
+
if (which.status === 0) {
|
|
3911
|
+
cmd = 'xclip'; args = ['-selection', 'clipboard'];
|
|
3912
|
+
} else {
|
|
3913
|
+
const which2 = spawnSync('which', ['wl-copy']);
|
|
3914
|
+
if (which2.status === 0) {
|
|
3915
|
+
cmd = 'wl-copy'; args = [];
|
|
3916
|
+
} else {
|
|
3917
|
+
return { ok: false, error: 'install xclip or wl-copy to use --copy' };
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
try {
|
|
3922
|
+
const res = spawnSync(cmd, args, { input: text, timeout: 2000 });
|
|
3923
|
+
if (res.status === 0) return { ok: true };
|
|
3924
|
+
return { ok: false, error: `${cmd} exited ${res.status}: ${res.stderr?.toString().slice(0, 200) || ''}` };
|
|
3925
|
+
} catch (err) {
|
|
3926
|
+
return { ok: false, error: `${cmd} failed: ${err.message}` };
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3893
3930
|
// Parse positional + flag args for CLI subcommands. Returns
|
|
3894
3931
|
// { positional: string[], flags: { json, limit, md_path, older_than_days, status, repo_path, yes } }.
|
|
3895
3932
|
function parseCliArgs(rest) {
|
|
@@ -3926,6 +3963,10 @@ function parseCliArgs(rest) {
|
|
|
3926
3963
|
flags.validate = true;
|
|
3927
3964
|
} else if (a === '--raw') {
|
|
3928
3965
|
flags.raw = true;
|
|
3966
|
+
} else if (a === '--copy') {
|
|
3967
|
+
flags.copy = true;
|
|
3968
|
+
} else if (a === '--card') {
|
|
3969
|
+
flags.card = true;
|
|
3929
3970
|
} else if (a === '--older-than') {
|
|
3930
3971
|
// Accept "7d" / "30d" / "12h" / raw number (days).
|
|
3931
3972
|
const raw = rest[++i] || '';
|
|
@@ -4088,6 +4129,24 @@ async function runResumeCli() {
|
|
|
4088
4129
|
process.exit(0);
|
|
4089
4130
|
}
|
|
4090
4131
|
|
|
4132
|
+
// --card: compact 2-3 line summary card for status widgets.
|
|
4133
|
+
// Format: [status · age · q=score] goal / → next_step
|
|
4134
|
+
if (flags.card) {
|
|
4135
|
+
const envelope = payload.resume_handoff;
|
|
4136
|
+
if (!envelope) process.exit(3);
|
|
4137
|
+
const h = envelope.handoff || {};
|
|
4138
|
+
const ageMin = envelope.handoff_age_minutes;
|
|
4139
|
+
const fmtAge = (m) => m == null ? '?' : m < 60 ? `${m}m` : m < 1440 ? `${Math.round(m/60)}h` : `${Math.round(m/1440)}d`;
|
|
4140
|
+
const status = h.status || '?';
|
|
4141
|
+
const q = typeof envelope.quality_score === 'number' ? ` · q=${envelope.quality_score}` : '';
|
|
4142
|
+
const staleMarker = envelope.stale ? ' · STALE' : '';
|
|
4143
|
+
process.stdout.write(`[${status} · ${fmtAge(ageMin)} ago${q}${staleMarker}]\n`);
|
|
4144
|
+
process.stdout.write(`${h.goal || '(no goal)'}\n`);
|
|
4145
|
+
const next = h.next_steps?.[0];
|
|
4146
|
+
if (next) process.stdout.write(`→ ${next}\n`);
|
|
4147
|
+
process.exit(0);
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4091
4150
|
// --chain: walk parent_session_id back through the thread history.
|
|
4092
4151
|
// Bounded at 20 iterations to avoid an infinite loop on malformed
|
|
4093
4152
|
// data. Prints each handoff as "<id> [age] status — goal".
|
|
@@ -4205,6 +4264,19 @@ async function runResumeCli() {
|
|
|
4205
4264
|
}
|
|
4206
4265
|
}
|
|
4207
4266
|
|
|
4267
|
+
// --copy: pipe rendered markdown to platform clipboard. Quick
|
|
4268
|
+
// paste into another chat client or doc tool.
|
|
4269
|
+
if (found && flags.copy && payload.resume_prompt) {
|
|
4270
|
+
const result = copyToClipboard(payload.resume_prompt);
|
|
4271
|
+
if (result.ok) {
|
|
4272
|
+
process.stdout.write(`Copied ${payload.resume_prompt.length} chars to clipboard.\n`);
|
|
4273
|
+
process.exit(0);
|
|
4274
|
+
} else {
|
|
4275
|
+
process.stderr.write(`Failed to copy to clipboard: ${result.error}\n`);
|
|
4276
|
+
process.exit(1);
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4208
4280
|
if (flags.json) {
|
|
4209
4281
|
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
4210
4282
|
process.exit(found ? 0 : 3);
|
|
@@ -4332,6 +4404,95 @@ async function runListCli() {
|
|
|
4332
4404
|
}
|
|
4333
4405
|
}
|
|
4334
4406
|
|
|
4407
|
+
async function runLinkCli() {
|
|
4408
|
+
if (!API_KEY) {
|
|
4409
|
+
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
4410
|
+
process.exit(1);
|
|
4411
|
+
}
|
|
4412
|
+
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
4413
|
+
const childArg = positional[0];
|
|
4414
|
+
// --parent <id> is parsed positionally too; grab it from process.argv.
|
|
4415
|
+
let parentId = null;
|
|
4416
|
+
const parentIdx = process.argv.indexOf('--parent');
|
|
4417
|
+
if (parentIdx !== -1 && process.argv[parentIdx + 1]) parentId = process.argv[parentIdx + 1];
|
|
4418
|
+
for (const a of process.argv) {
|
|
4419
|
+
if (a.startsWith('--parent=')) parentId = a.slice('--parent='.length);
|
|
4420
|
+
}
|
|
4421
|
+
if (!childArg || !parentId) {
|
|
4422
|
+
console.error('Usage: cf-memory-mcp link <child-id-or-prefix> --parent <parent-id-or-prefix>');
|
|
4423
|
+
process.exit(1);
|
|
4424
|
+
}
|
|
4425
|
+
const server = new CFMemoryMCP();
|
|
4426
|
+
server.logDebug = () => {};
|
|
4427
|
+
try {
|
|
4428
|
+
// Resolve child + parent prefixes to full UUIDs via get_context_bootstrap.
|
|
4429
|
+
const resolveId = async (idHint) => {
|
|
4430
|
+
if (idHint.length >= 36) return idHint;
|
|
4431
|
+
const r = await server.makeRequest({
|
|
4432
|
+
jsonrpc: '2.0', id: `cli-link-resolve-${Date.now()}`,
|
|
4433
|
+
method: 'tools/call',
|
|
4434
|
+
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: idHint } },
|
|
4435
|
+
});
|
|
4436
|
+
const t = r?.result?.content?.[0]?.text;
|
|
4437
|
+
const p = JSON.parse(t || '{}');
|
|
4438
|
+
return p.resume_handoff?.session_id || null;
|
|
4439
|
+
};
|
|
4440
|
+
const fullChild = await resolveId(childArg);
|
|
4441
|
+
const fullParent = await resolveId(parentId);
|
|
4442
|
+
if (!fullChild) {
|
|
4443
|
+
process.stderr.write(`Could not resolve child "${childArg}" to a session.\n`);
|
|
4444
|
+
process.exit(3);
|
|
4445
|
+
}
|
|
4446
|
+
if (!fullParent) {
|
|
4447
|
+
process.stderr.write(`Could not resolve parent "${parentId}" to a session.\n`);
|
|
4448
|
+
process.exit(3);
|
|
4449
|
+
}
|
|
4450
|
+
// Fetch current child handoff, set parent_session_id, write back via end_session keep_open.
|
|
4451
|
+
const childRes = await server.makeRequest({
|
|
4452
|
+
jsonrpc: '2.0', id: `cli-link-fetch-${Date.now()}`,
|
|
4453
|
+
method: 'tools/call',
|
|
4454
|
+
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: fullChild } },
|
|
4455
|
+
});
|
|
4456
|
+
const childText = childRes?.result?.content?.[0]?.text;
|
|
4457
|
+
const childPayload = JSON.parse(childText || '{}');
|
|
4458
|
+
const handoff = childPayload.resume_handoff?.handoff;
|
|
4459
|
+
if (!handoff) {
|
|
4460
|
+
process.stderr.write(`Child session has no handoff stored; cannot link.\n`);
|
|
4461
|
+
process.exit(3);
|
|
4462
|
+
}
|
|
4463
|
+
// Update with parent_session_id.
|
|
4464
|
+
const updated = { ...handoff, parent_session_id: fullParent };
|
|
4465
|
+
const updateRes = await server.makeRequest({
|
|
4466
|
+
jsonrpc: '2.0', id: `cli-link-update-${Date.now()}`,
|
|
4467
|
+
method: 'tools/call',
|
|
4468
|
+
params: { name: 'end_session', arguments: {
|
|
4469
|
+
session_id: fullChild,
|
|
4470
|
+
keep_open: true, // don't accidentally finalize
|
|
4471
|
+
handoff: updated,
|
|
4472
|
+
} },
|
|
4473
|
+
});
|
|
4474
|
+
const updateText = updateRes?.result?.content?.[0]?.text;
|
|
4475
|
+
const updatePayload = JSON.parse(updateText || '{}');
|
|
4476
|
+
if (flags.json) {
|
|
4477
|
+
process.stdout.write(JSON.stringify({
|
|
4478
|
+
child: fullChild,
|
|
4479
|
+
parent: fullParent,
|
|
4480
|
+
handoff_stored: !!updatePayload.handoff_stored,
|
|
4481
|
+
}, null, 2) + '\n');
|
|
4482
|
+
process.exit(updatePayload.handoff_stored ? 0 : 2);
|
|
4483
|
+
}
|
|
4484
|
+
if (updatePayload.handoff_stored) {
|
|
4485
|
+
process.stdout.write(`Linked ${fullChild.slice(0,8)} → parent ${fullParent.slice(0,8)}\n`);
|
|
4486
|
+
process.exit(0);
|
|
4487
|
+
}
|
|
4488
|
+
process.stderr.write(`Update failed: ${updateText}\n`);
|
|
4489
|
+
process.exit(2);
|
|
4490
|
+
} catch (err) {
|
|
4491
|
+
console.error('link command failed:', err.message);
|
|
4492
|
+
process.exit(1);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
|
|
4335
4496
|
async function runHistoryCli() {
|
|
4336
4497
|
if (!API_KEY) {
|
|
4337
4498
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
@@ -4589,7 +4750,7 @@ function runEnvCli() {
|
|
|
4589
4750
|
|
|
4590
4751
|
function runCompletionCli() {
|
|
4591
4752
|
const shell = process.argv[3] || 'bash';
|
|
4592
|
-
const commands = ['resume', 'list', 'history', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
4753
|
+
const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
4593
4754
|
const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
|
|
4594
4755
|
if (shell === 'bash') {
|
|
4595
4756
|
process.stdout.write(`# cf-memory-mcp bash completion
|
|
@@ -4679,6 +4840,8 @@ const PER_COMMAND_HELP = {
|
|
|
4679
4840
|
Print the prior resume handoff (markdown by default).
|
|
4680
4841
|
<session-id-prefix> Optional: pick a specific session (>=8 char prefix or full UUID).
|
|
4681
4842
|
--md <path> Write the markdown to a file instead of stdout.
|
|
4843
|
+
--copy Pipe the markdown to the platform clipboard (pbcopy/xclip/wl-copy/clip).
|
|
4844
|
+
--card Compact 2-3 line status card (for terminal status widgets).
|
|
4682
4845
|
Extract flags (pick one; each exits 3 if the section is empty):
|
|
4683
4846
|
--next-only First next_step only (for shell prompts).
|
|
4684
4847
|
--next-steps All next_steps, numbered.
|
|
@@ -4754,6 +4917,11 @@ const PER_COMMAND_HELP = {
|
|
|
4754
4917
|
--repo PATH Override the cwd's repo.
|
|
4755
4918
|
--since ISO Lower bound on handoff timestamp.
|
|
4756
4919
|
--json, -j Emit JSON.`,
|
|
4920
|
+
link: `cf-memory-mcp link <child-id-or-prefix> --parent <parent-id-or-prefix> [--json]
|
|
4921
|
+
Retroactively set parent_session_id on a child session, linking it to
|
|
4922
|
+
a parent in the chain. Useful when you forgot --force on checkpoint
|
|
4923
|
+
or want to manually chain sessions across repos.
|
|
4924
|
+
--json, -j Emit a JSON status object.`,
|
|
4757
4925
|
};
|
|
4758
4926
|
|
|
4759
4927
|
function printPerCommandHelp(cmd) {
|
|
@@ -5362,6 +5530,11 @@ if (process.argv[2] === 'history') {
|
|
|
5362
5530
|
return;
|
|
5363
5531
|
}
|
|
5364
5532
|
|
|
5533
|
+
if (process.argv[2] === 'link') {
|
|
5534
|
+
runLinkCli();
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
|
|
5365
5538
|
if (process.argv.includes('--diagnose')) {
|
|
5366
5539
|
(async () => {
|
|
5367
5540
|
console.log(`CF Memory MCP v${PACKAGE_VERSION} - Diagnostics`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.43.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": {
|