@feralfile/cli 2.10.1 → 4.0.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/README.md +31 -23
- package/config.json.example +6 -1
- package/dist/index.js +10 -2
- package/dist/package.json +5 -2
- package/dist/src/commands/build.js +40 -0
- package/dist/src/commands/config.js +1 -0
- package/dist/src/commands/device.js +85 -40
- package/dist/src/commands/enrich.js +18 -23
- package/dist/src/commands/find.js +144 -106
- package/dist/src/commands/helpers/device-control.js +937 -0
- package/dist/src/commands/helpers/hidden-alias-help.js +37 -0
- package/dist/src/commands/helpers/playlist-display.js +2 -15
- package/dist/src/commands/helpers/prompt.js +85 -10
- package/dist/src/commands/play.js +162 -84
- package/dist/src/commands/report.js +251 -0
- package/dist/src/commands/setup.js +82 -7
- package/dist/src/commands/status.js +4 -1
- package/dist/src/config.js +256 -6
- package/dist/src/main.js +14 -5
- package/dist/src/utilities/control/codec.js +578 -0
- package/dist/src/utilities/control/control-errors.js +157 -0
- package/dist/src/utilities/control/controller-store.js +964 -0
- package/dist/src/utilities/control/jwk.js +141 -0
- package/dist/src/utilities/control/pairing-channel.js +52 -0
- package/dist/src/utilities/control/pairing.js +416 -0
- package/dist/src/utilities/control/registration.js +391 -0
- package/dist/src/utilities/control/relay-pairing-channel.js +191 -0
- package/dist/src/utilities/control/secure-transport.js +850 -0
- package/dist/src/utilities/control/strict-json.js +376 -0
- package/dist/src/utilities/device-lookup.js +36 -11
- package/dist/src/utilities/device-network.js +129 -0
- package/dist/src/utilities/device-upsert.js +1 -1
- package/dist/src/utilities/feral-file-artwork.js +2 -2
- package/dist/src/utilities/ff1-compatibility.js +87 -11
- package/dist/src/utilities/ff1-device.js +161 -78
- package/dist/src/utilities/ff1-relayer.js +34 -14
- package/dist/src/utilities/handoff-client.js +23 -9
- package/dist/src/utilities/index.js +270 -259
- package/dist/src/utilities/media-url.js +117 -0
- package/dist/src/utilities/neort-marketplace.js +3 -3
- package/dist/src/utilities/nft-indexer.js +12 -376
- package/dist/src/utilities/playlist-builder.js +11 -2
- package/dist/src/utilities/playlist-cast.js +6 -6
- package/dist/src/utilities/playlist-source.js +223 -34
- package/dist/src/utilities/raster-client.js +101 -14
- package/dist/src/utilities/relay-client.js +774 -0
- package/dist/src/utilities/ssh-access.js +192 -58
- package/dist/src/utilities/support-report.js +511 -0
- package/dist/src/utilities/temporary-channel.js +139 -0
- package/dist/src/utilities/token-item.js +428 -0
- package/dist/src/utilities/token-source-contract.js +55 -0
- package/dist/src/utilities/token-source-indexer.js +99 -0
- package/dist/src/utilities/token-source-raster.js +378 -0
- package/dist/src/utilities/token-source.js +48 -0
- package/docs/CONFIGURATION.md +331 -28
- package/docs/EXAMPLES.md +33 -33
- package/docs/PROJECT_SPEC.md +55 -36
- package/docs/README.md +145 -43
- package/package.json +5 -2
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ff-cli Documentation
|
|
2
2
|
|
|
3
|
-
ff-cli is a set of deterministic commands for building DP-1 (Display Protocol 1) playlists and
|
|
3
|
+
ff-cli is a set of deterministic commands for building DP-1 (Display Protocol 1) playlists and displaying them on an Art Computer. There's no built-in chat — natural language belongs in your coding agent (Claude Code, Codex), which drives ff-cli through the `ff-control` skill. No LLM API key required. This doc covers install, config, and day‑to‑day usage.
|
|
4
4
|
|
|
5
5
|
For project-level planning and future agentic work, see `./PROJECT_SPEC.md`.
|
|
6
6
|
|
|
@@ -29,12 +29,15 @@ ff-cli setup --non-interactive --key-file /run/secrets/ff-signing.key \
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
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.
|
|
32
|
+
than quietly generating a new identity for the machine. Setup also never replaces a signing key that is
|
|
33
|
+
already configured: `--generate-key`, `--key`, and `--key-file` refuse when a different key is in place,
|
|
34
|
+
because that key owns every playlist you published (`publish --replace` and `unpublish` accept only it).
|
|
35
|
+
Add `--force` to replace it anyway. See
|
|
33
36
|
[Where the signing key can come from](./CONFIGURATION.md#where-the-signing-key-can-come-from).
|
|
34
37
|
|
|
35
38
|
See the full configuration reference here: `./CONFIGURATION.md`.
|
|
36
39
|
|
|
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 `
|
|
40
|
+
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 `display` commands (override with `-d`).
|
|
38
41
|
|
|
39
42
|
Manual config path:
|
|
40
43
|
|
|
@@ -52,7 +55,7 @@ ff-cli config validate
|
|
|
52
55
|
"privateKey": "your_ed25519_private_key_hex_or_base64_here",
|
|
53
56
|
"curatorName": "YOUR_CURATOR_NAME"
|
|
54
57
|
},
|
|
55
|
-
"feed": { "baseURLs": ["https://
|
|
58
|
+
"feed": { "baseURLs": ["https://feed.feralfile.com/api/v1"] },
|
|
56
59
|
"ff1Devices": {
|
|
57
60
|
"devices": [
|
|
58
61
|
{
|
|
@@ -74,8 +77,8 @@ See `./CONFIGURATION.md` for environment variable mappings.
|
|
|
74
77
|
# Resolve a URL, on-chain coords, or wallet and build a playlist
|
|
75
78
|
ff-cli find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak -o playlist.json
|
|
76
79
|
|
|
77
|
-
# Build and
|
|
78
|
-
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --
|
|
80
|
+
# Build and display it on your FF1 in one step
|
|
81
|
+
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
79
82
|
|
|
80
83
|
# Build from pre-structured params (JSON file or stdin)
|
|
81
84
|
ff-cli build examples/params-example.json -o playlist.json
|
|
@@ -85,28 +88,29 @@ For development in this repo:
|
|
|
85
88
|
|
|
86
89
|
```bash
|
|
87
90
|
npm run build
|
|
88
|
-
node dist/index.js find https://objkt.com/tokens/hicetnunc/111068 --
|
|
91
|
+
node dist/index.js find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
89
92
|
```
|
|
90
93
|
|
|
91
94
|
If you're running from source without a build, use:
|
|
92
95
|
|
|
93
96
|
```bash
|
|
94
|
-
npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --
|
|
97
|
+
npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
95
98
|
```
|
|
96
99
|
|
|
97
100
|
## Two ways to build a playlist
|
|
98
101
|
|
|
99
102
|
ff-cli is deterministic: no chat, no model orchestration. There are two entry points for assembling a playlist.
|
|
100
103
|
|
|
101
|
-
1. **`ff-cli find "<input>"`** — resolve a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a
|
|
104
|
+
1. **`ff-cli find "<input>"`** — resolve a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a DP-1 playlist ready to display. Add `--display` to build and display in one step. This is the fastest path for a single artwork, a collection/series, or a wallet.
|
|
102
105
|
2. **`ff-cli build <params.json>`** — build from pre-structured `requirements` and `playlistSettings` (JSON file or stdin). Use this when you already have the contract, token IDs, feed names, or artwork ids and want explicit control over ordering, durations, and title.
|
|
103
106
|
|
|
104
107
|
Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1 envelope, and validate it. Validation rejects malformed data rather than passing it through. Natural language lives in your coding agent (Claude Code, Codex) via the `ff-control` skill, which translates a request into one of these two commands.
|
|
105
108
|
|
|
106
109
|
## Commands (cheat sheet)
|
|
107
110
|
|
|
108
|
-
- `build [params.json]` – Deterministic build from JSON or stdin
|
|
111
|
+
- `build [params.json]` – Deterministic build from JSON or stdin. `ff-cli build --help` lists the requirement types and settings
|
|
109
112
|
- Options: `-o, --output <file>`, `-v, --verbose`
|
|
113
|
+
- Exits 1 when any requirement finds nothing or fails, when named `tokenIds` do not all resolve, or when the send to `playlistSettings.deviceName` fails. The playlist is still saved when anything was collected; when nothing was, no file is written
|
|
110
114
|
- `validate <file-or-url>` – Validate playlist structure only
|
|
111
115
|
- `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
|
|
112
116
|
- `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[]`.
|
|
@@ -116,14 +120,19 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
116
120
|
- 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`
|
|
117
121
|
- **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
|
|
118
122
|
- `--replace-signatures` names each discarded entry by role and the last 8 characters of its `kid`, and verifies it against the document as it now stands. Your own earlier signature is replaced by the same run. Another key's entry that **still verifies** — which is every entry when the content was not edited, since the signing payload excludes `signatures` — is reported as removed; keep the previous file if you want it back. An entry that does **not** verify is reported as exactly that and no more: the command has only the document as it stands, so it cannot tell an edit from an entry that was never valid, and it does not guess. Roles are carried through as found (`agent`, `institution` and `licensor` are as valid as `curator`), and a flat legacy `signature` is reported as not checkable, since it carries no key or role at all
|
|
119
|
-
- `
|
|
120
|
-
-
|
|
121
|
-
- `
|
|
123
|
+
- `display <source>` – Display a playlist file, playlist URL, media URL, or web page on an FF1 device (`play` still works for existing scripts; runs `verify` before sending; only the CLI-synthesized media/web URL fallback is auto-signed when a signing key is configured; use `--skip-verify` to bypass the gate)
|
|
124
|
+
- Any URL that does not load as a playlist is sent as a single item: a media file displays as media, and anything else displays as a web page
|
|
125
|
+
- A URL that answers 404 or 410, a host that does not resolve, and a `.json` URL that does not load as a playlist are refused, so a missing page is never cast as an error page. `--web` checks the first two as well. Other failures to read the URL (a 403 from bot protection, a 5xx, a timeout) still send it, with a note, because the device fetches it itself
|
|
126
|
+
- Marketplace links (Art Blocks, OpenSea, objkt, Feral File, …) are sent to `find`, which resolves the artwork. `--web` displays the page itself instead
|
|
127
|
+
- Options: `-d, --device <name>`, `--web` (display the URL as a web page, skipping the marketplace redirect), `--skip-verify` (skip signature verification; not recommended)
|
|
128
|
+
- `find <input>` – Resolve a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a DP-1 playlist ready to display
|
|
122
129
|
- Sources: Art Blocks, Objkt, fxhash, OpenSea, SuperRare, Feral File, Neort, Verse, raw on-chain coordinates, and wallet addresses
|
|
123
130
|
- 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)
|
|
124
131
|
- Verse forms: `/items/ethereum/{contract}/{tokenId}` and `/series/{slug}`
|
|
125
|
-
- Options: `-o, --output <path>`, `-l, --limit <n>`,
|
|
126
|
-
-
|
|
132
|
+
- Options: `-o, --output <path>`, `-l, --limit <n>`, `--display`, `--no-display` (never cast; combine with `-y` to build and save without displaying; `-p`, `--play` and `--no-play` still work for existing scripts), `-d, --device <name>`, `--publish`, `-s, --server <index>`, `-y, --yes` (skip prompts; without `-o` or `--publish` it displays the playlist on a device, so add `--no-display` to skip casting), `--skip-verify`
|
|
133
|
+
- A wallet address picks one work by the artist at that address. For what a wallet owns, use `build` with a `query_address` requirement
|
|
134
|
+
- Without a terminal, pass `-y`: a question with no terminal to answer it fails with exit 1 and names the flag, rather than waiting or exiting 0 with nothing built
|
|
135
|
+
- `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. With the opt-in Raster token source no still image is supplied, so `enrich` adds artist and description but no thumbnail; thumbnails come from the default indexer source
|
|
127
136
|
- 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
|
|
128
137
|
- Options: `-o, --output <file>`, `--force` (replace existing manifests), `--assume-ethereum`, `-v, --verbose`
|
|
129
138
|
- `publish <file>` – Publish a playlist to a feed server (runs `verify` before upload and rejects unsigned or broken playlists)
|
|
@@ -133,12 +142,19 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
133
142
|
- Options: `-s, --server <index>`, `-o, --output <file>`. There is no `--key`: a fetch is a plain read and signs nothing
|
|
134
143
|
- `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
|
|
135
144
|
- 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)
|
|
145
|
+
- `report` – Send a problem report privately to Feral File support and print its reference, `Support [<id>]`. The text comes from `--message` or `--file`, or from stdin when neither is given
|
|
146
|
+
- Options: `--message <text>`, `--file <path>`, `--email <address>` (where support may reply: a bare ASCII address such as `name@example.com`, no display name or angle brackets), `-d, --device <name>` (adds the Art Computer's device ID, FF OS version and branch, and asks it to upload its logs under the same reference), `--dry-run` (print the JSON and send nothing)
|
|
147
|
+
- Exits 1 only when the report did not reach support. See [Report a problem to Feral File support](#report-a-problem-to-feral-file-support)
|
|
136
148
|
- `ssh <enable|disable>` – Manage SSH access on an FF1 device
|
|
137
149
|
- Options: `-d, --device <name>`, `--pubkey <path>`, `--ttl <duration>`
|
|
138
150
|
- `device list` – List all configured FF1 devices
|
|
139
151
|
- `device add` – Add a device with mDNS or manual `--host`, `--name`, and `--id`
|
|
140
152
|
- Options: `--host <host>`, `--name <name>`, `--id <id>`
|
|
141
|
-
- `device pair [name]` –
|
|
153
|
+
- `device pair [name]` – Pair ff-cli with an FF1: on FF OS with secure control, show a QR code that an owner scans with "Add controller" in the Feral File app, then enroll with the role the owner picked (needs internet; the FF1 must already have an owner); on FF OS without secure control, display a one-time code for "Pair with ff-cli" in the app and store the relayer access it sends
|
|
154
|
+
- Options: `--label <label>`, `--timeout <seconds>` (10–600, default 300), `--resume` (finish a pairing an owner already approved), `-y, --yes` (move a corrupt controller file aside without asking), `--topic-handoff` (force the one-time-code path, e.g. when the FF1 is not on this LAN)
|
|
155
|
+
- `device controllers [name]` – List an FF1's paired controllers, Play sites with a display grant, and pending requests (owner only)
|
|
156
|
+
- `device revoke <controller-id> [name]` – Revoke a controller or deny its pending request (owner only)
|
|
157
|
+
- `device unpair [name]` – Revoke ff-cli's own access on the FF1 (best effort), then delete its controller keys (always asks first; `-y, --yes` skips)
|
|
142
158
|
- `device rename <name> <new-name>` – Rename a configured FF1 device (host, API key, and default status are untouched)
|
|
143
159
|
- `device remove <name>` – Remove a configured FF1 device
|
|
144
160
|
- `device default <name>` – Set the default FF1 device (used when `-d` is omitted)
|
|
@@ -148,19 +164,20 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
148
164
|
|
|
149
165
|
### Find an artwork
|
|
150
166
|
|
|
151
|
-
`ff-cli find` resolves a URL, raw `chain:contract:tokenId`, or wallet address into a
|
|
167
|
+
`ff-cli find` resolves a URL, raw `chain:contract:tokenId`, or wallet address into a DP-1 playlist ready to display.
|
|
152
168
|
|
|
153
169
|
```bash
|
|
154
|
-
# Paste a URL and
|
|
155
|
-
ff-cli find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak --
|
|
170
|
+
# Paste a URL and display it on your FF1
|
|
171
|
+
ff-cli find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak --display
|
|
156
172
|
|
|
157
173
|
# Tezos / hic et nunc via Objkt (the alias resolves to a KT1 contract)
|
|
158
|
-
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --
|
|
174
|
+
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
159
175
|
|
|
160
|
-
# Save without
|
|
176
|
+
# Save without displaying
|
|
161
177
|
ff-cli find ethereum:0xababababab20053426ad1c782de9ea8444358070:5001410 -o send-receive.json
|
|
162
178
|
|
|
163
|
-
# Wallet address
|
|
179
|
+
# Wallet address: one work by the artist at that address; cap the number of tokens with --limit
|
|
180
|
+
# (for what a wallet owns, use build with a query_address requirement)
|
|
164
181
|
ff-cli find 0xaeE022552B539dB18297D7481b6D547C622488B3 -l 10 -o wallet.json
|
|
165
182
|
|
|
166
183
|
# Build and publish to a configured feed server
|
|
@@ -178,7 +195,7 @@ cat params.json | ff-cli build -o playlist.json
|
|
|
178
195
|
|
|
179
196
|
`params.json` should include `requirements` and optional `playlistSettings`. See `examples/params-example.json`. Each requirement has a `type`:
|
|
180
197
|
|
|
181
|
-
- `build_playlist` — `blockchain`, `contractAddress`, `tokenIds`, optional `quantity`
|
|
198
|
+
- `build_playlist` — `blockchain`, `contractAddress`, `tokenIds`, optional `quantity`. Omitting `tokenIds` asks for random tokens from the contract, which only the default indexer token source supports.
|
|
182
199
|
- `feral_file_artwork` — `artworkId` (a Feral File public artwork id or `/exhibitions/artwork/{id}` URL)
|
|
183
200
|
- `query_address` — `ownerAddress`, optional `quantity` (random selection)
|
|
184
201
|
- `fetch_feed` — `playlistName`, `quantity`
|
|
@@ -211,8 +228,8 @@ copied from another document, or written before inline manifests existed has no
|
|
|
211
228
|
and an empty tile in the app's grid — the app can draw a thumbnail from an
|
|
212
229
|
image, video, or SVG source, but not from a live HTML work.
|
|
213
230
|
|
|
214
|
-
`ff-cli enrich` fills that in from the
|
|
215
|
-
`provenance.contract`:
|
|
231
|
+
`ff-cli enrich` fills that in from the configured token source (the Feral
|
|
232
|
+
File indexer by default), keyed on each item's `provenance.contract`:
|
|
216
233
|
|
|
217
234
|
```bash
|
|
218
235
|
ff-cli enrich playlist.json --assume-ethereum
|
|
@@ -222,8 +239,8 @@ ff-cli enrich playlist.json --assume-ethereum --force # replace existing manif
|
|
|
222
239
|
|
|
223
240
|
`--assume-ethereum` is needed for most playlists. DP-1 records an EVM
|
|
224
241
|
coordinate as `chain: "evm"`, which names Ethereum, Polygon, Arbitrum,
|
|
225
|
-
Optimism, Base and Zora alike, and the
|
|
226
|
-
same word. Enrichment will not pick one for you, because the wrong member
|
|
242
|
+
Optimism, Base and Zora alike, and the token source's answer collapses back to
|
|
243
|
+
the same word. Enrichment will not pick one for you, because the wrong member
|
|
227
244
|
returns a different artwork's artist and still — into a document you then sign.
|
|
228
245
|
|
|
229
246
|
It writes `inlineManifest` and nothing else. Items with no
|
|
@@ -232,7 +249,7 @@ reported rather than guessed at, and items carrying an external `ref` are left
|
|
|
232
249
|
alone because `ref` outranks an inline manifest at the device. The playlist is
|
|
233
250
|
validated before and after; on failure the file is left untouched. Enriching
|
|
234
251
|
changes the document, so the signature is removed and the playlist must be
|
|
235
|
-
re-signed before
|
|
252
|
+
re-signed before displaying:
|
|
236
253
|
|
|
237
254
|
```bash
|
|
238
255
|
ff-cli enrich playlist.json --assume-ethereum && ff-cli sign playlist.json
|
|
@@ -241,7 +258,7 @@ ff-cli enrich playlist.json --assume-ethereum && ff-cli sign playlist.json
|
|
|
241
258
|
That role is fine for casting. Use `ff-cli sign playlist.json -r curator` if the enriched playlist is
|
|
242
259
|
also going to a feed.
|
|
243
260
|
|
|
244
|
-
### Validate, sign, and
|
|
261
|
+
### Validate, sign, and display
|
|
245
262
|
|
|
246
263
|
```bash
|
|
247
264
|
# Optional explicit validation (build flows already validate)
|
|
@@ -250,28 +267,43 @@ npm run dev -- validate playlist.json
|
|
|
250
267
|
# Sign (uses key and role from config, or overrides via --key / --role)
|
|
251
268
|
npm run dev -- sign playlist.json -o signed.json
|
|
252
269
|
|
|
253
|
-
#
|
|
254
|
-
npm run dev --
|
|
270
|
+
# Display on configured default device (verifies playlists; only media URL fallbacks may be auto-signed)
|
|
271
|
+
npm run dev -- display playlist.json
|
|
255
272
|
|
|
256
|
-
#
|
|
257
|
-
npm run dev --
|
|
273
|
+
# Display on a specific named device
|
|
274
|
+
npm run dev -- display playlist.json -d "Living Room Display"
|
|
258
275
|
|
|
259
|
-
# The
|
|
276
|
+
# The display path performs a compatibility preflight check against the target FF1.
|
|
260
277
|
# If the device reports an unsupported FF1 OS version, the command fails with
|
|
261
278
|
# a clear version message before any cast request is sent.
|
|
262
279
|
# It also retries transient local-network errors (for example intermittent
|
|
263
280
|
# mDNS/Wi-Fi resolver failures) with a short backoff before returning a final error.
|
|
264
281
|
|
|
265
|
-
#
|
|
266
|
-
npm run dev --
|
|
282
|
+
# Display a hosted DP-1 playlist
|
|
283
|
+
npm run dev -- display "https://cdn.example.com/playlist.json"
|
|
267
284
|
|
|
268
|
-
#
|
|
269
|
-
npm run dev --
|
|
285
|
+
# Display a media URL or any web page directly
|
|
286
|
+
npm run dev -- display "https://example.com/video.mp4"
|
|
287
|
+
npm run dev -- display "https://example.com/"
|
|
288
|
+
|
|
289
|
+
# Display a page on a marketplace site as a page (display would otherwise send it to find)
|
|
290
|
+
npm run dev -- display --web "https://feralfile.com/"
|
|
270
291
|
|
|
271
292
|
# Skip signature verification only if you must send a non-conformant payload (not recommended)
|
|
272
|
-
npm run dev --
|
|
293
|
+
npm run dev -- display playlist.json --skip-verify
|
|
273
294
|
```
|
|
274
295
|
|
|
296
|
+
### Owner remote maintenance
|
|
297
|
+
|
|
298
|
+
FFOS images containing `feral-tailscale` support opt-in persistent owner SSH on
|
|
299
|
+
port 2222 through Tailscale. It requires the owner's authorized computer and SSH
|
|
300
|
+
key and remains available independently of controld. The procedure checks for
|
|
301
|
+
a complete installation. If support is absent, check the normal firmware release
|
|
302
|
+
channel; enrollment waits until a version containing it is published and installed. Follow the [owner-access procedure](https://github.com/feral-file/ffos/blob/36e25cec84505271a7232d6936bb46a19fab5cfd/docs/OWNER_TAILSCALE.md)
|
|
303
|
+
for enrollment, diagnostics, revocation and validation. The normal `ff-cli ssh`
|
|
304
|
+
commands below continue to manage temporary support SSH on port 22; disabling
|
|
305
|
+
support SSH does not revoke persistent owner access.
|
|
306
|
+
|
|
275
307
|
### SSH access
|
|
276
308
|
|
|
277
309
|
```bash
|
|
@@ -281,14 +313,52 @@ ff-cli ssh enable --pubkey ~/.ssh/id_ed25519.pub --ttl 30m -d "Living Room Displ
|
|
|
281
313
|
# Disable SSH access
|
|
282
314
|
ff-cli ssh disable -d "Living Room Display"
|
|
283
315
|
|
|
284
|
-
# `ff-cli ssh` also performs the same FF1 OS compatibility preflight used by `
|
|
316
|
+
# `ff-cli ssh` also performs the same FF1 OS compatibility preflight used by `display`.
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
On newer FF OS, a claimed FF1 only accepts `ff-cli ssh` from a paired CLI: run
|
|
320
|
+
`ff-cli device pair "Living Room Display"` once (an owner scans the code with
|
|
321
|
+
"Add controller" in the Feral File app) before `ff-cli ssh enable|disable`. Unclaimed devices and older
|
|
322
|
+
FF OS do not require pairing. On FF OS with secure control, `ssh` needs the
|
|
323
|
+
**owner** role; a controller grant gets "This command needs owner access".
|
|
324
|
+
|
|
325
|
+
### Report a problem to Feral File support
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# Report what went wrong, with the Art Computer's logs
|
|
329
|
+
ff-cli report -d "Living Room Display" --message "The work displays black on the Art Computer; in a desktop browser it renders."
|
|
330
|
+
|
|
331
|
+
# From a file, with an address support can reply to
|
|
332
|
+
ff-cli report --file report.txt --email you@example.com
|
|
333
|
+
|
|
334
|
+
# Print exactly what would be sent, and send nothing
|
|
335
|
+
ff-cli report -d "Living Room Display" --file report.txt --dry-run
|
|
285
336
|
```
|
|
286
337
|
|
|
338
|
+
The report goes privately to Feral File support, the same pipeline the app's
|
|
339
|
+
Support & Feedback feeds, and ff-cli prints its reference in the app's form,
|
|
340
|
+
`Support [<id>]`. It carries the report text, `--email` when given, and the
|
|
341
|
+
ff-cli, OS and Node versions; nothing else leaves the computer. A report that
|
|
342
|
+
contains the configured signing key is refused.
|
|
343
|
+
|
|
344
|
+
With `-d`, the report also carries the Art Computer's device ID, FF OS version
|
|
345
|
+
and branch from its `/api/status`, and once the report is in, ff-cli asks that
|
|
346
|
+
Art Computer to upload its logs under the same reference, with a short-lived
|
|
347
|
+
key that works for that one report only. When ff-cli is paired with the Art
|
|
348
|
+
Computer under secure control, the request is a sealed envelope over the LAN,
|
|
349
|
+
or through the relayer when the LAN cannot reach it; otherwise it is a plain
|
|
350
|
+
LAN request. The Art Computer takes it at once and uploads in the background,
|
|
351
|
+
so it needs internet. On FF OS with secure control the request needs the **owner** role;
|
|
352
|
+
a controller grant gets "This command needs owner access". A log request the
|
|
353
|
+
Art Computer does not take prints a line and leaves the exit status at 0: the
|
|
354
|
+
report itself reached support. The exit status is 1 only when the report did
|
|
355
|
+
not, and the message says what to do next.
|
|
356
|
+
|
|
287
357
|
### Publish to feed server
|
|
288
358
|
|
|
289
359
|
```bash
|
|
290
|
-
# Publish to first configured feed server
|
|
291
|
-
npm run dev -- publish playlist.json
|
|
360
|
+
# Publish to the first configured feed server (-s is required when more than one is configured)
|
|
361
|
+
npm run dev -- publish playlist.json -s 0
|
|
292
362
|
|
|
293
363
|
# Publish to specific server (if multiple configured)
|
|
294
364
|
npm run dev -- publish playlist.json -s 0
|
|
@@ -432,6 +502,38 @@ ff-cli device remove kitchen
|
|
|
432
502
|
ff-cli device default office
|
|
433
503
|
```
|
|
434
504
|
|
|
505
|
+
### Pair ff-cli as a controller (secure control)
|
|
506
|
+
|
|
507
|
+
Current FF OS only accepts commands from controllers an owner added. Pair once
|
|
508
|
+
per FF1:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
ff-cli device pair office
|
|
512
|
+
# Add ff-cli to office
|
|
513
|
+
# On an owner's phone, open this FF1 in the Feral File app, choose "Add controller", and scan this code:
|
|
514
|
+
# ...QR code, and the same text to paste...
|
|
515
|
+
# ✓ An owner added ff-cli to Office FF1 as controller. Finishing the pairing...
|
|
516
|
+
# ✓ ff-cli is paired with office as controller.
|
|
517
|
+
|
|
518
|
+
ff-cli device pair office --resume # finish a pairing an owner already approved
|
|
519
|
+
ff-cli device controllers office # owner only: who can control this FF1
|
|
520
|
+
ff-cli device revoke <controller-id> office
|
|
521
|
+
ff-cli device unpair office # revoke this ff-cli on the FF1, delete its keys
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The owner's app sends its answer over the Feral File relay, so pairing needs an
|
|
525
|
+
internet connection, and the FF1 must already have an owner: ff-cli cannot be
|
|
526
|
+
an FF1's first owner. ff-cli accepts only an answer bound to the code it showed
|
|
527
|
+
and to the configured FF1. If nobody answers before `--timeout`, the pairing is
|
|
528
|
+
not completed; run it again for a new code (it keeps the same controller keys).
|
|
529
|
+
FF1s without secure control pair with a one-time code instead ("Pair with
|
|
530
|
+
ff-cli" in the app); `--topic-handoff` forces that path. After pairing, `display`, `ssh` and the version preflight are sent encrypted and
|
|
531
|
+
signed; the FF1 decides what each controller may do. `owner` covers every
|
|
532
|
+
command; `controller` covers displaying art and status but not `ssh` or controller
|
|
533
|
+
management. Keys are stored in `~/.config/ff-cli/controllers/<device ID>.json`
|
|
534
|
+
(mode 0600). See `./CONFIGURATION.md#secure-control-pairing` for the file,
|
|
535
|
+
roles, and error messages.
|
|
536
|
+
|
|
435
537
|
Setup preserves existing devices when adding new ones. See selection rules and examples in `./CONFIGURATION.md`.
|
|
436
538
|
|
|
437
539
|
### Playlist signing (optional)
|
|
@@ -442,7 +544,7 @@ Setup preserves existing devices when adding new ones. See selection rules and e
|
|
|
442
544
|
|
|
443
545
|
## Constraints
|
|
444
546
|
|
|
445
|
-
-
|
|
547
|
+
- Per-requirement caps: `build_playlist` up to 20 with `tokenIds` (100 without), `query_address` up to 100 when `quantity` is a number (omitted or `"all"` takes the whole wallet), `fetch_feed` up to 20. Requirements add up; there is no total cap beyond DP-1's 1024 items
|
|
446
548
|
- Per-source caps enforced in utilities
|
|
447
549
|
- Per-item timing: when no duration is given, video/audio items carry **no** `duration` and `display.loop: false`, so the player advances at end-of-stream (DP-1 §4.1) — the media plays its natural length. Static items use `defaultDuration` (10s unless configured). An explicit duration always wins.
|
|
448
550
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feralfile/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI for building, signing, and
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "CLI for building, signing, and displaying DP-1 playlists of digital art on Feral File devices",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/feral-file/ff-cli.git"
|
|
@@ -68,10 +68,13 @@
|
|
|
68
68
|
"dp1-js": "2.4.1",
|
|
69
69
|
"ff1": "^1.0.0",
|
|
70
70
|
"fuzzysort": "^3.1.0",
|
|
71
|
+
"jose": "6.2.12",
|
|
72
|
+
"qrcode-terminal": "0.12.0",
|
|
71
73
|
"viem": "^2.56.3"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
74
76
|
"@types/node": "^24.7.1",
|
|
77
|
+
"@types/qrcode-terminal": "0.12.2",
|
|
75
78
|
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
76
79
|
"@typescript-eslint/parser": "^8.46.0",
|
|
77
80
|
"esbuild": "^0.25.10",
|