mnemospark 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -332,6 +332,34 @@ function normalizePaymentRequired(headers) {
332
332
  function normalizePaymentResponse(headers) {
333
333
  return headers.get("PAYMENT-RESPONSE") ?? headers.get("x-payment-response") ?? void 0;
334
334
  }
335
+ var BYTE_SI_UNITS = ["B", "KB", "MB", "GB", "TB"];
336
+ var BYTE_SI_BASE = 1e3;
337
+ function formatBytesForDisplay(bytes) {
338
+ if (!Number.isInteger(bytes) || bytes < 0 || !Number.isFinite(bytes)) {
339
+ throw new Error("formatBytesForDisplay expects a non-negative integer");
340
+ }
341
+ if (bytes === 0) {
342
+ return "0 B";
343
+ }
344
+ let value = bytes;
345
+ let unitIndex = 0;
346
+ while (value >= BYTE_SI_BASE && unitIndex < BYTE_SI_UNITS.length - 1) {
347
+ value /= BYTE_SI_BASE;
348
+ unitIndex += 1;
349
+ }
350
+ if (unitIndex === 0) {
351
+ return `${bytes} B`;
352
+ }
353
+ const nearestInt = Math.round(value);
354
+ const pickInt = nearestInt > 0 && Math.abs(value - nearestInt) / Math.max(value, 1e-9) <= 0.01;
355
+ let rounded = pickInt ? nearestInt : Math.round(value * 10) / 10;
356
+ if (rounded >= BYTE_SI_BASE && unitIndex < BYTE_SI_UNITS.length - 1) {
357
+ rounded = Math.round(rounded / BYTE_SI_BASE * 10) / 10;
358
+ unitIndex += 1;
359
+ }
360
+ const text = Number.isInteger(rounded) ? String(rounded) : String(rounded).replace(/\.0$/, "");
361
+ return `${text} ${BYTE_SI_UNITS[unitIndex]}`;
362
+ }
335
363
 
336
364
  // src/wallet-signature.ts
337
365
  function normalizeWalletSignature(value) {
@@ -1148,6 +1176,9 @@ function parseStoredAes256Key(raw, errorMessage = "Invalid key file format") {
1148
1176
  var STORAGE_LS_PROXY_PATH = "/mnemospark/storage/ls";
1149
1177
  var STORAGE_DOWNLOAD_PROXY_PATH = "/mnemospark/storage/download";
1150
1178
  var STORAGE_DELETE_PROXY_PATH = "/mnemospark/storage/delete";
1179
+ function isStorageLsListResponse(r) {
1180
+ return r.mode === "list";
1181
+ }
1151
1182
  var AES_GCM_TAG_BYTES = 16;
1152
1183
  function asBooleanOrDefault(value, defaultValue) {
1153
1184
  if (typeof value === "boolean") {
@@ -1235,7 +1266,7 @@ async function decryptDownloadBytes(encryptedBytes, wrappedDekBase64, walletAddr
1235
1266
  }
1236
1267
  return decryptAesGcm(encryptedBytes, dek);
1237
1268
  }
1238
- async function requestJsonViaProxy(proxyPath, request, parser, options = {}) {
1269
+ async function requestJsonViaProxy(proxyPath, jsonBody, parser, options = {}) {
1239
1270
  const fetchImpl = options.fetchImpl ?? fetch;
1240
1271
  const baseUrl = normalizeBaseUrl(
1241
1272
  options.proxyBaseUrl ?? `http://127.0.0.1:${PROXY_PORT.toString()}`
@@ -1248,7 +1279,7 @@ async function requestJsonViaProxy(proxyPath, request, parser, options = {}) {
1248
1279
  },
1249
1280
  options.correlation
1250
1281
  ),
1251
- body: JSON.stringify(request)
1282
+ body: JSON.stringify(jsonBody)
1252
1283
  });
1253
1284
  const bodyText = await response.text();
1254
1285
  if (!response.ok) {
@@ -1262,7 +1293,7 @@ async function requestJsonViaProxy(proxyPath, request, parser, options = {}) {
1262
1293
  }
1263
1294
  return parser(payload);
1264
1295
  }
1265
- async function forwardStorageToBackend(path, method, request, options = {}) {
1296
+ async function forwardStorageToBackend(path, method, jsonBody, options = {}) {
1266
1297
  const fetchImpl = options.fetchImpl ?? fetch;
1267
1298
  const backendBaseUrl = (options.backendBaseUrl ?? "").trim();
1268
1299
  const walletSignature = normalizeWalletSignature(options.walletSignature);
@@ -1281,7 +1312,7 @@ async function forwardStorageToBackend(path, method, request, options = {}) {
1281
1312
  "Content-Type": "application/json",
1282
1313
  "X-Wallet-Signature": walletSignature
1283
1314
  },
1284
- body: JSON.stringify(request)
1315
+ body: JSON.stringify(jsonBody)
1285
1316
  });
1286
1317
  const bodyBuffer = Buffer.from(await response.arrayBuffer());
1287
1318
  return {
@@ -1311,11 +1342,97 @@ function parseStorageObjectRequest(payload) {
1311
1342
  location
1312
1343
  };
1313
1344
  }
1345
+ function jsonBodyForObjectRequest(request) {
1346
+ const o = {
1347
+ wallet_address: request.wallet_address,
1348
+ object_key: request.object_key
1349
+ };
1350
+ if (request.location) {
1351
+ o.location = request.location;
1352
+ }
1353
+ return o;
1354
+ }
1355
+ function jsonBodyForLsRequest(request) {
1356
+ const o = { wallet_address: request.wallet_address };
1357
+ if (request.object_key) {
1358
+ o.object_key = request.object_key;
1359
+ }
1360
+ if (request.location) {
1361
+ o.location = request.location;
1362
+ }
1363
+ if (request.continuation_token) {
1364
+ o.continuation_token = request.continuation_token;
1365
+ }
1366
+ if (typeof request.max_keys === "number") {
1367
+ o.max_keys = request.max_keys;
1368
+ }
1369
+ if (request.prefix) {
1370
+ o.prefix = request.prefix;
1371
+ }
1372
+ return o;
1373
+ }
1374
+ function parseStorageLsRequestPayload(payload) {
1375
+ const record = asRecord(payload);
1376
+ if (!record) {
1377
+ return null;
1378
+ }
1379
+ const walletAddress = asNonEmptyString(record.wallet_address);
1380
+ if (!walletAddress) {
1381
+ return null;
1382
+ }
1383
+ const objectKey = asNonEmptyString(record.object_key) ?? void 0;
1384
+ const location = asNonEmptyString(record.location) ?? void 0;
1385
+ const continuation_token = asNonEmptyString(record.continuation_token) ?? void 0;
1386
+ const maxRaw = asNumber(record.max_keys);
1387
+ const max_keys = maxRaw !== null && Number.isInteger(maxRaw) && maxRaw >= 1 ? maxRaw : void 0;
1388
+ const prefix = asNonEmptyString(record.prefix) ?? void 0;
1389
+ return {
1390
+ wallet_address: walletAddress,
1391
+ ...objectKey ? { object_key: objectKey } : {},
1392
+ ...location ? { location } : {},
1393
+ ...continuation_token ? { continuation_token } : {},
1394
+ ...typeof max_keys === "number" ? { max_keys } : {},
1395
+ ...prefix ? { prefix } : {}
1396
+ };
1397
+ }
1314
1398
  function parseStorageLsResponse(payload) {
1315
1399
  const record = asRecord(payload);
1316
1400
  if (!record) {
1317
1401
  throw new Error("Invalid ls response payload");
1318
1402
  }
1403
+ if (record.list_mode === true) {
1404
+ const bucket2 = asNonEmptyString(record.bucket) ?? asNonEmptyString(record.bucket_name);
1405
+ const rawObjects = record.objects;
1406
+ if (!bucket2 || !Array.isArray(rawObjects)) {
1407
+ throw new Error("ls list response is missing required fields");
1408
+ }
1409
+ const objects = [];
1410
+ for (const item of rawObjects) {
1411
+ const row = asRecord(item);
1412
+ if (!row) {
1413
+ continue;
1414
+ }
1415
+ const key2 = asNonEmptyString(row.key);
1416
+ const sizeBytes2 = asNumber(row.size_bytes);
1417
+ if (!key2 || sizeBytes2 === null || !Number.isInteger(sizeBytes2) || sizeBytes2 < 0) {
1418
+ continue;
1419
+ }
1420
+ const last_modified = asNonEmptyString(row.last_modified) ?? void 0;
1421
+ objects.push({ key: key2, size_bytes: sizeBytes2, last_modified });
1422
+ }
1423
+ const is_truncated = asBooleanOrDefault(record.is_truncated, false);
1424
+ const nextRaw = record.next_continuation_token;
1425
+ const next_continuation_token = nextRaw === void 0 || nextRaw === null ? null : String(nextRaw);
1426
+ return {
1427
+ mode: "list",
1428
+ success: asBooleanOrDefault(record.success, true),
1429
+ list_mode: true,
1430
+ bucket: bucket2,
1431
+ objects,
1432
+ is_truncated,
1433
+ next_continuation_token
1434
+ };
1435
+ }
1319
1436
  const key = asNonEmptyString(record.key) ?? asNonEmptyString(record.object_key);
1320
1437
  const sizeBytes = asNumber(record.size_bytes);
1321
1438
  const bucket = asNonEmptyString(record.bucket) ?? asNonEmptyString(record.bucket_name);
@@ -1323,7 +1440,11 @@ function parseStorageLsResponse(payload) {
1323
1440
  if (!key || sizeBytes === null || !bucket) {
1324
1441
  throw new Error("ls response is missing required fields");
1325
1442
  }
1443
+ if (!Number.isInteger(sizeBytes) || sizeBytes < 0) {
1444
+ throw new Error("ls response has invalid size_bytes; expected non-negative integer");
1445
+ }
1326
1446
  return {
1447
+ mode: "stat",
1327
1448
  success: asBooleanOrDefault(record.success, true),
1328
1449
  key,
1329
1450
  size_bytes: sizeBytes,
@@ -1366,12 +1487,17 @@ function parseStorageDownloadProxyResponse(payload) {
1366
1487
  };
1367
1488
  }
1368
1489
  async function requestStorageLsViaProxy(request, options = {}) {
1369
- return requestJsonViaProxy(STORAGE_LS_PROXY_PATH, request, parseStorageLsResponse, options);
1490
+ return requestJsonViaProxy(
1491
+ STORAGE_LS_PROXY_PATH,
1492
+ jsonBodyForLsRequest(request),
1493
+ parseStorageLsResponse,
1494
+ options
1495
+ );
1370
1496
  }
1371
1497
  async function requestStorageDownloadViaProxy(request, options = {}) {
1372
1498
  return requestJsonViaProxy(
1373
1499
  STORAGE_DOWNLOAD_PROXY_PATH,
1374
- request,
1500
+ jsonBodyForObjectRequest(request),
1375
1501
  parseStorageDownloadProxyResponse,
1376
1502
  options
1377
1503
  );
@@ -1379,19 +1505,29 @@ async function requestStorageDownloadViaProxy(request, options = {}) {
1379
1505
  async function requestStorageDeleteViaProxy(request, options = {}) {
1380
1506
  return requestJsonViaProxy(
1381
1507
  STORAGE_DELETE_PROXY_PATH,
1382
- request,
1508
+ jsonBodyForObjectRequest(request),
1383
1509
  parseStorageDeleteResponse,
1384
1510
  options
1385
1511
  );
1386
1512
  }
1387
1513
  async function forwardStorageLsToBackend(request, options = {}) {
1388
- return forwardStorageToBackend("/storage/ls", "POST", request, options);
1514
+ return forwardStorageToBackend("/storage/ls", "POST", jsonBodyForLsRequest(request), options);
1389
1515
  }
1390
1516
  async function forwardStorageDownloadToBackend(request, options = {}) {
1391
- return forwardStorageToBackend("/storage/download", "POST", request, options);
1517
+ return forwardStorageToBackend(
1518
+ "/storage/download",
1519
+ "POST",
1520
+ jsonBodyForObjectRequest(request),
1521
+ options
1522
+ );
1392
1523
  }
1393
1524
  async function forwardStorageDeleteToBackend(request, options = {}) {
1394
- return forwardStorageToBackend("/storage/delete", "POST", request, options);
1525
+ return forwardStorageToBackend(
1526
+ "/storage/delete",
1527
+ "POST",
1528
+ jsonBodyForObjectRequest(request),
1529
+ options
1530
+ );
1395
1531
  }
1396
1532
  async function downloadStorageToDisk(request, backendResponse, options = {}) {
1397
1533
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -2227,13 +2363,13 @@ async function startProxy(options) {
2227
2363
  });
2228
2364
  return;
2229
2365
  }
2230
- const requestPayload = parseStorageObjectRequest(payload);
2366
+ const requestPayload = parseStorageLsRequestPayload(payload);
2231
2367
  if (!requestPayload) {
2232
2368
  logProxyEvent("warn", "proxy_ls_missing_fields");
2233
2369
  emitProxyTerminalFromStatus(correlation, 400, { reason: "missing_fields" });
2234
2370
  sendJson(res, 400, {
2235
2371
  error: "Bad request",
2236
- message: "Missing required fields: wallet_address, object_key"
2372
+ message: "Missing required field: wallet_address"
2237
2373
  });
2238
2374
  return;
2239
2375
  }
@@ -2749,6 +2885,259 @@ import { homedir as homedir6 } from "os";
2749
2885
  import { basename as basename2, dirname as dirname5, join as join8, resolve as resolve2 } from "path";
2750
2886
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
2751
2887
 
2888
+ // src/cloud-ls-format.ts
2889
+ import { CronExpressionParser } from "cron-parser";
2890
+ var LS_NAME_DISPLAY_MAX = 72;
2891
+ var LS_PAY_DISPLAY_MAX = 28;
2892
+ var LS_CRON_ID_MAX = 14;
2893
+ var LS_TIME_FIELD_WIDTH = 12;
2894
+ var MONTHS_SHORT = [
2895
+ "Jan",
2896
+ "Feb",
2897
+ "Mar",
2898
+ "Apr",
2899
+ "May",
2900
+ "Jun",
2901
+ "Jul",
2902
+ "Aug",
2903
+ "Sep",
2904
+ "Oct",
2905
+ "Nov",
2906
+ "Dec"
2907
+ ];
2908
+ function formatLsTimeFieldUtc(iso, now) {
2909
+ const placeholder = " - ".slice(0, LS_TIME_FIELD_WIDTH);
2910
+ if (!iso) {
2911
+ return placeholder.padEnd(LS_TIME_FIELD_WIDTH, " ");
2912
+ }
2913
+ const d = new Date(iso);
2914
+ if (Number.isNaN(d.getTime())) {
2915
+ return placeholder.padEnd(LS_TIME_FIELD_WIDTH, " ");
2916
+ }
2917
+ const mon = MONTHS_SHORT[d.getUTCMonth()] ?? "???";
2918
+ const day = String(d.getUTCDate()).padStart(2, " ");
2919
+ const y = d.getUTCFullYear();
2920
+ const nowY = now.getUTCFullYear();
2921
+ let core;
2922
+ if (y === nowY) {
2923
+ const hh = String(d.getUTCHours()).padStart(2, "0");
2924
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
2925
+ core = `${mon} ${day} ${hh}:${mm}`;
2926
+ } else {
2927
+ core = `${mon} ${day} ${y}`;
2928
+ }
2929
+ return core.padEnd(LS_TIME_FIELD_WIDTH, " ");
2930
+ }
2931
+ function truncateEnd(value, max) {
2932
+ if (value.length <= max) {
2933
+ return value;
2934
+ }
2935
+ if (max <= 1) {
2936
+ return "\u2026";
2937
+ }
2938
+ return `${value.slice(0, max - 1)}\u2026`;
2939
+ }
2940
+ function truncateMiddle(value, max, suffixMin) {
2941
+ if (value.length <= max) {
2942
+ return value;
2943
+ }
2944
+ if (max < suffixMin + 5) {
2945
+ return truncateEnd(value, max);
2946
+ }
2947
+ const suffixLen = Math.min(suffixMin, max - 5);
2948
+ const prefixLen = max - 3 - suffixLen;
2949
+ return `${value.slice(0, prefixLen)} \u2026 ${value.slice(-suffixLen)}`;
2950
+ }
2951
+ function formatCronIdCell(cronId, width) {
2952
+ if (!cronId) {
2953
+ return " - ".slice(0, width).padStart(width, " ");
2954
+ }
2955
+ const t = truncateEnd(cronId, width);
2956
+ return t.padStart(width, " ");
2957
+ }
2958
+ function formatPaymentCell(amount, network, maxWidth) {
2959
+ if (amount === null) {
2960
+ return " - ".slice(0, maxWidth).padStart(maxWidth, " ");
2961
+ }
2962
+ let s = amount.toFixed(6).replace(/\.?0+$/, "");
2963
+ if (network && network.trim()) {
2964
+ s = `${s} (${network.trim()})`;
2965
+ }
2966
+ return truncateEnd(s, maxWidth).padStart(maxWidth, " ");
2967
+ }
2968
+ function formatNextCronUtc(schedule, cronStatus, now) {
2969
+ const blank = " - ".slice(0, LS_TIME_FIELD_WIDTH).padEnd(LS_TIME_FIELD_WIDTH, " ");
2970
+ if (cronStatus !== "active") {
2971
+ return blank;
2972
+ }
2973
+ try {
2974
+ const expr = CronExpressionParser.parse(schedule, { tz: "UTC", currentDate: now });
2975
+ const next = expr.next().toDate();
2976
+ return formatLsTimeFieldUtc(next.toISOString(), now);
2977
+ } catch {
2978
+ return "?".padEnd(LS_TIME_FIELD_WIDTH, " ");
2979
+ }
2980
+ }
2981
+ async function prepareRows(objects, walletAddress, datastore, now) {
2982
+ const sorted = [...objects].sort((a, b) => {
2983
+ const ta = a.last_modified ? Date.parse(a.last_modified) : Number.NaN;
2984
+ const tb = b.last_modified ? Date.parse(b.last_modified) : Number.NaN;
2985
+ const aOk = Number.isFinite(ta);
2986
+ const bOk = Number.isFinite(tb);
2987
+ if (aOk && bOk && tb !== ta) {
2988
+ return tb - ta;
2989
+ }
2990
+ if (aOk && !bOk) {
2991
+ return -1;
2992
+ }
2993
+ if (!aOk && bOk) {
2994
+ return 1;
2995
+ }
2996
+ return a.key.localeCompare(b.key);
2997
+ });
2998
+ const rows = [];
2999
+ for (const obj of sorted) {
3000
+ const friendly = await datastore.findLatestFriendlyNameForObjectKey(walletAddress, obj.key);
3001
+ const cp = await datastore.findCronAndPaymentForObjectKey(walletAddress, obj.key);
3002
+ const sizeStr = formatBytesForDisplay(obj.size_bytes);
3003
+ const s3time = formatLsTimeFieldUtc(obj.last_modified, now);
3004
+ let cronIdDisp = null;
3005
+ let nextRun = " - ".slice(0, LS_TIME_FIELD_WIDTH).padEnd(LS_TIME_FIELD_WIDTH, " ");
3006
+ let payCell = "";
3007
+ if (cp) {
3008
+ cronIdDisp = cp.cronId;
3009
+ nextRun = formatNextCronUtc(cp.schedule, cp.cronStatus, now);
3010
+ payCell = formatPaymentCell(cp.amount, cp.network, LS_PAY_DISPLAY_MAX);
3011
+ } else {
3012
+ payCell = formatPaymentCell(null, null, LS_PAY_DISPLAY_MAX);
3013
+ }
3014
+ const nameRaw = friendly ? `${friendly} (${obj.key})` : obj.key;
3015
+ rows.push({
3016
+ perm: "----------",
3017
+ ln: " 1",
3018
+ user: "- ",
3019
+ grp: "- ",
3020
+ sizeStr,
3021
+ s3time,
3022
+ cronIdRaw: cronIdDisp,
3023
+ nextRun,
3024
+ payRaw: payCell,
3025
+ nameRaw: truncateMiddle(nameRaw, LS_NAME_DISPLAY_MAX, 8)
3026
+ });
3027
+ }
3028
+ return rows;
3029
+ }
3030
+ function columnWidths(rows) {
3031
+ let sizeW = 4;
3032
+ let cronW = 4;
3033
+ let payW = 3;
3034
+ for (const r of rows) {
3035
+ sizeW = Math.max(sizeW, r.sizeStr.length);
3036
+ const cid = r.cronIdRaw ? truncateEnd(r.cronIdRaw, LS_CRON_ID_MAX) : "";
3037
+ cronW = Math.max(cronW, cid.length || 1);
3038
+ payW = Math.max(payW, r.payRaw.length);
3039
+ }
3040
+ cronW = Math.min(Math.max(cronW, 4), LS_CRON_ID_MAX);
3041
+ payW = Math.min(Math.max(payW, 3), LS_PAY_DISPLAY_MAX);
3042
+ return { sizeW, cronW, payW };
3043
+ }
3044
+ function renderRow(r, w) {
3045
+ const cronPadded = formatCronIdCell(r.cronIdRaw, w.cronW);
3046
+ const sizePadded = r.sizeStr.padStart(w.sizeW, " ");
3047
+ const payPadded = r.payRaw.padStart(w.payW, " ");
3048
+ return [
3049
+ r.perm,
3050
+ r.ln,
3051
+ r.user,
3052
+ r.grp,
3053
+ sizePadded,
3054
+ r.s3time,
3055
+ cronPadded,
3056
+ r.nextRun,
3057
+ payPadded,
3058
+ r.nameRaw
3059
+ ].join(" ");
3060
+ }
3061
+ function renderHeader(w) {
3062
+ return [
3063
+ "PERM ",
3064
+ "LN",
3065
+ "USER ",
3066
+ "GRP ",
3067
+ "SIZE".padStart(w.sizeW, " "),
3068
+ "S3_TIME ".slice(0, LS_TIME_FIELD_WIDTH).padEnd(LS_TIME_FIELD_WIDTH, " "),
3069
+ "CRON".padStart(w.cronW, " "),
3070
+ "NEXT ".slice(0, LS_TIME_FIELD_WIDTH).padEnd(LS_TIME_FIELD_WIDTH, " "),
3071
+ "PAY".padStart(w.payW, " "),
3072
+ "NAME"
3073
+ ].join(" ");
3074
+ }
3075
+ async function buildMnemosparkLsMessage(result, ctx) {
3076
+ const now = ctx.now ?? /* @__PURE__ */ new Date();
3077
+ if (isStorageLsListResponse(result)) {
3078
+ const disclaimer2 = "Names, cron, and payment columns come from this machine's SQLite catalog when available; `-` means unknown locally. S3 is authoritative for which keys exist.";
3079
+ const legend2 = "Legend: S3_TIME and NEXT are UTC. NEXT is the next cron fire from the stored expression.";
3080
+ const bucketLine = `bucket: ${result.bucket}`;
3081
+ const sortLine = "sorted by: S3 last_modified descending (missing dates last), then key ascending.";
3082
+ if (result.objects.length === 0) {
3083
+ const lines = [disclaimer2, "", bucketLine, "", "No objects in this bucket."];
3084
+ return lines.join("\n");
3085
+ }
3086
+ const rows = await prepareRows(result.objects, ctx.walletAddress, ctx.datastore, now);
3087
+ const w2 = columnWidths(rows);
3088
+ const header2 = renderHeader(w2);
3089
+ const bodyLines = rows.map((r) => renderRow(r, w2));
3090
+ const totalLine = `total ${String(result.objects.length)}`;
3091
+ const truncLine = result.is_truncated ? "List truncated; more objects in bucket." : null;
3092
+ const prose2 = [disclaimer2, legend2, bucketLine, sortLine, totalLine, truncLine].filter((x) => Boolean(x)).join("\n");
3093
+ const fence2 = ["```", [header2, ...bodyLines].join("\n"), "```"].join("\n");
3094
+ return `${prose2}
3095
+
3096
+ ${fence2}`;
3097
+ }
3098
+ const friendly = await ctx.datastore.findLatestFriendlyNameForObjectKey(
3099
+ ctx.walletAddress,
3100
+ result.key
3101
+ );
3102
+ const cp = await ctx.datastore.findCronAndPaymentForObjectKey(ctx.walletAddress, result.key);
3103
+ const sizeStr = formatBytesForDisplay(result.size_bytes);
3104
+ const s3time = formatLsTimeFieldUtc(void 0, now);
3105
+ let payCell = formatPaymentCell(null, null, LS_PAY_DISPLAY_MAX);
3106
+ let cronIdDisp = null;
3107
+ let nextRun = " - ".slice(0, LS_TIME_FIELD_WIDTH).padEnd(LS_TIME_FIELD_WIDTH, " ");
3108
+ if (cp) {
3109
+ cronIdDisp = cp.cronId;
3110
+ nextRun = formatNextCronUtc(cp.schedule, cp.cronStatus, now);
3111
+ payCell = formatPaymentCell(cp.amount, cp.network, LS_PAY_DISPLAY_MAX);
3112
+ }
3113
+ const nameShown = truncateMiddle(
3114
+ friendly ? `${friendly} (${result.key})` : result.key,
3115
+ LS_NAME_DISPLAY_MAX,
3116
+ 8
3117
+ );
3118
+ const prep = {
3119
+ perm: "----------",
3120
+ ln: " 1",
3121
+ user: "- ",
3122
+ grp: "- ",
3123
+ sizeStr,
3124
+ s3time,
3125
+ cronIdRaw: cronIdDisp,
3126
+ nextRun,
3127
+ payRaw: payCell,
3128
+ nameRaw: nameShown
3129
+ };
3130
+ const w = columnWidths([prep]);
3131
+ const header = renderHeader(w);
3132
+ const line = renderRow(prep, w);
3133
+ const disclaimer = "Names, cron, and payment columns come from this machine's SQLite catalog when available; `-` means unknown locally.";
3134
+ const legend = "Legend: S3_TIME and NEXT are UTC.";
3135
+ const prose = [disclaimer, legend, `bucket: ${result.bucket}`, ""].join("\n");
3136
+ const fence = ["```", [header, line].join("\n"), "```"].join("\n");
3137
+ return `${prose}
3138
+ ${fence}`;
3139
+ }
3140
+
2752
3141
  // src/cloud-datastore.ts
2753
3142
  import { randomUUID as randomUUID2 } from "crypto";
2754
3143
  import { mkdir as mkdir4 } from "fs/promises";
@@ -3192,7 +3581,63 @@ async function createCloudDatastore(homeDir) {
3192
3581
  WHERE wallet_address = ? AND friendly_name = ? AND is_active = 1`
3193
3582
  ).get(normalizedWalletAddress, friendlyName);
3194
3583
  return Number(row?.cnt ?? 0);
3195
- }, 0)
3584
+ }, 0),
3585
+ findLatestFriendlyNameForObjectKey: async (walletAddress, objectKey) => safe(() => {
3586
+ const w = normalizeWalletAddress(walletAddress);
3587
+ const byKey = db.prepare(
3588
+ `SELECT friendly_name
3589
+ FROM friendly_names
3590
+ WHERE wallet_address = ? AND object_key = ? AND is_active = 1
3591
+ ORDER BY created_at DESC
3592
+ LIMIT 1`
3593
+ ).get(w, objectKey);
3594
+ if (byKey) {
3595
+ return byKey.friendly_name;
3596
+ }
3597
+ const obj = db.prepare(
3598
+ `SELECT object_id FROM objects WHERE wallet_address = ? AND object_key = ? LIMIT 1`
3599
+ ).get(w, objectKey);
3600
+ if (!obj) {
3601
+ return null;
3602
+ }
3603
+ const byObj = db.prepare(
3604
+ `SELECT friendly_name
3605
+ FROM friendly_names
3606
+ WHERE wallet_address = ? AND object_id = ? AND is_active = 1
3607
+ ORDER BY created_at DESC
3608
+ LIMIT 1`
3609
+ ).get(w, obj.object_id);
3610
+ return byObj?.friendly_name ?? null;
3611
+ }, null),
3612
+ findCronAndPaymentForObjectKey: async (walletAddress, objectKey) => safe(() => {
3613
+ const w = normalizeWalletAddress(walletAddress);
3614
+ const cron = db.prepare(
3615
+ `SELECT c.cron_id, c.quote_id, c.schedule, c.status
3616
+ FROM cron_jobs c
3617
+ INNER JOIN objects o ON o.object_id = c.object_id
3618
+ WHERE c.object_key = ? AND o.wallet_address = ?
3619
+ ORDER BY c.updated_at DESC
3620
+ LIMIT 1`
3621
+ ).get(objectKey, w);
3622
+ if (!cron) {
3623
+ return null;
3624
+ }
3625
+ const pay = db.prepare(
3626
+ `SELECT amount, network, status
3627
+ FROM payments
3628
+ WHERE quote_id = ? AND wallet_address = ?
3629
+ LIMIT 1`
3630
+ ).get(cron.quote_id, w);
3631
+ return {
3632
+ cronId: cron.cron_id,
3633
+ schedule: cron.schedule,
3634
+ quoteId: cron.quote_id,
3635
+ cronStatus: cron.status,
3636
+ amount: pay?.amount ?? null,
3637
+ network: pay?.network ?? null,
3638
+ paymentStatus: pay?.status ?? null
3639
+ };
3640
+ }, null)
3196
3641
  };
3197
3642
  }
3198
3643
 
@@ -3214,6 +3659,7 @@ var REQUIRED_PRICE_STORAGE = "--wallet-address, --object-id, --object-id-hash, -
3214
3659
  var REQUIRED_UPLOAD = "--quote-id, --wallet-address, --object-id, --object-id-hash";
3215
3660
  var REQUIRED_PAYMENT_SETTLE = "--quote-id and --wallet-address";
3216
3661
  var REQUIRED_STORAGE_OBJECT = "--wallet-address and one of (--object-key | --name [--latest|--at])";
3662
+ var REQUIRED_LS = "--wallet-address (for one object add --object-key or --name [--latest|--at]; omit both to list the bucket)";
3217
3663
  var PAYMENT_SETTLE_FLAG_NAMES = /* @__PURE__ */ new Set([
3218
3664
  "quote-id",
3219
3665
  "wallet-address",
@@ -3257,9 +3703,9 @@ var CLOUD_HELP_TEXT = [
3257
3703
  " Purpose: settle storage payment for a quote (e.g. monthly cron). Uses the same proxy + x402 path as upload pre-settlement.",
3258
3704
  " Required: --quote-id, --wallet-address (wallet private key must match the address).",
3259
3705
  "",
3260
- "\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>]`",
3261
- " Purpose: look up remote object metadata.",
3262
- " Required: " + REQUIRED_STORAGE_OBJECT,
3706
+ "\u2022 `/mnemospark_cloud ls --wallet-address <addr> [--object-key <key> | --name <friendly-name> | omit both to list bucket] [--latest|--at <timestamp>]`",
3707
+ " Purpose: stat one object or list all keys in the wallet bucket (S3).",
3708
+ " Required: " + REQUIRED_LS,
3263
3709
  "",
3264
3710
  "\u2022 `/mnemospark_cloud download --wallet-address <addr> [--object-key <object-key> | --name <friendly-name>] [--latest|--at <timestamp>] [--async] [--orchestrator <inline|subagent>] [--timeout-seconds <n>]`",
3265
3711
  " Purpose: fetch an object to local disk.",
@@ -3362,6 +3808,18 @@ function parseObjectSelector(flags) {
3362
3808
  if (objectKey) return { objectKey };
3363
3809
  return { nameSelector: { name, latest, at } };
3364
3810
  }
3811
+ function parseLsObjectSelector(flags) {
3812
+ const objectKey = flags["object-key"]?.trim();
3813
+ const name = flags.name?.trim();
3814
+ const latest = flags.latest === "true";
3815
+ const at = flags.at?.trim();
3816
+ if (objectKey && name) return null;
3817
+ if (latest && at) return null;
3818
+ if (!objectKey && !name && (latest || at)) return null;
3819
+ if (objectKey) return { objectKey };
3820
+ if (name) return { nameSelector: { name, latest, at } };
3821
+ return {};
3822
+ }
3365
3823
  function parseStorageObjectRequestInput(flags, selector) {
3366
3824
  const walletAddress = flags["wallet-address"]?.trim();
3367
3825
  if (!walletAddress) {
@@ -3572,15 +4030,36 @@ function parseCloudArgs(args) {
3572
4030
  if (!flags) {
3573
4031
  return { mode: "ls-invalid" };
3574
4032
  }
3575
- const selector = parseObjectSelector(flags);
3576
- if (!selector) {
4033
+ const walletAddress = flags["wallet-address"]?.trim() ?? flags["wallet_address"]?.trim() ?? "";
4034
+ if (!walletAddress) {
3577
4035
  return { mode: "ls-invalid" };
3578
4036
  }
3579
- const request = parseStorageObjectRequestInput(flags, selector);
3580
- if (!request) {
4037
+ const selector = parseLsObjectSelector(flags);
4038
+ if (!selector) {
3581
4039
  return { mode: "ls-invalid" };
3582
4040
  }
3583
- return { mode: "ls", storageObjectRequest: request, nameSelector: selector.nameSelector };
4041
+ const location = flags.location?.trim() || flags.region?.trim() || void 0;
4042
+ if (selector.nameSelector) {
4043
+ return {
4044
+ mode: "ls",
4045
+ storageObjectRequest: { wallet_address: walletAddress, location },
4046
+ nameSelector: selector.nameSelector
4047
+ };
4048
+ }
4049
+ if (selector.objectKey) {
4050
+ return {
4051
+ mode: "ls",
4052
+ storageObjectRequest: {
4053
+ wallet_address: walletAddress,
4054
+ object_key: selector.objectKey,
4055
+ location
4056
+ }
4057
+ };
4058
+ }
4059
+ return {
4060
+ mode: "ls",
4061
+ storageObjectRequest: { wallet_address: walletAddress, location }
4062
+ };
3584
4063
  }
3585
4064
  if (subcommand === "download") {
3586
4065
  const flags = parseNamedFlags(rest, BOOLEAN_SELECTOR_AND_ASYNC_FLAGS);
@@ -4217,16 +4696,28 @@ function extractUploadErrorMessage(error) {
4217
4696
  }
4218
4697
  return message;
4219
4698
  }
4699
+ function extractLsErrorMessage(error) {
4700
+ if (!(error instanceof Error)) {
4701
+ return null;
4702
+ }
4703
+ const message = error.message.trim();
4704
+ if (!message) {
4705
+ return null;
4706
+ }
4707
+ if (message.startsWith("ls response") || message.startsWith("ls list response") || message.startsWith("Invalid ls response payload")) {
4708
+ return `Cannot list storage object: ${message}`;
4709
+ }
4710
+ if (message === "formatBytesForDisplay expects a non-negative integer") {
4711
+ return "Cannot list storage object: ls response has invalid size_bytes; expected non-negative integer";
4712
+ }
4713
+ return null;
4714
+ }
4220
4715
  function formatPriceStorageUserMessage(quote) {
4221
4716
  return [
4222
4717
  `Your storage quote \`${quote.quote_id}\` is valid for 1 hour, the storage price is \`${quote.storage_price}\` for \`${quote.object_id}\` with file size of \`${quote.object_size_gb}\` in \`${quote.provider}\` \`${quote.location}\``,
4223
4718
  `If you accept this quote run the command /mnemospark_cloud upload --quote-id \`${quote.quote_id}\` --wallet-address \`${quote.addr}\` --object-id \`${quote.object_id}\` --object-id-hash \`${quote.object_id_hash}\``
4224
4719
  ].join("\n");
4225
4720
  }
4226
- function formatStorageLsUserMessage(result, requestedObjectKey) {
4227
- const objectId = result.object_id ?? result.key;
4228
- return `${objectId} with ${requestedObjectKey} is ${result.size_bytes} in ${result.bucket}`;
4229
- }
4230
4721
  function createInProcessSubagentOrchestrator() {
4231
4722
  const sessions = /* @__PURE__ */ new Map();
4232
4723
  const completeSession = async (sessionId, handler) => {
@@ -4404,7 +4895,23 @@ async function resolveFriendlyNameFromManifest(params, homeDir) {
4404
4895
  }
4405
4896
  async function resolveNameSelectorIfNeeded(datastore, request, selector, homeDir) {
4406
4897
  if (!selector) {
4407
- const parsedRequest2 = parseStorageObjectRequest(request);
4898
+ const walletAddress = request.wallet_address?.trim();
4899
+ if (!walletAddress) {
4900
+ return { error: "Cannot resolve storage object request." };
4901
+ }
4902
+ const objectKey = request.object_key?.trim();
4903
+ if (!objectKey) {
4904
+ const listRequest = { wallet_address: walletAddress };
4905
+ if (request.location) {
4906
+ listRequest.location = request.location;
4907
+ }
4908
+ return { request: listRequest };
4909
+ }
4910
+ const parsedRequest2 = parseStorageObjectRequest({
4911
+ wallet_address: walletAddress,
4912
+ object_key: objectKey,
4913
+ location: request.location
4914
+ });
4408
4915
  if (!parsedRequest2) {
4409
4916
  return { error: "Cannot resolve storage object request." };
4410
4917
  }
@@ -4465,6 +4972,21 @@ async function resolveNameSelectorIfNeeded(datastore, request, selector, homeDir
4465
4972
  degradedWarning
4466
4973
  };
4467
4974
  }
4975
+ function toStorageObjectRequestOrError(request, missingKeyMessage) {
4976
+ const key = request.object_key?.trim();
4977
+ if (!key) {
4978
+ return { ok: false, error: missingKeyMessage };
4979
+ }
4980
+ const parsed = parseStorageObjectRequest({
4981
+ wallet_address: request.wallet_address,
4982
+ object_key: key,
4983
+ location: request.location
4984
+ });
4985
+ if (!parsed) {
4986
+ return { ok: false, error: "Cannot resolve storage object request." };
4987
+ }
4988
+ return { ok: true, request: parsed };
4989
+ }
4468
4990
  async function emitCloudEvent(eventType, details, homeDir) {
4469
4991
  await appendJsonlEvent(
4470
4992
  "events.jsonl",
@@ -4691,7 +5213,7 @@ async function runCloudCommandHandler(ctx, options, executionContext = {}) {
4691
5213
  }
4692
5214
  if (parsed.mode === "ls-invalid") {
4693
5215
  return {
4694
- text: `Cannot list storage object: required arguments are ${REQUIRED_STORAGE_OBJECT}.`,
5216
+ text: `Cannot list storage object: required arguments are ${REQUIRED_LS}.`,
4695
5217
  isError: true
4696
5218
  };
4697
5219
  }
@@ -5686,15 +6208,17 @@ operation-id: ${operationId}`,
5686
6208
  "name_resolution.degraded",
5687
6209
  {
5688
6210
  wallet_address: resolvedRequest.wallet_address,
5689
- object_key: resolvedRequest.object_key,
6211
+ object_key: resolvedRequest.object_key ?? null,
5690
6212
  warning: resolved.degradedWarning
5691
6213
  },
5692
6214
  objectLogHomeDir
5693
6215
  );
5694
6216
  }
6217
+ const objectKeyForLs = resolvedRequest.object_key?.trim();
6218
+ const isBucketList = !objectKeyForLs;
5695
6219
  const correlation = buildRequestCorrelation();
5696
6220
  const operationId = correlation.operationId;
5697
- const knownObject = await datastore.findObjectByObjectKey(resolvedRequest.object_key);
6221
+ const knownObject = isBucketList ? null : await datastore.findObjectByObjectKey(objectKeyForLs);
5698
6222
  const operationObjectId = knownObject?.object_id ?? null;
5699
6223
  await datastore.upsertOperation({
5700
6224
  operation_id: operationId,
@@ -5728,17 +6252,23 @@ operation-id: ${operationId}`,
5728
6252
  operation_id: operationId,
5729
6253
  trace_id: correlation.traceId,
5730
6254
  wallet_address: resolvedRequest.wallet_address,
5731
- object_key: resolvedRequest.object_key,
5732
- status: "succeeded"
6255
+ object_key: resolvedRequest.object_key ?? null,
6256
+ status: "succeeded",
6257
+ list_mode: isBucketList
5733
6258
  },
5734
6259
  objectLogHomeDir
5735
6260
  );
5736
- const lsText = formatStorageLsUserMessage(lsResult, resolvedRequest.object_key);
6261
+ const lsText = await buildMnemosparkLsMessage(lsResult, {
6262
+ walletAddress: resolvedRequest.wallet_address,
6263
+ datastore
6264
+ });
5737
6265
  return {
5738
6266
  text: resolved.degradedWarning ? `${resolved.degradedWarning}
6267
+
5739
6268
  ${lsText}` : lsText
5740
6269
  };
5741
- } catch {
6270
+ } catch (error) {
6271
+ const lsErrorMessage = extractLsErrorMessage(error) ?? "Cannot list storage object";
5742
6272
  await datastore.upsertOperation({
5743
6273
  operation_id: operationId,
5744
6274
  type: "ls",
@@ -5746,7 +6276,7 @@ ${lsText}` : lsText
5746
6276
  quote_id: null,
5747
6277
  status: "failed",
5748
6278
  error_code: "LS_FAILED",
5749
- error_message: "Cannot list storage object"
6279
+ error_message: lsErrorMessage
5750
6280
  });
5751
6281
  await emitCloudEventBestEffort(
5752
6282
  "ls.completed",
@@ -5754,13 +6284,14 @@ ${lsText}` : lsText
5754
6284
  operation_id: operationId,
5755
6285
  trace_id: correlation.traceId,
5756
6286
  wallet_address: resolvedRequest.wallet_address,
5757
- object_key: resolvedRequest.object_key,
5758
- status: "failed"
6287
+ object_key: resolvedRequest.object_key ?? null,
6288
+ status: "failed",
6289
+ list_mode: isBucketList
5759
6290
  },
5760
6291
  objectLogHomeDir
5761
6292
  );
5762
6293
  return {
5763
- text: "Cannot list storage object",
6294
+ text: lsErrorMessage,
5764
6295
  isError: true
5765
6296
  };
5766
6297
  }
@@ -5775,7 +6306,14 @@ ${lsText}` : lsText
5775
6306
  if (resolved.error || !resolved.request) {
5776
6307
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
5777
6308
  }
5778
- const resolvedRequest = resolved.request;
6309
+ const narrowed = toStorageObjectRequestOrError(
6310
+ resolved.request,
6311
+ `Cannot download file: required arguments are ${REQUIRED_STORAGE_OBJECT}.`
6312
+ );
6313
+ if (!narrowed.ok) {
6314
+ return { text: narrowed.error, isError: true };
6315
+ }
6316
+ const resolvedRequest = narrowed.request;
5779
6317
  if (resolved.degradedWarning) {
5780
6318
  await emitCloudEventBestEffort(
5781
6319
  "name_resolution.degraded",
@@ -5873,7 +6411,14 @@ ${downloadText}` : downloadText
5873
6411
  if (resolved.error || !resolved.request) {
5874
6412
  return { text: resolved.error ?? "Cannot resolve storage object request.", isError: true };
5875
6413
  }
5876
- const resolvedRequest = resolved.request;
6414
+ const narrowedDelete = toStorageObjectRequestOrError(
6415
+ resolved.request,
6416
+ `Cannot delete file: required arguments are ${REQUIRED_STORAGE_OBJECT}.`
6417
+ );
6418
+ if (!narrowedDelete.ok) {
6419
+ return { text: narrowedDelete.error, isError: true };
6420
+ }
6421
+ const resolvedRequest = narrowedDelete.request;
5877
6422
  if (resolved.degradedWarning) {
5878
6423
  await emitCloudEventBestEffort(
5879
6424
  "name_resolution.degraded",