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 +50 -52
- package/CUSTOMER_AGENT_HANDBOOK.md +8 -3
- package/package.json +1 -1
- package/src/index.js +158 -132
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
|
-
|
|
175
|
+
indelible-mcp save --summary "what happened"
|
|
176
176
|
```
|
|
177
177
|
|
|
178
178
|
### Load from Blockchain
|
|
179
179
|
```bash
|
|
180
|
-
|
|
180
|
+
indelible-mcp load --sessions=5
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
### Check Status
|
|
184
184
|
```bash
|
|
185
|
-
|
|
185
|
+
indelible-mcp status
|
|
186
186
|
```
|
|
187
187
|
|
|
188
188
|
### Save a File
|
|
189
189
|
```bash
|
|
190
|
-
|
|
190
|
+
indelible-mcp vault save-file /path/to/file.js
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
### Ask Codex
|
|
194
194
|
```bash
|
|
195
|
-
|
|
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** | `
|
|
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
|
-
|
|
268
|
+
indelible-mcp status
|
|
271
269
|
|
|
272
270
|
# Blockchain read works
|
|
273
|
-
|
|
271
|
+
indelible-mcp load --sessions=1
|
|
274
272
|
|
|
275
273
|
# Style loads from chain
|
|
276
|
-
|
|
274
|
+
indelible-mcp vault load-style
|
|
277
275
|
```
|
|
278
276
|
|
|
279
277
|
### Function-level tests (no sats spent)
|
|
280
278
|
```bash
|
|
281
|
-
cd
|
|
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
|
|
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
|
|
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
|
-
|
|
303
|
+
indelible-mcp save --summary "test save"
|
|
306
304
|
|
|
307
305
|
# File save
|
|
308
|
-
|
|
306
|
+
indelible-mcp vault save-file /path/to/small/file.txt
|
|
309
307
|
|
|
310
308
|
# Style save (auto-prepends core rules via ensureCoreRules)
|
|
311
|
-
|
|
309
|
+
indelible-mcp vault save-style /path/to/rules.txt --name=test
|
|
312
310
|
|
|
313
311
|
# Diary chat (costs OpenAI tokens, not sats)
|
|
314
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
41
|
+
## 2. What fires automatically (the hooks)
|
|
42
42
|
|
|
43
|
-
When you ran setup, the installer wired
|
|
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
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,
|
|
1007
|
-
const linked = await linkReservation(token,
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
3047
|
-
const hasV3Fields =
|
|
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 (
|
|
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 (
|
|
3053
|
-
if (
|
|
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(
|
|
3057
|
-
if (!
|
|
3056
|
+
function detectVaultFormat(payload) {
|
|
3057
|
+
if (!payload) return "legacy";
|
|
3058
3058
|
return detectSaveFormat({
|
|
3059
|
-
wrap_owner:
|
|
3060
|
-
wrap_self:
|
|
3061
|
-
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
|
|
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(
|
|
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
|
|
4506
|
+
let payload;
|
|
4507
4507
|
if (typeof payloadOrEncrypted === "string") {
|
|
4508
|
-
|
|
4508
|
+
payload = { encrypted: payloadOrEncrypted, wrap_owner: null };
|
|
4509
4509
|
} else {
|
|
4510
|
-
|
|
4510
|
+
payload = payloadOrEncrypted;
|
|
4511
4511
|
}
|
|
4512
|
-
const encrypted =
|
|
4513
|
-
const 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:
|
|
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 (
|
|
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 !==
|
|
4533
|
-
if (
|
|
4534
|
-
const rr = await unwrapRecoveryV3(
|
|
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(
|
|
4542
|
-
if (!r.ok && r.reason === "corrupt" &&
|
|
4543
|
-
r = await unwrapRecoveryV3(
|
|
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
|
|
4626
|
-
if (
|
|
4627
|
-
const out = { encrypted:
|
|
4628
|
-
if (
|
|
4629
|
-
if (
|
|
4630
|
-
if (
|
|
4631
|
-
if (
|
|
4632
|
-
if (
|
|
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,
|
|
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 = { ...
|
|
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
|
|
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(
|
|
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,
|
|
9299
|
-
const ikBasis = digest2(DIGEST_DOMAINS.event, canonicalize2({ me, kind, payload
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
12983
|
-
if (!
|
|
12984
|
-
if (
|
|
12985
|
-
const hashes =
|
|
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 (
|
|
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
|
|
13023
|
+
let payload = null;
|
|
13024
13024
|
try {
|
|
13025
|
-
|
|
13025
|
+
payload = await fetchPayload(txid);
|
|
13026
13026
|
} catch {
|
|
13027
13027
|
fetchFailures++;
|
|
13028
13028
|
continue;
|
|
13029
13029
|
}
|
|
13030
|
-
if (payloadContentKey(
|
|
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,
|
|
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(
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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 (
|
|
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 !==
|
|
13337
|
-
if (
|
|
13338
|
-
const rr = await unwrapRecoveryV3(
|
|
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(
|
|
13345
|
-
if (!r.ok && r.reason === "corrupt" &&
|
|
13346
|
-
r = await unwrapRecoveryV3(
|
|
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
|
|
13394
|
-
if (!
|
|
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
|
-
|
|
13400
|
-
if (
|
|
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 (!
|
|
13405
|
+
if (!payload) {
|
|
13406
13406
|
return { success: false, error: `Could not fetch or parse tx: ${txId}` };
|
|
13407
13407
|
}
|
|
13408
13408
|
}
|
|
13409
|
-
if (
|
|
13410
|
-
return { success: false, error: `Not an indelible.file tx (got: ${
|
|
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(
|
|
13412
|
+
const vfmt = detectVaultFormat(payload);
|
|
13413
13413
|
if (vfmt === "v3-wallet") {
|
|
13414
|
-
const uv = await unwrapVaultV3(
|
|
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(
|
|
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(
|
|
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 :
|
|
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 =
|
|
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:
|
|
13445
|
-
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 (
|
|
13454
|
+
if (payload._chunks) {
|
|
13455
13455
|
let encryptedParts = "";
|
|
13456
|
-
for (let i = 1; i <=
|
|
13457
|
-
const chunkTxId =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
13498
|
-
const filePath =
|
|
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:
|
|
13513
|
-
enc:
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
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
|
|
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(
|
|
15046
|
+
body: JSON.stringify(payload)
|
|
15030
15047
|
});
|
|
15031
15048
|
if (!resp.ok) {
|
|
15032
|
-
return { success: false, error: `intake responded ${resp.status}`, sent: { summary:
|
|
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
|
|
15142
|
-
if (!
|
|
15143
|
-
const fmt = detectSaveFormat({ wrap_owner:
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
17027
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v5.7.7)
|
|
17003
17028
|
|
|
17004
17029
|
Setup:
|
|
17005
|
-
indelible-mcp
|
|
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.
|
|
17356
|
+
version: "5.7.7",
|
|
17331
17357
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
17332
17358
|
};
|
|
17333
17359
|
var TOOLS = [
|