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.
- package/.github/workflows/publish-npm.yml +39 -0
- package/CHANGELOG.md +30 -0
- package/CONTRIBUTING.md +346 -346
- package/README.en.md +129 -129
- package/README.md +435 -408
- package/docs/EXAMPLES.md +632 -632
- package/docs/FAQ.md +479 -479
- package/felo-search/LICENSE +21 -21
- package/felo-search/README.md +440 -440
- package/felo-search/SKILL.md +291 -291
- package/felo-slides/LICENSE +21 -21
- package/felo-slides/README.md +87 -87
- package/felo-slides/SKILL.md +166 -166
- package/felo-slides/scripts/run_ppt_task.mjs +251 -251
- package/felo-superAgent/LICENSE +21 -0
- package/felo-superAgent/README.md +125 -0
- package/felo-superAgent/SKILL.md +165 -0
- package/felo-web-fetch/README.md +127 -0
- package/felo-web-fetch/SKILL.md +204 -0
- package/felo-web-fetch/scripts/run_web_fetch.mjs +316 -0
- package/felo-x-search/SKILL.md +204 -0
- package/felo-x-search/scripts/run_x_search.mjs +385 -0
- package/felo-youtube-subtitling/README.md +59 -59
- package/felo-youtube-subtitling/SKILL.md +161 -161
- package/felo-youtube-subtitling/scripts/run_youtube_subtitling.mjs +239 -239
- package/package.json +37 -35
- package/src/cli.js +370 -252
- package/src/config.js +66 -66
- package/src/search.js +142 -142
- package/src/slides.js +332 -332
- package/src/superAgent.js +609 -0
- package/src/{webExtract.js → webFetch.js} +148 -148
- package/src/xSearch.js +366 -0
- package/src/youtubeSubtitling.js +179 -179
- package/tests/config.test.js +78 -78
- package/tests/search.test.js +100 -100
- package/felo-web-extract/README.md +0 -78
- package/felo-web-extract/SKILL.md +0 -200
- package/felo-web-extract/scripts/run_web_extract.mjs +0 -232
package/src/cli.js
CHANGED
|
@@ -1,252 +1,370 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { createRequire } from "module";
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import { search } from "./search.js";
|
|
6
|
-
import { slides } from "./slides.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
doExit();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
flushStderr();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
program
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
.option("-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.option(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
)
|
|
109
|
-
.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
)
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
.option(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
.
|
|
246
|
-
.
|
|
247
|
-
.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { search } from "./search.js";
|
|
6
|
+
import { slides } from "./slides.js";
|
|
7
|
+
import { superAgent } from "./superAgent.js";
|
|
8
|
+
import { webFetch } from "./webFetch.js";
|
|
9
|
+
import { youtubeSubtitling } from "./youtubeSubtitling.js";
|
|
10
|
+
import * as xSearch from "./xSearch.js";
|
|
11
|
+
import * as config from "./config.js";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pkg = require("../package.json");
|
|
15
|
+
|
|
16
|
+
/** Delay (ms) before process.exit to let Windows libuv finish handle cleanup. */
|
|
17
|
+
const EXIT_DELAY_MS = 50;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Flush stdout then stderr, then exit after a short delay. Avoids Node.js
|
|
21
|
+
* Windows UV_HANDLE_CLOSING assertion when process.exit() runs while streams
|
|
22
|
+
* or other handles are still closing.
|
|
23
|
+
* @param {number} code - Exit code.
|
|
24
|
+
*/
|
|
25
|
+
function flushStdioThenExit(code) {
|
|
26
|
+
const doExit = () => setTimeout(() => process.exit(code), EXIT_DELAY_MS);
|
|
27
|
+
const flushStderr = () => {
|
|
28
|
+
if (process.stderr?.writable && !process.stderr.destroyed) {
|
|
29
|
+
process.stderr.write("", () => doExit());
|
|
30
|
+
} else {
|
|
31
|
+
doExit();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
if (process.stdout?.writable && !process.stdout.destroyed) {
|
|
35
|
+
process.stdout.write("", () => flushStderr());
|
|
36
|
+
} else {
|
|
37
|
+
flushStderr();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const program = new Command();
|
|
42
|
+
|
|
43
|
+
program
|
|
44
|
+
.name("felo")
|
|
45
|
+
.description("Felo AI CLI - real-time search from the terminal")
|
|
46
|
+
.version(pkg.version);
|
|
47
|
+
|
|
48
|
+
program
|
|
49
|
+
.command("search")
|
|
50
|
+
.description("Search for current information (weather, news, docs, etc.)")
|
|
51
|
+
.argument("<query>", "search query")
|
|
52
|
+
.option("-j, --json", "output raw JSON")
|
|
53
|
+
.option("-v, --verbose", "show query analysis and sources")
|
|
54
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
55
|
+
.action(async (query, opts) => {
|
|
56
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
57
|
+
const code = await search(query, {
|
|
58
|
+
json: opts.json,
|
|
59
|
+
verbose: opts.verbose,
|
|
60
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
61
|
+
});
|
|
62
|
+
process.exitCode = code;
|
|
63
|
+
flushStdioThenExit(code);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("slides")
|
|
68
|
+
.description(
|
|
69
|
+
"Generate PPT/slides from a prompt (async task, outputs live doc URL when done)"
|
|
70
|
+
)
|
|
71
|
+
.argument(
|
|
72
|
+
"<query>",
|
|
73
|
+
'PPT generation prompt (e.g. "Felo, 2 pages" or "Introduction to React")'
|
|
74
|
+
)
|
|
75
|
+
.option("-j, --json", "output raw JSON with task_id and live_doc_url")
|
|
76
|
+
.option("-v, --verbose", "show polling status")
|
|
77
|
+
.option(
|
|
78
|
+
"-t, --timeout <seconds>",
|
|
79
|
+
"request timeout in seconds for each API call",
|
|
80
|
+
"60"
|
|
81
|
+
)
|
|
82
|
+
.option(
|
|
83
|
+
"--poll-timeout <seconds>",
|
|
84
|
+
"max seconds to wait for task completion",
|
|
85
|
+
"1200"
|
|
86
|
+
)
|
|
87
|
+
.action(async (query, opts) => {
|
|
88
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
89
|
+
const pollTimeoutMs = parseInt(opts.pollTimeout, 10) * 1000 || 1_200_000;
|
|
90
|
+
const code = await slides(query, {
|
|
91
|
+
json: opts.json,
|
|
92
|
+
verbose: opts.verbose,
|
|
93
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
94
|
+
pollTimeoutMs: Number.isNaN(pollTimeoutMs) ? 1_200_000 : pollTimeoutMs,
|
|
95
|
+
});
|
|
96
|
+
process.exitCode = code;
|
|
97
|
+
flushStdioThenExit(code);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
program
|
|
101
|
+
.command("superagent")
|
|
102
|
+
.description(
|
|
103
|
+
"SuperAgent conversation with SSE streaming and LiveDoc (create + stream answer)"
|
|
104
|
+
)
|
|
105
|
+
.argument("<query>", "user query (1–2000 chars)")
|
|
106
|
+
.option("-j, --json", "output JSON with answer, thread_short_id, live_doc_short_id")
|
|
107
|
+
.option("-v, --verbose", "log stream key, thread ID, LiveDoc ID to stderr")
|
|
108
|
+
.option("-t, --timeout <seconds>", "request/stream timeout in seconds", "60")
|
|
109
|
+
.option("--live-doc-id <id>", "reuse existing LiveDoc short_id for continuous conversation")
|
|
110
|
+
.option("--thread-id <id>", "existing thread/conversation ID for follow-up questions")
|
|
111
|
+
.option("--accept-language <lang>", "language preference (e.g. zh, en)")
|
|
112
|
+
.action(async (query, opts) => {
|
|
113
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
114
|
+
const code = await superAgent(query, {
|
|
115
|
+
json: opts.json,
|
|
116
|
+
verbose: opts.verbose,
|
|
117
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
118
|
+
liveDocId: opts.liveDocId || undefined,
|
|
119
|
+
threadId: opts.threadId || undefined,
|
|
120
|
+
acceptLanguage: opts.acceptLanguage || undefined,
|
|
121
|
+
});
|
|
122
|
+
process.exitCode = code;
|
|
123
|
+
flushStdioThenExit(code);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const configCmd = program
|
|
127
|
+
.command("config")
|
|
128
|
+
.description(
|
|
129
|
+
"Manage persisted config (e.g. FELO_API_KEY). Stored in ~/.felo/config.json"
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
configCmd
|
|
133
|
+
.command("set <key> <value>")
|
|
134
|
+
.description(
|
|
135
|
+
"Set a config value (e.g. felo config set FELO_API_KEY your-key)"
|
|
136
|
+
)
|
|
137
|
+
.action(async (key, value) => {
|
|
138
|
+
try {
|
|
139
|
+
await config.setConfig(key, value);
|
|
140
|
+
console.log(`Set ${key}`);
|
|
141
|
+
flushStdioThenExit(0);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error("Error:", e.message);
|
|
144
|
+
flushStdioThenExit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
configCmd
|
|
149
|
+
.command("get <key>")
|
|
150
|
+
.description("Get a config value (sensitive keys are masked)")
|
|
151
|
+
.action(async (key) => {
|
|
152
|
+
try {
|
|
153
|
+
const value = await config.getConfigValue(key);
|
|
154
|
+
if (value === undefined || value === null) {
|
|
155
|
+
console.log("(not set)");
|
|
156
|
+
} else {
|
|
157
|
+
console.log(config.maskValueForDisplay(key, value));
|
|
158
|
+
}
|
|
159
|
+
flushStdioThenExit(0);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error("Error:", e.message);
|
|
162
|
+
flushStdioThenExit(1);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
configCmd
|
|
167
|
+
.command("list")
|
|
168
|
+
.description("List all config keys (values are hidden)")
|
|
169
|
+
.action(async () => {
|
|
170
|
+
try {
|
|
171
|
+
const c = await config.listConfig();
|
|
172
|
+
const keys = Object.keys(c);
|
|
173
|
+
if (keys.length === 0) {
|
|
174
|
+
console.log("No config set. Use: felo config set FELO_API_KEY <key>");
|
|
175
|
+
} else {
|
|
176
|
+
keys.forEach((k) => console.log(k));
|
|
177
|
+
}
|
|
178
|
+
flushStdioThenExit(0);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error("Error:", e.message);
|
|
181
|
+
flushStdioThenExit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
configCmd
|
|
186
|
+
.command("unset <key>")
|
|
187
|
+
.description("Remove a config value")
|
|
188
|
+
.action(async (key) => {
|
|
189
|
+
try {
|
|
190
|
+
await config.unsetConfig(key);
|
|
191
|
+
console.log(`Unset ${key}`);
|
|
192
|
+
flushStdioThenExit(0);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error("Error:", e.message);
|
|
195
|
+
flushStdioThenExit(1);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
configCmd
|
|
200
|
+
.command("path")
|
|
201
|
+
.description("Show config file path")
|
|
202
|
+
.action(() => {
|
|
203
|
+
console.log(config.getConfigPath());
|
|
204
|
+
flushStdioThenExit(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
program
|
|
208
|
+
.command("web-fetch")
|
|
209
|
+
.description("Fetch webpage content from a URL (markdown, text, or html)")
|
|
210
|
+
.requiredOption("-u, --url <url>", "page URL to fetch")
|
|
211
|
+
.option(
|
|
212
|
+
"-f, --format <format>",
|
|
213
|
+
"output format: html, text, markdown",
|
|
214
|
+
"markdown"
|
|
215
|
+
)
|
|
216
|
+
.option(
|
|
217
|
+
"--target-selector <selector>",
|
|
218
|
+
"CSS selector for target element only"
|
|
219
|
+
)
|
|
220
|
+
.option(
|
|
221
|
+
"--wait-for-selector <selector>",
|
|
222
|
+
"wait for selector before fetching"
|
|
223
|
+
)
|
|
224
|
+
.option("--readability", "use readability (main content only)")
|
|
225
|
+
.option("--crawl-mode <mode>", "crawl mode: fast or fine", "fast")
|
|
226
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
227
|
+
.option("-j, --json", "output full API response as JSON")
|
|
228
|
+
.action(async (opts) => {
|
|
229
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
230
|
+
const code = await webFetch({
|
|
231
|
+
url: opts.url,
|
|
232
|
+
format: opts.format,
|
|
233
|
+
targetSelector: opts.targetSelector,
|
|
234
|
+
waitForSelector: opts.waitForSelector,
|
|
235
|
+
readability: opts.readability,
|
|
236
|
+
crawlMode: opts.crawlMode,
|
|
237
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
238
|
+
json: opts.json,
|
|
239
|
+
});
|
|
240
|
+
process.exitCode = code;
|
|
241
|
+
flushStdioThenExit(code);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
program
|
|
245
|
+
.command("youtube-subtitling")
|
|
246
|
+
.description("Fetch YouTube video subtitles/captions by video URL or ID")
|
|
247
|
+
.requiredOption("-v, --video-code <url-or-id>", "YouTube video URL or video ID (e.g. https://youtube.com/watch?v=ID)")
|
|
248
|
+
.option("-l, --language <code>", "Subtitle language (e.g. en, zh-CN)")
|
|
249
|
+
.option("--with-time", "Include start/duration per segment")
|
|
250
|
+
.option("-j, --json", "Output full API response as JSON")
|
|
251
|
+
.action(async (opts) => {
|
|
252
|
+
const code = await youtubeSubtitling({
|
|
253
|
+
videoCode: opts.videoCode,
|
|
254
|
+
language: opts.language,
|
|
255
|
+
withTime: opts.withTime,
|
|
256
|
+
json: opts.json,
|
|
257
|
+
});
|
|
258
|
+
process.exitCode = code;
|
|
259
|
+
flushStdioThenExit(code);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ── X Search ──
|
|
263
|
+
program
|
|
264
|
+
.command("x")
|
|
265
|
+
.description("Search X (Twitter) tweets, users, and replies")
|
|
266
|
+
.argument("[query]", "search keyword (default: search tweets)")
|
|
267
|
+
.option("-q, --query <text>", "search keyword (same as positional arg)")
|
|
268
|
+
.option("--id <values>", "tweet IDs or usernames (comma-separated)")
|
|
269
|
+
.option("--user", "switch to user mode")
|
|
270
|
+
.option("--tweets", "get user tweets (with --id --user)")
|
|
271
|
+
.option("-l, --limit <n>", "number of results to return")
|
|
272
|
+
.option("--cursor <cursor>", "pagination cursor")
|
|
273
|
+
.option("--include-replies", "include replies (with --tweets)")
|
|
274
|
+
.option("--query-type <type>", "query type filter (tweet search)")
|
|
275
|
+
.option("--since-time <val>", "start time filter")
|
|
276
|
+
.option("--until-time <val>", "end time filter")
|
|
277
|
+
.option("-j, --json", "output raw JSON")
|
|
278
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "30")
|
|
279
|
+
.action(async (queryArg, opts) => {
|
|
280
|
+
const query = (queryArg || opts.query || "").trim();
|
|
281
|
+
const ids = opts.id
|
|
282
|
+
? opts.id.split(",").map((s) => s.trim()).filter(Boolean)
|
|
283
|
+
: [];
|
|
284
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
285
|
+
const commonOpts = {
|
|
286
|
+
json: opts.json,
|
|
287
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 30000 : timeoutMs,
|
|
288
|
+
};
|
|
289
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : 0;
|
|
290
|
+
let code = 1;
|
|
291
|
+
|
|
292
|
+
if (query) {
|
|
293
|
+
// query mode
|
|
294
|
+
if (opts.user) {
|
|
295
|
+
// search users
|
|
296
|
+
code = await xSearch.userSearch(query, {
|
|
297
|
+
...commonOpts,
|
|
298
|
+
cursor: opts.cursor,
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
// search tweets (default)
|
|
302
|
+
code = await xSearch.tweetSearch(query, {
|
|
303
|
+
...commonOpts,
|
|
304
|
+
queryType: opts.queryType,
|
|
305
|
+
sinceTime: opts.sinceTime,
|
|
306
|
+
untilTime: opts.untilTime,
|
|
307
|
+
limit: limit || undefined,
|
|
308
|
+
cursor: opts.cursor,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
} else if (ids.length) {
|
|
312
|
+
// id mode
|
|
313
|
+
if (opts.user) {
|
|
314
|
+
if (opts.tweets) {
|
|
315
|
+
// get user tweets
|
|
316
|
+
code = await xSearch.userTweets({
|
|
317
|
+
...commonOpts,
|
|
318
|
+
username: ids[0],
|
|
319
|
+
limit: limit || undefined,
|
|
320
|
+
cursor: opts.cursor,
|
|
321
|
+
includeReplies: opts.includeReplies,
|
|
322
|
+
});
|
|
323
|
+
} else {
|
|
324
|
+
// get user info
|
|
325
|
+
code = await xSearch.userInfo(ids, commonOpts);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
// get tweet replies (default for --id)
|
|
329
|
+
code = await xSearch.tweetReplies(ids, {
|
|
330
|
+
...commonOpts,
|
|
331
|
+
cursor: opts.cursor,
|
|
332
|
+
sinceTime: opts.sinceTime ? parseInt(opts.sinceTime, 10) : undefined,
|
|
333
|
+
untilTime: opts.untilTime ? parseInt(opts.untilTime, 10) : undefined,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
process.stderr.write(
|
|
338
|
+
'Usage: felo x <query> or felo x --id <values>\n\n' +
|
|
339
|
+
'Examples:\n' +
|
|
340
|
+
' felo x "AI news" Search tweets\n' +
|
|
341
|
+
' felo x "OpenAI" --user Search users\n' +
|
|
342
|
+
' felo x --id "1234567890" Get tweet replies\n' +
|
|
343
|
+
' felo x --id "elonmusk" --user Get user info\n' +
|
|
344
|
+
' felo x --id "elonmusk" --user --tweets Get user tweets\n'
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
process.exitCode = code;
|
|
349
|
+
flushStdioThenExit(code);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
program
|
|
353
|
+
.command("summarize")
|
|
354
|
+
.description("Summarize text or URL (coming when API is available)")
|
|
355
|
+
.argument("[input]", "text or URL to summarize")
|
|
356
|
+
.action(() => {
|
|
357
|
+
console.error("summarize: not yet implemented. Use felo search for now.");
|
|
358
|
+
flushStdioThenExit(1);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
program
|
|
362
|
+
.command("translate")
|
|
363
|
+
.description("Translate text (coming when API is available)")
|
|
364
|
+
.argument("[text]", "text to translate")
|
|
365
|
+
.action(() => {
|
|
366
|
+
console.error("translate: not yet implemented. Use felo search for now.");
|
|
367
|
+
flushStdioThenExit(1);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
program.parse();
|