@finchagentic/mcp 4.6.1 → 4.6.2
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/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/_text-search.js +0 -39
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -150
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -456
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1059
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
package/dist/local-vault.js
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.getLocalVaultConfig = getLocalVaultConfig;
|
|
37
|
-
exports.localVaultSave = localVaultSave;
|
|
38
|
-
exports.localVaultRead = localVaultRead;
|
|
39
|
-
exports.localVaultList = localVaultList;
|
|
40
|
-
exports.localVaultSearch = localVaultSearch;
|
|
41
|
-
exports.localVaultHistory = localVaultHistory;
|
|
42
|
-
exports.localVaultDiff = localVaultDiff;
|
|
43
|
-
exports.localVaultExport = localVaultExport;
|
|
44
|
-
exports.localVaultPin = localVaultPin;
|
|
45
|
-
exports.localVaultDelete = localVaultDelete;
|
|
46
|
-
exports.localVaultTag = localVaultTag;
|
|
47
|
-
exports.localVaultLink = localVaultLink;
|
|
48
|
-
exports.localVaultRelated = localVaultRelated;
|
|
49
|
-
exports.localVaultStoreCredential = localVaultStoreCredential;
|
|
50
|
-
exports.localVaultGetCredential = localVaultGetCredential;
|
|
51
|
-
const fs = __importStar(require("fs"));
|
|
52
|
-
const os = __importStar(require("os"));
|
|
53
|
-
const path = __importStar(require("path"));
|
|
54
|
-
const crypto = __importStar(require("crypto"));
|
|
55
|
-
const config_js_1 = require("./config.js");
|
|
56
|
-
const _text_search_js_1 = require("./_text-search.js");
|
|
57
|
-
// Fully-local, user-owned Noel-Vault backend. Mirrors the two-tier pattern of
|
|
58
|
-
// local-memory.ts: when the user opts in (`vaultBackend: "local"`), the vault
|
|
59
|
-
// tools store versioned artifacts on the user's own disk under
|
|
60
|
-
// ~/.finch/vault/ - no Finch account, no Convex, no network, no cost to
|
|
61
|
-
// the platform. Zero runtime dependencies (plain JSON + one content file per
|
|
62
|
-
// version), same spirit as codebase-memory-mcp's local-first SQLite store.
|
|
63
|
-
//
|
|
64
|
-
// Every exported function returns objects shaped like the Convex /vault/*
|
|
65
|
-
// responses so tools/vault.ts can swap the data source with a one-line branch
|
|
66
|
-
// and reuse all of its existing rendering. Not-found cases THROW an Error whose
|
|
67
|
-
// message contains "not found", matching how callConvex surfaces a 404 (the
|
|
68
|
-
// vault handlers already catch and format that).
|
|
69
|
-
const VAULT_DIR = path.join(os.homedir(), ".finch", "vault");
|
|
70
|
-
// Local vault is enabled purely by config - it needs no server, just the
|
|
71
|
-
// filesystem. Returns null (→ "use Convex") when the user hasn't opted in.
|
|
72
|
-
function getLocalVaultConfig() {
|
|
73
|
-
try {
|
|
74
|
-
if ((0, config_js_1.readConfig)().vaultBackend !== "local")
|
|
75
|
-
return null;
|
|
76
|
-
return { dir: VAULT_DIR };
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function now() { return Date.now(); }
|
|
83
|
-
function ensureDir(cfg) {
|
|
84
|
-
fs.mkdirSync(path.join(cfg.dir, "data"), { recursive: true });
|
|
85
|
-
}
|
|
86
|
-
function indexPath(cfg) { return path.join(cfg.dir, "index.json"); }
|
|
87
|
-
function readIndex(cfg) {
|
|
88
|
-
try {
|
|
89
|
-
const raw = fs.readFileSync(indexPath(cfg), "utf8");
|
|
90
|
-
const parsed = JSON.parse(raw);
|
|
91
|
-
return { entries: parsed.entries ?? {}, links: parsed.links ?? [] };
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return { entries: {}, links: [] };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
// Atomic write: temp file + rename, so a crash mid-write can't corrupt the
|
|
98
|
-
// manifest every entry depends on.
|
|
99
|
-
function writeIndex(cfg, idx) {
|
|
100
|
-
ensureDir(cfg);
|
|
101
|
-
const tmp = indexPath(cfg) + `.tmp-${process.pid}`;
|
|
102
|
-
fs.writeFileSync(tmp, JSON.stringify(idx, null, 2), "utf8");
|
|
103
|
-
fs.renameSync(tmp, indexPath(cfg));
|
|
104
|
-
}
|
|
105
|
-
function keyDir(key) {
|
|
106
|
-
return crypto.createHash("sha1").update(key).digest("hex").slice(0, 16);
|
|
107
|
-
}
|
|
108
|
-
function contentPath(cfg, dir, version) {
|
|
109
|
-
return path.join(cfg.dir, "data", dir, `v${version}`);
|
|
110
|
-
}
|
|
111
|
-
function readContent(cfg, dir, version) {
|
|
112
|
-
try {
|
|
113
|
-
return fs.readFileSync(contentPath(cfg, dir, version), "utf8");
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return "";
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function writeContent(cfg, dir, version, content) {
|
|
120
|
-
const d = path.join(cfg.dir, "data", dir);
|
|
121
|
-
fs.mkdirSync(d, { recursive: true });
|
|
122
|
-
fs.writeFileSync(contentPath(cfg, dir, version), content, "utf8");
|
|
123
|
-
}
|
|
124
|
-
function slugify(s) {
|
|
125
|
-
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "entry";
|
|
126
|
-
}
|
|
127
|
-
function notFound(key) {
|
|
128
|
-
return new Error(`Vault entry not found: ${key}`);
|
|
129
|
-
}
|
|
130
|
-
function localVaultSave(cfg, input) {
|
|
131
|
-
const idx = readIndex(cfg);
|
|
132
|
-
let key = input.key ?? `${input.type}/${slugify(input.title)}`;
|
|
133
|
-
// An auto-generated key can collide with an unrelated entry when two
|
|
134
|
-
// different titles slugify to the same thing. Only version-in-place when the
|
|
135
|
-
// key was explicit or the existing entry is the same title; otherwise
|
|
136
|
-
// disambiguate, so a different note never silently gets folded into this one.
|
|
137
|
-
if (!input.key && idx.entries[key] && idx.entries[key].title !== input.title) {
|
|
138
|
-
let n = 2;
|
|
139
|
-
while (idx.entries[`${key}-${n}`])
|
|
140
|
-
n++;
|
|
141
|
-
key = `${key}-${n}`;
|
|
142
|
-
}
|
|
143
|
-
// Parity with the hosted vault: [[wikilinks]] become graph edges, #tags are
|
|
144
|
-
// pulled into the entry's tag set.
|
|
145
|
-
const inlineTags = Array.from(new Set((input.content.match(/(?:^|\s)#([a-z0-9][a-z0-9_-]*)/gi) ?? []).map((t) => t.trim().replace(/^#/, "").toLowerCase())));
|
|
146
|
-
const wikilinks = Array.from(new Set((input.content.match(/\[\[([^\]]+)\]\]/g) ?? []).map((w) => w.slice(2, -2).trim())));
|
|
147
|
-
const tags = Array.from(new Set([...(input.tags ?? []), ...inlineTags]));
|
|
148
|
-
const existing = idx.entries[key];
|
|
149
|
-
const ts = now();
|
|
150
|
-
if (existing) {
|
|
151
|
-
const currentContent = readContent(cfg, existing.dir, existing.currentVersion);
|
|
152
|
-
if (currentContent === input.content) {
|
|
153
|
-
// No change - don't bump the version (matches hosted "Unchanged").
|
|
154
|
-
return { key, version: existing.currentVersion, changed: false, linksCreated: 0, linksMissing: [], inlineTagsExtracted: inlineTags.length };
|
|
155
|
-
}
|
|
156
|
-
const version = existing.currentVersion + 1;
|
|
157
|
-
writeContent(cfg, existing.dir, version, input.content);
|
|
158
|
-
existing.currentVersion = version;
|
|
159
|
-
existing.title = input.title;
|
|
160
|
-
existing.type = input.type;
|
|
161
|
-
existing.contentType = input.contentType ?? existing.contentType;
|
|
162
|
-
existing.agentId = input.agentId ?? existing.agentId;
|
|
163
|
-
existing.tags = Array.from(new Set([...existing.tags, ...tags]));
|
|
164
|
-
existing.updatedAt = ts;
|
|
165
|
-
existing.metadata = input.metadata ?? existing.metadata;
|
|
166
|
-
existing.versions.push({ version, commitMsg: input.commitMsg, agentId: input.agentId, size: Buffer.byteLength(input.content), createdAt: ts });
|
|
167
|
-
const linkResult = applyWikilinks(idx, key, wikilinks);
|
|
168
|
-
writeIndex(cfg, idx);
|
|
169
|
-
return { key, version, changed: true, ...linkResult, inlineTagsExtracted: inlineTags.length };
|
|
170
|
-
}
|
|
171
|
-
const dir = keyDir(key);
|
|
172
|
-
writeContent(cfg, dir, 1, input.content);
|
|
173
|
-
idx.entries[key] = {
|
|
174
|
-
key, dir, type: input.type, title: input.title, contentType: input.contentType,
|
|
175
|
-
agentId: input.agentId, tags, pinned: false, currentVersion: 1,
|
|
176
|
-
createdAt: ts, updatedAt: ts, metadata: input.metadata,
|
|
177
|
-
versions: [{ version: 1, commitMsg: input.commitMsg, agentId: input.agentId, size: Buffer.byteLength(input.content), createdAt: ts }],
|
|
178
|
-
};
|
|
179
|
-
const linkResult = applyWikilinks(idx, key, wikilinks);
|
|
180
|
-
writeIndex(cfg, idx);
|
|
181
|
-
return { key, version: 1, changed: true, ...linkResult, inlineTagsExtracted: inlineTags.length };
|
|
182
|
-
}
|
|
183
|
-
function applyWikilinks(idx, fromKey, targets) {
|
|
184
|
-
let linksCreated = 0;
|
|
185
|
-
const linksMissing = [];
|
|
186
|
-
for (const toKey of targets) {
|
|
187
|
-
if (!idx.entries[toKey]) {
|
|
188
|
-
linksMissing.push(toKey);
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const dup = idx.links.find((l) => l.fromKey === fromKey && l.toKey === toKey && l.relation === "references");
|
|
192
|
-
if (!dup) {
|
|
193
|
-
idx.links.push({ fromKey, toKey, relation: "references" });
|
|
194
|
-
linksCreated++;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return { linksCreated, linksMissing };
|
|
198
|
-
}
|
|
199
|
-
// ── read ──────────────────────────────────────────────────────────────────────
|
|
200
|
-
function localVaultRead(cfg, key) {
|
|
201
|
-
const idx = readIndex(cfg);
|
|
202
|
-
const e = idx.entries[key];
|
|
203
|
-
if (!e)
|
|
204
|
-
throw notFound(key);
|
|
205
|
-
const linkedKeys = idx.links.filter((l) => l.fromKey === key).map((l) => `${l.toKey} (${l.relation})`);
|
|
206
|
-
const backlinks = idx.links
|
|
207
|
-
.filter((l) => l.toKey === key)
|
|
208
|
-
.map((l) => ({ key: l.fromKey, title: idx.entries[l.fromKey]?.title }));
|
|
209
|
-
return {
|
|
210
|
-
key: e.key, title: e.title, type: e.type, version: e.currentVersion,
|
|
211
|
-
size: e.versions[e.versions.length - 1]?.size, tags: e.tags, isPinned: e.pinned,
|
|
212
|
-
agentId: e.agentId, updatedAt: e.updatedAt, content: readContent(cfg, e.dir, e.currentVersion),
|
|
213
|
-
linkedKeys, backlinks,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
// ── list ──────────────────────────────────────────────────────────────────────
|
|
217
|
-
function localVaultList(cfg, opts) {
|
|
218
|
-
const idx = readIndex(cfg);
|
|
219
|
-
let rows = Object.values(idx.entries).filter((e) => e.type !== "credential");
|
|
220
|
-
if (opts.type)
|
|
221
|
-
rows = rows.filter((e) => e.type === opts.type);
|
|
222
|
-
if (opts.agentId)
|
|
223
|
-
rows = rows.filter((e) => e.agentId === opts.agentId);
|
|
224
|
-
if (opts.pinned !== undefined)
|
|
225
|
-
rows = rows.filter((e) => e.pinned === opts.pinned);
|
|
226
|
-
rows.sort((a, b) => (Number(b.pinned) - Number(a.pinned)) || (b.updatedAt - a.updatedAt));
|
|
227
|
-
const entries = rows.slice(0, opts.limit ?? 50).map((e) => ({
|
|
228
|
-
key: e.key, title: e.title, type: e.type, version: e.currentVersion,
|
|
229
|
-
size: e.versions[e.versions.length - 1]?.size, updatedAt: e.updatedAt, isPinned: e.pinned,
|
|
230
|
-
}));
|
|
231
|
-
return { entries };
|
|
232
|
-
}
|
|
233
|
-
// ── search (full-text, local) ─────────────────────────────────────────────────
|
|
234
|
-
function localVaultSearch(cfg, query, opts) {
|
|
235
|
-
const idx = readIndex(cfg);
|
|
236
|
-
const terms = (0, _text_search_js_1.meaningfulTerms)(query);
|
|
237
|
-
const scored = [];
|
|
238
|
-
if (terms.length === 0)
|
|
239
|
-
return { results: [] };
|
|
240
|
-
for (const e of Object.values(idx.entries)) {
|
|
241
|
-
if (e.type === "credential")
|
|
242
|
-
continue;
|
|
243
|
-
if (opts.type && e.type !== opts.type)
|
|
244
|
-
continue;
|
|
245
|
-
const content = readContent(cfg, e.dir, e.currentVersion);
|
|
246
|
-
const hay = `${e.title}\n${e.tags.join(" ")}\n${content}`.toLowerCase();
|
|
247
|
-
let score = 0;
|
|
248
|
-
for (const t of terms) {
|
|
249
|
-
if (e.title.toLowerCase().includes(t))
|
|
250
|
-
score += 3;
|
|
251
|
-
if (e.tags.some((tag) => tag.toLowerCase().includes(t)))
|
|
252
|
-
score += 2;
|
|
253
|
-
score += Math.min((0, _text_search_js_1.wordOccurrences)(hay, t), 5);
|
|
254
|
-
}
|
|
255
|
-
if (score > 0) {
|
|
256
|
-
const firstHit = terms.map((t) => content.toLowerCase().indexOf(t)).filter((i) => i >= 0).sort((a, b) => a - b)[0] ?? 0;
|
|
257
|
-
scored.push({ e, score, preview: content.slice(Math.max(0, firstHit - 20), firstHit + 180).replace(/\n/g, " ") });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
scored.sort((a, b) => b.score - a.score);
|
|
261
|
-
const results = scored.slice(0, opts.limit ?? 20).map(({ e, preview }) => ({
|
|
262
|
-
key: e.key, title: e.title, type: e.type, version: e.currentVersion, preview,
|
|
263
|
-
}));
|
|
264
|
-
return { results };
|
|
265
|
-
}
|
|
266
|
-
// ── history ───────────────────────────────────────────────────────────────────
|
|
267
|
-
function localVaultHistory(cfg, key) {
|
|
268
|
-
const idx = readIndex(cfg);
|
|
269
|
-
const e = idx.entries[key];
|
|
270
|
-
if (!e)
|
|
271
|
-
throw notFound(key);
|
|
272
|
-
return {
|
|
273
|
-
key: e.key, title: e.title, currentVersion: e.currentVersion,
|
|
274
|
-
history: [...e.versions].sort((a, b) => b.version - a.version),
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
// ── diff ──────────────────────────────────────────────────────────────────────
|
|
278
|
-
function lineDiff(a, b) {
|
|
279
|
-
const al = a.split("\n"), bl = b.split("\n");
|
|
280
|
-
const m = al.length, n = bl.length;
|
|
281
|
-
// LCS length table (bottom-up) - fine for the artifact sizes a vault holds.
|
|
282
|
-
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
283
|
-
for (let i = m - 1; i >= 0; i--)
|
|
284
|
-
for (let j = n - 1; j >= 0; j--)
|
|
285
|
-
dp[i][j] = al[i] === bl[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
286
|
-
const out = [];
|
|
287
|
-
let i = 0, j = 0;
|
|
288
|
-
while (i < m && j < n) {
|
|
289
|
-
if (al[i] === bl[j]) {
|
|
290
|
-
out.push(` ${al[i]}`);
|
|
291
|
-
i++;
|
|
292
|
-
j++;
|
|
293
|
-
}
|
|
294
|
-
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
295
|
-
out.push(`- ${al[i]}`);
|
|
296
|
-
i++;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
out.push(`+ ${bl[j]}`);
|
|
300
|
-
j++;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
while (i < m)
|
|
304
|
-
out.push(`- ${al[i++]}`);
|
|
305
|
-
while (j < n)
|
|
306
|
-
out.push(`+ ${bl[j++]}`);
|
|
307
|
-
return out.join("\n");
|
|
308
|
-
}
|
|
309
|
-
function localVaultDiff(cfg, key, fromVersion, toVersion) {
|
|
310
|
-
const idx = readIndex(cfg);
|
|
311
|
-
const e = idx.entries[key];
|
|
312
|
-
if (!e)
|
|
313
|
-
throw notFound(key);
|
|
314
|
-
const has = (v) => e.versions.some((x) => x.version === v);
|
|
315
|
-
if (!has(fromVersion) || !has(toVersion)) {
|
|
316
|
-
throw new Error(`version range v${fromVersion}->v${toVersion} invalid for ${key}`);
|
|
317
|
-
}
|
|
318
|
-
const from = readContent(cfg, e.dir, fromVersion);
|
|
319
|
-
const to = readContent(cfg, e.dir, toVersion);
|
|
320
|
-
// The LCS diff is O(m*n) in memory - guard against a pathologically large
|
|
321
|
-
// multi-line entry blowing up the process instead of returning a diff.
|
|
322
|
-
const la = from.split("\n").length, lb = to.split("\n").length;
|
|
323
|
-
if (la * lb > 4000000) {
|
|
324
|
-
return { key, diff: `(v${fromVersion}: ${la} lines · v${toVersion}: ${lb} lines — too large for a line-by-line diff)` };
|
|
325
|
-
}
|
|
326
|
-
return { key, diff: lineDiff(from, to) };
|
|
327
|
-
}
|
|
328
|
-
// ── export ────────────────────────────────────────────────────────────────────
|
|
329
|
-
function localVaultExport(cfg, type) {
|
|
330
|
-
const idx = readIndex(cfg);
|
|
331
|
-
let rows = Object.values(idx.entries).filter((e) => e.type !== "credential");
|
|
332
|
-
if (type)
|
|
333
|
-
rows = rows.filter((e) => e.type === type);
|
|
334
|
-
const entries = rows.map((e) => ({
|
|
335
|
-
key: e.key, title: e.title, type: e.type, version: e.currentVersion,
|
|
336
|
-
content: readContent(cfg, e.dir, e.currentVersion),
|
|
337
|
-
}));
|
|
338
|
-
return { exportedAt: now(), totalEntries: entries.length, entries };
|
|
339
|
-
}
|
|
340
|
-
// ── pin / delete / tag ────────────────────────────────────────────────────────
|
|
341
|
-
function localVaultPin(cfg, key, pinned) {
|
|
342
|
-
const idx = readIndex(cfg);
|
|
343
|
-
const e = idx.entries[key];
|
|
344
|
-
if (!e)
|
|
345
|
-
throw notFound(key);
|
|
346
|
-
e.pinned = pinned;
|
|
347
|
-
writeIndex(cfg, idx);
|
|
348
|
-
return { pinned };
|
|
349
|
-
}
|
|
350
|
-
function localVaultDelete(cfg, key) {
|
|
351
|
-
const idx = readIndex(cfg);
|
|
352
|
-
const e = idx.entries[key];
|
|
353
|
-
if (!e)
|
|
354
|
-
throw notFound(key);
|
|
355
|
-
const versionsRemoved = e.versions.length;
|
|
356
|
-
try {
|
|
357
|
-
fs.rmSync(path.join(cfg.dir, "data", e.dir), { recursive: true, force: true });
|
|
358
|
-
}
|
|
359
|
-
catch { /* best effort */ }
|
|
360
|
-
delete idx.entries[key];
|
|
361
|
-
idx.links = idx.links.filter((l) => l.fromKey !== key && l.toKey !== key);
|
|
362
|
-
writeIndex(cfg, idx);
|
|
363
|
-
return { versionsRemoved };
|
|
364
|
-
}
|
|
365
|
-
function localVaultTag(cfg, key, tags, replace) {
|
|
366
|
-
const idx = readIndex(cfg);
|
|
367
|
-
const e = idx.entries[key];
|
|
368
|
-
if (!e)
|
|
369
|
-
throw notFound(key);
|
|
370
|
-
e.tags = replace ? Array.from(new Set(tags)) : Array.from(new Set([...e.tags, ...tags]));
|
|
371
|
-
e.updatedAt = now();
|
|
372
|
-
writeIndex(cfg, idx);
|
|
373
|
-
return { tags: e.tags };
|
|
374
|
-
}
|
|
375
|
-
// ── link / related ────────────────────────────────────────────────────────────
|
|
376
|
-
function localVaultLink(cfg, fromKey, toKey, relation) {
|
|
377
|
-
const idx = readIndex(cfg);
|
|
378
|
-
if (!idx.entries[fromKey])
|
|
379
|
-
throw notFound(fromKey);
|
|
380
|
-
if (!idx.entries[toKey])
|
|
381
|
-
throw notFound(toKey);
|
|
382
|
-
const existing = idx.links.find((l) => l.fromKey === fromKey && l.toKey === toKey);
|
|
383
|
-
if (existing) {
|
|
384
|
-
existing.relation = relation;
|
|
385
|
-
writeIndex(cfg, idx);
|
|
386
|
-
return { updated: true };
|
|
387
|
-
}
|
|
388
|
-
idx.links.push({ fromKey, toKey, relation });
|
|
389
|
-
writeIndex(cfg, idx);
|
|
390
|
-
return { updated: false };
|
|
391
|
-
}
|
|
392
|
-
function localVaultRelated(cfg, key, relation) {
|
|
393
|
-
const idx = readIndex(cfg);
|
|
394
|
-
if (!idx.entries[key])
|
|
395
|
-
throw notFound(key);
|
|
396
|
-
const related = [];
|
|
397
|
-
for (const l of idx.links) {
|
|
398
|
-
if (relation && l.relation !== relation)
|
|
399
|
-
continue;
|
|
400
|
-
if (l.fromKey === key) {
|
|
401
|
-
const t = idx.entries[l.toKey];
|
|
402
|
-
if (t)
|
|
403
|
-
related.push({ key: l.toKey, title: t.title, type: t.type, relation: l.relation, direction: "→" });
|
|
404
|
-
}
|
|
405
|
-
else if (l.toKey === key) {
|
|
406
|
-
const f = idx.entries[l.fromKey];
|
|
407
|
-
if (f)
|
|
408
|
-
related.push({ key: l.fromKey, title: f.title, type: f.type, relation: l.relation, direction: "←" });
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return { key, related };
|
|
412
|
-
}
|
|
413
|
-
// ── credentials (AES-256-GCM, key at ~/.finch/vault/.credkey, 0600) ─────────
|
|
414
|
-
function credKeyPath(cfg) { return path.join(cfg.dir, ".credkey"); }
|
|
415
|
-
function credStorePath(cfg) { return path.join(cfg.dir, "credentials.json"); }
|
|
416
|
-
function getCredKey(cfg) {
|
|
417
|
-
ensureDir(cfg);
|
|
418
|
-
const p = credKeyPath(cfg);
|
|
419
|
-
try {
|
|
420
|
-
return Buffer.from(fs.readFileSync(p, "utf8").trim(), "hex");
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
const key = crypto.randomBytes(32);
|
|
424
|
-
fs.writeFileSync(p, key.toString("hex"), { mode: 0o600 });
|
|
425
|
-
return key;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
function readCredStore(cfg) {
|
|
429
|
-
try {
|
|
430
|
-
return JSON.parse(fs.readFileSync(credStorePath(cfg), "utf8"));
|
|
431
|
-
}
|
|
432
|
-
catch {
|
|
433
|
-
return {};
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
function localVaultStoreCredential(cfg, name, value, description) {
|
|
437
|
-
const key = getCredKey(cfg);
|
|
438
|
-
const iv = crypto.randomBytes(12);
|
|
439
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
440
|
-
const enc = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
441
|
-
const store = readCredStore(cfg);
|
|
442
|
-
store[name] = { iv: iv.toString("hex"), tag: cipher.getAuthTag().toString("hex"), data: enc.toString("hex"), description, storedAt: new Date(now()).toUTCString() };
|
|
443
|
-
fs.writeFileSync(credStorePath(cfg), JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
444
|
-
return { name, key: `credential/${name}` };
|
|
445
|
-
}
|
|
446
|
-
function localVaultGetCredential(cfg, name) {
|
|
447
|
-
const store = readCredStore(cfg);
|
|
448
|
-
const rec = store[name];
|
|
449
|
-
if (!rec)
|
|
450
|
-
throw notFound(`credential ${name}`);
|
|
451
|
-
const key = getCredKey(cfg);
|
|
452
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(rec.iv, "hex"));
|
|
453
|
-
decipher.setAuthTag(Buffer.from(rec.tag, "hex"));
|
|
454
|
-
const dec = Buffer.concat([decipher.update(Buffer.from(rec.data, "hex")), decipher.final()]).toString("utf8");
|
|
455
|
-
return { name, value: dec, description: rec.description, storedAt: rec.storedAt };
|
|
456
|
-
}
|