indelible-mcp 4.9.6 → 4.9.8
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 +1 -1
- package/src/index.js +466 -188
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1489,6 +1489,13 @@ function appendReceipt(receipt, meta = {}) {
|
|
|
1489
1489
|
trigger: meta.trigger || "interactive",
|
|
1490
1490
|
// interactive | auto (background/hook)
|
|
1491
1491
|
label: meta.label || null,
|
|
1492
|
+
// g-363 self-reconciling ledger (Ben's ask, 2026-07-16): persist the content
|
|
1493
|
+
// identity so a stranded receipt can ALWAYS be reconciled by content — even
|
|
1494
|
+
// months later, after the 60-min tx-cache has pruned. This is the durable
|
|
1495
|
+
// targetKey for findByContentKey; null for saves with no single content hash
|
|
1496
|
+
// (e.g. session deltas). Cheap to write, and it removes the biggest
|
|
1497
|
+
// "cannot learn content key" unresolvable bucket for good.
|
|
1498
|
+
contentHash: receipt.contentHash ?? receipt.content_hash ?? meta.contentHash ?? null,
|
|
1492
1499
|
surfaced: false
|
|
1493
1500
|
// has the IDE shown this entry yet?
|
|
1494
1501
|
};
|
|
@@ -3305,16 +3312,16 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
3305
3312
|
let resolvedHistoryPath = "~/.claude/projects/<project>/memory/session-history.md";
|
|
3306
3313
|
try {
|
|
3307
3314
|
const projectsDir = join10(homedir10(), ".claude", "projects");
|
|
3308
|
-
const { readdirSync: readdirSync3, statSync:
|
|
3315
|
+
const { readdirSync: readdirSync3, statSync: statSync7 } = await import("fs");
|
|
3309
3316
|
let newestProject = null;
|
|
3310
3317
|
let newestTime = 0;
|
|
3311
3318
|
for (const project of readdirSync3(projectsDir)) {
|
|
3312
3319
|
const projectPath = join10(projectsDir, project);
|
|
3313
3320
|
try {
|
|
3314
|
-
if (!
|
|
3321
|
+
if (!statSync7(projectPath).isDirectory()) continue;
|
|
3315
3322
|
for (const file of readdirSync3(projectPath)) {
|
|
3316
3323
|
if (!file.endsWith(".jsonl")) continue;
|
|
3317
|
-
const fStat =
|
|
3324
|
+
const fStat = statSync7(join10(projectPath, file));
|
|
3318
3325
|
if (fStat.mtimeMs > newestTime) {
|
|
3319
3326
|
newestTime = fStat.mtimeMs;
|
|
3320
3327
|
newestProject = projectPath;
|
|
@@ -3887,11 +3894,12 @@ __export(recall_index_exports, {
|
|
|
3887
3894
|
readGaps: () => readGaps,
|
|
3888
3895
|
readIndex: () => readIndex,
|
|
3889
3896
|
redactSecrets: () => redactSecrets,
|
|
3897
|
+
resetGaps: () => resetGaps,
|
|
3890
3898
|
resolveSaveType: () => resolveSaveType,
|
|
3891
3899
|
searchIndex: () => searchIndex,
|
|
3892
3900
|
searchIndexSemantic: () => searchIndexSemantic
|
|
3893
3901
|
});
|
|
3894
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, appendFileSync as appendFileSync3 } from "fs";
|
|
3902
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8, appendFileSync as appendFileSync3 } from "fs";
|
|
3895
3903
|
import { join as join18 } from "path";
|
|
3896
3904
|
import { homedir as homedir15 } from "os";
|
|
3897
3905
|
function ensureDirs() {
|
|
@@ -3961,6 +3969,33 @@ function readIndex(address) {
|
|
|
3961
3969
|
function readGaps(address) {
|
|
3962
3970
|
return readJsonlMap(gapsPath(address));
|
|
3963
3971
|
}
|
|
3972
|
+
async function resetGaps(address, reasons = []) {
|
|
3973
|
+
const set = new Set(reasons);
|
|
3974
|
+
let removed = 0;
|
|
3975
|
+
await withFileLock(gapsLock(address), () => {
|
|
3976
|
+
let raw;
|
|
3977
|
+
try {
|
|
3978
|
+
raw = readFileSync12(gapsPath(address), "utf8");
|
|
3979
|
+
} catch {
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
const kept = [];
|
|
3983
|
+
for (const line of raw.split("\n")) {
|
|
3984
|
+
if (!line.trim()) continue;
|
|
3985
|
+
let r;
|
|
3986
|
+
try {
|
|
3987
|
+
r = JSON.parse(line);
|
|
3988
|
+
} catch {
|
|
3989
|
+
kept.push(line);
|
|
3990
|
+
continue;
|
|
3991
|
+
}
|
|
3992
|
+
if (set.has(r.reason)) removed++;
|
|
3993
|
+
else kept.push(line);
|
|
3994
|
+
}
|
|
3995
|
+
if (removed) writeFileSync8(gapsPath(address), kept.length ? kept.join("\n") + "\n" : "");
|
|
3996
|
+
});
|
|
3997
|
+
return removed;
|
|
3998
|
+
}
|
|
3964
3999
|
async function appendJsonl(path, lockPath, records) {
|
|
3965
4000
|
if (!records.length) return;
|
|
3966
4001
|
ensureDirs();
|
|
@@ -4186,81 +4221,6 @@ var init_recall_index = __esm({
|
|
|
4186
4221
|
}
|
|
4187
4222
|
});
|
|
4188
4223
|
|
|
4189
|
-
// mcp-server/lib/semantic/fetch-pack.js
|
|
4190
|
-
var fetch_pack_exports = {};
|
|
4191
|
-
__export(fetch_pack_exports, {
|
|
4192
|
-
fetchPack: () => fetchPack
|
|
4193
|
-
});
|
|
4194
|
-
import { createHash as createHash5 } from "crypto";
|
|
4195
|
-
import { createWriteStream, existsSync as existsSync18, mkdirSync as mkdirSync8, renameSync as renameSync3, rmSync, statSync as statSync3 } from "fs";
|
|
4196
|
-
import { join as join20 } from "path";
|
|
4197
|
-
async function sha256File(path) {
|
|
4198
|
-
const { createReadStream: createReadStream3 } = await import("fs");
|
|
4199
|
-
return new Promise((resolve3, reject) => {
|
|
4200
|
-
const h = createHash5("sha256");
|
|
4201
|
-
createReadStream3(path).on("data", (d) => h.update(d)).on("end", () => resolve3(h.digest("hex"))).on("error", reject);
|
|
4202
|
-
});
|
|
4203
|
-
}
|
|
4204
|
-
async function fetchToTmp(url, tmpPath, timeoutMs) {
|
|
4205
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: "follow" });
|
|
4206
|
-
if (!res.ok || !res.body) throw new PackError("FETCH_FAIL", `HTTP ${res.status} for ${url}`);
|
|
4207
|
-
const { Readable } = await import("stream");
|
|
4208
|
-
const { pipeline } = await import("stream/promises");
|
|
4209
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(tmpPath));
|
|
4210
|
-
}
|
|
4211
|
-
async function fetchPack({ profile = PROFILE, timeoutMs = 3e5, onProgress = null } = {}) {
|
|
4212
|
-
const dir = packDir(profile);
|
|
4213
|
-
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
4214
|
-
const fetched = [];
|
|
4215
|
-
const skipped = [];
|
|
4216
|
-
for (const f of PACK_FILES) {
|
|
4217
|
-
const finalPath = join20(dir, f.name);
|
|
4218
|
-
if (existsSync18(finalPath) && statSync3(finalPath).size === f.bytes) {
|
|
4219
|
-
skipped.push(f.name);
|
|
4220
|
-
continue;
|
|
4221
|
-
}
|
|
4222
|
-
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
4223
|
-
try {
|
|
4224
|
-
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "fetching" });
|
|
4225
|
-
await fetchToTmp(f.url, tmpPath, timeoutMs);
|
|
4226
|
-
const gotHash = await sha256File(tmpPath);
|
|
4227
|
-
if (gotHash !== f.sha256) {
|
|
4228
|
-
throw new PackError(
|
|
4229
|
-
"HASH_MISMATCH",
|
|
4230
|
-
`${f.name}: expected ${f.sha256.slice(0, 12)}\u2026, got ${gotHash.slice(0, 12)}\u2026 \u2014 refusing (upstream drifted or tampered)`
|
|
4231
|
-
);
|
|
4232
|
-
}
|
|
4233
|
-
const gotSize = statSync3(tmpPath).size;
|
|
4234
|
-
if (gotSize !== f.bytes) {
|
|
4235
|
-
throw new PackError("HASH_MISMATCH", `${f.name}: expected ${f.bytes} bytes, got ${gotSize}`);
|
|
4236
|
-
}
|
|
4237
|
-
renameSync3(tmpPath, finalPath);
|
|
4238
|
-
fetched.push(f.name);
|
|
4239
|
-
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "verified" });
|
|
4240
|
-
} catch (err) {
|
|
4241
|
-
try {
|
|
4242
|
-
rmSync(tmpPath, { force: true });
|
|
4243
|
-
} catch {
|
|
4244
|
-
}
|
|
4245
|
-
if (err instanceof PackError) throw err;
|
|
4246
|
-
throw new PackError("FETCH_FAIL", `${f.name}: ${err.message}`);
|
|
4247
|
-
}
|
|
4248
|
-
}
|
|
4249
|
-
return { ok: true, fetched, skipped, dir };
|
|
4250
|
-
}
|
|
4251
|
-
var PackError;
|
|
4252
|
-
var init_fetch_pack = __esm({
|
|
4253
|
-
"mcp-server/lib/semantic/fetch-pack.js"() {
|
|
4254
|
-
init_manifest();
|
|
4255
|
-
PackError = class extends Error {
|
|
4256
|
-
constructor(code, message) {
|
|
4257
|
-
super(message);
|
|
4258
|
-
this.code = code;
|
|
4259
|
-
}
|
|
4260
|
-
};
|
|
4261
|
-
}
|
|
4262
|
-
});
|
|
4263
|
-
|
|
4264
4224
|
// mcp-server/lib/semantic/map-model.js
|
|
4265
4225
|
function computeMapLayout(dated) {
|
|
4266
4226
|
const n = dated.length;
|
|
@@ -4373,17 +4333,37 @@ function computeMapLayout(dated) {
|
|
|
4373
4333
|
};
|
|
4374
4334
|
const globalTF = /* @__PURE__ */ new Map();
|
|
4375
4335
|
for (const s of dated) for (const w of grams(s.title)) globalTF.set(w, (globalTF.get(w) || 0) + 1);
|
|
4376
|
-
function nameOf(members, nTerms) {
|
|
4336
|
+
function nameOf(members, nTerms, allowFallback = false) {
|
|
4377
4337
|
const tf = /* @__PURE__ */ new Map();
|
|
4378
4338
|
for (const s of members) for (const w of grams(s.title)) tf.set(w, (tf.get(w) || 0) + 1);
|
|
4379
|
-
const
|
|
4339
|
+
const minF = Math.min(3, Math.max(1, Math.round(members.length * 0.2)));
|
|
4340
|
+
const scored = [...tf.entries()].filter(([w, f]) => f >= minF).map(([w, f]) => [w, (w.includes(" ") ? 1.6 : 1) * f * f / (globalTF.get(w) || 1)]).sort((a, b) => b[1] - a[1]);
|
|
4380
4341
|
const picked = [];
|
|
4381
4342
|
for (const [w] of scored) {
|
|
4382
4343
|
if (picked.some((p) => p.includes(w) || w.includes(p))) continue;
|
|
4383
4344
|
picked.push(w);
|
|
4384
4345
|
if (picked.length >= nTerms) break;
|
|
4385
4346
|
}
|
|
4386
|
-
return picked.join(" \xB7 ")
|
|
4347
|
+
if (picked.length) return picked.join(" \xB7 ");
|
|
4348
|
+
if (!allowFallback) return "(misc)";
|
|
4349
|
+
let best = "";
|
|
4350
|
+
let bestScore = -1;
|
|
4351
|
+
for (const s of members) {
|
|
4352
|
+
let sc = 0;
|
|
4353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4354
|
+
for (const w of grams(s.title)) {
|
|
4355
|
+
if (!seen.has(w)) {
|
|
4356
|
+
seen.add(w);
|
|
4357
|
+
sc += (tf.get(w) || 0) / (globalTF.get(w) || 1);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
if (sc > bestScore) {
|
|
4361
|
+
bestScore = sc;
|
|
4362
|
+
best = s.title || "";
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
const distinct = tok(best).sort((a, b) => (globalTF.get(a) || 1) - (globalTF.get(b) || 1)).slice(0, nTerms);
|
|
4366
|
+
return distinct.length ? distinct.join(" \xB7 ") : "(untitled)";
|
|
4387
4367
|
}
|
|
4388
4368
|
const centroidOf = (members) => ({
|
|
4389
4369
|
cx: members.reduce((a, s) => a + s.x, 0) / (members.length || 1),
|
|
@@ -4391,7 +4371,7 @@ function computeMapLayout(dated) {
|
|
|
4391
4371
|
cz: members.reduce((a, s) => a + s.z, 0) / (members.length || 1)
|
|
4392
4372
|
});
|
|
4393
4373
|
function kmeansLocal(members, k) {
|
|
4394
|
-
if (members.length < k *
|
|
4374
|
+
if (members.length < k * 3) return null;
|
|
4395
4375
|
const c0 = [members[Math.floor(rnd() * members.length)].v.slice()];
|
|
4396
4376
|
while (c0.length < k) {
|
|
4397
4377
|
const d = members.map((s) => Math.min(...c0.map((c) => dist2(s.v, c))));
|
|
@@ -4433,13 +4413,29 @@ function computeMapLayout(dated) {
|
|
|
4433
4413
|
const subLabels = [];
|
|
4434
4414
|
for (let c = 0; c < K; c++) {
|
|
4435
4415
|
const members = dated.filter((s) => s.c === c);
|
|
4436
|
-
const label = nameOf(
|
|
4437
|
-
|
|
4438
|
-
|
|
4416
|
+
const label = nameOf(
|
|
4417
|
+
members,
|
|
4418
|
+
3,
|
|
4419
|
+
/* allowFallback */
|
|
4420
|
+
true
|
|
4421
|
+
);
|
|
4422
|
+
const mDates = members.map((s) => s.date).sort();
|
|
4423
|
+
clusterInfo.push({
|
|
4424
|
+
c,
|
|
4425
|
+
label,
|
|
4426
|
+
count: members.length,
|
|
4427
|
+
member_txIds: members.map((s) => s.txId),
|
|
4428
|
+
first_touched: mDates.length ? mDates[0].slice(0, 10) : null,
|
|
4429
|
+
last_touched: mDates.length ? mDates[mDates.length - 1].slice(0, 10) : null,
|
|
4430
|
+
...centroidOf(members)
|
|
4431
|
+
});
|
|
4432
|
+
const subK = members.length >= 30 ? 3 : 2;
|
|
4433
|
+
const subFloor = members.length >= 75 ? 25 : Math.max(4, Math.round(members.length * 0.15));
|
|
4434
|
+
const sub = members.length >= 12 ? kmeansLocal(members, subK) : null;
|
|
4439
4435
|
if (sub) {
|
|
4440
|
-
for (let sc = 0; sc <
|
|
4436
|
+
for (let sc = 0; sc < subK; sc++) {
|
|
4441
4437
|
const m = members.filter((_, i) => sub[i] === sc);
|
|
4442
|
-
if (m.length <
|
|
4438
|
+
if (m.length < subFloor) continue;
|
|
4443
4439
|
const sl = nameOf(m, 2);
|
|
4444
4440
|
if (sl === "(misc)" || sl === label) continue;
|
|
4445
4441
|
subLabels.push({ label: sl, count: m.length, ...centroidOf(m) });
|
|
@@ -4475,8 +4471,15 @@ function computeMapLayout(dated) {
|
|
|
4475
4471
|
}
|
|
4476
4472
|
const hMax = Math.max(...heat);
|
|
4477
4473
|
for (let i = 0; i < n; i++) heat[i] = Math.log1p(heat[i]) / Math.log1p(hMax);
|
|
4478
|
-
const pts = dated.map((s, i) => ({ x: +s.X.toFixed(4), y: +s.Y.toFixed(4), z: +s.Z.toFixed(4), h: +heat[i].toFixed(3), m: s.date.slice(0, 7), d: s.date.slice(0, 10), t: s.title, c: s.c }));
|
|
4479
|
-
const nc = (c) => ({
|
|
4474
|
+
const pts = dated.map((s, i) => ({ id: s.txId, x: +s.X.toFixed(4), y: +s.Y.toFixed(4), z: +s.Z.toFixed(4), h: +heat[i].toFixed(3), m: s.date.slice(0, 7), d: s.date.slice(0, 10), t: s.title, c: s.c }));
|
|
4475
|
+
const nc = (c) => ({
|
|
4476
|
+
label: c.label,
|
|
4477
|
+
count: c.count,
|
|
4478
|
+
x: +NORM(c.cx, "x").toFixed(4),
|
|
4479
|
+
y: +NORM(c.cy, "y").toFixed(4),
|
|
4480
|
+
z: +NORM(c.cz, "z").toFixed(4),
|
|
4481
|
+
...c.member_txIds ? { member_txIds: c.member_txIds, first_touched: c.first_touched, last_touched: c.last_touched } : {}
|
|
4482
|
+
});
|
|
4480
4483
|
const cls = clusterInfo.map(nc);
|
|
4481
4484
|
const subs = subLabels.map(nc);
|
|
4482
4485
|
return {
|
|
@@ -4504,13 +4507,20 @@ var build_map_exports = {};
|
|
|
4504
4507
|
__export(build_map_exports, {
|
|
4505
4508
|
buildSemanticMap: () => buildSemanticMap
|
|
4506
4509
|
});
|
|
4507
|
-
import { readFileSync as
|
|
4508
|
-
import { join as
|
|
4509
|
-
import { homedir as
|
|
4510
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, readdirSync, statSync as statSync3, renameSync as renameSync3 } from "node:fs";
|
|
4511
|
+
import { join as join19, dirname as dirname4 } from "node:path";
|
|
4512
|
+
import { homedir as homedir16 } from "node:os";
|
|
4513
|
+
function persistModel(outPath, model) {
|
|
4514
|
+
const modelPath = join19(dirname4(outPath), "SEMANTIC_MAP.json");
|
|
4515
|
+
const tmp = `${modelPath}.tmp-${process.pid}`;
|
|
4516
|
+
writeFileSync9(tmp, JSON.stringify({ ...model, generated_at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4517
|
+
renameSync3(tmp, modelPath);
|
|
4518
|
+
return modelPath;
|
|
4519
|
+
}
|
|
4510
4520
|
function buildSemanticMap({
|
|
4511
4521
|
addr,
|
|
4512
|
-
dir =
|
|
4513
|
-
outPath =
|
|
4522
|
+
dir = join19(homedir16(), ".indelible", "recall-index"),
|
|
4523
|
+
outPath = join19(homedir16(), ".indelible", "SEMANTIC_MAP.html"),
|
|
4514
4524
|
log = () => {
|
|
4515
4525
|
}
|
|
4516
4526
|
} = {}) {
|
|
@@ -4521,7 +4531,7 @@ function buildSemanticMap({
|
|
|
4521
4531
|
} catch {
|
|
4522
4532
|
cands = [];
|
|
4523
4533
|
}
|
|
4524
|
-
const sized = cands.map((f) => ({ f, s:
|
|
4534
|
+
const sized = cands.map((f) => ({ f, s: statSync3(join19(dir, f)).size })).sort((a, b) => b.s - a.s);
|
|
4525
4535
|
if (!sized.length) {
|
|
4526
4536
|
const e = new Error("no .embeds.jsonl found \u2014 run `indelible-mcp semantic-fetch` first");
|
|
4527
4537
|
e.code = "NO_INDEX";
|
|
@@ -4532,7 +4542,7 @@ function buildSemanticMap({
|
|
|
4532
4542
|
log("address: " + addr);
|
|
4533
4543
|
let embedsRaw;
|
|
4534
4544
|
try {
|
|
4535
|
-
embedsRaw =
|
|
4545
|
+
embedsRaw = readFileSync14(join19(dir, addr + ".embeds.jsonl"), "utf8");
|
|
4536
4546
|
} catch {
|
|
4537
4547
|
const e = new Error(`no semantic index for ${addr} \u2014 run \`indelible-mcp semantic-fetch\` first`);
|
|
4538
4548
|
e.code = "NO_INDEX";
|
|
@@ -4574,7 +4584,7 @@ function buildSemanticMap({
|
|
|
4574
4584
|
log("sessions with vectors: " + sessions.length);
|
|
4575
4585
|
const meta = /* @__PURE__ */ new Map();
|
|
4576
4586
|
try {
|
|
4577
|
-
for (const line of
|
|
4587
|
+
for (const line of readFileSync14(join19(dir, addr + ".jsonl"), "utf8").split("\n")) {
|
|
4578
4588
|
if (!line.trim()) continue;
|
|
4579
4589
|
let r;
|
|
4580
4590
|
try {
|
|
@@ -4597,16 +4607,18 @@ function buildSemanticMap({
|
|
|
4597
4607
|
log(`dated: ${dated.length} | span: ${span.from} \u2192 ${span.to}`);
|
|
4598
4608
|
if (dated.length < MIN_FOR_MAP) {
|
|
4599
4609
|
const html2 = sparsePage(addr, sessions.length, dated.length);
|
|
4600
|
-
|
|
4610
|
+
writeFileSync9(outPath, html2);
|
|
4611
|
+
persistModel(outPath, { sparse: true, count: dated.length, span });
|
|
4601
4612
|
log(`wrote ${outPath} (sparse floor, ${dated.length}/${MIN_FOR_MAP})`);
|
|
4602
4613
|
return { path: outPath, sessions: sessions.length, dated: dated.length, K: 0, districts: 0, sparse: true, span };
|
|
4603
4614
|
}
|
|
4604
4615
|
log("PCA + k-means + density (shared compute)...");
|
|
4605
4616
|
const model = computeMapLayout(dated);
|
|
4606
4617
|
const html = renderHtml(model);
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4618
|
+
writeFileSync9(outPath, html);
|
|
4619
|
+
const modelPath = persistModel(outPath, model);
|
|
4620
|
+
log(`wrote ${outPath} (${Math.round(html.length / 1024)} KB) + ${modelPath}`);
|
|
4621
|
+
return { path: outPath, modelPath, sessions: sessions.length, dated: dated.length, K: model.K, districts: model.subs.length, sparse: false, span };
|
|
4610
4622
|
}
|
|
4611
4623
|
function sparsePage(addr, total, dated) {
|
|
4612
4624
|
return `<!doctype html><meta charset="utf-8"><title>Indelible \u2014 The Shape of the Work</title>
|
|
@@ -4670,8 +4682,8 @@ function draw(){
|
|
|
4670
4682
|
const vg=g.createRadialGradient(W/2,H/2,H*0.2,W/2,H/2,Math.max(W,H)*0.75);
|
|
4671
4683
|
vg.addColorStop(0,'#0e0a07');vg.addColorStop(1,'#070503');g.fillStyle=vg;g.fillRect(0,0,W,H);
|
|
4672
4684
|
projected=P.map(p=>({p,q:proj(p)}));
|
|
4673
|
-
// time thread
|
|
4674
|
-
g.strokeStyle='rgba(244,168,58,0.04)';g.beginPath();
|
|
4685
|
+
// time thread \u2014 opacity scales with map size (a few segments need more alpha than 5,000)
|
|
4686
|
+
g.strokeStyle='rgba(244,168,58,'+Math.min(0.28,Math.max(0.04,4/P.length))+')';g.beginPath();
|
|
4675
4687
|
projected.forEach((e,i)=>{i?g.lineTo(e.q.X,e.q.Y):g.moveTo(e.q.X,e.q.Y)});g.stroke();
|
|
4676
4688
|
// points: crisp everywhere. Heat = the DOT'S OWN color from the density ramp
|
|
4677
4689
|
// (no additive accumulation \u2014 that both lagged and blew the core out to mush).
|
|
@@ -4731,6 +4743,81 @@ var init_build_map = __esm({
|
|
|
4731
4743
|
}
|
|
4732
4744
|
});
|
|
4733
4745
|
|
|
4746
|
+
// mcp-server/lib/semantic/fetch-pack.js
|
|
4747
|
+
var fetch_pack_exports = {};
|
|
4748
|
+
__export(fetch_pack_exports, {
|
|
4749
|
+
fetchPack: () => fetchPack
|
|
4750
|
+
});
|
|
4751
|
+
import { createHash as createHash5 } from "crypto";
|
|
4752
|
+
import { createWriteStream, existsSync as existsSync19, mkdirSync as mkdirSync8, renameSync as renameSync4, rmSync, statSync as statSync5 } from "fs";
|
|
4753
|
+
import { join as join22 } from "path";
|
|
4754
|
+
async function sha256File(path) {
|
|
4755
|
+
const { createReadStream: createReadStream3 } = await import("fs");
|
|
4756
|
+
return new Promise((resolve3, reject) => {
|
|
4757
|
+
const h = createHash5("sha256");
|
|
4758
|
+
createReadStream3(path).on("data", (d) => h.update(d)).on("end", () => resolve3(h.digest("hex"))).on("error", reject);
|
|
4759
|
+
});
|
|
4760
|
+
}
|
|
4761
|
+
async function fetchToTmp(url, tmpPath, timeoutMs) {
|
|
4762
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: "follow" });
|
|
4763
|
+
if (!res.ok || !res.body) throw new PackError("FETCH_FAIL", `HTTP ${res.status} for ${url}`);
|
|
4764
|
+
const { Readable } = await import("stream");
|
|
4765
|
+
const { pipeline } = await import("stream/promises");
|
|
4766
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(tmpPath));
|
|
4767
|
+
}
|
|
4768
|
+
async function fetchPack({ profile = PROFILE, timeoutMs = 3e5, onProgress = null } = {}) {
|
|
4769
|
+
const dir = packDir(profile);
|
|
4770
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
4771
|
+
const fetched = [];
|
|
4772
|
+
const skipped = [];
|
|
4773
|
+
for (const f of PACK_FILES) {
|
|
4774
|
+
const finalPath = join22(dir, f.name);
|
|
4775
|
+
if (existsSync19(finalPath) && statSync5(finalPath).size === f.bytes) {
|
|
4776
|
+
skipped.push(f.name);
|
|
4777
|
+
continue;
|
|
4778
|
+
}
|
|
4779
|
+
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
4780
|
+
try {
|
|
4781
|
+
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "fetching" });
|
|
4782
|
+
await fetchToTmp(f.url, tmpPath, timeoutMs);
|
|
4783
|
+
const gotHash = await sha256File(tmpPath);
|
|
4784
|
+
if (gotHash !== f.sha256) {
|
|
4785
|
+
throw new PackError(
|
|
4786
|
+
"HASH_MISMATCH",
|
|
4787
|
+
`${f.name}: expected ${f.sha256.slice(0, 12)}\u2026, got ${gotHash.slice(0, 12)}\u2026 \u2014 refusing (upstream drifted or tampered)`
|
|
4788
|
+
);
|
|
4789
|
+
}
|
|
4790
|
+
const gotSize = statSync5(tmpPath).size;
|
|
4791
|
+
if (gotSize !== f.bytes) {
|
|
4792
|
+
throw new PackError("HASH_MISMATCH", `${f.name}: expected ${f.bytes} bytes, got ${gotSize}`);
|
|
4793
|
+
}
|
|
4794
|
+
renameSync4(tmpPath, finalPath);
|
|
4795
|
+
fetched.push(f.name);
|
|
4796
|
+
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "verified" });
|
|
4797
|
+
} catch (err) {
|
|
4798
|
+
try {
|
|
4799
|
+
rmSync(tmpPath, { force: true });
|
|
4800
|
+
} catch {
|
|
4801
|
+
}
|
|
4802
|
+
if (err instanceof PackError) throw err;
|
|
4803
|
+
throw new PackError("FETCH_FAIL", `${f.name}: ${err.message}`);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
return { ok: true, fetched, skipped, dir };
|
|
4807
|
+
}
|
|
4808
|
+
var PackError;
|
|
4809
|
+
var init_fetch_pack = __esm({
|
|
4810
|
+
"mcp-server/lib/semantic/fetch-pack.js"() {
|
|
4811
|
+
init_manifest();
|
|
4812
|
+
PackError = class extends Error {
|
|
4813
|
+
constructor(code, message) {
|
|
4814
|
+
super(message);
|
|
4815
|
+
this.code = code;
|
|
4816
|
+
}
|
|
4817
|
+
};
|
|
4818
|
+
}
|
|
4819
|
+
});
|
|
4820
|
+
|
|
4734
4821
|
// mcp-server/lib/timecard.js
|
|
4735
4822
|
var timecard_exports = {};
|
|
4736
4823
|
__export(timecard_exports, {
|
|
@@ -4787,10 +4874,10 @@ init_config_customer();
|
|
|
4787
4874
|
init_api_client();
|
|
4788
4875
|
import { createInterface as createInterface3 } from "node:readline";
|
|
4789
4876
|
import { execSync as execSync2 } from "node:child_process";
|
|
4790
|
-
import { homedir as
|
|
4791
|
-
import { join as
|
|
4877
|
+
import { homedir as homedir19 } from "node:os";
|
|
4878
|
+
import { join as join23, dirname as dirname6, resolve as resolve2 } from "node:path";
|
|
4792
4879
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4793
|
-
import { readFileSync as
|
|
4880
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync10, existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync2, statSync as statSync6 } from "node:fs";
|
|
4794
4881
|
|
|
4795
4882
|
// mcp-server/lib/guardrails-run.js
|
|
4796
4883
|
function contextFromInput(input) {
|
|
@@ -5543,7 +5630,27 @@ async function _run({ dir, key, address, contentKey, build, broadcast, checkConf
|
|
|
5543
5630
|
});
|
|
5544
5631
|
prune(dir, now()).catch((e) => process.stderr.write(`[build-cache] prune failed (non-fatal): ${e?.message}
|
|
5545
5632
|
`));
|
|
5546
|
-
|
|
5633
|
+
let write;
|
|
5634
|
+
try {
|
|
5635
|
+
write = await broadcast(fresh.txHex);
|
|
5636
|
+
} catch (err) {
|
|
5637
|
+
let seen = null;
|
|
5638
|
+
try {
|
|
5639
|
+
seen = await checkConfirmation2(fresh.txId);
|
|
5640
|
+
} catch {
|
|
5641
|
+
}
|
|
5642
|
+
if (seen && (seen.confirmed || seen.inMempool || seen.exists === true)) {
|
|
5643
|
+
process.stderr.write(`[g-363] broadcast ack failed but ${fresh.txId.slice(0, 12)}\u2026 is on-chain/mempool \u2014 slow-ack recovered, no rival (${err?.message || "timeout"})
|
|
5644
|
+
`);
|
|
5645
|
+
return {
|
|
5646
|
+
...fresh,
|
|
5647
|
+
write: { txid: fresh.txId, via: "chain-verified", source: "slow-ack-recovered", confirmed: !!seen.confirmed },
|
|
5648
|
+
reused: false,
|
|
5649
|
+
alreadyOnChain: true
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
throw err;
|
|
5653
|
+
}
|
|
5547
5654
|
if (write?.txid && fresh.txId && write.txid.toLowerCase() !== fresh.txId.toLowerCase()) {
|
|
5548
5655
|
process.stderr.write(`[g-363 TRIPWIRE] bridge-computed txid ${write.txid} differs from built txid ${fresh.txId} \u2014 possible transit mutation, receipt kept as built
|
|
5549
5656
|
`);
|
|
@@ -5800,7 +5907,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
5800
5907
|
}
|
|
5801
5908
|
const chunkCount = encrypted.length <= MAX_CHUNK_SIZE ? 1 : Math.ceil(encrypted.length / MAX_CHUNK_SIZE);
|
|
5802
5909
|
const receipt = buildReceipt(_finalWrite, { txId: masterTxId, fee: _saveFee, txSize: _finalSize });
|
|
5803
|
-
appendReceipt(receipt, { trigger: "interactive", saveType: "file" });
|
|
5910
|
+
appendReceipt(receipt, { trigger: "interactive", saveType: "file", contentHash: `sha256:${contentHash}` });
|
|
5804
5911
|
return {
|
|
5805
5912
|
success: true,
|
|
5806
5913
|
txId: masterTxId,
|
|
@@ -7064,22 +7171,25 @@ async function findByContentKey({
|
|
|
7064
7171
|
excludeTxids = [],
|
|
7065
7172
|
maxScan = 400,
|
|
7066
7173
|
confirmedOnly = false,
|
|
7067
|
-
stopAtFirst = true
|
|
7174
|
+
stopAtFirst = true,
|
|
7175
|
+
matchCap = 10
|
|
7068
7176
|
}) {
|
|
7069
|
-
if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false };
|
|
7177
|
+
if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
|
|
7070
7178
|
const exclude = new Set(excludeTxids.map((t) => String(t).toLowerCase()));
|
|
7071
7179
|
let history = [];
|
|
7072
7180
|
try {
|
|
7073
7181
|
history = await getHistory(address);
|
|
7074
7182
|
} catch {
|
|
7075
|
-
return { matches: [], scanned: 0, truncated: false, error: "history-unreachable" };
|
|
7183
|
+
return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "inconclusive-history-unreachable", error: "history-unreachable" };
|
|
7076
7184
|
}
|
|
7077
|
-
if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false };
|
|
7185
|
+
if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
|
|
7078
7186
|
const ordered = [...history].sort((a, b) => (b.height || 0) - (a.height || 0));
|
|
7079
7187
|
const truncated = ordered.length > maxScan;
|
|
7080
7188
|
const candidates = ordered.slice(0, maxScan);
|
|
7081
7189
|
const matches = [];
|
|
7082
7190
|
let scanned = 0;
|
|
7191
|
+
let fetchFailures = 0;
|
|
7192
|
+
let confirmedFound = false;
|
|
7083
7193
|
for (const h of candidates) {
|
|
7084
7194
|
const txid = (h.tx_hash || h.txid || "").toString();
|
|
7085
7195
|
if (!txid || exclude.has(txid.toLowerCase())) continue;
|
|
@@ -7089,23 +7199,62 @@ async function findByContentKey({
|
|
|
7089
7199
|
try {
|
|
7090
7200
|
payload = await fetchPayload(txid);
|
|
7091
7201
|
} catch {
|
|
7202
|
+
fetchFailures++;
|
|
7092
7203
|
continue;
|
|
7093
7204
|
}
|
|
7094
7205
|
if (payloadContentKey(payload) === targetKey) {
|
|
7095
|
-
|
|
7096
|
-
|
|
7206
|
+
const height = h.height || null;
|
|
7207
|
+
matches.push({ txid, height });
|
|
7208
|
+
if (height > 0) confirmedFound = true;
|
|
7209
|
+
if (stopAtFirst && confirmedFound) break;
|
|
7210
|
+
if (matches.length >= matchCap) break;
|
|
7097
7211
|
}
|
|
7098
7212
|
}
|
|
7099
|
-
|
|
7213
|
+
matches.sort((a, b) => {
|
|
7214
|
+
const ac = a.height > 0 ? 1 : 0, bc = b.height > 0 ? 1 : 0;
|
|
7215
|
+
if (ac !== bc) return bc - ac;
|
|
7216
|
+
return (b.height || 0) - (a.height || 0);
|
|
7217
|
+
});
|
|
7218
|
+
const result = stopAtFirst && matches.length ? [matches[0]] : matches;
|
|
7219
|
+
let classification;
|
|
7220
|
+
if (result.length) classification = "found";
|
|
7221
|
+
else if (truncated) classification = "inconclusive-truncated";
|
|
7222
|
+
else if (fetchFailures > 0) classification = "inconclusive-fetch-degraded";
|
|
7223
|
+
else classification = "absent";
|
|
7224
|
+
return { matches: result, scanned, truncated, fetchFailures, classification };
|
|
7100
7225
|
}
|
|
7101
7226
|
|
|
7102
7227
|
// mcp-server/tools/load_file.js
|
|
7103
7228
|
var TX_CACHE_DIR3 = join12(homedir12(), ".indelible", "tx-cache");
|
|
7229
|
+
var SAVE_LOG_PATH = join12(homedir12(), ".indelible", "save-log.jsonl");
|
|
7230
|
+
async function contentKeyFromSaveLog(txid) {
|
|
7231
|
+
try {
|
|
7232
|
+
const raw = await readFile6(SAVE_LOG_PATH, "utf-8");
|
|
7233
|
+
for (const line of raw.split("\n")) {
|
|
7234
|
+
if (!line.trim()) continue;
|
|
7235
|
+
let e;
|
|
7236
|
+
try {
|
|
7237
|
+
e = JSON.parse(line);
|
|
7238
|
+
} catch {
|
|
7239
|
+
continue;
|
|
7240
|
+
}
|
|
7241
|
+
if (e.txid === txid && e.contentHash) return String(e.contentHash).replace(/^sha256:/, "");
|
|
7242
|
+
}
|
|
7243
|
+
} catch {
|
|
7244
|
+
}
|
|
7245
|
+
return null;
|
|
7246
|
+
}
|
|
7104
7247
|
async function healStrandedTxid(strandedTxId, config) {
|
|
7105
7248
|
try {
|
|
7249
|
+
let targetKey = null;
|
|
7106
7250
|
const cachedRaw = await readFile6(join12(TX_CACHE_DIR3, `${strandedTxId.trim()}.json`), "utf-8").catch(() => null);
|
|
7107
|
-
if (
|
|
7108
|
-
|
|
7251
|
+
if (cachedRaw) {
|
|
7252
|
+
try {
|
|
7253
|
+
targetKey = payloadContentKey(JSON.parse(cachedRaw));
|
|
7254
|
+
} catch {
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
if (!targetKey) targetKey = await contentKeyFromSaveLog(strandedTxId.trim());
|
|
7109
7258
|
const address = config?.address;
|
|
7110
7259
|
if (!targetKey || !address) return null;
|
|
7111
7260
|
const { matches } = await findByContentKey({
|
|
@@ -8302,32 +8451,108 @@ async function recallContext({ from_date = null, to_date = null, query = null, d
|
|
|
8302
8451
|
};
|
|
8303
8452
|
}
|
|
8304
8453
|
|
|
8454
|
+
// mcp-server/tools/map_themes.js
|
|
8455
|
+
init_config_customer();
|
|
8456
|
+
import { readFileSync as readFileSync15, existsSync as existsSync17, statSync as statSync4 } from "fs";
|
|
8457
|
+
import { join as join20 } from "path";
|
|
8458
|
+
import { homedir as homedir17 } from "os";
|
|
8459
|
+
var MODEL_PATH = join20(homedir17(), ".indelible", "SEMANTIC_MAP.json");
|
|
8460
|
+
var TXIDS_OVERVIEW = 5;
|
|
8461
|
+
var TXIDS_DEEP = 60;
|
|
8462
|
+
async function mapThemes({ sample_titles = 5, sort = "size", theme = null } = {}) {
|
|
8463
|
+
const config = await loadConfig();
|
|
8464
|
+
const addr = config?.address;
|
|
8465
|
+
if (!addr) return { success: false, error: "No wallet configured \u2014 run setup_wallet first." };
|
|
8466
|
+
const indexPath2 = join20(homedir17(), ".indelible", "recall-index", `${addr}.jsonl`);
|
|
8467
|
+
let fresh = false;
|
|
8468
|
+
try {
|
|
8469
|
+
fresh = existsSync17(MODEL_PATH) && (!existsSync17(indexPath2) || statSync4(MODEL_PATH).mtimeMs >= statSync4(indexPath2).mtimeMs);
|
|
8470
|
+
} catch {
|
|
8471
|
+
}
|
|
8472
|
+
if (!fresh) {
|
|
8473
|
+
try {
|
|
8474
|
+
const { buildSemanticMap: buildSemanticMap2 } = await Promise.resolve().then(() => (init_build_map(), build_map_exports));
|
|
8475
|
+
buildSemanticMap2({ addr, log: () => {
|
|
8476
|
+
} });
|
|
8477
|
+
} catch (e) {
|
|
8478
|
+
if (e.code === "NO_INDEX") {
|
|
8479
|
+
return { success: false, error: "No semantic index yet. Run `indelible-mcp semantic-fetch` once in a terminal (one-time, ~50MB, builds your index + map locally), then ask again." };
|
|
8480
|
+
}
|
|
8481
|
+
return { success: false, error: `Could not build the map model: ${e.message}` };
|
|
8482
|
+
}
|
|
8483
|
+
}
|
|
8484
|
+
let model;
|
|
8485
|
+
try {
|
|
8486
|
+
model = JSON.parse(readFileSync15(MODEL_PATH, "utf8"));
|
|
8487
|
+
} catch (e) {
|
|
8488
|
+
return { success: false, error: `Could not read the map model (${e.message}) \u2014 run \`indelible-mcp map\` to regenerate it.` };
|
|
8489
|
+
}
|
|
8490
|
+
if (model.sparse) {
|
|
8491
|
+
return { success: true, sparse: true, mapped_sessions: model.count || 0, themes: [], message: `Only ${model.count || 0} mapped sessions so far \u2014 themes appear once a couple dozen are saved. The map grows as you save.` };
|
|
8492
|
+
}
|
|
8493
|
+
const now = Date.now();
|
|
8494
|
+
const nTitles = Math.max(0, Math.min(20, Number(sample_titles) || 5));
|
|
8495
|
+
const themeFilter = theme ? String(theme).toLowerCase() : null;
|
|
8496
|
+
const txidCap = themeFilter ? TXIDS_DEEP : TXIDS_OVERVIEW;
|
|
8497
|
+
let themes = model.cls.map((c, i) => {
|
|
8498
|
+
const members = model.pts.filter((p) => p.c === i).sort((a, b) => a.d < b.d ? 1 : -1);
|
|
8499
|
+
const last = c.last_touched || members[0]?.d || null;
|
|
8500
|
+
const txids = c.member_txIds ? [...c.member_txIds].reverse() : members.map((p) => p.id);
|
|
8501
|
+
return {
|
|
8502
|
+
theme: c.label,
|
|
8503
|
+
sessions: c.count,
|
|
8504
|
+
first_touched: c.first_touched || members[members.length - 1]?.d || null,
|
|
8505
|
+
last_touched: last,
|
|
8506
|
+
days_since_touched: last ? Math.max(0, Math.floor((now - Date.parse(last)) / 864e5)) : null,
|
|
8507
|
+
sample_titles: members.slice(0, nTitles).map((p) => `${p.d} \xB7 ${p.t || "(untitled)"}`),
|
|
8508
|
+
member_txids: txids.slice(0, txidCap),
|
|
8509
|
+
member_txids_truncated: txids.length > txidCap ? txids.length : false
|
|
8510
|
+
};
|
|
8511
|
+
});
|
|
8512
|
+
if (themeFilter) {
|
|
8513
|
+
const matched = themes.filter((t) => t.theme.toLowerCase().includes(themeFilter));
|
|
8514
|
+
if (!matched.length) {
|
|
8515
|
+
return { success: false, error: `No theme matches "${theme}". Themes: ${themes.map((t) => t.theme).join(" | ")}` };
|
|
8516
|
+
}
|
|
8517
|
+
themes = matched;
|
|
8518
|
+
}
|
|
8519
|
+
themes.sort(sort === "cold" ? (a, b) => (b.days_since_touched ?? -1) - (a.days_since_touched ?? -1) : (a, b) => b.sessions - a.sessions);
|
|
8520
|
+
return {
|
|
8521
|
+
success: true,
|
|
8522
|
+
generated_at: model.generated_at || null,
|
|
8523
|
+
mapped_sessions: model.count,
|
|
8524
|
+
span: model.span || null,
|
|
8525
|
+
themes,
|
|
8526
|
+
note: themeFilter ? 'To read this theme, call recall_context with depth:"full" and txids from member_txids (up to 20 per call, newest first).' : `Overview shows ${TXIDS_OVERVIEW} txids per theme. To drill a theme, call map_themes again with theme:"<name fragment>" for its ${TXIDS_DEEP} newest txids, then recall_context depth:"full" (\u226420 txids per call).`
|
|
8527
|
+
};
|
|
8528
|
+
}
|
|
8529
|
+
|
|
8305
8530
|
// mcp-server/tools/report_bug.js
|
|
8306
|
-
import { readFileSync as
|
|
8307
|
-
import { join as
|
|
8308
|
-
import { homedir as
|
|
8531
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
|
|
8532
|
+
import { join as join21, dirname as dirname5 } from "path";
|
|
8533
|
+
import { homedir as homedir18 } from "os";
|
|
8309
8534
|
import { fileURLToPath } from "url";
|
|
8310
8535
|
import os from "os";
|
|
8311
8536
|
var INTAKE_URL = process.env.INDELIBLE_BUG_INTAKE || "https://indelible.one/api/bug-report";
|
|
8312
|
-
var INDELIBLE_DIR =
|
|
8537
|
+
var INDELIBLE_DIR = join21(homedir18(), ".indelible");
|
|
8313
8538
|
function getMcpVersion(injected) {
|
|
8314
8539
|
if (injected) return injected;
|
|
8315
8540
|
const candidates = [];
|
|
8316
8541
|
try {
|
|
8317
|
-
candidates.push(
|
|
8542
|
+
candidates.push(join21(dirname5(fileURLToPath(import.meta.url)), "..", "..", "package.json"));
|
|
8318
8543
|
} catch {
|
|
8319
8544
|
}
|
|
8320
8545
|
try {
|
|
8321
|
-
candidates.push(
|
|
8546
|
+
candidates.push(join21(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
8322
8547
|
} catch {
|
|
8323
8548
|
}
|
|
8324
|
-
candidates.push(
|
|
8549
|
+
candidates.push(join21(homedir18(), "AppData", "Roaming", "npm", "node_modules", "indelible-mcp", "package.json"));
|
|
8325
8550
|
candidates.push("/usr/local/lib/node_modules/indelible-mcp/package.json");
|
|
8326
8551
|
candidates.push("/usr/lib/node_modules/indelible-mcp/package.json");
|
|
8327
8552
|
for (const p of candidates) {
|
|
8328
8553
|
try {
|
|
8329
|
-
if (!
|
|
8330
|
-
const pkg = JSON.parse(
|
|
8554
|
+
if (!existsSync18(p)) continue;
|
|
8555
|
+
const pkg = JSON.parse(readFileSync16(p, "utf8"));
|
|
8331
8556
|
if (pkg && pkg.name === "indelible-mcp" && pkg.version) return pkg.version;
|
|
8332
8557
|
} catch {
|
|
8333
8558
|
}
|
|
@@ -8336,7 +8561,7 @@ function getMcpVersion(injected) {
|
|
|
8336
8561
|
}
|
|
8337
8562
|
function readJson(path) {
|
|
8338
8563
|
try {
|
|
8339
|
-
return
|
|
8564
|
+
return existsSync18(path) ? JSON.parse(readFileSync16(path, "utf8")) : null;
|
|
8340
8565
|
} catch {
|
|
8341
8566
|
return null;
|
|
8342
8567
|
}
|
|
@@ -8346,9 +8571,9 @@ function getWalletAddress(config) {
|
|
|
8346
8571
|
}
|
|
8347
8572
|
function getRecentReceipts(n = 5) {
|
|
8348
8573
|
try {
|
|
8349
|
-
const p =
|
|
8350
|
-
if (!
|
|
8351
|
-
const lines =
|
|
8574
|
+
const p = join21(INDELIBLE_DIR, "save-log.jsonl");
|
|
8575
|
+
if (!existsSync18(p)) return [];
|
|
8576
|
+
const lines = readFileSync16(p, "utf8").trim().split("\n").filter(Boolean).slice(-n);
|
|
8352
8577
|
return lines.map((l) => {
|
|
8353
8578
|
try {
|
|
8354
8579
|
const e = JSON.parse(l);
|
|
@@ -8373,11 +8598,11 @@ function redactSecrets2(s) {
|
|
|
8373
8598
|
return redactCredentials(String(s));
|
|
8374
8599
|
}
|
|
8375
8600
|
async function reportBug(args2 = {}, mcpVersion) {
|
|
8376
|
-
const { summary, description, severity = "medium", tool_name = null, last_error = null } = args2;
|
|
8601
|
+
const { summary, description, severity = "medium", tool_name = null, last_error = null, still_file = false } = args2;
|
|
8377
8602
|
if (!summary || !description) {
|
|
8378
8603
|
return { success: false, error: "summary and description are required (you supply the narrative; the tool supplies the facts)." };
|
|
8379
8604
|
}
|
|
8380
|
-
const config = readJson(
|
|
8605
|
+
const config = readJson(join21(INDELIBLE_DIR, "config.json")) || {};
|
|
8381
8606
|
const payload = {
|
|
8382
8607
|
// AI-supplied narrative (defensively redacted + length-capped)
|
|
8383
8608
|
summary: redactSecrets2(summary).slice(0, 200),
|
|
@@ -8385,6 +8610,10 @@ async function reportBug(args2 = {}, mcpVersion) {
|
|
|
8385
8610
|
severity: ["low", "medium", "high"].includes(severity) ? severity : "medium",
|
|
8386
8611
|
tool_name: tool_name ? String(tool_name).slice(0, 80) : null,
|
|
8387
8612
|
last_error: redactSecrets2(last_error),
|
|
8613
|
+
// User override: "no, my case is genuinely different." When true, the intake
|
|
8614
|
+
// files a real ticket + pages the team even if the report matches a known,
|
|
8615
|
+
// already-fixed issue — so a false match can never trap a real report.
|
|
8616
|
+
still_file: still_file === true,
|
|
8388
8617
|
// Machine-pulled facts (the AI cannot get these wrong)
|
|
8389
8618
|
mcp_version: getMcpVersion(mcpVersion),
|
|
8390
8619
|
wallet_address: getWalletAddress(config),
|
|
@@ -8404,9 +8633,17 @@ async function reportBug(args2 = {}, mcpVersion) {
|
|
|
8404
8633
|
}
|
|
8405
8634
|
const data = await resp.json().catch(() => ({}));
|
|
8406
8635
|
if (data.known_issue) {
|
|
8407
|
-
return {
|
|
8636
|
+
return {
|
|
8637
|
+
success: true,
|
|
8638
|
+
status: "known_issue",
|
|
8639
|
+
issue_id: data.issue_id || null,
|
|
8640
|
+
message: data.message || "Matched a known, already-fixed issue.",
|
|
8641
|
+
// The team was already notified (the intake never swallows), but if the
|
|
8642
|
+
// narrative describes something genuinely different, the user can escalate.
|
|
8643
|
+
override_hint: "If this does not match your situation, call report_bug again with still_file: true to open a ticket and page the team anyway."
|
|
8644
|
+
};
|
|
8408
8645
|
}
|
|
8409
|
-
return { success: true, status: "filed", ticket_id: data.ticket_id || null, message: data.message || "Bug report sent to the Indelible team." };
|
|
8646
|
+
return { success: true, status: "filed", ticket_id: data.ticket_id || null, forced: !!data.forced, message: data.message || "Bug report sent to the Indelible team." };
|
|
8410
8647
|
} catch (err) {
|
|
8411
8648
|
return { success: false, error: `could not reach intake (${err.message}). Fallback: save_file the report on-chain and share the txid.` };
|
|
8412
8649
|
}
|
|
@@ -8647,16 +8884,16 @@ init_wall_stub();
|
|
|
8647
8884
|
init_wall_stub();
|
|
8648
8885
|
init_wall_stub();
|
|
8649
8886
|
init_wall_stub();
|
|
8650
|
-
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve2(
|
|
8651
|
-
var CONTEXT_FILE2 =
|
|
8887
|
+
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve2(dirname6(fileURLToPath2(import.meta.url)), "../..");
|
|
8888
|
+
var CONTEXT_FILE2 = join23(homedir19(), ".indelible", "indelible-context.jsonl");
|
|
8652
8889
|
function installHooks() {
|
|
8653
|
-
const claudeDir =
|
|
8654
|
-
const settingsPath =
|
|
8655
|
-
if (!
|
|
8890
|
+
const claudeDir = join23(homedir19(), ".claude");
|
|
8891
|
+
const settingsPath = join23(claudeDir, "settings.local.json");
|
|
8892
|
+
if (!existsSync20(claudeDir)) mkdirSync9(claudeDir, { recursive: true });
|
|
8656
8893
|
let settings = {};
|
|
8657
|
-
if (
|
|
8894
|
+
if (existsSync20(settingsPath)) {
|
|
8658
8895
|
try {
|
|
8659
|
-
settings = JSON.parse(
|
|
8896
|
+
settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
|
|
8660
8897
|
} catch {
|
|
8661
8898
|
settings = {};
|
|
8662
8899
|
}
|
|
@@ -8707,13 +8944,13 @@ function installHooks() {
|
|
|
8707
8944
|
});
|
|
8708
8945
|
installed.push("PreToolUse:guardrails");
|
|
8709
8946
|
}
|
|
8710
|
-
|
|
8947
|
+
writeFileSync10(settingsPath, JSON.stringify(settings, null, 2));
|
|
8711
8948
|
return { settingsPath, installed, alreadyInstalled: installed.length === 0 };
|
|
8712
8949
|
}
|
|
8713
8950
|
function runPreToolUseGuard() {
|
|
8714
8951
|
let code = 0;
|
|
8715
8952
|
try {
|
|
8716
|
-
const raw =
|
|
8953
|
+
const raw = readFileSync17(0, "utf8").trim();
|
|
8717
8954
|
if (!raw) process.exit(0);
|
|
8718
8955
|
const hit = evaluate(JSON.parse(raw));
|
|
8719
8956
|
if (hit) {
|
|
@@ -8913,6 +9150,7 @@ Commands:
|
|
|
8913
9150
|
const { packPresent: packPresent2 } = await Promise.resolve().then(() => (init_manifest(), manifest_exports));
|
|
8914
9151
|
const { fetchPack: fetchPack2 } = await Promise.resolve().then(() => (init_fetch_pack(), fetch_pack_exports));
|
|
8915
9152
|
const { rebuildVectorSidecar: rebuildVectorSidecar2 } = await Promise.resolve().then(() => (init_vec_store(), vec_store_exports));
|
|
9153
|
+
const { buildIndexWindow: buildIndexWindow2, resetGaps: resetGaps2 } = await Promise.resolve().then(() => (init_recall_index(), recall_index_exports));
|
|
8916
9154
|
try {
|
|
8917
9155
|
if (!packPresent2()) {
|
|
8918
9156
|
console.log("Fetching the semantic model pack (~50MB, one time, hash-verified)...");
|
|
@@ -8926,15 +9164,37 @@ Commands:
|
|
|
8926
9164
|
console.log(JSON.stringify({ success: false, error: "No wallet configured \u2014 run setup first." }));
|
|
8927
9165
|
break;
|
|
8928
9166
|
}
|
|
9167
|
+
const wif = await getWif();
|
|
9168
|
+
if (!wif) {
|
|
9169
|
+
console.log("Wallet is locked \u2014 cannot read your history to build the map/recall index yet. Run setup_wallet to unlock, then re-run. (Pack is ready; embedding whatever is already indexed.)");
|
|
9170
|
+
} else {
|
|
9171
|
+
const retried = await resetGaps2(cfg.address, ["FETCH_TIMEOUT", "NOT_ON_CHAIN", "WALLET_LOCKED"]);
|
|
9172
|
+
if (retried) console.log(` re-attempting ${retried} previously-skipped sessions...`);
|
|
9173
|
+
console.log(`Reading your on-chain history for ${cfg.address} (one-time, resumable, all local)...`);
|
|
9174
|
+
let indexed = 0;
|
|
9175
|
+
for (; ; ) {
|
|
9176
|
+
const r = await buildIndexWindow2(cfg.address, wif, { maxDecrypt: 150 });
|
|
9177
|
+
indexed += r.indexed;
|
|
9178
|
+
console.log(` indexed ${indexed} sessions; ${r.remaining} to go`);
|
|
9179
|
+
if (r.remaining === 0 || r.indexed === 0 && r.gapped === 0) break;
|
|
9180
|
+
}
|
|
9181
|
+
}
|
|
8929
9182
|
console.log(`Backfilling meaning-vectors for ${cfg.address} (resumable \u2014 re-run to continue)...`);
|
|
8930
|
-
let total = 0;
|
|
9183
|
+
let total = 0, cov = { vecs: 0, indexed: 0 };
|
|
8931
9184
|
for (; ; ) {
|
|
8932
9185
|
const r = await rebuildVectorSidecar2(cfg.address, { maxEmbed: 100 });
|
|
8933
9186
|
total += r.embedded;
|
|
8934
|
-
|
|
9187
|
+
cov = r.coverage || cov;
|
|
9188
|
+
console.log(` embedded ${total} sessions; remaining ${r.remaining}; coverage ${cov.vecs}/${cov.indexed}`);
|
|
8935
9189
|
if (r.remaining === 0 || r.embedded === 0) break;
|
|
8936
9190
|
}
|
|
8937
|
-
|
|
9191
|
+
if (cov.vecs > 0) {
|
|
9192
|
+
console.log(JSON.stringify({ success: true, message: "Semantic recall + map ready \u2014 run `indelible-mcp map` to see the shape of your work.", embedded: total, mapped: cov.vecs }));
|
|
9193
|
+
} else if (!wif) {
|
|
9194
|
+
console.log(JSON.stringify({ success: false, error: "Wallet is locked \u2014 nothing to map yet. Run setup_wallet to unlock, then re-run to build your map." }));
|
|
9195
|
+
} else {
|
|
9196
|
+
console.log(JSON.stringify({ success: true, message: "No saved sessions to map yet \u2014 your map grows as you save. Save a few conversations, then re-run this.", embedded: 0, mapped: 0 }));
|
|
9197
|
+
}
|
|
8938
9198
|
} catch (e) {
|
|
8939
9199
|
console.log(JSON.stringify({ success: false, error: e.code === "HASH_MISMATCH" ? `Integrity check failed: ${e.message}` : e.message }));
|
|
8940
9200
|
}
|
|
@@ -8942,9 +9202,9 @@ Commands:
|
|
|
8942
9202
|
}
|
|
8943
9203
|
case "map": {
|
|
8944
9204
|
const { buildSemanticMap: buildSemanticMap2 } = await Promise.resolve().then(() => (init_build_map(), build_map_exports));
|
|
8945
|
-
const { join:
|
|
8946
|
-
const { homedir:
|
|
8947
|
-
const out =
|
|
9205
|
+
const { join: join24 } = await import("node:path");
|
|
9206
|
+
const { homedir: homedir20 } = await import("node:os");
|
|
9207
|
+
const out = join24(homedir20(), ".indelible", "SEMANTIC_MAP.html");
|
|
8948
9208
|
try {
|
|
8949
9209
|
const cfg = await loadConfig();
|
|
8950
9210
|
const r = buildSemanticMap2({ addr: cfg?.address, outPath: out, log: (m) => process.stderr.write(`[map] ${m}
|
|
@@ -8958,7 +9218,7 @@ Commands:
|
|
|
8958
9218
|
${r.path}`);
|
|
8959
9219
|
}
|
|
8960
9220
|
} catch (e) {
|
|
8961
|
-
if (e.code === "NO_INDEX") console.log("No
|
|
9221
|
+
if (e.code === "NO_INDEX") console.log("No map yet. Run `indelible-mcp semantic-fetch` to build it from your saved history (one-time, ~50MB, all local). If you just ran that and still see this, you have not saved any sessions yet \u2014 your map grows as you save.");
|
|
8962
9222
|
else console.log(JSON.stringify({ success: false, error: e.message }));
|
|
8963
9223
|
}
|
|
8964
9224
|
break;
|
|
@@ -8969,7 +9229,7 @@ Commands:
|
|
|
8969
9229
|
}
|
|
8970
9230
|
function printHelp() {
|
|
8971
9231
|
console.log(`
|
|
8972
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.
|
|
9232
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.8)
|
|
8973
9233
|
|
|
8974
9234
|
Setup:
|
|
8975
9235
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -9018,19 +9278,19 @@ Learn more: https://indelible.one
|
|
|
9018
9278
|
`);
|
|
9019
9279
|
}
|
|
9020
9280
|
function findNewestTranscript() {
|
|
9021
|
-
const projectsDir =
|
|
9022
|
-
if (!
|
|
9281
|
+
const projectsDir = join23(homedir19(), ".claude", "projects");
|
|
9282
|
+
if (!existsSync20(projectsDir)) return null;
|
|
9023
9283
|
let newestTime = 0;
|
|
9024
9284
|
let newest = null;
|
|
9025
9285
|
try {
|
|
9026
9286
|
for (const project of readdirSync2(projectsDir)) {
|
|
9027
|
-
const projectPath =
|
|
9287
|
+
const projectPath = join23(projectsDir, project);
|
|
9028
9288
|
try {
|
|
9029
|
-
if (!
|
|
9289
|
+
if (!statSync6(projectPath).isDirectory()) continue;
|
|
9030
9290
|
for (const file of readdirSync2(projectPath)) {
|
|
9031
9291
|
if (!file.endsWith(".jsonl")) continue;
|
|
9032
|
-
const p =
|
|
9033
|
-
const t =
|
|
9292
|
+
const p = join23(projectPath, file);
|
|
9293
|
+
const t = statSync6(p).mtimeMs;
|
|
9034
9294
|
if (t > newestTime) {
|
|
9035
9295
|
newestTime = t;
|
|
9036
9296
|
newest = p;
|
|
@@ -9053,13 +9313,13 @@ async function printTimeCard() {
|
|
|
9053
9313
|
};
|
|
9054
9314
|
try {
|
|
9055
9315
|
const t = findNewestTranscript();
|
|
9056
|
-
if (t) f.lastSeen = phraseDelta2(Date.now() -
|
|
9316
|
+
if (t) f.lastSeen = phraseDelta2(Date.now() - statSync6(t).mtimeMs);
|
|
9057
9317
|
} catch {
|
|
9058
9318
|
}
|
|
9059
9319
|
try {
|
|
9060
|
-
const gPath =
|
|
9061
|
-
if (
|
|
9062
|
-
const g = JSON.parse(
|
|
9320
|
+
const gPath = join23(homedir19(), ".indelible", "goals.json");
|
|
9321
|
+
if (existsSync20(gPath)) {
|
|
9322
|
+
const g = JSON.parse(readFileSync17(gPath, "utf8"));
|
|
9063
9323
|
const active = (g.goals || []).filter((x) => x.status === "active");
|
|
9064
9324
|
if (active.length) {
|
|
9065
9325
|
const oldest = active.reduce((a, b) => new Date(a.created_at) < new Date(b.created_at) ? a : b);
|
|
@@ -9095,21 +9355,21 @@ async function runPreCompactSave() {
|
|
|
9095
9355
|
process.stderr.write("Indelible: MCP disabled, skipping save\n");
|
|
9096
9356
|
process.exit(0);
|
|
9097
9357
|
}
|
|
9098
|
-
const target = transcriptPath &&
|
|
9358
|
+
const target = transcriptPath && existsSync20(transcriptPath) ? transcriptPath : findNewestTranscript() || CONTEXT_FILE2;
|
|
9099
9359
|
const result = await saveSession(target, `Auto-save before ${trigger} compaction`);
|
|
9100
9360
|
if (result.success) {
|
|
9101
9361
|
process.stderr.write(`Indelible: Saved ${result.newMessages} messages (${result.saveType}) tx:${result.txId?.slice(0, 12)}...
|
|
9102
9362
|
`);
|
|
9103
9363
|
} else if (result.gated && result.upsell) {
|
|
9104
|
-
const upsellMarker =
|
|
9364
|
+
const upsellMarker = join23(homedir19(), ".indelible", ".upsell-shown");
|
|
9105
9365
|
let shownRecently = false;
|
|
9106
9366
|
try {
|
|
9107
|
-
shownRecently = Date.now() -
|
|
9367
|
+
shownRecently = Date.now() - statSync6(upsellMarker).mtimeMs < 24 * 60 * 60 * 1e3;
|
|
9108
9368
|
} catch {
|
|
9109
9369
|
}
|
|
9110
9370
|
if (!shownRecently) {
|
|
9111
9371
|
try {
|
|
9112
|
-
|
|
9372
|
+
writeFileSync10(upsellMarker, (/* @__PURE__ */ new Date()).toISOString());
|
|
9113
9373
|
} catch {
|
|
9114
9374
|
}
|
|
9115
9375
|
process.stderr.write("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
@@ -9131,20 +9391,20 @@ async function runPreCompactSave() {
|
|
|
9131
9391
|
process.exit(0);
|
|
9132
9392
|
}
|
|
9133
9393
|
function findMemoryDir() {
|
|
9134
|
-
const projectsDir =
|
|
9135
|
-
if (!
|
|
9394
|
+
const projectsDir = join23(homedir19(), ".claude", "projects");
|
|
9395
|
+
if (!existsSync20(projectsDir)) return null;
|
|
9136
9396
|
let newestTime = 0;
|
|
9137
9397
|
let newestProject = null;
|
|
9138
9398
|
const projects = readdirSync2(projectsDir);
|
|
9139
9399
|
for (const project of projects) {
|
|
9140
|
-
const projectPath =
|
|
9400
|
+
const projectPath = join23(projectsDir, project);
|
|
9141
9401
|
try {
|
|
9142
|
-
const pStat =
|
|
9402
|
+
const pStat = statSync6(projectPath);
|
|
9143
9403
|
if (!pStat.isDirectory()) continue;
|
|
9144
9404
|
const files = readdirSync2(projectPath);
|
|
9145
9405
|
for (const file of files) {
|
|
9146
9406
|
if (!file.endsWith(".jsonl")) continue;
|
|
9147
|
-
const fStat =
|
|
9407
|
+
const fStat = statSync6(join23(projectPath, file));
|
|
9148
9408
|
if (fStat.mtimeMs > newestTime) {
|
|
9149
9409
|
newestTime = fStat.mtimeMs;
|
|
9150
9410
|
newestProject = projectPath;
|
|
@@ -9153,7 +9413,7 @@ function findMemoryDir() {
|
|
|
9153
9413
|
} catch {
|
|
9154
9414
|
}
|
|
9155
9415
|
}
|
|
9156
|
-
return newestProject ?
|
|
9416
|
+
return newestProject ? join23(newestProject, "memory") : null;
|
|
9157
9417
|
}
|
|
9158
9418
|
async function runPostCompactRestore() {
|
|
9159
9419
|
await printTimeCard();
|
|
@@ -9203,10 +9463,10 @@ async function runPostCompactRestore() {
|
|
|
9203
9463
|
try {
|
|
9204
9464
|
const memoryDir = findMemoryDir();
|
|
9205
9465
|
if (memoryDir && (config.memory_file_txid || config.session_history_txid)) {
|
|
9206
|
-
if (!
|
|
9466
|
+
if (!existsSync20(memoryDir)) mkdirSync9(memoryDir, { recursive: true });
|
|
9207
9467
|
if (config.memory_file_txid) {
|
|
9208
|
-
const memPath =
|
|
9209
|
-
if (!
|
|
9468
|
+
const memPath = join23(memoryDir, "MEMORY.md");
|
|
9469
|
+
if (!existsSync20(memPath)) {
|
|
9210
9470
|
const memResult = await loadFile(config.memory_file_txid, { outputPath: memPath });
|
|
9211
9471
|
if (memResult.success) {
|
|
9212
9472
|
process.stderr.write(`Indelible: MEMORY.md restored from chain (file was missing)
|
|
@@ -9215,8 +9475,8 @@ async function runPostCompactRestore() {
|
|
|
9215
9475
|
}
|
|
9216
9476
|
}
|
|
9217
9477
|
if (config.session_history_txid) {
|
|
9218
|
-
const histPath =
|
|
9219
|
-
if (!
|
|
9478
|
+
const histPath = join23(memoryDir, "session-history.md");
|
|
9479
|
+
if (!existsSync20(histPath)) {
|
|
9220
9480
|
const histResult = await loadFile(config.session_history_txid, { outputPath: histPath });
|
|
9221
9481
|
if (histResult.success) {
|
|
9222
9482
|
process.stderr.write(`Indelible: session-history.md restored from chain (file was missing)
|
|
@@ -9232,9 +9492,9 @@ async function runPostCompactRestore() {
|
|
|
9232
9492
|
try {
|
|
9233
9493
|
const histDir = findMemoryDir();
|
|
9234
9494
|
if (histDir) {
|
|
9235
|
-
const histPath =
|
|
9236
|
-
if (
|
|
9237
|
-
const histContent =
|
|
9495
|
+
const histPath = join23(histDir, "session-history.md");
|
|
9496
|
+
if (existsSync20(histPath)) {
|
|
9497
|
+
const histContent = readFileSync17(histPath, "utf-8");
|
|
9238
9498
|
const lines = histContent.split("\n");
|
|
9239
9499
|
const entryStarts = [];
|
|
9240
9500
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9276,7 +9536,7 @@ function readStdin() {
|
|
|
9276
9536
|
}
|
|
9277
9537
|
var SERVER_INFO = {
|
|
9278
9538
|
name: "indelible",
|
|
9279
|
-
version: "4.9.
|
|
9539
|
+
version: "4.9.8",
|
|
9280
9540
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
9281
9541
|
};
|
|
9282
9542
|
var TOOLS = [
|
|
@@ -9579,6 +9839,19 @@ var TOOLS = [
|
|
|
9579
9839
|
required: []
|
|
9580
9840
|
}
|
|
9581
9841
|
},
|
|
9842
|
+
{
|
|
9843
|
+
name: "map_themes",
|
|
9844
|
+
description: 'The table of contents of your mind: the semantic map THEME structure as data \u2014 every theme (continent) with label, session count, first/last touched, days since touched, sample titles, and member txids. Use it to answer "what have I been working on", "which themes have gone cold", then drill into a theme with recall_context depth:"full" txids:[...member_txids] (up to 20 per call). Reads the locally persisted map model (single source with the rendered map); 100% local, nothing leaves the machine. Requires the semantic index (`indelible-mcp semantic-fetch`, one-time).',
|
|
9845
|
+
inputSchema: {
|
|
9846
|
+
type: "object",
|
|
9847
|
+
properties: {
|
|
9848
|
+
sample_titles: { type: "number", description: "How many recent titles to include per theme (default 5, max 20)." },
|
|
9849
|
+
sort: { type: "string", enum: ["size", "cold"], description: '"size" (default) = biggest themes first; "cold" = longest-untouched first.' },
|
|
9850
|
+
theme: { type: "string", description: "Drill ONE theme: a fragment of its name (case-insensitive). Returns just that theme with its 60 newest member txids (the overview shows 5 per theme)." }
|
|
9851
|
+
},
|
|
9852
|
+
required: []
|
|
9853
|
+
}
|
|
9854
|
+
},
|
|
9582
9855
|
{
|
|
9583
9856
|
name: "report_bug",
|
|
9584
9857
|
description: `File an ACCURATE bug report to the Indelible team. You supply ONLY the narrative (summary, description, severity, optional tool_name/last_error). The tool auto-attaches the real MCP version, wallet address, recent save receipts, and environment \u2014 so DO NOT invent versions, txids, URLs, or destinations; leave the facts to the tool. Before filing: (1) triage transients \u2014 retry "No UTXOs"/"gateway unreachable" blips (usually an unconfirmed change UTXO) before reporting; (2) do not file known/already-fixed issues (the server auto-matches these); (3) get the user's OK first. Returns status:"filed" (ticket_id) or status:"known_issue" (instant triage).`,
|
|
@@ -9589,7 +9862,8 @@ var TOOLS = [
|
|
|
9589
9862
|
description: { type: "string", description: "What you were doing, what went wrong, and exact repro steps." },
|
|
9590
9863
|
severity: { type: "string", enum: ["low", "medium", "high"], description: "User-facing impact." },
|
|
9591
9864
|
tool_name: { type: "string", description: "Which Indelible tool/command failed (optional)." },
|
|
9592
|
-
last_error: { type: "string", description: "The exact error string you saw (optional)." }
|
|
9865
|
+
last_error: { type: "string", description: "The exact error string you saw (optional)." },
|
|
9866
|
+
still_file: { type: "boolean", description: "Set true ONLY to override a known-issue match \u2014 files a ticket and pages the team even if the report looks like an already-fixed issue. Use when the user says their case is genuinely different." }
|
|
9593
9867
|
},
|
|
9594
9868
|
required: ["summary", "description"]
|
|
9595
9869
|
}
|
|
@@ -9735,13 +10009,17 @@ async function handleMcpRequest(request) {
|
|
|
9735
10009
|
ranking: args2?.ranking || "blend"
|
|
9736
10010
|
});
|
|
9737
10011
|
break;
|
|
10012
|
+
case "map_themes":
|
|
10013
|
+
result = await mapThemes({ sample_titles: args2?.sample_titles, sort: args2?.sort || "size", theme: args2?.theme || null });
|
|
10014
|
+
break;
|
|
9738
10015
|
case "report_bug":
|
|
9739
10016
|
result = await reportBug({
|
|
9740
10017
|
summary: args2?.summary,
|
|
9741
10018
|
description: args2?.description,
|
|
9742
10019
|
severity: args2?.severity || "medium",
|
|
9743
10020
|
tool_name: args2?.tool_name || null,
|
|
9744
|
-
last_error: args2?.last_error || null
|
|
10021
|
+
last_error: args2?.last_error || null,
|
|
10022
|
+
still_file: args2?.still_file === true
|
|
9745
10023
|
});
|
|
9746
10024
|
break;
|
|
9747
10025
|
case "share_session":
|
|
@@ -9838,11 +10116,11 @@ async function runWizard() {
|
|
|
9838
10116
|
}
|
|
9839
10117
|
let mcpOk = false;
|
|
9840
10118
|
for (const cfgPath of [
|
|
9841
|
-
|
|
9842
|
-
|
|
10119
|
+
join23(homedir19(), ".claude.json"),
|
|
10120
|
+
join23(homedir19(), ".claude", "settings.json")
|
|
9843
10121
|
]) {
|
|
9844
10122
|
try {
|
|
9845
|
-
const s = JSON.parse(
|
|
10123
|
+
const s = JSON.parse(readFileSync17(cfgPath, "utf8"));
|
|
9846
10124
|
if (s?.mcpServers?.indelible) {
|
|
9847
10125
|
mcpOk = true;
|
|
9848
10126
|
break;
|
|
@@ -9852,10 +10130,10 @@ async function runWizard() {
|
|
|
9852
10130
|
}
|
|
9853
10131
|
console.log(mcpOk ? " \u2713 MCP registered with Claude Code" : " \u2717 MCP not registered");
|
|
9854
10132
|
if (!mcpOk) allGood = false;
|
|
9855
|
-
const hooksPath =
|
|
10133
|
+
const hooksPath = join23(homedir19(), ".claude", "settings.local.json");
|
|
9856
10134
|
let hooksOk = false;
|
|
9857
10135
|
try {
|
|
9858
|
-
const s = JSON.parse(
|
|
10136
|
+
const s = JSON.parse(readFileSync17(hooksPath, "utf8"));
|
|
9859
10137
|
hooksOk = s?.hooks?.PreCompact?.some(
|
|
9860
10138
|
(h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp")) || h.command?.includes("indelible-mcp")
|
|
9861
10139
|
) && s?.hooks?.SessionStart?.some(
|