felo-ai 0.2.1 → 0.2.2
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/felo-slides/README.md +4 -4
- package/felo-slides/SKILL.md +8 -6
- package/felo-slides/scripts/run_ppt_task.mjs +20 -13
- package/package.json +1 -1
- package/src/slides.js +20 -7
package/felo-slides/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Generate presentation slides with the Felo PPT Task API (asynchronous workflow).
|
|
|
6
6
|
|
|
7
7
|
- Generate a PPT deck from a single prompt
|
|
8
8
|
- Poll task status automatically until completion/failure/timeout
|
|
9
|
-
- Return `
|
|
9
|
+
- Return `ppt_url` immediately when the task is completed (fallback to `live_doc_url`)
|
|
10
10
|
- Return `task_id` for follow-up tracking
|
|
11
11
|
|
|
12
12
|
## Quick Start
|
|
@@ -54,12 +54,12 @@ Based on Felo v2 PPT Task API:
|
|
|
54
54
|
2. Query status (optional): `GET /v2/tasks/{task_id}/status`
|
|
55
55
|
3. Query historical/result: `GET /v2/tasks/{task_id}/historical`
|
|
56
56
|
|
|
57
|
-
The skill polls every 10 seconds (max wait
|
|
57
|
+
The skill polls every 10 seconds (max wait 1800 seconds). It stops immediately on `COMPLETED`/`SUCCESS` and returns `ppt_url` (fallback `live_doc_url`).
|
|
58
58
|
|
|
59
59
|
Internal script example:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
node felo-slides/scripts/run_ppt_task.mjs --query "Felo product intro, 3 slides" --interval 10 --max-wait
|
|
62
|
+
node felo-slides/scripts/run_ppt_task.mjs --query "Felo product intro, 3 slides" --interval 10 --max-wait 1800
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
## Troubleshooting
|
|
@@ -76,7 +76,7 @@ The key is invalid or revoked. Generate a new key from [felo.ai](https://felo.ai
|
|
|
76
76
|
|
|
77
77
|
The task may still be processing. Retry later with the same context, or run the script with `--verbose`.
|
|
78
78
|
|
|
79
|
-
### Task completed but no `live_doc_url`
|
|
79
|
+
### Task completed but no `ppt_url` / `live_doc_url`
|
|
80
80
|
|
|
81
81
|
Use the returned `task_id` to query historical endpoint again.
|
|
82
82
|
|
package/felo-slides/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: felo-slides
|
|
3
|
-
description: "Generate PPT/slides with Felo PPT Task API in Claude Code. Use when users ask to create/make/generate/export presentations or slide decks, or when explicit commands like /felo-slides are used. Handles API key check, task creation, polling, and final
|
|
3
|
+
description: "Generate PPT/slides with Felo PPT Task API in Claude Code. Use when users ask to create/make/generate/export presentations or slide decks, or when explicit commands like /felo-slides are used. Handles API key check, task creation, polling, and final ppt_url output."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Felo Slides Skill
|
|
@@ -67,7 +67,7 @@ Use the bundled script (no `jq` dependency):
|
|
|
67
67
|
node felo-slides/scripts/run_ppt_task.mjs \
|
|
68
68
|
--query "USER_PROMPT_HERE" \
|
|
69
69
|
--interval 10 \
|
|
70
|
-
--max-wait
|
|
70
|
+
--max-wait 1800 \
|
|
71
71
|
--timeout 60
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -77,7 +77,7 @@ Script behavior:
|
|
|
77
77
|
- Treats `COMPLETED`/`SUCCESS` as success terminal (case-insensitive)
|
|
78
78
|
- Treats `FAILED`/`ERROR` as failure terminal
|
|
79
79
|
- Stops polling immediately on terminal status
|
|
80
|
-
- Prints `
|
|
80
|
+
- Prints `ppt_url` on success (fallback: `live_doc_url`)
|
|
81
81
|
|
|
82
82
|
Optional debug output:
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ Optional debug output:
|
|
|
85
85
|
node felo-slides/scripts/run_ppt_task.mjs \
|
|
86
86
|
--query "USER_PROMPT_HERE" \
|
|
87
87
|
--interval 10 \
|
|
88
|
-
--max-wait
|
|
88
|
+
--max-wait 1800 \
|
|
89
89
|
--json \
|
|
90
90
|
--verbose
|
|
91
91
|
```
|
|
@@ -93,6 +93,7 @@ node felo-slides/scripts/run_ppt_task.mjs \
|
|
|
93
93
|
This outputs structured JSON including:
|
|
94
94
|
- `task_id`
|
|
95
95
|
- `task_status`
|
|
96
|
+
- `ppt_url`
|
|
96
97
|
- `live_doc_url`
|
|
97
98
|
- `livedoc_short_id`
|
|
98
99
|
- `ppt_business_id`
|
|
@@ -100,7 +101,7 @@ This outputs structured JSON including:
|
|
|
100
101
|
### Step 4: Return structured result
|
|
101
102
|
|
|
102
103
|
On success, return:
|
|
103
|
-
- `
|
|
104
|
+
- `ppt_url` immediately (script default output, fallback `live_doc_url`)
|
|
104
105
|
- if `--json` is used, also include `task_id`, terminal status, and optional metadata
|
|
105
106
|
|
|
106
107
|
## Output Format
|
|
@@ -111,7 +112,8 @@ Use this response structure:
|
|
|
111
112
|
## PPT Generation Result
|
|
112
113
|
- Task ID: <task_id>
|
|
113
114
|
- Status: <status>
|
|
114
|
-
-
|
|
115
|
+
- PPT URL: <ppt_url>
|
|
116
|
+
- Live Doc URL: <live_doc_url or N/A>
|
|
115
117
|
|
|
116
118
|
## Notes
|
|
117
119
|
- livedoc_short_id: <value or N/A>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_API_BASE = 'https://openapi.felo.ai';
|
|
4
4
|
const DEFAULT_INTERVAL_SEC = 10;
|
|
5
|
-
const DEFAULT_MAX_WAIT_SEC =
|
|
5
|
+
const DEFAULT_MAX_WAIT_SEC = 1800;
|
|
6
6
|
const DEFAULT_TIMEOUT_SEC = 60;
|
|
7
7
|
|
|
8
8
|
function usage() {
|
|
@@ -14,7 +14,7 @@ function usage() {
|
|
|
14
14
|
'Options:',
|
|
15
15
|
' --query <text> PPT prompt (required)',
|
|
16
16
|
' --interval <seconds> Poll interval, default 10',
|
|
17
|
-
' --max-wait <seconds> Max wait time, default
|
|
17
|
+
' --max-wait <seconds> Max wait time, default 1800',
|
|
18
18
|
' --timeout <seconds> Request timeout, default 60',
|
|
19
19
|
' --json Print JSON output',
|
|
20
20
|
' --verbose Print polling status to stderr',
|
|
@@ -114,12 +114,18 @@ async function fetchJson(url, init, timeoutMs) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
function extractTaskUrls(historicalData, createData) {
|
|
118
|
+
const pptUrl = historicalData?.ppt_url || '';
|
|
119
|
+
const liveDocUrl =
|
|
120
|
+
historicalData?.live_doc_url ||
|
|
121
|
+
(historicalData?.live_doc_short_id || historicalData?.livedoc_short_id || createData?.livedoc_short_id
|
|
122
|
+
? `https://felo.ai/livedoc/${historicalData?.live_doc_short_id || historicalData?.livedoc_short_id || createData?.livedoc_short_id}`
|
|
123
|
+
: '');
|
|
124
|
+
return {
|
|
125
|
+
pptUrl,
|
|
126
|
+
liveDocUrl,
|
|
127
|
+
displayUrl: pptUrl || liveDocUrl,
|
|
128
|
+
};
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
async function createTask(apiKey, apiBase, query, timeoutMs) {
|
|
@@ -192,7 +198,7 @@ async function main() {
|
|
|
192
198
|
while (Date.now() - startAt <= maxWaitMs) {
|
|
193
199
|
const historicalData = await queryHistorical(apiKey, apiBase, taskId, timeoutMs);
|
|
194
200
|
const taskStatus = normalizeStatus(historicalData.task_status || historicalData.status);
|
|
195
|
-
const
|
|
201
|
+
const urls = extractTaskUrls(historicalData, createData);
|
|
196
202
|
lastStatus = taskStatus || 'UNKNOWN';
|
|
197
203
|
|
|
198
204
|
if (args.verbose) {
|
|
@@ -201,8 +207,8 @@ async function main() {
|
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
if (taskStatus === 'COMPLETED' || taskStatus === 'SUCCESS') {
|
|
204
|
-
if (!
|
|
205
|
-
throw new Error('Task completed but live_doc_url is
|
|
210
|
+
if (!urls.displayUrl) {
|
|
211
|
+
throw new Error('Task completed but no ppt_url/live_doc_url is available');
|
|
206
212
|
}
|
|
207
213
|
if (args.json) {
|
|
208
214
|
console.log(
|
|
@@ -212,7 +218,8 @@ async function main() {
|
|
|
212
218
|
data: {
|
|
213
219
|
task_id: taskId,
|
|
214
220
|
task_status: taskStatus,
|
|
215
|
-
|
|
221
|
+
ppt_url: urls.pptUrl || null,
|
|
222
|
+
live_doc_url: urls.liveDocUrl || null,
|
|
216
223
|
livedoc_short_id: createData.livedoc_short_id ?? historicalData.live_doc_short_id ?? historicalData.livedoc_short_id ?? null,
|
|
217
224
|
ppt_business_id: createData.ppt_business_id ?? null,
|
|
218
225
|
},
|
|
@@ -222,7 +229,7 @@ async function main() {
|
|
|
222
229
|
)
|
|
223
230
|
);
|
|
224
231
|
} else {
|
|
225
|
-
console.log(
|
|
232
|
+
console.log(urls.displayUrl);
|
|
226
233
|
}
|
|
227
234
|
return;
|
|
228
235
|
}
|
package/package.json
CHANGED
package/src/slides.js
CHANGED
|
@@ -80,7 +80,7 @@ async function createPptTask(apiKey, query, timeoutMs, apiBase) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Get task historical info. Returns { task_status, live_doc_url?, live_doc_short_id? } or throws.
|
|
83
|
+
* Get task historical info. Returns { task_status, ppt_url?, ppt_biz_id?, live_doc_url?, live_doc_short_id? } or throws.
|
|
84
84
|
* Uses fetchWithTimeoutAndRetry for 5xx retry (per PPT Task API error codes).
|
|
85
85
|
*/
|
|
86
86
|
async function getTaskHistorical(apiKey, taskId, timeoutMs, apiBase) {
|
|
@@ -116,6 +116,8 @@ async function getTaskHistorical(apiKey, taskId, timeoutMs, apiBase) {
|
|
|
116
116
|
|
|
117
117
|
return {
|
|
118
118
|
task_status: payload.task_status ?? payload.status,
|
|
119
|
+
ppt_url: payload.ppt_url,
|
|
120
|
+
ppt_biz_id: payload.ppt_biz_id,
|
|
119
121
|
live_doc_url: payload.live_doc_url,
|
|
120
122
|
live_doc_short_id: payload.live_doc_short_id ?? payload.livedoc_short_id,
|
|
121
123
|
};
|
|
@@ -166,19 +168,25 @@ export async function slides(query, options = {}) {
|
|
|
166
168
|
const normalizedStatus = normalizeTaskStatus(historical.task_status);
|
|
167
169
|
lastStatus = normalizedStatus || historical.task_status;
|
|
168
170
|
|
|
171
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
169
172
|
if (useLiveStatus) {
|
|
170
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
171
173
|
spinIndex = (spinIndex + 1) % SPINNER_FRAMES.length;
|
|
172
174
|
writeStatusLine(SPINNER_FRAMES[spinIndex], elapsed);
|
|
175
|
+
} else if (!options.verbose && !options.json) {
|
|
176
|
+
process.stderr.write(` Generating... ${elapsed}s\n`);
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
const done = normalizedStatus === 'COMPLETED' || normalizedStatus === 'SUCCESS';
|
|
176
180
|
if (done) {
|
|
177
181
|
if (useLiveStatus) clearStatusLine();
|
|
178
|
-
const
|
|
182
|
+
const pptUrl =
|
|
183
|
+
historical.ppt_url ||
|
|
184
|
+
(historical.ppt_biz_id ? `https://dev.felo.ai/slides/${historical.ppt_biz_id}` : null);
|
|
185
|
+
const liveDocUrl =
|
|
179
186
|
historical.live_doc_url ||
|
|
180
187
|
(historical.live_doc_short_id ? `https://felo.ai/livedoc/${historical.live_doc_short_id}` : null) ||
|
|
181
188
|
(createResult.livedoc_short_id ? `https://felo.ai/livedoc/${createResult.livedoc_short_id}` : null);
|
|
189
|
+
const url = pptUrl || liveDocUrl;
|
|
182
190
|
if (options.json) {
|
|
183
191
|
console.log(
|
|
184
192
|
JSON.stringify(
|
|
@@ -187,8 +195,10 @@ export async function slides(query, options = {}) {
|
|
|
187
195
|
data: {
|
|
188
196
|
task_id: taskId,
|
|
189
197
|
task_status: normalizedStatus || historical.task_status,
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
ppt_url: pptUrl,
|
|
199
|
+
ppt_biz_id: historical.ppt_biz_id ?? createResult.ppt_business_id,
|
|
200
|
+
live_doc_url: liveDocUrl,
|
|
201
|
+
livedoc_short_id: historical.live_doc_short_id ?? createResult.livedoc_short_id,
|
|
192
202
|
ppt_business_id: createResult.ppt_business_id,
|
|
193
203
|
},
|
|
194
204
|
},
|
|
@@ -198,10 +208,13 @@ export async function slides(query, options = {}) {
|
|
|
198
208
|
);
|
|
199
209
|
} else {
|
|
200
210
|
if (url) {
|
|
201
|
-
|
|
211
|
+
if (pptUrl && !options.json) {
|
|
212
|
+
process.stderr.write('PPT ready. Open this link to preview:\n');
|
|
213
|
+
}
|
|
214
|
+
console.log(pptUrl || liveDocUrl);
|
|
202
215
|
} else {
|
|
203
216
|
if (useLiveStatus) clearStatusLine();
|
|
204
|
-
console.error('Error: Completed but no live_doc_url in response');
|
|
217
|
+
console.error('Error: Completed but no ppt_url or live_doc_url in response');
|
|
205
218
|
return 1;
|
|
206
219
|
}
|
|
207
220
|
}
|