@lzhzzzzwill/cofos 1.1.14 → 1.1.19
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/bin/cofos.js +98 -41
- package/package.json +1 -1
package/bin/cofos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { createInterface,
|
|
3
|
+
import { clearLine, createInterface, cursorTo } from "node:readline";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -16,6 +16,7 @@ const DEFAULT_CONFIG = process.env.COFOS_CONFIG || "config/config.yaml";
|
|
|
16
16
|
/* ─── ANSI ─── */
|
|
17
17
|
const R = "\x1b[0m", B = "\x1b[1m", D = "\x1b[2m";
|
|
18
18
|
const C = "\x1b[96m", W = "\x1b[97m", G = "\x1b[90m", Y = "\x1b[93m";
|
|
19
|
+
const M = "\x1b[95m", BL = "\x1b[94m", GR = "\x1b[92m", A = "\x1b[38;5;208m";
|
|
19
20
|
|
|
20
21
|
/* ─── Protocol markers ─── */
|
|
21
22
|
const READY = "===READY===", RESPONSE = "===RESPONSE===", DONE = "===DONE===";
|
|
@@ -30,9 +31,16 @@ const ART_W = ART[0].length;
|
|
|
30
31
|
|
|
31
32
|
function vis(t) { return t.replace(/\x1b\[[0-9;]*m/g, "").length; }
|
|
32
33
|
function fw() { return Math.min(Math.max((process.stdout.columns || 110) - 2, 82), 150); }
|
|
33
|
-
function
|
|
34
|
-
function
|
|
35
|
-
|
|
34
|
+
function plain(t) { return t.replace(/\x1b\[[0-9;]*m/g, ""); }
|
|
35
|
+
function topTitle(w, left, right = "") {
|
|
36
|
+
const l = ` ${C}${B}${left}${R} `;
|
|
37
|
+
const r = right ? ` ${G}${right}${R} ` : "";
|
|
38
|
+
const room = Math.max(0, w - vis(l) - vis(r));
|
|
39
|
+
return C + "╭" + R + l + C + "─".repeat(room) + R + r + C + "╮" + R;
|
|
40
|
+
}
|
|
41
|
+
function top(w) { return C + "╭" + "─".repeat(w) + "╮" + R; }
|
|
42
|
+
function bot(w) { return C + "╰" + "─".repeat(w) + "╯" + R; }
|
|
43
|
+
function sep(w) { return C + "├" + "─".repeat(w) + "┤" + R; }
|
|
36
44
|
function vpad(t, w) { return t + " ".repeat(Math.max(0, w - vis(t))); }
|
|
37
45
|
function clip(t, w) {
|
|
38
46
|
const plain = t.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -41,12 +49,12 @@ function clip(t, w) {
|
|
|
41
49
|
return plain.slice(0, w - 1) + "…";
|
|
42
50
|
}
|
|
43
51
|
function cell(t, w) { return vpad(clip(t, w), w); }
|
|
44
|
-
function row(left, right, lw, rw) { return "│ " + cell(left, lw) + " │ " + cell(right, rw) + " │"; }
|
|
45
|
-
function full(t, w) { return "│ " + cell(t, w - 2) + " │"; }
|
|
52
|
+
function row(left, right, lw, rw) { return C + "│ " + R + cell(left, lw) + C + " │ " + R + cell(right, rw) + C + " │" + R; }
|
|
53
|
+
function full(t, w) { return C + "│ " + R + cell(t, w - 2) + C + " │" + R; }
|
|
46
54
|
function empty(w) { return full("", w); }
|
|
47
55
|
|
|
48
56
|
function logoLines(width) {
|
|
49
|
-
if (width >= ART_W) return ART.map((line) => C + B + line + R);
|
|
57
|
+
if (width >= ART_W) return ART.map((line, i) => (i < 2 ? C : i < 4 ? BL : M) + B + line + R);
|
|
50
58
|
return [C + B + "COFOS" + R];
|
|
51
59
|
}
|
|
52
60
|
|
|
@@ -60,25 +68,26 @@ function render({ modelPath, cacheDir, pdfDir }) {
|
|
|
60
68
|
...logoLines(Math.floor((w - 5) * 0.48)),
|
|
61
69
|
"",
|
|
62
70
|
G + "Scientific RAG CLI" + R,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
Y + "Model" + R + " " + W + modelPath + R,
|
|
72
|
+
Y + "Params" + R + " " + W + "9B" + R,
|
|
73
|
+
Y + "Path" + R + " " + W + cwd + R,
|
|
74
|
+
Y + "Cache" + R + " " + W + cacheDir + R,
|
|
67
75
|
];
|
|
68
76
|
const right = [
|
|
69
|
-
|
|
77
|
+
GR + B + "What COFOS Does" + R,
|
|
70
78
|
"COFOS answers scientific questions about covalent organic frameworks,",
|
|
71
79
|
"photocatalysis, reactive oxygen species, and oxygen-reduction products.",
|
|
72
80
|
rag,
|
|
73
81
|
"",
|
|
74
|
-
|
|
75
|
-
"/help show commands and PDF-RAG usage",
|
|
76
|
-
"/topk 5 change retrieval depth",
|
|
77
|
-
"/rag off answer without retrieval",
|
|
78
|
-
"cofos --pdf-dir ./new_pdfs add local papers",
|
|
82
|
+
A + B + "Suggested Commands" + R,
|
|
83
|
+
C + "/help" + R + " show commands and PDF-RAG usage",
|
|
84
|
+
C + "/topk 5" + R + " change retrieval depth",
|
|
85
|
+
C + "/rag off" + R + " answer without retrieval",
|
|
86
|
+
M + "cofos --pdf-dir ./new_pdfs" + R + " add local papers",
|
|
79
87
|
];
|
|
80
88
|
|
|
81
|
-
const
|
|
89
|
+
const titleLeft = `COFOS CLI v${pkg.version}`;
|
|
90
|
+
const lines = [topTitle(w, titleLeft)];
|
|
82
91
|
if (w >= minSplit) {
|
|
83
92
|
const inner = w - 5;
|
|
84
93
|
const lw = Math.max(46, Math.floor(inner * 0.48));
|
|
@@ -114,7 +123,7 @@ function slashMatches(line) {
|
|
|
114
123
|
function slashCompletions(line) {
|
|
115
124
|
if (!line.startsWith("/")) return [[], line];
|
|
116
125
|
const hits = slashMatches(line).map(({ cmd }) => cmd);
|
|
117
|
-
return [hits
|
|
126
|
+
return [hits, line];
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
function slashHint(line = "/") {
|
|
@@ -247,6 +256,11 @@ function main() {
|
|
|
247
256
|
const inputQ = [];
|
|
248
257
|
const mainPrompt = C + " cofos " + R;
|
|
249
258
|
const contPrompt = G + " ... " + R;
|
|
259
|
+
const docked = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
260
|
+
let dockRows = process.stdout.rows || 24;
|
|
261
|
+
let dockCols = process.stdout.columns || 80;
|
|
262
|
+
let historyRow = Math.max(0, dockRows - 2);
|
|
263
|
+
let historyCol = 0;
|
|
250
264
|
const rl = createInterface({
|
|
251
265
|
input: process.stdin,
|
|
252
266
|
output: process.stdout,
|
|
@@ -254,32 +268,72 @@ function main() {
|
|
|
254
268
|
prompt: mainPrompt,
|
|
255
269
|
completer: slashCompletions,
|
|
256
270
|
});
|
|
257
|
-
let lastSlashHint = "";
|
|
258
271
|
rl.on("line", (l) => {
|
|
259
|
-
lastSlashHint = "";
|
|
260
272
|
if (!ready || generating) inputQ.push(l);
|
|
261
273
|
else if (!handle(l)) ask();
|
|
262
274
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
process.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
|
|
276
|
+
function resetDock() {
|
|
277
|
+
if (docked) process.stdout.write("\x1b[r");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function setupDock() {
|
|
281
|
+
if (!docked) return;
|
|
282
|
+
dockRows = process.stdout.rows || dockRows;
|
|
283
|
+
dockCols = process.stdout.columns || dockCols;
|
|
284
|
+
historyRow = Math.max(0, dockRows - 2);
|
|
285
|
+
historyCol = Math.min(historyCol, Math.max(0, dockCols - 1));
|
|
286
|
+
process.stdout.write(`\x1b[1;${Math.max(1, dockRows - 1)}r`);
|
|
287
|
+
cursorTo(process.stdout, 0, dockRows - 1);
|
|
288
|
+
clearLine(process.stdout, 0);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function redrawInput() {
|
|
292
|
+
if (!docked || closed) return;
|
|
293
|
+
cursorTo(rl.output, 0, dockRows - 1);
|
|
294
|
+
clearLine(rl.output, 0);
|
|
295
|
+
rl.prompt(true);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function writeHistory(data) {
|
|
299
|
+
if (!docked) {
|
|
300
|
+
process.stdout.write(data);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
cursorTo(process.stdout, historyCol, historyRow);
|
|
304
|
+
for (const ch of data) {
|
|
305
|
+
process.stdout.write(ch);
|
|
306
|
+
if (ch === "\n") {
|
|
307
|
+
historyCol = 0;
|
|
308
|
+
} else if (ch === "\r") {
|
|
309
|
+
historyCol = 0;
|
|
310
|
+
} else {
|
|
311
|
+
historyCol += 1;
|
|
312
|
+
if (historyCol >= dockCols) historyCol = 0;
|
|
277
313
|
}
|
|
278
|
-
}
|
|
314
|
+
}
|
|
315
|
+
redrawInput();
|
|
279
316
|
}
|
|
280
317
|
|
|
281
|
-
|
|
282
|
-
|
|
318
|
+
function preserveInputWrite(stream, chunk) {
|
|
319
|
+
const data = String(chunk);
|
|
320
|
+
if (!rl.terminal || closed) {
|
|
321
|
+
stream.write(data);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
writeHistory(data);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
setupDock();
|
|
328
|
+
process.stdout.on("resize", () => { setupDock(); redrawInput(); });
|
|
329
|
+
|
|
330
|
+
// Let users type while the Python backend is still loading. Submitted lines
|
|
331
|
+
// are queued until READY, while status logs are written above and the input
|
|
332
|
+
// line is restored afterward.
|
|
333
|
+
ask();
|
|
334
|
+
|
|
335
|
+
// Forward Python stderr (status / progress messages) without destroying the input line.
|
|
336
|
+
proc.stderr.on("data", (chunk) => { preserveInputWrite(process.stderr, chunk); });
|
|
283
337
|
|
|
284
338
|
proc.stdout.on("data", (chunk) => {
|
|
285
339
|
buf += chunk;
|
|
@@ -314,19 +368,19 @@ function main() {
|
|
|
314
368
|
const di = buf.indexOf(DONE);
|
|
315
369
|
if (di !== -1) {
|
|
316
370
|
const pre = buf.slice(0, di);
|
|
317
|
-
if (pre) process.stdout
|
|
371
|
+
if (pre) preserveInputWrite(process.stdout, pre);
|
|
318
372
|
buf = buf.slice(di + DONE.length);
|
|
319
373
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
320
374
|
generating = false;
|
|
321
375
|
responseOpen = false;
|
|
322
|
-
|
|
376
|
+
preserveInputWrite(process.stdout, "\n");
|
|
323
377
|
drain();
|
|
324
378
|
} else {
|
|
325
379
|
// Stream buffered content, keeping enough tail to catch a partial DONE marker.
|
|
326
380
|
const keep = DONE.length - 1;
|
|
327
381
|
if (buf.length > keep) {
|
|
328
382
|
const out = buf.slice(0, -keep);
|
|
329
|
-
if (out) process.stdout
|
|
383
|
+
if (out) preserveInputWrite(process.stdout, out);
|
|
330
384
|
buf = buf.slice(-keep);
|
|
331
385
|
}
|
|
332
386
|
}
|
|
@@ -335,6 +389,7 @@ function main() {
|
|
|
335
389
|
proc.on("error", (e) => { console.error("[cofos] Python error:", e.message); process.exit(1); });
|
|
336
390
|
proc.on("exit", (c) => {
|
|
337
391
|
closed = true;
|
|
392
|
+
resetDock();
|
|
338
393
|
if (c && c !== 0) console.error("[cofos] Python exited with code", c);
|
|
339
394
|
try { rl.close(); } catch (_) {}
|
|
340
395
|
// Let queued stdout writes flush before tearing the process down.
|
|
@@ -344,6 +399,7 @@ function main() {
|
|
|
344
399
|
function shutdown() {
|
|
345
400
|
if (closed) return;
|
|
346
401
|
closed = true;
|
|
402
|
+
resetDock();
|
|
347
403
|
try { proc.stdin.end(); } catch (_) {}
|
|
348
404
|
try { rl.close(); } catch (_) {}
|
|
349
405
|
proc.kill("SIGTERM");
|
|
@@ -353,6 +409,7 @@ function main() {
|
|
|
353
409
|
rl.on("SIGINT", () => { console.log("\n[cofos] Interrupted."); shutdown(); });
|
|
354
410
|
rl.on("close", () => { if (!closed) shutdown(); });
|
|
355
411
|
process.on("SIGTERM", shutdown);
|
|
412
|
+
process.on("exit", resetDock);
|
|
356
413
|
|
|
357
414
|
function drain() {
|
|
358
415
|
while (inputQ.length) {
|