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.
@@ -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.28",
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
- "<query>",
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 code = await slides(query, {
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)")
@@ -77,5 +77,6 @@ export async function contentToSlides(opts) {
77
77
  verbose: opts?.verbose,
78
78
  timeoutMs: fetchTimeoutMs,
79
79
  pollTimeoutMs: opts?.pollTimeoutMs,
80
+ pptConfig: opts?.theme ? { ai_theme_id: opts.theme } : undefined,
80
81
  });
81
82
  }