felo-ai 0.2.18 → 0.2.23

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.
@@ -0,0 +1,569 @@
1
+ #!/usr/bin/env node
2
+
3
+ const DEFAULT_API_BASE = 'https://openapi.felo.ai';
4
+ const DEFAULT_TIMEOUT_SEC = 60;
5
+ const STREAM_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;
6
+ const RECONNECT_DELAY_MS = 2000;
7
+ const HIDDEN_TOOLS = new Set(['manage_outline']);
8
+
9
+ function usage() {
10
+ console.error(
11
+ [
12
+ 'Usage:',
13
+ ' node felo-superAgent/scripts/run_superagent.mjs --query "your question" [options]',
14
+ '',
15
+ 'Options:',
16
+ ' --query <text> User question (required, 1-2000 chars)',
17
+ ' --thread-id <id> Existing thread ID for follow-up',
18
+ ' --live-doc-id <id> Reuse existing LiveDoc short_id',
19
+ ' --skill-id <id> Skill ID (new conversations only)',
20
+ ' --selected-resource-ids <ids> Comma-separated resource IDs (new conversations only)',
21
+ ' --ext <json> Extra params JSON (new conversations only)',
22
+ ' --accept-language <lang> Language preference (e.g. zh, en)',
23
+ ' --timeout <seconds> Request/stream timeout, default 60',
24
+ ' --json Output JSON with answer, thread_short_id, live_doc_short_id',
25
+ ' --verbose Log stream details to stderr',
26
+ ' --help Show this help',
27
+ ].join('\n')
28
+ );
29
+ }
30
+
31
+ function parseArgs(argv) {
32
+ const out = {
33
+ query: '',
34
+ threadId: '',
35
+ liveDocId: '',
36
+ skillId: '',
37
+ selectedResourceIds: [],
38
+ ext: null,
39
+ acceptLanguage: '',
40
+ timeoutSec: DEFAULT_TIMEOUT_SEC,
41
+ json: false,
42
+ verbose: false,
43
+ help: false,
44
+ };
45
+
46
+ for (let i = 0; i < argv.length; i++) {
47
+ const a = argv[i];
48
+ if (a === '--help' || a === '-h') {
49
+ out.help = true;
50
+ } else if (a === '--json' || a === '-j') {
51
+ out.json = true;
52
+ } else if (a === '--verbose' || a === '-v') {
53
+ out.verbose = true;
54
+ } else if (a === '--query' || a === '-q') {
55
+ out.query = (argv[++i] || '').trim();
56
+ } else if (a === '--thread-id') {
57
+ out.threadId = (argv[++i] || '').trim();
58
+ } else if (a === '--live-doc-id') {
59
+ out.liveDocId = (argv[++i] || '').trim();
60
+ } else if (a === '--skill-id') {
61
+ out.skillId = (argv[++i] || '').trim();
62
+ } else if (a === '--selected-resource-ids') {
63
+ out.selectedResourceIds = (argv[++i] || '').split(',').map((s) => s.trim()).filter(Boolean);
64
+ } else if (a === '--ext') {
65
+ const raw = (argv[++i] || '').trim();
66
+ try {
67
+ out.ext = JSON.parse(raw);
68
+ } catch {
69
+ console.error('Error: --ext must be valid JSON');
70
+ process.exit(1);
71
+ }
72
+ } else if (a === '--accept-language') {
73
+ out.acceptLanguage = (argv[++i] || '').trim();
74
+ } else if (a === '--timeout' || a === '-t') {
75
+ const n = parseInt(argv[++i] || '', 10);
76
+ if (Number.isFinite(n) && n > 0) out.timeoutSec = n;
77
+ } else if (!a.startsWith('-') && !out.query) {
78
+ out.query = a.trim();
79
+ }
80
+ }
81
+
82
+ return out;
83
+ }
84
+
85
+ function sleep(ms) {
86
+ return new Promise((resolve) => setTimeout(resolve, ms));
87
+ }
88
+
89
+ function getMessage(payload) {
90
+ return payload?.message || payload?.error || payload?.msg || payload?.code || 'Unknown error';
91
+ }
92
+
93
+ function isApiError(payload) {
94
+ const status = payload?.status;
95
+ const code = payload?.code;
96
+ if (typeof status === 'string' && status.toLowerCase() === 'error') return true;
97
+ if (typeof code === 'string' && code && code.toUpperCase() !== 'OK') return true;
98
+ return false;
99
+ }
100
+
101
+ async function fetchJson(url, init, timeoutMs) {
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
104
+ try {
105
+ const res = await fetch(url, { ...init, signal: controller.signal });
106
+ let body = {};
107
+ try {
108
+ body = await res.json();
109
+ } catch {
110
+ body = {};
111
+ }
112
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${getMessage(body)}`);
113
+ if (isApiError(body)) throw new Error(getMessage(body));
114
+ return body;
115
+ } catch (err) {
116
+ if (err?.name === 'AbortError') throw new Error(`Request timed out after ${timeoutMs / 1000}s`);
117
+ throw err;
118
+ } finally {
119
+ clearTimeout(timer);
120
+ }
121
+ }
122
+
123
+ // ── API ──
124
+
125
+ async function createConversation(apiKey, apiBase, body, timeoutMs, threadId) {
126
+ const url = threadId
127
+ ? `${apiBase}/v2/conversations/${encodeURIComponent(threadId)}/follow_up`
128
+ : `${apiBase}/v2/conversations`;
129
+ const payload = await fetchJson(
130
+ url,
131
+ {
132
+ method: 'POST',
133
+ headers: {
134
+ Accept: 'application/json',
135
+ Authorization: `Bearer ${apiKey}`,
136
+ 'Content-Type': 'application/json',
137
+ },
138
+ body: JSON.stringify(body),
139
+ },
140
+ timeoutMs
141
+ );
142
+ const data = payload?.data ?? {};
143
+ if (!data.stream_key) throw new Error('Unexpected response: missing stream_key');
144
+ return data;
145
+ }
146
+
147
+ // ── SSE ──
148
+
149
+ function extractToolParams(data) {
150
+ const out = [];
151
+ const tools = data?.tools;
152
+ if (!Array.isArray(tools)) return out;
153
+ for (const t of tools) {
154
+ if (HIDDEN_TOOLS.has(t?.name) || HIDDEN_TOOLS.has(t?.tool_name)) continue;
155
+ if (t?.name && t?.params) out.push({ name: t.name, params: t.params });
156
+ }
157
+ return out;
158
+ }
159
+
160
+ function extractToolResults(data) {
161
+ const out = [];
162
+ const tools = data?.tools;
163
+ if (!Array.isArray(tools)) return out;
164
+ for (const t of tools) {
165
+ if (HIDDEN_TOOLS.has(t?.name) || HIDDEN_TOOLS.has(t?.tool_name)) continue;
166
+ const callResult = t?.call_result;
167
+ if (t?.tool_name === 'generate_images' || t?.name === 'generate_images') {
168
+ if (!callResult) continue;
169
+ if (Array.isArray(callResult)) {
170
+ for (const item of callResult) {
171
+ if (item?.image_url) out.push({ type: 'image', title: item?.title || '', image_url: item.image_url });
172
+ }
173
+ } else if (callResult?.images && Array.isArray(callResult.images)) {
174
+ for (const img of callResult.images) {
175
+ if (img?.image_url) out.push({ type: 'image', title: img?.title || '', image_url: img.image_url });
176
+ }
177
+ } else if (callResult?.image_url) {
178
+ out.push({ type: 'image', title: callResult?.title || '', image_url: callResult.image_url });
179
+ }
180
+ }
181
+ if (t?.name === 'generate_discovery' && callResult?.status === 'success') {
182
+ out.push({ type: 'discovery', title: callResult?.title || t?.params?.title || 'Discovery' });
183
+ }
184
+ if (t?.name === 'generate_document' && callResult?.status === 'success') {
185
+ out.push({ type: 'document', title: callResult?.title || t?.params?.title || 'Document' });
186
+ }
187
+ if (t?.name === 'generate_ppt' && callResult?.status === 'success') {
188
+ out.push({ type: 'ppt', title: callResult?.title || t?.params?.title || 'PPT' });
189
+ }
190
+ if (t?.name === 'generate_html' && callResult?.status === 'success') {
191
+ out.push({ type: 'html', title: callResult?.title || t?.params?.title || 'HTML' });
192
+ }
193
+ if (t?.name === 'search_x' && callResult?.tweets && Array.isArray(callResult.tweets)) {
194
+ out.push({ type: 'search_x', status: callResult.status, tweets: callResult.tweets });
195
+ }
196
+ }
197
+ return out;
198
+ }
199
+
200
+ function dispatch(eventType, dataStr, callbacks) {
201
+ const { onMessage, onToolCall, onToolResult } = callbacks;
202
+ let payload = {};
203
+ if (dataStr) {
204
+ try {
205
+ payload = JSON.parse(dataStr);
206
+ } catch {
207
+ payload = { content: dataStr };
208
+ }
209
+ }
210
+
211
+ switch (eventType) {
212
+ case 'message':
213
+ if (typeof payload.content === 'string') onMessage(payload.content);
214
+ break;
215
+ case 'stream': {
216
+ const content = payload?.content;
217
+ if (typeof content === 'string') {
218
+ try {
219
+ const inner = JSON.parse(content);
220
+ const type = inner?.type;
221
+ const data = inner?.data;
222
+ if (type === 'content' || type === 'text' || type === 'delta' || type === 'answer') {
223
+ const text = data?.content ?? data?.text ?? data?.delta;
224
+ if (typeof text === 'string') onMessage(text);
225
+ } else if (type === 'tools' && onToolCall) {
226
+ const params = extractToolParams(data);
227
+ for (const item of params) onToolCall(item);
228
+ } else if ((type === 'tools_result_stream' || type === 'tools_result') && onToolResult) {
229
+ const results = extractToolResults(data);
230
+ for (const item of results) onToolResult(item);
231
+ } else if (type !== 'processing' && type !== 'tools' && type !== 'message' && data?.message && typeof data.message === 'string') {
232
+ onMessage(data.message);
233
+ }
234
+ } catch {
235
+ onMessage(content);
236
+ }
237
+ }
238
+ break;
239
+ }
240
+ case 'heartbeat':
241
+ case 'connected':
242
+ break;
243
+ default:
244
+ break;
245
+ }
246
+ }
247
+
248
+ async function readSSE(url, apiKey, startOffset, callbacks) {
249
+ const { onMessage, onDone, onEvent, onToolCall, onToolResult } = callbacks;
250
+ const controller = new AbortController();
251
+ let idleTimer = null;
252
+ const resetIdleTimer = () => {
253
+ if (idleTimer) clearTimeout(idleTimer);
254
+ idleTimer = setTimeout(() => controller.abort(), STREAM_IDLE_TIMEOUT_MS);
255
+ };
256
+
257
+ const connectUrl = startOffset >= 0 ? `${url}?offset=${startOffset}` : url;
258
+ let maxOffset = startOffset;
259
+ let streamDone = false;
260
+
261
+ try {
262
+ const res = await fetch(connectUrl, {
263
+ method: 'GET',
264
+ headers: {
265
+ Accept: 'text/event-stream',
266
+ Authorization: `Bearer ${apiKey}`,
267
+ },
268
+ signal: controller.signal,
269
+ });
270
+
271
+ if (!res.ok) {
272
+ const text = await res.text();
273
+ let msg = text;
274
+ try {
275
+ const j = JSON.parse(text);
276
+ msg = getMessage(j) || text;
277
+ } catch {}
278
+ throw new Error(`HTTP ${res.status}: ${msg}`);
279
+ }
280
+
281
+ const reader = res.body?.getReader();
282
+ if (!reader) throw new Error('No response body');
283
+
284
+ const decoder = new TextDecoder();
285
+ let buffer = '';
286
+ let currentEvent = '';
287
+ let currentData = undefined;
288
+
289
+ const processEvent = (evt, data) => {
290
+ if (!evt || data === undefined) return;
291
+ let eventOffset = -1;
292
+ try {
293
+ const parsed = JSON.parse(data);
294
+ if (typeof parsed?.offset === 'number') {
295
+ eventOffset = parsed.offset;
296
+ if (parsed.offset > maxOffset) maxOffset = parsed.offset;
297
+ }
298
+ } catch {}
299
+
300
+ if (eventOffset >= 0 && eventOffset <= startOffset) return;
301
+
302
+ if (onEvent) onEvent(evt, data);
303
+
304
+ if (evt === 'error') return; // server "not ready yet" signal, keep reading
305
+ if (evt === 'done' || evt === 'completed' || evt === 'complete') {
306
+ streamDone = true;
307
+ onDone();
308
+ return;
309
+ }
310
+ dispatch(evt, data, { onMessage, onToolCall, onToolResult });
311
+ };
312
+
313
+ resetIdleTimer();
314
+ while (true) {
315
+ const { done, value } = await reader.read();
316
+ resetIdleTimer();
317
+ if (done) break;
318
+
319
+ buffer += decoder.decode(value, { stream: true });
320
+ const lines = buffer.split(/\r?\n/);
321
+ buffer = lines.pop() ?? '';
322
+
323
+ for (const line of lines) {
324
+ if (line.startsWith('event:')) {
325
+ processEvent(currentEvent, currentData);
326
+ currentEvent = line.slice(6).trim();
327
+ currentData = undefined;
328
+ } else if (line.startsWith('data:')) {
329
+ currentData = line.slice(5).trim();
330
+ } else if (line === '') {
331
+ processEvent(currentEvent, currentData);
332
+ currentEvent = '';
333
+ currentData = undefined;
334
+ }
335
+ }
336
+ }
337
+
338
+ if (idleTimer) clearTimeout(idleTimer);
339
+ processEvent(currentEvent, currentData);
340
+ } catch (err) {
341
+ if (idleTimer) clearTimeout(idleTimer);
342
+ if (err?.name === 'AbortError') {
343
+ return { maxOffset, streamDone, streamError: `Stream idle timeout (no data for ${STREAM_IDLE_TIMEOUT_MS / 1000}s)` };
344
+ }
345
+ throw err;
346
+ }
347
+
348
+ return { maxOffset, streamDone, streamError: null };
349
+ }
350
+
351
+ async function consumeStream(apiKey, apiBase, streamKey, callbacks) {
352
+ const url = `${apiBase}/v2/conversations/stream/${encodeURIComponent(streamKey)}`;
353
+ let lastOffset = -1;
354
+ const startTime = Date.now();
355
+
356
+ while (true) {
357
+ if (Date.now() - startTime > STREAM_IDLE_TIMEOUT_MS) {
358
+ callbacks.onError('Stream timeout: no completion after ' + (STREAM_IDLE_TIMEOUT_MS / 1000) + 's');
359
+ return;
360
+ }
361
+
362
+ const result = await readSSE(url, apiKey, lastOffset, callbacks);
363
+
364
+ if (result.streamDone) return;
365
+ if (result.maxOffset > lastOffset) lastOffset = result.maxOffset;
366
+
367
+ await sleep(RECONNECT_DELAY_MS);
368
+ }
369
+ }
370
+
371
+ // ── Format helpers ──
372
+
373
+ function formatTweet(tweet) {
374
+ const info = tweet?.other_info || {};
375
+ const author = info.author || {};
376
+ const metrics = info.metrics || {};
377
+ const name = author.display_name || author.username || 'Unknown';
378
+ const handle = author.username ? `@${author.username}` : '';
379
+ const text = tweet?.snippet || tweet?.title || '';
380
+ const link = tweet?.link || info.url || '';
381
+ const stats = [];
382
+ if (metrics.favorite_count) stats.push(`${metrics.favorite_count} likes`);
383
+ if (metrics.retweet_count) stats.push(`${metrics.retweet_count} retweets`);
384
+ if (metrics.view_count) stats.push(`${metrics.view_count} views`);
385
+ const statsStr = stats.length > 0 ? ` [${stats.join(' | ')}]` : '';
386
+ return ` ${name} (${handle})${statsStr}\n ${text}\n ${link}`;
387
+ }
388
+
389
+ // ── Main ──
390
+
391
+ async function main() {
392
+ const args = parseArgs(process.argv.slice(2));
393
+ if (args.help) {
394
+ usage();
395
+ process.exit(0);
396
+ }
397
+ if (!args.query) {
398
+ usage();
399
+ process.exit(1);
400
+ }
401
+
402
+ const apiKey = process.env.FELO_API_KEY?.trim();
403
+ if (!apiKey) {
404
+ console.error(
405
+ 'ERROR: FELO_API_KEY not set\n\n' +
406
+ 'To use SuperAgent, set FELO_API_KEY:\n' +
407
+ ' export FELO_API_KEY="your-api-key-here"\n' +
408
+ 'Get your API key from https://felo.ai (Settings -> API Keys).'
409
+ );
410
+ process.exit(1);
411
+ }
412
+
413
+ const apiBase = (process.env.FELO_API_BASE?.trim() || DEFAULT_API_BASE).replace(/\/$/, '');
414
+ const timeoutMs = args.timeoutSec * 1000;
415
+
416
+ // Build request body
417
+ const body = { query: args.query.slice(0, 2000) };
418
+ if (args.liveDocId) body.live_doc_short_id = args.liveDocId;
419
+ if (args.acceptLanguage) body.accept_language = args.acceptLanguage;
420
+
421
+ const threadId = args.threadId || undefined;
422
+
423
+ // skill_id, selected_resource_ids, ext only for new conversations
424
+ if (threadId && (args.skillId || args.selectedResourceIds.length || args.ext)) {
425
+ process.stderr.write('Warning: --skill-id, --selected-resource-ids, --ext are ignored in follow-up mode.\n');
426
+ }
427
+ if (!threadId) {
428
+ if (args.skillId) body.skill_id = args.skillId;
429
+ if (args.selectedResourceIds.length) body.selected_resource_ids = args.selectedResourceIds;
430
+ if (args.ext) body.ext = args.ext;
431
+ }
432
+
433
+ process.stderr.write(threadId ? 'SuperAgent: following up...\n' : 'SuperAgent: creating conversation...\n');
434
+
435
+ const createData = await createConversation(apiKey, apiBase, body, timeoutMs, threadId);
436
+ const { stream_key, thread_short_id, live_doc_short_id } = createData;
437
+
438
+ if (args.verbose) {
439
+ process.stderr.write(`Stream key: ${stream_key}\n`);
440
+ process.stderr.write(`Thread ID: ${thread_short_id}\n`);
441
+ process.stderr.write(`LiveDoc ID: ${live_doc_short_id}\n`);
442
+ }
443
+
444
+ const feloBase = (process.env.FELO_WEB_BASE?.trim() || apiBase.replace(/\/\/openapi-/, '//').replace(/\/\/openapi\./, '//')).replace(/\/$/, '');
445
+ const liveDocUrl = live_doc_short_id ? `${feloBase}/zh-Hans/livedoc/${live_doc_short_id}` : '';
446
+
447
+ const chunks = [];
448
+ const toolResults = [];
449
+ const seenKeys = new Set();
450
+ const isJson = args.json;
451
+
452
+ const onToolCall = (item) => {
453
+ if (isJson) return;
454
+ const { name, params } = item;
455
+ console.log(`\n[Tool: ${name}]`);
456
+ if (name === 'search_x') {
457
+ console.log(` Query: ${params.query || ''}`);
458
+ if (params.query_type) console.log(` Type: ${params.query_type}`);
459
+ if (params.limit) console.log(` Limit: ${params.limit}`);
460
+ } else if (name === 'generate_images') {
461
+ const images = params?.images;
462
+ if (Array.isArray(images)) {
463
+ for (const img of images) console.log(` Image: ${img.title || '(untitled)'}`);
464
+ }
465
+ } else if (name === 'generate_discovery') {
466
+ console.log(` Title: ${params.title || params.query || ''}`);
467
+ } else if (name === 'generate_document') {
468
+ console.log(` Title: ${params.title || ''}`);
469
+ } else if (name === 'generate_ppt') {
470
+ console.log(` Title: ${params.title || ''}`);
471
+ } else if (name === 'generate_html') {
472
+ console.log(` Title: ${params.title || ''}`);
473
+ } else {
474
+ console.log(` Params: ${JSON.stringify(params)}`);
475
+ }
476
+ };
477
+
478
+ const onToolResult = (item) => {
479
+ const LIVEDOC_TYPES = new Set(['document', 'ppt', 'html', 'discovery']);
480
+ const key = item?.image_url || (LIVEDOC_TYPES.has(item?.type) ? item.type : `${item?.type}:${item?.title}`);
481
+ if (seenKeys.has(key)) return;
482
+ seenKeys.add(key);
483
+ toolResults.push(item);
484
+
485
+ if (isJson) return;
486
+ if (item.type === 'image') {
487
+ console.log(liveDocUrl ? `[${item.title || 'Image'}](${liveDocUrl})` : item.image_url);
488
+ } else if (item.type === 'discovery') {
489
+ console.log(liveDocUrl ? `[${item.title}](${liveDocUrl})` : item.title);
490
+ } else if (item.type === 'document') {
491
+ console.log(liveDocUrl ? `[${item.title || 'Document'}](${liveDocUrl})` : (item.title || 'Document'));
492
+ } else if (item.type === 'ppt') {
493
+ console.log(liveDocUrl ? `[${item.title || 'PPT'}](${liveDocUrl})` : (item.title || 'PPT'));
494
+ } else if (item.type === 'html') {
495
+ console.log(liveDocUrl ? `[${item.title || 'HTML'}](${liveDocUrl})` : (item.title || 'HTML'));
496
+ } else if (item.type === 'search_x') {
497
+ console.log(`\n[Twitter Search Results] (${item.tweets.length} tweets)`);
498
+ for (const tweet of item.tweets) {
499
+ console.log(formatTweet(tweet));
500
+ console.log('');
501
+ }
502
+ }
503
+ };
504
+
505
+ let streamError = null;
506
+ const onEvent = args.verbose
507
+ ? (eventType, dataStr) => {
508
+ process.stderr.write(`[stream] event=${eventType}\n`);
509
+ process.stderr.write(`[stream] data=${dataStr || ''}\n`);
510
+ }
511
+ : undefined;
512
+
513
+ await consumeStream(apiKey, apiBase, stream_key, {
514
+ onMessage: (content) => {
515
+ chunks.push(content);
516
+ if (!isJson) process.stdout.write(content);
517
+ },
518
+ onError: (err) => {
519
+ streamError = err;
520
+ },
521
+ onDone: () => {},
522
+ onEvent,
523
+ onToolCall,
524
+ onToolResult,
525
+ });
526
+
527
+ if (streamError) throw new Error(streamError);
528
+
529
+ const answer = chunks.join('').trim();
530
+
531
+ if (isJson) {
532
+ const images = toolResults.filter((r) => r.type === 'image');
533
+ const discoveries = toolResults.filter((r) => r.type === 'discovery');
534
+ const documents = toolResults.filter((r) => r.type === 'document');
535
+ const ppts = toolResults.filter((r) => r.type === 'ppt');
536
+ const htmls = toolResults.filter((r) => r.type === 'html');
537
+ const searches = toolResults.filter((r) => r.type === 'search_x');
538
+ console.log(
539
+ JSON.stringify(
540
+ {
541
+ status: 'ok',
542
+ data: {
543
+ answer: answer || null,
544
+ thread_short_id: thread_short_id ?? null,
545
+ live_doc_short_id: live_doc_short_id ?? null,
546
+ image_urls: images.length > 0 ? images.map((r) => ({ url: r.image_url, title: r.title })) : undefined,
547
+ discoveries: discoveries.length > 0 ? discoveries.map((r) => ({ title: r.title })) : undefined,
548
+ documents: documents.length > 0 ? documents.map((r) => ({ title: r.title })) : undefined,
549
+ ppts: ppts.length > 0 ? ppts.map((r) => ({ title: r.title })) : undefined,
550
+ htmls: htmls.length > 0 ? htmls.map((r) => ({ title: r.title })) : undefined,
551
+ search_x: searches.length > 0 ? searches.map((r) => ({ tweets: r.tweets })) : undefined,
552
+ live_doc_url: liveDocUrl || undefined,
553
+ },
554
+ },
555
+ null,
556
+ 2
557
+ )
558
+ );
559
+ } else {
560
+ if (answer) console.log('');
561
+ if (!answer && toolResults.length === 0) console.log('(No content in stream)');
562
+ process.stderr.write(`\n[state] thread_short_id=${thread_short_id || ''} live_doc_short_id=${live_doc_short_id || ''} live_doc_url=${liveDocUrl || ''}\n`);
563
+ }
564
+ }
565
+
566
+ main().catch((err) => {
567
+ console.error(`ERROR: ${err?.message || err}`);
568
+ process.exit(1);
569
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "felo-ai",
3
- "version": "0.2.18",
3
+ "version": "0.2.23",
4
4
  "description": "Felo AI CLI - real-time search, PPT generation, SuperAgent conversation, LiveDoc management, web fetch, YouTube subtitles, LiveDoc knowledge base, and X (Twitter) search from the terminal",
5
5
  "type": "module",
6
6
  "main": "src/cli.js",
@@ -32,8 +32,7 @@
32
32
  "url": "git+https://github.com/Felo-Inc/felo-skills.git"
33
33
  },
34
34
  "dependencies": {
35
- "commander": "^12.0.0",
36
- "felo-search": "^0.1.1"
35
+ "commander": "^12.0.0"
37
36
  },
38
37
  "scripts": {
39
38
  "test": "node --test tests/",