@arcadiasystems/morse-cli 0.5.0 → 0.6.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,51 @@ 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.0] - 2026-09-09
8
+
9
+ Mainnet support, consuming `@arcadiasystems/morse-sdk@0.5.0`. The Move contracts
10
+ went live on Sui mainnet on 2026-09-09; the CLI rejected `mainnet` outright until
11
+ now.
12
+
13
+ ### Added
14
+
15
+ - **`--network mainnet` works**, as do `MORSE_NETWORK=mainnet` and
16
+ `morse config add <name> --network mainnet`. `coerceNetwork` no longer
17
+ special-cases mainnet, and `localnet` is accepted too (it still fails at
18
+ startup unless you point the SDK at your own package and registry, which is
19
+ `morseConfig`'s error to raise, not the flag parser's).
20
+ - **Commands that spend gas now name the network they spent on.** The resolved
21
+ network appears as the first detail line under the result headline, and as a
22
+ `network` field in `--json` output. Applied on the write contexts only, so read
23
+ and list output is byte-for-byte unchanged. Nothing selects mainnet implicitly
24
+ (with no flag, env var, or profile the CLI targets testnet), so this is there
25
+ to make a mainnet spend visible after the fact rather than to gate it.
26
+
27
+ ### Fixed
28
+
29
+ - Status banner claimed v0.3.0, two releases stale, and said mainnet support was
30
+ pending.
31
+ - `--network` help text listed only testnet and localnet, in both the global
32
+ flag (`morse --help`) and `config add --network`, as did the `MORSE_NETWORK`
33
+ row of the README env table.
34
+ - Seven README links pointed into `examples/`, which is not in the package
35
+ `files` array, so they 404'd on the npm page. Now absolute GitHub URLs;
36
+ `docs/QUICKSTART.md` stays relative because `docs` does ship.
37
+ - Dead Walrus faucet link in `docs/QUICKSTART.md`. `docs.walrus.site` no longer
38
+ resolves; WAL acquisition now points at the working stake-wal.wal.app swap,
39
+ matching the README and morse-sdk.
40
+ - Known limitations now record that `file list` requires `--indexer-url`, since
41
+ public Sui fullnodes have retired the JSON-RPC event query it walks. This
42
+ affects testnet as well as mainnet and predates this release.
43
+
44
+ ### Known gaps
45
+
46
+ - `file list` is unusable without `--indexer-url` on every network, per above.
47
+ Porting the event source off `suix_queryEvents` is tracked separately.
48
+ - No mainnet run of the opt-in `bun run test:e2e` lifecycle; it stays pointed at
49
+ testnet, where it costs nothing real.
50
+ - The Move contracts remain unaudited.
51
+
7
52
  ## [0.5.0] - 2026-06-09
8
53
 
9
54
  Hardening pass from CLI testing: fail fast before paying for Walrus storage,
package/README.md CHANGED
@@ -6,16 +6,24 @@ 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.3.0, targeting Sui testnet. The command surface is stable; mainnet
10
- > support lands when the contracts are frozen.
9
+ > Status: v0.6.0. Mainnet and testnet are both supported; the command surface is
10
+ > stable.
11
+ >
12
+ > **The Move contracts are unaudited.** On mainnet, gas and Walrus storage cost
13
+ > real SUI and WAL, there is no faucet, and deletions are irreversible. Nothing
14
+ > selects mainnet for you: with no `--network`, `MORSE_NETWORK`, or profile
15
+ > setting, the CLI targets testnet. Every command that spends gas prints the
16
+ > network it used, and `--json` output carries a `network` field.
11
17
 
12
18
  ## Requirements
13
19
 
