cf-memory-mcp 3.28.0 → 3.30.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.
Files changed (2) hide show
  1. package/bin/cf-memory-mcp.js +178 -11
  2. package/package.json +1 -1
@@ -3001,7 +3001,19 @@ class CFMemoryMCP {
3001
3001
  cwd: process.env.CF_MEMORY_WATCH_PATH || process.cwd(),
3002
3002
  response_text: text,
3003
3003
  };
3004
- fs.writeFileSync(p, JSON.stringify(entry, null, 2));
3004
+ // Atomic write: serialize to a sibling .tmp then rename so a
3005
+ // crash mid-write never leaves a corrupt JSON file. rename(2)
3006
+ // is atomic on POSIX. On Windows it may fail if the target
3007
+ // exists, so fall back to a direct write there.
3008
+ const tmp = `${p}.tmp.${process.pid}`;
3009
+ try {
3010
+ fs.writeFileSync(tmp, JSON.stringify(entry, null, 2));
3011
+ fs.renameSync(tmp, p);
3012
+ } catch (renameErr) {
3013
+ // Fallback: direct write. Best-effort cleanup of .tmp.
3014
+ try { fs.unlinkSync(tmp); } catch (_) { /* ignore */ }
3015
+ fs.writeFileSync(p, JSON.stringify(entry, null, 2));
3016
+ }
3005
3017
  _mcpTrace('DISK_CACHE', `saved resume packet to ${p}`);
3006
3018
  } catch (err) {
3007
3019
  this.logDebug(`saveResumeToDisk failed: ${err && err.message}`);
@@ -3778,7 +3790,12 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
3778
3790
  process.exit(0);
3779
3791
  }
3780
3792
 
