@arcadiasystems/morse-cli 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -4,6 +4,45 @@ All notable changes to `@arcadiasystems/morse-cli` are documented here. The
4
4
  format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.0] - 2026-09-09
8
+
9
+ ### Added
10
+
11
+ - **`--upload-relay <url|auto>`** routes Walrus uploads through a relay instead of the direct fanout, with `MORSE_WALRUS_UPLOAD_RELAY` and a per-profile `uploadRelay` following the usual `flags > env > config > defaults` precedence. `auto` resolves to the canonical Mysten relay for the selected network, and is refused on localnet, which has none.
12
+
13
+ A direct upload pushes slivers to every storage node in the committee at once, 95 on mainnet. Networks that cannot sustain that burst fail with `NotEnoughBlobConfirmationsError` even when the nodes are healthy and far above write quorum, and until now the CLI had no way around it while the SDK did: `DefaultWalrusWriteAdapter.fromConfig` already accepted `uploadRelay`, the CLI just never offered a way to set it.
14
+
15
+ Opt-in rather than default: the direct path is free and trustless, the relay costs a tip and sees your bytes.
16
+ - **`--max-tip <mist>`** and `MORSE_WALRUS_MAX_TIP` cap the relay's per-upload tip, defaulting to 10000000 MIST (0.01 SUI). Mainnet prices the tip linearly in encoded blob size, so an uncapped relay upload has no upper bound on what it charges; over the cap `@mysten/walrus` refuses before spending. A non-integer or negative cap is rejected outright rather than falling back to the default, so a typo cannot silently authorise an unbounded tip.
17
+ - `config add` takes `--upload-relay` to persist the choice on a profile. The value is validated at add time the way `--network` already is, so `auto` on a network with no canonical relay is refused immediately rather than producing a profile that only fails the first time someone uploads. `config list` shows it in both the human and `--json` renderings.
18
+
19
+ ### Notes
20
+
21
+ - The relay setting and its tip cap are carried raw through `resolveSettings` and only resolved on the Walrus write path. `resolveSettings` runs for every command, so parsing them eagerly would let a typo'd `MORSE_WALRUS_MAX_TIP` in a shell profile or CI environment break reads that never upload anything. A malformed cap fails the upload, not `publication get`.
22
+
23
+ Verified live: uploads through the relay succeed on both networks and round-trip byte for byte, a too-low `--max-tip` refuses before spending, and a malformed one is rejected at parse time.
24
+
25
+ ## [0.6.1] - 2026-09-09
26
+
27
+ Picks up `@arcadiasystems/morse-sdk@0.6.0` and fixes a download regression it exposed. Upgrade if you use mainnet at all.
28
+
29
+ ### Fixed
30
+
31
+ - **`file download` worked on no mainnet file, public or not.** `buildFileDownloadContext` constructed the Seal adapter eagerly, so once SDK 0.6.0 stopped pinning mainnet key servers, every download failed at context build with a Seal error before the file was even looked up. Downloading a public file never touches Seal, so the adapter is now built on first use and memoized, matching the lazy-signer pattern the same context already used. Regression covered in `test/program.test.ts` against the real context builder.
32
+ - **Encrypted commands now explain themselves on mainnet.** They previously surfaced the SDK's `ConfigurationError`, whose advice ("pass seal.serverConfigs", "start from MAINNET_SEAL_COMMITTEE") names an API the CLI does not expose: there is no flag or env var for key servers, so on mainnet these commands cannot work at all. `entry add-encrypted`, `entry decrypt`, `file upload --encrypt` / `--recipient`, and file decryption now exit 2 with a message that says that and points at testnet or the SDK.
33
+ - **`file upload --public` is not caught by that guard.** It shares the encrypt context but never encrypts, so `EncryptContext.seal` became a lazy factory too. Building the adapter eagerly there would have blocked public mainnet uploads the same way the eager build blocked public downloads. Both the refusal and the exemption are covered in `test/program.test.ts`.
34
+ - **`file download` refused every public file, on every network.** The check asked whether the file had recipients, on the assumption that "a public file has no recipients". The Move contract auto-includes the owner on creation, so `members` is non-empty for every file that exists, public or not: a public file and an encrypted one both report exactly one member. The result was that `file upload --public` produced a file the CLI would not download, telling the user their plaintext was "unusable ciphertext" and pointing at `--raw`, which then wrote the correct bytes anyway.
35
+
36
+ Encryption is now determined by the Seal id prefix via the new `getRecipientFileSealPrefix` in SDK 0.7.0, which is the only reliable signal. This bug predates the mainnet work and shipped in 0.6.0 and earlier.
37
+
38
+ It escaped the tests because the download fixture defaulted to `members: []`, modelling an object the contract cannot produce. The fixture now carries a realistic owner-member, and a regression test covers a public file that has members.
39
+ - Dependency moves to `@arcadiasystems/morse-sdk@^0.7.0`. The published 0.6.0 declared `^0.5.0`, which resolves to the SDK release whose mainnet Seal default could produce permanently unreadable ciphertext.
40
+
41
+ ### Known gaps
42
+
43
+ - `file list` still needs `--indexer-url` on every network; public Sui fullnodes have retired the JSON-RPC event query it walks.
44
+ - Encrypted mainnet stays blocked until Seal operator credentials are available and the CLI grows a way to pass them.
45
+
7
46
  ## [0.6.0] - 2026-09-09
