@feralfile/cli 1.6.0 → 2.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 +6 -11
- package/config.json.example +0 -65
- package/dist/index.js +1 -3
- package/dist/package.json +3 -4
- package/dist/src/commands/config.js +15 -13
- package/dist/src/commands/play.js +63 -0
- package/dist/src/commands/setup.js +1 -73
- package/dist/src/commands/status.js +0 -13
- package/dist/src/config.js +3 -126
- package/dist/src/main.js +2 -361
- package/dist/src/utilities/ff1-device.js +55 -0
- package/dist/src/utilities/playlist-builder.js +79 -17
- package/docs/CONFIGURATION.md +1 -35
- package/docs/EXAMPLES.md +206 -215
- package/docs/PROJECT_SPEC.md +20 -18
- package/docs/README.md +51 -104
- package/package.json +3 -4
- package/dist/src/ai-orchestrator/index.js +0 -1040
- package/dist/src/ai-orchestrator/registry.js +0 -96
- package/dist/src/commands/chat.js +0 -189
- package/dist/src/intent-parser/index.js +0 -1393
- package/dist/src/intent-parser/utils.js +0 -116
- package/docs/FUNCTION_CALLING.md +0 -100
|
@@ -1,96 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,189 +0,0 @@
|
|
|
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: ff-cli 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('\nff-cli 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('\nff-cli 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
|
-
});
|