@lzhzzzzwill/cofos 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cofos.js +130 -12
- 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 } from "node:readline";
|
|
3
|
+
import { createInterface, emitKeypressEvents } 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";
|
|
@@ -134,6 +134,12 @@ function slashHint(line = "/") {
|
|
|
134
134
|
return `\n Commands\n${rows}\n Press Tab to complete.\n`;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
function slashInlineHint(line = "/") {
|
|
138
|
+
const hits = slashMatches(line);
|
|
139
|
+
if (!hits.length) return "";
|
|
140
|
+
return hits.map(({ cmd, desc }) => ` ${C}${cmd}${R} ${desc}`).join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
137
143
|
/* ─── helpers ─── */
|
|
138
144
|
|
|
139
145
|
function cmdExist(c) { return spawnSync(c, ["--version"], { encoding: "utf-8", stdio: "pipe" }).status === 0; }
|
|
@@ -352,9 +358,17 @@ async function createTui() {
|
|
|
352
358
|
};
|
|
353
359
|
}
|
|
354
360
|
|
|
355
|
-
function
|
|
361
|
+
function blessedToAnsi(text) {
|
|
356
362
|
return String(text)
|
|
357
|
-
.replace(/\{
|
|
363
|
+
.replace(/\{bold\}/g, B)
|
|
364
|
+
.replace(/\{\/bold\}/g, R)
|
|
365
|
+
.replace(/\{cyan-fg\}/g, C)
|
|
366
|
+
.replace(/\{yellow-fg\}/g, Y)
|
|
367
|
+
.replace(/\{green-fg\}/g, GR)
|
|
368
|
+
.replace(/\{gray-fg\}/g, G)
|
|
369
|
+
.replace(/\{red-fg\}/g, "\x1b[91m")
|
|
370
|
+
.replace(/\{black-fg\}|\{yellow-bg\}|\{\/yellow-bg\}|\{\/black-fg\}/g, "")
|
|
371
|
+
.replace(/\{\/(?:cyan-fg|yellow-fg|green-fg|gray-fg|red-fg)\}/g, R)
|
|
358
372
|
.replace(/[{}]/g, "");
|
|
359
373
|
}
|
|
360
374
|
|
|
@@ -368,6 +382,7 @@ function createPlainUi() {
|
|
|
368
382
|
let onSubmit = () => {};
|
|
369
383
|
let onExit = () => {};
|
|
370
384
|
let prompt = "cofos";
|
|
385
|
+
let slashHintShown = false;
|
|
371
386
|
|
|
372
387
|
function setPrompt(label) {
|
|
373
388
|
prompt = label;
|
|
@@ -375,7 +390,23 @@ function createPlainUi() {
|
|
|
375
390
|
}
|
|
376
391
|
|
|
377
392
|
setPrompt(prompt);
|
|
393
|
+
if (process.stdin.isTTY) {
|
|
394
|
+
emitKeypressEvents(process.stdin, rl);
|
|
395
|
+
process.stdin.on("keypress", () => {
|
|
396
|
+
setTimeout(() => {
|
|
397
|
+
const line = rl.line || "";
|
|
398
|
+
if (line === "/" && !slashHintShown) {
|
|
399
|
+
slashHintShown = true;
|
|
400
|
+
process.stdout.write(`\n${slashInlineHint("/")}\n`);
|
|
401
|
+
rl.prompt(true);
|
|
402
|
+
} else if (!line.startsWith("/")) {
|
|
403
|
+
slashHintShown = false;
|
|
404
|
+
}
|
|
405
|
+
}, 0);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
378
408
|
rl.on("line", (line) => {
|
|
409
|
+
slashHintShown = false;
|
|
379
410
|
onSubmit(line);
|
|
380
411
|
});
|
|
381
412
|
rl.on("SIGINT", () => onExit());
|
|
@@ -383,11 +414,11 @@ function createPlainUi() {
|
|
|
383
414
|
|
|
384
415
|
return {
|
|
385
416
|
supportsReplace: false,
|
|
386
|
-
append: (text = "") => process.stdout.write(
|
|
387
|
-
print: (text = "") => console.log(
|
|
417
|
+
append: (text = "") => process.stdout.write(blessedToAnsi(text)),
|
|
418
|
+
print: (text = "") => console.log(blessedToAnsi(text)),
|
|
388
419
|
clear: () => console.clear(),
|
|
389
420
|
length: () => 0,
|
|
390
|
-
replaceFrom: (_index, text = "") => process.stdout.write(
|
|
421
|
+
replaceFrom: (_index, text = "") => process.stdout.write(blessedToAnsi(text)),
|
|
391
422
|
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
392
423
|
setExitHandler: (fn) => { onExit = fn; },
|
|
393
424
|
focus: () => rl.prompt(),
|
|
@@ -510,7 +541,8 @@ async function main() {
|
|
|
510
541
|
proc.stderr.setEncoding("utf-8");
|
|
511
542
|
|
|
512
543
|
let buf = "", ready = false, generating = false, responseOpen = false, commandOpen = false, closed = false;
|
|
513
|
-
let currentAnswer = "", assistantStart = -1;
|
|
544
|
+
let currentAnswer = "", assistantStart = -1, plainAssistantMode = "undecided", plainAssistantPending = "";
|
|
545
|
+
let plainReasonBuffer = "", plainReasonPrinted = 0;
|
|
514
546
|
const inputQ = [];
|
|
515
547
|
let mlBuf = [];
|
|
516
548
|
|
|
@@ -551,6 +583,10 @@ async function main() {
|
|
|
551
583
|
input = input.trim();
|
|
552
584
|
if (!input) return false;
|
|
553
585
|
|
|
586
|
+
if (input === "/") {
|
|
587
|
+
ui.print(slashHint("/"));
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
554
590
|
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
555
591
|
if (/^\/(help)$/.test(input)) {
|
|
556
592
|
ui.print(slashHint("/") + "\n Add local papers to RAG:\n cofos --pdf-dir ./new_pdfs\n cofos --pdf-dir ./new_pdfs --rebuild-pdf-index\n\n Multi-line: end a line with \\ to continue.");
|
|
@@ -566,6 +602,10 @@ async function main() {
|
|
|
566
602
|
responseOpen = false;
|
|
567
603
|
assistantStart = -1;
|
|
568
604
|
currentAnswer = "";
|
|
605
|
+
plainAssistantMode = "undecided";
|
|
606
|
+
plainAssistantPending = "";
|
|
607
|
+
plainReasonBuffer = "";
|
|
608
|
+
plainReasonPrinted = 0;
|
|
569
609
|
commandOpen = true;
|
|
570
610
|
proc.stdin.write(input + "\n");
|
|
571
611
|
generating = true;
|
|
@@ -575,6 +615,10 @@ async function main() {
|
|
|
575
615
|
responseOpen = false;
|
|
576
616
|
assistantStart = -1;
|
|
577
617
|
currentAnswer = "";
|
|
618
|
+
plainAssistantMode = "undecided";
|
|
619
|
+
plainAssistantPending = "";
|
|
620
|
+
plainReasonBuffer = "";
|
|
621
|
+
plainReasonPrinted = 0;
|
|
578
622
|
commandOpen = true;
|
|
579
623
|
proc.stdin.write(input + "\n");
|
|
580
624
|
generating = true;
|
|
@@ -593,14 +637,22 @@ async function main() {
|
|
|
593
637
|
responseOpen = false;
|
|
594
638
|
assistantStart = -1;
|
|
595
639
|
currentAnswer = "";
|
|
640
|
+
plainAssistantMode = "undecided";
|
|
641
|
+
plainAssistantPending = "";
|
|
642
|
+
plainReasonBuffer = "";
|
|
643
|
+
plainReasonPrinted = 0;
|
|
596
644
|
generating = true;
|
|
597
645
|
proc.stdin.write(input + "\n");
|
|
598
646
|
return true;
|
|
599
647
|
}
|
|
600
648
|
|
|
601
649
|
ui.setSubmitHandler((line) => {
|
|
602
|
-
if (!ready || generating)
|
|
603
|
-
|
|
650
|
+
if (!ready || generating) {
|
|
651
|
+
inputQ.push(line);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const started = handle(line);
|
|
655
|
+
if (!started && !closed) ui.focus();
|
|
604
656
|
});
|
|
605
657
|
|
|
606
658
|
proc.stderr.on("data", (chunk) => ui.append(chunk));
|
|
@@ -635,11 +687,73 @@ async function main() {
|
|
|
635
687
|
return "{green-fg}{bold}◆{/bold}{/green-fg} " + raw;
|
|
636
688
|
}
|
|
637
689
|
|
|
690
|
+
const reasonRe = /^(The user|Okay[,,]|I need|We need|I should|I'll|Let me|Since the input|In COF nomenclature|Based on the provided information[,,]? I can|用户|让我)/i;
|
|
691
|
+
|
|
638
692
|
function redrawAssistant() {
|
|
639
693
|
if (assistantStart < 0 || !ui.supportsReplace) return;
|
|
640
694
|
ui.replaceFrom(assistantStart, formatAssistant(currentAnswer));
|
|
641
695
|
}
|
|
642
696
|
|
|
697
|
+
function streamPlainAssistant(text, final = false) {
|
|
698
|
+
if (ui.supportsReplace) return;
|
|
699
|
+
plainAssistantPending += text;
|
|
700
|
+
if (plainAssistantMode === "undecided") {
|
|
701
|
+
const start = plainAssistantPending.trimStart();
|
|
702
|
+
if (!start && !final) return;
|
|
703
|
+
if (start.startsWith("<think>") || reasonRe.test(start)) {
|
|
704
|
+
plainAssistantMode = "reason";
|
|
705
|
+
plainReasonBuffer = start.replace(/^<think>\s*/i, "");
|
|
706
|
+
plainReasonPrinted = 0;
|
|
707
|
+
plainAssistantPending = "";
|
|
708
|
+
ui.append(`${G}-${R} ${G}`);
|
|
709
|
+
} else if (final || start.length >= 12 || /\s/.test(start)) {
|
|
710
|
+
plainAssistantMode = "answer";
|
|
711
|
+
plainAssistantPending = start;
|
|
712
|
+
ui.append(`${GR}${B}◆${R} `);
|
|
713
|
+
} else {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (plainAssistantMode === "reason") {
|
|
719
|
+
plainReasonBuffer += plainAssistantPending;
|
|
720
|
+
plainAssistantPending = "";
|
|
721
|
+
const thinkEnd = plainReasonBuffer.indexOf("</think>");
|
|
722
|
+
const paraEnd = plainReasonBuffer.search(/\n\s*\n/);
|
|
723
|
+
const split = thinkEnd !== -1 ? thinkEnd : paraEnd;
|
|
724
|
+
if (split !== -1) {
|
|
725
|
+
const reason = plainReasonBuffer.slice(plainReasonPrinted, split).replace(/<\/think>/gi, "");
|
|
726
|
+
const restStart = thinkEnd !== -1 ? split + "</think>".length : split;
|
|
727
|
+
const rest = plainReasonBuffer.slice(restStart).trimStart();
|
|
728
|
+
if (reason) ui.append(reason);
|
|
729
|
+
ui.append(`${R}\n${GR}${B}◆${R} `);
|
|
730
|
+
plainAssistantMode = "answer";
|
|
731
|
+
plainAssistantPending = rest;
|
|
732
|
+
plainReasonBuffer = "";
|
|
733
|
+
plainReasonPrinted = 0;
|
|
734
|
+
} else {
|
|
735
|
+
const printableUntil = final ? plainReasonBuffer.length : Math.max(0, plainReasonBuffer.length - 2);
|
|
736
|
+
if (printableUntil > plainReasonPrinted) {
|
|
737
|
+
ui.append(plainReasonBuffer.slice(plainReasonPrinted, printableUntil));
|
|
738
|
+
plainReasonPrinted = printableUntil;
|
|
739
|
+
}
|
|
740
|
+
if (!final) return;
|
|
741
|
+
if (plainReasonPrinted < plainReasonBuffer.length) ui.append(plainReasonBuffer.slice(plainReasonPrinted));
|
|
742
|
+
plainReasonBuffer = "";
|
|
743
|
+
plainReasonPrinted = 0;
|
|
744
|
+
ui.append(R);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (plainAssistantMode === "answer") {
|
|
750
|
+
if (plainAssistantPending) {
|
|
751
|
+
ui.append(plainAssistantPending);
|
|
752
|
+
plainAssistantPending = "";
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
643
757
|
|
|
644
758
|
proc.stdout.on("data", (chunk) => {
|
|
645
759
|
buf += chunk;
|
|
@@ -684,16 +798,20 @@ async function main() {
|
|
|
684
798
|
responseOpen = true;
|
|
685
799
|
assistantStart = ui.supportsReplace ? ui.length() : 0;
|
|
686
800
|
currentAnswer = "";
|
|
801
|
+
plainAssistantMode = "undecided";
|
|
802
|
+
plainAssistantPending = "";
|
|
803
|
+
plainReasonBuffer = "";
|
|
804
|
+
plainReasonPrinted = 0;
|
|
687
805
|
}
|
|
688
806
|
|
|
689
807
|
const di = buf.indexOf(DONE);
|
|
690
808
|
if (di !== -1) {
|
|
691
809
|
const pre = buf.slice(0, di);
|
|
692
|
-
if (pre) { currentAnswer += pre; redrawAssistant(); }
|
|
810
|
+
if (pre) { currentAnswer += pre; if (ui.supportsReplace) redrawAssistant(); else streamPlainAssistant(pre); }
|
|
693
811
|
buf = buf.slice(di + DONE.length);
|
|
694
812
|
if (buf[0] === "\n") buf = buf.slice(1);
|
|
695
813
|
if (ui.supportsReplace) redrawAssistant();
|
|
696
|
-
else
|
|
814
|
+
else streamPlainAssistant("", true);
|
|
697
815
|
generating = false;
|
|
698
816
|
responseOpen = false;
|
|
699
817
|
if (ui.supportsReplace) ui.append("\n");
|
|
@@ -704,7 +822,7 @@ async function main() {
|
|
|
704
822
|
const keep = DONE.length - 1;
|
|
705
823
|
if (buf.length > keep) {
|
|
706
824
|
const out = buf.slice(0, -keep);
|
|
707
|
-
if (out) { currentAnswer += out; redrawAssistant(); }
|
|
825
|
+
if (out) { currentAnswer += out; if (ui.supportsReplace) redrawAssistant(); else streamPlainAssistant(out); }
|
|
708
826
|
buf = buf.slice(-keep);
|
|
709
827
|
}
|
|
710
828
|
}
|