14
20
  - [Node.js](https://nodejs.org) >= 18, or [Bun](https://bun.sh) >= 1.2. The
15
21
  published CLI runs under either.
16
- - A funded Sui testnet address for gas, and testnet WAL for Walrus storage when
17
- adding content. Get SUI from the [Sui faucet](https://faucet.sui.io/) and WAL
18
- from the [Walrus testnet faucet](https://docs.walrus.site/usage/web-tool.html).
22
+ - A funded Sui address for gas, and WAL for Walrus storage when adding content.
23
+ On testnet, get SUI from the [Sui faucet](https://faucet.sui.io/) and swap some
24
+ for WAL at [stake-wal.wal.app](https://stake-wal.wal.app/?network=testnet). On
25
+ mainnet there is no faucet: both have to be acquired, and every write spends
26
+ real value.
19
27
 
20
28
  ## Install
21
29
 
@@ -91,7 +99,7 @@ Environment variables (override the config file, overridden by flags):
91
99
  | Variable | Overrides | Notes |
92
100
  | --- | --- | --- |
93
101
  | `MORSE_PROFILE` | `--profile` | Profile to use. |
94
- | `MORSE_NETWORK` | `--network` | `testnet` or `localnet`. |
102
+ | `MORSE_NETWORK` | `--network` | `mainnet`, `testnet`, or `localnet`. |
95
103
  | `MORSE_RPC_URL` | `--rpc` | Sui RPC URL override. |
96
104
  | `MORSE_ADDRESS` | (no flag) | Active account address, selecting which keystore to use. |
97
105
  | `MORSE_PUBLICATION` | `-P, --publication` | Active publication id. |
@@ -123,7 +131,7 @@ Global options apply to every command and must appear before the subcommand
123
131
 
124
132
  | Global option | Purpose |
125
133
  | --- | --- |
126
- | `--network <testnet\|localnet>` | Network to target (default: testnet). |
134
+ | `--network <mainnet\|testnet\|localnet>` | Network to target (default: testnet). `localnet` needs a custom deployment. |
127
135
  | `-p, --profile <name>` | Config profile to use. |
128
136
  | `--rpc <url>` | Override the Sui RPC URL. |
129
137
  | `--json` | Machine-readable JSON on stdout. |
@@ -290,13 +298,13 @@ to fetch the full record per file (one read each) when you need them.
290
298
  ## Examples
291
299
 
292
300
  - [docs/QUICKSTART.md](./docs/QUICKSTART.md): a full, copy-pasteable walkthrough.
293
- - Runnable shell recipes in [examples/](./examples/):
294
- - [`lifecycle.sh`](./examples/lifecycle.sh): create, add an entry, read, revise, tear down.
295
- - [`content.sh`](./examples/content.sh): upload an image and a post, publish a revision, fetch content back, get a link, remove a collection.
296
- - [`encrypt-decrypt.sh`](./examples/encrypt-decrypt.sh): encrypt with Seal and decrypt back.
297
- - [`delegation.sh`](./examples/delegation.sh): issue a PublisherCap to a delegate, then revoke it.
298
- - [`ci-noninteractive.sh`](./examples/ci-noninteractive.sh): env-var auth, `--yes`, and `--json` parsing.
299
- - [`files.sh`](./examples/files.sh): RecipientFile round-trip (upload an encrypted file, download/decrypt via its share string, manage recipients, plus a public file).
301
+ - Runnable shell recipes in [examples/](https://github.com/arcadiasystems/morse-dcms/tree/main/morse-cli/examples):
302
+ - [`lifecycle.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/lifecycle.sh): create, add an entry, read, revise, tear down.
303
+ - [`content.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/content.sh): upload an image and a post, publish a revision, fetch content back, get a link, remove a collection.
304
+ - [`encrypt-decrypt.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/encrypt-decrypt.sh): encrypt with Seal and decrypt back.
305
+ - [`delegation.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/delegation.sh): issue a PublisherCap to a delegate, then revoke it.
306
+ - [`ci-noninteractive.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/ci-noninteractive.sh): env-var auth, `--yes`, and `--json` parsing.
307
+ - [`files.sh`](https://github.com/arcadiasystems/morse-dcms/blob/main/morse-cli/examples/files.sh): RecipientFile round-trip (upload an encrypted file, download/decrypt via its share string, manage recipients, plus a public file).
300
308
 
301
309
  ## Limitations
302
310
 
@@ -319,7 +327,11 @@ to fetch the full record per file (one read each) when you need them.
319
327
  created but not yet populated through the CLI; `entry add` refuses them.
320
328
  - There is no command to query Walrus blob storage expiry; `--epochs` sets the
321
329
  lease at upload time, but remaining lease time is not exposed by the SDK.
322
- - Mainnet is not yet deployed; use `testnet`.
330
+ - `localnet` is accepted as a network but has no canonical deployment, so it
331
+ fails at startup unless you point the SDK at your own package and registry.
332
+ - `file list` walks a JSON-RPC event query that public Sui fullnodes have
333
+ retired, so it needs `--indexer-url` pointing at a source that serves the same
334
+ query. This affects testnet as well as mainnet.
323
335
 
324
336
  ## Publishing
325
337
 
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.5.0",
151
+ version: "0.6.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.4.2",
206
+ "@arcadiasystems/morse-sdk": "^0.5.0",
207
207
  "@mysten/seal": "1.1.3",
208
208
  "@mysten/sui": "2.16.2",
209
209
  "@mysten/walrus": "1.1.6",
@@ -219,7 +219,7 @@ var package_default = {
219
219
  // src/cli/program.ts
220
220
  function buildProgram() {
221
221
  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: 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();
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
223
  return program;
224
224
  }
225
225
 
@@ -316,14 +316,39 @@ class Output {
316
316
  get isJson() {
317
317
  return this.options.json;
318
318
  }
319
+ withNetwork(network) {
320
+ return new Output({ ...this.options, network });
321
+ }
319
322
  result(human, data) {
320
323
  if (this.options.json) {
321
- this.out(`${toJson(data)}
324
+ this.out(`${toJson(this.stampJson(data))}
322
325
  `);
323
326
  return;
324
327
  }
325
- this.out(`${human}
328
+ this.out(`${this.stampHuman(human)}
326
329
  `);
330
+ }
331
+ stampJson(data) {
332
+ const network = this.options.network;
333
+ if (network === undefined || !isPlainObject(data) || "network" in data) {
334
+ return data;
335
+ }
336
+ return { network, ...data };
337
+ }
338
+ stampHuman(human) {
339
+ const network = this.options.network;
340
+ if (network === undefined) {
341
+ return human;
342
+ }
343
+ const line = ` network: ${network}`;
344
+ const breakAt = human.indexOf(`
345
+ `);
346
+ if (breakAt === -1) {
347
+ return `${human}
348
+ ${line}`;
349
+ }
350
+ return `${human.slice(0, breakAt)}
351
+ ${line}${human.slice(breakAt)}`;
327
352
  }
328
353
  info(message) {
329
354
  if (this.options.quiet || this.options.json) {
@@ -369,6 +394,9 @@ function resolveColor(json) {
369
394
  }
370
395
  return Boolean(process.stderr.isTTY);
371
396
  }
397
+ function isPlainObject(value) {
398
+ return typeof value === "object" && value !== null && !Array.isArray(value);
399
+ }
372
400
  function createOutput(opts) {
373
401
  const json = Boolean(opts.json);
374
402
  return new Output({
@@ -397,13 +425,10 @@ function isNetwork(value) {
397
425
  return typeof value === "string" && NETWORKS.has(value);
398
426
  }
399
427
  function coerceNetwork(value) {
400
- if (value === Network.Mainnet) {
401
- throw new UsageError("Morse is not yet deployed on mainnet. Use testnet or localnet.");
402
- }
403
428
  if (isNetwork(value)) {
404
429
  return value;
405
430
  }
406
- throw new UsageError(`Unknown network "${value}". Use one of: testnet, localnet.`);
431
+ throw new UsageError(`Unknown network "${value}". Use one of: mainnet, testnet, localnet.`);
407
432
  }
408
433
  function parseConfig(raw, source) {
409
434
  if (!isRecord(raw)) {
@@ -945,6 +970,7 @@ async function buildWriteContext(command) {
945
970
  const { base, keypair, address } = await buildSignedBase(command);
946
971
  return {
947
972
  ...base,
973
+ output: base.output.withNetwork(base.settings.network),
948
974
  adapter: new KeypairAdapter(keypair, base.client),
949
975
  address
950
976
  };
@@ -966,7 +992,13 @@ async function buildContentContext(command) {
966
992
  }
967
993
  const adapter = new KeypairAdapter(keypair, base.client);
968
994
  const walrus = DefaultWalrusWriteAdapter.fromConfig({ network, suiClient: base.client }, keypair);
969
- return { ...base, adapter, address, walrus };
995
+ return {
996
+ ...base,
997
+ output: base.output.withNetwork(network),
998
+ adapter,
999
+ address,
1000
+ walrus
1001
+ };
970
1002
  }
971
1003
  async function buildEncryptContext(command) {
972
1004
  const ctx = await buildContentContext(command);
@@ -1536,7 +1568,7 @@ function registerConfigCommands(program) {
1536
1568
  config.command("list").description("List profiles and show the default").action(async (_options, command) => {
1537
1569
  await runConfigList(outputFor(command));
1538
1570
  });
1539
- config.command("add <name>").description("Create or update a profile").requiredOption("--network <network>", "Sui network: testnet or localnet").option("--rpc <url>", "RPC URL override for this profile").action(async (name, options, command) => {
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) => {
1540
1572
  await runConfigAdd(outputFor(command), name, options);
1541
1573
  });
1542
1574
  config.command("use <name>").description("Set the default profile").action(async (name, _options, command) => {
@@ -2117,7 +2149,7 @@ async function runFileUpload(ctx, path, options) {
2117
2149
  const upload = { epochs, deletable: true };
2118
2150
  const onProgress = uploadProgress(ctx.output);
2119
2151
  if (options.public) {
2120
- const result2 = await uploadRecipientFileFromBytes(ctx.adapter, ctx.config, {
2152
+ const result = await uploadRecipientFileFromBytes(ctx.adapter, ctx.config, {
2121
2153
  walrus: ctx.walrus,
2122
2154
  bytes,
2123
2155
  recipients: [],
@@ -2128,10 +2160,10 @@ async function runFileUpload(ctx, path, options) {
2128
2160
  onProgress
2129
2161
  });
2130
2162
  const aggregator = ctx.config.walrusEndpoints.aggregator;
2131
- const viewUrl = aggregator.length > 0 ? `${aggregator}/v1/blobs/${result2.blobId}` : undefined;
2132
- const human2 = viewUrl === undefined ? `Uploaded public file ${result2.fileId}. (tx: ${result2.digest})` : `Uploaded public file ${result2.fileId}. (tx: ${result2.digest})
2163
+ const viewUrl = aggregator.length > 0 ? `${aggregator}/v1/blobs/${result.blobId}` : undefined;
2164
+ const human = viewUrl === undefined ? `Uploaded public file ${result.fileId}. (tx: ${result.digest})` : `Uploaded public file ${result.fileId}. (tx: ${result.digest})
2133
2165
  view: ${viewUrl}`;
2134
- ctx.output.result(human2, { ...result2, viewUrl: viewUrl ?? null });
2166
+ ctx.output.result(human, { ...result, viewUrl: viewUrl ?? null });
2135
2167
  return;
2136
2168
  }
2137
2169
  const result = await uploadEncryptedRecipientFileFromBytes(ctx.adapter, ctx.config, {
@@ -2588,11 +2620,11 @@ function registerRevisionCommands(program) {
2588
2620
  // src/commands/use.ts
2589
2621
  async function runUse(output, gopts, publication, collection, options, makeContext) {
2590
2622
  if (options.clear) {
2591
- const profile2 = await updateActiveProfile(gopts, {
2623
+ const profile = await updateActiveProfile(gopts, {
2592
2624
  publication: undefined,
2593
2625
  collection: undefined
2594
2626
  });
2595
- output.result(`Cleared the active publication and collection (profile "${profile2}").`, { profile: profile2, publication: null, collection: null });
2627
+ output.result(`Cleared the active publication and collection (profile "${profile}").`, { profile, publication: null, collection: null });
2596
2628
  return;
2597
2629
  }
2598
2630
  if (publication === undefined) {
@@ -5,14 +5,21 @@ Sui testnet, then tears it down. Every command is copy-pasteable; expected
5
5
  output is shown beneath each step. Replace IDs in later steps with the ones your
6
6
  own runs print.
7
7
 
8
+ The walkthrough uses testnet on purpose: it creates a publication, uploads
9
+ content to Walrus, and deletes it all again. On mainnet that sequence costs real
10
+ SUI and WAL. Once it works for you, the only change needed is the network on the
11
+ profile in step 1 (`--network mainnet`); every later command reads the network
12
+ from the profile and prints which one it spent on.
13
+
8
14
  ## 0. Prerequisites
9
15
 
10
16
  - Install [Bun](https://bun.sh) >= 1.2.
11
17
  - Have a Sui testnet keypair. If you use the Sui CLI, export one with
12
18
  `sui keytool export --key-identity <alias>` to get a `suiprivkey1...` string.
13
19
  - Fund the address with testnet SUI ([faucet](https://faucet.sui.io/)) for gas,
14
- and with WAL ([Walrus faucet](https://docs.walrus.site/usage/web-tool.html))
15
- for content uploads.
20
+ and with WAL by swapping some SUI at
21
+ [stake-wal.wal.app](https://stake-wal.wal.app/?network=testnet) for content
22
+ uploads.
16
23
 
17
24
  Install the CLI:
18
25
 
@@ -24,6 +31,8 @@ morse --version
24
31
  ## 1. Create a profile
25
32
 
26
33
  A profile pins the network. The first profile you add becomes the default.
34
+ `--network` takes `mainnet`, `testnet`, or `localnet`; with no profile, flag, or
35
+ `MORSE_NETWORK`, the CLI targets testnet.
27
36
 
28
37
  ```sh
29
38
  morse config add testnet --network testnet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadiasystems/morse-cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.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.4.2",
58
+ "@arcadiasystems/morse-sdk": "^0.5.0",
59
59
  "@mysten/seal": "1.1.3",
60
60
  "@mysten/sui": "2.16.2",
61
61
  "@mysten/walrus": "1.1.6",