3781
- if (process.argv.includes('--help') || process.argv.includes('-h')) {
3793
+ // Global --help only when no subcommand is present. With a subcommand, fall
3794
+ // through to the per-command help dispatch below.
3795
+ const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion']);
3796
+ const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
3797
+
3798
+ if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
3782
3799
  console.log(`
3783
3800
  CF Memory MCP v${PACKAGE_VERSION}
3784
3801
 
@@ -3795,6 +3812,8 @@ Usage:
3795
3812
  npx cf-memory-mcp export <id> Print a session's handoff as JSON bundle (backup)
3796
3813
  npx cf-memory-mcp import <file> Restore a handoff bundle (cross-machine sync); use "-" for stdin
3797
3814
  npx cf-memory-mcp doctor Diagnose common setup issues
3815
+ npx cf-memory-mcp completion bash Output shell completion script (bash|zsh|fish)
3816
+ npx cf-memory-mcp <cmd> --help Show flags for a specific command
3798
3817
  npx cf-memory-mcp --version Show version
3799
3818
 
3800
3819
  Short alias: \`cfm\` works as a drop-in for \`cf-memory-mcp\` (e.g., \`cfm resume\`).
@@ -4003,20 +4022,147 @@ async function runListCli() {
4003
4022
  }
4004
4023
  }
4005
4024
 
4025
+ function runCompletionCli() {
4026
+ const shell = process.argv[3] || 'bash';
4027
+ const commands = ['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion'];
4028
+ const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
4029
+ if (shell === 'bash') {
4030
+ process.stdout.write(`# cf-memory-mcp bash completion
4031
+ # Install: cf-memory-mcp completion bash > /usr/local/etc/bash_completion.d/cf-memory-mcp
4032
+ # Or for the current shell: source <(cf-memory-mcp completion bash)
4033
+ _cf_memory_mcp_complete() {
4034
+ local cur prev words
4035
+ COMPREPLY=()
4036
+ cur="\${COMP_WORDS[COMP_CWORD]}"
4037
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
4038
+ if [ "\$COMP_CWORD" -eq 1 ]; then
4039
+ COMPREPLY=( $(compgen -W "${commands.join(' ')}" -- "\$cur") )
4040
+ return 0
4041
+ fi
4042
+ case "\$cur" in
4043
+ --*)
4044
+ COMPREPLY=( $(compgen -W "${flags.filter(f => f.startsWith('--')).join(' ')}" -- "\$cur") )
4045
+ ;;
4046
+ esac
4047
+ }
4048
+ complete -F _cf_memory_mcp_complete cf-memory-mcp
4049
+ complete -F _cf_memory_mcp_complete cfm
4050
+ `);
4051
+ } else if (shell === 'zsh') {
4052
+ process.stdout.write(`# cf-memory-mcp zsh completion
4053
+ # Install: cf-memory-mcp completion zsh > "\${fpath[1]}/_cf-memory-mcp" && compinit
4054
+ # Or for the current shell: source <(cf-memory-mcp completion zsh)
4055
+ #compdef cf-memory-mcp cfm
4056
+ _cf_memory_mcp() {
4057
+ local -a commands
4058
+ commands=(
4059
+ ${commands.map(c => `'${c}:${c} command'`).join('\n ')}
4060
+ )
4061
+ if (( CURRENT == 2 )); then
4062
+ _describe 'command' commands
4063
+ else
4064
+ case "\$words[2]" in
4065
+ resume|export) _files ;;
4066
+ import) _files -g '*.json' ;;
4067
+ esac
4068
+ fi
4069
+ }
4070
+ _cf_memory_mcp "\$@"
4071
+ `);
4072
+ } else if (shell === 'fish') {
4073
+ process.stdout.write(`# cf-memory-mcp fish completion
4074
+ # Install: cf-memory-mcp completion fish > ~/.config/fish/completions/cf-memory-mcp.fish
4075
+ ${commands.map(c => `complete -c cf-memory-mcp -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`).join('\n')}
4076
+ ${commands.map(c => `complete -c cfm -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`).join('\n')}
4077
+ complete -c cf-memory-mcp -l json -s j -d 'Emit JSON for scripts'
4078
+ complete -c cf-memory-mcp -l limit -s n -d 'Limit number of results'
4079
+ complete -c cf-memory-mcp -l md -d 'Write markdown to file'
4080
+ complete -c cf-memory-mcp -l force -s f -d 'Bypass duplicate detection'
4081
+ complete -c cf-memory-mcp -l help -s h -d 'Show help'
4082
+ complete -c cf-memory-mcp -l version -s v -d 'Show version'
4083
+ `);
4084
+ } else {
4085
+ process.stderr.write(`Unknown shell "${shell}". Supported: bash, zsh, fish.\n`);
4086
+ process.exit(1);
4087
+ }
4088
+ process.exit(0);
4089
+ }
4090
+
4091
+ // Per-command help texts. Used when "cf-memory-mcp <cmd> --help" is invoked.
4092
+ const PER_COMMAND_HELP = {
4093
+ resume: `cf-memory-mcp resume [<session-id-prefix>] [--md path] [--json]
4094
+ Print the prior resume handoff (markdown by default).
4095
+ <session-id-prefix> Optional: pick a specific session (>=8 char prefix or full UUID).
4096
+ --md <path> Write the markdown to a file instead of stdout.
4097
+ --json, -j Emit the full bootstrap payload as JSON for scripts.
4098
+ Exit codes: 0 = handoff found, 3 = no handoff found.`,
4099
+ list: `cf-memory-mcp list [--limit N] [--json]
4100
+ List recent handoffs for the current cwd, status-ranked.
4101
+ --limit N, -n N Max number of entries (default 5, max 50).
4102
+ --json, -j Emit a JSON array for scripts.`,
4103
+ checkpoint: `cf-memory-mcp checkpoint ["<goal>"] [--force] [--json]
4104
+ Snapshot current state to a fresh handoff with keep_open:true. The
4105
+ session stays active for future checkpoints. The final end_session (or
4106
+ bridge shutdown) finalizes.
4107
+ <goal> Explicit goal string. Defaults to synthesized.
4108
+ --force, -f Bypass duplicate-session detection.
4109
+ --json, -j Emit a JSON status object.
4110
+ Exit codes: 0 = saved, 2 = not stored, 4 = duplicate detected.`,
4111
+ status: `cf-memory-mcp status [--json]
4112
+ Show bridge + server state for the current cwd: version, repo/branch,
4113
+ disk-cache age, server resume availability, and recent-handoff count.
4114
+ --json, -j Emit a JSON status object.`,
4115
+ clean: `cf-memory-mcp clean [--all] [--json]
4116
+ Delete the local disk cache for the current cwd.
4117
+ --all Delete ALL cf-memory disk caches.
4118
+ --json, -j Emit a JSON list of removed paths.`,
4119
+ export: `cf-memory-mcp export <session-id-or-prefix> [--md path]
4120
+ Print a handoff as a JSON bundle to stdout (or write to file). For
4121
+ backup or cross-machine sync.
4122
+ <session-id> Full UUID or short prefix (>=8 chars).
4123
+ --md <path> Write the JSON to a file.`,
4124
+ import: `cf-memory-mcp import [<file>|-] [--json]
4125
+ Restore a handoff bundle into a NEW session (keep_open). Cross-machine
4126
+ sync. Use "-" to read from stdin.
4127
+ <file> Bundle file path; "-" for stdin.
4128
+ --json, -j Emit a JSON status object.`,
4129
+ doctor: `cf-memory-mcp doctor [--json]
4130
+ Diagnose common setup issues. Checks API key, git repo/branch, disk-
4131
+ cache writability, worker reachability, project indexing, resume
4132
+ availability. Exit nonzero if any check fails.
4133
+ --json, -j Emit a JSON list of checks.`,
4134
+ completion: `cf-memory-mcp completion [bash|zsh|fish]
4135
+ Output shell completion script. Pipe to your shell's completion dir.`,
4136
+ };
4137
+
4138
+ function printPerCommandHelp(cmd) {
4139
+ const help = PER_COMMAND_HELP[cmd];
4140
+ if (help) {
4141
+ process.stdout.write(help + '\n');
4142
+ process.exit(0);
4143
+ }
4144
+ }
4145
+
4006
4146
  async function runDoctorCli() {
4007
4147
  const { flags } = parseCliArgs(process.argv.slice(3));
4008
4148
  const server = new CFMemoryMCP();
4009
4149
  server.logDebug = () => {};
4010
4150
  const checks = [];
4011
- const add = (label, ok, detail) => checks.push({ label, ok, detail });
4151
+ const add = (label, ok, detail, fix) => checks.push({ label, ok, detail, ...(fix && !ok ? { fix } : {}) });
4012
4152
 
4013
4153
  // 1. API key set?
4014
- add('CF_MEMORY_API_KEY set', !!API_KEY, API_KEY ? '(redacted)' : 'unset — most commands will fail');
4154
+ add('CF_MEMORY_API_KEY set', !!API_KEY,
4155
+ API_KEY ? '(redacted)' : 'unset — most commands will fail',
4156
+ 'export CF_MEMORY_API_KEY=<your-key> # get one at https://memcp.ai');
4015
4157
 
4016
4158
  // 2. Cwd is in a git repo?
4017
4159
  const meta = server.getRepoMetadata();
4018
- add('git repo detected', !!meta.repo_path, meta.repo_path || 'no .git in cwd; resume metadata will be empty');
4019
- add('git branch detected', !!meta.branch, meta.branch || 'detached HEAD or no commits');
4160
+ add('git repo detected', !!meta.repo_path,
4161
+ meta.repo_path || 'no .git in cwd; resume metadata will be empty',
4162
+ 'cd into a repo, or run: git init');
4163
+ add('git branch detected', !!meta.branch,
4164
+ meta.branch || 'detached HEAD or no commits',
4165
+ meta.repo_path ? 'git checkout -b main # or commit something first' : null);
4020
4166
 
4021
4167
  // 3. Disk cache writable?
4022
4168
  const cachePath = server.getDiskCachePath();
@@ -4030,7 +4176,8 @@ async function runDoctorCli() {
4030
4176
  fs.unlinkSync(probe);
4031
4177
  diskWritable = true;
4032
4178
  } catch (err) {
4033
- add('disk cache writable', false, `failed: ${err.message}`);
4179
+ add('disk cache writable', false, `failed: ${err.message}`,
4180
+ `chmod -R u+w ${path.dirname(cachePath)} # or set CF_MEMORY_DISK_CACHE=off to disable`);
4034
4181
  }
4035
4182
  }
