@finchagentic/mcp 4.0.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +345 -0
  3. package/dist/_http-cache.js +96 -0
  4. package/dist/agent-loop.js +231 -0
  5. package/dist/annotations.js +113 -0
  6. package/dist/cli.js +1195 -0
  7. package/dist/clink-input.js +15 -0
  8. package/dist/config.js +132 -0
  9. package/dist/convex.js +151 -0
  10. package/dist/dex-pair.js +54 -0
  11. package/dist/enrichment-router.js +315 -0
  12. package/dist/index.js +256 -0
  13. package/dist/llm.js +323 -0
  14. package/dist/local-memory.js +102 -0
  15. package/dist/local-vault.js +454 -0
  16. package/dist/output-schemas.js +551 -0
  17. package/dist/prompts.js +111 -0
  18. package/dist/public-url.js +107 -0
  19. package/dist/resources.js +116 -0
  20. package/dist/server.js +300 -0
  21. package/dist/signal-gate.js +57 -0
  22. package/dist/token-decimals.js +26 -0
  23. package/dist/token-gate.js +88 -0
  24. package/dist/tool-filter.js +44 -0
  25. package/dist/tools/_solidity-scan.js +313 -0
  26. package/dist/tools/agents.js +729 -0
  27. package/dist/tools/automation.js +314 -0
  28. package/dist/tools/base-mcp.js +478 -0
  29. package/dist/tools/base.js +269 -0
  30. package/dist/tools/chronicle.js +268 -0
  31. package/dist/tools/coder.js +94 -0
  32. package/dist/tools/deep-research.js +1416 -0
  33. package/dist/tools/defi.js +291 -0
  34. package/dist/tools/equity.js +364 -0
  35. package/dist/tools/events.js +182 -0
  36. package/dist/tools/framework.js +150 -0
  37. package/dist/tools/github.js +514 -0
  38. package/dist/tools/insider.js +264 -0
  39. package/dist/tools/insight.js +634 -0
  40. package/dist/tools/market.js +555 -0
  41. package/dist/tools/memory.js +1046 -0
  42. package/dist/tools/miroshark.js +343 -0
  43. package/dist/tools/monitor.js +319 -0
  44. package/dist/tools/os.js +226 -0
  45. package/dist/tools/packets.js +296 -0
  46. package/dist/tools/research-chain.js +226 -0
  47. package/dist/tools/research-compare.js +280 -0
  48. package/dist/tools/research.js +188 -0
  49. package/dist/tools/rh-bridge.js +148 -0
  50. package/dist/tools/rh-mcp.js +1411 -0
  51. package/dist/tools/rh-orders.js +471 -0
  52. package/dist/tools/scanner.js +534 -0
  53. package/dist/tools/vault.js +764 -0
  54. package/dist/tools/wallet.js +200 -0
  55. package/dist/types.js +2 -0
  56. package/dist/wallet.js +184 -0
  57. package/package.json +87 -0
