@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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/config.json.example +96 -0
  4. package/dist/index.js +54 -0
  5. package/dist/src/ai-orchestrator/index.js +1019 -0
  6. package/dist/src/ai-orchestrator/registry.js +96 -0
  7. package/dist/src/commands/build.js +69 -0
  8. package/dist/src/commands/chat.js +189 -0
  9. package/dist/src/commands/config.js +68 -0
  10. package/dist/src/commands/device.js +278 -0
  11. package/dist/src/commands/helpers/config-files.js +62 -0
  12. package/dist/src/commands/helpers/device-discovery.js +111 -0
  13. package/dist/src/commands/helpers/playlist-display.js +161 -0
  14. package/dist/src/commands/helpers/prompt.js +65 -0
  15. package/dist/src/commands/helpers/ssh-helpers.js +44 -0
  16. package/dist/src/commands/play.js +110 -0
  17. package/dist/src/commands/publish.js +115 -0
  18. package/dist/src/commands/setup.js +225 -0
  19. package/dist/src/commands/sign.js +41 -0
  20. package/dist/src/commands/ssh.js +108 -0
  21. package/dist/src/commands/status.js +126 -0
  22. package/dist/src/commands/validate.js +18 -0
  23. package/dist/src/config.js +441 -0
  24. package/dist/src/intent-parser/index.js +1382 -0
  25. package/dist/src/intent-parser/utils.js +108 -0
  26. package/dist/src/logger.js +82 -0
  27. package/dist/src/main.js +459 -0
  28. package/dist/src/types.js +5 -0
  29. package/dist/src/utilities/address-validator.js +242 -0
  30. package/dist/src/utilities/device-default.js +36 -0
  31. package/dist/src/utilities/device-lookup.js +107 -0
  32. package/dist/src/utilities/device-normalize.js +62 -0
  33. package/dist/src/utilities/device-upsert.js +91 -0
  34. package/dist/src/utilities/domain-resolver.js +291 -0
  35. package/dist/src/utilities/ed25519-key-derive.js +155 -0
  36. package/dist/src/utilities/feed-fetcher.js +471 -0
  37. package/dist/src/utilities/ff1-compatibility.js +269 -0
  38. package/dist/src/utilities/ff1-device.js +250 -0
  39. package/dist/src/utilities/ff1-discovery.js +330 -0
  40. package/dist/src/utilities/functions.js +308 -0
  41. package/dist/src/utilities/index.js +469 -0
  42. package/dist/src/utilities/nft-indexer.js +1024 -0
  43. package/dist/src/utilities/playlist-builder.js +523 -0
  44. package/dist/src/utilities/playlist-publisher.js +131 -0
  45. package/dist/src/utilities/playlist-send.js +260 -0
  46. package/dist/src/utilities/playlist-signer.js +204 -0
  47. package/dist/src/utilities/playlist-signing-role.js +41 -0
  48. package/dist/src/utilities/playlist-source.js +128 -0
  49. package/dist/src/utilities/playlist-verifier.js +274 -0
  50. package/dist/src/utilities/ssh-access.js +145 -0
  51. package/dist/src/utils.js +48 -0
  52. package/docs/CONFIGURATION.md +206 -0
  53. package/docs/EXAMPLES.md +390 -0
  54. package/docs/FUNCTION_CALLING.md +96 -0
  55. package/docs/PROJECT_SPEC.md +228 -0
  56. package/docs/README.md +348 -0
  57. package/docs/RELEASING.md +73 -0
  58. 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
+ });