@feralfile/cli 1.6.0 → 2.0.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.
@@ -21,35 +21,10 @@ npm run dev -- config show
21
21
 
22
22
  ## Top‑level fields
23
23
 
24
- - **defaultModel** (string)
25
- - The default AI model key to use. Must match a key under `models`.
26
- - Used by orchestration to pick API, timeouts, and model identifier.
27
-
28
24
  - **defaultDuration** (number, seconds)
29
25
  - Default per‑item display duration for media without an intrinsic runtime (images, code-based works). Falls back to 10s when unset.
30
26
  - Does NOT apply to video/audio items: with no explicit duration those are emitted without `duration` and with `display.loop: false`, so a DP-1 player advances at end-of-stream (§4.1) — the media plays its natural length.
31
27
 
32
- ## models
33
-
34
- Each key under `models` defines a model configuration used by the AI orchestrator.
35
-
36
- - `<modelName>.apiKey` (string): API key for the provider.
37
- - `<modelName>.baseURL` (string): Base API URL.
38
- - `<modelName>.model` (string): Model identifier (e.g., `grok-beta`, `gpt-4o`).
39
- - `<modelName>.availableModels` (string[], optional): Display/help only.
40
- - `<modelName>.timeout` (number, ms): HTTP timeout for requests.
41
- - `<modelName>.maxRetries` (number): Retry count for requests.
42
- - `<modelName>.temperature` (number): Generation temperature.
43
- - `<modelName>.maxTokens` (number): Token cap.
44
- - `<modelName>.supportsFunctionCalling` (boolean): Must be true; otherwise the CLI rejects the model.
45
-
46
- Environment variable helpers:
47
-
48
- - Anthropic (Claude): `ANTHROPIC_API_KEY`
49
- - Grok: `GROK_API_KEY`, `GROK_MODEL`, `GROK_API_BASE_URL`
50
- - OpenAI: `OPENAI_API_KEY`
51
- - Gemini: `GEMINI_API_KEY`
52
-
53
28
  ## browser
54
29
 
55
30
  Optional settings used where headless/browser‑like behavior is needed.
@@ -61,7 +36,7 @@ Optional settings used where headless/browser‑like behavior is needed.
61
36
 
62
37
  Used for signing DP‑1 playlists.
63
38
 
64
- - `playlist.privateKey` (string, Ed25519 private key in hex or base64): Used by the `sign` command to create DP-1 v1.1.0 multi-signatures. The `verify` command may derive the matching public key from this value (or `PLAYLIST_PRIVATE_KEY`) when you omit `--public-key`; **dp1-js applies that derived key only when verifying legacy flat `signature` strings**, not when checking `signatures[]` envelopes. If that derivation fails, `verify` prints a warning on stderr and continues without derived key material. The derived public key is emitted as PEM so Node can decode it without ambiguity. Hex may include or omit the `0x` prefix. You can also set this via `PLAYLIST_PRIVATE_KEY` in `.env`. `play` verifies playlists before delivery and only auto-signs the synthesized media URL fallback when signing is configured. `send` and `publish` verify before delivery or upload and reject unsigned or broken playlists.
39
+ - `playlist.privateKey` (string, Ed25519 private key in hex or base64): Used by the `sign` command to create DP-1 v1.1.0 multi-signatures. The `verify` command may derive the matching public key from this value (or `PLAYLIST_PRIVATE_KEY`) when you omit `--public-key`; **dp1-js applies that derived key only when verifying legacy flat `signature` strings**, not when checking `signatures[]` envelopes. If that derivation fails, `verify` prints a warning on stderr and continues without derived key material. The derived public key is emitted as PEM so Node can decode it without ambiguity. Hex may include or omit the `0x` prefix. You can also set this via `PLAYLIST_PRIVATE_KEY` in `.env`. `play` verifies playlists before delivery and only auto-signs the synthesized media URL fallback when signing is configured. `play` and `publish` verify before delivery or upload and reject unsigned or broken playlists.
65
40
 
