indelible-mcp 4.5.1 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +303 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "4.5.1",
3
+ "version": "4.6.0",
4
4
  "description": "Blockchain-backed memory and code storage for Claude Code. Save AI conversations and source code permanently on BSV.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -257,11 +257,42 @@ async function checkHealth() {
257
257
  }
258
258
  }
259
259
  async function getUtxos(address) {
260
- const res = await spvFetch(`/api/address/${address}/unspent`);
261
- const utxos = await res.json();
262
- process.stderr.write(`[MCP] UTXOs from bridge: ${Array.isArray(utxos) ? utxos.length : 0}
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
- return utxos;
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`);
@@ -1081,10 +1112,78 @@ var init_wall_stub = __esm({
1081
1112
  init_config_customer();
1082
1113
  import { createInterface as createInterface3 } from "node:readline";
1083
1114
  import { execSync as execSync2 } from "node:child_process";
1084
- import { homedir as homedir12 } from "node:os";
1085
- import { join as join13, dirname as dirname4, resolve } from "node:path";
1086
- import { fileURLToPath } from "node:url";
1087
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync, statSync } from "node:fs";
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
+ }
1088
1187
 
1089
1188
  // mcp-server/tools/setup_wallet.js
1090
1189
  init_config_customer();
@@ -5903,6 +6002,116 @@ async function recallContext({ from_date = null, to_date = null, query = null, d
5903
6002
  };
5904
6003
  }
5905
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
+
5906
6115
  // mcp-server/tools/share_session.js
5907
6116
  init_config_customer();
5908
6117
  init_spv();
@@ -6086,16 +6295,16 @@ init_wall_stub();
6086
6295
  init_wall_stub();
6087
6296
  init_wall_stub();
6088
6297
  init_wall_stub();
6089
- var PROJECT_ROOT_FOR_CLONE_CHECK = resolve(dirname4(fileURLToPath(import.meta.url)), "../..");
6090
- var CONTEXT_FILE2 = join13(homedir12(), ".indelible", "indelible-context.jsonl");
6298
+ var PROJECT_ROOT_FOR_CLONE_CHECK = resolve(dirname5(fileURLToPath2(import.meta.url)), "../..");
6299
+ var CONTEXT_FILE2 = join14(homedir13(), ".indelible", "indelible-context.jsonl");
6091
6300
  function installHooks() {
6092
- const claudeDir = join13(homedir12(), ".claude");
6093
- const settingsPath = join13(claudeDir, "settings.local.json");
6094
- if (!existsSync14(claudeDir)) mkdirSync6(claudeDir, { recursive: true });
6301
+ const claudeDir = join14(homedir13(), ".claude");
6302
+ const settingsPath = join14(claudeDir, "settings.local.json");
6303
+ if (!existsSync15(claudeDir)) mkdirSync6(claudeDir, { recursive: true });
6095
6304
  let settings = {};
6096
- if (existsSync14(settingsPath)) {
6305
+ if (existsSync15(settingsPath)) {
6097
6306
  try {
6098
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
6307
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
6099
6308
  } catch {
6100
6309
  settings = {};
6101
6310
  }
@@ -6110,6 +6319,9 @@ function installHooks() {
6110
6319
  const hasSessionStart = settings.hooks.SessionStart?.some(
6111
6320
  (h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp hook session-start")) || h.command?.includes("indelible-mcp hook session-start")
6112
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
+ );
6113
6325
  const installed = [];
6114
6326
  if (!hasPreCompact) {
6115
6327
  if (!settings.hooks.PreCompact) settings.hooks.PreCompact = [];
@@ -6135,9 +6347,38 @@ function installHooks() {
6135
6347
  });
6136
6348
  installed.push("SessionStart:style");
6137
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
+ }
6138
6358
  writeFileSync7(settingsPath, JSON.stringify(settings, null, 2));
6139
6359
  return { settingsPath, installed, alreadyInstalled: installed.length === 0 };
6140
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
+ }
6141
6382
  async function runCli(args2) {
6142
6383
  const command = args2[0];
6143
6384
  switch (command) {
@@ -6325,7 +6566,7 @@ Commands:
6325
6566
  }
6326
6567
  function printHelp() {
6327
6568
  console.log(`
6328
- Indelible MCP \u2014 Blockchain memory for Claude Code (v4.5.1)
6569
+ Indelible MCP \u2014 Blockchain memory for Claude Code (v4.6.0)
6329
6570
 
6330
6571
  Setup:
6331
6572
  indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
@@ -6365,6 +6606,7 @@ Payments:
6365
6606
  Hooks (auto-called by Claude Code):
6366
6607
  indelible-mcp hook pre-compact Auto-save before compaction
6367
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
6368
6610
 
6369
6611
  Get your key: Sign in at indelible.one \u2192 Settings \u2192 Private Key
6370
6612
  Learn more: https://indelible.one
@@ -6399,20 +6641,20 @@ async function runPreCompactSave() {
6399
6641
  process.exit(0);
6400
6642
  }
6401
6643
  function findMemoryDir() {
6402
- const projectsDir = join13(homedir12(), ".claude", "projects");
6403
- if (!existsSync14(projectsDir)) return null;
6644
+ const projectsDir = join14(homedir13(), ".claude", "projects");
6645
+ if (!existsSync15(projectsDir)) return null;
6404
6646
  let newestTime = 0;
6405
6647
  let newestProject = null;
6406
6648
  const projects = readdirSync(projectsDir);
6407
6649
  for (const project of projects) {
6408
- const projectPath = join13(projectsDir, project);
6650
+ const projectPath = join14(projectsDir, project);
6409
6651
  try {
6410
6652
  const pStat = statSync(projectPath);
6411
6653
  if (!pStat.isDirectory()) continue;
6412
6654
  const files = readdirSync(projectPath);
6413
6655
  for (const file of files) {
6414
6656
  if (!file.endsWith(".jsonl")) continue;
6415
- const fStat = statSync(join13(projectPath, file));
6657
+ const fStat = statSync(join14(projectPath, file));
6416
6658
  if (fStat.mtimeMs > newestTime) {
6417
6659
  newestTime = fStat.mtimeMs;
6418
6660
  newestProject = projectPath;
@@ -6421,7 +6663,7 @@ function findMemoryDir() {
6421
6663
  } catch {
6422
6664
  }
6423
6665
  }
6424
- return newestProject ? join13(newestProject, "memory") : null;
6666
+ return newestProject ? join14(newestProject, "memory") : null;
6425
6667
  }
6426
6668
  async function runPostCompactRestore() {
6427
6669
  const config = await loadConfig();
@@ -6470,10 +6712,10 @@ async function runPostCompactRestore() {
6470
6712
  try {
6471
6713
  const memoryDir = findMemoryDir();
6472
6714
  if (memoryDir && (config.memory_file_txid || config.session_history_txid)) {
6473
- if (!existsSync14(memoryDir)) mkdirSync6(memoryDir, { recursive: true });
6715
+ if (!existsSync15(memoryDir)) mkdirSync6(memoryDir, { recursive: true });
6474
6716
  if (config.memory_file_txid) {
6475
- const memPath = join13(memoryDir, "MEMORY.md");
6476
- if (!existsSync14(memPath)) {
6717
+ const memPath = join14(memoryDir, "MEMORY.md");
6718
+ if (!existsSync15(memPath)) {
6477
6719
  const memResult = await loadFile(config.memory_file_txid, { outputPath: memPath });
6478
6720
  if (memResult.success) {
6479
6721
  process.stderr.write(`Indelible: MEMORY.md restored from chain (file was missing)
@@ -6482,8 +6724,8 @@ async function runPostCompactRestore() {
6482
6724
  }
6483
6725
  }
6484
6726
  if (config.session_history_txid) {
6485
- const histPath = join13(memoryDir, "session-history.md");
6486
- if (!existsSync14(histPath)) {
6727
+ const histPath = join14(memoryDir, "session-history.md");
6728
+ if (!existsSync15(histPath)) {
6487
6729
  const histResult = await loadFile(config.session_history_txid, { outputPath: histPath });
6488
6730
  if (histResult.success) {
6489
6731
  process.stderr.write(`Indelible: session-history.md restored from chain (file was missing)
@@ -6499,9 +6741,9 @@ async function runPostCompactRestore() {
6499
6741
  try {
6500
6742
  const histDir = findMemoryDir();
6501
6743
  if (histDir) {
6502
- const histPath = join13(histDir, "session-history.md");
6503
- if (existsSync14(histPath)) {
6504
- const histContent = readFileSync10(histPath, "utf-8");
6744
+ const histPath = join14(histDir, "session-history.md");
6745
+ if (existsSync15(histPath)) {
6746
+ const histContent = readFileSync11(histPath, "utf-8");
6505
6747
  const lines = histContent.split("\n");
6506
6748
  const entryStarts = [];
6507
6749
  for (let i = 0; i < lines.length; i++) {
@@ -6543,7 +6785,7 @@ function readStdin() {
6543
6785
  }
6544
6786
  var SERVER_INFO = {
6545
6787
  name: "indelible",
6546
- version: "4.5.1",
6788
+ version: "4.6.0",
6547
6789
  description: "Blockchain-backed memory and code storage for Claude Code"
6548
6790
  };
6549
6791
  var TOOLS = [
@@ -6827,6 +7069,21 @@ var TOOLS = [
6827
7069
  required: []
6828
7070
  }
6829
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
+ },
6830
7087
  {
6831
7088
  name: "share_session",
6832
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.",
@@ -6947,6 +7204,15 @@ async function handleMcpRequest(request) {
6947
7204
  max_index: args2?.max_index || 150
6948
7205
  });
6949
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;
6950
7216
  case "share_session":
6951
7217
  result = await shareSession({
6952
7218
  session_txid: args2?.session_txid,
@@ -7022,11 +7288,11 @@ async function runWizard() {
7022
7288
  }
7023
7289
  let mcpOk = false;
7024
7290
  for (const cfgPath of [
7025
- join13(homedir12(), ".claude.json"),
7026
- join13(homedir12(), ".claude", "settings.json")
7291
+ join14(homedir13(), ".claude.json"),
7292
+ join14(homedir13(), ".claude", "settings.json")
7027
7293
  ]) {
7028
7294
  try {
7029
- const s = JSON.parse(readFileSync10(cfgPath, "utf8"));
7295
+ const s = JSON.parse(readFileSync11(cfgPath, "utf8"));
7030
7296
  if (s?.mcpServers?.indelible) {
7031
7297
  mcpOk = true;
7032
7298
  break;
@@ -7036,10 +7302,10 @@ async function runWizard() {
7036
7302
  }
7037
7303
  console.log(mcpOk ? " \u2713 MCP registered with Claude Code" : " \u2717 MCP not registered");
7038
7304
  if (!mcpOk) allGood = false;
7039
- const hooksPath = join13(homedir12(), ".claude", "settings.local.json");
7305
+ const hooksPath = join14(homedir13(), ".claude", "settings.local.json");
7040
7306
  let hooksOk = false;
7041
7307
  try {
7042
- const s = JSON.parse(readFileSync10(hooksPath, "utf8"));
7308
+ const s = JSON.parse(readFileSync11(hooksPath, "utf8"));
7043
7309
  hooksOk = s?.hooks?.PreCompact?.some(
7044
7310
  (h) => h.hooks?.some((hh) => hh.command?.includes("indelible-mcp")) || h.command?.includes("indelible-mcp")
7045
7311
  ) && s?.hooks?.SessionStart?.some(
@@ -7273,6 +7539,8 @@ if (args[0] === "hook") {
7273
7539
  }
7274
7540
  process.exit(0);
7275
7541
  })();
7542
+ } else if (args[1] === "pre-tool-use") {
7543
+ runPreToolUseGuard();
7276
7544
  } else {
7277
7545
  console.error("Unknown hook:", args[1]);
7278
7546
  process.exit(1);