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/config.json.example +1 -1
- package/dist/index.js +426 -107
- package/dist/src/ai-orchestrator/index.js +21 -21
- package/dist/src/config.js +23 -9
- package/dist/src/intent-parser/index.js +55 -44
- package/dist/src/intent-parser/utils.js +2 -5
- package/dist/src/logger.js +1 -1
- package/dist/src/main.js +41 -28
- package/dist/src/utilities/domain-resolver.js +2 -2
- package/dist/src/utilities/feed-fetcher.js +2 -2
- package/dist/src/utilities/functions.js +12 -12
- package/dist/src/utilities/index.js +65 -14
- package/dist/src/utilities/nft-indexer.js +30 -7
- package/dist/src/utilities/playlist-send.js +18 -18
- package/dist/src/utilities/playlist-verifier.js +11 -11
- package/docs/CONFIGURATION.md +3 -3
- package/docs/EXAMPLES.md +1 -1
- package/docs/README.md +1 -1
- package/docs/RELEASING.md +28 -4
- package/package.json +2 -10
|
@@ -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.
|
|
580
|
-
console.log(chalk.
|
|
581
|
-
console.log(chalk.
|
|
582
|
-
console.log(chalk.
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
647
|
-
console.log(chalk.
|
|
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.
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
802
|
+
console.log(chalk.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
803
803
|
}
|
|
804
804
|
if (publishResult.feedServer) {
|
|
805
|
-
console.log(chalk.
|
|
805
|
+
console.log(chalk.dim(` Server: ${publishResult.feedServer}`));
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
808
|
else {
|
|
809
|
-
console.error(chalk.red(
|
|
809
|
+
console.error(chalk.red(`Publish failed: ${publishResult.error}`));
|
|
810
810
|
if (publishResult.message) {
|
|
811
|
-
console.error(chalk.
|
|
811
|
+
console.error(chalk.dim(` ${publishResult.message}`));
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
}
|
|
815
815
|
catch (error) {
|
|
816
|
-
console.error(chalk.red(
|
|
816
|
+
console.error(chalk.red(`Publish failed: ${error.message}`));
|
|
817
817
|
if (verbose) {
|
|
818
|
-
console.error(chalk.
|
|
818
|
+
console.error(chalk.dim(error.stack));
|
|
819
819
|
}
|
|
820
820
|
}
|
|
821
821
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
|
117
|
-
•
|
|
118
|
-
•
|
|
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
|
-
•
|
|
121
|
-
•
|
|
122
|
-
•
|
|
123
|
-
• Example: "
|
|
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 -
|
|
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\`
|
|
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,
|
|
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.
|
|
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.
|
|
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('
|
|
797
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
787
798
|
if (publishResult.playlistId) {
|
|
788
|
-
console.log(chalk_1.default.
|
|
799
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
789
800
|
}
|
|
790
801
|
if (publishResult.feedServer) {
|
|
791
|
-
console.log(chalk_1.default.
|
|
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('
|
|
818
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
808
819
|
if (publishResult.message) {
|
|
809
|
-
console.error(chalk_1.default.
|
|
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('
|
|
851
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
841
852
|
if (publishResult.playlistId) {
|
|
842
|
-
console.log(chalk_1.default.
|
|
853
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
843
854
|
}
|
|
844
855
|
if (publishResult.feedServer) {
|
|
845
|
-
console.log(chalk_1.default.
|
|
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('
|
|
872
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
862
873
|
if (publishResult.message) {
|
|
863
|
-
console.error(chalk_1.default.
|
|
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('
|
|
972
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
962
973
|
if (publishResult.playlistId) {
|
|
963
|
-
console.log(chalk_1.default.
|
|
974
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
964
975
|
}
|
|
965
976
|
if (publishResult.feedServer) {
|
|
966
|
-
console.log(chalk_1.default.
|
|
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('
|
|
993
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
983
994
|
if (publishResult.message) {
|
|
984
|
-
console.error(chalk_1.default.
|
|
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('
|
|
1086
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
1076
1087
|
if (publishResult.playlistId) {
|
|
1077
|
-
console.log(chalk_1.default.
|
|
1088
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
1078
1089
|
}
|
|
1079
1090
|
if (publishResult.feedServer) {
|
|
1080
|
-
console.log(chalk_1.default.
|
|
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('
|
|
1107
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
1097
1108
|
if (publishResult.message) {
|
|
1098
|
-
console.error(chalk_1.default.
|
|
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('
|
|
1249
|
+
console.log(chalk_1.default.green('Published to feed server'));
|
|
1239
1250
|
if (publishResult.playlistId) {
|
|
1240
|
-
console.log(chalk_1.default.
|
|
1251
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${publishResult.playlistId}`));
|
|
1241
1252
|
}
|
|
1242
1253
|
if (publishResult.feedServer) {
|
|
1243
|
-
console.log(chalk_1.default.
|
|
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('
|
|
1270
|
+
console.error(chalk_1.default.red('Publish failed: ' + publishResult.error));
|
|
1260
1271
|
if (publishResult.message) {
|
|
1261
|
-
console.error(chalk_1.default.
|
|
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(`\
|
|
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(`\
|
|
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) {
|