66
41
  **Signing and key encoding:** Signing paths (`sign`, deterministic `build` when configured, and `-k/--key` overrides) pass the private key string through to **`dp1-js`** (`SignMultiEd25519`) without an extra decoding step in ff-cli. `dp1-js` recognizes **hex** (optional `0x`) or **base64** encodings of the PKCS#8 DER blob produced by the OpenSSL examples below, then loads the key for Ed25519. Use those formats; ff-cli does not add a separate normalizer ahead of the library.
67
42
 
@@ -182,15 +157,6 @@ Minimal `config.json` example (selected fields):
182
157
 
183
158
  ```json
184
159
  {
185
- "defaultModel": "claude",
186
- "models": {
187
- "claude": {
188
- "apiKey": "sk-ant-your-api-key-here",
189
- "baseURL": "https://api.anthropic.com/v1/",
190
- "model": "claude-sonnet-4-6",
191
- "supportsFunctionCalling": true
192
- }
193
- },
194
160
  "defaultDuration": 10,
195
161
  "playlist": {
196
162
  "privateKey": "your_ed25519_private_key_hex_or_base64_here",
package/docs/EXAMPLES.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Examples
2
2
 
3
- Copy‑pasteable commands that work with the current CLI.
3
+ Copy‑pasteable commands that work with the current CLI. ff-cli is deterministic: there's no built-in chat. Natural language belongs in your coding agent (Claude Code, Codex), which drives these commands via the `ff-control` skill. No LLM API key required.
4
4
 
5
5
  ## Setup
6
6
 
@@ -16,190 +16,249 @@ npm run dev -- config init
16
16
  npm run dev -- config validate
17
17
  ```
18
18
 
19
- ## OpenClaw skill prompt
19
+ ## Use with a coding agent
20
20
 
