mnemospark 0.1.20 → 0.1.21

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
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/proxy.ts
4
+ import { randomUUID } from "crypto";
4
5
  import { createServer } from "http";
5
- import { homedir as homedir2 } from "os";
6
- import { join as join3 } from "path";
6
+ import { homedir as homedir3 } from "os";
7
+ import { join as join4 } from "path";
7
8
  import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
8
9
 
9
10
  // src/balance.ts
@@ -1432,11 +1433,52 @@ async function downloadStorageToDisk(request, backendResponse, options = {}) {
1432
1433
  };
1433
1434
  }
1434
1435
 
1436
+ // src/cloud-jsonl.ts
1437
+ import { createGzip } from "zlib";
1438
+ import { createReadStream, createWriteStream } from "fs";
1439
+ import { appendFile, mkdir as mkdir2, readdir, rename, stat, unlink } from "fs/promises";
1440
+ import { homedir as homedir2 } from "os";
1441
+ import { basename, dirname as dirname2, join as join3 } from "path";
1442
+ import { pipeline } from "stream/promises";
1443
+ var BASE_DIR = join3(homedir2(), ".openclaw", "mnemospark");
1444
+ var MAX_BYTES = 10 * 1024 * 1024;
1445
+ var KEEP_ROTATED = 10;
1446
+ function resolvePath(fileName, homeDir) {
1447
+ const baseDir = homeDir ? join3(homeDir, ".openclaw", "mnemospark") : BASE_DIR;
1448
+ return join3(baseDir, fileName);
1449
+ }
1450
+ async function rotateIfNeeded(path) {
1451
+ let fileStat;
1452
+ try {
1453
+ fileStat = await stat(path);
1454
+ } catch {
1455
+ return;
1456
+ }
1457
+ if (fileStat.size < MAX_BYTES) return;
1458
+ const rotated = `${path}.${Date.now()}.1`;
1459
+ await rename(path, rotated);
1460
+ const gzPath = `${rotated}.gz`;
1461
+ await pipeline(createReadStream(rotated), createGzip(), createWriteStream(gzPath));
1462
+ await unlink(rotated).catch(() => void 0);
1463
+ const dir = dirname2(path);
1464
+ const base2 = basename(path) || "events.jsonl";
1465
+ const all = (await readdir(dir)).filter((name) => name.startsWith(`${base2}.`) && name.endsWith(".gz")).sort().reverse();
1466
+ const stale = all.slice(KEEP_ROTATED);
1467
+ await Promise.all(stale.map((name) => unlink(join3(dir, name)).catch(() => void 0)));
1468
+ }
1469
+ async function appendJsonlEvent(fileName, event, homeDir) {
1470
+ const filePath = resolvePath(fileName, homeDir);
1471
+ await mkdir2(dirname2(filePath), { recursive: true });
1472
+ await appendFile(filePath, `${JSON.stringify(event)}
1473
+ `, "utf-8");
1474
+ await rotateIfNeeded(filePath);
1475
+ }
1476
+
1435
1477
  // src/proxy.ts
1436
1478
  var HEALTH_CHECK_TIMEOUT_MS = 2e3;
1437
1479
  var PORT_RETRY_ATTEMPTS = 5;
1438
1480
  var PORT_RETRY_DELAY_MS = 1e3;
