@clude/sdk 3.0.4 → 3.2.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/dist/cli/index.js CHANGED
@@ -749,9 +749,9 @@ function createPrompt() {
749
749
  });
750
750
  }
751
751
  function ask(rl, question) {
752
- return new Promise((resolve4) => {
752
+ return new Promise((resolve5) => {
753
753
  rl.question(` ${c.white}?${c.reset} ${question}`, (answer) => {
754
- resolve4(answer.trim());
754
+ resolve5(answer.trim());
755
755
  });
756
756
  });
757
757
  }
@@ -759,14 +759,14 @@ function askChoice(rl, question, choices) {
759
759
  for (const [i, ch] of choices.entries()) {
760
760
  console.log(` ${c.cyan}${i + 1}${c.reset}) ${ch.label}`);
761
761
  }
762
- return new Promise((resolve4) => {
762
+ return new Promise((resolve5) => {
763
763
  rl.question(`
764
764
  ${c.white}?${c.reset} ${question} `, (answer) => {
765
765
  const idx = parseInt(answer.trim(), 10) - 1;
766
766
  if (idx >= 0 && idx < choices.length) {
767
- resolve4(choices[idx].key);
767
+ resolve5(choices[idx].key);
768
768
  } else {
769
- resolve4(choices[choices.length - 1].key);
769
+ resolve5(choices[choices.length - 1].key);
770
770
  }
771
771
  });
772
772
  });
@@ -1497,11 +1497,11 @@ async function getEmail(opts = {}) {
1497
1497
  }
1498
1498
  if (opts.skipPrompt) return null;
1499
1499
  const rl = createPrompt();
1500
- return new Promise((resolve4) => {
1500
+ return new Promise((resolve5) => {
1501
1501
  rl.question(" Email: ", (answer) => {
1502
1502
  rl.close();
1503
1503
  const trimmed = answer.trim();
1504
- resolve4(trimmed && emailRegex.test(trimmed) ? trimmed : null);
1504
+ resolve5(trimmed && emailRegex.test(trimmed) ? trimmed : null);
1505
1505
  });
1506
1506
  });
1507
1507
  }
@@ -1622,23 +1622,23 @@ function createPrompt2() {
1622
1622
  });
1623
1623
  }
1624
1624
  function ask2(rl, question) {
1625
- return new Promise((resolve4) => {
1625
+ return new Promise((resolve5) => {
1626
1626
  rl.question(` ${c.white}?${c.reset} ${question}`, (answer) => {
1627
- resolve4(answer.trim());
1627
+ resolve5(answer.trim());
1628
1628
  });
1629
1629
  });
1630
1630
  }
1631
1631
  function askChoice2(rl, question, choices) {
1632
1632
  const choiceStr = choices.map((ch, i) => `${c.cyan}${i + 1}${c.reset}) ${ch}`).join(" ");
1633
- return new Promise((resolve4) => {
1633
+ return new Promise((resolve5) => {
1634
1634
  rl.question(` ${c.white}?${c.reset} ${question} [${choiceStr}]: `, (answer) => {
1635
1635
  const idx = parseInt(answer.trim(), 10) - 1;
1636
1636
  if (idx >= 0 && idx < choices.length) {
1637
- resolve4(choices[idx]);
1637
+ resolve5(choices[idx]);
1638
1638
  } else if (choices.map((c2) => c2.toLowerCase()).includes(answer.trim().toLowerCase())) {
1639
- resolve4(answer.trim().toLowerCase());
1639
+ resolve5(answer.trim().toLowerCase());
1640
1640
  } else {
1641
- resolve4(choices[0]);
1641
+ resolve5(choices[0]);
1642
1642
  }
1643
1643
  });
1644
1644
  });
@@ -2172,9 +2172,9 @@ function createPrompt3() {
2172
2172
  });
2173
2173
  }
2174
2174
  function ask3(rl, question) {
2175
- return new Promise((resolve4) => {
2175
+ return new Promise((resolve5) => {
2176
2176
  rl.question(` ${c.white}?${c.reset} ${question}`, (answer) => {
2177
- resolve4(answer.trim());
2177
+ resolve5(answer.trim());
2178
2178
  });
2179
2179
  });
2180
2180
  }
@@ -2262,6 +2262,249 @@ var init_register = __esm({
2262
2262
  }
2263
2263
  });
2264
2264
 
2265
+ // packages/brain/src/cli/connect.ts
2266
+ var connect_exports = {};
2267
+ __export(connect_exports, {
2268
+ runConnect: () => runConnect
2269
+ });
2270
+ function createPrompt4() {
2271
+ return readline4.createInterface({ input: process.stdin, output: process.stdout });
2272
+ }
2273
+ function ask4(rl, question) {
2274
+ return new Promise((resolve5) => {
2275
+ rl.question(` ${c.white}?${c.reset} ${question}`, (answer) => resolve5(answer.trim()));
2276
+ });
2277
+ }
2278
+ function askYesNo(rl, question, defaultYes = true) {
2279
+ const hint = defaultYes ? "Y/n" : "y/N";
2280
+ return ask4(rl, `${question} [${hint}]: `).then((a) => {
2281
+ if (!a) return defaultYes;
2282
+ return /^y(es)?$/i.test(a);
2283
+ });
2284
+ }
2285
+ function readStoredConfig() {
2286
+ const p = path4.join(os3.homedir(), ".clude", "config.json");
2287
+ if (!fs3.existsSync(p)) return null;
2288
+ try {
2289
+ return JSON.parse(fs3.readFileSync(p, "utf8"));
2290
+ } catch {
2291
+ return null;
2292
+ }
2293
+ }
2294
+ function detectClaudeDesktopConfigPath() {
2295
+ const home = process.env.HOME || process.env.USERPROFILE || os3.homedir();
2296
+ if (process.platform === "darwin") {
2297
+ return path4.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2298
+ }
2299
+ if (process.platform === "win32") {
2300
+ return path4.join(home, "AppData", "Roaming", "Claude", "claude_desktop_config.json");
2301
+ }
2302
+ return path4.join(home, ".config", "Claude", "claude_desktop_config.json");
2303
+ }
2304
+ async function verifyMcpKey(host, apiKey) {
2305
+ try {
2306
+ const res = await fetch(`${host}${MCP_PATH}`, {
2307
+ method: "POST",
2308
+ headers: {
2309
+ "Content-Type": "application/json",
2310
+ "Accept": "application/json, text/event-stream",
2311
+ "Authorization": `Bearer ${apiKey}`
2312
+ },
2313
+ body: JSON.stringify({
2314
+ jsonrpc: "2.0",
2315
+ id: 1,
2316
+ method: "initialize",
2317
+ params: {
2318
+ protocolVersion: "2025-06-18",
2319
+ capabilities: {},
2320
+ clientInfo: { name: "clude-connect", version: "1.0.0" }
2321
+ }
2322
+ })
2323
+ });
2324
+ if (res.status === 401) return { ok: false, error: "Bearer key rejected (401)" };
2325
+ if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
2326
+ const body = await res.text();
2327
+ const match = body.match(/data:\s*(\{.*\})/);
2328
+ if (!match) return { ok: false, error: "No SSE data frame in response" };
2329
+ const doc = JSON.parse(match[1]);
2330
+ if (doc.error) return { ok: false, error: doc.error.message };
2331
+ return { ok: true, serverName: doc?.result?.serverInfo?.name };
2332
+ } catch (err) {
2333
+ return { ok: false, error: err.message ?? "fetch failed" };
2334
+ }
2335
+ }
2336
+ async function registerNewKey(host, name, email) {
2337
+ try {
2338
+ const res = await fetch(`${host}/api/cortex/register`, {
2339
+ method: "POST",
2340
+ headers: { "Content-Type": "application/json" },
2341
+ body: JSON.stringify(email ? { name, email } : { name })
2342
+ });
2343
+ const data = await res.json();
2344
+ if (!res.ok || !data.apiKey) return { error: data?.error ?? `HTTP ${res.status}` };
2345
+ return { apiKey: data.apiKey, agentId: data.agentId };
2346
+ } catch (err) {
2347
+ return { error: err.message ?? "register failed" };
2348
+ }
2349
+ }
2350
+ function buildDesktopEntry(host, apiKey) {
2351
+ return {
2352
+ name: "clude",
2353
+ entry: {
2354
+ url: `${host}${MCP_PATH}`,
2355
+ headers: { Authorization: `Bearer ${apiKey}` }
2356
+ }
2357
+ };
2358
+ }
2359
+ function writeDesktopConfig(configPath, host, apiKey) {
2360
+ const dir = path4.dirname(configPath);
2361
+ fs3.mkdirSync(dir, { recursive: true });
2362
+ let existing = {};
2363
+ let merged = false;
2364
+ if (fs3.existsSync(configPath)) {
2365
+ try {
2366
+ existing = JSON.parse(fs3.readFileSync(configPath, "utf8"));
2367
+ merged = true;
2368
+ } catch {
2369
+ existing = {};
2370
+ }
2371
+ }
2372
+ const { name, entry } = buildDesktopEntry(host, apiKey);
2373
+ existing.mcpServers = existing.mcpServers || {};
2374
+ existing.mcpServers[name] = entry;
2375
+ fs3.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
2376
+ return { wrote: true, merged };
2377
+ }
2378
+ async function runConnect() {
2379
+ const args = process.argv.slice(3);
2380
+ const nonInteractive = args.includes("--non-interactive") || args.includes("-y");
2381
+ const hostFlag = args.find((a) => a.startsWith("--host="));
2382
+ const host = hostFlag ? hostFlag.split("=")[1] : DEFAULT_HOST;
2383
+ const keyFlag = args.find((a) => a.startsWith("--key="));
2384
+ printBanner();
2385
+ console.log(` ${c.bold}Connect Clude to Claude Desktop${c.reset}
2386
+ `);
2387
+ console.log(` ${c.gray}Adds Clude as a remote MCP connector. You'll see memory tools${c.reset}`);
2388
+ console.log(` ${c.gray}(recall_memories, store_memory, get_memory_stats) in Claude.${c.reset}
2389
+ `);
2390
+ printStep(1, 3, "Get your API key");
2391
+ let apiKey = keyFlag?.split("=")[1] || process.env.CORTEX_API_KEY || "";
2392
+ let source = apiKey ? keyFlag ? "--key flag" : "CORTEX_API_KEY env var" : "";
2393
+ if (!apiKey) {
2394
+ const stored = readStoredConfig();
2395
+ if (stored?.apiKey) {
2396
+ apiKey = stored.apiKey;
2397
+ source = "~/.clude/config.json";
2398
+ }
2399
+ }
2400
+ if (apiKey) {
2401
+ printSuccess(`Using existing key from ${source}`);
2402
+ printInfo(` ${apiKey.slice(0, 12)}\u2026`);
2403
+ } else if (nonInteractive) {
2404
+ printError("No API key found. Provide one with --key=<clk_\u2026> or run `npx @clude/sdk setup` first.");
2405
+ process.exit(1);
2406
+ } else {
2407
+ const rl = createPrompt4();
2408
+ try {
2409
+ console.log(` ${c.gray}No API key found locally. Choose one:${c.reset}
2410
+ `);
2411
+ console.log(` ${c.cyan}1${c.reset}) Register a new key now (free)`);
2412
+ console.log(` ${c.cyan}2${c.reset}) Paste an existing ${c.gray}clk_\u2026${c.reset} key
2413
+ `);
2414
+ const choice = await ask4(rl, "Choice [1]: ");
2415
+ if (choice === "" || choice === "1") {
2416
+ const name = await ask4(rl, "Display name: ") || "claude-desktop-user";
2417
+ const email = await ask4(rl, "Email (optional, enables dashboard login): ");
2418
+ printInfo(" Registering\u2026");
2419
+ const reg = await registerNewKey(host, name, email || void 0);
2420
+ if ("error" in reg) {
2421
+ printError(`Registration failed: ${reg.error}`);
2422
+ process.exit(1);
2423
+ }
2424
+ apiKey = reg.apiKey;
2425
+ printSuccess(`API key created ${c.green}${apiKey}${c.reset}`);
2426
+ printWarn("Save this key. It will not be shown again.");
2427
+ } else {
2428
+ apiKey = await ask4(rl, "Paste your clk_\u2026 key: ");
2429
+ if (!apiKey.startsWith("clk_")) {
2430
+ printError("Key should start with clk_. Aborting.");
2431
+ process.exit(1);
2432
+ }
2433
+ }
2434
+ } finally {
2435
+ rl.close();
2436
+ }
2437
+ }
2438
+ printStep(2, 3, "Verify connector");
2439
+ printInfo(` POST ${host}${MCP_PATH} \u2192 initialize`);
2440
+ const verify = await verifyMcpKey(host, apiKey);
2441
+ if (!verify.ok) {
2442
+ printError(`Connector check failed: ${verify.error}`);
2443
+ printInfo(" If the host is right, double-check the bearer key at https://clude.io/dashboard");
2444
+ process.exit(1);
2445
+ }
2446
+ printSuccess(`Connector responded \u2014 server: ${c.green}${verify.serverName ?? "clude"}${c.reset}`);
2447
+ printStep(3, 3, "Install in Claude Desktop");
2448
+ const configPath = detectClaudeDesktopConfigPath();
2449
+ let shouldWrite = nonInteractive;
2450
+ if (!nonInteractive) {
2451
+ const rl = createPrompt4();
2452
+ try {
2453
+ console.log(` ${c.gray}Config path: ${configPath}${c.reset}`);
2454
+ shouldWrite = await askYesNo(rl, "Write this connector to your Claude Desktop config?", true);
2455
+ } finally {
2456
+ rl.close();
2457
+ }
2458
+ }
2459
+ if (shouldWrite) {
2460
+ try {
2461
+ const { merged } = writeDesktopConfig(configPath, host, apiKey);
2462
+ printSuccess(`${merged ? "Merged into" : "Created"} ${configPath}`);
2463
+ printInfo(" Restart Claude Desktop to pick up the new connector.");
2464
+ } catch (err) {
2465
+ printError(`Could not write config: ${err.message}`);
2466
+ printSnippet(host, apiKey);
2467
+ process.exit(1);
2468
+ }
2469
+ } else {
2470
+ printSnippet(host, apiKey);
2471
+ }
2472
+ printDivider();
2473
+ console.log(`
2474
+ ${c.bold}Claude Code users:${c.reset} add this to your project's ${c.cyan}.mcp.json${c.reset}
2475
+ `);
2476
+ printCodeBlock(JSON.stringify({
2477
+ mcpServers: { clude: buildDesktopEntry(host, apiKey).entry }
2478
+ }, null, 2));
2479
+ console.log(` ${c.bold}claude.ai web users:${c.reset}`);
2480
+ console.log(` 1. Open Settings \u2192 Connectors \u2192 Add custom connector`);
2481
+ console.log(` 2. URL: ${c.cyan}${host}${MCP_PATH}${c.reset}`);
2482
+ console.log(` 3. Header: ${c.cyan}Authorization: Bearer ${apiKey.slice(0, 12)}\u2026${c.reset}
2483
+ `);
2484
+ console.log(` ${c.gray}Docs: https://clude.io/docs/connector${c.reset}
2485
+ `);
2486
+ }
2487
+ function printSnippet(host, apiKey) {
2488
+ const configPath = detectClaudeDesktopConfigPath();
2489
+ printWarn(`Skipped writing config. Add manually to ${configPath}:`);
2490
+ printCodeBlock(JSON.stringify({
2491
+ mcpServers: { clude: buildDesktopEntry(host, apiKey).entry }
2492
+ }, null, 2));
2493
+ }
2494
+ var readline4, fs3, path4, os3, DEFAULT_HOST, MCP_PATH;
2495
+ var init_connect = __esm({
2496
+ "packages/brain/src/cli/connect.ts"() {
2497
+ "use strict";
2498
+ readline4 = __toESM(require("readline"));
2499
+ fs3 = __toESM(require("fs"));
2500
+ path4 = __toESM(require("path"));
2501
+ os3 = __toESM(require("os"));
2502
+ init_banner();
2503
+ DEFAULT_HOST = process.env.CORTEX_HOST_URL || "https://clude.io";
2504
+ MCP_PATH = "/api/mcp";
2505
+ }
2506
+ });
2507
+
2265
2508
  // packages/shared/src/config.ts
2266
2509
  var config_exports = {};
2267
2510
  __export(config_exports, {
@@ -2797,21 +3040,25 @@ var init_status = __esm({
2797
3040
  }
2798
3041
  });
2799
3042
 
2800
- // packages/brain/src/memorypack/types.ts
3043
+ // packages/memorypack/src/types.ts
2801
3044
  var MEMORYPACK_VERSION;
2802
3045
  var init_types = __esm({
2803
- "packages/brain/src/memorypack/types.ts"() {
3046
+ "packages/memorypack/src/types.ts"() {
2804
3047
  "use strict";
2805
- MEMORYPACK_VERSION = "0.1";
3048
+ MEMORYPACK_VERSION = "0.2";
2806
3049
  }
2807
3050
  });
2808
3051
 
2809
- // packages/brain/src/memorypack/sign.ts
3052
+ // packages/memorypack/src/sign.ts
2810
3053
  function hashRecordLine(line) {
2811
3054
  const clean = line.replace(/\n$/, "");
2812
3055
  const hex = (0, import_crypto.createHash)("sha256").update(clean, "utf-8").digest("hex");
2813
3056
  return `sha256:${hex}`;
2814
3057
  }
3058
+ function hashBuffer(buf) {
3059
+ const hex = (0, import_crypto.createHash)("sha256").update(buf).digest("hex");
3060
+ return `sha256:${hex}`;
3061
+ }
2815
3062
  function signHash(hash, secretKey) {
2816
3063
  const messageBytes = Buffer.from(hash, "utf-8");
2817
3064
  const sig = import_tweetnacl.default.sign.detached(messageBytes, secretKey);
@@ -2828,18 +3075,75 @@ function verifyHash(hash, signature, publicKey) {
2828
3075
  return false;
2829
3076
  }
2830
3077
  }
2831
- var import_crypto, import_tweetnacl, bs58Module, bs58;
3078
+ function revocationPayload(record_hash, revoked_at) {
3079
+ return `revoke:v1:${record_hash}:${revoked_at}`;
3080
+ }
3081
+ function verifyRevocation(record_hash, revoked_at, signature, publicKey) {
3082
+ try {
3083
+ const messageBytes = Buffer.from(revocationPayload(record_hash, revoked_at), "utf-8");
3084
+ const sigRaw = signature.startsWith("base58:") ? signature.slice("base58:".length) : signature;
3085
+ const sigBytes = bs58.decode(sigRaw);
3086
+ const pkBytes = bs58.decode(publicKey);
3087
+ return import_tweetnacl.default.sign.detached.verify(messageBytes, sigBytes, pkBytes);
3088
+ } catch {
3089
+ return false;
3090
+ }
3091
+ }
3092
+ function randomNonce() {
3093
+ return new Uint8Array((0, import_crypto.randomBytes)(ENCRYPTION_NONCE_BYTES));
3094
+ }
3095
+ function encryptString(plaintext, key) {
3096
+ if (key.length !== ENCRYPTION_KEY_BYTES) {
3097
+ throw new Error(`encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
3098
+ }
3099
+ const nonce = randomNonce();
3100
+ const messageBytes = Buffer.from(plaintext, "utf-8");
3101
+ const cipherBytes = import_tweetnacl.default.secretbox(messageBytes, nonce, key);
3102
+ return {
3103
+ ciphertext: Buffer.from(cipherBytes).toString("base64"),
3104
+ nonce: Buffer.from(nonce).toString("base64")
3105
+ };
3106
+ }
3107
+ function decryptString(ciphertext, nonce, key) {
3108
+ if (key.length !== ENCRYPTION_KEY_BYTES) {
3109
+ throw new Error(`encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
3110
+ }
3111
+ const cipherBytes = Buffer.from(ciphertext, "base64");
3112
+ const nonceBytes = Buffer.from(nonce, "base64");
3113
+ if (nonceBytes.length !== ENCRYPTION_NONCE_BYTES) {
3114
+ throw new Error(`nonce must be ${ENCRYPTION_NONCE_BYTES} bytes (decoded got ${nonceBytes.length})`);
3115
+ }
3116
+ const plain = import_tweetnacl.default.secretbox.open(cipherBytes, nonceBytes, key);
3117
+ if (!plain) {
3118
+ throw new Error("decryption failed (MAC mismatch \u2014 wrong key or tampered ciphertext)");
3119
+ }
3120
+ return Buffer.from(plain).toString("utf-8");
3121
+ }
3122
+ function encryptBuffer(plaintext, key) {
3123
+ if (key.length !== ENCRYPTION_KEY_BYTES) {
3124
+ throw new Error(`encryption key must be ${ENCRYPTION_KEY_BYTES} bytes`);
3125
+ }
3126
+ const nonce = randomNonce();
3127
+ const cipherBytes = import_tweetnacl.default.secretbox(plaintext, nonce, key);
3128
+ return {
3129
+ ciphertext: Buffer.from(cipherBytes),
3130
+ nonce: Buffer.from(nonce).toString("base64")
3131
+ };
3132
+ }
3133
+ var import_crypto, import_tweetnacl, bs58Module, bs58, ENCRYPTION_KEY_BYTES, ENCRYPTION_NONCE_BYTES;
2832
3134
  var init_sign = __esm({
2833
- "packages/brain/src/memorypack/sign.ts"() {
3135
+ "packages/memorypack/src/sign.ts"() {
2834
3136
  "use strict";
2835
3137
  import_crypto = require("crypto");
2836
3138
  import_tweetnacl = __toESM(require("tweetnacl"));
2837
3139
  bs58Module = __toESM(require("bs58"));
2838
3140
  bs58 = bs58Module.default || bs58Module;
3141
+ ENCRYPTION_KEY_BYTES = import_tweetnacl.default.secretbox.keyLength;
3142
+ ENCRYPTION_NONCE_BYTES = import_tweetnacl.default.secretbox.nonceLength;
2839
3143
  }
2840
3144
  });
2841
3145
 
2842
- // packages/brain/src/memorypack/writer.ts
3146
+ // packages/memorypack/src/writer.ts
2843
3147
  function serializeRecord(record) {
2844
3148
  const KNOWN_ORDER = [
2845
3149
  "id",
@@ -2857,7 +3161,9 @@ function serializeRecord(record) {
2857
3161
  "last_accessed_at",
2858
3162
  "parent_ids",
2859
3163
  "compacted_from",
2860
- "blob_ref"
3164
+ "blob_ref",
3165
+ "encrypted",
3166
+ "nonce"
2861
3167
  ];
2862
3168
  const out = {};
2863
3169
  for (const k of KNOWN_ORDER) {
@@ -2872,21 +3178,66 @@ function serializeRecord(record) {
2872
3178
  }
2873
3179
  return JSON.stringify(out);
2874
3180
  }
2875
- function writeMemoryPack(dir, records, opts, anchors) {
3181
+ function writeMemoryPack(targetPath, records, opts) {
3182
+ const format = opts.format ?? "directory";
3183
+ if (format === "tarball") {
3184
+ writeTarball(targetPath, records, opts);
3185
+ return;
3186
+ }
3187
+ writeDirectory(targetPath, records, opts);
3188
+ }
3189
+ function writeDirectory(dir, records, opts) {
3190
+ if ((0, import_fs2.existsSync)(dir)) {
3191
+ for (const f of [
3192
+ "manifest.json",
3193
+ "records.jsonl",
3194
+ "signatures.jsonl",
3195
+ "anchors.jsonl",
3196
+ "revocations.jsonl",
3197
+ "revocation_anchors.jsonl"
3198
+ ]) {
3199
+ const p = (0, import_path3.join)(dir, f);
3200
+ if ((0, import_fs2.existsSync)(p)) (0, import_fs2.rmSync)(p, { force: true });
3201
+ }
3202
+ const blobsDir = (0, import_path3.join)(dir, "blobs");
3203
+ if ((0, import_fs2.existsSync)(blobsDir)) (0, import_fs2.rmSync)(blobsDir, { recursive: true, force: true });
3204
+ }
2876
3205
  (0, import_fs2.mkdirSync)(dir, { recursive: true });
3206
+ const clock = opts.clock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3207
+ const encryption = opts.encryption ? {
3208
+ ...opts.encryption,
3209
+ scope: opts.encryption.scope ?? (opts.blobs && opts.blobs.size > 0 ? "records+blobs" : "records")
3210
+ } : void 0;
3211
+ if (encryption && encryption.key.length !== ENCRYPTION_KEY_BYTES) {
3212
+ throw new Error(`encryption.key must be ${ENCRYPTION_KEY_BYTES} bytes`);
3213
+ }
3214
+ const encryptBlobs = encryption?.scope === "records+blobs";
3215
+ const recordsToWrite = records.map((r) => {
3216
+ if (!encryption) return r;
3217
+ const { ciphertext, nonce } = encryptString(r.content, encryption.key);
3218
+ return { ...r, content: ciphertext, encrypted: true, nonce };
3219
+ });
2877
3220
  const manifest = {
2878
3221
  memorypack_version: MEMORYPACK_VERSION,
2879
3222
  producer: opts.producer,
2880
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
2881
- record_count: records.length,
3223
+ created_at: clock(),
3224
+ record_count: recordsToWrite.length,
2882
3225
  record_schema: opts.record_schema,
2883
3226
  signature_algorithm: opts.secretKey ? "ed25519" : void 0,
2884
- anchor_chain: opts.anchor_chain
3227
+ anchor_chain: opts.anchor_chain,
3228
+ pack_format: "directory",
3229
+ blobs_count: opts.blobs && opts.blobs.size > 0 ? opts.blobs.size : void 0,
3230
+ encryption: encryption ? {
3231
+ algorithm: "xsalsa20-poly1305",
3232
+ nonce_strategy: "per-record-random",
3233
+ key_derivation: "none",
3234
+ scope: encryption.scope
3235
+ } : void 0
2885
3236
  };
2886
3237
  (0, import_fs2.writeFileSync)((0, import_path3.join)(dir, "manifest.json"), JSON.stringify(manifest, null, 2));
2887
3238
  const recordLines = [];
2888
3239
  const sigs = [];
2889
- for (const record of records) {
3240
+ for (const record of recordsToWrite) {
2890
3241
  const line = serializeRecord(record);
2891
3242
  recordLines.push(line);
2892
3243
  if (opts.secretKey && opts.producer.public_key) {
@@ -2907,26 +3258,124 @@ function writeMemoryPack(dir, records, opts, anchors) {
2907
3258
  sigs.map((s) => JSON.stringify(s)).join("\n") + "\n"
2908
3259
  );
2909
3260
  }
2910
- if (anchors && anchors.length > 0) {
3261
+ if (opts.anchors && opts.anchors.length > 0) {
2911
3262
  (0, import_fs2.writeFileSync)(
2912
3263
  (0, import_path3.join)(dir, "anchors.jsonl"),
2913
- anchors.map((a) => JSON.stringify(a)).join("\n") + "\n"
3264
+ opts.anchors.map((a) => JSON.stringify(a)).join("\n") + "\n"
2914
3265
  );
2915
3266
  }
3267
+ if (opts.blobs && opts.blobs.size > 0) {
3268
+ const blobsDir = (0, import_path3.join)(dir, "blobs", "sha256");
3269
+ (0, import_fs2.mkdirSync)(blobsDir, { recursive: true });
3270
+ const indexEntries = [];
3271
+ for (const [declaredHash, blob] of opts.blobs.entries()) {
3272
+ const dataBuf = Buffer.from(blob.data);
3273
+ const computedPlainHash = hashBuffer(dataBuf);
3274
+ if (computedPlainHash !== declaredHash) {
3275
+ throw new Error(
3276
+ `MemoryPack: blob key '${declaredHash}' does not match sha256 of data (computed ${computedPlainHash})`
3277
+ );
3278
+ }
3279
+ let storedBytes;
3280
+ let entry;
3281
+ if (encryptBlobs && encryption) {
3282
+ const { ciphertext, nonce } = encryptBuffer(dataBuf, encryption.key);
3283
+ storedBytes = ciphertext;
3284
+ const storedHash = hashBuffer(ciphertext);
3285
+ entry = {
3286
+ hash: storedHash,
3287
+ byte_size: ciphertext.length,
3288
+ encrypted: true,
3289
+ nonce
3290
+ // filename + content_type intentionally omitted under records+blobs
3291
+ // encryption to avoid leaking attachment metadata in cleartext.
3292
+ };
3293
+ } else {
3294
+ storedBytes = dataBuf;
3295
+ entry = {
3296
+ hash: declaredHash,
3297
+ byte_size: dataBuf.length,
3298
+ content_type: blob.content_type,
3299
+ filename: blob.filename
3300
+ };
3301
+ }
3302
+ const hex = entry.hash.replace(/^sha256:/, "");
3303
+ (0, import_fs2.writeFileSync)((0, import_path3.join)(blobsDir, hex), storedBytes);
3304
+ indexEntries.push(entry);
3305
+ }
3306
+ (0, import_fs2.writeFileSync)(
3307
+ (0, import_path3.join)(dir, "blobs", "index.jsonl"),
3308
+ indexEntries.map((e) => JSON.stringify(e)).join("\n") + "\n"
3309
+ );
3310
+ }
3311
+ }
3312
+ function writeTarball(targetPath, records, opts) {
3313
+ const targetAbs = (0, import_path3.resolve)(targetPath);
3314
+ const parent = (0, import_path3.dirname)(targetAbs);
3315
+ const innerName = (0, import_path3.basename)(targetAbs).replace(/\.tar\.zst$/i, "") || "memorypack";
3316
+ const tmpRoot = `${targetAbs}.tmp-${process.pid}-${Date.now()}`;
3317
+ const stagingDir = (0, import_path3.join)(tmpRoot, innerName);
3318
+ (0, import_fs2.mkdirSync)(stagingDir, { recursive: true });
3319
+ try {
3320
+ writeDirectory(stagingDir, records, opts);
3321
+ const manifestPath = (0, import_path3.join)(stagingDir, "manifest.json");
3322
+ const manifest = JSON.parse(
3323
+ require("fs").readFileSync(manifestPath, "utf-8")
3324
+ );
3325
+ manifest.pack_format = "tarball";
3326
+ (0, import_fs2.writeFileSync)(manifestPath, JSON.stringify(manifest, null, 2));
3327
+ const res = (0, import_child_process2.spawnSync)(
3328
+ "tar",
3329
+ ["--zstd", "-cf", targetAbs, "-C", tmpRoot, innerName],
3330
+ { stdio: ["ignore", "ignore", "pipe"] }
3331
+ );
3332
+ if (res.error) {
3333
+ throw new Error(
3334
+ `MemoryPack: failed to invoke 'tar' (${res.error.message}). Install a recent tar with zstd support.`
3335
+ );
3336
+ }
3337
+ if (res.status !== 0) {
3338
+ const stderr = res.stderr ? res.stderr.toString() : "";
3339
+ throw new Error(
3340
+ `MemoryPack: tar --zstd failed (exit ${res.status}): ${stderr.trim() || "no stderr"}`
3341
+ );
3342
+ }
3343
+ if (!(0, import_fs2.existsSync)(targetAbs) || (0, import_fs2.statSync)(targetAbs).size === 0) {
3344
+ throw new Error("MemoryPack: tar produced an empty archive");
3345
+ }
3346
+ } finally {
3347
+ (0, import_fs2.rmSync)(tmpRoot, { recursive: true, force: true });
3348
+ }
2916
3349
  }
2917
- var import_fs2, import_path3;
3350
+ var import_fs2, import_child_process2, import_path3;
2918
3351
  var init_writer = __esm({
2919
- "packages/brain/src/memorypack/writer.ts"() {
3352
+ "packages/memorypack/src/writer.ts"() {
2920
3353
  "use strict";
2921
3354
  import_fs2 = require("fs");
3355
+ import_child_process2 = require("child_process");
2922
3356
  import_path3 = require("path");
2923
3357
  init_types();
2924
3358
  init_sign();
2925
3359
  }
2926
3360
  });
2927
3361
 
2928
- // packages/brain/src/memorypack/reader.ts
2929
- function readMemoryPack(dir, opts = {}) {
3362
+ // packages/memorypack/src/reader.ts
3363
+ function readMemoryPack(path9, opts = {}) {
3364
+ const isTarball = /\.tar\.zst$/i.test(path9) || (0, import_fs3.existsSync)(path9) && (0, import_fs3.statSync)(path9).isFile();
3365
+ if (!isTarball) {
3366
+ return readDirectory(path9, opts);
3367
+ }
3368
+ let extractRoot = null;
3369
+ try {
3370
+ extractRoot = extractTarball(path9);
3371
+ return readDirectory(extractRoot, opts);
3372
+ } finally {
3373
+ if (extractRoot) {
3374
+ (0, import_fs3.rmSync)(extractRoot, { recursive: true, force: true });
3375
+ }
3376
+ }
3377
+ }
3378
+ function readDirectory(dir, opts) {
2930
3379
  const warnings = [];
2931
3380
  const manifestPath = (0, import_path4.join)(dir, "manifest.json");
2932
3381
  if (!(0, import_fs3.existsSync)(manifestPath)) {
@@ -2954,12 +3403,14 @@ function readMemoryPack(dir, opts = {}) {
2954
3403
  lineByHash.set(hashRecordLine(line), line);
2955
3404
  }
2956
3405
  if (records.length !== manifest.record_count) {
2957
- warnings.push(`record_count mismatch: manifest ${manifest.record_count} vs jsonl ${records.length}`);
3406
+ warnings.push(
3407
+ `record_count mismatch: manifest ${manifest.record_count} vs jsonl ${records.length}`
3408
+ );
2958
3409
  }
2959
3410
  const verifiedRecords = /* @__PURE__ */ new Set();
2960
3411
  const sigsPath = (0, import_path4.join)(dir, "signatures.jsonl");
2961
- const signaturesFilePresent = (0, import_fs3.existsSync)(sigsPath);
2962
- if (signaturesFilePresent) {
3412
+ const signaturesPresent = (0, import_fs3.existsSync)(sigsPath);
3413
+ if (signaturesPresent) {
2963
3414
  const sigLines = (0, import_fs3.readFileSync)(sigsPath, "utf-8").split("\n").filter((l) => l.length > 0);
2964
3415
  const pubkey = opts.publicKey ?? manifest.producer.public_key;
2965
3416
  if (!pubkey) {
@@ -2984,7 +3435,7 @@ function readMemoryPack(dir, opts = {}) {
2984
3435
  }
2985
3436
  }
2986
3437
  const unsignedRecords = /* @__PURE__ */ new Set();
2987
- if (!signaturesFilePresent) {
3438
+ if (!signaturesPresent) {
2988
3439
  for (const hash of lineByHash.keys()) unsignedRecords.add(hash);
2989
3440
  }
2990
3441
  if (opts.strictSignatures && unsignedRecords.size > 0) {
@@ -3000,25 +3451,404 @@ function readMemoryPack(dir, opts = {}) {
3000
3451
  anchors.push(JSON.parse(line));
3001
3452
  }
3002
3453
  }
3003
- return { manifest, records, verifiedRecords, unsignedRecords, anchors, warnings };
3454
+ const revocations = [];
3455
+ const revokedRecordHashes = /* @__PURE__ */ new Set();
3456
+ const revocationsPath = (0, import_path4.join)(dir, "revocations.jsonl");
3457
+ if ((0, import_fs3.existsSync)(revocationsPath)) {
3458
+ const expectedSigner = opts.publicKey ?? manifest.producer.public_key;
3459
+ const revLines = (0, import_fs3.readFileSync)(revocationsPath, "utf-8").split("\n").filter((l) => l.length > 0);
3460
+ for (const line of revLines) {
3461
+ let entry;
3462
+ try {
3463
+ entry = JSON.parse(line);
3464
+ } catch {
3465
+ warnings.push(`revocations.jsonl: skipping malformed line`);
3466
+ continue;
3467
+ }
3468
+ if (expectedSigner && entry.public_key !== expectedSigner) {
3469
+ warnings.push(
3470
+ `revocation for ${entry.record_hash} signed by unexpected key ${entry.public_key} \u2014 rejected`
3471
+ );
3472
+ continue;
3473
+ }
3474
+ const ok = verifyRevocation(
3475
+ entry.record_hash,
3476
+ entry.revoked_at,
3477
+ entry.signature,
3478
+ entry.public_key
3479
+ );
3480
+ if (!ok) {
3481
+ warnings.push(
3482
+ `revocation for ${entry.record_hash} signature failed verification \u2014 rejected`
3483
+ );
3484
+ continue;
3485
+ }
3486
+ revocations.push(entry);
3487
+ revokedRecordHashes.add(entry.record_hash);
3488
+ }
3489
+ }
3490
+ const revocationAnchors = [];
3491
+ const revAnchorsPath = (0, import_path4.join)(dir, "revocation_anchors.jsonl");
3492
+ if ((0, import_fs3.existsSync)(revAnchorsPath)) {
3493
+ const validPairs = new Set(
3494
+ revocations.map((r) => `${r.record_hash}@${r.revoked_at}`)
3495
+ );
3496
+ const anchorLines = (0, import_fs3.readFileSync)(revAnchorsPath, "utf-8").split("\n").filter((l) => l.length > 0);
3497
+ for (const line of anchorLines) {
3498
+ let entry;
3499
+ try {
3500
+ entry = JSON.parse(line);
3501
+ } catch {
3502
+ warnings.push("revocation_anchors.jsonl: skipping malformed line");
3503
+ continue;
3504
+ }
3505
+ if (entry.anchor_format !== "memo-revoke-v1") {
3506
+ warnings.push(
3507
+ `revocation anchor ${entry.tx} has unsupported format '${entry.anchor_format}' \u2014 skipped`
3508
+ );
3509
+ continue;
3510
+ }
3511
+ const pair = `${entry.record_hash}@${entry.revoked_at}`;
3512
+ if (!validPairs.has(pair)) {
3513
+ warnings.push(
3514
+ `revocation anchor ${entry.tx} pair (${entry.record_hash}, ${entry.revoked_at}) does not match any verified revocation \u2014 skipped`
3515
+ );
3516
+ continue;
3517
+ }
3518
+ revocationAnchors.push(entry);
3519
+ }
3520
+ }
3521
+ const verifiedBlobs = /* @__PURE__ */ new Set();
3522
+ const blobsDir = (0, import_path4.join)(dir, "blobs", "sha256");
3523
+ const blobIndexPath = (0, import_path4.join)(dir, "blobs", "index.jsonl");
3524
+ const blobIndexEntries = /* @__PURE__ */ new Map();
3525
+ if ((0, import_fs3.existsSync)(blobIndexPath)) {
3526
+ const indexLines = (0, import_fs3.readFileSync)(blobIndexPath, "utf-8").split("\n").filter((l) => l.length > 0);
3527
+ for (const line of indexLines) {
3528
+ const entry = JSON.parse(line);
3529
+ blobIndexEntries.set(entry.hash, entry);
3530
+ const hex = entry.hash.replace(/^sha256:/, "");
3531
+ const blobPath = (0, import_path4.join)(blobsDir, hex);
3532
+ if (!(0, import_fs3.existsSync)(blobPath)) {
3533
+ warnings.push(`declared blob ${entry.hash} missing from blobs/`);
3534
+ continue;
3535
+ }
3536
+ const data = (0, import_fs3.readFileSync)(blobPath);
3537
+ const computed = hashBuffer(data);
3538
+ if (computed !== entry.hash) {
3539
+ throw new Error(
3540
+ `MemoryPack: blob hash mismatch \u2014 declared ${entry.hash}, computed ${computed}`
3541
+ );
3542
+ }
3543
+ const actualSize = (0, import_fs3.statSync)(blobPath).size;
3544
+ if (entry.byte_size !== actualSize) {
3545
+ warnings.push(
3546
+ `blob ${entry.hash} byte_size mismatch: declared ${entry.byte_size}, actual ${actualSize}`
3547
+ );
3548
+ }
3549
+ verifiedBlobs.add(entry.hash);
3550
+ }
3551
+ }
3552
+ for (const r of records) {
3553
+ if (r.blob_ref && !verifiedBlobs.has(r.blob_ref)) {
3554
+ const reason = blobIndexEntries.size === 0 ? "no blobs/index.jsonl in pack" : "not present or not verified";
3555
+ warnings.push(
3556
+ `record ${r.id} references blob ${r.blob_ref} (${reason})`
3557
+ );
3558
+ }
3559
+ }
3560
+ if (manifest.encryption) {
3561
+ if (!opts.decryptionKey) {
3562
+ warnings.push(
3563
+ `manifest declares encryption (${manifest.encryption.algorithm}, scope=${manifest.encryption.scope}) but no decryptionKey supplied \u2014 content remains ciphertext`
3564
+ );
3565
+ } else if (opts.decryptionKey.length !== ENCRYPTION_KEY_BYTES) {
3566
+ throw new Error(
3567
+ `MemoryPack: decryptionKey must be ${ENCRYPTION_KEY_BYTES} bytes (got ${opts.decryptionKey.length})`
3568
+ );
3569
+ } else {
3570
+ for (const r of records) {
3571
+ if (!r.encrypted) continue;
3572
+ if (!r.nonce) {
3573
+ warnings.push(`record ${r.id} marked encrypted but has no nonce \u2014 leaving ciphertext`);
3574
+ continue;
3575
+ }
3576
+ try {
3577
+ r.content = decryptString(r.content, r.nonce, opts.decryptionKey);
3578
+ r.encrypted = false;
3579
+ delete r.nonce;
3580
+ } catch (e) {
3581
+ warnings.push(
3582
+ `record ${r.id} decryption failed: ${e.message}`
3583
+ );
3584
+ }
3585
+ }
3586
+ }
3587
+ }
3588
+ const minimalRecords = records.filter((r) => !r.encrypted).map((r) => ({
3589
+ id: r.id,
3590
+ created_at: r.created_at,
3591
+ kind: r.kind,
3592
+ content: r.content,
3593
+ tags: r.tags,
3594
+ importance: r.importance,
3595
+ source: r.source,
3596
+ encrypted: r.encrypted
3597
+ }));
3598
+ return {
3599
+ manifest,
3600
+ records,
3601
+ verifiedRecords,
3602
+ unsignedRecords,
3603
+ anchors,
3604
+ verifiedBlobs,
3605
+ verifiedAnchors: /* @__PURE__ */ new Set(),
3606
+ revocations,
3607
+ revokedRecordHashes,
3608
+ revocationAnchors,
3609
+ verifiedRevocationAnchors: /* @__PURE__ */ new Set(),
3610
+ minimalRecords,
3611
+ warnings
3612
+ };
3613
+ }
3614
+ function extractTarball(path9) {
3615
+ if (!(0, import_fs3.existsSync)(path9)) {
3616
+ throw new Error(`MemoryPack: tarball not found: ${path9}`);
3617
+ }
3618
+ const list = (0, import_child_process3.spawnSync)("tar", ["--zstd", "-tvf", path9], {
3619
+ stdio: ["ignore", "pipe", "pipe"]
3620
+ });
3621
+ if (list.error) {
3622
+ throw new Error(
3623
+ `MemoryPack: failed to invoke 'tar' (${list.error.message}). Install a recent tar with zstd support.`
3624
+ );
3625
+ }
3626
+ if (list.status !== 0) {
3627
+ const stderr = list.stderr ? list.stderr.toString() : "";
3628
+ throw new Error(
3629
+ `MemoryPack: tar --zstd -tvf failed (exit ${list.status}): ${stderr.trim() || "no stderr"}`
3630
+ );
3631
+ }
3632
+ const lines = list.stdout.toString().split("\n").filter((l) => l.length > 0);
3633
+ for (const line of lines) {
3634
+ const typeChar = line.charAt(0);
3635
+ if (typeChar === "l" || typeChar === "h") {
3636
+ throw new Error(
3637
+ `MemoryPack: tarball contains symlink/hardlink \u2014 refusing to extract (${line.slice(0, 80)})`
3638
+ );
3639
+ }
3640
+ const name = line.split(/\s+/).pop() ?? "";
3641
+ if (!name) continue;
3642
+ if (name.startsWith("/")) {
3643
+ throw new Error(`MemoryPack: tarball contains absolute path \u2014 refusing (${name})`);
3644
+ }
3645
+ if (name.split("/").some((seg) => seg === "..")) {
3646
+ throw new Error(`MemoryPack: tarball contains '..' segment \u2014 refusing (${name})`);
3647
+ }
3648
+ if (!SAFE_MEMBER_RE.test(name.replace(/\/$/, ""))) {
3649
+ throw new Error(`MemoryPack: tarball member has unsafe characters \u2014 refusing (${name})`);
3650
+ }
3651
+ }
3652
+ const tmp = (0, import_fs3.mkdtempSync)((0, import_path4.join)((0, import_os.tmpdir)(), "mp-extract-"));
3653
+ const extract = (0, import_child_process3.spawnSync)(
3654
+ "tar",
3655
+ [
3656
+ "--zstd",
3657
+ "--no-same-owner",
3658
+ "--no-same-permissions",
3659
+ "-xf",
3660
+ path9,
3661
+ "-C",
3662
+ tmp
3663
+ ],
3664
+ { stdio: ["ignore", "ignore", "pipe"] }
3665
+ );
3666
+ if (extract.status !== 0) {
3667
+ (0, import_fs3.rmSync)(tmp, { recursive: true, force: true });
3668
+ const stderr = extract.stderr ? extract.stderr.toString() : "";
3669
+ throw new Error(
3670
+ `MemoryPack: tar --zstd -xf failed (exit ${extract.status}): ${stderr.trim() || "no stderr"}`
3671
+ );
3672
+ }
3673
+ if ((0, import_fs3.existsSync)((0, import_path4.join)(tmp, "manifest.json"))) {
3674
+ return tmp;
3675
+ }
3676
+ const fs7 = require("fs");
3677
+ const entries = fs7.readdirSync(tmp);
3678
+ if (entries.length === 1) {
3679
+ const inner = (0, import_path4.join)(tmp, entries[0]);
3680
+ if ((0, import_fs3.statSync)(inner).isDirectory() && (0, import_fs3.existsSync)((0, import_path4.join)(inner, "manifest.json"))) {
3681
+ return inner;
3682
+ }
3683
+ }
3684
+ (0, import_fs3.rmSync)(tmp, { recursive: true, force: true });
3685
+ throw new Error(
3686
+ `MemoryPack: tarball must contain manifest.json at the root or inside a single wrapping directory`
3687
+ );
3004
3688
  }
3005
- var import_fs3, import_path4;
3689
+ var import_fs3, import_child_process3, import_os, import_path4, SAFE_MEMBER_RE;
3006
3690
  var init_reader = __esm({
3007
- "packages/brain/src/memorypack/reader.ts"() {
3691
+ "packages/memorypack/src/reader.ts"() {
3008
3692
  "use strict";
3009
3693
  import_fs3 = require("fs");
3694
+ import_child_process3 = require("child_process");
3695
+ import_os = require("os");
3010
3696
  import_path4 = require("path");
3011
3697
  init_sign();
3698
+ SAFE_MEMBER_RE = /^[A-Za-z0-9._/-]+$/;
3012
3699
  }
3013
3700
  });
3014
3701
 
3015
- // packages/brain/src/memorypack/index.ts
3016
- var init_memorypack = __esm({
3017
- "packages/brain/src/memorypack/index.ts"() {
3702
+ // packages/memorypack/src/stream.ts
3703
+ var init_stream = __esm({
3704
+ "packages/memorypack/src/stream.ts"() {
3705
+ "use strict";
3706
+ init_sign();
3707
+ }
3708
+ });
3709
+
3710
+ // packages/memorypack/src/chain-verify.ts
3711
+ async function verifyChainAnchors(anchors, opts) {
3712
+ const verified = /* @__PURE__ */ new Set();
3713
+ const warnings = [];
3714
+ if (anchors.length === 0) return { verified, warnings };
3715
+ const conn = new import_web3.Connection(opts.rpcUrl, { commitment: "confirmed" });
3716
+ if (opts.cluster) {
3717
+ try {
3718
+ const genesis = await conn.getGenesisHash();
3719
+ const expected = GENESIS_HASHES[opts.cluster];
3720
+ if (!expected) {
3721
+ const msg = `unknown cluster '${opts.cluster}' \u2014 cannot cross-check genesis`;
3722
+ if (opts.strict) throw new Error(msg);
3723
+ warnings.push(msg);
3724
+ } else if (genesis !== expected) {
3725
+ const msg = `cluster mismatch: rpc reports genesis ${genesis}, expected ${expected} for ${opts.cluster}`;
3726
+ if (opts.strict) throw new Error(msg);
3727
+ warnings.push(msg);
3728
+ return { verified, warnings };
3729
+ }
3730
+ } catch (e) {
3731
+ const msg = `getGenesisHash failed: ${e.message}`;
3732
+ if (opts.strict) throw new Error(msg);
3733
+ warnings.push(msg);
3734
+ }
3735
+ }
3736
+ for (const anchor of anchors) {
3737
+ try {
3738
+ await verifyOne(anchor, conn, opts, verified);
3739
+ } catch (e) {
3740
+ const msg = `${anchor.tx} \u2192 ${e.message}`;
3741
+ if (opts.strict) throw new Error(msg);
3742
+ warnings.push(msg);
3743
+ }
3744
+ }
3745
+ return { verified, warnings };
3746
+ }
3747
+ async function verifyOne(anchor, conn, opts, verified) {
3748
+ if (anchor.anchor_format !== "memo-v1") {
3749
+ throw new Error(`unsupported anchor_format '${anchor.anchor_format}'`);
3750
+ }
3751
+ const cfg = {
3752
+ commitment: "confirmed",
3753
+ maxSupportedTransactionVersion: 0
3754
+ };
3755
+ const tx = await conn.getTransaction(anchor.tx, cfg);
3756
+ if (!tx) {
3757
+ throw new Error("transaction not found");
3758
+ }
3759
+ const message = tx.transaction.message;
3760
+ let accountKeys;
3761
+ if (Array.isArray(message.accountKeys)) {
3762
+ accountKeys = message.accountKeys;
3763
+ } else if (typeof message.getAccountKeys === "function") {
3764
+ accountKeys = message.getAccountKeys({
3765
+ accountKeysFromLookups: tx.meta?.loadedAddresses
3766
+ }).staticAccountKeys;
3767
+ } else {
3768
+ throw new Error("cannot resolve account keys from transaction message");
3769
+ }
3770
+ const numSigners = message.header.numRequiredSignatures;
3771
+ const signerKeys = accountKeys.slice(0, numSigners).map((k) => k.toBase58());
3772
+ const expectedMemo = expectedMemoForRecordHash(anchor.record_hash);
3773
+ const instructions = message.compiledInstructions ?? message.instructions ?? [];
3774
+ let memoInstructionFound = false;
3775
+ let memoMatched = false;
3776
+ for (const ix of instructions) {
3777
+ const programId = accountKeys[ix.programIdIndex]?.toBase58();
3778
+ if (!programId || !MEMO_PROGRAM_IDS.has(programId)) continue;
3779
+ memoInstructionFound = true;
3780
+ const dataStr = decodeMemoInstructionData(ix.data);
3781
+ if (dataStr === expectedMemo) {
3782
+ memoMatched = true;
3783
+ break;
3784
+ }
3785
+ }
3786
+ if (!memoInstructionFound) {
3787
+ throw new Error("no SPL Memo program instruction in transaction");
3788
+ }
3789
+ if (!memoMatched) {
3790
+ throw new Error(`no Memo instruction with data exactly equal to "${expectedMemo}"`);
3791
+ }
3792
+ if (opts.expectedSigner) {
3793
+ if (!signerKeys.includes(opts.expectedSigner)) {
3794
+ throw new Error(
3795
+ `expectedSigner ${opts.expectedSigner} is not among tx signers [${signerKeys.join(", ")}]`
3796
+ );
3797
+ }
3798
+ }
3799
+ verified.add(anchor.record_hash);
3800
+ }
3801
+ function expectedMemoForRecordHash(recordHash) {
3802
+ return `clude:v1:${recordHash}`;
3803
+ }
3804
+ function decodeMemoInstructionData(data) {
3805
+ if (typeof data === "string") {
3806
+ const bs583 = loadBs58();
3807
+ const bytes = bs583.decode(data);
3808
+ return Buffer.from(bytes).toString("utf-8");
3809
+ }
3810
+ if (data instanceof Uint8Array || Buffer.isBuffer(data)) {
3811
+ return Buffer.from(data).toString("utf-8");
3812
+ }
3813
+ if (data && typeof data === "object" && "type" in data && "data" in data) {
3814
+ return Buffer.from(data.data).toString("utf-8");
3815
+ }
3816
+ throw new Error(`unrecognized instruction data shape: ${typeof data}`);
3817
+ }
3818
+ function loadBs58() {
3819
+ if (_bs58) return _bs58;
3820
+ const mod = require("bs58");
3821
+ _bs58 = mod.default || mod;
3822
+ return _bs58;
3823
+ }
3824
+ var import_web3, MEMO_PROGRAM_IDS, GENESIS_HASHES, _bs58;
3825
+ var init_chain_verify = __esm({
3826
+ "packages/memorypack/src/chain-verify.ts"() {
3827
+ "use strict";
3828
+ import_web3 = require("@solana/web3.js");
3829
+ MEMO_PROGRAM_IDS = /* @__PURE__ */ new Set([
3830
+ "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr",
3831
+ "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
3832
+ ]);
3833
+ GENESIS_HASHES = {
3834
+ mainnet: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
3835
+ "mainnet-beta": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
3836
+ devnet: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
3837
+ testnet: "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY"
3838
+ };
3839
+ _bs58 = null;
3840
+ }
3841
+ });
3842
+
3843
+ // packages/memorypack/src/index.ts
3844
+ var init_src = __esm({
3845
+ "packages/memorypack/src/index.ts"() {
3018
3846
  "use strict";
3019
3847
  init_types();
3020
3848
  init_writer();
3021
3849
  init_reader();
3850
+ init_stream();
3851
+ init_chain_verify();
3022
3852
  init_sign();
3023
3853
  }
3024
3854
  });
@@ -3386,13 +4216,18 @@ async function initDatabase2() {
3386
4216
  is_active BOOLEAN DEFAULT TRUE,
3387
4217
  metadata JSONB DEFAULT '{}',
3388
4218
  owner_wallet TEXT,
3389
- privy_did TEXT
4219
+ privy_did TEXT,
4220
+ email TEXT
3390
4221
  );
3391
4222
 
4223
+ -- Backfill: older deployments may have agent_keys without the email column.
4224
+ ALTER TABLE agent_keys ADD COLUMN IF NOT EXISTS email TEXT;
4225
+
3392
4226
  CREATE INDEX IF NOT EXISTS idx_agent_keys_api_key ON agent_keys(api_key);
3393
4227
  CREATE INDEX IF NOT EXISTS idx_agent_keys_owner ON agent_keys(owner_wallet);
3394
4228
  CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_owner_unique ON agent_keys(owner_wallet) WHERE owner_wallet IS NOT NULL;
3395
4229
  CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_privy_did ON agent_keys(privy_did) WHERE privy_did IS NOT NULL;
4230
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_keys_email ON agent_keys(email) WHERE email IS NOT NULL AND is_active = true;
3396
4231
 
3397
4232
  -- Cortex recall performance: owner_wallet scoped queries
3398
4233
  CREATE INDEX IF NOT EXISTS idx_cortex_owner_recall ON memories(owner_wallet, decay_factor DESC, created_at DESC);
@@ -3826,6 +4661,19 @@ async function initDatabase2() {
3826
4661
  created_at TIMESTAMPTZ DEFAULT NOW()
3827
4662
  );
3828
4663
  CREATE INDEX IF NOT EXISTS idx_chat_usage_wallet ON chat_usage(wallet_address, created_at DESC);
4664
+
4665
+ -- Wiki pack installations: which packs (Workspace, Compliance, Sales)
4666
+ -- each wallet has installed. Drives the topic rail in /wiki and
4667
+ -- the auto-categorisation rules applied to incoming memories.
4668
+ CREATE TABLE IF NOT EXISTS wiki_pack_installations (
4669
+ id BIGSERIAL PRIMARY KEY,
4670
+ owner_wallet TEXT NOT NULL,
4671
+ pack_id TEXT NOT NULL,
4672
+ installed_at TIMESTAMPTZ DEFAULT NOW(),
4673
+ UNIQUE (owner_wallet, pack_id)
4674
+ );
4675
+ CREATE INDEX IF NOT EXISTS idx_wiki_pack_installations_owner
4676
+ ON wiki_pack_installations(owner_wallet);
3829
4677
  `
3830
4678
  });
3831
4679
  if (error) {
@@ -4110,11 +4958,13 @@ var init_openrouter_client = __esm({
4110
4958
  OPENROUTER_API_URL = "https://openrouter.ai/api/v1";
4111
4959
  OPENROUTER_MODELS = {
4112
4960
  // Frontier (Anthropic)
4961
+ "claude-opus-4.7": "anthropic/claude-opus-4.7",
4113
4962
  "claude-opus-4.6": "anthropic/claude-opus-4.6",
4114
4963
  "claude-sonnet-4.6": "anthropic/claude-sonnet-4.6",
4115
4964
  "claude-opus-4.5": "anthropic/claude-opus-4.5",
4116
4965
  "claude-sonnet-4.5": "anthropic/claude-sonnet-4.5",
4117
4966
  // Frontier (Other providers)
4967
+ "gpt-5.5": "openai/gpt-5.5",
4118
4968
  "gpt-5.4": "openai/gpt-5.4",
4119
4969
  "grok-4.1": "x-ai/grok-4.1-fast",
4120
4970
  "gemini-3-pro": "google/gemini-3-pro-preview",
@@ -4236,12 +5086,12 @@ __export(solana_client_exports, {
4236
5086
  });
4237
5087
  function getConnection() {
4238
5088
  if (!connection) {
4239
- connection = new import_web3.Connection(config.solana.rpcUrl, "confirmed");
5089
+ connection = new import_web32.Connection(config.solana.rpcUrl, "confirmed");
4240
5090
  }
4241
5091
  return connection;
4242
5092
  }
4243
5093
  function _configureSolana(rpcUrl, privateKey) {
4244
- connection = new import_web3.Connection(rpcUrl, "confirmed");
5094
+ connection = new import_web32.Connection(rpcUrl, "confirmed");
4245
5095
  if (privateKey) {
4246
5096
  try {
4247
5097
  const raw = privateKey.trim();
@@ -4251,7 +5101,7 @@ function _configureSolana(rpcUrl, privateKey) {
4251
5101
  } else {
4252
5102
  secretKey = bs582.decode(raw);
4253
5103
  }
4254
- botWallet = import_web3.Keypair.fromSecretKey(secretKey);
5104
+ botWallet = import_web32.Keypair.fromSecretKey(secretKey);
4255
5105
  } catch (err) {
4256
5106
  log6.error({ err }, "SDK: Failed to load bot wallet");
4257
5107
  }
@@ -4267,7 +5117,7 @@ function getBotWallet() {
4267
5117
  } else {
4268
5118
  secretKey = bs582.decode(raw);
4269
5119
  }
4270
- botWallet = import_web3.Keypair.fromSecretKey(secretKey);
5120
+ botWallet = import_web32.Keypair.fromSecretKey(secretKey);
4271
5121
  log6.info({ publicKey: botWallet.publicKey.toBase58() }, "Bot wallet loaded");
4272
5122
  } catch (err) {
4273
5123
  log6.error({ err }, "Failed to load bot wallet");
@@ -4283,14 +5133,14 @@ async function writeMemo(memo) {
4283
5133
  }
4284
5134
  const conn = getConnection();
4285
5135
  const truncatedMemo = memo.slice(0, MEMO_MAX_LENGTH);
4286
- const instruction = new import_web3.TransactionInstruction({
5136
+ const instruction = new import_web32.TransactionInstruction({
4287
5137
  keys: [{ pubkey: wallet.publicKey, isSigner: true, isWritable: true }],
4288
5138
  programId: MEMO_PROGRAM_ID2,
4289
5139
  data: Buffer.from(truncatedMemo, "utf-8")
4290
5140
  });
4291
- const transaction = new import_web3.Transaction().add(instruction);
5141
+ const transaction = new import_web32.Transaction().add(instruction);
4292
5142
  try {
4293
- const signature = await (0, import_web3.sendAndConfirmTransaction)(conn, transaction, [wallet]);
5143
+ const signature = await (0, import_web32.sendAndConfirmTransaction)(conn, transaction, [wallet]);
4294
5144
  log6.info({ signature, memoLength: truncatedMemo.length }, "Memo written on-chain");
4295
5145
  return signature;
4296
5146
  } catch (err) {
@@ -4300,7 +5150,7 @@ async function writeMemo(memo) {
4300
5150
  }
4301
5151
  function getDevnetConnection() {
4302
5152
  if (!devnetConnection) {
4303
- devnetConnection = new import_web3.Connection("https://api.devnet.solana.com", "confirmed");
5153
+ devnetConnection = new import_web32.Connection("https://api.devnet.solana.com", "confirmed");
4304
5154
  }
4305
5155
  return devnetConnection;
4306
5156
  }
@@ -4312,14 +5162,14 @@ async function writeMemoDevnet(memo) {
4312
5162
  }
4313
5163
  const conn = getDevnetConnection();
4314
5164
  const truncatedMemo = memo.slice(0, MEMO_MAX_LENGTH);
4315
- const instruction = new import_web3.TransactionInstruction({
5165
+ const instruction = new import_web32.TransactionInstruction({
4316
5166
  keys: [{ pubkey: wallet.publicKey, isSigner: true, isWritable: true }],
4317
5167
  programId: MEMO_PROGRAM_ID2,
4318
5168
  data: Buffer.from(truncatedMemo, "utf-8")
4319
5169
  });
4320
- const transaction = new import_web3.Transaction().add(instruction);
5170
+ const transaction = new import_web32.Transaction().add(instruction);
4321
5171
  try {
4322
- const signature = await (0, import_web3.sendAndConfirmTransaction)(conn, transaction, [wallet]);
5172
+ const signature = await (0, import_web32.sendAndConfirmTransaction)(conn, transaction, [wallet]);
4323
5173
  log6.info({ signature, memoLength: truncatedMemo.length }, "Memo written on devnet");
4324
5174
  return signature;
4325
5175
  } catch (err) {
@@ -4336,7 +5186,7 @@ function solscanTxUrl(signature) {
4336
5186
  }
4337
5187
  function _configureMemoryRegistry(programId) {
4338
5188
  try {
4339
- registryProgramId = new import_web3.PublicKey(programId);
5189
+ registryProgramId = new import_web32.PublicKey(programId);
4340
5190
  log6.info({ programId: programId.slice(0, 12) + "..." }, "Memory registry program configured");
4341
5191
  } catch (err) {
4342
5192
  log6.error({ err }, "Invalid memory registry program ID");
@@ -4347,14 +5197,14 @@ function isRegistryEnabled() {
4347
5197
  }
4348
5198
  function deriveRegistryPDA(authority) {
4349
5199
  if (!registryProgramId) throw new Error("Registry program not configured");
4350
- return import_web3.PublicKey.findProgramAddressSync(
5200
+ return import_web32.PublicKey.findProgramAddressSync(
4351
5201
  [Buffer.from("memory-registry"), authority.toBuffer()],
4352
5202
  registryProgramId
4353
5203
  );
4354
5204
  }
4355
5205
  function anchorDiscriminator(name) {
4356
- const { createHash: createHash4 } = require("crypto");
4357
- const hash = createHash4("sha256").update(`global:${name}`).digest();
5206
+ const { createHash: createHash5 } = require("crypto");
5207
+ const hash = createHash5("sha256").update(`global:${name}`).digest();
4358
5208
  return hash.subarray(0, 8);
4359
5209
  }
4360
5210
  async function initializeRegistry() {
@@ -4370,18 +5220,18 @@ async function initializeRegistry() {
4370
5220
  return;
4371
5221
  }
4372
5222
  const discriminator = anchorDiscriminator("initialize");
4373
- const instruction = new import_web3.TransactionInstruction({
5223
+ const instruction = new import_web32.TransactionInstruction({
4374
5224
  keys: [
4375
5225
  { pubkey: registryPDA, isSigner: false, isWritable: true },
4376
5226
  { pubkey: wallet.publicKey, isSigner: true, isWritable: true },
4377
- { pubkey: import_web3.SystemProgram.programId, isSigner: false, isWritable: false }
5227
+ { pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false }
4378
5228
  ],
4379
5229
  programId: registryProgramId,
4380
5230
  data: discriminator
4381
5231
  });
4382
- const transaction = new import_web3.Transaction().add(instruction);
5232
+ const transaction = new import_web32.Transaction().add(instruction);
4383
5233
  try {
4384
- const signature = await (0, import_web3.sendAndConfirmTransaction)(conn, transaction, [wallet]);
5234
+ const signature = await (0, import_web32.sendAndConfirmTransaction)(conn, transaction, [wallet]);
4385
5235
  registryInitialized = true;
4386
5236
  log6.info({ signature, registry: registryPDA.toBase58() }, "Registry PDA initialized on-chain");
4387
5237
  } catch (err) {
@@ -4421,18 +5271,18 @@ async function registerMemoryOnChain(contentHash, memoryType, importance, memory
4421
5271
  data.writeUInt8(importanceToTier(importance), 41);
4422
5272
  data.writeBigUInt64LE(BigInt(memoryId), 42);
4423
5273
  data.writeUInt8(encrypted ? 1 : 0, 50);
4424
- const instruction = new import_web3.TransactionInstruction({
5274
+ const instruction = new import_web32.TransactionInstruction({
4425
5275
  keys: [
4426
5276
  { pubkey: registryPDA, isSigner: false, isWritable: true },
4427
5277
  { pubkey: wallet.publicKey, isSigner: true, isWritable: true },
4428
- { pubkey: import_web3.SystemProgram.programId, isSigner: false, isWritable: false }
5278
+ { pubkey: import_web32.SystemProgram.programId, isSigner: false, isWritable: false }
4429
5279
  ],
4430
5280
  programId: registryProgramId,
4431
5281
  data
4432
5282
  });
4433
- const transaction = new import_web3.Transaction().add(instruction);
5283
+ const transaction = new import_web32.Transaction().add(instruction);
4434
5284
  try {
4435
- const signature = await (0, import_web3.sendAndConfirmTransaction)(conn, transaction, [wallet]);
5285
+ const signature = await (0, import_web32.sendAndConfirmTransaction)(conn, transaction, [wallet]);
4436
5286
  log6.info({ signature: signature.slice(0, 16), memoryId }, "Memory registered on-chain");
4437
5287
  return signature;
4438
5288
  } catch (err) {
@@ -4500,11 +5350,11 @@ async function verifyMemoTransaction(signature, contentHash) {
4500
5350
  return false;
4501
5351
  }
4502
5352
  }
4503
- var import_web3, bs58Module2, import_tweetnacl2, bs582, log6, connection, botWallet, MEMO_PROGRAM_ID2, devnetConnection, registryProgramId, registryInitialized;
5353
+ var import_web32, bs58Module2, import_tweetnacl2, bs582, log6, connection, botWallet, MEMO_PROGRAM_ID2, devnetConnection, registryProgramId, registryInitialized;
4504
5354
  var init_solana_client = __esm({
4505
5355
  "packages/shared/src/core/solana-client.ts"() {
4506
5356
  "use strict";
4507
- import_web3 = require("@solana/web3.js");
5357
+ import_web32 = require("@solana/web3.js");
4508
5358
  bs58Module2 = __toESM(require("bs58"));
4509
5359
  init_config();
4510
5360
  init_logger();
@@ -4513,12 +5363,126 @@ var init_solana_client = __esm({
4513
5363
  bs582 = bs58Module2.default || bs58Module2;
4514
5364
  log6 = createChildLogger("solana-client");
4515
5365
  botWallet = null;
4516
- MEMO_PROGRAM_ID2 = new import_web3.PublicKey(MEMO_PROGRAM_ID);
5366
+ MEMO_PROGRAM_ID2 = new import_web32.PublicKey(MEMO_PROGRAM_ID);
4517
5367
  registryProgramId = null;
4518
5368
  registryInitialized = false;
4519
5369
  }
4520
5370
  });
4521
5371
 
5372
+ // packages/tokenization/src/content-hash.ts
5373
+ function stableStringify(value) {
5374
+ if (value === null) return "null";
5375
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
5376
+ return JSON.stringify(value);
5377
+ }
5378
+ if (Array.isArray(value)) {
5379
+ return "[" + value.map(stableStringify).join(",") + "]";
5380
+ }
5381
+ if (typeof value === "object") {
5382
+ const obj = value;
5383
+ const keys = Object.keys(obj).sort();
5384
+ const pairs = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k]));
5385
+ return "{" + pairs.join(",") + "}";
5386
+ }
5387
+ throw new Error(`stableStringify: unsupported value of type ${typeof value}`);
5388
+ }
5389
+ function normaliseString(s) {
5390
+ return s.normalize("NFC").trim();
5391
+ }
5392
+ function normaliseNullable(s) {
5393
+ return s === null ? null : normaliseString(s);
5394
+ }
5395
+ function normaliseTags(tags) {
5396
+ const trimmed = tags.map(normaliseString).filter((t) => t.length > 0);
5397
+ return Array.from(new Set(trimmed)).sort();
5398
+ }
5399
+ function canonicaliseMemory(input) {
5400
+ const normalised = {
5401
+ algorithm: HASH_ALGORITHM,
5402
+ content: normaliseString(input.content),
5403
+ created_at: input.created_at,
5404
+ memory_type: input.memory_type,
5405
+ owner_wallet: normaliseNullable(input.owner_wallet),
5406
+ related_user: normaliseNullable(input.related_user),
5407
+ related_wallet: normaliseNullable(input.related_wallet),
5408
+ source: normaliseNullable(input.source),
5409
+ tags: normaliseTags(input.tags)
5410
+ };
5411
+ return stableStringify(normalised);
5412
+ }
5413
+ function memoryContentHash(input) {
5414
+ return (0, import_node_crypto.createHash)("sha256").update(canonicaliseMemory(input), "utf8").digest("hex");
5415
+ }
5416
+ var import_node_crypto, HASH_ALGORITHM;
5417
+ var init_content_hash = __esm({
5418
+ "packages/tokenization/src/content-hash.ts"() {
5419
+ "use strict";
5420
+ import_node_crypto = require("node:crypto");
5421
+ HASH_ALGORITHM = "memory-hash-v1";
5422
+ }
5423
+ });
5424
+
5425
+ // packages/tokenization/src/pack-merkle.ts
5426
+ var init_pack_merkle = __esm({
5427
+ "packages/tokenization/src/pack-merkle.ts"() {
5428
+ "use strict";
5429
+ }
5430
+ });
5431
+
5432
+ // packages/tokenization/src/mint-client.ts
5433
+ var init_mint_client = __esm({
5434
+ "packages/tokenization/src/mint-client.ts"() {
5435
+ "use strict";
5436
+ }
5437
+ });
5438
+
5439
+ // packages/tokenization/src/tokenize-memory.ts
5440
+ var init_tokenize_memory = __esm({
5441
+ "packages/tokenization/src/tokenize-memory.ts"() {
5442
+ "use strict";
5443
+ init_content_hash();
5444
+ }
5445
+ });
5446
+
5447
+ // packages/tokenization/src/tokenize-batch.ts
5448
+ var init_tokenize_batch = __esm({
5449
+ "packages/tokenization/src/tokenize-batch.ts"() {
5450
+ "use strict";
5451
+ init_content_hash();
5452
+ init_pack_merkle();
5453
+ }
5454
+ });
5455
+
5456
+ // packages/tokenization/src/tokenize-pack.ts
5457
+ var init_tokenize_pack = __esm({
5458
+ "packages/tokenization/src/tokenize-pack.ts"() {
5459
+ "use strict";
5460
+ init_pack_merkle();
5461
+ }
5462
+ });
5463
+
5464
+ // packages/tokenization/src/verify.ts
5465
+ var init_verify = __esm({
5466
+ "packages/tokenization/src/verify.ts"() {
5467
+ "use strict";
5468
+ init_pack_merkle();
5469
+ }
5470
+ });
5471
+
5472
+ // packages/tokenization/src/index.ts
5473
+ var init_src2 = __esm({
5474
+ "packages/tokenization/src/index.ts"() {
5475
+ "use strict";
5476
+ init_content_hash();
5477
+ init_pack_merkle();
5478
+ init_mint_client();
5479
+ init_tokenize_memory();
5480
+ init_tokenize_batch();
5481
+ init_tokenize_pack();
5482
+ init_verify();
5483
+ }
5484
+ });
5485
+
4522
5486
  // packages/shared/src/core/embeddings.ts
4523
5487
  function getCachedEmbedding(text) {
4524
5488
  const key = text.slice(0, 500).toLowerCase().trim();
@@ -4693,6 +5657,221 @@ var init_embeddings = __esm({
4693
5657
  }
4694
5658
  });
4695
5659
 
5660
+ // packages/shared/src/wiki-packs.ts
5661
+ function getPackManifest(id) {
5662
+ return ALL_PACK_MANIFESTS.find((p) => p.id === id);
5663
+ }
5664
+ function matchesKeyword(text, phrase) {
5665
+ if (!phrase) return false;
5666
+ const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5667
+ const re = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i");
5668
+ return re.test(text);
5669
+ }
5670
+ function autoCategorizeTags(opts) {
5671
+ const haystack = [
5672
+ opts.content || "",
5673
+ opts.summary || "",
5674
+ ...opts.existingTags || []
5675
+ ].join(" ");
5676
+ const matched = new Set(opts.existingTags || []);
5677
+ const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...opts.installedPackIds]);
5678
+ for (const packId of ids) {
5679
+ const pack = getPackManifest(packId);
5680
+ if (!pack) continue;
5681
+ for (const rule of pack.rules) {
5682
+ if (matched.has(rule.topicId)) continue;
5683
+ const hasMatch = rule.keywords.some((kw) => matchesKeyword(haystack, kw));
5684
+ if (!hasMatch) continue;
5685
+ const isVetoed = (rule.excludeKeywords || []).some((kw) => matchesKeyword(haystack, kw));
5686
+ if (isVetoed) continue;
5687
+ matched.add(rule.topicId);
5688
+ }
5689
+ }
5690
+ return Array.from(matched);
5691
+ }
5692
+ function topicEmbedSources(installedPackIds) {
5693
+ const out = [];
5694
+ const seen = /* @__PURE__ */ new Set();
5695
+ const ids = /* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...installedPackIds]);
5696
+ for (const packId of ids) {
5697
+ const pack = getPackManifest(packId);
5698
+ if (!pack) continue;
5699
+ for (const t of pack.topics) {
5700
+ if (seen.has(t.id)) continue;
5701
+ seen.add(t.id);
5702
+ const sectionTitles = (t.sectionTemplates || []).map((s) => s.title).join(". ");
5703
+ const text = [t.name, t.summary, sectionTitles].filter(Boolean).join(" \u2014 ");
5704
+ out.push({ topicId: t.id, packId, text });
5705
+ }
5706
+ }
5707
+ return out;
5708
+ }
5709
+ function cosineSimilarity2(a, b) {
5710
+ if (a.length !== b.length || a.length === 0) return 0;
5711
+ let dot = 0;
5712
+ let normA = 0;
5713
+ let normB = 0;
5714
+ for (let i = 0; i < a.length; i++) {
5715
+ dot += a[i] * b[i];
5716
+ normA += a[i] * a[i];
5717
+ normB += b[i] * b[i];
5718
+ }
5719
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
5720
+ return denom === 0 ? 0 : dot / denom;
5721
+ }
5722
+ function semanticTagMatches(memoryEmbedding, topicEmbeddings, threshold = EMBEDDING_TAG_THRESHOLD) {
5723
+ const matched = [];
5724
+ for (const { topicId, embedding } of topicEmbeddings) {
5725
+ const sim = cosineSimilarity2(memoryEmbedding, embedding);
5726
+ if (sim >= threshold) matched.push(topicId);
5727
+ }
5728
+ return matched;
5729
+ }
5730
+ var WORKSPACE_PACK, COMPLIANCE_PACK, SALES_PACK, ALL_PACK_MANIFESTS, DEFAULT_PACK_ID, EMBEDDING_TAG_THRESHOLD;
5731
+ var init_wiki_packs = __esm({
5732
+ "packages/shared/src/wiki-packs.ts"() {
5733
+ "use strict";
5734
+ WORKSPACE_PACK = {
5735
+ id: "workspace",
5736
+ name: "Workspace Essentials",
5737
+ vendor: "Clude",
5738
+ vertical: "General",
5739
+ version: "1.0.0",
5740
+ installedByDefault: true,
5741
+ description: "The default knowledge-worker pack. Roadmaps, decisions, customer research, hiring, team process \u2014 everything you'd want a colleague to be able to skim.",
5742
+ topics: [
5743
+ { id: "q3-roadmap", name: "Q3 Roadmap", cluster: "architecture", color: "#2244FF", summary: "What ships this quarter, what got cut, and the calls behind those decisions." },
5744
+ { id: "auth-migration", name: "Auth Migration", cluster: "architecture", color: "#2244FF", summary: "Moving from session cookies to JWT \u2014 incidents, fixes, and the rollback plan." },
5745
+ { id: "customer-research", name: "Customer Research", cluster: "research", color: "#F59E0B", summary: "Patterns surfaced across customer calls." },
5746
+ { id: "pricing-model", name: "Pricing Model", cluster: "product", color: "#10B981", summary: "Per-token vs per-seat \u2014 the active disagreement." },
5747
+ { id: "demo-day-prep", name: "Demo Day Prep", cluster: "product", color: "#10B981", summary: "What works in the live demo, what breaks." },
5748
+ { id: "hiring", name: "Hiring Pipeline", cluster: "product", color: "#10B981", summary: "Candidates in flight and interview-signal patterns." },
5749
+ { id: "team-process", name: "Team Process", cluster: "self", color: "#8B5CF6", summary: "What's working in how the team operates and what isn't." },
5750
+ { id: "design-decisions", name: "Design Decisions", cluster: "research", color: "#F59E0B", summary: "API shapes, naming choices, and the reasons behind them." }
5751
+ ],
5752
+ rules: [
5753
+ { topicId: "q3-roadmap", keywords: ["roadmap", "q3", "quarter", "sprint plan", "priority"] },
5754
+ { topicId: "auth-migration", keywords: ["auth", "jwt", "session cookie", "oauth", "sso", "token migration"] },
5755
+ { topicId: "customer-research", keywords: ["customer call", "interview", "feedback", "user research", "persona"] },
5756
+ { topicId: "pricing-model", keywords: ["pricing", "per-token", "per-seat", "subscription", "billing tier"] },
5757
+ { topicId: "demo-day-prep", keywords: ["demo", "rehearsal", "investor", "pitch"] },
5758
+ {
5759
+ topicId: "hiring",
5760
+ keywords: ["candidate", "interview", "hire", "phone screen", "onsite", "offer"],
5761
+ // Suppress when "hire" is in the wrong context — got fired, rejected an offer,
5762
+ // hire-purchase, etc. The classic substring-match false positive.
5763
+ excludeKeywords: ["fired me", "i was fired", "hire purchase", "rejected my offer", "rejected the offer"]
5764
+ },
5765
+ { topicId: "team-process", keywords: ["standup", "retro", "sprint", "1:1", "process", "team"] },
5766
+ { topicId: "design-decisions", keywords: ["api design", "schema", "naming", "rfc", "design doc"] }
5767
+ ]
5768
+ };
5769
+ COMPLIANCE_PACK = {
5770
+ id: "compliance",
5771
+ name: "Clude Compliance",
5772
+ vendor: "Clude",
5773
+ vertical: "Compliance",
5774
+ version: "1.0.0",
5775
+ description: "Auto-organises every compliance-relevant decision your agents make into an audit-ready wiki. Audit logs, evidence collection, regulator asks, policy decisions \u2014 each with a cryptographic receipt anchored to Solana.",
5776
+ topics: [
5777
+ {
5778
+ id: "audit-logs",
5779
+ name: "Audit Logs",
5780
+ cluster: "architecture",
5781
+ color: "#0EA5E9",
5782
+ summary: "Every flagged agent action with attribution, timestamp, and a Solana-anchored hash a regulator can verify without trusting us.",
5783
+ sectionTemplates: [
5784
+ { id: "recent-events", title: "Recent flagged events", kind: "overview" },
5785
+ { id: "anomalies", title: "Anomalies & escalations", kind: "concern" },
5786
+ { id: "retention", title: "Retention policy", kind: "decision" }
5787
+ ]
5788
+ },
5789
+ {
5790
+ id: "evidence",
5791
+ name: "Evidence Collection",
5792
+ cluster: "product",
5793
+ color: "#06B6D4",
5794
+ summary: "Documentation gathered for SOC2, HIPAA, and ISO 27001 \u2014 what we have, what we still need, and what auditors have already accepted.",
5795
+ sectionTemplates: [
5796
+ { id: "have", title: "What we have", kind: "highlight" },
5797
+ { id: "gaps", title: "Gaps to fill", kind: "action" },
5798
+ { id: "accepted", title: "What auditors accepted", kind: "decision" }
5799
+ ]
5800
+ },
5801
+ {
5802
+ id: "regulator-asks",
5803
+ name: "Regulator Asks",
5804
+ cluster: "research",
5805
+ color: "#F59E0B",
5806
+ summary: "Specific requests from regulators \u2014 owner, deadline, response status. Nothing falls through the cracks.",
5807
+ sectionTemplates: [
5808
+ { id: "open", title: "Open requests", kind: "action" },
5809
+ { id: "responded", title: "Responded", kind: "decision" },
5810
+ { id: "patterns", title: "Recurring patterns", kind: "highlight" }
5811
+ ]
5812
+ },
5813
+ {
5814
+ id: "policy-decisions",
5815
+ name: "Policy Decisions",
5816
+ cluster: "self",
5817
+ color: "#8B5CF6",
5818
+ summary: "Calls made about what the agents are allowed to do \u2014 data handling, escalation triggers, redaction rules \u2014 each anchored on-chain.",
5819
+ sectionTemplates: [
5820
+ { id: "standing", title: "Standing policy", kind: "decision" },
5821
+ { id: "changes", title: "Recent changes", kind: "overview" },
5822
+ { id: "open", title: "Open questions", kind: "question" }
5823
+ ]
5824
+ },
5825
+ {
5826
+ id: "soc2-status",
5827
+ name: "SOC2 Status",
5828
+ cluster: "product",
5829
+ color: "#10B981",
5830
+ summary: "Where we are in the SOC2 audit cycle, what controls are passing, what still needs evidence.",
5831
+ sectionTemplates: [
5832
+ { id: "controls", title: "Control status", kind: "overview" },
5833
+ { id: "next", title: "Up next", kind: "action" }
5834
+ ]
5835
+ }
5836
+ ],
5837
+ rules: [
5838
+ { topicId: "audit-logs", keywords: ["audit", "audit log", "trail", "attribution", "flagged"] },
5839
+ { topicId: "evidence", keywords: ["evidence", "soc2", "hipaa", "iso 27001", "compliance evidence", "control"] },
5840
+ { topicId: "regulator-asks", keywords: ["regulator", "subpoena", "compliance request", "data request"] },
5841
+ { topicId: "policy-decisions", keywords: ["policy", "redaction", "pii", "escalation rule", "data handling"] },
5842
+ { topicId: "soc2-status", keywords: ["soc2", "audit cycle", "control test"] }
5843
+ ]
5844
+ };
5845
+ SALES_PACK = {
5846
+ id: "sales",
5847
+ name: "Sales Intelligence",
5848
+ vendor: "Clude",
5849
+ vertical: "Sales",
5850
+ version: "1.0.0",
5851
+ description: "Auto-organises pipeline conversations, deal blockers, objection patterns, and post-call follow-ups. Built for AEs who hate CRM data entry.",
5852
+ topics: [
5853
+ { id: "pipeline", name: "Pipeline", cluster: "product", color: "#10B981", summary: "Active deals, stages, blockers." },
5854
+ { id: "objections", name: "Objections", cluster: "research", color: "#F59E0B", summary: "Patterns across discovery calls." },
5855
+ { id: "follow-ups", name: "Follow-ups", cluster: "product", color: "#10B981", summary: "What you committed to send and to whom." },
5856
+ { id: "champions", name: "Champions", cluster: "self", color: "#8B5CF6", summary: "Who is championing your product internally at each account." }
5857
+ ],
5858
+ rules: [
5859
+ { topicId: "pipeline", keywords: ["deal", "opportunity", "stage", "close date", "mql", "sql"] },
5860
+ { topicId: "objections", keywords: ["objection", "concern raised", "pushback", "competitor mentioned"] },
5861
+ { topicId: "follow-ups", keywords: ["follow up", "send", "next step", "committed to"] },
5862
+ { topicId: "champions", keywords: ["champion", "sponsor", "advocate", "introduced me to"] }
5863
+ ]
5864
+ };
5865
+ ALL_PACK_MANIFESTS = [
5866
+ WORKSPACE_PACK,
5867
+ COMPLIANCE_PACK,
5868
+ SALES_PACK
5869
+ ];
5870
+ DEFAULT_PACK_ID = "workspace";
5871
+ EMBEDDING_TAG_THRESHOLD = 0.78;
5872
+ }
5873
+ });
5874
+
4696
5875
  // packages/brain/src/experimental/config.ts
4697
5876
  function envBool(key, fallback) {
4698
5877
  const val = process.env[key];
@@ -5134,6 +6313,7 @@ __export(memory_exports, {
5134
6313
  getSelfModel: () => getSelfModel,
5135
6314
  hydrateMemories: () => hydrateMemories,
5136
6315
  inferConcepts: () => inferConcepts,
6316
+ invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
5137
6317
  isValidHashId: () => isValidHashId,
5138
6318
  listMemories: () => listMemories,
5139
6319
  markJepaQueried: () => markJepaQueried,
@@ -5236,13 +6416,83 @@ function isDuplicateWrite(source, summary) {
5236
6416
  }
5237
6417
  return false;
5238
6418
  }
6419
+ async function getInstalledPackIdsCached(ownerWallet) {
6420
+ const key = ownerWallet ?? "__no_owner__";
6421
+ const now = Date.now();
6422
+ const cached = installedPacksCache.get(key);
6423
+ if (cached && cached.expiresAt > now) return cached.ids;
6424
+ let ids = [DEFAULT_PACK_ID];
6425
+ if (ownerWallet) {
6426
+ try {
6427
+ const db = getDb();
6428
+ const { data, error } = await db.from("wiki_pack_installations").select("pack_id").eq("owner_wallet", ownerWallet);
6429
+ if (!error && data) {
6430
+ const fromDb = data.map((r) => r.pack_id);
6431
+ ids = Array.from(/* @__PURE__ */ new Set([DEFAULT_PACK_ID, ...fromDb]));
6432
+ }
6433
+ } catch (err) {
6434
+ log11.debug({ err }, "Failed to fetch installed packs (using default only)");
6435
+ }
6436
+ }
6437
+ installedPacksCache.set(key, { ids, expiresAt: now + INSTALLED_PACKS_TTL_MS });
6438
+ if (installedPacksCache.size > 200) {
6439
+ for (const [k, v] of installedPacksCache) {
6440
+ if (v.expiresAt <= now) installedPacksCache.delete(k);
6441
+ }
6442
+ }
6443
+ return ids;
6444
+ }
6445
+ function invalidateInstalledPacksCache(ownerWallet) {
6446
+ if (ownerWallet) installedPacksCache.delete(ownerWallet);
6447
+ else installedPacksCache.clear();
6448
+ }
6449
+ async function ensureTopicEmbeddings(installedPackIds) {
6450
+ const sources = topicEmbedSources(installedPackIds);
6451
+ const missing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
6452
+ if (missing.length === 0) {
6453
+ return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
6454
+ }
6455
+ if (topicEmbeddingPopulationLock) {
6456
+ await topicEmbeddingPopulationLock;
6457
+ }
6458
+ const stillMissing = sources.filter((s) => !topicEmbeddingCache.has(s.topicId));
6459
+ if (stillMissing.length === 0) {
6460
+ return new Map(sources.map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)]));
6461
+ }
6462
+ topicEmbeddingPopulationLock = (async () => {
6463
+ try {
6464
+ const texts = stillMissing.map((s) => s.text);
6465
+ const embeddings = await generateEmbeddings(texts);
6466
+ stillMissing.forEach((s, i) => {
6467
+ const emb = embeddings[i];
6468
+ if (emb) topicEmbeddingCache.set(s.topicId, emb);
6469
+ });
6470
+ } catch (err) {
6471
+ log11.warn({ err }, "Failed to populate topic embeddings cache");
6472
+ } finally {
6473
+ topicEmbeddingPopulationLock = null;
6474
+ }
6475
+ })();
6476
+ await topicEmbeddingPopulationLock;
6477
+ return new Map(
6478
+ sources.filter((s) => topicEmbeddingCache.has(s.topicId)).map((s) => [s.topicId, topicEmbeddingCache.get(s.topicId)])
6479
+ );
6480
+ }
5239
6481
  async function storeMemory(opts) {
5240
6482
  if (opts.source.startsWith("shiro_") && isDuplicateWrite(opts.source, opts.summary)) {
5241
6483
  log11.debug({ source: opts.source, summary: opts.summary.slice(0, 60) }, "Skipping duplicate memory write");
5242
6484
  return null;
5243
6485
  }
5244
6486
  const db = getDb();
6487
+ const ownerWallet = getOwnerWallet();
5245
6488
  const concepts = opts.concepts || inferConcepts(opts.summary, opts.source, opts.tags || []);
6489
+ const installedPackIds = await getInstalledPackIdsCached(ownerWallet);
6490
+ const taggedTags = autoCategorizeTags({
6491
+ content: opts.content,
6492
+ summary: opts.summary,
6493
+ existingTags: opts.tags || [],
6494
+ installedPackIds
6495
+ });
5246
6496
  const hashId = generateHashId();
5247
6497
  try {
5248
6498
  const plaintextContent = opts.content.slice(0, MEMORY_MAX_CONTENT_LENGTH);
@@ -5253,7 +6503,7 @@ async function storeMemory(opts) {
5253
6503
  memory_type: opts.type,
5254
6504
  content: storedContent,
5255
6505
  summary: opts.summary.slice(0, MEMORY_MAX_SUMMARY_LENGTH),
5256
- tags: opts.tags || [],
6506
+ tags: taggedTags,
5257
6507
  concepts,
5258
6508
  emotional_valence: clamp(opts.emotionalValence ?? 0, -1, 1),
5259
6509
  importance: clamp(opts.importance ?? 0.5, 0, 1),
@@ -5266,8 +6516,8 @@ async function storeMemory(opts) {
5266
6516
  compacted: false,
5267
6517
  encrypted: shouldEncrypt,
5268
6518
  encryption_pubkey: shouldEncrypt ? getEncryptionPubkey() : null,
5269
- owner_wallet: getOwnerWallet() || null
5270
- }).select("id, hash_id").single();
6519
+ owner_wallet: ownerWallet || null
6520
+ }).select("id, hash_id, content, memory_type, owner_wallet, created_at, tags, source, related_user, related_wallet").single();
5271
6521
  if (error) {
5272
6522
  log11.error({ error: error.message }, "Failed to store memory");
5273
6523
  return null;
@@ -5285,7 +6535,9 @@ async function storeMemory(opts) {
5285
6535
  memoryType: opts.type,
5286
6536
  source: opts.source
5287
6537
  });
5288
- commitMemoryToChain(data.id, opts).catch((err) => log11.warn({ err }, "On-chain memory commit failed"));
6538
+ commitMemoryToChain(data.id, opts, data).catch(
6539
+ (err) => log11.warn({ err }, "On-chain memory commit failed")
6540
+ );
5289
6541
  embedMemory(data.id, opts).catch((err) => log11.warn({ err }, "Embedding generation failed"));
5290
6542
  autoLinkMemory(data.id, opts).catch((err) => log11.warn({ err }, "Auto-linking failed"));
5291
6543
  extractAndLinkEntitiesForMemory(data.id, opts).catch((err) => log11.debug({ err }, "Entity extraction failed"));
@@ -5295,9 +6547,19 @@ async function storeMemory(opts) {
5295
6547
  return null;
5296
6548
  }
5297
6549
  }
5298
- async function commitMemoryToChain(memoryId, opts) {
5299
- if (opts.source === "demo" || opts.source === "demo-maas" || opts.source === "locomo-benchmark" || opts.source === "longmemeval-benchmark") return;
5300
- const contentHashBuf = (0, import_crypto3.createHash)("sha256").update(opts.content).digest();
6550
+ async function commitMemoryToChain(memoryId, opts, row) {
6551
+ if (TOKENISATION_SKIP_SOURCES.has(opts.source)) return;
6552
+ const canonicalHash = memoryContentHash({
6553
+ content: row.content,
6554
+ memory_type: row.memory_type,
6555
+ owner_wallet: row.owner_wallet,
6556
+ created_at: row.created_at,
6557
+ tags: row.tags ?? [],
6558
+ source: row.source,
6559
+ related_user: row.related_user,
6560
+ related_wallet: row.related_wallet
6561
+ });
6562
+ const contentHashBuf = Buffer.from(canonicalHash, "hex");
5301
6563
  let signature = null;
5302
6564
  if (isRegistryEnabled()) {
5303
6565
  const encrypted = isEncryptionEnabled();
@@ -5310,22 +6572,61 @@ async function commitMemoryToChain(memoryId, opts) {
5310
6572
  );
5311
6573
  }
5312
6574
  if (!signature) {
5313
- const contentHashHex = contentHashBuf.toString("hex");
5314
- const memo = `clude:v1:sha256:${contentHashHex}`;
6575
+ const memo = `clude:v1:sha256:${canonicalHash}`;
5315
6576
  signature = await writeMemo(memo);
5316
6577
  }
5317
- if (!signature) return;
5318
6578
  const db = getDb();
5319
- await db.from("memories").update({ solana_signature: signature }).eq("id", memoryId);
5320
- log11.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain");
6579
+ if (!signature) {
6580
+ await db.from("memories").update({ content_hash: canonicalHash, tokenization_status: "failed" }).eq("id", memoryId);
6581
+ return;
6582
+ }
6583
+ await db.from("memories").update({
6584
+ solana_signature: signature,
6585
+ content_hash: canonicalHash,
6586
+ cnft_address: signature,
6587
+ // PDA-based v0.1 — assetId = tx sig; LightMintClient (v0.2) will use real cNFT mints
6588
+ cnft_tx_sig: signature,
6589
+ cnft_tree: null,
6590
+ cnft_leaf_index: null,
6591
+ tokenization_status: "minted",
6592
+ tokenized_at: (/* @__PURE__ */ new Date()).toISOString()
6593
+ }).eq("id", memoryId);
6594
+ log11.debug({ memoryId, signature: signature.slice(0, 16) }, "Memory committed on-chain (PMP + legacy)");
5321
6595
  }
5322
6596
  async function embedMemory(memoryId, opts) {
5323
6597
  if (!isEmbeddingEnabled()) return;
5324
6598
  const db = getDb();
5325
6599
  const embeddings = await generateEmbeddings([opts.summary]);
5326
6600
  const summaryEmbedding = embeddings[0];
5327
- if (summaryEmbedding) {
5328
- await db.from("memories").update({ embedding: JSON.stringify(summaryEmbedding) }).eq("id", memoryId);
6601
+ if (!summaryEmbedding) {
6602
+ log11.debug({ memoryId }, "No embedding generated; skipping semantic tagging");
6603
+ return;
6604
+ }
6605
+ await db.from("memories").update({ embedding: JSON.stringify(summaryEmbedding) }).eq("id", memoryId);
6606
+ try {
6607
+ const ownerWallet = getOwnerWallet();
6608
+ const installedPacks = await getInstalledPackIdsCached(ownerWallet);
6609
+ const topicEmbeddings = await ensureTopicEmbeddings(installedPacks);
6610
+ if (topicEmbeddings.size > 0) {
6611
+ const semantic = semanticTagMatches(
6612
+ summaryEmbedding,
6613
+ Array.from(topicEmbeddings, ([topicId, embedding]) => ({ topicId, embedding }))
6614
+ );
6615
+ if (semantic.length > 0) {
6616
+ const { data: row } = await db.from("memories").select("tags").eq("id", memoryId).maybeSingle();
6617
+ const existing = row?.tags ?? [];
6618
+ const merged = Array.from(/* @__PURE__ */ new Set([...existing, ...semantic]));
6619
+ if (merged.length !== existing.length) {
6620
+ await db.from("memories").update({ tags: merged }).eq("id", memoryId);
6621
+ log11.debug({
6622
+ memoryId,
6623
+ added: semantic.filter((t) => !existing.includes(t))
6624
+ }, "Semantic pack tags appended");
6625
+ }
6626
+ }
6627
+ }
6628
+ } catch (err) {
6629
+ log11.debug({ err, memoryId }, "Semantic tagging skipped");
5329
6630
  }
5330
6631
  log11.debug({ memoryId }, "Memory embedded");
5331
6632
  }
@@ -5528,9 +6829,9 @@ async function recallMemories(opts) {
5528
6829
  }
5529
6830
  if (candidates.length === 0) return [];
5530
6831
  const linkPathMap = /* @__PURE__ */ new Map();
5531
- const addLinkPath = (id, path8) => {
6832
+ const addLinkPath = (id, path9) => {
5532
6833
  if (!linkPathMap.has(id)) linkPathMap.set(id, /* @__PURE__ */ new Set());
5533
- linkPathMap.get(id).add(path8);
6834
+ linkPathMap.get(id).add(path9);
5534
6835
  };
5535
6836
  for (const id of vectorScores.keys()) addLinkPath(id, "vector");
5536
6837
  for (const id of bm25Scores.keys()) addLinkPath(id, "bm25");
@@ -6047,25 +7348,31 @@ async function getMemoryStats() {
6047
7348
  embeddedCount: 0
6048
7349
  };
6049
7350
  try {
6050
- let countQuery = db.from("memories").select("id", { count: "exact", head: true }).gt("decay_factor", MEMORY_MIN_DECAY);
7351
+ let countQuery = db.from("memories").select("id", { count: "exact", head: true });
6051
7352
  countQuery = scopeToOwner(countQuery);
6052
7353
  const { count: totalCount } = await countQuery;
6053
7354
  stats.total = totalCount || 0;
6054
- let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).gt("decay_factor", MEMORY_MIN_DECAY).not("embedding", "is", null);
7355
+ let embeddedQuery = db.from("memories").select("id", { count: "exact", head: true }).not("embedding", "is", null);
6055
7356
  embeddedQuery = scopeToOwner(embeddedQuery);
6056
7357
  const { count: embCount } = await embeddedQuery;
6057
7358
  stats.embeddedCount = embCount || 0;
6058
- const PAGE_SIZE = 5e3;
7359
+ const TYPES = ["episodic", "semantic", "procedural", "self_model", "introspective"];
7360
+ await Promise.all(TYPES.map(async (type) => {
7361
+ let q = db.from("memories").select("id", { count: "exact", head: true }).eq("memory_type", type);
7362
+ q = scopeToOwner(q);
7363
+ const { count: count2 } = await q;
7364
+ stats.byType[type] = count2 || 0;
7365
+ }));
7366
+ const PAGE_SIZE = 1e3;
7367
+ const MAX_PAGES = 20;
6059
7368
  let allMemories = [];
6060
- let page = 0;
6061
- while (true) {
6062
- let pageQuery = db.from("memories").select("memory_type, importance, decay_factor, created_at, related_user, tags, concepts").gt("decay_factor", MEMORY_MIN_DECAY).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
7369
+ for (let page = 0; page < MAX_PAGES; page++) {
7370
+ let pageQuery = db.from("memories").select("importance, decay_factor, created_at, related_user, tags, concepts").order("id", { ascending: true }).range(page * PAGE_SIZE, (page + 1) * PAGE_SIZE - 1);
6063
7371
  pageQuery = scopeToOwner(pageQuery);
6064
7372
  const { data: pageData } = await pageQuery;
6065
7373
  if (!pageData || pageData.length === 0) break;
6066
7374
  allMemories = allMemories.concat(pageData);
6067
7375
  if (pageData.length < PAGE_SIZE) break;
6068
- page++;
6069
7376
  }
6070
7377
  if (allMemories.length > 0) {
6071
7378
  let impSum = 0;
@@ -6074,8 +7381,6 @@ async function getMemoryStats() {
6074
7381
  const conceptCounts = {};
6075
7382
  const users = /* @__PURE__ */ new Set();
6076
7383
  for (const m of allMemories) {
6077
- const type = m.memory_type;
6078
- if (type in stats.byType) stats.byType[type]++;
6079
7384
  impSum += m.importance;
6080
7385
  decaySum += m.decay_factor;
6081
7386
  if (m.related_user) users.add(m.related_user);
@@ -6263,7 +7568,7 @@ function moodToValence(mood) {
6263
7568
  return 0;
6264
7569
  }
6265
7570
  }
6266
- var import_crypto3, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN, HASH_ID_PREFIX, log11, DEDUP_TTL_MS, dedupCache, STOPWORDS;
7571
+ var import_crypto3, EMBED_CACHE_MAX, embeddingCache, _ownerWallet, SCOPE_BOT_OWN, HASH_ID_PREFIX, log11, DEDUP_TTL_MS, dedupCache, installedPacksCache, INSTALLED_PACKS_TTL_MS, topicEmbeddingCache, topicEmbeddingPopulationLock, TOKENISATION_SKIP_SOURCES, STOPWORDS;
6267
7572
  var init_memory = __esm({
6268
7573
  "packages/brain/src/memory/memory.ts"() {
6269
7574
  "use strict";
@@ -6272,7 +7577,9 @@ var init_memory = __esm({
6272
7577
  init_utils();
6273
7578
  init_claude_client();
6274
7579
  init_solana_client();
7580
+ init_src2();
6275
7581
  init_embeddings();
7582
+ init_wiki_packs();
6276
7583
  init_config2();
6277
7584
  init_bm25_search();
6278
7585
  init_openrouter_client();
@@ -6289,6 +7596,16 @@ var init_memory = __esm({
6289
7596
  log11 = createChildLogger("memory");
6290
7597
  DEDUP_TTL_MS = 10 * 60 * 1e3;
6291
7598
  dedupCache = /* @__PURE__ */ new Map();
7599
+ installedPacksCache = /* @__PURE__ */ new Map();
7600
+ INSTALLED_PACKS_TTL_MS = 6e4;
7601
+ topicEmbeddingCache = /* @__PURE__ */ new Map();
7602
+ topicEmbeddingPopulationLock = null;
7603
+ TOKENISATION_SKIP_SOURCES = /* @__PURE__ */ new Set([
7604
+ "demo",
7605
+ "demo-maas",
7606
+ "locomo-benchmark",
7607
+ "longmemeval-benchmark"
7608
+ ]);
6292
7609
  STOPWORDS = /* @__PURE__ */ new Set([
6293
7610
  "the",
6294
7611
  "a",
@@ -6394,6 +7711,7 @@ __export(memory_exports2, {
6394
7711
  getSelfModel: () => getSelfModel,
6395
7712
  hydrateMemories: () => hydrateMemories,
6396
7713
  inferConcepts: () => inferConcepts,
7714
+ invalidateInstalledPacksCache: () => invalidateInstalledPacksCache,
6397
7715
  isValidHashId: () => isValidHashId,
6398
7716
  listMemories: () => listMemories,
6399
7717
  moodToValence: () => moodToValence,
@@ -6778,7 +8096,7 @@ async function runExport() {
6778
8096
  writeMemoryPack(outputDir, records, {
6779
8097
  producer: {
6780
8098
  name: "clude",
6781
- version: "3.0.3",
8099
+ version: "3.1.0",
6782
8100
  agent_id: config3?.agent?.id,
6783
8101
  public_key: publicKey
6784
8102
  },
@@ -6851,7 +8169,7 @@ var init_export = __esm({
6851
8169
  "use strict";
6852
8170
  import_fs5 = require("fs");
6853
8171
  init_banner();
6854
- init_memorypack();
8172
+ init_src();
6855
8173
  }
6856
8174
  });
6857
8175
 
@@ -6863,9 +8181,9 @@ __export(import_exports, {
6863
8181
  function hasFlag2(args, flag) {
6864
8182
  return args.includes(flag);
6865
8183
  }
6866
- function isMemoryPackDir(path8) {
8184
+ function isMemoryPackDir(path9) {
6867
8185
  try {
6868
- return (0, import_fs6.statSync)(path8).isDirectory() && (0, import_fs6.existsSync)((0, import_path6.join)(path8, "manifest.json"));
8186
+ return (0, import_fs6.statSync)(path9).isDirectory() && (0, import_fs6.existsSync)((0, import_path6.join)(path9, "manifest.json"));
6869
8187
  } catch {
6870
8188
  return false;
6871
8189
  }
@@ -6889,19 +8207,19 @@ function parseMemoryPackDir(dir) {
6889
8207
  source: r.source || "memorypack"
6890
8208
  }));
6891
8209
  }
6892
- function isZipFile(path8) {
8210
+ function isZipFile(path9) {
6893
8211
  try {
6894
- const buf = (0, import_fs6.readFileSync)(path8);
8212
+ const buf = (0, import_fs6.readFileSync)(path9);
6895
8213
  return buf[0] === 80 && buf[1] === 75;
6896
8214
  } catch {
6897
8215
  return false;
6898
8216
  }
6899
8217
  }
6900
- function isJsonFile(path8) {
6901
- return path8.endsWith(".json");
8218
+ function isJsonFile(path9) {
8219
+ return path9.endsWith(".json");
6902
8220
  }
6903
- function isMarkdownOrText(path8) {
6904
- return path8.endsWith(".md") || path8.endsWith(".txt") || path8.endsWith(".markdown");
8221
+ function isMarkdownOrText(path9) {
8222
+ return path9.endsWith(".md") || path9.endsWith(".txt") || path9.endsWith(".markdown");
6905
8223
  }
6906
8224
  function isSubstantive(text) {
6907
8225
  if (!text || text.length < 20) return false;
@@ -7018,18 +8336,18 @@ function extractFromChatGPTConversations(conversations) {
7018
8336
  }
7019
8337
  async function parseChatGPTZip(filePath) {
7020
8338
  const { execSync: execSync2 } = require("child_process");
7021
- const { mkdtempSync, readdirSync: readdirSync2 } = require("fs");
7022
- const { join: join11 } = require("path");
7023
- const os5 = require("os");
7024
- const tmpDir = mkdtempSync(join11(os5.tmpdir(), "clude-import-"));
8339
+ const { mkdtempSync: mkdtempSync3, readdirSync: readdirSync3 } = require("fs");
8340
+ const { join: join13 } = require("path");
8341
+ const os6 = require("os");
8342
+ const tmpDir = mkdtempSync3(join13(os6.tmpdir(), "clude-import-"));
7025
8343
  try {
7026
8344
  execSync2(`unzip -o -q "${filePath}" -d "${tmpDir}"`);
7027
8345
  } catch {
7028
8346
  printError('Failed to unzip file. Make sure "unzip" is installed.');
7029
8347
  process.exit(1);
7030
8348
  }
7031
- const files = readdirSync2(tmpDir);
7032
- const convFile = files.find((f) => f === "conversations.json") ? join11(tmpDir, "conversations.json") : null;
8349
+ const files = readdirSync3(tmpDir);
8350
+ const convFile = files.find((f) => f === "conversations.json") ? join13(tmpDir, "conversations.json") : null;
7033
8351
  if (!convFile) {
7034
8352
  const { execSync: exec2 } = require("child_process");
7035
8353
  try {
@@ -7272,7 +8590,7 @@ var init_import = __esm({
7272
8590
  import_fs6 = require("fs");
7273
8591
  import_path6 = require("path");
7274
8592
  init_banner();
7275
- init_memorypack();
8593
+ init_src();
7276
8594
  }
7277
8595
  });
7278
8596
 
@@ -7423,13 +8741,13 @@ async function runInjectInstructions() {
7423
8741
  const forceAgents = args.includes("--agents");
7424
8742
  const forceClaude = args.includes("--claude");
7425
8743
  const dirFlag = args.find((a) => a.startsWith("--dir="));
7426
- const dir = dirFlag ? path5.resolve(dirFlag.slice(6)) : process.cwd();
8744
+ const dir = dirFlag ? path6.resolve(dirFlag.slice(6)) : process.cwd();
7427
8745
  let useAgentsmd = forceAgents;
7428
8746
  if (!forceAgents && !forceClaude) {
7429
- useAgentsmd = fs3.existsSync(path5.join(dir, "AGENTS.md")) || fs3.existsSync(path5.join(dir, "agents"));
8747
+ useAgentsmd = fs4.existsSync(path6.join(dir, "AGENTS.md")) || fs4.existsSync(path6.join(dir, "agents"));
7430
8748
  }
7431
8749
  const targetFile = useAgentsmd ? "AGENTS.md" : "CLAUDE.md";
7432
- printInfo(`Target: ${path5.join(dir, targetFile)}`);
8750
+ printInfo(`Target: ${path6.join(dir, targetFile)}`);
7433
8751
  console.log("");
7434
8752
  const result = injectInstructions(dir, { agentsmd: useAgentsmd });
7435
8753
  if (result) {
@@ -7448,12 +8766,12 @@ async function runInjectInstructions() {
7448
8766
  printDivider();
7449
8767
  console.log("");
7450
8768
  }
7451
- var path5, fs3;
8769
+ var path6, fs4;
7452
8770
  var init_inject_instructions = __esm({
7453
8771
  "packages/brain/src/cli/inject-instructions.ts"() {
7454
8772
  "use strict";
7455
- path5 = __toESM(require("path"));
7456
- fs3 = __toESM(require("fs"));
8773
+ path6 = __toESM(require("path"));
8774
+ fs4 = __toESM(require("fs"));
7457
8775
  init_banner();
7458
8776
  init_setup();
7459
8777
  }
@@ -7466,13 +8784,13 @@ __export(doctor_exports, {
7466
8784
  });
7467
8785
  async function runDoctor() {
7468
8786
  console.log("\n CLUDE DIAGNOSTICS\n");
7469
- const dbPath = path6.join(os3.homedir(), ".clude", "brain.db");
7470
- if (fs4.existsSync(dbPath)) {
8787
+ const dbPath = path7.join(os4.homedir(), ".clude", "brain.db");
8788
+ if (fs5.existsSync(dbPath)) {
7471
8789
  try {
7472
8790
  const Database2 = require("better-sqlite3");
7473
8791
  const db = new Database2(dbPath, { readonly: true });
7474
8792
  const count = db.prepare("SELECT COUNT(*) as c FROM memories").get().c;
7475
- const stats = fs4.statSync(dbPath);
8793
+ const stats = fs5.statSync(dbPath);
7476
8794
  const sizeMB = (stats.size / 1024 / 1024).toFixed(1);
7477
8795
  console.log(` \u2713 Database ${dbPath} (${count} memories, ${sizeMB} MB)`);
7478
8796
  db.close();
@@ -7482,15 +8800,15 @@ async function runDoctor() {
7482
8800
  } else {
7483
8801
  console.log(` \u2717 Database Not found (run 'npx @clude/sdk setup')`);
7484
8802
  }
7485
- const modelDir = path6.join(os3.homedir(), ".clude", "models");
7486
- const modelCached = fs4.existsSync(modelDir) && fs4.readdirSync(modelDir).length > 0;
8803
+ const modelDir = path7.join(os4.homedir(), ".clude", "models");
8804
+ const modelCached = fs5.existsSync(modelDir) && fs5.readdirSync(modelDir).length > 0;
7487
8805
  console.log(
7488
8806
  modelCached ? " \u2713 Embeddings all-MiniLM-L6-v2 (cached)" : " \u2717 Embeddings Model not cached (will download on first use)"
7489
8807
  );
7490
- const configPath = path6.join(os3.homedir(), ".clude", "config.json");
7491
- if (fs4.existsSync(configPath)) {
8808
+ const configPath = path7.join(os4.homedir(), ".clude", "config.json");
8809
+ if (fs5.existsSync(configPath)) {
7492
8810
  try {
7493
- const config3 = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
8811
+ const config3 = JSON.parse(fs5.readFileSync(configPath, "utf-8"));
7494
8812
  console.log(
7495
8813
  config3.apiKey ? ` \u2713 Cloud ${config3.apiKey.slice(0, 8)}... (configured)` : " \u2717 Cloud No API key configured"
7496
8814
  );
@@ -7513,13 +8831,13 @@ async function runDoctor() {
7513
8831
  console.log(" \u2717 Ollama Not running");
7514
8832
  }
7515
8833
  const mcpPaths = [
7516
- { name: "Claude Code", path: path6.join(os3.homedir(), ".claude", ".mcp.json") },
7517
- { name: "Cursor", path: path6.join(os3.homedir(), ".cursor", "mcp.json") }
8834
+ { name: "Claude Code", path: path7.join(os4.homedir(), ".claude", ".mcp.json") },
8835
+ { name: "Cursor", path: path7.join(os4.homedir(), ".cursor", "mcp.json") }
7518
8836
  ];
7519
8837
  for (const mcp of mcpPaths) {
7520
- if (fs4.existsSync(mcp.path)) {
8838
+ if (fs5.existsSync(mcp.path)) {
7521
8839
  try {
7522
- const config3 = JSON.parse(fs4.readFileSync(mcp.path, "utf-8"));
8840
+ const config3 = JSON.parse(fs5.readFileSync(mcp.path, "utf-8"));
7523
8841
  const hasClude = config3.mcpServers?.["clude-memory"] !== void 0;
7524
8842
  console.log(
7525
8843
  hasClude ? ` \u2713 MCP ${mcp.name}` : ` - MCP ${mcp.name} (no clude-memory entry)`
@@ -7531,7 +8849,7 @@ async function runDoctor() {
7531
8849
  console.log(` \u2717 MCP ${mcp.name} (not detected)`);
7532
8850
  }
7533
8851
  }
7534
- if (fs4.existsSync(dbPath)) {
8852
+ if (fs5.existsSync(dbPath)) {
7535
8853
  try {
7536
8854
  const Database2 = require("better-sqlite3");
7537
8855
  const db = new Database2(dbPath, { readonly: true });
@@ -7543,13 +8861,13 @@ async function runDoctor() {
7543
8861
  }
7544
8862
  console.log("");
7545
8863
  }
7546
- var path6, os3, fs4;
8864
+ var path7, os4, fs5;
7547
8865
  var init_doctor = __esm({
7548
8866
  "packages/brain/src/cli/doctor.ts"() {
7549
8867
  "use strict";
7550
- path6 = __toESM(require("path"));
7551
- os3 = __toESM(require("os"));
7552
- fs4 = __toESM(require("fs"));
8868
+ path7 = __toESM(require("path"));
8869
+ os4 = __toESM(require("os"));
8870
+ fs5 = __toESM(require("fs"));
7553
8871
  }
7554
8872
  });
7555
8873
 
@@ -7819,8 +9137,8 @@ __export(dream_exports, {
7819
9137
  });
7820
9138
  async function runDream() {
7821
9139
  console.log("\n DREAM CYCLE\n");
7822
- const dbPath = path7.join(os4.homedir(), ".clude", "brain.db");
7823
- if (!fs5.existsSync(dbPath)) {
9140
+ const dbPath = path8.join(os5.homedir(), ".clude", "brain.db");
9141
+ if (!fs6.existsSync(dbPath)) {
7824
9142
  console.log(' \u2717 No database found. Run "npx @clude/sdk setup" first.\n');
7825
9143
  return;
7826
9144
  }
@@ -7850,13 +9168,300 @@ async function runDream() {
7850
9168
  store.close();
7851
9169
  console.log("\n Dream cycle complete.\n");
7852
9170
  }
7853
- var path7, os4, fs5;
9171
+ var path8, os5, fs6;
7854
9172
  var init_dream = __esm({
7855
9173
  "packages/brain/src/cli/dream.ts"() {
7856
9174
  "use strict";
7857
- path7 = __toESM(require("path"));
7858
- os4 = __toESM(require("os"));
7859
- fs5 = __toESM(require("fs"));
9175
+ path8 = __toESM(require("path"));
9176
+ os5 = __toESM(require("os"));
9177
+ fs6 = __toESM(require("fs"));
9178
+ }
9179
+ });
9180
+
9181
+ // packages/brain/src/cli/verify.ts
9182
+ var verify_exports = {};
9183
+ __export(verify_exports, {
9184
+ runVerify: () => runVerify
9185
+ });
9186
+ function parseArgs() {
9187
+ const argv = process.argv.slice(3);
9188
+ const args = {
9189
+ path: "",
9190
+ strictSignatures: false,
9191
+ verifyChain: false,
9192
+ strictChain: false,
9193
+ rpcUrl: process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com",
9194
+ showHelp: false
9195
+ };
9196
+ for (let i = 0; i < argv.length; i++) {
9197
+ const a = argv[i];
9198
+ if (a === "--help" || a === "-h") args.showHelp = true;
9199
+ else if (a === "--public-key") args.publicKey = argv[++i];
9200
+ else if (a === "--strict-signatures") args.strictSignatures = true;
9201
+ else if (a === "--strict-chain") args.strictChain = true;
9202
+ else if (a === "--verify-chain") args.verifyChain = true;
9203
+ else if (a === "--rpc-url") args.rpcUrl = argv[++i];
9204
+ else if (a === "--cluster") args.cluster = argv[++i];
9205
+ else if (a === "--decrypt-key") args.decryptKey = argv[++i];
9206
+ else if (!a.startsWith("--") && !args.path) args.path = a;
9207
+ }
9208
+ return args;
9209
+ }
9210
+ function printUsage() {
9211
+ console.log(`Usage: clude verify <pack> [options]
9212
+
9213
+ Validates a MemoryPack directory or .tar.zst tarball.
9214
+
9215
+ Options:
9216
+ --public-key <base58> Override the public key (default: from manifest)
9217
+ --strict-signatures Fail if any record is unsigned
9218
+ --verify-chain Also verify Solana on-chain anchors
9219
+ --strict-chain Fail on any chain verification mismatch
9220
+ --rpc-url <url> Solana RPC URL for chain verification
9221
+ (default: SOLANA_RPC_URL env or mainnet-beta)
9222
+ --cluster <name> Cross-check RPC genesis hash against this
9223
+ cluster (mainnet | devnet | testnet)
9224
+ --decrypt-key <base64> Pack-level decryption key (32 bytes, base64)
9225
+
9226
+ Exit code: 0 if valid, 1 if any check failed.`);
9227
+ }
9228
+ async function runVerify() {
9229
+ const args = parseArgs();
9230
+ if (args.showHelp || !args.path) {
9231
+ printUsage();
9232
+ process.exit(args.showHelp ? 0 : 1);
9233
+ }
9234
+ console.log(`${c.bold}MemoryPack verify${c.reset} ${c.dim}${args.path}${c.reset}
9235
+ `);
9236
+ let decryptionKey;
9237
+ if (args.decryptKey) {
9238
+ const buf = Buffer.from(args.decryptKey, "base64");
9239
+ decryptionKey = new Uint8Array(buf);
9240
+ }
9241
+ let result;
9242
+ try {
9243
+ result = readMemoryPack(args.path, {
9244
+ publicKey: args.publicKey,
9245
+ strictSignatures: args.strictSignatures,
9246
+ decryptionKey
9247
+ });
9248
+ } catch (e) {
9249
+ console.error(`${c.red}REJECTED${c.reset}: ${e.message}`);
9250
+ process.exit(1);
9251
+ }
9252
+ const {
9253
+ manifest,
9254
+ records,
9255
+ verifiedRecords,
9256
+ unsignedRecords,
9257
+ anchors,
9258
+ verifiedBlobs,
9259
+ minimalRecords,
9260
+ warnings
9261
+ } = result;
9262
+ console.log(` ${c.dim}producer:${c.reset} ${manifest.producer.name} ${manifest.producer.version}`);
9263
+ if (manifest.producer.did) console.log(` ${c.dim}did:${c.reset} ${manifest.producer.did}`);
9264
+ if (manifest.producer.public_key) console.log(` ${c.dim}public_key:${c.reset} ${manifest.producer.public_key}`);
9265
+ console.log(` ${c.dim}created_at:${c.reset} ${manifest.created_at}`);
9266
+ console.log(` ${c.dim}records:${c.reset} ${records.length}`);
9267
+ console.log(` ${c.dim}schema:${c.reset} ${manifest.record_schema}`);
9268
+ console.log(` ${c.dim}encryption:${c.reset} ${manifest.encryption ? `${manifest.encryption.algorithm} (scope=${manifest.encryption.scope})` : "none"}`);
9269
+ console.log(` ${c.dim}pack_format:${c.reset} ${manifest.pack_format ?? "directory"}`);
9270
+ console.log();
9271
+ console.log(` ${c.bold}Signatures${c.reset}`);
9272
+ console.log(` verified: ${c.green}${verifiedRecords.size}${c.reset}`);
9273
+ console.log(` unsigned: ${unsignedRecords.size}`);
9274
+ console.log();
9275
+ if (verifiedBlobs.size > 0 || manifest.blobs_count) {
9276
+ console.log(` ${c.bold}Blobs${c.reset}`);
9277
+ const declared = manifest.blobs_count ?? "?";
9278
+ console.log(` verified: ${c.green}${verifiedBlobs.size}${c.reset} of ${declared}`);
9279
+ console.log();
9280
+ }
9281
+ console.log(` ${c.bold}Anchors${c.reset}`);
9282
+ console.log(` declared: ${anchors.length}`);
9283
+ let chainFailed = false;
9284
+ if (args.verifyChain && anchors.length > 0) {
9285
+ process.stdout.write(` fetching: `);
9286
+ try {
9287
+ const expectedSigner = args.publicKey ?? manifest.producer.public_key;
9288
+ const { verified, warnings: chainWarnings } = await verifyChainAnchors(anchors, {
9289
+ rpcUrl: args.rpcUrl,
9290
+ cluster: args.cluster,
9291
+ expectedSigner,
9292
+ strict: args.strictChain
9293
+ });
9294
+ const ok = verified.size;
9295
+ console.log(`${c.green}${ok}${c.reset}/${anchors.length} verified on-chain`);
9296
+ if (chainWarnings.length > 0) {
9297
+ for (const w of chainWarnings) console.log(` ${c.yellow}warn:${c.reset} ${w}`);
9298
+ }
9299
+ if (ok < anchors.length) chainFailed = true;
9300
+ } catch (e) {
9301
+ console.log(`${c.red}FAILED${c.reset}`);
9302
+ console.error(` ${c.red}${e.message}${c.reset}`);
9303
+ chainFailed = true;
9304
+ }
9305
+ }
9306
+ console.log();
9307
+ if (manifest.encryption) {
9308
+ console.log(` ${c.bold}Encryption${c.reset}`);
9309
+ if (decryptionKey) {
9310
+ const decrypted = records.filter((r) => !r.encrypted).length;
9311
+ console.log(` decrypted: ${c.green}${decrypted}${c.reset} of ${records.length}`);
9312
+ } else {
9313
+ console.log(` ${c.yellow}no --decrypt-key supplied; ${records.length - minimalRecords.length} record(s) excluded from minimalRecords${c.reset}`);
9314
+ }
9315
+ console.log();
9316
+ }
9317
+ if (warnings.length > 0) {
9318
+ console.log(` ${c.bold}Warnings${c.reset}`);
9319
+ for (const w of warnings) console.log(` ${c.yellow}!${c.reset} ${w}`);
9320
+ console.log();
9321
+ }
9322
+ if (chainFailed) {
9323
+ console.log(`${c.red}REJECTED${c.reset} Chain verification incomplete.`);
9324
+ process.exit(1);
9325
+ }
9326
+ console.log(`${c.green}OK${c.reset} Pack is valid.`);
9327
+ }
9328
+ var init_verify2 = __esm({
9329
+ "packages/brain/src/cli/verify.ts"() {
9330
+ "use strict";
9331
+ init_src();
9332
+ init_banner();
9333
+ }
9334
+ });
9335
+
9336
+ // packages/brain/src/cli/snapshot.ts
9337
+ var snapshot_exports = {};
9338
+ __export(snapshot_exports, {
9339
+ runSnapshot: () => runSnapshot
9340
+ });
9341
+ function parseArgs2() {
9342
+ const argv = process.argv.slice(3);
9343
+ const args = { showHelp: false };
9344
+ for (let i = 0; i < argv.length; i++) {
9345
+ const a = argv[i];
9346
+ if (a === "--help" || a === "-h") args.showHelp = true;
9347
+ else if (a === "--out") args.out = argv[++i];
9348
+ else if (a === "--encrypt-key") args.encryptKey = argv[++i];
9349
+ }
9350
+ return args;
9351
+ }
9352
+ function printUsage2() {
9353
+ console.log(`Usage: clude snapshot [options]
9354
+
9355
+ Writes a dated MemoryPack tarball of your local memories.
9356
+
9357
+ Options:
9358
+ --out <path> Output path (default:
9359
+ ~/.clude/snapshots/clude-YYYYMMDD-HHMMSS.tar.zst)
9360
+ --encrypt-key <base64> Encrypt the snapshot (32 bytes, base64).
9361
+ Without this, the snapshot is plaintext on
9362
+ disk \u2014 fine for personal local backups,
9363
+ but encrypt for anything you'll move off-host.
9364
+
9365
+ Cron example (daily 03:00):
9366
+ 0 3 * * * /usr/local/bin/node /usr/local/lib/node_modules/@clude/sdk/dist/cli/index.js snapshot
9367
+
9368
+ Exit code: 0 on success, 1 on failure. Single-line stdout on success
9369
+ (the snapshot path); errors go to stderr.`);
9370
+ }
9371
+ function timestamp() {
9372
+ const d = /* @__PURE__ */ new Date();
9373
+ const pad = (n) => String(n).padStart(2, "0");
9374
+ return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}-${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}`;
9375
+ }
9376
+ function defaultOutPath() {
9377
+ const dir = (0, import_path7.join)((0, import_os2.homedir)(), ".clude", "snapshots");
9378
+ if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true });
9379
+ return (0, import_path7.join)(dir, `clude-${timestamp()}.tar.zst`);
9380
+ }
9381
+ async function runSnapshot() {
9382
+ const args = parseArgs2();
9383
+ if (args.showHelp) {
9384
+ printUsage2();
9385
+ process.exit(0);
9386
+ }
9387
+ let raw;
9388
+ try {
9389
+ const { localRecall: localRecall2, localStats: localStats2 } = (init_local_store(), __toCommonJS(local_store_exports));
9390
+ const stats = localStats2();
9391
+ if (stats.total_memories === 0) {
9392
+ console.error("clude snapshot: no local memories found in ~/.clude/memories.json");
9393
+ process.exit(1);
9394
+ }
9395
+ raw = localRecall2({ query: "", limit: 1e4 });
9396
+ } catch (err) {
9397
+ console.error(`clude snapshot: failed to read local store: ${err.message}`);
9398
+ process.exit(1);
9399
+ }
9400
+ const records = raw.map((m, i) => {
9401
+ const rec = {
9402
+ id: m.id ? String(m.id) : String(i + 1),
9403
+ created_at: m.created_at || (/* @__PURE__ */ new Date()).toISOString(),
9404
+ kind: m.type || m.memory_type || "episodic",
9405
+ content: m.content || "",
9406
+ tags: m.tags || [],
9407
+ importance: typeof m.importance === "number" ? m.importance : 0.5,
9408
+ source: m.source || ""
9409
+ };
9410
+ if (m.summary) rec.summary = m.summary;
9411
+ if (m.access_count != null) rec.access_count = m.access_count;
9412
+ if (m.last_accessed_at) rec.last_accessed_at = m.last_accessed_at;
9413
+ return rec;
9414
+ });
9415
+ let secretKey;
9416
+ let publicKey;
9417
+ try {
9418
+ const { getBotWallet: getBotWallet2 } = (init_solana_client(), __toCommonJS(solana_client_exports));
9419
+ const wallet = getBotWallet2();
9420
+ if (wallet) {
9421
+ secretKey = wallet.secretKey;
9422
+ publicKey = wallet.publicKey.toBase58();
9423
+ }
9424
+ } catch {
9425
+ }
9426
+ let encryption;
9427
+ if (args.encryptKey) {
9428
+ const buf = Buffer.from(args.encryptKey, "base64");
9429
+ if (buf.length !== ENCRYPTION_KEY_BYTES) {
9430
+ console.error(
9431
+ `clude snapshot: --encrypt-key must decode to ${ENCRYPTION_KEY_BYTES} bytes (got ${buf.length})`
9432
+ );
9433
+ process.exit(1);
9434
+ }
9435
+ encryption = { key: new Uint8Array(buf) };
9436
+ }
9437
+ const outPath = args.out || defaultOutPath();
9438
+ try {
9439
+ writeMemoryPack(outPath, records, {
9440
+ producer: {
9441
+ name: "clude",
9442
+ version: "3.1.0",
9443
+ public_key: publicKey
9444
+ },
9445
+ record_schema: "clude-memory-v3",
9446
+ secretKey,
9447
+ anchor_chain: "solana-mainnet",
9448
+ format: "tarball",
9449
+ encryption
9450
+ });
9451
+ } catch (err) {
9452
+ console.error(`clude snapshot: write failed: ${err.message}`);
9453
+ process.exit(1);
9454
+ }
9455
+ console.log(outPath);
9456
+ }
9457
+ var import_fs8, import_os2, import_path7;
9458
+ var init_snapshot = __esm({
9459
+ "packages/brain/src/cli/snapshot.ts"() {
9460
+ "use strict";
9461
+ import_fs8 = require("fs");
9462
+ import_os2 = require("os");
9463
+ import_path7 = require("path");
9464
+ init_src();
7860
9465
  }
7861
9466
  });
7862
9467
 
@@ -8050,23 +9655,23 @@ var init_clinamen = __esm({
8050
9655
  var server_exports = {};
8051
9656
  async function getSqliteStore() {
8052
9657
  if (_sqliteStore) return _sqliteStore;
8053
- const path8 = require("path");
8054
- const os5 = require("os");
8055
- const fs6 = require("fs");
9658
+ const path9 = require("path");
9659
+ const os6 = require("os");
9660
+ const fs7 = require("fs");
8056
9661
  const { SqliteStore: SqliteStore2 } = (init_sqlite_store(), __toCommonJS(sqlite_store_exports));
8057
9662
  const { LocalEmbedder: LocalEmbedder2 } = (init_embedder(), __toCommonJS(embedder_exports));
8058
- const dbDir = path8.join(os5.homedir(), ".clude");
8059
- fs6.mkdirSync(dbDir, { recursive: true });
9663
+ const dbDir = path9.join(os6.homedir(), ".clude");
9664
+ fs7.mkdirSync(dbDir, { recursive: true });
8060
9665
  const embedder = new LocalEmbedder2();
8061
9666
  const store = new SqliteStore2({
8062
- dbPath: path8.join(dbDir, "brain.db"),
9667
+ dbPath: path9.join(dbDir, "brain.db"),
8063
9668
  embedder
8064
9669
  });
8065
9670
  _sqliteStore = store;
8066
9671
  return store;
8067
9672
  }
8068
- async function cortexFetch(method, path8, body) {
8069
- const url = `${CORTEX_HOST_URL}${path8}`;
9673
+ async function cortexFetch(method, path9, body) {
9674
+ const url = `${CORTEX_HOST_URL}${path9}`;
8070
9675
  const controller = new AbortController();
8071
9676
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
8072
9677
  try {
@@ -8086,7 +9691,7 @@ async function cortexFetch(method, path8, body) {
8086
9691
  return await res.json();
8087
9692
  } catch (err) {
8088
9693
  if (err.name === "AbortError") {
8089
- throw new Error(`Cortex API timeout after ${FETCH_TIMEOUT_MS / 1e3}s: ${method} ${path8}`);
9694
+ throw new Error(`Cortex API timeout after ${FETCH_TIMEOUT_MS / 1e3}s: ${method} ${path9}`);
8090
9695
  }
8091
9696
  throw err;
8092
9697
  } finally {
@@ -8962,7 +10567,9 @@ var require_package = __commonJS({
8962
10567
  "@ai-sdk/openai": "^3.0.49",
8963
10568
  "@ai-sdk/xai": "^3.0.75",
8964
10569
  "@anthropic-ai/sdk": "^0.39.0",
10570
+ "@clude/memorypack": "workspace:*",
8965
10571
  "@clude/shared": "workspace:*",
10572
+ "@clude/tokenization": "workspace:*",
8966
10573
  "@huggingface/transformers": "^4.1.0",
8967
10574
  "@modelcontextprotocol/sdk": "^1.26.0",
8968
10575
  "@openrouter/ai-sdk-provider": "^2.3.3",
@@ -9019,6 +10626,12 @@ if (command === "setup") {
9019
10626
  console.error("MCP install failed:", err.message);
9020
10627
  process.exit(1);
9021
10628
  });
10629
+ } else if (command === "connect") {
10630
+ const { runConnect: runConnect2 } = (init_connect(), __toCommonJS(connect_exports));
10631
+ runConnect2().catch((err) => {
10632
+ console.error("Connect failed:", err.message);
10633
+ process.exit(1);
10634
+ });
9022
10635
  } else if (command === "ship") {
9023
10636
  const message = process.argv.slice(3).join(" ");
9024
10637
  const { runShip: runShip2 } = (init_ship(), __toCommonJS(ship_exports));
@@ -9068,6 +10681,28 @@ if (command === "setup") {
9068
10681
  console.error("Dream failed:", err.message);
9069
10682
  process.exit(1);
9070
10683
  });
10684
+ } else if (command === "verify") {
10685
+ try {
10686
+ const { runVerify: runVerify2 } = (init_verify2(), __toCommonJS(verify_exports));
10687
+ runVerify2().catch((err) => {
10688
+ console.error("Verify failed:", err.message);
10689
+ process.exit(1);
10690
+ });
10691
+ } catch (err) {
10692
+ console.error("Verify failed:", err.message);
10693
+ process.exit(1);
10694
+ }
10695
+ } else if (command === "snapshot") {
10696
+ try {
10697
+ const { runSnapshot: runSnapshot2 } = (init_snapshot(), __toCommonJS(snapshot_exports));
10698
+ runSnapshot2().catch((err) => {
10699
+ console.error("Snapshot failed:", err.message);
10700
+ process.exit(1);
10701
+ });
10702
+ } catch (err) {
10703
+ console.error("Snapshot failed:", err.message);
10704
+ process.exit(1);
10705
+ }
9071
10706
  } else if (command === "mcp-serve") {
9072
10707
  init_server();
9073
10708
  } else if (command === "start" || command === "bot") {
@@ -9089,10 +10724,13 @@ if (command === "setup") {
9089
10724
  console.log(` ${c2.cyan}npx @clude/sdk status${c2.reset} Check if Clude is active + memory stats`);
9090
10725
  console.log(` ${c2.cyan}npx @clude/sdk init${c2.reset} Advanced setup (self-hosted options)`);
9091
10726
  console.log(` ${c2.cyan}npx @clude/sdk register${c2.reset} Get an API key only`);
9092
- console.log(` ${c2.cyan}npx @clude/sdk mcp-install${c2.reset} Install MCP server for your IDE`);
10727
+ console.log(` ${c2.cyan}npx @clude/sdk connect${c2.reset} Connect to Claude Desktop as a remote connector`);
10728
+ console.log(` ${c2.cyan}npx @clude/sdk mcp-install${c2.reset} Install MCP server for your IDE (local stdio)`);
9093
10729
  console.log(` ${c2.cyan}npx @clude/sdk inject-instructions${c2.reset} Write usage instructions to CLAUDE.md`);
9094
- console.log(` ${c2.cyan}npx @clude/sdk export${c2.reset} Export memories (json/md/chatgpt/gemini)`);
10730
+ console.log(` ${c2.cyan}npx @clude/sdk export${c2.reset} Export memories (json/md/chatgpt/gemini/memorypack)`);
9095
10731
  console.log(` ${c2.cyan}npx @clude/sdk import${c2.reset} Import from ChatGPT export, markdown, JSON`);
10732
+ console.log(` ${c2.cyan}npx @clude/sdk verify${c2.reset} Verify a MemoryPack (.tar.zst or directory)`);
10733
+ console.log(` ${c2.cyan}npx @clude/sdk snapshot${c2.reset} Cron-friendly tarball of local memories`);
9096
10734
  console.log(` ${c2.cyan}npx @clude/sdk sync${c2.reset} Auto-update system prompt file`);
9097
10735
  console.log(` ${c2.cyan}npx @clude/sdk ship "msg"${c2.reset} Broadcast to Telegram channel`);
9098
10736
  console.log(` ${c2.cyan}npx @clude/sdk doctor${c2.reset} Run diagnostics`);