@arcadiasystems/morse-cli 0.7.0 → 0.8.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,18 @@ 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.8.0] - 2026-09-10
8
+
9
+ Usability pass. Nothing is renamed or removed; every existing spelling keeps working.
10
+
11
+ ### Added
12
+
13
+ - **Verb aliases, so the word you guess is the word that works.** The command surface had grown four inconsistencies where one concept had two names depending on the noun: fetching one thing was `get` on publication, entry and file but `show` on account; deleting was `delete` everywhere except `config remove`; transferring was `transfer-ownership` on publication and file but `transfer` on cap; and reading content was `entry read` but `file download`, despite both taking `--out` and `--via-aggregator`.
14
+
15
+ Each now accepts both spellings: `account show|get`, `publication get|show`, `entry get|show`, `file get|show`, `config remove|delete`, `cap transfer|transfer-ownership`, `entry read|download`, `file download|read`. Aliases, not renames, so nothing published breaks.
16
+ - **`entry list --all`** does what `entry scan` does. `scan` is accurate but undiscoverable; people look for `--all` and conclude pagination is unsupported. `scan` stays. Combining `--all` with `--limit` or `--cursor` is refused rather than silently ignoring one.
17
+ - **`account import` reads a key piped on stdin.** Previously it required an interactive terminal or `MORSE_PRIVATE_KEY`, so the obvious scripted form failed outright. Still never a flag: argv is visible in `ps` and shell history. The error when no key can be found now names all three routes.
18
+
7
19
  ## [0.7.0] - 2026-09-09
8
20
 
9
21
  ### Added
package/README.md CHANGED
@@ -6,7 +6,7 @@ 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.7.0. Mainnet and testnet are both supported for public content;
9
+ > Status: v0.8.0. Mainnet and testnet are both supported for public content;
10
10
  > the command surface is stable.
11
11
  >
