@feralfile/cli 1.1.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/LICENSE +21 -0
- package/README.md +96 -0
- package/config.json.example +96 -0
- package/dist/index.js +54 -0
- package/dist/src/ai-orchestrator/index.js +1019 -0
- package/dist/src/ai-orchestrator/registry.js +96 -0
- package/dist/src/commands/build.js +69 -0
- package/dist/src/commands/chat.js +189 -0
- package/dist/src/commands/config.js +68 -0
- package/dist/src/commands/device.js +278 -0
- package/dist/src/commands/helpers/config-files.js +62 -0
- package/dist/src/commands/helpers/device-discovery.js +111 -0
- package/dist/src/commands/helpers/playlist-display.js +161 -0
- package/dist/src/commands/helpers/prompt.js +65 -0
- package/dist/src/commands/helpers/ssh-helpers.js +44 -0
- package/dist/src/commands/play.js +110 -0
- package/dist/src/commands/publish.js +115 -0
- package/dist/src/commands/setup.js +225 -0
- package/dist/src/commands/sign.js +41 -0
- package/dist/src/commands/ssh.js +108 -0
- package/dist/src/commands/status.js +126 -0
- package/dist/src/commands/validate.js +18 -0
- package/dist/src/config.js +441 -0
- package/dist/src/intent-parser/index.js +1382 -0
- package/dist/src/intent-parser/utils.js +108 -0
- package/dist/src/logger.js +82 -0
- package/dist/src/main.js +459 -0
- package/dist/src/types.js +5 -0
- package/dist/src/utilities/address-validator.js +242 -0
- package/dist/src/utilities/device-default.js +36 -0
- package/dist/src/utilities/device-lookup.js +107 -0
- package/dist/src/utilities/device-normalize.js +62 -0
- package/dist/src/utilities/device-upsert.js +91 -0
- package/dist/src/utilities/domain-resolver.js +291 -0
- package/dist/src/utilities/ed25519-key-derive.js +155 -0
- package/dist/src/utilities/feed-fetcher.js +471 -0
- package/dist/src/utilities/ff1-compatibility.js +269 -0
- package/dist/src/utilities/ff1-device.js +250 -0
- package/dist/src/utilities/ff1-discovery.js +330 -0
- package/dist/src/utilities/functions.js +308 -0
- package/dist/src/utilities/index.js +469 -0
- package/dist/src/utilities/nft-indexer.js +1024 -0
- package/dist/src/utilities/playlist-builder.js +523 -0
- package/dist/src/utilities/playlist-publisher.js +131 -0
- package/dist/src/utilities/playlist-send.js +260 -0
- package/dist/src/utilities/playlist-signer.js +204 -0
- package/dist/src/utilities/playlist-signing-role.js +41 -0
- package/dist/src/utilities/playlist-source.js +128 -0
- package/dist/src/utilities/playlist-verifier.js +274 -0
- package/dist/src/utilities/ssh-access.js +145 -0
- package/dist/src/utils.js +48 -0
- package/docs/CONFIGURATION.md +206 -0
- package/docs/EXAMPLES.md +390 -0
- package/docs/FUNCTION_CALLING.md +96 -0
- package/docs/PROJECT_SPEC.md +228 -0
- package/docs/README.md +348 -0
- package/docs/RELEASING.md +73 -0
- package/package.json +76 -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,69 @@
|
|
|
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.buildCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const main_1 = require("../main");
|
|
11
|
+
exports.buildCommand = new commander_1.Command('build')
|
|
12
|
+
.description('Build playlist from structured parameters (JSON file or stdin)')
|
|
13
|
+
.argument('[params-file]', 'Path to JSON parameters file (or use stdin)')
|
|
14
|
+
.option('-o, --output <filename>', 'Output filename for the playlist', 'playlist.json')
|
|
15
|
+
.option('-v, --verbose', 'Show detailed output', false)
|
|
16
|
+
.action(async (paramsFile, options) => {
|
|
17
|
+
try {
|
|
18
|
+
let params;
|
|
19
|
+
if (paramsFile) {
|
|
20
|
+
const content = await fs_1.promises.readFile(paramsFile, 'utf-8');
|
|
21
|
+
params = JSON.parse(content);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const stdin = await new Promise((resolve, reject) => {
|
|
25
|
+
let data = '';
|
|
26
|
+
process.stdin.setEncoding('utf8');
|
|
27
|
+
process.stdin.on('data', (chunk) => {
|
|
28
|
+
data += chunk;
|
|
29
|
+
});
|
|
30
|
+
process.stdin.on('end', () => {
|
|
31
|
+
resolve(data);
|
|
32
|
+
});
|
|
33
|
+
process.stdin.on('error', reject);
|
|
34
|
+
});
|
|
35
|
+
if (!stdin.trim()) {
|
|
36
|
+
console.error(chalk_1.default.red('No parameters provided'));
|
|
37
|
+
console.log(chalk_1.default.yellow('\nUsage:'));
|
|
38
|
+
console.log(' ff1 build params.json');
|
|
39
|
+
console.log(' cat params.json | ff1 build');
|
|
40
|
+
console.log(' echo \'{"requirements":[...]}\' | ff1 build');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
params = JSON.parse(stdin);
|
|
44
|
+
}
|
|
45
|
+
if (options.verbose) {
|
|
46
|
+
console.log(chalk_1.default.blue('\nParameters:'));
|
|
47
|
+
console.log(chalk_1.default.dim(JSON.stringify(params, null, 2)));
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk_1.default.blue('\nBuild playlist from parameters\n'));
|
|
51
|
+
const result = await (0, main_1.buildPlaylistDirect)(params, {
|
|
52
|
+
verbose: options.verbose,
|
|
53
|
+
outputPath: options.output,
|
|
54
|
+
});
|
|
55
|
+
if (result && result.playlist) {
|
|
56
|
+
console.log(chalk_1.default.green('\nPlaylist saved'));
|
|
57
|
+
console.log(chalk_1.default.dim(` Title: ${result.playlist.title}`));
|
|
58
|
+
console.log(chalk_1.default.dim(` Items: ${result.playlist.items?.length || 0}`));
|
|
59
|
+
console.log(chalk_1.default.dim(` Output: ${options.output}\n`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
64
|
+
if (options.verbose) {
|
|
65
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.chatCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const config_1 = require("../config");
|
|
44
|
+
const main_1 = require("../main");
|
|
45
|
+
const playlist_display_1 = require("./helpers/playlist-display");
|
|
46
|
+
// chat keeps an inline readline instance — historySize and the closed-flag
|
|
47
|
+
// lifecycle (Ctrl+C → close → graceful exit) are specific to this command
|
|
48
|
+
// and don't belong in the shared createPrompt() helper.
|
|
49
|
+
exports.chatCommand = new commander_1.Command('chat')
|
|
50
|
+
.description('Start an interactive chat to build playlists using natural language')
|
|
51
|
+
.argument('[content]', 'Optional: Direct chat content (non-interactive mode)')
|
|
52
|
+
.option('-o, --output <filename>', 'Output filename for the playlist', 'playlist.json')
|
|
53
|
+
.option('-m, --model <name>', 'AI model to use (claude, grok, gpt, gemini) - defaults to config setting')
|
|
54
|
+
.option('-d, --device <name>', 'Target FF1 device name (defaults to first configured device)')
|
|
55
|
+
.option('-v, --verbose', 'Show detailed technical output of function calls', false)
|
|
56
|
+
.action(async (content, options) => {
|
|
57
|
+
try {
|
|
58
|
+
const config = (0, config_1.getConfig)();
|
|
59
|
+
const availableModels = (0, config_1.listAvailableModels)();
|
|
60
|
+
if (options.model && !availableModels.includes(options.model)) {
|
|
61
|
+
console.error(chalk_1.default.red(`Invalid model: "${options.model}"`));
|
|
62
|
+
console.log(chalk_1.default.yellow(`Available models: ${availableModels.join(', ')}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const modelName = options.model || config.defaultModel;
|
|
66
|
+
const validation = (0, config_1.validateConfig)(modelName);
|
|
67
|
+
if (!validation.valid) {
|
|
68
|
+
console.error(chalk_1.default.red('Configuration error:'));
|
|
69
|
+
validation.errors.forEach((error) => {
|
|
70
|
+
console.error(chalk_1.default.red(` • ${error}`));
|
|
71
|
+
});
|
|
72
|
+
console.log(chalk_1.default.yellow('\nRun: ff1 setup\n'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Non-interactive mode: content was passed as a positional argument.
|
|
76
|
+
if (content) {
|
|
77
|
+
console.log(chalk_1.default.blue('\nFF1 Chat (non-interactive)\n'));
|
|
78
|
+
console.log(chalk_1.default.dim(`Model: ${modelName}\n`));
|
|
79
|
+
console.log(chalk_1.default.yellow('Request:'), content);
|
|
80
|
+
console.log();
|
|
81
|
+
try {
|
|
82
|
+
const result = await (0, main_1.buildPlaylist)(content, {
|
|
83
|
+
verbose: options.verbose,
|
|
84
|
+
outputPath: options.output,
|
|
85
|
+
modelName: modelName,
|
|
86
|
+
interactive: false,
|
|
87
|
+
deviceName: options.device,
|
|
88
|
+
});
|
|
89
|
+
if (result && result.playlist) {
|
|
90
|
+
console.log(chalk_1.default.green('\nPlaylist saved'));
|
|
91
|
+
console.log(chalk_1.default.dim(` Title: ${result.playlist.title}`));
|
|
92
|
+
console.log(chalk_1.default.dim(` Items: ${result.playlist.items?.length || 0}`));
|
|
93
|
+
console.log(chalk_1.default.dim(` Output: ${options.output}\n`));
|
|
94
|
+
}
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
99
|
+
if (options.verbose) {
|
|
100
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Interactive mode.
|
|
106
|
+
console.log(chalk_1.default.blue('\nFF1 Chat\n'));
|
|
107
|
+
console.log(chalk_1.default.dim('Describe the playlist you want. Ctrl+C to exit.'));
|
|
108
|
+
console.log(chalk_1.default.dim(`Model: ${modelName}\n`));
|
|
109
|
+
console.log(chalk_1.default.dim('Examples:'));
|
|
110
|
+
console.log(chalk_1.default.dim(' • Get 3 works from reas.eth'));
|
|
111
|
+
console.log(chalk_1.default.dim(' • Get 3 works from einstein-rosen.tez'));
|
|
112
|
+
console.log(chalk_1.default.dim(' • Get tokens 52932,52457 from Ethereum contract 0xb932a70A57673d89f4acfFBE830E8ed7f75Fb9e0'));
|
|
113
|
+
console.log(chalk_1.default.dim(' • Get 3 from Unsupervised'));
|
|
114
|
+
console.log(chalk_1.default.dim(' Tip: add -v to see tool calls'));
|
|
115
|
+
console.log();
|
|
116
|
+
const rl = readline.createInterface({
|
|
117
|
+
input: process.stdin,
|
|
118
|
+
output: process.stdout,
|
|
119
|
+
historySize: 100,
|
|
120
|
+
});
|
|
121
|
+
let closed = false;
|
|
122
|
+
rl.on('close', () => {
|
|
123
|
+
closed = true;
|
|
124
|
+
});
|
|
125
|
+
rl.on('SIGINT', () => {
|
|
126
|
+
rl.close();
|
|
127
|
+
});
|
|
128
|
+
const ask = async () => new Promise((resolve) => {
|
|
129
|
+
if (closed) {
|
|
130
|
+
resolve('');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
rl.question(chalk_1.default.yellow('You: '), (answer) => {
|
|
134
|
+
resolve(answer.trim());
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
while (!closed) {
|
|
138
|
+
const userInput = await ask();
|
|
139
|
+
if (closed) {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
if (!userInput) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
console.log();
|
|
146
|
+
try {
|
|
147
|
+
const result = await (0, main_1.buildPlaylist)(userInput, {
|
|
148
|
+
verbose: options.verbose,
|
|
149
|
+
outputPath: options.output,
|
|
150
|
+
modelName: modelName,
|
|
151
|
+
deviceName: options.device,
|
|
152
|
+
});
|
|
153
|
+
// Skip the playlist summary for send actions: the device send path
|
|
154
|
+
// already emits its own confirmation, and re-printing here would
|
|
155
|
+
// imply the playlist was only built (not sent).
|
|
156
|
+
if (options.verbose) {
|
|
157
|
+
console.log(chalk_1.default.dim(`\n[DEBUG] result.sentToDevice: ${result?.sentToDevice}`));
|
|
158
|
+
console.log(chalk_1.default.dim(`[DEBUG] result.action: ${result?.action}`));
|
|
159
|
+
}
|
|
160
|
+
if (result &&
|
|
161
|
+
result.playlist &&
|
|
162
|
+
result.action !== 'send_playlist' &&
|
|
163
|
+
!result.sentToDevice) {
|
|
164
|
+
(0, playlist_display_1.displayPlaylistSummary)(result.playlist, options.output);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
169
|
+
if (options.verbose) {
|
|
170
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
171
|
+
}
|
|
172
|
+
console.log();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (closed) {
|
|
176
|
+
throw new Error('readline was closed');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.message !== 'readline was closed') {
|
|
181
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
182
|
+
if (process.env.DEBUG) {
|
|
183
|
+
console.error(chalk_1.default.dim(error.stack));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(chalk_1.default.blue('\nGoodbye\n'));
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
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.configCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
// `config` is a single command with an action argument rather than a
|
|
11
|
+
// commander subcommand group. Kept this way to preserve the existing
|
|
12
|
+
// CLI surface (`ff1 config init|show|validate`) used in scripts.
|
|
13
|
+
exports.configCommand = new commander_1.Command('config')
|
|
14
|
+
.description('Manage configuration')
|
|
15
|
+
.argument('<action>', 'Action: init, show, or validate')
|
|
16
|
+
.action(async (action) => {
|
|
17
|
+
try {
|
|
18
|
+
if (action === 'init') {
|
|
19
|
+
console.log(chalk_1.default.blue('\nCreate config.json\n'));
|
|
20
|
+
const { userPath } = (0, config_1.getConfigPaths)();
|
|
21
|
+
const configPath = await (0, config_1.createSampleConfig)(userPath);
|
|
22
|
+
console.log(chalk_1.default.green(`Created ${configPath}`));
|
|
23
|
+
console.log(chalk_1.default.yellow('\nNext: ff1 setup\n'));
|
|
24
|
+
}
|
|
25
|
+
else if (action === 'show') {
|
|
26
|
+
const config = (0, config_1.getConfig)();
|
|
27
|
+
console.log(chalk_1.default.blue('\nCurrent configuration\n'));
|
|
28
|
+
console.log(chalk_1.default.bold('Default model:'), chalk_1.default.white(config.defaultModel));
|
|
29
|
+
console.log(chalk_1.default.bold('Default duration:'), chalk_1.default.white(config.defaultDuration + 's'));
|
|
30
|
+
console.log(chalk_1.default.bold('\nAvailable models:\n'));
|
|
31
|
+
const models = (0, config_1.listAvailableModels)();
|
|
32
|
+
models.forEach((modelName) => {
|
|
33
|
+
const modelConfig = config.models[modelName];
|
|
34
|
+
const isCurrent = modelName === config.defaultModel;
|
|
35
|
+
console.log(` ${isCurrent ? chalk_1.default.green('→') : ' '} ${chalk_1.default.bold(modelName)}`);
|
|
36
|
+
console.log(` API key: ${modelConfig.apiKey && modelConfig.apiKey !== 'your_api_key_here' ? chalk_1.default.green('Set') : chalk_1.default.red('Missing')}`);
|
|
37
|
+
console.log(` Base URL: ${chalk_1.default.dim(modelConfig.baseURL)}`);
|
|
38
|
+
console.log(` Model: ${chalk_1.default.dim(modelConfig.model)}`);
|
|
39
|
+
console.log(` Function calling: ${modelConfig.supportsFunctionCalling ? chalk_1.default.green('Supported') : chalk_1.default.red('Not supported')}`);
|
|
40
|
+
console.log();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else if (action === 'validate') {
|
|
44
|
+
const validation = (0, config_1.validateConfig)();
|
|
45
|
+
console.log(chalk_1.default.blue('\nValidate configuration\n'));
|
|
46
|
+
if (validation.valid) {
|
|
47
|
+
console.log(chalk_1.default.green('Configuration is valid\n'));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(chalk_1.default.red('Configuration has errors:\n'));
|
|
51
|
+
validation.errors.forEach((error) => {
|
|
52
|
+
console.log(chalk_1.default.red(` • ${error}`));
|
|
53
|
+
});
|
|
54
|
+
console.log();
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.error(chalk_1.default.red(`\nUnknown action: ${action}`));
|
|
60
|
+
console.log(chalk_1.default.yellow('Available actions: init, show, validate\n'));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(chalk_1.default.red('\nError:'), error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|