@arcadiasystems/morse-cli 0.1.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 ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@arcadiasystems/morse-cli` are documented here. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this
5
+ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - unreleased
8
+
9
+ ### Added
10
+
11
+ - Runs under Node (>= 18) or Bun. The published `bin` is a Node-targeted bundle
12
+ (`dist/index.js`, built with `bun run build`); file/stdin IO goes through a
13
+ cross-runtime layer (`cli/io.ts`) so `npm i -g` works without Bun.
14
+ - Active context: `morse use <slug|id> [collection]`, `morse use --clear`, and
15
+ `morse status`. Publication and collection commands default to the active
16
+ context and accept `-P, --publication <slug|id>` and `-C, --collection <name>`
17
+ overrides. `publication create` and `collection create` auto-select the new
18
+ object. Slugs resolve against publications owned by the active account.
19
+ - `morse entry read <entryId> [revisionIndex]`: fetch a public entry's content
20
+ to stdout or `--out <path>`. `entry add` prints a viewable Walrus aggregator
21
+ link (`viewUrl`) for the uploaded content.
22
+ - Initial CLI scaffold: package metadata, build/lint/test tooling, and the
23
+ top-level `morse` command with `--help` and `--version`.
24
+ - CLI core: global options (`--network`, `--profile`, `--rpc`, `--json`,
25
+ `--quiet`, `--yes`, `--debug`), stdout/stderr output discipline with
26
+ NO_COLOR/FORCE_COLOR handling, a documented exit-code taxonomy, an error
27
+ boundary that renders SDK errors via `formatUserMessage`, and hidden/confirm
28
+ prompts.
29
+ - `morse config` commands (`path`, `list`, `add`, `use`, `remove`) backed by a
30
+ profile config file under `$XDG_CONFIG_HOME/morse` with atomic writes and
31
+ `flags > MORSE_* env > config file > defaults` precedence.
32
+ - Encrypted keystore (scrypt + AES-256-GCM) and `morse account` commands
33
+ (`import`, `list`, `show`, `use`, `export`). Keys are unlocked by a hidden
34
+ password prompt or `MORSE_KEYSTORE_PASSWORD`, with `MORSE_PRIVATE_KEY` honored
35
+ for CI. Keystore files are `0600`; group/world-readable files are refused.
36
+ Keys are never accepted as flags and never printed except by the explicit,
37
+ interactive-only `account export`.
38
+ - Read commands: `morse publication get/list` and `morse entry get/list/scan`,
39
+ backed by `RpcPublicationReader`. `publication list` shows each publication's
40
+ slug and name (`--ids-only` for the fast single-RPC path). JSON output encodes
41
+ `bigint` as decimal strings and byte arrays (`sealId`, quilt patch ids) as
42
+ `0x` hex.
43
+ - Write commands: `morse publication create/delete/transfer-ownership` and
44
+ `morse collection create/list/delete`. OwnerCap and PublisherCap IDs are
45
+ auto-resolved from the active account (override with `--owner-cap` /
46
+ `--publisher-cap`). Destructive operations confirm unless `--yes`.
47
+ - Content commands: `morse entry add` (upload a file or stdin to Walrus and add
48
+ it as a new entry) and `morse entry delete`, plus `morse revision
49
+ publish-direct/append-draft/publish-from-draft`. Content type is inferred from
50
+ the file extension when not given; `--epochs` sets Walrus storage duration.
51
+ - PublisherCap commands: `morse cap issue/list/revoke/destroy/transfer`.
52
+ Destructive operations confirm unless `--yes`.
53
+ - Encrypted content (Seal): `morse entry add-encrypted` (encrypt with Seal,
54
+ upload, add an encrypted entry; prints the generated `sealId`) and `morse
55
+ entry decrypt` (fetch ciphertext, sign a SessionKey with the active account,
56
+ recover plaintext to stdout or `--out`).
57
+ - Documentation: full command reference and security model in the README, plus a
58
+ copy-pasteable end-to-end quick-start guide in `docs/QUICKSTART.md`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Morse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,294 @@
1
+ # morse-cli
2
+
3
+ Command-line interface for the [Morse](https://github.com/arcadiasystems/morse-dcms)
4
+ decentralized CMS on Sui. Create and manage publications, collections, and
5
+ content entries from your terminal, signing with a locally encrypted key.
6
+ Content is stored on [Walrus](https://walrus.xyz); private entries are encrypted
7
+ with [Seal](https://github.com/MystenLabs/seal).
8
+
9
+ > Status: v0.1.0, targeting Sui testnet. The command surface is stable; mainnet
10
+ > support lands when the contracts are frozen.
11
+
12
+ ## Requirements
13
+
14
+ - [Node.js](https://nodejs.org) >= 18, or [Bun](https://bun.sh) >= 1.2. The
15
+ 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).
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm i -g @arcadiasystems/morse-cli # or: bun add -g, pnpm add -g
24
+ morse --help
25
+ ```
26
+
27
+ Or run without installing:
28
+
29
+ ```sh
30
+ npx @arcadiasystems/morse-cli --help # or: bunx @arcadiasystems/morse-cli
31
+ ```
32
+
33
+ From a clone (development), run the source with Bun:
34
+
35
+ ```sh
36
+ bun morse-cli/src/index.ts --help
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ A full, copy-pasteable walkthrough lives in [docs/QUICKSTART.md](./docs/QUICKSTART.md).
42
+ The short version:
43
+
44
+ ```sh
45
+ morse config add testnet --network testnet # create a profile (becomes default)
46
+ morse account import # import a key (prompts for key + password)
47
+ morse publication create --name "My Blog" --slug my-blog # becomes the active publication
48
+ morse collection create posts # becomes the active collection
49
+ morse entry add hello --file post.txt # no ids needed
50
+ ```
51
+
52
+ ## Active context
53
+
54
+ Most commands act on a publication and a collection. Rather than pasting a
55
+ 64-character object id every time, select an active publication and collection
56
+ once; commands then default to them. This mirrors how `kubectl`/`gh` use a
57
+ current context.
58
+
59
+ ```sh
60
+ morse use my-blog # set the active publication (slug or id)
61
+ morse use my-blog posts # set both publication and collection
62
+ morse status # show the active profile, account, publication, collection
63
+ morse use --clear # clear the active publication and collection
64
+ ```
65
+
66
+ - `publication create` selects the new publication automatically; `collection
67
+ create` selects the new collection. So the common flow needs no ids at all.
68
+ - Override the context per command with `-P, --publication <slug|id>` and
69
+ `-C, --collection <name>`. Flags always win over the active context.
70
+ - Slugs are resolved against publications owned by the active account (the
71
+ registry has no slug index). Pass an object id to address any publication.
72
+
73
+ ## Configuration
74
+
75
+ Settings resolve with the precedence `flags > MORSE_* env > config file >
76
+ defaults`. The config file lives under `$XDG_CONFIG_HOME/morse` (default
77
+ `~/.config/morse`); encrypted keystores live in `keystores/` beside it.
78
+
79
+ Manage profiles with `morse config`:
80
+
81
+ ```sh
82
+ morse config add testnet --network testnet # create a profile (first becomes default)
83
+ morse config list # list profiles, * marks the default
84
+ morse config use testnet # change the default profile
85
+ morse config remove testnet # delete a profile
86
+ morse config path # print the config file path
87
+ ```
88
+
89
+ Environment variables (override the config file, overridden by flags):
90
+
91
+ | Variable | Overrides | Notes |
92
+ | --- | --- | --- |
93
+ | `MORSE_PROFILE` | `--profile` | Profile to use. |
94
+ | `MORSE_NETWORK` | `--network` | `testnet` or `localnet`. |
95
+ | `MORSE_RPC_URL` | `--rpc` | Sui RPC URL override. |
96
+ | `MORSE_ADDRESS` | (no flag) | Active account address, selecting which keystore to use. |
97
+ | `MORSE_PUBLICATION` | `-P, --publication` | Active publication id. |
98
+ | `MORSE_COLLECTION` | `-C, --collection` | Active collection name. |
99
+ | `MORSE_PRIVATE_KEY` | (no flag) | Raw Bech32 secret key. Highest-priority key source; never persisted. For CI. |
100
+ | `MORSE_KEYSTORE_PASSWORD` | (no flag) | Keystore password for non-interactive unlock. For CI. |
101
+ | `XDG_CONFIG_HOME` | (no flag) | Base config directory; defaults to `~/.config`. |
102
+
103
+ ## Security model
104
+
105
+ Private keys are stored in an encrypted keystore (scrypt + AES-256-GCM) and
106
+ unlocked by a password. Specifics:
107
+
108
+ - Keys are never accepted as command-line flags (they would leak via `ps`,
109
+ `/proc/<pid>/cmdline`, and shell history). They come from an interactive hidden
110
+ prompt, the `MORSE_PRIVATE_KEY` env var, or the encrypted keystore.
111
+ - Keystore files are written `chmod 600`; group- or world-readable key files are
112
+ refused on read.
113
+ - Keys are never printed or logged, including under `--debug`. The sole exception
114
+ is `morse account export`, which deliberately reveals a key: it is
115
+ interactive-only, requires confirmation, and is unavailable in `--json` mode.
116
+ - The key-source precedence is `MORSE_PRIVATE_KEY` (raw, ephemeral) > the active
117
+ account's keystore (password-unlocked).
118
+
119
+ ## Command reference
120
+
121
+ Global options apply to every command and must appear before the subcommand
122
+ (e.g. `morse --json publication list`).
123
+
124
+ | Global option | Purpose |
125
+ | --- | --- |
126
+ | `--network <testnet\|localnet>` | Network to target (default: testnet). |
127
+ | `-p, --profile <name>` | Config profile to use. |
128
+ | `--rpc <url>` | Override the Sui RPC URL. |
129
+ | `--json` | Machine-readable JSON on stdout. |
130
+ | `-q, --quiet` | Suppress progress and informational output. |
131
+ | `-y, --yes` | Assume yes for confirmation prompts. |
132
+ | `--debug` | Print stack traces on error. |
133
+ | `-V, --version` | Print the version. |
134
+ | `-h, --help` | Show help for any command. |
135
+
136
+ Commands that act on a publication/collection take `-P, --publication <slug|id>`
137
+ and `-C, --collection <name>`, both defaulting to the active context.
138
+
139
+ ### Context
140
+
141
+ | Command | Purpose |
142
+ | --- | --- |
143
+ | `use <publication> [collection]` | Set the active publication (slug or id) and optional collection. Omitting the collection clears it. |
144
+ | `use --clear` | Clear the active publication and collection. |
145
+ | `status` | Show the active profile, network, account, publication, and collection. |
146
+
147
+ ### config
148
+
149
+ | Command | Purpose |
150
+ | --- | --- |
151
+ | `config add <name> --network <net> [--rpc <url>]` | Create or update a profile. |
152
+ | `config list` | List profiles; `*` marks the default. |
153
+ | `config use <name>` | Set the default profile. |
154
+ | `config remove <name>` | Delete a profile. |
155
+ | `config path` | Print the config file path. |
156
+
157
+ ### account
158
+
159
+ | Command | Purpose |
160
+ | --- | --- |
161
+ | `account import` | Import a key into an encrypted keystore (prompts for key + password). |
162
+ | `account list` | List imported accounts; `*` marks the active one. |
163
+ | `account show` | Print the active account address. |
164
+ | `account use <address>` | Set the active account for the current profile. |
165
+ | `account export <address>` | Reveal a decrypted key (interactive-only, dangerous). |
166
+
167
+ ### publication (alias: pub)
168
+
169
+ | Command | Purpose |
170
+ | --- | --- |
171
+ | `publication get [publication]` | Fetch a publication (default: active). |
172
+ | `publication list [address] [--ids-only]` | List publications owned by an address (default: active account); shows slug, id, and name. `--ids-only` skips the per-row reads. |
173
+ | `publication create --name <name> --slug <slug>` | Create a publication and select it. |
174
+ | `publication delete [publication] [--owner-cap <id>]` | Delete an empty publication (default: active). |
175
+ | `publication transfer-ownership <recipient> [-P <slug\|id>] [--owner-cap <id>]` | Transfer the OwnerCap. |
176
+
177
+ ### collection
178
+
179
+ | Command | Purpose |
180
+ | --- | --- |
181
+ | `collection list [-P <slug\|id>]` | List collections. |
182
+ | `collection create <name> [--mode blob\|quilt] [-P <slug\|id>]` | Create a collection and select it. |
183
+ | `collection delete <name> [-P <slug\|id>] [--publisher-cap <id>]` | Delete an empty collection. |
184
+
185
+ ### entry
186
+
187
+ | Command | Purpose |
188
+ | --- | --- |
189
+ | `entry get <entryId> [-P …] [-C …]` | Fetch a single entry's metadata. |
190
+ | `entry read <entryId> [revisionIndex] [--out <path>] [-P …] [-C …]` | Fetch a public entry's content to stdout or a file. |
191
+ | `entry list [-P …] [-C …]` | List entries (paginated). |
192
+ | `entry scan [-P …] [-C …]` | List every entry (auto-paginated). |
193
+ | `entry add <name> --file <path> [-P …] [-C …]` | Upload content and add a new entry; prints a viewable link. |
194
+ | `entry delete <entryId> [-P …] [-C …]` | Delete an entry. |
195
+ | `entry add-encrypted <name> --file <path> [-P …] [-C …]` | Encrypt, upload, and add a new entry. |
196
+ | `entry decrypt <entryId> [revisionIndex] [--out <path>] [-P …] [-C …]` | Decrypt an encrypted revision. |
197
+
198
+ `add`, `add-encrypted`, and the revision commands accept `--file <path>` (or `-`
199
+ for stdin), `--stdin`, and `--content-type <type>` (inferred from the file
200
+ extension otherwise).
201
+
202
+ They also accept `--epochs <n>` (default 3): the number of Walrus storage epochs
203
+ the uploaded blob is paid to be stored for. More epochs means the content lives
204
+ longer before its storage registration expires, and costs more WAL up front.
205
+ Blobs are always uploaded as deletable (the contract rejects non-deletable
206
+ blobs), so that is not configurable. `--epochs` applies only to these upload
207
+ commands; pure on-chain commands (publication, collection, cap) have no epochs.
208
+
209
+ ### revision
210
+
211
+ | Command | Purpose |
212
+ | --- | --- |
213
+ | `revision publish-direct <entryId> --file <path> [-P …] [-C …]` | Upload content and append a public revision. |
214
+ | `revision append-draft <entryId> --file <path> [-P …] [-C …]` | Upload content and append a draft revision. |
215
+ | `revision publish-from-draft <entryId> <draftRevisionId> --file <path> [-P …] [-C …]` | Publish a new revision, referencing a draft. |
216
+
217
+ ### cap
218
+
219
+ | Command | Purpose |
220
+ | --- | --- |
221
+ | `cap list [address]` | List PublisherCaps held by an address. |
222
+ | `cap issue <holder> [-P <slug\|id>] [--owner-cap <id>]` | Issue a PublisherCap bound to an address. |
223
+ | `cap revoke <publisherCapId> [-P <slug\|id>] [--owner-cap <id>]` | Revoke a PublisherCap. |
224
+ | `cap destroy <publisherCapId> [-P <slug\|id>]` | Destroy a PublisherCap you hold. |
225
+ | `cap transfer <publisherCapId> <recipient>` | Transfer a PublisherCap object. |
226
+
227
+ OwnerCap and PublisherCap IDs are auto-resolved from the active account when the
228
+ `--owner-cap` / `--publisher-cap` override is omitted. Destructive operations
229
+ (`delete`, `revoke`, `destroy`, `transfer`) confirm interactively unless `--yes`.
230
+
231
+ ## Output and scripting
232
+
233
+ - Human-readable output goes to stdout; progress, warnings, and prompts go to
234
+ stderr. Redirecting stdout yields only the result.
235
+ - `--json` emits a single JSON document on stdout (nothing else). `bigint` values
236
+ (e.g. `gasUsedMist`) are encoded as decimal strings and byte arrays (`sealId`,
237
+ quilt patch ids) as `0x`-hex.
238
+ - Color is emitted only to a TTY and honors `NO_COLOR` and `FORCE_COLOR`.
239
+
240
+ ## Exit codes
241
+
242
+ | Code | Meaning |
243
+ | --- | --- |
244
+ | 0 | Success |
245
+ | 1 | Generic error (contract abort, uncategorized) |
246
+ | 2 | Usage error (bad or missing flags/arguments, invalid id/slug/address, declined confirmation) |
247
+ | 3 | Not found |
248
+ | 4 | Auth or permission failure (keystore unlock, unauthorized, no-access) |
249
+ | 5 | Network or transport failure |
250
+
251
+ ## Examples
252
+
253
+ - [docs/QUICKSTART.md](./docs/QUICKSTART.md): a full, copy-pasteable walkthrough.
254
+ - Runnable shell recipes in [examples/](./examples/):
255
+ - [`lifecycle.sh`](./examples/lifecycle.sh): create, add an entry, read, revise, tear down.
256
+ - [`content.sh`](./examples/content.sh): upload an image and a post, publish a revision, fetch content back, get a link, remove a collection.
257
+ - [`encrypt-decrypt.sh`](./examples/encrypt-decrypt.sh): encrypt with Seal and decrypt back.
258
+ - [`delegation.sh`](./examples/delegation.sh): issue a PublisherCap to a delegate, then revoke it.
259
+ - [`ci-noninteractive.sh`](./examples/ci-noninteractive.sh): env-var auth, `--yes`, and `--json` parsing.
260
+
261
+ ## Limitations
262
+
263
+ - A publication's `name` and `slug` are immutable on-chain (the slug is the
264
+ registry's unique key), so there is no rename command.
265
+ - `entry read` serves public content to stdout or a file; encrypted entries are
266
+ retrieved with `entry decrypt`, not `entry read`. A shareable Walrus link is
267
+ printed by `entry add` (the `viewUrl` field), since the content id is known at
268
+ upload time.
269
+ - Mainnet is not yet deployed; use `testnet`.
270
+
271
+ ## Publishing
272
+
273
+ Published to the public npm registry as the scoped package
274
+ `@arcadiasystems/morse-cli`. The `bin` entry exposes the `morse` command, so once
275
+ published users can:
276
+
277
+ ```sh
278
+ bun add -g @arcadiasystems/morse-cli # or: npm i -g @arcadiasystems/morse-cli
279
+ bunx @arcadiasystems/morse-cli --help # or: npx, run without installing
280
+ ```
281
+
282
+ The shipped `bin` is `dist/index.js`, a Node-targeted bundle (`bun run build`)
283
+ with a `#!/usr/bin/env node` shebang and external dependencies, so installs run
284
+ under Node or Bun. `prepublishOnly` runs typecheck, lint, tests, and the build,
285
+ so the published `dist/` is always fresh.
286
+
287
+ Release steps: bump the version in `package.json`, stamp the CHANGELOG date,
288
+ then `npm publish` (the package is `publishConfig.access: public`). `npm publish`
289
+ ships `dist`, `docs`, `README.md`, `LICENSE`, and `CHANGELOG.md` (see the `files`
290
+ allowlist). Publish the SDK first; the CLI depends on `@arcadiasystems/morse-sdk`.
291
+
292
+ ## License
293
+
294
+ MIT