mnemospark 0.9.0 → 0.9.2

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/index.js CHANGED
@@ -3024,10 +3024,12 @@ import {
3024
3024
  randomBytes as randomBytesNode,
3025
3025
  randomUUID as randomUUID3
3026
3026
  } from "crypto";
3027
- import { createReadStream as createReadStream2, statfsSync } from "fs";
3027
+ import { createReadStream as createReadStream2, createWriteStream as createWriteStream2, statfsSync } from "fs";
3028
3028
  import { lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
3029
- import { homedir as homedir6 } from "os";
3029
+ import { homedir as homedir6, tmpdir } from "os";
3030
3030
  import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
3031
+ import { Readable } from "stream";
3032
+ import { finished } from "stream/promises";
3031
3033
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
3032
3034
 
3033
3035
  // src/cloud-ls-format.ts
@@ -3817,6 +3819,7 @@ var DEFAULT_BACKUP_DIR = join8(homedir6(), BACKUP_DIR_SUBPATH);
3817
3819
  var BLOCKRUN_WALLET_KEY_SUBPATH = join8(".openclaw", "blockrun", "wallet.key");
3818
3820
  var MNEMOSPARK_WALLET_KEY_SUBPATH = join8(".openclaw", "mnemospark", "wallet", "wallet.key");
3819
3821
  var INLINE_UPLOAD_MAX_BYTES = 45e5;
3822
+ var NODE_FS_MAX_READFILE_BYTES = 2147483648;
3820
3823
  var PAYMENT_CRON_SCHEDULE = "0 0 1 * *";
3821
3824
  var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
3822
3825
  var QUOTE_VALIDITY_USER_NOTE = "Quotes are valid for one hour. Please run price-storage again if you need a new quote.";
@@ -4740,6 +4743,39 @@ function encryptAesGcm(plaintext, key, randomFn = randomBytesNode) {
4740
4743
  const tag = cipher.getAuthTag();
4741
4744
  return Buffer.concat([nonce, ciphertext, tag]);
4742
4745
  }
4746
+ async function encryptPlaintextFileToAesGcmPath(plaintextPath, dek, outPath, randomFn = randomBytesNode) {
4747
+ if (dek.length !== 32) {
4748
+ throw new Error("Expected 32-byte AES key");
4749
+ }
4750
+ const nonce = randomFn(AES_GCM_NONCE_BYTES);
4751
+ const cipher = createCipheriv("aes-256-gcm", dek, nonce);
4752
+ const writeStream = createWriteStream2(outPath, { flags: "w" });
4753
+ writeStream.write(nonce);
4754
+ try {
4755
+ for await (const chunk of createReadStream2(plaintextPath)) {
4756
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4757
+ const out = cipher.update(buf);
4758
+ if (out.length) {
4759
+ await new Promise((resolve3, reject) => {
4760
+ writeStream.write(out, (err) => err ? reject(err) : resolve3());
4761
+ });
4762
+ }
4763
+ }
4764
+ const final = cipher.final();
4765
+ const tag = cipher.getAuthTag();
4766
+ await new Promise((resolve3, reject) => {
4767
+ writeStream.write(final, (err) => err ? reject(err) : resolve3());
4768
+ });
4769
+ await new Promise((resolve3, reject) => {
4770
+ writeStream.write(tag, (err) => err ? reject(err) : resolve3());
4771
+ });
4772
+ writeStream.end();
4773
+ await finished(writeStream);
4774
+ } catch (err) {
4775
+ writeStream.destroy();
4776
+ throw err;
4777
+ }
4778
+ }
4743
4779
  async function loadOrCreateKek(walletAddress, homeDir) {
4744
4780
  const keyPath = resolveWalletKekPath(walletAddress, homeDir);
4745
4781
  await mkdir5(dirname5(keyPath), { recursive: true });
@@ -4756,11 +4792,42 @@ async function loadOrCreateKek(walletAddress, homeDir) {
4756
4792
  return { kek: generated, keyPath };
4757
4793
  }
4758
4794
  async function prepareUploadPayload(archivePath, walletAddress, homeDir) {
4759
- const plaintext = await readFile3(archivePath);
4795
+ const archiveStat = await stat2(archivePath);
4796
+ if (!archiveStat.isFile()) {
4797
+ throw new Error(`Cannot read backup archive: not a file (${archivePath}).`);
4798
+ }
4760
4799
  const { kek, keyPath } = await loadOrCreateKek(walletAddress, homeDir);
4761
4800
  const dek = randomBytesNode(32);
4762
- const encryptedContent = encryptAesGcm(plaintext, dek);
4763
4801
  const wrappedDek = encryptAesGcm(dek, kek);
4802
+ if (archiveStat.size >= NODE_FS_MAX_READFILE_BYTES) {
4803
+ const encryptedTempPath = join8(tmpdir(), `mnemospark-upload-${randomUUID3()}.enc`);
4804
+ try {
4805
+ await encryptPlaintextFileToAesGcmPath(archivePath, dek, encryptedTempPath);
4806
+ const encStat = await stat2(encryptedTempPath);
4807
+ const payloadHash2 = await sha256File(encryptedTempPath);
4808
+ const payload2 = {
4809
+ mode: "presigned",
4810
+ content_base64: void 0,
4811
+ content_sha256: payloadHash2,
4812
+ content_length_bytes: encStat.size,
4813
+ wrapped_dek: wrappedDek.toString("base64"),
4814
+ encryption_algorithm: "AES-256-GCM",
4815
+ bucket_name_hint: bucketNameForWallet(walletAddress),
4816
+ key_store_path_hint: keyPath
4817
+ };
4818
+ return {
4819
+ payload: payload2,
4820
+ encryptedContent: null,
4821
+ encryptedTempPath
4822
+ };
4823
+ } catch (err) {
4824
+ await rm(encryptedTempPath, { force: true }).catch(() => {
4825
+ });
4826
+ throw err;
4827
+ }
4828
+ }
4829
+ const plaintext = await readFile3(archivePath);
4830
+ const encryptedContent = encryptAesGcm(plaintext, dek);
4764
4831
  const payloadHash = sha256Buffer(encryptedContent);
4765
4832
  const payload = {
4766
4833
  mode: encryptedContent.length <= INLINE_UPLOAD_MAX_BYTES ? "inline" : "presigned",
@@ -4777,7 +4844,17 @@ async function prepareUploadPayload(archivePath, walletAddress, homeDir) {
4777
4844
  encryptedContent
4778
4845
  };
4779
4846
  }
4780
- async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encryptedContent, fetchImpl = fetch) {
4847
+ function presignedPutBodyInit(encryptedContent, encryptedTempPath) {
4848
+ if (encryptedTempPath?.trim()) {
4849
+ const body = Readable.toWeb(createReadStream2(encryptedTempPath));
4850
+ return { body, duplex: "half" };
4851
+ }
4852
+ if (encryptedContent) {
4853
+ return { body: new Uint8Array(encryptedContent) };
4854
+ }
4855
+ throw new Error("Cannot upload storage object: missing encrypted payload body.");
4856
+ }
4857
+ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encryptedContent, encryptedTempPath, fetchImpl = fetch, presignedStreamContentLengthBytes) {
4781
4858
  if (!uploadResponse.upload_url) {
4782
4859
  if (uploadMode === "presigned") {
4783
4860
  throw new Error("Cannot upload storage object: missing presigned upload URL.");
@@ -4788,24 +4865,39 @@ async function uploadPresignedObjectIfNeeded(uploadResponse, uploadMode, encrypt
4788
4865
  if (!headers.has("content-type")) {
4789
4866
  headers.set("content-type", "application/octet-stream");
4790
4867
  }
4791
- const putBody = new Uint8Array(encryptedContent);
4792
- const firstAttempt = await fetchImpl(uploadResponse.upload_url, {
4868
+ if (encryptedTempPath?.trim()) {
4869
+ const len = presignedStreamContentLengthBytes ?? (await stat2(encryptedTempPath.trim())).size;
4870
+ if (!headers.has("content-length")) {
4871
+ headers.set("content-length", String(len));
4872
+ }
4873
+ }
4874
+ const { body, duplex } = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4875
+ const firstInit = {
4793
4876
  method: "PUT",
4794
4877
  headers,
4795
- body: putBody,
4878
+ body,
4796
4879
  redirect: "manual"
4797
- });
4880
+ };
4881
+ if (duplex) {
4882
+ firstInit.duplex = duplex;
4883
+ }
4884
+ const firstAttempt = await fetchImpl(uploadResponse.upload_url, firstInit);
4798
4885
  if (firstAttempt.ok) {
4799
4886
  return;
4800
4887
  }
4801
4888
  if ((firstAttempt.status === 307 || firstAttempt.status === 308) && firstAttempt.headers.has("location")) {
4802
4889
  const location = firstAttempt.headers.get("location")?.trim();
4803
4890
  if (location) {
4804
- const redirectedAttempt = await fetchImpl(location, {
4891
+ const retryBody = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4892
+ const retryInit = {
4805
4893
  method: "PUT",
4806
4894
  headers,
4807
- body: putBody
4808
- });
4895
+ body: retryBody.body
4896
+ };
4897
+ if (retryBody.duplex) {
4898
+ retryInit.duplex = retryBody.duplex;
4899
+ }
4900
+ const redirectedAttempt = await fetchImpl(location, retryInit);
4809
4901
  if (redirectedAttempt.ok) {
4810
4902
  return;
4811
4903
  }
@@ -6218,6 +6310,7 @@ operation-id: ${operationId}`,
6218
6310
  executionContext.forcedOperationId ?? idempotencyKeyFn(),
6219
6311
  executionContext.forcedTraceId
6220
6312
  );
6313
+ let preparedPayload;
6221
6314
  try {
6222
6315
  const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id);
6223
6316
  if (!loggedQuote) {
@@ -6289,7 +6382,7 @@ operation-id: ${operationId}`,
6289
6382
  isError: true
6290
6383
  };
6291
6384
  }
6292
- const preparedPayload = await prepareUploadPayload(
6385
+ preparedPayload = await prepareUploadPayload(
6293
6386
  archivePath,
6294
6387
  parsed.uploadRequest.wallet_address,
6295
6388
  mnemosparkHomeDir
@@ -6333,7 +6426,9 @@ operation-id: ${operationId}`,
6333
6426
  uploadResponse,
6334
6427
  preparedPayload.payload.mode,
6335
6428
  preparedPayload.encryptedContent,
6336
- fetchImpl
6429
+ preparedPayload.encryptedTempPath,
6430
+ fetchImpl,
6431
+ preparedPayload.encryptedTempPath ? preparedPayload.payload.content_length_bytes : void 0
6337
6432
  );
6338
6433
  let finalizedUploadResponse = uploadResponse;
6339
6434
  if (preparedPayload.payload.mode === "presigned" && uploadResponse.confirmation_required === true) {
@@ -6470,6 +6565,11 @@ operation-id: ${operationId}`,
6470
6565
  text: uploadErrorMessage ?? "Cannot upload storage object",
6471
6566
  isError: true
6472
6567
  };
6568
+ } finally {
6569
+ if (preparedPayload?.encryptedTempPath) {
6570
+ await rm(preparedPayload.encryptedTempPath, { force: true }).catch(() => {
6571
+ });
6572
+ }
6473
6573
  }
6474
6574
  }
6475
6575
  if (parsed.mode === "ls") {