indelible-mcp 5.7.6 → 5.7.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/CLI_HANDBOOK.md CHANGED
@@ -172,27 +172,27 @@ How the money works, honestly: a buyer pays **your agent's own address** (never
172
172
 
173
173
  ### Save Session
174
174
  ```bash
175
- node C:/Indelible-main/indelible-cli/src/index.js save --summary "what happened"
175
+ indelible-mcp save --summary "what happened"
176
176
  ```
177
177
 
178
178
  ### Load from Blockchain
179
179
  ```bash
180
- node C:/Indelible-main/indelible-cli/src/index.js load --sessions=5
180
+ indelible-mcp load --sessions=5
181
181
  ```
182
182
 
183
183
  ### Check Status
184
184
  ```bash
185
- node C:/Indelible-main/indelible-cli/src/index.js status
185
+ indelible-mcp status
186
186
  ```
187
187
 
188
188
  ### Save a File
189
189
  ```bash
190
- node C:/Indelible-main/indelible-cli/src/index.js vault save-file /path/to/file.js
190
+ indelible-mcp vault save-file /path/to/file.js
191
191
  ```
192
192
 
193
193
  ### Ask Codex
194
194
  ```bash
195
- node C:/Indelible-main/indelible-cli/src/index.js diary chat "How should we architect this?"
195
+ indelible-mcp diary chat "How should we architect this?"
196
196
  ```
197
197
 
198
198
  ---
@@ -249,15 +249,13 @@ The CLI and MCP server are the **same codebase**. They import the same tool func
249
249
 
250
250
  | | CLI | MCP Server |
251
251
  |---|---|---|
252
- | **Location** | `C:/Indelible-main/indelible-cli/src/` | `C:/bsv-claude-wrapper/mcp-server/` |
252
+ | **Location** | `(published package)` | `(core source)` |
253
253
  | **Config** | Sync (`readFileSync`/`writeFileSync`) | Async (`await loadConfig()`) |
254
254
  | **Fetch** | Native `fetch` (Node 18+) | `node-fetch` package |
255
255
  | **Timeout** | `AbortSignal.timeout(ms)` | Manual `AbortController` + `setTimeout` |
256
256
  | **Entry point** | CLI arg parser in `index.js` | JSON-RPC stdin/stdout in `index.js` |
257
257
  | **How Claude calls it** | `node src/index.js <command>` | MCP protocol via Claude Code hooks |
258
258
 
259
- **Sync rule:** Any feature, fix, or change that goes into one MUST go into both. No exceptions. Adapt sync/async config pattern accordingly.
260
-
261
259
  ---
262
260
 
263
261
  ## Testing / Dog-Fooding
@@ -267,24 +265,24 @@ The CLI is a live test harness for the MCP. When you test a CLI command, you're
267
265
  ### Safe tests (no sats spent)
268
266
  ```bash
269
267
  # Config loads correctly
270
- node C:/Indelible-main/indelible-cli/src/index.js status
268
+ indelible-mcp status
271
269
 
272
270
  # Blockchain read works
273
- node C:/Indelible-main/indelible-cli/src/index.js load --sessions=1
271
+ indelible-mcp load --sessions=1
274
272
 
275
273
  # Style loads from chain
276
- node C:/Indelible-main/indelible-cli/src/index.js vault load-style
274
+ indelible-mcp vault load-style
277
275
  ```
278
276
 
279
277
  ### Function-level tests (no sats spent)
280
278
  ```bash
281
- cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
279
+ cd /path/to/indelible-cli && node --input-type=module -e "
282
280
  import { checkConfirmation } from './src/lib/spv.js';
283
281
  const r = await checkConfirmation('TXID_HERE');
284
282
  console.log(JSON.stringify(r, null, 2));
285
283
  " 2>&1
286
284
 
287
- cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
285
+ cd /path/to/indelible-cli && node --input-type=module -e "
288
286
  import { checkTier } from './src/lib/api-client.js';
289
287
  import { loadConfig } from './src/lib/config.js';
290
288
  const config = loadConfig();
@@ -292,7 +290,7 @@ const r = await checkTier(config.api_key);
292
290
  console.log(JSON.stringify(r, null, 2));
293
291
  " 2>&1
294
292
 
295
- cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
293
+ cd /path/to/indelible-cli && node --input-type=module -e "
296
294
  import { verifyRecentSaves } from './src/tools/save_file.js';
297
295
  const r = await verifyRecentSaves();
298
296
  console.log(JSON.stringify(r, null, 2));
@@ -302,16 +300,16 @@ console.log(JSON.stringify(r, null, 2));
302
300
  ### Live tests (spends sats)
303
301
  ```bash
304
302
  # Session save (delta if prior save exists)
305
- node C:/Indelible-main/indelible-cli/src/index.js save --summary "test save"
303
+ indelible-mcp save --summary "test save"
306
304
 
307
305
  # File save
308
- node C:/Indelible-main/indelible-cli/src/index.js vault save-file /path/to/small/file.txt
306
+ indelible-mcp vault save-file /path/to/small/file.txt
309
307
 
310
308
  # Style save (auto-prepends core rules via ensureCoreRules)
311
- node C:/Indelible-main/indelible-cli/src/index.js vault save-style /path/to/rules.txt --name=test
309
+ indelible-mcp vault save-style /path/to/rules.txt --name=test
312
310
 
313
311
  # Diary chat (costs OpenAI tokens, not sats)
314
- node C:/Indelible-main/indelible-cli/src/index.js diary chat "hello"
312
+ indelible-mcp diary chat "hello"
315
313
  ```
316
314
 
317
315
  ---
@@ -370,7 +368,7 @@ indelible-cli/src/
370
368
  ## Rebuild the Executable
371
369
 
372
370
  ```bash
