dante-watch 0.1.0 → 0.1.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/README.md +51 -20
- package/package.json +20 -5
- package/src/bib/answers.mjs +35 -0
- package/src/bib/bib.json +1 -0
- package/src/bib/ear.mjs +102 -0
- package/src/bib/net.json +1 -0
- package/src/bib/net.mjs +92 -0
- package/src/bib/probe.mjs +34 -0
- package/src/bib/router.mjs +144 -0
- package/src/bib/system.mjs +106 -0
- package/src/bib/wash.mjs +94 -0
- package/src/checks/battery.mjs +45 -0
- package/src/checks/common.mjs +29 -0
- package/src/checks/loc.mjs +12 -0
- package/src/checks/php2shell.mjs +107 -82
- package/src/checks/rechte.mjs +33 -0
- package/src/checks/secrets.mjs +55 -0
- package/src/checks/sshd.mjs +63 -0
- package/src/checks/webshell.mjs +57 -0
- package/src/cli.mjs +34 -15
- package/src/core/consent.mjs +43 -9
- package/src/core/render.mjs +22 -20
- package/src/core/sh.mjs +32 -2
- package/src/i18n-checks.mjs +125 -0
- package/src/i18n.mjs +99 -0
- package/src/mascot.mjs +68 -42
- package/src/pack/builtin.mjs +302 -0
- package/src/pack/install.mjs +108 -0
- package/src/pack/key.mjs +7 -0
- package/src/pack/know.mjs +65 -0
- package/src/pack/pack.mjs +76 -0
- package/src/pack/rules.mjs +131 -0
- package/src/pack/texts.mjs +87 -0
- package/src/session.mjs +316 -0
- package/src/ticker.mjs +68 -0
- package/src/ui/footer.mjs +46 -0
- package/src/ui/job-worker.mjs +18 -0
- package/src/ui/job.mjs +16 -0
- package/src/ui/quips.mjs +16 -0
- package/src/ui/wait.mjs +80 -0
package/src/bib/ear.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The two ears, trained once from the sorted bib that ships in the package (plain, readable JSON):
|
|
2
|
+
// bib.json — category → intent → [line, level, language]. Levels: G = first bulk run, E1 general,
|
|
3
|
+
// E2 general but in the person's own way, E3 the person's line itself, washed.
|
|
4
|
+
// The ear for what a human wants hears befehle + fragen + nichtdante; the ear for the
|
|
5
|
+
// "HUMAN, HÖR ZU" moment hears fixgespraech. Routing is flat across intents — measured: going
|
|
6
|
+
// category-first passed every category mix-up on (5 wrong instead of 2).
|
|
7
|
+
// Lines were written by DeepSeek playing real users, seeded by washed real goals; the real
|
|
8
|
+
// goals themselves are not in here.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { train, classify, tokens, sureFor, KNOWN } from "./router.mjs";
|
|
11
|
+
import { wash } from "./wash.mjs";
|
|
12
|
+
import { loadNet, predict } from "./net.mjs";
|
|
13
|
+
import { PROBE } from "./probe.mjs";
|
|
14
|
+
import { SYS_PROBE, MUSTER_PROBE, WISSEN_PROBE } from "./system.mjs";
|
|
15
|
+
|
|
16
|
+
let bib = null;
|
|
17
|
+
const load = () => (bib ||= JSON.parse(readFileSync(new URL("./bib.json", import.meta.url), "utf8")));
|
|
18
|
+
export const rowsOf = (cats) => cats.flatMap((c) => Object.entries(load().cats[c] || {}).flatMap(([intent, xs]) => xs.map(([text]) => ({ text, intent }))));
|
|
19
|
+
export const bibDate = () => load().date;
|
|
20
|
+
// Three neurons: the counting ear (bayes), the net, and the combiner (bayes^a · net^b, fitted on
|
|
21
|
+
// lines neither saw). WAY decides who may act:
|
|
22
|
+
// bayes — the counting ear alone
|
|
23
|
+
// einig — act only when BOTH ears are sure on their own and say the same; else ask back
|
|
24
|
+
// kombi — the combiner's joint probability decides
|
|
25
|
+
// The ranked list a human sees ("Meinst du …?") always comes from the joint probability.
|
|
26
|
+
// Measured per ear (25.10.): ohr bayes 131/138 · 1 wrong · einig 130 · 0 wrong · kombi 131 · 3 wrong
|
|
27
|
+
// janein bayes 30/33 · einig 30/33 · kombi 33/33 (it can never say yes)
|
|
28
|
+
const WAYS = { ohr: "einig", janein: "kombi", system: "einig", muster: "einig", wissen: "einig" };
|
|
29
|
+
export const setWay = (w) => { WAYS.ohr = WAYS.janein = WAYS.system = WAYS.muster = WAYS.wissen = w; };
|
|
30
|
+
const ears = {};
|
|
31
|
+
let nets = null;
|
|
32
|
+
// Exact memory (like Virgil's value cache): a line that stands 1:1 in the bib — washed, so
|
|
33
|
+
// "server root@1.2.3.4" meets "server {ZIEL}" — is known, no probability in between. This is what
|
|
34
|
+
// makes E3 work: the trust rule keeps one line from deciding for OTHER lines, not for itself.
|
|
35
|
+
const exactKey = (t) => tokens(wash(t).text).join(" ");
|
|
36
|
+
// The domain ears (system, muster, wissen) sit behind the main ear's gap. Each also learns the lines of
|
|
37
|
+
// the OTHER domains as "unklar", so it stays silent on what is not its own and the order they are asked
|
|
38
|
+
// in does not matter. NEG says whose lines those are.
|
|
39
|
+
export const NEG = { system: ["muster", "wissen"], muster: ["systemwissen", "wissen"], wissen: ["muster", "systemwissen"] };
|
|
40
|
+
// ...and a fixed sample of everyday lines ("not Dante's work", small talk), so "write my essay" or "what is
|
|
41
|
+
// the weather" never wakes a domain ear. The sample is chosen by a hash of the line: the same bib gives
|
|
42
|
+
// the same rows, so the nets stay reproducible.
|
|
43
|
+
const fnv = (s) => { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; };
|
|
44
|
+
const EVERYDAY = [["nichtdante", 4], ["smalltalk", 5]]; // [category, keep one line in n]
|
|
45
|
+
export const negRows = (name) => !NEG[name] ? [] : [
|
|
46
|
+
...NEG[name].flatMap((c) => Object.values(load().cats[c] || {}).flatMap((xs) => xs.map(([text]) => ({ text, intent: "unklar" })))),
|
|
47
|
+
...EVERYDAY.flatMap(([c, n]) => Object.values(load().cats[c] || {}).flatMap((xs) => xs.filter(([text]) => fnv(text) % n === 0).map(([text]) => ({ text, intent: "unklar" })))),
|
|
48
|
+
];
|
|
49
|
+
function hear(name, cats, extra, line) {
|
|
50
|
+
const WAY = WAYS[name];
|
|
51
|
+
const e = (ears[name] ||= (() => { const rows = [...extra, ...rowsOf(cats), ...negRows(name)]; const mem = new Map(rows.map((r) => [exactKey(r.text), r.intent])); return { m: train(rows), mem }; })());
|
|
52
|
+
const known = e.mem.get(exactKey(line));
|
|
53
|
+
if (known) return { intent: known, p: 1, known: 1, ranked: [{ intent: known, p: 1 }], exact: true };
|
|
54
|
+
const b = classify(e.m, line, { all: true });
|
|
55
|
+
const raw = (nets ||= JSON.parse(readFileSync(new URL("./net.json", import.meta.url), "utf8")))[name];
|
|
56
|
+
if (WAY === "bayes" || !raw) return { ...b, ranked: b.ranked.slice(0, 3) };
|
|
57
|
+
const net = (e.net ||= loadNet(raw)), { a, b: bw } = raw.comb;
|
|
58
|
+
const pn = Object.fromEntries(predict(net, line).map((x) => [x.intent, x.p]));
|
|
59
|
+
const s = b.ranked.map((x) => [x.intent, a * Math.log(x.p + 1e-9) + bw * Math.log((pn[x.intent] ?? 0) + 1e-9)]);
|
|
60
|
+
const top = Math.max(...s.map((x) => x[1])), z = s.reduce((p, x) => p + Math.exp(x[1] - top), 0);
|
|
61
|
+
const ranked = s.map(([intent, v]) => ({ intent, p: Math.exp(v - top) / z })).sort((x, y) => y.p - x.p);
|
|
62
|
+
const netTop = Object.entries(pn).sort((x, y) => y[1] - x[1])[0];
|
|
63
|
+
const netSure = netTop[1] >= sureFor(netTop[0]) && netTop[0] !== "unklar" ? netTop[0] : "unklar";
|
|
64
|
+
const joint = ranked[0].p >= sureFor(ranked[0].intent) && b.known >= KNOWN && ranked[0].intent !== "unklar" ? ranked[0].intent : "unklar";
|
|
65
|
+
const intent = WAY === "kombi" ? joint : b.intent === netSure ? b.intent : "unklar";
|
|
66
|
+
return { intent, p: ranked[0].p, known: b.known, ranked: ranked.slice(0, 3), bayes: b.intent, net: netSure };
|
|
67
|
+
}
|
|
68
|
+
export const hearMain = (line) => hear("ohr", ["befehle", "fragen", "smalltalk", "nichtdante"], PROBE, line);
|
|
69
|
+
export const hearConsent = (line) => hear("janein", ["fixgespraech"], [], line);
|
|
70
|
+
// The third ear: only system knowledge (the sixth bib). The main ear decides it is a system job
|
|
71
|
+
// (pruef/server), THEN this ear picks the move: fingerabdruck / finde_php / pruef_php2shell / beleg /
|
|
72
|
+
// fix_php2shell. Kept apart from the five language bibs on purpose — no E1/E2/E3, family is a slot.
|
|
73
|
+
export const hearSystem = (line) => hear("system", ["systemwissen"], SYS_PROBE, line);
|
|
74
|
+
// The fourth ear: the Muster-Bib — the watchman's "go and look for what's wrong" moves. Also sits
|
|
75
|
+
// behind the main ear's gap; conservative (einig) so plain talk is never pulled into a patrol.
|
|
76
|
+
export const hearMuster = (line) => hear("muster", ["muster"], MUSTER_PROBE, line);
|
|
77
|
+
// The fifth ear: the Fachwissen-Bib — when a person asks Dante to EXPLAIN (what he watches, one topic,
|
|
78
|
+
// what stands out, what is normal). It learns the way of asking; the topic comes from the cards.
|
|
79
|
+
export const hearWissen = (line) => hear("wissen", ["wissen"], WISSEN_PROBE, line);
|
|
80
|
+
|
|
81
|
+
// The domain ears behind the main ear's gap, in the order they are asked. Each knows what is not its
|
|
82
|
+
// own (the negative class), so the first one that speaks is the right one. null = none of them.
|
|
83
|
+
export function hearDomain(line) {
|
|
84
|
+
for (const [ear, f] of [["muster", hearMuster], ["system", hearSystem], ["wissen", hearWissen]]) {
|
|
85
|
+
const r = f(line);
|
|
86
|
+
if (r.intent !== "unklar") return { ear, intent: r.intent, p: r.p };
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// German, English or Spanish? A handful of telling words per language picks the answer's tongue.
|
|
92
|
+
const TELL = {
|
|
93
|
+
es: /\b(que|qué|es|mi|el|la|los|como|cómo|por|hola|esto|eso|está|estoy|puedes|quiero|tengo|servidor|seguro|gracias|hackearon)\b/gi,
|
|
94
|
+
en: /\b(the|is|my|what|how|why|you|are|did|do|does|this|that|it|i|me|can|someone|safe|hacked|anywhere|even|wont)\b/gi,
|
|
95
|
+
de: /\b(der|die|das|ist|mein|meine|was|wie|warum|du|bin|ich|mir|kann|jemand|sicher|gehackt|nicht|und|heißt|überhaupt)\b/gi,
|
|
96
|
+
};
|
|
97
|
+
// No telling word at all ("fix", "demo") → the fallback, so one bare command does not flip him.
|
|
98
|
+
export const langOf = (line, fallback = "de") => {
|
|
99
|
+
let best = fallback, n = 0;
|
|
100
|
+
for (const [k, re] of Object.entries(TELL)) { const c = (String(line).match(re) || []).length; if (c > n) { n = c; best = k; } }
|
|
101
|
+
return best;
|
|
102
|
+
};
|