21
- If you want OpenClaw to run ff-cli end-to-end without confirmation, start from:
22
-
23
- `skills/ff-control/SKILL.md`
21
+ ff-cli ships a [Claude Code](https://docs.claude.com/en/docs/claude-code) / Codex skill at `skills/ff-control/SKILL.md`. Once installed, ask your agent in plain language ("Get 3 works from reas.eth and play on Living Room", "Build a playlist from this Objkt URL and publish to my feed") and it translates the request into `ff-cli find` or `ff-cli build` and runs the validate → play/publish flow for you.
24
22
 
25
23
  Recommended local install path:
26
24
 
27
25
  ```bash
28
- mkdir -p ~/.openclaw/skills
29
- ln -sfn ~/Work/ff-cli/skills/ff-control ~/.openclaw/skills/ff-control
26
+ git clone --depth=1 https://github.com/feral-file/ff-cli /tmp/ff-cli \
27
+ && mkdir -p ~/.claude/skills \
28
+ && cp -r /tmp/ff-cli/skills/ff-control ~/.claude/skills/
30
29
  ```
31
30
 
32
- Compatibility copy (same prompt):
31
+ The rest of this doc shows the deterministic commands the skill (or you) drives directly.
33
32
 
34
- `examples/openclaw-ff-skill.md`
33
+ ## Find an artwork
35
34
 
36
- Paste that prompt into your OpenClaw skill config, then test with plain requests like:
35
+ `ff-cli find` resolves a marketplace URL, raw `chain:contract:tokenId`, or wallet address into a playable DP-1 playlist. Sources: Art Blocks, Objkt, fxhash, OpenSea, SuperRare, Feral File, Neort, Verse, raw on-chain coordinates, and wallet addresses.
37
36
 
38
37
  ```bash
39
- "Get 3 works from reas.eth and send to Living Room"
40
- "Get 3 from Unsupervised and publish to my feed"
38
+ # Paste a URL and play it on your FF1
39
+ npm run dev -- find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak --play
40
+
41
+ # Tezos / hic et nunc via Objkt (the alias resolves to a KT1 contract)
42
+ npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --play
43
+
44
+ # Feral File artwork (public id may be hex or numeric)
45
+ npm run dev -- find https://feralfile.com/exhibitions/artwork/f0240e04d64717e319584957f6a83954b029254ad1260b6320472ea8c0c5b1cf --play
46
+
47
+ # Save without playing
48
+ npm run dev -- find ethereum:0xababababab20053426ad1c782de9ea8444358070:5001410 -o send-receive.json
49
+
50
+ # Single on-chain token by coordinates
51
+ npm run dev -- find ethereum:0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0:52932 -o token.json
52
+
53
+ # Tezos token by coordinates
54
+ npm run dev -- find tezos:KT1BcNnzWze3vCviwiETYNwcFSwjv6RihZEQ:22 -o tez-token.json
55
+
56
+ # Wallet address (owner lookup); cap the number of tokens with --limit
57
+ npm run dev -- find 0xaeE022552B539dB18297D7481b6D547C622488B3 -l 10 -o wallet.json
58
+
59
+ # Limit a large series to the first N tokens
60
+ npm run dev -- find https://www.artblocks.io/collection/ringers-by-dmitri-cherniak -l 5 -o ringers.json
61
+
62
+ # Build and publish to a configured feed server
63
+ npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --publish
64
+
65
+ # Build, play on a named device, and skip interactive prompts
66
+ npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --play -d "Living Room Display" -y
41
67
  ```
42
68
 
43
- ## Natural Language
69
+ OpenSea and Verse accept both item and collection/series URLs:
44
70
 
45
71
  ```bash
46
- # Interactive chat
47
- npm run dev chat
72
+ # OpenSea collection (Ethereum); slug resolved from the public page, no API key
73
+ npm run dev -- find https://opensea.io/collection/your-collection-slug -l 5 -o opensea.json
48
74
 
49
- # One-shot requests
50
- npm run dev -- chat "Get 3 works from reas.eth" -o playlist.json
51
- npm run dev -- chat "Get 3 works from einstein-rosen.tez"
52
- npm run dev -- chat "Get tokens 52932,52457 from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0" -v
53
- npm run dev -- chat "Make a playlist of 10 works from 0xaeE022552B539dB18297D7481b6D547C622488B3" -v
75
+ # Verse series
76
+ npm run dev -- find https://verse.works/series/your-series-slug -l 5 -o verse.json
77
+ ```
54
78
 
55
- # Switch model
56
- npm run dev -- chat "your request" --model grok
57
- npm run dev -- chat "your request" --model gpt
58
- npm run dev -- chat "your request" --model gemini
79
+ Run `npm run dev -- find --help` for the full input list and options.
59
80
 
60
- # Model names must match keys in config.json under `models`.
61
- ```
81
+ ## Build from structured params
62
82
 
63
- ## Deterministic Build (no AI)
83
+ Use `ff-cli build` when you already have the contract, token IDs, feed names, or artwork ids and want explicit control over ordering, durations, and title. The input is a JSON object with `requirements` and optional `playlistSettings`.
64
84
 
65
85
  ```bash
66
- # From file
86
+ # From a file
67
87
  npm run dev -- build examples/params-example.json -o playlist.json
68
88
 
69
89
  # From stdin
70
90
  cat examples/params-example.json | npm run dev -- build -o playlist.json
71
91
  ```
72
92
 
73
- Build from a Feral File public artwork id or artwork URL:
93
+ Each requirement has a `type`:
94
+
95
+ - `build_playlist` — `blockchain`, `contractAddress`, `tokenIds`, optional `quantity`
96
+ - `feral_file_artwork` — `artworkId` (a Feral File public artwork id or `/exhibitions/artwork/{id}` URL)
97
+ - `query_address` — `ownerAddress`, optional `quantity` (random selection)
98
+ - `fetch_feed` — `playlistName`, `quantity`
99
+
100
+ `playlistSettings` may set `title`, `durationPerItem`, `preserveOrder` (set `false` to shuffle), and `deviceName`.
101
+
102
+ ### From a contract + token IDs
74
103
 
75
104
  ```bash
76
- cat > /tmp/ff-artwork.json <<'JSON'
105
+ cat > /tmp/eth-tokens.json <<'JSON'
77
106
  {
78
107
  "requirements": [
79
108
  {
80
- "type": "feral_file_artwork",
81
- "artworkId": "https://feralfile.com/exhibitions/artwork/f0240e04d64717e319584957f6a83954b029254ad1260b6320472ea8c0c5b1cf"
109
+ "type": "build_playlist",
110
+ "blockchain": "ethereum",
111
+ "contractAddress": "0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0",
112
+ "tokenIds": ["52932", "52457"]
82
113
  }
83
114
  ],
84
115
  "playlistSettings": {
85
- "title": "Feral File Artwork"
116
+ "title": "ETH Tokens",
117
+ "preserveOrder": false,
118
+ "durationPerItem": 7
86
119
  }
87
120
  }
88
121
  JSON
89
122
 
90
- npm run dev -- build /tmp/ff-artwork.json -o playlist.json
123
+ npm run dev -- build /tmp/eth-tokens.json -o playlist-eth.json -v
91
124
  ```
92
125
 
93
- ## AI‑Orchestrated Deterministic Flow (prompts)
126
+ Tezos works the same way with `"blockchain": "tezos"` and a `KT1...` contract:
94
127
 
95
128
  ```bash
96
- # Show tool‑call progress and validation
97
- npm run dev -- chat "Build a playlist of my works from reas.eth plus 3 from Unsupervised" -v -o playlist.json
98
-
99
- # Build from a Feral File artwork URL
100
- npm run dev -- chat "Build a playlist from https://feralfile.com/exhibitions/artwork/f0240e04d64717e319584957f6a83954b029254ad1260b6320472ea8c0c5b1cf" -v -o playlist.json
129
+ cat > /tmp/tez-tokens.json <<'JSON'
130
+ {
131
+ "requirements": [
132
+ {
133
+ "type": "build_playlist",
134
+ "blockchain": "tezos",
135
+ "contractAddress": "KT1BcNnzWze3vCviwiETYNwcFSwjv6RihZEQ",
136
+ "tokenIds": ["22", "8"]
137
+ }
138
+ ],
139
+ "playlistSettings": { "title": "Tezos Tokens", "preserveOrder": false }
140
+ }
141
+ JSON
101
142
 
102
- # Switch model if desired
103
- npm run dev -- chat "Build playlist from Ethereum address 0xaeE022552B539dB18297D7481b6D547C622488B3 and 2 from Unsupervised" --model gpt -v
143
+ npm run dev -- build /tmp/tez-tokens.json -o playlist-tez.json -v
104
144
  ```
105
145
 
106
- ### One‑shot complex prompt
107
-
108
- The CLI can parse rich requests and do it all in one go: fetch, build a DP‑1 playlist, shuffle, set durations, and send to a named device.
146
+ ### From a wallet address
109
147
 
110
148
  ```bash
111
- # Example: combine sources, shuffle, set 6s per item, and send to device
112
- npm run dev -- chat "Get tokens 52932,52457 from contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 and 2 from reas.eth; shuffle; 6 seconds each; send to 'Living Room Display'." -o playlist.json -v
149
+ cat > /tmp/wallet.json <<'JSON'
150
+ {
151
+ "requirements": [
152
+ {
153
+ "type": "query_address",
154
+ "ownerAddress": "0xaeE022552B539dB18297D7481b6D547C622488B3",
155
+ "quantity": 5
156
+ }
157
+ ],
158
+ "playlistSettings": { "title": "From a wallet", "preserveOrder": false }
159
+ }
160
+ JSON
161
+
162
+ npm run dev -- build /tmp/wallet.json -o playlist-wallet.json -v
113
163
  ```
114
164
 
115
- ## Feral File Built Playlists
165
+ ### From a feed playlist
116
166
 
117
- Use exhibition titles from the official playlist repository:
118
- `https://github.com/feral-file/dp1-feed/tree/main/playlists`
167
+ Feed playlist queries require reachable feed servers in your config. Reference exhibition titles from the official playlist repository: `https://github.com/feral-file/dp1-feed/tree/main/playlists`.
119
168
 
120
169
  ```bash
121
- # Fetch from a built playlist title
122
- npm run dev -- chat "Get 3 from Unsupervised" -v
170
+ cat > /tmp/feed.json <<'JSON'
171
+ {
172
+ "requirements": [
173
+ { "type": "fetch_feed", "playlistName": "Unsupervised", "quantity": 3 }
174
+ ],
175
+ "playlistSettings": { "title": "From a feed", "preserveOrder": false }
176
+ }
177
+ JSON
123
178
 
124
- # Mix a built playlist with wallet/domain sources
125
- npm run dev -- chat "Get 3 from Unsupervised and 2 from reas.eth" -v -o playlist.json
179
+ npm run dev -- build /tmp/feed.json -o playlist-feed.json -v
126
180
  ```
127
181
 
128
- Notes:
129
-
130
- - Feed playlist queries require reachable feed servers in your config.
131
- - If no feeds are reachable, feed playlist examples will fail even if the title exists.
132
- - Bare EVM prompts (`from 0x...`) now fall back from owner lookup to contract lookup when needed.
133
-
134
- ## Natural Language: Display and Publish
135
-
136
- The CLI recognizes publishing keywords like "publish", "publish to my feed", "push to feed", "send to feed" and automatically publishes after building.
137
-
138
- ### Basic Publishing
182
+ ### From a Feral File artwork
139
183
 
140
184
  ```bash
141
- # Build and publish
142
- npm run dev -- chat "Build playlist from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 with tokens 52932 and 52457; publish to my feed" -o playlist.json -v
143
-
144
- # With feed selection (if multiple servers configured)
145
- # The CLI will ask: "Which feed server? 1) https://dp1-feed-operator-api-prod.autonomy-system.workers.dev/api/v1 2) http://localhost:8787"
146
- npm run dev -- chat "Get 3 from Unsupervised and publish to feed" -v
147
-
148
- # Publish existing playlist (defaults to ./playlist.json)
149
- npm run dev chat
150
- # Then type: "publish playlist"
185
+ cat > /tmp/ff-artwork.json <<'JSON'
186
+ {
187
+ "requirements": [
188
+ {
189
+ "type": "feral_file_artwork",
190
+ "artworkId": "https://feralfile.com/exhibitions/artwork/f0240e04d64717e319584957f6a83954b029254ad1260b6320472ea8c0c5b1cf"
191
+ }
192
+ ],
193
+ "playlistSettings": { "title": "Feral File Artwork" }
194
+ }
195
+ JSON
151
196
 
152
- # Publish specific playlist file
153
- npm run dev chat
154
- # Then type: "publish the playlist ./playlist-temp.json"
197
+ npm run dev -- build /tmp/ff-artwork.json -o playlist.json
155
198
  ```
156
199
 
157
- ### Combined: Display + Publish
200
+ ### Mixing sources in one playlist
201
+
202
+ Combine multiple requirements; shuffle with `preserveOrder: false` and set per-item timing with `durationPerItem`.
158
203
 
159
204
  ```bash
160
- # Display on Art Computer AND publish to feed
161
- npm run dev -- chat "Build playlist from contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 with tokens 52932 and 52457; mix them up; send to my Art Computer and publish to my feed" -o playlist.json -v
205
+ cat > /tmp/mixed.json <<'JSON'
206
+ {
207
+ "requirements": [
208
+ {
209
+ "type": "build_playlist",
210
+ "blockchain": "tezos",
211
+ "contractAddress": "KT1BcNnzWze3vCviwiETYNwcFSwjv6RihZEQ",
212
+ "tokenIds": ["22", "8"]
213
+ },
214
+ {
215
+ "type": "build_playlist",
216
+ "blockchain": "ethereum",
217
+ "contractAddress": "0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0",
218
+ "tokenIds": ["52932", "52457"]
219
+ },
220
+ { "type": "fetch_feed", "playlistName": "Unsupervised", "quantity": 3 },
221
+ { "type": "query_address", "ownerAddress": "reas.eth", "quantity": 1 }
222
+ ],
223
+ "playlistSettings": {
224
+ "title": "Mixed",
225
+ "preserveOrder": false,
226
+ "durationPerItem": 6,
227
+ "deviceName": "Living Room Display"
228
+ }
229
+ }
230
+ JSON
162
231
 
163
- # With explicit device name
164
- npm run dev -- chat "Get 5 from Unsupervised, shuffle, display on 'Living Room', and publish to feed" -v
232
+ npm run dev -- build /tmp/mixed.json -o playlist-mixed.json -v
165
233
  ```
166
234
 
167
- ### How It Works
168
-
169
- **Mode 1: Build and Publish** (when sources are mentioned)
170
-
171
- 1. Intent parser detects "publish" keywords with sources/requirements
172
- 2. Calls `get_feed_servers` to retrieve configured servers
173
- 3. If 1 server → uses it automatically; if 2+ servers → asks user to pick
174
- 4. Builds playlist → validates structure → publishes automatically
235
+ ## Validate / Sign / Play
175
236
 
176
- **Mode 2: Publish Existing File** (e.g., "publish playlist")
237
+ ```bash
238
+ # Validate playlist structure
239
+ npm run dev -- validate playlist.json
240
+ npm run dev -- validate "https://cdn.example.com/playlist.json"
177
241
 
178
- 1. Intent parser detects "publish playlist" or similar phrases
179
- 2. Calls `get_feed_servers` to retrieve configured servers
180
- 3. If 1 server → uses it automatically; if 2+ servers → asks user to pick
181
- 4. Publishes the playlist from `./playlist.json` (or specified path)
242
+ # Validate structure AND verify signatures
243
+ npm run dev -- verify playlist.json
182
244
 
183
- Output shows:
245
+ # Sign playlist (uses key/role from config, or override via --key / --role)
246
+ npm run dev -- sign playlist.json -o signed.json
184
247
 
185
- - Playlist build progress (Mode 1 only)
186
- - Device sending (if requested): `✓ Sent to device: Living Room`
187
- - Publishing status: `✓ Published to feed server`
188
- - Playlist ID: `Playlist ID: 84e028f8-...`
248
+ # Play on the configured default device
249
+ npm run dev -- play playlist.json
189
250
 
190
- ## Validate / Sign / Send
251
+ # Play on a specific named device
252
+ npm run dev -- play signed.json -d "Living Room Display"
191
253
 
192
- ```bash
193
- # Validate playlist
194
- npm run dev -- validate playlist.json
195
- npm run dev -- validate "https://cdn.example.com/playlist.json"
254
+ # Play a hosted DP-1 playlist
255
+ npm run dev -- play "https://cdn.example.com/playlist.json"
196
256
 
197
- # Sign playlist
198
- npm run dev -- sign playlist.json -o signed.json
257
+ # Play a media URL directly
258
+ npm run dev -- play "https://example.com/video.mp4"
199
259
 
200
- # Send to device
201
- npm run dev -- send playlist.json -d "Living Room Display"
202
- npm run dev -- send "https://cdn.example.com/playlist.json" -d "Living Room Display"
260
+ # Skip verification only if you must send a non-conformant payload (not recommended)
261
+ npm run dev -- play playlist.json --skip-verify
203
262
  ```
204
263
 
205
264
  ## Publish to Feed Server
@@ -240,9 +299,9 @@ npm run dev -- publish --help
240
299
 
241
300
  ### Flow
242
301
 
243
- 1. **Validate** - Playlist structure checked (`validate`-style parse; use `verify` for signatures)
302
+ 1. **Verify** - Playlist structure and signatures checked; unsigned or broken playlists are rejected
244
303
  2. **Select Server** - If multiple servers, choose which one (interactive or via `-s` flag)
245
- 3. **Publish** - Send validated playlist to selected feed server
304
+ 3. **Publish** - Send the verified playlist to the selected feed server
246
305
  4. **Confirm** - Returns playlist ID and server details
247
306
 
248
307
  ### Example Output
@@ -287,11 +346,11 @@ Select server (0-based index): 0
287
346
  Failed to publish: {"error":"unauthorized","message":"Invalid API key"}
288
347
  ```
289
348
 
290
- ## Validate / Sign / Send / Publish (Complete Flow)
349
+ ## Complete Flow (build → validate → sign → play → publish)
291
350
 
292
351
  ```bash
293
- # 1. Create a playlist (via chat or build)
294
- npm run dev -- chat "Get 3 works from reas.eth" -o playlist.json
352
+ # 1. Build a playlist (via find or build)
353
+ npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 -o playlist.json
295
354
 
296
355
  # 2. Validate it
297
356
  npm run dev -- validate playlist.json
@@ -299,13 +358,38 @@ npm run dev -- validate playlist.json
299
358
  # 3. Sign it
300
359
  npm run dev -- sign playlist.json -o signed.json
301
360
 
302
- # 4. Send to device
303
- npm run dev -- send signed.json -d "My Display"
361
+ # 4. Play it on a device
362
+ npm run dev -- play signed.json -d "Living Room Display"
304
363
 
305
- # 5. Publish to feed server
364
+ # 5. Publish to a feed server
306
365
  npm run dev -- publish signed.json -s 0
307
366
  ```
308
367
 
368
+ `ff-cli find` can collapse build + play + publish into one command:
369
+
370
+ ```bash
371
+ npm run dev -- find https://objkt.com/tokens/hicetnunc/111068 --play -d "Living Room Display" --publish
372
+ ```
373
+
374
+ ## FF1 device management
375
+
376
+ ```bash
377
+ # List configured devices
378
+ npm run dev -- device list
379
+
380
+ # Add a device interactively (with mDNS discovery)
381
+ npm run dev -- device add
382
+
383
+ # Add a device non-interactively
384
+ npm run dev -- device add --host 192.168.1.100 --name kitchen
385
+
386
+ # Remove a device by name
387
+ npm run dev -- device remove kitchen
388
+
389
+ # Set the default device (used when -d is omitted)
390
+ npm run dev -- device default office
391
+ ```
392
+
309
393
  ## Troubleshooting
310
394
 
311
395
  ```bash
@@ -314,100 +398,7 @@ npm run dev -- config show
314
398
 
315
399
  # Reinitialize config
316
400
  npm run dev -- config init
317
- ```
318
401
 
319
- ### Natural‑language one‑shot examples (proven)
320
-
321
- - **ETH contract + token IDs (shuffle/mix, generic device)**
322
- - Format:
323
- ```bash
324
- npm run dev -- chat "Compose a playlist from Ethereum contract <0x...> with tokens <id> and <id>; [shuffle|mix]; [send to device|send to '<device>']" -o <output.json> -v
325
- ```
326
- - Example:
327
- ```bash
328
- npm run dev -- chat "Compose a playlist from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 with tokens 52932 and 52457; mix them up; send to device" -o playlist-eth.json -v
329
- ```
330
-
331
- - **TEZ contract + token IDs (shuffle, named device)**
332
- - Format:
333
- ```bash
334
- npm run dev -- chat "Build a playlist from Tezos contract <KT1...> with tokens <id> and <id>; shuffle; send to '<device>'" -o <output.json> -v
335
- ```
336
- - Example:
337
- ```bash
338
- npm run dev -- chat "Build a playlist from Tezos contract KT1BcNnzWze3vCviwiETYNwcFSwjv6RihZEQ with tokens 22 and 8; shuffle; send to 'Living Room'" -o playlist-tez.json -v
339
- ```
340
-
341
- - **Owner address (ENS → ETH), shuffled**
342
- - Format:
343
- ```bash
344
- npm run dev -- chat "Create a playlist from address <ens> (<n> items); [shuffle|mix]; [send/push to my device]" -o <output.json> -v
345
- ```
346
- - Example:
347
- ```bash
348
- npm run dev -- chat "Create a playlist from address reas.eth (5 items); shuffle; push to my device" -o playlist-ens.json -v
349
- ```
350
-
351
- - **Owner address (Tezos tz1), shuffled**
352
- - Format:
353
- ```bash
354
- npm run dev -- chat "Create a playlist from Tezos address <tz1...> (<n> items); [shuffle|mix]; [send to device]" -o <output.json> -v
355
- ```
356
- - Example:
357
- ```bash
358
- npm run dev -- chat "Create a playlist from Tezos address tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb (3 items); mix them up; send to device" -o playlist-tz1.json -v
359
- ```
360
-
361
- - **Feed playlists (named), shuffled**
362
- - Format:
363
- ```bash
364
- npm run dev -- chat "[Create|Build] a playlist from feed '<name>' (<n> items); shuffle; [send to device]" -o <output.json> -v
365
- ```
366
- - Examples:
367
- ```bash
368
- npm run dev -- chat "Create a playlist from feed 'Unsupervised' (3 items); shuffle; send to device" -o playlist-feed1.json -v
369
- npm run dev -- chat "Build a playlist from feed 'Unsupervised' (3 items); shuffle; send to device" -o playlist-feed2.json -v
370
- ```
371
-
372
- - **Mixed in one prompt (ETH + TEZ + feed + ENS), shuffled, named device**
373
- - Format:
374
- ```bash
375
- npm run dev -- chat "Compose a playlist: Tezos <KT1...> tokens <id>, <id>; Ethereum <0x...> tokens <id>, <id>; <n> from '<feed>'; <m> from <ens>; shuffle; send to '<device>'" -o <output.json> -v
376
- ```
377
- - Example:
378
- ```bash
379
- npm run dev -- chat "Compose a playlist: Tezos KT1BcNnzWze3vCviwiETYNwcFSwjv6RihZEQ tokens 22, 8; Ethereum 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 tokens 52932, 52457; 3 from 'Unsupervised'; 1 from reas.eth; shuffle; send to 'Living Room'" -o playlist-mixed.json -v
380
- ```
381
-
382
- - **Multiple instructions in one prompt (incremental), shuffled**
383
- - Format:
384
- ```bash
385
- npm run dev -- chat "Create a playlist from Ethereum contract <0x...> tokens <id>, <id>; then add <n> from '<feed>'; then add <m> from <ens>; shuffle; [send/push to my device]" -o <output.json> -v
386
- ```
387
- - Example:
388
- ```bash
389
- npm run dev -- chat "Create a playlist from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 tokens 52932, 52457; then add 2 from 'Unsupervised'; then add 1 from reas.eth; shuffle; push to my device" -o playlist-multi.json -v
390
- ```
391
-
392
- - **Synonym variants for the same ETH case**
393
- - Format:
394
- ```bash
395
- npm run dev -- chat "[Build|Create|Compose] a playlist from Ethereum contract <0x...> tokens <id> and <id>; send to device" -o <output.json> -v
396
- ```
397
- - Examples:
398
- ```bash
399
- npm run dev -- chat "Build a playlist from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 tokens 52932 and 52457; send to device" -o playlist-eth-build.json -v
400
- npm run dev -- chat "Create a playlist from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0 tokens 52932 and 52457; send to device" -o playlist-eth-create.json -v
401
- ```
402
-
403
- - **Device targeting: generic vs named**
404
- - Format:
405
- ```bash
406
- npm run dev -- chat "Compose a playlist from <ens/address> (<n> items); send to device" -o <output.json> -v
407
- npm run dev -- chat "Compose a playlist from <ens/address> (<n> items); send to '<device>'" -o <output.json> -v
408
- ```
409
- - Examples:
410
- ```bash
411
- npm run dev -- chat "Compose a playlist from reas.eth (3 items); send to device" -o playlist-generic-device.json -v
412
- npm run dev -- chat "Compose a playlist from reas.eth (3 items); send to 'Living Room'" -o playlist-named-device.json -v
413
- ```
402
+ # Validate configuration
403
+ npm run dev -- config validate
404
+ ```