373
- cd "C:/Indelible-main/indelible-cli" && bun build --compile src/index.js --outfile dist/indelible.exe
371
+ cd /path/to/indelible-cli && bun build --compile src/index.js --outfile dist/indelible.exe
374
372
  ```
375
373
 
376
374
  This creates a standalone `indelible.exe` — no Node.js required on the target machine.
@@ -395,7 +393,7 @@ This creates a standalone `indelible.exe` — no Node.js required on the target
395
393
 
396
394
  ## Config File
397
395
 
398
- `C:/Users/oorel/.indelible/config.json` — shared with MCP server.
396
+ `~/.indelible/config.json` — shared with MCP server.
399
397
 
400
398
  | Setting | Description |
401
399
  |---------|-------------|
@@ -420,35 +418,35 @@ This creates a standalone `indelible.exe` — no Node.js required on the target
420
418
  - **Balance:** `indelible-mcp status`, or look your address up in the Chain Browser at indelible.one/explorer
421
419
  - **Fund it:** Send BSV to your own address
422
420
  - **Cost:** ~$0.21/MB at BSV=$16. Session saves are fractions of a cent.
423
-
424
- ---
425
-
426
- ## The Strongbox — your raw session files, kept
427
-
428
- Claude Code deletes your raw session transcripts by default after about 30 days (its cleanup
429
- setting). Those files are the richest record you have — every tool call, every word, verbatim —
430
- richer even than your encrypted chain saves. The Strongbox keeps a verified, byte-identical copy
431
- of them under your own roof: `~/.indelible/transcript-vault/` on your machine. Nothing leaves
432
- your computer; nothing is redacted (it is a byte-copy in your own trust domain — your chain
433
- saves redact, your local Strongbox does not, deliberately).
434
-
435
- **You mostly never touch it.** `setup` installs two hooks so every compaction banks a copy and
436
- every session end does a forced verified refresh. Every successful save also protects the file
437
- it just read — including Codex sessions. If protecting ever fails, it fails silently rather
438
- than break your session.
439
-
440
- **Commands:**
441
-
442
- indelible-mcp strongbox # look: what's protected, sizes, when
443
- indelible-mcp strongbox run # protect the current project's session now
444
- indelible-mcp strongbox run --session <id> # pick one when several exist (it never guesses)
445
- indelible-mcp strongbox run --path <file> # protect a specific transcript file
446
-
447
- A path outside your recognized transcript folders is refused — if you're deliberately rescuing
448
- a stray transcript from a backup, add `--outside-transcript-roots` (named that loudly on
449
- purpose). Symlinks are refused. The Strongbox never shrinks: a copy is only ever replaced by a
450
- verified larger one, and the original source file is never written to, ever.
451
-
452
- **Disk honesty:** copies cost disk. The Strongbox warns loudly when space runs low and never
453
- deletes anything on its own. Don't cloud-sync `~/.indelible` — it holds your keys and now your
454
- raw conversations.
421
+
422
+ ---
423
+
424
+ ## The Strongbox — your raw session files, kept
425
+
426
+ Claude Code deletes your raw session transcripts by default after about 30 days (its cleanup
427
+ setting). Those files are the richest record you have — every tool call, every word, verbatim —
428
+ richer even than your encrypted chain saves. The Strongbox keeps a verified, byte-identical copy
429
+ of them under your own roof: `~/.indelible/transcript-vault/` on your machine. Nothing leaves
430
+ your computer; nothing is redacted (it is a byte-copy in your own trust domain — your chain
431
+ saves redact, your local Strongbox does not, deliberately).
432
+
433
+ **You mostly never touch it.** `setup` installs two hooks so every compaction banks a copy and
434
+ every session end does a forced verified refresh. Every successful save also protects the file
435
+ it just read — including Codex sessions. If protecting ever fails, it fails silently rather
436
+ than break your session.
437
+
438
+ **Commands:**
439
+
440
+ indelible-mcp strongbox # look: what's protected, sizes, when
441
+ indelible-mcp strongbox run # protect the current project's session now
442
+ indelible-mcp strongbox run --session <id> # pick one when several exist (it never guesses)
443
+ indelible-mcp strongbox run --path <file> # protect a specific transcript file
444
+
445
+ A path outside your recognized transcript folders is refused — if you're deliberately rescuing
446
+ a stray transcript from a backup, add `--outside-transcript-roots` (named that loudly on
447
+ purpose). Symlinks are refused. The Strongbox never shrinks: a copy is only ever replaced by a
448
+ verified larger one, and the original source file is never written to, ever.
449
+
450
+ **Disk honesty:** copies cost disk. The Strongbox warns loudly when space runs low and never
451
+ deletes anything on its own. Don't cloud-sync `~/.indelible` — it holds your keys and now your
452
+ raw conversations.
@@ -19,7 +19,7 @@ Your day-one roster is small and real: two guards, one companion, and one memory
19
19
 
20
20
  That is the whole list **for this package**. Your agent crew is a different thing and it lives in the web app: sign in at indelible.one, open the Sanctuary, and birth sixteen agents derived from your own wallet. They are yours today, not a preview. Section 7 walks through it, and section 8 is honest about the two parts that are still unfinished.
21
21
 
22
- ### The 29 tools you can call today
22
+ ### The 32 tools you can call today
23
23
 
24
24
  Grouped by what they cost:
25
25
 
@@ -38,9 +38,14 @@ One important nuance on "free": free means no subscription. On-chain writes stil
38
38
 
39
39
  ---
40
40
 
41
- ## 2. What fires automatically (the four hooks)
41
+ ## 2. What fires automatically (the hooks)
42
42
 
43
- When you ran setup, the installer wired four hooks into Claude Code (`~/.claude/settings.local.json`). This is what each one does and why it is there.
43
+ When you ran setup, the installer wired **six hooks across four events** into Claude Code, in
44
+ `~/.claude/settings.json`: two on PreCompact, two on SessionStart, one on PreToolUse, and one on
45
+ SessionEnd. (`settings.local.json` is the older location the installer migrates *from*.) You can
46
+ see exactly what is installed at any time with `indelible-mcp install-hooks`, which prints the
47
+ full inventory of every hook it manages before it writes anything. This is what each one does and
48
+ why it is there.
44
49
 
45
50
  | Hook | Fires | Does |
46
51
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "5.7.6",
3
+ "version": "5.7.7",
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
@@ -1003,8 +1003,8 @@ async function linkReservation(token, { buildCacheKey: buildCacheKey2 = null, tx
1003
1003
  return true;
1004
1004
  }, LOCK_OPTS);
1005
1005
  }
1006
- async function linkOrRefuse(token, payload2 = {}) {
1007
- const linked = await linkReservation(token, payload2);
1006
+ async function linkOrRefuse(token, payload = {}) {
1007
+ const linked = await linkReservation(token, payload);
1008
1008
  if (linked) return true;
1009
1009
  const err9 = new Error(
1010
1010
  "RESERVATION_LOST: the reservation holding these inputs is gone, so the coins are no longer excluded from another writer. Nothing was broadcast. Retry \u2014 the next attempt reserves fresh inputs. (Broadcasting now could spend a coin a second process has already sent.)"
@@ -2397,12 +2397,12 @@ async function commitSession(session, wif) {
2397
2397
  claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS });
2398
2398
  const utxos = claim2.utxos;
2399
2399
  if (utxos && utxos.length > 0) {
2400
- const payload2 = {
2400
+ const payload = {
2401
2401
  protocol: "indelible.claude-code",
2402
2402
  encrypted: session.encrypted,
2403
2403
  wrap_owner: session.wrap_owner || null
2404
2404
  };
2405
- let { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload2));
2405
+ let { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload));
2406
2406
  await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
2407
2407
  let writeReceipt;
2408
2408
  try {
@@ -2413,7 +2413,7 @@ async function commitSession(session, wif) {
2413
2413
  claim2 = await claimSpendableUtxos(session.address, getUtxos, { reconcile: RECONCILE_HOOKS });
2414
2414
  const fresh = claim2.utxos;
2415
2415
  if (!fresh || fresh.length === 0) throw bErr;
2416
- ({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, fresh, JSON.stringify(payload2)));
2416
+ ({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, fresh, JSON.stringify(payload)));
2417
2417
  await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
2418
2418
  try {
2419
2419
  writeReceipt = await broadcastTx(txHex);
@@ -3043,22 +3043,22 @@ function consumeShare(envelope, recipientPrivkey, ownerPubkey) {
3043
3043
  const aad = buildAAD(envelope.owner_addr, envelope.recipient_addr, envelope.scope_txid);
3044
3044
  return eciesUnwrap(envelope.wrap_recipient, recipientPrivkey, aad);
3045
3045
  }
3046
- function detectSaveFormat(payload2) {
3047
- const hasV3Fields = payload2 != null && (payload2.wrap_self !== void 0 || payload2.wrap_version !== void 0);
3046
+ function detectSaveFormat(payload) {
3047
+ const hasV3Fields = payload != null && (payload.wrap_self !== void 0 || payload.wrap_version !== void 0);
3048
3048
  if (hasV3Fields) {
3049
- if (payload2.wrap_version === 3 && typeof payload2.wrap_self === "string" && payload2.wrap_self.length > 0) return "v3-wallet";
3049
+ if (payload.wrap_version === 3 && typeof payload.wrap_self === "string" && payload.wrap_self.length > 0) return "v3-wallet";
3050
3050
  return "v3-malformed";
3051
3051
  }
3052
- if (payload2 && payload2.version === 2) return "v2-brc100";
3053
- if (payload2 && typeof payload2.wrap_owner === "string" && payload2.wrap_owner.length > 0) return "v2-wrapped";
3052
+ if (payload && payload.version === 2) return "v2-brc100";
3053
+ if (payload && typeof payload.wrap_owner === "string" && payload.wrap_owner.length > 0) return "v2-wrapped";
3054
3054
  return "legacy";
3055
3055
  }
3056
- function detectVaultFormat(payload2) {
3057
- if (!payload2) return "legacy";
3056
+ function detectVaultFormat(payload) {
3057
+ if (!payload) return "legacy";
3058
3058
  return detectSaveFormat({
3059
- wrap_owner: payload2.wrap_owner,
3060
- wrap_self: payload2.wrap_self,
3061
- wrap_version: payload2.wrap_version
3059
+ wrap_owner: payload.wrap_owner,
3060
+ wrap_self: payload.wrap_self,
3061
+ wrap_version: payload.wrap_version
3062
3062
  });
3063
3063
  }
3064
3064
  function generateContentKey() {
@@ -3367,7 +3367,7 @@ async function saveStyle(rulesText, styleName, description) {
3367
3367
  rules: rulesText
3368
3368
  };
3369
3369
  const encrypted = encrypt(JSON.stringify(style), wif);
3370
- const payload2 = {
3370
+ const payload = {
3371
3371
  protocol: "indelible.style",
3372
3372
  version: 1,
3373
3373
  name: styleName,
@@ -3390,7 +3390,7 @@ async function saveStyle(rulesText, styleName, description) {
3390
3390
  await settleReservation(claim2.token, "abort");
3391
3391
  return { success: false, error: "No UTXOs available. Fund your wallet first." };
3392
3392
  }
3393
- const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload2));
3393
+ const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload));
3394
3394
  await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
3395
3395
  let writeReceipt = null;
3396
3396
  let bcastErr = null;
@@ -4503,16 +4503,16 @@ import { PrivateKey as PrivateKey7, CompletedProtoWallet as CompletedProtoWallet
4503
4503
  import { Transaction as Transaction5 } from "@bsv/sdk";
4504
4504
  import { readFileSync as readFileSync11 } from "fs";
4505
4505
  async function decryptSession(payloadOrEncrypted, wif, wallet = null) {
4506
- let payload2;
4506
+ let payload;
4507
4507
  if (typeof payloadOrEncrypted === "string") {
4508
- payload2 = { encrypted: payloadOrEncrypted, wrap_owner: null };
4508
+ payload = { encrypted: payloadOrEncrypted, wrap_owner: null };
4509
4509
  } else {
4510
- payload2 = payloadOrEncrypted;
4510
+ payload = payloadOrEncrypted;
4511
4511
  }
4512
- const encrypted = payload2.encrypted;
4513
- const wrap_owner = payload2.wrap_owner;
4512
+ const encrypted = payload.encrypted;
4513
+ const wrap_owner = payload.wrap_owner;
4514
4514
  let text;
4515
- const fmt = detectSaveFormat({ wrap_owner, wrap_self: payload2.wrap_self, wrap_version: payload2.wrap_version });
4515
+ const fmt = detectSaveFormat({ wrap_owner, wrap_self: payload.wrap_self, wrap_version: payload.wrap_version });
4516
4516
  if (fmt === "v3-wallet") {
4517
4517
  let w = wallet;
4518
4518
  if (!w && wif) {
@@ -4523,24 +4523,24 @@ async function decryptSession(payloadOrEncrypted, wif, wallet = null) {
4523
4523
  }
4524
4524
  }
4525
4525
  let r = null;
4526
- if (payload2.owner_pubkey && w) {
4526
+ if (payload.owner_pubkey && w) {
4527
4527
  let idPub = null;
4528
4528
  try {
4529
4529
  idPub = (await w.getPublicKey({ identityKey: true }))?.publicKey || null;
4530
4530
  } catch {
4531
4531
  }
4532
- if (idPub && idPub !== payload2.owner_pubkey) {
4533
- if (payload2.wrap_recovery) {
4534
- const rr = await unwrapRecoveryV3(payload2.wrap_recovery, w, payload2.key_id, payload2.owner_pubkey);
4532
+ if (idPub && idPub !== payload.owner_pubkey) {
4533
+ if (payload.wrap_recovery) {
4534
+ const rr = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
4535
4535
  if (rr.ok) r = rr;
4536
4536
  }
4537
4537
  if (!r) r = { ok: false, reason: "wallet_unavailable" };
4538
4538
  }
4539
4539
  }
4540
4540
  if (!r) {
4541
- r = await unwrapForSelfV3(payload2.wrap_self, w, payload2.key_id);
4542
- if (!r.ok && r.reason === "corrupt" && payload2.wrap_recovery && payload2.owner_pubkey) {
4543
- r = await unwrapRecoveryV3(payload2.wrap_recovery, w, payload2.key_id, payload2.owner_pubkey);
4541
+ r = await unwrapForSelfV3(payload.wrap_self, w, payload.key_id);
4542
+ if (!r.ok && r.reason === "corrupt" && payload.wrap_recovery && payload.owner_pubkey) {
4543
+ r = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
4544
4544
  }
4545
4545
  }
4546
4546
  if (!r.ok) {
@@ -4622,14 +4622,14 @@ async function fetchEncryptedFromChain(txId) {
4622
4622
  }
4623
4623
  if (end > 0) {
4624
4624
  try {
4625
- const payload2 = JSON.parse(str.slice(jsonStart, end + 1));
4626
- if (payload2.encrypted) {
4627
- const out = { encrypted: payload2.encrypted, wrap_owner: payload2.wrap_owner || null };
4628
- if (payload2.wrap_version !== void 0) out.wrap_version = payload2.wrap_version;
4629
- if (payload2.wrap_self !== void 0) out.wrap_self = payload2.wrap_self;
4630
- if (payload2.key_id !== void 0) out.key_id = payload2.key_id;
4631
- if (payload2.wrap_recovery !== void 0) out.wrap_recovery = payload2.wrap_recovery;
4632
- if (payload2.owner_pubkey !== void 0) out.owner_pubkey = payload2.owner_pubkey;
4625
+ const payload = JSON.parse(str.slice(jsonStart, end + 1));
4626
+ if (payload.encrypted) {
4627
+ const out = { encrypted: payload.encrypted, wrap_owner: payload.wrap_owner || null };
4628
+ if (payload.wrap_version !== void 0) out.wrap_version = payload.wrap_version;
4629
+ if (payload.wrap_self !== void 0) out.wrap_self = payload.wrap_self;
4630
+ if (payload.key_id !== void 0) out.key_id = payload.key_id;
4631
+ if (payload.wrap_recovery !== void 0) out.wrap_recovery = payload.wrap_recovery;
4632
+ if (payload.owner_pubkey !== void 0) out.owner_pubkey = payload.owner_pubkey;
4633
4633
  return out;
4634
4634
  }
4635
4635
  } catch {
@@ -6462,11 +6462,11 @@ function canonicalJson(obj) {
6462
6462
  const keys = Object.keys(obj).sort();
6463
6463
  return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalJson(obj[k])).join(",") + "}";
6464
6464
  }
6465
- function signEnvelopeWith(identity, payload2) {
6465
+ function signEnvelopeWith(identity, payload) {
6466
6466
  if (!identity?.wif || !identity?.identity_key_hex) {
6467
6467
  throw new Error("customer-agent: signEnvelopeWith needs an identity with { wif, identity_key_hex }");
6468
6468
  }
6469
- const signedPayload = { ...payload2, issued_at: payload2.issued_at || (/* @__PURE__ */ new Date()).toISOString() };
6469
+ const signedPayload = { ...payload, issued_at: payload.issued_at || (/* @__PURE__ */ new Date()).toISOString() };
6470
6470
  const canonical = canonicalJson(signedPayload);
6471
6471
  const priv = PrivateKey11.fromWif(identity.wif);
6472
6472
  const digestBytes = Hash.sha256(canonical);
@@ -7676,7 +7676,7 @@ async function writeRecoveryArtifact(scope, plan, secret) {
7676
7676
  const cipher = createCipheriv4("aes-256-gcm", pinKey, iv);
7677
7677
  const enc = Buffer.concat([cipher.update(body, "utf8"), cipher.final()]);
7678
7678
  const tag = cipher.getAuthTag();
7679
- const payload2 = {
7679
+ const payload = {
7680
7680
  version: MIGRATION_VERSION,
7681
7681
  alg: "aes-256-gcm",
7682
7682
  kdf,
@@ -7691,7 +7691,7 @@ async function writeRecoveryArtifact(scope, plan, secret) {
7691
7691
  };
7692
7692
  const path = join33(dirname14(agentsDir(scope)), `agent-keys-recovery-${Date.now()}.json`);
7693
7693
  const tmp = `${path}.tmp`;
7694
- await writeFile8(tmp, JSON.stringify(payload2, null, 2), { mode: 384 });
7694
+ await writeFile8(tmp, JSON.stringify(payload, null, 2), { mode: 384 });
7695
7695
  await rename6(tmp, path);
7696
7696
  const back = JSON.parse(await readFile14(path, "utf8"));
7697
7697
  let recovered;
@@ -9295,8 +9295,8 @@ function makeHeartbeat(cfg2) {
9295
9295
  }
9296
9296
  return set;
9297
9297
  };
9298
- const emit2 = async (kind, payload2, parents, entryEpoch) => {
9299
- const ikBasis = digest2(DIGEST_DOMAINS.event, canonicalize2({ me, kind, payload: payload2, parents }));
9298
+ const emit2 = async (kind, payload, parents, entryEpoch) => {
9299
+ const ikBasis = digest2(DIGEST_DOMAINS.event, canonicalize2({ me, kind, payload, parents }));
9300
9300
  const ev = makeEvent({
9301
9301
  event_kind: kind,
9302
9302
  actor: { host: me, instance_ref: encryptRef(cfg2.wif, `inst:${me}`), thread_ref: encryptRef(cfg2.wif, `thread:${me}`) },
@@ -9304,7 +9304,7 @@ function makeHeartbeat(cfg2) {
9304
9304
  causality: { parents: parents ?? [], basis: [] },
9305
9305
  semantics: { provenance_class: kind === "state.inference" ? "derived" : "observed", claim_status: "asserted" },
9306
9306
  idempotency_key: `ik-${me}-${ikBasis.slice(0, 24)}`,
9307
- payload: payload2,
9307
+ payload,
9308
9308
  created_at: (/* @__PURE__ */ new Date()).toISOString()
9309
9309
  });
9310
9310
  const pre = gate();
@@ -10326,14 +10326,14 @@ async function setupWallet(apiUrl = DEFAULT_API_URL2, importWif, pin) {
10326
10326
  if (!importWif) {
10327
10327
  return {
10328
10328
  success: false,
10329
- error: "Private key required. Sign in at indelible.one with Google to get your key, then run: indelible-mcp setup --wif=YOUR_KEY --pin=YOUR_PIN",
10329
+ error: "Private key required. Get it at indelible.one \u2192 Settings \u2192 Private Key, then run `indelible-mcp` with no arguments and follow the prompts \u2014 that path takes the key at a prompt, so it never lands in your shell history. For automation: setup --wif=YOUR_KEY --pin=YOUR_PIN (clear your shell history afterward).",
10330
10330
  hint: "Your key is shown in Settings after signing in at indelible.one"
10331
10331
  };
10332
10332
  }
10333
10333
  if (!pin || pin.length < 4) {
10334
10334
  return {
10335
10335
  success: false,
10336
- error: "PIN required (minimum 4 characters). Run: indelible-mcp setup --wif=YOUR_KEY --pin=YOUR_PIN",
10336
+ error: "PIN required (minimum 4 characters). Run `indelible-mcp` with no arguments to be prompted for both \u2014 neither value is written to your shell history that way. For automation: setup --wif=YOUR_KEY --pin=YOUR_PIN.",
10337
10337
  hint: "Your PIN encrypts your private key locally. Never share it."
10338
10338
  };
10339
10339
  }
@@ -10561,9 +10561,9 @@ function base58Decode(str) {
10561
10561
  function isValidWif(candidate) {
10562
10562
  const decoded = base58Decode(candidate);
10563
10563
  if (!decoded || decoded.length < 5) return false;
10564
- const payload2 = decoded.subarray(0, decoded.length - 4);
10564
+ const payload = decoded.subarray(0, decoded.length - 4);
10565
10565
  const checksum = decoded.subarray(decoded.length - 4);
10566
- const h = createHash6("sha256").update(createHash6("sha256").update(payload2).digest()).digest();
10566
+ const h = createHash6("sha256").update(createHash6("sha256").update(payload).digest()).digest();
10567
10567
  return h.subarray(0, 4).equals(checksum);
10568
10568
  }
10569
10569
  var MAX_VALIDATED_CANDIDATES = 2e4;
@@ -10983,10 +10983,10 @@ var CODE_EXTS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", "
10983
10983
  var CLONE_CHECK_TIMEOUT_MS = 5e3;
10984
10984
  var MAX_CHUNK_SIZE = 1e7;
10985
10985
  var TX_CACHE_DIR = join11(homedir8(), ".indelible", "tx-cache");
10986
- async function cacheTx(txId, payload2) {
10986
+ async function cacheTx(txId, payload) {
10987
10987
  try {
10988
10988
  if (!existsSync8(TX_CACHE_DIR)) await mkdir2(TX_CACHE_DIR, { recursive: true });
10989
- await writeFile2(join11(TX_CACHE_DIR, `${txId}.json`), JSON.stringify(payload2));
10989
+ await writeFile2(join11(TX_CACHE_DIR, `${txId}.json`), JSON.stringify(payload));
10990
10990
  } catch (e) {
10991
10991
  }
10992
10992
  }
@@ -11111,7 +11111,7 @@ async function saveFile(filePath, options = {}) {
11111
11111
  const filenameEnc = encrypt(filename, wif);
11112
11112
  const pathEnc = encrypt(relativePath, wif);
11113
11113
  if (encrypted.length <= MAX_CHUNK_SIZE) {
11114
- const payload2 = {
11114
+ const payload = {
11115
11115
  protocol: "indelible.file",
11116
11116
  version: 2,
11117
11117
  filename_enc: filenameEnc,
@@ -11126,7 +11126,7 @@ async function saveFile(filePath, options = {}) {
11126
11126
  const bc = await idempotentBuildAndBroadcast({
11127
11127
  address: config2.address,
11128
11128
  contentKey: `file|${relativePath}|sha256:${contentHash}`,
11129
- build: () => buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload2)),
11129
+ build: () => buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload)),
11130
11130
  broadcast: (hex) => broadcastTx(hex),
11131
11131
  checkConfirmation: (id) => checkConfirmation(id),
11132
11132
  onPersisted: heldClaims[0] ? () => linkOrRefuse(heldClaims[0].token, { buildCacheKey: buildCacheKey(config2.address, `file|${relativePath}|sha256:${contentHash}`) }) : void 0
@@ -11137,7 +11137,7 @@ async function saveFile(filePath, options = {}) {
11137
11137
  _finalWrite = bc.write;
11138
11138
  _saveFee += fee;
11139
11139
  _finalSize = txSize;
11140
- if (!bc.reused) await cacheTx(txId, payload2).catch((e) => process.stderr.write(`[indelible] payload cache write failed (non-fatal): ${e?.message}
11140
+ if (!bc.reused) await cacheTx(txId, payload).catch((e) => process.stderr.write(`[indelible] payload cache write failed (non-fatal): ${e?.message}
11141
11141
  `));
