indelible-mcp 4.5.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +351 -56
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -257,11 +257,42 @@ async function checkHealth() {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
async function getUtxos(address) {
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
260
|
+
const path = `/api/address/${address}/unspent`;
|
|
261
|
+
const res = await spvFetch(path);
|
|
262
|
+
const first = await res.json();
|
|
263
|
+
if (Array.isArray(first) && first.length > 0) {
|
|
264
|
+
process.stderr.write(`[MCP] UTXOs from bridge: ${first.length}
|
|
263
265
|
`);
|
|
264
|
-
|
|
266
|
+
return first;
|
|
267
|
+
}
|
|
268
|
+
let reached = 0;
|
|
269
|
+
try {
|
|
270
|
+
const bridges = await getBridges();
|
|
271
|
+
const settled = await Promise.allSettled(bridges.map(async (bridge) => {
|
|
272
|
+
const r = await fetch(`${bridge.url}${path}`, {
|
|
273
|
+
signal: AbortSignal.timeout(6e3),
|
|
274
|
+
headers: { "X-API-Key": bridge.apiKey }
|
|
275
|
+
});
|
|
276
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
277
|
+
return { name: bridge.name, utxos: await r.json() };
|
|
278
|
+
}));
|
|
279
|
+
for (const s of settled) {
|
|
280
|
+
if (s.status !== "fulfilled") continue;
|
|
281
|
+
reached++;
|
|
282
|
+
const u = s.value.utxos;
|
|
283
|
+
if (Array.isArray(u) && u.length > 0) {
|
|
284
|
+
process.stderr.write(`[MCP] UTXOs recovered via fan-out from ${s.value.name}: ${u.length}
|
|
285
|
+
`);
|
|
286
|
+
return u;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (e) {
|
|
290
|
+
process.stderr.write(`[MCP] UTXO fan-out error: ${e.message}
|
|
291
|
+
`);
|
|
292
|
+
}
|
|
293
|
+
process.stderr.write(`[MCP] UTXOs: 0 across ${reached} bridge(s)
|
|
294
|
+
`);
|
|
295
|
+
return Array.isArray(first) ? first : [];
|
|
265
296
|
}
|
|
266
297
|
async function getAddressHistory(address) {
|
|
267
298
|
const res = await spvFetch(`/api/address/${address}/history`);
|
|
@@ -838,7 +869,8 @@ async function commitSession(session, wif) {
|
|
|
838
869
|
if (utxos && utxos.length > 0) {
|
|
839
870
|
const payload = {
|
|
840
871
|
protocol: "indelible.claude-code",
|
|
841
|
-
encrypted: session.encrypted
|
|
872
|
+
encrypted: session.encrypted,
|
|
873
|
+
wrap_owner: session.wrap_owner || null
|
|
842
874
|
};
|
|
843
875
|
const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload));
|
|
844
876
|
const writeReceipt = await broadcastTx(txHex);
|
|
@@ -849,17 +881,22 @@ async function commitSession(session, wif) {
|
|
|
849
881
|
} catch {
|
|
850
882
|
}
|
|
851
883
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
884
|
+
try {
|
|
885
|
+
await indexSession({
|
|
886
|
+
txId,
|
|
887
|
+
address: session.address,
|
|
888
|
+
encrypted: session.encrypted,
|
|
889
|
+
session_id: session.session_id,
|
|
890
|
+
prev_session_id: session.prev_session_id,
|
|
891
|
+
summary_enc: summaryEnc,
|
|
892
|
+
message_count: session.message_count,
|
|
893
|
+
save_type: session.type || "full",
|
|
894
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
895
|
+
}, config, wif);
|
|
896
|
+
} catch (idxErr) {
|
|
897
|
+
process.stderr.write(`[MCP] WARNING \u2014 indexSession failed (non-fatal, tx is on chain): ${idxErr.message}
|
|
898
|
+
`);
|
|
899
|
+
}
|
|
863
900
|
const receipt = buildReceipt(writeReceipt, { txId, fee, txSize });
|
|
864
901
|
return { success: true, txId, changeUtxos, receipt };
|
|
865
902
|
}
|
|
@@ -1075,10 +1112,78 @@ var init_wall_stub = __esm({
|
|
|
1075
1112
|
init_config_customer();
|
|
1076
1113
|
import { createInterface as createInterface3 } from "node:readline";
|
|
1077
1114
|
import { execSync as execSync2 } from "node:child_process";
|
|
1078
|
-
import { homedir as
|
|
1079
|
-
import { join as
|
|
1080
|
-
import { fileURLToPath } from "node:url";
|
|
1081
|
-
import { readFileSync as
|
|
1115
|
+
import { homedir as homedir13 } from "node:os";
|
|
1116
|
+
import { join as join14, dirname as dirname5, resolve } from "node:path";
|
|
1117
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1118
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync, statSync } from "node:fs";
|
|
1119
|
+
|
|
1120
|
+
// mcp-server/lib/guardrails-run.js
|
|
1121
|
+
function contextFromInput(input) {
|
|
1122
|
+
const tool = input?.tool_name;
|
|
1123
|
+
const ti = input?.tool_input || {};
|
|
1124
|
+
return {
|
|
1125
|
+
tool,
|
|
1126
|
+
filePath: ti.file_path || "",
|
|
1127
|
+
// For Write the new content is `content`; for Edit it's `new_string`.
|
|
1128
|
+
added: tool === "Write" ? ti.content || "" : ti.new_string || "",
|
|
1129
|
+
command: ti.command || ""
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function runRules(input, rules) {
|
|
1133
|
+
const ctx = contextFromInput(input);
|
|
1134
|
+
for (const rule of rules) {
|
|
1135
|
+
let reason = null;
|
|
1136
|
+
try {
|
|
1137
|
+
reason = rule.test(ctx);
|
|
1138
|
+
} catch {
|
|
1139
|
+
reason = null;
|
|
1140
|
+
}
|
|
1141
|
+
if (reason) return { id: rule.id, severity: rule.severity, reason };
|
|
1142
|
+
}
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// mcp-server/lib/guardrails.customer.js
|
|
1147
|
+
var RULES = [
|
|
1148
|
+
{
|
|
1149
|
+
// Reading a private key or seed into the chat sends it to the model and into
|
|
1150
|
+
// the saved transcript. The key controls real funds — this is the one worth
|
|
1151
|
+
// hard-blocking. If the agent needs a non-secret field, it can read just that.
|
|
1152
|
+
id: "credential-pull",
|
|
1153
|
+
severity: "BLOCK",
|
|
1154
|
+
test: ({ command }) => {
|
|
1155
|
+
if (!command) return null;
|
|
1156
|
+
const READ = /\b(cat|less|more|head|tail|type|Get-Content|gc)\b/i;
|
|
1157
|
+
const SECRET = /(config\.json|\.env\b|\bwif\b|\bseed\b|private[-_ ]?key|\.indelible[\\/])/i;
|
|
1158
|
+
return READ.test(command) && SECRET.test(command) ? "That command would print your wallet key or seed into the conversation \u2014 which hands it to the model and writes it into the saved transcript. Your private key controls your funds; never read it into the chat. If you only need a non-secret value, read just that field." : null;
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
// The irreversible-command guard. Advisory, never blocked — your repo, your
|
|
1163
|
+
// machine, your call — but a force-push / recursive delete / hard reset has no
|
|
1164
|
+
// undo, so make the blast radius visible before it runs.
|
|
1165
|
+
id: "destructive-op",
|
|
1166
|
+
severity: "WARN",
|
|
1167
|
+
test: ({ command }) => {
|
|
1168
|
+
if (!command) return null;
|
|
1169
|
+
const hits = [
|
|
1170
|
+
/git\s+push\b[^\n]*(--force\b|--force-with-lease\b|-f\b)/i,
|
|
1171
|
+
// rewrites remote history
|
|
1172
|
+
/git\s+reset\s+--hard\b/i,
|
|
1173
|
+
// discards local work
|
|
1174
|
+
/\brm\s+-[a-z]*r[a-z]*f\b/i,
|
|
1175
|
+
// rm -rf / -fr / -Rf
|
|
1176
|
+
/\brm\s+-[a-z]*f[a-z]*r\b/i,
|
|
1177
|
+
/\bRemove-Item\b[^\n]*-Recurse\b[^\n]*-Force\b/i
|
|
1178
|
+
// PowerShell recursive force-delete
|
|
1179
|
+
];
|
|
1180
|
+
return hits.some((re) => re.test(command)) ? "This command is irreversible \u2014 a force-push rewrites remote history, and rm -rf / reset --hard / Remove-Item -Recurse -Force cannot be undone. Confirm you have a backup and you are aiming at the right target before you run it." : null;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
];
|
|
1184
|
+
function evaluate(input) {
|
|
1185
|
+
return runRules(input, RULES);
|
|
1186
|
+
}
|
|
1082
1187
|
|
|
1083
1188
|
// mcp-server/tools/setup_wallet.js
|
|
1084
1189
|
init_config_customer();
|
|
@@ -2922,7 +3027,9 @@ async function _saveSessionInner(transcriptPath, summary, mode) {
|
|
|
2922
3027
|
timestamp: session.created_at
|
|
2923
3028
|
};
|
|
2924
3029
|
await appendFile(indexPath2, JSON.stringify(entry) + "\n");
|
|
2925
|
-
} catch {
|
|
3030
|
+
} catch (e) {
|
|
3031
|
+
process.stderr.write(`[save_session] WARNING \u2014 session-index append failed; wrap NOT cached locally (on-chain copy is primary): ${e?.message || e}
|
|
3032
|
+
`);
|
|
2926
3033
|
}
|
|
2927
3034
|
let memoryTxId = config.memory_file_txid || null;
|
|
2928
3035
|
let historyTxId = config.session_history_txid || null;
|
|
@@ -3194,20 +3301,25 @@ async function saveStyle(rulesText, styleName, description) {
|
|
|
3194
3301
|
return { success: false, error: "No UTXOs available. Fund your wallet first." };
|
|
3195
3302
|
}
|
|
3196
3303
|
const { txHex, txId } = await buildOpReturnTx(wif, utxos, JSON.stringify(payload));
|
|
3197
|
-
await broadcastTx(txHex);
|
|
3304
|
+
const writeReceipt = await broadcastTx(txHex);
|
|
3198
3305
|
await saveConfig({
|
|
3199
3306
|
...config,
|
|
3200
3307
|
style_txid: txId,
|
|
3201
3308
|
style_name: styleName,
|
|
3202
3309
|
style_id: styleId
|
|
3203
3310
|
});
|
|
3311
|
+
const receipt = buildReceipt(writeReceipt, { txId, fee: null, txSize: txHex.length / 2 });
|
|
3312
|
+
appendReceipt(receipt, { trigger: "interactive", saveType: "style" });
|
|
3204
3313
|
return {
|
|
3205
3314
|
success: true,
|
|
3206
3315
|
txId,
|
|
3207
3316
|
styleId,
|
|
3208
3317
|
styleName,
|
|
3209
3318
|
rulesLength: rulesText.length,
|
|
3210
|
-
|
|
3319
|
+
receipt,
|
|
3320
|
+
message: `Style "${styleName}" saved to blockchain.
|
|
3321
|
+
|
|
3322
|
+
${formatSaveReceipt(receipt)}`
|
|
3211
3323
|
};
|
|
3212
3324
|
} catch (error) {
|
|
3213
3325
|
return { success: false, error: `Failed to broadcast style: ${error.message}` };
|
|
@@ -5073,12 +5185,17 @@ async function updateVaultIndex() {
|
|
|
5073
5185
|
const result = await broadcastTx(txHex);
|
|
5074
5186
|
const indexTxId = result.txid || txId;
|
|
5075
5187
|
await saveConfig({ ...config, vault_index_txid: indexTxId });
|
|
5188
|
+
const receipt = buildReceipt(result, { txId: indexTxId, fee: null, txSize: txHex.length / 2 });
|
|
5189
|
+
appendReceipt(receipt, { trigger: "interactive", saveType: "vault-index" });
|
|
5076
5190
|
return {
|
|
5077
5191
|
success: true,
|
|
5078
5192
|
txId: indexTxId,
|
|
5079
5193
|
files: files.length,
|
|
5080
5194
|
projects: projects.length,
|
|
5081
|
-
|
|
5195
|
+
receipt,
|
|
5196
|
+
message: `Vault index updated: ${files.length} files, ${projects.length} projects.
|
|
5197
|
+
|
|
5198
|
+
${formatSaveReceipt(receipt)}`
|
|
5082
5199
|
};
|
|
5083
5200
|
} catch (error) {
|
|
5084
5201
|
return { success: false, error: `Failed to update vault index: ${error.message}` };
|
|
@@ -5523,6 +5640,13 @@ async function saveGoalsToChain() {
|
|
|
5523
5640
|
content: [{ type: "text", text: `Failed to parse ${GOALS_PATH2}: ${err.message}` }]
|
|
5524
5641
|
};
|
|
5525
5642
|
}
|
|
5643
|
+
const contentHash = crypto.createHash("sha256").update(JSON.stringify(goalsData)).digest("hex");
|
|
5644
|
+
const config = await loadConfig();
|
|
5645
|
+
if (config.goals_snapshot_hash === contentHash) {
|
|
5646
|
+
return {
|
|
5647
|
+
content: [{ type: "text", text: `Goals unchanged since the last snapshot${config.goals_snapshot_txid ? ` (txid ${config.goals_snapshot_txid})` : ""} \u2014 skipped broadcast, no cost.` }]
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5526
5650
|
const wif = await getWif();
|
|
5527
5651
|
if (!wif) {
|
|
5528
5652
|
return {
|
|
@@ -5559,19 +5683,21 @@ async function saveGoalsToChain() {
|
|
|
5559
5683
|
utxos,
|
|
5560
5684
|
JSON.stringify(payload)
|
|
5561
5685
|
);
|
|
5562
|
-
await broadcastTx(txHex);
|
|
5686
|
+
const writeReceipt = await broadcastTx(txHex);
|
|
5687
|
+
const receipt = buildReceipt(writeReceipt, { txId, fee, txSize });
|
|
5688
|
+
appendReceipt(receipt, { trigger: "interactive", saveType: "goals" });
|
|
5689
|
+
await saveConfig({ ...config, goals_snapshot_hash: contentHash, goals_snapshot_txid: txId });
|
|
5563
5690
|
return {
|
|
5564
5691
|
content: [{
|
|
5565
5692
|
type: "text",
|
|
5566
5693
|
text: `\u2713 Goals snapshot broadcast.
|
|
5567
5694
|
|
|
5568
|
-
txid: ${txId}
|
|
5569
5695
|
address: ${address}
|
|
5570
5696
|
goals: ${goals.length} active, ${completed.length} completed
|
|
5571
|
-
fee: ${fee} sats (${txSize} bytes)
|
|
5572
5697
|
|
|
5573
|
-
|
|
5574
|
-
|
|
5698
|
+
${formatSaveReceipt(receipt)}
|
|
5699
|
+
|
|
5700
|
+
Web Goals tab should reflect these on next refresh (chain scan).`
|
|
5575
5701
|
}]
|
|
5576
5702
|
};
|
|
5577
5703
|
}
|
|
@@ -5876,6 +6002,116 @@ async function recallContext({ from_date = null, to_date = null, query = null, d
|
|
|
5876
6002
|
};
|
|
5877
6003
|
}
|
|
5878
6004
|
|
|
6005
|
+
// mcp-server/tools/report_bug.js
|
|
6006
|
+
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
|
|
6007
|
+
import { join as join13, dirname as dirname4 } from "path";
|
|
6008
|
+
import { homedir as homedir12 } from "os";
|
|
6009
|
+
import { fileURLToPath } from "url";
|
|
6010
|
+
import os from "os";
|
|
6011
|
+
var INTAKE_URL = process.env.INDELIBLE_BUG_INTAKE || "https://indelible.one/api/bug-report";
|
|
6012
|
+
var INDELIBLE_DIR = join13(homedir12(), ".indelible");
|
|
6013
|
+
function getMcpVersion(injected) {
|
|
6014
|
+
if (injected) return injected;
|
|
6015
|
+
const candidates = [];
|
|
6016
|
+
try {
|
|
6017
|
+
candidates.push(join13(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json"));
|
|
6018
|
+
} catch {
|
|
6019
|
+
}
|
|
6020
|
+
try {
|
|
6021
|
+
candidates.push(join13(dirname4(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
6022
|
+
} catch {
|
|
6023
|
+
}
|
|
6024
|
+
candidates.push(join13(homedir12(), "AppData", "Roaming", "npm", "node_modules", "indelible-mcp", "package.json"));
|
|
6025
|
+
candidates.push("/usr/local/lib/node_modules/indelible-mcp/package.json");
|
|
6026
|
+
candidates.push("/usr/lib/node_modules/indelible-mcp/package.json");
|
|
6027
|
+
for (const p of candidates) {
|
|
6028
|
+
try {
|
|
6029
|
+
if (!existsSync14(p)) continue;
|
|
6030
|
+
const pkg = JSON.parse(readFileSync10(p, "utf8"));
|
|
6031
|
+
if (pkg && pkg.name === "indelible-mcp" && pkg.version) return pkg.version;
|
|
6032
|
+
} catch {
|
|
6033
|
+
}
|
|
6034
|
+
}
|
|
6035
|
+
return "unknown";
|
|
6036
|
+
}
|
|
6037
|
+
function readJson(path) {
|
|
6038
|
+
try {
|
|
6039
|
+
return existsSync14(path) ? JSON.parse(readFileSync10(path, "utf8")) : null;
|
|
6040
|
+
} catch {
|
|
6041
|
+
return null;
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
function getWalletAddress(config) {
|
|
6045
|
+
return config?.address || config?.wallet_address || config?.wallet?.address || null;
|
|
6046
|
+
}
|
|
6047
|
+
function getRecentReceipts(n = 5) {
|
|
6048
|
+
try {
|
|
6049
|
+
const p = join13(INDELIBLE_DIR, "save-log.jsonl");
|
|
6050
|
+
if (!existsSync14(p)) return [];
|
|
6051
|
+
const lines = readFileSync10(p, "utf8").trim().split("\n").filter(Boolean).slice(-n);
|
|
6052
|
+
return lines.map((l) => {
|
|
6053
|
+
try {
|
|
6054
|
+
const e = JSON.parse(l);
|
|
6055
|
+
return {
|
|
6056
|
+
txid: e.txid || e.txId || null,
|
|
6057
|
+
source: e.source || null,
|
|
6058
|
+
bridge: e.bridge || null,
|
|
6059
|
+
confirmed: e.confirmed ?? null,
|
|
6060
|
+
error: e.error || null,
|
|
6061
|
+
ts: e.ts || e.timestamp || null
|
|
6062
|
+
};
|
|
6063
|
+
} catch {
|
|
6064
|
+
return null;
|
|
6065
|
+
}
|
|
6066
|
+
}).filter(Boolean);
|
|
6067
|
+
} catch {
|
|
6068
|
+
return [];
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
function redactSecrets2(s) {
|
|
6072
|
+
if (s == null) return s;
|
|
6073
|
+
return String(s).replace(/\b[KL5][1-9A-HJ-NP-Za-km-z]{50,51}\b/g, "[REDACTED-WIF]");
|
|
6074
|
+
}
|
|
6075
|
+
async function reportBug(args2 = {}, mcpVersion) {
|
|
6076
|
+
const { summary, description, severity = "medium", tool_name = null, last_error = null } = args2;
|
|
6077
|
+
if (!summary || !description) {
|
|
6078
|
+
return { success: false, error: "summary and description are required (you supply the narrative; the tool supplies the facts)." };
|
|
6079
|
+
}
|
|
6080
|
+
const config = readJson(join13(INDELIBLE_DIR, "config.json")) || {};
|
|
6081
|
+
const payload = {
|
|
6082
|
+
// AI-supplied narrative (defensively redacted + length-capped)
|
|
6083
|
+
summary: redactSecrets2(summary).slice(0, 200),
|
|
6084
|
+
description: redactSecrets2(description).slice(0, 5e3),
|
|
6085
|
+
severity: ["low", "medium", "high"].includes(severity) ? severity : "medium",
|
|
6086
|
+
tool_name: tool_name ? String(tool_name).slice(0, 80) : null,
|
|
6087
|
+
last_error: redactSecrets2(last_error),
|
|
6088
|
+
// Machine-pulled facts (the AI cannot get these wrong)
|
|
6089
|
+
mcp_version: getMcpVersion(mcpVersion),
|
|
6090
|
+
wallet_address: getWalletAddress(config),
|
|
6091
|
+
recent_receipts: getRecentReceipts(5),
|
|
6092
|
+
environment: { os: `${os.platform()} ${os.release()}`, arch: os.arch(), node: process.version },
|
|
6093
|
+
reported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6094
|
+
client: "indelible-mcp"
|
|
6095
|
+
};
|
|
6096
|
+
try {
|
|
6097
|
+
const resp = await fetch(INTAKE_URL, {
|
|
6098
|
+
method: "POST",
|
|
6099
|
+
headers: { "Content-Type": "application/json" },
|
|
6100
|
+
body: JSON.stringify(payload)
|
|
6101
|
+
});
|
|
6102
|
+
if (!resp.ok) {
|
|
6103
|
+
return { success: false, error: `intake responded ${resp.status}`, sent: { summary: payload.summary, mcp_version: payload.mcp_version } };
|
|
6104
|
+
}
|
|
6105
|
+
const data = await resp.json().catch(() => ({}));
|
|
6106
|
+
if (data.known_issue) {
|
|
6107
|
+
return { success: true, status: "known_issue", issue_id: data.issue_id || null, message: data.message || "Matched a known, already-fixed issue." };
|
|
6108
|
+
}
|
|
6109
|
+
return { success: true, status: "filed", ticket_id: data.ticket_id || null, message: data.message || "Bug report sent to the Indelible team." };
|
|
6110
|
+
} catch (err) {
|
|
6111
|
+
return { success: false, error: `could not reach intake (${err.message}). Fallback: save_file the report on-chain and share the txid.` };
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
|
|
5879
6115
|
// mcp-server/tools/share_session.js
|
|
5880
6116
|
init_config_customer();
|
|
5881
6117
|
init_spv();
|
|
@@ -6059,16 +6295,16 @@ init_wall_stub();
|
|
|
6059
6295
|
init_wall_stub();
|
|
6060
6296
|
init_wall_stub();
|
|
6061
6297
|
init_wall_stub();
|
|
6062
|
-
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve(
|
|
6063
|
-
var CONTEXT_FILE2 =
|
|
6298
|
+
var PROJECT_ROOT_FOR_CLONE_CHECK = resolve(dirname5(fileURLToPath2(import.meta.url)), "../..");
|
|
6299
|
+
var CONTEXT_FILE2 = join14(homedir13(), ".indelible", "indelible-context.jsonl");
|
|
6064
6300
|
function installHooks() {
|
|
6065
|
-
const claudeDir =
|
|
6066
|
-
const settingsPath =
|
|
6067
|
-
if (!
|
|
6301
|
+
const claudeDir = join14(homedir13(), ".claude");
|
|
6302
|
+
const settingsPath = join14(claudeDir, "settings.local.json");
|
|
6303
|
+
if (!existsSync15(claudeDir)) mkdirSync6(claudeDir, { recursive: true });
|
|
6068
6304
|
let settings = {};
|
|
6069
|
-
if (
|
|
6305
|
+
if (existsSync15(settingsPath)) {
|
|
6070
6306
|
try {
|
|
6071
|
-
settings = JSON.parse(
|
|
6307
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
6072
6308
|
} catch {
|
|
6073
6309
|
settings = {};
|
|
6074
6310
|
}
|
|
@@ -6083,6 +6319,9 @@ function installHooks() {
|
|
|
6083
6319
|
const hasSessionStart = settings.hooks.SessionStart?.some(
|
|
6084
6320
|
(h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp hook session-start")) || h.command?.includes("indelible-mcp hook session-start")
|
|
6085
6321
|
);
|
|
6322
|
+
const hasPreToolUse = settings.hooks.PreToolUse?.some(
|
|
6323
|
+
(h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp hook pre-tool-use")) || h.command?.includes("indelible-mcp hook pre-tool-use")
|
|
6324
|
+
);
|
|
6086
6325
|
const installed = [];
|
|
6087
6326
|
if (!hasPreCompact) {
|
|
6088
6327
|
if (!settings.hooks.PreCompact) settings.hooks.PreCompact = [];
|
|
@@ -6108,9 +6347,38 @@ function installHooks() {
|
|
|
6108
6347
|
});
|
|
6109
6348
|
installed.push("SessionStart:style");
|
|
6110
6349
|
}
|
|
6350
|
+
if (!hasPreToolUse) {
|
|
6351
|
+
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
6352
|
+
settings.hooks.PreToolUse.push({
|
|
6353
|
+
matcher: "Bash",
|
|
6354
|
+
hooks: [{ type: "command", command: "indelible-mcp hook pre-tool-use" }]
|
|
6355
|
+
});
|
|
6356
|
+
installed.push("PreToolUse:guardrails");
|
|
6357
|
+
}
|
|
6111
6358
|
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2));
|
|
6112
6359
|
return { settingsPath, installed, alreadyInstalled: installed.length === 0 };
|
|
6113
6360
|
}
|
|
6361
|
+
function runPreToolUseGuard() {
|
|
6362
|
+
let code = 0;
|
|
6363
|
+
try {
|
|
6364
|
+
const raw = readFileSync11(0, "utf8").trim();
|
|
6365
|
+
if (!raw) process.exit(0);
|
|
6366
|
+
const hit = evaluate(JSON.parse(raw));
|
|
6367
|
+
if (hit) {
|
|
6368
|
+
if (hit.severity === "BLOCK") {
|
|
6369
|
+
process.stderr.write(`[indelible:${hit.id}] BLOCKED: ${hit.reason}
|
|
6370
|
+
`);
|
|
6371
|
+
code = 2;
|
|
6372
|
+
} else {
|
|
6373
|
+
process.stdout.write(`[indelible:${hit.id}] advisory: ${hit.reason} (not blocked)
|
|
6374
|
+
`);
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
} catch {
|
|
6378
|
+
code = 0;
|
|
6379
|
+
}
|
|
6380
|
+
process.exit(code);
|
|
6381
|
+
}
|
|
6114
6382
|
async function runCli(args2) {
|
|
6115
6383
|
const command = args2[0];
|
|
6116
6384
|
switch (command) {
|
|
@@ -6298,7 +6566,7 @@ Commands:
|
|
|
6298
6566
|
}
|
|
6299
6567
|
function printHelp() {
|
|
6300
6568
|
console.log(`
|
|
6301
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.
|
|
6569
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.6.0)
|
|
6302
6570
|
|
|
6303
6571
|
Setup:
|
|
6304
6572
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -6338,6 +6606,7 @@ Payments:
|
|
|
6338
6606
|
Hooks (auto-called by Claude Code):
|
|
6339
6607
|
indelible-mcp hook pre-compact Auto-save before compaction
|
|
6340
6608
|
indelible-mcp hook post-compact Auto-restore after compaction
|
|
6609
|
+
indelible-mcp hook pre-tool-use Guardrails: block a key leak / flag an irreversible command
|
|
6341
6610
|
|
|
6342
6611
|
Get your key: Sign in at indelible.one \u2192 Settings \u2192 Private Key
|
|
6343
6612
|
Learn more: https://indelible.one
|
|
@@ -6372,20 +6641,20 @@ async function runPreCompactSave() {
|
|
|
6372
6641
|
process.exit(0);
|
|
6373
6642
|
}
|
|
6374
6643
|
function findMemoryDir() {
|
|
6375
|
-
const projectsDir =
|
|
6376
|
-
if (!
|
|
6644
|
+
const projectsDir = join14(homedir13(), ".claude", "projects");
|
|
6645
|
+
if (!existsSync15(projectsDir)) return null;
|
|
6377
6646
|
let newestTime = 0;
|
|
6378
6647
|
let newestProject = null;
|
|
6379
6648
|
const projects = readdirSync(projectsDir);
|
|
6380
6649
|
for (const project of projects) {
|
|
6381
|
-
const projectPath =
|
|
6650
|
+
const projectPath = join14(projectsDir, project);
|
|
6382
6651
|
try {
|
|
6383
6652
|
const pStat = statSync(projectPath);
|
|
6384
6653
|
if (!pStat.isDirectory()) continue;
|
|
6385
6654
|
const files = readdirSync(projectPath);
|
|
6386
6655
|
for (const file of files) {
|
|
6387
6656
|
if (!file.endsWith(".jsonl")) continue;
|
|
6388
|
-
const fStat = statSync(
|
|
6657
|
+
const fStat = statSync(join14(projectPath, file));
|
|
6389
6658
|
if (fStat.mtimeMs > newestTime) {
|
|
6390
6659
|
newestTime = fStat.mtimeMs;
|
|
6391
6660
|
newestProject = projectPath;
|
|
@@ -6394,7 +6663,7 @@ function findMemoryDir() {
|
|
|
6394
6663
|
} catch {
|
|
6395
6664
|
}
|
|
6396
6665
|
}
|
|
6397
|
-
return newestProject ?
|
|
6666
|
+
return newestProject ? join14(newestProject, "memory") : null;
|
|
6398
6667
|
}
|
|
6399
6668
|
async function runPostCompactRestore() {
|
|
6400
6669
|
const config = await loadConfig();
|
|
@@ -6443,10 +6712,10 @@ async function runPostCompactRestore() {
|
|
|
6443
6712
|
try {
|
|
6444
6713
|
const memoryDir = findMemoryDir();
|
|
6445
6714
|
if (memoryDir && (config.memory_file_txid || config.session_history_txid)) {
|
|
6446
|
-
if (!
|
|
6715
|
+
if (!existsSync15(memoryDir)) mkdirSync6(memoryDir, { recursive: true });
|
|
6447
6716
|
if (config.memory_file_txid) {
|
|
6448
|
-
const memPath =
|
|
6449
|
-
if (!
|
|
6717
|
+
const memPath = join14(memoryDir, "MEMORY.md");
|
|
6718
|
+
if (!existsSync15(memPath)) {
|
|
6450
6719
|
const memResult = await loadFile(config.memory_file_txid, { outputPath: memPath });
|
|
6451
6720
|
if (memResult.success) {
|
|
6452
6721
|
process.stderr.write(`Indelible: MEMORY.md restored from chain (file was missing)
|
|
@@ -6455,8 +6724,8 @@ async function runPostCompactRestore() {
|
|
|
6455
6724
|
}
|
|
6456
6725
|
}
|
|
6457
6726
|
if (config.session_history_txid) {
|
|
6458
|
-
const histPath =
|
|
6459
|
-
if (!
|
|
6727
|
+
const histPath = join14(memoryDir, "session-history.md");
|
|
6728
|
+
if (!existsSync15(histPath)) {
|
|
6460
6729
|
const histResult = await loadFile(config.session_history_txid, { outputPath: histPath });
|
|
6461
6730
|
if (histResult.success) {
|
|
6462
6731
|
process.stderr.write(`Indelible: session-history.md restored from chain (file was missing)
|
|
@@ -6472,9 +6741,9 @@ async function runPostCompactRestore() {
|
|
|
6472
6741
|
try {
|
|
6473
6742
|
const histDir = findMemoryDir();
|
|
6474
6743
|
if (histDir) {
|
|
6475
|
-
const histPath =
|
|
6476
|
-
if (
|
|
6477
|
-
const histContent =
|
|
6744
|
+
const histPath = join14(histDir, "session-history.md");
|
|
6745
|
+
if (existsSync15(histPath)) {
|
|
6746
|
+
const histContent = readFileSync11(histPath, "utf-8");
|
|
6478
6747
|
const lines = histContent.split("\n");
|
|
6479
6748
|
const entryStarts = [];
|
|
6480
6749
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -6516,7 +6785,7 @@ function readStdin() {
|
|
|
6516
6785
|
}
|
|
6517
6786
|
var SERVER_INFO = {
|
|
6518
6787
|
name: "indelible",
|
|
6519
|
-
version: "4.
|
|
6788
|
+
version: "4.6.0",
|
|
6520
6789
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
6521
6790
|
};
|
|
6522
6791
|
var TOOLS = [
|
|
@@ -6800,6 +7069,21 @@ var TOOLS = [
|
|
|
6800
7069
|
required: []
|
|
6801
7070
|
}
|
|
6802
7071
|
},
|
|
7072
|
+
{
|
|
7073
|
+
name: "report_bug",
|
|
7074
|
+
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).`,
|
|
7075
|
+
inputSchema: {
|
|
7076
|
+
type: "object",
|
|
7077
|
+
properties: {
|
|
7078
|
+
summary: { type: "string", description: "One-line summary of the bug." },
|
|
7079
|
+
description: { type: "string", description: "What you were doing, what went wrong, and exact repro steps." },
|
|
7080
|
+
severity: { type: "string", enum: ["low", "medium", "high"], description: "User-facing impact." },
|
|
7081
|
+
tool_name: { type: "string", description: "Which Indelible tool/command failed (optional)." },
|
|
7082
|
+
last_error: { type: "string", description: "The exact error string you saw (optional)." }
|
|
7083
|
+
},
|
|
7084
|
+
required: ["summary", "description"]
|
|
7085
|
+
}
|
|
7086
|
+
},
|
|
6803
7087
|
{
|
|
6804
7088
|
name: "share_session",
|
|
6805
7089
|
description: "Issue a viewing key for a saved session, granting selective access to a recipient (g-170). Recipient gets an ECIES-wrapped content key bound to their wallet.",
|
|
@@ -6920,6 +7204,15 @@ async function handleMcpRequest(request) {
|
|
|
6920
7204
|
max_index: args2?.max_index || 150
|
|
6921
7205
|
});
|
|
6922
7206
|
break;
|
|
7207
|
+
case "report_bug":
|
|
7208
|
+
result = await reportBug({
|
|
7209
|
+
summary: args2?.summary,
|
|
7210
|
+
description: args2?.description,
|
|
7211
|
+
severity: args2?.severity || "medium",
|
|
7212
|
+
tool_name: args2?.tool_name || null,
|
|
7213
|
+
last_error: args2?.last_error || null
|
|
7214
|
+
});
|
|
7215
|
+
break;
|
|
6923
7216
|
case "share_session":
|
|
6924
7217
|
result = await shareSession({
|
|
6925
7218
|
session_txid: args2?.session_txid,
|
|
@@ -6995,11 +7288,11 @@ async function runWizard() {
|
|
|
6995
7288
|
}
|
|
6996
7289
|
let mcpOk = false;
|
|
6997
7290
|
for (const cfgPath of [
|
|
6998
|
-
|
|
6999
|
-
|
|
7291
|
+
join14(homedir13(), ".claude.json"),
|
|
7292
|
+
join14(homedir13(), ".claude", "settings.json")
|
|
7000
7293
|
]) {
|
|
7001
7294
|
try {
|
|
7002
|
-
const s = JSON.parse(
|
|
7295
|
+
const s = JSON.parse(readFileSync11(cfgPath, "utf8"));
|
|
7003
7296
|
if (s?.mcpServers?.indelible) {
|
|
7004
7297
|
mcpOk = true;
|
|
7005
7298
|
break;
|
|
@@ -7009,10 +7302,10 @@ async function runWizard() {
|
|
|
7009
7302
|
}
|
|
7010
7303
|
console.log(mcpOk ? " \u2713 MCP registered with Claude Code" : " \u2717 MCP not registered");
|
|
7011
7304
|
if (!mcpOk) allGood = false;
|
|
7012
|
-
const hooksPath =
|
|
7305
|
+
const hooksPath = join14(homedir13(), ".claude", "settings.local.json");
|
|
7013
7306
|
let hooksOk = false;
|
|
7014
7307
|
try {
|
|
7015
|
-
const s = JSON.parse(
|
|
7308
|
+
const s = JSON.parse(readFileSync11(hooksPath, "utf8"));
|
|
7016
7309
|
hooksOk = s?.hooks?.PreCompact?.some(
|
|
7017
7310
|
(h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp")) || h.command?.includes("indelible-mcp")
|
|
7018
7311
|
) && s?.hooks?.SessionStart?.some(
|
|
@@ -7246,6 +7539,8 @@ if (args[0] === "hook") {
|
|
|
7246
7539
|
}
|
|
7247
7540
|
process.exit(0);
|
|
7248
7541
|
})();
|
|
7542
|
+
} else if (args[1] === "pre-tool-use") {
|
|
7543
|
+
runPreToolUseGuard();
|
|
7249
7544
|
} else {
|
|
7250
7545
|
console.error("Unknown hook:", args[1]);
|
|
7251
7546
|
process.exit(1);
|