@feralfile/cli 2.9.0 → 2.10.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/dist/package.json +2 -2
- package/dist/src/commands/config.js +12 -5
- package/dist/src/commands/enrich.js +128 -19
- package/dist/src/commands/find.js +11 -2
- package/dist/src/commands/publish.js +22 -7
- package/dist/src/commands/setup.js +42 -15
- package/dist/src/commands/sign.js +8 -1
- package/dist/src/commands/status.js +28 -2
- package/dist/src/commands/unpublish.js +11 -10
- package/dist/src/utilities/feed-mutation.js +29 -16
- package/dist/src/utilities/playlist-publisher.js +1 -1
- package/dist/src/utilities/playlist-unpublisher.js +1 -1
- package/dist/src/utilities/signing-key-source.js +230 -0
- package/docs/CONFIGURATION.md +58 -4
- package/docs/EXAMPLES.md +39 -7
- package/docs/PROJECT_SPEC.md +14 -5
- package/docs/README.md +36 -10
- package/package.json +2 -2
package/docs/README.md
CHANGED
|
@@ -19,6 +19,19 @@ npm i -g @feralfile/cli
|
|
|
19
19
|
ff-cli setup
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Unattended provisioning takes the same flags without prompting. Pass an existing signing key with
|
|
23
|
+
`--key-file <path>` rather than `--key`, which puts the key in the process list of a machine nobody is
|
|
24
|
+
watching and in whatever shell or CI log recorded the command:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ff-cli setup --non-interactive --key-file /run/secrets/ff-signing.key \
|
|
28
|
+
--role curator --device-host http://192.168.1.50:1111 --device-name studio
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`--key` and `--key-file` together is an error, and an empty or unreadable key file is refused rather
|
|
32
|
+
than quietly generating a new identity for the machine. See
|
|
33
|
+
[Where the signing key can come from](./CONFIGURATION.md#where-the-signing-key-can-come-from).
|
|
34
|
+
|
|
22
35
|
See the full configuration reference here: `./CONFIGURATION.md`.
|
|
23
36
|
|
|
24
37
|
During setup, you can pick FF1 devices to add. Use `ff-cli device add` to add more devices later, and `ff-cli device list` to see what's configured. The first device is the default for `play` commands (override with `-d`).
|
|
@@ -97,7 +110,8 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
97
110
|
- `validate <file-or-url>` – Validate playlist structure only
|
|
98
111
|
- `verify <file-or-url>` – Validate structure and verify signatures. On failure, the CLI labels structure issues separately from signature verification. dp1-js uses `--public-key` (or a key derived from `playlist.privateKey` / `PLAYLIST_PRIVATE_KEY` when omitted) **only** for legacy flat `signature` verification; DP-1 v1.1.0 `signatures[]` envelopes are verified without relying on that argument. If deriving or normalizing key material fails, the CLI prints a warning on stderr and continues without it (legacy verification still requires a usable key when the playlist uses a flat `signature`). The derived key is emitted as PEM. Supported key forms: hex with optional `0x`, PEM, or 32-byte raw public key as hex or base64
|
|
99
112
|
- `sign <file>` – Sign playlist with a DP-1 v1.1.0 multi-signature envelope (private key string is forwarded to **`dp1-js`**; same hex or base64 PKCS#8 DER forms as `playlist.privateKey` in `./CONFIGURATION.md`). The command verifies the final envelope before writing output and refuses to persist tampered or otherwise unverifiable `signatures[]`.
|
|
100
|
-
- Options: `-k, --key <privateKey>`, `-r, --role <role>`, `-o, --output <file>`, `--replace-signatures`, `--force`
|
|
113
|
+
- Options: `-k, --key <privateKey>`, `--key-file <path>`, `-r, --role <role>`, `-o, --output <file>`, `--replace-signatures`, `--force`
|
|
114
|
+
- `--key-file <path>` reads the key from a file instead of the command line, which is where `--key` leaves it in your shell history and in the process list. Same key encodings, same normalization; keep the file private, since the CLI does not check its permissions. `--key` and `--key-file` together is an error, and an empty or unreadable key file is refused rather than falling back to the configured key
|
|
101
115
|
- **It overwrites only a document it has read.** Before writing, the destination is read back and compared with the document being signed; if it holds something else, the run is refused rather than destroying a file the command never inspected. `--force` allows that overwrite, and the report names the file it replaced. A destination holding the *same* document — a copy of the input — is treated as an in-place run, so it is refused only when it would discard a still-valid signature from another key, and `--force` does not override that one. The read and the overwrite are not atomic, so an editor writing in the same instant can still lose its change, as with any in-place tool — a fresh `--output` name is the write that cannot collide. A destination that already exists needs read permission
|
|
102
116
|
- Signing **appends** by default, so repeated runs accumulate endorsements. That is only correct while the signed content is unchanged: editing a signed playlist moves bytes the earlier signatures cover, and the envelope check then refuses to write the result. `--replace-signatures` discards every existing entry — including a feed's own co-signature — and signs the document as it stands. It is the required step before `publish --replace`
|
|
103
117
|
- **An in-place run refuses when it would discard a still-valid signature from another key**, since only its holder could make that signature again: write the result elsewhere with `-o, --output`, which leaves the original where it is. The refusal is about what cannot be recovered — an in-place run still proceeds when the only entries dropped are your own, or ones that no longer verify
|
|
@@ -108,17 +122,17 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
108
122
|
- Sources: Art Blocks, Objkt, fxhash, OpenSea, SuperRare, Feral File, Neort, Verse, raw on-chain coordinates, and wallet addresses
|
|
109
123
|
- OpenSea forms: `/assets/ethereum/{contract}/{tokenId}`, `/item/ethereum/{contract}/{tokenId}`, and `/collection/{slug}` (Ethereum collections; the slug is resolved from the public collection page, no API key needed)
|
|
110
124
|
- Verse forms: `/items/ethereum/{contract}/{tokenId}` and `/series/{slug}`
|
|
111
|
-
- Options: `-o, --output <path>`, `-l, --limit <n>`, `-p, --play`, `-d, --device <name>`, `--publish`, `-s, --server <index>`, `-y, --yes
|
|
125
|
+
- Options: `-o, --output <path>`, `-l, --limit <n>`, `-p, --play`, `--no-play` (never cast; combine with `-y` to build and save without playing), `-d, --device <name>`, `--publish`, `-s, --server <index>`, `-y, --yes` (skip prompts; without `-o` or `--publish` it plays the playlist on a device, so add `--no-play` to skip casting), `--skip-verify`
|
|
112
126
|
- `enrich <file>` – Add missing artist, title, and thumbnail metadata to a playlist that already exists. Resolves each item by its `provenance.contract` and writes an inline Ref Manifest; `source`, `duration`, `title`, and `id` are never touched. Drops the signature when it changes anything, so re-sign afterwards
|
|
113
127
|
- DP-1 `evm` names a chain family rather than a network, so those items are skipped unless `--assume-ethereum` asserts which one they are. Getting that wrong attaches another artwork's metadata
|
|
114
128
|
- Options: `-o, --output <file>`, `--force` (replace existing manifests), `--assume-ethereum`, `-v, --verbose`
|
|
115
129
|
- `publish <file>` – Publish a playlist to a feed server (runs `verify` before upload and rejects unsigned or broken playlists)
|
|
116
|
-
- Options: `-s, --server <index>` (server index if multiple configured), `--replace` (replace the playlist already stored under this document id instead of creating a new one), `-k, --key <privateKey>` (signs the replace authorization; `--replace` only)
|
|
117
|
-
- `--key`
|
|
130
|
+
- Options: `-s, --server <index>` (server index if multiple configured), `--replace` (replace the playlist already stored under this document id instead of creating a new one), `-k, --key <privateKey>` (signs the replace authorization; `--replace` only), `--key-file <path>` (the same key, read from a file)
|
|
131
|
+
- `--key` and `--key-file` are both refused on a plain `publish`, which signs nothing at request time — it uploads the `signatures[]` envelope the document already carries. Sign the file with `ff-cli sign --key-file` instead
|
|
118
132
|
- `fetch <id-or-url>` – Save a published playlist from a feed server. Accepts a playlist id, a slug, or a feed URL. The document goes to `-o` when given and to stdout otherwise (status lines always go to stderr, so `ff-cli fetch <id> > playlist.json` works). This is the starting point for `publish --replace`, which requires the stored `id`, `slug`, and `created`
|
|
119
133
|
- Options: `-s, --server <index>`, `-o, --output <file>`. There is no `--key`: a fetch is a plain read and signs nothing
|
|
120
134
|
- `unpublish <id-or-url>` – Delete a playlist from a feed server. Accepts a playlist id, a slug, or a feed URL. Requires the signing key to be an owner of the stored playlist, and confirms before deleting
|
|
121
|
-
- Options: `-s, --server <index>`, `-y, --yes` (skip the confirmation), `-k, --key <privateKey>` (signs the delete authorization)
|
|
135
|
+
- Options: `-s, --server <index>`, `-y, --yes` (skip the confirmation), `-k, --key <privateKey>` (signs the delete authorization), `--key-file <path>` (the same key, read from a file)
|
|
122
136
|
- `ssh <enable|disable>` – Manage SSH access on an FF1 device
|
|
123
137
|
- Options: `-d, --device <name>`, `--pubkey <path>`, `--ttl <duration>`
|
|
124
138
|
- `device list` – List all configured FF1 devices
|
|
@@ -319,6 +333,13 @@ If you sign with `sign --key <privateKey>`, read that key's identity with
|
|
|
319
333
|
and `build` need none of this: they declare the key and sign it as `curator` for you when a signing key
|
|
320
334
|
is configured. An `apiKey` left over in an existing config is ignored.
|
|
321
335
|
|
|
336
|
+
`--key-file <path>` is the same key from a file, on `sign`, `status`, `unpublish`, and
|
|
337
|
+
`publish --replace` alike — so `ff-cli status --key-file signing.key` reports the identity
|
|
338
|
+
`ff-cli sign --key-file signing.key` will assert. Prefer it: `--key` writes your private key into your
|
|
339
|
+
shell history and exposes it in the process list for the length of the run, and a signing key that has
|
|
340
|
+
been disclosed cannot be rotated out of a playlist that already names it. See
|
|
341
|
+
[Where the signing key can come from](./CONFIGURATION.md#where-the-signing-key-can-come-from).
|
|
342
|
+
|
|
322
343
|
```json
|
|
323
344
|
{
|
|
324
345
|
"feedServers": [
|
|
@@ -336,7 +357,10 @@ is configured. An `apiKey` left over in an existing config is ignored.
|
|
|
336
357
|
|
|
337
358
|
A feed's `PUT` and `DELETE` are owner-bound. Neither takes an API key, and neither is authorized by the
|
|
338
359
|
document alone: both carry a short-lived **intent** that ff-cli signs in the `curator` role — with the
|
|
339
|
-
configured `playlist.privateKey` by default, or with whatever `-k, --key`
|
|
360
|
+
configured `playlist.privateKey` by default, or with whatever `-k, --key` or `--key-file <path>` names
|
|
361
|
+
for that one command (see
|
|
362
|
+
[Where the signing key can come from](./CONFIGURATION.md#where-the-signing-key-can-come-from), which
|
|
363
|
+
explains why the file is the safer of the two). Only a key the *stored* playlist names in `curators[]` can authorize either one, which is
|
|
340
364
|
why `publish` refuses to create a playlist that carries no owner-role signature — such a document can be
|
|
341
365
|
neither replaced nor deleted, ever.
|
|
342
366
|
|
|
@@ -363,10 +387,12 @@ you published — re-running `find` or `build` mints a fresh id, slug, and `crea
|
|
|
363
387
|
playlist rather than a replacement. A `publish` without `--replace` is never silently upgraded to a
|
|
364
388
|
replace; an id the feed already holds fails with a conflict, as before.
|
|
365
389
|
|
|
366
|
-
The configured `playlist.privateKey` is only the default. `-k, --key`
|
|
367
|
-
for the intent signature and for the local ownership check alike — the way
|
|
368
|
-
`ff-cli
|
|
369
|
-
|
|
390
|
+
The configured `playlist.privateKey` is only the default. `-k, --key` and `--key-file <path>` override
|
|
391
|
+
it on both commands — for the intent signature and for the local ownership check alike — the way
|
|
392
|
+
`ff-cli sign` and `ff-cli status` already accept them. Passing both is an error rather than a precedence
|
|
393
|
+
rule, since a precedence rule silently ignores one of the two keys you supplied. A `--key` that is
|
|
394
|
+
present but empty (what `--key "$SIGNING_KEY"` becomes when the variable is unset), and a `--key-file`
|
|
395
|
+
that is missing, unreadable, or holds nothing, are all **rejected**, never
|
|
370
396
|
treated as absent: falling back to the configured key would authorize the write under an identity you did
|
|
371
397
|
not choose, and a delete cannot be taken back. `unpublish` derives the identity before it looks the
|
|
372
398
|
playlist up, so a bad credential fails before you are asked to confirm anything, and it prints
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feralfile/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "CLI for building, signing, and playing DP-1 playlists of digital art on Feral File devices",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"chalk": "^4.1.2",
|
|
66
66
|
"commander": "^11.1.0",
|
|
67
67
|
"dotenv": "^16.3.1",
|
|
68
|
-
"dp1-js": "2.
|
|
68
|
+
"dp1-js": "2.4.1",
|
|
69
69
|
"ff1": "^1.0.0",
|
|
70
70
|
"fuzzysort": "^3.1.0",
|
|
71
71
|
"viem": "^2.56.3"
|