@feralfile/cli 2.5.0 → 2.7.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.
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "playlist": {
17
17
  "privateKey": "YOUR_ED25519_PRIVATE_KEY__base64_PKCS8_DER_recommended__or_32byte_raw_seed_as_hex_or_base64__run_ff-cli_setup_to_generate",
18
- "role": "agent",
18
+ "role": "curator",
19
19
  "curatorName": "YOUR_CURATOR_NAME"
20
20
  },
21
21
  "ff1Relayer": {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feralfile/cli",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "CLI for building, signing, and playing DP-1 playlists of digital art on Feral File devices",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "@feralfile/source-resolver": "^1.0.0",
62
62
  "@napi-rs/keyring": "1.3.0",
63
- "axios": "^1.6.2",
63
+ "axios": "^1.20.0",
64
64
  "bonjour-service": "^1.3.0",
65
65
  "chalk": "^4.1.2",
66
66
  "commander": "^11.1.0",
@@ -68,7 +68,7 @@
68
68
  "dp1-js": "2.3.1",
69
69
  "ff1": "^1.0.0",
70
70
  "fuzzysort": "^3.1.0",
71
- "viem": "^2.38.2"
71
+ "viem": "^2.56.3"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@types/node": "^24.7.1",
@@ -12,7 +12,7 @@ const path_1 = require("path");
12
12
  const enrich_playlist_1 = require("../utilities/enrich-playlist");
13
13
  const playlist_verifier_1 = require("../utilities/playlist-verifier");
14
14
  // eslint-disable-next-line @typescript-eslint/no-require-imports
15
- const { getNFTTokenInfoBatch } = require('../utilities/nft-indexer');
15
+ const { resolveTokenBatch } = require('../utilities/nft-indexer');
16
16
  /**
17
17
  * writePlaylistAtomically replaces a file only once the new bytes are safely on
18
18
  * disk.
@@ -63,7 +63,16 @@ async function writePlaylistAtomically(destination, contents, expectedDigest) {
63
63
  // of it, or widen who can reach it. Carry the ownership across, and when
64
64
  // that is not permitted refuse the in-place replacement instead of
65
65
  // completing it with different access than the file had.
66
- if (current && (current.uid !== process.getuid?.() || current.gid !== process.getgid?.())) {
66
+ //
67
+ // Only where POSIX identity exists. Windows has no getuid/getgid and
68
+ // reports uid and gid as 0 on every stat, so the comparison there would
69
+ // always "differ" and chown a file whose ownership never moved. (libuv
70
+ // makes fchown a no-op on Windows, so nothing would break — but the
71
+ // branch would be asserting something it cannot know.)
72
+ const uid = process.getuid?.();
73
+ const gid = process.getgid?.();
74
+ const canCompareOwnership = uid !== undefined && gid !== undefined;
75
+ if (current && canCompareOwnership && (current.uid !== uid || current.gid !== gid)) {
67
76
  try {
68
77
  await handle.chown(current.uid, current.gid);
69
78
  }
@@ -230,12 +239,22 @@ exports.enrichCommand = new commander_1.Command('enrich')
230
239
  printedProgress = true;
231
240
  process.stdout.write(chalk_1.default.dim(`\r ${done}/${count} looked up...`));
232
241
  };
233
- // getNFTTokenInfoBatch is (tokens, duration, onProgress) — the second
242
+ // resolveTokenBatch is (tokens, duration, onProgress) — the second
234
243
  // positional is DP-1 display seconds, not the callback. Enrichment never
235
244
  // sets duration (the curator's timing is not ours to touch), so it is
236
245
  // passed undefined and the callback goes third. find.ts carries the same
237
246
  // warning; getting this wrong silently returns unusable results.
238
- const lookup = (tokens, onProgressCallback) => getNFTTokenInfoBatch(tokens, undefined, onProgressCallback);
247
+ //
248
+ // The raw results are used rather than getNFTTokenInfoBatch's items so
249
+ // the indexer's still rides beside each item: the item itself drops the
250
+ // still whenever it equals the indexer's chosen source, and enrichment
251
+ // needs it against the curator's source instead.
252
+ const lookup = async (tokens, onProgressCallback) => {
253
+ const results = await resolveTokenBatch(tokens, undefined, onProgressCallback);
254
+ return results
255
+ .filter((result) => result.success && result.item)
256
+ .map((result) => ({ ...result.item, still: result.still }));
257
+ };
239
258
  const result = await (0, enrich_playlist_1.enrichPlaylistManifests)(playlist, lookup, {
240
259
  force: options.force,
241
260
  assumeEthereum: options.assumeEthereum,
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -595,8 +562,8 @@ async function doPlay(playlist, deviceName, skipVerify) {
595
562
  }
596
563
  async function doPublish(savedPath, serverArg, nonInteractive) {
597
564
  console.log(chalk_1.default.blue('Publish to feed'));
598
- const { getFeedConfig } = await Promise.resolve().then(() => __importStar(require('../config')));
599
- const { publishPlaylist } = await Promise.resolve().then(() => __importStar(require('../utilities/playlist-publisher')));
565
+ const { getFeedConfig } = await import('../config.js');
566
+ const { publishPlaylist } = await import('../utilities/playlist-publisher.js');
600
567
  const feedConfig = getFeedConfig();
601
568
  if (!feedConfig.baseURLs || feedConfig.baseURLs.length === 0) {
602
569
  console.error(chalk_1.default.red('No feed servers configured.'));
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -98,7 +65,7 @@ function printPlaylistVerificationFailure(verifyResult, source) {
98
65
  */
99
66
  async function validatePlaylistSource(source) {
100
67
  const loaded = await (0, playlist_source_1.loadPlaylistSource)(source);
101
- const verifier = await Promise.resolve().then(() => __importStar(require('../../utilities/playlist-verifier')));
68
+ const verifier = await import('../../utilities/playlist-verifier.js');
102
69
  const { validatePlaylist } = verifier;
103
70
  const verifyResult = await validatePlaylist(loaded.playlist);
104
71
  return {
@@ -113,7 +80,7 @@ async function validatePlaylistSource(source) {
113
80
  async function runValidateCommand(source) {
114
81
  try {
115
82
  console.log(chalk_1.default.blue('\nValidate playlist\n'));
116
- const verifier = await Promise.resolve().then(() => __importStar(require('../../utilities/playlist-verifier')));
83
+ const verifier = await import('../../utilities/playlist-verifier.js');
117
84
  const { printVerificationResult } = verifier;
118
85
  const result = await validatePlaylistSource(source);
119
86
  printVerificationResult(result, source, { failureKind: 'structure' });
@@ -138,7 +105,7 @@ async function runValidateCommand(source) {
138
105
  async function runVerifyCommand(source, publicKey) {
139
106
  try {
140
107
  console.log(chalk_1.default.blue('\nVerify playlist\n'));
141
- const verifier = await Promise.resolve().then(() => __importStar(require('../../utilities/playlist-verifier')));
108
+ const verifier = await import('../../utilities/playlist-verifier.js');
142
109
  const { printVerificationResult } = verifier;
143
110
  const result = await validatePlaylistSource(source);
144
111
  if (!result.valid) {
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -47,8 +14,8 @@ exports.publishCommand = new commander_1.Command('publish')
47
14
  .action(async (file, options) => {
48
15
  try {
49
16
  console.log(chalk_1.default.blue('\nPublish playlist\n'));
50
- const { getFeedConfig } = await Promise.resolve().then(() => __importStar(require('../config')));
51
- const { publishPlaylist } = await Promise.resolve().then(() => __importStar(require('../utilities/playlist-publisher')));
17
+ const { getFeedConfig } = await import('../config.js');
18
+ const { publishPlaylist } = await import('../utilities/playlist-publisher.js');
52
19
  const feedConfig = getFeedConfig();
53
20
  if (!feedConfig.baseURLs || feedConfig.baseURLs.length === 0) {
54
21
  console.error(chalk_1.default.red('\nNo feed servers configured'));
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -71,7 +38,7 @@ exports.sshCommand = new commander_1.Command('ssh')
71
38
  if (options.ttl) {
72
39
  ttlSeconds = (0, ssh_helpers_1.parseTtlSeconds)(options.ttl);
73
40
  }
74
- const { sendSshAccessCommand } = await Promise.resolve().then(() => __importStar(require('../utilities/ssh-access')));
41
+ const { sendSshAccessCommand } = await import('../utilities/ssh-access.js');
75
42
  const result = await sendSshAccessCommand({
76
43
  enabled: isEnable,
77
44
  deviceName: options.device,
@@ -129,7 +129,43 @@ function coordinateKeyOf(provenance) {
129
129
  if (token.length === 0) {
130
130
  return null;
131
131
  }
132
- return `${canonicalChain(chain)}:${address.trim().toLowerCase()}:${token}`;
132
+ const canonical = canonicalChain(chain);
133
+ const standard = effectiveStandard(canonical, address, contract.standard);
134
+ return `${canonical}:${standard}:${address.trim().toLowerCase()}:${token}`;
135
+ }
136
+ /**
137
+ * Standards the indexer keys CIDs on. Mirrors CID_STANDARDS in nft-indexer.js.
138
+ */
139
+ const CID_STANDARDS = new Set(['erc721', 'erc1155', 'fa2']);
140
+ /**
141
+ * effectiveStandard names the standard a coordinate is looked up under, so the
142
+ * correlation key separates what the indexer separates.
143
+ *
144
+ * The indexer keys ERC-721 and ERC-1155 tokens at one address under different
145
+ * CIDs, so a hybrid contract can hold both standards at one token id. Two
146
+ * playlist items asserting different standards for one coordinate are two
147
+ * lookups, and each answer must reach only the item that asked for it.
148
+ *
149
+ * When the playlist asserts nothing the client detects, and this mirrors that
150
+ * detection (resolveTokenStandard in nft-indexer.js) so the request key and
151
+ * the key read off the response agree. The mirror is deliberate rather than
152
+ * an import: this module takes its lookup by injection and stays free of the
153
+ * indexer client. If the two ever drift, correlation fails closed — the item
154
+ * reports not-indexed — rather than attributing one token's metadata to
155
+ * another.
156
+ */
157
+ function effectiveStandard(canonicalChainName, address, asserted) {
158
+ const normalized = typeof asserted === 'string' ? asserted.trim().toLowerCase() : '';
159
+ if (CID_STANDARDS.has(normalized)) {
160
+ return normalized;
161
+ }
162
+ if (canonicalChainName === 'tezos' || address.trim().startsWith('KT')) {
163
+ return 'fa2';
164
+ }
165
+ if (canonicalChainName === 'evm') {
166
+ return 'erc721';
167
+ }
168
+ return 'other';
133
169
  }
134
170
  /**
135
171
  * describeItem names an item for operator output, preferring its title.
@@ -160,14 +196,22 @@ function coordinateFor(item) {
160
196
  return null;
161
197
  }
162
198
  const normalized = chain.trim().toLowerCase();
163
- return {
199
+ const coordinate = {
164
200
  chain: CHAIN_ALIASES[normalized] ?? normalized,
165
201
  contractAddress: address,
166
202
  tokenId: token,
167
203
  };
204
+ // Carried as written. The indexer client accepts the standards its CIDs
205
+ // are keyed on and falls back to detection for anything else, so an
206
+ // unexpected value costs nothing beyond the lookup it would have lost anyway.
207
+ const standard = contract.standard;
208
+ if (typeof standard === 'string' && standard.trim().length > 0) {
209
+ coordinate.standard = standard.trim().toLowerCase();
210
+ }
211
+ return coordinate;
168
212
  }
169
213
  /**
170
- * stillFromIndexerSource recovers a thumbnail the manifest builder suppressed.
214
+ * stillFor recovers a thumbnail the manifest builder suppressed.
171
215
  *
172
216
  * `resolveStillUri` blanks a still that equals the source it was given, on the
173
217
  * reasonable ground that pointing a thumbnail at the artwork itself adds
@@ -179,64 +223,26 @@ function coordinateFor(item) {
179
223
  * exists to repair: a live work the app cannot rasterize, whose grid tile
180
224
  * stays empty.
181
225
  *
182
- * Recovering it is the inverse of the suppression. When the indexer's source
183
- * differs from the item's and is an http(s) URL, that source IS the still that
184
- * was elided, so it becomes the thumbnail relative to the item's own source.
226
+ * Recovery is the same rule applied against the curator's source instead of
227
+ * the indexer's: the still the indexer names for the token (`resolved.still`,
228
+ * its `display.image_url`) is used whenever it differs from the item's own
229
+ * source. It is the same field `find` and `build` write into
230
+ * `thumbnails.default`, and it is taken on the indexer's word, as they take
231
+ * it. No guess is made from a URL's shape — the indexer's source is often a
232
+ * live rendition and its stills are often extensionless CDN URLs, so a
233
+ * suffix check gets both cases wrong — and a lookup that names no still
234
+ * leaves the item without one.
185
235
  */
186
- function stillFromIndexerSource(item, resolved) {
187
- const indexerSource = typeof resolved.source === 'string' ? resolved.source.trim() : '';
236
+ function stillFor(item, resolved) {
237
+ const still = typeof resolved.still === 'string' ? resolved.still.trim() : '';
188
238
  const itemSource = typeof item.source === 'string' ? item.source.trim() : '';
189
- if (!indexerSource || indexerSource === itemSource) {
239
+ if (!still || still === itemSource) {
190
240
  return '';
191
241
  }
192
- if (!indexerSource.startsWith('http://') && !indexerSource.startsWith('https://')) {
242
+ if (!still.startsWith('http://') && !still.startsWith('https://')) {
193
243
  return '';
194
244
  }
195
- // The indexer's source is not necessarily a still. getBestMediaUrl prefers
196
- // display.animation_url and media assets over display.image_url, so it is
197
- // frequently a live HTML rendition — and writing one into the thumbnail slot
198
- // is worse than leaving the slot empty: the grid still cannot rasterize it,
199
- // and the item now claims a still it does not have.
200
- //
201
- // Only a URL that is demonstrably an image is used. The indexer exposes no
202
- // separately identified still on the item this command receives, so the file
203
- // extension is the evidence available; carrying a real image field through
204
- // the batch result would be the better fix and belongs upstream.
205
- return looksLikeImage(indexerSource) ? indexerSource : '';
206
- }
207
- /**
208
- * Media the app can draw a grid tile from, matching the types
209
- * playlist-builder.js already recognizes. SVG and video belong here: the app
210
- * rasterizes both, and excluding them left works with the empty tile this
211
- * command exists to remove. HTML is deliberately absent — an unrasterizable
212
- * page in the thumbnail slot is worse than an empty slot, because the item
213
- * then claims a still it does not have.
214
- */
215
- const RASTERIZABLE_EXTENSIONS = [
216
- '.png',
217
- '.jpg',
218
- '.jpeg',
219
- '.gif',
220
- '.webp',
221
- '.avif',
222
- '.bmp',
223
- '.svg',
224
- '.mp4',
225
- '.webm',
226
- ];
227
- /**
228
- * looksLikeImage tests a URL's path extension, ignoring query and fragment.
229
- */
230
- function looksLikeImage(url) {
231
- let pathname = url;
232
- try {
233
- pathname = new URL(url).pathname;
234
- }
235
- catch {
236
- pathname = url.split('?')[0].split('#')[0];
237
- }
238
- const lower = pathname.toLowerCase();
239
- return RASTERIZABLE_EXTENSIONS.some((extension) => lower.endsWith(extension));
245
+ return still;
240
246
  }
241
247
  /**
242
248
  * hasThumbnail reports whether a manifest already carries a default still.
@@ -258,9 +264,10 @@ function hasThumbnail(manifest) {
258
264
  * thumbnails) is what enrichment exists to fetch and is taken as given.
259
265
  */
260
266
  function manifestFor(item, resolved) {
261
- const still = stillFromIndexerSource(item, resolved);
267
+ const still = stillFor(item, resolved);
268
+ const curatorTitle = typeof item.title === 'string' ? item.title.trim() : '';
269
+ const resolvedTitle = typeof resolved.title === 'string' ? resolved.title.trim() : '';
262
270
  let manifest = resolved.inlineManifest;
263
- let rekey = false;
264
271
  if (manifest && still && !hasThumbnail(manifest)) {
265
272
  manifest = {
266
273
  ...manifest,
@@ -269,30 +276,36 @@ function manifestFor(item, resolved) {
269
276
  thumbnails: { default: { uri: still } },
270
277
  },
271
278
  };
272
- rekey = true;
273
279
  }
274
- else if (!manifest && still) {
275
- // Nothing was emitted because the still was the only content the token
276
- // had, and it had been suppressed. A thumbnail-only manifest is worth its
277
- // payload in a way a title-only one is not: it is the difference between
278
- // a grid tile and an empty square.
279
- const synthesizedTitle = typeof item.title === 'string' ? item.title.trim() : '';
280
- manifest = {
281
- refVersion: '1.1.0',
282
- id: derivedManifestId('', `${synthesizedTitle}\n${still}`),
283
- created: MANIFEST_CREATED,
284
- locale: 'en',
285
- metadata: {
286
- ...(synthesizedTitle ? { title: synthesizedTitle } : {}),
287
- thumbnails: { default: { uri: still } },
288
- },
289
- };
280
+ else if (!manifest) {
281
+ // Nothing was emitted because the token had only a title, or only a still
282
+ // that was suppressed against the indexer's source. Either can still be
283
+ // worth a manifest here, for a reason `find` and `build` never meet: their
284
+ // items always carry the token's title, so a title-only manifest would
285
+ // repeat what the item already says. A curator's item may carry no title
286
+ // at all, and the tombstone then shows nothing — so a substantiated title
287
+ // is written when the item lacks one, and a still is written whenever
288
+ // there is one, because it is the difference between a grid tile and an
289
+ // empty square.
290
+ const synthesizedTitle = curatorTitle || resolvedTitle;
291
+ const titleWorthCarrying = !curatorTitle && resolvedTitle.length > 0;
292
+ if (still || titleWorthCarrying) {
293
+ manifest = {
294
+ refVersion: '1.1.0',
295
+ id: derivedManifestId('', `${synthesizedTitle}\n${still}`),
296
+ created: MANIFEST_CREATED,
297
+ locale: 'en',
298
+ metadata: {
299
+ ...(synthesizedTitle ? { title: synthesizedTitle } : {}),
300
+ ...(still ? { thumbnails: { default: { uri: still } } } : {}),
301
+ },
302
+ };
303
+ }
290
304
  }
291
305
  if (!manifest) {
292
306
  return undefined;
293
307
  }
294
308
  const metadata = manifest.metadata ?? {};
295
- const curatorTitle = typeof item.title === 'string' ? item.title.trim() : '';
296
309
  const overrideTitle = curatorTitle.length > 0 && metadata.title !== curatorTitle;
297
310
  // The id is derived from the finished payload every time, not only when this
298
311
  // command changed something. The indexer keys its id on the token
@@ -304,10 +317,6 @@ function manifestFor(item, resolved) {
304
317
  //
305
318
  // The original id is folded in, so the result stays anchored to the token
306
319
  // rather than floating free of it, and stays deterministic across runs.
307
- //
308
- // `rekey` is read only to record that a thumbnail was recovered; the
309
- // derivation below covers it either way.
310
- void rekey;
311
320
  const finalMetadata = overrideTitle ? { ...metadata, title: curatorTitle } : metadata;
312
321
  return {
313
322
  ...manifest,
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * DP1 Feed Fetcher
3
4
  * Utilities to fetch playlists from DP1 Feed API
@@ -1,40 +1,8 @@
1
+ "use strict";
1
2
  /**
2
3
  * Function Calling Layer
3
4
  * These are the actual implementations called by AI orchestrator via function calling
4
5
  */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
6
  const chalk = require('chalk');
39
7
  const playlistBuilder = require('./playlist-builder');
40
8
  const ff1Device = require('./ff1-device');
@@ -162,7 +130,7 @@ async function verifyPlaylist(params) {
162
130
  }
163
131
  logger.verbose(chalk.cyan('\nValidate playlist'));
164
132
  // Dynamic import to avoid circular dependency
165
- const playlistVerifier = await Promise.resolve().then(() => __importStar(require('./playlist-verifier')));
133
+ const playlistVerifier = await import('./playlist-verifier.js');
166
134
  const validate = playlistVerifier.validatePlaylist ||
167
135
  (playlistVerifier.default && playlistVerifier.default.validatePlaylist) ||
168
136
  playlistVerifier.default;
@@ -226,7 +194,7 @@ async function verifyAddresses(params) {
226
194
  };
227
195
  }
228
196
  // Dynamic import to avoid circular dependency
229
- const addressValidator = await Promise.resolve().then(() => __importStar(require('./address-validator')));
197
+ const addressValidator = await import('./address-validator.js');
230
198
  const validateAddresses = addressValidator.validateAddresses ||
231
199
  (addressValidator.default && addressValidator.default.validateAddresses) ||
232
200
  addressValidator.default;
@@ -268,7 +236,7 @@ async function verifyAddresses(params) {
268
236
  * }
269
237
  */
270
238
  async function getConfiguredDevices() {
271
- const configModule = await Promise.resolve().then(() => __importStar(require('../config')));
239
+ const configModule = await import('../config.js');
272
240
  const getFF1DeviceConfig = configModule.getFF1DeviceConfig ||
273
241
  (configModule.default && configModule.default.getFF1DeviceConfig) ||
274
242
  configModule.default;
@@ -1,40 +1,8 @@
1
+ "use strict";
1
2
  /**
2
3
  * Utilities - Actual function implementations
3
4
  * Contains the real logic for querying NFTs and building playlists
4
5
  */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
6
  const chalk = require('chalk');
39
7
  const nftIndexer = require('./nft-indexer');
40
8
  const feedFetcher = require('./feed-fetcher');
@@ -400,7 +368,7 @@ async function buildDP1Playlist(items, title, slug) {
400
368
  * @returns {Promise<Object>} Result
401
369
  */
402
370
  async function sendToDevice(playlist, deviceName) {
403
- const playlistVerifier = await Promise.resolve().then(() => __importStar(require('./playlist-verifier')));
371
+ const playlistVerifier = await import('./playlist-verifier.js');
404
372
  const verifyPlaylist = playlistVerifier.verifyPlaylist ||
405
373
  (playlistVerifier.default && playlistVerifier.default.verifyPlaylist) ||
406
374
  playlistVerifier.default;