cf-memory-mcp 3.42.0 → 3.44.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
@@ -3964,6 +3965,10 @@ function parseCliArgs(rest) {
3964
3965
  flags.raw = true;
3965
3966
  } else if (a === '--copy') {
3966
3967
  flags.copy = true;
3968
+ } else if (a === '--card') {
3969
+ flags.card = true;
3970
+ } else if (a === '--short') {
3971
+ flags.short = true;
3967
3972
  } else if (a === '--older-than') {
3968
3973
  // Accept "7d" / "30d" / "12h" / raw number (days).
3969
3974
  const raw = rest[++i] || '';
@@ -4126,6 +4131,37 @@ async function runResumeCli() {
4126
4131
  process.exit(0);
4127
4132
  }
4128
4133
 
4134
+ // --short: single line for tmux/status bars. "goal · status"
4135
+ // truncated to terminal-friendly length.
4136
+ if (flags.short) {
4137
+ const envelope = payload.resume_handoff;
4138
+ if (!envelope) process.exit(3);
4139
+ const h = envelope.handoff || {};
4140
+ const goal = (h.goal || '(no goal)').slice(0, 60);
4141
+ const status = h.status || '?';
4142
+ const stale = envelope.stale ? ' [STALE]' : '';
4143
+ process.stdout.write(`${goal} · ${status}${stale}\n`);
4144
+ process.exit(0);
4145
+ }
4146
+
4147
+ // --card: compact 2-3 line summary card for status widgets.
4148
+ // Format: [status · age · q=score] goal / → next_step
4149
+ if (flags.card) {
4150
+ const envelope = payload.resume_handoff;
4151
+ if (!envelope) process.exit(3);
4152
+ const h = envelope.handoff || {};
4153
+ const ageMin = envelope.handoff_age_minutes;
4154
+ const fmtAge = (m) => m == null ? '?' : m < 60 ? `${m}m` : m < 1440 ? `${Math.round(m/60)}h` : `${Math.round(m/1440)}d`;
4155
+ const status = h.status || '?';
4156
+ const q = typeof envelope.quality_score === 'number' ? ` · q=${envelope.quality_score}` : '';
4157
+ const staleMarker = envelope.stale ? ' · STALE' : '';
4158
+ process.stdout.write(`[${status} · ${fmtAge(ageMin)} ago${q}${staleMarker}]\n`);
4159
+ process.stdout.write(`${h.goal || '(no goal)'}\n`);
4160
+ const next = h.next_steps?.[0];
4161
+ if (next) process.stdout.write(`→ ${next}\n`);
4162
+ process.exit(0);
4163
+ }
4164
+
4129
4165
  // --chain: walk parent_session_id back through the thread history.
4130
4166
  // Bounded at 20 iterations to avoid an infinite loop on malformed
4131
4167
  // data. Prints each handoff as "<id> [age] status — goal".
@@ -4383,6 +4419,95 @@ async function runListCli() {
4383
4419
  }
4384
4420
  }
4385
4421
 
