felo-ai 0.2.6 → 0.2.9

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 (39) hide show
  1. package/.github/workflows/publish-npm.yml +39 -0
  2. package/CHANGELOG.md +30 -0
  3. package/CONTRIBUTING.md +346 -346
  4. package/README.en.md +129 -129
  5. package/README.md +435 -408
  6. package/docs/EXAMPLES.md +632 -632
  7. package/docs/FAQ.md +479 -479
  8. package/felo-search/LICENSE +21 -21
  9. package/felo-search/README.md +440 -440
  10. package/felo-search/SKILL.md +291 -291
  11. package/felo-slides/LICENSE +21 -21
  12. package/felo-slides/README.md +87 -87
  13. package/felo-slides/SKILL.md +166 -166
  14. package/felo-slides/scripts/run_ppt_task.mjs +251 -251
  15. package/felo-superAgent/LICENSE +21 -0
  16. package/felo-superAgent/README.md +125 -0
  17. package/felo-superAgent/SKILL.md +165 -0
  18. package/felo-web-fetch/README.md +127 -0
  19. package/felo-web-fetch/SKILL.md +204 -0
  20. package/felo-web-fetch/scripts/run_web_fetch.mjs +316 -0
  21. package/felo-x-search/SKILL.md +204 -0
  22. package/felo-x-search/scripts/run_x_search.mjs +385 -0
  23. package/felo-youtube-subtitling/README.md +59 -59
  24. package/felo-youtube-subtitling/SKILL.md +161 -161
  25. package/felo-youtube-subtitling/scripts/run_youtube_subtitling.mjs +239 -239
  26. package/package.json +37 -35
  27. package/src/cli.js +370 -252
  28. package/src/config.js +66 -66
  29. package/src/search.js +142 -142
  30. package/src/slides.js +332 -332
  31. package/src/superAgent.js +609 -0
  32. package/src/{webExtract.js → webFetch.js} +148 -148
  33. package/src/xSearch.js +366 -0
  34. package/src/youtubeSubtitling.js +179 -179
  35. package/tests/config.test.js +78 -78
  36. package/tests/search.test.js +100 -100
  37. package/felo-web-extract/README.md +0 -78
  38. package/felo-web-extract/SKILL.md +0 -200
  39. package/felo-web-extract/scripts/run_web_extract.mjs +0 -232
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const DEFAULT_API_BASE = 'https://openapi.felo.ai';
4
- const DEFAULT_FORMAT = 'markdown';
5
- const DEFAULT_TIMEOUT_MS = 60_000;
6
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
7
- const SPINNER_INTERVAL_MS = 80;
8
- const STATUS_PAD = 56;
9
-
10
- function startSpinner(message) {
11
- const start = Date.now();
12
- let i = 0;
13
- const id = setInterval(() => {
14
- const elapsed = Math.floor((Date.now() - start) / 1000);
15
- const line = `${message} ${SPINNER_FRAMES[i % SPINNER_FRAMES.length]} ${elapsed}s`;
16
- process.stderr.write(`\r${line.padEnd(STATUS_PAD, ' ')}`);
17
- i += 1;
18
- }, SPINNER_INTERVAL_MS);
19
- return id;
20
- }
21
-
22
- function stopSpinner(id) {
23
- if (id != null) clearInterval(id);
24
- process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
25
- }
26
-
27
- function usage() {
28
- console.error(
29
- [
30
- 'Usage:',
31
- ' node felo-web-extract/scripts/run_web_extract.mjs --url <url> [options]',
32
- '',
33
- 'Options:',
34
- ' --url <url> Page URL to extract (required)',
35
- ' --format <format> Output format: html, text, markdown (default: markdown)',
36
- ' --target-selector <s> CSS selector for target element only',
37
- ' --wait-for-selector <s> Wait for selector before extract',
38
- ' --readability Enable readability (main content only)',
39
- ' --crawl-mode <mode> fast or fine (default: fast)',
40
- ' --timeout <ms> Request timeout in ms (default: 60000)',
41
- ' --json Print full API response as JSON',
42
- ' --help Show this help',
43
- ].join('\n')
44
- );
45
- }
46
-
47
- function parseArgs(argv) {
48
- const out = {
49
- url: '',
50
- format: DEFAULT_FORMAT,
51
- targetSelector: '',
52
- waitForSelector: '',
53
- readability: false,
54
- crawlMode: 'fast',
55
- timeoutMs: DEFAULT_TIMEOUT_MS,
56
- json: false,
57
- };
58
-
59
- for (let i = 0; i < argv.length; i += 1) {
60
- const a = argv[i];
61
- if (a === '--help' || a === '-h') {
62
- out.help = true;
63
- } else if (a === '--json') {
64
- out.json = true;
65
- } else if (a === '--readability') {
66
- out.readability = true;
67
- } else if (a === '--url') {
68
- const next = argv[i + 1];
69
- if (next === undefined || next === null || String(next).trim() === '' || String(next).startsWith('-')) {
70
- out.url = '';
71
- } else {
72
- out.url = String(next).trim();
73
- }
74
- i += 1;
75
- } else if (a === '--format') {
76
- const f = (argv[i + 1] ?? '').toLowerCase();
77
- out.format = ['html', 'text', 'markdown'].includes(f) ? f : DEFAULT_FORMAT;
78
- i += 1;
79
- } else if (a === '--target-selector') {
80
- out.targetSelector = (argv[i + 1] ?? '').trim();
81
- i += 1;
82
- } else if (a === '--wait-for-selector') {
83
- out.waitForSelector = (argv[i + 1] ?? '').trim();
84
- i += 1;
85
- } else if (a === '--crawl-mode') {
86
- const m = (argv[i + 1] ?? '').toLowerCase();
87
- out.crawlMode = ['fast', 'fine'].includes(m) ? m : 'fast';
88
- i += 1;
89
- } else if (a === '--timeout') {
90
- const n = Number.parseInt(argv[i + 1] ?? '', 10);
91
- if (Number.isFinite(n) && n > 0) out.timeoutMs = n;
92
- i += 1;
93
- }
94
- }
95
-
96
- return out;
97
- }
98
-
99
- function getMessage(payload) {
100
- return (
101
- payload?.message ||
102
- payload?.error ||
103
- payload?.msg ||
104
- payload?.code ||
105
- 'Unknown error'
106
- );
107
- }
108
-
109
- async function fetchJson(url, init, timeoutMs) {
110
- const controller = new AbortController();
111
- const timer = setTimeout(() => controller.abort(), timeoutMs);
112
- try {
113
- const res = await fetch(url, { ...init, signal: controller.signal });
114
- let body = {};
115
- try {
116
- body = await res.json();
117
- } catch {
118
- body = {};
119
- }
120
-
121
- if (!res.ok) {
122
- throw new Error(`HTTP ${res.status}: ${getMessage(body)}`);
123
- }
124
- const code = body.code;
125
- const hasData = body?.data != null;
126
- const successCodes = [0, 200];
127
- const hasSuccessCode =
128
- successCodes.includes(Number(code)) ||
129
- code === undefined ||
130
- code === null ||
131
- (hasData && res.ok);
132
- if (!hasSuccessCode) {
133
- throw new Error(getMessage(body));
134
- }
135
- return body;
136
- } finally {
137
- clearTimeout(timer);
138
- }
139
- }
140
-
141
- function stringifyContent(content) {
142
- if (content == null) return '';
143
- if (typeof content === 'string') return content;
144
- if (typeof content === 'object') {
145
- if (content.markdown) return content.markdown;
146
- if (content.text) return content.text;
147
- if (content.html) return content.html;
148
- return JSON.stringify(content, null, 2);
149
- }
150
- return String(content);
151
- }
152
-
153
- async function main() {
154
- const args = parseArgs(process.argv.slice(2));
155
- if (args.help) {
156
- usage();
157
- process.exit(0);
158
- }
159
- if (!args.url) {
160
- usage();
161
- process.exit(1);
162
- }
163
-
164
- const apiKey = process.env.FELO_API_KEY?.trim();
165
- if (!apiKey) {
166
- console.error('ERROR: FELO_API_KEY not set');
167
- process.exit(1);
168
- }
169
-
170
- const apiBase = (process.env.FELO_API_BASE?.trim() || DEFAULT_API_BASE).replace(/\/$/, '');
171
-
172
- const shortUrl = args.url.length > 45 ? args.url.slice(0, 42) + '...' : args.url;
173
- const spinnerId = startSpinner(`Fetching ${shortUrl}`);
174
-
175
- try {
176
- const body = {
177
- url: args.url,
178
- output_format: args.format,
179
- crawl_mode: args.crawlMode,
180
- with_readability: args.readability,
181
- timeout: args.timeoutMs,
182
- };
183
- if (args.targetSelector) body.target_selector = args.targetSelector;
184
- if (args.waitForSelector) body.wait_for_selector = args.waitForSelector;
185
-
186
- const payload = await fetchJson(
187
- `${apiBase}/v2/web/extract`,
188
- {
189
- method: 'POST',
190
- headers: {
191
- Accept: 'application/json',
192
- Authorization: `Bearer ${apiKey}`,
193
- 'Content-Type': 'application/json',
194
- },
195
- body: JSON.stringify(body),
196
- },
197
- args.timeoutMs
198
- );
199
-
200
- const data = payload?.data ?? {};
201
- const content = data?.content;
202
-
203
- if (args.json) {
204
- console.log(JSON.stringify(payload, null, 2));
205
- return;
206
- }
207
-
208
- const out = stringifyContent(content);
209
- const isEmpty = out == null || String(out).trim() === '';
210
- if (isEmpty) {
211
- stopSpinner(spinnerId);
212
- process.stderr.write(
213
- `No content extracted from ${args.url}. The page may be empty, blocked, or the selector did not match.\n`
214
- );
215
- process.exit(1);
216
- }
217
- console.log(out);
218
- } finally {
219
- stopSpinner(spinnerId);
220
- }
221
- }
222
-
223
- main().catch((err) => {
224
- let url = '';
225
- const argv = process.argv.slice(2);
226
- const i = argv.findIndex((a) => a === '--url' || a === '-u');
227
- if (i >= 0 && argv[i + 1]) url = argv[i + 1];
228
- process.stderr.write(
229
- `Web extract failed${url ? ` for ${url}` : ''}: ${err?.message || err}\n`
230
- );
231
- process.exit(1);
232
- });