@@ -0,0 +1,764 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VAULT_TOOLS = void 0;
4
+ exports.handleVaultTool = handleVaultTool;
5
+ const zod_1 = require("zod");
6
+ const convex_js_1 = require("../convex.js");
7
+ const memory_js_1 = require("./memory.js");
8
+ const local_vault_js_1 = require("../local-vault.js");
9
+ const local_memory_js_1 = require("../local-memory.js");
10
+ const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "memory", "credential"];
11
+ exports.VAULT_TOOLS = [
12
+ {
13
+ name: "vault_save",
14
+ description: "Save or update a versioned artifact in Noel-Vault. Same key = update (git-style: prior version snapshotted, patched to v+1). " +
15
+ "Types: research | execution | workflow | prompt | file | memory. " +
16
+ "Entries up to 10MB - content over 600KB auto-offloads to blob storage. " +
17
+ "For quick unstructured notes, use memory_add instead.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ type: { type: "string", enum: [...VAULT_TYPES], description: "Entry type" },
22
+ title: { type: "string", description: "Human-readable title (auto-generated from content if omitted)" },
23
+ content: { type: "string", description: "Main content - markdown, JSON, code, or plain text" },
24
+ key: { type: "string", description: "Optional slug key e.g. 'research/btc-dominance-analysis'. Auto-generated if omitted." },
25
+ contentType: { type: "string", enum: ["markdown", "json", "text", "code"], description: "Content format hint" },
26
+ agentId: { type: "string", description: "Agent ID writing this entry" },
27
+ tags: { type: "array", items: { type: "string" }, description: "Tags for filtering and search" },
28
+ commitMsg: { type: "string", description: "Commit message for this version, e.g. 'initial research', 'refined with on-chain data'" },
29
+ metadata: { type: "string", description: "Optional JSON string for extra structured fields" },
30
+ },
31
+ required: ["type", "content"],
32
+ },
33
+ },
34
+ {
35
+ name: "vault_read",
36
+ description: "Read a Noel-Vault entry by its key. Returns full content, version, tags, and any linked entries.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ key: { type: "string", description: "Entry key e.g. 'research/btc-dominance-analysis'" },
41
+ },
42
+ required: ["key"],
43
+ },
44
+ },
45
+ {
46
+ name: "vault_list",
47
+ description: "List Noel-Vault entries. Filter by type, agent, or pinned status. Returns previews, not full content.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ type: { type: "string", enum: [...VAULT_TYPES], description: "Filter by type" },
52
+ agentId: { type: "string", description: "Filter by agent that wrote the entries" },
53
+ pinned: { type: "boolean", description: "Show only pinned entries" },
54
+ limit: { type: "number", description: "Max entries to return (default 50)" },
55
+ },
56
+ required: [],
57
+ },
58
+ },
59
+ {
60
+ name: "vault_search",
61
+ description: "Search Noel-Vault using semantic AI search (powered by Supermemory) when available, " +
62
+ "with automatic fallback to full-text search. Semantic search understands meaning - " +
63
+ "'low risk DeFi yield' matches 'conservative staking strategies' without exact keywords. " +
64
+ "Optionally filter by type. Returns ranked results with previews.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {
68
+ query: { type: "string", description: "Search query - natural language works best with semantic mode" },
69
+ type: { type: "string", enum: [...VAULT_TYPES], description: "Narrow search to a specific type" },
70
+ limit: { type: "number", description: "Max results (default 20)" },
71
+ },
72
+ required: ["query"],
73
+ },
74
+ },
75
+ {
76
+ name: "vault_history",
77
+ description: "Get the full version history of a Noel-Vault entry - like git log. " +
78
+ "Shows each version with its commit message, author agent, size, and timestamp.",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ key: { type: "string", description: "Entry key" },
83
+ },
84
+ required: ["key"],
85
+ },
86
+ },
87
+ {
88
+ name: "vault_diff",
89
+ description: "Compare two versions of a Noel-Vault entry - like git diff. " +
90
+ "Shows lines added (+) and removed (-) between fromVersion and toVersion.",
91
+ inputSchema: {
92
+ type: "object",
93
+ properties: {
94
+ key: { type: "string", description: "Entry key" },
95
+ fromVersion: { type: "number", description: "Older version number" },
96
+ toVersion: { type: "number", description: "Newer version number" },
97
+ },
98
+ required: ["key", "fromVersion", "toVersion"],
99
+ },
100
+ },
101
+ {
102
+ name: "vault_export",
103
+ description: "Export your entire Noel-Vault or a specific type as a structured bundle. " +
104
+ "Useful for archiving, syncing to GitHub, or passing context to another agent.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: {
108
+ type: { type: "string", enum: [...VAULT_TYPES], description: "Export only this type (omit for full export)" },
109
+ },
110
+ required: [],
111
+ },
112
+ },
113
+ {
114
+ name: "vault_store_credential",
115
+ description: "Securely store an API key, token, or secret in your vault. " +
116
+ "Credentials are stored under type=credential and are excluded from normal search and export. " +
117
+ "Use this to keep API keys organized and accessible across agent sessions.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ name: { type: "string", description: "Credential name, e.g. 'ALCHEMY_API_KEY', 'TELEGRAM_BOT_TOKEN'" },
122
+ value: { type: "string", description: "The secret value to store" },
123
+ description: { type: "string", description: "Optional note about this credential - what it's for, expiry, etc." },
124
+ },
125
+ required: ["name", "value"],
126
+ },
127
+ },
128
+ {
129
+ name: "vault_get_credential",
130
+ description: "Retrieve a stored credential from the vault by name. " +
131
+ "Only returns credentials owned by the authenticated user.",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ name: { type: "string", description: "Credential name as used in vault_store_credential" },
136
+ },
137
+ required: ["name"],
138
+ },
139
+ },
140
+ {
141
+ name: "vault_pin",
142
+ description: "Pin or unpin a Noel-Vault entry. Pinned entries always appear first in vault_list and are " +
143
+ "prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ key: { type: "string", description: "Entry key to pin or unpin" },
148
+ pinned: { type: "boolean", description: "true to pin, false to unpin (default: true)" },
149
+ },
150
+ required: ["key"],
151
+ },
152
+ },
153
+ {
154
+ name: "vault_unpublish",
155
+ description: "Make a previously shared Noel-Vault entry private again, removing it from the public community " +
156
+ "listing. Use this to reverse vault publishing or packet_share. Note that anyone who already " +
157
+ "copied the content while it was public still has it โ€” unpublishing stops future discovery, " +
158
+ "it does not retract what was taken.",
159
+ inputSchema: {
160
+ type: "object",
161
+ properties: {
162
+ key: { type: "string", description: "Entry key to make private again, e.g. 'packets/daily-research'" },
163
+ },
164
+ required: ["key"],
165
+ },
166
+ },
167
+ {
168
+ name: "vault_delete",
169
+ description: "PERMANENT. Delete a Noel-Vault entry and ALL of its version history โ€” this cannot be undone. " +
170
+ "Requires confirm: true. Use vault_list to browse first, and show the user the exact entry " +
171
+ "(key + title) you are about to destroy before confirming.",
172
+ inputSchema: {
173
+ type: "object",
174
+ properties: {
175
+ key: { type: "string", description: "Entry key to delete permanently" },
176
+ confirm: { type: "boolean", description: "Must be true to delete. Guards against irreversible loss." },
177
+ },
178
+ required: ["key", "confirm"],
179
+ },
180
+ },
181
+ {
182
+ name: "vault_tag",
183
+ description: "Add or replace tags on an existing Noel-Vault entry without modifying its content. " +
184
+ "Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
185
+ inputSchema: {
186
+ type: "object",
187
+ properties: {
188
+ key: { type: "string", description: "Entry key to update tags on" },
189
+ tags: { type: "array", items: { type: "string" }, description: "Tags to add (or replace if replace=true)" },
190
+ replace: { type: "boolean", description: "If true, replaces all existing tags. If false (default), merges with existing." },
191
+ },
192
+ required: ["key", "tags"],
193
+ },
194
+ },
195
+ {
196
+ name: "vault_link",
197
+ description: "Create a semantic relationship between two Noel-Vault entries - building a knowledge graph. " +
198
+ "Relations: references | derived_from | supersedes | related | continues. " +
199
+ "Example: link a synthesis entry as 'derived_from' several research entries, or mark a newer analysis as 'supersedes' an older one. " +
200
+ "Duplicate links are updated in-place.",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ fromKey: { type: "string", description: "Source entry key" },
205
+ toKey: { type: "string", description: "Target entry key" },
206
+ relation: {
207
+ type: "string",
208
+ enum: ["references", "derived_from", "supersedes", "related", "continues"],
209
+ description: "How fromKey relates to toKey",
210
+ },
211
+ },
212
+ required: ["fromKey", "toKey", "relation"],
213
+ },
214
+ },
215
+ {
216
+ name: "vault_related",
217
+ description: "Traverse the Noel-Vault knowledge graph - get all entries linked to a given entry. " +
218
+ "Returns both outbound links (entries this entry references) and inbound links (entries that reference this one). " +
219
+ "Filter by relation type to find only derived entries, superseded versions, continuations, etc.",
220
+ inputSchema: {
221
+ type: "object",
222
+ properties: {
223
+ key: { type: "string", description: "Entry key to find related entries for" },
224
+ relation: {
225
+ type: "string",
226
+ enum: ["references", "derived_from", "supersedes", "related", "continues"],
227
+ description: "Filter to only this relation type (omit for all relations)",
228
+ },
229
+ },
230
+ required: ["key"],
231
+ },
232
+ },
233
+ ];
234
+ // โ”€โ”€โ”€ Zod schemas โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
235
+ const SaveSchema = zod_1.z.object({
236
+ type: zod_1.z.enum(VAULT_TYPES),
237
+ title: zod_1.z.string().optional(),
238
+ content: zod_1.z.string().min(1),
239
+ key: zod_1.z.string().optional(),
240
+ contentType: zod_1.z.enum(["markdown", "json", "text", "code"]).optional(),
241
+ agentId: zod_1.z.string().optional(),
242
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
243
+ commitMsg: zod_1.z.string().optional(),
244
+ metadata: zod_1.z.string().optional(),
245
+ });
246
+ const ReadSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
247
+ const ListSchema = zod_1.z.object({
248
+ type: zod_1.z.enum(VAULT_TYPES).optional(),
249
+ agentId: zod_1.z.string().optional(),
250
+ pinned: zod_1.z.boolean().optional(),
251
+ limit: zod_1.z.number().optional(),
252
+ });
253
+ const SearchSchema = zod_1.z.object({
254
+ query: zod_1.z.string().min(1),
255
+ type: zod_1.z.enum(VAULT_TYPES).optional(),
256
+ limit: zod_1.z.number().optional(),
257
+ });
258
+ const HistorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
259
+ const DiffSchema = zod_1.z.object({ key: zod_1.z.string().min(1), fromVersion: zod_1.z.number(), toVersion: zod_1.z.number() });
260
+ const ExportSchema = zod_1.z.object({ type: zod_1.z.enum(VAULT_TYPES).optional() });
261
+ const StoreCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1), value: zod_1.z.string().min(1), description: zod_1.z.string().optional() });
262
+ const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
263
+ const PinSchema = zod_1.z.object({ key: zod_1.z.string().min(1), pinned: zod_1.z.boolean().optional() });
264
+ const DeleteSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
265
+ const UnpublishSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
266
+ const TagSchema = zod_1.z.object({ key: zod_1.z.string().min(1), tags: zod_1.z.array(zod_1.z.string()).min(1), replace: zod_1.z.boolean().optional() });
267
+ const VAULT_RELATIONS = ["references", "derived_from", "supersedes", "related", "continues"];
268
+ const LinkSchema = zod_1.z.object({ fromKey: zod_1.z.string().min(1), toKey: zod_1.z.string().min(1), relation: zod_1.z.enum(VAULT_RELATIONS) });
269
+ const RelatedSchema = zod_1.z.object({ key: zod_1.z.string().min(1), relation: zod_1.z.enum(VAULT_RELATIONS).optional() });
270
+ // โ”€โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
271
+ function formatBytes(n) {
272
+ if (!n)
273
+ return "-";
274
+ if (n < 1024)
275
+ return `${n}B`;
276
+ if (n < 1024 * 1024)
277
+ return `${(n / 1024).toFixed(1)}KB`;
278
+ return `${(n / 1024 / 1024).toFixed(2)}MB`;
279
+ }
280
+ function formatDate(ts) {
281
+ return new Date(ts).toUTCString();
282
+ }
283
+ // โ”€โ”€โ”€ Handler โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
284
+ async function handleVaultTool(name, args) {
285
+ // When the user has opted into a fully-local, user-owned vault
286
+ // (`vaultBackend: "local"`), every operation runs against ~/.finch/vault/
287
+ // on their own disk - no Convex, no account. Falls through to the hosted
288
+ // path when local isn't enabled. Same two-tier shape as local memory.
289
+ const localVault = (0, local_vault_js_1.getLocalVaultConfig)();
290
+ switch (name) {
291
+ case "vault_save": {
292
+ const parsed = SaveSchema.safeParse(args);
293
+ if (!parsed.success)
294
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
295
+ // Auto-generate title from content if not provided
296
+ const firstLine = parsed.data.content.split("\n")[0].replace(/^#+\s*/, "").slice(0, 80);
297
+ const autoTitle = parsed.data.title ?? (firstLine || `${parsed.data.type} - ${new Date().toISOString().slice(0, 10)}`);
298
+ const savePayload = { ...parsed.data, title: autoTitle };
299
+ const data = localVault
300
+ ? (0, local_vault_js_1.localVaultSave)(localVault, savePayload)
301
+ : await (0, convex_js_1.callConvex)("/vault/save", "POST", savePayload, "vault_save");
302
+ if (data.error)
303
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
304
+ const { key, version, changed } = data;
305
+ // Mirror to semantic memory (fire-and-forget). Skip when running a local
306
+ // vault WITHOUT local memory - otherwise the mirror would phone home to
307
+ // the hosted proxy (needs auth the local-only user doesn't have, and
308
+ // ships their data off-machine, defeating the whole point). vault_search
309
+ // already full-text-searches the local vault directly in that mode.
310
+ const mirrorToMemory = savePayload.type !== "credential" && (!localVault || !!(0, local_memory_js_1.getLocalMemoryConfig)());
311
+ if (mirrorToMemory) {
312
+ (0, memory_js_1.syncToSupermemory)(savePayload.content, {
313
+ vaultKey: key, title: autoTitle, type: savePayload.type,
314
+ tags: savePayload.tags, version, source: "vault_save",
315
+ });
316
+ }
317
+ // Surface inline-syntax results so the user sees [[wikilinks]] and
318
+ // #tags doing their work. Backend reports inlineLinksDetected,
319
+ // linksCreated, linksMissing[], inlineTagsExtracted.
320
+ const linkSummary = [];
321
+ if (typeof data.linksCreated === "number" && data.linksCreated > 0) {
322
+ linkSummary.push(`๐Ÿ”— ${data.linksCreated} wikilink edge(s) created from \`[[...]]\``);
323
+ }
324
+ if (Array.isArray(data.linksMissing) && data.linksMissing.length > 0) {
325
+ linkSummary.push(`โš ๏ธ Missing target(s): ${data.linksMissing.map((k) => `\`${k}\``).join(", ")} - save them later to backlink`);
326
+ }
327
+ if (typeof data.inlineTagsExtracted === "number" && data.inlineTagsExtracted > 0) {
328
+ linkSummary.push(`๐Ÿท๏ธ ${data.inlineTagsExtracted} \`#tag\`(s) auto-extracted`);
329
+ }
330
+ if (data.blobStored) {
331
+ linkSummary.push(`๐Ÿ“ Large content (${Math.round((data.originalSize ?? 0) / 1024)}KB) stored as blob; chunked indexing in progress`);
332
+ }
333
+ const lines = [
334
+ `๐Ÿ“ฆ **Vault ${changed ? (version === 1 ? "Created" : "Updated") : "Unchanged"}**`,
335
+ `Key: \`${key}\``,
336
+ `Version: v${version}`,
337
+ changed && version > 1 ? `Previous version auto-snapshotted.` : "",
338
+ mirrorToMemory ? `๐Ÿง  Synced to semantic memory` : (localVault ? `๐Ÿ’พ Stored locally at ~/.finch/vault` : ""),
339
+ ...linkSummary,
340
+ ``,
341
+ `Use \`vault_read\` to retrieve, \`vault_history\` to see all versions.`,
342
+ ].filter(Boolean);
343
+ return { content: [{ type: "text", text: lines.join("\n") }] };
344
+ }
345
+ case "vault_read": {
346
+ const parsed = ReadSchema.safeParse(args);
347
+ if (!parsed.success)
348
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
349
+ const vaultReadKey = parsed.data.key;
350
+ let data;
351
+ try {
352
+ data = localVault
353
+ ? (0, local_vault_js_1.localVaultRead)(localVault, vaultReadKey)
354
+ : await (0, convex_js_1.callConvex)(`/vault/entry?key=${encodeURIComponent(vaultReadKey)}`, "GET", undefined, "vault_read");
355
+ }
356
+ catch (e) {
357
+ const msg = String(e?.message ?? e).toLowerCase();
358
+ const searchTerms = vaultReadKey.split("/").pop()?.replace(/-/g, " ") ?? vaultReadKey;
359
+ if (msg.includes("404") || msg.includes("not found")) {
360
+ return { content: [{ type: "text", text: [
361
+ `vault_read: entry \`${vaultReadKey}\` not found.`,
362
+ ``,
363
+ `Try searching for it:`,
364
+ `- \`vault_search query="${searchTerms}"\` โ€” semantic search across all entries`,
365
+ `- \`vault_list\` โ€” browse all entries`,
366
+ `- \`vault_search query="${vaultReadKey.split("/")[0]}"\` โ€” search by type prefix`,
367
+ ].join("\n") }], isError: true };
368
+ }
369
+ if (msg.includes("401") || msg.includes("auth") || msg.includes("unauthorized")) {
370
+ return { content: [{ type: "text", text: `vault_read: not authenticated. Run \`finch login\` to sign in.` }], isError: true };
371
+ }
372
+ return { content: [{ type: "text", text: `vault_read failed: ${e?.message ?? e}` }], isError: true };
373
+ }
374
+ if (data?.error) {
375
+ const err = String(data.error).toLowerCase();
376
+ if (err.includes("not found") || err.includes("404") || err.includes("no entry")) {
377
+ const searchTerms = vaultReadKey.split("/").pop()?.replace(/-/g, " ") ?? vaultReadKey;
378
+ return { content: [{ type: "text", text: [
379
+ `vault_read: entry \`${vaultReadKey}\` not found.`,
380
+ `Try: vault_search query="${searchTerms}"`,
381
+ `Or: vault_list`,
382
+ ].join("\n") }], isError: true };
383
+ }
384
+ return { content: [{ type: "text", text: `vault_read error: ${data.error}` }], isError: true };
385
+ }
386
+ // Large entries are offloaded to Convex File Storage. The doc holds a
387
+ // preview only; pull the real content from /vault/blob.
388
+ let fullContent = data.content ?? "";
389
+ if (data.contentFileId) {
390
+ try {
391
+ fullContent = await (0, convex_js_1.callConvexRaw)(`/vault/blob?id=${encodeURIComponent(data.contentFileId)}`, "vault_read");
392
+ }
393
+ catch (err) {
394
+ fullContent = (data.content ?? "") + `\n\n_(could not load full blob: ${err.message})_`;
395
+ }
396
+ }
397
+ const sizeLabel = data.originalSize ? formatBytes(data.originalSize) : formatBytes(data.size);
398
+ const backlinksBlock = Array.isArray(data.backlinks) && data.backlinks.length > 0
399
+ ? `\n๐Ÿ”™ Linked from (${data.backlinks.length}):\n${data.backlinks.map((b) => ` โ† \`${b.key}\`${b.title ? ` - ${b.title}` : ""}`).join("\n")}`
400
+ : "";
401
+ const lines = [
402
+ `๐Ÿ“‚ **${data.title}**`,
403
+ `Key: \`${data.key}\` ยท Type: ${data.type} ยท v${data.version} ยท ${sizeLabel}${data.contentFileId ? " ยท blob" : ""}`,
404
+ data.tags?.length ? `Tags: ${data.tags.join(", ")}` : "",
405
+ data.isPinned ? "๐Ÿ“Œ Pinned" : "",
406
+ data.agentId ? `Agent: ${data.agentId}` : "",
407
+ `Updated: ${formatDate(data.updatedAt)}`,
408
+ data.linkedKeys?.length ? `\nLinks out:\n${data.linkedKeys.map((l) => ` โ†’ ${l}`).join("\n")}` : "",
409
+ backlinksBlock,
410
+ ``,
411
+ `---`,
412
+ ``,
413
+ fullContent,
414
+ ].filter((l) => l !== "");
415
+ return { content: [{ type: "text", text: lines.join("\n") }] };
416
+ }
417
+ case "vault_list": {
418
+ const parsed = ListSchema.safeParse(args ?? {});
419
+ if (!parsed.success)
420
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
421
+ const params = new URLSearchParams();
422
+ if (parsed.data.type)
423
+ params.set("type", parsed.data.type);
424
+ if (parsed.data.agentId)
425
+ params.set("agentId", parsed.data.agentId);
426
+ if (parsed.data.pinned !== undefined)
427
+ params.set("pinned", String(parsed.data.pinned));
428
+ if (parsed.data.limit)
429
+ params.set("limit", String(parsed.data.limit));
430
+ const data = localVault
431
+ ? (0, local_vault_js_1.localVaultList)(localVault, parsed.data)
432
+ : await (0, convex_js_1.callConvex)(`/vault/list?${params}`, "GET", undefined, "vault_list");
433
+ if (data.error)
434
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
435
+ const entries = data.entries ?? [];
436
+ if (!entries.length)
437
+ return { content: [{ type: "text", text: `No vault entries found${parsed.data.type ? ` of type '${parsed.data.type}'` : ""}.` }] };
438
+ const header = `๐Ÿ“š **Noel-Vault** (${entries.length} entries)`;
439
+ const rows = entries.map((e) => `${e.isPinned ? "๐Ÿ“Œ " : ""}[\`${e.key}\`] ${e.title} - v${e.version} ยท ${e.type} ยท ${formatBytes(e.size)} ยท ${formatDate(e.updatedAt)}`);
440
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
441
+ }
442
+ case "vault_search": {
443
+ const parsed = SearchSchema.safeParse(args);
444
+ if (!parsed.success)
445
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
446
+ // Try semantic search first (proxied through Convex). Large vault
447
+ // entries are indexed as multiple chunks tagged with isVaultChunk +
448
+ // vaultKey - group chunks back to their parent entry so the result
449
+ // list shows one row per entry, not one row per chunk.
450
+ {
451
+ const limit = parsed.data.limit ?? 20;
452
+ // Over-fetch so that after chunk dedup we still have ~limit rows.
453
+ const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.query, Math.min(50, limit * 3));
454
+ if (smResults.length > 0) {
455
+ const filtered = parsed.data.type
456
+ ? smResults.filter(r => r.metadata?.type === parsed.data.type)
457
+ : smResults;
458
+ if (filtered.length > 0) {
459
+ const groups = new Map();
460
+ for (const r of filtered) {
461
+ const isChunk = r.metadata?.isVaultChunk === true;
462
+ const groupKey = isChunk ? (r.metadata?.vaultKey ?? r.id) : r.id;
463
+ const existing = groups.get(groupKey);
464
+ const score = r.score ?? 0;
465
+ const preview = (r.content ?? "").slice(0, 200).replace(/\n/g, " ");
466
+ const title = r.metadata?.title ?? r.content.slice(0, 60);
467
+ const type = r.metadata?.type ?? (isChunk ? "vault" : "memory");
468
+ if (!existing) {
469
+ groups.set(groupKey, {
470
+ key: groupKey,
471
+ title,
472
+ type,
473
+ bestScore: score,
474
+ bestPreview: preview,
475
+ chunkHits: 1,
476
+ isVaultChunk: isChunk,
477
+ });
478
+ }
479
+ else {
480
+ existing.chunkHits += 1;
481
+ if (score > existing.bestScore) {
482
+ existing.bestScore = score;
483
+ existing.bestPreview = preview;
484
+ }
485
+ }
486
+ }
487
+ const grouped = Array.from(groups.values())
488
+ .sort((a, b) => b.bestScore - a.bestScore)
489
+ .slice(0, limit);
490
+ const header = `๐Ÿ” **Vault Search** [Semantic]: "${parsed.data.query}" - ${grouped.length} entry/entries`;
491
+ const rows = grouped.map((g, i) => {
492
+ const score = g.bestScore ? ` ${(g.bestScore * 100).toFixed(0)}%` : "";
493
+ const chunkBadge = g.isVaultChunk && g.chunkHits > 1
494
+ ? ` ยท ${g.chunkHits} chunk hits`
495
+ : "";
496
+ return [
497
+ `${i + 1}.${score} [\`${g.key}\`] **${g.title}** (${g.type}${chunkBadge})`,
498
+ ` ${g.bestPreview}${g.bestPreview.length >= 200 ? "โ€ฆ" : ""}`,
499
+ ].join("\n");
500
+ });
501
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
502
+ }
503
+ }
504
+ }
505
+ // Fallback: local full-text (user-owned vault) or Convex full-text.
506
+ const params = new URLSearchParams({ q: parsed.data.query });
507
+ if (parsed.data.type)
508
+ params.set("type", parsed.data.type);
509
+ if (parsed.data.limit)
510
+ params.set("limit", String(parsed.data.limit));
511
+ const data = localVault
512
+ ? (0, local_vault_js_1.localVaultSearch)(localVault, parsed.data.query, { type: parsed.data.type, limit: parsed.data.limit })
513
+ : await (0, convex_js_1.callConvex)(`/vault/search?${params}`, "GET", undefined, "vault_search");
514
+ if (data.error)
515
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
516
+ const results = data.results ?? [];
517
+ if (!results.length)
518
+ return { content: [{ type: "text", text: `No vault entries found for: "${parsed.data.query}"` }] };
519
+ const header = `๐Ÿ” **Vault Search**: "${parsed.data.query}" - ${results.length} result(s)`;
520
+ const rows = results.map((r, i) => [
521
+ `${i + 1}. [\`${r.key}\`] **${r.title}** (${r.type} ยท v${r.version})`,
522
+ ` ${r.preview}`,
523
+ ].join("\n"));
524
+ return { content: [{ type: "text", text: [header, "", ...rows].join("\n") }] };
525
+ }
526
+ case "vault_history": {
527
+ const parsed = HistorySchema.safeParse(args);
528
+ if (!parsed.success)
529
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
530
+ const histKey = parsed.data.key;
531
+ let data;
532
+ try {
533
+ data = localVault
534
+ ? (0, local_vault_js_1.localVaultHistory)(localVault, histKey)
535
+ : await (0, convex_js_1.callConvex)(`/vault/history?key=${encodeURIComponent(histKey)}`, "GET", undefined, "vault_history");
536
+ }
537
+ catch (e) {
538
+ const msg = String(e?.message ?? e).toLowerCase();
539
+ const searchTerms = histKey.split("/").pop()?.replace(/-/g, " ") ?? histKey;
540
+ if (msg.includes("404") || msg.includes("not found")) {
541
+ return { content: [{ type: "text", text: [
542
+ `vault_history: entry \`${histKey}\` not found.`,
543
+ `Try: vault_search query="${searchTerms}"`,
544
+ `Or: vault_list`,
545
+ ].join("\n") }], isError: true };
546
+ }
547
+ if (msg.includes("401") || msg.includes("unauthorized")) {
548
+ return { content: [{ type: "text", text: `vault_history: not authenticated. Run \`finch login\`.` }], isError: true };
549
+ }
550
+ return { content: [{ type: "text", text: `vault_history failed: ${e?.message ?? e}` }], isError: true };
551
+ }
552
+ if (data?.error) {
553
+ return { content: [{ type: "text", text: `vault_history failed: ${data.error}` }], isError: true };
554
+ }
555
+ const { key, title, currentVersion, history } = data;
556
+ const header = [
557
+ `๐Ÿ“œ **History**: ${title}`,
558
+ `Key: \`${key}\` ยท Current: v${currentVersion}`,
559
+ ``,
560
+ `| Version | Commit | Agent | Size | Date |`,
561
+ `|---------|--------|-------|------|------|`,
562
+ ];
563
+ const rows = history.map((v) => `| v${v.version} | ${v.commitMsg ?? "-"} | ${v.agentId ?? "-"} | ${formatBytes(v.size)} | ${formatDate(v.createdAt)} |`);
564
+ return { content: [{ type: "text", text: [...header, ...rows].join("\n") }] };
565
+ }
566
+ case "vault_diff": {
567
+ const parsed = DiffSchema.safeParse(args);
568
+ if (!parsed.success)
569
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
570
+ const { key, fromVersion, toVersion } = parsed.data;
571
+ let data;
572
+ try {
573
+ data = localVault
574
+ ? (0, local_vault_js_1.localVaultDiff)(localVault, key, fromVersion, toVersion)
575
+ : await (0, convex_js_1.callConvex)(`/vault/diff?key=${encodeURIComponent(key)}&from=${fromVersion}&to=${toVersion}`, "GET", undefined, "vault_diff");
576
+ }
577
+ catch (e) {
578
+ const msg = String(e?.message ?? e).toLowerCase();
579
+ const searchTerms = key.split("/").pop()?.replace(/-/g, " ") ?? key;
580
+ if (msg.includes("404") || msg.includes("not found")) {
581
+ return { content: [{ type: "text", text: [
582
+ `vault_diff: entry \`${key}\` not found.`,
583
+ `Try: vault_search query="${searchTerms}"`,
584
+ `Or: vault_history key="${key}" to see available versions`,
585
+ ].join("\n") }], isError: true };
586
+ }
587
+ if (msg.includes("version") || msg.includes("range")) {
588
+ return { content: [{ type: "text", text: [
589
+ `vault_diff: version range v${fromVersion}โ†’v${toVersion} invalid for \`${key}\`.`,
590
+ `Check available versions: vault_history key="${key}"`,
591
+ ].join("\n") }], isError: true };
592
+ }
593
+ if (msg.includes("401") || msg.includes("unauthorized")) {
594
+ return { content: [{ type: "text", text: `vault_diff: not authenticated. Run \`finch login\`.` }], isError: true };
595
+ }
596
+ return { content: [{ type: "text", text: `vault_diff failed: ${e?.message ?? e}` }], isError: true };
597
+ }
598
+ if (data?.error) {
599
+ return { content: [{ type: "text", text: `vault_diff failed: ${data.error}` }], isError: true };
600
+ }
601
+ const lines = [
602
+ `๐Ÿ“ **Diff**: \`${data.key}\` - v${fromVersion} โ†’ v${toVersion}`,
603
+ ``,
604
+ "```diff",
605
+ data.diff,
606
+ "```",
607
+ ];
608
+ return { content: [{ type: "text", text: lines.join("\n") }] };
609
+ }
610
+ case "vault_export": {
611
+ const parsed = ExportSchema.safeParse(args ?? {});
612
+ if (!parsed.success)
613
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
614
+ const params = parsed.data.type ? `?type=${parsed.data.type}` : "";
615
+ const data = localVault
616
+ ? (0, local_vault_js_1.localVaultExport)(localVault, parsed.data.type)
617
+ : await (0, convex_js_1.callConvex)(`/vault/export${params}`, "GET", undefined, "vault_export");
618
+ if (data.error)
619
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
620
+ const { exportedAt, totalEntries, entries } = data;
621
+ const header = [
622
+ `๐Ÿ“ค **Vault Export**`,
623
+ `Exported: ${formatDate(exportedAt)} ยท ${totalEntries} entries${parsed.data.type ? ` (type: ${parsed.data.type})` : ""}`,
624
+ ``,
625
+ ];
626
+ const rows = entries.map((e) => `**[\`${e.key}\`]** ${e.title} (${e.type} ยท v${e.version})\n${e.content.slice(0, 500)}${e.content.length > 500 ? "\nโ€ฆ" : ""}`);
627
+ return { content: [{ type: "text", text: [...header, ...rows.join("\n\n---\n\n").split("\n")].join("\n") }] };
628
+ }
629
+ case "vault_store_credential": {
630
+ const parsed = StoreCredentialSchema.safeParse(args);
631
+ if (!parsed.success)
632
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
633
+ const data = localVault
634
+ ? (0, local_vault_js_1.localVaultStoreCredential)(localVault, parsed.data.name, parsed.data.value, parsed.data.description)
635
+ : await (0, convex_js_1.callConvex)("/vault/credential/store", "POST", parsed.data, "vault_store_credential");
636
+ if (data.error)
637
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
638
+ return { content: [{ type: "text", text: `๐Ÿ” Credential stored: \`${data.name}\`\nKey: \`${data.key}\`\nRetrieve with: \`vault_get_credential name: "${data.name}"\`` }] };
639
+ }
640
+ case "vault_get_credential": {
641
+ const parsed = GetCredentialSchema.safeParse(args);
642
+ if (!parsed.success)
643
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
644
+ const params = new URLSearchParams({ name: parsed.data.name });
645
+ let data;
646
+ try {
647
+ data = localVault
648
+ ? (0, local_vault_js_1.localVaultGetCredential)(localVault, parsed.data.name)
649
+ : await (0, convex_js_1.callConvex)(`/vault/credential?${params}`, "GET", undefined, "vault_get_credential");
650
+ }
651
+ catch {
652
+ return { content: [{ type: "text", text: `vault_get_credential: no credential named \`${parsed.data.name}\`.` }], isError: true };
653
+ }
654
+ if (data.error)
655
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
656
+ const lines = [`๐Ÿ” **${data.name}**`, `Value: \`${data.value}\``];
657
+ if (data.description)
658
+ lines.push(`Note: ${data.description}`);
659
+ if (data.storedAt)
660
+ lines.push(`Stored: ${data.storedAt}`);
661
+ return { content: [{ type: "text", text: lines.join("\n") }] };
662
+ }
663
+ case "vault_pin": {
664
+ const parsed = PinSchema.safeParse(args);
665
+ if (!parsed.success)
666
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
667
+ const { key, pinned = true } = parsed.data;
668
+ const data = localVault
669
+ ? (0, local_vault_js_1.localVaultPin)(localVault, key, pinned)
670
+ : await (0, convex_js_1.callConvex)("/vault/pin", "POST", { key, pinned }, "vault_pin");
671
+ if (data.error)
672
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
673
+ return { content: [{ type: "text", text: pinned ? `๐Ÿ“Œ Pinned: \`${key}\`` : `๐Ÿ“Œ Unpinned: \`${key}\`` }] };
674
+ }
675
+ case "vault_unpublish": {
676
+ const parsed = UnpublishSchema.safeParse(args);
677
+ if (!parsed.success)
678
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
679
+ // Publishing is a hosted/marketplace concept - a local vault is private
680
+ // by construction, so there is nothing to retract.
681
+ if (localVault) {
682
+ return { content: [{ type: "text", text: `๐Ÿ”’ \`${parsed.data.key}\` is in your local vault โ€” already private, nothing to unpublish. (Publishing only applies to the hosted vault.)` }] };
683
+ }
684
+ const data = await (0, convex_js_1.callConvex)("/vault/unpublish", "POST", { key: parsed.data.key }, "vault_unpublish");
685
+ if (data.error)
686
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
687
+ return {
688
+ content: [{
689
+ type: "text",
690
+ text: `๐Ÿ”’ **Private again:** \`${parsed.data.key}\`\n\n` +
691
+ `Removed from the public community listing. Anyone who copied it while it was public ` +
692
+ `still has that copy โ€” this stops discovery, not distribution.`,
693
+ }],
694
+ };
695
+ }
696
+ case "vault_delete": {
697
+ const parsed = DeleteSchema.safeParse(args);
698
+ if (!parsed.success)
699
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
700
+ if (args?.confirm !== true) {
701
+ return {
702
+ content: [{
703
+ type: "text",
704
+ text: "Refusing to delete: this permanently removes the entry **and its entire version " +
705
+ "history**, and cannot be undone. Show the user the exact entry, then pass `confirm: true`.",
706
+ }],
707
+ isError: true,
708
+ };
709
+ }
710
+ const data = localVault
711
+ ? (0, local_vault_js_1.localVaultDelete)(localVault, parsed.data.key)
712
+ : await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
713
+ if (data.error)
714
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
715
+ return { content: [{ type: "text", text: `๐Ÿ—‘๏ธ Deleted: \`${parsed.data.key}\` (${data.versionsRemoved ?? 0} versions removed)` }] };
716
+ }
717
+ case "vault_tag": {
718
+ const parsed = TagSchema.safeParse(args);
719
+ if (!parsed.success)
720
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
721
+ const { key, tags, replace = false } = parsed.data;
722
+ const data = localVault
723
+ ? (0, local_vault_js_1.localVaultTag)(localVault, key, tags, replace)
724
+ : await (0, convex_js_1.callConvex)("/vault/tag", "POST", { key, tags, replace }, "vault_tag");
725
+ if (data.error)
726
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
727
+ return { content: [{ type: "text", text: `๐Ÿท๏ธ Tags ${replace ? "set" : "updated"} on \`${key}\`: ${(data.tags ?? tags).join(", ")}` }] };
728
+ }
729
+ case "vault_link": {
730
+ const parsed = LinkSchema.safeParse(args);
731
+ if (!parsed.success)
732
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
733
+ const { fromKey, toKey, relation } = parsed.data;
734
+ const data = localVault
735
+ ? (0, local_vault_js_1.localVaultLink)(localVault, fromKey, toKey, relation)
736
+ : await (0, convex_js_1.callConvex)("/vault/link", "POST", { fromKey, toKey, relation }, "vault_link");
737
+ if (data.error)
738
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
739
+ const action = data.updated ? "Updated link" : "Linked";
740
+ return { content: [{ type: "text", text: `๐Ÿ”— ${action}: \`${fromKey}\` -[${relation}]โ†’ \`${toKey}\`` }] };
741
+ }
742
+ case "vault_related": {
743
+ const parsed = RelatedSchema.safeParse(args);
744
+ if (!parsed.success)
745
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
746
+ const { key, relation } = parsed.data;
747
+ const params = new URLSearchParams({ key });
748
+ if (relation)
749
+ params.set("relation", relation);
750
+ const data = (localVault
751
+ ? (0, local_vault_js_1.localVaultRelated)(localVault, key, relation)
752
+ : await (0, convex_js_1.callConvex)(`/vault/related?${params}`, "GET", undefined, "vault_related"));
753
+ if (data.error)
754
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
755
+ const items = data.related ?? [];
756
+ if (!items.length)
757
+ return { content: [{ type: "text", text: `No related entries found for \`${key}\`${relation ? ` (relation: ${relation})` : ""}.` }] };
758
+ const lines = items.map(r => `- **${r.title}** (\`${r.key}\`) [${r.type}] - ${r.direction} \`${r.relation}\``);
759
+ return { content: [{ type: "text", text: `## Related entries for \`${key}\` (${items.length})\n\n${lines.join("\n")}` }] };
760
+ }
761
+ default:
762
+ return null;
763
+ }
764
+ }