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