indelible-mcp 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +2 -2
- package/src/tools/save_file.js +7 -3
- package/src/tools/save_project.js +52 -59
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -220,7 +220,7 @@ Commands:
|
|
|
220
220
|
|
|
221
221
|
function printHelp() {
|
|
222
222
|
console.log(`
|
|
223
|
-
Indelible MCP — Blockchain memory for Claude Code (v2.
|
|
223
|
+
Indelible MCP — Blockchain memory for Claude Code (v2.6.0)
|
|
224
224
|
|
|
225
225
|
Setup:
|
|
226
226
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -316,7 +316,7 @@ function readStdin() {
|
|
|
316
316
|
|
|
317
317
|
const SERVER_INFO = {
|
|
318
318
|
name: 'indelible',
|
|
319
|
-
version: '2.
|
|
319
|
+
version: '2.6.0',
|
|
320
320
|
description: 'Blockchain-backed memory and code storage for Claude Code'
|
|
321
321
|
}
|
|
322
322
|
|
package/src/tools/save_file.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function saveFile(filePath, options = {}) {
|
|
|
34
34
|
const filenameEnc = encrypt(filename, wif)
|
|
35
35
|
const pathEnc = encrypt(relativePath, wif)
|
|
36
36
|
|
|
37
|
-
let utxos = await spv.getUtxos(config.address)
|
|
37
|
+
let utxos = options.utxos || await spv.getUtxos(config.address)
|
|
38
38
|
if (!utxos || utxos.length === 0) {
|
|
39
39
|
return { success: false, error: 'No UTXOs available. Fund your wallet first.' }
|
|
40
40
|
}
|
|
@@ -43,6 +43,7 @@ export async function saveFile(filePath, options = {}) {
|
|
|
43
43
|
|
|
44
44
|
try {
|
|
45
45
|
let masterTxId
|
|
46
|
+
let finalChangeUtxos = null
|
|
46
47
|
|
|
47
48
|
if (encrypted.length <= MAX_CHUNK_SIZE) {
|
|
48
49
|
const payload = {
|
|
@@ -57,11 +58,12 @@ export async function saveFile(filePath, options = {}) {
|
|
|
57
58
|
timestamp
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
const { txHex, txId } = await spv.buildOpReturnTxWithChange(
|
|
61
|
+
const { txHex, txId, changeUtxos } = await spv.buildOpReturnTxWithChange(
|
|
61
62
|
wif, utxos, JSON.stringify(payload)
|
|
62
63
|
)
|
|
63
64
|
const result = await spv.broadcastTx(txHex)
|
|
64
65
|
masterTxId = result.txid || txId
|
|
66
|
+
finalChangeUtxos = changeUtxos
|
|
65
67
|
} else {
|
|
66
68
|
// Chunked upload
|
|
67
69
|
const chunks = []
|
|
@@ -118,11 +120,12 @@ export async function saveFile(filePath, options = {}) {
|
|
|
118
120
|
await new Promise(r => setTimeout(r, 500))
|
|
119
121
|
utxos = await spv.getUtxos(config.address)
|
|
120
122
|
}
|
|
121
|
-
const { txHex, txId } = await spv.buildOpReturnTxWithChange(
|
|
123
|
+
const { txHex, txId, changeUtxos: masterChangeUtxos } = await spv.buildOpReturnTxWithChange(
|
|
122
124
|
wif, utxos, JSON.stringify(masterPayload)
|
|
123
125
|
)
|
|
124
126
|
const result = await spv.broadcastTx(txHex)
|
|
125
127
|
masterTxId = result.txid || txId
|
|
128
|
+
finalChangeUtxos = masterChangeUtxos
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
// Track in config
|
|
@@ -147,6 +150,7 @@ export async function saveFile(filePath, options = {}) {
|
|
|
147
150
|
size: fileSize,
|
|
148
151
|
contentHash: `sha256:${contentHash}`,
|
|
149
152
|
chunks: chunkCount,
|
|
153
|
+
changeUtxos: finalChangeUtxos,
|
|
150
154
|
message: `File "${filename}" saved to blockchain. txId: ${masterTxId}${chunkCount > 1 ? ` (${chunkCount} chunks)` : ''}`
|
|
151
155
|
}
|
|
152
156
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* save_project tool — Save a directory to BSV blockchain
|
|
3
|
-
* Walks directory (respecting .gitignore),
|
|
4
|
-
*
|
|
3
|
+
* Walks directory (respecting .gitignore), encrypts all files client-side,
|
|
4
|
+
* bundles into a single OP_RETURN transaction.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
|
@@ -9,7 +9,6 @@ import { join, relative, basename } from 'node:path'
|
|
|
9
9
|
import { loadConfig, saveConfig, getWif } from '../lib/config.js'
|
|
10
10
|
import { encrypt, sha256 } from '../lib/crypto.js'
|
|
11
11
|
import * as spv from '../lib/spv.js'
|
|
12
|
-
import { saveFile } from './save_file.js'
|
|
13
12
|
|
|
14
13
|
const ALWAYS_IGNORE = [
|
|
15
14
|
'node_modules', '.git', '.next', 'dist', 'build',
|
|
@@ -93,96 +92,90 @@ export async function saveProject(dirPath, options = {}) {
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
const timestamp = new Date().toISOString()
|
|
96
|
-
const savedFiles = []
|
|
97
95
|
let totalSize = 0
|
|
98
96
|
|
|
99
97
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
// Read and encrypt all files into a single bundle
|
|
99
|
+
const bundleFiles = []
|
|
100
|
+
for (const file of allFiles) {
|
|
101
|
+
const content = readFileSync(file.absolutePath, 'utf-8')
|
|
102
|
+
const fileSize = Buffer.byteLength(content, 'utf-8')
|
|
103
|
+
const contentHash = `sha256:${sha256(content)}`
|
|
104
|
+
const encrypted = encrypt(content, wif)
|
|
105
|
+
const filename = file.relativePath.split(/[/\\]/).pop()
|
|
106
|
+
|
|
107
|
+
bundleFiles.push({
|
|
108
|
+
path_enc: encrypt(file.relativePath, wif),
|
|
109
|
+
filename_enc: encrypt(filename, wif),
|
|
110
|
+
size: fileSize,
|
|
111
|
+
content_hash: contentHash,
|
|
112
|
+
encrypted
|
|
104
113
|
})
|
|
105
|
-
|
|
106
|
-
if (!result.success) {
|
|
107
|
-
return {
|
|
108
|
-
success: false,
|
|
109
|
-
error: `Failed to save file ${file.relativePath}: ${result.error}`,
|
|
110
|
-
savedSoFar: savedFiles.length,
|
|
111
|
-
savedFiles
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
savedFiles.push({
|
|
116
|
-
path: file.relativePath,
|
|
117
|
-
txid: result.txId,
|
|
118
|
-
size: result.size
|
|
119
|
-
})
|
|
120
|
-
totalSize += result.size
|
|
121
|
-
|
|
122
|
-
if (i < allFiles.length - 1) {
|
|
123
|
-
await new Promise(r => setTimeout(r, 200))
|
|
124
|
-
}
|
|
114
|
+
totalSize += fileSize
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
txid: f.txid,
|
|
131
|
-
size: f.size
|
|
132
|
-
}))
|
|
133
|
-
|
|
134
|
-
const manifest = {
|
|
135
|
-
protocol: 'indelible.project',
|
|
136
|
-
version: 2,
|
|
117
|
+
const payload = {
|
|
118
|
+
protocol: 'indelible.project-bundle',
|
|
119
|
+
version: 1,
|
|
137
120
|
name_enc: encrypt(projectName, wif),
|
|
138
|
-
file_count:
|
|
121
|
+
file_count: allFiles.length,
|
|
139
122
|
total_size: totalSize,
|
|
140
|
-
files:
|
|
123
|
+
files: bundleFiles,
|
|
141
124
|
owner: config.address,
|
|
142
125
|
timestamp
|
|
143
126
|
}
|
|
144
127
|
|
|
145
|
-
|
|
128
|
+
const utxos = await spv.getUtxos(config.address)
|
|
146
129
|
if (!utxos || utxos.length === 0) {
|
|
147
|
-
return {
|
|
148
|
-
success: false,
|
|
149
|
-
error: 'No UTXOs for manifest tx. Files were saved but manifest was not.',
|
|
150
|
-
savedFiles
|
|
151
|
-
}
|
|
130
|
+
return { success: false, error: 'No UTXOs available. Fund your wallet first.' }
|
|
152
131
|
}
|
|
153
132
|
|
|
154
|
-
const { txHex, txId } = await spv.
|
|
155
|
-
wif, utxos, JSON.stringify(
|
|
133
|
+
const { txHex, txId } = await spv.buildOpReturnTxWithChange(
|
|
134
|
+
wif, utxos, JSON.stringify(payload), 'INDELIBLE_PROJECT_BUNDLE'
|
|
156
135
|
)
|
|
157
|
-
|
|
158
|
-
const manifestTxId = result.txid || txId
|
|
136
|
+
await spv.broadcastTx(txHex)
|
|
159
137
|
|
|
160
138
|
// Track in config
|
|
161
139
|
const projects = config.project_txids || []
|
|
162
140
|
projects.push({
|
|
163
|
-
txId
|
|
141
|
+
txId,
|
|
164
142
|
name: projectName,
|
|
165
|
-
fileCount:
|
|
143
|
+
fileCount: allFiles.length,
|
|
166
144
|
totalSize,
|
|
167
145
|
timestamp
|
|
168
146
|
})
|
|
169
147
|
saveConfig({ ...config, project_txids: projects })
|
|
170
148
|
|
|
149
|
+
// Index in server Redis so Vault UI can see it
|
|
150
|
+
try {
|
|
151
|
+
const apiUrl = config.api_url || 'https://indelible.one'
|
|
152
|
+
await fetch(`${apiUrl}/api/files/project/index`, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
address: config.address,
|
|
157
|
+
txId,
|
|
158
|
+
name: projectName,
|
|
159
|
+
fileCount: allFiles.length,
|
|
160
|
+
totalSize,
|
|
161
|
+
timestamp,
|
|
162
|
+
bundle: true
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
} catch { /* don't block save on index failure */ }
|
|
166
|
+
|
|
171
167
|
return {
|
|
172
168
|
success: true,
|
|
173
|
-
txId
|
|
169
|
+
txId,
|
|
174
170
|
name: projectName,
|
|
175
|
-
fileCount:
|
|
171
|
+
fileCount: allFiles.length,
|
|
176
172
|
totalSize,
|
|
177
|
-
files:
|
|
178
|
-
message: `Project "${projectName}" saved to blockchain. ${savedFiles.length} files, ${totalSize} bytes total. Manifest txId: ${manifestTxId}`
|
|
173
|
+
message: `Project "${projectName}" saved as single tx bundle. ${allFiles.length} files, ${totalSize} bytes. txId: ${txId}`
|
|
179
174
|
}
|
|
180
175
|
} catch (error) {
|
|
181
176
|
return {
|
|
182
177
|
success: false,
|
|
183
|
-
error: `Failed to save project: ${error.message}
|
|
184
|
-
savedSoFar: savedFiles.length,
|
|
185
|
-
savedFiles
|
|
178
|
+
error: `Failed to save project: ${error.message}`
|
|
186
179
|
}
|
|
187
180
|
}
|
|
188
181
|
}
|