cf-memory-mcp 3.62.0 → 3.64.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 +179 -7
- 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
|
|
@@ -4144,6 +4145,10 @@ function parseCliArgs(rest) {
|
|
|
4144
4145
|
flags.repo_path = rest[++i];
|
|
4145
4146
|
} else if (a.startsWith('--repo=')) {
|
|
4146
4147
|
flags.repo_path = a.slice('--repo='.length);
|
|
4148
|
+
} else if (a === '--repo-prefix') {
|
|
4149
|
+
flags.repo_path_prefix = rest[++i];
|
|
4150
|
+
} else if (a.startsWith('--repo-prefix=')) {
|
|
4151
|
+
flags.repo_path_prefix = a.slice('--repo-prefix='.length);
|
|
4147
4152
|
} else if (a === '--project-id') {
|
|
4148
4153
|
flags.project_id = rest[++i];
|
|
4149
4154
|
} else if (a.startsWith('--project-id=')) {
|
|
@@ -4158,6 +4163,10 @@ function parseCliArgs(rest) {
|
|
|
4158
4163
|
flags.since = a.slice('--since='.length);
|
|
4159
4164
|
} else if (a === '--yes' || a === '-y') {
|
|
4160
4165
|
flags.yes = true;
|
|
4166
|
+
} else if (a === '--ids-only') {
|
|
4167
|
+
flags.ids_only = true;
|
|
4168
|
+
} else if (a === '--here') {
|
|
4169
|
+
flags.here = true;
|
|
4161
4170
|
} else positional.push(a);
|
|
4162
4171
|
}
|
|
4163
4172
|
return { positional, flags };
|
|
@@ -4995,6 +5004,116 @@ async function runListCli() {
|
|
|
4995
5004
|
}
|
|
4996
5005
|
}
|
|
4997
5006
|
|
|
5007
|
+
async function runSearchCli() {
|
|
5008
|
+
if (!API_KEY) {
|
|
5009
|
+
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
5010
|
+
process.exit(1);
|
|
5011
|
+
}
|
|
5012
|
+
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
5013
|
+
const query = positional[0];
|
|
5014
|
+
if (!query) {
|
|
5015
|
+
console.error('Usage: cf-memory-mcp search <query> [--repo <path>] [--project-id <id>] [--status <s>] [--since <iso>] [--limit N] [--json] [--ids-only]');
|
|
5016
|
+
console.error('Searches across goal/next_steps/notes/decisions/blockers (case-insensitive substring).');
|
|
5017
|
+
console.error('Defaults to ALL your handoffs (cross-repo). Use --repo or --project-id to scope.');
|
|
5018
|
+
process.exit(1);
|
|
5019
|
+
}
|
|
5020
|
+
const server = new CFMemoryMCP();
|
|
5021
|
+
server.logDebug = () => {};
|
|
5022
|
+
server.logError = (...a) => process.stderr.write(a.join(' ') + '\n');
|
|
5023
|
+
try {
|
|
5024
|
+
// Server-side substring filter via goal_contains (which actually
|
|
5025
|
+
// matches the whole serialized handoff JSON — goal, next_steps,
|
|
5026
|
+
// notes, decisions, blockers, code_anchors, files_touched).
|
|
5027
|
+
const args = { resume: true, goal_contains: query };
|
|
5028
|
+
// Default: NO repo/project scoping — search is meant to be global.
|
|
5029
|
+
// Opt-in via --repo or --project-id (or --here for cwd's repo).
|
|
5030
|
+
if (flags.repo_path) args.repo_path = flags.repo_path;
|
|
5031
|
+
if (flags.project_id) args.project_id = flags.project_id;
|
|
5032
|
+
if (flags.here) {
|
|
5033
|
+
const meta = server.getRepoMetadata();
|
|
5034
|
+
if (meta.repo_path) args.repo_path = meta.repo_path;
|
|
5035
|
+
if (meta.branch) args.branch = meta.branch;
|
|
5036
|
+
}
|
|
5037
|
+
if (flags.status) args.status_filter = flags.status;
|
|
5038
|
+
if (flags.since) args.since = flags.since;
|
|
5039
|
+
|
|
5040
|
+
const response = await server.makeRequest({
|
|
5041
|
+
jsonrpc: '2.0',
|
|
5042
|
+
id: `cli-search-${Date.now()}`,
|
|
5043
|
+
method: 'tools/call',
|
|
5044
|
+
params: { name: 'get_context_bootstrap', arguments: args },
|
|
5045
|
+
});
|
|
5046
|
+
const text = response?.result?.content?.[0]?.text;
|
|
5047
|
+
const payload = JSON.parse(text || '{}');
|
|
5048
|
+
const allList = Array.isArray(payload.recent_handoffs) ? payload.recent_handoffs : [];
|
|
5049
|
+
const list = flags.limit ? allList.slice(0, flags.limit) : allList;
|
|
5050
|
+
|
|
5051
|
+
if (flags.ids_only) {
|
|
5052
|
+
for (const h of list) process.stdout.write(`${h.session_id || ''}\n`);
|
|
5053
|
+
process.exit(list.length === 0 ? 3 : 0);
|
|
5054
|
+
}
|
|
5055
|
+
if (flags.json) {
|
|
5056
|
+
process.stdout.write(JSON.stringify({
|
|
5057
|
+
query,
|
|
5058
|
+
scope: args.repo_path ? 'repo' : args.project_id ? 'project' : 'global',
|
|
5059
|
+
repo_path: args.repo_path,
|
|
5060
|
+
project_id: args.project_id,
|
|
5061
|
+
status_filter: flags.status,
|
|
5062
|
+
since: flags.since,
|
|
5063
|
+
count: list.length,
|
|
5064
|
+
handoffs: list,
|
|
5065
|
+
}, null, 2) + '\n');
|
|
5066
|
+
process.exit(list.length === 0 ? 3 : 0);
|
|
5067
|
+
}
|
|
5068
|
+
if (list.length === 0) {
|
|
5069
|
+
process.stderr.write(`No handoffs match "${query}".\n`);
|
|
5070
|
+
process.exit(3);
|
|
5071
|
+
}
|
|
5072
|
+
// Render with terminal highlight (ANSI bold) when stdout is a TTY.
|
|
5073
|
+
const tty = process.stdout.isTTY;
|
|
5074
|
+
const lcQuery = query.toLowerCase();
|
|
5075
|
+
const highlight = (str) => {
|
|
5076
|
+
if (!str) return str;
|
|
5077
|
+
if (!tty) return str;
|
|
5078
|
+
const idx = str.toLowerCase().indexOf(lcQuery);
|
|
5079
|
+
if (idx < 0) return str;
|
|
5080
|
+
const before = str.slice(0, idx);
|
|
5081
|
+
const match = str.slice(idx, idx + query.length);
|
|
5082
|
+
const after = str.slice(idx + query.length);
|
|
5083
|
+
return `${before}\x1b[1;33m${match}\x1b[0m${after}`;
|
|
5084
|
+
};
|
|
5085
|
+
const fmtAge = (iso) => {
|
|
5086
|
+
if (!iso) return '?';
|
|
5087
|
+
const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
|
|
5088
|
+
if (min < 60) return `${min}m`;
|
|
5089
|
+
if (min < 1440) return `${Math.round(min/60)}h`;
|
|
5090
|
+
return `${Math.round(min/1440)}d`;
|
|
5091
|
+
};
|
|
5092
|
+
const scopeLabel = args.repo_path ? `in repo ${args.repo_path}` : args.project_id ? `in project ${args.project_id}` : 'across all your handoffs';
|
|
5093
|
+
process.stdout.write(`Found ${list.length} handoff${list.length === 1 ? '' : 's'} matching "${query}" ${scopeLabel}:\n\n`);
|
|
5094
|
+
for (const h of list) {
|
|
5095
|
+
const shortId = (h.session_id || '').slice(0, 8);
|
|
5096
|
+
const age = fmtAge(h.ended_at || h.started_at);
|
|
5097
|
+
const status = (h.status || 'unknown').padEnd(11);
|
|
5098
|
+
const branch = h.branch ? `[${h.branch}] ` : '';
|
|
5099
|
+
const goal = highlight((h.goal || '(no goal)').slice(0, 100));
|
|
5100
|
+
const q = typeof h.quality_score === 'number' ? ` q=${h.quality_score.toFixed(2)}` : '';
|
|
5101
|
+
process.stdout.write(` ${shortId} ${status} ${age.padStart(4)} ago${q} ${branch}${goal}\n`);
|
|
5102
|
+
if (h.next_step) process.stdout.write(` next: ${highlight(h.next_step.slice(0, 100))}\n`);
|
|
5103
|
+
// If the repo differs from the user's cwd, surface it so they
|
|
5104
|
+
// know which project the handoff belongs to.
|
|
5105
|
+
if (h.repo_path && !args.repo_path) {
|
|
5106
|
+
process.stdout.write(` repo: ${h.repo_path}\n`);
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
5109
|
+
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`);
|
|
5110
|
+
process.exit(0);
|
|
5111
|
+
} catch (err) {
|
|
5112
|
+
console.error('search command failed:', err.message);
|
|
5113
|
+
process.exit(1);
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
|
|
4998
5117
|
async function runLinkCli() {
|
|
4999
5118
|
if (!API_KEY) {
|
|
5000
5119
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
@@ -5165,14 +5284,15 @@ async function runDeleteCli() {
|
|
|
5165
5284
|
}
|
|
5166
5285
|
const { positional, flags } = parseCliArgs(process.argv.slice(3));
|
|
5167
5286
|
const idArg = positional[0];
|
|
5168
|
-
const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path);
|
|
5287
|
+
const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path || flags.repo_path_prefix);
|
|
5169
5288
|
if (!idArg && !bulkMode) {
|
|
5170
5289
|
console.error('Usage:');
|
|
5171
5290
|
console.error(' cf-memory-mcp delete <session-id-or-prefix> # single delete');
|
|
5172
5291
|
console.error(' cf-memory-mcp delete --older-than 30d # bulk delete by age');
|
|
5173
5292
|
console.error(' cf-memory-mcp delete --status abandoned # bulk by status');
|
|
5174
|
-
console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by repo');
|
|
5175
|
-
console.error('
|
|
5293
|
+
console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by exact repo');
|
|
5294
|
+
console.error(' cf-memory-mcp delete --repo-prefix /tmp/cfm- # bulk by repo prefix');
|
|
5295
|
+
console.error(' Combine filters; without --yes you get a dry-run preview.');
|
|
5176
5296
|
process.exit(1);
|
|
5177
5297
|
}
|
|
5178
5298
|
const server = new CFMemoryMCP();
|
|
@@ -5183,6 +5303,7 @@ async function runDeleteCli() {
|
|
|
5183
5303
|
if (flags.status) args.status = flags.status;
|
|
5184
5304
|
if (flags.older_than_days !== undefined) args.older_than_days = flags.older_than_days;
|
|
5185
5305
|
if (flags.repo_path) args.repo_path = flags.repo_path;
|
|
5306
|
+
if (flags.repo_path_prefix) args.repo_path_prefix = flags.repo_path_prefix;
|
|
5186
5307
|
|
|
5187
5308
|
// Dry-run preview when --yes is missing. The server returns
|
|
5188
5309
|
// {deleted:false, deleted_count, deleted_session_ids[]} so the
|
|
@@ -5193,6 +5314,16 @@ async function runDeleteCli() {
|
|
|
5193
5314
|
method: 'tools/call',
|
|
5194
5315
|
params: { name: 'delete_session', arguments: { ...args, dry_run: true } },
|
|
5195
5316
|
});
|
|
5317
|
+
// Surface JSON-RPC errors (e.g. "repo_path_prefix too short")
|
|
5318
|
+
// instead of swallowing them into a "no matches" message.
|
|
5319
|
+
if (previewRes?.error) {
|
|
5320
|
+
if (flags.json) {
|
|
5321
|
+
process.stdout.write(JSON.stringify({ error: previewRes.error }, null, 2) + '\n');
|
|
5322
|
+
} else {
|
|
5323
|
+
process.stderr.write(`Error: ${previewRes.error.message || JSON.stringify(previewRes.error)}\n`);
|
|
5324
|
+
}
|
|
5325
|
+
process.exit(1);
|
|
5326
|
+
}
|
|
5196
5327
|
const previewText = previewRes?.result?.content?.[0]?.text;
|
|
5197
5328
|
const preview = JSON.parse(previewText || '{}');
|
|
5198
5329
|
if (flags.json) {
|
|
@@ -5249,6 +5380,14 @@ async function runDeleteCli() {
|
|
|
5249
5380
|
method: 'tools/call',
|
|
5250
5381
|
params: { name: 'delete_session', arguments: args },
|
|
5251
5382
|
});
|
|
5383
|
+
if (deleteRes?.error) {
|
|
5384
|
+
if (flags.json) {
|
|
5385
|
+
process.stdout.write(JSON.stringify({ error: deleteRes.error }, null, 2) + '\n');
|
|
5386
|
+
} else {
|
|
5387
|
+
process.stderr.write(`Error: ${deleteRes.error.message || JSON.stringify(deleteRes.error)}\n`);
|
|
5388
|
+
}
|
|
5389
|
+
process.exit(1);
|
|
5390
|
+
}
|
|
5252
5391
|
const deleteText = deleteRes?.result?.content?.[0]?.text;
|
|
5253
5392
|
const payload = JSON.parse(deleteText || '{}');
|
|
5254
5393
|
// Implicit-cache invalidation: if any deleted id matches the
|
|
@@ -5294,6 +5433,14 @@ async function runDeleteCli() {
|
|
|
5294
5433
|
method: 'tools/call',
|
|
5295
5434
|
params: { name: 'delete_session', arguments: { session_id: fullId } },
|
|
5296
5435
|
});
|
|
5436
|
+
if (deleteRes?.error) {
|
|
5437
|
+
if (flags.json) {
|
|
5438
|
+
process.stdout.write(JSON.stringify({ error: deleteRes.error, session_id: fullId }, null, 2) + '\n');
|
|
5439
|
+
} else {
|
|
5440
|
+
process.stderr.write(`Error: ${deleteRes.error.message || JSON.stringify(deleteRes.error)}\n`);
|
|
5441
|
+
}
|
|
5442
|
+
process.exit(1);
|
|
5443
|
+
}
|
|
5297
5444
|
const deleteText = deleteRes?.result?.content?.[0]?.text;
|
|
5298
5445
|
const payload = JSON.parse(deleteText || '{}');
|
|
5299
5446
|
// Drop the implicit-session disk cache if the deleted id matches
|
|
@@ -5389,7 +5536,7 @@ function runCompletionCli() {
|
|
|
5389
5536
|
process.stderr.write(`\nDone. Installed completion for ${installed} shell${installed === 1 ? '' : 's'}.\n`);
|
|
5390
5537
|
process.exit(installed > 0 ? 0 : 1);
|
|
5391
5538
|
}
|
|
5392
|
-
const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'explain', 'gha', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
5539
|
+
const commands = ['resume', 'list', 'search', 'history', 'checkpoint', 'link', 'explain', 'gha', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
5393
5540
|
const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
|
|
5394
5541
|
|
|
5395
5542
|
// Determine the install target for each shell. Use user-local paths
|
|
@@ -5593,6 +5740,23 @@ const PER_COMMAND_HELP = {
|
|
|
5593
5740
|
--repo PATH Override the cwd's repo (peek at another repo).
|
|
5594
5741
|
--limit N, -n N Max number of entries (default 5, max 50).
|
|
5595
5742
|
--json, -j Emit a JSON array for scripts.`,
|
|
5743
|
+
search: `cf-memory-mcp search <query> [--here | --repo PATH | --project-id ID] [--status S] [--since ISO] [--limit N] [--json|--ids-only]
|
|
5744
|
+
Find handoffs matching <query> across goal, next_steps, notes,
|
|
5745
|
+
decisions, blockers, code_anchors, and files_touched (case-insensitive
|
|
5746
|
+
substring). Default scope is ALL your handoffs (cross-repo). Highlights
|
|
5747
|
+
the matched substring in terminal output.
|
|
5748
|
+
<query> Required substring to search for.
|
|
5749
|
+
--here Scope to cwd's repo (otherwise: global).
|
|
5750
|
+
--repo PATH Scope to a specific repo path.
|
|
5751
|
+
--project-id ID Scope to a specific project.
|
|
5752
|
+
--status S Filter by status (in_progress, completed, ...).
|
|
5753
|
+
--since ISO Lower bound on handoff timestamp (ISO 8601).
|
|
5754
|
+
--limit N Max entries to render (server may cap at 50).
|
|
5755
|
+
--ids-only Print only session_ids (xargs-friendly).
|
|
5756
|
+
--json, -j Emit JSON for scripts.
|
|
5757
|
+
Exit codes: 0 = matches, 3 = no matches.
|
|
5758
|
+
Example: cf-memory-mcp search "OAuth" --status in_progress
|
|
5759
|
+
cf-memory-mcp search "migration" --ids-only | xargs -I{} cfm resume {}`,
|
|
5596
5760
|
checkpoint: `cf-memory-mcp checkpoint ["<goal>"] [--force] [--json]
|
|
5597
5761
|
Snapshot current state to a fresh handoff with keep_open:true. The
|
|
5598
5762
|
session stays active for future checkpoints. The final end_session (or
|
|
@@ -5634,12 +5798,15 @@ const PER_COMMAND_HELP = {
|
|
|
5634
5798
|
--uninstall Remove a previously-installed script.
|
|
5635
5799
|
--all-shells With --install: install for every shell at once.`,
|
|
5636
5800
|
delete: `cf-memory-mcp delete <session-id-or-prefix> [--json]
|
|
5637
|
-
cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] --yes [--json]
|
|
5801
|
+
cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] [--repo-prefix PFX] --yes [--json]
|
|
5638
5802
|
Delete session row(s) and any session_summary memories tied to them.
|
|
5639
5803
|
<session-id> Single delete: full UUID or short prefix (>=8 chars).
|
|
5640
5804
|
--status STATUS Bulk filter by status (abandoned, completed, ...).
|
|
5641
5805
|
--older-than 30d Bulk filter by age (suffixes: d, h, m).
|
|
5642
|
-
--repo PATH Bulk filter by repo_path.
|
|
5806
|
+
--repo PATH Bulk filter by repo_path (exact match).
|
|
5807
|
+
--repo-prefix PFX Bulk filter by repo_path PREFIX (min 4 chars).
|
|
5808
|
+
E.g. --repo-prefix /tmp/cfm-lifecycle- nukes
|
|
5809
|
+
accumulated benchmark artifacts in one call.
|
|
5643
5810
|
--yes, -y Required for bulk delete to confirm intent.
|
|
5644
5811
|
--json, -j Emit JSON.
|
|
5645
5812
|
Exit codes: 0 = deleted, 2 = bulk missing --yes, 3 = nothing matched.`,
|
|
@@ -6428,6 +6595,11 @@ if (process.argv[2] === 'list') {
|
|
|
6428
6595
|
return;
|
|
6429
6596
|
}
|
|
6430
6597
|
|
|
6598
|
+
if (process.argv[2] === 'search') {
|
|
6599
|
+
runSearchCli();
|
|
6600
|
+
return;
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6431
6603
|
if (process.argv[2] === 'checkpoint') {
|
|
6432
6604
|
runCheckpointCli();
|
|
6433
6605
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cf-memory-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.64.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": {
|