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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "felo-ai",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Felo-Inc",
|
|
5
|
+
"url": "https://github.com/Felo-Inc"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "Felo AI skills for Claude Code — real-time search, PPT generation, SuperAgent, LiveDoc knowledge base, web fetch, YouTube subtitles, and X (Twitter) search",
|
|
9
|
+
"version": "1.0.0"
|
|
10
|
+
},
|
|
11
|
+
"plugins": [
|
|
12
|
+
{
|
|
13
|
+
"name": "felo-search",
|
|
14
|
+
"description": "Real-time web search powered by Felo AI",
|
|
15
|
+
"source": "./",
|
|
16
|
+
"strict": false,
|
|
17
|
+
"skills": [
|
|
18
|
+
"./felo-search"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "felo-livedoc",
|
|
23
|
+
"description": "Manage knowledge bases (LiveDocs) and semantic retrieval via Felo API",
|
|
24
|
+
"source": "./",
|
|
25
|
+
"strict": false,
|
|
26
|
+
"skills": [
|
|
27
|
+
"./felo-livedoc"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "felo-slides",
|
|
32
|
+
"description": "Generate PPT presentations from a prompt using Felo AI",
|
|
33
|
+
"source": "./",
|
|
34
|
+
"strict": false,
|
|
35
|
+
"skills": [
|
|
36
|
+
"./felo-slides"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "felo-superAgent",
|
|
41
|
+
"description": "SuperAgent conversation with SSE streaming and LiveDoc integration",
|
|
42
|
+
"source": "./",
|
|
43
|
+
"strict": false,
|
|
44
|
+
"skills": [
|
|
45
|
+
"./felo-superAgent"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "felo-web-fetch",
|
|
50
|
+
"description": "Fetch and extract webpage content in markdown, text, or HTML format",
|
|
51
|
+
"source": "./",
|
|
52
|
+
"strict": false,
|
|
53
|
+
"skills": [
|
|
54
|
+
"./felo-web-fetch"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "felo-youtube-subtitling",
|
|
59
|
+
"description": "Fetch YouTube video subtitles and captions by URL or video ID",
|
|
60
|
+
"source": "./",
|
|
61
|
+
"strict": false,
|
|
62
|
+
"skills": [
|
|
63
|
+
"./felo-youtube-subtitling"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "felo-x-search",
|
|
68
|
+
"description": "Search X (Twitter) tweets, users, and replies via Felo API",
|
|
69
|
+
"source": "./",
|
|
70
|
+
"strict": false,
|
|
71
|
+
"skills": [
|
|
72
|
+
"./felo-x-search"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "felo-content-to-slides",
|
|
77
|
+
"description": "Fetch content from a webpage or YouTube video and generate a PPT",
|
|
78
|
+
"source": "./",
|
|
79
|
+
"strict": false,
|
|
80
|
+
"skills": [
|
|
81
|
+
"./felo-content-to-slides"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.29] - 2026-03-18
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fix spinner animation displaying repeated lines in non-TTY environments (e.g. Claude Code TUI, pipes, CI) by checking `process.stderr.isTTY` before writing `\r` control characters; spinner is silently skipped when stderr is not a TTY
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
8
16
|
## [0.2.25] - 2026-03-17
|
|
9
17
|
|
|
10
18
|
### Added
|
|
@@ -56,6 +56,7 @@ felo content-to-slides -v "https://www.youtube.com/watch?v=ID" [options]
|
|
|
56
56
|
| `-l, --language <code>` | For --video: subtitle language (e.g. en, zh-CN) |
|
|
57
57
|
| `-t, --timeout <seconds>` | Fetch timeout (default 60) |
|
|
58
58
|
| `--poll-timeout <seconds>` | Max seconds to wait for PPT task (default 1200) |
|
|
59
|
+
| `--theme <id>` | PPT theme ID (list themes with `felo ppt-themes`) |
|
|
59
60
|
| `-j, --json` | Output JSON with task_id and ppt/live_doc URL |
|
|
60
61
|
| `--verbose` | Show polling status |
|
|
61
62
|
|
|
@@ -67,6 +68,9 @@ Provide **either** `--url` or `--video`, not both.
|
|
|
67
68
|
# Web page → PPT (with readability)
|
|
68
69
|
node src/cli.js content-to-slides --url "https://openclaw.ai/" --readability
|
|
69
70
|
|
|
71
|
+
# Web page → PPT with a specific theme
|
|
72
|
+
node src/cli.js content-to-slides --url "https://openclaw.ai/" --readability --theme "THEME_ID"
|
|
73
|
+
|
|
70
74
|
# YouTube → PPT, with extra instruction
|
|
71
75
|
node src/cli.js content-to-slides -v "https://www.youtube.com/watch?v=xxx" --extra-prompt "max 10 slides"
|
|
72
76
|
|
package/felo-livedoc/README.md
CHANGED
|
@@ -11,6 +11,8 @@ Manage knowledge bases (LiveDocs) and their resources via the Felo API.
|
|
|
11
11
|
- Semantic retrieval across knowledge base resources
|
|
12
12
|
- Route relevant resources by query for targeted retrieval
|
|
13
13
|
- Full CRUD for resources within a LiveDoc
|
|
14
|
+
- Manage README content for each LiveDoc
|
|
15
|
+
- Task management: create, update, delete tasks with comments and change history
|
|
14
16
|
|
|
15
17
|
**When to use:**
|
|
16
18
|
- Building or managing a knowledge base
|
|
@@ -87,8 +89,19 @@ felo livedoc route SHORT_ID --query "latest AI research" --max-resources 5
|
|
|
87
89
|
| `add-urls <short_id>` | Add URL resources (max 10) |
|
|
88
90
|
| `upload <short_id>` | Upload a file resource |
|
|
89
91
|
| `remove-resource <short_id> <resource_id>` | Delete a resource |
|
|
92
|
+
| `update-resource <short_id> <resource_id>` | Update resource title, snippet, or thumbnail |
|
|
90
93
|
| `retrieve <short_id>` | Semantic retrieval (auto-routes if no `--resource-ids`) |
|
|
91
94
|
| `route <short_id>` | Route relevant resource IDs by query |
|
|
95
|
+
| `get-readme <short_id>` | Get README content |
|
|
96
|
+
| `update-readme <short_id>` | Create or replace README |
|
|
97
|
+
| `append-readme <short_id>` | Append content to README |
|
|
98
|
+
| `delete-readme <short_id>` | Delete README |
|
|
99
|
+
| `tasks <short_id>` | List tasks (filter by `--status`, `--labels`) |
|
|
100
|
+
| `create-task <short_id>` | Create a task |
|
|
101
|
+
| `update-task <short_id> <task_id>` | Partially update a task |
|
|
102
|
+
| `delete-task <short_id> <task_id>` | Delete a task |
|
|
103
|
+
| `task-records <short_id> <task_id>` | List task records (comments + change history) |
|
|
104
|
+
| `add-task-comment <short_id> <task_id>` | Add a comment to a task |
|
|
92
105
|
|
|
93
106
|
---
|
|
94
107
|
|
package/felo-livedoc/SKILL.md
CHANGED
|
@@ -14,10 +14,12 @@ Trigger this skill when users want to:
|
|
|
14
14
|
- **Semantic retrieval:** Search across knowledge base resources using natural language queries
|
|
15
15
|
- **Route resources:** Find relevant resource IDs by query for targeted retrieval
|
|
16
16
|
- **Resource management:** List, view, or delete resources within a LiveDoc
|
|
17
|
+
- **README management:** Get, create, update, append, or delete a LiveDoc's README
|
|
18
|
+
- **Task management:** Create, update, delete tasks; add comments; view change history
|
|
17
19
|
|
|
18
20
|
**Trigger words:**
|
|
19
|
-
- English: knowledge base, livedoc, live doc, upload document, add URL, semantic search, retrieve, knowledge retrieval, route resources
|
|
20
|
-
- 简体中文: 知识库, 文档库, 上传文档, 添加链接, 语义检索,
|
|
21
|
+
- English: knowledge base, livedoc, live doc, upload document, add URL, semantic search, retrieve, knowledge retrieval, route resources, readme, task, task management, comment
|
|
22
|
+
- 简体中文: 知识库, 文档库, 上传文档, 添加链接, 语义检索, 知识检索, 任务, 任务管理, 评论
|
|
21
23
|
|
|
22
24
|
**Explicit commands:** `/felo-livedoc`, "livedoc", "felo livedoc"
|
|
23
25
|
|
|
@@ -111,6 +113,12 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs resource SHORT_ID RES
|
|
|
111
113
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs remove-resource SHORT_ID RESOURCE_ID
|
|
112
114
|
```
|
|
113
115
|
|
|
116
|
+
**Update a resource (title/snippet/thumbnail):**
|
|
117
|
+
```bash
|
|
118
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-resource SHORT_ID RESOURCE_ID --title "New Title"
|
|
119
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-resource SHORT_ID RESOURCE_ID --snippet "New summary" --thumbnail "https://example.com/thumb.png"
|
|
120
|
+
```
|
|
121
|
+
|
|
114
122
|
### Semantic Retrieval
|
|
115
123
|
|
|
116
124
|
**Route relevant resources by query:**
|
|
@@ -147,6 +155,67 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs content SHORT_ID RESO
|
|
|
147
155
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs ppt-retrieve SHORT_ID --resource-id RESOURCE_ID --page-number 3 --query "pricing information"
|
|
148
156
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs ppt-retrieve SHORT_ID --resource-id RESOURCE_ID --page-number 3 --query "pricing information" --max-chunk 5
|
|
149
157
|
```
|
|
158
|
+
|
|
159
|
+
### README Management
|
|
160
|
+
|
|
161
|
+
**Get README:**
|
|
162
|
+
```bash
|
|
163
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs get-readme SHORT_ID
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Create or replace README:**
|
|
167
|
+
```bash
|
|
168
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-readme SHORT_ID --content "# My KB\n\nThis is the README."
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Append to README:**
|
|
172
|
+
```bash
|
|
173
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs append-readme SHORT_ID --content "\n\n## New Section\n\nAdditional content."
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Delete README:**
|
|
177
|
+
```bash
|
|
178
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs delete-readme SHORT_ID
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Task Management
|
|
182
|
+
|
|
183
|
+
**List tasks (with optional filters):**
|
|
184
|
+
```bash
|
|
185
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs tasks SHORT_ID
|
|
186
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs tasks SHORT_ID --status 0
|
|
187
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs tasks SHORT_ID --labels "docs,priority-high"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Create a task:**
|
|
191
|
+
```bash
|
|
192
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs create-task SHORT_ID --title "Write docs" --status 0 --sort 0
|
|
193
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs create-task SHORT_ID --title "Write docs" --status 0 --sort 0 --description "API docs" --labels "docs"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Task status values: `0`=TODO, `1`=IN_PROGRESS, `2`=DONE
|
|
197
|
+
|
|
198
|
+
**Update a task (partial update):**
|
|
199
|
+
```bash
|
|
200
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-task SHORT_ID TASK_ID --status 1
|
|
201
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-task SHORT_ID TASK_ID --title "New title" --labels "docs,done"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Delete a task:**
|
|
205
|
+
```bash
|
|
206
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs delete-task SHORT_ID TASK_ID
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**List task records (comments + change history):**
|
|
210
|
+
```bash
|
|
211
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs task-records SHORT_ID TASK_ID
|
|
212
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs task-records SHORT_ID TASK_ID --record-type comment
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Add a comment to a task:**
|
|
216
|
+
```bash
|
|
217
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs add-task-comment SHORT_ID TASK_ID --content "This is a comment."
|
|
218
|
+
```
|
|
150
219
|
### Options
|
|
151
220
|
|
|
152
221
|
All commands support:
|
|
@@ -194,6 +263,16 @@ The API returns JSON with this structure:
|
|
|
194
263
|
- `LIVEDOC_RESOURCE_UPLOAD_FAILED` — File upload failed
|
|
195
264
|
- `LIVEDOC_RESOURCE_ADD_URLS_FAILED` — URL addition failed
|
|
196
265
|
- `LIVEDOC_RESOURCE_RETRIEVE_FAILED` — Semantic retrieval failed
|
|
266
|
+
- `LIVEDOC_README_GET_FAILED` — Failed to get README
|
|
267
|
+
- `LIVEDOC_README_UPDATE_FAILED` — Failed to create or update README
|
|
268
|
+
- `LIVEDOC_README_DELETE_FAILED` — Failed to delete README
|
|
269
|
+
- `LIVEDOC_TASK_LIST_FAILED` — Failed to list tasks
|
|
270
|
+
- `LIVEDOC_TASK_CREATE_FAILED` — Failed to create task
|
|
271
|
+
- `LIVEDOC_TASK_UPDATE_FAILED` — Failed to update task
|
|
272
|
+
- `LIVEDOC_TASK_DELETE_FAILED` — Failed to delete task
|
|
273
|
+
- `LIVEDOC_TASK_NOT_FOUND` — Task does not exist
|
|
274
|
+
- `LIVEDOC_TASK_RECORD_LIST_FAILED` — Failed to list task records
|
|
275
|
+
- `LIVEDOC_TASK_COMMENT_CREATE_FAILED` — Failed to add comment
|
|
197
276
|
|
|
198
277
|
### Missing API Key
|
|
199
278
|
|
|
@@ -12,6 +12,7 @@ const SPINNER_INTERVAL_MS = 80;
|
|
|
12
12
|
const STATUS_PAD = 56;
|
|
13
13
|
|
|
14
14
|
function startSpinner(message) {
|
|
15
|
+
if (!process.stderr.isTTY) return null;
|
|
15
16
|
const start = Date.now();
|
|
16
17
|
let i = 0;
|
|
17
18
|
const id = setInterval(() => {
|
|
@@ -25,7 +26,7 @@ function startSpinner(message) {
|
|
|
25
26
|
|
|
26
27
|
function stopSpinner(id) {
|
|
27
28
|
if (id != null) clearInterval(id);
|
|
28
|
-
process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
29
|
+
if (process.stderr.isTTY) process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
@@ -126,6 +127,28 @@ function formatRetrieveResult(r) {
|
|
|
126
127
|
return out;
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
function formatTask(t) {
|
|
131
|
+
if (!t) return '';
|
|
132
|
+
let out = `### ${t.title || '(untitled)'}\n`;
|
|
133
|
+
out += `- Task ID: \`${t.id}\`\n`;
|
|
134
|
+
out += `- Status: ${t.status === 0 ? 'TODO' : t.status === 1 ? 'IN_PROGRESS' : t.status === 2 ? 'DONE' : t.status}\n`;
|
|
135
|
+
if (t.sort != null) out += `- Sort: ${t.sort}\n`;
|
|
136
|
+
if (t.description) out += `- Description: ${t.description}\n`;
|
|
137
|
+
if (t.labels?.length) out += `- Labels: ${t.labels.join(', ')}\n`;
|
|
138
|
+
if (t.created_at) out += `- Created: ${t.created_at}\n`;
|
|
139
|
+
out += '\n';
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatTaskRecord(r) {
|
|
144
|
+
if (!r) return '';
|
|
145
|
+
let out = `- [${r.record_type}] `;
|
|
146
|
+
if (r.content) out += r.content;
|
|
147
|
+
else if (r.meta) out += JSON.stringify(r.meta);
|
|
148
|
+
out += ` (id: ${r.id}, ${r.created_at || ''})\n`;
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
129
152
|
// ── CLI ──
|
|
130
153
|
|
|
131
154
|
function usage() {
|
|
@@ -143,11 +166,22 @@ function usage() {
|
|
|
143
166
|
' add-urls <short_id> Add URLs (--urls required, comma-separated, max 10)',
|
|
144
167
|
' upload <short_id> Upload file (--file required, --convert optional)',
|
|
145
168
|
' remove-resource <short_id> <resource_id> Delete a resource',
|
|
169
|
+
' update-resource <short_id> <resource_id> Update resource title/snippet/thumbnail',
|
|
146
170
|
' retrieve <short_id> Semantic search (--query required, --resource-ids optional)',
|
|
147
171
|
' route <short_id> Route relevant resources by query (--query required)',
|
|
148
172
|
' download <short_id> <resource_id> Download source file to disk',
|
|
149
173
|
' content <short_id> <resource_id> Get text content of a resource',
|
|
150
174
|
' ppt-retrieve <short_id> PPT page deep retrieval (--resource-id, --page-number, --query required)',
|
|
175
|
+
' get-readme <short_id> Get README content',
|
|
176
|
+
' update-readme <short_id> Create or replace README (--content required)',
|
|
177
|
+
' append-readme <short_id> Append to README (--content required)',
|
|
178
|
+
' delete-readme <short_id> Delete README',
|
|
179
|
+
' tasks <short_id> List tasks (--status, --labels optional)',
|
|
180
|
+
' create-task <short_id> Create a task (--title, --status, --sort required)',
|
|
181
|
+
' update-task <short_id> <task_id> Partially update a task',
|
|
182
|
+
' delete-task <short_id> <task_id> Delete a task',
|
|
183
|
+
' task-records <short_id> <task_id> List task records (comments + history)',
|
|
184
|
+
' add-task-comment <short_id> <task_id> Add a comment (--content required)',
|
|
151
185
|
'',
|
|
152
186
|
'Options:',
|
|
153
187
|
' --name <name> LiveDoc name',
|
|
@@ -157,8 +191,8 @@ function usage() {
|
|
|
157
191
|
' --page <n> Page number',
|
|
158
192
|
' --size <n> Page size',
|
|
159
193
|
' --type <type> Resource type filter',
|
|
160
|
-
' --content <text> Document content',
|
|
161
|
-
' --title <title> Document title',
|
|
194
|
+
' --content <text> Document/README/comment content',
|
|
195
|
+
' --title <title> Document/task title',
|
|
162
196
|
' --urls <urls> Comma-separated URLs',
|
|
163
197
|
' --file <path> File path to upload',
|
|
164
198
|
' --convert Convert uploaded file to document',
|
|
@@ -170,6 +204,10 @@ function usage() {
|
|
|
170
204
|
' --max-chunk <n> Max chunks to return (ppt-retrieve, default 3)',
|
|
171
205
|
' --expires-in <s> Presigned URL expiry in seconds (download, default 3600)',
|
|
172
206
|
' --output <path> Output file path (download, default: filename from response)',
|
|
207
|
+
' --status <n> Task status: 0=TODO, 1=IN_PROGRESS, 2=DONE',
|
|
208
|
+
' --sort <n> Task sort order (non-negative integer)',
|
|
209
|
+
' --labels <labels> Comma-separated labels (tasks)',
|
|
210
|
+
' --record-type <type> Record type filter: comment, edit, status_change',
|
|
173
211
|
' -j, --json Output raw JSON',
|
|
174
212
|
' -t, --timeout <ms> Timeout in ms (default: 60000)',
|
|
175
213
|
' --help Show this help',
|
|
@@ -180,7 +218,8 @@ function parseArgs(argv) {
|
|
|
180
218
|
action: '', positional: [], name: '', description: '', icon: '',
|
|
181
219
|
keyword: '', page: '', size: '', type: '', content: '', title: '',
|
|
182
220
|
urls: '', file: '', convert: false, query: '', resourceIds: '', maxResources: '',
|
|
183
|
-
resourceId: '', pageNumber: '', maxChunk: '', expiresIn: '',
|
|
221
|
+
resourceId: '', pageNumber: '', maxChunk: '', expiresIn: '', output: '',
|
|
222
|
+
status: '', sort: '', labels: '', recordType: '',
|
|
184
223
|
json: false, timeoutMs: DEFAULT_TIMEOUT_MS, help: false,
|
|
185
224
|
};
|
|
186
225
|
const positional = [];
|
|
@@ -207,6 +246,11 @@ function parseArgs(argv) {
|
|
|
207
246
|
else if (a === '--page-number') out.pageNumber = argv[++i] || '';
|
|
208
247
|
else if (a === '--max-chunk') out.maxChunk = argv[++i] || '';
|
|
209
248
|
else if (a === '--expires-in') out.expiresIn = argv[++i] || '';
|
|
249
|
+
else if (a === '--output') out.output = argv[++i] || '';
|
|
250
|
+
else if (a === '--status') out.status = argv[++i] || '';
|
|
251
|
+
else if (a === '--sort') out.sort = argv[++i] || '';
|
|
252
|
+
else if (a === '--labels') out.labels = argv[++i] || '';
|
|
253
|
+
else if (a === '--record-type') out.recordType = argv[++i] || '';
|
|
210
254
|
else if (a === '-t' || a === '--timeout') {
|
|
211
255
|
const n = parseInt(argv[++i] || '', 10);
|
|
212
256
|
if (Number.isFinite(n) && n > 0) out.timeoutMs = n;
|
|
@@ -373,6 +417,19 @@ async function main() {
|
|
|
373
417
|
code = 0;
|
|
374
418
|
break;
|
|
375
419
|
}
|
|
420
|
+
case 'update-resource': {
|
|
421
|
+
if (!shortId || !resourceId) { console.error('ERROR: short_id and resource_id are required'); break; }
|
|
422
|
+
spinnerId = startSpinner('Updating resource');
|
|
423
|
+
const body = {};
|
|
424
|
+
if (args.title !== undefined) body.title = args.title;
|
|
425
|
+
if (args.snippet !== undefined) body.snippet = args.snippet;
|
|
426
|
+
if (args.thumbnail !== undefined) body.thumbnail = args.thumbnail;
|
|
427
|
+
const payload = await apiRequest('PUT', `/livedocs/${shortId}/resources/${resourceId}`, body, apiKey, apiBase, timeoutMs);
|
|
428
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
429
|
+
else { process.stdout.write('Resource updated!\n\n'); process.stdout.write(formatResource(payload?.data)); }
|
|
430
|
+
code = 0;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
376
433
|
case 'retrieve': {
|
|
377
434
|
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
378
435
|
if (!args.query) { console.error('ERROR: --query is required'); break; }
|
|
@@ -489,6 +546,140 @@ async function main() {
|
|
|
489
546
|
code = 0;
|
|
490
547
|
break;
|
|
491
548
|
}
|
|
549
|
+
case 'get-readme': {
|
|
550
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
551
|
+
spinnerId = startSpinner('Fetching README');
|
|
552
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/readme`, null, apiKey, apiBase, timeoutMs);
|
|
553
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
554
|
+
else { process.stdout.write(payload?.data?.content || '(empty)\n'); }
|
|
555
|
+
code = 0;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case 'update-readme': {
|
|
559
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
560
|
+
if (args.content === undefined || args.content === '') { console.error('ERROR: --content is required'); break; }
|
|
561
|
+
spinnerId = startSpinner('Updating README');
|
|
562
|
+
await apiRequest('PUT', `/livedocs/${shortId}/readme`, { content: args.content }, apiKey, apiBase, timeoutMs);
|
|
563
|
+
if (json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); }
|
|
564
|
+
else { process.stdout.write('README updated.\n'); }
|
|
565
|
+
code = 0;
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case 'append-readme': {
|
|
569
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
570
|
+
if (!args.content) { console.error('ERROR: --content is required'); break; }
|
|
571
|
+
spinnerId = startSpinner('Appending to README');
|
|
572
|
+
await apiRequest('POST', `/livedocs/${shortId}/readme/append`, { content: args.content }, apiKey, apiBase, timeoutMs);
|
|
573
|
+
if (json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); }
|
|
574
|
+
else { process.stdout.write('README appended.\n'); }
|
|
575
|
+
code = 0;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
case 'delete-readme': {
|
|
579
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
580
|
+
spinnerId = startSpinner('Deleting README');
|
|
581
|
+
await apiRequest('DELETE', `/livedocs/${shortId}/readme`, null, apiKey, apiBase, timeoutMs);
|
|
582
|
+
if (json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); }
|
|
583
|
+
else { process.stdout.write('README deleted.\n'); }
|
|
584
|
+
code = 0;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case 'tasks': {
|
|
588
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
589
|
+
spinnerId = startSpinner('Listing tasks');
|
|
590
|
+
const params = new URLSearchParams();
|
|
591
|
+
if (args.status !== '') params.set('status', args.status);
|
|
592
|
+
if (args.labels) args.labels.split(',').map(l => l.trim()).filter(Boolean).forEach(l => params.append('labels', l));
|
|
593
|
+
if (args.page) params.set('page', args.page);
|
|
594
|
+
if (args.size) params.set('size', args.size);
|
|
595
|
+
const qs = params.toString();
|
|
596
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/tasks${qs ? `?${qs}` : ''}`, null, apiKey, apiBase, timeoutMs);
|
|
597
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
598
|
+
else {
|
|
599
|
+
const items = payload?.data?.items || [];
|
|
600
|
+
if (!items.length) { process.stderr.write('No tasks found.\n'); }
|
|
601
|
+
else {
|
|
602
|
+
process.stdout.write(`Found ${payload.data.total || items.length} task(s)\n\n`);
|
|
603
|
+
for (const t of items) process.stdout.write(formatTask(t));
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
code = 0;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
case 'create-task': {
|
|
610
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
611
|
+
if (!args.title) { console.error('ERROR: --title is required'); break; }
|
|
612
|
+
if (args.status === '') { console.error('ERROR: --status is required'); break; }
|
|
613
|
+
if (args.sort === '') { console.error('ERROR: --sort is required'); break; }
|
|
614
|
+
spinnerId = startSpinner('Creating task');
|
|
615
|
+
const body = { title: args.title, status: parseInt(args.status, 10), sort: parseInt(args.sort, 10) };
|
|
616
|
+
if (args.description) body.description = args.description;
|
|
617
|
+
if (args.labels) body.labels = args.labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
618
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks`, body, apiKey, apiBase, timeoutMs);
|
|
619
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
620
|
+
else { process.stdout.write('Task created!\n\n'); process.stdout.write(formatTask(payload?.data)); }
|
|
621
|
+
code = 0;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case 'update-task': {
|
|
625
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
626
|
+
if (!resourceId) { console.error('ERROR: task_id is required'); break; }
|
|
627
|
+
spinnerId = startSpinner('Updating task');
|
|
628
|
+
const body = {};
|
|
629
|
+
if (args.title) body.title = args.title;
|
|
630
|
+
if (args.description !== undefined) body.description = args.description;
|
|
631
|
+
if (args.status !== '') body.status = parseInt(args.status, 10);
|
|
632
|
+
if (args.sort !== '') body.sort = parseInt(args.sort, 10);
|
|
633
|
+
if (args.labels !== undefined) body.labels = args.labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
634
|
+
const payload = await apiRequest('PATCH', `/livedocs/${shortId}/tasks/${resourceId}`, body, apiKey, apiBase, timeoutMs);
|
|
635
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
636
|
+
else { process.stdout.write('Task updated!\n\n'); process.stdout.write(formatTask(payload?.data)); }
|
|
637
|
+
code = 0;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
case 'delete-task': {
|
|
641
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
642
|
+
if (!resourceId) { console.error('ERROR: task_id is required'); break; }
|
|
643
|
+
spinnerId = startSpinner('Deleting task');
|
|
644
|
+
await apiRequest('DELETE', `/livedocs/${shortId}/tasks/${resourceId}`, null, apiKey, apiBase, timeoutMs);
|
|
645
|
+
if (json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); }
|
|
646
|
+
else { process.stdout.write(`Task \`${resourceId}\` deleted.\n`); }
|
|
647
|
+
code = 0;
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
case 'task-records': {
|
|
651
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
652
|
+
if (!resourceId) { console.error('ERROR: task_id is required'); break; }
|
|
653
|
+
spinnerId = startSpinner('Fetching task records');
|
|
654
|
+
const params = new URLSearchParams();
|
|
655
|
+
if (args.recordType) params.set('record_type', args.recordType);
|
|
656
|
+
if (args.page) params.set('page', args.page);
|
|
657
|
+
if (args.size) params.set('size', args.size);
|
|
658
|
+
const qs = params.toString();
|
|
659
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/tasks/${resourceId}/records${qs ? `?${qs}` : ''}`, null, apiKey, apiBase, timeoutMs);
|
|
660
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
661
|
+
else {
|
|
662
|
+
const items = payload?.data?.items || [];
|
|
663
|
+
if (!items.length) { process.stderr.write('No records found.\n'); }
|
|
664
|
+
else {
|
|
665
|
+
process.stdout.write(`Found ${payload.data.total || items.length} record(s)\n\n`);
|
|
666
|
+
for (const r of items) process.stdout.write(formatTaskRecord(r));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
code = 0;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
case 'add-task-comment': {
|
|
673
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
674
|
+
if (!resourceId) { console.error('ERROR: task_id is required'); break; }
|
|
675
|
+
if (!args.content) { console.error('ERROR: --content is required'); break; }
|
|
676
|
+
spinnerId = startSpinner('Adding comment');
|
|
677
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks/${resourceId}/comments`, { content: args.content }, apiKey, apiBase, timeoutMs);
|
|
678
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
679
|
+
else { process.stdout.write('Comment added.\n'); process.stdout.write(formatTaskRecord(payload?.data)); }
|
|
680
|
+
code = 0;
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
492
683
|
default:
|
|
493
684
|
console.error(`Unknown action: ${action}`);
|
|
494
685
|
usage();
|
package/felo-slides/SKILL.md
CHANGED
|
@@ -75,9 +75,22 @@ node felo-slides/scripts/run_ppt_task.mjs \
|
|
|
75
75
|
--timeout 60
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
To apply a specific theme, first list available themes with `felo ppt-themes`, then pass the theme ID:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
node felo-slides/scripts/run_ppt_task.mjs \
|
|
82
|
+
--query "USER_PROMPT_HERE" \
|
|
83
|
+
--theme "THEME_ID_HERE" \
|
|
84
|
+
--interval 10 \
|
|
85
|
+
--max-wait 1800 \
|
|
86
|
+
--timeout 60
|
|
87
|
+
```
|
|
88
|
+
|
|
78
89
|
Script behavior:
|
|
79
90
|
|
|
80
91
|
- Creates task via `POST https://openapi.felo.ai/v2/ppts`
|
|
92
|
+
- Supports optional `--theme <id>` to apply a PPT theme (sends `ppt_config.ai_theme_id`)
|
|
93
|
+
- Supports optional `--task-id <id>` to resume polling an existing task (skips creation)
|
|
81
94
|
- Polls via `GET https://openapi.felo.ai/v2/tasks/{task_id}/historical`
|
|
82
95
|
- Treats `COMPLETED`/`SUCCESS` as success terminal (case-insensitive)
|
|
83
96
|
- Treats `FAILED`/`ERROR` as failure terminal
|
|
@@ -152,6 +165,14 @@ Timeout handling:
|
|
|
152
165
|
|
|
153
166
|
- If timeout reached, return last known status and instruct user to retry later
|
|
154
167
|
- Include `task_id` so user can query again
|
|
168
|
+
- **IMPORTANT**: To resume a timed-out task, use `--task-id` instead of `--query` to avoid creating a duplicate PPT:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
node felo-slides/scripts/run_ppt_task.mjs \
|
|
172
|
+
--task-id "TASK_ID_HERE" \
|
|
173
|
+
--interval 10 \
|
|
174
|
+
--max-wait 1800
|
|
175
|
+
```
|
|
155
176
|
|
|
156
177
|
## Important Notes
|
|
157
178
|
|
|
@@ -12,7 +12,9 @@ function usage() {
|
|
|
12
12
|
' node felo-slides/scripts/run_ppt_task.mjs --query "your prompt" [options]',
|
|
13
13
|
'',
|
|
14
14
|
'Options:',
|
|
15
|
-
' --query <text> PPT prompt (required)',
|
|
15
|
+
' --query <text> PPT prompt (required unless --task-id is given)',
|
|
16
|
+
' --task-id <id> Resume polling an existing task (skip creation)',
|
|
17
|
+
' --theme <id> PPT theme ID (from ppt-themes)',
|
|
16
18
|
' --interval <seconds> Poll interval, default 10',
|
|
17
19
|
' --max-wait <seconds> Max wait time, default 1800',
|
|
18
20
|
' --timeout <seconds> Request timeout, default 60',
|
|
@@ -26,6 +28,8 @@ function usage() {
|
|
|
26
28
|
function parseArgs(argv) {
|
|
27
29
|
const out = {
|
|
28
30
|
query: '',
|
|
31
|
+
taskId: '',
|
|
32
|
+
theme: '',
|
|
29
33
|
intervalSec: DEFAULT_INTERVAL_SEC,
|
|
30
34
|
maxWaitSec: DEFAULT_MAX_WAIT_SEC,
|
|
31
35
|
timeoutSec: DEFAULT_TIMEOUT_SEC,
|
|
@@ -44,6 +48,12 @@ function parseArgs(argv) {
|
|
|
44
48
|
} else if (a === '--query') {
|
|
45
49
|
out.query = argv[i + 1] ?? '';
|
|
46
50
|
i += 1;
|
|
51
|
+
} else if (a === '--task-id') {
|
|
52
|
+
out.taskId = argv[i + 1] ?? '';
|
|
53
|
+
i += 1;
|
|
54
|
+
} else if (a === '--theme') {
|
|
55
|
+
out.theme = argv[i + 1] ?? '';
|
|
56
|
+
i += 1;
|
|
47
57
|
} else if (a === '--interval') {
|
|
48
58
|
out.intervalSec = Number.parseInt(argv[i + 1] ?? '', 10);
|
|
49
59
|
i += 1;
|
|
@@ -128,7 +138,11 @@ function extractTaskUrls(historicalData, createData) {
|
|
|
128
138
|
};
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
async function createTask(apiKey, apiBase, query, timeoutMs) {
|
|
141
|
+
async function createTask(apiKey, apiBase, query, timeoutMs, theme) {
|
|
142
|
+
const reqBody = { query };
|
|
143
|
+
if (theme) {
|
|
144
|
+
reqBody.ppt_config = { ai_theme_id: theme };
|
|
145
|
+
}
|
|
132
146
|
const payload = await fetchJson(
|
|
133
147
|
`${apiBase}/v2/ppts`,
|
|
134
148
|
{
|
|
@@ -138,7 +152,7 @@ async function createTask(apiKey, apiBase, query, timeoutMs) {
|
|
|
138
152
|
Authorization: `Bearer ${apiKey}`,
|
|
139
153
|
'Content-Type': 'application/json',
|
|
140
154
|
},
|
|
141
|
-
body: JSON.stringify(
|
|
155
|
+
body: JSON.stringify(reqBody),
|
|
142
156
|
},
|
|
143
157
|
timeoutMs
|
|
144
158
|
);
|
|
@@ -170,7 +184,7 @@ async function main() {
|
|
|
170
184
|
usage();
|
|
171
185
|
process.exit(0);
|
|
172
186
|
}
|
|
173
|
-
if (!args.query) {
|
|
187
|
+
if (!args.query && !args.taskId) {
|
|
174
188
|
usage();
|
|
175
189
|
process.exit(1);
|
|
176
190
|
}
|
|
@@ -186,10 +200,22 @@ async function main() {
|
|
|
186
200
|
const intervalMs = args.intervalSec * 1000;
|
|
187
201
|
const maxWaitMs = args.maxWaitSec * 1000;
|
|
188
202
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
let createData = {};
|
|
204
|
+
let taskId;
|
|
205
|
+
|
|
206
|
+
if (args.taskId) {
|
|
207
|
+
// Resume polling an existing task
|
|
208
|
+
taskId = args.taskId;
|
|
209
|
+
if (args.verbose) {
|
|
210
|
+
console.error(`Resuming task: ${taskId}`);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Create a new task
|
|
214
|
+
createData = await createTask(apiKey, apiBase, args.query, timeoutMs, args.theme);
|
|
215
|
+
taskId = createData.task_id;
|
|
216
|
+
if (args.verbose) {
|
|
217
|
+
console.error(`Task ID: ${taskId}`);
|
|
218
|
+
}
|
|
193
219
|
}
|
|
194
220
|
|
|
195
221
|
const startAt = Date.now();
|