felo-ai 0.2.28 → 0.2.31
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/.claude-plugin/marketplace.json +85 -0
- package/CHANGELOG.md +8 -0
- package/felo-content-to-slides/SKILL.md +4 -0
- package/felo-livedoc/README.md +13 -0
- package/felo-livedoc/SKILL.md +81 -2
- package/felo-livedoc/scripts/run_livedoc.mjs +195 -4
- package/felo-slides/SKILL.md +21 -0
- package/felo-slides/scripts/run_ppt_task.mjs +34 -8
- package/felo-web-fetch/scripts/run_web_fetch.mjs +2 -1
- package/felo-x-search/scripts/run_x_search.mjs +2 -1
- package/felo-youtube-subtitling/scripts/run_youtube_subtitling.mjs +2 -1
- package/package.json +1 -1
- package/src/cli.js +255 -3
- package/src/contentToSlides.js +1 -0
- package/src/livedoc.js +289 -1
- package/src/slides.js +109 -12
- package/src/webFetch.js +2 -1
- package/src/xSearch.js +2 -1
- package/src/youtubeSubtitling.js +2 -1
|
@@ -7,6 +7,7 @@ const SPINNER_INTERVAL_MS = 80;
|
|
|
7
7
|
const STATUS_PAD = 56;
|
|
8
8
|
|
|
9
9
|
function startSpinner(message) {
|
|
10
|
+
if (!process.stderr.isTTY) return null;
|
|
10
11
|
const start = Date.now();
|
|
11
12
|
let i = 0;
|
|
12
13
|
const id = setInterval(() => {
|
|
@@ -20,7 +21,7 @@ function startSpinner(message) {
|
|
|
20
21
|
|
|
21
22
|
function stopSpinner(id) {
|
|
22
23
|
if (id != null) clearInterval(id);
|
|
23
|
-
process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
24
|
+
if (process.stderr.isTTY) process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
function usage() {
|
|
@@ -9,6 +9,7 @@ const SPINNER_INTERVAL_MS = 80;
|
|
|
9
9
|
const STATUS_PAD = 56;
|
|
10
10
|
|
|
11
11
|
function startSpinner(message) {
|
|
12
|
+
if (!process.stderr.isTTY) return null;
|
|
12
13
|
const start = Date.now();
|
|
13
14
|
let i = 0;
|
|
14
15
|
const id = setInterval(() => {
|
|
@@ -22,7 +23,7 @@ function startSpinner(message) {
|
|
|
22
23
|
|
|
23
24
|
function stopSpinner(id) {
|
|
24
25
|
if (id != null) clearInterval(id);
|
|
25
|
-
process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
26
|
+
if (process.stderr.isTTY) process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
function sleep(ms) {
|
|
@@ -7,6 +7,7 @@ const SPINNER_INTERVAL_MS = 80;
|
|
|
7
7
|
const STATUS_PAD = 52;
|
|
8
8
|
|
|
9
9
|
function startSpinner(message) {
|
|
10
|
+
if (!process.stderr.isTTY) return null;
|
|
10
11
|
const start = Date.now();
|
|
11
12
|
let i = 0;
|
|
12
13
|
const id = setInterval(() => {
|
|
@@ -20,7 +21,7 @@ function startSpinner(message) {
|
|
|
20
21
|
|
|
21
22
|
function stopSpinner(id) {
|
|
22
23
|
if (id != null) clearInterval(id);
|
|
23
|
-
process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
24
|
+
if (process.stderr.isTTY) process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/** Extract video ID from YouTube URL or return string if plain ID. Returns null if invalid. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "felo-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.31",
|
|
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",
|
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { search } from "./search.js";
|
|
6
|
-
import { slides } from "./slides.js";
|
|
6
|
+
import { slides, listPptThemes } from "./slides.js";
|
|
7
7
|
import { superAgent, listLiveDocs, listLiveDocResources } from "./superAgent.js";
|
|
8
8
|
import { webFetch } from "./webFetch.js";
|
|
9
9
|
import { youtubeSubtitling } from "./youtubeSubtitling.js";
|
|
@@ -71,7 +71,7 @@ program
|
|
|
71
71
|
"Generate PPT/slides from a prompt (async task, outputs live doc URL when done)"
|
|
72
72
|
)
|
|
73
73
|
.argument(
|
|
74
|
-
"
|
|
74
|
+
"[query]",
|
|
75
75
|
'PPT generation prompt (e.g. "Felo, 2 pages" or "Introduction to React")'
|
|
76
76
|
)
|
|
77
77
|
.option("-j, --json", "output raw JSON with task_id and live_doc_url")
|
|
@@ -86,14 +86,49 @@ program
|
|
|
86
86
|
"max seconds to wait for task completion",
|
|
87
87
|
"1200"
|
|
88
88
|
)
|
|
89
|
+
.option("--theme <id>", "PPT theme ID (from ppt-themes command)")
|
|
90
|
+
.option("--task-id <id>", "resume polling an existing task (skip creation)")
|
|
89
91
|
.action(async (query, opts) => {
|
|
92
|
+
if (!query && !opts.taskId) {
|
|
93
|
+
console.error("Error: provide a <query> or --task-id to resume an existing task");
|
|
94
|
+
flushStdioThenExit(1);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
90
97
|
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
91
98
|
const pollTimeoutMs = parseInt(opts.pollTimeout, 10) * 1000 || 1_200_000;
|
|
92
|
-
const
|
|
99
|
+
const pptConfig = opts.theme ? { ai_theme_id: opts.theme } : undefined;
|
|
100
|
+
const code = await slides(query || "", {
|
|
93
101
|
json: opts.json,
|
|
94
102
|
verbose: opts.verbose,
|
|
95
103
|
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
96
104
|
pollTimeoutMs: Number.isNaN(pollTimeoutMs) ? 1_200_000 : pollTimeoutMs,
|
|
105
|
+
pptConfig,
|
|
106
|
+
taskId: opts.taskId,
|
|
107
|
+
});
|
|
108
|
+
process.exitCode = code;
|
|
109
|
+
flushStdioThenExit(code);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.command("ppt-themes")
|
|
114
|
+
.description("List available PPT themes with optional filtering")
|
|
115
|
+
.option("--lang <code>", "language code (e.g. en, zh-Hans)")
|
|
116
|
+
.option("--type <type>", "filter by theme type (e.g. default, custom)")
|
|
117
|
+
.option("-k, --keyword <keyword>", "search keyword for theme titles")
|
|
118
|
+
.option("-p, --page <number>", "page number (starting from 1)", "1")
|
|
119
|
+
.option("-s, --size <number>", "page size (max 100)", "20")
|
|
120
|
+
.option("-j, --json", "output raw JSON")
|
|
121
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
122
|
+
.action(async (opts) => {
|
|
123
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
124
|
+
const code = await listPptThemes({
|
|
125
|
+
lang: opts.lang,
|
|
126
|
+
type: opts.type,
|
|
127
|
+
keyword: opts.keyword,
|
|
128
|
+
page: parseInt(opts.page, 10) || 1,
|
|
129
|
+
size: parseInt(opts.size, 10) || 20,
|
|
130
|
+
json: opts.json,
|
|
131
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
97
132
|
});
|
|
98
133
|
process.exitCode = code;
|
|
99
134
|
flushStdioThenExit(code);
|
|
@@ -341,6 +376,7 @@ program
|
|
|
341
376
|
)
|
|
342
377
|
.option("-j, --json", "Output JSON with task_id and ppt/live_doc URL")
|
|
343
378
|
.option("--verbose", "Show polling status")
|
|
379
|
+
.option("--theme <id>", "PPT theme ID (from ppt-themes command)")
|
|
344
380
|
.action(async (opts) => {
|
|
345
381
|
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
346
382
|
const pollTimeoutMs = parseInt(opts.pollTimeout, 10) * 1000;
|
|
@@ -354,6 +390,7 @@ program
|
|
|
354
390
|
pollTimeoutMs: Number.isNaN(pollTimeoutMs) ? 1_200_000 : pollTimeoutMs,
|
|
355
391
|
json: opts.json,
|
|
356
392
|
verbose: opts.verbose,
|
|
393
|
+
theme: opts.theme,
|
|
357
394
|
});
|
|
358
395
|
process.exitCode = code;
|
|
359
396
|
flushStdioThenExit(code);
|
|
@@ -636,6 +673,27 @@ livedocCmd
|
|
|
636
673
|
flushStdioThenExit(code);
|
|
637
674
|
});
|
|
638
675
|
|
|
676
|
+
livedocCmd
|
|
677
|
+
.command("update-resource <short_id> <resource_id>")
|
|
678
|
+
.description("Update a resource's title, snippet, or thumbnail")
|
|
679
|
+
.option("--title <title>", "new resource title (max 500 characters)")
|
|
680
|
+
.option("--snippet <text>", "new resource summary (max 2000 characters)")
|
|
681
|
+
.option("--thumbnail <url>", "new thumbnail URL (max 2000 characters)")
|
|
682
|
+
.option("-j, --json", "output raw JSON")
|
|
683
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
684
|
+
.action(async (shortId, resourceId, opts) => {
|
|
685
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
686
|
+
const code = await livedoc.updateResource(shortId, resourceId, {
|
|
687
|
+
title: opts.title,
|
|
688
|
+
snippet: opts.snippet,
|
|
689
|
+
thumbnail: opts.thumbnail,
|
|
690
|
+
json: opts.json,
|
|
691
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
692
|
+
});
|
|
693
|
+
process.exitCode = code;
|
|
694
|
+
flushStdioThenExit(code);
|
|
695
|
+
});
|
|
696
|
+
|
|
639
697
|
livedocCmd
|
|
640
698
|
.command("retrieve <short_id>")
|
|
641
699
|
.description("Semantic search across resources")
|
|
@@ -729,6 +787,200 @@ livedocCmd
|
|
|
729
787
|
flushStdioThenExit(code);
|
|
730
788
|
});
|
|
731
789
|
|
|
790
|
+
// ── LiveDoc README ──
|
|
791
|
+
|
|
792
|
+
livedocCmd
|
|
793
|
+
.command("get-readme <short_id>")
|
|
794
|
+
.description("Get the README of a LiveDoc")
|
|
795
|
+
.option("-j, --json", "output raw JSON")
|
|
796
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
797
|
+
.action(async (shortId, opts) => {
|
|
798
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
799
|
+
const code = await livedoc.getReadme(shortId, {
|
|
800
|
+
json: opts.json,
|
|
801
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
802
|
+
});
|
|
803
|
+
process.exitCode = code;
|
|
804
|
+
flushStdioThenExit(code);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
livedocCmd
|
|
808
|
+
.command("update-readme <short_id>")
|
|
809
|
+
.description("Create or replace the README of a LiveDoc")
|
|
810
|
+
.requiredOption("--content <text>", "README content (Markdown)")
|
|
811
|
+
.option("-j, --json", "output raw JSON")
|
|
812
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
813
|
+
.action(async (shortId, opts) => {
|
|
814
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
815
|
+
const code = await livedoc.upsertReadme(shortId, {
|
|
816
|
+
content: opts.content,
|
|
817
|
+
json: opts.json,
|
|
818
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
819
|
+
});
|
|
820
|
+
process.exitCode = code;
|
|
821
|
+
flushStdioThenExit(code);
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
livedocCmd
|
|
825
|
+
.command("append-readme <short_id>")
|
|
826
|
+
.description("Append content to the README of a LiveDoc")
|
|
827
|
+
.requiredOption("--content <text>", "content to append (Markdown)")
|
|
828
|
+
.option("-j, --json", "output raw JSON")
|
|
829
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
830
|
+
.action(async (shortId, opts) => {
|
|
831
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
832
|
+
const code = await livedoc.appendReadme(shortId, {
|
|
833
|
+
content: opts.content,
|
|
834
|
+
json: opts.json,
|
|
835
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
836
|
+
});
|
|
837
|
+
process.exitCode = code;
|
|
838
|
+
flushStdioThenExit(code);
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
livedocCmd
|
|
842
|
+
.command("delete-readme <short_id>")
|
|
843
|
+
.description("Delete the README of a LiveDoc")
|
|
844
|
+
.option("-j, --json", "output raw JSON")
|
|
845
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
846
|
+
.action(async (shortId, opts) => {
|
|
847
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
848
|
+
const code = await livedoc.deleteReadme(shortId, {
|
|
849
|
+
json: opts.json,
|
|
850
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
851
|
+
});
|
|
852
|
+
process.exitCode = code;
|
|
853
|
+
flushStdioThenExit(code);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// ── LiveDoc Tasks ──
|
|
857
|
+
|
|
858
|
+
livedocCmd
|
|
859
|
+
.command("tasks <short_id>")
|
|
860
|
+
.description("List tasks in a LiveDoc")
|
|
861
|
+
.option("--status <n>", "filter by status: 0=TODO, 1=IN_PROGRESS, 2=DONE")
|
|
862
|
+
.option("--labels <labels>", "filter by labels (comma-separated)")
|
|
863
|
+
.option("--page <n>", "page number")
|
|
864
|
+
.option("--size <n>", "page size")
|
|
865
|
+
.option("-j, --json", "output raw JSON")
|
|
866
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
867
|
+
.action(async (shortId, opts) => {
|
|
868
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
869
|
+
const code = await livedoc.listTasks(shortId, {
|
|
870
|
+
status: opts.status,
|
|
871
|
+
labels: opts.labels,
|
|
872
|
+
page: opts.page,
|
|
873
|
+
size: opts.size,
|
|
874
|
+
json: opts.json,
|
|
875
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
876
|
+
});
|
|
877
|
+
process.exitCode = code;
|
|
878
|
+
flushStdioThenExit(code);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
livedocCmd
|
|
882
|
+
.command("create-task <short_id>")
|
|
883
|
+
.description("Create a task in a LiveDoc")
|
|
884
|
+
.requiredOption("--title <title>", "task title")
|
|
885
|
+
.requiredOption("--status <n>", "task status: 0=TODO, 1=IN_PROGRESS, 2=DONE")
|
|
886
|
+
.requiredOption("--sort <n>", "sort order (non-negative integer)")
|
|
887
|
+
.option("--description <desc>", "task description")
|
|
888
|
+
.option("--labels <labels>", "comma-separated labels (max 10)")
|
|
889
|
+
.option("-j, --json", "output raw JSON")
|
|
890
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
891
|
+
.action(async (shortId, opts) => {
|
|
892
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
893
|
+
const code = await livedoc.createTask(shortId, {
|
|
894
|
+
title: opts.title,
|
|
895
|
+
status: opts.status,
|
|
896
|
+
sort: opts.sort,
|
|
897
|
+
description: opts.description,
|
|
898
|
+
labels: opts.labels,
|
|
899
|
+
json: opts.json,
|
|
900
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
901
|
+
});
|
|
902
|
+
process.exitCode = code;
|
|
903
|
+
flushStdioThenExit(code);
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
livedocCmd
|
|
907
|
+
.command("update-task <short_id> <task_id>")
|
|
908
|
+
.description("Partially update a task (only provided fields are changed)")
|
|
909
|
+
.option("--title <title>", "new task title")
|
|
910
|
+
.option("--description <desc>", "new task description")
|
|
911
|
+
.option("--status <n>", "new status: 0=TODO, 1=IN_PROGRESS, 2=DONE")
|
|
912
|
+
.option("--sort <n>", "new sort order")
|
|
913
|
+
.option("--labels <labels>", "new comma-separated labels")
|
|
914
|
+
.option("-j, --json", "output raw JSON")
|
|
915
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
916
|
+
.action(async (shortId, taskId, opts) => {
|
|
917
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
918
|
+
const code = await livedoc.updateTask(shortId, taskId, {
|
|
919
|
+
title: opts.title,
|
|
920
|
+
description: opts.description,
|
|
921
|
+
status: opts.status,
|
|
922
|
+
sort: opts.sort,
|
|
923
|
+
labels: opts.labels,
|
|
924
|
+
json: opts.json,
|
|
925
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
926
|
+
});
|
|
927
|
+
process.exitCode = code;
|
|
928
|
+
flushStdioThenExit(code);
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
livedocCmd
|
|
932
|
+
.command("delete-task <short_id> <task_id>")
|
|
933
|
+
.description("Delete a task")
|
|
934
|
+
.option("-j, --json", "output raw JSON")
|
|
935
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
936
|
+
.action(async (shortId, taskId, opts) => {
|
|
937
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
938
|
+
const code = await livedoc.deleteTask(shortId, taskId, {
|
|
939
|
+
json: opts.json,
|
|
940
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
941
|
+
});
|
|
942
|
+
process.exitCode = code;
|
|
943
|
+
flushStdioThenExit(code);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
livedocCmd
|
|
947
|
+
.command("task-records <short_id> <task_id>")
|
|
948
|
+
.description("List records (comments + change history) for a task")
|
|
949
|
+
.option("--record-type <type>", "filter by type: comment, edit, status_change")
|
|
950
|
+
.option("--page <n>", "page number")
|
|
951
|
+
.option("--size <n>", "page size")
|
|
952
|
+
.option("-j, --json", "output raw JSON")
|
|
953
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
954
|
+
.action(async (shortId, taskId, opts) => {
|
|
955
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
956
|
+
const code = await livedoc.listTaskRecords(shortId, taskId, {
|
|
957
|
+
recordType: opts.recordType,
|
|
958
|
+
page: opts.page,
|
|
959
|
+
size: opts.size,
|
|
960
|
+
json: opts.json,
|
|
961
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
962
|
+
});
|
|
963
|
+
process.exitCode = code;
|
|
964
|
+
flushStdioThenExit(code);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
livedocCmd
|
|
968
|
+
.command("add-task-comment <short_id> <task_id>")
|
|
969
|
+
.description("Add a comment to a task")
|
|
970
|
+
.requiredOption("--content <text>", "comment content")
|
|
971
|
+
.option("-j, --json", "output raw JSON")
|
|
972
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
973
|
+
.action(async (shortId, taskId, opts) => {
|
|
974
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
975
|
+
const code = await livedoc.createTaskComment(shortId, taskId, {
|
|
976
|
+
content: opts.content,
|
|
977
|
+
json: opts.json,
|
|
978
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
979
|
+
});
|
|
980
|
+
process.exitCode = code;
|
|
981
|
+
flushStdioThenExit(code);
|
|
982
|
+
});
|
|
983
|
+
|
|
732
984
|
program
|
|
733
985
|
.command("summarize")
|
|
734
986
|
.description("Summarize text or URL (coming when API is available)")
|