indelible-mcp 4.9.6 → 4.9.7
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 +330 -162
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -3305,16 +3305,16 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
3305
3305
|
let resolvedHistoryPath = "~/.claude/projects/<project>/memory/session-history.md";
|
|
3306
3306
|
try {
|
|
3307
3307
|
const projectsDir = join10(homedir10(), ".claude", "projects");
|
|
3308
|
-
const { readdirSync: readdirSync3, statSync:
|
|
3308
|
+
const { readdirSync: readdirSync3, statSync: statSync7 } = await import("fs");
|
|
3309
3309
|
let newestProject = null;
|
|
3310
3310
|
let newestTime = 0;
|
|
3311
3311
|
for (const project of readdirSync3(projectsDir)) {
|
|
3312
3312
|
const projectPath = join10(projectsDir, project);
|
|
3313
3313
|
try {
|
|
3314
|
-
if (!
|
|
3314
|
+
if (!statSync7(projectPath).isDirectory()) continue;
|
|
3315
3315
|
for (const file of readdirSync3(projectPath)) {
|
|
3316
3316
|
if (!file.endsWith(".jsonl")) continue;
|
|
3317
|
-
const fStat =
|
|
3317
|
+
const fStat = statSync7(join10(projectPath, file));
|
|
3318
3318
|
if (fStat.mtimeMs > newestTime) {
|
|
3319
3319
|
newestTime = fStat.mtimeMs;
|
|
3320
3320
|
newestProject = projectPath;
|
|
@@ -3887,11 +3887,12 @@ __export(recall_index_exports, {
|
|
|
3887
3887
|
readGaps: () => readGaps,
|
|
3888
3888
|
readIndex: () => readIndex,
|
|
3889
3889
|
redactSecrets: () => redactSecrets,
|
|
3890
|
+
resetGaps: () => resetGaps,
|
|
3890
3891
|
resolveSaveType: () => resolveSaveType,
|
|
3891
3892
|
searchIndex: () => searchIndex,
|
|
3892
3893
|
searchIndexSemantic: () => searchIndexSemantic
|
|
3893
3894
|
});
|
|
3894
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, appendFileSync as appendFileSync3 } from "fs";
|
|
3895
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8, appendFileSync as appendFileSync3 } from "fs";
|
|
3895
3896
|
import { join as join18 } from "path";
|
|
3896
3897
|
import { homedir as homedir15 } from "os";
|
|
3897
3898
|
function ensureDirs() {
|
|
@@ -3961,6 +3962,33 @@ function readIndex(address) {
|
|
|
3961
3962
|
function readGaps(address) {
|
|
3962
3963
|
return readJsonlMap(gapsPath(address));
|
|
3963
3964
|
}
|
|
3965
|
+
async function resetGaps(address, reasons = []) {
|
|
3966
|
+
const set = new Set(reasons);
|
|
3967
|
+
let removed = 0;
|
|
3968
|
+
await withFileLock(gapsLock(address), () => {
|
|
3969
|
+
let raw;
|
|
3970
|
+
try {
|
|
3971
|
+
raw = readFileSync12(gapsPath(address), "utf8");
|
|
3972
|
+
} catch {
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
const kept = [];
|
|
3976
|
+
for (const line of raw.split("\n")) {
|
|
3977
|
+
if (!line.trim()) continue;
|
|
3978
|
+
let r;
|
|
3979
|
+
try {
|
|
3980
|
+
r = JSON.parse(line);
|
|
3981
|
+
} catch {
|
|
3982
|
+
kept.push(line);
|
|
3983
|
+
continue;
|
|
3984
|
+
}
|
|
3985
|
+
if (set.has(r.reason)) removed++;
|
|
3986
|
+
else kept.push(line);
|
|
3987
|
+
}
|
|
3988
|
+
if (removed) writeFileSync8(gapsPath(address), kept.length ? kept.join("\n") + "\n" : "");
|
|
3989
|
+
});
|
|
3990
|
+
return removed;
|
|
3991
|
+
}
|
|
3964
3992
|
async function appendJsonl(path, lockPath, records) {
|
|
3965
3993
|
if (!records.length) return;
|
|
3966
3994
|
ensureDirs();
|
|
@@ -4186,81 +4214,6 @@ var init_recall_index = __esm({
|
|
|
4186
4214
|
}
|
|
4187
4215
|
});
|
|
4188
4216
|
|
|
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
4217
|
// mcp-server/lib/semantic/map-model.js
|
|
4265
4218
|
function computeMapLayout(dated) {
|
|
4266
4219
|
const n = dated.length;
|
|
@@ -4434,7 +4387,16 @@ function computeMapLayout(dated) {
|
|
|
4434
4387
|
for (let c = 0; c < K; c++) {
|
|
4435
4388
|
const members = dated.filter((s) => s.c === c);
|
|
4436
4389
|
const label = nameOf(members, 3);
|
|
4437
|
-
|
|
4390
|
+
const mDates = members.map((s) => s.date).sort();
|
|
4391
|
+
clusterInfo.push({
|
|
4392
|
+
c,
|
|
4393
|
+
label,
|
|
4394
|
+
count: members.length,
|
|
4395
|
+
member_txIds: members.map((s) => s.txId),
|
|
4396
|
+
first_touched: mDates.length ? mDates[0].slice(0, 10) : null,
|
|
4397
|
+
last_touched: mDates.length ? mDates[mDates.length - 1].slice(0, 10) : null,
|
|
4398
|
+
...centroidOf(members)
|
|
4399
|
+
});
|
|
4438
4400
|
const sub = kmeansLocal(members, 3);
|
|
4439
4401
|
if (sub) {
|
|
4440
4402
|
for (let sc = 0; sc < 3; sc++) {
|
|
@@ -4475,8 +4437,15 @@ function computeMapLayout(dated) {
|
|
|
4475
4437
|
}
|
|
4476
4438
|
const hMax = Math.max(...heat);
|
|
4477
4439
|
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) => ({
|
|
4440
|
+
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 }));
|
|
4441
|
+
const nc = (c) => ({
|
|
4442
|
+
label: c.label,
|
|
4443
|
+
count: c.count,
|
|
4444
|
+
x: +NORM(c.cx, "x").toFixed(4),
|
|
4445
|
+
y: +NORM(c.cy, "y").toFixed(4),
|
|
4446
|
+
z: +NORM(c.cz, "z").toFixed(4),
|
|
4447
|
+
...c.member_txIds ? { member_txIds: c.member_txIds, first_touched: c.first_touched, last_touched: c.last_touched } : {}
|
|
4448
|
+
});
|
|
4480
4449
|
const cls = clusterInfo.map(nc);
|
|
4481
4450
|
const subs = subLabels.map(nc);
|
|
4482
4451
|
return {
|
|
@@ -4504,13 +4473,20 @@ var build_map_exports = {};
|
|
|
4504
4473
|
__export(build_map_exports, {
|
|
4505
4474
|
buildSemanticMap: () => buildSemanticMap
|
|
4506
4475
|
});
|
|
4507
|
-
import { readFileSync as
|
|
4508
|
-
import { join as
|
|
4509
|
-
import { homedir as
|
|
4476
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, readdirSync, statSync as statSync3, renameSync as renameSync3 } from "node:fs";
|
|
4477
|
+
import { join as join19, dirname as dirname4 } from "node:path";
|
|
4478
|
+
import { homedir as homedir16 } from "node:os";
|
|
4479
|
+
function persistModel(outPath, model) {
|
|
4480
|
+
const modelPath = join19(dirname4(outPath), "SEMANTIC_MAP.json");
|
|
4481
|
+
const tmp = `${modelPath}.tmp-${process.pid}`;
|
|
4482
|
+
writeFileSync9(tmp, JSON.stringify({ ...model, generated_at: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4483
|
+
renameSync3(tmp, modelPath);
|
|
4484
|
+
return modelPath;
|
|
4485
|
+
}
|
|
4510
4486
|
function buildSemanticMap({
|
|
4511
4487
|
addr,
|
|
4512
|
-
dir =
|
|
4513
|
-
outPath =
|
|
4488
|
+
dir = join19(homedir16(), ".indelible", "recall-index"),
|
|
4489
|
+
outPath = join19(homedir16(), ".indelible", "SEMANTIC_MAP.html"),
|
|
4514
4490
|
log = () => {
|
|
4515
4491
|
}
|
|
4516
4492
|
} = {}) {
|
|
@@ -4521,7 +4497,7 @@ function buildSemanticMap({
|
|
|
4521
4497
|
} catch {
|
|
4522
4498
|
cands = [];
|
|
4523
4499
|
}
|
|
4524
|
-
const sized = cands.map((f) => ({ f, s:
|
|
4500
|
+
const sized = cands.map((f) => ({ f, s: statSync3(join19(dir, f)).size })).sort((a, b) => b.s - a.s);
|
|
4525
4501
|
if (!sized.length) {
|
|
4526
4502
|
const e = new Error("no .embeds.jsonl found \u2014 run `indelible-mcp semantic-fetch` first");
|
|
4527
4503
|
e.code = "NO_INDEX";
|
|
@@ -4532,7 +4508,7 @@ function buildSemanticMap({
|
|
|
4532
4508
|
log("address: " + addr);
|
|
4533
4509
|
let embedsRaw;
|
|
4534
4510
|
try {
|
|
4535
|
-
embedsRaw =
|
|
4511
|
+
embedsRaw = readFileSync14(join19(dir, addr + ".embeds.jsonl"), "utf8");
|
|
4536
4512
|
} catch {
|
|
4537
4513
|
const e = new Error(`no semantic index for ${addr} \u2014 run \`indelible-mcp semantic-fetch\` first`);
|
|
4538
4514
|
e.code = "NO_INDEX";
|
|
@@ -4574,7 +4550,7 @@ function buildSemanticMap({
|
|
|
4574
4550
|
log("sessions with vectors: " + sessions.length);
|
|
4575
4551
|
const meta = /* @__PURE__ */ new Map();
|
|
4576
4552
|
try {
|
|
4577
|
-
for (const line of
|
|
4553
|
+
for (const line of readFileSync14(join19(dir, addr + ".jsonl"), "utf8").split("\n")) {
|
|
4578
4554
|
if (!line.trim()) continue;
|
|
4579
4555
|
let r;
|
|
4580
4556
|
try {
|
|
@@ -4597,16 +4573,18 @@ function buildSemanticMap({
|
|
|
4597
4573
|
log(`dated: ${dated.length} | span: ${span.from} \u2192 ${span.to}`);
|
|
4598
4574
|
if (dated.length < MIN_FOR_MAP) {
|
|
4599
4575
|
const html2 = sparsePage(addr, sessions.length, dated.length);
|
|
4600
|
-
|
|
4576
|
+
writeFileSync9(outPath, html2);
|
|
4577
|
+
persistModel(outPath, { sparse: true, count: dated.length, span });
|
|
4601
4578
|
log(`wrote ${outPath} (sparse floor, ${dated.length}/${MIN_FOR_MAP})`);
|
|
4602
4579
|
return { path: outPath, sessions: sessions.length, dated: dated.length, K: 0, districts: 0, sparse: true, span };
|
|
4603
4580
|
}
|
|
4604
4581
|
log("PCA + k-means + density (shared compute)...");
|
|
4605
4582
|
const model = computeMapLayout(dated);
|
|
4606
4583
|
const html = renderHtml(model);
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4584
|
+
writeFileSync9(outPath, html);
|
|
4585
|
+
const modelPath = persistModel(outPath, model);
|
|
4586
|
+
log(`wrote ${outPath} (${Math.round(html.length / 1024)} KB) + ${modelPath}`);
|
|
4587
|
+
return { path: outPath, modelPath, sessions: sessions.length, dated: dated.length, K: model.K, districts: model.subs.length, sparse: false, span };
|
|
4610
4588
|
}
|
|
4611
4589
|
function sparsePage(addr, total, dated) {
|
|
4612
4590
|
return `<!doctype html><meta charset="utf-8"><title>Indelible \u2014 The Shape of the Work</title>
|
|
@@ -4731,6 +4709,81 @@ var init_build_map = __esm({
|
|
|
4731
4709
|
}
|
|
4732
4710
|
});
|
|
4733
4711
|
|
|
4712
|
+
// mcp-server/lib/semantic/fetch-pack.js
|
|
4713
|
+
var fetch_pack_exports = {};
|
|
4714
|
+
__export(fetch_pack_exports, {
|
|
4715
|
+
fetchPack: () => fetchPack
|
|
4716
|
+
});
|
|
4717
|
+
import { createHash as createHash5 } from "crypto";
|
|
4718
|
+
import { createWriteStream, existsSync as existsSync19, mkdirSync as mkdirSync8, renameSync as renameSync4, rmSync, statSync as statSync5 } from "fs";
|
|
4719
|
+
import { join as join22 } from "path";
|
|
4720
|
+
async function sha256File(path) {
|
|
4721
|
+
const { createReadStream: createReadStream3 } = await import("fs");
|
|
4722
|
+
return new Promise((resolve3, reject) => {
|
|
4723
|
+
const h = createHash5("sha256");
|
|
4724
|
+
createReadStream3(path).on("data", (d) => h.update(d)).on("end", () => resolve3(h.digest("hex"))).on("error", reject);
|
|
4725
|
+
});
|
|
4726
|
+
}
|
|
4727
|
+
async function fetchToTmp(url, tmpPath, timeoutMs) {
|
|
4728
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs), redirect: "follow" });
|
|
4729
|
+
if (!res.ok || !res.body) throw new PackError("FETCH_FAIL", `HTTP ${res.status} for ${url}`);
|
|
4730
|
+
const { Readable } = await import("stream");
|
|
4731
|
+
const { pipeline } = await import("stream/promises");
|
|
4732
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(tmpPath));
|
|
4733
|
+
}
|
|
4734
|
+
async function fetchPack({ profile = PROFILE, timeoutMs = 3e5, onProgress = null } = {}) {
|
|
4735
|
+
const dir = packDir(profile);
|
|
4736
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
4737
|
+
const fetched = [];
|
|
4738
|
+
const skipped = [];
|
|
4739
|
+
for (const f of PACK_FILES) {
|
|
4740
|
+
const finalPath = join22(dir, f.name);
|
|
4741
|
+
if (existsSync19(finalPath) && statSync5(finalPath).size === f.bytes) {
|
|
4742
|
+
skipped.push(f.name);
|
|
4743
|
+
continue;
|
|
4744
|
+
}
|
|
4745
|
+
const tmpPath = `${finalPath}.tmp-${process.pid}`;
|
|
4746
|
+
try {
|
|
4747
|
+
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "fetching" });
|
|
4748
|
+
await fetchToTmp(f.url, tmpPath, timeoutMs);
|
|
4749
|
+
const gotHash = await sha256File(tmpPath);
|
|
4750
|
+
if (gotHash !== f.sha256) {
|
|
4751
|
+
throw new PackError(
|
|
4752
|
+
"HASH_MISMATCH",
|
|
4753
|
+
`${f.name}: expected ${f.sha256.slice(0, 12)}\u2026, got ${gotHash.slice(0, 12)}\u2026 \u2014 refusing (upstream drifted or tampered)`
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
const gotSize = statSync5(tmpPath).size;
|
|
4757
|
+
if (gotSize !== f.bytes) {
|
|
4758
|
+
throw new PackError("HASH_MISMATCH", `${f.name}: expected ${f.bytes} bytes, got ${gotSize}`);
|
|
4759
|
+
}
|
|
4760
|
+
renameSync4(tmpPath, finalPath);
|
|
4761
|
+
fetched.push(f.name);
|
|
4762
|
+
if (onProgress) onProgress({ file: f.name, bytes: f.bytes, state: "verified" });
|
|
4763
|
+
} catch (err) {
|
|
4764
|
+
try {
|
|
4765
|
+
rmSync(tmpPath, { force: true });
|
|
4766
|
+
} catch {
|
|
4767
|
+
}
|
|
4768
|
+
if (err instanceof PackError) throw err;
|
|
4769
|
+
throw new PackError("FETCH_FAIL", `${f.name}: ${err.message}`);
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
return { ok: true, fetched, skipped, dir };
|
|
4773
|
+
}
|
|
4774
|
+
var PackError;
|
|
4775
|
+
var init_fetch_pack = __esm({
|
|
4776
|
+
"mcp-server/lib/semantic/fetch-pack.js"() {
|
|
4777
|
+
init_manifest();
|
|
4778
|
+
PackError = class extends Error {
|
|
4779
|
+
constructor(code, message) {
|
|
4780
|
+
super(message);
|
|
4781
|
+
this.code = code;
|
|
4782
|
+
}
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
|
|
4734
4787
|
// mcp-server/lib/timecard.js
|
|
4735
4788
|
var timecard_exports = {};
|
|
4736
4789
|
__export(timecard_exports, {
|
|
@@ -4787,10 +4840,10 @@ init_config_customer();
|
|
|
4787
4840
|
init_api_client();
|
|
4788
4841
|
import { createInterface as createInterface3 } from "node:readline";
|
|
4789
4842
|
import { execSync as execSync2 } from "node:child_process";
|
|
4790
|
-
import { homedir as
|
|
4791
|
-
import { join as
|
|
4843
|
+
import { homedir as homedir19 } from "node:os";
|
|
4844
|
+
import { join as join23, dirname as dirname6, resolve as resolve2 } from "node:path";
|
|
4792
4845
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4793
|
-
import { readFileSync as
|
|
4846
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync10, existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync2, statSync as statSync6 } from "node:fs";
|
|
4794
4847
|
|
|
4795
4848
|
// mcp-server/lib/guardrails-run.js
|
|
4796
4849
|
function contextFromInput(input) {
|
|
@@ -8302,32 +8355,108 @@ async function recallContext({ from_date = null, to_date = null, query = null, d
|
|
|
8302
8355
|
};
|
|
8303
8356
|
}
|
|
8304
8357
|
|
|
8358
|
+
// mcp-server/tools/map_themes.js
|
|
8359
|
+
init_config_customer();
|
|
8360
|
+
import { readFileSync as readFileSync15, existsSync as existsSync17, statSync as statSync4 } from "fs";
|
|
8361
|
+
import { join as join20 } from "path";
|
|
8362
|
+
import { homedir as homedir17 } from "os";
|
|
8363
|
+
var MODEL_PATH = join20(homedir17(), ".indelible", "SEMANTIC_MAP.json");
|
|
8364
|
+
var TXIDS_OVERVIEW = 5;
|
|
8365
|
+
var TXIDS_DEEP = 60;
|
|
8366
|
+
async function mapThemes({ sample_titles = 5, sort = "size", theme = null } = {}) {
|
|
8367
|
+
const config = await loadConfig();
|
|
8368
|
+
const addr = config?.address;
|
|
8369
|
+
if (!addr) return { success: false, error: "No wallet configured \u2014 run setup_wallet first." };
|
|
8370
|
+
const indexPath2 = join20(homedir17(), ".indelible", "recall-index", `${addr}.jsonl`);
|
|
8371
|
+
let fresh = false;
|
|
8372
|
+
try {
|
|
8373
|
+
fresh = existsSync17(MODEL_PATH) && (!existsSync17(indexPath2) || statSync4(MODEL_PATH).mtimeMs >= statSync4(indexPath2).mtimeMs);
|
|
8374
|
+
} catch {
|
|
8375
|
+
}
|
|
8376
|
+
if (!fresh) {
|
|
8377
|
+
try {
|
|
8378
|
+
const { buildSemanticMap: buildSemanticMap2 } = await Promise.resolve().then(() => (init_build_map(), build_map_exports));
|
|
8379
|
+
buildSemanticMap2({ addr, log: () => {
|
|
8380
|
+
} });
|
|
8381
|
+
} catch (e) {
|
|
8382
|
+
if (e.code === "NO_INDEX") {
|
|
8383
|
+
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." };
|
|
8384
|
+
}
|
|
8385
|
+
return { success: false, error: `Could not build the map model: ${e.message}` };
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
let model;
|
|
8389
|
+
try {
|
|
8390
|
+
model = JSON.parse(readFileSync15(MODEL_PATH, "utf8"));
|
|
8391
|
+
} catch (e) {
|
|
8392
|
+
return { success: false, error: `Could not read the map model (${e.message}) \u2014 run \`indelible-mcp map\` to regenerate it.` };
|
|
8393
|
+
}
|
|
8394
|
+
if (model.sparse) {
|
|
8395
|
+
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.` };
|
|
8396
|
+
}
|
|
8397
|
+
const now = Date.now();
|
|
8398
|
+
const nTitles = Math.max(0, Math.min(20, Number(sample_titles) || 5));
|
|
8399
|
+
const themeFilter = theme ? String(theme).toLowerCase() : null;
|
|
8400
|
+
const txidCap = themeFilter ? TXIDS_DEEP : TXIDS_OVERVIEW;
|
|
8401
|
+
let themes = model.cls.map((c, i) => {
|
|
8402
|
+
const members = model.pts.filter((p) => p.c === i).sort((a, b) => a.d < b.d ? 1 : -1);
|
|
8403
|
+
const last = c.last_touched || members[0]?.d || null;
|
|
8404
|
+
const txids = c.member_txIds ? [...c.member_txIds].reverse() : members.map((p) => p.id);
|
|
8405
|
+
return {
|
|
8406
|
+
theme: c.label,
|
|
8407
|
+
sessions: c.count,
|
|
8408
|
+
first_touched: c.first_touched || members[members.length - 1]?.d || null,
|
|
8409
|
+
last_touched: last,
|
|
8410
|
+
days_since_touched: last ? Math.max(0, Math.floor((now - Date.parse(last)) / 864e5)) : null,
|
|
8411
|
+
sample_titles: members.slice(0, nTitles).map((p) => `${p.d} \xB7 ${p.t || "(untitled)"}`),
|
|
8412
|
+
member_txids: txids.slice(0, txidCap),
|
|
8413
|
+
member_txids_truncated: txids.length > txidCap ? txids.length : false
|
|
8414
|
+
};
|
|
8415
|
+
});
|
|
8416
|
+
if (themeFilter) {
|
|
8417
|
+
const matched = themes.filter((t) => t.theme.toLowerCase().includes(themeFilter));
|
|
8418
|
+
if (!matched.length) {
|
|
8419
|
+
return { success: false, error: `No theme matches "${theme}". Themes: ${themes.map((t) => t.theme).join(" | ")}` };
|
|
8420
|
+
}
|
|
8421
|
+
themes = matched;
|
|
8422
|
+
}
|
|
8423
|
+
themes.sort(sort === "cold" ? (a, b) => (b.days_since_touched ?? -1) - (a.days_since_touched ?? -1) : (a, b) => b.sessions - a.sessions);
|
|
8424
|
+
return {
|
|
8425
|
+
success: true,
|
|
8426
|
+
generated_at: model.generated_at || null,
|
|
8427
|
+
mapped_sessions: model.count,
|
|
8428
|
+
span: model.span || null,
|
|
8429
|
+
themes,
|
|
8430
|
+
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).`
|
|
8431
|
+
};
|
|
8432
|
+
}
|
|
8433
|
+
|
|
8305
8434
|
// mcp-server/tools/report_bug.js
|
|
8306
|
-
import { readFileSync as
|
|
8307
|
-
import { join as
|
|
8308
|
-
import { homedir as
|
|
8435
|
+
import { readFileSync as readFileSync16, existsSync as existsSync18 } from "fs";
|
|
8436
|
+
import { join as join21, dirname as dirname5 } from "path";
|
|
8437
|
+
import { homedir as homedir18 } from "os";
|
|
8309
8438
|
import { fileURLToPath } from "url";
|
|
8310
8439
|
import os from "os";
|
|
8311
8440
|
var INTAKE_URL = process.env.INDELIBLE_BUG_INTAKE || "https://indelible.one/api/bug-report";
|
|
8312
|
-
var INDELIBLE_DIR =
|
|
8441
|
+
var INDELIBLE_DIR = join21(homedir18(), ".indelible");
|
|
8313
8442
|
function getMcpVersion(injected) {
|
|
8314
8443
|
if (injected) return injected;
|
|
8315
8444
|
const candidates = [];
|
|
8316
8445
|
try {
|
|
8317
|
-
candidates.push(
|
|
8446
|
+
candidates.push(join21(dirname5(fileURLToPath(import.meta.url)), "..", "..", "package.json"));
|
|
8318
8447
|
} catch {
|
|
8319
8448
|
}
|
|
8320
8449
|
try {
|
|
8321
|
-
candidates.push(
|
|
8450
|
+
candidates.push(join21(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
8322
8451
|
} catch {
|
|
8323
8452
|
}
|
|
8324
|
-
candidates.push(
|
|
8453
|
+
candidates.push(join21(homedir18(), "AppData", "Roaming", "npm", "node_modules", "indelible-mcp", "package.json"));
|
|
8325
8454
|
candidates.push("/usr/local/lib/node_modules/indelible-mcp/package.json");
|
|
8326
8455
|
candidates.push("/usr/lib/node_modules/indelible-mcp/package.json");
|
|
8327
8456
|
for (const p of candidates) {
|
|
8328
8457
|
try {
|
|
8329
|
-
if (!
|
|
8330
|
-
const pkg = JSON.parse(
|
|
8458
|
+
if (!existsSync18(p)) continue;
|
|
8459
|
+
const pkg = JSON.parse(readFileSync16(p, "utf8"));
|
|
8331
8460
|
if (pkg && pkg.name === "indelible-mcp" && pkg.version) return pkg.version;
|
|
8332
8461
|
} catch {
|
|
8333
8462
|
}
|
|
@@ -8336,7 +8465,7 @@ function getMcpVersion(injected) {
|
|
|
8336
8465
|
}
|
|
8337
8466
|
function readJson(path) {
|
|
8338
8467
|
try {
|
|
8339
|
-
return
|
|
8468
|
+
return existsSync18(path) ? JSON.parse(readFileSync16(path, "utf8")) : null;
|
|
8340
8469
|
} catch {
|
|
8341
8470
|
return null;
|
|
8342
8471
|
}
|
|
@@ -8346,9 +8475,9 @@ function getWalletAddress(config) {
|
|
|
8346
8475
|
}
|
|
8347
8476
|
function getRecentReceipts(n = 5) {
|
|
8348
8477
|
try {
|
|
8349
|
-
const p =
|
|
8350
|
-
if (!
|
|
8351
|
-
const lines =
|
|
8478
|
+
const p = join21(INDELIBLE_DIR, "save-log.jsonl");
|
|
8479
|
+
if (!existsSync18(p)) return [];
|
|
8480
|
+
const lines = readFileSync16(p, "utf8").trim().split("\n").filter(Boolean).slice(-n);
|
|
8352
8481
|
return lines.map((l) => {
|
|
8353
8482
|
try {
|
|
8354
8483
|
const e = JSON.parse(l);
|
|
@@ -8377,7 +8506,7 @@ async function reportBug(args2 = {}, mcpVersion) {
|
|
|
8377
8506
|
if (!summary || !description) {
|
|
8378
8507
|
return { success: false, error: "summary and description are required (you supply the narrative; the tool supplies the facts)." };
|
|
8379
8508
|
}
|
|
8380
|
-
const config = readJson(
|
|
8509
|
+
const config = readJson(join21(INDELIBLE_DIR, "config.json")) || {};
|
|
8381
8510
|
const payload = {
|
|
8382
8511
|
// AI-supplied narrative (defensively redacted + length-capped)
|
|
8383
8512
|
summary: redactSecrets2(summary).slice(0, 200),
|
|
@@ -8647,16 +8776,16 @@ init_wall_stub();
|
|
|
8647
8776
|
init_wall_stub();
|
|
8648
8777
|
init_wall_stub();
|
|
8649
8778
|
init_wall_stub();
|
|
8650
|
-
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve2(
|
|
8651
|
-
var CONTEXT_FILE2 =
|
|
8779
|
+
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve2(dirname6(fileURLToPath2(import.meta.url)), "../..");
|
|
8780
|
+
var CONTEXT_FILE2 = join23(homedir19(), ".indelible", "indelible-context.jsonl");
|
|
8652
8781
|
function installHooks() {
|
|
8653
|
-
const claudeDir =
|
|
8654
|
-
const settingsPath =
|
|
8655
|
-
if (!
|
|
8782
|
+
const claudeDir = join23(homedir19(), ".claude");
|
|
8783
|
+
const settingsPath = join23(claudeDir, "settings.local.json");
|
|
8784
|
+
if (!existsSync20(claudeDir)) mkdirSync9(claudeDir, { recursive: true });
|
|
8656
8785
|
let settings = {};
|
|
8657
|
-
if (
|
|
8786
|
+
if (existsSync20(settingsPath)) {
|
|
8658
8787
|
try {
|
|
8659
|
-
settings = JSON.parse(
|
|
8788
|
+
settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
|
|
8660
8789
|
} catch {
|
|
8661
8790
|
settings = {};
|
|
8662
8791
|
}
|
|
@@ -8707,13 +8836,13 @@ function installHooks() {
|
|
|
8707
8836
|
});
|
|
8708
8837
|
installed.push("PreToolUse:guardrails");
|
|
8709
8838
|
}
|
|
8710
|
-
|
|
8839
|
+
writeFileSync10(settingsPath, JSON.stringify(settings, null, 2));
|
|
8711
8840
|
return { settingsPath, installed, alreadyInstalled: installed.length === 0 };
|
|
8712
8841
|
}
|
|
8713
8842
|
function runPreToolUseGuard() {
|
|
8714
8843
|
let code = 0;
|
|
8715
8844
|
try {
|
|
8716
|
-
const raw =
|
|
8845
|
+
const raw = readFileSync17(0, "utf8").trim();
|
|
8717
8846
|
if (!raw) process.exit(0);
|
|
8718
8847
|
const hit = evaluate(JSON.parse(raw));
|
|
8719
8848
|
if (hit) {
|
|
@@ -8913,6 +9042,7 @@ Commands:
|
|
|
8913
9042
|
const { packPresent: packPresent2 } = await Promise.resolve().then(() => (init_manifest(), manifest_exports));
|
|
8914
9043
|
const { fetchPack: fetchPack2 } = await Promise.resolve().then(() => (init_fetch_pack(), fetch_pack_exports));
|
|
8915
9044
|
const { rebuildVectorSidecar: rebuildVectorSidecar2 } = await Promise.resolve().then(() => (init_vec_store(), vec_store_exports));
|
|
9045
|
+
const { buildIndexWindow: buildIndexWindow2, resetGaps: resetGaps2 } = await Promise.resolve().then(() => (init_recall_index(), recall_index_exports));
|
|
8916
9046
|
try {
|
|
8917
9047
|
if (!packPresent2()) {
|
|
8918
9048
|
console.log("Fetching the semantic model pack (~50MB, one time, hash-verified)...");
|
|
@@ -8926,15 +9056,37 @@ Commands:
|
|
|
8926
9056
|
console.log(JSON.stringify({ success: false, error: "No wallet configured \u2014 run setup first." }));
|
|
8927
9057
|
break;
|
|
8928
9058
|
}
|
|
9059
|
+
const wif = await getWif();
|
|
9060
|
+
if (!wif) {
|
|
9061
|
+
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.)");
|
|
9062
|
+
} else {
|
|
9063
|
+
const retried = await resetGaps2(cfg.address, ["FETCH_TIMEOUT", "NOT_ON_CHAIN", "WALLET_LOCKED"]);
|
|
9064
|
+
if (retried) console.log(` re-attempting ${retried} previously-skipped sessions...`);
|
|
9065
|
+
console.log(`Reading your on-chain history for ${cfg.address} (one-time, resumable, all local)...`);
|
|
9066
|
+
let indexed = 0;
|
|
9067
|
+
for (; ; ) {
|
|
9068
|
+
const r = await buildIndexWindow2(cfg.address, wif, { maxDecrypt: 150 });
|
|
9069
|
+
indexed += r.indexed;
|
|
9070
|
+
console.log(` indexed ${indexed} sessions; ${r.remaining} to go`);
|
|
9071
|
+
if (r.remaining === 0 || r.indexed === 0 && r.gapped === 0) break;
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
8929
9074
|
console.log(`Backfilling meaning-vectors for ${cfg.address} (resumable \u2014 re-run to continue)...`);
|
|
8930
|
-
let total = 0;
|
|
9075
|
+
let total = 0, cov = { vecs: 0, indexed: 0 };
|
|
8931
9076
|
for (; ; ) {
|
|
8932
9077
|
const r = await rebuildVectorSidecar2(cfg.address, { maxEmbed: 100 });
|
|
8933
9078
|
total += r.embedded;
|
|
8934
|
-
|
|
9079
|
+
cov = r.coverage || cov;
|
|
9080
|
+
console.log(` embedded ${total} sessions; remaining ${r.remaining}; coverage ${cov.vecs}/${cov.indexed}`);
|
|
8935
9081
|
if (r.remaining === 0 || r.embedded === 0) break;
|
|
8936
9082
|
}
|
|
8937
|
-
|
|
9083
|
+
if (cov.vecs > 0) {
|
|
9084
|
+
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 }));
|
|
9085
|
+
} else if (!wif) {
|
|
9086
|
+
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." }));
|
|
9087
|
+
} else {
|
|
9088
|
+
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 }));
|
|
9089
|
+
}
|
|
8938
9090
|
} catch (e) {
|
|
8939
9091
|
console.log(JSON.stringify({ success: false, error: e.code === "HASH_MISMATCH" ? `Integrity check failed: ${e.message}` : e.message }));
|
|
8940
9092
|
}
|
|
@@ -8942,9 +9094,9 @@ Commands:
|
|
|
8942
9094
|
}
|
|
8943
9095
|
case "map": {
|
|
8944
9096
|
const { buildSemanticMap: buildSemanticMap2 } = await Promise.resolve().then(() => (init_build_map(), build_map_exports));
|
|
8945
|
-
const { join:
|
|
8946
|
-
const { homedir:
|
|
8947
|
-
const out =
|
|
9097
|
+
const { join: join24 } = await import("node:path");
|
|
9098
|
+
const { homedir: homedir20 } = await import("node:os");
|
|
9099
|
+
const out = join24(homedir20(), ".indelible", "SEMANTIC_MAP.html");
|
|
8948
9100
|
try {
|
|
8949
9101
|
const cfg = await loadConfig();
|
|
8950
9102
|
const r = buildSemanticMap2({ addr: cfg?.address, outPath: out, log: (m) => process.stderr.write(`[map] ${m}
|
|
@@ -8958,7 +9110,7 @@ Commands:
|
|
|
8958
9110
|
${r.path}`);
|
|
8959
9111
|
}
|
|
8960
9112
|
} catch (e) {
|
|
8961
|
-
if (e.code === "NO_INDEX") console.log("No
|
|
9113
|
+
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
9114
|
else console.log(JSON.stringify({ success: false, error: e.message }));
|
|
8963
9115
|
}
|
|
8964
9116
|
break;
|
|
@@ -8969,7 +9121,7 @@ Commands:
|
|
|
8969
9121
|
}
|
|
8970
9122
|
function printHelp() {
|
|
8971
9123
|
console.log(`
|
|
8972
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.
|
|
9124
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.7)
|
|
8973
9125
|
|
|
8974
9126
|
Setup:
|
|
8975
9127
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -9018,19 +9170,19 @@ Learn more: https://indelible.one
|
|
|
9018
9170
|
`);
|
|
9019
9171
|
}
|
|
9020
9172
|
function findNewestTranscript() {
|
|
9021
|
-
const projectsDir =
|
|
9022
|
-
if (!
|
|
9173
|
+
const projectsDir = join23(homedir19(), ".claude", "projects");
|
|
9174
|
+
if (!existsSync20(projectsDir)) return null;
|
|
9023
9175
|
let newestTime = 0;
|
|
9024
9176
|
let newest = null;
|
|
9025
9177
|
try {
|
|
9026
9178
|
for (const project of readdirSync2(projectsDir)) {
|
|
9027
|
-
const projectPath =
|
|
9179
|
+
const projectPath = join23(projectsDir, project);
|
|
9028
9180
|
try {
|
|
9029
|
-
if (!
|
|
9181
|
+
if (!statSync6(projectPath).isDirectory()) continue;
|
|
9030
9182
|
for (const file of readdirSync2(projectPath)) {
|
|
9031
9183
|
if (!file.endsWith(".jsonl")) continue;
|
|
9032
|
-
const p =
|
|
9033
|
-
const t =
|
|
9184
|
+
const p = join23(projectPath, file);
|
|
9185
|
+
const t = statSync6(p).mtimeMs;
|
|
9034
9186
|
if (t > newestTime) {
|
|
9035
9187
|
newestTime = t;
|
|
9036
9188
|
newest = p;
|
|
@@ -9053,13 +9205,13 @@ async function printTimeCard() {
|
|
|
9053
9205
|
};
|
|
9054
9206
|
try {
|
|
9055
9207
|
const t = findNewestTranscript();
|
|
9056
|
-
if (t) f.lastSeen = phraseDelta2(Date.now() -
|
|
9208
|
+
if (t) f.lastSeen = phraseDelta2(Date.now() - statSync6(t).mtimeMs);
|
|
9057
9209
|
} catch {
|
|
9058
9210
|
}
|
|
9059
9211
|
try {
|
|
9060
|
-
const gPath =
|
|
9061
|
-
if (
|
|
9062
|
-
const g = JSON.parse(
|
|
9212
|
+
const gPath = join23(homedir19(), ".indelible", "goals.json");
|
|
9213
|
+
if (existsSync20(gPath)) {
|
|
9214
|
+
const g = JSON.parse(readFileSync17(gPath, "utf8"));
|
|
9063
9215
|
const active = (g.goals || []).filter((x) => x.status === "active");
|
|
9064
9216
|
if (active.length) {
|
|
9065
9217
|
const oldest = active.reduce((a, b) => new Date(a.created_at) < new Date(b.created_at) ? a : b);
|
|
@@ -9095,21 +9247,21 @@ async function runPreCompactSave() {
|
|
|
9095
9247
|
process.stderr.write("Indelible: MCP disabled, skipping save\n");
|
|
9096
9248
|
process.exit(0);
|
|
9097
9249
|
}
|
|
9098
|
-
const target = transcriptPath &&
|
|
9250
|
+
const target = transcriptPath && existsSync20(transcriptPath) ? transcriptPath : findNewestTranscript() || CONTEXT_FILE2;
|
|
9099
9251
|
const result = await saveSession(target, `Auto-save before ${trigger} compaction`);
|
|
9100
9252
|
if (result.success) {
|
|
9101
9253
|
process.stderr.write(`Indelible: Saved ${result.newMessages} messages (${result.saveType}) tx:${result.txId?.slice(0, 12)}...
|
|
9102
9254
|
`);
|
|
9103
9255
|
} else if (result.gated && result.upsell) {
|
|
9104
|
-
const upsellMarker =
|
|
9256
|
+
const upsellMarker = join23(homedir19(), ".indelible", ".upsell-shown");
|
|
9105
9257
|
let shownRecently = false;
|
|
9106
9258
|
try {
|
|
9107
|
-
shownRecently = Date.now() -
|
|
9259
|
+
shownRecently = Date.now() - statSync6(upsellMarker).mtimeMs < 24 * 60 * 60 * 1e3;
|
|
9108
9260
|
} catch {
|
|
9109
9261
|
}
|
|
9110
9262
|
if (!shownRecently) {
|
|
9111
9263
|
try {
|
|
9112
|
-
|
|
9264
|
+
writeFileSync10(upsellMarker, (/* @__PURE__ */ new Date()).toISOString());
|
|
9113
9265
|
} catch {
|
|
9114
9266
|
}
|
|
9115
9267
|
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 +9283,20 @@ async function runPreCompactSave() {
|
|
|
9131
9283
|
process.exit(0);
|
|
9132
9284
|
}
|
|
9133
9285
|
function findMemoryDir() {
|
|
9134
|
-
const projectsDir =
|
|
9135
|
-
if (!
|
|
9286
|
+
const projectsDir = join23(homedir19(), ".claude", "projects");
|
|
9287
|
+
if (!existsSync20(projectsDir)) return null;
|
|
9136
9288
|
let newestTime = 0;
|
|
9137
9289
|
let newestProject = null;
|
|
9138
9290
|
const projects = readdirSync2(projectsDir);
|
|
9139
9291
|
for (const project of projects) {
|
|
9140
|
-
const projectPath =
|
|
9292
|
+
const projectPath = join23(projectsDir, project);
|
|
9141
9293
|
try {
|
|
9142
|
-
const pStat =
|
|
9294
|
+
const pStat = statSync6(projectPath);
|
|
9143
9295
|
if (!pStat.isDirectory()) continue;
|
|
9144
9296
|
const files = readdirSync2(projectPath);
|
|
9145
9297
|
for (const file of files) {
|
|
9146
9298
|
if (!file.endsWith(".jsonl")) continue;
|
|
9147
|
-
const fStat =
|
|
9299
|
+
const fStat = statSync6(join23(projectPath, file));
|
|
9148
9300
|
if (fStat.mtimeMs > newestTime) {
|
|
9149
9301
|
newestTime = fStat.mtimeMs;
|
|
9150
9302
|
newestProject = projectPath;
|
|
@@ -9153,7 +9305,7 @@ function findMemoryDir() {
|
|
|
9153
9305
|
} catch {
|
|
9154
9306
|
}
|
|
9155
9307
|
}
|
|
9156
|
-
return newestProject ?
|
|
9308
|
+
return newestProject ? join23(newestProject, "memory") : null;
|
|
9157
9309
|
}
|
|
9158
9310
|
async function runPostCompactRestore() {
|
|
9159
9311
|
await printTimeCard();
|
|
@@ -9203,10 +9355,10 @@ async function runPostCompactRestore() {
|
|
|
9203
9355
|
try {
|
|
9204
9356
|
const memoryDir = findMemoryDir();
|
|
9205
9357
|
if (memoryDir && (config.memory_file_txid || config.session_history_txid)) {
|
|
9206
|
-
if (!
|
|
9358
|
+
if (!existsSync20(memoryDir)) mkdirSync9(memoryDir, { recursive: true });
|
|
9207
9359
|
if (config.memory_file_txid) {
|
|
9208
|
-
const memPath =
|
|
9209
|
-
if (!
|
|
9360
|
+
const memPath = join23(memoryDir, "MEMORY.md");
|
|
9361
|
+
if (!existsSync20(memPath)) {
|
|
9210
9362
|
const memResult = await loadFile(config.memory_file_txid, { outputPath: memPath });
|
|
9211
9363
|
if (memResult.success) {
|
|
9212
9364
|
process.stderr.write(`Indelible: MEMORY.md restored from chain (file was missing)
|
|
@@ -9215,8 +9367,8 @@ async function runPostCompactRestore() {
|
|
|
9215
9367
|
}
|
|
9216
9368
|
}
|
|
9217
9369
|
if (config.session_history_txid) {
|
|
9218
|
-
const histPath =
|
|
9219
|
-
if (!
|
|
9370
|
+
const histPath = join23(memoryDir, "session-history.md");
|
|
9371
|
+
if (!existsSync20(histPath)) {
|
|
9220
9372
|
const histResult = await loadFile(config.session_history_txid, { outputPath: histPath });
|
|
9221
9373
|
if (histResult.success) {
|
|
9222
9374
|
process.stderr.write(`Indelible: session-history.md restored from chain (file was missing)
|
|
@@ -9232,9 +9384,9 @@ async function runPostCompactRestore() {
|
|
|
9232
9384
|
try {
|
|
9233
9385
|
const histDir = findMemoryDir();
|
|
9234
9386
|
if (histDir) {
|
|
9235
|
-
const histPath =
|
|
9236
|
-
if (
|
|
9237
|
-
const histContent =
|
|
9387
|
+
const histPath = join23(histDir, "session-history.md");
|
|
9388
|
+
if (existsSync20(histPath)) {
|
|
9389
|
+
const histContent = readFileSync17(histPath, "utf-8");
|
|
9238
9390
|
const lines = histContent.split("\n");
|
|
9239
9391
|
const entryStarts = [];
|
|
9240
9392
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -9276,7 +9428,7 @@ function readStdin() {
|
|
|
9276
9428
|
}
|
|
9277
9429
|
var SERVER_INFO = {
|
|
9278
9430
|
name: "indelible",
|
|
9279
|
-
version: "4.9.
|
|
9431
|
+
version: "4.9.7",
|
|
9280
9432
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
9281
9433
|
};
|
|
9282
9434
|
var TOOLS = [
|
|
@@ -9579,6 +9731,19 @@ var TOOLS = [
|
|
|
9579
9731
|
required: []
|
|
9580
9732
|
}
|
|
9581
9733
|
},
|
|
9734
|
+
{
|
|
9735
|
+
name: "map_themes",
|
|
9736
|
+
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).',
|
|
9737
|
+
inputSchema: {
|
|
9738
|
+
type: "object",
|
|
9739
|
+
properties: {
|
|
9740
|
+
sample_titles: { type: "number", description: "How many recent titles to include per theme (default 5, max 20)." },
|
|
9741
|
+
sort: { type: "string", enum: ["size", "cold"], description: '"size" (default) = biggest themes first; "cold" = longest-untouched first.' },
|
|
9742
|
+
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)." }
|
|
9743
|
+
},
|
|
9744
|
+
required: []
|
|
9745
|
+
}
|
|
9746
|
+
},
|
|
9582
9747
|
{
|
|
9583
9748
|
name: "report_bug",
|
|
9584
9749
|
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).`,
|
|
@@ -9735,6 +9900,9 @@ async function handleMcpRequest(request) {
|
|
|
9735
9900
|
ranking: args2?.ranking || "blend"
|
|
9736
9901
|
});
|
|
9737
9902
|
break;
|
|
9903
|
+
case "map_themes":
|
|
9904
|
+
result = await mapThemes({ sample_titles: args2?.sample_titles, sort: args2?.sort || "size", theme: args2?.theme || null });
|
|
9905
|
+
break;
|
|
9738
9906
|
case "report_bug":
|
|
9739
9907
|
result = await reportBug({
|
|
9740
9908
|
summary: args2?.summary,
|
|
@@ -9838,11 +10006,11 @@ async function runWizard() {
|
|
|
9838
10006
|
}
|
|
9839
10007
|
let mcpOk = false;
|
|
9840
10008
|
for (const cfgPath of [
|
|
9841
|
-
|
|
9842
|
-
|
|
10009
|
+
join23(homedir19(), ".claude.json"),
|
|
10010
|
+
join23(homedir19(), ".claude", "settings.json")
|
|
9843
10011
|
]) {
|
|
9844
10012
|
try {
|
|
9845
|
-
const s = JSON.parse(
|
|
10013
|
+
const s = JSON.parse(readFileSync17(cfgPath, "utf8"));
|
|
9846
10014
|
if (s?.mcpServers?.indelible) {
|
|
9847
10015
|
mcpOk = true;
|
|
9848
10016
|
break;
|
|
@@ -9852,10 +10020,10 @@ async function runWizard() {
|
|
|
9852
10020
|
}
|
|
9853
10021
|
console.log(mcpOk ? " \u2713 MCP registered with Claude Code" : " \u2717 MCP not registered");
|
|
9854
10022
|
if (!mcpOk) allGood = false;
|
|
9855
|
-
const hooksPath =
|
|
10023
|
+
const hooksPath = join23(homedir19(), ".claude", "settings.local.json");
|
|
9856
10024
|
let hooksOk = false;
|
|
9857
10025
|
try {
|
|
9858
|
-
const s = JSON.parse(
|
|
10026
|
+
const s = JSON.parse(readFileSync17(hooksPath, "utf8"));
|
|
9859
10027
|
hooksOk = s?.hooks?.PreCompact?.some(
|
|
9860
10028
|
(h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp")) || h.command?.includes("indelible-mcp")
|
|
9861
10029
|
) && s?.hooks?.SessionStart?.some(
|