8
47
 
9
48
  Mainnet support, consuming `@arcadiasystems/morse-sdk@0.5.0`. The Move contracts
package/README.md CHANGED
@@ -6,8 +6,20 @@ content entries from your terminal, signing with a locally encrypted key.
6
6
  Content is stored on [Walrus](https://walrus.xyz); private entries are encrypted
7
7
  with [Seal](https://github.com/MystenLabs/seal).
8
8
 
9
- > Status: v0.6.0. Mainnet and testnet are both supported; the command surface is
10
- > stable.
9
+ > Status: v0.7.0. Mainnet and testnet are both supported for public content;
10
+ > the command surface is stable.
11
+ >
12
+ > **Encrypted commands are testnet-only.** `entry add-encrypted`, `entry
13
+ > decrypt`, `file upload --encrypt` (and `--recipient`, which implies it), and
14
+ > decrypting a downloaded file all need Seal key servers. Testnet has an open
15
+ > set; mainnet does not, because every mainnet Seal operator is commercial, and
16
+ > the CLI has no flag or env var for supplying one. These commands exit 2 on
17
+ > mainnet with an explanation.
18
+ >
19
+ > Everything that does not encrypt works on both networks, including
20
+ > `file upload --public`, `file download` of a public file, and the whole
21
+ > publication, collection and entry surface. All of that is verified against
22
+ > live mainnet, including a public upload and download round trip.
11
23
  >
12
24
  > **The Move contracts are unaudited.** On mainnet, gas and Walrus storage cost
13
25
  > real SUI and WAL, there is no faucet, and deletions are irreversible. Nothing
@@ -100,6 +112,8 @@ Environment variables (override the config file, overridden by flags):
100
112
  | --- | --- | --- |
101
113
  | `MORSE_PROFILE` | `--profile` | Profile to use. |
102
114
  | `MORSE_NETWORK` | `--network` | `mainnet`, `testnet`, or `localnet`. |
115
+ | `MORSE_WALRUS_UPLOAD_RELAY` | `--upload-relay` | Upload through a Walrus relay instead of the direct fanout. `auto` picks the canonical relay for the network. |
116
+ | `MORSE_WALRUS_MAX_TIP` | `--max-tip` | Cap the relay's per-upload tip, in MIST. Defaults to 10000000 (0.01 SUI). |
103
117
  | `MORSE_RPC_URL` | `--rpc` | Sui RPC URL override. |
104
118
  | `MORSE_ADDRESS` | (no flag) | Active account address, selecting which keystore to use. |
105
119
  | `MORSE_PUBLICATION` | `-P, --publication` | Active publication id. |
@@ -134,6 +148,8 @@ Global options apply to every command and must appear before the subcommand
134
148
  | `--network <mainnet\|testnet\|localnet>` | Network to target (default: testnet). `localnet` needs a custom deployment. |
135
149
  | `-p, --profile <name>` | Config profile to use. |
136
150
  | `--rpc <url>` | Override the Sui RPC URL. |
151
+ | `--upload-relay <url\|auto>` | Upload Walrus blobs through a relay instead of the direct fanout. `auto` picks the canonical relay for the network. Costs a tip per upload. |
152
+ | `--max-tip <mist>` | Cap the relay's per-upload tip, in MIST (default: 10000000). |
137
153
  | `--json` | Machine-readable JSON on stdout. |
138
154
  | `-q, --quiet` | Suppress progress and informational output. |
139
155
  | `-y, --yes` | Assume yes for confirmation prompts. |
@@ -156,7 +172,7 @@ and `-C, --collection <name>`, both defaulting to the active context.
156
172
 
157
173
  | Command | Purpose |
158
174
  | --- | --- |
159
- | `config add <name> --network <net> [--rpc <url>]` | Create or update a profile. |
175
+ | `config add <name> --network <net> [--rpc <url>] [--upload-relay <url\|auto>]` | Create or update a profile. |
160
176
  | `config list` | List profiles; `*` marks the default. |
161
177
  | `config use <name>` | Set the default profile. |
162
178
  | `config remove <name>` | Delete a profile. |
@@ -327,12 +343,49 @@ to fetch the full record per file (one read each) when you need them.
327
343
  created but not yet populated through the CLI; `entry add` refuses them.
328
344
  - There is no command to query Walrus blob storage expiry; `--epochs` sets the
329
345
  lease at upload time, but remaining lease time is not exposed by the SDK.
346
+ - Encrypted commands do not work on mainnet, per the status note above. There
347
+ is no way to supply Seal key servers to the CLI; if you have credentials from
348
+ an operator, use the SDK directly for now.
330
349
  - `localnet` is accepted as a network but has no canonical deployment, so it
331
350
  fails at startup unless you point the SDK at your own package and registry.
332
351
  - `file list` walks a JSON-RPC event query that public Sui fullnodes have
333
352
  retired, so it needs `--indexer-url` pointing at a source that serves the same
334
353
  query. This affects testnet as well as mainnet.
335
354
 
355
+ ## When uploads fail: the upload relay
356
+
357
+ A Walrus upload pushes slivers to every storage node in the committee at once,
358
+ 95 of them on mainnet. Networks that cannot sustain that burst fail with
359
+ `NotEnoughBlobConfirmationsError` or "Unable to connect", **even when the nodes
360
+ are healthy and far above write quorum**. If uploads fail while reads work, this
361
+ is almost certainly why, and it is not a fault in your config.
362
+
363
+ Route the upload through a relay and one connection replaces ~95:
364
+
365
+ ```sh
366
+ morse --upload-relay auto file upload ./photo.jpg --public -n photo
367
+ ```
368
+
369
+ `auto` resolves to Mysten's canonical relay for the selected network. Pass a URL
370
+ instead to use a different operator, or persist it on a profile:
371
+
372
+ ```sh
373
+ morse config add mainnet --network mainnet --upload-relay auto
374
+ ```
375
+
376
+ Three things to know before reaching for it:
377
+
378
+ - **It costs a tip per upload**, on top of WAL and gas. Mainnet prices it
379
+ linearly in encoded blob size; a small blob runs about 0.0026 SUI.
380
+ - **The tip is capped** at `--max-tip` (default 10000000 MIST, 0.01 SUI). Over
381
+ the cap the upload is refused before anything is spent, rather than quietly
382
+ charging more than you expected.
383
+ - **The relay sees your bytes.** Same trade as any hosted service; encrypt first
384
+ if that matters.
385
+
386
+ It is opt-in on purpose. The direct path is free and trustless, and most
387
+ networks handle it fine.
388
+
336
389
  ## Publishing
337
390
 
338
391
  Published to the public npm registry as the scoped package
package/dist/index.js CHANGED
@@ -148,7 +148,7 @@ import { Command } from "commander";
148
148
  // package.json
149
149
  var package_default = {
150
150
  name: "@arcadiasystems/morse-cli",
151
- version: "0.6.0",
151
+ version: "0.7.0",
152
152
  description: "Command-line interface for the Morse decentralized CMS on Sui.",
153
153
  license: "MIT",
154
154
  type: "module",
@@ -203,7 +203,7 @@ var package_default = {
203
203
  prepublishOnly: "bun run check && bun run build"
204
204
  },
205
205
  dependencies: {
206
- "@arcadiasystems/morse-sdk": "^0.5.0",
206
+ "@arcadiasystems/morse-sdk": "^0.7.0",
207
207
  "@mysten/seal": "1.1.3",
208
208
  "@mysten/sui": "2.16.2",
209
209
  "@mysten/walrus": "1.1.6",
@@ -217,9 +217,10 @@ var package_default = {
217
217
  };
218
218
 
219
219
  // src/cli/program.ts
220
+ var DEFAULT_MAX_TIP_MIST = 1e7;
220
221
  function buildProgram() {
221
222
  const program = new Command;
222
- program.name("morse").description("Command-line interface for the Morse decentralized CMS on Sui.").version(package_default.version, "-V, --version", "Print the version and exit").option("--network <network>", "Sui network: mainnet, testnet, or localnet [env: MORSE_NETWORK]").option("-p, --profile <name>", "Config profile to use [env: MORSE_PROFILE]").option("--rpc <url>", "Override the Sui RPC URL [env: MORSE_RPC_URL]").option("--json", "Output machine-readable JSON on stdout").option("-q, --quiet", "Suppress progress and informational output").option("-y, --yes", "Assume yes for confirmation prompts").option("--debug", "Print stack traces on error").enablePositionalOptions().showHelpAfterError().exitOverride();
223
+ program.name("morse").description("Command-line interface for the Morse decentralized CMS on Sui.").version(package_default.version, "-V, --version", "Print the version and exit").option("--network <network>", "Sui network: mainnet, testnet, or localnet [env: MORSE_NETWORK]").option("-p, --profile <name>", "Config profile to use [env: MORSE_PROFILE]").option("--rpc <url>", "Override the Sui RPC URL [env: MORSE_RPC_URL]").option("--upload-relay <url|auto>", "Upload Walrus blobs through a relay instead of fanning out to every storage node; 'auto' picks the canonical relay for the network. Costs a tip per upload [env: MORSE_WALRUS_UPLOAD_RELAY]").option("--max-tip <mist>", `Cap the per-upload relay tip, in MIST (default: ${DEFAULT_MAX_TIP_MIST}) [env: MORSE_WALRUS_MAX_TIP]`).option("--json", "Output machine-readable JSON on stdout").option("-q, --quiet", "Suppress progress and informational output").option("-y, --yes", "Assume yes for confirmation prompts").option("--debug", "Print stack traces on error").enablePositionalOptions().showHelpAfterError().exitOverride();
223
224
  return program;
224
225
  }
225
226
 
@@ -462,6 +463,9 @@ function parseProfile(name, value, source) {
462
463
  if (typeof value.rpc === "string") {
463
464
  profile.rpc = value.rpc;
464
465
  }
466
+ if (typeof value.uploadRelay === "string") {
467
+ profile.uploadRelay = value.uploadRelay;
468
+ }
465
469
  if (typeof value.account === "string") {
466
470
  profile.account = value.account;
467
471
  }
@@ -564,6 +568,35 @@ async function updateActiveProfile(opts, patch, env = process.env) {
564
568
  }
565
569
 
566
570
  // src/config/profile.ts
571
+ function canonicalUploadRelay(network) {
572
+ if (network === "mainnet" || network === "testnet") {
573
+ return `https://upload-relay.${network}.walrus.space`;
574
+ }
575
+ return;
576
+ }
577
+ function resolveUploadRelay(value, network) {
578
+ if (value === undefined || value === "") {
579
+ return;
580
+ }
581
+ if (value !== "auto") {
582
+ return value;
583
+ }
584
+ const canonical = canonicalUploadRelay(network);
585
+ if (canonical === undefined) {
586
+ throw new UsageError(`--upload-relay auto has no canonical relay for ${network}. Pass an explicit relay URL.`);
587
+ }
588
+ return canonical;
589
+ }
590
+ function parseMaxTip(value) {
591
+ if (value === undefined || value === "") {
592
+ return DEFAULT_MAX_TIP_MIST;
593
+ }
594
+ const parsed = /^\d+$/.test(value) ? Number(value) : Number.NaN;
595
+ if (!Number.isSafeInteger(parsed)) {
596
+ throw new UsageError(`--max-tip must be a non-negative integer in MIST; got "${value}".`);
597
+ }
598
+ return parsed;
599
+ }
567
600
  function resolveSettings(opts, config, env = process.env) {
568
601
  const explicitProfile = opts.profile ?? env.MORSE_PROFILE;
569
602
  const profileName = explicitProfile ?? config.defaultProfile;
@@ -574,10 +607,21 @@ function resolveSettings(opts, config, env = process.env) {
574
607
  const networkValue = opts.network ?? env.MORSE_NETWORK ?? profile?.network ?? "testnet";
575
608
  const network = coerceNetwork(networkValue);
576
609
  const rpcUrl = opts.rpc ?? env.MORSE_RPC_URL ?? profile?.rpc;
610
+ const uploadRelay = opts.uploadRelay ?? env.MORSE_WALRUS_UPLOAD_RELAY ?? profile?.uploadRelay;
611
+ const maxTip = opts.maxTip ?? env.MORSE_WALRUS_MAX_TIP;
577
612
  const account = env.MORSE_ADDRESS ?? profile?.account;
578
613
  const publication = env.MORSE_PUBLICATION ?? profile?.publication;
579
614
  const collection = env.MORSE_COLLECTION ?? profile?.collection;
580
- return { profileName, network, rpcUrl, account, publication, collection };
615
+ return {
616
+ profileName,
617
+ network,
618
+ rpcUrl,
619
+ uploadRelay,
620
+ maxTip,
621
+ account,
622
+ publication,
623
+ collection
624
+ };
581
625
  }
582
626
 
583
627
  // src/keystore/keystore.ts
@@ -991,7 +1035,7 @@ async function buildContentContext(command) {
991
1035
  throw new CliError("Walrus content uploads are not available on localnet. Use testnet or mainnet.", ExitCode.Usage);
992
1036
  }
993
1037
  const adapter = new KeypairAdapter(keypair, base.client);
994
- const walrus = DefaultWalrusWriteAdapter.fromConfig({ network, suiClient: base.client }, keypair);
1038
+ const walrus = DefaultWalrusWriteAdapter.fromConfig(walrusWriteConfig(base, network), keypair);
995
1039
  return {
996
1040
  ...base,
997
1041
  output: base.output.withNetwork(network),
@@ -1000,10 +1044,26 @@ async function buildContentContext(command) {
1000
1044
  walrus
1001
1045
  };
1002
1046
  }
1047
+ function assertSealAvailable(config) {
1048
+ if (config.sealKeyServers.length > 0) {
1049
+ return;
1050
+ }
1051
+ throw new CliError(`Encrypted commands are not available on ${config.network}. Seal key servers there are operated commercially and the CLI cannot yet be pointed at one. Use --network testnet, or drive @arcadiasystems/morse-sdk directly with your own seal.serverConfigs.`, ExitCode.Usage);
1052
+ }
1053
+ function walrusWriteConfig(base, network) {
1054
+ const host = resolveUploadRelay(base.settings.uploadRelay, network);
1055
+ if (host === undefined) {
1056
+ return { network, suiClient: base.client };
1057
+ }
1058
+ return {
1059
+ network,
1060
+ suiClient: base.client,
1061
+ uploadRelay: { host, sendTip: { max: parseMaxTip(base.settings.maxTip) } }
1062
+ };
1063
+ }
1003
1064
  async function buildEncryptContext(command) {
1004
1065
  const ctx = await buildContentContext(command);
1005
- const seal = DefaultSealAdapter.fromMorseConfig(ctx.config, {}, ctx.client);
1006
- return { ...ctx, seal };
1066
+ return { ...ctx, seal: lazySeal(ctx) };
1007
1067
  }
1008
1068
  function walrusReadAdapter(base, network, viaAggregator) {
1009
1069
  if (viaAggregator) {
@@ -1031,6 +1091,7 @@ async function buildDecryptContext(command, opts = {}) {
1031
1091
  if (network === "localnet") {
1032
1092
  throw new CliError("Seal decryption is not available on localnet. Use testnet or mainnet.", ExitCode.Usage);
1033
1093
  }
1094
+ assertSealAvailable(base.config);
1034
1095
  const seal = DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client);
1035
1096
  return {
1036
1097
  ...base,
@@ -1052,10 +1113,18 @@ async function buildFileDownloadContext(command, opts = {}) {
1052
1113
  packageId: base.config.packageId
1053
1114
  }),
1054
1115
  walrusRead: walrusReadAdapter(base, network, Boolean(opts.viaAggregator)),
1055
- seal: DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client),
1116
+ seal: lazySeal(base),
1056
1117
  unlockSigner: () => resolveSigner(base.settings.account, process.env, base.signal)
1057
1118
  };
1058
1119
  }
1120
+ function lazySeal(base) {
1121
+ let adapter;
1122
+ return () => {
1123
+ assertSealAvailable(base.config);
1124
+ adapter ??= DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client);
1125
+ return adapter;
1126
+ };
1127
+ }
1059
1128
  async function buildFileListContext(command, opts = {}) {
1060
1129
  const base = await buildReadContext(command);
1061
1130
  const originPackageId = base.config.recipientFileEventOriginPackageId;
@@ -1525,12 +1594,14 @@ async function runConfigList(output) {
1525
1594
  }
1526
1595
  async function runConfigAdd(output, name, options) {
1527
1596
  const network = coerceNetwork(options.network);
1597
+ resolveUploadRelay(options.uploadRelay, network);
1528
1598
  const cfg = await loadConfig();
1529
1599
  const profiles = {
1530
1600
  ...cfg.profiles,
1531
1601
  [name]: {
1532
1602
  network,
1533
- ...options.rpc === undefined ? {} : { rpc: options.rpc }
1603
+ ...options.rpc === undefined ? {} : { rpc: options.rpc },
1604
+ ...options.uploadRelay === undefined ? {} : { uploadRelay: options.uploadRelay }
1534
1605
  }
1535
1606
  };
1536
1607
  const defaultProfile = Object.keys(cfg.profiles).length === 0 ? name : cfg.defaultProfile;
@@ -1539,6 +1610,7 @@ async function runConfigAdd(output, name, options) {
1539
1610
  profile: name,
1540
1611
  network,
1541
1612
  rpc: options.rpc,
1613
+ uploadRelay: options.uploadRelay,
1542
1614
  default: defaultProfile === name
1543
1615
  });
1544
1616
  }
@@ -1568,7 +1640,7 @@ function registerConfigCommands(program) {
1568
1640
  config.command("list").description("List profiles and show the default").action(async (_options, command) => {
1569
1641
  await runConfigList(outputFor(command));
1570
1642
  });
1571
- config.command("add <name>").description("Create or update a profile").requiredOption("--network <network>", "Sui network: mainnet, testnet, or localnet").option("--rpc <url>", "RPC URL override for this profile").action(async (name, options, command) => {
1643
+ config.command("add <name>").description("Create or update a profile").requiredOption("--network <network>", "Sui network: mainnet, testnet, or localnet").option("--rpc <url>", "RPC URL override for this profile").option("--upload-relay <url|auto>", "Walrus upload relay for this profile; 'auto' picks the canonical relay for the network").action(async (name, options, command) => {
1572
1644
  await runConfigAdd(outputFor(command), name, options);
1573
1645
  });
1574
1646
  config.command("use <name>").description("Set the default profile").action(async (name, _options, command) => {
@@ -1598,6 +1670,9 @@ function renderProfiles(config) {
1598
1670
  if (profile.rpc !== undefined) {
1599
1671
  parts.push(profile.rpc);
1600
1672
  }
1673
+ if (profile.uploadRelay !== undefined) {
1674
+ parts.push(`relay=${profile.uploadRelay}`);
1675
+ }
1601
1676
  if (profile.account !== undefined) {
1602
1677
  parts.push(profile.account);
1603
1678
  }
@@ -1681,7 +1756,7 @@ async function runEntryAddEncrypted(ctx, name, options) {
1681
1756
  ctx.output.info(`Encrypting and uploading ${plaintext.length} bytes...`);
1682
1757
  const result = await addEncryptedEntryFromBytes(ctx.adapter, ctx.config, {
1683
1758
  walrus: ctx.walrus,
1684
- seal: ctx.seal,
1759
+ seal: ctx.seal(),
1685
1760
  publicationId: id,
1686
1761
  publisherCapId,
1687
1762
  collectionName: collection,
@@ -2168,7 +2243,7 @@ async function runFileUpload(ctx, path, options) {
2168
2243
  }
2169
2244
  const result = await uploadEncryptedRecipientFileFromBytes(ctx.adapter, ctx.config, {
2170
2245
  walrus: ctx.walrus,
2171
- seal: ctx.seal,
2246
+ seal: ctx.seal(),
2172
2247
  plaintext: bytes,
2173
2248
  recipients,
2174
2249
  name: options.name,
@@ -2238,7 +2313,7 @@ async function decryptRecipientFile(ctx, fileId, ciphertext, prefix, nonce) {
2238
2313
  signer: keypair,
2239
2314
  suiClient: ctx.client
2240
2315
  });
2241
- return ctx.seal.decryptUnderRecipientFile(ciphertext, {
2316
+ return ctx.seal().decryptUnderRecipientFile(ciphertext, {
2242
2317
  sessionKey,
2243
2318
  sealId,
2244
2319
  fileId
@@ -2254,9 +2329,10 @@ async function runFileDownload(ctx, positional, options) {
2254
2329
  }
2255
2330
  const fileId = toRecipientFileId(target.fileId);
2256
2331
  const file = await ctx.filesReader.getRecipientFile(fileId, ctx.signal);
2257
- if (target.decrypt === undefined && file.members.length > 0) {
2332
+ const encrypted = target.decrypt !== undefined || await ctx.filesReader.getRecipientFileSealPrefix(fileId, ctx.signal) !== null;
2333
+ if (target.decrypt === undefined && encrypted) {
2258
2334
  if (!options.raw) {
2259
- throw new UsageError("This file has a recipient list, so its bytes are encrypted. Pass --share (or --prefix and --nonce) to decrypt, or --raw to write the raw ciphertext anyway.");
2335
+ throw new UsageError("This file is encrypted. Pass --share (or --prefix and --nonce) to decrypt, or --raw to write the raw ciphertext anyway.");
2260
2336
  }
2261
2337
  ctx.output.warn("Writing raw ciphertext (--raw); the output is unusable without decryption.");
2262
2338
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadiasystems/morse-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Command-line interface for the Morse decentralized CMS on Sui.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,7 +55,7 @@
55
55
  "prepublishOnly": "bun run check && bun run build"
56
56
  },
57
57
  "dependencies": {
58
- "@arcadiasystems/morse-sdk": "^0.5.0",
58
+ "@arcadiasystems/morse-sdk": "^0.7.0",
59
59
  "@mysten/seal": "1.1.3",
60
60
  "@mysten/sui": "2.16.2",
61
61
  "@mysten/walrus": "1.1.6",