indelible-mcp 4.8.0 → 4.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +467 -78
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -314,15 +314,17 @@ async function getRawTx(txid) {
|
|
|
314
314
|
`);
|
|
315
315
|
return text;
|
|
316
316
|
}
|
|
317
|
+
function bcastTimeoutMs(rawTxHex) {
|
|
318
|
+
const txMB = (rawTxHex?.length || 0) / 2 / 1048576;
|
|
319
|
+
return Math.min(18e4, Math.round(15e3 + txMB * 12e3));
|
|
320
|
+
}
|
|
317
321
|
async function broadcastViaBridges(rawTxHex) {
|
|
318
322
|
const bridges = await getBridges();
|
|
319
|
-
process.stderr.write(`[MCP] Bridge fallback \u2014 broadcasting to ${bridges.length} bridges (parallel)...
|
|
320
|
-
`);
|
|
321
323
|
const t0 = Date.now();
|
|
322
|
-
const
|
|
324
|
+
const tryBridge = async (bridge) => {
|
|
323
325
|
const bt0 = Date.now();
|
|
324
326
|
const controller = new AbortController();
|
|
325
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
327
|
+
const timeout = setTimeout(() => controller.abort(), bcastTimeoutMs(rawTxHex));
|
|
326
328
|
try {
|
|
327
329
|
const res = await fetch(`${bridge.url}/api/broadcast`, {
|
|
328
330
|
method: "POST",
|
|
@@ -349,16 +351,34 @@ async function broadcastViaBridges(rawTxHex) {
|
|
|
349
351
|
`);
|
|
350
352
|
throw err;
|
|
351
353
|
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
};
|
|
355
|
+
let ordered = bridges;
|
|
356
|
+
if (bridges.length > 1) {
|
|
357
|
+
try {
|
|
358
|
+
const winner = await Promise.any(bridges.map(async (b) => {
|
|
359
|
+
const r = await fetch(`${b.url}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
360
|
+
if (!r.ok) throw new Error("unhealthy");
|
|
361
|
+
return b;
|
|
362
|
+
}));
|
|
363
|
+
ordered = [winner, ...bridges.filter((b) => b.url !== winner.url)];
|
|
364
|
+
process.stderr.write(`[MCP] Bridge broadcast \u2014 ${winner.name} answered first; sending the tx to it (single bridge; mesh gossips the rest)
|
|
365
|
+
`);
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
let lastErr = null;
|
|
370
|
+
for (const bridge of ordered) {
|
|
371
|
+
try {
|
|
372
|
+
const r = await tryBridge(bridge);
|
|
373
|
+
setStickyBridge(bridge);
|
|
374
|
+
process.stderr.write(`[MCP] Bridge broadcast accepted via ${bridge.name} in ${Date.now() - t0}ms
|
|
356
375
|
`);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
376
|
+
return r;
|
|
377
|
+
} catch (e) {
|
|
378
|
+
lastErr = e;
|
|
379
|
+
}
|
|
361
380
|
}
|
|
381
|
+
throw new Error(`Bridge broadcast failed \u2014 all ${bridges.length} bridges down: ${lastErr && lastErr.message}`);
|
|
362
382
|
}
|
|
363
383
|
function setStickyBridge(bridge) {
|
|
364
384
|
_stickyBridge = { url: bridge.url, name: bridge.name, until: Date.now() + STICKY_WINDOW_MS };
|
|
@@ -438,8 +458,8 @@ async function buildOpReturnTxWithChange(wif, utxos, dataStr) {
|
|
|
438
458
|
});
|
|
439
459
|
totalInput += utxo.value;
|
|
440
460
|
}
|
|
441
|
-
const prefixBytes =
|
|
442
|
-
const dataBytes =
|
|
461
|
+
const prefixBytes = Buffer.from("BSVWRAPPER_MERKLE_E", "utf8");
|
|
462
|
+
const dataBytes = Buffer.from(dataStr, "utf8");
|
|
443
463
|
const opReturnScript = new LockingScript([
|
|
444
464
|
{ op: OP.OP_FALSE },
|
|
445
465
|
{ op: OP.OP_RETURN },
|
|
@@ -454,7 +474,7 @@ async function buildOpReturnTxWithChange(wif, utxos, dataStr) {
|
|
|
454
474
|
lockingScript: p2pkh.lock(address),
|
|
455
475
|
change: true
|
|
456
476
|
});
|
|
457
|
-
await tx.fee(new SatoshisPerKilobyte(
|
|
477
|
+
await tx.fee(new SatoshisPerKilobyte(150));
|
|
458
478
|
await tx.sign();
|
|
459
479
|
const txId = tx.id("hex");
|
|
460
480
|
const txHex = tx.toHex();
|
|
@@ -504,7 +524,7 @@ async function buildPaymentTx(wif, utxos, payToAddress, satoshis) {
|
|
|
504
524
|
lockingScript: p2pkh.lock(address),
|
|
505
525
|
change: true
|
|
506
526
|
});
|
|
507
|
-
await tx.fee(new SatoshisPerKilobyte(
|
|
527
|
+
await tx.fee(new SatoshisPerKilobyte(150));
|
|
508
528
|
await tx.sign();
|
|
509
529
|
const txId = tx.id("hex");
|
|
510
530
|
const txHex = tx.toHex();
|
|
@@ -1561,10 +1581,23 @@ function verifyShare(envelope, ownerPubkey) {
|
|
|
1561
1581
|
}
|
|
1562
1582
|
}
|
|
1563
1583
|
function detectSaveFormat(payload) {
|
|
1584
|
+
const hasV3Fields = payload != null && (payload.wrap_self !== void 0 || payload.wrap_version !== void 0);
|
|
1585
|
+
if (hasV3Fields) {
|
|
1586
|
+
if (payload.wrap_version === 3 && typeof payload.wrap_self === "string" && payload.wrap_self.length > 0) return "v3-wallet";
|
|
1587
|
+
return "v3-malformed";
|
|
1588
|
+
}
|
|
1564
1589
|
if (payload && payload.version === 2) return "v2-brc100";
|
|
1565
1590
|
if (payload && typeof payload.wrap_owner === "string" && payload.wrap_owner.length > 0) return "v2-wrapped";
|
|
1566
1591
|
return "legacy";
|
|
1567
1592
|
}
|
|
1593
|
+
function detectVaultFormat(payload) {
|
|
1594
|
+
if (!payload) return "legacy";
|
|
1595
|
+
return detectSaveFormat({
|
|
1596
|
+
wrap_owner: payload.wrap_owner,
|
|
1597
|
+
wrap_self: payload.wrap_self,
|
|
1598
|
+
wrap_version: payload.wrap_version
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1568
1601
|
function generateContentKey() {
|
|
1569
1602
|
return randomBytes3(32);
|
|
1570
1603
|
}
|
|
@@ -1579,6 +1612,49 @@ function unwrapForOwner(wrapStr, ownerPrivkey) {
|
|
|
1579
1612
|
const aad = buildAAD(ownerAddr, ownerAddr, SELF_WRAP_TXID);
|
|
1580
1613
|
return eciesUnwrap(wrapStr, ownerPrivkey, aad);
|
|
1581
1614
|
}
|
|
1615
|
+
function isValidHex(s) {
|
|
1616
|
+
return typeof s === "string" && s.length > 0 && s.length % 2 === 0 && /^[0-9a-f]+$/i.test(s);
|
|
1617
|
+
}
|
|
1618
|
+
async function unwrapForSelfV3(wrapHex, wallet, keyID) {
|
|
1619
|
+
if (!wallet) return { ok: false, reason: "wallet_unavailable" };
|
|
1620
|
+
if (!isValidHex(wrapHex)) return { ok: false, reason: "corrupt" };
|
|
1621
|
+
if (typeof keyID !== "string" || keyID.length === 0) return { ok: false, reason: "corrupt" };
|
|
1622
|
+
try {
|
|
1623
|
+
const { plaintext } = await wallet.decrypt({
|
|
1624
|
+
protocolID: ENCRYPT_PROTOCOL_ID_V2,
|
|
1625
|
+
keyID,
|
|
1626
|
+
counterparty: "self",
|
|
1627
|
+
ciphertext: Array.from(Buffer.from(wrapHex, "hex"))
|
|
1628
|
+
});
|
|
1629
|
+
const contentKey = Buffer.from(plaintext);
|
|
1630
|
+
if (contentKey.length !== 32) return { ok: false, reason: "corrupt" };
|
|
1631
|
+
return { ok: true, contentKey };
|
|
1632
|
+
} catch {
|
|
1633
|
+
return { ok: false, reason: "corrupt" };
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
async function unwrapRecoveryV3(wrapHex, wallet, keyID, ownerPubkeyHex) {
|
|
1637
|
+
if (!wallet) return { ok: false, reason: "wallet_unavailable" };
|
|
1638
|
+
if (!isValidHex(wrapHex)) return { ok: false, reason: "corrupt" };
|
|
1639
|
+
if (typeof keyID !== "string" || keyID.length === 0) return { ok: false, reason: "corrupt" };
|
|
1640
|
+
if (typeof ownerPubkeyHex !== "string" || !/^[0-9a-f]{66}$/i.test(ownerPubkeyHex)) {
|
|
1641
|
+
return { ok: false, reason: "corrupt" };
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
const { plaintext } = await wallet.decrypt({
|
|
1645
|
+
protocolID: ENCRYPT_PROTOCOL_ID_V2,
|
|
1646
|
+
keyID,
|
|
1647
|
+
counterparty: ownerPubkeyHex,
|
|
1648
|
+
ciphertext: Array.from(Buffer.from(wrapHex, "hex"))
|
|
1649
|
+
});
|
|
1650
|
+
const contentKey = Buffer.from(plaintext);
|
|
1651
|
+
if (contentKey.length !== 32) return { ok: false, reason: "corrupt" };
|
|
1652
|
+
return { ok: true, contentKey };
|
|
1653
|
+
} catch {
|
|
1654
|
+
return { ok: false, reason: "corrupt" };
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
var ENCRYPT_PROTOCOL_ID_V2 = [2, "indelible share"];
|
|
1582
1658
|
|
|
1583
1659
|
// mcp-server/tools/save_session.js
|
|
1584
1660
|
init_api_client();
|
|
@@ -1668,6 +1744,36 @@ async function pollInfraEmotions() {
|
|
|
1668
1744
|
}
|
|
1669
1745
|
|
|
1670
1746
|
// mcp-server/lib/redact.js
|
|
1747
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1748
|
+
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1749
|
+
function base58Decode(str) {
|
|
1750
|
+
const bytes = [0];
|
|
1751
|
+
for (let i = 0; i < str.length; i++) {
|
|
1752
|
+
const c = B58_ALPHABET.indexOf(str[i]);
|
|
1753
|
+
if (c < 0) return null;
|
|
1754
|
+
let carry = c;
|
|
1755
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
1756
|
+
carry += bytes[j] * 58;
|
|
1757
|
+
bytes[j] = carry & 255;
|
|
1758
|
+
carry >>= 8;
|
|
1759
|
+
}
|
|
1760
|
+
while (carry > 0) {
|
|
1761
|
+
bytes.push(carry & 255);
|
|
1762
|
+
carry >>= 8;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
for (let i = 0; i < str.length && str[i] === "1"; i++) bytes.push(0);
|
|
1766
|
+
return Buffer.from(bytes.reverse());
|
|
1767
|
+
}
|
|
1768
|
+
function isValidWif(candidate) {
|
|
1769
|
+
const decoded = base58Decode(candidate);
|
|
1770
|
+
if (!decoded || decoded.length < 5) return false;
|
|
1771
|
+
const payload = decoded.subarray(0, decoded.length - 4);
|
|
1772
|
+
const checksum = decoded.subarray(decoded.length - 4);
|
|
1773
|
+
const h = createHash3("sha256").update(createHash3("sha256").update(payload).digest()).digest();
|
|
1774
|
+
return h.subarray(0, 4).equals(checksum);
|
|
1775
|
+
}
|
|
1776
|
+
var MAX_VALIDATED_CANDIDATES = 2e4;
|
|
1671
1777
|
var CREDENTIAL_PATTERNS = [
|
|
1672
1778
|
{ name: "anthropic_key", pattern: /\bsk-ant-api03-[A-Za-z0-9_\-]{80,}/g },
|
|
1673
1779
|
// Catches legacy `sk-XXXX...` (51 chars) and modern `sk-proj-XXXX...` (longer,
|
|
@@ -1675,8 +1781,8 @@ var CREDENTIAL_PATTERNS = [
|
|
|
1675
1781
|
{ name: "openai_key", pattern: /\bsk-(?!ant-)(?:proj-)?[A-Za-z0-9_\-]{40,}/g },
|
|
1676
1782
|
{ name: "stripe_key", pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{20,}\b/g },
|
|
1677
1783
|
{ name: "aws_access_key", pattern: /\bAKIA[A-Z0-9]{16}\b/g },
|
|
1678
|
-
{ name: "bsv_wif", pattern: /\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b/g },
|
|
1679
|
-
//
|
|
1784
|
+
{ name: "bsv_wif", pattern: /\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b/g, validate: isValidWif },
|
|
1785
|
+
// base58 shape + Base58Check checksum (no base64 false positives)
|
|
1680
1786
|
{ name: "telegram_token", pattern: /\b\d{8,12}:[A-Za-z0-9_-]{30,40}\b/g },
|
|
1681
1787
|
{ name: "github_pat", pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
|
|
1682
1788
|
{ name: "github_pat_v2", pattern: /\bgithub_pat_[A-Za-z0-9_]{82}\b/g },
|
|
@@ -1685,10 +1791,17 @@ var CREDENTIAL_PATTERNS = [
|
|
|
1685
1791
|
function scanContentForCredentials(text) {
|
|
1686
1792
|
if (typeof text !== "string" || text.length === 0) return [];
|
|
1687
1793
|
const found = [];
|
|
1688
|
-
for (const { name, pattern } of CREDENTIAL_PATTERNS) {
|
|
1794
|
+
for (const { name, pattern, validate } of CREDENTIAL_PATTERNS) {
|
|
1689
1795
|
pattern.lastIndex = 0;
|
|
1690
1796
|
let m;
|
|
1797
|
+
let validated = 0;
|
|
1691
1798
|
while ((m = pattern.exec(text)) !== null) {
|
|
1799
|
+
if (validate) {
|
|
1800
|
+
if (validated < MAX_VALIDATED_CANDIDATES) {
|
|
1801
|
+
validated++;
|
|
1802
|
+
if (!validate(m[0])) continue;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1692
1805
|
found.push({
|
|
1693
1806
|
pattern_name: name,
|
|
1694
1807
|
preview: m[0].slice(0, 12) + "..."
|
|
@@ -2060,7 +2173,7 @@ import { fileURLToPath as _fileURLToPath } from "url";
|
|
|
2060
2173
|
var PROJECT_ROOT_FOR_SAVE_FILE_CLONE_CHECK = _pathResolve(_dirname(_fileURLToPath(import.meta.url)), "..");
|
|
2061
2174
|
var CODE_EXTS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"]);
|
|
2062
2175
|
var CLONE_CHECK_TIMEOUT_MS = 5e3;
|
|
2063
|
-
var MAX_CHUNK_SIZE =
|
|
2176
|
+
var MAX_CHUNK_SIZE = 1e7;
|
|
2064
2177
|
var TX_CACHE_DIR = join4(homedir4(), ".indelible", "tx-cache");
|
|
2065
2178
|
async function cacheTx(txId, payload) {
|
|
2066
2179
|
try {
|
|
@@ -2080,9 +2193,13 @@ async function saveFile(filePath, options = {}) {
|
|
|
2080
2193
|
if (!existsSync4(filePath)) {
|
|
2081
2194
|
return { success: false, error: `File not found: ${filePath}` };
|
|
2082
2195
|
}
|
|
2083
|
-
const
|
|
2084
|
-
const
|
|
2085
|
-
const
|
|
2196
|
+
const buf = await readFile(filePath);
|
|
2197
|
+
const asText = buf.toString("utf-8");
|
|
2198
|
+
const isText = Buffer.from(asText, "utf-8").equals(buf);
|
|
2199
|
+
const fileEnc = isText ? "utf8" : "b64";
|
|
2200
|
+
const content = isText ? asText : "";
|
|
2201
|
+
const contentHash = sha256(buf);
|
|
2202
|
+
const fileSize = buf.length;
|
|
2086
2203
|
const filename = basename(filePath);
|
|
2087
2204
|
const relativePath = options.relativePath || filePath;
|
|
2088
2205
|
const fileVerdict = reviewFile({ filename, relativePath, contentHash, fileSize, content });
|
|
@@ -2157,7 +2274,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
2157
2274
|
`);
|
|
2158
2275
|
}
|
|
2159
2276
|
}
|
|
2160
|
-
const compressed = gzipSync(
|
|
2277
|
+
const compressed = gzipSync(buf);
|
|
2161
2278
|
const encrypted = encrypt("gz:" + compressed.toString("base64"), wif);
|
|
2162
2279
|
let utxos = options.utxos || await getSpendableUtxos(config.address, getUtxos);
|
|
2163
2280
|
if (!utxos || utxos.length === 0) {
|
|
@@ -2180,6 +2297,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
2180
2297
|
path_enc: pathEnc,
|
|
2181
2298
|
size: fileSize,
|
|
2182
2299
|
content_hash: `sha256:${contentHash}`,
|
|
2300
|
+
enc: fileEnc,
|
|
2183
2301
|
encrypted,
|
|
2184
2302
|
owner: config.address,
|
|
2185
2303
|
timestamp
|
|
@@ -2236,6 +2354,7 @@ async function saveFile(filePath, options = {}) {
|
|
|
2236
2354
|
path_enc: pathEnc,
|
|
2237
2355
|
size: fileSize,
|
|
2238
2356
|
content_hash: `sha256:${contentHash}`,
|
|
2357
|
+
enc: fileEnc,
|
|
2239
2358
|
_chunks: chunks.length,
|
|
2240
2359
|
owner: config.address,
|
|
2241
2360
|
timestamp
|
|
@@ -2868,6 +2987,13 @@ async function _saveSessionInner(transcriptPath, summary, mode) {
|
|
|
2868
2987
|
const config = await loadConfig();
|
|
2869
2988
|
const wif = await getWif();
|
|
2870
2989
|
if (!wif) {
|
|
2990
|
+
if (config?.external_identity) {
|
|
2991
|
+
return {
|
|
2992
|
+
success: false,
|
|
2993
|
+
wallet_native: true,
|
|
2994
|
+
error: "Wallet-native account: this box holds no signing key (it lives in your external wallet). Save from the web app, or link a recovery key to enable box saves."
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2871
2997
|
return {
|
|
2872
2998
|
success: false,
|
|
2873
2999
|
error: "Wallet not configured or PIN incorrect. Run setup_wallet first."
|
|
@@ -3305,7 +3431,7 @@ import { join as join9 } from "path";
|
|
|
3305
3431
|
import { homedir as homedir9 } from "os";
|
|
3306
3432
|
init_api_client();
|
|
3307
3433
|
init_spv();
|
|
3308
|
-
import { PrivateKey as PrivateKey7 } from "@bsv/sdk";
|
|
3434
|
+
import { PrivateKey as PrivateKey7, CompletedProtoWallet as CompletedProtoWallet2 } from "@bsv/sdk";
|
|
3309
3435
|
import { Transaction as Transaction5 } from "@bsv/sdk";
|
|
3310
3436
|
|
|
3311
3437
|
// mcp-server/tools/load_style.js
|
|
@@ -4397,22 +4523,80 @@ async function updateInnerState({ observed, source, preoccupations, open_loops,
|
|
|
4397
4523
|
|
|
4398
4524
|
// mcp-server/tools/load_context.js
|
|
4399
4525
|
import { readFileSync as readFileSync7 } from "fs";
|
|
4400
|
-
function decryptSession(payloadOrEncrypted, wif) {
|
|
4401
|
-
let
|
|
4526
|
+
async function decryptSession(payloadOrEncrypted, wif, wallet = null) {
|
|
4527
|
+
let payload;
|
|
4402
4528
|
if (typeof payloadOrEncrypted === "string") {
|
|
4403
|
-
|
|
4404
|
-
wrap_owner = null;
|
|
4529
|
+
payload = { encrypted: payloadOrEncrypted, wrap_owner: null };
|
|
4405
4530
|
} else {
|
|
4406
|
-
|
|
4407
|
-
wrap_owner = payloadOrEncrypted.wrap_owner;
|
|
4531
|
+
payload = payloadOrEncrypted;
|
|
4408
4532
|
}
|
|
4533
|
+
const encrypted = payload.encrypted;
|
|
4534
|
+
const wrap_owner = payload.wrap_owner;
|
|
4409
4535
|
let text;
|
|
4410
|
-
const fmt = detectSaveFormat({ wrap_owner });
|
|
4411
|
-
if (fmt === "
|
|
4412
|
-
|
|
4536
|
+
const fmt = detectSaveFormat({ wrap_owner, wrap_self: payload.wrap_self, wrap_version: payload.wrap_version });
|
|
4537
|
+
if (fmt === "v3-wallet") {
|
|
4538
|
+
let w = wallet;
|
|
4539
|
+
if (!w && wif) {
|
|
4540
|
+
try {
|
|
4541
|
+
w = new CompletedProtoWallet2(PrivateKey7.fromWif(wif));
|
|
4542
|
+
} catch {
|
|
4543
|
+
w = null;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
let r = null;
|
|
4547
|
+
if (payload.owner_pubkey && w) {
|
|
4548
|
+
let idPub = null;
|
|
4549
|
+
try {
|
|
4550
|
+
idPub = (await w.getPublicKey({ identityKey: true }))?.publicKey || null;
|
|
4551
|
+
} catch {
|
|
4552
|
+
}
|
|
4553
|
+
if (idPub && idPub !== payload.owner_pubkey) {
|
|
4554
|
+
if (payload.wrap_recovery) {
|
|
4555
|
+
const rr = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
|
|
4556
|
+
if (rr.ok) r = rr;
|
|
4557
|
+
}
|
|
4558
|
+
if (!r) r = { ok: false, reason: "wallet_unavailable" };
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
if (!r) {
|
|
4562
|
+
r = await unwrapForSelfV3(payload.wrap_self, w, payload.key_id);
|
|
4563
|
+
if (!r.ok && r.reason === "corrupt" && payload.wrap_recovery && payload.owner_pubkey) {
|
|
4564
|
+
r = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
if (!r.ok) {
|
|
4568
|
+
const err = new Error(r.reason === "wallet_unavailable" ? "wallet-native (v3) save: no wallet available to unwrap \u2014 connect the wallet that made this save" : `wallet-native (v3) save: unwrap failed (${r.reason})`);
|
|
4569
|
+
err.code = r.reason === "wallet_unavailable" ? "WALLET_UNAVAILABLE" : "V3_CORRUPT";
|
|
4570
|
+
throw err;
|
|
4571
|
+
}
|
|
4572
|
+
text = decryptWithKey(encrypted, r.contentKey);
|
|
4573
|
+
} else if (fmt === "v3-malformed") {
|
|
4574
|
+
const err = new Error("v3 fields present but malformed \u2014 failing closed");
|
|
4575
|
+
err.code = "V3_CORRUPT";
|
|
4576
|
+
throw err;
|
|
4577
|
+
} else if (fmt === "v2-wrapped") {
|
|
4578
|
+
let ownerPriv = null;
|
|
4579
|
+
if (wif) {
|
|
4580
|
+
try {
|
|
4581
|
+
ownerPriv = PrivateKey7.fromWif(wif);
|
|
4582
|
+
} catch {
|
|
4583
|
+
ownerPriv = null;
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
if (!ownerPriv) {
|
|
4587
|
+
const err = new Error("v2-wrapped save: no valid key available to unwrap (wif missing or invalid)");
|
|
4588
|
+
err.code = "WALLET_UNAVAILABLE";
|
|
4589
|
+
throw err;
|
|
4590
|
+
}
|
|
4591
|
+
const r = unwrapForOwner(wrap_owner, ownerPriv);
|
|
4413
4592
|
if (!r.ok) throw new Error(`wrap_owner unwrap failed: ${r.reason}`);
|
|
4414
4593
|
text = decryptWithKey(encrypted, r.contentKey);
|
|
4415
4594
|
} else {
|
|
4595
|
+
if (!wif) {
|
|
4596
|
+
const err = new Error("legacy save: no key available to decrypt (wif missing)");
|
|
4597
|
+
err.code = "WALLET_UNAVAILABLE";
|
|
4598
|
+
throw err;
|
|
4599
|
+
}
|
|
4416
4600
|
text = decrypt(encrypted, wif);
|
|
4417
4601
|
}
|
|
4418
4602
|
if (text.startsWith("gz:")) {
|
|
@@ -4462,7 +4646,15 @@ async function fetchEncryptedFromChain(txId) {
|
|
|
4462
4646
|
if (end > 0) {
|
|
4463
4647
|
try {
|
|
4464
4648
|
const payload = JSON.parse(str.slice(jsonStart, end + 1));
|
|
4465
|
-
if (payload.encrypted)
|
|
4649
|
+
if (payload.encrypted) {
|
|
4650
|
+
const out = { encrypted: payload.encrypted, wrap_owner: payload.wrap_owner || null };
|
|
4651
|
+
if (payload.wrap_version !== void 0) out.wrap_version = payload.wrap_version;
|
|
4652
|
+
if (payload.wrap_self !== void 0) out.wrap_self = payload.wrap_self;
|
|
4653
|
+
if (payload.key_id !== void 0) out.key_id = payload.key_id;
|
|
4654
|
+
if (payload.wrap_recovery !== void 0) out.wrap_recovery = payload.wrap_recovery;
|
|
4655
|
+
if (payload.owner_pubkey !== void 0) out.owner_pubkey = payload.owner_pubkey;
|
|
4656
|
+
return out;
|
|
4657
|
+
}
|
|
4466
4658
|
} catch {
|
|
4467
4659
|
}
|
|
4468
4660
|
}
|
|
@@ -4647,14 +4839,19 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4647
4839
|
process.stderr.write(`[indelible] Scavenging ${recent.length} sessions from JSONL index (streamed tail)
|
|
4648
4840
|
`);
|
|
4649
4841
|
}
|
|
4842
|
+
let v3Locked = 0;
|
|
4650
4843
|
if (recent.length) {
|
|
4651
4844
|
for (const entry of recent) {
|
|
4652
4845
|
try {
|
|
4653
4846
|
if (entry.encrypted) {
|
|
4654
|
-
const data = decryptSession(entry, wif);
|
|
4847
|
+
const data = await decryptSession(entry, wif);
|
|
4655
4848
|
decrypted.push(data);
|
|
4656
4849
|
}
|
|
4657
|
-
} catch {
|
|
4850
|
+
} catch (e) {
|
|
4851
|
+
if (e?.code === "WALLET_UNAVAILABLE") {
|
|
4852
|
+
v3Locked++;
|
|
4853
|
+
continue;
|
|
4854
|
+
}
|
|
4658
4855
|
}
|
|
4659
4856
|
}
|
|
4660
4857
|
decrypted.reverse();
|
|
@@ -4668,7 +4865,7 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4668
4865
|
try {
|
|
4669
4866
|
const enc = await fetchEncryptedFromChain(cursorTx);
|
|
4670
4867
|
if (!enc) break;
|
|
4671
|
-
const data = decryptSession(enc, wif);
|
|
4868
|
+
const data = await decryptSession(enc, wif);
|
|
4672
4869
|
if (data.type === "diary" || data.type === "duo") {
|
|
4673
4870
|
cursorTx = data.prev_tx_id;
|
|
4674
4871
|
continue;
|
|
@@ -4676,7 +4873,8 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4676
4873
|
decrypted.push(data);
|
|
4677
4874
|
if (data.type !== "delta") break;
|
|
4678
4875
|
cursorTx = data.prev_tx_id;
|
|
4679
|
-
} catch {
|
|
4876
|
+
} catch (e) {
|
|
4877
|
+
if (e?.code === "WALLET_UNAVAILABLE") v3Locked++;
|
|
4680
4878
|
break;
|
|
4681
4879
|
}
|
|
4682
4880
|
}
|
|
@@ -4698,16 +4896,20 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4698
4896
|
for (const session of sessions) {
|
|
4699
4897
|
try {
|
|
4700
4898
|
if (session.encrypted) {
|
|
4701
|
-
const data = decryptSession(session, wif);
|
|
4899
|
+
const data = await decryptSession(session, wif);
|
|
4702
4900
|
decrypted.push(data);
|
|
4703
4901
|
} else if (session.txId) {
|
|
4704
4902
|
const fromChain = await fetchEncryptedFromChain(session.txId);
|
|
4705
4903
|
if (fromChain) {
|
|
4706
|
-
const data = decryptSession(fromChain, wif);
|
|
4904
|
+
const data = await decryptSession(fromChain, wif);
|
|
4707
4905
|
decrypted.push(data);
|
|
4708
4906
|
}
|
|
4709
4907
|
}
|
|
4710
|
-
} catch {
|
|
4908
|
+
} catch (e) {
|
|
4909
|
+
if (e?.code === "WALLET_UNAVAILABLE") {
|
|
4910
|
+
v3Locked++;
|
|
4911
|
+
continue;
|
|
4912
|
+
}
|
|
4711
4913
|
}
|
|
4712
4914
|
}
|
|
4713
4915
|
if (decrypted.length === 0) {
|
|
@@ -4738,6 +4940,10 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4738
4940
|
"# Restored from Blockchain Memory (Indelible)",
|
|
4739
4941
|
""
|
|
4740
4942
|
];
|
|
4943
|
+
if (v3Locked > 0) {
|
|
4944
|
+
contextParts.push(`\u26A0\uFE0F PARTIAL RESTORE \u2014 ${v3Locked} wallet-native (v3) save(s) could NOT be decrypted because the wallet that made them is not available here. Connect that wallet (e.g. MetaNet Desktop via the web app) to restore them. Nothing was skipped silently \u2014 this notice is the record.`);
|
|
4945
|
+
contextParts.push("");
|
|
4946
|
+
}
|
|
4741
4947
|
if (merged.length > 0) {
|
|
4742
4948
|
contextParts.push(formatCurrentConversation(merged[0], pagination));
|
|
4743
4949
|
for (let i = 1; i < merged.length; i++) {
|
|
@@ -4863,7 +5069,8 @@ async function loadContext(numSessions = 5, pagination = null, opts = {}) {
|
|
|
4863
5069
|
total_preserved: driftResult.total_preserved,
|
|
4864
5070
|
warning_emitted: !!driftWarning
|
|
4865
5071
|
} : null,
|
|
4866
|
-
|
|
5072
|
+
wallet_native_locked: v3Locked,
|
|
5073
|
+
message: `Smart restore (${source}): ${merged.length} conversation(s) from ${decrypted.length} chunks. Current conversation shown in full.` + (v3Locked > 0 ? ` \u26A0\uFE0F ${v3Locked} wallet-native save(s) locked \u2014 wallet not available (PARTIAL restore).` : "")
|
|
4867
5074
|
};
|
|
4868
5075
|
} catch (error) {
|
|
4869
5076
|
return {
|
|
@@ -5045,12 +5252,41 @@ ${formatSaveReceipt(receipt)}`
|
|
|
5045
5252
|
// mcp-server/tools/load_file.js
|
|
5046
5253
|
init_config_customer();
|
|
5047
5254
|
init_crypto();
|
|
5048
|
-
init_spv();
|
|
5049
5255
|
import { gunzipSync as gunzipSync2 } from "zlib";
|
|
5050
5256
|
import { writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
5051
5257
|
import { existsSync as existsSync11 } from "fs";
|
|
5052
5258
|
import { dirname as dirname2 } from "path";
|
|
5053
|
-
import { Transaction as Transaction6 } from "@bsv/sdk";
|
|
5259
|
+
import { Transaction as Transaction6, PrivateKey as PrivateKey8, CompletedProtoWallet as CompletedProtoWallet3 } from "@bsv/sdk";
|
|
5260
|
+
init_spv();
|
|
5261
|
+
async function unwrapVaultV3(payload, wif) {
|
|
5262
|
+
let w = null;
|
|
5263
|
+
try {
|
|
5264
|
+
w = new CompletedProtoWallet3(PrivateKey8.fromWif(wif));
|
|
5265
|
+
} catch {
|
|
5266
|
+
w = null;
|
|
5267
|
+
}
|
|
5268
|
+
if (!w) return { ok: false, code: "WALLET_UNAVAILABLE" };
|
|
5269
|
+
if (payload.owner_pubkey) {
|
|
5270
|
+
let idPub = null;
|
|
5271
|
+
try {
|
|
5272
|
+
idPub = (await w.getPublicKey({ identityKey: true }))?.publicKey || null;
|
|
5273
|
+
} catch {
|
|
5274
|
+
}
|
|
5275
|
+
if (idPub && idPub !== payload.owner_pubkey) {
|
|
5276
|
+
if (payload.wrap_recovery) {
|
|
5277
|
+
const rr = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
|
|
5278
|
+
if (rr.ok) return { ok: true, contentKey: rr.contentKey };
|
|
5279
|
+
}
|
|
5280
|
+
return { ok: false, code: "WALLET_UNAVAILABLE" };
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
let r = await unwrapForSelfV3(payload.wrap_self, w, payload.key_id);
|
|
5284
|
+
if (!r.ok && r.reason === "corrupt" && payload.wrap_recovery && payload.owner_pubkey) {
|
|
5285
|
+
r = await unwrapRecoveryV3(payload.wrap_recovery, w, payload.key_id, payload.owner_pubkey);
|
|
5286
|
+
}
|
|
5287
|
+
if (!r.ok) return { ok: false, code: r.reason === "wallet_unavailable" ? "WALLET_UNAVAILABLE" : "V3_CORRUPT" };
|
|
5288
|
+
return { ok: true, contentKey: r.contentKey };
|
|
5289
|
+
}
|
|
5054
5290
|
async function fetchJsonPayload(txId) {
|
|
5055
5291
|
const cleanTxId = txId.trim();
|
|
5056
5292
|
try {
|
|
@@ -5100,6 +5336,47 @@ async function loadFile(txId, options = {}) {
|
|
|
5100
5336
|
if (payload.protocol !== "indelible.file") {
|
|
5101
5337
|
return { success: false, error: `Not an indelible.file tx (got: ${payload.protocol || "unknown"})` };
|
|
5102
5338
|
}
|
|
5339
|
+
const vfmt = detectVaultFormat(payload);
|
|
5340
|
+
if (vfmt === "v3-wallet") {
|
|
5341
|
+
const uv = await unwrapVaultV3(payload, wif);
|
|
5342
|
+
if (!uv.ok) {
|
|
5343
|
+
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)" };
|
|
5344
|
+
}
|
|
5345
|
+
let outBuf2;
|
|
5346
|
+
try {
|
|
5347
|
+
const dec = decryptWithKey(payload.encrypted, Buffer.from(uv.contentKey));
|
|
5348
|
+
outBuf2 = dec.startsWith("gz:") ? gunzipSync2(Buffer.from(dec.slice(3), "base64")) : Buffer.from(dec, "utf8");
|
|
5349
|
+
} catch (err) {
|
|
5350
|
+
return { success: false, code: "V3_CORRUPT", error: `wallet-native decrypt failed: ${err.message}` };
|
|
5351
|
+
}
|
|
5352
|
+
const contentHash2 = sha256(outBuf2);
|
|
5353
|
+
let meta = {};
|
|
5354
|
+
try {
|
|
5355
|
+
meta = JSON.parse(decryptWithKey(payload.meta_enc, Buffer.from(uv.contentKey)));
|
|
5356
|
+
} catch {
|
|
5357
|
+
}
|
|
5358
|
+
const verified2 = meta.sha256 ? contentHash2 === meta.sha256 : payload.content_hash ? contentHash2 === payload.content_hash.replace("sha256:", "") : null;
|
|
5359
|
+
if (options.outputPath) {
|
|
5360
|
+
const dir = dirname2(options.outputPath);
|
|
5361
|
+
if (!existsSync11(dir)) await mkdir4(dir, { recursive: true });
|
|
5362
|
+
await writeFile4(options.outputPath, outBuf2);
|
|
5363
|
+
}
|
|
5364
|
+
const isBinary2 = payload.enc === "b64";
|
|
5365
|
+
return {
|
|
5366
|
+
success: true,
|
|
5367
|
+
txId,
|
|
5368
|
+
wallet_native: true,
|
|
5369
|
+
filename: meta.filename,
|
|
5370
|
+
path: meta.path,
|
|
5371
|
+
size: payload.size,
|
|
5372
|
+
content_hash: payload.content_hash,
|
|
5373
|
+
verified: verified2,
|
|
5374
|
+
content: isBinary2 ? `[binary file: ${outBuf2.length} bytes${options.outputPath ? ", written to disk" : ""}]` : outBuf2.toString("utf8")
|
|
5375
|
+
};
|
|
5376
|
+
}
|
|
5377
|
+
if (vfmt === "v3-malformed") {
|
|
5378
|
+
return { success: false, code: "V3_CORRUPT", error: "file carries malformed v3 wrap fields \u2014 failing closed" };
|
|
5379
|
+
}
|
|
5103
5380
|
let encrypted;
|
|
5104
5381
|
if (payload._chunks) {
|
|
5105
5382
|
let encryptedParts = "";
|
|
@@ -5121,16 +5398,18 @@ async function loadFile(txId, options = {}) {
|
|
|
5121
5398
|
if (!encrypted) {
|
|
5122
5399
|
return { success: false, error: "No encrypted content found in tx" };
|
|
5123
5400
|
}
|
|
5124
|
-
let
|
|
5401
|
+
let outBuf;
|
|
5125
5402
|
try {
|
|
5126
|
-
|
|
5127
|
-
if (
|
|
5128
|
-
|
|
5403
|
+
const dec = decrypt(encrypted, wif);
|
|
5404
|
+
if (dec.startsWith("gz:")) {
|
|
5405
|
+
outBuf = gunzipSync2(Buffer.from(dec.slice(3), "base64"));
|
|
5406
|
+
} else {
|
|
5407
|
+
outBuf = Buffer.from(dec, "utf8");
|
|
5129
5408
|
}
|
|
5130
5409
|
} catch (err) {
|
|
5131
5410
|
return { success: false, error: `Decryption failed: ${err.message}` };
|
|
5132
5411
|
}
|
|
5133
|
-
const contentHash = sha256(
|
|
5412
|
+
const contentHash = sha256(outBuf);
|
|
5134
5413
|
const expectedHash = payload.content_hash?.replace("sha256:", "") || null;
|
|
5135
5414
|
const verified = expectedHash ? contentHash === expectedHash : null;
|
|
5136
5415
|
if (options.outputPath) {
|
|
@@ -5138,8 +5417,10 @@ async function loadFile(txId, options = {}) {
|
|
|
5138
5417
|
if (!existsSync11(dir)) {
|
|
5139
5418
|
await mkdir4(dir, { recursive: true });
|
|
5140
5419
|
}
|
|
5141
|
-
await writeFile4(options.outputPath,
|
|
5420
|
+
await writeFile4(options.outputPath, outBuf);
|
|
5142
5421
|
}
|
|
5422
|
+
const isBinary = payload.enc === "b64";
|
|
5423
|
+
const content = isBinary ? `[binary file: ${outBuf.length} bytes${options.outputPath ? ", written to disk" : ""}]` : outBuf.toString("utf8");
|
|
5143
5424
|
const filename = payload.filename_enc ? decrypt(payload.filename_enc, wif) : payload.filename;
|
|
5144
5425
|
const filePath = payload.path_enc ? decrypt(payload.path_enc, wif) : payload.path;
|
|
5145
5426
|
return {
|
|
@@ -5148,6 +5429,7 @@ async function loadFile(txId, options = {}) {
|
|
|
5148
5429
|
filename,
|
|
5149
5430
|
path: filePath,
|
|
5150
5431
|
size: payload.size,
|
|
5432
|
+
enc: payload.enc || "utf8",
|
|
5151
5433
|
content,
|
|
5152
5434
|
contentHash: `sha256:${contentHash}`,
|
|
5153
5435
|
verified,
|
|
@@ -5159,11 +5441,12 @@ async function loadFile(txId, options = {}) {
|
|
|
5159
5441
|
// mcp-server/tools/load_project.js
|
|
5160
5442
|
init_config_customer();
|
|
5161
5443
|
init_crypto();
|
|
5162
|
-
|
|
5444
|
+
import { gunzipSync as gunzipSync3 } from "zlib";
|
|
5163
5445
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
5164
5446
|
import { existsSync as existsSync12 } from "fs";
|
|
5165
5447
|
import { join as join11, dirname as dirname3 } from "path";
|
|
5166
5448
|
import { Transaction as Transaction7 } from "@bsv/sdk";
|
|
5449
|
+
init_spv();
|
|
5167
5450
|
async function fetchJsonPayload2(txId) {
|
|
5168
5451
|
const cleanTxId = txId.trim();
|
|
5169
5452
|
try {
|
|
@@ -5216,6 +5499,61 @@ async function loadProject(txId, options = {}) {
|
|
|
5216
5499
|
if (!manifest.files || !Array.isArray(manifest.files)) {
|
|
5217
5500
|
return { success: false, error: "Manifest has no files array" };
|
|
5218
5501
|
}
|
|
5502
|
+
if (detectVaultFormat(manifest) === "v3-wallet") {
|
|
5503
|
+
const uv = await unwrapVaultV3(manifest, wif);
|
|
5504
|
+
if (!uv.ok) {
|
|
5505
|
+
return { success: false, code: uv.code, error: uv.code === "WALLET_UNAVAILABLE" ? "wallet-native project: not readable from this box \u2014 open it in the web app with the wallet that saved it" : "wallet-native project: manifest unwrap failed (corrupt or wrong wallet)" };
|
|
5506
|
+
}
|
|
5507
|
+
const ck = Buffer.from(uv.contentKey);
|
|
5508
|
+
const dk = (s) => decryptWithKey(s, ck);
|
|
5509
|
+
let sealed = {};
|
|
5510
|
+
try {
|
|
5511
|
+
sealed = JSON.parse(dk(manifest.name_enc));
|
|
5512
|
+
} catch {
|
|
5513
|
+
}
|
|
5514
|
+
const v3Files = [];
|
|
5515
|
+
const v3Errors = [];
|
|
5516
|
+
for (const fileRef of manifest.files) {
|
|
5517
|
+
try {
|
|
5518
|
+
const meta = JSON.parse(dk(fileRef.meta_enc));
|
|
5519
|
+
const dec = dk(fileRef.encrypted);
|
|
5520
|
+
const outBuf = dec.startsWith("gz:") ? gunzipSync3(Buffer.from(dec.slice(3), "base64")) : Buffer.from(dec, "utf8");
|
|
5521
|
+
const verified = meta.sha256 ? sha256(outBuf) === meta.sha256 : null;
|
|
5522
|
+
if (verified === false) {
|
|
5523
|
+
v3Errors.push({ path: "(tampered)", error: "content hash mismatch vs sealed meta" });
|
|
5524
|
+
continue;
|
|
5525
|
+
}
|
|
5526
|
+
if (options.outputDir) {
|
|
5527
|
+
const outputPath = join11(options.outputDir, sealed.name || "project", meta.path);
|
|
5528
|
+
const dir = dirname3(outputPath);
|
|
5529
|
+
if (!existsSync12(dir)) await mkdir5(dir, { recursive: true });
|
|
5530
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
5531
|
+
await writeFile5(outputPath, outBuf);
|
|
5532
|
+
}
|
|
5533
|
+
const isBinary = fileRef.enc === "b64";
|
|
5534
|
+
v3Files.push({ path: meta.path, size: fileRef.size, verified, content: options.outputDir ? void 0 : isBinary ? `[binary: ${outBuf.length} bytes]` : outBuf.toString("utf8") });
|
|
5535
|
+
} catch (err) {
|
|
5536
|
+
v3Errors.push({ path: "(locked)", error: err.message });
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
const setOk = sealed.file_count == null || sealed.file_count === manifest.files.length;
|
|
5540
|
+
return {
|
|
5541
|
+
success: true,
|
|
5542
|
+
txId,
|
|
5543
|
+
wallet_native: true,
|
|
5544
|
+
projectName: sealed.name,
|
|
5545
|
+
fileCount: sealed.file_count ?? manifest.files.length,
|
|
5546
|
+
set_integrity: setOk ? "ok" : "FAILED",
|
|
5547
|
+
totalSize: manifest.total_size,
|
|
5548
|
+
loadedFiles: v3Files.length,
|
|
5549
|
+
files: v3Files,
|
|
5550
|
+
errors: v3Errors.length ? v3Errors : void 0,
|
|
5551
|
+
message: `Project "${sealed.name || ""}" loaded (wallet-native): ${v3Files.length}/${sealed.file_count ?? manifest.files.length} files${setOk ? "" : " \u2014 \u26A0\uFE0F SET-INTEGRITY MISMATCH"}`
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5554
|
+
if (detectVaultFormat(manifest) === "v3-malformed") {
|
|
5555
|
+
return { success: false, code: "V3_CORRUPT", error: "project manifest carries malformed v3 wrap fields \u2014 failing closed" };
|
|
5556
|
+
}
|
|
5219
5557
|
const isBundle = manifest.protocol === "indelible.project-bundle";
|
|
5220
5558
|
const projectName = manifest.name_enc ? decrypt(manifest.name_enc, wif) : manifest.name;
|
|
5221
5559
|
const loadedFiles = [];
|
|
@@ -5292,20 +5630,20 @@ async function updateVaultIndex() {
|
|
|
5292
5630
|
if (!_gate.ok) return { success: false, gated: true, plan: _gate.plan, upsell: _gate.upsell, error: _gate.error };
|
|
5293
5631
|
const files = config.file_txids || [];
|
|
5294
5632
|
const projects = config.project_txids || [];
|
|
5295
|
-
const encFiles = files.map((f) =>
|
|
5633
|
+
const encFiles = files.map((f) => f.filename ? {
|
|
5296
5634
|
txid: f.txId,
|
|
5297
|
-
filename_enc: encrypt(f.filename
|
|
5298
|
-
path_enc: encrypt(f.path ||
|
|
5635
|
+
filename_enc: encrypt(f.filename, wif),
|
|
5636
|
+
path_enc: encrypt(f.path || f.filename, wif),
|
|
5299
5637
|
size: f.size || 0,
|
|
5300
5638
|
timestamp: f.timestamp || null
|
|
5301
|
-
})
|
|
5302
|
-
const encProjects = projects.map((p) =>
|
|
5639
|
+
} : { txid: f.txId, size: f.size || 0, timestamp: f.timestamp || null, wrap: "v3" });
|
|
5640
|
+
const encProjects = projects.map((p) => p.name ? {
|
|
5303
5641
|
txid: p.txId,
|
|
5304
|
-
name_enc: encrypt(p.name
|
|
5642
|
+
name_enc: encrypt(p.name, wif),
|
|
5305
5643
|
fileCount: p.fileCount || 0,
|
|
5306
5644
|
totalSize: p.totalSize || 0,
|
|
5307
5645
|
timestamp: p.timestamp || null
|
|
5308
|
-
})
|
|
5646
|
+
} : { txid: p.txId, fileCount: p.fileCount || 0, totalSize: p.totalSize || 0, timestamp: p.timestamp || null, wrap: "v3" });
|
|
5309
5647
|
const payload = {
|
|
5310
5648
|
protocol: "indelible.vault-index",
|
|
5311
5649
|
version: 1,
|
|
@@ -5676,7 +6014,7 @@ async function buildIndexWindow(address, wif, { fromDate = null, toDate = null,
|
|
|
5676
6014
|
if (!fromChain || !fromChain.encrypted) {
|
|
5677
6015
|
newGaps.push({ txId: s.txId, date: s.timestamp || null, save_type: s.save_type || null, reason: "NOT_ON_CHAIN", on_chain: false, logged_at: nowISO });
|
|
5678
6016
|
} else {
|
|
5679
|
-
const session = decryptSession(fromChain, wif);
|
|
6017
|
+
const session = await decryptSession(fromChain, wif);
|
|
5680
6018
|
const { title, searchText, message_count } = deriveTitleSearch(session);
|
|
5681
6019
|
newRecords.push({
|
|
5682
6020
|
txId: s.txId,
|
|
@@ -5693,7 +6031,7 @@ async function buildIndexWindow(address, wif, { fromDate = null, toDate = null,
|
|
|
5693
6031
|
}
|
|
5694
6032
|
} catch (e) {
|
|
5695
6033
|
const msg = String(e.message || e);
|
|
5696
|
-
const reason = /timeout/i.test(msg) ? "FETCH_TIMEOUT" : "DECRYPT_FAIL";
|
|
6034
|
+
const reason = e?.code === "WALLET_UNAVAILABLE" ? "WALLET_LOCKED" : /timeout/i.test(msg) ? "FETCH_TIMEOUT" : "DECRYPT_FAIL";
|
|
5697
6035
|
newGaps.push({ txId: s.txId, date: s.timestamp || null, save_type: s.save_type || null, reason, detail: redactSecrets(msg).slice(0, 120), on_chain: reason === "DECRYPT_FAIL", logged_at: nowISO });
|
|
5698
6036
|
}
|
|
5699
6037
|
done++;
|
|
@@ -5769,7 +6107,7 @@ async function diaryRecall({ from_date = null, to_date = null, query = null, dep
|
|
|
5769
6107
|
entries.push({ txId, error: "not_on_chain" });
|
|
5770
6108
|
continue;
|
|
5771
6109
|
}
|
|
5772
|
-
const s = decryptSession(fromChain, wif);
|
|
6110
|
+
const s = await decryptSession(fromChain, wif);
|
|
5773
6111
|
if (!saveTypes.includes(s?.type)) {
|
|
5774
6112
|
entries.push({ txId, error: "wrong_scope", type: s?.type || null });
|
|
5775
6113
|
continue;
|
|
@@ -5964,7 +6302,7 @@ ${context}` });
|
|
|
5964
6302
|
init_config_customer();
|
|
5965
6303
|
init_spv();
|
|
5966
6304
|
init_utxo_cache();
|
|
5967
|
-
import { PrivateKey as
|
|
6305
|
+
import { PrivateKey as PrivateKey9 } from "@bsv/sdk";
|
|
5968
6306
|
var DEFAULT_MAX_SATS = 1e4;
|
|
5969
6307
|
async function x402Fetch({ url, method = "GET", headers = {}, body, maxSats }) {
|
|
5970
6308
|
const cap = typeof maxSats === "number" && maxSats > 0 ? maxSats : DEFAULT_MAX_SATS;
|
|
@@ -6025,7 +6363,7 @@ async function x402Fetch({ url, method = "GET", headers = {}, body, maxSats }) {
|
|
|
6025
6363
|
pricing
|
|
6026
6364
|
};
|
|
6027
6365
|
}
|
|
6028
|
-
const privateKey =
|
|
6366
|
+
const privateKey = PrivateKey9.fromWif(wif);
|
|
6029
6367
|
const address = privateKey.toPublicKey().toAddress();
|
|
6030
6368
|
let utxos;
|
|
6031
6369
|
try {
|
|
@@ -6110,10 +6448,10 @@ init_utxo_cache();
|
|
|
6110
6448
|
init_config_customer();
|
|
6111
6449
|
init_api_client();
|
|
6112
6450
|
import crypto from "crypto";
|
|
6113
|
-
import { PrivateKey as
|
|
6451
|
+
import { PrivateKey as PrivateKey10 } from "@bsv/sdk";
|
|
6114
6452
|
var GOALS_PATH2 = goalsPath("");
|
|
6115
6453
|
function deriveAddress(wif) {
|
|
6116
|
-
return
|
|
6454
|
+
return PrivateKey10.fromWif(wif).toAddress().toString();
|
|
6117
6455
|
}
|
|
6118
6456
|
function encryptGoals(goalsData, wif) {
|
|
6119
6457
|
const key = crypto.createHash("sha256").update(wif).digest();
|
|
@@ -6223,7 +6561,7 @@ async function recallContext({ from_date = null, to_date = null, query = null, d
|
|
|
6223
6561
|
sessions.push({ txId, error: "not_on_chain" });
|
|
6224
6562
|
continue;
|
|
6225
6563
|
}
|
|
6226
|
-
const s = decryptSession(fromChain, wif);
|
|
6564
|
+
const s = await decryptSession(fromChain, wif);
|
|
6227
6565
|
const msgs = Array.isArray(s?.messages) ? s.messages : [];
|
|
6228
6566
|
sessions.push({
|
|
6229
6567
|
txId,
|
|
@@ -6380,11 +6718,13 @@ init_spv();
|
|
|
6380
6718
|
init_api_client();
|
|
6381
6719
|
import {
|
|
6382
6720
|
Transaction as Transaction8,
|
|
6383
|
-
PrivateKey as
|
|
6721
|
+
PrivateKey as PrivateKey11,
|
|
6384
6722
|
P2PKH as P2PKH3,
|
|
6385
6723
|
SatoshisPerKilobyte as SatoshisPerKilobyte3,
|
|
6386
6724
|
LockingScript as LockingScript3,
|
|
6387
|
-
OP as OP3
|
|
6725
|
+
OP as OP3,
|
|
6726
|
+
CompletedProtoWallet as CompletedProtoWallet4,
|
|
6727
|
+
AuthFetch as AuthFetch2
|
|
6388
6728
|
} from "@bsv/sdk";
|
|
6389
6729
|
var SHARE_DUST_SATS = 546;
|
|
6390
6730
|
async function findRecipientPubkey(recipientAddr, scanLimit = 20) {
|
|
@@ -6458,16 +6798,27 @@ async function shareSession({ session_txid, recipient_addr, expires_at = null })
|
|
|
6458
6798
|
if (!wif) return { success: false, error: "wallet not configured \u2014 run setup_wallet first" };
|
|
6459
6799
|
const _gate = await requireProForWrite(config.address);
|
|
6460
6800
|
if (!_gate.ok) return { success: false, gated: true, plan: _gate.plan, upsell: _gate.upsell, error: _gate.error };
|
|
6461
|
-
const ownerPriv =
|
|
6801
|
+
const ownerPriv = PrivateKey11.fromWif(wif);
|
|
6462
6802
|
const ownerAddr = ownerPriv.toPublicKey().toAddress();
|
|
6463
6803
|
const payload = await fetchScopePayload(session_txid);
|
|
6464
6804
|
if (!payload) return { success: false, error: `could not fetch scope tx ${session_txid}` };
|
|
6465
|
-
|
|
6805
|
+
const fmt = detectSaveFormat({ wrap_owner: payload.wrap_owner, wrap_self: payload.wrap_self, wrap_version: payload.wrap_version });
|
|
6806
|
+
let contentKey;
|
|
6807
|
+
if (fmt === "v3-wallet") {
|
|
6808
|
+
const rv3 = await unwrapForSelfV3(payload.wrap_self, new CompletedProtoWallet4(ownerPriv), payload.key_id);
|
|
6809
|
+
if (!rv3.ok) {
|
|
6810
|
+
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)` };
|
|
6811
|
+
}
|
|
6812
|
+
contentKey = rv3.contentKey;
|
|
6813
|
+
} else if (fmt === "v2-wrapped") {
|
|
6814
|
+
const r = unwrapForOwner(payload.wrap_owner, ownerPriv);
|
|
6815
|
+
if (!r.ok) return { success: false, error: `failed to unwrap own content_key: ${r.reason}` };
|
|
6816
|
+
contentKey = r.contentKey;
|
|
6817
|
+
} else if (fmt === "v3-malformed") {
|
|
6818
|
+
return { success: false, error: "scope carries malformed v3 fields \u2014 failing closed, not shareable" };
|
|
6819
|
+
} else {
|
|
6466
6820
|
return { success: false, error: "scope is legacy format (no wrap_owner) \u2014 only post-g-170 saves are shareable. Re-save the session to make it shareable." };
|
|
6467
6821
|
}
|
|
6468
|
-
const r = unwrapForOwner(payload.wrap_owner, ownerPriv);
|
|
6469
|
-
if (!r.ok) return { success: false, error: `failed to unwrap own content_key: ${r.reason}` };
|
|
6470
|
-
const contentKey = r.contentKey;
|
|
6471
6822
|
const recipientPubkey = await findRecipientPubkey(recipient_addr);
|
|
6472
6823
|
if (!recipientPubkey) {
|
|
6473
6824
|
return { success: false, error: `recipient ${recipient_addr} has no signed tx in their history \u2014 they must broadcast at least one tx for their pubkey to be discoverable` };
|
|
@@ -6522,7 +6873,7 @@ async function shareSession({ session_txid, recipient_addr, expires_at = null })
|
|
|
6522
6873
|
tx.addOutput({ lockingScript: opReturnScript, satoshis: 0 });
|
|
6523
6874
|
tx.addOutput({ lockingScript: p2pkh.lock(recipient_addr), satoshis: SHARE_DUST_SATS });
|
|
6524
6875
|
tx.addOutput({ lockingScript: ownerLock, change: true });
|
|
6525
|
-
await tx.fee(new SatoshisPerKilobyte3(
|
|
6876
|
+
await tx.fee(new SatoshisPerKilobyte3(150));
|
|
6526
6877
|
await tx.sign();
|
|
6527
6878
|
txHex = tx.toHex();
|
|
6528
6879
|
txId = tx.id("hex");
|
|
@@ -6530,7 +6881,45 @@ async function shareSession({ session_txid, recipient_addr, expires_at = null })
|
|
|
6530
6881
|
} catch (e) {
|
|
6531
6882
|
return { success: false, error: `broadcast failed: ${e.message}` };
|
|
6532
6883
|
}
|
|
6533
|
-
|
|
6884
|
+
const index_status = await indexShareOnBridge(txId, envelope, ownerPriv);
|
|
6885
|
+
return { success: true, share_txid: txId, envelope, index_status };
|
|
6886
|
+
}
|
|
6887
|
+
async function indexShareOnBridge(txId, envelope, ownerPriv) {
|
|
6888
|
+
try {
|
|
6889
|
+
const bridges = await getBridges();
|
|
6890
|
+
const record = {
|
|
6891
|
+
txId,
|
|
6892
|
+
recipient: envelope.recipient_addr,
|
|
6893
|
+
sender: envelope.owner_addr,
|
|
6894
|
+
identityKey: ownerPriv.toPublicKey().toString(),
|
|
6895
|
+
protocol: envelope.protocol,
|
|
6896
|
+
payload: JSON.stringify(envelope),
|
|
6897
|
+
// decryptShare recovers the full envelope from here
|
|
6898
|
+
// the envelope stamps ISO; the bridge index demands ms-epoch (putShare validation)
|
|
6899
|
+
issued_at: typeof envelope.issued_at === "number" ? envelope.issued_at : Date.parse(envelope.issued_at)
|
|
6900
|
+
};
|
|
6901
|
+
const af = new AuthFetch2(new CompletedProtoWallet4(ownerPriv));
|
|
6902
|
+
let lastErr = null;
|
|
6903
|
+
for (const b of bridges.slice(0, 3)) {
|
|
6904
|
+
try {
|
|
6905
|
+
const res = await af.fetch(`${b.url}/api/shares/index`, {
|
|
6906
|
+
method: "POST",
|
|
6907
|
+
headers: { "Content-Type": "application/json" },
|
|
6908
|
+
body: JSON.stringify(record)
|
|
6909
|
+
});
|
|
6910
|
+
if (res.ok) {
|
|
6911
|
+
const body = await res.json().catch(() => ({}));
|
|
6912
|
+
return { ok: true, bridge: b.url, gossip: body.gossip_status || "unknown" };
|
|
6913
|
+
}
|
|
6914
|
+
lastErr = `${b.url}: HTTP ${res.status}`;
|
|
6915
|
+
} catch (e) {
|
|
6916
|
+
lastErr = `${b.url}: ${e.message}`;
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
return { ok: false, error: lastErr || "no bridge accepted the index record" };
|
|
6920
|
+
} catch (e) {
|
|
6921
|
+
return { ok: false, error: e.message };
|
|
6922
|
+
}
|
|
6534
6923
|
}
|
|
6535
6924
|
function pushDataChunk2(data) {
|
|
6536
6925
|
const len = data.length;
|
|
@@ -6828,7 +7217,7 @@ Commands:
|
|
|
6828
7217
|
}
|
|
6829
7218
|
function printHelp() {
|
|
6830
7219
|
console.log(`
|
|
6831
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.8.
|
|
7220
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.8.2)
|
|
6832
7221
|
|
|
6833
7222
|
Setup:
|
|
6834
7223
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -7047,7 +7436,7 @@ function readStdin() {
|
|
|
7047
7436
|
}
|
|
7048
7437
|
var SERVER_INFO = {
|
|
7049
7438
|
name: "indelible",
|
|
7050
|
-
version: "4.8.
|
|
7439
|
+
version: "4.8.2",
|
|
7051
7440
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
7052
7441
|
};
|
|
7053
7442
|
var TOOLS = [
|