11142
11142
  masterTxId = txId;
11143
11143
  finalChangeUtxos = changeUtxos;
@@ -12979,13 +12979,13 @@ import { join as join19, relative, basename as basename3 } from "path";
12979
12979
  import { homedir as homedir15 } from "os";
12980
12980
 
12981
12981
  // mcp-server/lib/reconcile.js
12982
- function payloadContentKey(payload2) {
12983
- if (!payload2 || typeof payload2 !== "object") return null;
12984
- if (payload2.protocol === "indelible.project-bundle" && Array.isArray(payload2.files)) {
12985
- const hashes = payload2.files.map((f) => f && f.content_hash).filter(Boolean);
12982
+ function payloadContentKey(payload) {
12983
+ if (!payload || typeof payload !== "object") return null;
12984
+ if (payload.protocol === "indelible.project-bundle" && Array.isArray(payload.files)) {
12985
+ const hashes = payload.files.map((f) => f && f.content_hash).filter(Boolean);
12986
12986
  return hashes.length ? `bundle:${hashes.join(",")}` : null;
12987
12987
  }
12988
- if (payload2.content_hash) return String(payload2.content_hash).replace(/^sha256:/, "");
12988
+ if (payload.content_hash) return String(payload.content_hash).replace(/^sha256:/, "");
12989
12989
  return null;
12990
12990
  }
12991
12991
  async function findByContentKey({
@@ -13020,14 +13020,14 @@ async function findByContentKey({
13020
13020
  if (!txid || exclude.has(txid.toLowerCase())) continue;
13021
13021
  if (confirmedOnly && !(h.height > 0)) continue;
13022
13022
  scanned++;
13023
- let payload2 = null;
13023
+ let payload = null;
13024
13024
  try {
13025
- payload2 = await fetchPayload(txid);
13025
+ payload = await fetchPayload(txid);
13026
13026
  } catch {
13027
13027
  fetchFailures++;
13028
13028
  continue;
13029
13029
  }
13030
- if (payloadContentKey(payload2) === targetKey) {
13030
+ if (payloadContentKey(payload) === targetKey) {
13031
13031
  const height = h.height || null;
13032
13032
  matches.push({ txid, height });
13033
13033
  if (height > 0) confirmedFound = true;
@@ -13051,10 +13051,10 @@ async function findByContentKey({
13051
13051
 
13052
13052
  // mcp-server/tools/save_project.js
13053
13053
  var TX_CACHE_DIR2 = join19(homedir15(), ".indelible", "tx-cache");
13054
- async function cacheTx2(txId, payload2) {
13054
+ async function cacheTx2(txId, payload) {
13055
13055
  try {
13056
13056
  if (!existsSync15(TX_CACHE_DIR2)) await mkdir5(TX_CACHE_DIR2, { recursive: true });
13057
- await writeFile4(join19(TX_CACHE_DIR2, `${txId}.json`), JSON.stringify(payload2));
13057
+ await writeFile4(join19(TX_CACHE_DIR2, `${txId}.json`), JSON.stringify(payload));
13058
13058
  } catch (e) {
13059
13059
  }
13060
13060
  }
@@ -13187,7 +13187,7 @@ async function saveProject(dirPath, options = {}) {
13187
13187
  });
13188
13188
  totalSize += fileSize;
13189
13189
  }
13190
- const payload2 = {
13190
+ const payload = {
13191
13191
  protocol: "indelible.project-bundle",
13192
13192
  version: 1,
13193
13193
  name_enc: encrypt(projectName, wif),
@@ -13214,7 +13214,7 @@ async function saveProject(dirPath, options = {}) {
13214
13214
  const bc = await idempotentBuildAndBroadcast({
13215
13215
  address: config2.address,
13216
13216
  contentKey: `project|${projectName}|${bundleKey}`,
13217
- build: () => buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload2), "INDELIBLE_PROJECT_BUNDLE"),
13217
+ build: () => buildOpReturnTxWithChange(wif, utxos, JSON.stringify(payload), "INDELIBLE_PROJECT_BUNDLE"),
13218
13218
  broadcast: (hex) => broadcastTx(hex),
13219
13219
  checkConfirmation: (id) => checkConfirmation(id),
13220
13220
  onPersisted: () => linkOrRefuse(claim2.token, { buildCacheKey: buildCacheKey(config2.address, `project|${projectName}|${bundleKey}`) })
@@ -13224,7 +13224,7 @@ async function saveProject(dirPath, options = {}) {
13224
13224
  `);
13225
13225
  const _wr = bc.write;
13226
13226
  await settleFromWriteResult(claim2.token, { writeReceipt: _wr, changeUtxos });
13227
- if (!bc.reused) await cacheTx2(txId, payload2).catch((e) => process.stderr.write(`[indelible] payload cache write failed (non-fatal): ${e?.message}
13227
+ if (!bc.reused) await cacheTx2(txId, payload).catch((e) => process.stderr.write(`[indelible] payload cache write failed (non-fatal): ${e?.message}
13228
13228
  `));
13229
13229
  const freshConfig = await loadConfig();
13230
13230
  const projects = freshConfig.project_txids || [];
@@ -13240,7 +13240,7 @@ async function saveProject(dirPath, options = {}) {
13240
13240
  } catch {
13241
13241
  }
13242
13242
  const receipt = buildReceipt(_wr, { txId, fee, txSize });
13243
- appendReceipt(receipt, { trigger: "interactive", saveType: "project", contentHash: payloadContentKey(payload2) });
13243
+ appendReceipt(receipt, { trigger: "interactive", saveType: "project", contentHash: payloadContentKey(payload) });
13244
13244
  return {
13245
13245
  success: true,
13246
13246
  txId,
@@ -13319,7 +13319,7 @@ async function healStrandedTxid(strandedTxId, config2) {
13319
13319
  return null;
13320
13320
  }
13321
13321
  }
13322
- async function unwrapVaultV3(payload2, wif) {
13322
+ async function unwrapVaultV3(payload, wif) {
13323
13323
  let w = null;
13324
13324
  try {
13325
13325
  w = new CompletedProtoWallet3(PrivateKey8.fromWif(wif));
@@ -13327,23 +13327,23 @@ async function unwrapVaultV3(payload2, wif) {
13327
13327
  w = null;
13328
13328
  }
13329
13329
  if (!w) return { ok: false, code: "WALLET_UNAVAILABLE" };
13330
- if (payload2.owner_pubkey) {
13330
+ if (payload.owner_pubkey) {
13331
13331
  let idPub = null;
13332
13332
  try {
13333
13333
  idPub = (await w.getPublicKey({ identityKey: true }))?.publicKey || null;
13334
13334
  } catch {
13335
13335
  }
13336
- if (idPub && idPub !== payload2.owner_pubkey) {
13337
- if (payload2.wrap_recovery) {
13338
- const rr = await unwrapRecoveryV3(payload2.wrap_recovery, w, payload2.key_id, payload2.owner_pubkey);
13336
+ if (idPub && idPub !== payload.owner_pubkey) {
13337
+ if (payload.wrap_recovery) {
13338
+ const rr = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
13339
13339
  if (rr.ok) return { ok: true, contentKey: rr.contentKey };
13340
13340
  }
13341
13341
  return { ok: false, code: "WALLET_UNAVAILABLE" };
13342
13342
  }
13343
13343
  }
13344
- let r = await unwrapForSelfV3(payload2.wrap_self, w, payload2.key_id);
13345
- if (!r.ok && r.reason === "corrupt" && payload2.wrap_recovery && payload2.owner_pubkey) {
13346
- r = await unwrapRecoveryV3(payload2.wrap_recovery, w, payload2.key_id, payload2.owner_pubkey);
13344
+ let r = await unwrapForSelfV3(payload.wrap_self, w, payload.key_id);
13345
+ if (!r.ok && r.reason === "corrupt" && payload.wrap_recovery && payload.owner_pubkey) {
13346
+ r = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
13347
13347
  }
13348
13348
  if (!r.ok) return { ok: false, code: r.reason === "wallet_unavailable" ? "WALLET_UNAVAILABLE" : "V3_CORRUPT" };
13349
13349
  return { ok: true, contentKey: r.contentKey };
@@ -13390,34 +13390,34 @@ async function loadFile(txId, options = {}) {
13390
13390
  if (!wif) {
13391
13391
  return { success: false, error: "Wallet not configured or PIN incorrect. Run setup_wallet first." };
13392
13392
  }
13393
- let payload2 = await fetchJsonPayload(txId);
13394
- if (!payload2) {
13393
+ let payload = await fetchJsonPayload(txId);
13394
+ if (!payload) {
13395
13395
  const healed = await healStrandedTxid(txId, config2);
13396
13396
  if (healed) {
13397
13397
  process.stderr.write(`[g-363] receipt ${txId.slice(0, 12)}\u2026 was stranded \u2014 real tx is ${healed.txid.slice(0, 12)}\u2026, loading that
13398
13398
  `);
13399
- payload2 = await fetchJsonPayload(healed.txid);
13400
- if (payload2) {
13399
+ payload = await fetchJsonPayload(healed.txid);
13400
+ if (payload) {
13401
13401
  txId = healed.txid;
13402
13402
  options._healedFrom = healed.from;
13403
13403
  }
13404
13404
  }
13405
- if (!payload2) {
13405
+ if (!payload) {
13406
13406
  return { success: false, error: `Could not fetch or parse tx: ${txId}` };
13407
13407
  }
13408
13408
  }
13409
- if (payload2.protocol !== "indelible.file") {
13410
- return { success: false, error: `Not an indelible.file tx (got: ${payload2.protocol || "unknown"})` };
13409
+ if (payload.protocol !== "indelible.file") {
13410
+ return { success: false, error: `Not an indelible.file tx (got: ${payload.protocol || "unknown"})` };
13411
13411
  }
13412
- const vfmt = detectVaultFormat(payload2);
13412
+ const vfmt = detectVaultFormat(payload);
13413
13413
  if (vfmt === "v3-wallet") {
13414
- const uv = await unwrapVaultV3(payload2, wif);
13414
+ const uv = await unwrapVaultV3(payload, wif);
13415
13415
  if (!uv.ok) {
13416
13416
  return { success: false, code: uv.code, error: uv.code === "WALLET_UNAVAILABLE" ? "wallet-native file: not readable from this box \u2014 its key lives in an external wallet (open it in the web app), or this WIF is not the owner" : "wallet-native file: unwrap failed (corrupt or wrong wallet)" };
13417
13417
  }
13418
13418
  let outBuf2;
13419
13419
  try {
13420
- const dec = decryptWithKey(payload2.encrypted, Buffer.from(uv.contentKey));
13420
+ const dec = decryptWithKey(payload.encrypted, Buffer.from(uv.contentKey));
13421
13421
  outBuf2 = dec.startsWith("gz:") ? gunzipSync2(Buffer.from(dec.slice(3), "base64")) : Buffer.from(dec, "utf8");
13422
13422
  } catch (err9) {
13423
13423
  return { success: false, code: "V3_CORRUPT", error: `wallet-native decrypt failed: ${err9.message}` };
@@ -13425,24 +13425,24 @@ async function loadFile(txId, options = {}) {
13425
13425
  const contentHash2 = sha256(outBuf2);
13426
13426
  let meta = {};
13427
13427
  try {
13428
- meta = JSON.parse(decryptWithKey(payload2.meta_enc, Buffer.from(uv.contentKey)));
13428
+ meta = JSON.parse(decryptWithKey(payload.meta_enc, Buffer.from(uv.contentKey)));
13429
13429
  } catch {
13430
13430
  }
13431
- const verified2 = meta.sha256 ? contentHash2 === meta.sha256 : payload2.content_hash ? contentHash2 === payload2.content_hash.replace("sha256:", "") : null;
13431
+ const verified2 = meta.sha256 ? contentHash2 === meta.sha256 : payload.content_hash ? contentHash2 === payload.content_hash.replace("sha256:", "") : null;
13432
13432
  if (options.outputPath) {
13433
13433
  const dir = dirname8(options.outputPath);
13434
13434
  if (!existsSync16(dir)) await mkdir6(dir, { recursive: true });
13435
13435
  await writeFile5(options.outputPath, outBuf2);
13436
13436
  }
13437
- const isBinary2 = payload2.enc === "b64";
13437
+ const isBinary2 = payload.enc === "b64";
13438
13438
  return {
13439
13439
  success: true,
13440
13440
  txId,
13441
13441
  wallet_native: true,
13442
13442
  filename: meta.filename,
13443
13443
  path: meta.path,
13444
- size: payload2.size,
13445
- content_hash: payload2.content_hash,
13444
+ size: payload.size,
13445
+ content_hash: payload.content_hash,
13446
13446
  verified: verified2,
13447
13447
  content: isBinary2 ? `[binary file: ${outBuf2.length} bytes${options.outputPath ? ", written to disk" : ""}]` : outBuf2.toString("utf8")
13448
13448
  };
@@ -13451,10 +13451,10 @@ async function loadFile(txId, options = {}) {
13451
13451
  return { success: false, code: "V3_CORRUPT", error: "file carries malformed v3 wrap fields \u2014 failing closed" };
13452
13452
  }
13453
13453
  let encrypted;
13454
- if (payload2._chunks) {
13454
+ if (payload._chunks) {
13455
13455
  let encryptedParts = "";
13456
- for (let i = 1; i <= payload2._chunks; i++) {
13457
- const chunkTxId = payload2[`chunk_${i}`];
13456
+ for (let i = 1; i <= payload._chunks; i++) {
13457
+ const chunkTxId = payload[`chunk_${i}`];
13458
13458
  if (!chunkTxId) {
13459
13459
  return { success: false, error: `Missing chunk_${i} reference in master index` };
13460
13460
  }
@@ -13466,7 +13466,7 @@ async function loadFile(txId, options = {}) {
13466
13466
  }
13467
13467
  encrypted = encryptedParts;
13468
13468
  } else {
13469
- encrypted = payload2.encrypted;
13469
+ encrypted = payload.encrypted;
13470
13470
  }
13471
13471
  if (!encrypted) {
13472
13472
  return { success: false, error: "No encrypted content found in tx" };
@@ -13483,7 +13483,7 @@ async function loadFile(txId, options = {}) {
13483
13483
  return { success: false, error: `Decryption failed: ${err9.message}` };
13484
13484
  }
13485
13485
  const contentHash = sha256(outBuf);
13486
- const expectedHash = payload2.content_hash?.replace("sha256:", "") || null;
13486
+ const expectedHash = payload.content_hash?.replace("sha256:", "") || null;
13487
13487
  const verified = expectedHash ? contentHash === expectedHash : null;
13488
13488
  if (options.outputPath) {
13489
13489
  const dir = dirname8(options.outputPath);
@@ -13492,10 +13492,10 @@ async function loadFile(txId, options = {}) {
13492
13492
  }
13493
13493
  await writeFile5(options.outputPath, outBuf);
13494
13494
  }
13495
- const isBinary = payload2.enc === "b64";
13495
+ const isBinary = payload.enc === "b64";
13496
13496
  const content = isBinary ? `[binary file: ${outBuf.length} bytes${options.outputPath ? ", written to disk" : ""}]` : outBuf.toString("utf8");
13497
- const filename = payload2.filename_enc ? decrypt(payload2.filename_enc, wif) : payload2.filename;
13498
- const filePath = payload2.path_enc ? decrypt(payload2.path_enc, wif) : payload2.path;
13497
+ const filename = payload.filename_enc ? decrypt(payload.filename_enc, wif) : payload.filename;
13498
+ const filePath = payload.path_enc ? decrypt(payload.path_enc, wif) : payload.path;
13499
13499
  let onChain = null, blockHeight = null;
13500
13500
  try {
13501
13501
  const seen = await checkConfirmation(txId);
@@ -13509,8 +13509,8 @@ async function loadFile(txId, options = {}) {
13509
13509
  txId,
13510
13510
  filename,
13511
13511
  path: filePath,
13512
- size: payload2.size,
13513
- enc: payload2.enc || "utf8",
13512
+ size: payload.size,
13513
+ enc: payload.enc || "utf8",
13514
13514
  content,
13515
13515
  contentHash: `sha256:${contentHash}`,
13516
13516
  verified,
@@ -13519,7 +13519,7 @@ async function loadFile(txId, options = {}) {
13519
13519
  on_chain: onChain,
13520
13520
  block_height: blockHeight,
13521
13521
  source: onChain === true ? "chain" : "federation-custody",
13522
- chunks: payload2._chunks || 1,
13522
+ chunks: payload._chunks || 1,
13523
13523
  ...options._healedFrom ? { healed_from: options._healedFrom, healed: true } : {},
13524
13524
  message: `File "${filename}" loaded.${verified === true ? " Hash verified." : verified === false ? " WARNING: Hash mismatch!" : ""} ${custody}${options._healedFrom ? ` (Your saved receipt ${options._healedFrom.slice(0, 12)}\u2026 was a stranded save-race attempt; the real on-chain tx is ${txId.slice(0, 12)}\u2026 \u2014 same content, verified. Your data was never lost.)` : ""}`
13525
13525
  };
@@ -13737,7 +13737,7 @@ async function updateVaultIndex() {
13737
13737
  totalSize: p.totalSize || 0,
13738
13738
  timestamp: p.timestamp || null
13739
13739
  } : { txid: p.txId, fileCount: p.fileCount || 0, totalSize: p.totalSize || 0, timestamp: p.timestamp || null, wrap: "v3" });
13740
- const payload2 = {
13740
+ const payload = {
13741
13741
  protocol: "indelible.vault-index",
13742
13742
  version: 1,
13743
13743
  files: encFiles,
@@ -13762,7 +13762,7 @@ async function updateVaultIndex() {
13762
13762
  const { txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(
13763
13763
  wif,
13764
13764
  utxos,
13765
- JSON.stringify(payload2)
13765
+ JSON.stringify(payload)
13766
13766
  );
13767
13767
  await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
13768
13768
  let result = null;
@@ -14432,10 +14432,10 @@ async function saveGoalsToChain() {
14432
14432
  content: [{ type: "text", text: "No UTXOs available. Fund your wallet." }]
14433
14433
  };
14434
14434
  }
14435
- let txId, fee, txSize, writeReceipt;
14435
+ let txId, fee, txSize, writeReceipt, snapshotAt;
14436
14436
  try {
14437
14437
  const encrypted = encryptGoals(goalsData, wif);
14438
- const payload2 = {
14438
+ const payload = {
14439
14439
  protocol: "indelible.goals-snapshot",
14440
14440
  version: 1,
14441
14441
  owner: address,
@@ -14444,11 +14444,12 @@ async function saveGoalsToChain() {
14444
14444
  completed_count: completed.length,
14445
14445
  encrypted
14446
14446
  };
14447
+ snapshotAt = payload.timestamp;
14447
14448
  let txHex, changeUtxos;
14448
14449
  ({ txHex, txId, changeUtxos, fee, txSize } = await buildOpReturnTxWithChange(
14449
14450
  wif,
14450
14451
  utxos,
14451
- JSON.stringify(payload2)
14452
+ JSON.stringify(payload)
14452
14453
  ));
14453
14454
  await linkOrRefuse(claim2.token, { txId, txHex, changeUtxos });
14454
14455
  writeReceipt = null;
@@ -14465,17 +14466,31 @@ async function saveGoalsToChain() {
14465
14466
  });
14466
14467
  throw err9;
14467
14468
  }
14468
- const receipt = buildReceipt(writeReceipt, { txId, fee, txSize });
14469
- appendReceipt(receipt, { trigger: "interactive", saveType: "goals" });
14470
- await updateConfig((c) => ({
14471
- ...c,
14472
- goals_snapshot_hash: contentHash,
14473
- goals_snapshot_txid: txId,
14474
- goals_snapshot_at: payload.timestamp,
14475
- goals_snapshot_count: goals.length,
14476
- goals_snapshot_completed_count: completed.length
14477
- }));
14478
- pushGoalsToServer(goalsData, { always: true, snapshot: { txid: txId, at: payload.timestamp, count: goals.length, completed_count: completed.length } });
14469
+ const warnings = [];
14470
+ let receipt = null;
14471
+ try {
14472
+ receipt = buildReceipt(writeReceipt, { txId, fee, txSize });
14473
+ appendReceipt(receipt, { trigger: "interactive", saveType: "goals" });
14474
+ } catch (e) {
14475
+ warnings.push(`\u26A0\uFE0F THE BROADCAST SUCCEEDED (txid ${txId}) BUT LOCAL RECONCILIATION AND AUDIT STATE WERE NOT RECORDED: the save-log row could not be written (${e && e.message || e}). Your goals ARE on chain and your coin IS spent \u2014 do NOT re-run this to "fix" it. Record that txid somewhere now; the local ledger is missing this save, so reconciliation will not see it.`);
14476
+ }
14477
+ try {
14478
+ await updateConfig((c) => ({
14479
+ ...c,
14480
+ goals_snapshot_hash: contentHash,
14481
+ goals_snapshot_txid: txId,
14482
+ goals_snapshot_at: snapshotAt,
14483
+ goals_snapshot_count: goals.length,
14484
+ goals_snapshot_completed_count: completed.length
14485
+ }));
14486
+ } catch (e) {
14487
+ warnings.push(`the local snapshot marker was not updated (${e && e.message || e}) \u2014 the save landed, but an unchanged re-save will not know it can skip, so it may re-pay.`);
14488
+ }
14489
+ try {
14490
+ await Promise.resolve(pushGoalsToServer(goalsData, { always: true, snapshot: { txid: txId, at: snapshotAt, count: goals.length, completed_count: completed.length } }));
14491
+ } catch (e) {
14492
+ warnings.push(`the web Goals board was not refreshed (${e && e.message || e}) \u2014 chain data is unaffected; the tab catches up on its next sync.`);
14493
+ }
14479
14494
  return {
14480
14495
  content: [{
14481
14496
  type: "text",
@@ -14484,9 +14499,11 @@ async function saveGoalsToChain() {
14484
14499
  address: ${address}
14485
14500
  goals: ${goals.length} active, ${completed.length} completed
14486
14501
 
14487
- ${formatSaveReceipt(receipt)}
14502
+ ${receipt ? formatSaveReceipt(receipt) : `txid: ${txId}`}
14503
+
14504
+ ` + (warnings.length ? `${warnings.join("\n\n")}
14488
14505
 
14489
- The Goals tab's "On chain" section shows this snapshot on next refresh.`
14506
+ ` : "") + `The Goals tab's "On chain" section shows this snapshot on next refresh.`
14490
14507
  }]
14491
14508
  };
14492
14509
  }
@@ -15003,7 +15020,7 @@ async function reportBug(args2 = {}, mcpVersion) {
15003
15020
  return { success: false, error: "summary and description are required (you supply the narrative; the tool supplies the facts)." };
15004
15021
  }
15005
15022
  const config2 = readJson(join31(INDELIBLE_DIR, "config.json")) || {};
15006
- const payload2 = {
15023
+ const payload = {
15007
15024
  // AI-supplied narrative (defensively redacted + length-capped)
15008
15025
  summary: redactSecrets2(summary).slice(0, 200),
15009
15026
  description: redactSecrets2(description).slice(0, 5e3),
@@ -15026,10 +15043,10 @@ async function reportBug(args2 = {}, mcpVersion) {
15026
15043
  const resp = await fetch(INTAKE_URL, {
15027
15044
  method: "POST",
15028
15045
  headers: { "Content-Type": "application/json" },
15029
- body: JSON.stringify(payload2)
15046
+ body: JSON.stringify(payload)
15030
15047
  });
15031
15048
  if (!resp.ok) {
15032
- return { success: false, error: `intake responded ${resp.status}`, sent: { summary: payload2.summary, mcp_version: payload2.mcp_version } };
15049
+ return { success: false, error: `intake responded ${resp.status}`, sent: { summary: payload.summary, mcp_version: payload.mcp_version } };
15033
15050
  }
15034
15051
  const data = await resp.json().catch(() => ({}));
15035
15052
  if (data.known_issue) {
@@ -15138,18 +15155,18 @@ async function shareSession({ session_txid, recipient_addr, expires_at = null })
15138
15155
  if (!_gate.ok) return { success: false, gated: true, plan: _gate.plan, upsell: _gate.upsell, error: _gate.error };
15139
15156
  const ownerPriv = PrivateKey13.fromWif(wif);
15140
15157
  const ownerAddr = ownerPriv.toPublicKey().toAddress();
15141
- const payload2 = await fetchScopePayload(session_txid);
15142
- if (!payload2) return { success: false, error: `could not fetch scope tx ${session_txid}` };
15143
- const fmt = detectSaveFormat({ wrap_owner: payload2.wrap_owner, wrap_self: payload2.wrap_self, wrap_version: payload2.wrap_version });
15158
+ const payload = await fetchScopePayload(session_txid);
15159
+ if (!payload) return { success: false, error: `could not fetch scope tx ${session_txid}` };
15160
+ const fmt = detectSaveFormat({ wrap_owner: payload.wrap_owner, wrap_self: payload.wrap_self, wrap_version: payload.wrap_version });
15144
15161
  let contentKey;
15145
15162
  if (fmt === "v3-wallet") {
15146
- const rv3 = await unwrapForSelfV3(payload2.wrap_self, new CompletedProtoWallet4(ownerPriv), payload2.key_id);
15163
+ const rv3 = await unwrapForSelfV3(payload.wrap_self, new CompletedProtoWallet4(ownerPriv), payload.key_id);
15147
15164
  if (!rv3.ok) {
15148
15165
  return { success: false, error: rv3.reason === "wallet_unavailable" ? "failed to unwrap own v3 content_key: no wallet available" : `failed to unwrap own v3 content_key: ${rv3.reason} (wallet-native saves made by an external wallet can only be shared from the web app)` };
15149
15166
  }
15150
15167
  contentKey = rv3.contentKey;
15151
15168
  } else if (fmt === "v2-wrapped") {
15152
- const r = unwrapForOwner(payload2.wrap_owner, ownerPriv);
15169
+ const r = unwrapForOwner(payload.wrap_owner, ownerPriv);
15153
15170
  if (!r.ok) return { success: false, error: `failed to unwrap own content_key: ${r.reason}` };
15154
15171
  contentKey = r.contentKey;
15155
15172
  } else if (fmt === "v3-malformed") {
@@ -15733,9 +15750,17 @@ async function runCli(args2) {
15733
15750
  const importWif = args2.find((a) => a.startsWith("--wif="))?.split("=")[1];
15734
15751
  const pin = args2.find((a) => a.startsWith("--pin="))?.split("=")[1];
15735
15752
  const result = await setupWallet(apiUrl || void 0, importWif, pin);
15753
+ if (!result || result.success === false) {
15754
+ result.hooks = "not installed \u2014 setup did not complete";
15755
+ if (!importWif) {
15756
+ result.hint = "Run `indelible-mcp` with no arguments and follow the prompts. That path takes your key at a prompt, so it never lands in your shell history. Use `setup --wif=\u2026 --pin=\u2026` only for automation, and clear your shell history afterward if you do.";
15757
+ }
15758
+ emit(result);
15759
+ break;
15760
+ }
15736
15761
  const hooks = installHooks();
15737
15762
  result.hooks = hooks.alreadyInstalled ? "Auto-save hooks already installed" : `Installed hooks: ${hooks.installed.join(", ")} \u2192 ${hooks.settingsPath}`;
15738
- console.log(JSON.stringify(result, null, 2));
15763
+ emit(result);
15739
15764
  break;
15740
15765
  }
15741
15766
  case "save": {
@@ -16999,10 +17024,11 @@ Answer THAT message and nothing else \u2014 the wire also carries unrelated conv
16999
17024
  }
17000
17025
  function printHelp() {
17001
17026
  console.log(`
17002
- Indelible MCP \u2014 Blockchain memory for Claude Code (v5.7.6)
17027
+ Indelible MCP \u2014 Blockchain memory for Claude Code (v5.7.7)
17003
17028
 
17004
17029
  Setup:
17005
- indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
17030
+ indelible-mcp Set up interactively (recommended \u2014 your key is never written to shell history)
17031
+ indelible-mcp setup --wif=KEY --pin=PIN Same, for automation (both values land in shell history)
17006
17032
  indelible-mcp install-hooks Install auto-save/restore hooks
17007
17033
  indelible-mcp semantic-fetch Enable meaning-based recall (one-time ~50MB local model; nothing leaves your machine)
17008
17034
  indelible-mcp map Render "The Shape of the Work" \u2014 a 3D map of everything you've thought about, built locally from your own history
@@ -17327,7 +17353,7 @@ function readStdin() {
17327
17353
  }
17328
17354
  var SERVER_INFO = {
17329
17355
  name: "indelible",
17330
- version: "5.7.6",
17356
+ version: "5.7.7",
17331
17357
  description: "Blockchain-backed memory and code storage for Claude Code"
17332
17358
  };
17333
17359
  var TOOLS = [