@arcadiasystems/morse-cli 0.6.0 → 0.6.1

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,27 @@ 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.6.1] - 2026-09-09
8
+
9
+ Picks up `@arcadiasystems/morse-sdk@0.6.0` and fixes a download regression it exposed. Upgrade if you use mainnet at all.
10
+
11
+ ### Fixed
12
+
13
+ - **`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.
14
+ - **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.
15
+ - **`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`.
16
+ - **`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.
17
+
18
+ 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.
19
+
20
+ 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.
21
+ - 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.
22
+
23
+ ### Known gaps
24
+
25
+ - `file list` still needs `--indexer-url` on every network; public Sui fullnodes have retired the JSON-RPC event query it walks.
26
+ - Encrypted mainnet stays blocked until Seal operator credentials are available and the CLI grows a way to pass them.
27
+
7
28
  ## [0.6.0] - 2026-09-09
8
29
 
9
30
  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.6.1. 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
@@ -327,6 +339,9 @@ to fetch the full record per file (one read each) when you need them.
327
339
  created but not yet populated through the CLI; `entry add` refuses them.
328
340
  - There is no command to query Walrus blob storage expiry; `--epochs` sets the
329
341
  lease at upload time, but remaining lease time is not exposed by the SDK.
342
+ - Encrypted commands do not work on mainnet, per the status note above. There
343
+ is no way to supply Seal key servers to the CLI; if you have credentials from
344
+ an operator, use the SDK directly for now.
330
345
  - `localnet` is accepted as a network but has no canonical deployment, so it
331
346
  fails at startup unless you point the SDK at your own package and registry.
332
347
  - `file list` walks a JSON-RPC event query that public Sui fullnodes have
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.6.1",
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",
@@ -1000,10 +1000,15 @@ async function buildContentContext(command) {
1000
1000
  walrus
1001
1001
  };
1002
1002
  }
1003
+ function assertSealAvailable(config) {
1004
+ if (config.sealKeyServers.length > 0) {
1005
+ return;
1006
+ }
1007
+ 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);
1008
+ }
1003
1009
  async function buildEncryptContext(command) {
1004
1010
  const ctx = await buildContentContext(command);
1005
- const seal = DefaultSealAdapter.fromMorseConfig(ctx.config, {}, ctx.client);
1006
- return { ...ctx, seal };
1011
+ return { ...ctx, seal: lazySeal(ctx) };
1007
1012
  }
1008
1013
  function walrusReadAdapter(base, network, viaAggregator) {
1009
1014
  if (viaAggregator) {
@@ -1031,6 +1036,7 @@ async function buildDecryptContext(command, opts = {}) {
1031
1036
  if (network === "localnet") {
1032
1037
  throw new CliError("Seal decryption is not available on localnet. Use testnet or mainnet.", ExitCode.Usage);
1033
1038
  }
1039
+ assertSealAvailable(base.config);
1034
1040
  const seal = DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client);
1035
1041
  return {
1036
1042
  ...base,
@@ -1052,10 +1058,18 @@ async function buildFileDownloadContext(command, opts = {}) {
1052
1058
  packageId: base.config.packageId
1053
1059
  }),
1054
1060
  walrusRead: walrusReadAdapter(base, network, Boolean(opts.viaAggregator)),
1055
- seal: DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client),
1061
+ seal: lazySeal(base),
1056
1062
  unlockSigner: () => resolveSigner(base.settings.account, process.env, base.signal)
1057
1063
  };
1058
1064
  }
1065
+ function lazySeal(base) {
1066
+ let adapter;
1067
+ return () => {
1068
+ assertSealAvailable(base.config);
1069
+ adapter ??= DefaultSealAdapter.fromMorseConfig(base.config, {}, base.client);
1070
+ return adapter;
1071
+ };
1072
+ }
1059
1073
  async function buildFileListContext(command, opts = {}) {
1060
1074
  const base = await buildReadContext(command);
1061
1075
  const originPackageId = base.config.recipientFileEventOriginPackageId;
@@ -1681,7 +1695,7 @@ async function runEntryAddEncrypted(ctx, name, options) {
1681
1695
  ctx.output.info(`Encrypting and uploading ${plaintext.length} bytes...`);
1682
1696
  const result = await addEncryptedEntryFromBytes(ctx.adapter, ctx.config, {
1683
1697
  walrus: ctx.walrus,
1684
- seal: ctx.seal,
1698
+ seal: ctx.seal(),
1685
1699
  publicationId: id,
1686
1700
  publisherCapId,
1687
1701
  collectionName: collection,
@@ -2168,7 +2182,7 @@ async function runFileUpload(ctx, path, options) {
2168
2182
  }
2169
2183
  const result = await uploadEncryptedRecipientFileFromBytes(ctx.adapter, ctx.config, {
2170
2184
  walrus: ctx.walrus,
2171
- seal: ctx.seal,
2185
+ seal: ctx.seal(),
2172
2186
  plaintext: bytes,
2173
2187
  recipients,
2174
2188
  name: options.name,
@@ -2238,7 +2252,7 @@ async function decryptRecipientFile(ctx, fileId, ciphertext, prefix, nonce) {
2238
2252
  signer: keypair,
2239
2253
  suiClient: ctx.client
2240
2254
  });
2241
- return ctx.seal.decryptUnderRecipientFile(ciphertext, {
2255
+ return ctx.seal().decryptUnderRecipientFile(ciphertext, {
2242
2256
  sessionKey,
2243
2257
  sealId,
2244
2258
  fileId
@@ -2254,9 +2268,10 @@ async function runFileDownload(ctx, positional, options) {
2254
2268
  }
2255
2269
  const fileId = toRecipientFileId(target.fileId);
2256
2270
  const file = await ctx.filesReader.getRecipientFile(fileId, ctx.signal);
2257
- if (target.decrypt === undefined && file.members.length > 0) {
2271
+ const encrypted = target.decrypt !== undefined || await ctx.filesReader.getRecipientFileSealPrefix(fileId, ctx.signal) !== null;
2272
+ if (target.decrypt === undefined && encrypted) {
2258
2273
  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.");
2274
+ throw new UsageError("This file is encrypted. Pass --share (or --prefix and --nonce) to decrypt, or --raw to write the raw ciphertext anyway.");
2260
2275
  }
2261
2276
  ctx.output.warn("Writing raw ciphertext (--raw); the output is unusable without decryption.");
2262
2277
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadiasystems/morse-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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",