felo-ai 0.2.24 → 0.2.25
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/CHANGELOG.md +11 -0
- package/felo-livedoc/SKILL.md +21 -0
- package/felo-livedoc/scripts/run_livedoc.mjs +86 -0
- package/package.json +2 -2
- package/src/cli.js +55 -0
- package/src/livedoc.js +133 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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.24] - 2026-03-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`felo livedoc download <id> <resource_id>`**: download a resource's source file directly to disk; follows the 302 redirect to the S3 presigned URL and streams the file; supports `--output <path>` to specify the destination filename (defaults to the filename from the `Content-Disposition` header)
|
|
13
|
+
- **`felo livedoc content <id> <resource_id>`**: fetch the extracted text content of a resource; supported for document, web, video, ai_doc, ai_ppt, text, voice, and mindmap types
|
|
14
|
+
- **`felo livedoc ppt-retrieve <id>`**: deep content retrieval from a specific PPT slide page; requires `--resource-id`, `--page-number`, and `--query`; supports `--max-chunk` (default 3); output format is identical to `retrieve`
|
|
15
|
+
- **`add-urls` custom title support**: the API now accepts each URL entry as either a plain string or a `{"url": "...", "title": "..."}` object, allowing custom resource titles when adding URLs
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
8
19
|
## [0.2.18] - 2026-03-14
|
|
9
20
|
|
|
10
21
|
### Added
|
package/felo-livedoc/SKILL.md
CHANGED
|
@@ -88,6 +88,8 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs add-doc SHORT_ID --ti
|
|
|
88
88
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs add-urls SHORT_ID --urls "https://example.com,https://example.org"
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
Each URL can also be passed as a `url:title` pair using the `--url-titles` option, or by providing a JSON array. The API accepts both plain strings and `{"url": "...", "title": "..."}` objects — the script passes them through as-is when using `--json` mode. To add URLs with custom titles, use the JSON flag and construct the body manually, or rely on the auto-title from the page.
|
|
92
|
+
|
|
91
93
|
**Upload a file:**
|
|
92
94
|
```bash
|
|
93
95
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs upload SHORT_ID --file ./document.pdf
|
|
@@ -126,6 +128,25 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs retrieve SHORT_ID --q
|
|
|
126
128
|
```bash
|
|
127
129
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs retrieve SHORT_ID --query "your search query" --resource-ids "id1,id2,id3"
|
|
128
130
|
```
|
|
131
|
+
|
|
132
|
+
### Resource File & Content
|
|
133
|
+
|
|
134
|
+
**Download resource source file (get presigned URL):**
|
|
135
|
+
```bash
|
|
136
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs download SHORT_ID RESOURCE_ID
|
|
137
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs download SHORT_ID RESOURCE_ID --expires-in 7200
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Get extracted text content of a resource:**
|
|
141
|
+
```bash
|
|
142
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs content SHORT_ID RESOURCE_ID
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**PPT page deep retrieval:**
|
|
146
|
+
```bash
|
|
147
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs ppt-retrieve SHORT_ID --resource-id RESOURCE_ID --page-number 3 --query "pricing information"
|
|
148
|
+
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
|
+
```
|
|
129
150
|
### Options
|
|
130
151
|
|
|
131
152
|
All commands support:
|
|
@@ -145,6 +145,9 @@ function usage() {
|
|
|
145
145
|
' remove-resource <short_id> <resource_id> Delete a resource',
|
|
146
146
|
' retrieve <short_id> Semantic search (--query required, --resource-ids optional)',
|
|
147
147
|
' route <short_id> Route relevant resources by query (--query required)',
|
|
148
|
+
' download <short_id> <resource_id> Download source file to disk',
|
|
149
|
+
' content <short_id> <resource_id> Get text content of a resource',
|
|
150
|
+
' ppt-retrieve <short_id> PPT page deep retrieval (--resource-id, --page-number, --query required)',
|
|
148
151
|
'',
|
|
149
152
|
'Options:',
|
|
150
153
|
' --name <name> LiveDoc name',
|
|
@@ -162,6 +165,11 @@ function usage() {
|
|
|
162
165
|
' --query <text> Retrieval/route query',
|
|
163
166
|
' --resource-ids <ids> Comma-separated resource IDs to search within (retrieve)',
|
|
164
167
|
' --max-resources <n> Max resources to return (route)',
|
|
168
|
+
' --resource-id <id> PPT resource ID (ppt-retrieve)',
|
|
169
|
+
' --page-number <n> PPT page number, starts from 1 (ppt-retrieve)',
|
|
170
|
+
' --max-chunk <n> Max chunks to return (ppt-retrieve, default 3)',
|
|
171
|
+
' --expires-in <s> Presigned URL expiry in seconds (download, default 3600)',
|
|
172
|
+
' --output <path> Output file path (download, default: filename from response)',
|
|
165
173
|
' -j, --json Output raw JSON',
|
|
166
174
|
' -t, --timeout <ms> Timeout in ms (default: 60000)',
|
|
167
175
|
' --help Show this help',
|
|
@@ -172,6 +180,7 @@ function parseArgs(argv) {
|
|
|
172
180
|
action: '', positional: [], name: '', description: '', icon: '',
|
|
173
181
|
keyword: '', page: '', size: '', type: '', content: '', title: '',
|
|
174
182
|
urls: '', file: '', convert: false, query: '', resourceIds: '', maxResources: '',
|
|
183
|
+
resourceId: '', pageNumber: '', maxChunk: '', expiresIn: '',
|
|
175
184
|
json: false, timeoutMs: DEFAULT_TIMEOUT_MS, help: false,
|
|
176
185
|
};
|
|
177
186
|
const positional = [];
|
|
@@ -194,6 +203,10 @@ function parseArgs(argv) {
|
|
|
194
203
|
else if (a === '--query') out.query = argv[++i] || '';
|
|
195
204
|
else if (a === '--resource-ids') out.resourceIds = argv[++i] || '';
|
|
196
205
|
else if (a === '--max-resources') out.maxResources = argv[++i] || '';
|
|
206
|
+
else if (a === '--resource-id') out.resourceId = argv[++i] || '';
|
|
207
|
+
else if (a === '--page-number') out.pageNumber = argv[++i] || '';
|
|
208
|
+
else if (a === '--max-chunk') out.maxChunk = argv[++i] || '';
|
|
209
|
+
else if (a === '--expires-in') out.expiresIn = argv[++i] || '';
|
|
197
210
|
else if (a === '-t' || a === '--timeout') {
|
|
198
211
|
const n = parseInt(argv[++i] || '', 10);
|
|
199
212
|
if (Number.isFinite(n) && n > 0) out.timeoutMs = n;
|
|
@@ -403,6 +416,79 @@ async function main() {
|
|
|
403
416
|
code = 0;
|
|
404
417
|
break;
|
|
405
418
|
}
|
|
419
|
+
case 'download': {
|
|
420
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
421
|
+
if (!resourceId) { console.error('ERROR: resource_id is required'); break; }
|
|
422
|
+
spinnerId = startSpinner('Downloading resource');
|
|
423
|
+
const dlUrl = `${apiBase}/v2/livedocs/${shortId}/resources/${resourceId}/download${args.expiresIn ? `?expires_in=${args.expiresIn}` : ''}`;
|
|
424
|
+
const dlRes = await fetchWithRetry(dlUrl, { method: 'GET', headers: { Authorization: `Bearer ${apiKey}` }, redirect: 'follow' }, timeoutMs);
|
|
425
|
+
if (!dlRes.ok) {
|
|
426
|
+
let msg = dlRes.statusText;
|
|
427
|
+
try { const d = await dlRes.json(); msg = d?.message || d?.error || msg; } catch { /* ignore */ }
|
|
428
|
+
console.error(`ERROR: ${dlRes.status} ${msg}`);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
let filename = args.output;
|
|
432
|
+
if (!filename) {
|
|
433
|
+
const cd = dlRes.headers.get('content-disposition') || '';
|
|
434
|
+
const match = cd.match(/filename\*?=(?:UTF-8'')?["']?([^"';\r\n]+)/i);
|
|
435
|
+
filename = match ? decodeURIComponent(match[1].trim()) : resourceId;
|
|
436
|
+
}
|
|
437
|
+
const { createWriteStream } = await import('fs');
|
|
438
|
+
const writer = createWriteStream(filename);
|
|
439
|
+
const reader = dlRes.body.getReader();
|
|
440
|
+
await new Promise((resolve, reject) => {
|
|
441
|
+
writer.on('error', reject);
|
|
442
|
+
writer.on('finish', resolve);
|
|
443
|
+
const pump = async () => {
|
|
444
|
+
try {
|
|
445
|
+
while (true) {
|
|
446
|
+
const { done, value } = await reader.read();
|
|
447
|
+
if (done) { writer.end(); break; }
|
|
448
|
+
writer.write(value);
|
|
449
|
+
}
|
|
450
|
+
} catch (err) { reject(err); }
|
|
451
|
+
};
|
|
452
|
+
pump();
|
|
453
|
+
});
|
|
454
|
+
process.stdout.write(`Downloaded: ${filename}\n`);
|
|
455
|
+
code = 0;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
case 'content': {
|
|
459
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
460
|
+
if (!resourceId) { console.error('ERROR: resource_id is required'); break; }
|
|
461
|
+
spinnerId = startSpinner('Fetching resource content');
|
|
462
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/resources/${resourceId}/content`, null, apiKey, apiBase, timeoutMs);
|
|
463
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
464
|
+
else {
|
|
465
|
+
const d = payload?.data;
|
|
466
|
+
process.stdout.write(`## ${d?.title || '(untitled)'}\n- Type: ${d?.type}\n\n${d?.content || '(empty)'}\n`);
|
|
467
|
+
}
|
|
468
|
+
code = 0;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case 'ppt-retrieve': {
|
|
472
|
+
if (!shortId) { console.error('ERROR: short_id is required'); break; }
|
|
473
|
+
if (!args.resourceId) { console.error('ERROR: --resource-id is required'); break; }
|
|
474
|
+
if (!args.pageNumber) { console.error('ERROR: --page-number is required'); break; }
|
|
475
|
+
if (!args.query) { console.error('ERROR: --query is required'); break; }
|
|
476
|
+
spinnerId = startSpinner('Retrieving PPT page content');
|
|
477
|
+
const body = { resource_id: args.resourceId, page_number: parseInt(args.pageNumber, 10), query: args.query };
|
|
478
|
+
if (args.maxChunk) { const n = parseInt(args.maxChunk, 10); if (Number.isFinite(n) && n > 0) body.max_chunk = n; }
|
|
479
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/resources/ppt-retrieve`, body, apiKey, apiBase, timeoutMs);
|
|
480
|
+
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
481
|
+
else {
|
|
482
|
+
const results = payload?.data || [];
|
|
483
|
+
if (!results.length) { process.stderr.write('No results found.\n'); }
|
|
484
|
+
else {
|
|
485
|
+
process.stdout.write(`Found ${results.length} result(s)\n\n`);
|
|
486
|
+
for (const r of results) process.stdout.write(formatRetrieveResult(r));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
code = 0;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
406
492
|
default:
|
|
407
493
|
console.error(`Unknown action: ${action}`);
|
|
408
494
|
usage();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "felo-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
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",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "node --test tests/",
|
|
39
|
-
"publish": "npm publish"
|
|
39
|
+
"publish:local": "npm publish"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/cli.js
CHANGED
|
@@ -674,6 +674,61 @@ livedocCmd
|
|
|
674
674
|
flushStdioThenExit(code);
|
|
675
675
|
});
|
|
676
676
|
|
|
677
|
+
livedocCmd
|
|
678
|
+
.command("download <short_id> <resource_id>")
|
|
679
|
+
.description("Download a resource source file to disk")
|
|
680
|
+
.option("--output <path>", "output file path (default: filename from response)")
|
|
681
|
+
.option("--expires-in <seconds>", "presigned URL expiry in seconds (default 3600)")
|
|
682
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
683
|
+
.action(async (shortId, resourceId, opts) => {
|
|
684
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
685
|
+
const code = await livedoc.downloadResource(shortId, resourceId, {
|
|
686
|
+
output: opts.output,
|
|
687
|
+
expiresIn: opts.expiresIn,
|
|
688
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
689
|
+
});
|
|
690
|
+
process.exitCode = code;
|
|
691
|
+
flushStdioThenExit(code);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
livedocCmd
|
|
695
|
+
.command("content <short_id> <resource_id>")
|
|
696
|
+
.description("Get extracted text content of a resource")
|
|
697
|
+
.option("-j, --json", "output raw JSON")
|
|
698
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
699
|
+
.action(async (shortId, resourceId, opts) => {
|
|
700
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
701
|
+
const code = await livedoc.getResourceContent(shortId, resourceId, {
|
|
702
|
+
json: opts.json,
|
|
703
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
704
|
+
});
|
|
705
|
+
process.exitCode = code;
|
|
706
|
+
flushStdioThenExit(code);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
livedocCmd
|
|
710
|
+
.command("ppt-retrieve <short_id>")
|
|
711
|
+
.description("Deep content retrieval from a specific PPT page")
|
|
712
|
+
.requiredOption("--resource-id <id>", "PPT resource ID")
|
|
713
|
+
.requiredOption("--page-number <n>", "page number (starts from 1)")
|
|
714
|
+
.requiredOption("--query <query>", "retrieval query")
|
|
715
|
+
.option("--max-chunk <n>", "max chunks to return (default 3)")
|
|
716
|
+
.option("-j, --json", "output raw JSON")
|
|
717
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
718
|
+
.action(async (shortId, opts) => {
|
|
719
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
720
|
+
const code = await livedoc.pptRetrieve(shortId, {
|
|
721
|
+
resourceId: opts.resourceId,
|
|
722
|
+
pageNumber: opts.pageNumber,
|
|
723
|
+
query: opts.query,
|
|
724
|
+
maxChunk: opts.maxChunk,
|
|
725
|
+
json: opts.json,
|
|
726
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
727
|
+
});
|
|
728
|
+
process.exitCode = code;
|
|
729
|
+
flushStdioThenExit(code);
|
|
730
|
+
});
|
|
731
|
+
|
|
677
732
|
program
|
|
678
733
|
.command("summarize")
|
|
679
734
|
.description("Summarize text or URL (coming when API is available)")
|
package/src/livedoc.js
CHANGED
|
@@ -432,3 +432,136 @@ export async function retrieve(shortId, opts = {}) {
|
|
|
432
432
|
return 1;
|
|
433
433
|
} finally { stopSpinner(spinnerId); }
|
|
434
434
|
}
|
|
435
|
+
|
|
436
|
+
export async function downloadResource(shortId, resourceId, opts = {}) {
|
|
437
|
+
const apiKey = await getApiKey();
|
|
438
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
439
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
440
|
+
if (!resourceId) { process.stderr.write('ERROR: resource_id is required.\n'); return 1; }
|
|
441
|
+
|
|
442
|
+
const apiBase = await getApiBase();
|
|
443
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
444
|
+
const spinnerId = startSpinner('Downloading resource');
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const params = new URLSearchParams();
|
|
448
|
+
if (opts.expiresIn) params.set('expires_in', opts.expiresIn);
|
|
449
|
+
const qs = params.toString();
|
|
450
|
+
const url = `${apiBase}/v2/livedocs/${shortId}/resources/${resourceId}/download${qs ? `?${qs}` : ''}`;
|
|
451
|
+
|
|
452
|
+
// Follow redirects to get the actual file stream from S3
|
|
453
|
+
const res = await fetchWithTimeoutAndRetry(
|
|
454
|
+
url,
|
|
455
|
+
{ method: 'GET', headers: { Authorization: `Bearer ${apiKey}` }, redirect: 'follow' },
|
|
456
|
+
timeoutMs,
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
if (!res.ok) {
|
|
460
|
+
let msg = res.statusText;
|
|
461
|
+
try { const d = await res.json(); msg = getMessage(d) || msg; } catch { /* ignore */ }
|
|
462
|
+
process.stderr.write(`ERROR: ${res.status} ${msg}\n`);
|
|
463
|
+
return 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Determine output filename
|
|
467
|
+
let filename = opts.output;
|
|
468
|
+
if (!filename) {
|
|
469
|
+
// Try Content-Disposition header first
|
|
470
|
+
const cd = res.headers.get('content-disposition') || '';
|
|
471
|
+
const match = cd.match(/filename\*?=(?:UTF-8'')?["']?([^"';\r\n]+)/i);
|
|
472
|
+
if (match) {
|
|
473
|
+
filename = decodeURIComponent(match[1].trim());
|
|
474
|
+
} else {
|
|
475
|
+
// Fall back to resource_id as filename
|
|
476
|
+
filename = resourceId;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Write file stream to disk
|
|
481
|
+
const { createWriteStream } = await import('fs');
|
|
482
|
+
const writer = createWriteStream(filename);
|
|
483
|
+
const reader = res.body.getReader();
|
|
484
|
+
await new Promise((resolve, reject) => {
|
|
485
|
+
writer.on('error', reject);
|
|
486
|
+
writer.on('finish', resolve);
|
|
487
|
+
const pump = async () => {
|
|
488
|
+
try {
|
|
489
|
+
while (true) {
|
|
490
|
+
const { done, value } = await reader.read();
|
|
491
|
+
if (done) { writer.end(); break; }
|
|
492
|
+
writer.write(value);
|
|
493
|
+
}
|
|
494
|
+
} catch (err) { reject(err); }
|
|
495
|
+
};
|
|
496
|
+
pump();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
stopSpinner(spinnerId);
|
|
500
|
+
process.stdout.write(`Downloaded: ${filename}\n`);
|
|
501
|
+
return 0;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
process.stderr.write(`Failed to download resource: ${err?.message || err}\n`);
|
|
504
|
+
return 1;
|
|
505
|
+
} finally { stopSpinner(spinnerId); }
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export async function getResourceContent(shortId, resourceId, opts = {}) {
|
|
509
|
+
const apiKey = await getApiKey();
|
|
510
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
511
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
512
|
+
if (!resourceId) { process.stderr.write('ERROR: resource_id is required.\n'); return 1; }
|
|
513
|
+
|
|
514
|
+
const apiBase = await getApiBase();
|
|
515
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
516
|
+
const spinnerId = startSpinner('Fetching resource content');
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/resources/${resourceId}/content`, null, apiKey, apiBase, timeoutMs);
|
|
520
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
521
|
+
const d = payload?.data;
|
|
522
|
+
if (!d) { process.stderr.write('No content returned.\n'); return 0; }
|
|
523
|
+
process.stdout.write(`## ${d.title || '(untitled)'}\n`);
|
|
524
|
+
process.stdout.write(`- Type: ${d.type}\n\n`);
|
|
525
|
+
process.stdout.write(d.content || '(empty)');
|
|
526
|
+
process.stdout.write('\n');
|
|
527
|
+
return 0;
|
|
528
|
+
} catch (err) {
|
|
529
|
+
process.stderr.write(`Failed to get resource content: ${err?.message || err}\n`);
|
|
530
|
+
return 1;
|
|
531
|
+
} finally { stopSpinner(spinnerId); }
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export async function pptRetrieve(shortId, opts = {}) {
|
|
535
|
+
const apiKey = await getApiKey();
|
|
536
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
537
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
538
|
+
if (!opts.resourceId) { process.stderr.write('ERROR: --resource-id is required.\n'); return 1; }
|
|
539
|
+
if (!opts.pageNumber) { process.stderr.write('ERROR: --page-number is required.\n'); return 1; }
|
|
540
|
+
if (!opts.query) { process.stderr.write('ERROR: --query is required.\n'); return 1; }
|
|
541
|
+
|
|
542
|
+
const apiBase = await getApiBase();
|
|
543
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
544
|
+
const spinnerId = startSpinner('Retrieving PPT page content');
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
const body = {
|
|
548
|
+
resource_id: opts.resourceId,
|
|
549
|
+
page_number: parseInt(opts.pageNumber, 10),
|
|
550
|
+
query: opts.query,
|
|
551
|
+
};
|
|
552
|
+
if (opts.maxChunk) {
|
|
553
|
+
const n = parseInt(opts.maxChunk, 10);
|
|
554
|
+
if (Number.isFinite(n) && n > 0) body.max_chunk = n;
|
|
555
|
+
}
|
|
556
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/resources/ppt-retrieve`, body, apiKey, apiBase, timeoutMs);
|
|
557
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
558
|
+
const results = payload?.data || [];
|
|
559
|
+
if (!results.length) { process.stderr.write('No results found.\n'); return 0; }
|
|
560
|
+
process.stdout.write(`Found ${results.length} result(s)\n\n`);
|
|
561
|
+
for (const r of results) { process.stdout.write(formatRetrieveResult(r)); }
|
|
562
|
+
return 0;
|
|
563
|
+
} catch (err) {
|
|
564
|
+
process.stderr.write(`Failed to ppt-retrieve: ${err?.message || err}\n`);
|
|
565
|
+
return 1;
|
|
566
|
+
} finally { stopSpinner(spinnerId); }
|
|
567
|
+
}
|