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.
@@ -576,17 +576,17 @@ async function buildPlaylistWithAI(params, options = {}) {
576
576
  // - finish_reason includes 'MALFORMED_FUNCTION_CALL' (Gemini tried but failed)
577
577
  // - Any other case where we have items but no playlist
578
578
  if (verbose) {
579
- console.log(chalk.gray(`→ finish_reason: ${response.choices[0].finish_reason}`));
580
- console.log(chalk.gray(`→ has content: ${!!message.content}`));
581
- console.log(chalk.gray(`→ has tool_calls: ${!!message.tool_calls}`));
582
- console.log(chalk.gray(`→ collectedItems: ${collectedItems.length}, finalPlaylist: ${!!finalPlaylist}`));
579
+ console.log(chalk.dim(`→ finish_reason: ${response.choices[0].finish_reason}`));
580
+ console.log(chalk.dim(`→ has content: ${!!message.content}`));
581
+ console.log(chalk.dim(`→ has tool_calls: ${!!message.tool_calls}`));
582
+ console.log(chalk.dim(`→ collectedItems: ${collectedItems.length}, finalPlaylist: ${!!finalPlaylist}`));
583
583
  }
584
584
  if (!message.tool_calls && collectedItems.length > 0 && !finalPlaylist) {
585
585
  const finishReason = response.choices[0].finish_reason || '';
586
586
  // If Gemini keeps failing with MALFORMED_FUNCTION_CALL, call build_playlist directly
587
587
  if (finishReason.includes('MALFORMED_FUNCTION_CALL') || finishReason.includes('filter')) {
588
588
  if (verbose) {
589
- console.log(chalk.yellow(`⚠️ AI's function call is malformed - calling build_playlist directly...`));
589
+ console.log(chalk.yellow(`AI function call malformed. Calling build_playlist directly.`));
590
590
  }
591
591
  // Call build_playlist directly with the collected item IDs
592
592
  try {
@@ -610,14 +610,14 @@ async function buildPlaylistWithAI(params, options = {}) {
610
610
  }
611
611
  catch (error) {
612
612
  if (verbose) {
613
- console.log(chalk.red(`✗ Failed to build playlist directly: ${error.message}`));
613
+ console.log(chalk.red(`Failed to build playlist directly: ${error.message}`));
614
614
  }
615
615
  }
616
616
  }
617
617
  else if (iterationCount < maxIterations - 1) {
618
618
  // Try one more time with a system message
619
619
  if (verbose) {
620
- console.log(chalk.yellow(`⚠️ AI stopped without calling build_playlist (reason: ${finishReason}) - forcing it to continue...`));
620
+ console.log(chalk.yellow(`AI stopped without calling build_playlist (reason: ${finishReason}). Forcing it to continue.`));
621
621
  }
622
622
  messages.push({
623
623
  role: 'system',
@@ -632,24 +632,24 @@ async function buildPlaylistWithAI(params, options = {}) {
632
632
  console.log(chalk.cyan(message.content));
633
633
  }
634
634
  if (verbose) {
635
- console.log(chalk.gray(`\nIteration ${iterationCount}:`));
635
+ console.log(chalk.dim(`\nIteration ${iterationCount}:`));
636
636
  }
637
637
  // Execute function calls if any
638
638
  if (message.tool_calls && message.tool_calls.length > 0) {
639
639
  if (verbose) {
640
- console.log(chalk.gray(`→ Executing ${message.tool_calls.length} function(s)...`));
640
+ console.log(chalk.dim(`→ Executing ${message.tool_calls.length} function(s)...`));
641
641
  }
642
642
  for (const toolCall of message.tool_calls) {
643
643
  const functionName = toolCall.function.name;
644
644
  const args = JSON.parse(toolCall.function.arguments);
645
645
  if (verbose) {
646
- console.log(chalk.gray(`\n • Function: ${chalk.bold(functionName)}`));
647
- console.log(chalk.gray(` Input: ${JSON.stringify(args, null, 2).split('\n').join('\n ')}`));
646
+ console.log(chalk.dim(`\n • Function: ${chalk.bold(functionName)}`));
647
+ console.log(chalk.dim(` Input: ${JSON.stringify(args, null, 2).split('\n').join('\n ')}`));
648
648
  }
649
649
  try {
650
650
  const result = await executeFunction(functionName, args);
651
651
  if (verbose) {
652
- console.log(chalk.gray(` Output: ${JSON.stringify(result, null, 2).split('\n').join('\n ')}`));
652
+ console.log(chalk.dim(` Output: ${JSON.stringify(result, null, 2).split('\n').join('\n ')}`));
653
653
  }
654
654
  // Track collected item IDs from query_requirement
655
655
  if (functionName === 'query_requirement' && Array.isArray(result)) {
@@ -692,12 +692,12 @@ async function buildPlaylistWithAI(params, options = {}) {
692
692
  else {
693
693
  verificationFailures++;
694
694
  if (verbose) {
695
- console.log(chalk.yellow(`⚠️ Playlist verification failed (attempt ${verificationFailures}/${maxVerificationRetries})`));
695
+ console.log(chalk.yellow(`Playlist verification failed (attempt ${verificationFailures}/${maxVerificationRetries})`));
696
696
  }
697
697
  // Check if we've exceeded max retries
698
698
  if (verificationFailures >= maxVerificationRetries) {
699
699
  if (verbose) {
700
- console.log(chalk.red(`✗ Playlist validation failed after ${maxVerificationRetries} retries`));
700
+ console.log(chalk.red(`Playlist validation failed after ${maxVerificationRetries} retries`));
701
701
  }
702
702
  return {
703
703
  success: false,
@@ -752,7 +752,7 @@ async function buildPlaylistWithAI(params, options = {}) {
752
752
  else {
753
753
  // AI has finished
754
754
  if (verbose) {
755
- console.log(chalk.gray('\n→ AI has finished (no more tool calls)'));
755
+ console.log(chalk.dim('\n→ AI has finished (no more tool calls)'));
756
756
  if (!message.content) {
757
757
  console.log(chalk.red('→ AI sent NO content and NO tool calls!'));
758
758
  }
@@ -799,23 +799,23 @@ async function buildPlaylistWithAI(params, options = {}) {
799
799
  if (publishResult.success) {
800
800
  console.log(chalk.green(`✓ Published to feed server`));
801
801
  if (publishResult.playlistId) {
802
- console.log(chalk.gray(` Playlist ID: ${publishResult.playlistId}`));
802
+ console.log(chalk.dim(` Playlist ID: ${publishResult.playlistId}`));
803
803
  }
804
804
  if (publishResult.feedServer) {
805
- console.log(chalk.gray(` Server: ${publishResult.feedServer}`));
805
+ console.log(chalk.dim(` Server: ${publishResult.feedServer}`));
806
806
  }
807
807
  }
808
808
  else {
809
- console.error(chalk.red(`✗ Failed to publish: ${publishResult.error}`));
809
+ console.error(chalk.red(`Publish failed: ${publishResult.error}`));
810
810
  if (publishResult.message) {
811
- console.error(chalk.gray(` ${publishResult.message}`));
811
+ console.error(chalk.dim(` ${publishResult.message}`));
812
812
  }
813
813
  }
814
814
  }
815
815
  catch (error) {
816
- console.error(chalk.red(`✗ Failed to publish: ${error.message}`));
816
+ console.error(chalk.red(`Publish failed: ${error.message}`));
817
817
  if (verbose) {
818
- console.error(chalk.gray(error.stack));
818
+ console.error(chalk.dim(error.stack));
819
819
  }
820
820
  }
821
821
  }
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getConfigPaths = getConfigPaths;
6
7
  exports.getConfig = getConfig;
7
8
  exports.sanitizationLevelToNumber = sanitizationLevelToNumber;
8
9
  exports.getBrowserConfig = getBrowserConfig;
@@ -15,6 +16,13 @@ exports.createSampleConfig = createSampleConfig;
15
16
  exports.listAvailableModels = listAvailableModels;
16
17
  const fs_1 = __importDefault(require("fs"));
17
18
  const path_1 = __importDefault(require("path"));
19
+ const os_1 = __importDefault(require("os"));
20
+ function getConfigPaths() {
21
+ const localPath = path_1.default.join(process.cwd(), 'config.json');
22
+ const configBase = process.env.XDG_CONFIG_HOME || path_1.default.join(os_1.default.homedir(), '.config');
23
+ const userPath = path_1.default.join(configBase, 'ff1', 'config.json');
24
+ return { localPath, userPath };
25
+ }
18
26
  /**
19
27
  * Load configuration from config.json or environment variables
20
28
  * Priority: config.json > .env > defaults
@@ -25,7 +33,7 @@ const path_1 = __importDefault(require("path"));
25
33
  * @returns {number} returns.defaultDuration - Default duration per item in seconds
26
34
  */
27
35
  function loadConfig() {
28
- const configPath = path_1.default.join(process.cwd(), 'config.json');
36
+ const { localPath, userPath } = getConfigPaths();
29
37
  // Default configuration supporting Grok as default
30
38
  const defaultConfig = {
31
39
  defaultModel: process.env.DEFAULT_MODEL || 'grok',
@@ -72,10 +80,11 @@ function loadConfig() {
72
80
  feed: {
73
81
  baseURLs: process.env.FEED_BASE_URLS
74
82
  ? process.env.FEED_BASE_URLS.split(',')
75
- : ['https://feed.feralfile.com/api/v1'],
83
+ : ['https://dp1-feed-operator-api-prod.autonomy-system.workers.dev/api/v1'],
76
84
  },
77
85
  };
78
86
  // Try to load config.json if it exists
87
+ const configPath = fs_1.default.existsSync(localPath) ? localPath : userPath;
79
88
  if (fs_1.default.existsSync(configPath)) {
80
89
  try {
81
90
  const fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
@@ -193,7 +202,7 @@ function getFeedConfig() {
193
202
  }
194
203
  else {
195
204
  // Default feed URL
196
- urls = ['https://feed.feralfile.com/api/v1'];
205
+ urls = ['https://dp1-feed-operator-api-prod.autonomy-system.workers.dev/api/v1'];
197
206
  }
198
207
  return {
199
208
  baseURLs: urls,
@@ -323,8 +332,9 @@ function validateConfig(modelName) {
323
332
  * @returns {Promise<string>} Path to the created config file
324
333
  * @throws {Error} If config.json already exists or example file is missing
325
334
  */
326
- async function createSampleConfig() {
327
- const configPath = path_1.default.join(process.cwd(), 'config.json');
335
+ async function createSampleConfig(targetPath) {
336
+ const { userPath } = getConfigPaths();
337
+ const configPath = targetPath || userPath;
328
338
  // Check if config.json already exists in user's directory
329
339
  if (fs_1.default.existsSync(configPath)) {
330
340
  throw new Error('config.json already exists');
@@ -332,12 +342,16 @@ async function createSampleConfig() {
332
342
  // Look for config.json.example in the package directory
333
343
  // When compiled, this file is in dist/src/config.js
334
344
  // The template is at the package root: ../../config.json.example
335
- const packageRoot = path_1.default.join(__dirname, '../..');
336
- const examplePath = path_1.default.join(packageRoot, 'config.json.example');
337
- if (!fs_1.default.existsSync(examplePath)) {
338
- throw new Error(`config.json.example not found at ${examplePath}. This is likely a package installation issue.`);
345
+ const exampleCandidates = [
346
+ path_1.default.join(process.cwd(), 'config.json.example'),
347
+ path_1.default.join(__dirname, '../..', 'config.json.example'),
348
+ ];
349
+ const examplePath = exampleCandidates.find((candidate) => fs_1.default.existsSync(candidate));
350
+ if (!examplePath) {
351
+ throw new Error('config.json.example not found. This is likely a package installation issue.');
339
352
  }
340
353
  const exampleConfig = fs_1.default.readFileSync(examplePath, 'utf-8');
354
+ fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
341
355
  fs_1.default.writeFileSync(configPath, exampleConfig, 'utf-8');
342
356
  return configPath;
343
357
  }
@@ -113,38 +113,48 @@ OUTPUT CONTRACT
113
113
  - Use correct types; never truncate addresses/tokenIds; tokenIds are strings; quantity is a number.
114
114
 
115
115
  REQUIREMENT TYPES (BUILD)
116
- - build_playlist: { type, blockchain: "ethereum"|"tezos", contractAddress, tokenIds: string[], quantity?: number, source?: string }
117
- ONLY use when user explicitly provides BOTH contract address AND specific token IDs
118
- Example: "tokens 1, 2, 3 from contract 0x123"
116
+ - build_playlist: { type, blockchain: "ethereum"|"tezos", contractAddress, tokenIds?: string[], quantity?: number, source?: string }
117
+ USE THIS when user mentions "contract" with a quantity: "N items from [blockchain] contract [address]"
118
+ tokenIds is OPTIONAL - omit it when user wants random tokens from a contract
119
+ • Examples:
120
+ - "tokens 1, 2, 3 from contract 0x123" → build_playlist with tokenIds: ["1", "2", "3"]
121
+ - "100 items from ethereum contract 0xABC" → build_playlist with quantity: 100, NO tokenIds
122
+ - "50 random tokens from tezos contract KT1..." → build_playlist with quantity: 50, NO tokenIds
119
123
  - query_address: { type, ownerAddress: 0x…|tz…|domain.eth|domain.tez, quantity?: number | "all" }
120
- Domains (.eth/.tez) are OWNER DOMAINS. Do not ask for tokenIds. Do not treat as contracts.
121
- A raw 0x…/tz… without tokenIds is an OWNER ADDRESS (query_address), not a contract.
122
- CRITICAL: Phrases like "N items from [address]", "NFTs from [address]", "tokens from [address]" → query_address
123
- • Example: "30 items from 0xABC" → query_address with quantity=30
124
+ USE THIS for owner/wallet addresses WITHOUT the word "contract"
125
+ Patterns: "N items from [address]", "NFTs from [address]", "tokens owned by [address]"
126
+ Domains (.eth/.tez) are ALWAYS owner addresses
127
+ • Example: "100 items from 0xABC" (without mentioning "contract") → query_address
124
128
  • When user says "all", "all tokens", "all NFTs" → use quantity="all" (string, not number)
125
- • quantity="all" will fetch ALL tokens using pagination, can handle thousands of tokens
126
129
  - fetch_feed: { type, playlistName: string, quantity?: number (default 5) }
127
130
 
131
+ CRITICAL DISTINCTION:
132
+ - User says "contract" + address → build_playlist (queries tokens FROM that contract)
133
+ - User says just address (no "contract" word) → query_address (queries tokens OWNED by that address)
134
+ - User says ".eth" or ".tez" domain → ALWAYS query_address (owner domain)
135
+
128
136
  DOMAIN OWNER RULES (CRITICAL)
129
137
  - Interpret \`*.eth\` as an Ethereum OWNER DOMAIN → produce \`query_address\` with \`ownerAddress\` set to the domain string (e.g., \`reas.eth\`).
130
138
  - Interpret \`*.tez\` as a Tezos OWNER DOMAIN → produce \`query_address\` with \`ownerAddress\` set to the domain string (e.g., \`einstein-rosen.tez\`).
131
139
  - Never treat \.eth or \.tez as a contract or collection identifier.
132
140
  - Never invent or request \`tokenIds\` for \.eth/\.tez domains. Use \`quantity\` only.
133
141
 
134
- EXAMPLES (query_address - NO tokenIds needed)
142
+ EXAMPLES (query_address - owner/wallet addresses)
135
143
  - "Pick 3 artworks from reas.eth" → \`query_address\` { ownerAddress: "reas.eth", quantity: 3 }
136
- - "3 from einstein-rosen.tez and play on my FF1" → \`query_address\` { ownerAddress: "einstein-rosen.tez", quantity: 3 } and set \`playlistSettings.deviceName\` accordingly
144
+ - "3 from einstein-rosen.tez and play on my FF1" → \`query_address\` { ownerAddress: "einstein-rosen.tez", quantity: 3 } and set \`playlistSettings.deviceName\`
137
145
  - "create a playlist of 30 items from 0xABC" → \`query_address\` { ownerAddress: "0xABC", quantity: 30 }
138
146
  - "get 20 NFTs from 0x123" → \`query_address\` { ownerAddress: "0x123", quantity: 20 }
139
- - "create a playlist from all tokens in reas.eth" → \`query_address\` { ownerAddress: "reas.eth", quantity: "all" }
140
147
  - "get all NFTs from 0xABC" → \`query_address\` { ownerAddress: "0xABC", quantity: "all" }
141
148
 
149
+ EXAMPLES (build_playlist - contract addresses)
150
+ - "tokens 5, 10, 15 from contract 0xABC on ethereum" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xABC", tokenIds: ["5", "10", "15"] }
151
+ - "create a playlist of 100 items from ethereum contract 0xa7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xa7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270", quantity: 100 }
152
+ - "100 random tokens from tezos contract KT1abc" → \`build_playlist\` { blockchain: "tezos", contractAddress: "KT1abc", quantity: 100 }
153
+ - "get 50 from contract 0xDEF" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xDEF", quantity: 50 }
154
+
142
155
  EXAMPLES (fetch_feed)
143
156
  - "Pick 3 artworks from Social Codes and 2 from a2p. Mix them up." → \`fetch_feed\` { playlistName: "Social Codes", quantity: 3 } + \`fetch_feed\` { playlistName: "a2p", quantity: 2 }, and set \`playlistSettings.preserveOrder\` = false
144
157
 
145
- EXAMPLES (build_playlist - requires BOTH contract AND tokenIds)
146
- - "tokens 5, 10, 15 from contract 0xABC on ethereum" → \`build_playlist\` { blockchain: "ethereum", contractAddress: "0xABC", tokenIds: ["5", "10", "15"] }
147
-
148
158
  PLAYLIST SETTINGS EXTRACTION
149
159
  - durationPerItem: parse phrases (e.g., "6 seconds each" → 6)
150
160
  - preserveOrder: default true; synonyms ("shuffle", "randomize", "mix", "mix them up", "scramble") → false
@@ -167,11 +177,12 @@ MISSING INFO POLICY (ASK AT MOST ONE QUESTION)
167
177
  - send: ask for device name only if user specifies a device by name and it's ambiguous; for generic references, always use get_configured_devices
168
178
 
169
179
  ADDRESS VALIDATION (CRITICAL)
170
- - When user enters any Ethereum (0x...) or Tezos (tz.../KT1) addresses, IMMEDIATELY call verify_addresses() BEFORE parsing requirements
180
+ - When user enters any Ethereum (0x...) or Tezos (tz.../KT1/KT...) addresses, IMMEDIATELY call verify_addresses() BEFORE parsing requirements
171
181
  - This includes: contract addresses in build_playlist, owner addresses in query_address, or any wallet/contract address mentioned
172
182
  - Example: user says "get tokens from 0xABC" → first call verify_addresses(['0xABC']) → get validation result → then parse_requirements
173
183
  - If verify_addresses returns valid=false, show user the error and ask them to provide the correct address
174
- - If valid=true, proceed to parse_requirements with the verified addresses
184
+ - If valid=true, the validation result shows the blockchain type (ethereum or tezos) - use this information when parsing requirements
185
+ - IMPORTANT: When user explicitly mentions blockchain (e.g., "ethereum contract" or "tezos contract"), you already know the blockchain type - DO NOT ask for it again
175
186
 
176
187
  FREE‑FORM COLLECTION NAMES
177
188
  - Treat as fetch_feed; do not guess contracts. If user says "some", default quantity = 5.
@@ -199,7 +210,7 @@ PUBLISH INTENT (CRITICAL)
199
210
  2. If only 1 server → use it directly in playlistSettings.feedServer
200
211
  3. If 2+ servers → ask user "Which feed server?" with numbered list (e.g., "1) https://feed.feralfile.com 2) http://localhost:8787")
201
212
  4. After selection, set playlistSettings.feedServer = { baseUrl, apiKey } from selected server
202
- 5. Acknowledge in Settings bullets (e.g., "publish to: https://feed.feralfile.com/api/v1")
213
+ 5. Acknowledge in Settings bullets (e.g., "publish to: https://dp1-feed-operator-api-prod.autonomy-system.workers.dev/api/v1")
203
214
  - User can request both device display AND publishing (e.g., "send to FF1 and publish to feed") → set both deviceName and feedServer
204
215
  - Publishing happens automatically after playlist verification passes
205
216
 
@@ -279,7 +290,7 @@ const intentParserFunctionSchemas = [
279
290
  },
280
291
  tokenIds: {
281
292
  type: 'array',
282
- description: 'Array of token IDs to fetch - only for build_playlist type',
293
+ description: 'Array of specific token IDs to fetch - only for build_playlist type. OPTIONAL: omit this field when user wants random tokens from the contract (e.g., "100 items from contract"). Only include when user specifies exact token IDs (e.g., "tokens 1, 2, 3").',
283
294
  items: {
284
295
  type: 'string',
285
296
  },
@@ -460,7 +471,7 @@ function formatMarkdown(text) {
460
471
  formatted = formatted.replace(/\*([^*]+)\*/g, (_, p1) => chalk_1.default.italic(p1));
461
472
  formatted = formatted.replace(/(?<!\w)_([^_]+)_(?!\w)/g, (_, p1) => chalk_1.default.italic(p1));
462
473
  // Inline code: `code` - light grey color
463
- formatted = formatted.replace(/`([^`]+)`/g, (_, p1) => chalk_1.default.grey(p1));
474
+ formatted = formatted.replace(/`([^`]+)`/g, (_, p1) => chalk_1.default.dim(p1));
464
475
  // Links: [text](url) - show text in blue
465
476
  formatted = formatted.replace(/\[([^\]]+)\]\([^)]+\)/g, (_, p1) => chalk_1.default.blue(p1));
466
477
  return formatted;
@@ -783,12 +794,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
783
794
  console.log(chalk_1.default.cyan('Publishing to feed server...'));
784
795
  const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
785
796
  if (publishResult.success) {
786
- console.log(chalk_1.default.green('Published to feed server'));
797
+ console.log(chalk_1.default.green('Published to feed server'));
787
798
  if (publishResult.playlistId) {
788
- console.log(chalk_1.default.gray(` Playlist ID: ${publishResult.playlistId}`));
799
+ console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
789
800
  }
790
801
  if (publishResult.feedServer) {
791
- console.log(chalk_1.default.gray(` Server: ${publishResult.feedServer}`));
802
+ console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
792
803
  }
793
804
  console.log();
794
805
  return {
@@ -804,9 +815,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
804
815
  };
805
816
  }
806
817
  else {
807
- console.error(chalk_1.default.red(' Failed to publish: ' + publishResult.error));
818
+ console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
808
819
  if (publishResult.message) {
809
- console.error(chalk_1.default.gray(` ${publishResult.message}`));
820
+ console.error(chalk_1.default.dim(` ${publishResult.message}`));
810
821
  }
811
822
  console.log();
812
823
  return {
@@ -837,12 +848,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
837
848
  console.log(chalk_1.default.cyan('Publishing to feed server...'));
838
849
  const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
839
850
  if (publishResult.success) {
840
- console.log(chalk_1.default.green('Published to feed server'));
851
+ console.log(chalk_1.default.green('Published to feed server'));
841
852
  if (publishResult.playlistId) {
842
- console.log(chalk_1.default.gray(` Playlist ID: ${publishResult.playlistId}`));
853
+ console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
843
854
  }
844
855
  if (publishResult.feedServer) {
845
- console.log(chalk_1.default.gray(` Server: ${publishResult.feedServer}`));
856
+ console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
846
857
  }
847
858
  console.log();
848
859
  return {
@@ -858,9 +869,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
858
869
  };
859
870
  }
860
871
  else {
861
- console.error(chalk_1.default.red(' Failed to publish: ' + publishResult.error));
872
+ console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
862
873
  if (publishResult.message) {
863
- console.error(chalk_1.default.gray(` ${publishResult.message}`));
874
+ console.error(chalk_1.default.dim(` ${publishResult.message}`));
864
875
  }
865
876
  console.log();
866
877
  return {
@@ -958,12 +969,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
958
969
  console.log(chalk_1.default.cyan('Publishing to feed server...'));
959
970
  const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
960
971
  if (publishResult.success) {
961
- console.log(chalk_1.default.green('Published to feed server'));
972
+ console.log(chalk_1.default.green('Published to feed server'));
962
973
  if (publishResult.playlistId) {
963
- console.log(chalk_1.default.gray(` Playlist ID: ${publishResult.playlistId}`));
974
+ console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
964
975
  }
965
976
  if (publishResult.feedServer) {
966
- console.log(chalk_1.default.gray(` Server: ${publishResult.feedServer}`));
977
+ console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
967
978
  }
968
979
  console.log();
969
980
  return {
@@ -979,9 +990,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
979
990
  };
980
991
  }
981
992
  else {
982
- console.error(chalk_1.default.red(' Failed to publish: ' + publishResult.error));
993
+ console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
983
994
  if (publishResult.message) {
984
- console.error(chalk_1.default.gray(` ${publishResult.message}`));
995
+ console.error(chalk_1.default.dim(` ${publishResult.message}`));
985
996
  }
986
997
  console.log();
987
998
  return {
@@ -1072,12 +1083,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
1072
1083
  console.log(chalk_1.default.cyan('Publishing to feed server...'));
1073
1084
  const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
1074
1085
  if (publishResult.success) {
1075
- console.log(chalk_1.default.green('Published to feed server'));
1086
+ console.log(chalk_1.default.green('Published to feed server'));
1076
1087
  if (publishResult.playlistId) {
1077
- console.log(chalk_1.default.gray(` Playlist ID: ${publishResult.playlistId}`));
1088
+ console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
1078
1089
  }
1079
1090
  if (publishResult.feedServer) {
1080
- console.log(chalk_1.default.gray(` Server: ${publishResult.feedServer}`));
1091
+ console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
1081
1092
  }
1082
1093
  console.log();
1083
1094
  return {
@@ -1093,9 +1104,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
1093
1104
  };
1094
1105
  }
1095
1106
  else {
1096
- console.error(chalk_1.default.red(' Failed to publish: ' + publishResult.error));
1107
+ console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
1097
1108
  if (publishResult.message) {
1098
- console.error(chalk_1.default.gray(` ${publishResult.message}`));
1109
+ console.error(chalk_1.default.dim(` ${publishResult.message}`));
1099
1110
  }
1100
1111
  console.log();
1101
1112
  return {
@@ -1235,12 +1246,12 @@ async function processIntentParserRequest(userRequest, options = {}) {
1235
1246
  console.log(chalk_1.default.cyan('Publishing to feed server...'));
1236
1247
  const publishResult = await publishPlaylist(args.filePath, args.feedServer.baseUrl, args.feedServer.apiKey);
1237
1248
  if (publishResult.success) {
1238
- console.log(chalk_1.default.green('Published to feed server'));
1249
+ console.log(chalk_1.default.green('Published to feed server'));
1239
1250
  if (publishResult.playlistId) {
1240
- console.log(chalk_1.default.gray(` Playlist ID: ${publishResult.playlistId}`));
1251
+ console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
1241
1252
  }
1242
1253
  if (publishResult.feedServer) {
1243
- console.log(chalk_1.default.gray(` Server: ${publishResult.feedServer}`));
1254
+ console.log(chalk_1.default.dim(` Server: ${publishResult.feedServer}`));
1244
1255
  }
1245
1256
  console.log();
1246
1257
  return {
@@ -1256,9 +1267,9 @@ async function processIntentParserRequest(userRequest, options = {}) {
1256
1267
  };
1257
1268
  }
1258
1269
  else {
1259
- console.error(chalk_1.default.red(' Failed to publish: ' + publishResult.error));
1270
+ console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
1260
1271
  if (publishResult.message) {
1261
- console.error(chalk_1.default.gray(` ${publishResult.message}`));
1272
+ console.error(chalk_1.default.dim(` ${publishResult.message}`));
1262
1273
  }
1263
1274
  console.log();
1264
1275
  return {
@@ -40,9 +40,6 @@ function applyConstraints(params, config) {
40
40
  if (!r.contractAddress) {
41
41
  throw new Error(`Requirement ${index + 1}: contractAddress is required for build_playlist`);
42
42
  }
43
- if (!r.tokenIds || r.tokenIds.length === 0) {
44
- throw new Error(`Requirement ${index + 1}: tokenIds are required for build_playlist`);
45
- }
46
43
  }
47
44
  else if (r.type === 'query_address') {
48
45
  if (!r.ownerAddress) {
@@ -87,10 +84,10 @@ function applyConstraints(params, config) {
87
84
  return sum;
88
85
  }, 0);
89
86
  if (hasAllQuantity) {
90
- console.log(chalk_1.default.yellow(`\n⚠️ Requesting all tokens from one or more addresses. This may take a while to fetch and process...\n`));
87
+ console.log(chalk_1.default.yellow(`\nRequesting all tokens from one or more addresses. This may take a while to fetch and process.\n`));
91
88
  }
92
89
  else if (totalRequested > 100) {
93
- console.log(chalk_1.default.yellow(`\n⚠️ Requesting ${totalRequested} items. This may take a while to fetch and process...\n`));
90
+ console.log(chalk_1.default.yellow(`\nRequesting ${totalRequested} items. This may take a while to fetch and process.\n`));
94
91
  }
95
92
  // Set playlist defaults
96
93
  if (!params.playlistSettings) {
@@ -28,7 +28,7 @@ function setVerbose(verbose) {
28
28
  */
29
29
  function debug(...args) {
30
30
  if (isVerbose) {
31
- console.log(chalk_1.default.gray('[DEBUG]'), ...args);
31
+ console.log(chalk_1.default.dim('[DEBUG]'), ...args);
32
32
  }
33
33
  }
34
34
  /**