@feralfile/cli 2.6.0 → 2.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/config.json.example +1 -1
- package/dist/index.js +4 -0
- package/dist/package.json +1 -1
- package/dist/src/commands/fetch.js +89 -0
- package/dist/src/commands/find.js +15 -75
- package/dist/src/commands/helpers/feed-server.js +105 -0
- package/dist/src/commands/helpers/playlist-display.js +3 -36
- package/dist/src/commands/publish.js +20 -65
- package/dist/src/commands/sign.js +22 -1
- package/dist/src/commands/ssh.js +1 -34
- package/dist/src/commands/unpublish.js +103 -0
- package/dist/src/utilities/feed-fetcher.js +1 -0
- package/dist/src/utilities/feed-mutation.js +371 -0
- package/dist/src/utilities/functions.js +4 -36
- package/dist/src/utilities/index.js +2 -34
- package/dist/src/utilities/nft-indexer.js +127 -30
- package/dist/src/utilities/playlist-builder.js +1 -0
- package/dist/src/utilities/playlist-cast.js +1 -34
- package/dist/src/utilities/playlist-publisher.js +382 -148
- package/dist/src/utilities/playlist-send.js +2 -35
- package/dist/src/utilities/playlist-signer.js +32 -38
- package/dist/src/utilities/playlist-signing-role.js +1 -0
- package/dist/src/utilities/playlist-unpublisher.js +159 -0
- package/dist/src/utilities/playlist-verifier.js +4 -37
- package/docs/CONFIGURATION.md +21 -3
- package/docs/EXAMPLES.md +199 -0
- package/docs/PROJECT_SPEC.md +43 -1
- package/docs/README.md +48 -2
- package/package.json +1 -1
package/docs/PROJECT_SPEC.md
CHANGED
|
@@ -159,6 +159,16 @@ Today the CLI groups into these workflow areas:
|
|
|
159
159
|
- `find`
|
|
160
160
|
- `build`
|
|
161
161
|
|
|
162
|
+
An item's `provenance.contract` is read from the indexer row, not inferred. Each
|
|
163
|
+
row carries the token's own `standard` (`erc721`, `erc1155`, `fa2`) and its
|
|
164
|
+
CAIP-2 `chain` (`eip155:1`, `tezos:mainnet`), and both are written through. The
|
|
165
|
+
client's own detection sees a chain name and an address and nothing else, so it
|
|
166
|
+
answers `erc721` for every EVM contract; it applies only to rows that carry no
|
|
167
|
+
standard, and to the CID a lookup by coordinate must build before the indexer
|
|
168
|
+
has been asked anything. A row naming a chain DP-1 has no word for is recorded
|
|
169
|
+
as `other` — the item is still built, and the client's guess is never
|
|
170
|
+
substituted for a chain the row already named.
|
|
171
|
+
|
|
162
172
|
### DP-1 output integrity
|
|
163
173
|
|
|
164
174
|
- `verify`
|
|
@@ -168,7 +178,39 @@ Today the CLI groups into these workflow areas:
|
|
|
168
178
|
### Delivery
|
|
169
179
|
|
|
170
180
|
- `play` (handles playlist files, playlist URLs, and media URLs)
|
|
171
|
-
- `
|
|
181
|
+
- `fetch`
|
|
182
|
+
- `publish` (`--replace` sends an owner-bound replacement instead of a create)
|
|
183
|
+
- `unpublish`
|
|
184
|
+
|
|
185
|
+
### Owner-bound feed mutations
|
|
186
|
+
|
|
187
|
+
Creating on a feed is open; changing or removing something is not. A feed accepts a `POST` from any
|
|
188
|
+
document that is self-signed by a curator it declares, and the signer becomes that resource's owner. A
|
|
189
|
+
`PUT` and a `DELETE` are authorized instead against the **stored** document's `curators[]`, and neither
|
|
190
|
+
accepts an API key. Both therefore carry a signed intent — `{ action, target: { type, id, slug },
|
|
191
|
+
created, signatures }`, with a `payloadHash` for replace — whose `created` must fall inside the feed's
|
|
192
|
+
freshness window; the intent exists because a document's own signatures are public via `GET` and could
|
|
193
|
+
otherwise be replayed to roll a resource back. The CLI proves ownership before it signs anything: it
|
|
194
|
+
`GET`s the stored playlist and requires the configured key to be both named in the stored `curators[]`
|
|
195
|
+
and the signer of a cryptographically valid `curator`-role signature over that stored document. Being
|
|
196
|
+
named is a claim; the owner-role signature is the proof, and checking only the claim is what would let a
|
|
197
|
+
legacy `agent`-signed document pass preflight and come back as a `403` reported as a missing
|
|
198
|
+
declaration — the one thing not wrong with it. The three local refusals are therefore distinct: no
|
|
199
|
+
declared curators, declared but never proved (terminal, and the shape of anything published while the
|
|
200
|
+
default role was `agent`), and proved by a key other than this one. A `403` that survives all three is
|
|
201
|
+
reported as the feed's own judgement, not as a missing declaration. This is also why `publish` refuses to
|
|
202
|
+
create a document whose declared curator did not sign in the `curator` role: that refusal is
|
|
203
|
+
recoverable and the resulting stored playlist would not be. A delete tombstones the id, so the operation
|
|
204
|
+
is final and the id is not reusable.
|
|
205
|
+
|
|
206
|
+
Editing a published playlist therefore has a required shape: `fetch` the stored document, change it,
|
|
207
|
+
re-sign with `sign --replace-signatures`, then `publish --replace`. `fetch` exists because every other
|
|
208
|
+
way of producing that starting file is wrong: `find` and `build` mint a new identity, and `verify` only
|
|
209
|
+
reports on a document rather than saving one. Signing appends by default, which is
|
|
210
|
+
correct only while the signed content is unchanged; an edit invalidates every entry the document
|
|
211
|
+
carries, including the feed's own co-signature, and the envelope check refuses to persist a mix of stale
|
|
212
|
+
and fresh. The fresh mode drops them all — the feed re-appends its signature after it verifies the
|
|
213
|
+
replacement.
|
|
172
214
|
|
|
173
215
|
### Device operations
|
|
174
216
|
|
package/docs/README.md
CHANGED
|
@@ -97,7 +97,8 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
97
97
|
- `validate <file-or-url>` – Validate playlist structure only
|
|
98
98
|
- `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
99
|
- `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
|
|
100
|
+
- Options: `-k, --key <privateKey>`, `-r, --role <role>`, `-o, --output <file>`, `--replace-signatures`
|
|
101
|
+
- 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`
|
|
101
102
|
- `play <source>` – Play a playlist file, playlist URL, or media URL on an FF1 device (runs `verify` before sending; only the CLI-synthesized media URL fallback is auto-signed when a signing key is configured; use `--skip-verify` to bypass the gate)
|
|
102
103
|
- Options: `-d, --device <name>`, `--skip-verify` (skip signature verification; not recommended)
|
|
103
104
|
- `find <input>` – Resolve a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a playable DP-1 playlist
|
|
@@ -109,7 +110,11 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
109
110
|
- 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
|
|
110
111
|
- Options: `-o, --output <file>`, `--force` (replace existing manifests), `--assume-ethereum`, `-v, --verbose`
|
|
111
112
|
- `publish <file>` – Publish a playlist to a feed server (runs `verify` before upload and rejects unsigned or broken playlists)
|
|
112
|
-
- Options: `-s, --server <index>` (server index if multiple configured)
|
|
113
|
+
- 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)
|
|
114
|
+
- `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`
|
|
115
|
+
- Options: `-s, --server <index>`, `-o, --output <file>`
|
|
116
|
+
- `unpublish <id-or-url>` – Delete a playlist from a feed server. Accepts a playlist id, a slug, or a feed URL. Requires the configured key to be an owner of the stored playlist, and confirms before deleting
|
|
117
|
+
- Options: `-s, --server <index>`, `-y, --yes` (skip the confirmation)
|
|
113
118
|
- `ssh <enable|disable>` – Manage SSH access on an FF1 device
|
|
114
119
|
- Options: `-d, --device <name>`, `--pubkey <path>`, `--ttl <duration>`
|
|
115
120
|
- `device list` – List all configured FF1 devices
|
|
@@ -323,6 +328,47 @@ is configured. An `apiKey` left over in an existing config is ignored.
|
|
|
323
328
|
}
|
|
324
329
|
```
|
|
325
330
|
|
|
331
|
+
### Change or remove a published playlist
|
|
332
|
+
|
|
333
|
+
A feed's `PUT` and `DELETE` are owner-bound. Neither takes an API key, and neither is authorized by the
|
|
334
|
+
document alone: both carry a short-lived **intent** that ff-cli signs with the configured key in the
|
|
335
|
+
`curator` role. Only a key the *stored* playlist names in `curators[]` can authorize either one, which is
|
|
336
|
+
why `publish` refuses to create a playlist that carries no owner-role signature — such a document can be
|
|
337
|
+
neither replaced nor deleted, ever.
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Edit a published playlist: fetch it, change the file, re-sign fresh, then replace.
|
|
341
|
+
ff-cli fetch <id> -o playlist.json -s 0
|
|
342
|
+
# ...edit the title, items, or metadata...
|
|
343
|
+
ff-cli sign playlist.json -r curator --replace-signatures
|
|
344
|
+
ff-cli publish playlist.json --replace -s 0
|
|
345
|
+
|
|
346
|
+
# Delete a published playlist (id, slug, or feed URL all work).
|
|
347
|
+
ff-cli unpublish 97595a2f-a790-477c-aa42-b4f2ec9f1e3b -s 0
|
|
348
|
+
ff-cli unpublish https://feed.example.com/api/v1/playlists/97595a2f-a790-477c-aa42-b4f2ec9f1e3b
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`--replace-signatures` is not optional on that path. The document you fetched carries the curator's
|
|
352
|
+
signature and the feed's, both taken over the pre-edit content; a plain `ff-cli sign` appends to them and
|
|
353
|
+
the envelope check then refuses to write a file with stale entries in it. Signing fresh drops both — the
|
|
354
|
+
feed re-appends its own after it verifies the replacement.
|
|
355
|
+
|
|
356
|
+
`--replace` keeps identity fixed: `id`, `slug`, and `created` must equal the stored document's, and the
|
|
357
|
+
`curators[]` owner set may not change. Editing a published playlist therefore means editing the document
|
|
358
|
+
you published — re-running `find` or `build` mints a fresh id, slug, and `created`, which is a new
|
|
359
|
+
playlist rather than a replacement. A `publish` without `--replace` is never silently upgraded to a
|
|
360
|
+
replace; an id the feed already holds fails with a conflict, as before.
|
|
361
|
+
|
|
362
|
+
Both verbs prove ownership locally before signing anything. A key counts as an owner only when the
|
|
363
|
+
**stored** playlist names it in `curators[]` **and** carries its valid `curator`-role signature — being
|
|
364
|
+
named is a claim, signing in the owner role is the proof. A playlist that declares a curator but was
|
|
365
|
+
signed under another role (the shape of anything published while the default role was `agent`) can never
|
|
366
|
+
be replaced or deleted by anyone, and the CLI says so rather than sending a request the feed will refuse.
|
|
367
|
+
|
|
368
|
+
`unpublish` asks for confirmation first, showing the title and the server. `-y` skips it for scripts.
|
|
369
|
+
Deletion is final in a way worth knowing before you run it: the feed **tombstones** the id, so the
|
|
370
|
+
playlist cannot be restored and a later publish naming that id is refused. Build a new playlist instead.
|
|
371
|
+
|
|
326
372
|
### FF1 device management
|
|
327
373
|
|
|
328
374
|
```bash
|