felo-ai 0.2.12 → 0.2.14
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/README.md +113 -374
- package/felo-livedoc/LICENSE +21 -0
- package/felo-livedoc/README.md +53 -0
- package/felo-livedoc/SKILL.md +180 -0
- package/felo-livedoc/scripts/run_livedoc.mjs +391 -0
- package/package.json +4 -2
- package/src/cli.js +205 -0
- package/src/livedoc.js +401 -0
package/src/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { superAgent } from "./superAgent.js";
|
|
|
8
8
|
import { webFetch } from "./webFetch.js";
|
|
9
9
|
import { youtubeSubtitling } from "./youtubeSubtitling.js";
|
|
10
10
|
import * as xSearch from "./xSearch.js";
|
|
11
|
+
import * as livedoc from "./livedoc.js";
|
|
11
12
|
import * as config from "./config.js";
|
|
12
13
|
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
@@ -349,6 +350,210 @@ program
|
|
|
349
350
|
flushStdioThenExit(code);
|
|
350
351
|
});
|
|
351
352
|
|
|
353
|
+
// ── LiveDoc ──
|
|
354
|
+
const livedocCmd = program
|
|
355
|
+
.command("livedoc")
|
|
356
|
+
.description("Manage Felo LiveDocs (knowledge bases) and resources");
|
|
357
|
+
|
|
358
|
+
livedocCmd
|
|
359
|
+
.command("create")
|
|
360
|
+
.description("Create a new LiveDoc")
|
|
361
|
+
.requiredOption("--name <name>", "LiveDoc name")
|
|
362
|
+
.option("--description <desc>", "LiveDoc description")
|
|
363
|
+
.option("--icon <icon>", "LiveDoc icon")
|
|
364
|
+
.option("-j, --json", "output raw JSON")
|
|
365
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
366
|
+
.action(async (opts) => {
|
|
367
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
368
|
+
const code = await livedoc.createLiveDoc({
|
|
369
|
+
name: opts.name,
|
|
370
|
+
description: opts.description,
|
|
371
|
+
icon: opts.icon,
|
|
372
|
+
json: opts.json,
|
|
373
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
374
|
+
});
|
|
375
|
+
process.exitCode = code;
|
|
376
|
+
flushStdioThenExit(code);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
livedocCmd
|
|
380
|
+
.command("list")
|
|
381
|
+
.description("List LiveDocs")
|
|
382
|
+
.option("--keyword <kw>", "search keyword")
|
|
383
|
+
.option("--page <n>", "page number")
|
|
384
|
+
.option("--size <n>", "page size")
|
|
385
|
+
.option("-j, --json", "output raw JSON")
|
|
386
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
387
|
+
.action(async (opts) => {
|
|
388
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
389
|
+
const code = await livedoc.listLiveDocs({
|
|
390
|
+
keyword: opts.keyword,
|
|
391
|
+
page: opts.page,
|
|
392
|
+
size: opts.size,
|
|
393
|
+
json: opts.json,
|
|
394
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
395
|
+
});
|
|
396
|
+
process.exitCode = code;
|
|
397
|
+
flushStdioThenExit(code);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
livedocCmd
|
|
401
|
+
.command("update <short_id>")
|
|
402
|
+
.description("Update a LiveDoc")
|
|
403
|
+
.option("--name <name>", "new name")
|
|
404
|
+
.option("--description <desc>", "new description")
|
|
405
|
+
.option("-j, --json", "output raw JSON")
|
|
406
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
407
|
+
.action(async (shortId, opts) => {
|
|
408
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
409
|
+
const code = await livedoc.updateLiveDoc(shortId, {
|
|
410
|
+
name: opts.name,
|
|
411
|
+
description: opts.description,
|
|
412
|
+
json: opts.json,
|
|
413
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
414
|
+
});
|
|
415
|
+
process.exitCode = code;
|
|
416
|
+
flushStdioThenExit(code);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
livedocCmd
|
|
420
|
+
.command("delete <short_id>")
|
|
421
|
+
.description("Delete a LiveDoc")
|
|
422
|
+
.option("-j, --json", "output raw JSON")
|
|
423
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
424
|
+
.action(async (shortId, opts) => {
|
|
425
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
426
|
+
const code = await livedoc.deleteLiveDoc(shortId, {
|
|
427
|
+
json: opts.json,
|
|
428
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
429
|
+
});
|
|
430
|
+
process.exitCode = code;
|
|
431
|
+
flushStdioThenExit(code);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
livedocCmd
|
|
435
|
+
.command("resources <short_id>")
|
|
436
|
+
.description("List resources in a LiveDoc")
|
|
437
|
+
.option("--type <type>", "filter by resource type")
|
|
438
|
+
.option("--page <n>", "page number")
|
|
439
|
+
.option("--size <n>", "page size")
|
|
440
|
+
.option("-j, --json", "output raw JSON")
|
|
441
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
442
|
+
.action(async (shortId, opts) => {
|
|
443
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
444
|
+
const code = await livedoc.listResources(shortId, {
|
|
445
|
+
type: opts.type,
|
|
446
|
+
page: opts.page,
|
|
447
|
+
size: opts.size,
|
|
448
|
+
json: opts.json,
|
|
449
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
450
|
+
});
|
|
451
|
+
process.exitCode = code;
|
|
452
|
+
flushStdioThenExit(code);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
livedocCmd
|
|
456
|
+
.command("resource <short_id> <resource_id>")
|
|
457
|
+
.description("Get a single resource")
|
|
458
|
+
.option("-j, --json", "output raw JSON")
|
|
459
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
460
|
+
.action(async (shortId, resourceId, opts) => {
|
|
461
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
462
|
+
const code = await livedoc.getResource(shortId, resourceId, {
|
|
463
|
+
json: opts.json,
|
|
464
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
465
|
+
});
|
|
466
|
+
process.exitCode = code;
|
|
467
|
+
flushStdioThenExit(code);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
livedocCmd
|
|
471
|
+
.command("add-doc <short_id>")
|
|
472
|
+
.description("Create a text document resource")
|
|
473
|
+
.requiredOption("--content <content>", "document content")
|
|
474
|
+
.option("--title <title>", "document title")
|
|
475
|
+
.option("-j, --json", "output raw JSON")
|
|
476
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
477
|
+
.action(async (shortId, opts) => {
|
|
478
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
479
|
+
const code = await livedoc.addDoc(shortId, {
|
|
480
|
+
content: opts.content,
|
|
481
|
+
title: opts.title,
|
|
482
|
+
json: opts.json,
|
|
483
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
484
|
+
});
|
|
485
|
+
process.exitCode = code;
|
|
486
|
+
flushStdioThenExit(code);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
livedocCmd
|
|
490
|
+
.command("add-urls <short_id>")
|
|
491
|
+
.description("Add URL resources (max 10, comma-separated)")
|
|
492
|
+
.requiredOption("--urls <urls>", "comma-separated URLs")
|
|
493
|
+
.option("-j, --json", "output raw JSON")
|
|
494
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
495
|
+
.action(async (shortId, opts) => {
|
|
496
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
497
|
+
const code = await livedoc.addUrls(shortId, {
|
|
498
|
+
urls: opts.urls,
|
|
499
|
+
json: opts.json,
|
|
500
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
501
|
+
});
|
|
502
|
+
process.exitCode = code;
|
|
503
|
+
flushStdioThenExit(code);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
livedocCmd
|
|
507
|
+
.command("upload <short_id>")
|
|
508
|
+
.description("Upload a file as resource")
|
|
509
|
+
.requiredOption("--file <path>", "file path to upload")
|
|
510
|
+
.option("--convert", "convert file to document (use upload-doc endpoint)")
|
|
511
|
+
.option("-j, --json", "output raw JSON")
|
|
512
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
513
|
+
.action(async (shortId, opts) => {
|
|
514
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
515
|
+
const code = await livedoc.uploadFile(shortId, {
|
|
516
|
+
file: opts.file,
|
|
517
|
+
convert: opts.convert,
|
|
518
|
+
json: opts.json,
|
|
519
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
520
|
+
});
|
|
521
|
+
process.exitCode = code;
|
|
522
|
+
flushStdioThenExit(code);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
livedocCmd
|
|
526
|
+
.command("remove-resource <short_id> <resource_id>")
|
|
527
|
+
.description("Delete a resource")
|
|
528
|
+
.option("-j, --json", "output raw JSON")
|
|
529
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
530
|
+
.action(async (shortId, resourceId, opts) => {
|
|
531
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
532
|
+
const code = await livedoc.removeResource(shortId, resourceId, {
|
|
533
|
+
json: opts.json,
|
|
534
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
535
|
+
});
|
|
536
|
+
process.exitCode = code;
|
|
537
|
+
flushStdioThenExit(code);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
livedocCmd
|
|
541
|
+
.command("retrieve <short_id>")
|
|
542
|
+
.description("Semantic search across resources")
|
|
543
|
+
.requiredOption("--query <query>", "search query")
|
|
544
|
+
.option("-j, --json", "output raw JSON")
|
|
545
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
546
|
+
.action(async (shortId, opts) => {
|
|
547
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
548
|
+
const code = await livedoc.retrieve(shortId, {
|
|
549
|
+
query: opts.query,
|
|
550
|
+
json: opts.json,
|
|
551
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
552
|
+
});
|
|
553
|
+
process.exitCode = code;
|
|
554
|
+
flushStdioThenExit(code);
|
|
555
|
+
});
|
|
556
|
+
|
|
352
557
|
program
|
|
353
558
|
.command("summarize")
|
|
354
559
|
.description("Summarize text or URL (coming when API is available)")
|
package/src/livedoc.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { getApiKey, fetchWithTimeoutAndRetry, NO_KEY_MESSAGE } from './search.js';
|
|
2
|
+
import * as config from './config.js';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_API_BASE = 'https://openapi.felo.ai';
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
8
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
9
|
+
const SPINNER_INTERVAL_MS = 80;
|
|
10
|
+
const STATUS_PAD = 56;
|
|
11
|
+
|
|
12
|
+
// ── Shared helpers ──
|
|
13
|
+
|
|
14
|
+
function startSpinner(message) {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
let i = 0;
|
|
17
|
+
const id = setInterval(() => {
|
|
18
|
+
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
19
|
+
const line = `${message} ${SPINNER_FRAMES[i % SPINNER_FRAMES.length]} ${elapsed}s`;
|
|
20
|
+
process.stderr.write(`\r${line.padEnd(STATUS_PAD, ' ')}`);
|
|
21
|
+
i += 1;
|
|
22
|
+
}, SPINNER_INTERVAL_MS);
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function stopSpinner(id) {
|
|
27
|
+
if (id != null) clearInterval(id);
|
|
28
|
+
process.stderr.write(`\r${' '.repeat(STATUS_PAD)}\r`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getApiBase() {
|
|
32
|
+
let base = process.env.FELO_API_BASE?.trim();
|
|
33
|
+
if (!base) {
|
|
34
|
+
const v = await config.getConfigValue('FELO_API_BASE');
|
|
35
|
+
base = typeof v === 'string' ? v.trim() : '';
|
|
36
|
+
}
|
|
37
|
+
return (base || DEFAULT_API_BASE).replace(/\/$/, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getMessage(payload) {
|
|
41
|
+
return payload?.message || payload?.error || payload?.msg || payload?.code || 'Unknown error';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function apiRequest(method, apiPath, body, apiKey, apiBase, timeoutMs) {
|
|
45
|
+
const url = `${apiBase}/v2${apiPath}`;
|
|
46
|
+
const headers = {
|
|
47
|
+
Accept: 'application/json',
|
|
48
|
+
Authorization: `Bearer ${apiKey}`,
|
|
49
|
+
};
|
|
50
|
+
const init = { method, headers };
|
|
51
|
+
|
|
52
|
+
if (body !== undefined && body !== null) {
|
|
53
|
+
headers['Content-Type'] = 'application/json';
|
|
54
|
+
init.body = JSON.stringify(body);
|
|
55
|
+
}
|
|
56
|
+
const res = await fetchWithTimeoutAndRetry(url, init, timeoutMs);
|
|
57
|
+
let data = {};
|
|
58
|
+
try { data = await res.json(); } catch { data = {}; }
|
|
59
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${getMessage(data)}`);
|
|
60
|
+
if (data.status === 'error') throw new Error(getMessage(data));
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function uploadFormData(apiPath, formData, apiKey, apiBase, timeoutMs) {
|
|
65
|
+
const url = `${apiBase}/v2${apiPath}`;
|
|
66
|
+
const res = await fetchWithTimeoutAndRetry(
|
|
67
|
+
url,
|
|
68
|
+
{
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
Accept: 'application/json',
|
|
72
|
+
Authorization: `Bearer ${apiKey}`,
|
|
73
|
+
},
|
|
74
|
+
body: formData,
|
|
75
|
+
},
|
|
76
|
+
timeoutMs,
|
|
77
|
+
);
|
|
78
|
+
let data = {};
|
|
79
|
+
try { data = await res.json(); } catch { data = {}; }
|
|
80
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${getMessage(data)}`);
|
|
81
|
+
if (data.status === 'error') throw new Error(getMessage(data));
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Formatting helpers ──
|
|
86
|
+
|
|
87
|
+
function formatLiveDoc(doc) {
|
|
88
|
+
if (!doc) return '';
|
|
89
|
+
let out = `## ${doc.name || '(untitled)'}\n`;
|
|
90
|
+
out += `- ID: \`${doc.short_id}\`\n`;
|
|
91
|
+
if (doc.description) out += `- Description: ${doc.description}\n`;
|
|
92
|
+
if (doc.icon) out += `- Icon: ${doc.icon}\n`;
|
|
93
|
+
if (doc.created_at) out += `- Created: ${doc.created_at}\n`;
|
|
94
|
+
if (doc.modified_at) out += `- Modified: ${doc.modified_at}\n`;
|
|
95
|
+
out += '\n';
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function formatResource(res) {
|
|
99
|
+
if (!res) return '';
|
|
100
|
+
let out = `### ${res.title || '(untitled)'}\n`;
|
|
101
|
+
out += `- Resource ID: \`${res.id}\`\n`;
|
|
102
|
+
if (res.resource_type) out += `- Type: ${res.resource_type}\n`;
|
|
103
|
+
if (res.status) out += `- Status: ${res.status}\n`;
|
|
104
|
+
if (res.source) out += `- Source: ${res.source}\n`;
|
|
105
|
+
if (res.link) out += `- Link: ${res.link}\n`;
|
|
106
|
+
if (res.snippet) out += `- Snippet: ${res.snippet}\n`;
|
|
107
|
+
if (res.created_at) out += `- Created: ${res.created_at}\n`;
|
|
108
|
+
out += '\n';
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatRetrieveResult(r) {
|
|
113
|
+
if (!r) return '';
|
|
114
|
+
const score = r.score != null ? `${(r.score * 100).toFixed(1)}%` : 'N/A';
|
|
115
|
+
let out = `### ${r.title || '(untitled)'} (score: ${score})\n`;
|
|
116
|
+
out += `- ID: \`${r.id}\`\n`;
|
|
117
|
+
if (r.content) {
|
|
118
|
+
const preview = r.content.length > 300 ? r.content.slice(0, 300) + '...' : r.content;
|
|
119
|
+
out += `- Content: ${preview}\n`;
|
|
120
|
+
}
|
|
121
|
+
out += '\n';
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Exported functions ──
|
|
126
|
+
|
|
127
|
+
export async function createLiveDoc(opts = {}) {
|
|
128
|
+
const apiKey = await getApiKey();
|
|
129
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
130
|
+
if (!opts.name) { process.stderr.write('ERROR: --name is required.\n'); return 1; }
|
|
131
|
+
|
|
132
|
+
const apiBase = await getApiBase();
|
|
133
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
134
|
+
const spinnerId = startSpinner('Creating LiveDoc');
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const body = { name: opts.name };
|
|
138
|
+
if (opts.description) body.description = opts.description;
|
|
139
|
+
if (opts.icon) body.icon = opts.icon;
|
|
140
|
+
const payload = await apiRequest('POST', '/livedocs', body, apiKey, apiBase, timeoutMs);
|
|
141
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
142
|
+
const doc = payload?.data;
|
|
143
|
+
process.stdout.write(`LiveDoc created successfully!\n\n`);
|
|
144
|
+
process.stdout.write(formatLiveDoc(doc));
|
|
145
|
+
return 0;
|
|
146
|
+
} catch (err) {
|
|
147
|
+
process.stderr.write(`Failed to create LiveDoc: ${err?.message || err}\n`);
|
|
148
|
+
return 1;
|
|
149
|
+
} finally { stopSpinner(spinnerId); }
|
|
150
|
+
}
|
|
151
|
+
export async function listLiveDocs(opts = {}) {
|
|
152
|
+
const apiKey = await getApiKey();
|
|
153
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
154
|
+
|
|
155
|
+
const apiBase = await getApiBase();
|
|
156
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
157
|
+
const spinnerId = startSpinner('Listing LiveDocs');
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const params = new URLSearchParams();
|
|
161
|
+
if (opts.keyword) params.set('keyword', opts.keyword);
|
|
162
|
+
if (opts.page) params.set('page', opts.page);
|
|
163
|
+
if (opts.size) params.set('size', opts.size);
|
|
164
|
+
const qs = params.toString();
|
|
165
|
+
const apiPath = `/livedocs${qs ? `?${qs}` : ''}`;
|
|
166
|
+
const payload = await apiRequest('GET', apiPath, null, apiKey, apiBase, timeoutMs);
|
|
167
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
168
|
+
|
|
169
|
+
const data = payload?.data;
|
|
170
|
+
const items = data?.items || [];
|
|
171
|
+
if (!items.length) { process.stderr.write('No LiveDocs found.\n'); return 0; }
|
|
172
|
+
process.stdout.write(`Found ${data.total || items.length} LiveDoc(s)\n\n`);
|
|
173
|
+
for (const doc of items) { process.stdout.write(formatLiveDoc(doc)); }
|
|
174
|
+
return 0;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`Failed to list LiveDocs: ${err?.message || err}\n`);
|
|
177
|
+
return 1;
|
|
178
|
+
} finally { stopSpinner(spinnerId); }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function updateLiveDoc(shortId, opts = {}) {
|
|
182
|
+
const apiKey = await getApiKey();
|
|
183
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
184
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
185
|
+
|
|
186
|
+
const apiBase = await getApiBase();
|
|
187
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
188
|
+
const spinnerId = startSpinner('Updating LiveDoc');
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const body = {};
|
|
192
|
+
if (opts.name) body.name = opts.name;
|
|
193
|
+
if (opts.description) body.description = opts.description;
|
|
194
|
+
const payload = await apiRequest('PUT', `/livedocs/${shortId}`, body, apiKey, apiBase, timeoutMs);
|
|
195
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
196
|
+
process.stdout.write(`LiveDoc updated successfully!\n\n`);
|
|
197
|
+
process.stdout.write(formatLiveDoc(payload?.data));
|
|
198
|
+
return 0;
|
|
199
|
+
} catch (err) {
|
|
200
|
+
process.stderr.write(`Failed to update LiveDoc: ${err?.message || err}\n`);
|
|
201
|
+
return 1;
|
|
202
|
+
} finally { stopSpinner(spinnerId); }
|
|
203
|
+
}
|
|
204
|
+
export async function deleteLiveDoc(shortId, opts = {}) {
|
|
205
|
+
const apiKey = await getApiKey();
|
|
206
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
207
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
208
|
+
|
|
209
|
+
const apiBase = await getApiBase();
|
|
210
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
211
|
+
const spinnerId = startSpinner('Deleting LiveDoc');
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await apiRequest('DELETE', `/livedocs/${shortId}`, null, apiKey, apiBase, timeoutMs);
|
|
215
|
+
if (opts.json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); return 0; }
|
|
216
|
+
process.stdout.write(`LiveDoc \`${shortId}\` deleted.\n`);
|
|
217
|
+
return 0;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
process.stderr.write(`Failed to delete LiveDoc: ${err?.message || err}\n`);
|
|
220
|
+
return 1;
|
|
221
|
+
} finally { stopSpinner(spinnerId); }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function listResources(shortId, opts = {}) {
|
|
225
|
+
const apiKey = await getApiKey();
|
|
226
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
227
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
228
|
+
|
|
229
|
+
const apiBase = await getApiBase();
|
|
230
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
231
|
+
const spinnerId = startSpinner('Listing resources');
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const params = new URLSearchParams();
|
|
235
|
+
if (opts.type) params.set('resource_types', opts.type);
|
|
236
|
+
if (opts.page) params.set('page', opts.page);
|
|
237
|
+
if (opts.size) params.set('size', opts.size);
|
|
238
|
+
const qs = params.toString();
|
|
239
|
+
const apiPath = `/livedocs/${shortId}/resources${qs ? `?${qs}` : ''}`;
|
|
240
|
+
const payload = await apiRequest('GET', apiPath, null, apiKey, apiBase, timeoutMs);
|
|
241
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
242
|
+
|
|
243
|
+
const data = payload?.data;
|
|
244
|
+
const items = data?.items || [];
|
|
245
|
+
if (!items.length) { process.stderr.write('No resources found.\n'); return 0; }
|
|
246
|
+
process.stdout.write(`Found ${data.total || items.length} resource(s)\n\n`);
|
|
247
|
+
for (const r of items) { process.stdout.write(formatResource(r)); }
|
|
248
|
+
return 0;
|
|
249
|
+
} catch (err) {
|
|
250
|
+
process.stderr.write(`Failed to list resources: ${err?.message || err}\n`);
|
|
251
|
+
return 1;
|
|
252
|
+
} finally { stopSpinner(spinnerId); }
|
|
253
|
+
}
|
|
254
|
+
export async function getResource(shortId, resourceId, opts = {}) {
|
|
255
|
+
const apiKey = await getApiKey();
|
|
256
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
257
|
+
if (!shortId || !resourceId) { process.stderr.write('ERROR: short_id and resource_id are required.\n'); return 1; }
|
|
258
|
+
|
|
259
|
+
const apiBase = await getApiBase();
|
|
260
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
261
|
+
const spinnerId = startSpinner('Fetching resource');
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const payload = await apiRequest('GET', `/livedocs/${shortId}/resources/${resourceId}`, null, apiKey, apiBase, timeoutMs);
|
|
265
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
266
|
+
process.stdout.write(formatResource(payload?.data));
|
|
267
|
+
return 0;
|
|
268
|
+
} catch (err) {
|
|
269
|
+
process.stderr.write(`Failed to get resource: ${err?.message || err}\n`);
|
|
270
|
+
return 1;
|
|
271
|
+
} finally { stopSpinner(spinnerId); }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function addDoc(shortId, opts = {}) {
|
|
275
|
+
const apiKey = await getApiKey();
|
|
276
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
277
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
278
|
+
if (!opts.content) { process.stderr.write('ERROR: --content is required.\n'); return 1; }
|
|
279
|
+
|
|
280
|
+
const apiBase = await getApiBase();
|
|
281
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
282
|
+
const spinnerId = startSpinner('Creating document resource');
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const body = { content: opts.content };
|
|
286
|
+
if (opts.title) body.title = opts.title;
|
|
287
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/resources/doc`, body, apiKey, apiBase, timeoutMs);
|
|
288
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
289
|
+
process.stdout.write(`Document resource created!\n\n`);
|
|
290
|
+
process.stdout.write(formatResource(payload?.data));
|
|
291
|
+
return 0;
|
|
292
|
+
} catch (err) {
|
|
293
|
+
process.stderr.write(`Failed to create document: ${err?.message || err}\n`);
|
|
294
|
+
return 1;
|
|
295
|
+
} finally { stopSpinner(spinnerId); }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function addUrls(shortId, opts = {}) {
|
|
299
|
+
const apiKey = await getApiKey();
|
|
300
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
301
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
302
|
+
if (!opts.urls) { process.stderr.write('ERROR: --urls is required.\n'); return 1; }
|
|
303
|
+
|
|
304
|
+
const urls = opts.urls.split(',').map(u => u.trim()).filter(Boolean);
|
|
305
|
+
if (!urls.length) { process.stderr.write('ERROR: at least one URL is required.\n'); return 1; }
|
|
306
|
+
if (urls.length > 10) { process.stderr.write('ERROR: maximum 10 URLs allowed.\n'); return 1; }
|
|
307
|
+
const apiBase = await getApiBase();
|
|
308
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
309
|
+
const spinnerId = startSpinner(`Adding ${urls.length} URL(s)`);
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/resources/urls`, { urls }, apiKey, apiBase, timeoutMs);
|
|
313
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
314
|
+
|
|
315
|
+
const results = payload?.data || [];
|
|
316
|
+
for (const r of results) {
|
|
317
|
+
const icon = r.status === 'success' ? '✓' : r.status === 'existed' ? '~' : '✗';
|
|
318
|
+
let line = `${icon} ${r.url} → ${r.status}`;
|
|
319
|
+
if (r.resource_id) line += ` (id: ${r.resource_id})`;
|
|
320
|
+
if (r.fail_reason) line += ` (${r.fail_reason})`;
|
|
321
|
+
process.stdout.write(line + '\n');
|
|
322
|
+
}
|
|
323
|
+
return 0;
|
|
324
|
+
} catch (err) {
|
|
325
|
+
process.stderr.write(`Failed to add URLs: ${err?.message || err}\n`);
|
|
326
|
+
return 1;
|
|
327
|
+
} finally { stopSpinner(spinnerId); }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function uploadFile(shortId, opts = {}) {
|
|
331
|
+
const apiKey = await getApiKey();
|
|
332
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
333
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
334
|
+
if (!opts.file) { process.stderr.write('ERROR: --file is required.\n'); return 1; }
|
|
335
|
+
|
|
336
|
+
const apiBase = await getApiBase();
|
|
337
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
338
|
+
const endpoint = opts.convert ? 'upload-doc' : 'upload';
|
|
339
|
+
const spinnerId = startSpinner(`Uploading file (${endpoint})`);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const fileBuffer = await fs.readFile(opts.file);
|
|
343
|
+
const blob = new Blob([fileBuffer]);
|
|
344
|
+
const formData = new FormData();
|
|
345
|
+
formData.append('file', blob, path.basename(opts.file));
|
|
346
|
+
|
|
347
|
+
const payload = await uploadFormData(`/livedocs/${shortId}/resources/${endpoint}`, formData, apiKey, apiBase, timeoutMs);
|
|
348
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
349
|
+
process.stdout.write(`File uploaded successfully!\n\n`);
|
|
350
|
+
process.stdout.write(formatResource(payload?.data));
|
|
351
|
+
return 0;
|
|
352
|
+
} catch (err) {
|
|
353
|
+
process.stderr.write(`Failed to upload file: ${err?.message || err}\n`);
|
|
354
|
+
return 1;
|
|
355
|
+
} finally { stopSpinner(spinnerId); }
|
|
356
|
+
}
|
|
357
|
+
export async function removeResource(shortId, resourceId, opts = {}) {
|
|
358
|
+
const apiKey = await getApiKey();
|
|
359
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
360
|
+
if (!shortId || !resourceId) { process.stderr.write('ERROR: short_id and resource_id are required.\n'); return 1; }
|
|
361
|
+
|
|
362
|
+
const apiBase = await getApiBase();
|
|
363
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
364
|
+
const spinnerId = startSpinner('Deleting resource');
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
await apiRequest('DELETE', `/livedocs/${shortId}/resources/${resourceId}`, null, apiKey, apiBase, timeoutMs);
|
|
368
|
+
if (opts.json) { console.log(JSON.stringify({ status: 'ok' }, null, 2)); return 0; }
|
|
369
|
+
process.stdout.write(`Resource \`${resourceId}\` deleted.\n`);
|
|
370
|
+
return 0;
|
|
371
|
+
} catch (err) {
|
|
372
|
+
process.stderr.write(`Failed to delete resource: ${err?.message || err}\n`);
|
|
373
|
+
return 1;
|
|
374
|
+
} finally { stopSpinner(spinnerId); }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export async function retrieve(shortId, opts = {}) {
|
|
378
|
+
const apiKey = await getApiKey();
|
|
379
|
+
if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
|
|
380
|
+
if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
|
|
381
|
+
if (!opts.query) { process.stderr.write('ERROR: --query is required.\n'); return 1; }
|
|
382
|
+
|
|
383
|
+
const apiBase = await getApiBase();
|
|
384
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
385
|
+
const spinnerId = startSpinner('Retrieving from knowledge base');
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const body = { content: opts.query };
|
|
389
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/resources/retrieve`, body, apiKey, apiBase, timeoutMs);
|
|
390
|
+
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
391
|
+
|
|
392
|
+
const results = payload?.data || [];
|
|
393
|
+
if (!results.length) { process.stderr.write('No results found.\n'); return 0; }
|
|
394
|
+
process.stdout.write(`Found ${results.length} result(s)\n\n`);
|
|
395
|
+
for (const r of results) { process.stdout.write(formatRetrieveResult(r)); }
|
|
396
|
+
return 0;
|
|
397
|
+
} catch (err) {
|
|
398
|
+
process.stderr.write(`Failed to retrieve: ${err?.message || err}\n`);
|
|
399
|
+
return 1;
|
|
400
|
+
} finally { stopSpinner(spinnerId); }
|
|
401
|
+
}
|