4036
4183
  if (diskWritable) add('disk cache writable', true, path.dirname(cachePath));
@@ -4048,14 +4195,18 @@ async function runDoctorCli() {
4048
4195
  workerReachable = !res?.error;
4049
4196
  workerMs = Date.now() - t0;
4050
4197
  } catch (_) { /* unreachable */ }
4051
- add('worker reachable', workerReachable, workerReachable ? `${workerMs}ms` : `failed`);
4198
+ add('worker reachable', workerReachable,
4199
+ workerReachable ? `${workerMs}ms` : `failed`,
4200
+ `curl -I ${BASE_URL}/health # check the worker URL and your network`);
4052
4201
 
4053
4202
  // 5. Project indexed?
4054
4203
  if (workerReachable) {
4055
4204
  const fake = { params: { name: 'retrieve_context', arguments: {} } };
4056
4205
  await server.maybeFillProjectId(fake);
4057
4206
  const pid = fake.params.arguments.project_id;
4058
- add('project indexed', !!pid, pid || 'no project matched cwd — run index_project to enable retrieve_context');
4207
+ add('project indexed', !!pid,
4208
+ pid || 'no project matched cwd — run index_project to enable retrieve_context',
4209
+ 'In your MCP client: call index_project({project_path:"' + (meta.repo_path || process.cwd()) + '"})');
4059
4210
 
4060
4211
  // 6. Resume handoff available?
4061
4212
  if (meta.repo_path) {
@@ -4073,7 +4224,8 @@ async function runDoctorCli() {
4073
4224
  add('resume handoff available', hasHandoff,
4074
4225
  hasHandoff
4075
4226
  ? `${(probePayload.resume_handoff.session_id||'').slice(0,8)} (${probePayload.resume_handoff.handoff_age_minutes}m old)`
4076
- : 'none for this context');
4227
+ : 'none for this context',
4228
+ 'cf-memory-mcp checkpoint "what you are working on" # or end_session({handoff:...}) in your MCP client');
4077
4229
  } catch (_) { /* skip */ }
4078
4230
  }
