ff1-cli 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -8
- package/dist/index.js +411 -106
- package/dist/src/ai-orchestrator/index.js +21 -21
- package/dist/src/config.js +47 -11
- package/dist/src/intent-parser/index.js +107 -112
- 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/EXAMPLES.md +9 -4
- package/docs/README.md +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -49,10 +49,12 @@ require("dotenv/config");
|
|
|
49
49
|
const commander_1 = require("commander");
|
|
50
50
|
const chalk_1 = __importDefault(require("chalk"));
|
|
51
51
|
const fs_1 = require("fs");
|
|
52
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
52
53
|
const readline = __importStar(require("readline"));
|
|
53
54
|
const config_1 = require("./src/config");
|
|
54
55
|
const main_1 = require("./src/main");
|
|
55
56
|
const program = new commander_1.Command();
|
|
57
|
+
const placeholderPattern = /YOUR_|your_/;
|
|
56
58
|
/**
|
|
57
59
|
* Display playlist creation summary with next steps.
|
|
58
60
|
*
|
|
@@ -60,24 +62,327 @@ const program = new commander_1.Command();
|
|
|
60
62
|
* @param {string} outputPath - Path where the playlist was saved
|
|
61
63
|
*/
|
|
62
64
|
function displayPlaylistSummary(playlist, outputPath) {
|
|
63
|
-
console.log(chalk_1.default.green('\
|
|
64
|
-
console.log();
|
|
65
|
-
console.log(chalk_1.default.
|
|
66
|
-
console.log(chalk_1.default.gray(` • View it locally: open ./${outputPath}`));
|
|
67
|
-
console.log(chalk_1.default.gray(` • Send it to your FF1: send last`));
|
|
68
|
-
console.log(chalk_1.default.gray(` • Publish to feed: publish playlist`));
|
|
65
|
+
console.log(chalk_1.default.green('\nPlaylist created'));
|
|
66
|
+
console.log(chalk_1.default.dim(` Output: ./${outputPath}`));
|
|
67
|
+
console.log(chalk_1.default.dim(' Next: send last | publish playlist'));
|
|
69
68
|
console.log();
|
|
70
69
|
}
|
|
70
|
+
function isMissingConfigValue(value) {
|
|
71
|
+
if (!value) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return placeholderPattern.test(value);
|
|
75
|
+
}
|
|
76
|
+
async function readConfigFile(configPath) {
|
|
77
|
+
const file = await fs_1.promises.readFile(configPath, 'utf-8');
|
|
78
|
+
return JSON.parse(file);
|
|
79
|
+
}
|
|
80
|
+
async function resolveExistingConfigPath() {
|
|
81
|
+
const { localPath, userPath } = (0, config_1.getConfigPaths)();
|
|
82
|
+
try {
|
|
83
|
+
await fs_1.promises.access(localPath);
|
|
84
|
+
return localPath;
|
|
85
|
+
}
|
|
86
|
+
catch (_error) {
|
|
87
|
+
try {
|
|
88
|
+
await fs_1.promises.access(userPath);
|
|
89
|
+
return userPath;
|
|
90
|
+
}
|
|
91
|
+
catch (_innerError) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function ensureConfigFile() {
|
|
97
|
+
const { userPath } = (0, config_1.getConfigPaths)();
|
|
98
|
+
const existingPath = await resolveExistingConfigPath();
|
|
99
|
+
if (existingPath) {
|
|
100
|
+
return { path: existingPath, created: false };
|
|
101
|
+
}
|
|
102
|
+
const createdPath = await (0, config_1.createSampleConfig)(userPath);
|
|
103
|
+
return { path: createdPath, created: true };
|
|
104
|
+
}
|
|
105
|
+
function normalizeDeviceHost(host) {
|
|
106
|
+
let normalized = host.trim();
|
|
107
|
+
if (!normalized) {
|
|
108
|
+
return normalized;
|
|
109
|
+
}
|
|
110
|
+
if (!normalized.startsWith('http://') && !normalized.startsWith('https://')) {
|
|
111
|
+
normalized = `http://${normalized}`;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(normalized);
|
|
115
|
+
const port = url.port || '1111';
|
|
116
|
+
return `${url.protocol}//${url.hostname}:${port}`;
|
|
117
|
+
}
|
|
118
|
+
catch (_error) {
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function promptYesNo(ask, question, defaultYes = true) {
|
|
123
|
+
const suffix = defaultYes ? 'Y/n' : 'y/N';
|
|
124
|
+
const answer = (await ask(`${question} [${suffix}] `)).trim().toLowerCase();
|
|
125
|
+
if (!answer) {
|
|
126
|
+
return defaultYes;
|
|
127
|
+
}
|
|
128
|
+
return answer === 'y' || answer === 'yes';
|
|
129
|
+
}
|
|
71
130
|
program
|
|
72
131
|
.name('ff1')
|
|
73
132
|
.description('CLI to fetch NFT information and build DP1 playlists using AI (Grok, ChatGPT, Gemini)')
|
|
74
|
-
.version('1.0.
|
|
133
|
+
.version('1.0.1')
|
|
134
|
+
.addHelpText('after', `\nQuick start:\n 1) ff1 setup\n 2) ff1 chat\n\nDocs: https://github.com/feralfile/ff1-cli\n`);
|
|
135
|
+
program
|
|
136
|
+
.command('setup')
|
|
137
|
+
.description('Guided setup for config, signing key, and device')
|
|
138
|
+
.action(async () => {
|
|
139
|
+
let rl = null;
|
|
140
|
+
try {
|
|
141
|
+
const { path: configPath, created } = await ensureConfigFile();
|
|
142
|
+
if (created) {
|
|
143
|
+
console.log(chalk_1.default.green(`Created ${configPath}`));
|
|
144
|
+
}
|
|
145
|
+
const config = await readConfigFile(configPath);
|
|
146
|
+
const modelNames = Object.keys(config.models || {});
|
|
147
|
+
if (modelNames.length === 0) {
|
|
148
|
+
console.error(chalk_1.default.red('No models found in config.json'));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
console.log(chalk_1.default.blue('\nFF1 Setup\n'));
|
|
152
|
+
rl = readline.createInterface({
|
|
153
|
+
input: process.stdin,
|
|
154
|
+
output: process.stdout,
|
|
155
|
+
});
|
|
156
|
+
const ask = async (question) => new Promise((resolve) => {
|
|
157
|
+
rl.question(chalk_1.default.yellow(question), (answer) => {
|
|
158
|
+
resolve(answer.trim());
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
const currentModel = config.defaultModel && modelNames.includes(config.defaultModel)
|
|
162
|
+
? config.defaultModel
|
|
163
|
+
: modelNames[0];
|
|
164
|
+
let selectedModel = currentModel;
|
|
165
|
+
while (true) {
|
|
166
|
+
const modelAnswer = await ask(`Default model (${modelNames.join(', ')}) [${currentModel}]: `);
|
|
167
|
+
if (!modelAnswer) {
|
|
168
|
+
selectedModel = currentModel;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
if (modelNames.includes(modelAnswer)) {
|
|
172
|
+
selectedModel = modelAnswer;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
console.log(chalk_1.default.red(`Unknown model: ${modelAnswer}`));
|
|
176
|
+
}
|
|
177
|
+
config.defaultModel = selectedModel;
|
|
178
|
+
const selectedModelConfig = config.models[selectedModel] || {
|
|
179
|
+
apiKey: '',
|
|
180
|
+
baseURL: '',
|
|
181
|
+
model: '',
|
|
182
|
+
timeout: 0,
|
|
183
|
+
maxRetries: 0,
|
|
184
|
+
temperature: 0,
|
|
185
|
+
maxTokens: 0,
|
|
186
|
+
supportsFunctionCalling: true,
|
|
187
|
+
};
|
|
188
|
+
const hasApiKeyForModel = !isMissingConfigValue(selectedModelConfig.apiKey);
|
|
189
|
+
const keyHelpUrls = {
|
|
190
|
+
grok: 'https://console.x.ai/',
|
|
191
|
+
gpt: 'https://platform.openai.com/api-keys',
|
|
192
|
+
chatgpt: 'https://platform.openai.com/api-keys',
|
|
193
|
+
gemini: 'https://aistudio.google.com/app/apikey',
|
|
194
|
+
};
|
|
195
|
+
if (!hasApiKeyForModel) {
|
|
196
|
+
const helpUrl = keyHelpUrls[selectedModel];
|
|
197
|
+
if (helpUrl) {
|
|
198
|
+
console.log(chalk_1.default.dim(helpUrl));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const apiKeyPrompt = hasApiKeyForModel
|
|
202
|
+
? `API key for ${selectedModel} (leave blank to keep current): `
|
|
203
|
+
: `API key for ${selectedModel}: `;
|
|
204
|
+
const apiKeyAnswer = await ask(apiKeyPrompt);
|
|
205
|
+
if (apiKeyAnswer) {
|
|
206
|
+
selectedModelConfig.apiKey = apiKeyAnswer;
|
|
207
|
+
}
|
|
208
|
+
config.models[selectedModel] = selectedModelConfig;
|
|
209
|
+
const currentKey = config.playlist?.privateKey || '';
|
|
210
|
+
let signingKey = currentKey;
|
|
211
|
+
if (isMissingConfigValue(currentKey)) {
|
|
212
|
+
const keyPair = crypto_1.default.generateKeyPairSync('ed25519');
|
|
213
|
+
signingKey = keyPair.privateKey.export({ format: 'der', type: 'pkcs8' }).toString('base64');
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const keepKey = await promptYesNo(ask, 'Keep existing signing key?', true);
|
|
217
|
+
if (!keepKey) {
|
|
218
|
+
const keyAnswer = await ask('Paste signing key (base64 or hex), or leave blank to regenerate: ');
|
|
219
|
+
if (keyAnswer) {
|
|
220
|
+
signingKey = keyAnswer;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const keyPair = crypto_1.default.generateKeyPairSync('ed25519');
|
|
224
|
+
signingKey = keyPair.privateKey
|
|
225
|
+
.export({ format: 'der', type: 'pkcs8' })
|
|
226
|
+
.toString('base64');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (signingKey) {
|
|
231
|
+
config.playlist = {
|
|
232
|
+
...(config.playlist || {}),
|
|
233
|
+
privateKey: signingKey,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const existingDevice = config.ff1Devices?.devices?.[0];
|
|
237
|
+
{
|
|
238
|
+
const existingHost = existingDevice?.host || '';
|
|
239
|
+
let rawDefaultDeviceId = '';
|
|
240
|
+
if (existingHost) {
|
|
241
|
+
// If host is a .local device, extract just the device ID segment.
|
|
242
|
+
// Otherwise keep the full host (IP address or multi-label domain).
|
|
243
|
+
const hostWithoutScheme = existingHost.replace(/^https?:\/\//, '');
|
|
244
|
+
if (hostWithoutScheme.includes('.local')) {
|
|
245
|
+
rawDefaultDeviceId = hostWithoutScheme.split('.')[0] || '';
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
rawDefaultDeviceId = hostWithoutScheme;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const defaultDeviceId = isMissingConfigValue(rawDefaultDeviceId) ? '' : rawDefaultDeviceId;
|
|
252
|
+
const idPrompt = defaultDeviceId
|
|
253
|
+
? `Device ID (e.g. ff1-ABCD1234) [${defaultDeviceId}]: `
|
|
254
|
+
: 'Device ID (e.g. ff1-ABCD1234): ';
|
|
255
|
+
const idAnswer = await ask(idPrompt);
|
|
256
|
+
const rawDeviceId = idAnswer || defaultDeviceId;
|
|
257
|
+
let hostValue = '';
|
|
258
|
+
if (rawDeviceId) {
|
|
259
|
+
const looksLikeHost = rawDeviceId.includes('.') ||
|
|
260
|
+
rawDeviceId.includes(':') ||
|
|
261
|
+
rawDeviceId.startsWith('http');
|
|
262
|
+
if (looksLikeHost) {
|
|
263
|
+
hostValue = normalizeDeviceHost(rawDeviceId);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const deviceId = rawDeviceId.startsWith('ff1-') ? rawDeviceId : `ff1-${rawDeviceId}`;
|
|
267
|
+
hostValue = normalizeDeviceHost(`${deviceId}.local`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const rawName = existingDevice?.name || 'ff1';
|
|
271
|
+
const defaultName = isMissingConfigValue(rawName) ? '' : rawName;
|
|
272
|
+
const namePrompt = defaultName
|
|
273
|
+
? `Device name (kitchen, office, etc.) [${defaultName}]: `
|
|
274
|
+
: 'Device name (kitchen, office, etc.): ';
|
|
275
|
+
const nameAnswer = await ask(namePrompt);
|
|
276
|
+
const deviceName = nameAnswer || defaultName || 'ff1';
|
|
277
|
+
if (hostValue) {
|
|
278
|
+
config.ff1Devices = {
|
|
279
|
+
devices: [
|
|
280
|
+
{
|
|
281
|
+
...existingDevice,
|
|
282
|
+
name: deviceName,
|
|
283
|
+
host: hostValue,
|
|
284
|
+
apiKey: existingDevice?.apiKey || '',
|
|
285
|
+
topicID: existingDevice?.topicID || '',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
await fs_1.promises.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
|
|
292
|
+
rl.close();
|
|
293
|
+
rl = null;
|
|
294
|
+
console.log(chalk_1.default.green('\nSetup complete'));
|
|
295
|
+
console.log(chalk_1.default.dim(` Config: ${configPath}`));
|
|
296
|
+
const hasApiKey = !isMissingConfigValue(config.models[selectedModel]?.apiKey);
|
|
297
|
+
const hasSigningKey = !isMissingConfigValue(config.playlist?.privateKey || '');
|
|
298
|
+
const hasDevice = Boolean(config.ff1Devices?.devices?.[0]?.host);
|
|
299
|
+
if (!hasApiKey || !hasSigningKey || !hasDevice) {
|
|
300
|
+
console.log(chalk_1.default.yellow('\nNext steps:'));
|
|
301
|
+
if (!hasApiKey) {
|
|
302
|
+
console.log(chalk_1.default.yellow(` • Add API key for ${selectedModel}`));
|
|
303
|
+
}
|
|
304
|
+
if (!hasSigningKey) {
|
|
305
|
+
console.log(chalk_1.default.yellow(' • Add a playlist signing key'));
|
|
306
|
+
}
|
|
307
|
+
if (!hasDevice) {
|
|
308
|
+
console.log(chalk_1.default.yellow(' • Add an FF1 device host'));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
console.log(chalk_1.default.dim('\nRun: ff1 chat'));
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
console.error(chalk_1.default.red('\nSetup failed:'), error.message);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
if (rl) {
|
|
319
|
+
rl.close();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
program
|
|
324
|
+
.command('status')
|
|
325
|
+
.description('Show configuration status')
|
|
326
|
+
.action(async () => {
|
|
327
|
+
try {
|
|
328
|
+
const configPath = await resolveExistingConfigPath();
|
|
329
|
+
if (!configPath) {
|
|
330
|
+
console.log(chalk_1.default.red('config.json not found'));
|
|
331
|
+
console.log(chalk_1.default.dim('Run: ff1 setup'));
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
const config = await readConfigFile(configPath);
|
|
335
|
+
const modelNames = Object.keys(config.models || {});
|
|
336
|
+
const defaultModel = config.defaultModel && modelNames.includes(config.defaultModel)
|
|
337
|
+
? config.defaultModel
|
|
338
|
+
: modelNames[0];
|
|
339
|
+
const defaultModelLabel = defaultModel || 'unknown';
|
|
340
|
+
const defaultModelConfig = defaultModel ? config.models?.[defaultModel] : undefined;
|
|
341
|
+
const statuses = [
|
|
342
|
+
{
|
|
343
|
+
label: 'Config file',
|
|
344
|
+
ok: true,
|
|
345
|
+
detail: configPath,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
label: `Default model (${defaultModelLabel}) API key`,
|
|
349
|
+
ok: defaultModel ? !isMissingConfigValue(defaultModelConfig?.apiKey) : false,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
label: 'Playlist signing key',
|
|
353
|
+
ok: !isMissingConfigValue(config.playlist?.privateKey || ''),
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
label: 'FF1 device host',
|
|
357
|
+
ok: !isMissingConfigValue(config.ff1Devices?.devices?.[0]?.host),
|
|
358
|
+
detail: isMissingConfigValue(config.ff1Devices?.devices?.[0]?.host)
|
|
359
|
+
? undefined
|
|
360
|
+
: config.ff1Devices?.devices?.[0]?.host,
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
console.log(chalk_1.default.blue('\n🔎 FF1 Status\n'));
|
|
364
|
+
statuses.forEach((status) => {
|
|
365
|
+
const label = status.ok ? chalk_1.default.green('OK') : chalk_1.default.red('Missing');
|
|
366
|
+
const detail = status.detail ? chalk_1.default.dim(` (${status.detail})`) : '';
|
|
367
|
+
console.log(`${label} ${status.label}${detail}`);
|
|
368
|
+
});
|
|
369
|
+
const hasMissing = statuses.some((status) => !status.ok);
|
|
370
|
+
if (hasMissing) {
|
|
371
|
+
console.log(chalk_1.default.dim('\nRun: ff1 setup'));
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error(chalk_1.default.red('\nStatus check failed:'), error.message);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
75
380
|
program
|
|
76
381
|
.command('chat')
|
|
77
382
|
.description('Start an interactive chat to build playlists using natural language')
|
|
78
383
|
.argument('[content]', 'Optional: Direct chat content (non-interactive mode)')
|
|
79
384
|
.option('-o, --output <filename>', 'Output filename for the playlist', 'playlist.json')
|
|
80
|
-
.option('-m, --model <name>', 'AI model to use (grok,
|
|
385
|
+
.option('-m, --model <name>', 'AI model to use (grok, gpt, gemini) - defaults to config setting')
|
|
81
386
|
.option('-v, --verbose', 'Show detailed technical output of function calls', false)
|
|
82
387
|
.action(async (content, options) => {
|
|
83
388
|
try {
|
|
@@ -86,24 +391,24 @@ program
|
|
|
86
391
|
const availableModels = (0, config_1.listAvailableModels)();
|
|
87
392
|
// Validate model selection
|
|
88
393
|
if (options.model && !availableModels.includes(options.model)) {
|
|
89
|
-
console.error(chalk_1.default.red(
|
|
90
|
-
console.log(chalk_1.default.yellow(
|
|
394
|
+
console.error(chalk_1.default.red(`Invalid model: "${options.model}"`));
|
|
395
|
+
console.log(chalk_1.default.yellow(`Available models: ${availableModels.join(', ')}`));
|
|
91
396
|
process.exit(1);
|
|
92
397
|
}
|
|
93
398
|
const modelName = options.model || config.defaultModel;
|
|
94
399
|
const validation = (0, config_1.validateConfig)(modelName);
|
|
95
400
|
if (!validation.valid) {
|
|
96
|
-
console.error(chalk_1.default.red('
|
|
401
|
+
console.error(chalk_1.default.red('Configuration error:'));
|
|
97
402
|
validation.errors.forEach((error) => {
|
|
98
403
|
console.error(chalk_1.default.red(` • ${error}`));
|
|
99
404
|
});
|
|
100
|
-
console.log(chalk_1.default.yellow('\nRun:
|
|
405
|
+
console.log(chalk_1.default.yellow('\nRun: ff1 setup\n'));
|
|
101
406
|
process.exit(1);
|
|
102
407
|
}
|
|
103
408
|
// NON-INTERACTIVE MODE: If content is provided as argument
|
|
104
409
|
if (content) {
|
|
105
|
-
console.log(chalk_1.default.blue('\
|
|
106
|
-
console.log(chalk_1.default.
|
|
410
|
+
console.log(chalk_1.default.blue('\nFF1 Chat (non-interactive)\n'));
|
|
411
|
+
console.log(chalk_1.default.dim(`Model: ${modelName}\n`));
|
|
107
412
|
console.log(chalk_1.default.yellow('Request:'), content);
|
|
108
413
|
console.log(); // Blank line
|
|
109
414
|
try {
|
|
@@ -115,32 +420,31 @@ program
|
|
|
115
420
|
});
|
|
116
421
|
// Print final summary
|
|
117
422
|
if (result && result.playlist) {
|
|
118
|
-
console.log(chalk_1.default.green('\
|
|
119
|
-
console.log(chalk_1.default.
|
|
120
|
-
console.log(chalk_1.default.
|
|
121
|
-
console.log(chalk_1.default.
|
|
423
|
+
console.log(chalk_1.default.green('\nPlaylist created'));
|
|
424
|
+
console.log(chalk_1.default.dim(` Title: ${result.playlist.title}`));
|
|
425
|
+
console.log(chalk_1.default.dim(` Items: ${result.playlist.items?.length || 0}`));
|
|
426
|
+
console.log(chalk_1.default.dim(` Output: ${options.output}\n`));
|
|
122
427
|
}
|
|
123
428
|
process.exit(0);
|
|
124
429
|
}
|
|
125
430
|
catch (error) {
|
|
126
|
-
console.error(chalk_1.default.red('\
|
|
431
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
127
432
|
if (options.verbose) {
|
|
128
|
-
console.error(chalk_1.default.
|
|
433
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
129
434
|
}
|
|
130
435
|
process.exit(1);
|
|
131
436
|
}
|
|
132
437
|
}
|
|
133
438
|
// INTERACTIVE MODE: Start conversation loop
|
|
134
|
-
console.log(chalk_1.default.blue('\
|
|
135
|
-
console.log(chalk_1.default.
|
|
136
|
-
console.log(chalk_1.default.
|
|
137
|
-
console.log(chalk_1.default.
|
|
138
|
-
console.log(chalk_1.default.
|
|
139
|
-
console.log(chalk_1.default.
|
|
140
|
-
console.log(chalk_1.default.
|
|
141
|
-
console.log(chalk_1.default.
|
|
142
|
-
console.log(chalk_1.default.
|
|
143
|
-
console.log(chalk_1.default.gray(' (Tip) Add -v to see tool calls'));
|
|
439
|
+
console.log(chalk_1.default.blue('\nFF1 Chat\n'));
|
|
440
|
+
console.log(chalk_1.default.dim('Describe the playlist you want. Ctrl+C to exit.'));
|
|
441
|
+
console.log(chalk_1.default.dim(`Model: ${modelName}\n`));
|
|
442
|
+
console.log(chalk_1.default.dim('Examples:'));
|
|
443
|
+
console.log(chalk_1.default.dim(' • Get tokens 1,2,3 from Ethereum contract 0xabc'));
|
|
444
|
+
console.log(chalk_1.default.dim(' • Get token 42 from Tezos contract KT1abc'));
|
|
445
|
+
console.log(chalk_1.default.dim(' • Get 3 from Social Codes and 2 from 0xdef'));
|
|
446
|
+
console.log(chalk_1.default.dim(' • Build a playlist of my Tezos works from address tz1... plus 3 from Social Codes'));
|
|
447
|
+
console.log(chalk_1.default.dim(' Tip: add -v to see tool calls'));
|
|
144
448
|
console.log();
|
|
145
449
|
const rl = readline.createInterface({
|
|
146
450
|
input: process.stdin,
|
|
@@ -183,8 +487,8 @@ program
|
|
|
183
487
|
// Only show playlist summary for build operations, not send operations
|
|
184
488
|
// Skip summary if playlist was already sent to device
|
|
185
489
|
if (options.verbose) {
|
|
186
|
-
console.log(chalk_1.default.
|
|
187
|
-
console.log(chalk_1.default.
|
|
490
|
+
console.log(chalk_1.default.dim(`\n[DEBUG] result.sentToDevice: ${result?.sentToDevice}`));
|
|
491
|
+
console.log(chalk_1.default.dim(`[DEBUG] result.action: ${result?.action}`));
|
|
188
492
|
}
|
|
189
493
|
if (result &&
|
|
190
494
|
result.playlist &&
|
|
@@ -194,9 +498,9 @@ program
|
|
|
194
498
|
}
|
|
195
499
|
}
|
|
196
500
|
catch (error) {
|
|
197
|
-
console.error(chalk_1.default.red('
|
|
501
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
198
502
|
if (options.verbose) {
|
|
199
|
-
console.error(chalk_1.default.
|
|
503
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
200
504
|
}
|
|
201
505
|
console.log(); // Blank line after error
|
|
202
506
|
}
|
|
@@ -207,12 +511,12 @@ program
|
|
|
207
511
|
}
|
|
208
512
|
catch (error) {
|
|
209
513
|
if (error.message !== 'readline was closed') {
|
|
210
|
-
console.error(chalk_1.default.red('\
|
|
514
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
211
515
|
if (process.env.DEBUG) {
|
|
212
|
-
console.error(chalk_1.default.
|
|
516
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
213
517
|
}
|
|
214
518
|
}
|
|
215
|
-
console.log(chalk_1.default.blue('\n
|
|
519
|
+
console.log(chalk_1.default.blue('\nGoodbye\n'));
|
|
216
520
|
process.exit(0);
|
|
217
521
|
}
|
|
218
522
|
});
|
|
@@ -222,7 +526,7 @@ program
|
|
|
222
526
|
.argument('<file>', 'Path to the playlist file')
|
|
223
527
|
.action(async (file) => {
|
|
224
528
|
try {
|
|
225
|
-
console.log(chalk_1.default.blue('\
|
|
529
|
+
console.log(chalk_1.default.blue('\nVerify playlist\n'));
|
|
226
530
|
// Import the verification utility
|
|
227
531
|
const verifier = await Promise.resolve().then(() => __importStar(require('./src/utilities/playlist-verifier')));
|
|
228
532
|
const { verifyPlaylistFile, printVerificationResult } = verifier;
|
|
@@ -235,7 +539,7 @@ program
|
|
|
235
539
|
}
|
|
236
540
|
}
|
|
237
541
|
catch (error) {
|
|
238
|
-
console.error(chalk_1.default.red('\
|
|
542
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
239
543
|
process.exit(1);
|
|
240
544
|
}
|
|
241
545
|
});
|
|
@@ -245,7 +549,7 @@ program
|
|
|
245
549
|
.argument('<file>', 'Path to the playlist file')
|
|
246
550
|
.action(async (file) => {
|
|
247
551
|
try {
|
|
248
|
-
console.log(chalk_1.default.blue('\
|
|
552
|
+
console.log(chalk_1.default.blue('\nVerify playlist\n'));
|
|
249
553
|
// Import the verification utility
|
|
250
554
|
const verifier = await Promise.resolve().then(() => __importStar(require('./src/utilities/playlist-verifier')));
|
|
251
555
|
const { verifyPlaylistFile, printVerificationResult } = verifier;
|
|
@@ -258,7 +562,7 @@ program
|
|
|
258
562
|
}
|
|
259
563
|
}
|
|
260
564
|
catch (error) {
|
|
261
|
-
console.error(chalk_1.default.red('\
|
|
565
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
262
566
|
process.exit(1);
|
|
263
567
|
}
|
|
264
568
|
});
|
|
@@ -270,26 +574,26 @@ program
|
|
|
270
574
|
.option('-o, --output <file>', 'Output file path (defaults to overwriting input file)')
|
|
271
575
|
.action(async (file, options) => {
|
|
272
576
|
try {
|
|
273
|
-
console.log(chalk_1.default.blue('\
|
|
577
|
+
console.log(chalk_1.default.blue('\nSign playlist\n'));
|
|
274
578
|
// Import the signing utility
|
|
275
579
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
276
580
|
const { signPlaylistFile } = require('./src/utilities/playlist-signer');
|
|
277
581
|
// Sign the playlist
|
|
278
582
|
const result = await signPlaylistFile(file, options.key, options.output);
|
|
279
583
|
if (result.success) {
|
|
280
|
-
console.log(chalk_1.default.green('\
|
|
584
|
+
console.log(chalk_1.default.green('\nPlaylist signed'));
|
|
281
585
|
if (result.playlist?.signature) {
|
|
282
|
-
console.log(chalk_1.default.
|
|
586
|
+
console.log(chalk_1.default.dim(` Signature: ${result.playlist.signature.substring(0, 30)}...`));
|
|
283
587
|
}
|
|
284
588
|
console.log();
|
|
285
589
|
}
|
|
286
590
|
else {
|
|
287
|
-
console.error(chalk_1.default.red('\
|
|
591
|
+
console.error(chalk_1.default.red('\nSign failed:'), result.error);
|
|
288
592
|
process.exit(1);
|
|
289
593
|
}
|
|
290
594
|
}
|
|
291
595
|
catch (error) {
|
|
292
|
-
console.error(chalk_1.default.red('\
|
|
596
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
293
597
|
process.exit(1);
|
|
294
598
|
}
|
|
295
599
|
});
|
|
@@ -301,12 +605,12 @@ program
|
|
|
301
605
|
.option('--skip-verify', 'Skip playlist verification before sending')
|
|
302
606
|
.action(async (url, options) => {
|
|
303
607
|
try {
|
|
304
|
-
console.log(chalk_1.default.blue('\
|
|
608
|
+
console.log(chalk_1.default.blue('\nPlay on FF1\n'));
|
|
305
609
|
try {
|
|
306
610
|
new URL(url);
|
|
307
611
|
}
|
|
308
612
|
catch (error) {
|
|
309
|
-
console.error(chalk_1.default.red('\
|
|
613
|
+
console.error(chalk_1.default.red('\nInvalid URL:'), error.message);
|
|
310
614
|
process.exit(1);
|
|
311
615
|
}
|
|
312
616
|
const config = (0, config_1.getConfig)();
|
|
@@ -320,7 +624,7 @@ program
|
|
|
320
624
|
const { verifyPlaylist } = verifier;
|
|
321
625
|
const verifyResult = verifyPlaylist(playlist);
|
|
322
626
|
if (!verifyResult.valid) {
|
|
323
|
-
console.error(chalk_1.default.red('\
|
|
627
|
+
console.error(chalk_1.default.red('\nPlaylist verification failed:'), verifyResult.error);
|
|
324
628
|
if (verifyResult.details && verifyResult.details.length > 0) {
|
|
325
629
|
console.log(chalk_1.default.yellow('\n Validation errors:'));
|
|
326
630
|
verifyResult.details.forEach((detail) => {
|
|
@@ -338,25 +642,25 @@ program
|
|
|
338
642
|
deviceName: options.device,
|
|
339
643
|
});
|
|
340
644
|
if (result.success) {
|
|
341
|
-
console.log(chalk_1.default.green('
|
|
645
|
+
console.log(chalk_1.default.green('✓ Sent'));
|
|
342
646
|
if (result.deviceName) {
|
|
343
|
-
console.log(chalk_1.default.
|
|
647
|
+
console.log(chalk_1.default.dim(` Device: ${result.deviceName}`));
|
|
344
648
|
}
|
|
345
649
|
if (result.device) {
|
|
346
|
-
console.log(chalk_1.default.
|
|
650
|
+
console.log(chalk_1.default.dim(` Host: ${result.device}`));
|
|
347
651
|
}
|
|
348
652
|
console.log();
|
|
349
653
|
}
|
|
350
654
|
else {
|
|
351
|
-
console.error(chalk_1.default.red('\
|
|
655
|
+
console.error(chalk_1.default.red('\nSend failed:'), result.error);
|
|
352
656
|
if (result.details) {
|
|
353
|
-
console.error(chalk_1.default.
|
|
657
|
+
console.error(chalk_1.default.dim(` Details: ${result.details}`));
|
|
354
658
|
}
|
|
355
659
|
process.exit(1);
|
|
356
660
|
}
|
|
357
661
|
}
|
|
358
662
|
catch (error) {
|
|
359
|
-
console.error(chalk_1.default.red('\
|
|
663
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
360
664
|
process.exit(1);
|
|
361
665
|
}
|
|
362
666
|
});
|
|
@@ -368,18 +672,18 @@ program
|
|
|
368
672
|
.option('--skip-verify', 'Skip playlist verification before sending')
|
|
369
673
|
.action(async (file, options) => {
|
|
370
674
|
try {
|
|
371
|
-
console.log(chalk_1.default.blue('\
|
|
675
|
+
console.log(chalk_1.default.blue('\nSend playlist to FF1\n'));
|
|
372
676
|
// Read the playlist file
|
|
373
677
|
const content = await fs_1.promises.readFile(file, 'utf-8');
|
|
374
678
|
const playlist = JSON.parse(content);
|
|
375
679
|
// Verify playlist before sending (unless skipped)
|
|
376
680
|
if (!options.skipVerify) {
|
|
377
|
-
console.log(chalk_1.default.cyan('
|
|
681
|
+
console.log(chalk_1.default.cyan('Verify playlist'));
|
|
378
682
|
const verifier = await Promise.resolve().then(() => __importStar(require('./src/utilities/playlist-verifier')));
|
|
379
683
|
const { verifyPlaylist } = verifier;
|
|
380
684
|
const verifyResult = verifyPlaylist(playlist);
|
|
381
685
|
if (!verifyResult.valid) {
|
|
382
|
-
console.error(chalk_1.default.red('\
|
|
686
|
+
console.error(chalk_1.default.red('\nPlaylist verification failed:'), verifyResult.error);
|
|
383
687
|
if (verifyResult.details && verifyResult.details.length > 0) {
|
|
384
688
|
console.log(chalk_1.default.yellow('\n Validation errors:'));
|
|
385
689
|
verifyResult.details.forEach((detail) => {
|
|
@@ -389,7 +693,7 @@ program
|
|
|
389
693
|
console.log(chalk_1.default.yellow('\n Use --skip-verify to send anyway (not recommended)\n'));
|
|
390
694
|
process.exit(1);
|
|
391
695
|
}
|
|
392
|
-
console.log(chalk_1.default.green('✓
|
|
696
|
+
console.log(chalk_1.default.green('✓ Verified\n'));
|
|
393
697
|
}
|
|
394
698
|
// Import the sending utility
|
|
395
699
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -400,25 +704,25 @@ program
|
|
|
400
704
|
deviceName: options.device,
|
|
401
705
|
});
|
|
402
706
|
if (result.success) {
|
|
403
|
-
console.log(chalk_1.default.green('
|
|
707
|
+
console.log(chalk_1.default.green('✓ Sent'));
|
|
404
708
|
if (result.deviceName) {
|
|
405
|
-
console.log(chalk_1.default.
|
|
709
|
+
console.log(chalk_1.default.dim(` Device: ${result.deviceName}`));
|
|
406
710
|
}
|
|
407
711
|
if (result.device) {
|
|
408
|
-
console.log(chalk_1.default.
|
|
712
|
+
console.log(chalk_1.default.dim(` Host: ${result.device}`));
|
|
409
713
|
}
|
|
410
714
|
console.log();
|
|
411
715
|
}
|
|
412
716
|
else {
|
|
413
|
-
console.error(chalk_1.default.red('\
|
|
717
|
+
console.error(chalk_1.default.red('\nSend failed:'), result.error);
|
|
414
718
|
if (result.details) {
|
|
415
|
-
console.error(chalk_1.default.
|
|
719
|
+
console.error(chalk_1.default.dim(` Details: ${result.details}`));
|
|
416
720
|
}
|
|
417
721
|
process.exit(1);
|
|
418
722
|
}
|
|
419
723
|
}
|
|
420
724
|
catch (error) {
|
|
421
|
-
console.error(chalk_1.default.red('\
|
|
725
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
422
726
|
process.exit(1);
|
|
423
727
|
}
|
|
424
728
|
});
|
|
@@ -429,13 +733,13 @@ program
|
|
|
429
733
|
.option('-s, --server <index>', 'Feed server index (use this if multiple servers configured)')
|
|
430
734
|
.action(async (file, options) => {
|
|
431
735
|
try {
|
|
432
|
-
console.log(chalk_1.default.blue('\
|
|
736
|
+
console.log(chalk_1.default.blue('\nPublish playlist\n'));
|
|
433
737
|
const { getFeedConfig } = await Promise.resolve().then(() => __importStar(require('./src/config')));
|
|
434
738
|
const { publishPlaylist } = await Promise.resolve().then(() => __importStar(require('./src/utilities/playlist-publisher')));
|
|
435
739
|
const feedConfig = getFeedConfig();
|
|
436
740
|
if (!feedConfig.baseURLs || feedConfig.baseURLs.length === 0) {
|
|
437
|
-
console.error(chalk_1.default.red('\
|
|
438
|
-
console.log(chalk_1.default.yellow('
|
|
741
|
+
console.error(chalk_1.default.red('\nNo feed servers configured'));
|
|
742
|
+
console.log(chalk_1.default.yellow(' Add feed server URLs to config.json: feed.baseURLs\n'));
|
|
439
743
|
process.exit(1);
|
|
440
744
|
}
|
|
441
745
|
// If multiple servers and no index specified, show options
|
|
@@ -464,7 +768,7 @@ program
|
|
|
464
768
|
}
|
|
465
769
|
const serverIndex = parseInt(options.server || '0', 10);
|
|
466
770
|
if (isNaN(serverIndex) || serverIndex < 0 || serverIndex >= feedConfig.baseURLs.length) {
|
|
467
|
-
console.error(chalk_1.default.red('\
|
|
771
|
+
console.error(chalk_1.default.red('\nInvalid server index'));
|
|
468
772
|
process.exit(1);
|
|
469
773
|
}
|
|
470
774
|
serverUrl = feedConfig.baseURLs[serverIndex];
|
|
@@ -479,20 +783,20 @@ program
|
|
|
479
783
|
}
|
|
480
784
|
const result = await publishPlaylist(file, serverUrl, serverApiKey);
|
|
481
785
|
if (result.success) {
|
|
482
|
-
console.log(chalk_1.default.green('
|
|
786
|
+
console.log(chalk_1.default.green('Published'));
|
|
483
787
|
if (result.playlistId) {
|
|
484
|
-
console.log(chalk_1.default.
|
|
788
|
+
console.log(chalk_1.default.dim(` Playlist ID: ${result.playlistId}`));
|
|
485
789
|
}
|
|
486
|
-
console.log(chalk_1.default.
|
|
790
|
+
console.log(chalk_1.default.dim(` Server: ${result.feedServer}`));
|
|
487
791
|
if (result.message) {
|
|
488
|
-
console.log(chalk_1.default.
|
|
792
|
+
console.log(chalk_1.default.dim(` Status: ${result.message}`));
|
|
489
793
|
}
|
|
490
794
|
console.log();
|
|
491
795
|
}
|
|
492
796
|
else {
|
|
493
|
-
console.error(chalk_1.default.red('\
|
|
797
|
+
console.error(chalk_1.default.red('\nPublish failed'));
|
|
494
798
|
if (result.error) {
|
|
495
|
-
console.error(chalk_1.default.red(`
|
|
799
|
+
console.error(chalk_1.default.red(` ${result.error}`));
|
|
496
800
|
}
|
|
497
801
|
if (result.message) {
|
|
498
802
|
console.log(chalk_1.default.yellow(`\n${result.message}`));
|
|
@@ -502,7 +806,7 @@ program
|
|
|
502
806
|
}
|
|
503
807
|
}
|
|
504
808
|
catch (error) {
|
|
505
|
-
console.error(chalk_1.default.red('\
|
|
809
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
506
810
|
process.exit(1);
|
|
507
811
|
}
|
|
508
812
|
});
|
|
@@ -534,36 +838,36 @@ program
|
|
|
534
838
|
process.stdin.on('error', reject);
|
|
535
839
|
});
|
|
536
840
|
if (!stdin.trim()) {
|
|
537
|
-
console.error(chalk_1.default.red('
|
|
841
|
+
console.error(chalk_1.default.red('No parameters provided'));
|
|
538
842
|
console.log(chalk_1.default.yellow('\nUsage:'));
|
|
539
|
-
console.log('
|
|
540
|
-
console.log(' cat params.json |
|
|
541
|
-
console.log(' echo \'{"requirements":[...]}\' |
|
|
843
|
+
console.log(' ff1 build params.json');
|
|
844
|
+
console.log(' cat params.json | ff1 build');
|
|
845
|
+
console.log(' echo \'{"requirements":[...]}\' | ff1 build');
|
|
542
846
|
process.exit(1);
|
|
543
847
|
}
|
|
544
848
|
params = JSON.parse(stdin);
|
|
545
849
|
}
|
|
546
850
|
if (options.verbose) {
|
|
547
|
-
console.log(chalk_1.default.blue('\
|
|
548
|
-
console.log(chalk_1.default.
|
|
851
|
+
console.log(chalk_1.default.blue('\nParameters:'));
|
|
852
|
+
console.log(chalk_1.default.dim(JSON.stringify(params, null, 2)));
|
|
549
853
|
console.log();
|
|
550
854
|
}
|
|
551
|
-
console.log(chalk_1.default.blue('\
|
|
855
|
+
console.log(chalk_1.default.blue('\nBuild playlist from parameters\n'));
|
|
552
856
|
const result = await (0, main_1.buildPlaylistDirect)(params, {
|
|
553
857
|
verbose: options.verbose,
|
|
554
858
|
outputPath: options.output,
|
|
555
859
|
});
|
|
556
860
|
if (result && result.playlist) {
|
|
557
|
-
console.log(chalk_1.default.green('\
|
|
558
|
-
console.log(chalk_1.default.
|
|
559
|
-
console.log(chalk_1.default.
|
|
560
|
-
console.log(chalk_1.default.
|
|
861
|
+
console.log(chalk_1.default.green('\nPlaylist created'));
|
|
862
|
+
console.log(chalk_1.default.dim(` Title: ${result.playlist.title}`));
|
|
863
|
+
console.log(chalk_1.default.dim(` Items: ${result.playlist.items?.length || 0}`));
|
|
864
|
+
console.log(chalk_1.default.dim(` Output: ${options.output}\n`));
|
|
561
865
|
}
|
|
562
866
|
}
|
|
563
867
|
catch (error) {
|
|
564
|
-
console.error(chalk_1.default.red('\
|
|
868
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
565
869
|
if (options.verbose) {
|
|
566
|
-
console.error(chalk_1.default.
|
|
870
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
567
871
|
}
|
|
568
872
|
process.exit(1);
|
|
569
873
|
}
|
|
@@ -575,37 +879,38 @@ program
|
|
|
575
879
|
.action(async (action) => {
|
|
576
880
|
try {
|
|
577
881
|
if (action === 'init') {
|
|
578
|
-
console.log(chalk_1.default.blue('\
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
console.log(chalk_1.default.
|
|
882
|
+
console.log(chalk_1.default.blue('\nCreate config.json\n'));
|
|
883
|
+
const { userPath } = (0, config_1.getConfigPaths)();
|
|
884
|
+
const configPath = await (0, config_1.createSampleConfig)(userPath);
|
|
885
|
+
console.log(chalk_1.default.green(`Created ${configPath}`));
|
|
886
|
+
console.log(chalk_1.default.yellow('\nNext: ff1 setup\n'));
|
|
582
887
|
}
|
|
583
888
|
else if (action === 'show') {
|
|
584
889
|
const config = (0, config_1.getConfig)();
|
|
585
|
-
console.log(chalk_1.default.blue('\
|
|
586
|
-
console.log(chalk_1.default.bold('Default
|
|
587
|
-
console.log(chalk_1.default.bold('Default
|
|
588
|
-
console.log(chalk_1.default.bold('\nAvailable
|
|
890
|
+
console.log(chalk_1.default.blue('\nCurrent configuration\n'));
|
|
891
|
+
console.log(chalk_1.default.bold('Default model:'), chalk_1.default.white(config.defaultModel));
|
|
892
|
+
console.log(chalk_1.default.bold('Default duration:'), chalk_1.default.white(config.defaultDuration + 's'));
|
|
893
|
+
console.log(chalk_1.default.bold('\nAvailable models:\n'));
|
|
589
894
|
const models = (0, config_1.listAvailableModels)();
|
|
590
895
|
models.forEach((modelName) => {
|
|
591
896
|
const modelConfig = config.models[modelName];
|
|
592
897
|
const isCurrent = modelName === config.defaultModel;
|
|
593
898
|
console.log(` ${isCurrent ? chalk_1.default.green('→') : ' '} ${chalk_1.default.bold(modelName)}`);
|
|
594
|
-
console.log(` API
|
|
595
|
-
console.log(` Base URL: ${chalk_1.default.
|
|
596
|
-
console.log(` Model: ${chalk_1.default.
|
|
597
|
-
console.log(` Function
|
|
899
|
+
console.log(` API key: ${modelConfig.apiKey && modelConfig.apiKey !== 'your_api_key_here' ? chalk_1.default.green('Set') : chalk_1.default.red('Missing')}`);
|
|
900
|
+
console.log(` Base URL: ${chalk_1.default.dim(modelConfig.baseURL)}`);
|
|
901
|
+
console.log(` Model: ${chalk_1.default.dim(modelConfig.model)}`);
|
|
902
|
+
console.log(` Function calling: ${modelConfig.supportsFunctionCalling ? chalk_1.default.green('Supported') : chalk_1.default.red('Not supported')}`);
|
|
598
903
|
console.log();
|
|
599
904
|
});
|
|
600
905
|
}
|
|
601
906
|
else if (action === 'validate') {
|
|
602
907
|
const validation = (0, config_1.validateConfig)();
|
|
603
|
-
console.log(chalk_1.default.blue('\
|
|
908
|
+
console.log(chalk_1.default.blue('\nValidate configuration\n'));
|
|
604
909
|
if (validation.valid) {
|
|
605
|
-
console.log(chalk_1.default.green('
|
|
910
|
+
console.log(chalk_1.default.green('Configuration is valid\n'));
|
|
606
911
|
}
|
|
607
912
|
else {
|
|
608
|
-
console.log(chalk_1.default.red('
|
|
913
|
+
console.log(chalk_1.default.red('Configuration has errors:\n'));
|
|
609
914
|
validation.errors.forEach((error) => {
|
|
610
915
|
console.log(chalk_1.default.red(` • ${error}`));
|
|
611
916
|
});
|
|
@@ -614,13 +919,13 @@ program
|
|
|
614
919
|
}
|
|
615
920
|
}
|
|
616
921
|
else {
|
|
617
|
-
console.error(chalk_1.default.red(`\
|
|
922
|
+
console.error(chalk_1.default.red(`\nUnknown action: ${action}`));
|
|
618
923
|
console.log(chalk_1.default.yellow('Available actions: init, show, validate\n'));
|
|
619
924
|
process.exit(1);
|
|
620
925
|
}
|
|
621
926
|
}
|
|
622
927
|
catch (error) {
|
|
623
|
-
console.error(chalk_1.default.red('\
|
|
928
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
624
929
|
process.exit(1);
|
|
625
930
|
}
|
|
626
931
|
});
|