ff1-cli 1.0.0
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/LICENSE +21 -0
- package/README.md +65 -0
- package/config.json.example +78 -0
- package/dist/index.js +627 -0
- package/dist/src/ai-orchestrator/index.js +870 -0
- package/dist/src/ai-orchestrator/registry.js +96 -0
- package/dist/src/config.js +352 -0
- package/dist/src/intent-parser/index.js +1342 -0
- package/dist/src/intent-parser/utils.js +108 -0
- package/dist/src/logger.js +72 -0
- package/dist/src/main.js +393 -0
- package/dist/src/types.js +5 -0
- package/dist/src/utilities/address-validator.js +242 -0
- package/dist/src/utilities/domain-resolver.js +291 -0
- package/dist/src/utilities/feed-fetcher.js +387 -0
- package/dist/src/utilities/ff1-device.js +176 -0
- package/dist/src/utilities/functions.js +325 -0
- package/dist/src/utilities/index.js +372 -0
- package/dist/src/utilities/nft-indexer.js +1013 -0
- package/dist/src/utilities/playlist-builder.js +522 -0
- package/dist/src/utilities/playlist-publisher.js +131 -0
- package/dist/src/utilities/playlist-send.js +241 -0
- package/dist/src/utilities/playlist-signer.js +171 -0
- package/dist/src/utilities/playlist-verifier.js +156 -0
- package/dist/src/utils.js +48 -0
- package/docs/CONFIGURATION.md +178 -0
- package/docs/EXAMPLES.md +331 -0
- package/docs/FUNCTION_CALLING.md +92 -0
- package/docs/README.md +267 -0
- package/docs/RELEASING.md +22 -0
- package/package.json +75 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Registry for Playlists and Items
|
|
3
|
+
* Reduces AI context usage by storing full objects and passing only IDs
|
|
4
|
+
*/
|
|
5
|
+
// Internal storage
|
|
6
|
+
const itemRegistry = new Map();
|
|
7
|
+
const playlistRegistry = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Store a playlist item in the registry
|
|
10
|
+
*
|
|
11
|
+
* @param {string} id - Item ID
|
|
12
|
+
* @param {Object} item - Full DP1 item object
|
|
13
|
+
*/
|
|
14
|
+
function storeItem(id, item) {
|
|
15
|
+
if (!id) {
|
|
16
|
+
throw new Error('Item ID is required');
|
|
17
|
+
}
|
|
18
|
+
itemRegistry.set(id, item);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve a playlist item from the registry
|
|
22
|
+
*
|
|
23
|
+
* @param {string} id - Item ID
|
|
24
|
+
* @returns {Object|undefined} DP1 item object or undefined
|
|
25
|
+
*/
|
|
26
|
+
function getItem(id) {
|
|
27
|
+
return itemRegistry.get(id);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if an item exists in the registry
|
|
31
|
+
*
|
|
32
|
+
* @param {string} id - Item ID
|
|
33
|
+
* @returns {boolean} True if item exists
|
|
34
|
+
*/
|
|
35
|
+
function hasItem(id) {
|
|
36
|
+
return itemRegistry.has(id);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Store a playlist in the registry
|
|
40
|
+
*
|
|
41
|
+
* @param {string} id - Playlist ID
|
|
42
|
+
* @param {Object} playlist - Full DP1 playlist object
|
|
43
|
+
*/
|
|
44
|
+
function storePlaylist(id, playlist) {
|
|
45
|
+
if (!id) {
|
|
46
|
+
throw new Error('Playlist ID is required');
|
|
47
|
+
}
|
|
48
|
+
playlistRegistry.set(id, playlist);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Retrieve a playlist from the registry
|
|
52
|
+
*
|
|
53
|
+
* @param {string} id - Playlist ID
|
|
54
|
+
* @returns {Object|undefined} DP1 playlist object or undefined
|
|
55
|
+
*/
|
|
56
|
+
function getPlaylist(id) {
|
|
57
|
+
return playlistRegistry.get(id);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if a playlist exists in the registry
|
|
61
|
+
*
|
|
62
|
+
* @param {string} id - Playlist ID
|
|
63
|
+
* @returns {boolean} True if playlist exists
|
|
64
|
+
*/
|
|
65
|
+
function hasPlaylist(id) {
|
|
66
|
+
return playlistRegistry.has(id);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Clear all registries
|
|
70
|
+
* Should be called after successful playlist build or on error
|
|
71
|
+
*/
|
|
72
|
+
function clearRegistries() {
|
|
73
|
+
itemRegistry.clear();
|
|
74
|
+
playlistRegistry.clear();
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get registry statistics (for debugging)
|
|
78
|
+
*
|
|
79
|
+
* @returns {Object} Registry stats
|
|
80
|
+
*/
|
|
81
|
+
function getStats() {
|
|
82
|
+
return {
|
|
83
|
+
itemCount: itemRegistry.size,
|
|
84
|
+
playlistCount: playlistRegistry.size,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
module.exports = {
|
|
88
|
+
storeItem,
|
|
89
|
+
getItem,
|
|
90
|
+
hasItem,
|
|
91
|
+
storePlaylist,
|
|
92
|
+
getPlaylist,
|
|
93
|
+
hasPlaylist,
|
|
94
|
+
clearRegistries,
|
|
95
|
+
getStats,
|
|
96
|
+
};
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getConfig = getConfig;
|
|
7
|
+
exports.sanitizationLevelToNumber = sanitizationLevelToNumber;
|
|
8
|
+
exports.getBrowserConfig = getBrowserConfig;
|
|
9
|
+
exports.getPlaylistConfig = getPlaylistConfig;
|
|
10
|
+
exports.getFeedConfig = getFeedConfig;
|
|
11
|
+
exports.getFF1DeviceConfig = getFF1DeviceConfig;
|
|
12
|
+
exports.getModelConfig = getModelConfig;
|
|
13
|
+
exports.validateConfig = validateConfig;
|
|
14
|
+
exports.createSampleConfig = createSampleConfig;
|
|
15
|
+
exports.listAvailableModels = listAvailableModels;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
/**
|
|
19
|
+
* Load configuration from config.json or environment variables
|
|
20
|
+
* Priority: config.json > .env > defaults
|
|
21
|
+
*
|
|
22
|
+
* @returns {Object} Configuration object with model settings
|
|
23
|
+
* @returns {string} returns.defaultModel - Name of the default model to use
|
|
24
|
+
* @returns {Object} returns.models - Available models configuration
|
|
25
|
+
* @returns {number} returns.defaultDuration - Default duration per item in seconds
|
|
26
|
+
*/
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
const configPath = path_1.default.join(process.cwd(), 'config.json');
|
|
29
|
+
// Default configuration supporting Grok as default
|
|
30
|
+
const defaultConfig = {
|
|
31
|
+
defaultModel: process.env.DEFAULT_MODEL || 'grok',
|
|
32
|
+
models: {
|
|
33
|
+
grok: {
|
|
34
|
+
apiKey: process.env.GROK_API_KEY || '',
|
|
35
|
+
baseURL: process.env.GROK_API_BASE_URL || 'https://api.x.ai/v1',
|
|
36
|
+
model: process.env.GROK_MODEL || 'grok-beta',
|
|
37
|
+
availableModels: ['grok-beta', 'grok-2-1212', 'grok-2-vision-1212'],
|
|
38
|
+
timeout: parseInt(process.env.TIMEOUT || '30000', 10),
|
|
39
|
+
maxRetries: parseInt(process.env.MAX_RETRIES || '3', 10),
|
|
40
|
+
temperature: parseFloat(process.env.TEMPERATURE || '0.3'),
|
|
41
|
+
maxTokens: parseInt(process.env.MAX_TOKENS || '4000', 10),
|
|
42
|
+
supportsFunctionCalling: true,
|
|
43
|
+
},
|
|
44
|
+
chatgpt: {
|
|
45
|
+
apiKey: process.env.OPENAI_API_KEY || '',
|
|
46
|
+
baseURL: 'https://api.openai.com/v1',
|
|
47
|
+
model: 'gpt-4o',
|
|
48
|
+
availableModels: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'],
|
|
49
|
+
timeout: 30000,
|
|
50
|
+
maxRetries: 3,
|
|
51
|
+
temperature: 0.3,
|
|
52
|
+
maxTokens: 4000,
|
|
53
|
+
supportsFunctionCalling: true,
|
|
54
|
+
},
|
|
55
|
+
gemini: {
|
|
56
|
+
apiKey: process.env.GEMINI_API_KEY || '',
|
|
57
|
+
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai/',
|
|
58
|
+
model: 'gemini-2.5-flash',
|
|
59
|
+
availableModels: ['gemini-2.5-flash-lite', 'gemini-2.5-flash', 'gemini-flash-lite-latest'],
|
|
60
|
+
timeout: 30000,
|
|
61
|
+
maxRetries: 3,
|
|
62
|
+
temperature: 0.3,
|
|
63
|
+
maxTokens: 4000,
|
|
64
|
+
supportsFunctionCalling: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
defaultDuration: parseInt(process.env.DEFAULT_DURATION || '10', 10),
|
|
68
|
+
browser: {
|
|
69
|
+
timeout: parseInt(process.env.BROWSER_TIMEOUT || '90000', 10),
|
|
70
|
+
sanitizationLevel: process.env.SANITIZATION_LEVEL || 'medium',
|
|
71
|
+
},
|
|
72
|
+
feed: {
|
|
73
|
+
baseURLs: process.env.FEED_BASE_URLS
|
|
74
|
+
? process.env.FEED_BASE_URLS.split(',')
|
|
75
|
+
: ['https://feed.feralfile.com/api/v1'],
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
// Try to load config.json if it exists
|
|
79
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const fileConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
82
|
+
// Deep merge models configuration
|
|
83
|
+
const mergedModels = { ...defaultConfig.models };
|
|
84
|
+
if (fileConfig.models) {
|
|
85
|
+
Object.keys(fileConfig.models).forEach((modelName) => {
|
|
86
|
+
mergedModels[modelName] = {
|
|
87
|
+
...(defaultConfig.models[modelName] || {}),
|
|
88
|
+
...fileConfig.models[modelName],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Merge with defaults, file config takes precedence
|
|
93
|
+
return {
|
|
94
|
+
...defaultConfig,
|
|
95
|
+
...fileConfig,
|
|
96
|
+
models: mergedModels,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (_error) {
|
|
100
|
+
console.warn('Warning: Failed to parse config.json, using defaults');
|
|
101
|
+
return defaultConfig;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Return default config if no file exists
|
|
105
|
+
return defaultConfig;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get current configuration
|
|
109
|
+
*
|
|
110
|
+
* @returns {Object} Current configuration
|
|
111
|
+
*/
|
|
112
|
+
function getConfig() {
|
|
113
|
+
return loadConfig();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Convert sanitization level string to numeric value
|
|
117
|
+
*
|
|
118
|
+
* @param {string|number} level - Sanitization level ('none', 'low', 'medium', 'high') or number (0-3)
|
|
119
|
+
* @returns {number} Numeric level (0 = none, 1 = low, 2 = medium, 3 = high)
|
|
120
|
+
*/
|
|
121
|
+
function sanitizationLevelToNumber(level) {
|
|
122
|
+
if (typeof level === 'number') {
|
|
123
|
+
return level;
|
|
124
|
+
}
|
|
125
|
+
const levelMap = {
|
|
126
|
+
none: 0,
|
|
127
|
+
low: 1,
|
|
128
|
+
medium: 2,
|
|
129
|
+
high: 3,
|
|
130
|
+
};
|
|
131
|
+
return levelMap[level] !== undefined ? levelMap[level] : 2; // Default to medium (2)
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get browser configuration
|
|
135
|
+
*
|
|
136
|
+
* @returns {Object} Browser configuration
|
|
137
|
+
* @returns {number} returns.timeout - Browser timeout in milliseconds
|
|
138
|
+
* @returns {number} returns.sanitizationLevel - Numeric sanitization level (0-3)
|
|
139
|
+
*/
|
|
140
|
+
function getBrowserConfig() {
|
|
141
|
+
const config = getConfig();
|
|
142
|
+
const browserConfig = config.browser || {
|
|
143
|
+
timeout: 90000,
|
|
144
|
+
sanitizationLevel: 'medium',
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
timeout: browserConfig.timeout,
|
|
148
|
+
sanitizationLevel: sanitizationLevelToNumber(browserConfig.sanitizationLevel),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get playlist configuration including private key for signing
|
|
153
|
+
*
|
|
154
|
+
* @returns {Object} Playlist configuration
|
|
155
|
+
* @returns {string|null} returns.privateKey - Ed25519 private key in base64 format (null if not configured)
|
|
156
|
+
*/
|
|
157
|
+
function getPlaylistConfig() {
|
|
158
|
+
const config = getConfig();
|
|
159
|
+
const playlistConfig = config.playlist || {};
|
|
160
|
+
return {
|
|
161
|
+
privateKey: playlistConfig.privateKey || process.env.PLAYLIST_PRIVATE_KEY || null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get feed configuration for DP1 feed API
|
|
166
|
+
*
|
|
167
|
+
* Supports both legacy (feed.baseURLs/apiKey) and new (feedServers array) formats.
|
|
168
|
+
*
|
|
169
|
+
* @returns {Object} Feed configuration
|
|
170
|
+
* @returns {string[]} returns.baseURLs - Array of base URLs for feed APIs
|
|
171
|
+
* @returns {string} [returns.apiKey] - Optional API key for authentication (legacy)
|
|
172
|
+
* @returns {Array<Object>} [returns.servers] - Array of feed servers with individual API keys (new)
|
|
173
|
+
*/
|
|
174
|
+
function getFeedConfig() {
|
|
175
|
+
const config = getConfig();
|
|
176
|
+
// Check for new feedServers format first
|
|
177
|
+
if (config.feedServers && Array.isArray(config.feedServers) && config.feedServers.length > 0) {
|
|
178
|
+
const baseURLs = config.feedServers.map((server) => server.baseUrl);
|
|
179
|
+
return {
|
|
180
|
+
baseURLs,
|
|
181
|
+
servers: config.feedServers,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// Fall back to legacy feed format
|
|
185
|
+
const feedConfig = config.feed || {};
|
|
186
|
+
// Support both legacy baseURL and new baseURLs
|
|
187
|
+
let urls = [];
|
|
188
|
+
if (feedConfig.baseURLs && Array.isArray(feedConfig.baseURLs)) {
|
|
189
|
+
urls = feedConfig.baseURLs;
|
|
190
|
+
}
|
|
191
|
+
else if (feedConfig.baseURL) {
|
|
192
|
+
urls = [feedConfig.baseURL];
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Default feed URL
|
|
196
|
+
urls = ['https://feed.feralfile.com/api/v1'];
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
baseURLs: urls,
|
|
200
|
+
apiKey: feedConfig.apiKey,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get FF1 device configuration for casting playlists
|
|
205
|
+
*
|
|
206
|
+
* @returns {Object} FF1 device configuration
|
|
207
|
+
* @returns {Array<Object>} returns.devices - Array of configured FF1 devices
|
|
208
|
+
* @returns {string} returns.devices[].host - Device host URL
|
|
209
|
+
* @returns {string} [returns.devices[].apiKey] - Optional device API key
|
|
210
|
+
* @returns {string} [returns.devices[].topicID] - Optional device topic ID
|
|
211
|
+
* @returns {string} [returns.devices[].name] - Optional device name
|
|
212
|
+
*/
|
|
213
|
+
function getFF1DeviceConfig() {
|
|
214
|
+
const config = getConfig();
|
|
215
|
+
const ff1Devices = config.ff1Devices || { devices: [] };
|
|
216
|
+
return {
|
|
217
|
+
devices: ff1Devices.devices || [],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get configuration for a specific model
|
|
222
|
+
*
|
|
223
|
+
* @param {string} [modelName] - Name of the model (defaults to defaultModel from config)
|
|
224
|
+
* @returns {Object} Model configuration
|
|
225
|
+
* @returns {string} returns.apiKey - API key for the model
|
|
226
|
+
* @returns {string} returns.baseURL - Base URL for the API
|
|
227
|
+
* @returns {string} returns.model - Model name/identifier
|
|
228
|
+
* @returns {number} returns.timeout - Request timeout in milliseconds
|
|
229
|
+
* @returns {number} returns.maxRetries - Maximum number of retries
|
|
230
|
+
* @returns {number} returns.temperature - Temperature for generation
|
|
231
|
+
* @returns {number} returns.maxTokens - Maximum tokens for generation
|
|
232
|
+
* @returns {boolean} returns.supportsFunctionCalling - Whether model supports function calling
|
|
233
|
+
* @throws {Error} If model is not configured or doesn't support function calling
|
|
234
|
+
*/
|
|
235
|
+
function getModelConfig(modelName) {
|
|
236
|
+
const config = getConfig();
|
|
237
|
+
const selectedModel = modelName || config.defaultModel;
|
|
238
|
+
if (!config.models[selectedModel]) {
|
|
239
|
+
throw new Error(`Model "${selectedModel}" is not configured. Available models: ${Object.keys(config.models).join(', ')}`);
|
|
240
|
+
}
|
|
241
|
+
const modelConfig = config.models[selectedModel];
|
|
242
|
+
if (!modelConfig.supportsFunctionCalling) {
|
|
243
|
+
throw new Error(`Model "${selectedModel}" does not support function calling`);
|
|
244
|
+
}
|
|
245
|
+
const normalizedBaseURL = modelConfig.baseURL?.replace(/\/+$/, '');
|
|
246
|
+
return {
|
|
247
|
+
...modelConfig,
|
|
248
|
+
baseURL: normalizedBaseURL,
|
|
249
|
+
defaultDuration: config.defaultDuration,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Validate configuration for a specific model
|
|
254
|
+
*
|
|
255
|
+
* @param {string} [modelName] - Name of the model to validate
|
|
256
|
+
* @returns {Object} Validation result
|
|
257
|
+
* @returns {boolean} returns.valid - Whether the configuration is valid
|
|
258
|
+
* @returns {Array<string>} returns.errors - List of validation errors
|
|
259
|
+
*/
|
|
260
|
+
function validateConfig(modelName) {
|
|
261
|
+
const errors = [];
|
|
262
|
+
try {
|
|
263
|
+
const config = getConfig();
|
|
264
|
+
const selectedModel = modelName || config.defaultModel;
|
|
265
|
+
if (!config.models[selectedModel]) {
|
|
266
|
+
errors.push(`Model "${selectedModel}" is not configured. Available: ${Object.keys(config.models).join(', ')}`);
|
|
267
|
+
return { valid: false, errors };
|
|
268
|
+
}
|
|
269
|
+
const modelConfig = config.models[selectedModel];
|
|
270
|
+
if (!modelConfig.apiKey || modelConfig.apiKey === 'your_api_key_here') {
|
|
271
|
+
errors.push(`API key for "${selectedModel}" is missing or not configured`);
|
|
272
|
+
}
|
|
273
|
+
if (!modelConfig.baseURL) {
|
|
274
|
+
errors.push(`Base URL for "${selectedModel}" is missing`);
|
|
275
|
+
}
|
|
276
|
+
if (!modelConfig.model) {
|
|
277
|
+
errors.push(`Model identifier for "${selectedModel}" is not set`);
|
|
278
|
+
}
|
|
279
|
+
if (!modelConfig.supportsFunctionCalling) {
|
|
280
|
+
errors.push(`Model "${selectedModel}" does not support function calling (required)`);
|
|
281
|
+
}
|
|
282
|
+
// Validate browser configuration
|
|
283
|
+
if (config.browser) {
|
|
284
|
+
if (config.browser.timeout && typeof config.browser.timeout !== 'number') {
|
|
285
|
+
errors.push('Browser timeout must be a number');
|
|
286
|
+
}
|
|
287
|
+
const validLevels = ['none', 'low', 'medium', 'high'];
|
|
288
|
+
if (config.browser.sanitizationLevel &&
|
|
289
|
+
!validLevels.includes(config.browser.sanitizationLevel) &&
|
|
290
|
+
typeof config.browser.sanitizationLevel !== 'number') {
|
|
291
|
+
errors.push(`Invalid browser.sanitizationLevel: "${config.browser.sanitizationLevel}". Must be one of: ${validLevels.join(', ')} or 0-3`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Validate playlist configuration (optional, but warn if configured incorrectly)
|
|
295
|
+
if (config.playlist && config.playlist.privateKey) {
|
|
296
|
+
const key = config.playlist.privateKey;
|
|
297
|
+
if (key !== 'your_ed25519_private_key_base64_here' &&
|
|
298
|
+
typeof key === 'string' &&
|
|
299
|
+
key.length > 0) {
|
|
300
|
+
// Check if it looks like valid base64
|
|
301
|
+
const base64Regex = /^[A-Za-z0-9+/]+=*$/;
|
|
302
|
+
if (!base64Regex.test(key)) {
|
|
303
|
+
errors.push('playlist.privateKey must be a valid base64-encoded ed25519 private key');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
valid: errors.length === 0,
|
|
309
|
+
errors,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
errors.push(error.message);
|
|
314
|
+
return { valid: false, errors };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Create a sample config.json file from config.json.example
|
|
319
|
+
*
|
|
320
|
+
* Loads the bundled config.json.example template from the package directory
|
|
321
|
+
* and writes it to the user's current working directory.
|
|
322
|
+
*
|
|
323
|
+
* @returns {Promise<string>} Path to the created config file
|
|
324
|
+
* @throws {Error} If config.json already exists or example file is missing
|
|
325
|
+
*/
|
|
326
|
+
async function createSampleConfig() {
|
|
327
|
+
const configPath = path_1.default.join(process.cwd(), 'config.json');
|
|
328
|
+
// Check if config.json already exists in user's directory
|
|
329
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
330
|
+
throw new Error('config.json already exists');
|
|
331
|
+
}
|
|
332
|
+
// Look for config.json.example in the package directory
|
|
333
|
+
// When compiled, this file is in dist/src/config.js
|
|
334
|
+
// The template is at the package root: ../../config.json.example
|
|
335
|
+
const packageRoot = path_1.default.join(__dirname, '../..');
|
|
336
|
+
const examplePath = path_1.default.join(packageRoot, 'config.json.example');
|
|
337
|
+
if (!fs_1.default.existsSync(examplePath)) {
|
|
338
|
+
throw new Error(`config.json.example not found at ${examplePath}. This is likely a package installation issue.`);
|
|
339
|
+
}
|
|
340
|
+
const exampleConfig = fs_1.default.readFileSync(examplePath, 'utf-8');
|
|
341
|
+
fs_1.default.writeFileSync(configPath, exampleConfig, 'utf-8');
|
|
342
|
+
return configPath;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* List all available models
|
|
346
|
+
*
|
|
347
|
+
* @returns {Array<string>} List of available model names
|
|
348
|
+
*/
|
|
349
|
+
function listAvailableModels() {
|
|
350
|
+
const config = getConfig();
|
|
351
|
+
return Object.keys(config.models);
|
|
352
|
+
}
|