4079
4231
  }
@@ -4089,9 +4241,12 @@ async function runDoctorCli() {
4089
4241
  const mark = c.ok ? '✓' : '✗';
4090
4242
  if (!c.ok) anyFailed = true;
4091
4243
  process.stdout.write(` ${mark} ${c.label.padEnd(28)} ${c.detail || ''}\n`);
4244
+ if (!c.ok && c.fix) {
4245
+ process.stdout.write(` → ${c.fix}\n`);
4246
+ }
4092
4247
  }
4093
4248
  process.stdout.write('\n');
4094
- process.stdout.write(anyFailed ? 'Some checks failed. See details above.\n' : 'All checks passed.\n');
4249
+ process.stdout.write(anyFailed ? 'Some checks failed. See suggestions above.\n' : 'All checks passed.\n');
4095
4250
  process.exit(anyFailed ? 1 : 0);
4096
4251
  }
4097
4252
 
@@ -4498,6 +4653,13 @@ async function runCheckpointCli() {
4498
4653
  }
4499
4654
  }
4500
4655
 
4656
+ // Per-command --help: intercept BEFORE any subcommand dispatch so the
4657
+ // dispatched function doesn't see --help as a positional arg.
4658
+ if (process.argv[2] && process.argv.slice(3).some(a => a === '--help' || a === '-h')) {
4659
+ printPerCommandHelp(process.argv[2]);
4660
+ // If not a known command, fall through to general help.
4661
+ }
4662
+
4501
4663
  if (process.argv[2] === 'resume') {
4502
4664
  runResumeCli();
4503
4665
  return;
@@ -4538,6 +4700,11 @@ if (process.argv[2] === 'doctor') {
4538
4700
  return;
4539
4701
  }
4540
4702
 
4703
+ if (process.argv[2] === 'completion') {
4704
+ runCompletionCli();
4705
+ return;
4706
+ }
4707
+
4541
4708
  if (process.argv.includes('--diagnose')) {
4542
4709
  (async () => {
4543
4710
  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.28.0",
3
+ "version": "3.30.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": {