@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 +39 -0
- package/README.md +56 -3
- package/dist/index.js +91 -15
- package/package.json +2 -2
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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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",
|