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.
- package/bin/cf-memory-mcp.js +139 -2
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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.
|
|
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": {
|