claude-mem-lite 2.2.3 → 2.3.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/install.mjs +21 -0
- package/package.json +1 -1
- package/server.mjs +260 -2
- package/tool-schemas.mjs +27 -0
package/install.mjs
CHANGED
|
@@ -564,6 +564,27 @@ const RESOURCE_METADATA = {
|
|
|
564
564
|
capability_summary: 'Autonomous skill extraction and continuous learning from Claude Code work sessions',
|
|
565
565
|
trigger_patterns: 'when user wants to extract reusable skills from work sessions or enable continuous learning',
|
|
566
566
|
},
|
|
567
|
+
'skill:mem-memory': {
|
|
568
|
+
intent_tags: 'memory,save,store,remember,note,record,persist,auto-save',
|
|
569
|
+
domain_tags: 'memory,ai,claude',
|
|
570
|
+
capability_summary: 'Save content to memory — with explicit content, instructions, or auto-summarize current session',
|
|
571
|
+
trigger_patterns: 'when user wants to save something to memory or auto-save session highlights',
|
|
572
|
+
invocation_name: 'claude-mem-lite:memory',
|
|
573
|
+
},
|
|
574
|
+
'skill:mem-update': {
|
|
575
|
+
intent_tags: 'maintenance,cleanup,deduplicate,decay,optimize,reindex,health',
|
|
576
|
+
domain_tags: 'memory,registry,maintenance',
|
|
577
|
+
capability_summary: 'Auto-maintain memory and resource registry — deduplicate, merge, decay, cleanup, reindex',
|
|
578
|
+
trigger_patterns: 'when user wants to clean up memory database or maintain the tool registry',
|
|
579
|
+
invocation_name: 'claude-mem-lite:update',
|
|
580
|
+
},
|
|
581
|
+
'skill:mem-tools': {
|
|
582
|
+
intent_tags: 'import,tools,github,skills,agents,registry,add,discover',
|
|
583
|
+
domain_tags: 'memory,registry,github,tools',
|
|
584
|
+
capability_summary: 'Import skills and agents from GitHub repositories into the tool resource registry',
|
|
585
|
+
trigger_patterns: 'when user wants to import or add new skills and agents from GitHub to the tool registry',
|
|
586
|
+
invocation_name: 'claude-mem-lite:tools',
|
|
587
|
+
},
|
|
567
588
|
'skill:anthropic-architect': {
|
|
568
589
|
intent_tags: 'anthropic,architecture,system-design,claude,patterns,best-practices',
|
|
569
590
|
domain_tags: 'anthropic,architecture,ai',
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { jaccardSimilarity, truncate, typeIcon, sanitizeFtsQuery, relaxFtsQueryToOr, inferProject, computeMinHash, scrubSecrets, fmtDate, isoWeekKey, debugLog, debugCatch } from './utils.mjs';
|
|
8
|
-
import { ensureDb, DB_PATH } from './schema.mjs';
|
|
8
|
+
import { ensureDb, DB_PATH, DB_DIR } from './schema.mjs';
|
|
9
9
|
import { reRankWithContext, markSuperseded, extractPRFTerms, expandQueryByConcepts } from './server-internals.mjs';
|
|
10
|
-
import { memSearchSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema } from './tool-schemas.mjs';
|
|
10
|
+
import { memSearchSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema, memMaintainSchema, memRegistrySchema } from './tool-schemas.mjs';
|
|
11
|
+
import { ensureRegistryDb, upsertResource } from './registry.mjs';
|
|
12
|
+
import { join } from 'path';
|
|
11
13
|
|
|
12
14
|
// ─── Database ───────────────────────────────────────────────────────────────
|
|
13
15
|
|
|
@@ -34,6 +36,22 @@ try {
|
|
|
34
36
|
// Server process uses longer busy_timeout for concurrent MCP requests
|
|
35
37
|
db.pragma('busy_timeout = 5000');
|
|
36
38
|
|
|
39
|
+
// ─── Registry Database (lazy-loaded on first mem_registry call) ─────────────
|
|
40
|
+
|
|
41
|
+
const REGISTRY_DB_PATH = join(DB_DIR, 'resource-registry.db');
|
|
42
|
+
let registryDb = null;
|
|
43
|
+
|
|
44
|
+
function getRegistryDb() {
|
|
45
|
+
if (registryDb) return registryDb;
|
|
46
|
+
try {
|
|
47
|
+
registryDb = ensureRegistryDb(REGISTRY_DB_PATH);
|
|
48
|
+
registryDb.pragma('busy_timeout = 3000');
|
|
49
|
+
} catch (e) {
|
|
50
|
+
debugLog('WARN', 'server', `Registry DB not available: ${e.message}`);
|
|
51
|
+
}
|
|
52
|
+
return registryDb;
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
// inferProject, jaccardSimilarity, sanitizeFtsQuery, typeIcon, truncate, fmtDate imported from utils.mjs
|
|
38
56
|
|
|
39
57
|
// ─── Scoring Model Constants ────────────────────────────────────────────────
|
|
@@ -977,6 +995,245 @@ server.registerTool(
|
|
|
977
995
|
})
|
|
978
996
|
);
|
|
979
997
|
|
|
998
|
+
// ─── Tool: mem_maintain ──────────────────────────────────────────────────────
|
|
999
|
+
|
|
1000
|
+
server.registerTool(
|
|
1001
|
+
'mem_maintain',
|
|
1002
|
+
{
|
|
1003
|
+
description: 'Memory maintenance: scan for duplicates/stale/broken items, then execute cleanup/decay/boost/dedup operations.',
|
|
1004
|
+
inputSchema: memMaintainSchema,
|
|
1005
|
+
},
|
|
1006
|
+
safeHandler(async (args) => {
|
|
1007
|
+
const STALE_AGE_MS = 30 * 86400000;
|
|
1008
|
+
const SIMILARITY_THRESHOLD = 0.7;
|
|
1009
|
+
const SCAN_LIMIT = 500;
|
|
1010
|
+
const DUPLICATE_LIMIT = 50;
|
|
1011
|
+
const DUPLICATE_DISPLAY = 15;
|
|
1012
|
+
|
|
1013
|
+
const action = args.action;
|
|
1014
|
+
const project = args.project;
|
|
1015
|
+
const projectFilter = project ? 'AND project = ?' : '';
|
|
1016
|
+
const baseParams = project ? [project] : [];
|
|
1017
|
+
|
|
1018
|
+
if (action === 'scan') {
|
|
1019
|
+
// 1. Find near-duplicate titles (pre-compute word sets, then O(n²) Jaccard)
|
|
1020
|
+
const recent = db.prepare(`
|
|
1021
|
+
SELECT id, title, project, importance, access_count, created_at_epoch
|
|
1022
|
+
FROM observations
|
|
1023
|
+
WHERE COALESCE(compressed_into, 0) = 0 ${projectFilter}
|
|
1024
|
+
ORDER BY created_at_epoch DESC
|
|
1025
|
+
LIMIT ${SCAN_LIMIT}
|
|
1026
|
+
`).all(...baseParams);
|
|
1027
|
+
|
|
1028
|
+
const wordSets = recent.map(r => new Set((r.title || '').toLowerCase().split(/\s+/).filter(w => w.length > 2)));
|
|
1029
|
+
const duplicates = [];
|
|
1030
|
+
for (let i = 0; i < recent.length && duplicates.length < DUPLICATE_LIMIT; i++) {
|
|
1031
|
+
if (wordSets[i].size === 0) continue;
|
|
1032
|
+
for (let j = i + 1; j < recent.length; j++) {
|
|
1033
|
+
if (wordSets[j].size === 0) continue;
|
|
1034
|
+
const sim = jaccardSimilarity(wordSets[i], wordSets[j]);
|
|
1035
|
+
if (sim > SIMILARITY_THRESHOLD) {
|
|
1036
|
+
duplicates.push({
|
|
1037
|
+
a: { id: recent[i].id, title: recent[i].title, importance: recent[i].importance },
|
|
1038
|
+
b: { id: recent[j].id, title: recent[j].title, importance: recent[j].importance },
|
|
1039
|
+
similarity: sim.toFixed(2),
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
if (duplicates.length >= DUPLICATE_LIMIT) break;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// 2. Consolidated stats query (single table scan instead of 4 separate COUNTs)
|
|
1047
|
+
const staleAge = Date.now() - STALE_AGE_MS;
|
|
1048
|
+
const stats = db.prepare(`
|
|
1049
|
+
SELECT
|
|
1050
|
+
COUNT(*) as total,
|
|
1051
|
+
SUM(CASE WHEN COALESCE(importance, 1) = 1 AND COALESCE(access_count, 0) = 0
|
|
1052
|
+
AND created_at_epoch < ? THEN 1 ELSE 0 END) as stale,
|
|
1053
|
+
SUM(CASE WHEN (title IS NULL OR title = '') AND (narrative IS NULL OR narrative = '')
|
|
1054
|
+
THEN 1 ELSE 0 END) as broken,
|
|
1055
|
+
SUM(CASE WHEN COALESCE(access_count, 0) > 3 AND COALESCE(importance, 1) < 3
|
|
1056
|
+
THEN 1 ELSE 0 END) as boostable
|
|
1057
|
+
FROM observations
|
|
1058
|
+
WHERE COALESCE(compressed_into, 0) = 0 ${projectFilter}
|
|
1059
|
+
`).get(staleAge, ...baseParams);
|
|
1060
|
+
|
|
1061
|
+
const lines = [
|
|
1062
|
+
`Memory maintenance scan:`,
|
|
1063
|
+
` Total active observations: ${stats.total}`,
|
|
1064
|
+
` Near-duplicate pairs: ${duplicates.length}`,
|
|
1065
|
+
` Stale (>30d, imp=1, no access): ${stats.stale}`,
|
|
1066
|
+
` Broken (no title/narrative): ${stats.broken}`,
|
|
1067
|
+
` Boostable (accessed>3, imp<3): ${stats.boostable}`,
|
|
1068
|
+
];
|
|
1069
|
+
if (duplicates.length > 0) {
|
|
1070
|
+
lines.push('', 'Top duplicates:');
|
|
1071
|
+
for (const d of duplicates.slice(0, DUPLICATE_DISPLAY)) {
|
|
1072
|
+
lines.push(` [${d.a.id}] "${truncate(d.a.title, 40)}" <-> [${d.b.id}] "${truncate(d.b.title, 40)}" (${d.similarity})`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (action === 'execute') {
|
|
1079
|
+
const ops = args.operations || [];
|
|
1080
|
+
const results = [];
|
|
1081
|
+
const staleAge = Date.now() - STALE_AGE_MS;
|
|
1082
|
+
|
|
1083
|
+
db.transaction(() => {
|
|
1084
|
+
if (ops.includes('cleanup')) {
|
|
1085
|
+
const deleted = db.prepare(`
|
|
1086
|
+
DELETE FROM observations
|
|
1087
|
+
WHERE COALESCE(compressed_into, 0) = 0
|
|
1088
|
+
AND (title IS NULL OR title = '')
|
|
1089
|
+
AND (narrative IS NULL OR narrative = '')
|
|
1090
|
+
${projectFilter}
|
|
1091
|
+
`).run(...baseParams);
|
|
1092
|
+
results.push(`Cleaned up ${deleted.changes} broken observations`);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (ops.includes('decay')) {
|
|
1096
|
+
const decayed = db.prepare(`
|
|
1097
|
+
UPDATE observations SET importance = MAX(1, COALESCE(importance, 1) - 1)
|
|
1098
|
+
WHERE COALESCE(compressed_into, 0) = 0
|
|
1099
|
+
AND COALESCE(importance, 1) > 1
|
|
1100
|
+
AND COALESCE(access_count, 0) = 0
|
|
1101
|
+
AND created_at_epoch < ?
|
|
1102
|
+
${projectFilter}
|
|
1103
|
+
`).run(staleAge, ...baseParams);
|
|
1104
|
+
results.push(`Decayed ${decayed.changes} stale observations`);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (ops.includes('boost')) {
|
|
1108
|
+
const boosted = db.prepare(`
|
|
1109
|
+
UPDATE observations SET importance = MIN(3, COALESCE(importance, 1) + 1)
|
|
1110
|
+
WHERE COALESCE(compressed_into, 0) = 0
|
|
1111
|
+
AND COALESCE(access_count, 0) > 3
|
|
1112
|
+
AND COALESCE(importance, 1) < 3
|
|
1113
|
+
${projectFilter}
|
|
1114
|
+
`).run(...baseParams);
|
|
1115
|
+
results.push(`Boosted ${boosted.changes} frequently-accessed observations`);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (ops.includes('dedup') && args.merge_ids) {
|
|
1119
|
+
let totalMerged = 0;
|
|
1120
|
+
const mergeStmt = db.prepare('UPDATE observations SET compressed_into = ? WHERE id = ? AND COALESCE(compressed_into, 0) = 0');
|
|
1121
|
+
for (const group of args.merge_ids) {
|
|
1122
|
+
if (group.length < 2) continue;
|
|
1123
|
+
const [keepId, ...removeIds] = group;
|
|
1124
|
+
for (const removeId of removeIds) mergeStmt.run(keepId, removeId);
|
|
1125
|
+
totalMerged += removeIds.length;
|
|
1126
|
+
}
|
|
1127
|
+
results.push(`Merged ${totalMerged} duplicate observations`);
|
|
1128
|
+
}
|
|
1129
|
+
})();
|
|
1130
|
+
|
|
1131
|
+
// FTS5 optimize (outside transaction)
|
|
1132
|
+
db.exec("INSERT INTO observations_fts(observations_fts) VALUES('optimize')");
|
|
1133
|
+
results.push('FTS5 index optimized');
|
|
1134
|
+
|
|
1135
|
+
return { content: [{ type: 'text', text: results.join('\n') }] };
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return { content: [{ type: 'text', text: `Unknown action: ${action}. Use "scan" or "execute".` }], isError: true };
|
|
1139
|
+
})
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
// ─── Tool: mem_registry ─────────────────────────────────────────────────────
|
|
1143
|
+
|
|
1144
|
+
server.registerTool(
|
|
1145
|
+
'mem_registry',
|
|
1146
|
+
{
|
|
1147
|
+
description: 'Manage tool resource registry: list resources, view stats, import/remove tools, reindex FTS5.',
|
|
1148
|
+
inputSchema: memRegistrySchema,
|
|
1149
|
+
},
|
|
1150
|
+
safeHandler(async (args) => {
|
|
1151
|
+
const rdb = getRegistryDb();
|
|
1152
|
+
if (!rdb) {
|
|
1153
|
+
return { content: [{ type: 'text', text: 'Registry DB not available. Run install first.' }], isError: true };
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const action = args.action;
|
|
1157
|
+
|
|
1158
|
+
if (action === 'list') {
|
|
1159
|
+
const typeFilter = args.type;
|
|
1160
|
+
const where = typeFilter ? 'WHERE type = ? AND status = ?' : 'WHERE status = ?';
|
|
1161
|
+
const params = typeFilter ? [typeFilter, 'active'] : ['active'];
|
|
1162
|
+
const resources = rdb.prepare(`
|
|
1163
|
+
SELECT name, type, invocation_name, recommend_count, adopt_count, capability_summary
|
|
1164
|
+
FROM resources ${where} ORDER BY type, name
|
|
1165
|
+
`).all(...params);
|
|
1166
|
+
|
|
1167
|
+
if (resources.length === 0) return { content: [{ type: 'text', text: 'No resources found.' }] };
|
|
1168
|
+
|
|
1169
|
+
const lines = resources.map(r =>
|
|
1170
|
+
`${r.type === 'skill' ? 'S' : 'A'} ${r.name}${r.invocation_name ? ` (${r.invocation_name})` : ''} — rec:${r.recommend_count} adopt:${r.adopt_count} — ${truncate(r.capability_summary || '', 60)}`
|
|
1171
|
+
);
|
|
1172
|
+
return { content: [{ type: 'text', text: `Resources (${resources.length}):\n${lines.join('\n')}` }] };
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (action === 'stats') {
|
|
1176
|
+
const total = rdb.prepare('SELECT COUNT(*) as c FROM resources WHERE status = ?').get('active');
|
|
1177
|
+
const byType = rdb.prepare('SELECT type, COUNT(*) as c FROM resources WHERE status = ? GROUP BY type').all('active');
|
|
1178
|
+
const topAdopted = rdb.prepare(`
|
|
1179
|
+
SELECT name, type, adopt_count, recommend_count
|
|
1180
|
+
FROM resources WHERE status = ? AND adopt_count > 0
|
|
1181
|
+
ORDER BY adopt_count DESC LIMIT 10
|
|
1182
|
+
`).all('active');
|
|
1183
|
+
const zeroAdopt = rdb.prepare(`
|
|
1184
|
+
SELECT COUNT(*) as c FROM resources
|
|
1185
|
+
WHERE status = ? AND recommend_count > 0 AND adopt_count = 0
|
|
1186
|
+
`).get('active');
|
|
1187
|
+
const userAdded = rdb.prepare(`
|
|
1188
|
+
SELECT COUNT(*) as c FROM resources WHERE status = ? AND source = 'user'
|
|
1189
|
+
`).get('active');
|
|
1190
|
+
|
|
1191
|
+
const lines = [
|
|
1192
|
+
`Registry Stats:`,
|
|
1193
|
+
` Total active: ${total.c}`,
|
|
1194
|
+
...byType.map(t => ` ${t.type}: ${t.c}`),
|
|
1195
|
+
` User-added: ${userAdded.c}`,
|
|
1196
|
+
` Zero adoption (recommended but never adopted): ${zeroAdopt.c}`,
|
|
1197
|
+
];
|
|
1198
|
+
if (topAdopted.length > 0) {
|
|
1199
|
+
lines.push('', 'Top adopted:');
|
|
1200
|
+
for (const r of topAdopted) {
|
|
1201
|
+
lines.push(` ${r.name} (${r.type}): ${r.adopt_count}/${r.recommend_count}`);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (action === 'import') {
|
|
1208
|
+
if (!args.name || !args.resource_type) {
|
|
1209
|
+
return { content: [{ type: 'text', text: 'import requires name and resource_type' }], isError: true };
|
|
1210
|
+
}
|
|
1211
|
+
const IMPORT_STRING_FIELDS = ['repo_url', 'local_path', 'invocation_name', 'intent_tags',
|
|
1212
|
+
'domain_tags', 'trigger_patterns', 'capability_summary', 'keywords', 'tech_stack', 'use_cases'];
|
|
1213
|
+
const fields = { name: args.name, type: args.resource_type, status: 'active', source: args.source || 'user' };
|
|
1214
|
+
for (const f of IMPORT_STRING_FIELDS) fields[f] = args[f] || '';
|
|
1215
|
+
const id = upsertResource(rdb, fields);
|
|
1216
|
+
return { content: [{ type: 'text', text: `Imported: ${args.resource_type}:${args.name} (id=${id})` }] };
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (action === 'remove') {
|
|
1220
|
+
if (!args.name || !args.resource_type) {
|
|
1221
|
+
return { content: [{ type: 'text', text: 'remove requires name and resource_type' }], isError: true };
|
|
1222
|
+
}
|
|
1223
|
+
const result = rdb.prepare('DELETE FROM resources WHERE type = ? AND name = ?').run(args.resource_type, args.name);
|
|
1224
|
+
return { content: [{ type: 'text', text: result.changes > 0 ? `Removed: ${args.resource_type}:${args.name}` : 'Not found.' }] };
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (action === 'reindex') {
|
|
1228
|
+
rdb.exec("INSERT INTO resources_fts(resources_fts) VALUES('rebuild')");
|
|
1229
|
+
const count = rdb.prepare('SELECT COUNT(*) as c FROM resources WHERE status = ?').get('active');
|
|
1230
|
+
return { content: [{ type: 'text', text: `FTS5 reindexed. ${count.c} active resources.` }] };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return { content: [{ type: 'text', text: `Unknown action: ${action}. Valid: list, stats, import, remove, reindex` }], isError: true };
|
|
1234
|
+
})
|
|
1235
|
+
);
|
|
1236
|
+
|
|
980
1237
|
// ─── WAL Checkpoint (periodic) ───────────────────────────────────────────────
|
|
981
1238
|
|
|
982
1239
|
// Checkpoint WAL every 5 minutes to prevent unbounded growth
|
|
@@ -1042,6 +1299,7 @@ function shutdown(exitCode = 0) {
|
|
|
1042
1299
|
clearInterval(idleTimer);
|
|
1043
1300
|
try { db.pragma('wal_checkpoint(TRUNCATE)'); } catch {}
|
|
1044
1301
|
try { db.close(); } catch {}
|
|
1302
|
+
try { if (registryDb) registryDb.close(); } catch {}
|
|
1045
1303
|
process.exit(exitCode);
|
|
1046
1304
|
}
|
|
1047
1305
|
process.on('SIGINT', () => shutdown(0));
|
package/tool-schemas.mjs
CHANGED
|
@@ -54,3 +54,30 @@ export const memCompressSchema = {
|
|
|
54
54
|
age_days: z.number().int().min(30).max(365).optional().describe('Min age in days (default: 60)'),
|
|
55
55
|
project: z.string().optional().describe('Filter by project'),
|
|
56
56
|
};
|
|
57
|
+
|
|
58
|
+
export const memMaintainSchema = {
|
|
59
|
+
action: z.enum(['scan', 'execute']).describe('scan=analyze candidates, execute=apply changes'),
|
|
60
|
+
operations: z.array(z.enum(['dedup', 'decay', 'cleanup', 'boost'])).optional()
|
|
61
|
+
.describe('Operations to execute (for action=execute)'),
|
|
62
|
+
merge_ids: z.array(z.array(z.number().int()).min(2)).optional()
|
|
63
|
+
.describe('For dedup: [[keepId, removeId1, removeId2], ...] — first ID in each group is kept'),
|
|
64
|
+
project: z.string().optional().describe('Filter by project'),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const memRegistrySchema = {
|
|
68
|
+
action: z.enum(['list', 'stats', 'import', 'remove', 'reindex']).describe('Registry operation'),
|
|
69
|
+
type: z.enum(['skill', 'agent']).optional().describe('Filter by resource type (for list)'),
|
|
70
|
+
name: z.string().optional().describe('Resource name (for import/remove)'),
|
|
71
|
+
resource_type: z.enum(['skill', 'agent']).optional().describe('Resource type (for import/remove)'),
|
|
72
|
+
source: z.enum(['preinstalled', 'user']).optional().describe('Source (for import, default: user)'),
|
|
73
|
+
repo_url: z.string().optional().describe('GitHub repository URL (for import)'),
|
|
74
|
+
local_path: z.string().optional().describe('Local file path (for import)'),
|
|
75
|
+
invocation_name: z.string().optional().describe('Invocation name like "plugin:skill" (for import)'),
|
|
76
|
+
intent_tags: z.string().optional().describe('Comma-separated intent tags (for import)'),
|
|
77
|
+
domain_tags: z.string().optional().describe('Comma-separated domain/tech tags (for import)'),
|
|
78
|
+
trigger_patterns: z.string().optional().describe('When to recommend this tool (for import)'),
|
|
79
|
+
capability_summary: z.string().optional().describe('What this tool does (for import)'),
|
|
80
|
+
keywords: z.string().optional().describe('Search keywords (for import)'),
|
|
81
|
+
tech_stack: z.string().optional().describe('Technology stack tags (for import)'),
|
|
82
|
+
use_cases: z.string().optional().describe('Usage scenarios (for import)'),
|
|
83
|
+
};
|