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.
@@ -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.41.0",
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": {