1439
- var DEFAULT_DOWNLOAD_OUTPUT_DIR = join3(homedir2(), ".openclaw", "mnemospark", "downloads");
1481
+ var DEFAULT_DOWNLOAD_OUTPUT_DIR = join4(homedir3(), ".openclaw", "mnemospark", "downloads");
1440
1482
  function resolveDownloadOutputDir() {
1441
1483
  const configuredOutputDir = process.env.MNEMOSPARK_DOWNLOAD_DIR?.trim();
1442
1484
  if (configuredOutputDir && configuredOutputDir.length > 0) {
@@ -1490,6 +1532,20 @@ function logProxyEvent(level, event, fields = {}) {
1490
1532
  }
1491
1533
  console.info(message);
1492
1534
  }
1535
+ function emitProxyEvent(eventType, status, correlation, details = {}) {
1536
+ void appendJsonlEvent("proxy-events.jsonl", {
1537
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1538
+ event_type: eventType,
1539
+ status,
1540
+ trace_id: correlation.trace_id,
1541
+ operation_id: correlation.operation_id,
1542
+ quote_id: correlation.quote_id ?? null,
1543
+ wallet_address: correlation.wallet_address ?? null,
1544
+ object_id: correlation.object_id ?? null,
1545
+ object_key: correlation.object_key ?? null,
1546
+ details
1547
+ }).catch(() => void 0);
1548
+ }
1493
1549
  function isAlreadySettledConflict(status, bodyText) {
1494
1550
  if (status !== 409) {
1495
1551
  return false;
@@ -1614,7 +1670,12 @@ async function startProxy(options) {
1614
1670
  console.error(`[mnemospark] Response stream error: ${err.message}`);
1615
1671
  });
1616
1672
  if (req.method === "POST" && matchesProxyPath(req.url, PRICE_STORAGE_PROXY_PATH)) {
1673
+ const correlation = {
1674
+ trace_id: randomUUID(),
1675
+ operation_id: randomUUID()
1676
+ };
1617
1677
  logProxyEvent("info", "proxy_price_storage_received");
1678
+ emitProxyEvent("request.received", "start", correlation, { path: PRICE_STORAGE_PROXY_PATH });
1618
1679
  try {
1619
1680
  let payload;
1620
1681
  try {
@@ -1636,6 +1697,9 @@ async function startProxy(options) {
1636
1697
  });
1637
1698
  return;
1638
1699
  }
1700
+ correlation.wallet_address = requestPayload.wallet_address;
1701
+ correlation.object_id = requestPayload.object_id;
1702
+ emitProxyEvent("storage.call", "start", correlation, { target: "price-storage" });
1639
1703
  const walletSignature = await createBackendWalletSignature(
1640
1704
  "POST",
1641
1705
  "/price-storage",
@@ -1654,6 +1718,7 @@ async function startProxy(options) {
1654
1718
  logProxyEvent("info", "proxy_price_storage_backend_response", {
1655
1719
  status: backendResponse.status
1656
1720
  });
1721
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
1657
1722
  const authFailure = normalizeBackendAuthFailure(
1658
1723
  backendResponse.status,
1659
1724
  backendResponse.bodyText
@@ -1674,7 +1739,13 @@ async function startProxy(options) {
1674
1739
  const responseHeaders = createBackendForwardHeaders(backendResponse);
1675
1740
  res.writeHead(backendResponse.status, responseHeaders);
1676
1741
  res.end(backendResponse.bodyText);
1742
+ emitProxyEvent("terminal.success", "success", correlation, {
1743
+ status: backendResponse.status
1744
+ });
1677
1745
  } catch (err) {
1746
+ emitProxyEvent("terminal.failure", "failure", correlation, {
1747
+ error: err instanceof Error ? err.message : String(err)
1748
+ });
1678
1749
  logProxyEvent("error", "proxy_price_storage_forward_failed", {
1679
1750
  error: err instanceof Error ? err.message : String(err)
1680
1751
  });
@@ -1686,7 +1757,12 @@ async function startProxy(options) {
1686
1757
  return;
1687
1758
  }
1688
1759
  if (req.method === "POST" && matchesProxyPath(req.url, PAYMENT_SETTLE_PROXY_PATH)) {
1760
+ const correlation = {
1761
+ trace_id: randomUUID(),
1762
+ operation_id: randomUUID()
1763
+ };
1689
1764
  logProxyEvent("info", "proxy_payment_settle_received");
1765
+ emitProxyEvent("request.received", "start", correlation, { path: PAYMENT_SETTLE_PROXY_PATH });
1690
1766
  try {
1691
1767
  let payload;
1692
1768
  try {
@@ -1750,6 +1826,9 @@ async function startProxy(options) {
1750
1826
  res.end(createWalletRequiredBody());
1751
1827
  return;
1752
1828
  }
1829
+ correlation.quote_id = quoteId;
1830
+ correlation.wallet_address = walletAddress;
1831
+ emitProxyEvent("payment.settle", "start", correlation);
1753
1832
  const paymentFetch = createPaymentFetch(walletPrivateKey).fetch;
1754
1833
  const backendResponse = await forwardPaymentSettleToBackend(quoteId, walletAddress, {
1755
1834
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
@@ -1763,6 +1842,7 @@ async function startProxy(options) {
1763
1842
  logProxyEvent("info", "proxy_payment_settle_backend_response", {
1764
1843
  status: backendResponse.status
1765
1844
  });
1845
+ emitProxyEvent("payment.settle", "result", correlation, { status: backendResponse.status });
1766
1846
  const authFailure = normalizeBackendAuthFailure(
1767
1847
  backendResponse.status,
1768
1848
  backendResponse.bodyText
@@ -1783,7 +1863,13 @@ async function startProxy(options) {
1783
1863
  const responseHeaders = createBackendForwardHeaders(backendResponse);
1784
1864
  res.writeHead(backendResponse.status, responseHeaders);
1785
1865
  res.end(backendResponse.bodyText);
1866
+ emitProxyEvent("terminal.success", "success", correlation, {
1867
+ status: backendResponse.status
1868
+ });
1786
1869
  } catch (err) {
1870
+ emitProxyEvent("terminal.failure", "failure", correlation, {
1871
+ error: err instanceof Error ? err.message : String(err)
1872
+ });
1787
1873
  logProxyEvent("error", "proxy_payment_settle_forward_failed", {
1788
1874
  error: err instanceof Error ? err.message : String(err)
1789
1875
  });
@@ -1795,7 +1881,12 @@ async function startProxy(options) {
1795
1881
  return;
1796
1882
  }
1797
1883
  if (req.method === "POST" && matchesProxyPath(req.url, UPLOAD_PROXY_PATH)) {
1884
+ const correlation = {
1885
+ trace_id: randomUUID(),
1886
+ operation_id: randomUUID()
1887
+ };
1798
1888
  logProxyEvent("info", "proxy_upload_received");
1889
+ emitProxyEvent("request.received", "start", correlation, { path: UPLOAD_PROXY_PATH });
1799
1890
  try {
1800
1891
  let payload;
1801
1892
  try {
@@ -1817,6 +1908,10 @@ async function startProxy(options) {
1817
1908
  });
1818
1909
  return;
1819
1910
  }
1911
+ correlation.quote_id = requestPayload.quote_id;
1912
+ correlation.wallet_address = requestPayload.wallet_address;
1913
+ correlation.object_id = requestPayload.object_id;
1914
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/upload" });
1820
1915
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
1821
1916
  logProxyEvent("warn", "proxy_upload_wallet_mismatch", {
1822
1917
  request_wallet: requestPayload.wallet_address,
@@ -1886,6 +1981,7 @@ async function startProxy(options) {
1886
1981
  return;
1887
1982
  }
1888
1983
  const uploadPaymentFetch = createPaymentFetch(walletPrivateKey).fetch;
1984
+ emitProxyEvent("payment.settle", "start", correlation, { via: "upload" });
1889
1985
  const settleResponse = await forwardPaymentSettleToBackend(
1890
1986
  requestPayload.quote_id,
1891
1987
  requestPayload.wallet_address,
@@ -1895,6 +1991,7 @@ async function startProxy(options) {
1895
1991
  fetchImpl: uploadPaymentFetch
1896
1992
  }
1897
1993
  );
1994
+ emitProxyEvent("payment.settle", "result", correlation, { status: settleResponse.status });
1898
1995
  const settledAlready = isAlreadySettledConflict(
1899
1996
  settleResponse.status,
1900
1997
  settleResponse.bodyText
@@ -1913,6 +2010,10 @@ async function startProxy(options) {
1913
2010
  return;
1914
2011
  }
1915
2012
  if (settledAlready) {
2013
+ emitProxyEvent("retry.decision", "decision", correlation, {
2014
+ reason: "payment_already_settled_conflict",
2015
+ status: settleResponse.status
2016
+ });
1916
2017
  logProxyEvent("info", "proxy_upload_settle_already_confirmed", {
1917
2018
  status: settleResponse.status
1918
2019
  });
@@ -1925,6 +2026,7 @@ async function startProxy(options) {
1925
2026
  logProxyEvent("info", "proxy_upload_backend_response", {
1926
2027
  status: backendResponse.status
1927
2028
  });
2029
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
1928
2030
  const authFailure = normalizeBackendAuthFailure(
1929
2031
  backendResponse.status,
1930
2032
  backendResponse.bodyText
@@ -1945,7 +2047,13 @@ async function startProxy(options) {
1945
2047
  const responseHeaders = createBackendForwardHeaders(backendResponse);
1946
2048
  res.writeHead(backendResponse.status, responseHeaders);
1947
2049
  res.end(backendResponse.bodyText);
2050
+ emitProxyEvent("terminal.success", "success", correlation, {
2051
+ status: backendResponse.status
2052
+ });
1948
2053
  } catch (err) {
2054
+ emitProxyEvent("terminal.failure", "failure", correlation, {
2055
+ error: err instanceof Error ? err.message : String(err)
2056
+ });
1949
2057
  logProxyEvent("error", "proxy_upload_forward_failed", {
1950
2058
  error: err instanceof Error ? err.message : String(err)
1951
2059
  });
@@ -2040,7 +2148,12 @@ async function startProxy(options) {
2040
2148
  return;
2041
2149
  }
2042
2150
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_LS_PROXY_PATH)) {
2151
+ const correlation = {
2152
+ trace_id: randomUUID(),
2153
+ operation_id: randomUUID()
2154
+ };
2043
2155
  logProxyEvent("info", "proxy_ls_received");
2156
+ emitProxyEvent("request.received", "start", correlation, { path: STORAGE_LS_PROXY_PATH });
2044
2157
  try {
2045
2158
  let payload;
2046
2159
  try {
@@ -2062,6 +2175,8 @@ async function startProxy(options) {
2062
2175
  });
2063
2176
  return;
2064
2177
  }
2178
+ correlation.wallet_address = requestPayload.wallet_address;
2179
+ correlation.object_key = requestPayload.object_key;
2065
2180
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2066
2181
  logProxyEvent("warn", "proxy_ls_wallet_mismatch");
2067
2182
  sendJson(res, 403, {
@@ -2081,11 +2196,13 @@ async function startProxy(options) {
2081
2196
  res.end(createWalletRequiredBody());
2082
2197
  return;
2083
2198
  }
2199
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/ls" });
2084
2200
  const backendResponse = await forwardStorageLsToBackend(requestPayload, {
2085
2201
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
2086
2202
  walletSignature
2087
2203
  });
2088
2204
  logProxyEvent("info", "proxy_ls_backend_response", { status: backendResponse.status });
2205
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
2089
2206
  const authFailure = normalizeBackendAuthFailure(
2090
2207
  backendResponse.status,
2091
2208
  backendResponse.bodyText
@@ -2104,7 +2221,13 @@ async function startProxy(options) {
2104
2221
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2105
2222
  res.writeHead(backendResponse.status, responseHeaders);
2106
2223
  res.end(backendResponse.bodyText);
2224
+ emitProxyEvent("terminal.success", "success", correlation, {
2225
+ status: backendResponse.status
2226
+ });
2107
2227
  } catch (err) {
2228
+ emitProxyEvent("terminal.failure", "failure", correlation, {
2229
+ error: err instanceof Error ? err.message : String(err)
2230
+ });
2108
2231
  logProxyEvent("error", "proxy_ls_forward_failed", {
2109
2232
  error: err instanceof Error ? err.message : String(err)
2110
2233
  });
@@ -2116,7 +2239,14 @@ async function startProxy(options) {
2116
2239
  return;
2117
2240
  }
2118
2241
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_DOWNLOAD_PROXY_PATH)) {
2242
+ const correlation = {
2243
+ trace_id: randomUUID(),
2244
+ operation_id: randomUUID()
2245
+ };
2119
2246
  logProxyEvent("info", "proxy_download_received");
2247
+ emitProxyEvent("request.received", "start", correlation, {
2248
+ path: STORAGE_DOWNLOAD_PROXY_PATH
2249
+ });
2120
2250
  try {
2121
2251
  let payload;
2122
2252
  try {
@@ -2138,6 +2268,8 @@ async function startProxy(options) {
2138
2268
  });
2139
2269
  return;
2140
2270
  }
2271
+ correlation.wallet_address = requestPayload.wallet_address;
2272
+ correlation.object_key = requestPayload.object_key;
2141
2273
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2142
2274
  logProxyEvent("warn", "proxy_download_wallet_mismatch");
2143
2275
  sendJson(res, 403, {
@@ -2157,6 +2289,7 @@ async function startProxy(options) {
2157
2289
  res.end(createWalletRequiredBody());
2158
2290
  return;
2159
2291
  }
2292
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/download" });
2160
2293
  const backendResponse = await forwardStorageDownloadToBackend(requestPayload, {
2161
2294
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
2162
2295
  walletSignature
@@ -2164,6 +2297,7 @@ async function startProxy(options) {
2164
2297
  logProxyEvent("info", "proxy_download_backend_response", {
2165
2298
  status: backendResponse.status
2166
2299
  });
2300
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
2167
2301
  const authFailure = normalizeBackendAuthFailure(
2168
2302
  backendResponse.status,
2169
2303
  backendResponse.bodyText
@@ -2204,7 +2338,13 @@ async function startProxy(options) {
2204
2338
  file_path: downloadResult.filePath,
2205
2339
  bytes_written: downloadResult.bytesWritten
2206
2340
  });
2341
+ emitProxyEvent("terminal.success", "success", correlation, {
2342
+ status: 200
2343
+ });
2207
2344
  } catch (err) {
2345
+ emitProxyEvent("terminal.failure", "failure", correlation, {
2346
+ error: err instanceof Error ? err.message : String(err)
2347
+ });
2208
2348
  logProxyEvent("error", "proxy_download_forward_failed", {
2209
2349
  error: err instanceof Error ? err.message : String(err)
2210
2350
  });
@@ -2216,7 +2356,12 @@ async function startProxy(options) {
2216
2356
  return;
2217
2357
  }
2218
2358
  if (req.method === "POST" && matchesProxyPath(req.url, STORAGE_DELETE_PROXY_PATH)) {
2359
+ const correlation = {
2360
+ trace_id: randomUUID(),
2361
+ operation_id: randomUUID()
2362
+ };
2219
2363
  logProxyEvent("info", "proxy_delete_received");
2364
+ emitProxyEvent("request.received", "start", correlation, { path: STORAGE_DELETE_PROXY_PATH });
2220
2365
  try {
2221
2366
  let payload;
2222
2367
  try {
@@ -2238,6 +2383,8 @@ async function startProxy(options) {
2238
2383
  });
2239
2384
  return;
2240
2385
  }
2386
+ correlation.wallet_address = requestPayload.wallet_address;
2387
+ correlation.object_key = requestPayload.object_key;
2241
2388
  if (requestPayload.wallet_address.toLowerCase() !== proxyWalletAddressLower) {
2242
2389
  logProxyEvent("warn", "proxy_delete_wallet_mismatch");
2243
2390
  sendJson(res, 403, {
@@ -2257,6 +2404,7 @@ async function startProxy(options) {
2257
2404
  res.end(createWalletRequiredBody());
2258
2405
  return;
2259
2406
  }
2407
+ emitProxyEvent("storage.call", "start", correlation, { target: "storage/delete" });
2260
2408
  const backendResponse = await forwardStorageDeleteToBackend(requestPayload, {
2261
2409
  backendBaseUrl: MNEMOSPARK_BACKEND_API_BASE_URL,
2262
2410
  walletSignature
@@ -2264,6 +2412,7 @@ async function startProxy(options) {
2264
2412
  logProxyEvent("info", "proxy_delete_backend_response", {
2265
2413
  status: backendResponse.status
2266
2414
  });
2415
+ emitProxyEvent("storage.call", "result", correlation, { status: backendResponse.status });
2267
2416
  const authFailure = normalizeBackendAuthFailure(
2268
2417
  backendResponse.status,
2269
2418
  backendResponse.bodyText
@@ -2284,7 +2433,13 @@ async function startProxy(options) {
2284
2433
  const responseHeaders = createBackendForwardHeaders(backendResponse);
2285
2434
  res.writeHead(backendResponse.status, responseHeaders);
2286
2435
  res.end(backendResponse.bodyText);
2436
+ emitProxyEvent("terminal.success", "success", correlation, {
2437
+ status: backendResponse.status
2438
+ });
2287
2439
  } catch (err) {
2440
+ emitProxyEvent("terminal.failure", "failure", correlation, {
2441
+ error: err instanceof Error ? err.message : String(err)
2442
+ });
2288
2443
  logProxyEvent("error", "proxy_delete_forward_failed", {
2289
2444
  error: err instanceof Error ? err.message : String(err)
2290
2445
  });
@@ -2438,9 +2593,9 @@ async function startProxy(options) {
2438
2593
  }
2439
2594
 
2440
2595
  // src/auth.ts
2441
- import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir2 } from "fs/promises";
2442
- import { join as join4 } from "path";
2443
- import { homedir as homedir3 } from "os";
2596
+ import { writeFile as writeFile2, readFile as readFile2, mkdir as mkdir3 } from "fs/promises";
2597
+ import { join as join5 } from "path";
2598
+ import { homedir as homedir4 } from "os";
2444
2599
  import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
2445
2600
 
2446
2601
  // src/wallet-key.ts
@@ -2449,10 +2604,10 @@ function isValidWalletPrivateKey(value) {
2449
2604
  }
2450
2605
 
2451
2606
  // src/auth.ts
2452
- var LEGACY_WALLET_DIR = join4(homedir3(), ".openclaw", "blockrun");
2453
- var LEGACY_WALLET_FILE = join4(LEGACY_WALLET_DIR, "wallet.key");
2454
- var WALLET_DIR = join4(homedir3(), ".openclaw", "mnemospark", "wallet");
2455
- var WALLET_FILE = join4(WALLET_DIR, "wallet.key");
2607
+ var LEGACY_WALLET_DIR = join5(homedir4(), ".openclaw", "blockrun");
2608
+ var LEGACY_WALLET_FILE = join5(LEGACY_WALLET_DIR, "wallet.key");
2609
+ var WALLET_DIR = join5(homedir4(), ".openclaw", "mnemospark", "wallet");
2610
+ var WALLET_FILE = join5(WALLET_DIR, "wallet.key");
2456
2611
  async function loadSavedWallet() {
2457
2612
  for (const path of [WALLET_FILE, LEGACY_WALLET_FILE]) {
2458
2613
  try {
@@ -2475,7 +2630,7 @@ async function loadSavedWallet() {
2475
2630
  async function generateAndSaveWallet() {
2476
2631
  const key = generatePrivateKey();
2477
2632
  const account = privateKeyToAccount4(key);
2478
- await mkdir2(WALLET_DIR, { recursive: true });
2633
+ await mkdir3(WALLET_DIR, { recursive: true });
2479
2634
  await writeFile2(WALLET_FILE, key + "\n", { mode: 384 });
2480
2635
  try {
2481
2636
  const verification = (await readFile2(WALLET_FILE, "utf-8")).trim();
@@ -2508,11 +2663,11 @@ async function resolveOrGenerateWalletKey() {
2508
2663
  // src/version.ts
2509
2664
  import { createRequire } from "module";
2510
2665
  import { fileURLToPath } from "url";
2511
- import { dirname as dirname2, join as join5 } from "path";
2666
+ import { dirname as dirname3, join as join6 } from "path";
2512
2667
  var __filename = fileURLToPath(import.meta.url);
2513
- var __dirname = dirname2(__filename);
2668
+ var __dirname = dirname3(__filename);
2514
2669
  var require2 = createRequire(import.meta.url);
2515
- var pkg = require2(join5(__dirname, "..", "package.json"));
2670
+ var pkg = require2(join6(__dirname, "..", "package.json"));
2516
2671
  var VERSION = pkg.version;
2517
2672
  var USER_AGENT = `mnemospark/${VERSION}`;
2518
2673
 
@@ -2522,20 +2677,383 @@ import {
2522
2677
  createCipheriv,
2523
2678
  createHash as createHash2,
2524
2679
  randomBytes as randomBytesNode,
2525
- randomUUID
2680
+ randomUUID as randomUUID3
2526
2681
  } from "crypto";
2527
- import { createReadStream, statfsSync } from "fs";
2528
- import { appendFile, lstat, mkdir as mkdir3, readFile as readFile3, readdir, rm, stat, writeFile as writeFile3 } from "fs/promises";
2529
- import { homedir as homedir4 } from "os";
2530
- import { basename, dirname as dirname3, join as join6, resolve as resolve2 } from "path";
2682
+ import { createReadStream as createReadStream2, statfsSync } from "fs";
2683
+ import { appendFile as appendFile2, lstat, mkdir as mkdir5, readFile as readFile3, readdir as readdir2, rm, stat as stat2, writeFile as writeFile3 } from "fs/promises";
2684
+ import { homedir as homedir6 } from "os";
2685
+ import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
2531
2686
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
2687
+
2688
+ // src/cloud-datastore.ts
2689
+ import { randomUUID as randomUUID2 } from "crypto";
2690
+ import { mkdir as mkdir4 } from "fs/promises";
2691
+ import { homedir as homedir5 } from "os";
2692
+ import { dirname as dirname4, join as join7 } from "path";
2693
+ var DB_SUBPATH = join7(".openclaw", "mnemospark", "state.db");
2694
+ var SCHEMA_VERSION = 2;
2695
+ function resolveDbPath(homeDir) {
2696
+ return join7(homeDir ?? homedir5(), DB_SUBPATH);
2697
+ }
2698
+ function nowIso() {
2699
+ return (/* @__PURE__ */ new Date()).toISOString();
2700
+ }
2701
+ async function createCloudDatastore(homeDir) {
2702
+ const dbPath = resolveDbPath(homeDir);
2703
+ let db = null;
2704
+ const ensureReady = async () => {
2705
+ if (db) return;
2706
+ if (process.env.MNEMOSPARK_DISABLE_SQLITE === "1") {
2707
+ throw new Error("SQLite disabled by MNEMOSPARK_DISABLE_SQLITE=1");
2708
+ }
2709
+ await mkdir4(dirname4(dbPath), { recursive: true });
2710
+ const sqliteMod = await import("sqlite");
2711
+ const DatabaseSync = sqliteMod.DatabaseSync;
2712
+ if (!DatabaseSync) {
2713
+ throw new Error("node:sqlite DatabaseSync is unavailable");
2714
+ }
2715
+ const nextDb = new DatabaseSync(dbPath);
2716
+ nextDb.exec("PRAGMA journal_mode=WAL;");
2717
+ nextDb.exec("PRAGMA foreign_keys=ON;");
2718
+ nextDb.exec(`
2719
+ CREATE TABLE IF NOT EXISTS schema_migrations (
2720
+ version INTEGER PRIMARY KEY,
2721
+ applied_at TEXT NOT NULL
2722
+ );
2723
+
2724
+ CREATE TABLE IF NOT EXISTS objects (
2725
+ object_id TEXT PRIMARY KEY,
2726
+ object_key TEXT,
2727
+ wallet_address TEXT NOT NULL,
2728
+ quote_id TEXT,
2729
+ provider TEXT,
2730
+ bucket_name TEXT,
2731
+ region TEXT,
2732
+ sha256 TEXT,
2733
+ status TEXT NOT NULL,
2734
+ created_at TEXT NOT NULL,
2735
+ updated_at TEXT NOT NULL
2736
+ );
2737
+ CREATE INDEX IF NOT EXISTS idx_objects_wallet ON objects(wallet_address);
2738
+ CREATE INDEX IF NOT EXISTS idx_objects_quote ON objects(quote_id);
2739
+ CREATE INDEX IF NOT EXISTS idx_objects_object_key ON objects(object_key);
2740
+
2741
+ CREATE TABLE IF NOT EXISTS payments (
2742
+ quote_id TEXT PRIMARY KEY,
2743
+ wallet_address TEXT NOT NULL,
2744
+ trans_id TEXT,
2745
+ amount REAL NOT NULL,
2746
+ network TEXT,
2747
+ status TEXT NOT NULL,
2748
+ settled_at TEXT,
2749
+ created_at TEXT NOT NULL,
2750
+ updated_at TEXT NOT NULL
2751
+ );
2752
+ CREATE INDEX IF NOT EXISTS idx_payments_wallet ON payments(wallet_address);
2753
+
2754
+ CREATE TABLE IF NOT EXISTS cron_jobs (
2755
+ cron_id TEXT PRIMARY KEY,
2756
+ object_id TEXT NOT NULL,
2757
+ object_key TEXT NOT NULL,
2758
+ quote_id TEXT NOT NULL,
2759
+ schedule TEXT NOT NULL,
2760
+ command TEXT NOT NULL,
2761
+ status TEXT NOT NULL,
2762
+ created_at TEXT NOT NULL,
2763
+ updated_at TEXT NOT NULL
2764
+ );
2765
+ CREATE INDEX IF NOT EXISTS idx_cron_jobs_object_key ON cron_jobs(object_key);
2766
+
2767
+ CREATE TABLE IF NOT EXISTS operations (
2768
+ operation_id TEXT PRIMARY KEY,
2769
+ type TEXT NOT NULL,
2770
+ object_id TEXT,
2771
+ quote_id TEXT,
2772
+ status TEXT NOT NULL,
2773
+ error_code TEXT,
2774
+ error_message TEXT,
2775
+ started_at TEXT,
2776
+ finished_at TEXT,
2777
+ created_at TEXT NOT NULL,
2778
+ updated_at TEXT NOT NULL
2779
+ );
2780
+ CREATE INDEX IF NOT EXISTS idx_operations_type ON operations(type);
2781
+ CREATE INDEX IF NOT EXISTS idx_operations_object_id ON operations(object_id);
2782
+ CREATE INDEX IF NOT EXISTS idx_operations_quote_id ON operations(quote_id);
2783
+
2784
+ CREATE TABLE IF NOT EXISTS friendly_names (
2785
+ friendly_name_id TEXT PRIMARY KEY,
2786
+ friendly_name TEXT NOT NULL,
2787
+ object_id TEXT NOT NULL,
2788
+ object_key TEXT,
2789
+ quote_id TEXT,
2790
+ wallet_address TEXT NOT NULL,
2791
+ created_at TEXT NOT NULL,
2792
+ updated_at TEXT NOT NULL,
2793
+ is_active INTEGER NOT NULL DEFAULT 1
2794
+ );
2795
+ CREATE INDEX IF NOT EXISTS idx_friendly_names_name ON friendly_names(friendly_name);
2796
+ CREATE INDEX IF NOT EXISTS idx_friendly_names_object_id ON friendly_names(object_id);
2797
+ CREATE INDEX IF NOT EXISTS idx_friendly_names_wallet ON friendly_names(wallet_address);
2798
+ CREATE INDEX IF NOT EXISTS idx_friendly_names_created_at ON friendly_names(created_at);
2799
+ `);
2800
+ nextDb.prepare(
2801
+ `INSERT INTO schema_migrations(version, applied_at)
2802
+ VALUES(?, ?)
2803
+ ON CONFLICT(version) DO NOTHING`
2804
+ ).run(SCHEMA_VERSION, nowIso());
2805
+ db = nextDb;
2806
+ };
2807
+ const safe = async (fn, fallback) => {
2808
+ try {
2809
+ await ensureReady();
2810
+ return fn();
2811
+ } catch {
2812
+ return fallback;
2813
+ }
2814
+ };
2815
+ return {
2816
+ dbPath,
2817
+ ensureReady,
2818
+ upsertObject: async (row) => {
2819
+ await safe(() => {
2820
+ const ts = nowIso();
2821
+ db.prepare(
2822
+ `INSERT INTO objects(object_id, object_key, wallet_address, quote_id, provider, bucket_name, region, sha256, status, created_at, updated_at)
2823
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2824
+ ON CONFLICT(object_id) DO UPDATE SET
2825
+ object_key=excluded.object_key,
2826
+ wallet_address=excluded.wallet_address,
2827
+ quote_id=excluded.quote_id,
2828
+ provider=excluded.provider,
2829
+ bucket_name=excluded.bucket_name,
2830
+ region=excluded.region,
2831
+ sha256=excluded.sha256,
2832
+ status=excluded.status,
2833
+ updated_at=excluded.updated_at`
2834
+ ).run(
2835
+ row.object_id,
2836
+ row.object_key,
2837
+ row.wallet_address,
2838
+ row.quote_id,
2839
+ row.provider,
2840
+ row.bucket_name,
2841
+ row.region,
2842
+ row.sha256,
2843
+ row.status,
2844
+ ts,
2845
+ ts
2846
+ );
2847
+ }, void 0);
2848
+ },
2849
+ findObjectByObjectKey: async (objectKey) => safe(() => {
2850
+ const row = db.prepare(
2851
+ `SELECT object_id, object_key, wallet_address, quote_id, provider, bucket_name, region, sha256, status
2852
+ FROM objects
2853
+ WHERE object_key = ?
2854
+ ORDER BY updated_at DESC
2855
+ LIMIT 1`
2856
+ ).get(objectKey);
2857
+ return row ?? null;
2858
+ }, null),
2859
+ findObjectById: async (objectId) => safe(() => {
2860
+ const row = db.prepare(
2861
+ `SELECT object_id, object_key, wallet_address, quote_id, provider, bucket_name, region, sha256, status
2862
+ FROM objects
2863
+ WHERE object_id = ?
2864
+ LIMIT 1`
2865
+ ).get(objectId);
2866
+ return row ?? null;
2867
+ }, null),
2868
+ upsertPayment: async (row) => {
2869
+ await safe(() => {
2870
+ const ts = nowIso();
2871
+ db.prepare(
2872
+ `INSERT INTO payments(quote_id, wallet_address, trans_id, amount, network, status, settled_at, created_at, updated_at)
2873
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
2874
+ ON CONFLICT(quote_id) DO UPDATE SET
2875
+ wallet_address=excluded.wallet_address,
2876
+ trans_id=excluded.trans_id,
2877
+ amount=excluded.amount,
2878
+ network=excluded.network,
2879
+ status=excluded.status,
2880
+ settled_at=excluded.settled_at,
2881
+ updated_at=excluded.updated_at`
2882
+ ).run(
2883
+ row.quote_id,
2884
+ row.wallet_address,
2885
+ row.trans_id,
2886
+ row.amount,
2887
+ row.network,
2888
+ row.status,
2889
+ row.settled_at ?? null,
2890
+ ts,
2891
+ ts
2892
+ );
2893
+ }, void 0);
2894
+ },
2895
+ upsertCronJob: async (row) => {
2896
+ await safe(() => {
2897
+ const ts = nowIso();
2898
+ db.prepare(
2899
+ `INSERT INTO cron_jobs(cron_id, object_id, object_key, quote_id, schedule, command, status, created_at, updated_at)
2900
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
2901
+ ON CONFLICT(cron_id) DO UPDATE SET
2902
+ object_id=excluded.object_id,
2903
+ object_key=excluded.object_key,
2904
+ quote_id=excluded.quote_id,
2905
+ schedule=excluded.schedule,
2906
+ command=excluded.command,
2907
+ status=excluded.status,
2908
+ updated_at=excluded.updated_at`
2909
+ ).run(
2910
+ row.cron_id,
2911
+ row.object_id,
2912
+ row.object_key,
2913
+ row.quote_id,
2914
+ row.schedule,
2915
+ row.command,
2916
+ row.status,
2917
+ ts,
2918
+ ts
2919
+ );
2920
+ }, void 0);
2921
+ },
2922
+ removeCronJob: async (cronId) => safe(() => {
2923
+ const res = db.prepare(`DELETE FROM cron_jobs WHERE cron_id = ?`).run(cronId);
2924
+ return Number(res.changes ?? 0) > 0;
2925
+ }, false),
2926
+ findCronByObjectKey: async (objectKey) => safe(() => {
2927
+ const row = db.prepare(
2928
+ `SELECT cron_id, object_id FROM cron_jobs WHERE object_key = ? ORDER BY updated_at DESC LIMIT 1`
2929
+ ).get(objectKey);
2930
+ if (!row) return null;
2931
+ return { cronId: row.cron_id, objectId: row.object_id };
2932
+ }, null),
2933
+ upsertOperation: async (row) => {
2934
+ await safe(() => {
2935
+ const ts = nowIso();
2936
+ db.prepare(
2937
+ `INSERT INTO operations(operation_id, type, object_id, quote_id, status, error_code, error_message, started_at, finished_at, created_at, updated_at)
2938
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2939
+ ON CONFLICT(operation_id) DO UPDATE SET
2940
+ type=excluded.type,
2941
+ object_id=excluded.object_id,
2942
+ quote_id=excluded.quote_id,
2943
+ status=excluded.status,
2944
+ error_code=excluded.error_code,
2945
+ error_message=excluded.error_message,
2946
+ started_at=COALESCE(excluded.started_at, operations.started_at),
2947
+ finished_at=COALESCE(excluded.finished_at, operations.finished_at),
2948
+ updated_at=excluded.updated_at`
2949
+ ).run(
2950
+ row.operation_id,
2951
+ row.type,
2952
+ row.object_id,
2953
+ row.quote_id,
2954
+ row.status,
2955
+ row.error_code,
2956
+ row.error_message,
2957
+ row.status === "started" ? ts : null,
2958
+ row.status === "succeeded" || row.status === "failed" ? ts : null,
2959
+ ts,
2960
+ ts
2961
+ );
2962
+ }, void 0);
2963
+ },
2964
+ findOperationById: async (operationId) => safe(() => {
2965
+ const row = db.prepare(
2966
+ `SELECT operation_id, type, status, error_code, error_message, started_at, finished_at, updated_at
2967
+ FROM operations
2968
+ WHERE operation_id = ?
2969
+ LIMIT 1`
2970
+ ).get(operationId);
2971
+ return row ?? null;
2972
+ }, null),
2973
+ findQuoteById: async (quoteId) => safe(() => {
2974
+ const row = db.prepare(
2975
+ `SELECT quote_id, amount, wallet_address FROM payments WHERE quote_id = ? ORDER BY updated_at DESC LIMIT 1`
2976
+ ).get(quoteId);
2977
+ const object = db.prepare(
2978
+ `SELECT object_id, sha256, provider, region FROM objects WHERE quote_id = ? ORDER BY updated_at DESC LIMIT 1`
2979
+ ).get(quoteId);
2980
+ if (!row || !object) return null;
2981
+ if (object.sha256 === null || object.provider === null || object.region === null)
2982
+ return null;
2983
+ return {
2984
+ quoteId,
2985
+ storagePrice: Number(row.amount),
2986
+ walletAddress: row.wallet_address,
2987
+ objectId: object.object_id,
2988
+ objectIdHash: object.sha256,
2989
+ provider: object.provider,
2990
+ location: object.region
2991
+ };
2992
+ }, null),
2993
+ upsertFriendlyName: async (row) => {
2994
+ await safe(() => {
2995
+ const ts = nowIso();
2996
+ db.prepare(
2997
+ `INSERT INTO friendly_names(friendly_name_id, friendly_name, object_id, object_key, quote_id, wallet_address, created_at, updated_at, is_active)
2998
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`
2999
+ ).run(
3000
+ randomUUID2(),
3001
+ row.friendly_name,
3002
+ row.object_id,
3003
+ row.object_key,
3004
+ row.quote_id,
3005
+ row.wallet_address,
3006
+ ts,
3007
+ ts,
3008
+ row.is_active ?? 1
3009
+ );
3010
+ }, void 0);
3011
+ },
3012
+ resolveFriendlyName: async (params) => safe(() => {
3013
+ const atIso = params.at ? new Date(params.at).toISOString() : null;
3014
+ const row = params.latest || !atIso ? db.prepare(
3015
+ `SELECT friendly_name_id, friendly_name, object_id, object_key, quote_id, wallet_address, created_at
3016
+ FROM friendly_names
3017
+ WHERE wallet_address = ? AND friendly_name = ? AND is_active = 1
3018
+ ORDER BY created_at DESC
3019
+ LIMIT 1`
3020
+ ).get(params.walletAddress, params.friendlyName) : db.prepare(
3021
+ `SELECT friendly_name_id, friendly_name, object_id, object_key, quote_id, wallet_address, created_at
3022
+ FROM friendly_names
3023
+ WHERE wallet_address = ? AND friendly_name = ? AND is_active = 1 AND created_at <= ?
3024
+ ORDER BY created_at DESC
3025
+ LIMIT 1`
3026
+ ).get(params.walletAddress, params.friendlyName, atIso);
3027
+ if (!row) return null;
3028
+ return {
3029
+ friendlyNameId: row.friendly_name_id,
3030
+ friendlyName: row.friendly_name,
3031
+ objectId: row.object_id,
3032
+ objectKey: row.object_key,
3033
+ quoteId: row.quote_id,
3034
+ walletAddress: row.wallet_address,
3035
+ createdAt: row.created_at
3036
+ };
3037
+ }, null),
3038
+ countFriendlyNameMatches: async (walletAddress, friendlyName) => safe(() => {
3039
+ const row = db.prepare(
3040
+ `SELECT COUNT(1) AS cnt
3041
+ FROM friendly_names
3042
+ WHERE wallet_address = ? AND friendly_name = ? AND is_active = 1`
3043
+ ).get(walletAddress, friendlyName);
3044
+ return Number(row?.cnt ?? 0);
3045
+ }, 0)
3046
+ };
3047
+ }
3048
+
3049
+ // src/cloud-command.ts
2532
3050
  var SUPPORTED_BACKUP_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "linux"]);
2533
- var BACKUP_DIR_SUBPATH = join6(".openclaw", "mnemospark", "backup");
2534
- var DEFAULT_BACKUP_DIR = join6(homedir4(), BACKUP_DIR_SUBPATH);
2535
- var OBJECT_LOG_SUBPATH = join6(".openclaw", "mnemospark", "object.log");
2536
- var CRON_TABLE_SUBPATH = join6(".openclaw", "mnemospark", "crontab.txt");
2537
- var BLOCKRUN_WALLET_KEY_SUBPATH = join6(".openclaw", "blockrun", "wallet.key");
2538
- var MNEMOSPARK_WALLET_KEY_SUBPATH = join6(".openclaw", "mnemospark", "wallet", "wallet.key");
3051
+ var BACKUP_DIR_SUBPATH = join8(".openclaw", "mnemospark", "backup");
3052
+ var DEFAULT_BACKUP_DIR = join8(homedir6(), BACKUP_DIR_SUBPATH);
3053
+ var OBJECT_LOG_SUBPATH = join8(".openclaw", "mnemospark", "object.log");
3054
+ var CRON_TABLE_SUBPATH = join8(".openclaw", "mnemospark", "crontab.txt");
3055
+ var BLOCKRUN_WALLET_KEY_SUBPATH = join8(".openclaw", "blockrun", "wallet.key");
3056
+ var MNEMOSPARK_WALLET_KEY_SUBPATH = join8(".openclaw", "mnemospark", "wallet", "wallet.key");
2539
3057
  var INLINE_UPLOAD_MAX_BYTES = 45e5;
2540
3058
  var PAYMENT_REMINDER_INTERVAL_DAYS = 30;
2541
3059
  var PAYMENT_DELETE_DEADLINE_DAYS = 32;
@@ -2544,14 +3062,17 @@ var CRON_LOG_ROW_PREFIX = "cron";
2544
3062
  var TAR_OVERHEAD_BYTES = 10 * 1024 * 1024;
2545
3063
  var REQUIRED_PRICE_STORAGE = "--wallet-address, --object-id, --object-id-hash, --gb, --provider, --region";
2546
3064
  var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-hash";
2547
- var REQUIRED_STORAGE_OBJECT = "--wallet-address, --object-key";
3065
+ var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
3066
+ var BOOLEAN_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["latest"]);
3067
+ var BOOLEAN_ASYNC_FLAGS = /* @__PURE__ */ new Set(["async"]);
3068
+ var BOOLEAN_SELECTOR_AND_ASYNC_FLAGS = /* @__PURE__ */ new Set(["latest", "async"]);
2548
3069
  function expandTilde(path) {
2549
3070
  const trimmed = path.trim();
2550
3071
  if (trimmed === "~") {
2551
- return homedir4();
3072
+ return homedir6();
2552
3073
  }
2553
3074
  if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
2554
- return join6(homedir4(), trimmed.slice(2));
3075
+ return join8(homedir6(), trimmed.slice(2));
2555
3076
  }
2556
3077
  return path;
2557
3078
  }
@@ -2560,24 +3081,27 @@ var CLOUD_HELP_TEXT = [
2560
3081
  "",
2561
3082
  "\u2022 `/mnemospark-cloud` or `/mnemospark-cloud help` \u2014 show this message",
2562
3083
  "",
2563
- "\u2022 `/mnemospark-cloud backup <file>` or `/mnemospark-cloud backup <directory>`",
3084
+ "\u2022 `/mnemospark-cloud backup <file>` or `/mnemospark-cloud backup <directory> [--name <friendly-name>]`",
2564
3085
  " Required: <file> or <directory> (path to back up)",
2565
3086
  "",
2566
3087
  "\u2022 `/mnemospark-cloud price-storage --wallet-address <addr> --object-id <id> --object-id-hash <hash> --gb <gb> --provider <provider> --region <region>`",
2567
3088
  " Required: " + REQUIRED_PRICE_STORAGE,
2568
3089
  "",
2569
- "\u2022 `/mnemospark-cloud upload --quote-id <quote-id> --wallet-address <addr> --object-id <id> --object-id-hash <hash>`",
3090
+ "\u2022 `/mnemospark-cloud upload --quote-id <quote-id> --wallet-address <addr> --object-id <id> --object-id-hash <hash> [--name <friendly-name>] [--async]`",
2570
3091
  " Required: " + REQUIRED_UPLOAD,
2571
3092
  "",
2572
- "\u2022 `/mnemospark-cloud ls --wallet-address <addr> --object-key <object-key>`",
3093
+ "\u2022 `/mnemospark-cloud ls --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
2573
3094
  " Required: " + REQUIRED_STORAGE_OBJECT,
2574
3095
  "",
2575
- "\u2022 `/mnemospark-cloud download --wallet-address <addr> --object-key <object-key>`",
3096
+ "\u2022 `/mnemospark-cloud download --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>] [--async]`",
2576
3097
  " Required: " + REQUIRED_STORAGE_OBJECT,
2577
3098
  "",
2578
- "\u2022 `/mnemospark-cloud delete --wallet-address <addr> --object-key <object-key>`",
3099
+ "\u2022 `/mnemospark-cloud delete --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
2579
3100
  " Required: " + REQUIRED_STORAGE_OBJECT,
2580
3101
  "",
3102
+ "\u2022 `/mnemospark-cloud op-status --operation-id <id>`",
3103
+ " Required: --operation-id",
3104
+ "",
2581
3105
  "Backup creates a tar+gzip object in ~/.openclaw/mnemospark/backup and appends object metadata to ~/.openclaw/mnemospark/object.log. Upload appends storage rows and cron-tracking rows to object.log, and keeps job entries in ~/.openclaw/mnemospark/crontab.txt. All storage commands (price-storage, upload, ls, download, delete) require --wallet-address."
2582
3106
  ].join("\n");
2583
3107
  var UnsupportedBackupPlatformError = class extends Error {
@@ -2600,15 +3124,17 @@ function stripWrappingQuotes(input) {
2600
3124
  }
2601
3125
  return trimmed;
2602
3126
  }
2603
- function tokenizeArgs(input) {
3127
+ function tokenizeArgsRaw(input) {
2604
3128
  const tokens = input.match(/"[^"]*"|'[^']*'|\S+/g);
2605
3129
  if (!tokens) {
2606
3130
  return [];
2607
3131
  }
2608
- return tokens.map((token) => stripWrappingQuotes(token));
3132
+ return tokens;
2609
3133
  }
2610
- function parseNamedFlags(input) {
2611
- const tokens = tokenizeArgs(input);
3134
+ function tokenizeArgs(input) {
3135
+ return tokenizeArgsRaw(input).map((token) => stripWrappingQuotes(token));
3136
+ }
3137
+ function parseNamedFlagsTokens(tokens, booleanFlags = /* @__PURE__ */ new Set()) {
2612
3138
  if (tokens.length === 0) {
2613
3139
  return null;
2614
3140
  }
@@ -2621,6 +3147,10 @@ function parseNamedFlags(input) {
2621
3147
  const key = keyToken.slice(2).toLowerCase();
2622
3148
  const value = tokens[i + 1];
2623
3149
  if (!value || value.startsWith("--")) {
3150
+ if (booleanFlags.has(key)) {
3151
+ parsed[key] = "true";
3152
+ continue;
3153
+ }
2624
3154
  return null;
2625
3155
  }
2626
3156
  parsed[key] = value;
@@ -2628,6 +3158,44 @@ function parseNamedFlags(input) {
2628
3158
  }
2629
3159
  return parsed;
2630
3160
  }
3161
+ function parseNamedFlags(input, booleanFlags = /* @__PURE__ */ new Set()) {
3162
+ const tokens = tokenizeArgs(input);
3163
+ return parseNamedFlagsTokens(tokens, booleanFlags);
3164
+ }
3165
+ function parseObjectSelector(flags) {
3166
+ const objectKey = flags["object-key"]?.trim();
3167
+ const name = flags.name?.trim();
3168
+ const latest = flags.latest === "true";
3169
+ const at = flags.at?.trim();
3170
+ if (objectKey && name) return null;
3171
+ if (!objectKey && !name) return null;
3172
+ if (latest && at) return null;
3173
+ if (objectKey) return { objectKey };
3174
+ return { nameSelector: { name, latest, at } };
3175
+ }
3176
+ function parseStorageObjectRequestInput(flags, selector) {
3177
+ const walletAddress = flags["wallet-address"]?.trim();
3178
+ if (!walletAddress) {
3179
+ return null;
3180
+ }
3181
+ const location = flags.location?.trim() || flags.region?.trim() || void 0;
3182
+ if (!selector.objectKey) {
3183
+ return {
3184
+ wallet_address: walletAddress,
3185
+ location
3186
+ };
3187
+ }
3188
+ return parseStorageObjectRequest({
3189
+ wallet_address: walletAddress,
3190
+ object_key: selector.objectKey,
3191
+ location
3192
+ });
3193
+ }
3194
+ function stripAsyncFlag(args) {
3195
+ const tokens = tokenizeArgsRaw(args ?? "");
3196
+ const filtered = tokens.filter((token) => token.toLowerCase() !== "--async");
3197
+ return filtered.join(" ");
3198
+ }
2631
3199
  function parseCloudArgs(args) {
2632
3200
  const trimmed = args?.trim() ?? "";
2633
3201
  if (!trimmed) {
@@ -2640,11 +3208,16 @@ function parseCloudArgs(args) {
2640
3208
  return { mode: "help" };
2641
3209
  }
2642
3210
  if (subcommand === "backup") {
2643
- const backupTarget = stripWrappingQuotes(rest);
3211
+ const tokens = tokenizeArgs(rest);
3212
+ if (tokens.length === 0) {
3213
+ return { mode: "unknown" };
3214
+ }
3215
+ const backupTarget = tokens[0] ?? "";
2644
3216
  if (!backupTarget) {
2645
3217
  return { mode: "unknown" };
2646
3218
  }
2647
- return { mode: "backup", backupTarget };
3219
+ const flags = parseNamedFlagsTokens(tokens.slice(1));
3220
+ return { mode: "backup", backupTarget, friendlyName: flags?.name?.trim() || void 0 };
2648
3221
  }
2649
3222
  if (subcommand === "price-storage") {
2650
3223
  const flags = parseNamedFlags(rest);
@@ -2666,7 +3239,7 @@ function parseCloudArgs(args) {
2666
3239
  return { mode: "price-storage", priceStorageRequest: request };
2667
3240
  }
2668
3241
  if (subcommand === "upload") {
2669
- const flags = parseNamedFlags(rest);
3242
+ const flags = parseNamedFlags(rest, BOOLEAN_ASYNC_FLAGS);
2670
3243
  if (!flags) {
2671
3244
  return { mode: "upload-invalid" };
2672
3245
  }
@@ -2679,6 +3252,8 @@ function parseCloudArgs(args) {
2679
3252
  }
2680
3253
  return {
2681
3254
  mode: "upload",
3255
+ friendlyName: flags.name?.trim() || void 0,
3256
+ async: flags.async === "true",
2682
3257
  uploadRequest: {
2683
3258
  quote_id: quoteId,
2684
3259
  wallet_address: walletAddress,
@@ -2688,62 +3263,75 @@ function parseCloudArgs(args) {
2688
3263
  };
2689
3264
  }
2690
3265
  if (subcommand === "ls") {
2691
- const flags = parseNamedFlags(rest);
3266
+ const flags = parseNamedFlags(rest, BOOLEAN_SELECTOR_FLAGS);
2692
3267
  if (!flags) {
2693
3268
  return { mode: "ls-invalid" };
2694
3269
  }
2695
- const request = parseStorageObjectRequest({
2696
- wallet_address: flags["wallet-address"],
2697
- object_key: flags["object-key"],
2698
- location: flags.location ?? flags.region
2699
- });
3270
+ const selector = parseObjectSelector(flags);
3271
+ if (!selector) {
3272
+ return { mode: "ls-invalid" };
3273
+ }
3274
+ const request = parseStorageObjectRequestInput(flags, selector);
2700
3275
  if (!request) {
2701
3276
  return { mode: "ls-invalid" };
2702
3277
  }
2703
- return { mode: "ls", storageObjectRequest: request };
3278
+ return { mode: "ls", storageObjectRequest: request, nameSelector: selector.nameSelector };
2704
3279
  }
2705
3280
  if (subcommand === "download") {
2706
- const flags = parseNamedFlags(rest);
3281
+ const flags = parseNamedFlags(rest, BOOLEAN_SELECTOR_AND_ASYNC_FLAGS);
2707
3282
  if (!flags) {
2708
3283
  return { mode: "download-invalid" };
2709
3284
  }
2710
- const request = parseStorageObjectRequest({
2711
- wallet_address: flags["wallet-address"],
2712
- object_key: flags["object-key"],
2713
- location: flags.location ?? flags.region
2714
- });
3285
+ const selector = parseObjectSelector(flags);
3286
+ if (!selector) {
3287
+ return { mode: "download-invalid" };
3288
+ }
3289
+ const request = parseStorageObjectRequestInput(flags, selector);
2715
3290
  if (!request) {
2716
3291
  return { mode: "download-invalid" };
2717
3292
  }
2718
- return { mode: "download", storageObjectRequest: request };
3293
+ return {
3294
+ mode: "download",
3295
+ storageObjectRequest: request,
3296
+ nameSelector: selector.nameSelector,
3297
+ async: flags.async === "true"
3298
+ };
2719
3299
  }
2720
3300
  if (subcommand === "delete") {
2721
- const flags = parseNamedFlags(rest);
3301
+ const flags = parseNamedFlags(rest, BOOLEAN_SELECTOR_FLAGS);
2722
3302
  if (!flags) {
2723
3303
  return { mode: "delete-invalid" };
2724
3304
  }
2725
- const request = parseStorageObjectRequest({
2726
- wallet_address: flags["wallet-address"],
2727
- object_key: flags["object-key"],
2728
- location: flags.location ?? flags.region
2729
- });
3305
+ const selector = parseObjectSelector(flags);
3306
+ if (!selector) {
3307
+ return { mode: "delete-invalid" };
3308
+ }
3309
+ const request = parseStorageObjectRequestInput(flags, selector);
2730
3310
  if (!request) {
2731
3311
  return { mode: "delete-invalid" };
2732
3312
  }
2733
- return { mode: "delete", storageObjectRequest: request };
3313
+ return { mode: "delete", storageObjectRequest: request, nameSelector: selector.nameSelector };
3314
+ }
3315
+ if (subcommand === "op-status") {
3316
+ const flags = parseNamedFlags(rest);
3317
+ const operationId = flags?.["operation-id"]?.trim();
3318
+ if (!operationId) {
3319
+ return { mode: "op-status-invalid" };
3320
+ }
3321
+ return { mode: "op-status", operationId };
2734
3322
  }
2735
3323
  return { mode: "unknown" };
2736
3324
  }
2737
3325
  function resolveObjectLogPath(homeDir) {
2738
- return join6(homeDir ?? homedir4(), OBJECT_LOG_SUBPATH);
3326
+ return join8(homeDir ?? homedir6(), OBJECT_LOG_SUBPATH);
2739
3327
  }
2740
3328
  function resolveCronTablePath(homeDir) {
2741
- return join6(homeDir ?? homedir4(), CRON_TABLE_SUBPATH);
3329
+ return join8(homeDir ?? homedir6(), CRON_TABLE_SUBPATH);
2742
3330
  }
2743
3331
  async function appendObjectLogLine(line, homeDir) {
2744
3332
  const objectLogPath = resolveObjectLogPath(homeDir);
2745
- await mkdir3(dirname3(objectLogPath), { recursive: true });
2746
- await appendFile(objectLogPath, `${line}
3333
+ await mkdir5(dirname5(objectLogPath), { recursive: true });
3334
+ await appendFile2(objectLogPath, `${line}
2747
3335
  `, "utf-8");
2748
3336
  return objectLogPath;
2749
3337
  }
@@ -2756,9 +3344,9 @@ async function calculateInputSizeBytes(targetPath) {
2756
3344
  throw new Error("Backup target must be a file or directory");
2757
3345
  }
2758
3346
  let total = 0;
2759
- const entries = await readdir(targetPath, { withFileTypes: true });
3347
+ const entries = await readdir2(targetPath, { withFileTypes: true });
2760
3348
  for (const entry of entries) {
2761
- total += await calculateInputSizeBytes(join6(targetPath, entry.name));
3349
+ total += await calculateInputSizeBytes(join8(targetPath, entry.name));
2762
3350
  }
2763
3351
  return total;
2764
3352
  }
@@ -2770,8 +3358,8 @@ function getAvailableDiskBytes(tmpDir, options) {
2770
3358
  return stats.bavail * stats.bsize;
2771
3359
  }
2772
3360
  async function runTarGzip(archivePath, sourcePath) {
2773
- const sourceDir = dirname3(sourcePath);
2774
- const sourceName = basename(sourcePath);
3361
+ const sourceDir = dirname5(sourcePath);
3362
+ const sourceName = basename2(sourcePath);
2775
3363
  await new Promise((resolvePromise, rejectPromise) => {
2776
3364
  let stderr = "";
2777
3365
  const child = spawn("tar", ["-czf", archivePath, "-C", sourceDir, sourceName], {
@@ -2793,7 +3381,7 @@ async function runTarGzip(archivePath, sourcePath) {
2793
3381
  async function sha256File(filePath) {
2794
3382
  const hash = createHash2("sha256");
2795
3383
  await new Promise((resolvePromise, rejectPromise) => {
2796
- const stream = createReadStream(filePath);
3384
+ const stream = createReadStream2(filePath);
2797
3385
  stream.on("data", (chunk) => hash.update(chunk));
2798
3386
  stream.on("error", rejectPromise);
2799
3387
  stream.on("end", () => resolvePromise());
@@ -2818,11 +3406,11 @@ async function buildBackupObject(targetPathArg, options = {}) {
2818
3406
  const tmpDir = options.tmpDir ?? DEFAULT_BACKUP_DIR;
2819
3407
  let tmpStats;
2820
3408
  try {
2821
- tmpStats = await stat(tmpDir);
3409
+ tmpStats = await stat2(tmpDir);
2822
3410
  } catch (error) {
2823
3411
  if (error.code === "ENOENT") {
2824
- await mkdir3(tmpDir, { recursive: true });
2825
- tmpStats = await stat(tmpDir);
3412
+ await mkdir5(tmpDir, { recursive: true });
3413
+ tmpStats = await stat2(tmpDir);
2826
3414
  } else {
2827
3415
  throw error;
2828
3416
  }
@@ -2837,10 +3425,10 @@ async function buildBackupObject(targetPathArg, options = {}) {
2837
3425
  throw new Error("Insufficient disk space for backup object");
2838
3426
  }
2839
3427
  const objectId = createObjectId(options);
2840
- const archivePath = join6(tmpDir, objectId);
3428
+ const archivePath = join8(tmpDir, objectId);
2841
3429
  try {
2842
3430
  await runTarGzip(archivePath, targetPath);
2843
- const archiveStats = await stat(archivePath);
3431
+ const archiveStats = await stat2(archivePath);
2844
3432
  const objectIdHash = await sha256File(archivePath);
2845
3433
  const objectSizeGb = toGbString(archiveStats.size);
2846
3434
  const objectLogPath = await appendObjectLogLine(
@@ -3059,8 +3647,8 @@ async function appendStoragePaymentCronLog(cronJob, homeDir) {
3059
3647
  }
3060
3648
  async function appendStoragePaymentCronJob(cronJob, homeDir) {
3061
3649
  const cronTablePath = resolveCronTablePath(homeDir);
3062
- await mkdir3(dirname3(cronTablePath), { recursive: true });
3063
- await appendFile(cronTablePath, `${JSON.stringify(cronJob)}
3650
+ await mkdir5(dirname5(cronTablePath), { recursive: true });
3651
+ await appendFile2(cronTablePath, `${JSON.stringify(cronJob)}
3064
3652
  `, "utf-8");
3065
3653
  return cronTablePath;
3066
3654
  }
@@ -3093,14 +3681,14 @@ async function removeStoragePaymentCronJob(cronId, homeDir) {
3093
3681
  if (!removed) {
3094
3682
  return false;
3095
3683
  }
3096
- await mkdir3(dirname3(cronTablePath), { recursive: true });
3684
+ await mkdir5(dirname5(cronTablePath), { recursive: true });
3097
3685
  const nextContent = keptLines.length > 0 ? `${keptLines.join("\n")}
3098
3686
  ` : "";
3099
3687
  await writeFile3(cronTablePath, nextContent, "utf-8");
3100
3688
  return true;
3101
3689
  }
3102
3690
  async function createStoragePaymentCronJob(upload, storagePrice, homeDir, nowDateFn = () => /* @__PURE__ */ new Date()) {
3103
- const cronId = randomUUID();
3691
+ const cronId = randomUUID3();
3104
3692
  const createdAt = formatTimestamp(nowDateFn());
3105
3693
  const cronJob = {
3106
3694
  cronId,
@@ -3142,9 +3730,9 @@ async function resolveWalletPrivateKey(homeDir) {
3142
3730
  if (isValidWalletPrivateKey(envKey)) {
3143
3731
  return envKey;
3144
3732
  }
3145
- const baseHome = homeDir ?? homedir4();
3146
- const primaryWalletPath = join6(baseHome, MNEMOSPARK_WALLET_KEY_SUBPATH);
3147
- const fallbackWalletPath = join6(baseHome, BLOCKRUN_WALLET_KEY_SUBPATH);
3733
+ const baseHome = homeDir ?? homedir6();
3734
+ const primaryWalletPath = join8(baseHome, MNEMOSPARK_WALLET_KEY_SUBPATH);
3735
+ const fallbackWalletPath = join8(baseHome, BLOCKRUN_WALLET_KEY_SUBPATH);
3148
3736
  const fromPrimary = await readWalletKeyIfPresent(primaryWalletPath);
3149
3737
  if (fromPrimary) {
3150
3738
  return fromPrimary;
@@ -3175,7 +3763,7 @@ function encryptAesGcm(plaintext, key, randomFn = randomBytesNode) {
3175
3763
  }
3176
3764
  async function loadOrCreateKek(walletAddress, homeDir) {
3177
3765
  const keyPath = resolveWalletKekPath(walletAddress, homeDir);
3178
- await mkdir3(dirname3(keyPath), { recursive: true });
3766
+ await mkdir5(dirname5(keyPath), { recursive: true });
3179
3767
  try {
3180
3768
  const existing = await readFile3(keyPath);
3181
3769
  return { kek: parseStoredAes256Key(existing), keyPath };
@@ -3326,7 +3914,7 @@ function createCloudCommand(options = {}) {
3326
3914
  createPaymentFetchFn: options.createPaymentFetchFn ?? createPaymentFetch,
3327
3915
  fetchImpl: options.fetchImpl ?? fetch,
3328
3916
  nowDateFn: options.nowDateFn ?? (() => /* @__PURE__ */ new Date()),
3329
- idempotencyKeyFn: options.idempotencyKeyFn ?? randomUUID,
3917
+ idempotencyKeyFn: options.idempotencyKeyFn ?? randomUUID3,
3330
3918
  requestStorageLsFn: options.requestStorageLsFn ?? requestStorageLsViaProxy,
3331
3919
  requestStorageDownloadFn: options.requestStorageDownloadFn ?? requestStorageDownloadViaProxy,
3332
3920
  requestStorageDeleteFn: options.requestStorageDeleteFn ?? requestStorageDeleteViaProxy,
@@ -3344,6 +3932,57 @@ function createCloudCommand(options = {}) {
3344
3932
  }
3345
3933
  };
3346
3934
  }
3935
+ async function resolveNameSelectorIfNeeded(datastore, request, selector) {
3936
+ if (!selector) {
3937
+ const parsedRequest2 = parseStorageObjectRequest(request);
3938
+ if (!parsedRequest2) {
3939
+ return { error: "Cannot resolve storage object request." };
3940
+ }
3941
+ return { request: parsedRequest2 };
3942
+ }
3943
+ const matches = await datastore.countFriendlyNameMatches(request.wallet_address, selector.name);
3944
+ if (matches > 1 && !selector.latest && !selector.at) {
3945
+ return {
3946
+ error: `Multiple objects match --name ${selector.name}. Add --latest or --at <timestamp>.`
3947
+ };
3948
+ }
3949
+ const resolved = await datastore.resolveFriendlyName({
3950
+ walletAddress: request.wallet_address,
3951
+ friendlyName: selector.name,
3952
+ latest: selector.latest,
3953
+ at: selector.at
3954
+ });
3955
+ if (!resolved || !resolved.objectKey) {
3956
+ return { error: `No object found for --name ${selector.name}.` };
3957
+ }
3958
+ const parsedRequest = parseStorageObjectRequest({
3959
+ ...request,
3960
+ object_key: resolved.objectKey
3961
+ });
3962
+ if (!parsedRequest) {
3963
+ return { error: "Cannot resolve storage object request." };
3964
+ }
3965
+ return {
3966
+ request: parsedRequest
3967
+ };
3968
+ }
3969
+ async function emitCloudEvent(eventType, details, homeDir) {
3970
+ await appendJsonlEvent(
3971
+ "events.jsonl",
3972
+ {
3973
+ ...details,
3974
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3975
+ event_type: eventType
3976
+ },
3977
+ homeDir
3978
+ );
3979
+ }
3980
+ async function emitCloudEventBestEffort(eventType, details, homeDir) {
3981
+ try {
3982
+ await emitCloudEvent(eventType, details, homeDir);
3983
+ } catch {
3984
+ }
3985
+ }
3347
3986
  async function runCloudCommandHandler(ctx, options) {
3348
3987
  const parsed = parseCloudArgs(ctx.args);
3349
3988
  const objectLogHomeDir = options.objectLogHomeDir;
@@ -3395,9 +4034,109 @@ async function runCloudCommandHandler(ctx, options) {
3395
4034
  isError: true
3396
4035
  };
3397
4036
  }
4037
+ if (parsed.mode === "op-status-invalid") {
4038
+ return {
4039
+ text: "Cannot get operation status: required arguments are --operation-id.",
4040
+ isError: true
4041
+ };
4042
+ }
4043
+ const datastore = await createCloudDatastore(objectLogHomeDir);
4044
+ if (parsed.mode === "op-status") {
4045
+ const operation = await datastore.findOperationById(parsed.operationId);
4046
+ if (!operation) {
4047
+ return {
4048
+ text: `Operation not found: ${parsed.operationId}`,
4049
+ isError: true
4050
+ };
4051
+ }
4052
+ return {
4053
+ text: [
4054
+ `operation-id: ${operation.operation_id}`,
4055
+ `type: ${operation.type}`,
4056
+ `status: ${operation.status}`,
4057
+ `started-at: ${operation.started_at ?? "n/a"}`,
4058
+ `finished-at: ${operation.finished_at ?? "n/a"}`,
4059
+ operation.error_code ? `error-code: ${operation.error_code}` : null,
4060
+ operation.error_message ? `error-message: ${operation.error_message}` : null
4061
+ ].filter((v) => Boolean(v)).join("\n"),
4062
+ isError: operation.status === "failed"
4063
+ };
4064
+ }
4065
+ if ((parsed.mode === "upload" || parsed.mode === "download") && parsed.async) {
4066
+ const operationId = randomUUID3();
4067
+ const opType = parsed.mode;
4068
+ const opObject = parsed.mode === "upload" ? parsed.uploadRequest.object_id : parsed.storageObjectRequest.object_key ?? null;
4069
+ const opQuote = parsed.mode === "upload" ? parsed.uploadRequest.quote_id : null;
4070
+ await datastore.upsertOperation({
4071
+ operation_id: operationId,
4072
+ type: opType,
4073
+ object_id: opObject,
4074
+ quote_id: opQuote,
4075
+ status: "started",
4076
+ error_code: null,
4077
+ error_message: null
4078
+ });
4079
+ const syncArgs = stripAsyncFlag(ctx.args);
4080
+ void runCloudCommandHandler({ args: syncArgs }, options).then(async (result) => {
4081
+ await datastore.upsertOperation({
4082
+ operation_id: operationId,
4083
+ type: opType,
4084
+ object_id: opObject,
4085
+ quote_id: opQuote,
4086
+ status: result.isError ? "failed" : "succeeded",
4087
+ error_code: result.isError ? "ASYNC_FAILED" : null,
4088
+ error_message: result.isError ? result.text : null
4089
+ });
4090
+ }).catch(async (err) => {
4091
+ await datastore.upsertOperation({
4092
+ operation_id: operationId,
4093
+ type: opType,
4094
+ object_id: opObject,
4095
+ quote_id: opQuote,
4096
+ status: "failed",
4097
+ error_code: "ASYNC_EXCEPTION",
4098
+ error_message: err instanceof Error ? err.message : String(err)
4099
+ });
4100
+ });
4101
+ return {
4102
+ text: `Operation started in background. operation-id: ${operationId}
4103
+ Use /mnemospark-cloud op-status --operation-id ${operationId}`
4104
+ };
4105
+ }
3398
4106
  if (parsed.mode === "backup") {
3399
4107
  try {
3400
4108
  const result = await backupBuilder(parsed.backupTarget, options.backupOptions);
4109
+ await emitCloudEventBestEffort(
4110
+ "backup.completed",
4111
+ {
4112
+ operation_id: randomUUID3(),
4113
+ object_id: result.objectId,
4114
+ status: "succeeded",
4115
+ details: { friendly_name: parsed.friendlyName ?? basename2(parsed.backupTarget) }
4116
+ },
4117
+ objectLogHomeDir
4118
+ );
4119
+ await datastore.upsertObject({
4120
+ object_id: result.objectId,
4121
+ object_key: null,
4122
+ wallet_address: "unknown",
4123
+ quote_id: null,
4124
+ provider: null,
4125
+ bucket_name: null,
4126
+ region: null,
4127
+ sha256: result.objectIdHash,
4128
+ status: "backed_up"
4129
+ });
4130
+ const friendlyName = parsed.friendlyName?.trim() || basename2(parsed.backupTarget);
4131
+ if (friendlyName) {
4132
+ await datastore.upsertFriendlyName({
4133
+ friendly_name: friendlyName,
4134
+ object_id: result.objectId,
4135
+ object_key: null,
4136
+ quote_id: null,
4137
+ wallet_address: "unknown"
4138
+ });
4139
+ }
3401
4140
  return {
3402
4141
  text: [
3403
4142
  `object-id: ${result.objectId}`,
@@ -3425,6 +4164,25 @@ async function runCloudCommandHandler(ctx, options) {
3425
4164
  options.proxyQuoteOptions
3426
4165
  );
3427
4166
  await appendPriceStorageQuoteLog(quote, objectLogHomeDir);
4167
+ await datastore.upsertObject({
4168
+ object_id: quote.object_id,
4169
+ object_key: null,
4170
+ wallet_address: quote.addr,
4171
+ quote_id: quote.quote_id,
4172
+ provider: quote.provider,
4173
+ bucket_name: null,
4174
+ region: quote.location,
4175
+ sha256: quote.object_id_hash,
4176
+ status: "quoted"
4177
+ });
4178
+ await datastore.upsertPayment({
4179
+ quote_id: quote.quote_id,
4180
+ wallet_address: quote.addr,
4181
+ trans_id: null,
4182
+ amount: quote.storage_price,
4183
+ network: null,
4184
+ status: "quoted"
4185
+ });
3428
4186
  return {
3429
4187
  text: formatPriceStorageUserMessage(quote)
3430
4188
  };
@@ -3438,10 +4196,7 @@ async function runCloudCommandHandler(ctx, options) {
3438
4196
  }
3439
4197
  if (parsed.mode === "upload") {
3440
4198
  try {
3441
- const loggedQuote = await findLoggedPriceStorageQuote(
3442
- parsed.uploadRequest.quote_id,
3443
- objectLogHomeDir
3444
- );
4199
+ const loggedQuote = await datastore.findQuoteById(parsed.uploadRequest.quote_id) ?? await findLoggedPriceStorageQuote(parsed.uploadRequest.quote_id, objectLogHomeDir);
3445
4200
  if (!loggedQuote) {
3446
4201
  return {
3447
4202
  text: "Cannot upload storage object: quote-id not found in object.log. Run /mnemospark-cloud price-storage first.",
@@ -3454,13 +4209,13 @@ async function runCloudCommandHandler(ctx, options) {
3454
4209
  isError: true
3455
4210
  };
3456
4211
  }
3457
- const archivePath = join6(
4212
+ const archivePath = join8(
3458
4213
  options.backupOptions?.tmpDir ?? DEFAULT_BACKUP_DIR,
3459
4214
  parsed.uploadRequest.object_id
3460
4215
  );
3461
4216
  let archiveStats;
3462
4217
  try {
3463
- archiveStats = await stat(archivePath);
4218
+ archiveStats = await stat2(archivePath);
3464
4219
  } catch {
3465
4220
  return {
3466
4221
  text: `Cannot upload storage object: local archive not found at ${archivePath}. Run /mnemospark-cloud backup first.`,
@@ -3561,6 +4316,71 @@ async function runCloudCommandHandler(ctx, options) {
3561
4316
  objectLogHomeDir,
3562
4317
  nowDateFn
3563
4318
  );
4319
+ await datastore.upsertObject({
4320
+ object_id: finalizedUploadResponse.object_id,
4321
+ object_key: finalizedUploadResponse.object_key,
4322
+ wallet_address: finalizedUploadResponse.addr,
4323
+ quote_id: finalizedUploadResponse.quote_id,
4324
+ provider: finalizedUploadResponse.provider,
4325
+ bucket_name: finalizedUploadResponse.bucket_name,
4326
+ region: finalizedUploadResponse.location,
4327
+ sha256: parsed.uploadRequest.object_id_hash,
4328
+ status: "uploaded"
4329
+ });
4330
+ await datastore.upsertPayment({
4331
+ quote_id: finalizedUploadResponse.quote_id,
4332
+ wallet_address: finalizedUploadResponse.addr,
4333
+ trans_id: finalizedUploadResponse.trans_id ?? null,
4334
+ amount: cronStoragePrice,
4335
+ network: null,
4336
+ status: "settled",
4337
+ settled_at: (/* @__PURE__ */ new Date()).toISOString()
4338
+ });
4339
+ await datastore.upsertCronJob({
4340
+ cron_id: cronJob.cronId,
4341
+ object_id: cronJob.objectId,
4342
+ object_key: cronJob.objectKey,
4343
+ quote_id: cronJob.quoteId,
4344
+ schedule: cronJob.schedule,
4345
+ command: cronJob.command,
4346
+ status: "active"
4347
+ });
4348
+ if (parsed.friendlyName?.trim()) {
4349
+ await datastore.upsertFriendlyName({
4350
+ friendly_name: parsed.friendlyName.trim(),
4351
+ object_id: finalizedUploadResponse.object_id,
4352
+ object_key: finalizedUploadResponse.object_key,
4353
+ quote_id: finalizedUploadResponse.quote_id,
4354
+ wallet_address: finalizedUploadResponse.addr
4355
+ });
4356
+ try {
4357
+ await appendJsonlEvent(
4358
+ "manifest.jsonl",
4359
+ {
4360
+ friendly_name: parsed.friendlyName.trim(),
4361
+ object_id: finalizedUploadResponse.object_id,
4362
+ object_key: finalizedUploadResponse.object_key,
4363
+ quote_id: finalizedUploadResponse.quote_id,
4364
+ wallet_address: finalizedUploadResponse.addr,
4365
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
4366
+ },
4367
+ objectLogHomeDir
4368
+ );
4369
+ } catch {
4370
+ }
4371
+ }
4372
+ await emitCloudEventBestEffort(
4373
+ "upload.completed",
4374
+ {
4375
+ operation_id: idempotencyKey,
4376
+ wallet_address: finalizedUploadResponse.addr,
4377
+ object_id: finalizedUploadResponse.object_id,
4378
+ object_key: finalizedUploadResponse.object_key,
4379
+ quote_id: finalizedUploadResponse.quote_id,
4380
+ status: "succeeded"
4381
+ },
4382
+ objectLogHomeDir
4383
+ );
3564
4384
  await maybeCleanupLocalBackupArchive(archivePath);
3565
4385
  return {
3566
4386
  text: formatStorageUploadUserMessage(finalizedUploadResponse, cronJob.cronId)
@@ -3574,18 +4394,72 @@ async function runCloudCommandHandler(ctx, options) {
3574
4394
  }
3575
4395
  }
3576
4396
  if (parsed.mode === "ls") {
4397
+ const resolved = await resolveNameSelectorIfNeeded(
4398
+ datastore,
4399
+ parsed.storageObjectRequest,
4400
+ parsed.nameSelector
4401
+ );
4402
+ if (resolved.error || !resolved.request) {
4403
+ return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4404
+ }
4405
+ const resolvedRequest = resolved.request;
4406
+ const operationId = randomUUID3();
4407
+ await datastore.upsertOperation({
4408
+ operation_id: operationId,
4409
+ type: "ls",
4410
+ object_id: resolvedRequest.object_key,
4411
+ quote_id: null,
4412
+ status: "started",
4413
+ error_code: null,
4414
+ error_message: null
4415
+ });
3577
4416
  try {
3578
- const lsResult = await requestStorageLs(
3579
- parsed.storageObjectRequest,
3580
- options.proxyStorageOptions
3581
- );
4417
+ const lsResult = await requestStorageLs(resolvedRequest, options.proxyStorageOptions);
3582
4418
  if (!lsResult.success) {
3583
4419
  throw new Error("ls failed");
3584
4420
  }
4421
+ await datastore.upsertOperation({
4422
+ operation_id: operationId,
4423
+ type: "ls",
4424
+ object_id: resolvedRequest.object_key,
4425
+ quote_id: null,
4426
+ status: "succeeded",
4427
+ error_code: null,
4428
+ error_message: null
4429
+ });
4430
+ await emitCloudEventBestEffort(
4431
+ "ls.completed",
4432
+ {
4433
+ operation_id: operationId,
4434
+ wallet_address: resolvedRequest.wallet_address,
4435
+ object_key: resolvedRequest.object_key,
4436
+ status: "succeeded"
4437
+ },
4438
+ objectLogHomeDir
4439
+ );
3585
4440
  return {
3586
- text: formatStorageLsUserMessage(lsResult, parsed.storageObjectRequest.object_key)
4441
+ text: formatStorageLsUserMessage(lsResult, resolvedRequest.object_key)
3587
4442
  };
3588
4443
  } catch {
4444
+ await datastore.upsertOperation({
4445
+ operation_id: operationId,
4446
+ type: "ls",
4447
+ object_id: resolvedRequest.object_key,
4448
+ quote_id: null,
4449
+ status: "failed",
4450
+ error_code: "LS_FAILED",
4451
+ error_message: "Cannot list storage object"
4452
+ });
4453
+ await emitCloudEventBestEffort(
4454
+ "ls.completed",
4455
+ {
4456
+ operation_id: operationId,
4457
+ wallet_address: resolvedRequest.wallet_address,
4458
+ object_key: resolvedRequest.object_key,
4459
+ status: "failed"
4460
+ },
4461
+ objectLogHomeDir
4462
+ );
3589
4463
  return {
3590
4464
  text: "Cannot list storage object",
3591
4465
  isError: true
@@ -3593,18 +4467,75 @@ async function runCloudCommandHandler(ctx, options) {
3593
4467
  }
3594
4468
  }
3595
4469
  if (parsed.mode === "download") {
4470
+ const resolved = await resolveNameSelectorIfNeeded(
4471
+ datastore,
4472
+ parsed.storageObjectRequest,
4473
+ parsed.nameSelector
4474
+ );
4475
+ if (resolved.error || !resolved.request) {
4476
+ return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4477
+ }
4478
+ const resolvedRequest = resolved.request;
4479
+ const operationId = randomUUID3();
4480
+ await datastore.upsertOperation({
4481
+ operation_id: operationId,
4482
+ type: "download",
4483
+ object_id: resolvedRequest.object_key,
4484
+ quote_id: null,
4485
+ status: "started",
4486
+ error_code: null,
4487
+ error_message: null
4488
+ });
3596
4489
  try {
3597
4490
  const downloadResult = await requestStorageDownload(
3598
- parsed.storageObjectRequest,
4491
+ resolvedRequest,
3599
4492
  options.proxyStorageOptions
3600
4493
  );
3601
4494
  if (!downloadResult.success) {
3602
4495
  throw new Error("download failed");
3603
4496
  }
4497
+ await datastore.upsertOperation({
4498
+ operation_id: operationId,
4499
+ type: "download",
4500
+ object_id: resolvedRequest.object_key,
4501
+ quote_id: null,
4502
+ status: "succeeded",
4503
+ error_code: null,
4504
+ error_message: null
4505
+ });
4506
+ await emitCloudEventBestEffort(
4507
+ "download.completed",
4508
+ {
4509
+ operation_id: operationId,
4510
+ wallet_address: resolvedRequest.wallet_address,
4511
+ object_key: resolvedRequest.object_key,
4512
+ status: "succeeded"
4513
+ },
4514
+ objectLogHomeDir
4515
+ );
3604
4516
  return {
3605
- text: `File ${parsed.storageObjectRequest.object_key} downloaded to ${downloadResult.file_path}`
4517
+ text: `File ${resolvedRequest.object_key} downloaded to ${downloadResult.file_path}`
3606
4518
  };
3607
4519
  } catch {
4520
+ await datastore.upsertOperation({
4521
+ operation_id: operationId,
4522
+ type: "download",
4523
+ object_id: resolvedRequest.object_key,
4524
+ quote_id: null,
4525
+ status: "failed",
4526
+ error_code: "DOWNLOAD_FAILED",
4527
+ error_message: "Cannot download file"
4528
+ });
4529
+ await emitCloudEventBestEffort(
4530
+ "download.completed",
4531
+ {
4532
+ operation_id: operationId,
4533
+ wallet_address: resolvedRequest.wallet_address,
4534
+ object_key: resolvedRequest.object_key,
4535
+ status: "failed"
4536
+ },
4537
+ objectLogHomeDir
4538
+ );
3608
4539
  return {
3609
4540
  text: "Cannot download file",
3610
4541
  isError: true
@@ -3612,15 +4543,33 @@ async function runCloudCommandHandler(ctx, options) {
3612
4543
  }
3613
4544
  }
3614
4545
  if (parsed.mode === "delete") {
4546
+ const resolved = await resolveNameSelectorIfNeeded(
4547
+ datastore,
4548
+ parsed.storageObjectRequest,
4549
+ parsed.nameSelector
4550
+ );
4551
+ if (resolved.error || !resolved.request) {
4552
+ return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
4553
+ }
4554
+ const resolvedRequest = resolved.request;
4555
+ const operationId = randomUUID3();
4556
+ const existingObjectByKey = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
3615
4557
  try {
3616
- const deleteResult = await requestStorageDelete(
3617
- parsed.storageObjectRequest,
3618
- options.proxyStorageOptions
3619
- );
4558
+ const deleteResult = await requestStorageDelete(resolvedRequest, options.proxyStorageOptions);
3620
4559
  if (!deleteResult.success) {
3621
4560
  throw new Error("delete failed");
3622
4561
  }
3623
4562
  } catch {
4563
+ await emitCloudEventBestEffort(
4564
+ "delete.completed",
4565
+ {
4566
+ operation_id: operationId,
4567
+ wallet_address: resolvedRequest.wallet_address,
4568
+ object_key: resolvedRequest.object_key,
4569
+ status: "failed"
4570
+ },
4571
+ objectLogHomeDir
4572
+ );
3624
4573
  return {
3625
4574
  text: "Cannot delete file",
3626
4575
  isError: true
@@ -3629,16 +4578,58 @@ async function runCloudCommandHandler(ctx, options) {
3629
4578
  let cronEntry = null;
3630
4579
  let cronDeleted = false;
3631
4580
  try {
3632
- cronEntry = await findLoggedStoragePaymentCronByObjectKey(
3633
- parsed.storageObjectRequest.object_key,
3634
- objectLogHomeDir
3635
- );
3636
- cronDeleted = cronEntry ? await removeStoragePaymentCronJob(cronEntry.cronId, objectLogHomeDir) : false;
4581
+ const dbCron = await datastore.findCronByObjectKey(resolvedRequest.object_key);
4582
+ if (dbCron) {
4583
+ cronEntry = {
4584
+ cronId: dbCron.cronId,
4585
+ objectId: dbCron.objectId,
4586
+ objectKey: resolvedRequest.object_key
4587
+ };
4588
+ }
4589
+ if (!cronEntry) {
4590
+ cronEntry = await findLoggedStoragePaymentCronByObjectKey(
4591
+ resolvedRequest.object_key,
4592
+ objectLogHomeDir
4593
+ );
4594
+ }
4595
+ if (cronEntry) {
4596
+ const fileCronDeleted = await removeStoragePaymentCronJob(
4597
+ cronEntry.cronId,
4598
+ objectLogHomeDir
4599
+ );
4600
+ const dbCronDeleted = await datastore.removeCronJob(cronEntry.cronId);
4601
+ cronDeleted = fileCronDeleted || dbCronDeleted;
4602
+ }
3637
4603
  } catch {
3638
4604
  }
4605
+ const objectId = cronEntry?.objectId ?? existingObjectByKey?.object_id ?? null;
4606
+ if (objectId) {
4607
+ const existingObject = existingObjectByKey?.object_id === objectId ? existingObjectByKey : await datastore.findObjectById(objectId);
4608
+ await datastore.upsertObject({
4609
+ object_id: objectId,
4610
+ object_key: resolvedRequest.object_key,
4611
+ wallet_address: existingObject?.wallet_address ?? resolvedRequest.wallet_address,
4612
+ quote_id: existingObject?.quote_id ?? null,
4613
+ provider: existingObject?.provider ?? null,
4614
+ bucket_name: existingObject?.bucket_name ?? null,
4615
+ region: resolvedRequest.location ?? existingObject?.region ?? null,
4616
+ sha256: existingObject?.sha256 ?? null,
4617
+ status: "deleted"
4618
+ });
4619
+ }
4620
+ await emitCloudEventBestEffort(
4621
+ "delete.completed",
4622
+ {
4623
+ operation_id: operationId,
4624
+ wallet_address: resolvedRequest.wallet_address,
4625
+ object_key: resolvedRequest.object_key,
4626
+ status: "succeeded"
4627
+ },
4628
+ objectLogHomeDir
4629
+ );
3639
4630
  return {
3640
4631
  text: formatStorageDeleteUserMessage(
3641
- parsed.storageObjectRequest.object_key,
4632
+ resolvedRequest.object_key,
3642
4633
  cronEntry?.cronId ?? null,
3643
4634
  cronDeleted
3644
4635
  )
@@ -3652,11 +4643,11 @@ async function runCloudCommandHandler(ctx, options) {
3652
4643
 
3653
4644
  // src/cli.ts
3654
4645
  import { spawn as spawn2 } from "child_process";
3655
- import { dirname as dirname4, join as join7 } from "path";
4646
+ import { dirname as dirname6, join as join9 } from "path";
3656
4647
  import { fileURLToPath as fileURLToPath2 } from "url";
3657
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
4648
+ import { mkdir as mkdir6, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
3658
4649
  import { existsSync } from "fs";
3659
- import { homedir as homedir5 } from "os";
4650
+ import { homedir as homedir7 } from "os";
3660
4651
  function isHexPrivateKey(value) {
3661
4652
  return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value.trim());
3662
4653
  }
@@ -3761,20 +4752,20 @@ function parseArgs(args) {
3761
4752
  return result;
3762
4753
  }
3763
4754
  var __filename2 = fileURLToPath2(import.meta.url);
3764
- var __dirname2 = dirname4(__filename2);
3765
- var PACKAGE_ROOT = dirname4(__dirname2);
4755
+ var __dirname2 = dirname6(__filename2);
4756
+ var PACKAGE_ROOT = dirname6(__dirname2);
3766
4757
  async function ensureDir(path) {
3767
- await mkdir4(path, { recursive: true });
4758
+ await mkdir6(path, { recursive: true });
3768
4759
  }
3769
4760
  async function deployExtensionFiles() {
3770
- const scriptsSource = join7(PACKAGE_ROOT, "scripts");
4761
+ const scriptsSource = join9(PACKAGE_ROOT, "scripts");
3771
4762
  if (!existsSync(scriptsSource)) return;
3772
- const mnemoScriptsDir = join7(homedir5(), ".openclaw", "mnemospark", "scripts");
4763
+ const mnemoScriptsDir = join9(homedir7(), ".openclaw", "mnemospark", "scripts");
3773
4764
  await ensureDir(mnemoScriptsDir);
3774
- const uninstallSrc = join7(scriptsSource, "uninstall.sh");
4765
+ const uninstallSrc = join9(scriptsSource, "uninstall.sh");
3775
4766
  if (existsSync(uninstallSrc)) {
3776
4767
  const content = await readFile4(uninstallSrc);
3777
- await writeFile4(join7(mnemoScriptsDir, "uninstall.sh"), content, { mode: 493 });
4768
+ await writeFile4(join9(mnemoScriptsDir, "uninstall.sh"), content, { mode: 493 });
3778
4769
  }
3779
4770
  }
3780
4771
  function isOpenClawAvailable() {
@@ -3788,8 +4779,8 @@ function isOpenClawAvailable() {
3788
4779
  });
3789
4780
  }
3790
4781
  function getOpenClawConfigPath() {
3791
- const stateDir = process.env.OPENCLAW_STATE_DIR ?? join7(homedir5(), ".openclaw");
3792
- return join7(stateDir, "openclaw.json");
4782
+ const stateDir = process.env.OPENCLAW_STATE_DIR ?? join9(homedir7(), ".openclaw");
4783
+ return join9(stateDir, "openclaw.json");
3793
4784
  }
3794
4785
  async function ensureMnemosparkInPluginsAllow() {
3795
4786
  const configPath = getOpenClawConfigPath();
@@ -3851,7 +4842,7 @@ async function readLegacyWalletIfPresent() {
3851
4842
  }
3852
4843
  }
3853
4844
  async function writeMnemosparkWallet(key) {
3854
- const dir = dirname4(WALLET_FILE);
4845
+ const dir = dirname6(WALLET_FILE);
3855
4846
  await ensureDir(dir);
3856
4847
  await writeFile4(WALLET_FILE, `${key}
3857
4848
  `, { mode: 384 });