12
12
  > **Encrypted commands are testnet-only.** `entry add-encrypted`, `entry
@@ -173,9 +173,9 @@ and `-C, --collection <name>`, both defaulting to the active context.
173
173
  | Command | Purpose |
174
174
  | --- | --- |
175
175
  | `config add <name> --network <net> [--rpc <url>] [--upload-relay <url\|auto>]` | Create or update a profile. |
176
+ | `config remove <name>` | Delete a profile. Also `config delete`. |
176
177
  | `config list` | List profiles; `*` marks the default. |
177
178
  | `config use <name>` | Set the default profile. |
178
- | `config remove <name>` | Delete a profile. |
179
179
  | `config path` | Print the config file path. |
180
180
 
181
181
  ### account
@@ -352,6 +352,25 @@ to fetch the full record per file (one read each) when you need them.
352
352
  retired, so it needs `--indexer-url` pointing at a source that serves the same
353
353
  query. This affects testnet as well as mainnet.
354
354
 
355
+ ## Command spellings
356
+
357
+ Several commands accept two verbs for the same action, so whichever you reach
358
+ for works. These are aliases, not separate commands:
359
+
360
+ | Canonical | Also accepts |
361
+ | --- | --- |
362
+ | `account show` | `account get` |
363
+ | `publication get` | `publication show` |
364
+ | `entry get` | `entry show` |
365
+ | `file get` | `file show` |
366
+ | `config remove` | `config delete` |
367
+ | `cap transfer` | `cap transfer-ownership` |
368
+ | `entry read` | `entry download` |
369
+ | `file download` | `file read` |
370
+ | `entry scan` | `entry list --all` |
371
+
372
+ `publication` also answers to `pub`.
373
+
355
374
  ## When uploads fail: the upload relay
356
375
 
357
376
  A Walrus upload pushes slivers to every storage node in the committee at once,
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.7.0",
151
+ version: "0.8.0",
152
152
  description: "Command-line interface for the Morse decentralized CMS on Sui.",
153
153
  license: "MIT",
154
154
  type: "module",
@@ -224,6 +224,33 @@ function buildProgram() {
224
224
  return program;
225
225
  }
226
226
 
227
+ // src/cli/io.ts
228
+ import { access, readFile, writeFile } from "node:fs/promises";
229
+ async function fileExists(path) {
230
+ try {
231
+ await access(path);
232
+ return true;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ async function readBytes(path) {
238
+ return new Uint8Array(await readFile(path));
239
+ }
240
+ async function readJson(path) {
241
+ return JSON.parse(await readFile(path, "utf8"));
242
+ }
243
+ async function writeFileContents(path, data) {
244
+ await writeFile(path, data);
245
+ }
246
+ async function readStdin() {
247
+ const chunks = [];
248
+ for await (const chunk of process.stdin) {
249
+ chunks.push(chunk);
250
+ }
251
+ return new Uint8Array(Buffer.concat(chunks));
252
+ }
253
+
227
254
  // src/cli/prompts.ts
228
255
  import * as readline from "node:readline";
229
256
  function isInteractive() {
@@ -487,33 +514,6 @@ function malformed(source, detail) {
487
514
  // src/config/store.ts
488
515
  import { chmod, mkdir, rename } from "node:fs/promises";
489
516
 
490
- // src/cli/io.ts
491
- import { access, readFile, writeFile } from "node:fs/promises";
492
- async function fileExists(path) {
493
- try {
494
- await access(path);
495
- return true;
496
- } catch {
497
- return false;
498
- }
499
- }
500
- async function readBytes(path) {
501
- return new Uint8Array(await readFile(path));
502
- }
503
- async function readJson(path) {
504
- return JSON.parse(await readFile(path, "utf8"));
505
- }
506
- async function writeFileContents(path, data) {
507
- await writeFile(path, data);
508
- }
509
- async function readStdin() {
510
- const chunks = [];
511
- for await (const chunk of process.stdin) {
512
- chunks.push(chunk);
513
- }
514
- return new Uint8Array(Buffer.concat(chunks));
515
- }
516
-
517
517
  // src/config/paths.ts
518
518
  import { homedir } from "node:os";
519
519
  import { join } from "node:path";
@@ -925,7 +925,7 @@ function registerAccountCommands(program) {
925
925
  account.command("list").description("List imported accounts").action(async (_options, command) => {
926
926
  await runAccountList(outputFor(command), globalOptions(command));
927
927
  });
928
- account.command("show").description("Print the active account address").action(async (_options, command) => {
928
+ account.command("show").alias("get").description("Print the active account address").action(async (_options, command) => {
929
929
  await runAccountShow(outputFor(command), globalOptions(command));
930
930
  });
931
931
  account.command("use <address>").description("Set the active account for the current profile").action(async (address, _options, command) => {
@@ -940,8 +940,15 @@ async function readSecretToImport(env, signal) {
940
940
  if (raw !== undefined && raw.length > 0) {
941
941
  return raw;
942
942
  }
943
+ if (!process.stdin.isTTY) {
944
+ const piped = new TextDecoder().decode(await readStdin()).trim();
945
+ if (piped.length > 0) {
946
+ return piped;
947
+ }
948
+ throw new UsageError("Cannot read a private key: pipe it on stdin, set MORSE_PRIVATE_KEY, or run in an interactive terminal.");
949
+ }
943
950
  if (!isInteractive()) {
944
- throw new UsageError("Cannot read a private key: set MORSE_PRIVATE_KEY or run in an interactive terminal.");
951
+ throw new UsageError("Cannot read a private key: pipe it on stdin, set MORSE_PRIVATE_KEY, or run in an interactive terminal.");
945
952
  }
946
953
  process.stderr.write(`Paste your Sui private key (starts with suiprivkey1...), then press Enter. Input is hidden.
