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/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, presignedStreamContentLengthBytes) {
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,39 @@ 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
+ if (encryptedTempPath?.trim()) {
4818
+ const len = presignedStreamContentLengthBytes ?? (await stat2(encryptedTempPath.trim())).size;
4819
+ if (!headers.has("content-length")) {
4820
+ headers.set("content-length", String(len));
4821
+ }
4822
+ }
4823
+ const { body, duplex } = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4824
+ const firstInit = {
4742
4825
  method: "PUT",
4743
4826
  headers,
4744
- body: putBody,
4827
+ body,
4745
4828
  redirect: "manual"
4746
- });
4829
+ };
4830
+ if (duplex) {
4831
+ firstInit.duplex = duplex;
4832
+ }
4833
+ const firstAttempt = await fetchImpl(uploadResponse.upload_url, firstInit);
4747
4834
  if (firstAttempt.ok) {
4748
4835
  return;
4749
4836
  }
4750
4837
  if ((firstAttempt.status === 307 || firstAttempt.status === 308) && firstAttempt.headers.has("location")) {
4751
4838
  const location = firstAttempt.headers.get("location")?.trim();
4752
4839
  if (location) {
4753
- const redirectedAttempt = await fetchImpl(location, {
4840
+ const retryBody = presignedPutBodyInit(encryptedContent, encryptedTempPath);
4841
+ const retryInit = {
4754
4842
  method: "PUT",
4755
4843
  headers,
4756
- body: putBody
4757
- });
4844
+ body: retryBody.body
4845
+ };
4846
+ if (retryBody.duplex) {
4847
+ retryInit.duplex = retryBody.duplex;
4848
+ }
4849
+ const redirectedAttempt = await fetchImpl(location, retryInit);
4758
4850
  if (redirectedAttempt.ok) {
4759
4851
  return;
4760
4852
  }
@@ -6167,6 +6259,7 @@ operation-id: ${operationId}`,
6167
6259
  executionContext.forcedOperationId ?? idempotencyKeyFn(),
6168
6260
  executionContext.forcedTraceId
6169
6261
  );
6262
+ let preparedPayload;
6170
6263
  try {
6171
6264
  const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id);
6172
6265
  if (!loggedQuote) {
@@ -6238,7 +6331,7 @@ operation-id: ${operationId}`,
6238
6331
  isError: true
6239
6332
  };
6240
6333
  }
6241
- const preparedPayload = await prepareUploadPayload(
6334
+ preparedPayload = await prepareUploadPayload(
6242
6335
  archivePath,
6243
6336
  parsed.uploadRequest.wallet_address,
6244
6337
  mnemosparkHomeDir
@@ -6282,7 +6375,9 @@ operation-id: ${operationId}`,
6282
6375
  uploadResponse,
6283
6376
  preparedPayload.payload.mode,
6284
6377
  preparedPayload.encryptedContent,
6285
- fetchImpl
6378
+ preparedPayload.encryptedTempPath,
6379
+ fetchImpl,
6380
+ preparedPayload.encryptedTempPath ? preparedPayload.payload.content_length_bytes : void 0
6286
6381
  );
6287
6382
  let finalizedUploadResponse = uploadResponse;
6288
6383
  if (preparedPayload.payload.mode === "presigned" && uploadResponse.confirmation_required === true) {
@@ -6419,6 +6514,11 @@ operation-id: ${operationId}`,
6419
6514
  text: uploadErrorMessage ?? "Cannot upload storage object",
6420
6515
  isError: true
6421
6516
  };
6517
+ } finally {
6518
+ if (preparedPayload?.encryptedTempPath) {
6519
+ await rm(preparedPayload.encryptedTempPath, { force: true }).catch(() => {
6520
+ });
6521
+ }
6422
6522
  }
6423
6523
  }
6424
6524
  if (parsed.mode === "ls") {