ff1-cli 1.0.0 → 1.0.2

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/dist/src/main.js CHANGED
@@ -116,17 +116,30 @@ function validateRequirements(requirements) {
116
116
  if (!req.blockchain) {
117
117
  throw new Error(`Requirement ${index + 1}: blockchain is required for build_playlist`);
118
118
  }
119
- if (!req.tokenIds || req.tokenIds.length === 0) {
120
- throw new Error(`Requirement ${index + 1}: at least one token ID is required`);
119
+ if (!req.contractAddress) {
120
+ throw new Error(`Requirement ${index + 1}: contractAddress is required for build_playlist`);
121
+ }
122
+ // tokenIds is now optional - if not provided, query random tokens from contract
123
+ if (req.tokenIds && req.tokenIds.length > 0) {
124
+ // Specific token IDs provided
125
+ const quantity = typeof req.quantity === 'number'
126
+ ? Math.min(req.quantity, 20)
127
+ : Math.min(req.tokenIds.length, 20);
128
+ return {
129
+ ...req,
130
+ quantity,
131
+ tokenIds: req.tokenIds,
132
+ };
133
+ }
134
+ else {
135
+ // No token IDs - query random tokens from contract
136
+ const quantity = typeof req.quantity === 'number' ? Math.min(req.quantity, 100) : 100;
137
+ return {
138
+ ...req,
139
+ quantity,
140
+ tokenIds: undefined, // Explicitly set to undefined
141
+ };
121
142
  }
122
- const quantity = typeof req.quantity === 'number'
123
- ? Math.min(req.quantity, 20)
124
- : Math.min(req.tokenIds.length, 20);
125
- return {
126
- ...req,
127
- quantity,
128
- tokenIds: req.tokenIds || [],
129
- };
130
143
  }
131
144
  throw new Error(`Requirement ${index + 1}: invalid type "${req.type}"`);
132
145
  });
@@ -210,9 +223,9 @@ async function buildPlaylist(userRequest, options = {}) {
210
223
  }
211
224
  const sendResult = await utilities.sendToDevice(confirmation.playlist, confirmation.deviceName);
