mnemospark 0.9.0 → 0.9.1

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.js CHANGED
@@ -2973,10 +2973,12 @@ import {
2973
2973
  randomBytes as randomBytesNode,
2974
2974
  randomUUID as randomUUID3
2975
2975
  } from "crypto";
2976
- import { createReadStream as createReadStream2, statfsSync } from "fs";
2976
+ import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, statfsSync } from "fs";
2977
2977
  import { lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
2978
- import { homedir as homedir6 } from "os";
2978
+ import { homedir as homedir6, tmpdir } from "os";
2979
2979
  import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
2980
+ import { Readable } from "stream";
2981
+ import { finished } from "stream/promises";
2980
2982
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
2981
2983
 
2982
2984
  // src/cloud-ls-format.ts
@@ -3766,6 +3768,7 @@ var DEFAULT_BACKUP_DIR = join8(homedir6(), BACKUP_DIR_SUBPATH);
3766
3768
  var BLOCKRUN_WALLET_KEY_SUBPATH = join8(".openclaw", "blockrun", "wallet.key");
3767
3769
  var MNEMOSPARK_WALLET_KEY_SUBPATH = join8(".openclaw", "mnemospark", "wallet", "wallet.key");
3768
3770
  var INLINE_UPLOAD_MAX_BYTES = 45e5;
3771
+ var NODE_FS_MAX_READFILE_BYTES = 2147483648;
3769
3772
  var PAYMENT_CRON_SCHEDULE = "0 0 1 * *";
3770
3773
  var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
3771
3774
  var QUOTE_VALIDITY_USER_NOTE = "Quotes are valid for one hour. Please run price-storage again if you need a new quote.";
@@ -4689,6 +4692,39 @@ function encryptAesGcm(plaintext, key, randomFn = randomBytesNode) {
4689
4692
  const tag = cipher.getAuthTag();
4690
4693
  return Buffer.concat([nonce, ciphertext, tag]);
4691
4694
  }
4695
+ async function encryptPlaintextFileToAesGcmPath(plaintextPath, dek, outPath, randomFn = randomBytesNode) {
4696
+ if (dek.length !== 32) {
4697
+ throw new Error("Expected 32-byte AES key");
4698
+ }
4699
+ const nonce = randomFn(AES_GCM_NONCE_BYTES);
4700
+ const cipher = createCipheriv("aes-256-gcm", dek, nonce);
4701
+ const writeStream = createWriteStream2(outPath, { flags: "w" });
4702
+ writeStream.write(nonce);
4703
+ try {
4704
+ for await (const chunk of createReadStream2(plaintextPath)) {
4705
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4706
+ const out = cipher.update(buf);
4707
+ if (out.length) {
4708
+ await new Promise((resolve3, reject) => {
4709
+ writeStream.write(out, (err) => err ? reject(err) : resolve3());
4710
+ });
4711
+ }
4712
+ }
4713
+ const final = cipher.final();
4714
+ const tag = cipher.getAuthTag();
4715
+ await new Promise((resolve3, reject) => {
4716
+ writeStream.write(final, (err) => err ? reject(err) : resolve3());
4717
+ });
4718
+ await new Promise((resolve3, reject) => {
4719
+ writeStream.write(tag, (err) => err ? reject(err) : resolve3());
4720
+ });
4721
+ writeStream.end();
4722
+ await finished(writeStream);
4723
+ } catch (err) {
4724
+ writeStream.destroy();
4725
+ throw err;
4726
+ }
4727
+ }
4692
4728
  async function loadOrCreateKek(walletAddress, homeDir) {
4693
4729
  const keyPath = resolveWalletKekPath(walletAddress, homeDir);
4694
4730
  await mkdir5(dirname5(keyPath), { recursive: true });
@@ -4705,11 +4741,42 @@ async function loadOrCreateKek(walletAddress, homeDir) {
4705
4741
  return { kek: generated, keyPath };
4706
4742
  }
4707
4743
  async function prepareUploadPayload(archivePath, walletAddress, homeDir) {
4708
- const plaintext = await readFile3(archivePath);
4744
+ const archiveStat = await stat2(archivePath);
4745
+ if (!archiveStat.isFile()) {
4746
+ throw new Error(`Cannot read backup archive: not a file (${archivePath}).`);
4747
+ }
4709
4748
  const { kek, keyPath } = await loadOrCreateKek(walletAddress, homeDir);
4710
4749
  const dek = randomBytesNode(32);
4711
- const encryptedContent = encryptAesGcm(plaintext, dek);
4712
4750
  const wrappedDek = encryptAesGcm(dek, kek);
4751
+ if (archiveStat.size >= NODE_FS_MAX_READFILE_BYTES) {
4752
+ const encryptedTempPath = join8(tmpdir(), `mnemospark-upload-${randomUUID3()}.enc`);
4753
+ try {
4754
+ await encryptPlaintextFileToAesGcmPath(archivePath, dek, encryptedTempPath);
4755
+ const encStat = await stat2(encryptedTempPath);
4756
+ const payloadHash2 = await sha256File(encryptedTempPath);
4757
+ const payload2 = {
4758
+ mode: "presigned",
4759
+ content_base64: void 0,
4760
+ content_sha256: payloadHash2,
4761
+ content_length_bytes: encStat.size,
4762
+ wrapped_dek: wrappedDek.toString("base64"),
4763
+ encryption_algorithm: "AES-256-GCM",
4764
+ bucket_name_hint: bucketNameForWallet(walletAddress),
4765
+ key_store_path_hint: keyPath
4766
+ };
4767
+ return {
4768
+ payload: payload2,
4769
+ encryptedContent: null,
4770
+ encryptedTempPath
4771
+ };
4772
+ } catch (err) {
4773
+ await rm(encryptedTempPath, { force: true }).catch(() => {
4774
+ });
4775
+ throw err;
4776
+ }
4777
+ }
4778
+ const plaintext = await readFile3(archivePath);
4779
+ const encryptedContent = encryptAesGcm(plaintext, dek);
4713
4780
  const payloadHash = sha256Buffer(encryptedContent);
4714
4781
  const payload = {
4715
4782
  mode: encryptedContent.length <= INLINE_UPLOAD_MAX_BYTES ? "inline" : "presigned",
@@ -4726,7 +4793,17 @@ async function prepareUploadPayload(archivePath, walletAddress, homeDir) {
4726
4793
  encryptedContent
4727
4794
  };
4728
4795
  }
4729
- async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encryptedContent, fetchImpl = fetch) {
4796
+ function presignedPutBodyInit(encryptedContent, encryptedTempPath) {
4797
+ if (encryptedTempPath?.trim()) {
4798
+ const body = Readable.toWeb(createReadStream2(encryptedTempPath));
4799
+ return { body, duplex: "half" };
4800
+ }
4801
+ if (encryptedContent) {
4802
+ return { body: new Uint8Array(encryptedContent) };
4803
+ }
4804
+ throw new Error("Cannot upload storage object: missing encrypted payload body.");
4805
+ }
4806
+ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encryptedContent, encryptedTempPath, fetchImpl = fetch) {
4730
4807
  if (!uploadResponse.upload_url) {
4731
4808
  if (uploadMode === "presigned") {
4732
4809
  throw new Error("Cannot upload storage object: missing presigned upload URL.");
@@ -4737,24 +4814,33 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
4737
4814
  if (!headers.has("content-type")) {
4738
4815
  headers.set("content-type", "application/octet-stream");
4739
4816
  }
4740
- const putBody = new Uint8Array(encryptedContent);
4741
- const firstAttempt = await fetchImpl(uploadResponse.upload_url, {
4817
+ const { body, duplex } = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4818
+ const firstInit = {
4742
4819
  method: "PUT",
4743
4820
  headers,
4744
- body: putBody,
4821
+ body,
4745
4822
  redirect: "manual"
4746
- });
4823
+ };
4824
+ if (duplex) {
4825
+ firstInit.duplex = duplex;
4826
+ }
4827
+ const firstAttempt = await fetchImpl(uploadResponse.upload_url, firstInit);
4747
4828
  if (firstAttempt.ok) {
4748
4829
  return;
4749
4830
  }
4750
4831
  if ((firstAttempt.status === 307 || firstAttempt.status === 308) && firstAttempt.headers.has("location")) {
4751
4832
  const location = firstAttempt.headers.get("location")?.trim();
4752
4833
  if (location) {
4753
- const redirectedAttempt = await fetchImpl(location, {
4834
+ const retryBody = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4835
+ const retryInit = {
4754
4836
  method: "PUT",
4755
4837
  headers,
4756
- body: putBody
4757
- });
4838
+ body: retryBody.body
4839
+ };
4840
+ if (retryBody.duplex) {
4841
+ retryInit.duplex = retryBody.duplex;
4842
+ }
4843
+ const redirectedAttempt = await fetchImpl(location, retryInit);
4758
4844
  if (redirectedAttempt.ok) {
4759
4845
  return;
4760
4846
  }
@@ -6167,6 +6253,7 @@ operation-id: ${operationId}`,
6167
6253
  executionContext.forcedOperationId ?? idempotencyKeyFn(),
6168
6254
  executionContext.forcedTraceId
6169
6255
  );
6256
+ let preparedPayload;
6170
6257
  try {
6171
6258
  const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id);
6172
6259
  if (!loggedQuote) {
@@ -6238,7 +6325,7 @@ operation-id: ${operationId}`,
6238
6325
  isError: true
6239
6326
  };
6240
6327
  }
6241
- const preparedPayload = await prepareUploadPayload(
6328
+ preparedPayload = await prepareUploadPayload(
6242
6329
  archivePath,
6243
6330
  parsed.uploadRequest.wallet_address,
6244
6331
  mnemosparkHomeDir
@@ -6282,6 +6369,7 @@ operation-id: ${operationId}`,
6282
6369
  uploadResponse,
6283
6370
  preparedPayload.payload.mode,
6284
6371
  preparedPayload.encryptedContent,
6372
+ preparedPayload.encryptedTempPath,
6285
6373
  fetchImpl
6286
6374
  );
6287
6375
  let finalizedUploadResponse = uploadResponse;
@@ -6419,6 +6507,11 @@ operation-id: ${operationId}`,
6419
6507
  text: uploadErrorMessage ?? "Cannot upload storage object",
6420
6508
  isError: true
6421
6509
  };
6510
+ } finally {
6511
+ if (preparedPayload?.encryptedTempPath) {
6512
+ await rm(preparedPayload.encryptedTempPath, { force: true }).catch(() => {
6513
+ });
6514
+ }
6422
6515
  }
6423
6516
  }
6424
6517
  if (parsed.mode === "ls") {