quill/oracle Public Public: anyone can read this; nobody can change it.

workbenchthis machine
Files oracle-mind
99 lines · 3.7 KB · ~1.1k tok Raw History
1
/**
2
 * The oracle in a terminal. The forge runs this on a pty when someone
3
 * `ssh`es to the app, with FORGE_APP_DATA pointing at the same ledger the
4
 * web page reads: j/k move, enter opens nothing because everything is
5
 * already on screen, `a` asks, `r`/`w` score the verdict under the cursor,
6
 * `u` takes a score back, `q` leaves. Plain ANSI, no dependencies.
7
 */
8
9
import * as ledger from "./db.ts";
10
import { calibrate, verdictOnItself } from "./oracle.ts";
11
12
const db = ledger.open();
13
const where =
14
  process.env.FORGE_APP_ENV === "preview"
15
    ? `preview of #${process.env.FORGE_APP_PULL ?? "?"}`
16
    : (process.env.FORGE_APP_ENV ?? "local");
17
18
const esc = "\x1b[";
19
const out = (s: string): void => {
20
  process.stdout.write(s);
21
};
22
const dim = (s: string): string => `${esc}2m${s}${esc}0m`;
23
const bold = (s: string): string => `${esc}1m${s}${esc}0m`;
24
const green = (s: string): string => `${esc}32m${s}${esc}0m`;
25
const red = (s: string): string => `${esc}31m${s}${esc}0m`;
26
27
let cursor = 0;
28
let typing: string | undefined;
29
let flash = "";
30
31
function mark(row: ledger.Row): string {
32
  if (row.outcome === "right") return green("right");
33
  if (row.outcome === "wrong") return red("wrong");
34
  return dim("unjudged");
35
}
36
37
function draw(): void {
38
  const rows = ledger.ledger(db, "all", 100);
39
  if (cursor >= rows.length) cursor = Math.max(0, rows.length - 1);
40
  const width = Math.max(40, (process.stdout.columns ?? 80) - 2);
41
  const clip = (s: string, n: number): string =>
42
    s.length <= n ? s : `${s.slice(0, Math.max(0, n - 1))}…`;
43
  out(`${esc}2J${esc}H`);
44
  out(`${bold("the oracle")} ${dim(`· ${where} · ${rows.length} verdict${rows.length === 1 ? "" : "s"}`)}\r\n`);
45
  out(`${dim(verdictOnItself(calibrate(ledger.scored(db))))}\r\n\r\n`);
46
  if (rows.length === 0) out(dim("  nothing asked yet — press a\r\n"));
47
  for (const [i, row] of rows.entries()) {
48
    const here = i === cursor;
49
    out(`${here ? bold("▸ ") : "  "}${clip(row.question, width - 22)}\r\n`);
50
    out(`    ${dim(`${row.confidence}% · ${mark(row)}`)}\r\n`);
51
    if (here) out(`    ${clip(row.stance, width - 6)}\r\n    ${dim(clip(row.auspice, width - 6))}\r\n`);
52
  }
53
  out(`\r\n${flash === "" ? dim("j/k move · a ask · r right · w wrong · u unjudge · q quit") : flash}\r\n`);
54
  if (typing !== undefined) out(`\r\n${bold("ask:")} ${typing}${dim("▏")}\r\n`);
55
}
56
57
function judgeUnderCursor(outcome: "right" | "wrong" | "unjudged"): void {
58
  const rows = ledger.ledger(db, "all", 100);
59
  const row = rows[cursor];
60
  if (row === undefined) return;
61
  ledger.judge(db, row.id, outcome);
62
  flash = dim(`scored #${row.id} ${outcome}`);
63
}
64
65
function key(data: Buffer): void {
66
  const s = data.toString("utf8");
67
  if (typing !== undefined) {
68
    if (s === "\r" || s === "\n") {
69
      const question = typing.trim();
70
      typing = undefined;
71
      if (question !== "") {
72
        const row = ledger.ask(db, question);
73
        flash = dim(`the oracle: ${row.stance}`);
74
        cursor = 0;
75
      }
76
    } else if (s === "\x7f") typing = typing.slice(0, -1);
77
    else if (s === "\x1b" || s === "\x03") typing = undefined;
78
    else if (s >= " " && typing.length < 280) typing += s;
79
    draw();
80
    return;
81
  }
82
  flash = "";
83
  if (s === "q" || s === "\x03" || s === "\x04") {
84
    out(`${esc}2J${esc}H`);
85
    db.close();
86
    process.exit(0);
87
  } else if (s === "j" || s === `${esc}B`) cursor += 1;
88
  else if (s === "k" || s === `${esc}A`) cursor = Math.max(0, cursor - 1);
89
  else if (s === "a") typing = "";
90
  else if (s === "r") judgeUnderCursor("right");
91
  else if (s === "w") judgeUnderCursor("wrong");
92
  else if (s === "u") judgeUnderCursor("unjudged");
93
  draw();
94
}
95
96
if (process.stdin.isTTY) process.stdin.setRawMode(true);
97
process.stdin.on("data", key);
98
process.stdout.on("resize", draw);
99
draw();
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts