indelible-mcp 4.9.9 → 5.0.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.
@@ -0,0 +1,251 @@
1
+ # Indelible CLI Handbook
2
+
3
+ Standalone command-line interface for Indelible blockchain memory. Same wallet, same blockchain, same functions as the MCP server — but runnable directly.
4
+
5
+ ---
6
+
7
+ ## Quick Reference
8
+
9
+ ### Save Session
10
+ ```bash
11
+ node C:/Indelible-main/indelible-cli/src/index.js save --summary "what happened"
12
+ ```
13
+
14
+ ### Load from Blockchain
15
+ ```bash
16
+ node C:/Indelible-main/indelible-cli/src/index.js load --sessions=5
17
+ ```
18
+
19
+ ### Check Status
20
+ ```bash
21
+ node C:/Indelible-main/indelible-cli/src/index.js status
22
+ ```
23
+
24
+ ### Save a File
25
+ ```bash
26
+ node C:/Indelible-main/indelible-cli/src/index.js vault save-file /path/to/file.js
27
+ ```
28
+
29
+ ### Ask Codex
30
+ ```bash
31
+ node C:/Indelible-main/indelible-cli/src/index.js diary chat "How should we architect this?"
32
+ ```
33
+
34
+ ---
35
+
36
+ ## All Commands
37
+
38
+ ### Sessions
39
+ ```
40
+ save Save current session to blockchain
41
+ save --summary "note" Save with custom summary
42
+ load Load context from blockchain (default: 5 sessions)
43
+ load --sessions=10 Load N sessions
44
+ status Show wallet address, API key, last session
45
+ ```
46
+
47
+ ### Code Vault
48
+ ```
49
+ vault save-file <path> Save a file (encrypted, chunked if >50KB)
50
+ vault save-project <dir> [--name=NAME] Save a project directory
51
+ vault load-file <txid> [--output=path] Load a file from blockchain
52
+ vault load-project <txid> [--output-dir=dir] Load a project from blockchain
53
+ vault save-style <file> [--name=N] [--desc=D] Save an AI style to blockchain
54
+ vault load-style [txid] Load an AI style from blockchain
55
+ vault update-index Update on-chain vault index
56
+ ```
57
+
58
+ ### Diary AI (Dual-Agent)
59
+ ```
60
+ diary connect --key=SK [--model=MODEL] [--name=NAME] Connect OpenAI companion
61
+ diary chat "message" Ask the AI companion
62
+ diary save [--summary="..."] Save exchange to blockchain
63
+ ```
64
+
65
+ ### Setup & Hooks
66
+ ```
67
+ setup --wif=KEY --pin=PIN Import and encrypt your private key
68
+ install-hooks Install auto-save/restore hooks into Claude Code
69
+ hook pre-compact Auto-save before compaction (called by hook)
70
+ hook post-compact Auto-restore after compaction (called by hook)
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Relationship to MCP
76
+
77
+ The CLI and MCP server are the **same codebase**. They import the same tool functions (`saveSession`, `loadContext`, `saveFile`, `diaryChat`, etc.) and use the same wallet at `~/.indelible/config.json`.
78
+
79
+ | | CLI | MCP Server |
80
+ |---|---|---|
81
+ | **Location** | `C:/Indelible-main/indelible-cli/src/` | `C:/bsv-claude-wrapper/mcp-server/` |
82
+ | **Config** | Sync (`readFileSync`/`writeFileSync`) | Async (`await loadConfig()`) |
83
+ | **Fetch** | Native `fetch` (Node 18+) | `node-fetch` package |
84
+ | **Timeout** | `AbortSignal.timeout(ms)` | Manual `AbortController` + `setTimeout` |
85
+ | **Entry point** | CLI arg parser in `index.js` | JSON-RPC stdin/stdout in `index.js` |
86
+ | **How Claude calls it** | `node src/index.js <command>` | MCP protocol via Claude Code hooks |
87
+
88
+ **Sync rule:** Any feature, fix, or change that goes into one MUST go into both. No exceptions. Adapt sync/async config pattern accordingly.
89
+
90
+ ---
91
+
92
+ ## Testing / Dog-Fooding
93
+
94
+ The CLI is a live test harness for the MCP. When you test a CLI command, you're testing the exact same function the MCP calls.
95
+
96
+ ### Safe tests (no sats spent)
97
+ ```bash
98
+ # Config loads correctly
99
+ node C:/Indelible-main/indelible-cli/src/index.js status
100
+
101
+ # Blockchain read works
102
+ node C:/Indelible-main/indelible-cli/src/index.js load --sessions=1
103
+
104
+ # Style loads from chain
105
+ node C:/Indelible-main/indelible-cli/src/index.js vault load-style
106
+ ```
107
+
108
+ ### Function-level tests (no sats spent)
109
+ ```bash
110
+ cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
111
+ import { checkConfirmation } from './src/lib/spv.js';
112
+ const r = await checkConfirmation('TXID_HERE');
113
+ console.log(JSON.stringify(r, null, 2));
114
+ " 2>&1
115
+
116
+ cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
117
+ import { checkTier } from './src/lib/api-client.js';
118
+ import { loadConfig } from './src/lib/config.js';
119
+ const config = loadConfig();
120
+ const r = await checkTier(config.api_key);
121
+ console.log(JSON.stringify(r, null, 2));
122
+ " 2>&1
123
+
124
+ cd "C:/Indelible-main/indelible-cli" && node --input-type=module -e "
125
+ import { verifyRecentSaves } from './src/tools/save_file.js';
126
+ const r = await verifyRecentSaves();
127
+ console.log(JSON.stringify(r, null, 2));
128
+ " 2>&1
129
+ ```
130
+
131
+ ### Live tests (spends sats)
132
+ ```bash
133
+ # Session save (delta if prior save exists)
134
+ node C:/Indelible-main/indelible-cli/src/index.js save --summary "test save"
135
+
136
+ # File save
137
+ node C:/Indelible-main/indelible-cli/src/index.js vault save-file /path/to/small/file.txt
138
+
139
+ # Style save (auto-prepends core rules via ensureCoreRules)
140
+ node C:/Indelible-main/indelible-cli/src/index.js vault save-style /path/to/rules.txt --name=test
141
+
142
+ # Diary chat (costs OpenAI tokens, not sats)
143
+ node C:/Indelible-main/indelible-cli/src/index.js diary chat "hello"
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Architecture
149
+
150
+ ```
151
+ indelible-cli/src/
152
+ ├── index.js CLI arg parser + MCP JSON-RPC server (dual mode)
153
+ ├── lib/
154
+ │ ├── config.js Sync config: loadConfig(), saveConfig(), getWif()
155
+ │ ├── crypto.js AES-256-GCM encrypt/decrypt, SHA-256, WIF derivation
156
+ │ ├── spv.js Multi-bridge SPV client, UTXO chaining, health tracking
157
+ │ └── api-client.js commitSession(), getLatestSessions(), checkTier()
158
+ ├── tools/
159
+ │ ├── save_session.js Parse transcript, delta detect, encrypt, broadcast
160
+ │ │ + getRecentPlans() — scans ~/.claude/plans/
161
+ │ │ + updateMemory() — MEMORY.md hierarchy enforcement
162
+ │ │ + dashboard sync — PATCH to indelible.one
163
+ │ ├── load_context.js Fetch + decrypt + merge deltas + smart format
164
+ │ ├── setup_wallet.js Generate BSV keypair, encrypt WIF with PIN
165
+ │ ├── save_file.js Encrypt + broadcast file (chunked if >50KB)
166
+ │ │ + cacheTx() — local backup for re-broadcast
167
+ │ │ + verifyRecentSaves() — check confirmations
168
+ │ ├── save_project.js Walk dir + save each file + broadcast manifest
169
+ │ │ + cacheTx() — local backup for re-broadcast
170
+ │ ├── load_file.js Fetch + decrypt file from blockchain
171
+ │ ├── load_project.js Fetch manifest + decrypt + restore files
172
+ │ ├── save_style.js Encrypt style rules + broadcast
173
+ │ │ + ensureCoreRules() — auto-prepend core rules
174
+ │ ├── load_style.js Fetch + decrypt style from blockchain
175
+ │ ├── update_vault_index.js Update on-chain file/project index
176
+ │ ├── diary_connect.js Store OpenAI API key in config
177
+ │ ├── diary_chat.js Send message to Codex via OpenAI API
178
+ │ │ + blockchain history loading (last 3 sessions)
179
+ │ └── diary_save.js Save diary exchange to blockchain
180
+ └── dist/
181
+ └── indelible.exe Compiled standalone (bun build --compile)
182
+ ```
183
+
184
+ ### Key Internal Features
185
+
186
+ | Feature | What It Does |
187
+ |---|---|
188
+ | **Bridge health tracking** | Tracks success/failure per SPV bridge, skips unhealthy ones |
189
+ | **UTXO chaining** | Back-to-back saves chain change outputs — no mempool conflicts |
190
+ | **Delta saves** | Only commits new messages since last save |
191
+ | **tx-cache** | Caches broadcast payloads to `~/.indelible/tx-cache/` for re-broadcast |
192
+ | **MEMORY.md enforcement** | Strips rules, archives done items, enforces 200-line / 20-line limits |
193
+ | **Core rules injection** | `ensureCoreRules()` auto-prepends infrastructure rules to every style |
194
+ | **Diary history** | `diary chat` loads last 3 blockchain sessions for conversation continuity |
195
+ | **Dashboard sync** | PATCHes indelible.one after every save |
196
+
197
+ ---
198
+
199
+ ## Rebuild the Executable
200
+
201
+ ```bash
202
+ cd "C:/Indelible-main/indelible-cli" && bun build --compile src/index.js --outfile dist/indelible.exe
203
+ ```
204
+
205
+ This creates a standalone `indelible.exe` — no Node.js required on the target machine.
206
+
207
+ ---
208
+
209
+ ## Common Errors
210
+
211
+ | Error | Cause | Fix |
212
+ |-------|-------|-----|
213
+ | `Wallet not configured` | No config.json or no WIF | Run `setup --wif=KEY --pin=PIN` |
214
+ | `No UTXOs available` | Wallet is empty | Send BSV to your address |
215
+ | `Broadcast failed on all bridges` | All 4 SPV relays down or rejecting | Check relay health, wait and retry |
216
+ | `No new messages since last save` | Already saved this transcript | Nothing to do — this is fine |
217
+ | `File not found` | Bad path or backslashes | Use forward slashes: `C:/path/to/file` |
218
+ | `Decrypt failed` | Wrong WIF or tampered data | Check `~/.indelible/config.json` has correct WIF |
219
+ | `txn-mempool-conflict` | Spending already-spent UTXO | UTXO chaining should prevent this — check code |
220
+ | `OpenAI rate limit exceeded` | Too many diary chat calls | Wait a moment and retry |
221
+ | `Diary AI not configured` | No OpenAI key | Run `diary connect --key=SK` |
222
+
223
+ ---
224
+
225
+ ## Config File
226
+
227
+ `C:/Users/oorel/.indelible/config.json` — shared with MCP server.
228
+
229
+ | Setting | Description |
230
+ |---------|-------------|
231
+ | `wif` or `wif_encrypted` | BSV private key (plaintext or PIN-encrypted) |
232
+ | `address` | BSV address (derived from WIF) |
233
+ | `spv_bridges` | Array of `{url, name}` relay objects |
234
+ | `api_url` | Indelible web app URL (`https://indelible.one`) |
235
+ | `api_key` | API key for authenticated endpoints |
236
+ | `auto_delta` | Auto-save every N messages (bool) |
237
+ | `auto_delta_interval` | Messages between auto-saves (default: 10) |
238
+ | `file_txids` | *(auto)* Index of files saved to chain |
239
+ | `project_txids` | *(auto)* Index of projects saved to chain |
240
+ | `last_session_id` | *(auto)* Previous session for chaining |
241
+ | `last_tx_id` | *(auto)* Last committed tx |
242
+ | `diary` | `{ apiKey, model, name }` — OpenAI companion config |
243
+
244
+ ---
245
+
246
+ ## Wallet
247
+
248
+ - **Address:** shown by `indelible-mcp status` (yours is generated at setup and lives in `~/.indelible/config.json` — back that file up)
249
+ - **Balance:** `indelible-mcp status`, or look your address up in the Chain Browser at indelible.one/explorer
250
+ - **Fund it:** Send BSV to your own address
251
+ - **Cost:** ~$0.21/MB at BSV=$16. Session saves are fractions of a cent.
@@ -0,0 +1,320 @@
1
+ # Your Agents: The Indelible Handbook
2
+
3
+ Indelible gives your AI a permanent memory on Bitcoin. It also gives you agents. This is the honest map of what runs on your machine from day one: what fires on its own, what each message means, and what is coming later.
4
+
5
+ Everything in this handbook describes the software you installed. Nothing here is aspirational. Where a capability belongs to the future, we say so plainly.
6
+
7
+ ---
8
+
9
+ ## 1. What you actually have on day one
10
+
11
+ Your day-one roster is small and real: two guards, one companion, and one memory.
12
+
13
+ | Agent | When it fires | What it needs | Free or Pro |
14
+ |---|---|---|---|
15
+ | **Witness** (the guard) | Inside every session save and file save, before anything is broadcast to the chain | Nothing. On by default. | Everyone |
16
+ | **Guardrails** (the immune system) | Before any shell command your AI runs | Nothing. Installed with the hooks. | Everyone |
17
+ | **Diary companion** ("Codex" by default) | When you talk to it (`diary_chat`, `diary_save`) | Nothing to start. Runs free on Groq by default. Want a premium model? Bring an xAI Grok or OpenAI key with `diary_connect`. Persisting rounds on chain uses tiny wallet fees like any save. | Free |
18
+ | **The memory engine** | Auto-save before every compaction, restore at session start, recall on demand | A funded wallet. Session, file, and project saves need Pro. All reads are free. | Mixed |
19
+
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
+
22
+ ### The 25 tools you can call today
23
+
24
+ Grouped by what they cost:
25
+
26
+ - **Setup:** `setup_wallet`
27
+ - **Saving to chain (Pro):** `save_session`, `delta_save`, `save_all`, `save_file`, `save_project`, `save_style`, `save_goals_to_chain`, `update_vault_index`
28
+ - **Reading your chain (free):** `load_context`, `load_file`, `load_project`, `load_style`, `recall_context`
29
+ - **Finding your way around it (free, local):** `map_themes` — the table of contents of everything you have saved. It reads the local semantic index and answers with your recurring themes, how recently each was touched, and the sessions inside them, so you can hand those to `recall_context` and go straight to the right one. Nothing leaves your machine.
30
+ - **Diary and Duo (free):** `diary_connect`, `diary_chat`, `diary_save`, `diary_recall`
31
+ - **Goals (free, local-first):** `get_goals`, `manage_goal`
32
+ - **Inner state (free, local):** `get_inner_state`, `update_inner_state`
33
+ - **Everything else:** `x402_fetch` (pays real sats to paid endpoints, capped at 10,000 sats per request unless you raise it), `report_bug`
34
+
35
+ One important nuance on "free": free means no subscription. On-chain writes still cost miner fees, paid in sats from your own wallet. Each save costs less than a cent. Diary and Duo saves are not Pro-gated; they only need a few sats in the wallet.
36
+
37
+ ---
38
+
39
+ ## 2. What fires automatically (the four hooks)
40
+
41
+ When you ran setup, the installer wired four hooks into Claude Code (`~/.claude/settings.local.json`). This is what each one does and why it is there.
42
+
43
+ | Hook | Fires | Does |
44
+ |---|---|---|
45
+ | **PreCompact** | Right before Claude Code compacts (summarizes away) your conversation | Saves the new messages of your real transcript to the chain, so nothing is lost to compaction. On success you see a line like `Indelible: Saved 42 messages (delta) tx:a1b2c3d4e5f6...` |
46
+ | **SessionStart (after compaction)** | Right after a compaction | Restamps the time card (compaction resets the model's sense of time), reloads your saved AI style, and restores your recent sessions from chain so Claude picks up where it left off |
47
+ | **SessionStart** | At the start of every new session | Prints the time card and loads your on-chain AI style rules, if you saved one |
48
+ | **PreToolUse (Bash)** | Before any shell command your AI runs | Runs the Guardrails check: blocks a command that would leak your wallet key, warns on irreversible commands |
49
+
50
+ Free tier and the hooks: all four hooks run for everyone. Restore, the time card, style loading, and Guardrails are fully free. The pre-compaction **save** is the one piece that needs Pro. On a free wallet the hook still fires, but instead of saving it prints the plain notice that saving is a Pro feature. That is not an error. Your reads, diary, and recall keep working.
51
+
52
+ Hook installation is idempotent. If you ever suspect the hooks are missing, run:
53
+
54
+ ```
55
+ indelible-mcp install-hooks
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 3. How to read a Witness verdict
61
+
62
+ The Witness reviews every session save and file save before it is broadcast. The chain is permanent, so this is the one gate that stands between a pasted secret and an immutable public record.
63
+
64
+ ### The anatomy of a block
65
+
66
+ When the Witness rejects a save, you see a line like this:
67
+
68
+ ```
69
+ [Witness] BLOCKED session 1a2b3c4d — credential_in_content: bsv_wif detected in message 12 (user)
70
+ ```
71
+
72
+ And the tool returns an error shaped like:
73
+
74
+ ```
75
+ Witness rejected save (1 critical issues): credential_in_content: ...
76
+ ```
77
+
78
+ The part after the code is the reason in plain words. The code itself is the part to act on.
79
+
80
+ ### The reason codes you will actually see
81
+
82
+ | Code | Meaning | Blocks? |
83
+ |---|---|---|
84
+ | `credential_in_content` | A private key, API key, or other credential-shaped string is in the content | **Always blocks.** No setting changes this. |
85
+ | `secret_filename` | The file looks like a secrets file: `.env`, `credentials.json`, `*.pem`, `*.key`, `id_rsa`, a `wif` file, and similar | **Always blocks.** No setting changes this. |
86
+ | `empty_file` | The file is 0 bytes | Observed by default |
87
+ | `oversize` | The payload is over the hard size ceiling | Observed by default |
88
+ | `no_messages` | The session has nothing to commit | Observed by default |
89
+ | `empty_summary` | The save has no summary | Observed by default |
90
+ | `bad_hash` | The file's content hash is missing or invalid | Observed by default |
91
+ | `junk_filename` | Looks like junk (`.DS_Store`, `*.tmp`, swap files) | Warning only |
92
+ | `large_file` | Over 10 MB: many chunks, real cost | Warning only |
93
+
94
+ "Observed by default" means the verdict is logged to `~/.indelible/witness/` but the save proceeds, unless you turn enforcement on (below).
95
+
96
+ The two hard-block codes are different. `credential_in_content` and `secret_filename` block **regardless of any setting**, because permanent is forever. A leaked key on an immutable public chain cannot be taken back. The Witness would rather fail your save than etch your key.
97
+
98
+ ### What redaction already did for you
99
+
100
+ For session saves, you get a layer of protection before the gate even runs. If you pasted a key into the conversation, the save engine scrubs credential-shaped strings from the content first, and tells you:
101
+
102
+ ```
103
+ [indelible] redact-on-save: scrubbed 2 credential-shaped string(s) from session content before commit
104
+ ```
105
+
106
+ So most pasted keys save clean, redacted, without you doing anything. The Witness block is the backstop for anything the scrubber misses.
107
+
108
+ Files are different. Your file bytes are never altered, because the save proves integrity by hash. A credential inside a file cannot be redacted, so it blocks the save outright. Remove the secret from the file, then save again.
109
+
110
+ ### The recovery path
111
+
112
+ 1. Find the flagged content (the verdict names the message or the file).
113
+ 2. Remove the secret. If it was a real, live key, **rotate it**. It passed through the conversation, so treat it as exposed.
114
+ 3. Save again. The block does not persist; it re-checks fresh every time.
115
+ 4. Never paste the key back to "test" it.
116
+
117
+ ### Turning enforcement up
118
+
119
+ In `~/.indelible/config.json`:
120
+
121
+ ```json
122
+ { "witness": { "enforce": true } }
123
+ ```
124
+
125
+ - `false` (default): observe only, except the two always-block codes
126
+ - `true`: block on any critical reason
127
+ - `["oversize", "empty_summary"]`: block only the listed codes
128
+
129
+ There is an escape hatch for false positives: running a save with the environment variable `WITNESS_BYPASS=1` skips the gate. Use it only when you are certain the flagged content is not a real secret. Never use it on an actual credential. The chain does not forget.
130
+
131
+ ### Reading a Guardrails verdict
132
+
133
+ Guardrails checks shell commands, not saves. Two rules ship:
134
+
135
+ **credential-pull (blocks).** A command that would print your wallet key or seed into the conversation, such as `cat ~/.indelible/config.json` or reading a `.env` file, is stopped before it runs:
136
+
137
+ ```
138
+ [indelible:credential-pull] BLOCKED: That command would print your wallet key or seed into the conversation...
139
+ ```
140
+
141
+ Why: anything printed into the chat goes to the model and into the saved transcript. If your AI needs a non-secret value from a config file, ask it to read just that field.
142
+
143
+ **destructive-op (warns, never blocks).** A force-push, `rm -rf`, `git reset --hard`, or recursive force-delete gets a visible advisory before it runs:
144
+
145
+ ```
146
+ [indelible:destructive-op] advisory: This command is irreversible... (not blocked)
147
+ ```
148
+
149
+ Your repo, your machine, your call. The warning just makes the blast radius visible first.
150
+
151
+ Guardrails fails open: if the guard itself ever errors, your command runs normally. A guard bug will never wedge your tools.
152
+
153
+ ### The diary has its own gate
154
+
155
+ Diary and Duo saves are checked for credentials too, with a refusal instead of a block:
156
+
157
+ ```
158
+ Refusing to save — credential detected in diary content (bsv_wif). Remove or rotate the secret, then save again. It is NOT on chain.
159
+ ```
160
+
161
+ Same recovery path: remove it, rotate it, save again.
162
+
163
+ ---
164
+
165
+ ## 4. Your memory, whole
166
+
167
+ Everything in this section is free.
168
+
169
+ **`load_context`** is the recent view. It restores your last few sessions from chain, and it is what the post-compaction hook calls for you automatically.
170
+
171
+ **`recall_context`** is the deep view: your entire on-chain history, from your first save to now. Search it by date range, by keyword, or by meaning:
172
+
173
+ - `depth: "list"` returns dated summaries you can scan
174
+ - `depth: "full"` drills into specific sessions
175
+ - `from_date` / `to_date` take dates or prefixes like `"2026-03"`
176
+ - `ranking` controls how results are ordered:
177
+ - `"blend"` (default): keyword and meaning-based ranking fused
178
+ - `"keyword"`: exact terms only
179
+ - `"semantic"`: meaning only
180
+
181
+ Recall builds a local index in `~/.indelible/recall-index/` the first time it runs, decrypting your history once so later searches are fast. The index never leaves your machine.
182
+
183
+ **Meaning-based search needs a one-time download.** The semantic model is about 50 MB, fetched once, hash-verified, and stored locally in `~/.indelible/models/`. After that, nothing ever leaves your machine: no query, no session content, no request of any kind. To enable it:
184
+
185
+ ```
186
+ indelible-mcp semantic-fetch
187
+ ```
188
+
189
+ This fetches the pack and backfills meaning-vectors for your existing history. It is resumable; re-run it if it stops partway. Until you do this, `recall_context` works fine with keyword ranking and tells you the semantic option is available.
190
+
191
+ **`diary_recall`** is the same power scoped to your companion's own history. `scope: "diary"` for saved diary entries, `"duo"` for live Claude-plus-companion rounds, `"all"` for both.
192
+
193
+ ---
194
+
195
+ ## 5. Your first adversarial habit
196
+
197
+ You may have read about the adversarial pack: agents that attack a piece of work from different angles before it ships. Honest answer up front: **the pack does not ship in your bundle.** It is the operator's court. You can watch it work in the Sanctuary tab, but you cannot summon it.
198
+
199
+ Here is what you can do today, and it is genuinely useful:
200
+
201
+ **1. Ask for the self-review.** Before any big change, tell your Claude: "Before you finalize this, try to refute your own conclusion. What is the failure mode you would be embarrassed to have missed?" One sentence, real results.
202
+
203
+ **2. Structure it with goals.** `get_goals` and `manage_goal` ship complete: propose, accept, complete, checklists with progress, and claims so parallel sessions do not collide. A goal with explicit success criteria gives the self-review something concrete to check against.
204
+
205
+ **3. Use Duo as your second opinion.** This is the real one. Duo mode puts a second AI in the room: your companion working live alongside Claude. It runs free on Groq by default, or you can upgrade it to xAI's Grok or an OpenAI model (see below). Ask your companion to critique what Claude proposed, and let Claude defend it. Two different models, two different failure modes, one honest argument. Every round auto-saves to your chain, so the argument itself becomes part of your permanent record.
206
+
207
+ That is the day-one shape of adversarial work in this package: one reviewer you summon by asking, not a court. The full court, where your own review pack argues a matter out and each seat signs its position, lives with your crew in the web app (section 7).
208
+
209
+ ### Upgrading your companion to Grok (or OpenAI)
210
+
211
+ Your companion is free on Groq out of the box, no key required. To run a premium model instead, bring your own key. xAI's Grok is a favorite here, and it is worth knowing why: Grok has no lasting memory of its own, so an on-chain diary is the one place it actually remembers you between sessions.
212
+
213
+ **Getting an xAI Grok key:**
214
+
215
+ 1. Go to **console.x.ai** and sign in with your X account.
216
+ 2. Add a few dollars of credits under Settings, then Billing. Grok is pay-as-you-go, and a test costs pennies.
217
+ 3. Open **API Keys**, click **Create key**, and copy it. It starts with `xai-` and is shown only once.
218
+
219
+ **Connecting it:**
220
+
221
+ - In Claude Code: run `diary_connect` with the key. Provider is detected automatically (`xai-` for Grok, `sk-` for OpenAI).
222
+ - On indelible.one: open the **Diary**, click the **Companion** button in its header, pick **xAI (Grok)**, and paste the key. (The Diary is the one home for your companion — model, keys, and Duo Mode all live there.)
223
+
224
+ A note on safety: never paste an API key into a chat with your AI. Put it straight into `diary_connect` or the Settings field. Every save is credential-scanned before it touches the chain, so a leaked key gets caught, but the habit is to keep keys out of the conversation entirely.
225
+
226
+ Once connected, every diary and Duo round runs on Grok and persists to Bitcoin. It remembers after xAI forgets. A Groq (`gsk_`) key is refused on purpose here, because Groq is already your free default; there is nothing to add.
227
+
228
+ ---
229
+
230
+ ## 6. Goals: the board you share with your AI
231
+
232
+ The goal system is the part of Indelible that turns "my AI helps me" into "my AI works toward something with me." Two rules give it its shape: **your AI proposes, you accept** — nothing becomes active without your say — and **completion requires evidence**: a txid, a commit, a test result. Done means receipts.
233
+
234
+ **In Claude Code:** `get_goals` shows the board (your Claude reads it at session start and steers toward active goals). `manage_goal` runs the lifecycle: propose, accept (with a 0–100 priority), block with a reason, complete with evidence. Goals live in `~/.indelible/goals.json` on your machine — that file is the source of truth.
235
+
236
+ **On indelible.one/goals:** the same board, live. From v4.9.5, it syncs automatically whenever you touch goals in Claude Code — no setup. You can also work the board directly on the web: accept, complete, block, drag to reprioritize, propose with the **New goal** button. One honest boundary: your machine wins. Web changes hold until your next goals action in Claude Code pushes the local board up.
237
+
238
+ **Sealing to Bitcoin:** `save_goals_to_chain` encrypts your entire board with your key and broadcasts it as one transaction — the permanent record of what you set out to do and what you finished. It costs the miner fee (a fraction of a cent for most boards), skips the broadcast for free if nothing changed, and the receipt shows up in the **On chain** card on the Goals tab. Sealing is part of the Pro toolkit; the board itself is free. Re-seal after big changes — over time the chain holds the honest history of your ambition.
239
+
240
+ The full walkthrough lives at **indelible.one/docs/goals**.
241
+
242
+ ---
243
+
244
+ ## 7. The Sanctuary tab: your city
245
+
246
+ Sign in at indelible.one and open the Sanctuary tab. You are looking at your own city.
247
+
248
+ **Birth your crew.** Under Crew & Forge there is one button. Press it and sixteen agents are derived, in your browser, from the key you already hold: the review pack (Goblin, Gremlin, Troll, Ogre) and the Inquisitor who convenes them, the Witness and the Scribe, the Quartermaster, the Bookkeeper, the Calibrator, the Cartographer, and five envoys for the gate.
249
+
250
+ They are not assigned to you. Nothing is issued. Each one is computed from your wallet, and each has its own Bitcoin address.
251
+
252
+ **Four generations.** Your wallet derives a master key that exists only for you. The master derives five house keys, one per kind of work: verification, governance, orchestration, support, commerce. Each house derives its own agents. So Witness and Goblin are siblings in verification; Skeptic and Escrow are siblings in commerce. The whole family walks back to one key.
253
+
254
+ **Check it yourself.** Press "Verify crew" and the browser re-derives all sixteen from your wallet and compares them to what is stored. Close the browser, open it on another machine, unlock, and you get the same sixteen addresses. Nothing is fetched from us to make that happen.
255
+
256
+ Your crew is also **signed by your wallet**, and the signature is public. Anyone can fetch `/api/sanctuary/crew/<your-address>` with no account and check it offline: every agent is signed, and a separate crew signature commits to the exact set, so nobody can quietly serve someone a shortened version.
257
+
258
+ **What that does and does not prove.** It proves we did not invent your crew and cannot alter it. It does not prove the agents were derived from your wallet, and nothing ever can: derivation runs on a shared secret, so only someone holding the private key can reproduce it. That is a recovery property, not a proof to strangers. It also does not stop us withholding the record entirely, which is what the on-chain anchor in section 8 is for. Until that lands the app marks every agent unattested, and means it.
259
+
260
+ **How to read a bust:** the dot is liveness, the number is verdicts on record, the line under the name is the agent's latest real task, and the bar is how recently it fired. A newly born agent reads "at post · awaiting first task", because it has done nothing yet and we would rather say so than animate something.
261
+
262
+ ---
263
+
264
+ ## 8. What is still not done
265
+
266
+ Your crew exists and is yours. Two things about it are honestly unfinished, and we would rather you read them here than discover them.
267
+
268
+ **No on-chain anchor yet.** Your crew lives on our server, signed by you. That signature means we cannot forge or alter it, which is the serious half of the problem. The half still open is availability: if we went down, or decided not to serve your record, a stranger would have nowhere to look. Writing a commitment to the chain fixes that, because the record would then exist somewhere we do not control, with a timestamp we cannot move. It is the next piece of work. Until it ships, every agent reads `unattested` in the app, and that word is accurate.
269
+
270
+ **Earning needs your box running.** The five envoys are commerce agents with their own derived addresses, and payment for their work goes to those addresses, not to us. But an agent can only take a paid order if the machine that can actually run it has checked in recently. Open the Counter with your MCP box running and your agents are deliverable; with the box off, the Counter will not let you open an agent for orders, on purpose. An order nobody can fill is worse than no order.
271
+
272
+ Timing: we do not put dates on unfinished work, because a date we miss is worse than a roadmap we keep.
273
+
274
+ ---
275
+
276
+ ## 9. When something goes wrong
277
+
278
+ The short crosswalk from message to fix.
279
+
280
+ **`[Witness] BLOCKED ...`**
281
+ Not a malfunction. The guard found something that must not reach a permanent chain. Go to section 3: find it, remove it, rotate it if it was real, save again.
282
+
283
+ **`[indelible:credential-pull] BLOCKED ...`**
284
+ Guardrails stopped a command that would print your wallet key into the conversation. Read only the specific non-secret field you need.
285
+
286
+ **"Reads are free" / "saving to the blockchain is a Pro feature"**
287
+ You are on the free tier. Reads, diary, Duo, and recall all keep working. To keep sessions permanently, go Pro at indelible.one/pricing.
288
+
289
+ **"Cannot verify your plan right now"**
290
+ The billing check could not reach indelible.one. Saves fail closed on purpose when your plan cannot be verified. Check your connection and retry.
291
+
292
+ **"No funds" / "fund to persist"**
293
+ Your wallet has no sats. Chain writes carry a real (tiny) miner fee. Send a small amount of BSV to your address (`indelible-mcp status` shows it). Even one dollar covers hundreds of saves. Duo replies still work with an empty wallet; they just are not persisted until you fund.
294
+
295
+ **Restore comes back empty**
296
+ Work the checklist in order:
297
+ 1. Have you ever saved? Restore can only return what was written. Free wallets read, but only Pro writes sessions.
298
+ 2. Same wallet? Your history lives under one address. Compare `indelible-mcp status` on this machine against Settings on indelible.one.
299
+ 3. Just saved? Give the network a minute; propagation is not instant.
300
+
301
+ **"semantic ranking unavailable (model pack not downloaded)"**
302
+ Recall is working, keyword-ranked. To enable meaning-based search, run `indelible-mcp semantic-fetch` (about 50 MB, one time, local only).
303
+
304
+ **"Refusing to save" (credential detected in diary content)**
305
+ The diary's own gate. The secret is NOT on chain. Remove it, rotate it, save again.
306
+
307
+ **Indelible tools do not appear in Claude Code**
308
+ Re-register the MCP server, then restart Claude Code:
309
+ ```
310
+ claude mcp add --scope user indelible -- indelible-mcp
311
+ ```
312
+ If auto-save or restore is not firing, reinstall the hooks: `indelible-mcp install-hooks`.
313
+
314
+ **Back up your key.** Your wallet key lives in `~/.indelible/config.json` on this machine, unlocked by your PIN. Your history is encrypted on chain with that key. If this machine dies and you have no copy of the key, the history survives on chain forever, but nothing can decrypt it. Copy your key (indelible.one → Settings → Private Key) somewhere safe today: a password manager, a printed copy, a USB drive in a drawer.
315
+
316
+ **Still stuck?** Tell your AI to file a bug: the `report_bug` tool sends your description plus machine-gathered facts (version, address, recent save receipts) to our intake. It redacts keys and never sends your session content. You describe the problem; the system supplies the facts.
317
+
318
+ ---
319
+
320
+ *This handbook ships with your install and describes the code in it. When the product grows, this document grows with it. Receipts over reputation.*
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Blockchain-backed memory for Claude Code. Save your AI conversations permanently on BSV via a federated mesh of SPV bridges.
4
4
 
5
+ > **New here?** Two guides ship with this package: **CUSTOMER_AGENT_HANDBOOK.md** (your agents on day one — the Witness, the Scribe, what runs for you) and **CLI_HANDBOOK.md** (every command + common errors). They are in the package install folder, or on indelible.one/docs.
6
+
5
7
  ## Quick Start
6
8
 
7
9
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "4.9.9",
3
+ "version": "5.0.0",
4
4
  "description": "Blockchain-backed memory and code storage for Claude Code. Save AI conversations and source code permanently on BSV.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -8,7 +8,9 @@
8
8
  "indelible-mcp": "src/index.js"
9
9
  },
10
10
  "files": [
11
- "src/"
11
+ "src/",
12
+ "CUSTOMER_AGENT_HANDBOOK.md",
13
+ "CLI_HANDBOOK.md"
12
14
  ],
13
15
  "scripts": {
14
16
  "test": "node --test test/*.test.js",
package/src/index.js CHANGED
@@ -6989,6 +6989,79 @@ import { homedir as homedir12 } from "os";
6989
6989
  init_utxo_cache();
6990
6990
  init_api_client();
6991
6991
  init_save_log();
6992
+
6993
+ // mcp-server/lib/reconcile.js
6994
+ function payloadContentKey(payload) {
6995
+ if (!payload || typeof payload !== "object") return null;
6996
+ if (payload.protocol === "indelible.project-bundle" && Array.isArray(payload.files)) {
6997
+ const hashes = payload.files.map((f) => f && f.content_hash).filter(Boolean);
6998
+ return hashes.length ? `bundle:${hashes.join(",")}` : null;
6999
+ }
7000
+ if (payload.content_hash) return String(payload.content_hash).replace(/^sha256:/, "");
7001
+ return null;
7002
+ }
7003
+ async function findByContentKey({
7004
+ address,
7005
+ targetKey,
7006
+ getHistory,
7007
+ fetchPayload,
7008
+ excludeTxids = [],
7009
+ maxScan = 400,
7010
+ confirmedOnly = false,
7011
+ stopAtFirst = true,
7012
+ matchCap = 10
7013
+ }) {
7014
+ if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
7015
+ const exclude = new Set(excludeTxids.map((t) => String(t).toLowerCase()));
7016
+ let history = [];
7017
+ try {
7018
+ history = await getHistory(address);
7019
+ } catch {
7020
+ return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "inconclusive-history-unreachable", error: "history-unreachable" };
7021
+ }
7022
+ if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
7023
+ const ordered = [...history].sort((a, b) => (b.height || 0) - (a.height || 0));
7024
+ const truncated = ordered.length > maxScan;
7025
+ const candidates = ordered.slice(0, maxScan);
7026
+ const matches = [];
7027
+ let scanned = 0;
7028
+ let fetchFailures = 0;
7029
+ let confirmedFound = false;
7030
+ for (const h of candidates) {
7031
+ const txid = (h.tx_hash || h.txid || "").toString();
7032
+ if (!txid || exclude.has(txid.toLowerCase())) continue;
7033
+ if (confirmedOnly && !(h.height > 0)) continue;
7034
+ scanned++;
7035
+ let payload = null;
7036
+ try {
7037
+ payload = await fetchPayload(txid);
7038
+ } catch {
7039
+ fetchFailures++;
7040
+ continue;
7041
+ }
7042
+ if (payloadContentKey(payload) === targetKey) {
7043
+ const height = h.height || null;
7044
+ matches.push({ txid, height });
7045
+ if (height > 0) confirmedFound = true;
7046
+ if (stopAtFirst && confirmedFound) break;
7047
+ if (matches.length >= matchCap) break;
7048
+ }
7049
+ }
7050
+ matches.sort((a, b) => {
7051
+ const ac = a.height > 0 ? 1 : 0, bc = b.height > 0 ? 1 : 0;
7052
+ if (ac !== bc) return bc - ac;
7053
+ return (b.height || 0) - (a.height || 0);
7054
+ });
7055
+ const result = stopAtFirst && matches.length ? [matches[0]] : matches;
7056
+ let classification;
7057
+ if (result.length) classification = "found";
7058
+ else if (truncated) classification = "inconclusive-truncated";
7059
+ else if (fetchFailures > 0) classification = "inconclusive-fetch-degraded";
7060
+ else classification = "absent";
7061
+ return { matches: result, scanned, truncated, fetchFailures, classification };
7062
+ }
7063
+
7064
+ // mcp-server/tools/save_project.js
6992
7065
  var TX_CACHE_DIR2 = join12(homedir12(), ".indelible", "tx-cache");
6993
7066
  async function cacheTx2(txId, payload) {
6994
7067
  try {
@@ -7169,7 +7242,7 @@ async function saveProject(dirPath, options = {}) {
7169
7242
  } catch {
7170
7243
  }
7171
7244
  const receipt = buildReceipt(_wr, { txId, fee, txSize });
7172
- appendReceipt(receipt, { trigger: "interactive", saveType: "project" });
7245
+ appendReceipt(receipt, { trigger: "interactive", saveType: "project", contentHash: payloadContentKey(payload) });
7173
7246
  return {
7174
7247
  success: true,
7175
7248
  txId,
@@ -7200,79 +7273,6 @@ import { existsSync as existsSync11 } from "fs";
7200
7273
  import { dirname as dirname3, join as join13 } from "path";
7201
7274
  import { homedir as homedir13 } from "os";
7202
7275
  import { Transaction as Transaction6, PrivateKey as PrivateKey8, CompletedProtoWallet as CompletedProtoWallet3 } from "@bsv/sdk";
7203
-
7204
- // mcp-server/lib/reconcile.js
7205
- function payloadContentKey(payload) {
7206
- if (!payload || typeof payload !== "object") return null;
7207
- if (payload.protocol === "indelible.project-bundle" && Array.isArray(payload.files)) {
7208
- const hashes = payload.files.map((f) => f && f.content_hash).filter(Boolean);
7209
- return hashes.length ? `bundle:${hashes.join(",")}` : null;
7210
- }
7211
- if (payload.content_hash) return String(payload.content_hash).replace(/^sha256:/, "");
7212
- return null;
7213
- }
7214
- async function findByContentKey({
7215
- address,
7216
- targetKey,
7217
- getHistory,
7218
- fetchPayload,
7219
- excludeTxids = [],
7220
- maxScan = 400,
7221
- confirmedOnly = false,
7222
- stopAtFirst = true,
7223
- matchCap = 10
7224
- }) {
7225
- if (!address || !targetKey) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
7226
- const exclude = new Set(excludeTxids.map((t) => String(t).toLowerCase()));
7227
- let history = [];
7228
- try {
7229
- history = await getHistory(address);
7230
- } catch {
7231
- return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "inconclusive-history-unreachable", error: "history-unreachable" };
7232
- }
7233
- if (!Array.isArray(history)) return { matches: [], scanned: 0, truncated: false, fetchFailures: 0, classification: "absent" };
7234
- const ordered = [...history].sort((a, b) => (b.height || 0) - (a.height || 0));
7235
- const truncated = ordered.length > maxScan;
7236
- const candidates = ordered.slice(0, maxScan);
7237
- const matches = [];
7238
- let scanned = 0;
7239
- let fetchFailures = 0;
7240
- let confirmedFound = false;
7241
- for (const h of candidates) {
7242
- const txid = (h.tx_hash || h.txid || "").toString();
7243
- if (!txid || exclude.has(txid.toLowerCase())) continue;
7244
- if (confirmedOnly && !(h.height > 0)) continue;
7245
- scanned++;
7246
- let payload = null;
7247
- try {
7248
- payload = await fetchPayload(txid);
7249
- } catch {
7250
- fetchFailures++;
7251
- continue;
7252
- }
7253
- if (payloadContentKey(payload) === targetKey) {
7254
- const height = h.height || null;
7255
- matches.push({ txid, height });
7256
- if (height > 0) confirmedFound = true;
7257
- if (stopAtFirst && confirmedFound) break;
7258
- if (matches.length >= matchCap) break;
7259
- }
7260
- }
7261
- matches.sort((a, b) => {
7262
- const ac = a.height > 0 ? 1 : 0, bc = b.height > 0 ? 1 : 0;
7263
- if (ac !== bc) return bc - ac;
7264
- return (b.height || 0) - (a.height || 0);
7265
- });
7266
- const result = stopAtFirst && matches.length ? [matches[0]] : matches;
7267
- let classification;
7268
- if (result.length) classification = "found";
7269
- else if (truncated) classification = "inconclusive-truncated";
7270
- else if (fetchFailures > 0) classification = "inconclusive-fetch-degraded";
7271
- else classification = "absent";
7272
- return { matches: result, scanned, truncated, fetchFailures, classification };
7273
- }
7274
-
7275
- // mcp-server/tools/load_file.js
7276
7276
  var TX_CACHE_DIR3 = join13(homedir13(), ".indelible", "tx-cache");
7277
7277
  var SAVE_LOG_PATH = join13(homedir13(), ".indelible", "save-log.jsonl");
7278
7278
  async function contentKeyFromSaveLog(txid) {
@@ -9331,7 +9331,7 @@ Commands:
9331
9331
  }
9332
9332
  function printHelp() {
9333
9333
  console.log(`
9334
- Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.9)
9334
+ Indelible MCP \u2014 Blockchain memory for Claude Code (v5.0.0)
9335
9335
 
9336
9336
  Setup:
9337
9337
  indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
@@ -9638,7 +9638,7 @@ function readStdin() {
9638
9638
  }
9639
9639
  var SERVER_INFO = {
9640
9640
  name: "indelible",
9641
- version: "4.9.9",
9641
+ version: "5.0.0",
9642
9642
  description: "Blockchain-backed memory and code storage for Claude Code"
9643
9643
  };
9644
9644
  var TOOLS = [
@@ -10010,7 +10010,7 @@ async function handleMcpRequest(request) {
10010
10010
  }
10011
10011
  };
10012
10012
  case "tools/list":
10013
- return { jsonrpc: "2.0", id, result: { tools: TOOLS.filter((t) => !["share_session", "load_shared"].includes(t.name)) } };
10013
+ return { jsonrpc: "2.0", id, result: { tools: TOOLS.filter((t) => !["share_session", "load_shared", "birth_custom_agent", "run_custom_agent", "convene_chamber", "transmute_agents"].includes(t.name)) } };
10014
10014
  case "tools/call": {
10015
10015
  const { name, arguments: args2 } = params;
10016
10016
  let result;