@bndynet/ragbox 0.1.0 → 0.1.1
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 +74 -21
- package/README.zh-CN.md +74 -21
- package/dist/src/cli.js +382 -14
- package/dist/src/config-file.d.ts +17 -1
- package/dist/src/config-file.js +49 -4
- package/dist/src/folder-index/config.js +7 -0
- package/dist/src/folder-index/indexer.js +48 -2
- package/dist/src/folder-index/pageindex-runner.d.ts +15 -0
- package/dist/src/folder-index/pageindex-runner.js +436 -0
- package/dist/src/folder-index/types.d.ts +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/sdk.d.ts +2 -1
- package/dist/src/sdk.js +1 -0
- package/dist/src/serve.js +24 -15
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -4,7 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
7
9
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
8
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
12
|
const commander_1 = require("commander");
|
|
10
13
|
const config_file_1 = require("./config-file");
|
|
@@ -24,6 +27,12 @@ function parseConcurrency(value) {
|
|
|
24
27
|
}
|
|
25
28
|
return parsed;
|
|
26
29
|
}
|
|
30
|
+
function parsePageIndexRunner(value) {
|
|
31
|
+
if (value === "auto" || value === "single" || value === "batch") {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
throw new Error("--pageindex-runner must be one of: auto, single, batch");
|
|
35
|
+
}
|
|
27
36
|
function parseNonNegativeInteger(value, optionName) {
|
|
28
37
|
const parsed = Number.parseInt(value, 10);
|
|
29
38
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -40,6 +49,9 @@ function parseRetryDelayMs(value) {
|
|
|
40
49
|
function parseDebounceMs(value) {
|
|
41
50
|
return parseNonNegativeInteger(value, "--debounce-ms");
|
|
42
51
|
}
|
|
52
|
+
function parseStopTimeoutMs(value) {
|
|
53
|
+
return parseNonNegativeInteger(value, "--timeout-ms");
|
|
54
|
+
}
|
|
43
55
|
function parseServePort(value) {
|
|
44
56
|
const parsed = Number.parseInt(value, 10);
|
|
45
57
|
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 65535) {
|
|
@@ -76,6 +88,32 @@ function logProgress(event) {
|
|
|
76
88
|
break;
|
|
77
89
|
}
|
|
78
90
|
}
|
|
91
|
+
function firstLine(value) {
|
|
92
|
+
return value.split(/\r?\n/, 1)[0] ?? value;
|
|
93
|
+
}
|
|
94
|
+
function printIndexProgress(event) {
|
|
95
|
+
switch (event.type) {
|
|
96
|
+
case "scan":
|
|
97
|
+
console.error(`[ragbox] scan complete output=${event.outputDir} total=${event.total} toIndex=${event.toIndex} unchanged=${event.unchanged} deleted=${event.deleted}`);
|
|
98
|
+
break;
|
|
99
|
+
case "index-start":
|
|
100
|
+
console.error(`[ragbox] indexing ${event.index}/${event.total} ${event.path}`);
|
|
101
|
+
break;
|
|
102
|
+
case "index-done":
|
|
103
|
+
console.error(`[ragbox] indexed ${event.index}/${event.total} ${event.path}`);
|
|
104
|
+
if (event.summary) {
|
|
105
|
+
console.error(`[ragbox] summary ${event.path}: ${event.summary}`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "index-failed":
|
|
109
|
+
console.error(`[ragbox] failed ${event.index}/${event.total} ${event.path}: ${firstLine(event.error)}`);
|
|
110
|
+
break;
|
|
111
|
+
case "write":
|
|
112
|
+
console.error(`[ragbox] wrote manifest=${event.manifestPath}`);
|
|
113
|
+
console.error(`[ragbox] wrote rootTree=${event.rootTreePath}`);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
79
117
|
function addLlmOptions(command) {
|
|
80
118
|
return command
|
|
81
119
|
.option("--api-key <key>", "OpenAI-compatible API key")
|
|
@@ -218,6 +256,7 @@ function buildOptions(configOptions, commandOptions, progress = logProgress) {
|
|
|
218
256
|
cliPath: commandOptions.pageindexCli,
|
|
219
257
|
model: commandOptions.model,
|
|
220
258
|
outputDir: commandOptions.outputDir,
|
|
259
|
+
pageIndexRunner: commandOptions.pageindexRunner,
|
|
221
260
|
pythonPath: commandOptions.pageindexPython,
|
|
222
261
|
watchDebounceMs: commandOptions.debounceMs,
|
|
223
262
|
watchHealthFile: commandOptions.healthFile,
|
|
@@ -245,6 +284,94 @@ function parseSourceNames(source) {
|
|
|
245
284
|
.map((name) => name.trim())
|
|
246
285
|
.filter(Boolean);
|
|
247
286
|
}
|
|
287
|
+
function resolveServeProbeHost(host) {
|
|
288
|
+
if (host === "0.0.0.0") {
|
|
289
|
+
return "127.0.0.1";
|
|
290
|
+
}
|
|
291
|
+
if (host === "::") {
|
|
292
|
+
return "::1";
|
|
293
|
+
}
|
|
294
|
+
return host;
|
|
295
|
+
}
|
|
296
|
+
function serveHealthUrl(host, port) {
|
|
297
|
+
const urlHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
298
|
+
return `http://${urlHost}:${port}/health`;
|
|
299
|
+
}
|
|
300
|
+
function isServeHealthResult(value) {
|
|
301
|
+
if (typeof value !== "object" || value === null) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
const health = value;
|
|
305
|
+
return health.version === 1
|
|
306
|
+
&& typeof health.ok === "boolean"
|
|
307
|
+
&& (health.status === "ready" || health.status === "degraded" || health.status === "error")
|
|
308
|
+
&& typeof health.indexes === "object"
|
|
309
|
+
&& health.indexes !== null;
|
|
310
|
+
}
|
|
311
|
+
async function checkServeHealth(configPath) {
|
|
312
|
+
const { config } = await (0, config_file_1.readRagboxConfig)(configPath);
|
|
313
|
+
const serveConfig = (0, config_file_1.resolveRagboxServeConfig)({ config });
|
|
314
|
+
const host = resolveServeProbeHost(serveConfig.host);
|
|
315
|
+
const port = serveConfig.port;
|
|
316
|
+
const url = serveHealthUrl(host, port);
|
|
317
|
+
return await new Promise((resolve) => {
|
|
318
|
+
const request = node_http_1.default.get(url, { timeout: 2000 }, (response) => {
|
|
319
|
+
let raw = "";
|
|
320
|
+
response.setEncoding("utf8");
|
|
321
|
+
response.on("data", (chunk) => {
|
|
322
|
+
raw += chunk;
|
|
323
|
+
});
|
|
324
|
+
response.on("end", () => {
|
|
325
|
+
let health;
|
|
326
|
+
let error;
|
|
327
|
+
if (raw.trim()) {
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(raw);
|
|
330
|
+
if (isServeHealthResult(parsed)) {
|
|
331
|
+
health = parsed;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
error = "Response was not a ragbox health payload.";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (parseError) {
|
|
338
|
+
error = parseError instanceof Error ? parseError.message : String(parseError);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
error = "Response body was empty.";
|
|
343
|
+
}
|
|
344
|
+
const statusCode = response.statusCode ?? 0;
|
|
345
|
+
resolve({
|
|
346
|
+
version: 1,
|
|
347
|
+
url,
|
|
348
|
+
host,
|
|
349
|
+
port,
|
|
350
|
+
ok: Boolean(health?.ok) && statusCode >= 200 && statusCode < 300,
|
|
351
|
+
reachable: true,
|
|
352
|
+
status: health?.status,
|
|
353
|
+
statusCode,
|
|
354
|
+
health,
|
|
355
|
+
error
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
request.on("timeout", () => {
|
|
360
|
+
request.destroy(new Error("Timed out after 2000ms."));
|
|
361
|
+
});
|
|
362
|
+
request.on("error", (error) => {
|
|
363
|
+
resolve({
|
|
364
|
+
version: 1,
|
|
365
|
+
url,
|
|
366
|
+
host,
|
|
367
|
+
port,
|
|
368
|
+
ok: false,
|
|
369
|
+
reachable: false,
|
|
370
|
+
error: error.message
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
248
375
|
function requireFolder(folder, commandName) {
|
|
249
376
|
if (!folder) {
|
|
250
377
|
throw new Error(`Missing folder. Pass a folder argument or configure a source rootDir before running ragbox ${commandName}.`);
|
|
@@ -433,7 +560,7 @@ async function loadStartTargets(command, commandOptions, folder) {
|
|
|
433
560
|
}
|
|
434
561
|
];
|
|
435
562
|
}
|
|
436
|
-
async function buildStatusOutput(targets) {
|
|
563
|
+
async function buildStatusOutput(targets, configPath) {
|
|
437
564
|
const statusTargets = [];
|
|
438
565
|
for (const target of targets) {
|
|
439
566
|
const validation = await (0, sdk_1.validateIndex)(target.target);
|
|
@@ -446,13 +573,40 @@ async function buildStatusOutput(targets) {
|
|
|
446
573
|
warnings: validation.warnings
|
|
447
574
|
});
|
|
448
575
|
}
|
|
576
|
+
const serve = await checkServeHealth(configPath);
|
|
449
577
|
return {
|
|
450
578
|
version: 1,
|
|
451
579
|
command: "status",
|
|
452
|
-
ok: statusTargets.every((target) => target.ok),
|
|
453
|
-
targets: statusTargets
|
|
580
|
+
ok: statusTargets.every((target) => target.ok) && serve.ok,
|
|
581
|
+
targets: statusTargets,
|
|
582
|
+
serve
|
|
454
583
|
};
|
|
455
584
|
}
|
|
585
|
+
function serveHealthSummary(serve) {
|
|
586
|
+
if (!serve.reachable) {
|
|
587
|
+
return `HTTP server is not reachable at ${serve.url}: ${serve.error ?? "connection failed"}`;
|
|
588
|
+
}
|
|
589
|
+
if (!serve.health) {
|
|
590
|
+
return `HTTP server responded at ${serve.url}, but health could not be parsed: ${serve.error ?? "invalid response"}`;
|
|
591
|
+
}
|
|
592
|
+
const indexes = serve.health.indexes;
|
|
593
|
+
return `HTTP server ${serve.health.status} at ${serve.url}; indexes ready=${indexes.ready}/${indexes.total}.`;
|
|
594
|
+
}
|
|
595
|
+
function printServeHealthOutput(serve) {
|
|
596
|
+
console.log(`${serve.ok ? "ok" : "error"} serve ${serve.url}`);
|
|
597
|
+
console.log(` reachable=${serve.reachable}`);
|
|
598
|
+
if (serve.statusCode !== undefined) {
|
|
599
|
+
console.log(` http=${serve.statusCode}`);
|
|
600
|
+
}
|
|
601
|
+
if (serve.health) {
|
|
602
|
+
const indexes = serve.health.indexes;
|
|
603
|
+
console.log(` status=${serve.health.status}`);
|
|
604
|
+
console.log(` indexes=${indexes.total} ready=${indexes.ready} failed=${indexes.failed}`);
|
|
605
|
+
}
|
|
606
|
+
if (serve.error) {
|
|
607
|
+
console.log(` error ${serve.error}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
456
610
|
function printStatusOutput(status) {
|
|
457
611
|
for (const target of status.targets) {
|
|
458
612
|
const label = target.source ? `${target.source} ${target.target}` : target.target;
|
|
@@ -470,6 +624,9 @@ function printStatusOutput(status) {
|
|
|
470
624
|
console.log(` warning ${warning.code}: ${warning.message}`);
|
|
471
625
|
}
|
|
472
626
|
}
|
|
627
|
+
if (status.serve) {
|
|
628
|
+
printServeHealthOutput(status.serve);
|
|
629
|
+
}
|
|
473
630
|
}
|
|
474
631
|
function startTargetLabel(target) {
|
|
475
632
|
return target.source ? `${target.source} ${target.rootDir}` : target.rootDir;
|
|
@@ -517,6 +674,177 @@ async function closeStartHandles(watchHandles, serveHandle) {
|
|
|
517
674
|
...(serveHandle ? [serveHandle.close()] : [])
|
|
518
675
|
]);
|
|
519
676
|
}
|
|
677
|
+
const BACKGROUND_CHILD_ENV = "RAGBOX_BACKGROUND_CHILD";
|
|
678
|
+
function stripBackgroundStartArgs(args) {
|
|
679
|
+
const stripped = [];
|
|
680
|
+
let afterTerminator = false;
|
|
681
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
682
|
+
const arg = args[index];
|
|
683
|
+
if (afterTerminator) {
|
|
684
|
+
stripped.push(arg);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (arg === "--") {
|
|
688
|
+
afterTerminator = true;
|
|
689
|
+
stripped.push(arg);
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (arg === "--background" || arg.startsWith("--background=")) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (arg === "--pid-file" || arg === "--log-file") {
|
|
696
|
+
index += 1;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (arg === "--no-pid-file" || arg.startsWith("--pid-file=") || arg.startsWith("--log-file=")) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
stripped.push(arg);
|
|
703
|
+
}
|
|
704
|
+
return stripped;
|
|
705
|
+
}
|
|
706
|
+
async function writeBackgroundPidFile(pidFile, pid) {
|
|
707
|
+
if (!pidFile) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
const resolvedPidFile = node_path_1.default.resolve(pidFile);
|
|
711
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(resolvedPidFile), { recursive: true });
|
|
712
|
+
await promises_1.default.writeFile(resolvedPidFile, `${pid}\n`, "utf8");
|
|
713
|
+
return resolvedPidFile;
|
|
714
|
+
}
|
|
715
|
+
async function launchBackgroundStart(commandOptions) {
|
|
716
|
+
const cliScript = process.argv[1];
|
|
717
|
+
if (!cliScript) {
|
|
718
|
+
throw new Error("Cannot start ragbox in the background because the CLI script path is unavailable.");
|
|
719
|
+
}
|
|
720
|
+
const logFile = node_path_1.default.resolve(commandOptions.logFile ?? "ragbox.log");
|
|
721
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(logFile), { recursive: true });
|
|
722
|
+
const logFd = (0, node_fs_1.openSync)(logFile, "a");
|
|
723
|
+
const childArgs = [cliScript, ...stripBackgroundStartArgs(process.argv.slice(2))];
|
|
724
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, childArgs, {
|
|
725
|
+
cwd: process.cwd(),
|
|
726
|
+
detached: true,
|
|
727
|
+
env: {
|
|
728
|
+
...process.env,
|
|
729
|
+
[BACKGROUND_CHILD_ENV]: "1"
|
|
730
|
+
},
|
|
731
|
+
stdio: ["ignore", logFd, logFd]
|
|
732
|
+
});
|
|
733
|
+
try {
|
|
734
|
+
if (!child.pid) {
|
|
735
|
+
throw new Error("Failed to start background ragbox process.");
|
|
736
|
+
}
|
|
737
|
+
const pidFile = await writeBackgroundPidFile(commandOptions.pidFile === false ? undefined : commandOptions.pidFile ?? "ragbox.pid", child.pid);
|
|
738
|
+
child.unref();
|
|
739
|
+
return {
|
|
740
|
+
pid: child.pid,
|
|
741
|
+
logFile,
|
|
742
|
+
pidFile
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
child.kill("SIGTERM");
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
finally {
|
|
750
|
+
(0, node_fs_1.closeSync)(logFd);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function printBackgroundStartResult(result, jsonl) {
|
|
754
|
+
if (jsonl) {
|
|
755
|
+
writeStartJsonLine("start-background", result);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
console.log(`Started ragbox in the background (pid=${result.pid})`);
|
|
759
|
+
console.log(`log=${result.logFile}`);
|
|
760
|
+
if (result.pidFile) {
|
|
761
|
+
console.log(`pidFile=${result.pidFile}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function sleep(ms) {
|
|
765
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
766
|
+
}
|
|
767
|
+
function isNoSuchProcessError(error) {
|
|
768
|
+
return error.code === "ESRCH";
|
|
769
|
+
}
|
|
770
|
+
async function readStopPidFile(pidFile) {
|
|
771
|
+
let raw;
|
|
772
|
+
try {
|
|
773
|
+
raw = await promises_1.default.readFile(pidFile, "utf8");
|
|
774
|
+
}
|
|
775
|
+
catch (error) {
|
|
776
|
+
if (error.code === "ENOENT") {
|
|
777
|
+
throw new Error(`No ragbox pid file found: ${pidFile}. Run ragbox start --background first, or pass --pid-file.`);
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
const trimmed = raw.trim();
|
|
782
|
+
const pid = Number.parseInt(trimmed, 10);
|
|
783
|
+
if (!trimmed || !Number.isInteger(pid) || pid <= 0 || String(pid) !== trimmed) {
|
|
784
|
+
throw new Error(`Invalid ragbox pid file: ${pidFile}`);
|
|
785
|
+
}
|
|
786
|
+
return pid;
|
|
787
|
+
}
|
|
788
|
+
function isProcessRunning(pid) {
|
|
789
|
+
try {
|
|
790
|
+
process.kill(pid, 0);
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
if (isNoSuchProcessError(error)) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async function waitForProcessExit(pid, timeoutMs) {
|
|
801
|
+
const startedAt = Date.now();
|
|
802
|
+
do {
|
|
803
|
+
if (!isProcessRunning(pid)) {
|
|
804
|
+
return true;
|
|
805
|
+
}
|
|
806
|
+
await sleep(50);
|
|
807
|
+
} while (Date.now() - startedAt < timeoutMs);
|
|
808
|
+
return !isProcessRunning(pid);
|
|
809
|
+
}
|
|
810
|
+
async function runStopAction(commandOptions) {
|
|
811
|
+
const pidFile = node_path_1.default.resolve(commandOptions.pidFile ?? "ragbox.pid");
|
|
812
|
+
const pid = await readStopPidFile(pidFile);
|
|
813
|
+
const signal = commandOptions.force ? "SIGKILL" : "SIGTERM";
|
|
814
|
+
const timeoutMs = commandOptions.timeoutMs ?? 5000;
|
|
815
|
+
let stale = false;
|
|
816
|
+
try {
|
|
817
|
+
process.kill(pid, signal);
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
if (!isNoSuchProcessError(error)) {
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
823
|
+
stale = true;
|
|
824
|
+
}
|
|
825
|
+
const stopped = stale || await waitForProcessExit(pid, timeoutMs);
|
|
826
|
+
if (!stopped) {
|
|
827
|
+
throw new Error(`ragbox process ${pid} did not stop within ${timeoutMs}ms; pid file left in place: ${pidFile}`);
|
|
828
|
+
}
|
|
829
|
+
await promises_1.default.rm(pidFile, { force: true });
|
|
830
|
+
return {
|
|
831
|
+
version: 1,
|
|
832
|
+
command: "stop",
|
|
833
|
+
pid,
|
|
834
|
+
pidFile,
|
|
835
|
+
signal,
|
|
836
|
+
stopped,
|
|
837
|
+
stale,
|
|
838
|
+
removedPidFile: true
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function printStopResult(result) {
|
|
842
|
+
if (result.stale) {
|
|
843
|
+
console.log(`Removed stale ragbox pid file ${result.pidFile} (pid=${result.pid})`);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
console.log(`Stopped ragbox (pid=${result.pid}, signal=${result.signal})`);
|
|
847
|
+
}
|
|
520
848
|
function isPathLikeCommand(value) {
|
|
521
849
|
return node_path_1.default.isAbsolute(value) || value.startsWith(".") || value.includes("/") || value.includes("\\");
|
|
522
850
|
}
|
|
@@ -576,12 +904,20 @@ async function buildDoctorOutput(command, commandOptions, target) {
|
|
|
576
904
|
ok: Boolean(runtime.apiKey),
|
|
577
905
|
message: runtime.apiKey ? "LLM API key is configured." : "OPENAI_API_KEY or llm.apiKey is not configured."
|
|
578
906
|
});
|
|
579
|
-
const status = await buildStatusOutput(targets);
|
|
907
|
+
const status = await buildStatusOutput(targets, globalOptions.config);
|
|
908
|
+
const indexStatusOk = status.targets.every((target) => target.ok);
|
|
580
909
|
checks.push({
|
|
581
910
|
name: "index-status",
|
|
582
|
-
ok:
|
|
911
|
+
ok: indexStatusOk,
|
|
583
912
|
message: status.targets.length > 0 ? `Checked ${status.targets.length} index target(s).` : "No index target was checked."
|
|
584
913
|
});
|
|
914
|
+
if (status.serve) {
|
|
915
|
+
checks.push({
|
|
916
|
+
name: "serve-health",
|
|
917
|
+
ok: status.serve.ok,
|
|
918
|
+
message: serveHealthSummary(status.serve)
|
|
919
|
+
});
|
|
920
|
+
}
|
|
585
921
|
return {
|
|
586
922
|
version: 1,
|
|
587
923
|
command: "doctor",
|
|
@@ -718,11 +1054,6 @@ async function runStartAction(folder, commandOptions, command) {
|
|
|
718
1054
|
});
|
|
719
1055
|
watchHandles.push(handle);
|
|
720
1056
|
}
|
|
721
|
-
const readyResults = await Promise.all(watchHandles.map((handle) => handle.ready));
|
|
722
|
-
const failedReady = readyResults.find((ready) => !ready.ok);
|
|
723
|
-
if (failedReady && !failedReady.ok) {
|
|
724
|
-
throw new Error(`Initial index failed: ${failedReady.error}`);
|
|
725
|
-
}
|
|
726
1057
|
const sourceNames = targets.map((target) => target.source).filter((source) => Boolean(source));
|
|
727
1058
|
const singleTarget = targets.length === 1 ? targets[0] : undefined;
|
|
728
1059
|
serveHandle = await (0, serve_1.startServe)({
|
|
@@ -747,6 +1078,12 @@ async function runStartAction(folder, commandOptions, command) {
|
|
|
747
1078
|
else {
|
|
748
1079
|
console.log(`Serving ragbox at ${serveHandle.url}`);
|
|
749
1080
|
}
|
|
1081
|
+
const readyResults = await Promise.all(watchHandles.map((handle) => handle.ready));
|
|
1082
|
+
const failedReady = readyResults.find((ready) => !ready.ok);
|
|
1083
|
+
if (failedReady && !failedReady.ok) {
|
|
1084
|
+
throw new Error(`Initial index failed: ${failedReady.error}`);
|
|
1085
|
+
}
|
|
1086
|
+
await reloadServe();
|
|
750
1087
|
await new Promise((resolve) => {
|
|
751
1088
|
let closing = false;
|
|
752
1089
|
const stop = () => {
|
|
@@ -828,11 +1165,15 @@ async function main() {
|
|
|
828
1165
|
.option("--pageindex-cli <path>", "PageIndex script path")
|
|
829
1166
|
.option("-o, --output-dir <folder>", "folder for ragbox index files")
|
|
830
1167
|
.option("--pageindex-python <path>", "Python executable used to run PageIndex")
|
|
1168
|
+
.option("--pageindex-runner <mode>", "PageIndex runner mode: auto, single, or batch", parsePageIndexRunner)
|
|
831
1169
|
.option("--json", "print a stable JSON result")))
|
|
832
1170
|
.action(async (folder, commandOptions, command) => {
|
|
833
1171
|
const loaded = await loadCommandConfig(command, commandOptions);
|
|
834
1172
|
const indexFolderPath = requireFolder(folder ?? loaded.rootDir, "index");
|
|
835
|
-
|
|
1173
|
+
if (!commandOptions.json) {
|
|
1174
|
+
console.error(`[ragbox] indexing ${indexFolderPath}`);
|
|
1175
|
+
}
|
|
1176
|
+
const result = await (0, indexer_1.indexFolder)(indexFolderPath, buildOptions(loaded.options, commandOptions, commandOptions.json ? logProgress : printIndexProgress));
|
|
836
1177
|
if (commandOptions.json) {
|
|
837
1178
|
writeJson(indexJsonOutput(result));
|
|
838
1179
|
return;
|
|
@@ -873,8 +1214,9 @@ async function main() {
|
|
|
873
1214
|
.option("--all-sources", "check every configured source")
|
|
874
1215
|
.option("--json", "print a stable JSON result"))
|
|
875
1216
|
.action(async (target, commandOptions, command) => {
|
|
1217
|
+
const globalOptions = getGlobalOptions(command);
|
|
876
1218
|
const targets = await loadDiagnosticTargets(command, commandOptions, target, true);
|
|
877
|
-
const status = await buildStatusOutput(targets);
|
|
1219
|
+
const status = await buildStatusOutput(targets, globalOptions.config);
|
|
878
1220
|
if (commandOptions.json) {
|
|
879
1221
|
writeJson(status);
|
|
880
1222
|
return;
|
|
@@ -921,15 +1263,20 @@ async function main() {
|
|
|
921
1263
|
.argument("[folder]", "folder to index, watch, and serve")
|
|
922
1264
|
.option("--all-sources", "start every configured source")
|
|
923
1265
|
.option("--auth-token <token>", "bearer token required for non-health endpoints")
|
|
1266
|
+
.option("--background", "run start as a detached background process")
|
|
924
1267
|
.option("-c, --concurrency <number>", "PageIndex concurrency", parseConcurrency)
|
|
925
1268
|
.option("--pageindex-cli <path>", "PageIndex script path")
|
|
926
1269
|
.option("-o, --output-dir <folder>", "folder for ragbox index files")
|
|
927
1270
|
.option("--pageindex-python <path>", "Python executable used to run PageIndex")
|
|
1271
|
+
.option("--pageindex-runner <mode>", "PageIndex runner mode: auto, single, or batch", parsePageIndexRunner)
|
|
928
1272
|
.option("--debounce-ms <ms>", "watch change debounce in milliseconds", parseDebounceMs)
|
|
929
1273
|
.option("--health-file <path>", "write a watch health JSON file")
|
|
930
|
-
.option("--host <host>", "host to bind"
|
|
1274
|
+
.option("--host <host>", "host to bind")
|
|
931
1275
|
.option("--jsonl", "print stable JSON Lines start, watch, and index progress events")
|
|
1276
|
+
.option("--log-file <path>", "background stdout/stderr log file; defaults to ./ragbox.log")
|
|
932
1277
|
.option("--lock-file <path>", "create an exclusive lock file while start is running")
|
|
1278
|
+
.option("--pid-file <path>", "background process id file; defaults to ./ragbox.pid")
|
|
1279
|
+
.option("--no-pid-file", "do not write a background process id file")
|
|
933
1280
|
.option("--port <number>", "port to bind", parseServePort)
|
|
934
1281
|
.option("--retry-attempts <number>", "retry failed watch index runs", parseRetryAttempts)
|
|
935
1282
|
.option("--retry-delay-ms <ms>", "delay between watch retries in milliseconds", parseRetryDelayMs)
|
|
@@ -937,14 +1284,34 @@ async function main() {
|
|
|
937
1284
|
.option("--staging-output-dir <folder>", "staging directory used with --staging")
|
|
938
1285
|
.option("--webhook <url>", "POST watch events to a webhook URL")))
|
|
939
1286
|
.action(async (folder, commandOptions, command) => {
|
|
1287
|
+
if (commandOptions.background && process.env[BACKGROUND_CHILD_ENV] !== "1") {
|
|
1288
|
+
const result = await launchBackgroundStart(commandOptions);
|
|
1289
|
+
printBackgroundStartResult(result, commandOptions.jsonl);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
940
1292
|
await runStartAction(folder, commandOptions, command);
|
|
941
1293
|
});
|
|
1294
|
+
program
|
|
1295
|
+
.command("stop")
|
|
1296
|
+
.description("stop a background ragbox start process")
|
|
1297
|
+
.option("--pid-file <path>", "pid file written by ragbox start --background", "ragbox.pid")
|
|
1298
|
+
.option("--force", "send SIGKILL instead of SIGTERM")
|
|
1299
|
+
.option("--timeout-ms <ms>", "time to wait for the process to exit", parseStopTimeoutMs)
|
|
1300
|
+
.option("--json", "print a stable JSON result")
|
|
1301
|
+
.action(async (commandOptions) => {
|
|
1302
|
+
const result = await runStopAction(commandOptions);
|
|
1303
|
+
if (commandOptions.json) {
|
|
1304
|
+
writeJson(result);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
printStopResult(result);
|
|
1308
|
+
});
|
|
942
1309
|
addProjectOptions(addLlmOptions(program
|
|
943
1310
|
.command("serve")
|
|
944
1311
|
.argument("[target]", "docs folder or ragbox output directory")
|
|
945
1312
|
.option("--all-sources", "serve every configured source by default")
|
|
946
1313
|
.option("--auth-token <token>", "bearer token required for non-health endpoints")
|
|
947
|
-
.option("--host <host>", "host to bind"
|
|
1314
|
+
.option("--host <host>", "host to bind")
|
|
948
1315
|
.option("--port <number>", "port to bind", parseServePort)))
|
|
949
1316
|
.action(async (target, commandOptions, command) => {
|
|
950
1317
|
const globalOptions = getGlobalOptions(command);
|
|
@@ -986,6 +1353,7 @@ async function main() {
|
|
|
986
1353
|
.option("--pageindex-cli <path>", "PageIndex script path")
|
|
987
1354
|
.option("-o, --output-dir <folder>", "folder for ragbox index files")
|
|
988
1355
|
.option("--pageindex-python <path>", "Python executable used to run PageIndex")
|
|
1356
|
+
.option("--pageindex-runner <mode>", "PageIndex runner mode: auto, single, or batch", parsePageIndexRunner)
|
|
989
1357
|
.option("--debounce-ms <ms>", "watch change debounce in milliseconds", parseDebounceMs)
|
|
990
1358
|
.option("--health-file <path>", "write a watch health JSON file")
|
|
991
1359
|
.option("--jsonl", "print stable JSON Lines watch and index progress events")
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { PageIndexOptions } from "./folder-index/types";
|
|
1
|
+
import { PageIndexOptions, PageIndexRunner } from "./folder-index/types";
|
|
2
2
|
export declare const RAGBOX_CONFIG_FILE = "ragbox.config.json";
|
|
3
|
+
export declare const DEFAULT_SERVE_HOST = "127.0.0.1";
|
|
4
|
+
export declare const DEFAULT_SERVE_PORT = 8787;
|
|
3
5
|
export type RagboxPageIndexConfig = {
|
|
4
6
|
cli?: string;
|
|
5
7
|
concurrency?: number;
|
|
6
8
|
extraArgs?: string[];
|
|
7
9
|
outputArg?: string;
|
|
8
10
|
python?: string;
|
|
11
|
+
runner?: PageIndexRunner;
|
|
9
12
|
};
|
|
10
13
|
export type RagboxLlmConfig = {
|
|
11
14
|
apiKey?: string;
|
|
@@ -17,6 +20,11 @@ export type RagboxIndexConfig = {
|
|
|
17
20
|
include?: string[];
|
|
18
21
|
outputDir?: string;
|
|
19
22
|
};
|
|
23
|
+
export type RagboxServeConfig = {
|
|
24
|
+
authToken?: string;
|
|
25
|
+
host?: string;
|
|
26
|
+
port?: number;
|
|
27
|
+
};
|
|
20
28
|
export type RagboxConfigSource = RagboxIndexConfig & {
|
|
21
29
|
index?: RagboxIndexConfig;
|
|
22
30
|
llm?: RagboxLlmConfig;
|
|
@@ -29,6 +37,7 @@ export type RagboxConfig = {
|
|
|
29
37
|
index?: RagboxIndexConfig;
|
|
30
38
|
llm?: RagboxLlmConfig;
|
|
31
39
|
pageIndex?: RagboxPageIndexConfig;
|
|
40
|
+
serve?: RagboxServeConfig;
|
|
32
41
|
sources?: Record<string, RagboxConfigSource>;
|
|
33
42
|
};
|
|
34
43
|
export type ResolvedRagboxConfig = {
|
|
@@ -57,6 +66,13 @@ export type WritePageIndexSetupConfigOptions = {
|
|
|
57
66
|
cwd?: string;
|
|
58
67
|
pythonPath?: string;
|
|
59
68
|
};
|
|
69
|
+
export type ResolveRagboxServeConfigOptions = RagboxServeConfig & {
|
|
70
|
+
config?: RagboxConfig;
|
|
71
|
+
env?: NodeJS.ProcessEnv;
|
|
72
|
+
};
|
|
73
|
+
export type ResolvedRagboxServeConfig = Required<Pick<RagboxServeConfig, "host" | "port">> & Pick<RagboxServeConfig, "authToken">;
|
|
74
|
+
export declare function parseRagboxServePort(value: unknown, source: string): number | undefined;
|
|
75
|
+
export declare function resolveRagboxServeConfig(options?: ResolveRagboxServeConfigOptions): ResolvedRagboxServeConfig;
|
|
60
76
|
export declare function createDefaultRagboxConfig(options?: Pick<WriteDefaultRagboxConfigOptions, "docsDir" | "outputDir">): RagboxConfig;
|
|
61
77
|
export declare function listRagboxConfigSourceNames(config: RagboxConfig | undefined): string[];
|
|
62
78
|
export declare function writeDefaultRagboxConfig(options?: WriteDefaultRagboxConfigOptions): Promise<string>;
|
package/dist/src/config-file.js
CHANGED
|
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.RAGBOX_CONFIG_FILE = void 0;
|
|
6
|
+
exports.DEFAULT_SERVE_PORT = exports.DEFAULT_SERVE_HOST = exports.RAGBOX_CONFIG_FILE = void 0;
|
|
7
|
+
exports.parseRagboxServePort = parseRagboxServePort;
|
|
8
|
+
exports.resolveRagboxServeConfig = resolveRagboxServeConfig;
|
|
7
9
|
exports.createDefaultRagboxConfig = createDefaultRagboxConfig;
|
|
8
10
|
exports.listRagboxConfigSourceNames = listRagboxConfigSourceNames;
|
|
9
11
|
exports.writeDefaultRagboxConfig = writeDefaultRagboxConfig;
|
|
@@ -13,8 +15,14 @@ exports.resolveRagboxConfig = resolveRagboxConfig;
|
|
|
13
15
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
14
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
17
|
exports.RAGBOX_CONFIG_FILE = "ragbox.config.json";
|
|
18
|
+
exports.DEFAULT_SERVE_HOST = "127.0.0.1";
|
|
19
|
+
exports.DEFAULT_SERVE_PORT = 8787;
|
|
16
20
|
const DEFAULT_INCLUDE = ["**/*.md", "**/*.mdx"];
|
|
17
21
|
const DEFAULT_EXCLUDE = ["node_modules/**", ".git/**", ".pageindex/**", "dist/**", "build/**"];
|
|
22
|
+
const DEFAULT_API_KEY_PLACEHOLDER = "YOUR_OPENAI_API_KEY";
|
|
23
|
+
const DEFAULT_SERVE_TOKEN_PLACEHOLDER = "YOUR_RAGBOX_SERVE_TOKEN";
|
|
24
|
+
const API_KEY_PLACEHOLDERS = new Set([DEFAULT_API_KEY_PLACEHOLDER, "sk-..."]);
|
|
25
|
+
const SERVE_TOKEN_PLACEHOLDERS = new Set([DEFAULT_SERVE_TOKEN_PLACEHOLDER, "<token>"]);
|
|
18
26
|
async function pathExists(filePath) {
|
|
19
27
|
try {
|
|
20
28
|
await promises_1.default.access(filePath);
|
|
@@ -94,12 +102,14 @@ function pageIndexConfigToOptions(configDir, config) {
|
|
|
94
102
|
concurrency: config.concurrency,
|
|
95
103
|
extraArgs: config.extraArgs,
|
|
96
104
|
outputArg: config.outputArg,
|
|
105
|
+
pageIndexRunner: config.runner,
|
|
97
106
|
pythonPath: resolveConfigCommandPath(configDir, config.python)
|
|
98
107
|
};
|
|
99
108
|
}
|
|
100
109
|
function llmConfigToOptions(config) {
|
|
110
|
+
const apiKey = config.apiKey?.trim();
|
|
101
111
|
return {
|
|
102
|
-
apiKey: config.apiKey,
|
|
112
|
+
apiKey: apiKey && !API_KEY_PLACEHOLDERS.has(apiKey) ? config.apiKey : undefined,
|
|
103
113
|
baseUrl: config.baseUrl,
|
|
104
114
|
model: config.model
|
|
105
115
|
};
|
|
@@ -111,17 +121,52 @@ function indexConfigToOptions(configDir, config) {
|
|
|
111
121
|
outputDir: resolveConfigRelativePath(configDir, config.outputDir)
|
|
112
122
|
};
|
|
113
123
|
}
|
|
124
|
+
function parseRagboxServePort(value, source) {
|
|
125
|
+
if (value === undefined) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number.parseInt(value, 10) : NaN;
|
|
129
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
|
|
130
|
+
throw new Error(`Invalid ${source}: ${String(value)}`);
|
|
131
|
+
}
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
function resolveRagboxServeToken(value) {
|
|
135
|
+
const authToken = value?.trim();
|
|
136
|
+
return authToken && !SERVE_TOKEN_PLACEHOLDERS.has(authToken) ? authToken : undefined;
|
|
137
|
+
}
|
|
138
|
+
function resolveRagboxServeConfig(options = {}) {
|
|
139
|
+
const env = options.env ?? process.env;
|
|
140
|
+
return {
|
|
141
|
+
authToken: resolveRagboxServeToken(options.authToken)
|
|
142
|
+
?? resolveRagboxServeToken(options.config?.serve?.authToken)
|
|
143
|
+
?? resolveRagboxServeToken(env.RAGBOX_SERVE_TOKEN),
|
|
144
|
+
host: options.host ?? options.config?.serve?.host ?? env.RAGBOX_SERVE_HOST ?? exports.DEFAULT_SERVE_HOST,
|
|
145
|
+
port: options.port
|
|
146
|
+
?? parseRagboxServePort(options.config?.serve?.port, "ragbox config serve.port")
|
|
147
|
+
?? parseRagboxServePort(env.RAGBOX_SERVE_PORT, "RAGBOX_SERVE_PORT")
|
|
148
|
+
?? exports.DEFAULT_SERVE_PORT
|
|
149
|
+
};
|
|
150
|
+
}
|
|
114
151
|
function createDefaultRagboxConfig(options = {}) {
|
|
115
152
|
const docsDir = options.docsDir ?? "./docs";
|
|
116
153
|
const outputDir = options.outputDir ?? "./.ragbox-index";
|
|
117
154
|
return {
|
|
118
155
|
version: 1,
|
|
119
156
|
pageIndex: {
|
|
120
|
-
cli: "/path/to/PageIndex/run_pageindex.py"
|
|
157
|
+
cli: "/path/to/PageIndex/run_pageindex.py",
|
|
158
|
+
concurrency: 1,
|
|
159
|
+
runner: "auto"
|
|
121
160
|
},
|
|
122
161
|
llm: {
|
|
123
162
|
baseUrl: "https://api.openai.com/v1",
|
|
124
|
-
model: "gpt-4o-mini"
|
|
163
|
+
model: "gpt-4o-mini",
|
|
164
|
+
apiKey: DEFAULT_API_KEY_PLACEHOLDER
|
|
165
|
+
},
|
|
166
|
+
serve: {
|
|
167
|
+
authToken: DEFAULT_SERVE_TOKEN_PLACEHOLDER,
|
|
168
|
+
host: exports.DEFAULT_SERVE_HOST,
|
|
169
|
+
port: exports.DEFAULT_SERVE_PORT
|
|
125
170
|
},
|
|
126
171
|
docs: {
|
|
127
172
|
rootDir: docsDir,
|