cf-memory-mcp 3.62.0 → 3.63.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.
@@ -3955,7 +3955,7 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
3955
3955
 
3956
3956
  // Global --help only when no subcommand is present. With a subcommand, fall
3957
3957
  // through to the per-command help dispatch below.
3958
- const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history', 'link', 'explain', 'gha']);
3958
+ const SUBCOMMANDS = new Set(['resume', 'list', 'search', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history', 'link', 'explain', 'gha']);
3959
3959
  const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
3960
3960
 
3961
3961
  if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
@@ -3968,6 +3968,7 @@ Usage:
3968
3968
  npx cf-memory-mcp Start the MCP server
3969
3969
  npx cf-memory-mcp resume [id] Print the prior resume handoff (markdown)
3970
3970
  npx cf-memory-mcp list List recent handoffs for cwd (status-ranked)
3971
+ npx cf-memory-mcp search <query> Find handoffs by goal/notes/decisions text (cross-repo default)
3971
3972
  npx cf-memory-mcp history List all handoffs for cwd, chronological
3972
3973
  npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
3973
3974
  npx cf-memory-mcp link <child> --parent <id> Retroactively link a child session to a parent
@@ -4158,6 +4159,10 @@ function parseCliArgs(rest) {
4158
4159
  flags.since = a.slice('--since='.length);
4159
4160
  } else if (a === '--yes' || a === '-y') {
4160
4161
  flags.yes = true;
4162
+ } else if (a === '--ids-only') {
4163
+ flags.ids_only = true;
4164
+ } else if (a === '--here') {
4165
+ flags.here = true;
4161
4166
  } else positional.push(a);
4162
4167
  }
4163
4168
  return { positional, flags };
@@ -4995,6 +5000,116 @@ async function runListCli() {
4995
5000
  }
4996
5001
  }
4997
5002
 