947
954
  `);
@@ -1502,7 +1509,7 @@ function registerCapCommands(program) {
1502
1509
  publicationOption(destroy).action(async (publisherCapId, options, command) => {
1503
1510
  await runCapDestroy(await buildWriteContext(command), publisherCapId, options, globalOptions(command));
1504
1511
  });
1505
- cap.command("transfer <publisherCapId> <recipient>").description("Transfer a PublisherCap object to another address").action(async (publisherCapId, recipient, _options, command) => {
1512
+ cap.command("transfer <publisherCapId> <recipient>").alias("transfer-ownership").description("Transfer a PublisherCap object to another address").action(async (publisherCapId, recipient, _options, command) => {
1506
1513
  await runCapTransfer(await buildWriteContext(command), publisherCapId, recipient, globalOptions(command));
1507
1514
  });
1508
1515
  }
@@ -1646,7 +1653,7 @@ function registerConfigCommands(program) {
1646
1653
  config.command("use <name>").description("Set the default profile").action(async (name, _options, command) => {
1647
1654
  await runConfigUse(outputFor(command), name);
1648
1655
  });
1649
- config.command("remove <name>").description("Delete a profile").action(async (name, _options, command) => {
1656
+ config.command("remove <name>").alias("delete").description("Delete a profile").action(async (name, _options, command) => {
1650
1657
  await runConfigRemove(outputFor(command), name);
1651
1658
  });
1652
1659
  }
@@ -1850,6 +1857,12 @@ async function runEntryGet(ctx, entryId, options) {
1850
1857
  ctx.output.result(renderEntry(result), result);
1851
1858
  }
1852
1859
  async function runEntryList(ctx, options) {
1860
+ if (options.all) {
1861
+ if (options.limit !== undefined || options.cursor !== undefined) {
1862
+ throw new UsageError("--all cannot be combined with --limit or --cursor.");
1863
+ }
1864
+ return runEntryScan(ctx, options);
1865
+ }
1853
1866
  const id = await resolvePublication(ctx, options.publication);
1854
1867
  const collection = resolveCollection(ctx, options.collection);
1855
1868
  const page = await ctx.reader.listEntries(id, collection, {
@@ -1961,11 +1974,11 @@ async function runEntryRead(ctx, entryId, revisionId, options) {
1961
1974
  }
1962
1975
  function registerEntryCommands(program) {
1963
1976
  const entry = program.command("entry").description("Read, add, and delete entries in a collection");
1964
- const get = entry.command("get <entryId>").description("Fetch a single entry");
1977
+ const get = entry.command("get <entryId>").alias("show").description("Fetch a single entry");
1965
1978
  collectionOption(publicationOption(get)).action(async (entryId, options, command) => {
1966
1979
  await runEntryGet(await buildReadContext(command), entryId, options);
1967
1980
  });
1968
- const list = entry.command("list").description("List entries in a collection").option("--limit <n>", "Maximum results per page").option("--cursor <cursor>", "Continue from a previous page cursor").option("--drafts-only", "Show only entries with a pending (unpublished) draft");
1981
+ const list = entry.command("list").description("List entries in a collection").option("--limit <n>", "Maximum results per page").option("--cursor <cursor>", "Continue from a previous page cursor").option("--all", "Fetch every page (same as `entry scan`)").option("--drafts-only", "Show only entries with a pending (unpublished) draft");
1969
1982
  collectionOption(publicationOption(list)).action(async (options, command) => {
1970
1983
  await runEntryList(await buildReadContext(command), options);
1971
1984
  });
@@ -1981,7 +1994,7 @@ function registerEntryCommands(program) {
1981
1994
  collectionOption(publicationOption(publisherCapOption(remove))).action(async (entryId, options, command) => {
1982
1995
  await runEntryDelete(await buildWriteContext(command), entryId, options, globalOptions(command));
1983
1996
  });
1984
- const read = entry.command("read <entryId> [revisionIndex]").description("Fetch a public entry's content to stdout or a file").option("--out <path>", "Write content to a file instead of stdout");
1997
+ const read = entry.command("read <entryId> [revisionIndex]").alias("download").description("Fetch a public entry's content to stdout or a file").option("--out <path>", "Write content to a file instead of stdout");
1985
1998
  collectionOption(publicationOption(viaAggregatorOption(read))).action(async (entryId, revisionId, options, command) => {
1986
1999
  await runEntryRead(await buildReadContentContext(command, {
1987
2000
  viaAggregator: options.viaAggregator
@@ -2411,13 +2424,13 @@ function registerFileCommands(program) {
2411
2424
  indexerUrl: options.indexerUrl
2412
2425
  }), options);
2413
2426
  });
2414
- const download = file.command("download [file]").description("Download a file's content; decrypts in place when encrypted").option("--out <path>", "Write content to a file instead of stdout").option("--share <string>", "Share string from upload (bundles file id, prefix, nonce)").option("--prefix <hex>", "Seal prefix (with --nonce) to decrypt").option("--nonce <hex>", "Seal nonce (with --prefix) to decrypt").option("--raw", "Write the raw (still-encrypted) bytes when no decrypt input is given");
2427
+ const download = file.command("download [file]").alias("read").description("Download a file's content; decrypts in place when encrypted").option("--out <path>", "Write content to a file instead of stdout").option("--share <string>", "Share string from upload (bundles file id, prefix, nonce)").option("--prefix <hex>", "Seal prefix (with --nonce) to decrypt").option("--nonce <hex>", "Seal nonce (with --prefix) to decrypt").option("--raw", "Write the raw (still-encrypted) bytes when no decrypt input is given");
2415
2428
  viaAggregatorOption(download).action(async (target, options, command) => {
2416
2429
  await runFileDownload(await buildFileDownloadContext(command, {
2417
2430
  viaAggregator: options.viaAggregator
2418
2431
  }), target, options);
2419
2432
  });
2420
- file.command("get <file>").description("Fetch a file's on-chain metadata").action(async (target, _options, command) => {
2433
+ file.command("get <file>").alias("show").description("Fetch a file's on-chain metadata").action(async (target, _options, command) => {
2421
2434
  await runFileGet(await buildFilesReadContext(command), target);
2422
2435
  });
2423
2436
  const register = file.command("register").description("Register on-chain metadata for a blob already on Walrus").requiredOption("--blob-id <id>", "Walrus content id").requiredOption("-n, --name <name>", "File name").requiredOption("--content-type <mime>", "MIME content type").requiredOption("--size <bytes>", "Plaintext byte length").option("--public", "Register a world-readable (unencrypted) file").option("--encrypted", "Register an encrypted file (needs --seal-prefix)").option("--seal-prefix <hex>", "Seal prefix the blob was encrypted under").option("--blob-object-id <id>", "On-chain Walrus Blob object id, if known");
@@ -2553,7 +2566,7 @@ async function runPublicationTransferOwnership(ctx, recipient, options, gopts) {
2553
2566
  }
2554
2567
  function registerPublicationCommands(program) {
2555
2568
  const publication = program.command("publication").alias("pub").description("Work with publications");
2556
- publication.command("get [publication]").description("Fetch a publication (slug or id; default: the active publication)").action(async (target, _options, command) => {
2569
+ publication.command("get [publication]").alias("show").description("Fetch a publication (slug or id; default: the active publication)").action(async (target, _options, command) => {
2557
2570
  await runPublicationGet(await buildReadContext(command), target);
2558
2571
  });
2559
2572
  publication.command("list [address]").description("List publications owned by an address (default: the active account)").option("--limit <n>", "Maximum results per page").option("--cursor <cursor>", "Continue from a previous page cursor").option("--ids-only", "Skip slug/name resolution (one RPC, no per-publication reads)").action(async (address, options, command) => {
@@ -43,6 +43,18 @@ Saved profile "testnet" (testnet).
43
43
 
44
44
  ## 2. Import your key
45
45
 
46
+ Three ways to give the CLI a key. It is never accepted as a flag, because argv
47
+ is visible in `ps` and shell history.
48
+
49
+ - **Interactive**: run `morse account import` and paste at the hidden prompt.
50
+ - **Piped**: `echo "$KEY" | morse account import`
51
+ - **No keystore at all**: export `MORSE_PRIVATE_KEY` and skip the import; every
52
+ command picks it up.
53
+
54
+ The first two store the key encrypted and need `MORSE_KEYSTORE_PASSWORD` (or an
55
+ interactive password prompt).
56
+
57
+
46
58
  `account import` prompts for the secret key and a keystore password, both hidden.
47
59
  The key is encrypted at rest (scrypt + AES-256-GCM) and never stored in plaintext.
48
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadiasystems/morse-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Command-line interface for the Morse decentralized CMS on Sui.",
5
5
  "license": "MIT",
6
6
  "type": "module",