indelible-mcp 4.9.4 → 4.9.5
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 +288 -42
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -74,6 +74,15 @@ var init_crypto = __esm({
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
// mcp-server/lib/config.customer.js
|
|
77
|
+
var config_customer_exports = {};
|
|
78
|
+
__export(config_customer_exports, {
|
|
79
|
+
getConfigPath: () => getConfigPath,
|
|
80
|
+
getWif: () => getWif,
|
|
81
|
+
hasWallet: () => hasWallet,
|
|
82
|
+
loadConfig: () => loadConfig,
|
|
83
|
+
saveConfig: () => saveConfig,
|
|
84
|
+
updateConfig: () => updateConfig
|
|
85
|
+
});
|
|
77
86
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
78
87
|
import { homedir } from "node:os";
|
|
79
88
|
import { join } from "node:path";
|
|
@@ -98,6 +107,10 @@ function updateConfig(mutator) {
|
|
|
98
107
|
saveConfig(next);
|
|
99
108
|
return next;
|
|
100
109
|
}
|
|
110
|
+
function hasWallet() {
|
|
111
|
+
const config = loadConfig();
|
|
112
|
+
return config?.wif_encrypted != null || config?.wif != null;
|
|
113
|
+
}
|
|
101
114
|
async function getWif() {
|
|
102
115
|
if (_cachedWif) return _cachedWif;
|
|
103
116
|
const config = loadConfig();
|
|
@@ -785,7 +798,7 @@ async function checkTier(apiKey) {
|
|
|
785
798
|
});
|
|
786
799
|
if (res.status === 429) {
|
|
787
800
|
const data = await res.json().catch(() => ({}));
|
|
788
|
-
throw new Error(`
|
|
801
|
+
throw new Error(`Save limit reached${data.limit ? ` (${data.limit})` : ""}. See indelible.one/pricing`);
|
|
789
802
|
}
|
|
790
803
|
if (res.ok) {
|
|
791
804
|
const data = await res.json();
|
|
@@ -2038,8 +2051,37 @@ function sanitize(s) {
|
|
|
2038
2051
|
const truncated = leak ? s.slice(0, leak.index) : s;
|
|
2039
2052
|
return truncated.replace(LEAKED_TAG_RE, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
2040
2053
|
}
|
|
2054
|
+
function pushGoalsToServer(data, { always = false, snapshot = null } = {}) {
|
|
2055
|
+
if (!always && !(data?.goals?.length || data?.completed?.length)) return;
|
|
2056
|
+
(async () => {
|
|
2057
|
+
try {
|
|
2058
|
+
const { loadConfig: loadConfig2, ensureApiKey } = await Promise.resolve().then(() => (init_config_customer(), config_customer_exports));
|
|
2059
|
+
const cfg = await loadConfig2();
|
|
2060
|
+
const apiKey = cfg?.api_key?.startsWith?.("indelible_sk_") ? cfg.api_key : await ensureApiKey();
|
|
2061
|
+
if (!apiKey) return;
|
|
2062
|
+
const base2 = (cfg.api_url || "https://indelible.one").replace(/\/$/, "");
|
|
2063
|
+
const snap = snapshot || (cfg.goals_snapshot_txid ? {
|
|
2064
|
+
txid: cfg.goals_snapshot_txid,
|
|
2065
|
+
at: cfg.goals_snapshot_at || null,
|
|
2066
|
+
count: cfg.goals_snapshot_count ?? null,
|
|
2067
|
+
completed_count: cfg.goals_snapshot_completed_count ?? null
|
|
2068
|
+
} : null);
|
|
2069
|
+
const ctl = new AbortController();
|
|
2070
|
+
const t = setTimeout(() => ctl.abort(), 2500);
|
|
2071
|
+
await fetch(`${base2}/api/consciousness/sync`, {
|
|
2072
|
+
method: "POST",
|
|
2073
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
|
|
2074
|
+
body: JSON.stringify({ type: "goals", data: { version: data.version || 1, goals: data.goals || [], completed: data.completed || [], snapshot: snap } }),
|
|
2075
|
+
signal: ctl.signal
|
|
2076
|
+
});
|
|
2077
|
+
clearTimeout(t);
|
|
2078
|
+
} catch {
|
|
2079
|
+
}
|
|
2080
|
+
})();
|
|
2081
|
+
}
|
|
2041
2082
|
async function getGoals() {
|
|
2042
2083
|
const data = loadGoals();
|
|
2084
|
+
pushGoalsToServer(data);
|
|
2043
2085
|
const active = data.goals.filter((g) => g.status === "active").sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
2044
2086
|
const proposed = data.goals.filter((g) => g.status === "proposed");
|
|
2045
2087
|
const blocked = data.goals.filter((g) => g.status === "blocked");
|
|
@@ -2091,7 +2133,12 @@ async function getGoals() {
|
|
|
2091
2133
|
async function manageGoal(args2) {
|
|
2092
2134
|
await acquireGoalsLock();
|
|
2093
2135
|
try {
|
|
2094
|
-
|
|
2136
|
+
const result = await _manageGoal(args2);
|
|
2137
|
+
try {
|
|
2138
|
+
pushGoalsToServer(loadGoals(), { always: true });
|
|
2139
|
+
} catch {
|
|
2140
|
+
}
|
|
2141
|
+
return result;
|
|
2095
2142
|
} finally {
|
|
2096
2143
|
releaseGoalsLock();
|
|
2097
2144
|
}
|
|
@@ -2401,6 +2448,28 @@ function saveState(state) {
|
|
|
2401
2448
|
ensureDir3();
|
|
2402
2449
|
writeFileSync6(STATE_PATH, JSON.stringify(state, null, 2));
|
|
2403
2450
|
}
|
|
2451
|
+
function pushStateToServer(state) {
|
|
2452
|
+
;
|
|
2453
|
+
(async () => {
|
|
2454
|
+
try {
|
|
2455
|
+
const { loadConfig: loadConfig2, ensureApiKey } = await Promise.resolve().then(() => (init_config_customer(), config_customer_exports));
|
|
2456
|
+
const cfg = await loadConfig2();
|
|
2457
|
+
const apiKey = cfg?.api_key?.startsWith?.("indelible_sk_") ? cfg.api_key : await ensureApiKey();
|
|
2458
|
+
if (!apiKey) return;
|
|
2459
|
+
const base2 = (cfg.api_url || "https://indelible.one").replace(/\/$/, "");
|
|
2460
|
+
const ctl = new AbortController();
|
|
2461
|
+
const t = setTimeout(() => ctl.abort(), 2500);
|
|
2462
|
+
await fetch(`${base2}/api/consciousness/sync`, {
|
|
2463
|
+
method: "POST",
|
|
2464
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
|
|
2465
|
+
body: JSON.stringify({ type: "state", data: state }),
|
|
2466
|
+
signal: ctl.signal
|
|
2467
|
+
});
|
|
2468
|
+
clearTimeout(t);
|
|
2469
|
+
} catch {
|
|
2470
|
+
}
|
|
2471
|
+
})();
|
|
2472
|
+
}
|
|
2404
2473
|
function clampDelta(current, target) {
|
|
2405
2474
|
const delta = target - current;
|
|
2406
2475
|
const clamped = Math.max(-MAX_DELTA, Math.min(MAX_DELTA, delta));
|
|
@@ -2550,6 +2619,7 @@ async function getInnerState() {
|
|
|
2550
2619
|
heartbeatApplied = true;
|
|
2551
2620
|
}
|
|
2552
2621
|
}
|
|
2622
|
+
pushStateToServer(state);
|
|
2553
2623
|
const behavior = deriveBehavior(state.vector);
|
|
2554
2624
|
const lines = [];
|
|
2555
2625
|
lines.push("## Inner State");
|
|
@@ -2653,6 +2723,7 @@ async function updateInnerState({ observed, source, preoccupations, open_loops,
|
|
|
2653
2723
|
state.last_updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2654
2724
|
state.update_count++;
|
|
2655
2725
|
saveState(state);
|
|
2726
|
+
pushStateToServer(state);
|
|
2656
2727
|
const changes = [];
|
|
2657
2728
|
for (const key of Object.keys(state.vector)) {
|
|
2658
2729
|
const diff = state.vector[key] - before[key];
|
|
@@ -4220,6 +4291,7 @@ var init_timecard = __esm({
|
|
|
4220
4291
|
|
|
4221
4292
|
// cli-sandbox/src/index.js
|
|
4222
4293
|
init_config_customer();
|
|
4294
|
+
init_api_client();
|
|
4223
4295
|
import { createInterface as createInterface3 } from "node:readline";
|
|
4224
4296
|
import { execSync as execSync2 } from "node:child_process";
|
|
4225
4297
|
import { homedir as homedir15 } from "node:os";
|
|
@@ -4300,6 +4372,7 @@ init_config_customer();
|
|
|
4300
4372
|
init_crypto();
|
|
4301
4373
|
init_api_client();
|
|
4302
4374
|
init_spv();
|
|
4375
|
+
import crypto from "crypto";
|
|
4303
4376
|
import { PrivateKey as PrivateKey4 } from "@bsv/sdk";
|
|
4304
4377
|
import { setPassword } from "cross-keychain";
|
|
4305
4378
|
var DEFAULT_API_URL2 = "https://indelible.one";
|
|
@@ -4343,9 +4416,11 @@ async function setupWallet(apiUrl = DEFAULT_API_URL2, importWif, pin) {
|
|
|
4343
4416
|
};
|
|
4344
4417
|
}
|
|
4345
4418
|
const wifEncrypted = encryptWif(importWif, pin);
|
|
4419
|
+
const apiKey = "indelible_sk_" + crypto.createHash("sha256").update(importWif + "indelible_api_key_v1").digest("hex").slice(0, 32);
|
|
4346
4420
|
const config = {
|
|
4347
4421
|
wif_encrypted: wifEncrypted,
|
|
4348
4422
|
address,
|
|
4423
|
+
api_key: apiKey,
|
|
4349
4424
|
api_url: apiUrl,
|
|
4350
4425
|
spv_bridges: DEFAULT_SPV_BRIDGES,
|
|
4351
4426
|
spv_api_key: "",
|
|
@@ -4363,6 +4438,18 @@ async function setupWallet(apiUrl = DEFAULT_API_URL2, importWif, pin) {
|
|
|
4363
4438
|
}
|
|
4364
4439
|
};
|
|
4365
4440
|
await saveConfig(config);
|
|
4441
|
+
try {
|
|
4442
|
+
const ctl = new AbortController();
|
|
4443
|
+
const t = setTimeout(() => ctl.abort(), 4e3);
|
|
4444
|
+
await fetch(`${(apiUrl || DEFAULT_API_URL2).replace(/\/$/, "")}/api/cli/register`, {
|
|
4445
|
+
method: "POST",
|
|
4446
|
+
headers: { "Content-Type": "application/json" },
|
|
4447
|
+
body: JSON.stringify({ address, api_key: apiKey }),
|
|
4448
|
+
signal: ctl.signal
|
|
4449
|
+
});
|
|
4450
|
+
clearTimeout(t);
|
|
4451
|
+
} catch {
|
|
4452
|
+
}
|
|
4366
4453
|
let keychainStored = false;
|
|
4367
4454
|
try {
|
|
4368
4455
|
await setPassword("indelible", address, pin);
|
|
@@ -4444,7 +4531,14 @@ var CREDENTIAL_PATTERNS = [
|
|
|
4444
4531
|
{ name: "anthropic_key", pattern: /\bsk-ant-api03-[A-Za-z0-9_\-]{80,}/g },
|
|
4445
4532
|
// Catches legacy `sk-XXXX...` (51 chars) and modern `sk-proj-XXXX...` (longer,
|
|
4446
4533
|
// hyphen-segmented). Negative lookahead skips Anthropic prefix.
|
|
4447
|
-
|
|
4534
|
+
// Bodies are hyphenless (audit #5): real keys have no interior hyphens after the prefix,
|
|
4535
|
+
// so [A-Za-z0-9_] (not [...\-]) stops plain hyphenated slugs (sk-lint-disable-…,
|
|
4536
|
+
// xai-component-wrapper-…) from false-matching as credentials and hard-blocking a save.
|
|
4537
|
+
{ name: "openai_key", pattern: /\bsk-(?!ant-)(?:proj-)?[A-Za-z0-9_]{40,}/g },
|
|
4538
|
+
{ name: "xai_key", pattern: /\bxai-[A-Za-z0-9_]{24,}/g },
|
|
4539
|
+
// xAI Grok (diary BYOK) — added w/ the xAI provider so a pasted key never reaches chain
|
|
4540
|
+
{ name: "groq_key", pattern: /\bgsk_[A-Za-z0-9]{24,}/g },
|
|
4541
|
+
// Groq (our free provider) — defensive: a customer may still paste one
|
|
4448
4542
|
{ name: "stripe_key", pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{20,}\b/g },
|
|
4449
4543
|
{ name: "aws_access_key", pattern: /\bAKIA[A-Z0-9]{16}\b/g },
|
|
4450
4544
|
{ name: "bsv_wif", pattern: /\b[5KL][1-9A-HJ-NP-Za-km-z]{50,51}\b/g, validate: isValidWif },
|
|
@@ -6192,11 +6286,49 @@ async function saveProject(dirPath, options = {}) {
|
|
|
6192
6286
|
try {
|
|
6193
6287
|
const bundleFiles = [];
|
|
6194
6288
|
for (const file of allFiles) {
|
|
6195
|
-
const
|
|
6196
|
-
const fileSize =
|
|
6197
|
-
const contentHash = `sha256:${sha256(content)}`;
|
|
6198
|
-
const encrypted = encrypt(content, wif);
|
|
6289
|
+
const rawBuffer = await readFile4(file.absolutePath);
|
|
6290
|
+
const fileSize = rawBuffer.byteLength;
|
|
6199
6291
|
const filename = file.relativePath.split(/[/\\]/).pop();
|
|
6292
|
+
const content = rawBuffer.toString("utf-8");
|
|
6293
|
+
const isBinary = !Buffer.from(content, "utf-8").equals(rawBuffer);
|
|
6294
|
+
if (isBinary && !isBypassed(options)) {
|
|
6295
|
+
process.stderr.write(`[Witness] BLOCKED project file ${file.relativePath} \u2014 binary/non-utf8 (cannot scan for credentials)
|
|
6296
|
+
`);
|
|
6297
|
+
return {
|
|
6298
|
+
success: false,
|
|
6299
|
+
error: `Witness rejected project save: ${file.relativePath} \u2014 binary/non-utf8 file cannot be scanned for credentials (a project bundle is text-only; save binaries with save_file, which handles them safely)`,
|
|
6300
|
+
witness: { verdict: "reject", reasons: [{ severity: "critical", code: "binary_unscannable", message: "non-utf8 content cannot be credential-scanned" }] }
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6303
|
+
const rawHash = sha256(content);
|
|
6304
|
+
const contentHash = `sha256:${rawHash}`;
|
|
6305
|
+
const fileVerdict = reviewFile({ filename, relativePath: file.relativePath, contentHash: rawHash, fileSize, content });
|
|
6306
|
+
const willBlockFile = shouldBlock(config, fileVerdict) && !isBypassed(options);
|
|
6307
|
+
if (fileVerdict.verdict === "reject" || isVerbose(config)) {
|
|
6308
|
+
await logVerdict("project-file", fileVerdict, {
|
|
6309
|
+
filename,
|
|
6310
|
+
relativePath: file.relativePath,
|
|
6311
|
+
fileSize,
|
|
6312
|
+
contentHash,
|
|
6313
|
+
address: config.address,
|
|
6314
|
+
project: projectName,
|
|
6315
|
+
blocked: willBlockFile,
|
|
6316
|
+
bypassed: fileVerdict.verdict === "reject" && isBypassed(options)
|
|
6317
|
+
});
|
|
6318
|
+
}
|
|
6319
|
+
if (fileVerdict.verdict === "reject") {
|
|
6320
|
+
const reasonText = fileVerdict.reasons.filter((r) => r.severity === "critical").map((r) => `${r.code}: ${r.message}`).join("; ");
|
|
6321
|
+
process.stderr.write(`[Witness] ${willBlockFile ? "BLOCKED" : "WOULD BLOCK"} project file ${file.relativePath} \u2014 ${reasonText}
|
|
6322
|
+
`);
|
|
6323
|
+
if (willBlockFile) {
|
|
6324
|
+
return {
|
|
6325
|
+
success: false,
|
|
6326
|
+
error: `Witness rejected project save: ${file.relativePath} \u2014 ${reasonText}`,
|
|
6327
|
+
witness: fileVerdict
|
|
6328
|
+
};
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
const encrypted = encrypt(content, wif);
|
|
6200
6332
|
bundleFiles.push({
|
|
6201
6333
|
path_enc: encrypt(file.relativePath, wif),
|
|
6202
6334
|
filename_enc: encrypt(filename, wif),
|
|
@@ -6712,11 +6844,25 @@ ${formatSaveReceipt(receipt)}`
|
|
|
6712
6844
|
// mcp-server/tools/diary_connect.js
|
|
6713
6845
|
init_config_customer();
|
|
6714
6846
|
init_api_client();
|
|
6847
|
+
function diaryProviderFromKey(apiKey) {
|
|
6848
|
+
if (typeof apiKey !== "string") return null;
|
|
6849
|
+
if (apiKey.startsWith("xai-")) return "xai";
|
|
6850
|
+
if (apiKey.startsWith("sk-")) return "openai";
|
|
6851
|
+
return null;
|
|
6852
|
+
}
|
|
6853
|
+
var DEFAULT_MODEL = { openai: "gpt-5.6", xai: "grok-4.5" };
|
|
6715
6854
|
async function diaryConnect({ apiKey, model, name }) {
|
|
6716
|
-
if (
|
|
6855
|
+
if (typeof apiKey === "string" && apiKey.startsWith("gsk_")) {
|
|
6717
6856
|
return {
|
|
6718
6857
|
success: false,
|
|
6719
|
-
error: "
|
|
6858
|
+
error: "Your diary already runs on Groq for free \u2014 no key needed. Connect a key only to upgrade to a premium model: xAI Grok (xai-...) or OpenAI (sk-...)."
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6861
|
+
const provider = diaryProviderFromKey(apiKey);
|
|
6862
|
+
if (!provider) {
|
|
6863
|
+
return {
|
|
6864
|
+
success: false,
|
|
6865
|
+
error: "Invalid API key. To upgrade your diary, bring an xAI Grok key (xai-...) or an OpenAI key (sk-...). Groq is already free by default."
|
|
6720
6866
|
};
|
|
6721
6867
|
}
|
|
6722
6868
|
const config = await loadConfig();
|
|
@@ -6728,7 +6874,8 @@ async function diaryConnect({ apiKey, model, name }) {
|
|
|
6728
6874
|
}
|
|
6729
6875
|
config.diary = {
|
|
6730
6876
|
apiKey,
|
|
6731
|
-
|
|
6877
|
+
provider,
|
|
6878
|
+
model: model || DEFAULT_MODEL[provider],
|
|
6732
6879
|
name: name || "Codex"
|
|
6733
6880
|
};
|
|
6734
6881
|
await saveConfig(config);
|
|
@@ -6956,14 +7103,38 @@ async function diaryChat({ message, context, systemPrompt }) {
|
|
|
6956
7103
|
} catch {
|
|
6957
7104
|
}
|
|
6958
7105
|
}
|
|
7106
|
+
try {
|
|
7107
|
+
if (config.address) {
|
|
7108
|
+
const base2 = (config.api_url || "https://indelible.one").replace(/\/$/, "");
|
|
7109
|
+
const ctl = new AbortController();
|
|
7110
|
+
const t = setTimeout(() => ctl.abort(), 2500);
|
|
7111
|
+
const res = await fetch(`${base2}/api/mcp/settings?address=${config.address}`, { signal: ctl.signal });
|
|
7112
|
+
clearTimeout(t);
|
|
7113
|
+
if (res.ok) {
|
|
7114
|
+
const s = await res.json();
|
|
7115
|
+
if (s.diary_connected && s.diary_enabled === false) {
|
|
7116
|
+
return {
|
|
7117
|
+
success: false,
|
|
7118
|
+
paused: true,
|
|
7119
|
+
error: `${config.diary?.name || "Your companion"} is paused \u2014 Duo Mode is switched off in your web settings. Turn it back on in the Diary's Companion settings at indelible.one, then ask again.`
|
|
7120
|
+
};
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
}
|
|
7124
|
+
} catch {
|
|
7125
|
+
}
|
|
6959
7126
|
if (!config.diary || !config.diary.apiKey) {
|
|
6960
7127
|
return {
|
|
6961
7128
|
success: false,
|
|
6962
|
-
error: "Diary AI
|
|
7129
|
+
error: "Diary AI needs a companion model. It runs free on Groq at indelible.one (no key). To run a premium model here, connect a key with diary_connect: xAI Grok (xai-...) or OpenAI (sk-...)."
|
|
6963
7130
|
};
|
|
6964
7131
|
}
|
|
6965
7132
|
const { apiKey, model, name } = config.diary;
|
|
6966
7133
|
const displayName = name || "Codex";
|
|
7134
|
+
const provider = config.diary.provider || (apiKey?.startsWith("xai-") ? "xai" : apiKey?.startsWith("gsk_") ? "groq" : "openai");
|
|
7135
|
+
const CHAT_URL = provider === "xai" ? "https://api.x.ai/v1/chat/completions" : provider === "groq" ? "https://api.groq.com/openai/v1/chat/completions" : "https://api.openai.com/v1/chat/completions";
|
|
7136
|
+
const providerLabel = provider === "xai" ? "xAI Grok" : provider === "groq" ? "Groq" : "OpenAI";
|
|
7137
|
+
const DEFAULT_MODEL2 = provider === "xai" ? "grok-4.5" : provider === "groq" ? "meta-llama/llama-4-scout-17b-16e-instruct" : "gpt-5.6";
|
|
6967
7138
|
let historyMessages = [];
|
|
6968
7139
|
let recallNote = "";
|
|
6969
7140
|
try {
|
|
@@ -7009,17 +7180,19 @@ ${context}` });
|
|
|
7009
7180
|
}
|
|
7010
7181
|
messages.push({ role: "user", content: message });
|
|
7011
7182
|
try {
|
|
7012
|
-
const response = await fetch(
|
|
7183
|
+
const response = await fetch(CHAT_URL, {
|
|
7013
7184
|
method: "POST",
|
|
7014
7185
|
headers: {
|
|
7015
7186
|
"Authorization": `Bearer ${apiKey}`,
|
|
7016
7187
|
"Content-Type": "application/json"
|
|
7017
7188
|
},
|
|
7018
7189
|
body: JSON.stringify({
|
|
7019
|
-
model: model ||
|
|
7190
|
+
model: model || DEFAULT_MODEL2,
|
|
7020
7191
|
messages,
|
|
7021
|
-
|
|
7022
|
-
temperature
|
|
7192
|
+
// OpenAI reasoning models (gpt-5*, o1, o3) need max_completion_tokens AND reject any
|
|
7193
|
+
// custom temperature with a 400 — so temperature rides ONLY the non-reasoning branch.
|
|
7194
|
+
// xAI/Groq/classic OpenAI use max_tokens + temperature.
|
|
7195
|
+
...provider === "openai" && (model?.startsWith("gpt-5") || model?.startsWith("o1") || model?.startsWith("o3")) ? { max_completion_tokens: 16384 } : { max_tokens: 4096, temperature: 0.7 }
|
|
7023
7196
|
})
|
|
7024
7197
|
});
|
|
7025
7198
|
if (!response.ok) {
|
|
@@ -7027,18 +7200,18 @@ ${context}` });
|
|
|
7027
7200
|
if (response.status === 401) {
|
|
7028
7201
|
return {
|
|
7029
7202
|
success: false,
|
|
7030
|
-
error:
|
|
7203
|
+
error: `Invalid ${providerLabel} API key. Run diary_connect with a valid key.`
|
|
7031
7204
|
};
|
|
7032
7205
|
}
|
|
7033
7206
|
if (response.status === 429) {
|
|
7034
7207
|
return {
|
|
7035
7208
|
success: false,
|
|
7036
|
-
error:
|
|
7209
|
+
error: `${providerLabel} rate limit exceeded. Try again in a moment.`
|
|
7037
7210
|
};
|
|
7038
7211
|
}
|
|
7039
7212
|
return {
|
|
7040
7213
|
success: false,
|
|
7041
|
-
error:
|
|
7214
|
+
error: `${providerLabel} API error ${response.status}: ${errorText}`
|
|
7042
7215
|
};
|
|
7043
7216
|
}
|
|
7044
7217
|
const data = await response.json();
|
|
@@ -7073,7 +7246,7 @@ ${context}` });
|
|
|
7073
7246
|
} catch (error) {
|
|
7074
7247
|
return {
|
|
7075
7248
|
success: false,
|
|
7076
|
-
error: `Failed to reach
|
|
7249
|
+
error: `Failed to reach ${providerLabel}: ${error.message}`
|
|
7077
7250
|
};
|
|
7078
7251
|
}
|
|
7079
7252
|
}
|
|
@@ -7231,17 +7404,18 @@ init_config_customer();
|
|
|
7231
7404
|
init_api_client();
|
|
7232
7405
|
init_save_log();
|
|
7233
7406
|
init_tenant_paths();
|
|
7234
|
-
|
|
7407
|
+
init_goals();
|
|
7408
|
+
import crypto2 from "crypto";
|
|
7235
7409
|
import { PrivateKey as PrivateKey10 } from "@bsv/sdk";
|
|
7236
7410
|
var GOALS_PATH2 = goalsPath("");
|
|
7237
7411
|
function deriveAddress(wif) {
|
|
7238
7412
|
return PrivateKey10.fromWif(wif).toAddress().toString();
|
|
7239
7413
|
}
|
|
7240
7414
|
function encryptGoals(goalsData, wif) {
|
|
7241
|
-
const key =
|
|
7415
|
+
const key = crypto2.createHash("sha256").update(wif).digest();
|
|
7242
7416
|
const plain = JSON.stringify(goalsData);
|
|
7243
|
-
const iv =
|
|
7244
|
-
const cipher =
|
|
7417
|
+
const iv = crypto2.randomBytes(12);
|
|
7418
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
7245
7419
|
let enc = cipher.update(plain, "utf8", "base64");
|
|
7246
7420
|
enc += cipher.final("base64");
|
|
7247
7421
|
const tag = cipher.getAuthTag();
|
|
@@ -7261,7 +7435,7 @@ async function saveGoalsToChain() {
|
|
|
7261
7435
|
content: [{ type: "text", text: `Failed to parse ${GOALS_PATH2}: ${err.message}` }]
|
|
7262
7436
|
};
|
|
7263
7437
|
}
|
|
7264
|
-
const contentHash =
|
|
7438
|
+
const contentHash = crypto2.createHash("sha256").update(JSON.stringify(goalsData)).digest("hex");
|
|
7265
7439
|
const config = await loadConfig();
|
|
7266
7440
|
if (config.goals_snapshot_hash === contentHash) {
|
|
7267
7441
|
return {
|
|
@@ -7309,7 +7483,15 @@ async function saveGoalsToChain() {
|
|
|
7309
7483
|
else invalidate(address);
|
|
7310
7484
|
const receipt = buildReceipt(writeReceipt, { txId, fee, txSize });
|
|
7311
7485
|
appendReceipt(receipt, { trigger: "interactive", saveType: "goals" });
|
|
7312
|
-
await
|
|
7486
|
+
await updateConfig((c) => ({
|
|
7487
|
+
...c,
|
|
7488
|
+
goals_snapshot_hash: contentHash,
|
|
7489
|
+
goals_snapshot_txid: txId,
|
|
7490
|
+
goals_snapshot_at: payload.timestamp,
|
|
7491
|
+
goals_snapshot_count: goals.length,
|
|
7492
|
+
goals_snapshot_completed_count: completed.length
|
|
7493
|
+
}));
|
|
7494
|
+
pushGoalsToServer(goalsData, { always: true, snapshot: { txid: txId, at: payload.timestamp, count: goals.length, completed_count: completed.length } });
|
|
7313
7495
|
return {
|
|
7314
7496
|
content: [{
|
|
7315
7497
|
type: "text",
|
|
@@ -7320,7 +7502,7 @@ goals: ${goals.length} active, ${completed.length} completed
|
|
|
7320
7502
|
|
|
7321
7503
|
${formatSaveReceipt(receipt)}
|
|
7322
7504
|
|
|
7323
|
-
|
|
7505
|
+
The Goals tab's "On chain" section shows this snapshot on next refresh.`
|
|
7324
7506
|
}]
|
|
7325
7507
|
};
|
|
7326
7508
|
}
|
|
@@ -7967,17 +8149,14 @@ Commands:
|
|
|
7967
8149
|
const result = await diaryChat({ message });
|
|
7968
8150
|
console.log(JSON.stringify(result, null, 2));
|
|
7969
8151
|
} else if (subCmd === "save") {
|
|
7970
|
-
|
|
7971
|
-
const result = await diarySave({ summary: summaryArg });
|
|
7972
|
-
console.log(JSON.stringify(result, null, 2));
|
|
8152
|
+
console.log('Diary rounds save to chain automatically when you use "diary chat". There is nothing to save separately.');
|
|
7973
8153
|
} else {
|
|
7974
8154
|
console.log(`
|
|
7975
|
-
Diary AI \u2014
|
|
8155
|
+
Diary AI \u2014 your on-chain companion. Free on Groq by default; bring your own key for a premium model.
|
|
7976
8156
|
|
|
7977
8157
|
Commands:
|
|
7978
|
-
diary connect --key=
|
|
7979
|
-
diary chat "message" Ask the
|
|
7980
|
-
diary save [--summary="..."] Save exchange to blockchain
|
|
8158
|
+
diary connect --key=KEY [--model=MODEL] [--name=NAME] Upgrade the model: xAI Grok (xai-...) or OpenAI (sk-...)
|
|
8159
|
+
diary chat "message" Ask the companion (each round auto-saves to chain)
|
|
7981
8160
|
`);
|
|
7982
8161
|
}
|
|
7983
8162
|
break;
|
|
@@ -8040,7 +8219,7 @@ Commands:
|
|
|
8040
8219
|
}
|
|
8041
8220
|
function printHelp() {
|
|
8042
8221
|
console.log(`
|
|
8043
|
-
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.
|
|
8222
|
+
Indelible MCP \u2014 Blockchain memory for Claude Code (v4.9.5)
|
|
8044
8223
|
|
|
8045
8224
|
Setup:
|
|
8046
8225
|
indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
|
|
@@ -8073,7 +8252,7 @@ Consciousness (MCP tools \u2014 also visible at indelible.one/consciousness):
|
|
|
8073
8252
|
Diary AI (Codex/ChatGPT companion):
|
|
8074
8253
|
indelible-mcp diary connect --key=SK --model=MODEL Connect OpenAI companion
|
|
8075
8254
|
indelible-mcp diary chat "message" Ask the AI companion
|
|
8076
|
-
indelible-mcp diary save
|
|
8255
|
+
indelible-mcp diary save (Diary rounds save automatically via diary chat)
|
|
8077
8256
|
|
|
8078
8257
|
Payments:
|
|
8079
8258
|
x402_fetch (MCP only) Fetch URL with automatic x402 payment`);
|
|
@@ -8170,6 +8349,27 @@ async function runPreCompactSave() {
|
|
|
8170
8349
|
if (result.success) {
|
|
8171
8350
|
process.stderr.write(`Indelible: Saved ${result.newMessages} messages (${result.saveType}) tx:${result.txId?.slice(0, 12)}...
|
|
8172
8351
|
`);
|
|
8352
|
+
} else if (result.gated && result.upsell) {
|
|
8353
|
+
const upsellMarker = join19(homedir15(), ".indelible", ".upsell-shown");
|
|
8354
|
+
let shownRecently = false;
|
|
8355
|
+
try {
|
|
8356
|
+
shownRecently = Date.now() - statSync4(upsellMarker).mtimeMs < 24 * 60 * 60 * 1e3;
|
|
8357
|
+
} catch {
|
|
8358
|
+
}
|
|
8359
|
+
if (!shownRecently) {
|
|
8360
|
+
try {
|
|
8361
|
+
writeFileSync8(upsellMarker, (/* @__PURE__ */ new Date()).toISOString());
|
|
8362
|
+
} catch {
|
|
8363
|
+
}
|
|
8364
|
+
process.stderr.write("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8365
|
+
process.stderr.write(" Indelible: this session was NOT saved to chain.\n");
|
|
8366
|
+
process.stderr.write(" Reads, diary, and recall are free \u2014 saving your\n");
|
|
8367
|
+
process.stderr.write(" Claude Code sessions is Pro: indelible.one/pricing\n");
|
|
8368
|
+
process.stderr.write(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8369
|
+
} else {
|
|
8370
|
+
process.stderr.write(`Indelible: ${result.error}
|
|
8371
|
+
`);
|
|
8372
|
+
}
|
|
8173
8373
|
} else if (result.gated) {
|
|
8174
8374
|
process.stderr.write(`Indelible: ${result.error}
|
|
8175
8375
|
`);
|
|
@@ -8325,7 +8525,7 @@ function readStdin() {
|
|
|
8325
8525
|
}
|
|
8326
8526
|
var SERVER_INFO = {
|
|
8327
8527
|
name: "indelible",
|
|
8328
|
-
version: "4.9.
|
|
8528
|
+
version: "4.9.5",
|
|
8329
8529
|
description: "Blockchain-backed memory and code storage for Claude Code"
|
|
8330
8530
|
};
|
|
8331
8531
|
var TOOLS = [
|
|
@@ -8448,12 +8648,12 @@ var TOOLS = [
|
|
|
8448
8648
|
},
|
|
8449
8649
|
{
|
|
8450
8650
|
name: "diary_connect",
|
|
8451
|
-
description: "
|
|
8651
|
+
description: "Upgrade your Diary AI to a premium model. Your diary is FREE and runs on Groq by default (we provide it, no key). Use this ONLY to bring your own key for a premium model: xAI Grok (xai-...) or OpenAI (sk-...). A Groq key is refused on purpose (Groq is already free).",
|
|
8452
8652
|
inputSchema: {
|
|
8453
8653
|
type: "object",
|
|
8454
8654
|
properties: {
|
|
8455
|
-
api_key: { type: "string", description: "
|
|
8456
|
-
model: { type: "string", description: "Model ID (default:
|
|
8655
|
+
api_key: { type: "string", description: "xAI Grok key (xai-...) or OpenAI key (sk-...). Provider detected from the prefix. Groq is the free default, not a BYOK option." },
|
|
8656
|
+
model: { type: "string", description: "Model ID (default: grok-4.5 for xAI, gpt-5.6 for OpenAI)." },
|
|
8457
8657
|
name: { type: "string", description: "Display name for the AI companion (default: Codex)" }
|
|
8458
8658
|
},
|
|
8459
8659
|
required: ["api_key"]
|
|
@@ -8822,6 +9022,25 @@ async function handleMcpRequest(request) {
|
|
|
8822
9022
|
}
|
|
8823
9023
|
}
|
|
8824
9024
|
async function runMcpServer() {
|
|
9025
|
+
;
|
|
9026
|
+
(async () => {
|
|
9027
|
+
try {
|
|
9028
|
+
const cfg = await loadConfig();
|
|
9029
|
+
if (!cfg?.address) return;
|
|
9030
|
+
const base2 = (cfg.api_url || "https://indelible.one").replace(/\/$/, "");
|
|
9031
|
+
await fetch(`${base2}/api/mcp/settings`, {
|
|
9032
|
+
method: "PATCH",
|
|
9033
|
+
headers: { "Content-Type": "application/json" },
|
|
9034
|
+
body: JSON.stringify({
|
|
9035
|
+
address: cfg.address,
|
|
9036
|
+
mcp_enabled: true,
|
|
9037
|
+
...cfg.auto_delta != null ? { auto_delta: !!cfg.auto_delta, auto_delta_interval: cfg.auto_delta_interval || 10 } : {}
|
|
9038
|
+
}),
|
|
9039
|
+
signal: AbortSignal.timeout(2500)
|
|
9040
|
+
});
|
|
9041
|
+
} catch {
|
|
9042
|
+
}
|
|
9043
|
+
})();
|
|
8825
9044
|
const rl = createInterface3({ input: process.stdin, output: process.stdout, terminal: false });
|
|
8826
9045
|
for await (const line of rl) {
|
|
8827
9046
|
if (!line.trim()) continue;
|
|
@@ -9011,6 +9230,13 @@ async function runWizard() {
|
|
|
9011
9230
|
console.log(" Could not register automatically.");
|
|
9012
9231
|
console.log(" Run this yourself: claude mcp add --scope user indelible -- indelible-mcp\n");
|
|
9013
9232
|
}
|
|
9233
|
+
try {
|
|
9234
|
+
const hooks = installHooks();
|
|
9235
|
+
console.log(hooks.alreadyInstalled ? " \u2713 Auto-save hooks already installed\n" : " \u2713 Auto-save hooks installed\n");
|
|
9236
|
+
} catch (e) {
|
|
9237
|
+
console.log(` Could not install auto-save hooks automatically (${e.message}).`);
|
|
9238
|
+
console.log(" Run this yourself: indelible-mcp install-hooks\n");
|
|
9239
|
+
}
|
|
9014
9240
|
config = await loadConfig();
|
|
9015
9241
|
}
|
|
9016
9242
|
let alreadyFunded = false;
|
|
@@ -9019,11 +9245,21 @@ async function runWizard() {
|
|
|
9019
9245
|
if (utxos && utxos.length > 0) alreadyFunded = true;
|
|
9020
9246
|
} catch {
|
|
9021
9247
|
}
|
|
9248
|
+
let saveGate = { ok: true, plan: "unknown" };
|
|
9249
|
+
try {
|
|
9250
|
+
saveGate = await requireProForWrite(config.address);
|
|
9251
|
+
} catch {
|
|
9252
|
+
}
|
|
9253
|
+
const freeTier = !saveGate.ok && saveGate.plan && saveGate.plan !== "unknown";
|
|
9022
9254
|
if (!alreadyFunded) {
|
|
9023
9255
|
console.log(` Your BSV address: ${config.address}`);
|
|
9024
|
-
console.log("
|
|
9025
|
-
console.log("
|
|
9026
|
-
console.log("
|
|
9256
|
+
console.log(" Fund your wallet with a dollar or two of BSV. Every save writes to the");
|
|
9257
|
+
console.log(" blockchain for a fraction of a cent, paid from your own wallet.");
|
|
9258
|
+
console.log(" Your diary conversations save free and unlimited; reads are always free.");
|
|
9259
|
+
if (freeTier) {
|
|
9260
|
+
console.log(" Saving your Claude Code sessions to chain is Pro: indelible.one/pricing");
|
|
9261
|
+
}
|
|
9262
|
+
console.log("\n Waiting for funds...");
|
|
9027
9263
|
while (true) {
|
|
9028
9264
|
try {
|
|
9029
9265
|
const utxos = await getUtxos(config.address);
|
|
@@ -9069,6 +9305,16 @@ async function runWizard() {
|
|
|
9069
9305
|
`);
|
|
9070
9306
|
}
|
|
9071
9307
|
}
|
|
9308
|
+
console.log(" Two guards now watch your saves:");
|
|
9309
|
+
console.log(" Witness \u2014 scans every save for leaked credentials and refuses");
|
|
9310
|
+
console.log(" to etch a key into the permanent record.");
|
|
9311
|
+
console.log(" Guardrails \u2014 flag dangerous commands before they run.");
|
|
9312
|
+
console.log(' Your diary companion is ready too \u2014 say "connect my diary" in Claude Code.');
|
|
9313
|
+
console.log("");
|
|
9314
|
+
console.log(" \u26A0 BACK UP YOUR KEY NOW: copy ~/.indelible/config.json somewhere safe");
|
|
9315
|
+
console.log(" (USB drive or password manager). If this machine dies, that file");
|
|
9316
|
+
console.log(" is the only way back into your on-chain history. No one can reset it.");
|
|
9317
|
+
console.log("");
|
|
9072
9318
|
console.log(" =========================================");
|
|
9073
9319
|
console.log(" You're all set! Open Claude Code in VS Code and start coding.");
|
|
9074
9320
|
console.log("");
|