wyrm-mcp 3.2.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/LICENSE +667 -0
- package/README.md +384 -0
- package/dist/analytics.d.ts +100 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +368 -0
- package/dist/analytics.js.map +1 -0
- package/dist/auto-orchestrator.d.ts +118 -0
- package/dist/auto-orchestrator.d.ts.map +1 -0
- package/dist/auto-orchestrator.js +325 -0
- package/dist/auto-orchestrator.js.map +1 -0
- package/dist/autoconfig.d.ts +89 -0
- package/dist/autoconfig.d.ts.map +1 -0
- package/dist/autoconfig.js +576 -0
- package/dist/autoconfig.js.map +1 -0
- package/dist/cli.d.ts +148 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +281 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud-backup.d.ts +100 -0
- package/dist/cloud-backup.d.ts.map +1 -0
- package/dist/cloud-backup.js +545 -0
- package/dist/cloud-backup.js.map +1 -0
- package/dist/crypto.d.ts +72 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +164 -0
- package/dist/crypto.js.map +1 -0
- package/dist/database.d.ts +218 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +1058 -0
- package/dist/database.js.map +1 -0
- package/dist/http-auth.d.ts +68 -0
- package/dist/http-auth.d.ts.map +1 -0
- package/dist/http-auth.js +296 -0
- package/dist/http-auth.js.map +1 -0
- package/dist/http-fast.d.ts +13 -0
- package/dist/http-fast.d.ts.map +1 -0
- package/dist/http-fast.js +325 -0
- package/dist/http-fast.js.map +1 -0
- package/dist/http-server.d.ts +12 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +383 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1695 -0
- package/dist/index.js.map +1 -0
- package/dist/license.d.ts +177 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +405 -0
- package/dist/license.js.map +1 -0
- package/dist/logger.d.ts +76 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +195 -0
- package/dist/logger.js.map +1 -0
- package/dist/performance.d.ts +114 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +228 -0
- package/dist/performance.js.map +1 -0
- package/dist/resilience.d.ts +146 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +563 -0
- package/dist/resilience.js.map +1 -0
- package/dist/security.d.ts +68 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +215 -0
- package/dist/security.js.map +1 -0
- package/dist/setup.d.ts +21 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +261 -0
- package/dist/setup.js.map +1 -0
- package/dist/summarizer.d.ts +30 -0
- package/dist/summarizer.d.ts.map +1 -0
- package/dist/summarizer.js +139 -0
- package/dist/summarizer.js.map +1 -0
- package/dist/sync.d.ts +39 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +356 -0
- package/dist/sync.js.map +1 -0
- package/dist/types.d.ts +267 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -0
- package/dist/vectors.d.ts +103 -0
- package/dist/vectors.d.ts.map +1 -0
- package/dist/vectors.js +311 -0
- package/dist/vectors.js.map +1 -0
- package/package.json +73 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1695 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Wyrm MCP Server - Model Context Protocol for AI memory
|
|
4
|
+
*
|
|
5
|
+
* @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
6
|
+
* @license Proprietary - See LICENSE file for details.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Auto-discovery of projects in watched directories
|
|
10
|
+
* - Multi-project tracking with unified context
|
|
11
|
+
* - Data lake for large dataset storage
|
|
12
|
+
* - Full-text search across all data
|
|
13
|
+
* - Prompt caching with cache_control hints (saves credits on Claude, etc.)
|
|
14
|
+
* - In-memory response cache for read-only tools
|
|
15
|
+
* - Usage tracking for token/cost monitoring
|
|
16
|
+
* - Compact responses to minimize token burn
|
|
17
|
+
*/
|
|
18
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
+
import { WyrmDB } from "./database.js";
|
|
22
|
+
import { WyrmSync } from "./sync.js";
|
|
23
|
+
import { createContextBundle } from "./summarizer.js";
|
|
24
|
+
import { autoConfigureAll, removeFromAll, getStatusSummary, findWyrmServerPath, getDefaultDbPath } from "./autoconfig.js";
|
|
25
|
+
import { cache, estimateTokens } from "./performance.js";
|
|
26
|
+
import { createHash } from "crypto";
|
|
27
|
+
import { initializeOrchestrator, getDefaultConfig } from "./auto-orchestrator.js";
|
|
28
|
+
import { sanitizeFtsQuery, sanitizeString, validateBatchSize } from "./security.js";
|
|
29
|
+
import { getCrypto, initializeCrypto } from "./crypto.js";
|
|
30
|
+
import { createVectorStore } from "./vectors.js";
|
|
31
|
+
import { initializeLicense, getLicenseInfo, hasFeature, getTier, activateLicense } from "./license.js";
|
|
32
|
+
import { WyrmAnalytics } from "./analytics.js";
|
|
33
|
+
import { WyrmCloudBackup } from "./cloud-backup.js";
|
|
34
|
+
const db = new WyrmDB();
|
|
35
|
+
const sync = new WyrmSync(db);
|
|
36
|
+
// ==================== OPTIONAL ENCRYPTION ====================
|
|
37
|
+
if (process.env.WYRM_ENCRYPTION_KEY) {
|
|
38
|
+
try {
|
|
39
|
+
initializeCrypto(process.env.WYRM_ENCRYPTION_KEY);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.error('Wyrm: Failed to initialize encryption:', e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const wyrmCrypto = getCrypto();
|
|
46
|
+
// ==================== OPTIONAL VECTOR SEARCH ====================
|
|
47
|
+
let vectorStore = null;
|
|
48
|
+
try {
|
|
49
|
+
const provider = (process.env.WYRM_VECTOR_PROVIDER || 'none');
|
|
50
|
+
if (provider !== 'none') {
|
|
51
|
+
vectorStore = createVectorStore({ provider });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
console.error('Wyrm: Failed to initialize vector store:', e);
|
|
56
|
+
}
|
|
57
|
+
// ==================== AUTO-ORCHESTRATION ====================
|
|
58
|
+
const orchestrator = initializeOrchestrator({
|
|
59
|
+
autoOrchestrateEnabled: true,
|
|
60
|
+
minConfidenceThreshold: 65,
|
|
61
|
+
maxParallelAgents: 6,
|
|
62
|
+
defaultHaikuBoosting: true,
|
|
63
|
+
trackMetrics: true,
|
|
64
|
+
});
|
|
65
|
+
// ==================== LICENSE SYSTEM ====================
|
|
66
|
+
initializeLicense();
|
|
67
|
+
const licenseInfo = getLicenseInfo();
|
|
68
|
+
if (licenseInfo.tier !== 'free') {
|
|
69
|
+
console.error(`Wyrm: Licensed ā ${licenseInfo.tier} tier (expires: ${licenseInfo.expiresAt || 'never'})`);
|
|
70
|
+
}
|
|
71
|
+
// ==================== PERSISTENT ANALYTICS ====================
|
|
72
|
+
const analytics = new WyrmAnalytics(db.getDatabase());
|
|
73
|
+
// ==================== CLOUD BACKUP (Pro+) ====================
|
|
74
|
+
const cloudBackup = new WyrmCloudBackup();
|
|
75
|
+
if (process.env.WYRM_R2_ENDPOINT && process.env.WYRM_R2_ACCESS_KEY && process.env.WYRM_R2_SECRET_KEY) {
|
|
76
|
+
cloudBackup.configure({
|
|
77
|
+
endpoint: process.env.WYRM_R2_ENDPOINT,
|
|
78
|
+
bucket: process.env.WYRM_R2_BUCKET || 'wyrm-backups',
|
|
79
|
+
accessKeyId: process.env.WYRM_R2_ACCESS_KEY,
|
|
80
|
+
secretAccessKey: process.env.WYRM_R2_SECRET_KEY,
|
|
81
|
+
}, process.env.WYRM_ENCRYPTION_KEY);
|
|
82
|
+
}
|
|
83
|
+
const usageLog = [];
|
|
84
|
+
const USAGE_MAX_ENTRIES = 500;
|
|
85
|
+
function trackUsage(entry) {
|
|
86
|
+
usageLog.push(entry);
|
|
87
|
+
if (usageLog.length > USAGE_MAX_ENTRIES) {
|
|
88
|
+
usageLog.splice(0, usageLog.length - USAGE_MAX_ENTRIES);
|
|
89
|
+
}
|
|
90
|
+
// Persist to analytics (Pro+ feature, but record for all ā dashboard gated)
|
|
91
|
+
analytics.record({
|
|
92
|
+
tool: entry.tool,
|
|
93
|
+
tokens_in: entry.tokens_in,
|
|
94
|
+
tokens_out: entry.tokens_out,
|
|
95
|
+
cached: entry.cached,
|
|
96
|
+
ms: entry.ms,
|
|
97
|
+
success: true,
|
|
98
|
+
timestamp: entry.timestamp,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function getUsageStats(last) {
|
|
102
|
+
const entries = last ? usageLog.slice(-last) : usageLog;
|
|
103
|
+
const cachedEntries = entries.filter(e => e.cached);
|
|
104
|
+
const toolMap = new Map();
|
|
105
|
+
let totalTokensIn = 0, totalTokensOut = 0, tokensSaved = 0, totalMs = 0;
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
totalTokensIn += e.tokens_in;
|
|
108
|
+
totalTokensOut += e.tokens_out;
|
|
109
|
+
totalMs += e.ms;
|
|
110
|
+
if (e.cached)
|
|
111
|
+
tokensSaved += e.tokens_out;
|
|
112
|
+
const existing = toolMap.get(e.tool) || { calls: 0, tokens: 0 };
|
|
113
|
+
existing.calls++;
|
|
114
|
+
existing.tokens += e.tokens_out;
|
|
115
|
+
toolMap.set(e.tool, existing);
|
|
116
|
+
}
|
|
117
|
+
const topTools = [...toolMap.entries()]
|
|
118
|
+
.map(([tool, data]) => ({ tool, ...data }))
|
|
119
|
+
.sort((a, b) => b.tokens - a.tokens)
|
|
120
|
+
.slice(0, 10);
|
|
121
|
+
return {
|
|
122
|
+
totalCalls: entries.length,
|
|
123
|
+
cachedCalls: cachedEntries.length,
|
|
124
|
+
cacheHitRate: entries.length > 0
|
|
125
|
+
? `${((cachedEntries.length / entries.length) * 100).toFixed(1)}%`
|
|
126
|
+
: '0%',
|
|
127
|
+
totalTokensIn,
|
|
128
|
+
totalTokensOut,
|
|
129
|
+
tokensSaved,
|
|
130
|
+
avgResponseMs: entries.length > 0 ? Math.round(totalMs / entries.length) : 0,
|
|
131
|
+
topTools,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// ==================== RESPONSE HELPERS ====================
|
|
135
|
+
// Response fingerprints for delta detection
|
|
136
|
+
const responseFingerprints = new Map();
|
|
137
|
+
function fingerprint(data) {
|
|
138
|
+
return createHash('md5').update(data).digest('hex').slice(0, 12);
|
|
139
|
+
}
|
|
140
|
+
/** Wrap response with cache_control hint for Anthropic prompt caching */
|
|
141
|
+
function cachedResponse(text, ephemeral = false) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{
|
|
144
|
+
type: "text",
|
|
145
|
+
text,
|
|
146
|
+
// MCP cache_control hint ā tells Claude to cache this content block
|
|
147
|
+
// "ephemeral" = cache for the duration of the conversation
|
|
148
|
+
...(ephemeral ? {} : { _meta: { cacheControl: { type: "ephemeral" } } }),
|
|
149
|
+
}],
|
|
150
|
+
// Top-level _meta for SDK-level cache hints
|
|
151
|
+
_meta: {
|
|
152
|
+
cacheControl: { type: "ephemeral" },
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** Read-only tools that benefit from caching */
|
|
157
|
+
const READ_ONLY_TOOLS = new Set([
|
|
158
|
+
"wyrm_list_projects",
|
|
159
|
+
"wyrm_project_context",
|
|
160
|
+
"wyrm_global_context",
|
|
161
|
+
"wyrm_all_quests",
|
|
162
|
+
"wyrm_data_query",
|
|
163
|
+
"wyrm_data_categories",
|
|
164
|
+
"wyrm_search",
|
|
165
|
+
"wyrm_stats",
|
|
166
|
+
"wyrm_skill_list",
|
|
167
|
+
"wyrm_skill_get",
|
|
168
|
+
"wyrm_skill_search",
|
|
169
|
+
"wyrm_skill_stats",
|
|
170
|
+
"wyrm_orchestration_config",
|
|
171
|
+
"wyrm_orchestration_stats",
|
|
172
|
+
"wyrm_license",
|
|
173
|
+
"wyrm_analytics_dashboard",
|
|
174
|
+
"wyrm_cost_report",
|
|
175
|
+
]);
|
|
176
|
+
/** Tools that mutate data ā invalidate relevant caches */
|
|
177
|
+
const WRITE_TOOLS = new Set([
|
|
178
|
+
"wyrm_session_start",
|
|
179
|
+
"wyrm_session_update",
|
|
180
|
+
"wyrm_quest_add",
|
|
181
|
+
"wyrm_quest_complete",
|
|
182
|
+
"wyrm_data_insert",
|
|
183
|
+
"wyrm_data_batch_insert",
|
|
184
|
+
"wyrm_set_global",
|
|
185
|
+
"wyrm_scan_projects",
|
|
186
|
+
"wyrm_sync",
|
|
187
|
+
"wyrm_maintenance",
|
|
188
|
+
"wyrm_skill_register",
|
|
189
|
+
"wyrm_skill_delete",
|
|
190
|
+
"wyrm_skill_activate",
|
|
191
|
+
"wyrm_skill_deactivate",
|
|
192
|
+
"wyrm_activate",
|
|
193
|
+
"wyrm_cloud_backup",
|
|
194
|
+
"wyrm_encrypt_setup",
|
|
195
|
+
]);
|
|
196
|
+
const server = new Server({
|
|
197
|
+
name: "wyrm",
|
|
198
|
+
version: "3.2.0",
|
|
199
|
+
}, {
|
|
200
|
+
capabilities: {
|
|
201
|
+
tools: {},
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
// Tool definitions
|
|
205
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
206
|
+
tools: [
|
|
207
|
+
// Project Management
|
|
208
|
+
{
|
|
209
|
+
name: "wyrm_scan_projects",
|
|
210
|
+
description: "Scan a directory for git projects and register them. Use this to auto-discover all projects.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
path: { type: "string", description: "Directory path to scan (e.g., /home/user/Git Projects)" },
|
|
215
|
+
watch: { type: "boolean", description: "Add to watch list for future auto-scans" },
|
|
216
|
+
recursive: { type: "boolean", description: "Scan subdirectories recursively" },
|
|
217
|
+
},
|
|
218
|
+
required: ["path"],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "wyrm_list_projects",
|
|
223
|
+
description: "List all registered projects with their status",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
search: { type: "string", description: "Search query to filter projects" },
|
|
228
|
+
limit: { type: "number", description: "Max projects to return" },
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "wyrm_project_context",
|
|
234
|
+
description: "Get full context for a specific project including recent sessions, quests, and stored context",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
projectPath: { type: "string", description: "Project path" },
|
|
239
|
+
projectName: { type: "string", description: "Or project name" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
// Multi-Project Overview
|
|
244
|
+
{
|
|
245
|
+
name: "wyrm_global_context",
|
|
246
|
+
description: "Get overview of all projects, pending quests across projects, and global context",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
includeQuests: { type: "boolean", description: "Include all pending quests" },
|
|
251
|
+
maxProjects: { type: "number", description: "Max projects to include" },
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
// Session Management
|
|
256
|
+
{
|
|
257
|
+
name: "wyrm_session_start",
|
|
258
|
+
description: "Start or continue a session for a project",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
projectPath: { type: "string", description: "Project path" },
|
|
263
|
+
objectives: { type: "string", description: "Session objectives" },
|
|
264
|
+
},
|
|
265
|
+
required: ["projectPath"],
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "wyrm_session_update",
|
|
270
|
+
description: "Update the current session with completed work, issues, or notes",
|
|
271
|
+
inputSchema: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
projectPath: { type: "string", description: "Project path" },
|
|
275
|
+
completed: { type: "string", description: "What was completed" },
|
|
276
|
+
issues: { type: "string", description: "Any issues encountered" },
|
|
277
|
+
commits: { type: "string", description: "Git commits made" },
|
|
278
|
+
notes: { type: "string", description: "Additional notes" },
|
|
279
|
+
},
|
|
280
|
+
required: ["projectPath"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
// Quest Management
|
|
284
|
+
{
|
|
285
|
+
name: "wyrm_quest_add",
|
|
286
|
+
description: "Add a quest (task) to a project",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
projectPath: { type: "string", description: "Project path" },
|
|
291
|
+
title: { type: "string", description: "Quest title" },
|
|
292
|
+
description: { type: "string", description: "Quest description" },
|
|
293
|
+
priority: { type: "string", enum: ["critical", "high", "medium", "low"] },
|
|
294
|
+
tags: { type: "string", description: "Comma-separated tags" },
|
|
295
|
+
},
|
|
296
|
+
required: ["projectPath", "title"],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "wyrm_quest_complete",
|
|
301
|
+
description: "Mark a quest as completed",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: {
|
|
305
|
+
questId: { type: "number", description: "Quest ID" },
|
|
306
|
+
},
|
|
307
|
+
required: ["questId"],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "wyrm_all_quests",
|
|
312
|
+
description: "Get all pending quests across all projects",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
priority: { type: "string", enum: ["critical", "high", "medium", "low"], description: "Filter by priority" },
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
// Data Lake Operations
|
|
321
|
+
{
|
|
322
|
+
name: "wyrm_data_insert",
|
|
323
|
+
description: "Insert data into the data lake for a project",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
projectPath: { type: "string", description: "Project path" },
|
|
328
|
+
category: { type: "string", description: "Data category (e.g., 'logs', 'metrics', 'artifacts')" },
|
|
329
|
+
key: { type: "string", description: "Data key/identifier" },
|
|
330
|
+
value: { type: "string", description: "Data value (can be JSON string)" },
|
|
331
|
+
metadata: { type: "object", description: "Optional metadata object" },
|
|
332
|
+
},
|
|
333
|
+
required: ["projectPath", "category", "key", "value"],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "wyrm_data_batch_insert",
|
|
338
|
+
description: "Batch insert multiple data points efficiently",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
projectPath: { type: "string", description: "Project path" },
|
|
343
|
+
data: {
|
|
344
|
+
type: "array",
|
|
345
|
+
items: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
category: { type: "string" },
|
|
349
|
+
key: { type: "string" },
|
|
350
|
+
value: { type: "string" },
|
|
351
|
+
metadata: { type: "object" },
|
|
352
|
+
},
|
|
353
|
+
required: ["category", "key", "value"],
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
required: ["projectPath", "data"],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "wyrm_data_query",
|
|
362
|
+
description: "Query data from the data lake",
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
projectPath: { type: "string", description: "Project path" },
|
|
367
|
+
category: { type: "string", description: "Filter by category" },
|
|
368
|
+
search: { type: "string", description: "Full-text search query" },
|
|
369
|
+
limit: { type: "number", description: "Max results" },
|
|
370
|
+
offset: { type: "number", description: "Offset for pagination" },
|
|
371
|
+
},
|
|
372
|
+
required: ["projectPath"],
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "wyrm_data_categories",
|
|
377
|
+
description: "List all data categories for a project",
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
projectPath: { type: "string", description: "Project path" },
|
|
382
|
+
},
|
|
383
|
+
required: ["projectPath"],
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
// Skill Management
|
|
387
|
+
{
|
|
388
|
+
name: "wyrm_skill_register",
|
|
389
|
+
description: "Register or update a skill and store metadata in Wyrm",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
name: { type: "string", description: "Skill name (e.g., 'professional-lead-scraping')" },
|
|
394
|
+
description: { type: "string", description: "Skill description" },
|
|
395
|
+
skillPath: { type: "string", description: "File path to skill (e.g., ~/.copilot/skills/name or project relative path)" },
|
|
396
|
+
category: { type: "string", description: "Skill category (e.g., 'data-extraction', 'testing', 'documentation')" },
|
|
397
|
+
author: { type: "string", description: "Skill author or creator" },
|
|
398
|
+
version: { type: "string", description: "Skill version" },
|
|
399
|
+
tags: { type: "string", description: "Comma-separated tags (e.g., 'scraping,leads,email-validation')" },
|
|
400
|
+
},
|
|
401
|
+
required: ["name", "description", "skillPath"],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "wyrm_skill_list",
|
|
406
|
+
description: "List registered skills with filtering options",
|
|
407
|
+
inputSchema: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
active: { type: "boolean", description: "Filter by active status" },
|
|
411
|
+
category: { type: "string", description: "Filter by category" },
|
|
412
|
+
search: { type: "string", description: "Full-text search by name, description, or tags" },
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "wyrm_skill_get",
|
|
418
|
+
description: "Get detailed information about a specific skill",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
properties: {
|
|
422
|
+
name: { type: "string", description: "Skill name" },
|
|
423
|
+
},
|
|
424
|
+
required: ["name"],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "wyrm_skill_delete",
|
|
429
|
+
description: "Delete a skill from Wyrm registry",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {
|
|
433
|
+
name: { type: "string", description: "Skill name" },
|
|
434
|
+
},
|
|
435
|
+
required: ["name"],
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "wyrm_skill_activate",
|
|
440
|
+
description: "Activate a skill (mark as active)",
|
|
441
|
+
inputSchema: {
|
|
442
|
+
type: "object",
|
|
443
|
+
properties: {
|
|
444
|
+
name: { type: "string", description: "Skill name" },
|
|
445
|
+
},
|
|
446
|
+
required: ["name"],
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: "wyrm_skill_deactivate",
|
|
451
|
+
description: "Deactivate a skill (mark as inactive)",
|
|
452
|
+
inputSchema: {
|
|
453
|
+
type: "object",
|
|
454
|
+
properties: {
|
|
455
|
+
name: { type: "string", description: "Skill name" },
|
|
456
|
+
},
|
|
457
|
+
required: ["name"],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "wyrm_skill_search",
|
|
462
|
+
description: "Search for skills by name, description, or tags",
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: "object",
|
|
465
|
+
properties: {
|
|
466
|
+
query: { type: "string", description: "Search query" },
|
|
467
|
+
limit: { type: "number", description: "Max results (default: 20)" },
|
|
468
|
+
},
|
|
469
|
+
required: ["query"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "wyrm_skill_stats",
|
|
474
|
+
description: "Get statistics on registered skills",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {},
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
// Global Context
|
|
481
|
+
{
|
|
482
|
+
name: "wyrm_set_global",
|
|
483
|
+
description: "Set global context that applies across all projects",
|
|
484
|
+
inputSchema: {
|
|
485
|
+
type: "object",
|
|
486
|
+
properties: {
|
|
487
|
+
key: { type: "string", description: "Context key" },
|
|
488
|
+
value: { type: "string", description: "Context value" },
|
|
489
|
+
},
|
|
490
|
+
required: ["key", "value"],
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
// Search
|
|
494
|
+
{
|
|
495
|
+
name: "wyrm_search",
|
|
496
|
+
description: "Search across all projects, sessions, quests, and data",
|
|
497
|
+
inputSchema: {
|
|
498
|
+
type: "object",
|
|
499
|
+
properties: {
|
|
500
|
+
query: { type: "string", description: "Search query" },
|
|
501
|
+
type: { type: "string", enum: ["all", "sessions", "quests", "data"], description: "What to search" },
|
|
502
|
+
projectPath: { type: "string", description: "Limit to specific project" },
|
|
503
|
+
},
|
|
504
|
+
required: ["query"],
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
// Sync & Maintenance
|
|
508
|
+
{
|
|
509
|
+
name: "wyrm_sync",
|
|
510
|
+
description: "Sync database with .wyrm folders in all projects",
|
|
511
|
+
inputSchema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
properties: {
|
|
514
|
+
projectPath: { type: "string", description: "Sync specific project, or all if not specified" },
|
|
515
|
+
direction: { type: "string", enum: ["import", "export", "both"], description: "Sync direction" },
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: "wyrm_stats",
|
|
521
|
+
description: "Get Wyrm database statistics",
|
|
522
|
+
inputSchema: {
|
|
523
|
+
type: "object",
|
|
524
|
+
properties: {},
|
|
525
|
+
},
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "wyrm_maintenance",
|
|
529
|
+
description: "Run database maintenance (vacuum, archive old sessions)",
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: "object",
|
|
532
|
+
properties: {
|
|
533
|
+
vacuum: { type: "boolean", description: "Run vacuum to reclaim space" },
|
|
534
|
+
archiveDays: { type: "number", description: "Archive sessions older than N days" },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
// Usage & Cost Tracking
|
|
539
|
+
{
|
|
540
|
+
name: "wyrm_usage",
|
|
541
|
+
description: "View token usage stats, cache hit rates, and estimated cost savings. Helps monitor and optimize AI credit consumption.",
|
|
542
|
+
inputSchema: {
|
|
543
|
+
type: "object",
|
|
544
|
+
properties: {
|
|
545
|
+
last: { type: "number", description: "Show stats for last N calls (default: all)" },
|
|
546
|
+
reset: { type: "boolean", description: "Reset usage counters" },
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
// Auto-Configure
|
|
551
|
+
{
|
|
552
|
+
name: "wyrm_setup",
|
|
553
|
+
description: "Auto-detect installed AI clients (VS Code, Claude Desktop, Cursor, Windsurf, Zed) and configure Wyrm's MCP server in all of them. Run this to connect Wyrm to a new AI or after switching providers.",
|
|
554
|
+
inputSchema: {
|
|
555
|
+
type: "object",
|
|
556
|
+
properties: {
|
|
557
|
+
action: { type: "string", enum: ["configure", "check", "remove"], description: "Action: configure (default), check status, or remove from all" },
|
|
558
|
+
serverPath: { type: "string", description: "Override Wyrm server path (auto-detected if empty)" },
|
|
559
|
+
dbPath: { type: "string", description: "Override database path (default: ~/.wyrm/wyrm.db)" },
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
// Auto-Orchestration
|
|
564
|
+
{
|
|
565
|
+
name: "wyrm_orchestrate_task",
|
|
566
|
+
description: "Classify a task and get automatic orchestration recommendation (ensemble voting, parallel research, etc)",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: "object",
|
|
569
|
+
properties: {
|
|
570
|
+
task: { type: "string", description: "Task description to classify and orchestrate" },
|
|
571
|
+
},
|
|
572
|
+
required: ["task"],
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: "wyrm_orchestration_config",
|
|
577
|
+
description: "Get or update auto-orchestration configuration",
|
|
578
|
+
inputSchema: {
|
|
579
|
+
type: "object",
|
|
580
|
+
properties: {
|
|
581
|
+
get: { type: "boolean", description: "Get current configuration (default: true)" },
|
|
582
|
+
autoOrchestrateEnabled: { type: "boolean", description: "Enable/disable auto-orchestration" },
|
|
583
|
+
minConfidenceThreshold: { type: "number", description: "Min confidence for auto-apply (0-100)" },
|
|
584
|
+
maxParallelAgents: { type: "number", description: "Max agents to spawn in parallel" },
|
|
585
|
+
defaultHaikuBoosting: { type: "boolean", description: "Auto-boost all Haiku calls" },
|
|
586
|
+
trackMetrics: { type: "boolean", description: "Track quality/cost metrics" },
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "wyrm_orchestration_stats",
|
|
592
|
+
description: "Get auto-orchestration effectiveness statistics and task distribution",
|
|
593
|
+
inputSchema: {
|
|
594
|
+
type: "object",
|
|
595
|
+
properties: {},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
// ==================== LICENSE & PRO FEATURES ====================
|
|
599
|
+
{
|
|
600
|
+
name: "wyrm_license",
|
|
601
|
+
description: "View current license status, tier, and available features",
|
|
602
|
+
inputSchema: {
|
|
603
|
+
type: "object",
|
|
604
|
+
properties: {},
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: "wyrm_activate",
|
|
609
|
+
description: "Activate a license key to unlock Pro/Team/Enterprise features",
|
|
610
|
+
inputSchema: {
|
|
611
|
+
type: "object",
|
|
612
|
+
properties: {
|
|
613
|
+
license: { type: "string", description: "License JSON string (signed license from Ghost Protocol)" },
|
|
614
|
+
},
|
|
615
|
+
required: ["license"],
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "wyrm_analytics_dashboard",
|
|
620
|
+
description: "View usage analytics dashboard ā tool calls, tokens, costs, cache efficiency (Pro+)",
|
|
621
|
+
inputSchema: {
|
|
622
|
+
type: "object",
|
|
623
|
+
properties: {
|
|
624
|
+
days: { type: "number", description: "Number of days to analyze (default: 30)" },
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: "wyrm_cost_report",
|
|
630
|
+
description: "View estimated API cost report for current billing period (Pro+)",
|
|
631
|
+
inputSchema: {
|
|
632
|
+
type: "object",
|
|
633
|
+
properties: {
|
|
634
|
+
period: { type: "string", description: "Period in YYYY-MM format (default: current month)" },
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "wyrm_cloud_backup",
|
|
640
|
+
description: "Backup Wyrm database to encrypted cloud storage (Pro+)",
|
|
641
|
+
inputSchema: {
|
|
642
|
+
type: "object",
|
|
643
|
+
properties: {
|
|
644
|
+
action: { type: "string", description: "Action: backup, restore, list, prune", enum: ["backup", "restore", "list", "prune"] },
|
|
645
|
+
backupKey: { type: "string", description: "Backup key for restore (from list)" },
|
|
646
|
+
keepCount: { type: "number", description: "Number of backups to keep when pruning (default: 10)" },
|
|
647
|
+
},
|
|
648
|
+
required: ["action"],
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: "wyrm_encrypt_setup",
|
|
653
|
+
description: "Set up or check encryption status for sensitive data (Pro+)",
|
|
654
|
+
inputSchema: {
|
|
655
|
+
type: "object",
|
|
656
|
+
properties: {
|
|
657
|
+
action: { type: "string", description: "Action: status, enable, test", enum: ["status", "enable", "test"] },
|
|
658
|
+
password: { type: "string", description: "Encryption password (min 8 chars, required for enable)" },
|
|
659
|
+
},
|
|
660
|
+
required: ["action"],
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
}));
|
|
665
|
+
// Tool implementations
|
|
666
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
667
|
+
const { name, arguments: args } = request.params;
|
|
668
|
+
const startTime = performance.now();
|
|
669
|
+
const argsStr = JSON.stringify(args || {});
|
|
670
|
+
const tokensIn = estimateTokens(argsStr);
|
|
671
|
+
// Cache key for read-only tools
|
|
672
|
+
const cacheKey = READ_ONLY_TOOLS.has(name) ? `${name}:${argsStr}` : null;
|
|
673
|
+
// Check in-memory cache for read-only tools
|
|
674
|
+
if (cacheKey) {
|
|
675
|
+
const cached = cache.get(cacheKey);
|
|
676
|
+
if (cached) {
|
|
677
|
+
const ms = Math.round(performance.now() - startTime);
|
|
678
|
+
const tokensOut = estimateTokens(JSON.stringify(cached));
|
|
679
|
+
trackUsage({ tool: name, tokens_in: tokensIn, tokens_out: tokensOut, cached: true, ms, timestamp: new Date().toISOString() });
|
|
680
|
+
// Return cached response with cache_control hints
|
|
681
|
+
return {
|
|
682
|
+
...cached,
|
|
683
|
+
_meta: { cacheControl: { type: "ephemeral" } },
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Invalidate caches on write operations
|
|
688
|
+
if (WRITE_TOOLS.has(name)) {
|
|
689
|
+
const projectPath = args?.projectPath;
|
|
690
|
+
if (projectPath) {
|
|
691
|
+
// Invalidate project-specific caches
|
|
692
|
+
cache.invalidate(projectPath);
|
|
693
|
+
}
|
|
694
|
+
// Invalidate global caches
|
|
695
|
+
cache.invalidate('wyrm_list_projects');
|
|
696
|
+
cache.invalidate('wyrm_global_context');
|
|
697
|
+
cache.invalidate('wyrm_all_quests');
|
|
698
|
+
cache.invalidate('wyrm_stats');
|
|
699
|
+
}
|
|
700
|
+
let result;
|
|
701
|
+
try {
|
|
702
|
+
result = await (async () => {
|
|
703
|
+
switch (name) {
|
|
704
|
+
// ==================== PROJECT MANAGEMENT ====================
|
|
705
|
+
case "wyrm_scan_projects": {
|
|
706
|
+
const { path, watch, recursive } = args;
|
|
707
|
+
if (watch) {
|
|
708
|
+
db.addWatchDir(path, recursive !== false);
|
|
709
|
+
}
|
|
710
|
+
const projects = db.scanForProjects(path, recursive !== false);
|
|
711
|
+
return {
|
|
712
|
+
content: [{
|
|
713
|
+
type: "text",
|
|
714
|
+
text: `š Scanned ${path}\n\nDiscovered ${projects.length} projects:\n${projects.map(p => `- ${p.name} (${p.stack || 'unknown'}) - ${p.branch || 'no branch'}`).join('\n')}${watch ? '\n\nAdded to watch list for future auto-scans.' : ''}`
|
|
715
|
+
}]
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
case "wyrm_list_projects": {
|
|
719
|
+
const { search, limit } = args;
|
|
720
|
+
const projects = search
|
|
721
|
+
? db.searchProjects(search)
|
|
722
|
+
: db.getAllProjects(limit || 50);
|
|
723
|
+
const projectList = projects.map(p => {
|
|
724
|
+
const stats = db.getProjectStats(p.id);
|
|
725
|
+
return `## ${p.name}\n` +
|
|
726
|
+
`- **Path:** ${p.path}\n` +
|
|
727
|
+
`- **Stack:** ${p.stack || 'unknown'}\n` +
|
|
728
|
+
`- **Branch:** ${p.branch || 'N/A'}\n` +
|
|
729
|
+
`- **Sessions:** ${stats.sessions} | **Quests:** ${stats.quests.pending}p/${stats.quests.completed}c\n` +
|
|
730
|
+
`- **Data:** ${stats.dataPoints}`;
|
|
731
|
+
}).join('\n\n');
|
|
732
|
+
const response = cachedResponse(`š **${projects.length} Projects**\n\n${projectList}`);
|
|
733
|
+
if (cacheKey)
|
|
734
|
+
cache.set(cacheKey, response, 60000); // 60s TTL
|
|
735
|
+
return response;
|
|
736
|
+
}
|
|
737
|
+
case "wyrm_project_context": {
|
|
738
|
+
const { projectPath, projectName } = args;
|
|
739
|
+
let project = projectPath ? db.getProject(projectPath) : undefined;
|
|
740
|
+
if (!project && projectName) {
|
|
741
|
+
project = db.getProjectByName(projectName);
|
|
742
|
+
}
|
|
743
|
+
if (!project) {
|
|
744
|
+
return { content: [{ type: "text", text: "Project not found. Run wyrm_scan_projects first." }] };
|
|
745
|
+
}
|
|
746
|
+
// Gather data for context bundle
|
|
747
|
+
const recentSessions = db.getRecentSessions(project.id, 10);
|
|
748
|
+
const quests = db.getPendingQuests(project.id).map(q => ({ title: q.title, priority: q.priority }));
|
|
749
|
+
const currentContext = db.getAllContext(project.id);
|
|
750
|
+
const context = createContextBundle({ name: project.name, stack: project.stack }, currentContext, recentSessions, quests);
|
|
751
|
+
const response = cachedResponse(`š **Context for ${project.name}**\n\n${context}`);
|
|
752
|
+
if (cacheKey)
|
|
753
|
+
cache.set(cacheKey, response, 30000); // 30s TTL ā context changes more often
|
|
754
|
+
return response;
|
|
755
|
+
}
|
|
756
|
+
// ==================== GLOBAL CONTEXT ====================
|
|
757
|
+
case "wyrm_global_context": {
|
|
758
|
+
const { includeQuests, maxProjects } = args;
|
|
759
|
+
const projects = db.getAllProjects(maxProjects || 20);
|
|
760
|
+
const globalContext = db.getAllGlobalContext();
|
|
761
|
+
let text = `š **Wyrm Global Overview**\n\n`;
|
|
762
|
+
// Global context
|
|
763
|
+
if (Object.keys(globalContext).length > 0) {
|
|
764
|
+
text += `## Global Context\n`;
|
|
765
|
+
for (const [key, value] of Object.entries(globalContext)) {
|
|
766
|
+
text += `- **${key}:** ${value.slice(0, 200)}${value.length > 200 ? '...' : ''}\n`;
|
|
767
|
+
}
|
|
768
|
+
text += '\n';
|
|
769
|
+
}
|
|
770
|
+
// Projects summary
|
|
771
|
+
text += `## Projects (${projects.length})\n`;
|
|
772
|
+
for (const p of projects) {
|
|
773
|
+
const stats = db.getProjectStats(p.id);
|
|
774
|
+
text += `- **${p.name}** (${p.stack || '?'}) - ${stats.quests.pending} quests, ${stats.sessions} sessions\n`;
|
|
775
|
+
}
|
|
776
|
+
text += '\n';
|
|
777
|
+
// All pending quests
|
|
778
|
+
if (includeQuests) {
|
|
779
|
+
const allQuests = db.getAllPendingQuests();
|
|
780
|
+
text += `## All Pending Quests (${allQuests.length})\n`;
|
|
781
|
+
for (const q of allQuests.slice(0, 20)) {
|
|
782
|
+
const emoji = { critical: 'š“', high: 'š ', medium: 'š”', low: 'š¢' }[q.priority];
|
|
783
|
+
text += `- ${emoji} [${q.project_name}] ${q.title}\n`;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const response = cachedResponse(text);
|
|
787
|
+
if (cacheKey)
|
|
788
|
+
cache.set(cacheKey, response, 45000); // 45s TTL
|
|
789
|
+
return response;
|
|
790
|
+
}
|
|
791
|
+
// ==================== SESSIONS ====================
|
|
792
|
+
case "wyrm_session_start": {
|
|
793
|
+
const { projectPath, objectives } = args;
|
|
794
|
+
let project = db.getProject(projectPath);
|
|
795
|
+
if (!project) {
|
|
796
|
+
// Auto-register project
|
|
797
|
+
const { basename } = await import('path');
|
|
798
|
+
project = db.registerProject(basename(projectPath), projectPath);
|
|
799
|
+
}
|
|
800
|
+
let session = db.getTodaySession(project.id);
|
|
801
|
+
if (!session) {
|
|
802
|
+
session = db.createSession(project.id, { objectives: objectives || '' });
|
|
803
|
+
}
|
|
804
|
+
else if (objectives) {
|
|
805
|
+
session = db.updateSession(session.id, {
|
|
806
|
+
objectives: session.objectives ? `${session.objectives}\n${objectives}` : objectives
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
// Archive old sessions
|
|
810
|
+
db.archiveOldSessions(project.id, 10);
|
|
811
|
+
return {
|
|
812
|
+
content: [{
|
|
813
|
+
type: "text",
|
|
814
|
+
text: `š Session ${session.id} for ${project.name}\n` +
|
|
815
|
+
`**Date:** ${session.date}\n` +
|
|
816
|
+
`**Objectives:** ${session.objectives || 'None set'}`
|
|
817
|
+
}]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
case "wyrm_session_update": {
|
|
821
|
+
const { projectPath, completed, issues, commits, notes } = args;
|
|
822
|
+
const project = db.getProject(projectPath);
|
|
823
|
+
if (!project) {
|
|
824
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
825
|
+
}
|
|
826
|
+
let session = db.getTodaySession(project.id);
|
|
827
|
+
if (!session) {
|
|
828
|
+
session = db.createSession(project.id, {});
|
|
829
|
+
}
|
|
830
|
+
const updates = {};
|
|
831
|
+
if (completed)
|
|
832
|
+
updates.completed = session.completed ? `${session.completed}\n${completed}` : completed;
|
|
833
|
+
if (issues)
|
|
834
|
+
updates.issues = session.issues ? `${session.issues}\n${issues}` : issues;
|
|
835
|
+
if (commits)
|
|
836
|
+
updates.commits = session.commits ? `${session.commits}\n${commits}` : commits;
|
|
837
|
+
if (notes)
|
|
838
|
+
updates.notes = session.notes ? `${session.notes}\n${notes}` : notes;
|
|
839
|
+
session = db.updateSession(session.id, updates);
|
|
840
|
+
return {
|
|
841
|
+
content: [{
|
|
842
|
+
type: "text",
|
|
843
|
+
text: `š Session updated for ${project.name}`
|
|
844
|
+
}]
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
// ==================== QUESTS ====================
|
|
848
|
+
case "wyrm_quest_add": {
|
|
849
|
+
const { projectPath, title, description, priority, tags } = args;
|
|
850
|
+
const project = db.getProject(projectPath);
|
|
851
|
+
if (!project) {
|
|
852
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
853
|
+
}
|
|
854
|
+
const quest = db.addQuest(project.id, title, description, priority || 'medium', tags);
|
|
855
|
+
return {
|
|
856
|
+
content: [{
|
|
857
|
+
type: "text",
|
|
858
|
+
text: `š Quest #${quest.id} added: ${title}`
|
|
859
|
+
}]
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
case "wyrm_quest_complete": {
|
|
863
|
+
const { questId } = args;
|
|
864
|
+
const quest = db.updateQuest(questId, 'completed');
|
|
865
|
+
return {
|
|
866
|
+
content: [{
|
|
867
|
+
type: "text",
|
|
868
|
+
text: `š Quest #${questId} completed: ${quest.title}`
|
|
869
|
+
}]
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
case "wyrm_all_quests": {
|
|
873
|
+
const { priority } = args;
|
|
874
|
+
let quests = db.getAllPendingQuests();
|
|
875
|
+
if (priority) {
|
|
876
|
+
quests = quests.filter(q => q.priority === priority);
|
|
877
|
+
}
|
|
878
|
+
const text = quests.map(q => {
|
|
879
|
+
const emoji = { critical: 'š“', high: 'š ', medium: 'š”', low: 'š¢' }[q.priority];
|
|
880
|
+
return `${emoji} [${q.project_name}] #${q.id}: ${q.title}`;
|
|
881
|
+
}).join('\n');
|
|
882
|
+
const response = cachedResponse(`š **${quests.length} Pending Quests**\n\n${text}`);
|
|
883
|
+
if (cacheKey)
|
|
884
|
+
cache.set(cacheKey, response, 30000);
|
|
885
|
+
return response;
|
|
886
|
+
}
|
|
887
|
+
// ==================== DATA LAKE ====================
|
|
888
|
+
case "wyrm_data_insert": {
|
|
889
|
+
const { projectPath, category, key, value, metadata } = args;
|
|
890
|
+
const project = db.getProject(projectPath);
|
|
891
|
+
if (!project) {
|
|
892
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
893
|
+
}
|
|
894
|
+
const sanitizedCategory = sanitizeString(category, 200);
|
|
895
|
+
const sanitizedKey = sanitizeString(key, 500);
|
|
896
|
+
const encryptedValue = wyrmCrypto.maybeEncrypt(sanitizeString(value));
|
|
897
|
+
const dataPoint = db.insertData(project.id, sanitizedCategory, sanitizedKey, encryptedValue, metadata);
|
|
898
|
+
// Index in vector store if available
|
|
899
|
+
if (vectorStore) {
|
|
900
|
+
vectorStore.addVector(`${category} ${key} ${value}`, 'note', dataPoint.id, project.id).catch(() => { });
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
content: [{
|
|
904
|
+
type: "text",
|
|
905
|
+
text: `š Data inserted: ${category}/${key} (ID: ${dataPoint.id})${wyrmCrypto.isEnabled() ? ' š' : ''}`
|
|
906
|
+
}]
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
case "wyrm_data_batch_insert": {
|
|
910
|
+
const { projectPath, data } = args;
|
|
911
|
+
validateBatchSize(data);
|
|
912
|
+
const project = db.getProject(projectPath);
|
|
913
|
+
if (!project) {
|
|
914
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
915
|
+
}
|
|
916
|
+
const items = data.map(d => ({
|
|
917
|
+
...d,
|
|
918
|
+
projectId: project.id,
|
|
919
|
+
value: wyrmCrypto.maybeEncrypt(sanitizeString(d.value)),
|
|
920
|
+
}));
|
|
921
|
+
const count = db.insertDataBatch(items);
|
|
922
|
+
return {
|
|
923
|
+
content: [{
|
|
924
|
+
type: "text",
|
|
925
|
+
text: `š Batch inserted ${count} data points${wyrmCrypto.isEnabled() ? ' š' : ''}`
|
|
926
|
+
}]
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
case "wyrm_data_query": {
|
|
930
|
+
const { projectPath, category, search, limit, offset } = args;
|
|
931
|
+
const project = db.getProject(projectPath);
|
|
932
|
+
if (!project) {
|
|
933
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
934
|
+
}
|
|
935
|
+
let results;
|
|
936
|
+
if (search) {
|
|
937
|
+
const sanitizedSearch = sanitizeFtsQuery(search);
|
|
938
|
+
results = db.searchData(sanitizedSearch, project.id);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
results = db.queryData(project.id, category, limit || 100, offset || 0);
|
|
942
|
+
}
|
|
943
|
+
const text = results.slice(0, 50).map(d => {
|
|
944
|
+
const val = wyrmCrypto.maybeDecrypt(d.value);
|
|
945
|
+
return `- **${d.category}/${d.key}:** ${val.slice(0, 100)}${val.length > 100 ? '...' : ''}`;
|
|
946
|
+
}).join('\n');
|
|
947
|
+
const response = cachedResponse(`š **${results.length} Results**\n\n${text}`);
|
|
948
|
+
if (cacheKey)
|
|
949
|
+
cache.set(cacheKey, response, 30000);
|
|
950
|
+
return response;
|
|
951
|
+
}
|
|
952
|
+
case "wyrm_data_categories": {
|
|
953
|
+
const { projectPath } = args;
|
|
954
|
+
const project = db.getProject(projectPath);
|
|
955
|
+
if (!project) {
|
|
956
|
+
return { content: [{ type: "text", text: "Project not found" }] };
|
|
957
|
+
}
|
|
958
|
+
const categories = db.getDataCategories(project.id);
|
|
959
|
+
const text = categories.map(c => `- ${c.category}: ${c.count} items`).join('\n');
|
|
960
|
+
const response = cachedResponse(`š **Data Categories for ${project.name}**\n\n${text}`);
|
|
961
|
+
if (cacheKey)
|
|
962
|
+
cache.set(cacheKey, response, 30000);
|
|
963
|
+
return response;
|
|
964
|
+
}
|
|
965
|
+
// ==================== GLOBAL CONTEXT ====================
|
|
966
|
+
case "wyrm_set_global": {
|
|
967
|
+
const { key, value } = args;
|
|
968
|
+
db.setGlobalContext(key, value);
|
|
969
|
+
return {
|
|
970
|
+
content: [{
|
|
971
|
+
type: "text",
|
|
972
|
+
text: `š Global context set: ${key}`
|
|
973
|
+
}]
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
// ==================== SKILLS MANAGEMENT ====================
|
|
977
|
+
case "wyrm_skill_register": {
|
|
978
|
+
const { name, description, skillPath, category, author, version, tags } = args;
|
|
979
|
+
const skill = db.registerSkill(name, description, skillPath, category, author, version, tags);
|
|
980
|
+
cache.invalidate('wyrm_skill_list');
|
|
981
|
+
cache.invalidate('wyrm_skill_stats');
|
|
982
|
+
return {
|
|
983
|
+
content: [{
|
|
984
|
+
type: "text",
|
|
985
|
+
text: `š **Skill Registered**\n\nName: ${skill.name}\nPath: ${skill.skill_path}\nCategory: ${skill.category || 'uncategorized'}\nVersion: ${skill.version || '1.0.0'}\nStatus: ${skill.is_active ? 'Active' : 'Inactive'}`
|
|
986
|
+
}]
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
case "wyrm_skill_list": {
|
|
990
|
+
const { active, category, search } = args;
|
|
991
|
+
const skills = db.listSkills(active, category, search);
|
|
992
|
+
if (skills.length === 0) {
|
|
993
|
+
return {
|
|
994
|
+
content: [{
|
|
995
|
+
type: "text",
|
|
996
|
+
text: `š **Skills**\n\nNo skills found matching your criteria.`
|
|
997
|
+
}]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
let text = `š **Skills Registry** (${skills.length} total)\n\n`;
|
|
1001
|
+
for (const skill of skills) {
|
|
1002
|
+
text += `### ${skill.name} ${skill.is_active ? 'ā
' : 'ā'}\n`;
|
|
1003
|
+
text += `Category: ${skill.category || 'uncategorized'}\n`;
|
|
1004
|
+
text += `Description: ${skill.description}\n`;
|
|
1005
|
+
if (skill.version)
|
|
1006
|
+
text += `Version: ${skill.version}\n`;
|
|
1007
|
+
if (skill.author)
|
|
1008
|
+
text += `Author: ${skill.author}\n`;
|
|
1009
|
+
if (skill.tags)
|
|
1010
|
+
text += `Tags: ${skill.tags}\n`;
|
|
1011
|
+
text += `Path: \`${skill.skill_path}\`\n`;
|
|
1012
|
+
text += `Used: ${skill.usage_count} times`;
|
|
1013
|
+
if (skill.last_used)
|
|
1014
|
+
text += ` (last: ${skill.last_used})`;
|
|
1015
|
+
text += '\n\n';
|
|
1016
|
+
}
|
|
1017
|
+
const response = cachedResponse(text);
|
|
1018
|
+
if (cacheKey)
|
|
1019
|
+
cache.set(cacheKey, response, 30000);
|
|
1020
|
+
return response;
|
|
1021
|
+
}
|
|
1022
|
+
case "wyrm_skill_get": {
|
|
1023
|
+
const { name } = args;
|
|
1024
|
+
const skill = db.getSkill(name);
|
|
1025
|
+
if (!skill) {
|
|
1026
|
+
return {
|
|
1027
|
+
content: [{
|
|
1028
|
+
type: "text",
|
|
1029
|
+
text: `š **Skill Not Found**: ${name}`
|
|
1030
|
+
}]
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
let text = `š **Skill: ${skill.name}** ${skill.is_active ? 'ā
Active' : 'ā Inactive'}\n\n`;
|
|
1034
|
+
text += `**Description:** ${skill.description}\n`;
|
|
1035
|
+
text += `**Path:** \`${skill.skill_path}\`\n`;
|
|
1036
|
+
text += `**Category:** ${skill.category || 'uncategorized'}\n`;
|
|
1037
|
+
if (skill.author)
|
|
1038
|
+
text += `**Author:** ${skill.author}\n`;
|
|
1039
|
+
if (skill.version)
|
|
1040
|
+
text += `**Version:** ${skill.version}\n`;
|
|
1041
|
+
if (skill.tags)
|
|
1042
|
+
text += `**Tags:** ${skill.tags}\n`;
|
|
1043
|
+
text += `**Usage Count:** ${skill.usage_count}\n`;
|
|
1044
|
+
if (skill.last_used)
|
|
1045
|
+
text += `**Last Used:** ${skill.last_used}\n`;
|
|
1046
|
+
text += `**Created:** ${skill.created_at}\n`;
|
|
1047
|
+
text += `**Updated:** ${skill.updated_at}\n`;
|
|
1048
|
+
const response = cachedResponse(text);
|
|
1049
|
+
if (cacheKey)
|
|
1050
|
+
cache.set(cacheKey, response, 30000);
|
|
1051
|
+
return response;
|
|
1052
|
+
}
|
|
1053
|
+
case "wyrm_skill_delete": {
|
|
1054
|
+
const { name } = args;
|
|
1055
|
+
const success = db.deleteSkill(name);
|
|
1056
|
+
cache.invalidate('wyrm_skill_list');
|
|
1057
|
+
cache.invalidate('wyrm_skill_stats');
|
|
1058
|
+
if (success) {
|
|
1059
|
+
return {
|
|
1060
|
+
content: [{
|
|
1061
|
+
type: "text",
|
|
1062
|
+
text: `š **Skill Deleted**: ${name}`
|
|
1063
|
+
}]
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
return {
|
|
1068
|
+
content: [{
|
|
1069
|
+
type: "text",
|
|
1070
|
+
text: `š **Skill Not Found**: ${name}`
|
|
1071
|
+
}]
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
case "wyrm_skill_activate": {
|
|
1076
|
+
const { name } = args;
|
|
1077
|
+
const skill = db.activateSkill(name);
|
|
1078
|
+
cache.invalidate('wyrm_skill_list');
|
|
1079
|
+
cache.invalidate('wyrm_skill_stats');
|
|
1080
|
+
if (skill) {
|
|
1081
|
+
return {
|
|
1082
|
+
content: [{
|
|
1083
|
+
type: "text",
|
|
1084
|
+
text: `š **Skill Activated**: ${name}`
|
|
1085
|
+
}]
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
return {
|
|
1090
|
+
content: [{
|
|
1091
|
+
type: "text",
|
|
1092
|
+
text: `š **Skill Not Found**: ${name}`
|
|
1093
|
+
}]
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
case "wyrm_skill_deactivate": {
|
|
1098
|
+
const { name } = args;
|
|
1099
|
+
const skill = db.deactivateSkill(name);
|
|
1100
|
+
cache.invalidate('wyrm_skill_list');
|
|
1101
|
+
cache.invalidate('wyrm_skill_stats');
|
|
1102
|
+
if (skill) {
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{
|
|
1105
|
+
type: "text",
|
|
1106
|
+
text: `š **Skill Deactivated**: ${name}`
|
|
1107
|
+
}]
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
return {
|
|
1112
|
+
content: [{
|
|
1113
|
+
type: "text",
|
|
1114
|
+
text: `š **Skill Not Found**: ${name}`
|
|
1115
|
+
}]
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
case "wyrm_skill_search": {
|
|
1120
|
+
const { query, limit } = args;
|
|
1121
|
+
const sanitizedQuery = sanitizeFtsQuery(query);
|
|
1122
|
+
const skills = db.searchSkills(sanitizedQuery, limit || 20);
|
|
1123
|
+
if (skills.length === 0) {
|
|
1124
|
+
return {
|
|
1125
|
+
content: [{
|
|
1126
|
+
type: "text",
|
|
1127
|
+
text: `š **Search Skills**: No results found for "${query}"`
|
|
1128
|
+
}]
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
let text = `š **Skill Search Results for "${query}"** (${skills.length} found)\n\n`;
|
|
1132
|
+
for (const skill of skills) {
|
|
1133
|
+
text += `- **${skill.name}** (${skill.category || 'uncategorized'}) ${skill.is_active ? 'ā
' : 'ā'}\n`;
|
|
1134
|
+
text += ` ${skill.description}\n\n`;
|
|
1135
|
+
}
|
|
1136
|
+
const response = cachedResponse(text);
|
|
1137
|
+
if (cacheKey)
|
|
1138
|
+
cache.set(cacheKey, response, 30000);
|
|
1139
|
+
return response;
|
|
1140
|
+
}
|
|
1141
|
+
case "wyrm_skill_stats": {
|
|
1142
|
+
const stats = db.getSkillStats();
|
|
1143
|
+
let text = `š **Skill Statistics**\n\n`;
|
|
1144
|
+
text += `**Total Skills:** ${stats.total}\n`;
|
|
1145
|
+
text += `**Active Skills:** ${stats.active}\n`;
|
|
1146
|
+
text += `**Inactive Skills:** ${stats.total - stats.active}\n\n`;
|
|
1147
|
+
if (Object.keys(stats.byCategory).length > 0) {
|
|
1148
|
+
text += `**By Category:**\n`;
|
|
1149
|
+
for (const [category, count] of Object.entries(stats.byCategory)) {
|
|
1150
|
+
text += `- ${category}: ${count}\n`;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const response = cachedResponse(text);
|
|
1154
|
+
if (cacheKey)
|
|
1155
|
+
cache.set(cacheKey, response, 30000);
|
|
1156
|
+
return response;
|
|
1157
|
+
}
|
|
1158
|
+
// ==================== SEARCH ====================
|
|
1159
|
+
case "wyrm_search": {
|
|
1160
|
+
const { query, type, projectPath } = args;
|
|
1161
|
+
const sanitizedQuery = sanitizeFtsQuery(query);
|
|
1162
|
+
const project = projectPath ? db.getProject(projectPath) : undefined;
|
|
1163
|
+
const projectId = project?.id;
|
|
1164
|
+
const searchType = type || 'all';
|
|
1165
|
+
let text = `š **Search Results for "${query}"**\n\n`;
|
|
1166
|
+
if (searchType === 'all' || searchType === 'sessions') {
|
|
1167
|
+
const sessions = db.searchSessions(sanitizedQuery, projectId);
|
|
1168
|
+
if (sessions.length > 0) {
|
|
1169
|
+
text += `## Sessions (${sessions.length})\n`;
|
|
1170
|
+
for (const s of sessions.slice(0, 10)) {
|
|
1171
|
+
text += `- ${s.date}: ${s.objectives?.slice(0, 80) || s.completed?.slice(0, 80) || 'No info'}...\n`;
|
|
1172
|
+
}
|
|
1173
|
+
text += '\n';
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (searchType === 'all' || searchType === 'quests') {
|
|
1177
|
+
const quests = db.searchQuests(sanitizedQuery);
|
|
1178
|
+
if (quests.length > 0) {
|
|
1179
|
+
text += `## Quests (${quests.length})\n`;
|
|
1180
|
+
for (const q of quests.slice(0, 10)) {
|
|
1181
|
+
text += `- #${q.id}: ${q.title}\n`;
|
|
1182
|
+
}
|
|
1183
|
+
text += '\n';
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (searchType === 'all' || searchType === 'data') {
|
|
1187
|
+
const data = db.searchData(sanitizedQuery, projectId);
|
|
1188
|
+
if (data.length > 0) {
|
|
1189
|
+
text += `## Data (${data.length})\n`;
|
|
1190
|
+
for (const d of data.slice(0, 10)) {
|
|
1191
|
+
const val = wyrmCrypto.maybeDecrypt(d.value);
|
|
1192
|
+
text += `- ${d.category}/${d.key}: ${val.slice(0, 60)}${val.length > 60 ? '...' : ''}\n`;
|
|
1193
|
+
}
|
|
1194
|
+
text += '\n';
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (searchType === 'all' || searchType === 'skills') {
|
|
1198
|
+
const skills = db.searchSkills(sanitizedQuery, 10);
|
|
1199
|
+
if (skills.length > 0) {
|
|
1200
|
+
text += `## Skills (${skills.length})\n`;
|
|
1201
|
+
for (const s of skills) {
|
|
1202
|
+
text += `- **${s.name}** (${s.category || 'uncategorized'}) ${s.is_active ? '\u2705' : '\u274c'}\n`;
|
|
1203
|
+
text += ` ${s.description}\n`;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
// Optional vector search enhancement
|
|
1208
|
+
if (vectorStore && (searchType === 'all' || searchType === 'data')) {
|
|
1209
|
+
try {
|
|
1210
|
+
const vectorResults = await vectorStore.search(query, 5, projectId);
|
|
1211
|
+
if (vectorResults.length > 0) {
|
|
1212
|
+
text += `\n## Semantic Matches (${vectorResults.length})\n`;
|
|
1213
|
+
for (const v of vectorResults) {
|
|
1214
|
+
text += `- ${v.content_type} #${v.content_id} (similarity: ${(v.similarity * 100).toFixed(1)}%)\n`;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
catch {
|
|
1219
|
+
// Vector search failed gracefully ā FTS results still returned
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const response = cachedResponse(text);
|
|
1223
|
+
if (cacheKey)
|
|
1224
|
+
cache.set(cacheKey, response, 20000);
|
|
1225
|
+
return response;
|
|
1226
|
+
}
|
|
1227
|
+
// ==================== SYNC & MAINTENANCE ====================
|
|
1228
|
+
case "wyrm_sync": {
|
|
1229
|
+
const { projectPath, direction } = args;
|
|
1230
|
+
const dir = direction || 'both';
|
|
1231
|
+
let count = 0;
|
|
1232
|
+
if (projectPath) {
|
|
1233
|
+
const project = db.getProject(projectPath);
|
|
1234
|
+
if (project) {
|
|
1235
|
+
if (dir === 'import' || dir === 'both')
|
|
1236
|
+
sync.importFromFolder(projectPath);
|
|
1237
|
+
if (dir === 'export' || dir === 'both')
|
|
1238
|
+
sync.exportToFolder(projectPath);
|
|
1239
|
+
count = 1;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
const projects = db.getAllProjects(1000);
|
|
1244
|
+
for (const p of projects) {
|
|
1245
|
+
try {
|
|
1246
|
+
if (dir === 'import' || dir === 'both')
|
|
1247
|
+
sync.importFromFolder(p.path);
|
|
1248
|
+
if (dir === 'export' || dir === 'both')
|
|
1249
|
+
sync.exportToFolder(p.path);
|
|
1250
|
+
count++;
|
|
1251
|
+
}
|
|
1252
|
+
catch {
|
|
1253
|
+
// Skip failed syncs
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
content: [{
|
|
1259
|
+
type: "text",
|
|
1260
|
+
text: `š Synced ${count} project(s)`
|
|
1261
|
+
}]
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
case "wyrm_stats": {
|
|
1265
|
+
const stats = db.getStats();
|
|
1266
|
+
const cacheStats = cache.stats();
|
|
1267
|
+
const usage = getUsageStats();
|
|
1268
|
+
const response = cachedResponse(`š **Wyrm Statistics**\n\n` +
|
|
1269
|
+
`- **Projects:** ${stats.projects}\n` +
|
|
1270
|
+
`- **Sessions:** ${stats.sessions}\n` +
|
|
1271
|
+
`- **Quests:** ${stats.quests}\n` +
|
|
1272
|
+
`- **Data Points:** ${stats.dataPoints}\n` +
|
|
1273
|
+
`- **Active Tokens:** ~${stats.totalTokens.toLocaleString()}\n` +
|
|
1274
|
+
`- **Database Size:** ${stats.dbSize}\n\n` +
|
|
1275
|
+
`**Cache:** ${cacheStats.size} entries | Hit rate: ${usage.cacheHitRate}\n` +
|
|
1276
|
+
`**Usage:** ${usage.totalCalls} calls | ~${usage.tokensSaved.toLocaleString()} tokens saved by cache`);
|
|
1277
|
+
if (cacheKey)
|
|
1278
|
+
cache.set(cacheKey, response, 15000);
|
|
1279
|
+
return response;
|
|
1280
|
+
}
|
|
1281
|
+
case "wyrm_maintenance": {
|
|
1282
|
+
const { vacuum, archiveDays } = args;
|
|
1283
|
+
let text = 'š **Maintenance Complete**\n\n';
|
|
1284
|
+
if (archiveDays) {
|
|
1285
|
+
const projects = db.getAllProjects(1000);
|
|
1286
|
+
let archived = 0;
|
|
1287
|
+
for (const p of projects) {
|
|
1288
|
+
archived += db.archiveOldSessions(p.id, archiveDays);
|
|
1289
|
+
}
|
|
1290
|
+
text += `- Archived ${archived} old sessions\n`;
|
|
1291
|
+
}
|
|
1292
|
+
if (vacuum) {
|
|
1293
|
+
db.vacuum();
|
|
1294
|
+
text += `- Vacuumed database\n`;
|
|
1295
|
+
}
|
|
1296
|
+
db.checkpoint();
|
|
1297
|
+
text += `- Checkpointed WAL\n`;
|
|
1298
|
+
const stats = db.getStats();
|
|
1299
|
+
text += `\n**New Database Size:** ${stats.dbSize}`;
|
|
1300
|
+
return { content: [{ type: "text", text }] };
|
|
1301
|
+
}
|
|
1302
|
+
case "wyrm_setup": {
|
|
1303
|
+
const { action, serverPath, dbPath } = args;
|
|
1304
|
+
const setupAction = action || 'configure';
|
|
1305
|
+
if (setupAction === 'check') {
|
|
1306
|
+
return {
|
|
1307
|
+
content: [{
|
|
1308
|
+
type: "text",
|
|
1309
|
+
text: getStatusSummary()
|
|
1310
|
+
}]
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
if (setupAction === 'remove') {
|
|
1314
|
+
const results = removeFromAll();
|
|
1315
|
+
const removed = results.filter(r => r.action === 'configured');
|
|
1316
|
+
const text = `š **Wyrm Removed**\n\n` +
|
|
1317
|
+
`Removed from ${removed.length} AI client(s):\n` +
|
|
1318
|
+
results.map(r => `- ${r.client.icon} ${r.client.name}: ${r.message}`).join('\n') +
|
|
1319
|
+
`\n\nRun wyrm_setup again to reconnect.`;
|
|
1320
|
+
return { content: [{ type: "text", text }] };
|
|
1321
|
+
}
|
|
1322
|
+
// Default: configure
|
|
1323
|
+
const results = autoConfigureAll({
|
|
1324
|
+
serverPath: serverPath || undefined,
|
|
1325
|
+
dbPath: dbPath || undefined,
|
|
1326
|
+
});
|
|
1327
|
+
const configured = results.filter(r => r.action === 'configured' || r.action === 'updated');
|
|
1328
|
+
const failed = results.filter(r => r.action === 'failed');
|
|
1329
|
+
let text = `š **Wyrm Auto-Configure Complete**\n\n`;
|
|
1330
|
+
text += `Connected to ${configured.length} AI client(s):\n`;
|
|
1331
|
+
for (const r of results) {
|
|
1332
|
+
const icon = r.action === 'configured' ? 'ā
' :
|
|
1333
|
+
r.action === 'updated' ? 'š' :
|
|
1334
|
+
r.action === 'skipped' ? 'ā' : 'ā';
|
|
1335
|
+
text += `- ${icon} ${r.client.icon} ${r.client.name}: ${r.message}\n`;
|
|
1336
|
+
}
|
|
1337
|
+
if (failed.length > 0) {
|
|
1338
|
+
text += `\nā ļø ${failed.length} client(s) failed. Check errors above.`;
|
|
1339
|
+
}
|
|
1340
|
+
text += `\n\nServer: ${findWyrmServerPath()}\nDB: ${getDefaultDbPath()}`;
|
|
1341
|
+
text += `\n\n_Switch AIs anytime ā run wyrm_setup again to reconnect._`;
|
|
1342
|
+
return { content: [{ type: "text", text }] };
|
|
1343
|
+
}
|
|
1344
|
+
// ==================== USAGE TRACKING ====================
|
|
1345
|
+
case "wyrm_usage": {
|
|
1346
|
+
const { last, reset } = args;
|
|
1347
|
+
if (reset) {
|
|
1348
|
+
usageLog.length = 0;
|
|
1349
|
+
responseFingerprints.clear();
|
|
1350
|
+
cache.invalidate();
|
|
1351
|
+
return { content: [{ type: "text", text: "š Usage counters reset, caches cleared." }] };
|
|
1352
|
+
}
|
|
1353
|
+
const usage = getUsageStats(last);
|
|
1354
|
+
const cacheStats = cache.stats();
|
|
1355
|
+
let text = `š **Wyrm Usage Report**\n\n`;
|
|
1356
|
+
text += `## Overview${last ? ` (last ${last} calls)` : ''}\n`;
|
|
1357
|
+
text += `- **Total Calls:** ${usage.totalCalls}\n`;
|
|
1358
|
+
text += `- **Cache Hits:** ${usage.cachedCalls} (${usage.cacheHitRate})\n`;
|
|
1359
|
+
text += `- **Tokens In:** ~${usage.totalTokensIn.toLocaleString()}\n`;
|
|
1360
|
+
text += `- **Tokens Out:** ~${usage.totalTokensOut.toLocaleString()}\n`;
|
|
1361
|
+
text += `- **Tokens Saved (cache):** ~${usage.tokensSaved.toLocaleString()}\n`;
|
|
1362
|
+
text += `- **Avg Response:** ${usage.avgResponseMs}ms\n`;
|
|
1363
|
+
text += `- **Active Cache Entries:** ${cacheStats.size}\n\n`;
|
|
1364
|
+
if (usage.topTools.length > 0) {
|
|
1365
|
+
text += `## Top Tools by Token Usage\n`;
|
|
1366
|
+
for (const t of usage.topTools) {
|
|
1367
|
+
text += `- **${t.tool}:** ${t.calls} calls, ~${t.tokens.toLocaleString()} tokens\n`;
|
|
1368
|
+
}
|
|
1369
|
+
text += '\n';
|
|
1370
|
+
}
|
|
1371
|
+
// Cost estimate (Claude Opus pricing: $15/M input, $75/M output)
|
|
1372
|
+
const costInput = (usage.totalTokensIn / 1_000_000) * 15;
|
|
1373
|
+
const costOutput = (usage.totalTokensOut / 1_000_000) * 75;
|
|
1374
|
+
const costSaved = (usage.tokensSaved / 1_000_000) * 75;
|
|
1375
|
+
text += `## Estimated Cost (Claude Opus rates)\n`;
|
|
1376
|
+
text += `- **Input:** $${costInput.toFixed(4)}\n`;
|
|
1377
|
+
text += `- **Output:** $${costOutput.toFixed(4)}\n`;
|
|
1378
|
+
text += `- **Saved by cache:** $${costSaved.toFixed(4)}\n`;
|
|
1379
|
+
text += `- **Net cost:** $${(costInput + costOutput - costSaved).toFixed(4)}`;
|
|
1380
|
+
return cachedResponse(text, true); // ephemeral ā don't cache the usage report itself
|
|
1381
|
+
}
|
|
1382
|
+
// ==================== AUTO-ORCHESTRATION ====================
|
|
1383
|
+
case "wyrm_orchestrate_task": {
|
|
1384
|
+
const { task } = args;
|
|
1385
|
+
const plan = await orchestrator.processTask(task);
|
|
1386
|
+
let text = `š **Orchestration Plan**\n\n`;
|
|
1387
|
+
text += `## Task Classification\n`;
|
|
1388
|
+
text += `- **Type:** ${plan.taskType}\n`;
|
|
1389
|
+
text += `- **Confidence:** ${plan.confidence}%\n`;
|
|
1390
|
+
text += `- **Recommended Approach:** ${plan.approach}\n\n`;
|
|
1391
|
+
if (plan.appliedPatterns.length > 0) {
|
|
1392
|
+
text += `## Patterns Applied\n`;
|
|
1393
|
+
for (const pattern of plan.appliedPatterns) {
|
|
1394
|
+
text += `- ā ${pattern}\n`;
|
|
1395
|
+
}
|
|
1396
|
+
text += '\n';
|
|
1397
|
+
}
|
|
1398
|
+
text += `## Expected Outcomes\n`;
|
|
1399
|
+
text += `- **Quality Boost:** +${plan.quality - 60}% (estimated)\n`;
|
|
1400
|
+
text += `- **Cost Savings:** ${plan.costSavings}% vs Opus\n`;
|
|
1401
|
+
text += `- **Parallel Execution:** ~${plan.parallelExecutionTime}ms for full pipeline\n\n`;
|
|
1402
|
+
text += `## Token Efficiency\n`;
|
|
1403
|
+
text += `- **Boosting Overhead:** ~${plan.metrics.tokensBoosting} tokens\n`;
|
|
1404
|
+
text += `- **Ensemble Voting:** ~${plan.metrics.tokensEnsemble} tokens\n`;
|
|
1405
|
+
text += `- **Verification:** ~${plan.metrics.tokensVerification} tokens\n`;
|
|
1406
|
+
text += `- **Total Estimated:** ~${plan.metrics.tokensBoosting + plan.metrics.tokensEnsemble + plan.metrics.tokensVerification} tokens\n`;
|
|
1407
|
+
return { content: [{ type: "text", text }] };
|
|
1408
|
+
}
|
|
1409
|
+
case "wyrm_orchestration_config": {
|
|
1410
|
+
const { get = true, autoOrchestrateEnabled, minConfidenceThreshold, maxParallelAgents, defaultHaikuBoosting, trackMetrics, } = args;
|
|
1411
|
+
// Update config if any properties provided
|
|
1412
|
+
if (!get && (autoOrchestrateEnabled !== undefined || minConfidenceThreshold !== undefined ||
|
|
1413
|
+
maxParallelAgents !== undefined || defaultHaikuBoosting !== undefined ||
|
|
1414
|
+
trackMetrics !== undefined)) {
|
|
1415
|
+
const updates = {};
|
|
1416
|
+
if (autoOrchestrateEnabled !== undefined)
|
|
1417
|
+
updates.autoOrchestrateEnabled = autoOrchestrateEnabled;
|
|
1418
|
+
if (minConfidenceThreshold !== undefined)
|
|
1419
|
+
updates.minConfidenceThreshold = minConfidenceThreshold;
|
|
1420
|
+
if (maxParallelAgents !== undefined)
|
|
1421
|
+
updates.maxParallelAgents = maxParallelAgents;
|
|
1422
|
+
if (defaultHaikuBoosting !== undefined)
|
|
1423
|
+
updates.defaultHaikuBoosting = defaultHaikuBoosting;
|
|
1424
|
+
if (trackMetrics !== undefined)
|
|
1425
|
+
updates.trackMetrics = trackMetrics;
|
|
1426
|
+
orchestrator.updateConfig(updates);
|
|
1427
|
+
return { content: [{ type: "text", text: `š Configuration updated:\n${JSON.stringify(updates, null, 2)}` }] };
|
|
1428
|
+
}
|
|
1429
|
+
// Get current config
|
|
1430
|
+
const config = getDefaultConfig();
|
|
1431
|
+
let text = `š **Auto-Orchestration Configuration**\n\n`;
|
|
1432
|
+
text += `- **Auto-Orchestrate Enabled:** ${config.autoOrchestrateEnabled ? 'ā Yes' : 'ā No'}\n`;
|
|
1433
|
+
text += `- **Min Confidence Threshold:** ${config.minConfidenceThreshold}%\n`;
|
|
1434
|
+
text += `- **Max Parallel Agents:** ${config.maxParallelAgents}\n`;
|
|
1435
|
+
text += `- **Default Haiku Boosting:** ${config.defaultHaikuBoosting ? 'ā Yes' : 'ā No'}\n`;
|
|
1436
|
+
text += `- **Track Metrics:** ${config.trackMetrics ? 'ā Yes' : 'ā No'}\n`;
|
|
1437
|
+
text += `- **Thinking Budget:** ${config.thinkingBudget} tokens max\n`;
|
|
1438
|
+
return cachedResponse(text);
|
|
1439
|
+
}
|
|
1440
|
+
case "wyrm_orchestration_stats": {
|
|
1441
|
+
const stats = orchestrator.getStats();
|
|
1442
|
+
let text = `š **Auto-Orchestration Statistics**\n\n`;
|
|
1443
|
+
text += `## Overall\n`;
|
|
1444
|
+
text += `- **Tasks Processed:** ${stats.tasksProcessed}\n`;
|
|
1445
|
+
text += `- **Average Quality Boost:** +${stats.estimatedQualityBoost}%\n`;
|
|
1446
|
+
text += `- **Average Cost Savings:** ${stats.estimatedCostSavings}% vs Opus\n`;
|
|
1447
|
+
text += `- **Average Complexity:** ${stats.averageComplexity}\n\n`;
|
|
1448
|
+
text += `## Task Distribution\n`;
|
|
1449
|
+
const total = stats.tasksProcessed || 1;
|
|
1450
|
+
for (const [type, count] of Object.entries(stats.distribution)) {
|
|
1451
|
+
const pct = Math.round((count / total) * 100);
|
|
1452
|
+
text += `- **${type}:** ${count} (${pct}%)\n`;
|
|
1453
|
+
}
|
|
1454
|
+
return cachedResponse(text);
|
|
1455
|
+
}
|
|
1456
|
+
// ==================== LICENSE & PRO FEATURE TOOLS ====================
|
|
1457
|
+
case "wyrm_license": {
|
|
1458
|
+
const license = getLicenseInfo();
|
|
1459
|
+
const tier = getTier();
|
|
1460
|
+
let text = `š **Wyrm License Status**\n\n`;
|
|
1461
|
+
text += `- **Tier:** ${tier.charAt(0).toUpperCase() + tier.slice(1)}\n`;
|
|
1462
|
+
if (license.valid && license.key) {
|
|
1463
|
+
text += `- **Key:** ${license.key}\n`;
|
|
1464
|
+
text += `- **Issued To:** ${license.issuedTo || 'Unknown'}\n`;
|
|
1465
|
+
text += `- **Expires:** ${license.expiresAt ? new Date(license.expiresAt).toLocaleDateString() : 'Never'}\n`;
|
|
1466
|
+
text += `- **Status:** ā Valid\n\n`;
|
|
1467
|
+
text += `### Features\n`;
|
|
1468
|
+
for (const f of license.features) {
|
|
1469
|
+
text += `- ā ${f}\n`;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
text += `- **Status:** Free tier (no license key)\n\n`;
|
|
1474
|
+
text += `### Free Features\n`;
|
|
1475
|
+
text += `- ā Local SQLite storage\n`;
|
|
1476
|
+
text += `- ā 32 MCP tools\n`;
|
|
1477
|
+
text += `- ā FTS5 search\n`;
|
|
1478
|
+
text += `- ā File-based sync\n\n`;
|
|
1479
|
+
text += `### Upgrade to Pro\n`;
|
|
1480
|
+
text += `- Analytics dashboard & cost tracking\n`;
|
|
1481
|
+
text += `- Cloud backup to R2\n`;
|
|
1482
|
+
text += `- Encryption at rest\n`;
|
|
1483
|
+
text += `- Priority support\n`;
|
|
1484
|
+
text += `\nVisit https://ghosts.lk/wyrm to get a license key.`;
|
|
1485
|
+
}
|
|
1486
|
+
return cachedResponse(text);
|
|
1487
|
+
}
|
|
1488
|
+
case "wyrm_activate": {
|
|
1489
|
+
const licenseStr = args?.license;
|
|
1490
|
+
if (!licenseStr)
|
|
1491
|
+
throw new Error("License JSON string required");
|
|
1492
|
+
try {
|
|
1493
|
+
const result = activateLicense(licenseStr);
|
|
1494
|
+
if (result.valid) {
|
|
1495
|
+
return {
|
|
1496
|
+
content: [{ type: "text", text: `š ā License activated!\n\n- **Tier:** ${result.tier}\n- **Features:** ${result.features.join(', ')}\n\nRestart Wyrm to apply all features.` }],
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
return {
|
|
1501
|
+
content: [{ type: "text", text: `š ā License activation failed: ${result.error || 'unknown error'}` }],
|
|
1502
|
+
isError: true,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
catch (e) {
|
|
1507
|
+
return {
|
|
1508
|
+
content: [{ type: "text", text: `š ā Invalid license format: ${e.message}` }],
|
|
1509
|
+
isError: true,
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
case "wyrm_analytics_dashboard": {
|
|
1514
|
+
if (!hasFeature('analytics')) {
|
|
1515
|
+
return {
|
|
1516
|
+
content: [{ type: "text", text: `š Analytics requires a Pro license or higher.\nVisit https://ghosts.lk/wyrm to upgrade.` }],
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
const days = args?.days || 30;
|
|
1520
|
+
const dashboard = analytics.dashboard(days);
|
|
1521
|
+
let text = `š **Usage Analytics** (last ${days} days)\n\n`;
|
|
1522
|
+
text += `### Overview\n`;
|
|
1523
|
+
text += `- **Total Tool Calls:** ${dashboard.summary.total_calls.toLocaleString()}\n`;
|
|
1524
|
+
text += `- **Unique Tools Used:** ${dashboard.summary.unique_tools}\n`;
|
|
1525
|
+
text += `- **Total Tokens (In):** ${dashboard.summary.total_tokens_in.toLocaleString()}\n`;
|
|
1526
|
+
text += `- **Total Tokens (Out):** ${dashboard.summary.total_tokens_out.toLocaleString()}\n`;
|
|
1527
|
+
text += `- **Cache Hit Rate:** ${(dashboard.summary.cache_hit_rate * 100).toFixed(1)}%\n`;
|
|
1528
|
+
text += `- **Estimated Cost:** $${dashboard.summary.estimated_cost_usd.toFixed(4)}\n\n`;
|
|
1529
|
+
if (dashboard.top_tools.length > 0) {
|
|
1530
|
+
text += `### Top Tools\n`;
|
|
1531
|
+
for (const t of dashboard.top_tools.slice(0, 10)) {
|
|
1532
|
+
text += `- **${t.tool}** ā ${t.calls} calls, ${t.tokens.toLocaleString()} tokens\n`;
|
|
1533
|
+
}
|
|
1534
|
+
text += `\n`;
|
|
1535
|
+
}
|
|
1536
|
+
if (dashboard.daily.length > 0) {
|
|
1537
|
+
text += `### Recent Activity\n`;
|
|
1538
|
+
for (const d of dashboard.daily.slice(-7)) {
|
|
1539
|
+
text += `- ${d.date}: ${d.calls} calls, ${d.tokens.toLocaleString()} tokens\n`;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return { content: [{ type: "text", text }] };
|
|
1543
|
+
}
|
|
1544
|
+
case "wyrm_cost_report": {
|
|
1545
|
+
if (!hasFeature('analytics')) {
|
|
1546
|
+
return {
|
|
1547
|
+
content: [{ type: "text", text: `š Cost reports require a Pro license or higher.\nVisit https://ghosts.lk/wyrm to upgrade.` }],
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
const period = args?.period;
|
|
1551
|
+
const report = analytics.costReport(period);
|
|
1552
|
+
let text = `š **Cost Report** ā ${report.period}\n\n`;
|
|
1553
|
+
text += `- **Total Cost:** $${report.total_cost_usd.toFixed(4)}\n`;
|
|
1554
|
+
text += `- **Projected Monthly:** $${report.projected_monthly_usd.toFixed(2)}\n\n`;
|
|
1555
|
+
if (report.tools.length > 0) {
|
|
1556
|
+
text += `### By Tool\n`;
|
|
1557
|
+
for (const t of report.tools.slice(0, 10)) {
|
|
1558
|
+
text += `- **${t.tool}** ā ${t.calls} calls, $${t.cost_usd.toFixed(4)}\n`;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return { content: [{ type: "text", text }] };
|
|
1562
|
+
}
|
|
1563
|
+
case "wyrm_cloud_backup": {
|
|
1564
|
+
if (!hasFeature('cloud_backup')) {
|
|
1565
|
+
return {
|
|
1566
|
+
content: [{ type: "text", text: `š Cloud backup requires a Pro license or higher.\nVisit https://ghosts.lk/wyrm to upgrade.` }],
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
const action = args?.action;
|
|
1570
|
+
switch (action) {
|
|
1571
|
+
case "backup": {
|
|
1572
|
+
const result = await cloudBackup.backup(db.getDatabasePath());
|
|
1573
|
+
const meta = result.metadata;
|
|
1574
|
+
return {
|
|
1575
|
+
content: [{ type: "text", text: `š ā Backup complete!\n\n- **Key:** ${result.key}\n- **Size:** ${(meta.db_size / 1024).toFixed(1)} KB ā ${(meta.compressed_size / 1024).toFixed(1)} KB compressed\n- **Encrypted:** ${meta.encrypted ? 'Yes' : 'No'}\n- **Checksum:** ${meta.checksum.slice(0, 16)}...` }],
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
case "restore": {
|
|
1579
|
+
const backupKey = args?.backupKey;
|
|
1580
|
+
if (!backupKey)
|
|
1581
|
+
throw new Error("backupKey required for restore");
|
|
1582
|
+
const result = await cloudBackup.restore(backupKey, db.getDatabasePath());
|
|
1583
|
+
return {
|
|
1584
|
+
content: [{ type: "text", text: `š ā Restore complete!\n\n- **Restored:** ${result.restored ? 'Yes' : 'No'}\n- **Size:** ${(result.size / 1024).toFixed(1)} KB\n- **Previous DB backed up to:** ${db.getDatabasePath()}.bak\n\nā ļø Restart Wyrm to use the restored database.` }],
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
case "list": {
|
|
1588
|
+
const backups = await cloudBackup.listBackups();
|
|
1589
|
+
if (backups.length === 0) {
|
|
1590
|
+
return { content: [{ type: "text", text: `š No backups found.` }] };
|
|
1591
|
+
}
|
|
1592
|
+
let text = `š **Cloud Backups** (${backups.length} found)\n\n`;
|
|
1593
|
+
for (const b of backups) {
|
|
1594
|
+
text += `- **${b.timestamp}** ā ${(b.db_size / 1024).toFixed(1)} KB, encrypted: ${b.encrypted}, machine: ${b.machine_id.slice(0, 8)}...\n`;
|
|
1595
|
+
}
|
|
1596
|
+
return { content: [{ type: "text", text }] };
|
|
1597
|
+
}
|
|
1598
|
+
case "prune": {
|
|
1599
|
+
const keepCount = args?.keepCount || 10;
|
|
1600
|
+
const result = await cloudBackup.pruneBackups(keepCount);
|
|
1601
|
+
return {
|
|
1602
|
+
content: [{ type: "text", text: `š ā Pruned ${result.deleted} old backup(s). Kept ${keepCount} most recent.` }],
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
default:
|
|
1606
|
+
return { content: [{ type: "text", text: `Unknown backup action: ${action}. Use: backup, restore, list, prune` }], isError: true };
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
case "wyrm_encrypt_setup": {
|
|
1610
|
+
if (!hasFeature('encryption')) {
|
|
1611
|
+
return {
|
|
1612
|
+
content: [{ type: "text", text: `š Encryption setup requires a Pro license or higher.\nVisit https://ghosts.lk/wyrm to upgrade.` }],
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
const encAction = args?.action;
|
|
1616
|
+
const cryptoInstance = getCrypto();
|
|
1617
|
+
switch (encAction) {
|
|
1618
|
+
case "status": {
|
|
1619
|
+
const enabled = cryptoInstance.isEnabled();
|
|
1620
|
+
let text = `š **Encryption Status**\n\n`;
|
|
1621
|
+
text += `- **Enabled:** ${enabled ? 'ā Yes' : 'ā No'}\n`;
|
|
1622
|
+
text += `- **Algorithm:** AES-256-GCM\n`;
|
|
1623
|
+
if (enabled) {
|
|
1624
|
+
text += `- **Status:** Active ā new data is encrypted at rest\n`;
|
|
1625
|
+
}
|
|
1626
|
+
else {
|
|
1627
|
+
text += `\nTo enable, run: \`wyrm_encrypt_setup\` with action "enable" and a strong password.\n`;
|
|
1628
|
+
}
|
|
1629
|
+
return { content: [{ type: "text", text }] };
|
|
1630
|
+
}
|
|
1631
|
+
case "enable": {
|
|
1632
|
+
const password = args?.password;
|
|
1633
|
+
if (!password || password.length < 8) {
|
|
1634
|
+
return { content: [{ type: "text", text: `š Password must be at least 8 characters.` }], isError: true };
|
|
1635
|
+
}
|
|
1636
|
+
initializeCrypto(password);
|
|
1637
|
+
return {
|
|
1638
|
+
content: [{ type: "text", text: `š ā Encryption enabled!\n\n- **Algorithm:** AES-256-GCM\n- **Key derived from:** your password (PBKDF2)\n\nā ļø **Store your password safely.** Lost passwords cannot be recovered.\nNew data lake entries will be encrypted at rest.` }],
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
case "test": {
|
|
1642
|
+
if (!cryptoInstance.isEnabled()) {
|
|
1643
|
+
return { content: [{ type: "text", text: `š Encryption not enabled. Run with action "enable" first.` }] };
|
|
1644
|
+
}
|
|
1645
|
+
const testData = "Wyrm encryption test " + Date.now();
|
|
1646
|
+
const encrypted = cryptoInstance.encrypt(testData);
|
|
1647
|
+
const decrypted = cryptoInstance.decrypt(encrypted);
|
|
1648
|
+
const ok = decrypted === testData;
|
|
1649
|
+
return {
|
|
1650
|
+
content: [{ type: "text", text: `š Encryption test: ${ok ? 'ā PASSED' : 'ā FAILED'}\n- Encrypted ${testData.length} chars ā decrypted back to ${decrypted.length} chars` }],
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
default:
|
|
1654
|
+
return { content: [{ type: "text", text: `Unknown encrypt action: ${encAction}. Use: status, enable, test` }], isError: true };
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
default:
|
|
1658
|
+
return {
|
|
1659
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1660
|
+
isError: true,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
})();
|
|
1664
|
+
// Track usage for all non-cached responses
|
|
1665
|
+
const ms = Math.round(performance.now() - startTime);
|
|
1666
|
+
const tokensOut = estimateTokens(JSON.stringify(result));
|
|
1667
|
+
trackUsage({ tool: name, tokens_in: tokensIn, tokens_out: tokensOut, cached: false, ms, timestamp: new Date().toISOString() });
|
|
1668
|
+
return result;
|
|
1669
|
+
}
|
|
1670
|
+
catch (error) {
|
|
1671
|
+
const ms = Math.round(performance.now() - startTime);
|
|
1672
|
+
trackUsage({ tool: name, tokens_in: tokensIn, tokens_out: 0, cached: false, ms, timestamp: new Date().toISOString() });
|
|
1673
|
+
return {
|
|
1674
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
1675
|
+
isError: true,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
// Start server
|
|
1680
|
+
async function main() {
|
|
1681
|
+
const transport = new StdioServerTransport();
|
|
1682
|
+
await server.connect(transport);
|
|
1683
|
+
// Graceful shutdown ā flush analytics buffer
|
|
1684
|
+
const shutdown = () => {
|
|
1685
|
+
try {
|
|
1686
|
+
analytics.shutdown();
|
|
1687
|
+
}
|
|
1688
|
+
catch { }
|
|
1689
|
+
process.exit(0);
|
|
1690
|
+
};
|
|
1691
|
+
process.on('SIGINT', shutdown);
|
|
1692
|
+
process.on('SIGTERM', shutdown);
|
|
1693
|
+
}
|
|
1694
|
+
main().catch(console.error);
|
|
1695
|
+
//# sourceMappingURL=index.js.map
|