5003
+ async function runSearchCli() {
5004
+ if (!API_KEY) {
5005
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
5006
+ process.exit(1);
5007
+ }
5008
+ const { positional, flags } = parseCliArgs(process.argv.slice(3));
5009
+ const query = positional[0];
5010
+ if (!query) {
5011
+ console.error('Usage: cf-memory-mcp search <query> [--repo <path>] [--project-id <id>] [--status <s>] [--since <iso>] [--limit N] [--json] [--ids-only]');
5012
+ console.error('Searches across goal/next_steps/notes/decisions/blockers (case-insensitive substring).');
5013
+ console.error('Defaults to ALL your handoffs (cross-repo). Use --repo or --project-id to scope.');
5014
+ process.exit(1);
5015
+ }
5016
+ const server = new CFMemoryMCP();
5017
+ server.logDebug = () => {};
5018
+ server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
5019
+ try {
5020
+ // Server-side substring filter via goal_contains (which actually
5021
+ // matches the whole serialized handoff JSON — goal, next_steps,
5022
+ // notes, decisions, blockers, code_anchors, files_touched).
5023
+ const args = { resume: true, goal_contains: query };
5024
+ // Default: NO repo/project scoping — search is meant to be global.
5025
+ // Opt-in via --repo or --project-id (or --here for cwd's repo).
5026
+ if (flags.repo_path) args.repo_path = flags.repo_path;
5027
+ if (flags.project_id) args.project_id = flags.project_id;
5028
+ if (flags.here) {
5029
+ const meta = server.getRepoMetadata();
5030
+ if (meta.repo_path) args.repo_path = meta.repo_path;
5031
+ if (meta.branch) args.branch = meta.branch;
5032
+ }
5033
+ if (flags.status) args.status_filter = flags.status;
5034
+ if (flags.since) args.since = flags.since;
5035
+
5036
+ const response = await server.makeRequest({
5037
+ jsonrpc: '2.0',
5038
+ id: `cli-search-${Date.now()}`,
5039
+ method: 'tools/call',
5040
+ params: { name: 'get_context_bootstrap', arguments: args },
5041
+ });
5042
+ const text = response?.result?.content?.[0]?.text;
5043
+ const payload = JSON.parse(text || '{}');
5044
+ const allList = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
5045
+ const list = flags.limit ? allList.slice(0, flags.limit) : allList;
5046
+
5047
+ if (flags.ids_only) {
5048
+ for (const h of list) process.stdout.write(`${h.session_id || ''}\n`);
5049
+ process.exit(list.length === 0 ? 3 : 0);
5050
+ }
5051
+ if (flags.json) {
5052
+ process.stdout.write(JSON.stringify({
5053
+ query,
5054
+ scope: args.repo_path ? 'repo' : args.project_id ? 'project' : 'global',
5055
+ repo_path: args.repo_path,
5056
+ project_id: args.project_id,
5057
+ status_filter: flags.status,
5058
+ since: flags.since,
5059
+ count: list.length,
5060
+ handoffs: list,
5061
+ }, null, 2) + '\n');
5062
+ process.exit(list.length === 0 ? 3 : 0);
5063
+ }
5064
+ if (list.length === 0) {
5065
+ process.stderr.write(`No handoffs match "${query}".\n`);
5066
+ process.exit(3);
5067
+ }
5068
+ // Render with terminal highlight (ANSI bold) when stdout is a TTY.
5069
+ const tty = process.stdout.isTTY;
5070
+ const lcQuery = query.toLowerCase();
5071
+ const highlight = (str) => {
5072
+ if (!str) return str;
5073
+ if (!tty) return str;
5074
+ const idx = str.toLowerCase().indexOf(lcQuery);
5075
+ if (idx < 0) return str;
5076
+ const before = str.slice(0, idx);
5077
+ const match = str.slice(idx, idx + query.length);
5078
+ const after = str.slice(idx + query.length);
5079
+ return `${before}\x1b[1;33m${match}\x1b[0m${after}`;
5080
+ };
5081
+ const fmtAge = (iso) => {
5082
+ if (!iso) return '?';
5083
+ const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
5084
+ if (min < 60) return `${min}m`;
5085
+ if (min < 1440) return `${Math.round(min/60)}h`;
5086
+ return `${Math.round(min/1440)}d`;
5087
+ };
5088
+ const scopeLabel = args.repo_path ? `in repo ${args.repo_path}` : args.project_id ? `in project ${args.project_id}` : 'across all your handoffs';
5089
+ process.stdout.write(`Found ${list.length} handoff${list.length === 1 ? '' : 's'} matching "${query}" ${scopeLabel}:\n\n`);
5090
+ for (const h of list) {
5091
+ const shortId = (h.session_id || '').slice(0, 8);
5092
+ const age = fmtAge(h.ended_at || h.started_at);
5093
+ const status = (h.status || 'unknown').padEnd(11);
5094
+ const branch = h.branch ? `[${h.branch}] ` : '';
5095
+ const goal = highlight((h.goal || '(no goal)').slice(0, 100));
5096
+ const q = typeof h.quality_score === 'number' ? ` q=${h.quality_score.toFixed(2)}` : '';
5097
+ process.stdout.write(` ${shortId} ${status} ${age.padStart(4)} ago${q} ${branch}${goal}\n`);
5098
+ if (h.next_step) process.stdout.write(` next: ${highlight(h.next_step.slice(0, 100))}\n`);
5099
+ // If the repo differs from the user's cwd, surface it so they
5100
+ // know which project the handoff belongs to.
5101
+ if (h.repo_path && !args.repo_path) {
5102
+ process.stdout.write(` repo: ${h.repo_path}\n`);
5103
+ }
5104
+ }
5105
+ process.stdout.write(`\n(Run "npx cf-memory-mcp resume <id>" to load one, or pipe to "xargs -I{} npx cf-memory-mcp resume {}" with --ids-only)\n`);
5106
+ process.exit(0);
5107
+ } catch (err) {
5108
+ console.error('search command failed:', err.message);
5109
+ process.exit(1);
5110
+ }
5111
+ }
5112
+
4998
5113
  async function runLinkCli() {
4999
5114
  if (!API_KEY) {
5000
5115
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
@@ -5389,7 +5504,7 @@ function runCompletionCli() {
5389
5504
  process.stderr.write(`\nDone. Installed completion for ${installed} shell${installed === 1 ? '' : 's'}.\n`);
5390
5505
  process.exit(installed > 0 ? 0 : 1);
5391
5506
  }
5392
- const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'explain', 'gha', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
5507
+ const commands = ['resume', 'list', 'search', 'history', 'checkpoint', 'link', 'explain', 'gha', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
5393
5508
  const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
5394
5509
 
5395
5510
  // Determine the install target for each shell. Use user-local paths
@@ -5593,6 +5708,23 @@ const PER_COMMAND_HELP = {
5593
5708
  --repo PATH Override the cwd's repo (peek at another repo).
5594
5709
  --limit N, -n N Max number of entries (default 5, max 50).
5595
5710
  --json, -j Emit a JSON array for scripts.`,
5711
+ search: `cf-memory-mcp search <query> [--here | --repo PATH | --project-id ID] [--status S] [--since ISO] [--limit N] [--json|--ids-only]
5712
+ Find handoffs matching <query> across goal, next_steps, notes,
5713
+ decisions, blockers, code_anchors, and files_touched (case-insensitive
5714
+ substring). Default scope is ALL your handoffs (cross-repo). Highlights
5715
+ the matched substring in terminal output.
5716
+ <query> Required substring to search for.
5717
+ --here Scope to cwd's repo (otherwise: global).
5718
+ --repo PATH Scope to a specific repo path.
5719
+ --project-id ID Scope to a specific project.
5720
+ --status S Filter by status (in_progress, completed, ...).
5721
+ --since ISO Lower bound on handoff timestamp (ISO 8601).
5722
+ --limit N Max entries to render (server may cap at 50).
5723
+ --ids-only Print only session_ids (xargs-friendly).
5724
+ --json, -j Emit JSON for scripts.
5725
+ Exit codes: 0 = matches, 3 = no matches.
5726
+ Example: cf-memory-mcp search "OAuth" --status in_progress
5727
+ cf-memory-mcp search "migration" --ids-only | xargs -I{} cfm resume {}`,
5596
5728
  checkpoint: `cf-memory-mcp checkpoint ["<goal>"] [--force] [--json]
5597
5729
  Snapshot current state to a fresh handoff with keep_open:true. The
5598
5730
  session stays active for future checkpoints. The final end_session (or
@@ -6428,6 +6560,11 @@ if (process.argv[2] === 'list') {
6428
6560
  return;
6429
6561
  }
6430
6562
 
6563
+ if (process.argv[2] === 'search') {
6564
+ runSearchCli();
6565
+ return;
6566
+ }
6567
+
6431
6568
  if (process.argv[2] === 'checkpoint') {
6432
6569
  runCheckpointCli();
6433
6570
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.62.0",
3
+ "version": "3.63.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": {