@feralfile/cli 3.0.0 → 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 +20 -17
- package/config.json.example +1 -1
- package/dist/index.js +10 -2
- package/dist/package.json +5 -2
- package/dist/src/commands/build.js +1 -1
- package/dist/src/commands/device.js +84 -39
- package/dist/src/commands/enrich.js +2 -2
- package/dist/src/commands/find.js +71 -37
- 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/play.js +39 -26
- package/dist/src/commands/report.js +251 -0
- package/dist/src/commands/setup.js +1 -1
- package/dist/src/config.js +162 -6
- 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-network.js +129 -0
- package/dist/src/utilities/device-upsert.js +1 -1
- 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/neort-marketplace.js +3 -3
- package/dist/src/utilities/playlist-builder.js +3 -3
- package/dist/src/utilities/playlist-cast.js +6 -6
- package/dist/src/utilities/playlist-source.js +7 -7
- package/dist/src/utilities/raster-client.js +1 -1
- 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/docs/CONFIGURATION.md +270 -28
- package/docs/EXAMPLES.md +31 -31
- package/docs/PROJECT_SPEC.md +32 -19
- package/docs/README.md +113 -36
- package/package.json +5 -2
package/docs/PROJECT_SPEC.md
CHANGED
|
@@ -15,7 +15,7 @@ It is derived from the behavior and interfaces implemented in this repository as
|
|
|
15
15
|
- Project: `ff-cli`
|
|
16
16
|
- Type: Node.js CLI
|
|
17
17
|
- System role: a control and integration surface for FF1 and DP-1 workflows
|
|
18
|
-
- Primary purpose: turn resolved sources or structured parameters into valid DP-1 playlists deterministically, then validate them and optionally sign, publish, and
|
|
18
|
+
- Primary purpose: turn resolved sources or structured parameters into valid DP-1 playlists deterministically, then validate them and optionally sign, publish, and display them through FF1 and feed paths
|
|
19
19
|
|
|
20
20
|
There is no built-in natural-language interface and no model orchestration. Natural language belongs in the user's coding agent (Claude Code, Codex), which drives ff-cli's deterministic commands via the `ff-control` skill. ff-cli requires no LLM API key.
|
|
21
21
|
|
|
@@ -25,11 +25,11 @@ In the Feral File architecture bands, `ff-cli` sits primarily in the presentatio
|
|
|
25
25
|
|
|
26
26
|
The CLI supports the broader Feral File goal of making it effortless to live with digital art every day.
|
|
27
27
|
|
|
28
|
-
Its value is reducing friction in the publish-to-
|
|
28
|
+
Its value is reducing friction in the publish-to-display path:
|
|
29
29
|
|
|
30
30
|
- assemble playlists quickly
|
|
31
31
|
- keep outputs DP-1 conformant
|
|
32
|
-
- make FF1
|
|
32
|
+
- make displaying on FF1 and feed publishing easier to exercise
|
|
33
33
|
- provide deterministic tooling that agents and scripts can drive reliably
|
|
34
34
|
|
|
35
35
|
The CLI should strengthen the Gold Path, not invent a parallel product model.
|
|
@@ -39,7 +39,7 @@ The CLI should strengthen the Gold Path, not invent a parallel product model.
|
|
|
39
39
|
Current likely users:
|
|
40
40
|
|
|
41
41
|
- internal engineers working on FF1, DP-1, feed, and device flows
|
|
42
|
-
- operators and launch teammates validating publish-to-
|
|
42
|
+
- operators and launch teammates validating publish-to-display behavior
|
|
43
43
|
- advanced users or partners building and testing playlists
|
|
44
44
|
- developers using the CLI as a reference implementation for DP-1 and FF1 command flows
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ Primary use cases:
|
|
|
50
50
|
- validate or verify a local or hosted DP-1 playlist
|
|
51
51
|
- sign a playlist with an Ed25519 key
|
|
52
52
|
- publish a validated playlist to a configured feed server
|
|
53
|
-
-
|
|
53
|
+
- display a playlist or direct media URL on a configured FF1 device
|
|
54
54
|
- manage local config and FF1 SSH access
|
|
55
55
|
- exercise compatibility checks against FF1 OS versions before risky commands
|
|
56
56
|
|
|
@@ -73,11 +73,11 @@ Use these terms consistently:
|
|
|
73
73
|
|
|
74
74
|
The current code and internal context imply these practical goals:
|
|
75
75
|
|
|
76
|
-
- Make playlist creation and
|
|
76
|
+
- Make playlist creation and display testing fast enough for daily use.
|
|
77
77
|
- Keep the path from intent to valid DP-1 output deterministic and inspectable.
|
|
78
78
|
- Preserve openness by relying on DP-1 as the compatibility layer instead of a CLI-specific format.
|
|
79
|
-
- Support FF1 as the reference
|
|
80
|
-
- Serve as a reference surface for publish, verify, and
|
|
79
|
+
- Support FF1 as the reference display target without making correctness depend on proprietary-only infrastructure.
|
|
80
|
+
- Serve as a reference surface for publish, verify, and display flows used elsewhere in the Feral File stack.
|
|
81
81
|
|
|
82
82
|
### Current behavior
|
|
83
83
|
|
|
@@ -103,16 +103,22 @@ Based on the code today, the CLI is responsible for:
|
|
|
103
103
|
|
|
104
104
|
- loading configuration from `config.json`, `.env`, and defaults, with `config.json` taking precedence
|
|
105
105
|
- resolving marketplace URLs, on-chain coordinates, and wallet addresses into playlists (`find`)
|
|
106
|
-
- running feed fetches, address queries, contract-based NFT queries, domain resolution, playlist building, verification, publishing, and
|
|
106
|
+
- running feed fetches, address queries, contract-based NFT queries, domain resolution, playlist building, verification, publishing, and display
|
|
107
107
|
- supporting a deterministic build path from structured JSON (`build`)
|
|
108
108
|
- building DP-1 playlist envelopes from NFT metadata or direct media URLs via `dp1-js` document and leaf builders (so constructed playlists stay schema-conformant), including inline Ref Manifests on items that carry token-source metadata
|
|
109
109
|
- validating and verifying playlist structure and signatures
|
|
110
110
|
- signing playlists when a private key is configured
|
|
111
111
|
- publishing validated playlists to configured feed servers
|
|
112
|
-
- discovering configured FF1 devices and sending playlists or direct media
|
|
113
|
-
- pairing
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
- discovering configured FF1 devices and sending playlists or direct media display requests
|
|
113
|
+
- pairing as an FF1 controller through owner-initiated registration: the CLI
|
|
114
|
+
shows a QR of its public keys and a one-time secret, an owner adds it in the
|
|
115
|
+
Feral File app, and the CLI pins the FF1 profile only from the owner's sealed
|
|
116
|
+
answer over the relay pairing channel, then enrolls (any relayer topic the
|
|
117
|
+
answer carries goes to the operating system credential vault)
|
|
118
|
+
- pairing an FF1 without secure control by its signed relayer topic through
|
|
119
|
+
a short-lived end-to-end encrypted mobile handoff, authenticating the CLI key
|
|
120
|
+
with a user-compared security check, and storing it in the operating system
|
|
121
|
+
credential vault
|
|
116
122
|
- falling back from unreachable LAN delivery to the shared FF1 relayer with an
|
|
117
123
|
explicit terminal notice, so sandboxed and cross-subnet control remains
|
|
118
124
|
observable to the mobile app
|
|
@@ -181,7 +187,7 @@ substituted for a chain the row already named.
|
|
|
181
187
|
|
|
182
188
|
### Delivery
|
|
183
189
|
|
|
184
|
-
- `
|
|
190
|
+
- `display` (handles playlist files, playlist URLs, and media URLs; `play` is a hidden alias kept for existing scripts and agents)
|
|
185
191
|
- `fetch`
|
|
186
192
|
- `publish` (`--replace` sends an owner-bound replacement instead of a create)
|
|
187
193
|
- `unpublish`
|
|
@@ -252,6 +258,13 @@ replacement.
|
|
|
252
258
|
|
|
253
259
|
- `ssh enable|disable`
|
|
254
260
|
|
|
261
|
+
### Support
|
|
262
|
+
|
|
263
|
+
- `report` (posts a problem report to Feral File's support-logs, which mints the `Support [<id>]`
|
|
264
|
+
reference and a token scoped to that one report; with `-d`, the Art Computer is then asked to upload
|
|
265
|
+
its logs under the same reference with its existing `uploadLogs` command. ff-cli holds no support
|
|
266
|
+
credential, since a key in a public package is public)
|
|
267
|
+
|
|
255
268
|
### Inline Ref Manifests
|
|
256
269
|
|
|
257
270
|
Playlist items carry a full Ref Manifest inline (DP-1 Playlist Extension §3.6)
|
|
@@ -417,7 +430,7 @@ and nothing else. `enrich` is the repair path for those.
|
|
|
417
430
|
- The playlist is validated before lookup and the enriched candidate is
|
|
418
431
|
validated before writing. On either failure the diagnostics are printed and
|
|
419
432
|
the file is left untouched, rather than replaced with a document that sign,
|
|
420
|
-
publish, and
|
|
433
|
+
publish, and display would each reject later.
|
|
421
434
|
|
|
422
435
|
## Deterministic-first behavior
|
|
423
436
|
|
|
@@ -494,16 +507,16 @@ Important constraints from the broader FF system:
|
|
|
494
507
|
### Current behavior
|
|
495
508
|
|
|
496
509
|
- reliability matters more than novelty
|
|
497
|
-
- the publish-to-
|
|
498
|
-
- the CLI should help prove the path from canonical JSON to FF1
|
|
510
|
+
- the publish-to-display path should stay simple and testable
|
|
511
|
+
- the CLI should help prove the path from canonical JSON to FF1 display
|
|
499
512
|
- compatibility checks fail clearly when incompatibility is confirmed
|
|
500
513
|
- if FF1 OS version cannot be determined during preflight, the CLI currently proceeds and logs a warning instead of hard-blocking the command
|
|
501
514
|
|
|
502
515
|
### Design direction
|
|
503
516
|
|
|
504
517
|
- reliability matters more than novelty
|
|
505
|
-
- the publish-to-
|
|
506
|
-
- the CLI should help prove the path from canonical JSON to FF1
|
|
518
|
+
- the publish-to-display path should stay simple and testable
|
|
519
|
+
- the CLI should help prove the path from canonical JSON to FF1 display
|
|
507
520
|
- compatibility messaging should remain explicit enough that operators can distinguish confirmed incompatibility from uncertain device state
|
|
508
521
|
|
|
509
522
|
## Code and design constraints
|
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
|
|
|
@@ -37,7 +37,7 @@ Add `--force` to replace it anyway. See
|
|
|
37
37
|
|
|
38
38
|
See the full configuration reference here: `./CONFIGURATION.md`.
|
|
39
39
|
|
|
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 `
|
|
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`).
|
|
41
41
|
|
|
42
42
|
Manual config path:
|
|
43
43
|
|
|
@@ -55,7 +55,7 @@ ff-cli config validate
|
|
|
55
55
|
"privateKey": "your_ed25519_private_key_hex_or_base64_here",
|
|
56
56
|
"curatorName": "YOUR_CURATOR_NAME"
|
|
57
57
|
},
|
|
58
|
-
"feed": { "baseURLs": ["https://
|
|
58
|
+
"feed": { "baseURLs": ["https://feed.feralfile.com/api/v1"] },
|
|
59
59
|
"ff1Devices": {
|
|
60
60
|
"devices": [
|
|
61
61
|
{
|
|
@@ -77,8 +77,8 @@ See `./CONFIGURATION.md` for environment variable mappings.
|
|
|
77
77
|
# Resolve a URL, on-chain coords, or wallet and build a playlist
|
|
78
78
|
ff-cli find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak -o playlist.json
|
|
79
79
|
|
|
80
|
-
# Build and
|
|
81
|
-
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
|
|
82
82
|
|
|
83
83
|
# Build from pre-structured params (JSON file or stdin)
|
|
84
84
|
ff-cli build examples/params-example.json -o playlist.json
|
|
@@ -88,20 +88,20 @@ For development in this repo:
|
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
npm run build
|
|
91
|
-
node dist/index.js find https://objkt.com/tokens/hicetnunc/111068 --
|
|
91
|
+
node dist/index.js find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
If you're running from source without a build, use:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --
|
|
97
|
+
npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
## Two ways to build a playlist
|
|
101
101
|
|
|
102
102
|
ff-cli is deterministic: no chat, no model orchestration. There are two entry points for assembling a playlist.
|
|
103
103
|
|
|
104
|
-
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.
|
|
105
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.
|
|
106
106
|
|
|
107
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.
|
|
@@ -120,16 +120,16 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
120
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`
|
|
121
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
|
|
122
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
|
|
123
|
-
- `
|
|
124
|
-
- Any URL that does not load as a playlist is sent as a single item: a media file
|
|
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
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`
|
|
127
|
-
- Options: `-d, --device <name>`, `--web` (
|
|
128
|
-
- `find <input>` – Resolve a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a
|
|
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
|
|
129
129
|
- Sources: Art Blocks, Objkt, fxhash, OpenSea, SuperRare, Feral File, Neort, Verse, raw on-chain coordinates, and wallet addresses
|
|
130
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)
|
|
131
131
|
- Verse forms: `/items/ethereum/{contract}/{tokenId}` and `/series/{slug}`
|
|
132
|
-
- Options: `-o, --output <path>`, `-l, --limit <n>`,
|
|
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
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
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
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
|
|
@@ -142,12 +142,19 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
142
142
|
- Options: `-s, --server <index>`, `-o, --output <file>`. There is no `--key`: a fetch is a plain read and signs nothing
|
|
143
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
|
|
144
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)
|
|
145
148
|
- `ssh <enable|disable>` – Manage SSH access on an FF1 device
|
|
146
149
|
- Options: `-d, --device <name>`, `--pubkey <path>`, `--ttl <duration>`
|
|
147
150
|
- `device list` – List all configured FF1 devices
|
|
148
151
|
- `device add` – Add a device with mDNS or manual `--host`, `--name`, and `--id`
|
|
149
152
|
- Options: `--host <host>`, `--name <name>`, `--id <id>`
|
|
150
|
-
- `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)
|
|
151
158
|
- `device rename <name> <new-name>` – Rename a configured FF1 device (host, API key, and default status are untouched)
|
|
152
159
|
- `device remove <name>` – Remove a configured FF1 device
|
|
153
160
|
- `device default <name>` – Set the default FF1 device (used when `-d` is omitted)
|
|
@@ -157,16 +164,16 @@ Both paths run the same deterministic pipeline: fetch metadata, assemble a DP-1
|
|
|
157
164
|
|
|
158
165
|
### Find an artwork
|
|
159
166
|
|
|
160
|
-
`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.
|
|
161
168
|
|
|
162
169
|
```bash
|
|
163
|
-
# Paste a URL and
|
|
164
|
-
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
|
|
165
172
|
|
|
166
173
|
# Tezos / hic et nunc via Objkt (the alias resolves to a KT1 contract)
|
|
167
|
-
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --
|
|
174
|
+
ff-cli find https://objkt.com/tokens/hicetnunc/111068 --display
|
|
168
175
|
|
|
169
|
-
# Save without
|
|
176
|
+
# Save without displaying
|
|
170
177
|
ff-cli find ethereum:0xababababab20053426ad1c782de9ea8444358070:5001410 -o send-receive.json
|
|
171
178
|
|
|
172
179
|
# Wallet address: one work by the artist at that address; cap the number of tokens with --limit
|
|
@@ -242,7 +249,7 @@ reported rather than guessed at, and items carrying an external `ref` are left
|
|
|
242
249
|
alone because `ref` outranks an inline manifest at the device. The playlist is
|
|
243
250
|
validated before and after; on failure the file is left untouched. Enriching
|
|
244
251
|
changes the document, so the signature is removed and the playlist must be
|
|
245
|
-
re-signed before
|
|
252
|
+
re-signed before displaying:
|
|
246
253
|
|
|
247
254
|
```bash
|
|
248
255
|
ff-cli enrich playlist.json --assume-ethereum && ff-cli sign playlist.json
|
|
@@ -251,7 +258,7 @@ ff-cli enrich playlist.json --assume-ethereum && ff-cli sign playlist.json
|
|
|
251
258
|
That role is fine for casting. Use `ff-cli sign playlist.json -r curator` if the enriched playlist is
|
|
252
259
|
also going to a feed.
|
|
253
260
|
|
|
254
|
-
### Validate, sign, and
|
|
261
|
+
### Validate, sign, and display
|
|
255
262
|
|
|
256
263
|
```bash
|
|
257
264
|
# Optional explicit validation (build flows already validate)
|
|
@@ -260,30 +267,30 @@ npm run dev -- validate playlist.json
|
|
|
260
267
|
# Sign (uses key and role from config, or overrides via --key / --role)
|
|
261
268
|
npm run dev -- sign playlist.json -o signed.json
|
|
262
269
|
|
|
263
|
-
#
|
|
264
|
-
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
|
|
265
272
|
|
|
266
|
-
#
|
|
267
|
-
npm run dev --
|
|
273
|
+
# Display on a specific named device
|
|
274
|
+
npm run dev -- display playlist.json -d "Living Room Display"
|
|
268
275
|
|
|
269
|
-
# The
|
|
276
|
+
# The display path performs a compatibility preflight check against the target FF1.
|
|
270
277
|
# If the device reports an unsupported FF1 OS version, the command fails with
|
|
271
278
|
# a clear version message before any cast request is sent.
|
|
272
279
|
# It also retries transient local-network errors (for example intermittent
|
|
273
280
|
# mDNS/Wi-Fi resolver failures) with a short backoff before returning a final error.
|
|
274
281
|
|
|
275
|
-
#
|
|
276
|
-
npm run dev --
|
|
282
|
+
# Display a hosted DP-1 playlist
|
|
283
|
+
npm run dev -- display "https://cdn.example.com/playlist.json"
|
|
277
284
|
|
|
278
|
-
#
|
|
279
|
-
npm run dev --
|
|
280
|
-
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/"
|
|
281
288
|
|
|
282
|
-
#
|
|
283
|
-
npm run dev --
|
|
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/"
|
|
284
291
|
|
|
285
292
|
# Skip signature verification only if you must send a non-conformant payload (not recommended)
|
|
286
|
-
npm run dev --
|
|
293
|
+
npm run dev -- display playlist.json --skip-verify
|
|
287
294
|
```
|
|
288
295
|
|
|
289
296
|
### Owner remote maintenance
|
|
@@ -306,9 +313,47 @@ ff-cli ssh enable --pubkey ~/.ssh/id_ed25519.pub --ttl 30m -d "Living Room Displ
|
|
|
306
313
|
# Disable SSH access
|
|
307
314
|
ff-cli ssh disable -d "Living Room Display"
|
|
308
315
|
|
|
309
|
-
# `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`.
|
|
310
317
|
```
|
|
311
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
|
|
336
|
+
```
|
|
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
|
+
|
|
312
357
|
### Publish to feed server
|
|
313
358
|
|
|
314
359
|
```bash
|
|
@@ -457,6 +502,38 @@ ff-cli device remove kitchen
|
|
|
457
502
|
ff-cli device default office
|
|
458
503
|
```
|
|
459
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
|
+
|
|
460
537
|
Setup preserves existing devices when adding new ones. See selection rules and examples in `./CONFIGURATION.md`.
|
|
461
538
|
|
|
462
539
|
### Playlist signing (optional)
|
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",
|