212
225
  if (sendResult.success) {
213
- console.log(chalk_1.default.green('\n✅ Playlist sent successfully!'));
226
+ console.log(chalk_1.default.green('\nPlaylist sent'));
214
227
  if (sendResult.deviceName) {
215
- console.log(chalk_1.default.gray(` Device: ${sendResult.deviceName}`));
228
+ console.log(chalk_1.default.dim(` Device: ${sendResult.deviceName}`));
216
229
  }
217
230
  console.log();
218
231
  return {
@@ -222,9 +235,9 @@ async function buildPlaylist(userRequest, options = {}) {
222
235
  };
223
236
  }
224
237
  console.log();
225
- console.error(chalk_1.default.red(' Failed to send playlist'));
238
+ console.error(chalk_1.default.red('Send failed'));
226
239
  if (sendResult.error) {
227
- console.error(chalk_1.default.red(` ${sendResult.error}`));
240
+ console.error(chalk_1.default.red(` ${sendResult.error}`));
228
241
  }
229
242
  return {
230
243
  success: false,
@@ -243,9 +256,9 @@ async function buildPlaylist(userRequest, options = {}) {
243
256
  while (intentParserResult.needsMoreInfo) {
244
257
  if (!interactive) {
245
258
  // Non-interactive mode: cannot ask for clarification
246
- console.error(chalk_1.default.red('\n❌ More information needed but running in non-interactive mode. Please provide a complete request.'));
259
+ console.error(chalk_1.default.red('\nNeed more information, but running in non-interactive mode. Provide a complete request.'));
247
260
  if (intentParserResult.question) {
248
- console.error(chalk_1.default.yellow('\nAI asked: ') + intentParserResult.question);
261
+ console.error(chalk_1.default.yellow('\nQuestion: ') + intentParserResult.question);
249
262
  }
250
263
  process.exit(1);
251
264
  }
@@ -256,7 +269,7 @@ async function buildPlaylist(userRequest, options = {}) {
256
269
  });
257
270
  // Display the AI's question before asking for input
258
271
  if (intentParserResult.question) {
259
- console.log(chalk_1.default.cyan('\n🤖 ') + intentParserResult.question);
272
+ console.log(chalk_1.default.cyan('\n') + intentParserResult.question);
260
273
  }
261
274
  const userResponse = await new Promise((resolve) => {
262
275
  rl.question(chalk_1.default.yellow('Your response: '), (answer) => {
@@ -265,7 +278,7 @@ async function buildPlaylist(userRequest, options = {}) {
265
278
  });
266
279
  });
267
280
  if (!userResponse) {
268
- console.error(chalk_1.default.red('\n❌ No response provided. Exiting.'));
281
+ console.error(chalk_1.default.red('\nNo response provided. Exiting.'));
269
282
  process.exit(1);
270
283
  }
271
284
  console.log();
@@ -278,7 +291,7 @@ async function buildPlaylist(userRequest, options = {}) {
278
291
  });
279
292
  }
280
293
  if (!intentParserResult.approved) {
281
- console.error(chalk_1.default.red('\n❌ Request not approved by intent parser'));
294
+ console.error(chalk_1.default.red('\nRequest not approved by intent parser'));
282
295
  return null;
283
296
  }
284
297
  const params = intentParserResult.params;
@@ -288,12 +301,12 @@ async function buildPlaylist(userRequest, options = {}) {
288
301
  const sendParams = params;
289
302
  const utilities = getUtilities();
290
303
  console.log();
291
- console.log(chalk_1.default.cyan('Sending to device...'));
304
+ console.log(chalk_1.default.cyan('Sending to device'));
292
305
  const sendResult = await utilities.sendToDevice(sendParams.playlist, sendParams.deviceName);
293
306
  if (sendResult.success) {
294
- console.log(chalk_1.default.green('\n✅ Playlist sent successfully!'));
307
+ console.log(chalk_1.default.green('\nPlaylist sent'));
295
308
  if (sendResult.deviceName) {
296
- console.log(chalk_1.default.gray(` Device: ${sendResult.deviceName}`));
309
+ console.log(chalk_1.default.dim(` Device: ${sendResult.deviceName}`));
297
310
  }
298
311
  console.log();
299
312
  return {
@@ -305,9 +318,9 @@ async function buildPlaylist(userRequest, options = {}) {
305
318
  else {
306
319
  // Send failed - return error without showing the playlist summary
307
320
  console.log();
308
- console.error(chalk_1.default.red(' Failed to send playlist'));
321
+ console.error(chalk_1.default.red('Send failed'));
309
322
  if (sendResult.error) {
310
- console.error(chalk_1.default.red(` ${sendResult.error}`));
323
+ console.error(chalk_1.default.red(` ${sendResult.error}`));
311
324
  }
312
325
  return {
313
326
  success: false,
@@ -361,7 +374,7 @@ async function buildPlaylist(userRequest, options = {}) {
361
374
  });
362
375
  });
363
376
  if (!userResponse) {
364
- console.error(chalk_1.default.red('\n❌ No response provided. Canceling.'));
377
+ console.error(chalk_1.default.red('\nNo response provided. Canceling.'));
365
378
  return null;
366
379
  }
367
380
  console.log();
@@ -379,14 +392,14 @@ async function buildPlaylist(userRequest, options = {}) {
379
392
  }
380
393
  // If no playlist was built, display the AI's message
381
394
  if (!result.playlist && result.message) {
382
- console.log(chalk_1.default.yellow('\n⚠️ ' + result.message));
395
+ console.log(chalk_1.default.yellow('\n' + result.message));
383
396
  }
384
397
  return result;
385
398
  }
386
399
  catch (error) {
387
- console.error(chalk_1.default.red('\n❌ Error:'), error.message);
400
+ console.error(chalk_1.default.red('\nError:'), error.message);
388
401
  if (verbose) {
389
- console.error(chalk_1.default.gray(error.stack));
402
+ console.error(chalk_1.default.dim(error.stack));
390
403
  }
391
404
  throw error;
392
405
  }
@@ -277,14 +277,14 @@ function displayResolutionResults(result) {
277
277
  const successful = result.resolutions.filter((r) => r.resolved);
278
278
  if (successful.length > 0) {
279
279
  successful.forEach((resolution) => {
280
- console.log(chalk_1.default.gray(` ${resolution.domain} → ${resolution.address}`));
280
+ console.log(chalk_1.default.dim(` ${resolution.domain} → ${resolution.address}`));
281
281
  });
282
282
  }
283
283
  // Display failures (but don't make them too prominent)
284
284
  const failed = result.resolutions.filter((r) => !r.resolved);
285
285
  if (failed.length > 0) {
286
286
  failed.forEach((resolution) => {
287
- console.log(chalk_1.default.yellow(` ${resolution.domain}: ${resolution.error || 'Could not resolve'}`));
287
+ console.log(chalk_1.default.yellow(` ${resolution.domain}: ${resolution.error || 'Could not resolve'}`));
288
288
  });
289
289
  }
290
290
  console.log();
@@ -28,7 +28,7 @@ async function fetchPlaylistsFromFeed(feedUrl, limit = 100) {
28
28
  const validLimit = Math.min(limit, 100);
29
29
  const response = await fetch(`${feedUrl}/playlists?limit=${validLimit}&sort=-created`);
30
30
  if (!response.ok) {
31
- console.log(chalk.yellow(` ⚠️ Feed ${feedUrl} returned ${response.status}`));
31
+ console.log(chalk.yellow(` Feed ${feedUrl} returned ${response.status}`));
32
32
  return [];
33
33
  }
34
34
  const data = await response.json();
@@ -40,7 +40,7 @@ async function fetchPlaylistsFromFeed(feedUrl, limit = 100) {
40
40
  }));
41
41
  }
42
42
  catch (error) {
43
- console.log(chalk.yellow(` ⚠️ Failed to fetch from ${feedUrl}: ${error.message}`));
43
+ console.log(chalk.yellow(` Failed to fetch from ${feedUrl}: ${error.message}`));
44
44
  return [];
45
45
  }
46
46
  }
@@ -80,13 +80,13 @@ async function sendPlaylistToDevice(params) {
80
80
  deviceName,
81
81
  });
82
82
  if (result.success) {
83
- console.log(chalk.green('\n✓ Sent to device'));
83
+ console.log(chalk.green('\nSent to device'));
84
84
  if (result.deviceName) {
85
- console.log(chalk.gray(` ${result.deviceName}`));
85
+ console.log(chalk.dim(` ${result.deviceName}`));
86
86
  }
87
87
  }
88
88
  else {
89
- console.error(chalk.red('\n✗ Could not send to device'));
89
+ console.error(chalk.red('\nSend failed'));
90
90
  if (result.error) {
91
91
  console.error(chalk.red(` ${result.error}`));
92
92
  }
@@ -115,7 +115,7 @@ async function resolveDomains(params) {
115
115
  const { domains, displayResults = true } = params;
116
116
  if (!domains || !Array.isArray(domains) || domains.length === 0) {
117
117
  const error = 'No domains provided for resolution';
118
- console.error(chalk.red(`\n${error}`));
118
+ console.error(chalk.red(`\n${error}`));
119
119
  return {
120
120
  success: false,
121
121
  domainMap: {},
@@ -158,7 +158,7 @@ async function verifyPlaylist(params) {
158
158
  error: 'No playlist provided for verification',
159
159
  };
160
160
  }
161
- console.log(chalk.cyan('\nValidating playlist...'));
161
+ console.log(chalk.cyan('\nValidate playlist'));
162
162
  // Dynamic import to avoid circular dependency
163
163
  const playlistVerifier = await Promise.resolve().then(() => __importStar(require('./playlist-verifier')));
164
164
  const verify = playlistVerifier.verifyPlaylist ||
@@ -172,17 +172,17 @@ async function verifyPlaylist(params) {
172
172
  }
173
173
  const result = verify(playlist);
174
174
  if (result.valid) {
175
- console.log(chalk.green('Playlist looks good'));
175
+ console.log(chalk.green('Playlist looks good'));
176
176
  if (playlist.title) {
177
- console.log(chalk.gray(` Title: ${playlist.title}`));
177
+ console.log(chalk.dim(` Title: ${playlist.title}`));
178
178
  }
179
179
  if (playlist.items) {
180
- console.log(chalk.gray(` Items: ${playlist.items.length}`));
180
+ console.log(chalk.dim(` Items: ${playlist.items.length}`));
181
181
  }
182
182
  console.log();
183
183
  }
184
184
  else {
185
- console.error(chalk.red('Playlist has issues'));
185
+ console.error(chalk.red('Playlist has issues'));
186
186
  console.error(chalk.red(` ${result.error}`));
187
187
  if (result.details && result.details.length > 0) {
188
188
  console.log(chalk.yellow('\n Missing or invalid fields:'));
@@ -249,15 +249,15 @@ async function verifyAddresses(params) {
249
249
  : r.type === 'contract'
250
250
  ? 'Tezos Contract'
251
251
  : 'Tezos User';
252
- console.log(chalk.gray(` • ${r.address} (${typeLabel})`));
252
+ console.log(chalk.dim(` • ${r.address} (${typeLabel})`));
253
253
  if (r.normalized) {
254
- console.log(chalk.gray(` Checksummed: ${r.normalized}`));
254
+ console.log(chalk.dim(` Checksummed: ${r.normalized}`));
255
255
  }
256
256
  });
257
257
  console.log();
258
258
  }
259
259
  else {
260
- console.error(chalk.red('\n✗ Address validation failed'));
260
+ console.error(chalk.red('\nAddress validation failed'));
261
261
  result.errors.forEach((err) => {
262
262
  console.error(chalk.red(` • ${err}`));
263
263
  });
@@ -34,7 +34,7 @@ function initializeUtilities(_config) {
34
34
  async function queryTokensByAddress(ownerAddress, quantity, duration = 10) {
35
35
  try {
36
36
  const shouldFetchAll = quantity === 'all' || quantity === undefined || quantity === null;
37
- const batchSize = 100; // Fetch 100 tokens per page
37
+ const batchSize = 50; // Fetch 50 tokens per page
38
38
  let allTokens = [];
39
39
  let offset = 0;
40
40
  let hasMore = true;
@@ -61,7 +61,7 @@ async function queryTokensByAddress(ownerAddress, quantity, duration = 10) {
61
61
  }
62
62
  allTokens = allTokens.concat(result.tokens);
63
63
  offset += batchSize;
64
- console.log(chalk.gray(` → Fetched ${allTokens.length} tokens so far...`));
64
+ console.log(chalk.dim(` → Fetched ${allTokens.length} tokens so far...`));
65
65
  // If we got fewer tokens than the batch size, we've reached the end
66
66
  if (result.tokens.length < batchSize) {
67
67
  hasMore = false;
@@ -94,7 +94,7 @@ async function queryTokensByAddress(ownerAddress, quantity, duration = 10) {
94
94
  if (typeof quantity === 'number' && selectedTokens.length > quantity) {
95
95
  selectedTokens = shuffleArray([...selectedTokens]).slice(0, quantity);
96
96
  }
97
- console.log(chalk.grey(`✓ Got ${selectedTokens.length} token(s)`));
97
+ console.log(chalk.dim(`Got ${selectedTokens.length} token(s)`));
98
98
  // Convert tokens to DP1 items
99
99
  const items = [];
100
100
  let skippedCount = 0;
@@ -118,7 +118,7 @@ async function queryTokensByAddress(ownerAddress, quantity, duration = 10) {
118
118
  }
119
119
  }
120
120
  if (skippedCount > 0) {
121
- console.log(chalk.yellow(` Skipped ${skippedCount} token(s) with invalid data (data URIs or URLs too long)`));
121
+ console.log(chalk.yellow(` Skipped ${skippedCount} token(s) with invalid data (data URIs or URLs too long)`));
122
122
  }
123
123
  return items;
124
124
  }
@@ -150,7 +150,7 @@ async function queryRequirement(requirement, duration = 10) {
150
150
  console.log(chalk.cyan(`\nResolving domain ${ownerAddress}...`));
151
151
  const resolution = await domainResolver.resolveDomain(ownerAddress);
152
152
  if (resolution.resolved && resolution.address) {
153
- console.log(chalk.gray(` ${resolution.domain} → ${resolution.address}`));
153
+ console.log(chalk.dim(` ${resolution.domain} → ${resolution.address}`));
154
154
  // Use resolved address instead of domain
155
155
  return await queryTokensByAddress(resolution.address, quantity, duration);
156
156
  }
@@ -179,7 +179,58 @@ async function queryRequirement(requirement, duration = 10) {
179
179
  console.log(chalk.cyan(`Querying ${blockchain}${contractAddress ? ' (' + contractAddress.substring(0, 10) + '...)' : ''}...`));
180
180
  let items = [];
181
181
  try {
182
- // Handle different blockchain types
182
+ // Check if we're querying by contract (no specific token IDs) or by specific token IDs
183
+ const isContractQuery = contractAddress && (!tokenIds || tokenIds.length === 0);
184
+ if (isContractQuery) {
185
+ // Query random tokens from the contract
186
+ console.log(chalk.cyan(` Fetching ${quantity || 100} random token(s) from contract ${contractAddress.substring(0, 10)}...`));
187
+ const limit = Math.min(quantity || 100, 100);
188
+ const result = await nftIndexer.queryTokensByContract(contractAddress, limit);
189
+ if (!result.success) {
190
+ console.log(chalk.yellow(` Could not fetch tokens from contract`));
191
+ console.log(chalk.cyan(` → Trying as owner address instead...`));
192
+ return await queryTokensByAddress(contractAddress, quantity, duration);
193
+ }
194
+ if (result.tokens.length === 0) {
195
+ console.log(chalk.yellow(` No tokens found in contract ${contractAddress}`));
196
+ console.log(chalk.cyan(` → Trying as owner address instead...`));
197
+ return await queryTokensByAddress(contractAddress, quantity, duration);
198
+ }
199
+ // Convert tokens to DP1 items (similar to queryTokensByAddress logic)
200
+ let allTokens = result.tokens;
201
+ // Apply quantity limit with random selection if needed
202
+ if (quantity && allTokens.length > quantity) {
203
+ allTokens = shuffleArray([...allTokens]).slice(0, quantity);
204
+ }
205
+ console.log(chalk.dim(` Got ${allTokens.length} token(s)`));
206
+ // Convert tokens to DP1 items
207
+ const dp1Items = [];
208
+ let skippedCount = 0;
209
+ for (const token of allTokens) {
210
+ // Detect blockchain from contract address
211
+ let chain = 'ethereum';
212
+ const contractAddr = token.contract_address || token.contractAddress || '';
213
+ if (contractAddr.startsWith('KT')) {
214
+ chain = 'tezos';
215
+ }
216
+ // Map indexer token data to standard format
217
+ const tokenData = nftIndexer.mapIndexerDataToStandardFormat(token, chain);
218
+ if (tokenData.success) {
219
+ const dp1Result = nftIndexer.convertToDP1Item(tokenData, duration);
220
+ if (dp1Result.success && dp1Result.item) {
221
+ dp1Items.push(dp1Result.item);
222
+ }
223
+ else if (!dp1Result.success) {
224
+ skippedCount++;
225
+ }
226
+ }
227
+ }
228
+ if (skippedCount > 0) {
229
+ console.log(chalk.yellow(` Skipped ${skippedCount} token(s) with invalid data (data URIs or URLs too long)`));
230
+ }
231
+ return dp1Items;
232
+ }
233
+ // Handle specific token IDs (original logic)
183
234
  if (blockchain.toLowerCase() === 'tezos') {
184
235
  // Tezos NFTs
185
236
  if (tokenIds && tokenIds.length > 0) {
@@ -294,9 +345,9 @@ async function buildPlaylistDirect(params, options = {}) {
294
345
  allItems.push(...items);
295
346
  }
296
347
  catch (error) {
297
- console.error(chalk.red(`Failed: ${error.message}`));
348
+ console.error(chalk.red(` Failed: ${error.message}`));
298
349
  if (verbose) {
299
- console.error(chalk.gray(error.stack));
350
+ console.error(chalk.dim(error.stack));
300
351
  }
301
352
  }
302
353
  }
@@ -331,23 +382,23 @@ async function buildPlaylistDirect(params, options = {}) {
331
382
  if (publishResult.success) {
332
383
  console.log(chalk.green(`✓ Published to feed server`));
333
384
  if (publishResult.playlistId) {
334
- console.log(chalk.gray(` Playlist ID: ${publishResult.playlistId}`));
385
+ console.log(chalk.dim(` Playlist ID: ${publishResult.playlistId}`));
335
386
  }
336
387
  if (publishResult.feedServer) {
337
- console.log(chalk.gray(` Server: ${publishResult.feedServer}`));
388
+ console.log(chalk.dim(` Server: ${publishResult.feedServer}`));
338
389
  }
339
390
  }
340
391
  else {
341
- console.error(chalk.red(`✗ Failed to publish: ${publishResult.error}`));
392
+ console.error(chalk.red(`Publish failed: ${publishResult.error}`));
342
393
  if (publishResult.message) {
343
- console.error(chalk.gray(` ${publishResult.message}`));
394
+ console.error(chalk.dim(` ${publishResult.message}`));
344
395
  }
345
396
  }
346
397
  }
347
398
  catch (error) {
348
- console.error(chalk.red(`✗ Failed to publish: ${error.message}`));
399
+ console.error(chalk.red(`Publish failed: ${error.message}`));
349
400
  if (verbose) {
350
- console.error(chalk.gray(error.stack));
401
+ console.error(chalk.dim(error.stack));
351
402
  }
352
403
  }
353
404
  }
@@ -98,14 +98,17 @@ function buildTokenCID(chain, contractAddress, tokenId) {
98
98
  * const tokens = await queryTokens({ token_cids: ['eip155:1:erc721:0xabc:123'], owners: ['0x1234...'] });
99
99
  */
100
100
  async function queryTokens(params = {}) {
101
- const { token_cids = [], owners = [], limit = 50, offset = 0 } = params;
101
+ const { token_cids = [], owners = [], contract_addresses = [], limit = 50, offset = 0 } = params;
102
102
  // Build GraphQL query without variables - inline parameters
103
103
  // (API expects inline parameters, not variables)
104
104
  const ownerFilter = owners.length > 0 ? `owners: ${JSON.stringify(owners)},` : '';
105
105
  const tokenCidsFilter = token_cids.length > 0 ? `token_cids: ${JSON.stringify(token_cids)},` : '';
106
+ const contractFilter = contract_addresses.length > 0
107
+ ? `contract_addresses: ${JSON.stringify(contract_addresses)},`
108
+ : '';
106
109
  const query = `
107
110
  query {
108
- tokens(${ownerFilter} ${tokenCidsFilter} expands: ["enrichment_source", "metadata_media_asset", "enrichment_source_media_asset"], limit: ${limit}, offset: ${offset}) {
111
+ tokens(${ownerFilter} ${tokenCidsFilter} ${contractFilter} expands: ["enrichment_source", "metadata_media_asset", "enrichment_source_media_asset"], limit: ${limit}, offset: ${offset}) {
109
112
  items {
110
113
  token_cid
111
114
  chain
@@ -958,26 +961,45 @@ async function searchNFTs(params) {
958
961
  */
959
962
  async function queryTokensByOwner(ownerAddress, limit = 100, offset = 0) {
960
963
  try {
961
- logger.info(`[NFT Indexer] Querying tokens for owner: ${ownerAddress} (limit: ${limit}, offset: ${offset})`);
964
+ logger.info(`[NFT Indexer] Querying tokens by owner: ${ownerAddress}`);
962
965
  const tokens = await queryTokens({
963
966
  owners: [ownerAddress],
964
967
  limit,
965
968
  offset,
966
969
  });
967
- logger.info(`[NFT Indexer] Found ${tokens.length} token(s) for owner ${ownerAddress}`);
968
970
  return {
969
971
  success: true,
970
972
  tokens,
971
- count: tokens.length,
972
973
  };
973
974
  }
974
975
  catch (error) {
975
- logger.error('[NFT Indexer] Failed to query tokens by owner:', error.message);
976
+ logger.error(`[NFT Indexer] Failed to query tokens by owner: ${error.message}`);
976
977
  return {
977
978
  success: false,
979
+ tokens: [],
978
980
  error: error.message,
981
+ };
982
+ }
983
+ }
984
+ async function queryTokensByContract(contractAddress, limit = 100, offset = 0) {
985
+ try {
986
+ logger.info(`[NFT Indexer] Querying tokens by contract: ${contractAddress}`);
987
+ const tokens = await queryTokens({
988
+ contract_addresses: [contractAddress],
989
+ limit,
990
+ offset,
991
+ });
992
+ return {
993
+ success: true,
994
+ tokens,
995
+ };
996
+ }
997
+ catch (error) {
998
+ logger.error(`[NFT Indexer] Failed to query tokens by contract: ${error.message}`);
999
+ return {
1000
+ success: false,
979
1001
  tokens: [],
980
- count: 0,
1002
+ error: error.message,
981
1003
  };
982
1004
  }
983
1005
  }
@@ -998,6 +1020,7 @@ module.exports = {
998
1020
  convertToDP1Item,
999
1021
  // Address-based functions
1000
1022
  queryTokensByOwner,
1023
+ queryTokensByContract,
1001
1024
  // Unified GraphQL query
1002
1025
  queryTokens,
1003
1026
  // Workflow and polling functions
@@ -65,7 +65,7 @@ async function getAvailableDevices() {
65
65
  }
66
66
  catch (error) {
67
67
  if (process.env.DEBUG) {
68
- console.log(chalk_1.default.gray(`[DEBUG] Error loading devices: ${error.message}`));
68
+ console.log(chalk_1.default.dim(`[DEBUG] Error loading devices: ${error.message}`));
69
69
  }
70
70
  // Silently fail if config can't be loaded
71
71
  }
@@ -87,23 +87,23 @@ async function confirmPlaylistForSending(filePath, deviceName) {
87
87
  // Convert string "null" to undefined (in case model passes it literally)
88
88
  const actualDeviceName = deviceName === 'null' || deviceName === '' ? undefined : deviceName;
89
89
  if (process.env.DEBUG) {
90
- console.error(chalk_1.default.gray(`[DEBUG] confirmPlaylistForSending called with: filePath="${filePath}", deviceName="${deviceName}" -> "${actualDeviceName}"`));
90
+ console.error(chalk_1.default.dim(`[DEBUG] confirmPlaylistForSending called with: filePath="${filePath}", deviceName="${deviceName}" -> "${actualDeviceName}"`));
91
91
  }
92
92
  try {
93
93
  // Check if file exists
94
- console.log(chalk_1.default.cyan(`Checking playlist file: ${resolvedPath}...`));
94
+ console.log(chalk_1.default.cyan(`Playlist file: ${resolvedPath}`));
95
95
  let _fileExists = false;
96
96
  let playlist;
97
97
  try {
98
98
  const content = await fs_1.promises.readFile(resolvedPath, 'utf-8');
99
99
  playlist = JSON.parse(content);
100
100
  _fileExists = true;
101
- console.log(chalk_1.default.green('File found'));
101
+ console.log(chalk_1.default.green('File loaded'));
102
102
  }
103
103
  catch (error) {
104
104
  const errorMsg = error.message;
105
105
  if (errorMsg.includes('ENOENT') || errorMsg.includes('no such file')) {
106
- console.log(chalk_1.default.red(`✗ File not found: ${resolvedPath}`));
106
+ console.log(chalk_1.default.red(`File not found: ${resolvedPath}`));
107
107
  return {
108
108
  success: false,
109
109
  filePath: resolvedPath,
@@ -125,12 +125,12 @@ async function confirmPlaylistForSending(filePath, deviceName) {
125
125
  };
126
126
  }
127
127
  // Validate playlist structure
128
- console.log(chalk_1.default.cyan('Validating playlist...'));
128
+ console.log(chalk_1.default.cyan('Validation'));
129
129
  // Dynamic import to avoid circular dependency
130
130
  const { verifyPlaylist } = await Promise.resolve().then(() => __importStar(require('./playlist-verifier')));
131
131
  const verifyResult = verifyPlaylist(playlist);
132
132
  if (!verifyResult.valid) {
133
- console.log(chalk_1.default.red('Playlist validation failed'));
133
+ console.log(chalk_1.default.red('Playlist validation failed'));
134
134
  const detailLines = verifyResult.details?.map((d) => ` • ${d.path}: ${d.message}`).join('\n') ||
135
135
  verifyResult.error;
136
136
  const detailPaths = verifyResult.details?.map((d) => d.path) || [];
@@ -153,7 +153,7 @@ async function confirmPlaylistForSending(filePath, deviceName) {
153
153
  message: `This playlist doesn't match DP-1 specification.\n\nErrors:\n${detailLines}${hintText}`,
154
154
  };
155
155
  }
156
- console.log(chalk_1.default.green(' Playlist is valid'));
156
+ console.log(chalk_1.default.green('Valid DP-1 playlist'));
157
157
  // Display confirmation details
158
158
  const itemCount = playlist.items?.length || 0;
159
159
  const title = playlist.title || 'Untitled';
@@ -165,10 +165,10 @@ async function confirmPlaylistForSending(filePath, deviceName) {
165
165
  // Get available devices
166
166
  availableDevices = await getAvailableDevices();
167
167
  if (process.env.DEBUG) {
168
- console.error(chalk_1.default.gray(`[DEBUG] selectedDevice is null/undefined`));
169
- console.error(chalk_1.default.gray(`[DEBUG] Available devices found: ${availableDevices.length}`));
168
+ console.error(chalk_1.default.dim(`[DEBUG] selectedDevice is null/undefined`));
169
+ console.error(chalk_1.default.dim(`[DEBUG] Available devices found: ${availableDevices.length}`));
170
170
  availableDevices.forEach((d) => {
171
- console.error(chalk_1.default.gray(`[DEBUG] Device: ${d.name} (${d.host})`));
171
+ console.error(chalk_1.default.dim(`[DEBUG] Device: ${d.name} (${d.host})`));
172
172
  });
173
173
  }
174
174
  if (availableDevices.length === 0) {
@@ -185,7 +185,7 @@ async function confirmPlaylistForSending(filePath, deviceName) {
185
185
  else if (availableDevices.length === 1) {
186
186
  // Auto-select single device
187
187
  selectedDevice = availableDevices[0].name || availableDevices[0].host;
188
- console.log(chalk_1.default.cyan(`Auto-selecting device: ${selectedDevice}`));
188
+ console.log(chalk_1.default.cyan(`Device: ${selectedDevice} (auto)`));
189
189
  }
190
190
  else {
191
191
  // Multiple devices - need user to choose
@@ -193,14 +193,14 @@ async function confirmPlaylistForSending(filePath, deviceName) {
193
193
  }
194
194
  }
195
195
  console.log();
196
- console.log(chalk_1.default.bold('Playlist Summary:'));
197
- console.log(chalk_1.default.gray(` Title: ${title}`));
198
- console.log(chalk_1.default.gray(` Items: ${itemCount}`));
196
+ console.log(chalk_1.default.bold('Send Summary'));
197
+ console.log(chalk_1.default.dim(` Title: ${title}`));
198
+ console.log(chalk_1.default.dim(` Items: ${itemCount}`));
199
199
  if (selectedDevice) {
200
- console.log(chalk_1.default.gray(` Device: ${selectedDevice}`));
200
+ console.log(chalk_1.default.dim(` Device: ${selectedDevice}`));
201
201
  }
202
202
  else if (availableDevices.length > 1) {
203
- console.log(chalk_1.default.gray(` Device: (to be selected)`));
203
+ console.log(chalk_1.default.dim(' Device: select one'));
204
204
  }
205
205
  console.log();
206
206
  // If multiple devices, return needsDeviceSelection flag
@@ -229,7 +229,7 @@ async function confirmPlaylistForSending(filePath, deviceName) {
229
229
  }
230
230
  catch (error) {
231
231
  const errorMsg = error.message;
232
- console.log(chalk_1.default.red(`✗ Error: ${errorMsg}`));
232
+ console.log(chalk_1.default.red(`Error: ${errorMsg}`));
233
233
  return {
234
234
  success: false,
235
235
  filePath: resolvedPath,
@@ -125,30 +125,30 @@ async function verifyPlaylistFile(playlistPath) {
125
125
  */
126
126
  function printVerificationResult(result, filename) {
127
127
  if (result.valid) {
128
- console.log(chalk_1.default.green('\n✅ Playlist is valid!'));
128
+ console.log(chalk_1.default.green('\nPlaylist is valid'));
129
129
  if (filename) {
130
- console.log(chalk_1.default.gray(` File: ${filename}`));
130
+ console.log(chalk_1.default.dim(` File: ${filename}`));
131
131
  }
132
132
  if (result.playlist) {
133
- console.log(chalk_1.default.gray(` Title: ${result.playlist.title}`));
134
- console.log(chalk_1.default.gray(` Items: ${result.playlist.items?.length || 0}`));
135
- console.log(chalk_1.default.gray(` DP Version: ${result.playlist.dpVersion}`));
133
+ console.log(chalk_1.default.dim(` Title: ${result.playlist.title}`));
134
+ console.log(chalk_1.default.dim(` Items: ${result.playlist.items?.length || 0}`));
135
+ console.log(chalk_1.default.dim(` DP Version: ${result.playlist.dpVersion}`));
136
136
  if (result.playlist.signature && typeof result.playlist.signature === 'string') {
137
- console.log(chalk_1.default.gray(` Signature: ${result.playlist.signature.substring(0, 30)}...`));
137
+ console.log(chalk_1.default.dim(` Signature: ${result.playlist.signature.substring(0, 30)}...`));
138
138
  }
139
139
  }
140
140
  console.log();
141
141
  }
142
142
  else {
143
- console.log(chalk_1.default.red('\n❌ Playlist validation failed!'));
143
+ console.log(chalk_1.default.red('\nPlaylist validation failed'));
144
144
  if (filename) {
145
- console.log(chalk_1.default.gray(` File: ${filename}`));
145
+ console.log(chalk_1.default.dim(` File: ${filename}`));
146
146
  }
147
- console.log(chalk_1.default.red(` Error: ${result.error}`));
147
+ console.log(chalk_1.default.red(` Error: ${result.error}`));
148
148
  if (result.details && result.details.length > 0) {
149
- console.log(chalk_1.default.yellow('\n Validation errors:'));
149
+ console.log(chalk_1.default.yellow('\n Validation errors:'));
150
150
  result.details.forEach((detail) => {
151
- console.log(chalk_1.default.yellow(` • ${detail.path}: ${detail.message}`));
151
+ console.log(chalk_1.default.yellow(` • ${detail.path}: ${detail.message}`));
152
152
  });
153
153
  }
154
154
  console.log();