4422
+ async function runLinkCli() {
4423
+ if (!API_KEY) {
4424
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
4425
+ process.exit(1);
4426
+ }
4427
+ const { positional, flags } = parseCliArgs(process.argv.slice(3));
4428
+ const childArg = positional[0];
4429
+ // --parent <id> is parsed positionally too; grab it from process.argv.
4430
+ let parentId = null;
4431
+ const parentIdx = process.argv.indexOf('--parent');
4432
+ if (parentIdx !== -1 && process.argv[parentIdx + 1]) parentId = process.argv[parentIdx + 1];
4433
+ for (const a of process.argv) {
4434
+ if (a.startsWith('--parent=')) parentId = a.slice('--parent='.length);
4435
+ }
4436
+ if (!childArg || !parentId) {
4437
+ console.error('Usage: cf-memory-mcp link <child-id-or-prefix> --parent <parent-id-or-prefix>');
4438
+ process.exit(1);
4439
+ }
4440
+ const server = new CFMemoryMCP();
4441
+ server.logDebug = () => {};
4442
+ try {
4443
+ // Resolve child + parent prefixes to full UUIDs via get_context_bootstrap.
4444
+ const resolveId = async (idHint) => {
4445
+ if (idHint.length >= 36) return idHint;
4446
+ const r = await server.makeRequest({
4447
+ jsonrpc: '2.0', id: `cli-link-resolve-${Date.now()}`,
4448
+ method: 'tools/call',
4449
+ params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: idHint } },
4450
+ });
4451
+ const t = r?.result?.content?.[0]?.text;
4452
+ const p = JSON.parse(t || '{}');
4453
+ return p.resume_handoff?.session_id || null;
4454
+ };
4455
+ const fullChild = await resolveId(childArg);
4456
+ const fullParent = await resolveId(parentId);
4457
+ if (!fullChild) {
4458
+ process.stderr.write(`Could not resolve child "${childArg}" to a session.\n`);
4459
+ process.exit(3);
4460
+ }
4461
+ if (!fullParent) {
4462
+ process.stderr.write(`Could not resolve parent "${parentId}" to a session.\n`);
4463
+ process.exit(3);
4464
+ }
4465
+ // Fetch current child handoff, set parent_session_id, write back via end_session keep_open.
4466
+ const childRes = await server.makeRequest({
4467
+ jsonrpc: '2.0', id: `cli-link-fetch-${Date.now()}`,
4468
+ method: 'tools/call',
4469
+ params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: fullChild } },
4470
+ });
4471
+ const childText = childRes?.result?.content?.[0]?.text;
4472
+ const childPayload = JSON.parse(childText || '{}');
4473
+ const handoff = childPayload.resume_handoff?.handoff;
4474
+ if (!handoff) {
4475
+ process.stderr.write(`Child session has no handoff stored; cannot link.\n`);
4476
+ process.exit(3);
4477
+ }
4478
+ // Update with parent_session_id.
4479
+ const updated = { ...handoff, parent_session_id: fullParent };
4480
+ const updateRes = await server.makeRequest({
4481
+ jsonrpc: '2.0', id: `cli-link-update-${Date.now()}`,
4482
+ method: 'tools/call',
4483
+ params: { name: 'end_session', arguments: {
4484
+ session_id: fullChild,
4485
+ keep_open: true, // don't accidentally finalize
4486
+ handoff: updated,
4487
+ } },
4488
+ });
4489
+ const updateText = updateRes?.result?.content?.[0]?.text;
4490
+ const updatePayload = JSON.parse(updateText || '{}');
4491
+ if (flags.json) {
4492
+ process.stdout.write(JSON.stringify({
4493
+ child: fullChild,
4494
+ parent: fullParent,
4495
+ handoff_stored: !!updatePayload.handoff_stored,
4496
+ }, null, 2) + '\n');
4497
+ process.exit(updatePayload.handoff_stored ? 0 : 2);
4498
+ }
4499
+ if (updatePayload.handoff_stored) {
4500
+ process.stdout.write(`Linked ${fullChild.slice(0,8)} → parent ${fullParent.slice(0,8)}\n`);
4501
+ process.exit(0);
4502
+ }
4503
+ process.stderr.write(`Update failed: ${updateText}\n`);
4504
+ process.exit(2);
4505
+ } catch (err) {
4506
+ console.error('link command failed:', err.message);
4507
+ process.exit(1);
4508
+ }
4509
+ }
4510
+
4386
4511
  async function runHistoryCli() {
4387
4512
  if (!API_KEY) {
4388
4513
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
@@ -4640,10 +4765,41 @@ function runEnvCli() {
4640
4765
 
4641
4766
  function runCompletionCli() {
4642
4767
  const shell = process.argv[3] || 'bash';
4643
- const commands = ['resume', 'list', 'history', 'checkpoint', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
4768
+ const install = process.argv.includes('--install');
4769
+ const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
4644
4770
  const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
4771
+
4772
+ // Determine the install target for each shell. Use user-local paths
4773
+ // only (no sudo). Caller can override via stdout-redirect when
4774
+ // --install isn't used.
4775
+ const installTarget = (() => {
4776
+ const home = os.homedir();
4777
+ if (shell === 'bash') {
4778
+ // Prefer ~/.bash_completion.d/ if it exists; fall back to ~/.bash_completion.
4779
+ const dir = path.join(home, '.bash_completion.d');
4780
+ return fs.existsSync(dir) ? path.join(dir, 'cf-memory-mcp') : path.join(home, '.bash_completion');
4781
+ }
4782
+ if (shell === 'zsh') {
4783
+ // Honor $ZSH_CUSTOM/plugins or ~/.zsh/completions/
4784
+ const zdir = process.env.ZSH_CUSTOM
4785
+ ? path.join(process.env.ZSH_CUSTOM, 'plugins', 'cf-memory-mcp')
4786
+ : path.join(home, '.zsh', 'completions');
4787
+ return path.join(zdir, '_cf-memory-mcp');
4788
+ }
4789
+ if (shell === 'fish') {
4790
+ return path.join(home, '.config', 'fish', 'completions', 'cf-memory-mcp.fish');
4791
+ }
4792
+ if (shell === 'powershell' || shell === 'pwsh') {
4793
+ return path.join(home, '.config', 'powershell', 'cf-memory-mcp.ps1');
4794
+ }
4795
+ return null;
4796
+ })();
4797
+ // Buffer the output so we can either print or write-to-disk.
4798
+ let output = '';
4799
+ const emit = (chunk) => { output += chunk; };
4800
+
4645
4801
  if (shell === 'bash') {
4646
- process.stdout.write(`# cf-memory-mcp bash completion
4802
+ emit(`# cf-memory-mcp bash completion
4647
4803
  # Install: cf-memory-mcp completion bash > /usr/local/etc/bash_completion.d/cf-memory-mcp
4648
4804
  # Or for the current shell: source <(cf-memory-mcp completion bash)
4649
4805
  _cf_memory_mcp_complete() {
@@ -4665,7 +4821,7 @@ complete -F _cf_memory_mcp_complete cf-memory-mcp
4665
4821
  complete -F _cf_memory_mcp_complete cfm
4666
4822
  `);
4667
4823
  } else if (shell === 'zsh') {
4668
- process.stdout.write(`# cf-memory-mcp zsh completion
4824
+ emit(`# cf-memory-mcp zsh completion
4669
4825
  # Install: cf-memory-mcp completion zsh > "\${fpath[1]}/_cf-memory-mcp" && compinit
4670
4826
  # Or for the current shell: source <(cf-memory-mcp completion zsh)
4671
4827
  #compdef cf-memory-mcp cfm
@@ -4686,7 +4842,7 @@ _cf_memory_mcp() {
4686
4842
  _cf_memory_mcp "\$@"
4687
4843
  `);
4688
4844
  } else if (shell === 'powershell' || shell === 'pwsh') {
4689
- process.stdout.write(`# cf-memory-mcp PowerShell completion
4845
+ emit(`# cf-memory-mcp PowerShell completion
4690
4846
  # Install: cf-memory-mcp completion powershell | Out-String | Invoke-Expression
4691
4847
  # To persist: add the above to your $PROFILE
4692
4848
  Register-ArgumentCompleter -Native -CommandName cf-memory-mcp,cfm -ScriptBlock {
@@ -4706,7 +4862,7 @@ Register-ArgumentCompleter -Native -CommandName cf-memory-mcp,cfm -ScriptBlock {
4706
4862
  }
4707
4863
  `);
4708
4864
  } else if (shell === 'fish') {
4709
- process.stdout.write(`# cf-memory-mcp fish completion
4865
+ emit(`# cf-memory-mcp fish completion
4710
4866
  # Install: cf-memory-mcp completion fish > ~/.config/fish/completions/cf-memory-mcp.fish
4711
4867
  ${commands.map(c => `complete -c cf-memory-mcp -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`).join('\n')}
4712
4868
  ${commands.map(c => `complete -c cfm -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`).join('\n')}
@@ -4721,6 +4877,31 @@ complete -c cf-memory-mcp -l version -s v -d 'Show version'
4721
4877
  process.stderr.write(`Unknown shell "${shell}". Supported: bash, zsh, fish, powershell.\n`);
4722
4878
  process.exit(1);
4723
4879
  }
4880
+
4881
+ // Either install to a user-local path or print to stdout.
4882
+ if (install) {
4883
+ if (!installTarget) {
4884
+ process.stderr.write(`No install target for shell ${shell}.\n`);
4885
+ process.exit(1);
4886
+ }
4887
+ try {
4888
+ const dir = path.dirname(installTarget);
4889
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
4890
+ fs.writeFileSync(installTarget, output);
4891
+ process.stderr.write(`Installed ${shell} completion to ${installTarget}\n`);
4892
+ if (shell === 'bash') {
4893
+ process.stderr.write(`Add to ~/.bashrc to autoload: source ${installTarget}\n`);
4894
+ } else if (shell === 'zsh') {
4895
+ const dirToFpath = path.dirname(installTarget);
4896
+ process.stderr.write(`Add to ~/.zshrc:\n fpath=(${dirToFpath} $fpath)\n autoload -Uz compinit && compinit\n`);
4897
+ }
4898
+ } catch (err) {
4899
+ process.stderr.write(`Failed to install: ${err.message}\n`);
4900
+ process.exit(1);
4901
+ }
4902
+ } else {
4903
+ process.stdout.write(output);
4904
+ }
4724
4905
  process.exit(0);
4725
4906
  }
4726
4907
 
@@ -4731,6 +4912,7 @@ const PER_COMMAND_HELP = {
4731
4912
  <session-id-prefix> Optional: pick a specific session (>=8 char prefix or full UUID).
4732
4913
  --md <path> Write the markdown to a file instead of stdout.
4733
4914
  --copy Pipe the markdown to the platform clipboard (pbcopy/xclip/wl-copy/clip).
4915
+ --card Compact 2-3 line status card (for terminal status widgets).
4734
4916
  Extract flags (pick one; each exits 3 if the section is empty):
4735
4917
  --next-only First next_step only (for shell prompts).
4736
4918
  --next-steps All next_steps, numbered.
@@ -4806,6 +4988,11 @@ const PER_COMMAND_HELP = {
4806
4988
  --repo PATH Override the cwd's repo.
4807
4989
  --since ISO Lower bound on handoff timestamp.
4808
4990
  --json, -j Emit JSON.`,
4991
+ link: `cf-memory-mcp link <child-id-or-prefix> --parent <parent-id-or-prefix> [--json]
4992
+ Retroactively set parent_session_id on a child session, linking it to
4993
+ a parent in the chain. Useful when you forgot --force on checkpoint
4994
+ or want to manually chain sessions across repos.
4995
+ --json, -j Emit a JSON status object.`,
4809
4996
  };
4810
4997
 
4811
4998
  function printPerCommandHelp(cmd) {
@@ -5414,6 +5601,11 @@ if (process.argv[2] === 'history') {
5414
5601
  return;
5415
5602
  }
5416
5603
 
5604
+ if (process.argv[2] === 'link') {
5605
+ runLinkCli();
5606
+ return;
5607
+ }
5608
+
5417
5609
  if (process.argv.includes('--diagnose')) {
5418
5610
  (async () => {
5419
5611
  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.42.0",
3
+ "version": "3.44.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": {