@clutor/cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,178 @@
1
+ import {
2
+ ConfigError,
3
+ loadConfig,
4
+ table
5
+ } from "./chunk-U5GHVJJO.js";
6
+
7
+ // src/repl.ts
8
+ import { TOOLS } from "@clutor/core";
9
+ var FORWARD = {
10
+ status: { usage: "/status", help: "machine, model backend, memory plan, receipts" },
11
+ doctor: { usage: "/doctor", help: "check this machine and the configuration" },
12
+ models: { usage: "/models", help: "model catalog and the .gguf files in your models folder" },
13
+ plan: { usage: "/plan <model> [--ram GiB]", help: "will it fit, where the memory goes, how fast it could possibly run" },
14
+ simulate: { usage: "/simulate <model> [--cache GiB]", help: "replay a routing trace through the expert cache (synthetic)" },
15
+ inspect: { usage: "/inspect <file.gguf>", help: "read a model file's header" },
16
+ bench: { usage: "/bench [--file model.gguf]", help: "measure real disk reads; with the engine, compare cache strategies" },
17
+ stream: { usage: "/stream <model.gguf>", help: "stream a model's experts through the native engine (synthetic routing)" },
18
+ pull: { usage: "/pull [id]", help: "list or download a small test model" },
19
+ init: { usage: "/init", help: "create clutor.config.json and a models folder here" },
20
+ backend: { usage: "/backend", help: "show how llama-server would be started" },
21
+ ledger: { usage: "/ledger", help: "verify the receipt log and list recent tasks and checkpoints" },
22
+ decide: { usage: '/decide --prompt "<text>" --choices "a,b"', help: "one forward pass: probability of each choice" }
23
+ };
24
+ var OWN = {
25
+ help: { usage: "/help", help: "this list" },
26
+ model: { usage: "/model", help: "which model this session talks to" },
27
+ verify: { usage: "/verify [command|off]", help: "run this check after every task and only offer changes that pass it" },
28
+ yes: { usage: "/yes", help: "toggle: run project commands without asking (only for projects you trust)" },
29
+ undo: { usage: "/undo", help: "restore the files changed by the last applied task" },
30
+ tools: { usage: "/tools", help: "what the agent can do" },
31
+ cost: { usage: "/cost", help: "tokens used this session, as reported by the model server" },
32
+ history: { usage: "/history", help: "tasks done in this session" },
33
+ clear: { usage: "/clear", help: "forget this session's task history" },
34
+ layers: { usage: "/layers", help: "where the model file's bytes are, per layer (native engine)" },
35
+ cache: { usage: "/cache", help: "a streaming pass over the model file: cache, prefetch, disk waits" },
36
+ exit: { usage: "/exit", help: "leave (also /quit, Ctrl-D)" }
37
+ };
38
+ function splitWords(line) {
39
+ const out = [];
40
+ let cur = "", quote, has = false;
41
+ for (const ch of line) {
42
+ if (quote) {
43
+ if (ch === quote) quote = void 0;
44
+ else cur += ch;
45
+ } else if (ch === '"' || ch === "'") {
46
+ quote = ch;
47
+ has = true;
48
+ } else if (/\s/.test(ch)) {
49
+ if (cur || has) out.push(cur);
50
+ cur = "";
51
+ has = false;
52
+ } else cur += ch;
53
+ }
54
+ if (cur || has) out.push(cur);
55
+ return out;
56
+ }
57
+ function helpText() {
58
+ const rows = [...Object.values(OWN), ...Object.values(FORWARD)].map((c) => [c.usage, c.help]);
59
+ return `Type a task in plain words and the agent works on this folder. Its changes are held aside; you see the diff and approve before anything is written.
60
+
61
+ ${table(rows)}`;
62
+ }
63
+ async function runRepl(io, run, engine) {
64
+ const ask = io.prompt;
65
+ const session = { auto: false, notes: [], usage: { promptTokens: 0, completionTokens: 0 } };
66
+ const load = () => {
67
+ try {
68
+ return loadConfig(io.cwd).config;
69
+ } catch (e) {
70
+ io.err(e instanceof ConfigError || e instanceof Error ? e.message : String(e));
71
+ return void 0;
72
+ }
73
+ };
74
+ const cfg = load();
75
+ const model = (c) => !c ? "not configured" : c.backend.kind === "openai" ? `${c.backend.model ?? "default"} at ${c.backend.baseUrl}` : c.backend.llamaServer.model ?? "not configured (set backend.llamaServer.model)";
76
+ io.out(`clutor interactive session
77
+ folder ${io.cwd}
78
+ model ${model(cfg)}
79
+ Type a task, or /help for commands. Nothing is written to your files without your approval.`);
80
+ try {
81
+ for (; ; ) {
82
+ const line = (await ask("clt> ")).trim();
83
+ if (!line) continue;
84
+ if (!line.startsWith("/")) {
85
+ await guarded(io, () => run(["code", line, "--dir", io.cwd], session));
86
+ continue;
87
+ }
88
+ const [word = "", ...rest] = splitWords(line.slice(1));
89
+ const name = word.toLowerCase();
90
+ if (name === "exit" || name === "quit") return 0;
91
+ if (name === "help" || name === "?") {
92
+ io.out(helpText());
93
+ continue;
94
+ }
95
+ if (name === "model") {
96
+ io.out(`model: ${model(load())}${session.backend ? " (running)" : ""}
97
+ verify: ${session.verify ?? "none"} auto-approve: ${session.auto ? "on" : "off"}`);
98
+ continue;
99
+ }
100
+ if (name === "verify") {
101
+ const v = rest.join(" ").trim();
102
+ if (!v) io.out(session.verify ? `verify: ${session.verify}` : "verify: none. Set one with /verify npm test");
103
+ else if (v === "off") {
104
+ delete session.verify;
105
+ io.out("verify: off");
106
+ } else {
107
+ session.verify = v;
108
+ io.out(`verify: ${v}. It runs in a scratch copy after each task; changes are only offered when it passes.`);
109
+ }
110
+ continue;
111
+ }
112
+ if (name === "yes") {
113
+ session.auto = !session.auto;
114
+ io.out(session.auto ? "auto-approve ON: commands from the agent run without asking." : "auto-approve off.");
115
+ continue;
116
+ }
117
+ if (name === "undo") {
118
+ if (!session.lastCheckpoint) {
119
+ io.out("Nothing to undo in this session.");
120
+ continue;
121
+ }
122
+ const { id, dir } = session.lastCheckpoint;
123
+ if (await guarded(io, () => run(["rollback", id, "--dir", dir], session)) === 0) delete session.lastCheckpoint;
124
+ continue;
125
+ }
126
+ if (name === "tools") {
127
+ io.out(table(TOOLS.map((t) => [t.name, t.description.split(". ")[0] ?? ""])));
128
+ continue;
129
+ }
130
+ if (name === "cost") {
131
+ const u = session.usage;
132
+ io.out(`${u.promptTokens} prompt + ${u.completionTokens} generated tokens this session (reported by the model server; local models cost no money).`);
133
+ continue;
134
+ }
135
+ if (name === "history") {
136
+ io.out(session.notes.length ? session.notes.map((n, i) => `${i + 1}. ${n}`).join("\n") : "No tasks yet.");
137
+ continue;
138
+ }
139
+ if (name === "clear") {
140
+ session.notes.length = 0;
141
+ io.out("(session history cleared)");
142
+ continue;
143
+ }
144
+ if (name === "layers" || name === "cache") {
145
+ const c = load();
146
+ if (c) await guarded(io, async () => {
147
+ await engine(c, name);
148
+ return 0;
149
+ });
150
+ continue;
151
+ }
152
+ if (FORWARD[name]) {
153
+ await guarded(io, () => run([name, ...rest], session));
154
+ continue;
155
+ }
156
+ io.err(`unknown command /${word}. /help lists them.`);
157
+ }
158
+ } finally {
159
+ try {
160
+ await session.backend?.stop();
161
+ } catch {
162
+ }
163
+ }
164
+ }
165
+ async function guarded(io, f) {
166
+ try {
167
+ return await f();
168
+ } catch (e) {
169
+ io.err(e instanceof Error ? e.message : String(e));
170
+ return void 0;
171
+ }
172
+ }
173
+
174
+ export {
175
+ splitWords,
176
+ runRepl
177
+ };
178
+ //# sourceMappingURL=chunk-RBSFKJ6L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/repl.ts"],"sourcesContent":["import { TOOLS } from \"@clutor/core\";\nimport { loadConfig, ConfigError, type ClutorConfig } from \"./config\";\nimport { table } from \"./format\";\nimport type { Io, SessionState } from \"./cli\";\n\n/**\n * The interactive session that `clutor` opens when it is started with no command in a terminal. Text you type is a task for the coding\n * agent (its changes are held aside until you approve them); a line starting with `/` is a command. Most commands are the ordinary\n * clutor commands, so they behave exactly as they do outside the session.\n */\n\ntype Run = (argv: string[], session: SessionState) => Promise<number>;\ntype EngineView = (config: ClutorConfig, what: \"layers\" | \"cache\") => Promise<void>;\n\ninterface Slash { usage: string; help: string }\n\n/** Commands that are forwarded unchanged to the normal CLI. */\nconst FORWARD: Record<string, Slash> = {\n status: { usage: \"/status\", help: \"machine, model backend, memory plan, receipts\" },\n doctor: { usage: \"/doctor\", help: \"check this machine and the configuration\" },\n models: { usage: \"/models\", help: \"model catalog and the .gguf files in your models folder\" },\n plan: { usage: \"/plan <model> [--ram GiB]\", help: \"will it fit, where the memory goes, how fast it could possibly run\" },\n simulate: { usage: \"/simulate <model> [--cache GiB]\", help: \"replay a routing trace through the expert cache (synthetic)\" },\n inspect: { usage: \"/inspect <file.gguf>\", help: \"read a model file's header\" },\n bench: { usage: \"/bench [--file model.gguf]\", help: \"measure real disk reads; with the engine, compare cache strategies\" },\n stream: { usage: \"/stream <model.gguf>\", help: \"stream a model's experts through the native engine (synthetic routing)\" },\n pull: { usage: \"/pull [id]\", help: \"list or download a small test model\" },\n init: { usage: \"/init\", help: \"create clutor.config.json and a models folder here\" },\n backend: { usage: \"/backend\", help: \"show how llama-server would be started\" },\n ledger: { usage: \"/ledger\", help: \"verify the receipt log and list recent tasks and checkpoints\" },\n decide: { usage: '/decide --prompt \"<text>\" --choices \"a,b\"', help: \"one forward pass: probability of each choice\" },\n};\n\nconst OWN: Record<string, Slash> = {\n help: { usage: \"/help\", help: \"this list\" },\n model: { usage: \"/model\", help: \"which model this session talks to\" },\n verify: { usage: \"/verify [command|off]\", help: \"run this check after every task and only offer changes that pass it\" },\n yes: { usage: \"/yes\", help: \"toggle: run project commands without asking (only for projects you trust)\" },\n undo: { usage: \"/undo\", help: \"restore the files changed by the last applied task\" },\n tools: { usage: \"/tools\", help: \"what the agent can do\" },\n cost: { usage: \"/cost\", help: \"tokens used this session, as reported by the model server\" },\n history: { usage: \"/history\", help: \"tasks done in this session\" },\n clear: { usage: \"/clear\", help: \"forget this session's task history\" },\n layers: { usage: \"/layers\", help: \"where the model file's bytes are, per layer (native engine)\" },\n cache: { usage: \"/cache\", help: \"a streaming pass over the model file: cache, prefetch, disk waits\" },\n exit: { usage: \"/exit\", help: \"leave (also /quit, Ctrl-D)\" },\n};\n\n/** Splits a line into words, honouring single and double quotes. */\nexport function splitWords(line: string): string[] {\n const out: string[] = [];\n let cur = \"\", quote: string | undefined, has = false;\n for (const ch of line) {\n if (quote) { if (ch === quote) quote = undefined; else cur += ch; }\n else if (ch === '\"' || ch === \"'\") { quote = ch; has = true; }\n else if (/\\s/.test(ch)) { if (cur || has) out.push(cur); cur = \"\"; has = false; }\n else cur += ch;\n }\n if (cur || has) out.push(cur);\n return out;\n}\n\nfunction helpText(): string {\n const rows = [...Object.values(OWN), ...Object.values(FORWARD)].map((c) => [c.usage, c.help]);\n return `Type a task in plain words and the agent works on this folder. Its changes are held aside; you see the diff and approve before anything is written.\\n\\n${table(rows)}`;\n}\n\nexport async function runRepl(io: Io, run: Run, engine: EngineView): Promise<number> {\n const ask = io.prompt as NonNullable<Io[\"prompt\"]>;\n const session: SessionState = { auto: false, notes: [], usage: { promptTokens: 0, completionTokens: 0 } };\n const load = (): ClutorConfig | undefined => {\n try { return loadConfig(io.cwd).config; } catch (e) { io.err(e instanceof ConfigError || e instanceof Error ? e.message : String(e)); return undefined; }\n };\n const cfg = load();\n const model = (c: ClutorConfig | undefined): string => !c ? \"not configured\" : c.backend.kind === \"openai\" ? `${c.backend.model ?? \"default\"} at ${c.backend.baseUrl}` : c.backend.llamaServer.model ?? \"not configured (set backend.llamaServer.model)\";\n io.out(`clutor interactive session\\n folder ${io.cwd}\\n model ${model(cfg)}\\n Type a task, or /help for commands. Nothing is written to your files without your approval.`);\n\n try {\n for (;;) {\n const line = (await ask(\"clt> \")).trim();\n if (!line) continue;\n if (!line.startsWith(\"/\")) { await guarded(io, () => run([\"code\", line, \"--dir\", io.cwd], session)); continue; }\n\n const [word = \"\", ...rest] = splitWords(line.slice(1));\n const name = word.toLowerCase();\n if (name === \"exit\" || name === \"quit\") return 0;\n if (name === \"help\" || name === \"?\") { io.out(helpText()); continue; }\n if (name === \"model\") { io.out(`model: ${model(load())}${session.backend ? \" (running)\" : \"\"}\\nverify: ${session.verify ?? \"none\"} auto-approve: ${session.auto ? \"on\" : \"off\"}`); continue; }\n if (name === \"verify\") {\n const v = rest.join(\" \").trim();\n if (!v) io.out(session.verify ? `verify: ${session.verify}` : \"verify: none. Set one with /verify npm test\");\n else if (v === \"off\") { delete session.verify; io.out(\"verify: off\"); }\n else { session.verify = v; io.out(`verify: ${v}. It runs in a scratch copy after each task; changes are only offered when it passes.`); }\n continue;\n }\n if (name === \"yes\") { session.auto = !session.auto; io.out(session.auto ? \"auto-approve ON: commands from the agent run without asking.\" : \"auto-approve off.\"); continue; }\n if (name === \"undo\") {\n if (!session.lastCheckpoint) { io.out(\"Nothing to undo in this session.\"); continue; }\n const { id, dir } = session.lastCheckpoint;\n if (await guarded(io, () => run([\"rollback\", id, \"--dir\", dir], session)) === 0) delete session.lastCheckpoint;\n continue;\n }\n if (name === \"tools\") { io.out(table(TOOLS.map((t) => [t.name, t.description.split(\". \")[0] ?? \"\"]))); continue; }\n if (name === \"cost\") {\n const u = session.usage;\n io.out(`${u.promptTokens} prompt + ${u.completionTokens} generated tokens this session (reported by the model server; local models cost no money).`);\n continue;\n }\n if (name === \"history\") { io.out(session.notes.length ? session.notes.map((n, i) => `${i + 1}. ${n}`).join(\"\\n\") : \"No tasks yet.\"); continue; }\n if (name === \"clear\") { session.notes.length = 0; io.out(\"(session history cleared)\"); continue; }\n if (name === \"layers\" || name === \"cache\") {\n const c = load();\n if (c) await guarded(io, async () => { await engine(c, name); return 0; });\n continue;\n }\n if (FORWARD[name]) { await guarded(io, () => run([name, ...rest], session)); continue; }\n io.err(`unknown command /${word}. /help lists them.`);\n }\n } finally {\n try { await session.backend?.stop(); } catch { /* the server may already be gone */ }\n }\n}\n\nasync function guarded(io: Io, f: () => Promise<number>): Promise<number | undefined> {\n try { return await f(); } catch (e) { io.err(e instanceof Error ? e.message : String(e)); return undefined; }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,135 @@
1
+ // src/config.ts
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { join, resolve } from "path";
4
+ import { z } from "zod";
5
+ var CONFIG_FILE = "clutor.config.json";
6
+ var DEFAULT_PORT = 4320;
7
+ var ConfigSchema = z.object({
8
+ port: z.number().int().min(0).max(65535).default(DEFAULT_PORT),
9
+ host: z.string().min(1).default("127.0.0.1"),
10
+ /** Folder scanned for .gguf files. */
11
+ models: z.string().min(1).default("models"),
12
+ /** What answers chat requests. `none` still serves planning, inspection, and the agent's read-only tools. */
13
+ backend: z.object({
14
+ kind: z.enum(["none", "openai", "llama-server"]).default("none"),
15
+ /** `openai`: the base URL of any OpenAI-compatible server, ending in /v1. */
16
+ baseUrl: z.string().url().optional(),
17
+ model: z.string().optional(),
18
+ /** Name of the environment variable holding an API key (never put the key in this file). */
19
+ apiKeyEnv: z.string().optional(),
20
+ llamaServer: z.object({
21
+ /** Path to the llama-server binary; found on PATH if omitted. */
22
+ binary: z.string().optional(),
23
+ /** Path to the .gguf file to load. */
24
+ model: z.string().optional(),
25
+ port: z.number().int().min(1).max(65535).default(8081),
26
+ threads: z.number().int().min(1).max(512).optional(),
27
+ extraArgs: z.array(z.string()).default([])
28
+ }).strict().default({ port: 8081, extraArgs: [] })
29
+ }).strict().default({ kind: "none", llamaServer: { port: 8081, extraArgs: [] } }),
30
+ memory: z.object({
31
+ ramCeilingGiB: z.number().min(1).max(4096).optional(),
32
+ osHeadroomGiB: z.number().min(0).max(512).optional(),
33
+ contextTokens: z.number().int().min(256).max(2e6).default(8192),
34
+ kvBits: z.union([z.literal(16), z.literal(8), z.literal(4)]).default(16),
35
+ /** Disk read speed in GB/s if you know it; `clutor bench` measures it. */
36
+ diskGBps: z.number().min(0.01).max(100).optional()
37
+ }).strict().default({ contextTokens: 8192, kvBits: 16 }),
38
+ cache: z.object({
39
+ policy: z.enum(["lru", "wtinylfu", "adaptive"]).default("adaptive"),
40
+ smartRoute: z.object({ enabled: z.boolean().default(false), maxSubstitutions: z.number().int().min(0).max(8).default(2), candidates: z.number().int().min(1).max(64).default(12), maxDeltaP: z.number().min(0).max(1).default(0.015) }).strict().default({ enabled: false, maxSubstitutions: 2, candidates: 12, maxDeltaP: 0.015 })
41
+ }).strict().default({ policy: "adaptive", smartRoute: { enabled: false, maxSubstitutions: 2, candidates: 12, maxDeltaP: 0.015 } }),
42
+ agent: z.object({
43
+ toolMode: z.enum(["json", "native"]).default("json"),
44
+ maxSteps: z.number().int().min(1).max(200).default(30),
45
+ verifyCommand: z.string().max(500).optional(),
46
+ /** Run commands that execute project code without asking. Off by default; see docs/SECURITY.md. */
47
+ autoApproveProjectCommands: z.boolean().default(false),
48
+ commandTimeoutSeconds: z.number().int().min(5).max(3600).default(120),
49
+ /** Ask the model server to constrain each reply to the tool-call JSON Schema (llama.cpp turns it into a grammar). Replies are still validated. */
50
+ constrainToolCalls: z.boolean().default(true),
51
+ /** With llama-server, save the KV cache of the project-map prefix under .clutor/slots and restore it on the next run. */
52
+ prefixSnapshots: z.boolean().default(true)
53
+ }).strict().default({ toolMode: "json", maxSteps: 30, autoApproveProjectCommands: false, commandTimeoutSeconds: 120, constrainToolCalls: true, prefixSnapshots: true }),
54
+ /** Web origins allowed to call the server from a browser. The desktop app is always allowed. */
55
+ corsOrigins: z.array(z.string().url()).default([])
56
+ }).strict();
57
+ var ConfigError = class extends Error {
58
+ constructor(message) {
59
+ super(message);
60
+ this.name = "ConfigError";
61
+ }
62
+ };
63
+ function loadConfig(dir, file) {
64
+ const root = resolve(dir);
65
+ const path = file ? resolve(root, file) : join(root, CONFIG_FILE);
66
+ if (!existsSync(path)) {
67
+ if (file) throw new ConfigError(`${path}: no such file`);
68
+ return { config: ConfigSchema.parse({}), path: null, root };
69
+ }
70
+ let raw;
71
+ try {
72
+ raw = JSON.parse(readFileSync(path, "utf8"));
73
+ } catch (e) {
74
+ throw new ConfigError(`${path}: ${e instanceof Error ? e.message : "cannot be read"}`);
75
+ }
76
+ const parsed = ConfigSchema.safeParse(raw);
77
+ if (!parsed.success) throw new ConfigError(parsed.error.issues.map((i) => `${path}: ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n"));
78
+ return { config: parsed.data, path, root };
79
+ }
80
+
81
+ // src/format.ts
82
+ import { fmtGiB } from "@clutor/core";
83
+ function table(rows, header) {
84
+ const all = header ? [header, ...rows] : rows;
85
+ const widths = all[0]?.map((_, i) => Math.max(...all.map((r) => (r[i] ?? "").length))) ?? [];
86
+ const line = (r) => r.map((c, i) => c.padEnd(widths[i] ?? 0)).join(" ").trimEnd();
87
+ return [...header ? [line(header), widths.map((w) => "-".repeat(w)).join(" ")] : [], ...rows.map(line)].join("\n");
88
+ }
89
+ var pct = (x) => `${(x * 100).toFixed(1)}%`;
90
+ var tps = (x) => x === Infinity ? "no disk limit" : x >= 10 ? x.toFixed(1) : x >= 0.1 ? x.toFixed(2) : x.toFixed(3);
91
+ function formatPlan(name, quant, p) {
92
+ const out = [];
93
+ out.push(`${name}${quant ? ` (${quant})` : ""}: ${p.fits ? p.strategy.replace(/-/g, " ") : "DOES NOT FIT"} within ${fmtGiB(p.ceilingBytes)}`);
94
+ out.push("", table(p.components.filter((c) => c.bytes > 0).map((c) => [fmtGiB(c.bytes), c.label])));
95
+ if (p.fits && p.strategy !== "fully-resident") {
96
+ out.push("", `Expert cache: ${p.expertCache.slots} of ${p.expertCache.totalExperts} experts (${pct(p.expertCache.fractionOfExperts)}); one token uses ${p.expertCache.slotsPerToken}.`);
97
+ out.push(`Per token, from disk: ${fmtGiB(p.perToken.denseStreamBytes)} dense weights + ${fmtGiB(p.perToken.activeExpertBytes)} of active experts (before the cache helps).`);
98
+ out.push("", `Fastest the disk allows (${(p.disk.bytesPerSec / 2 ** 30).toFixed(1)} GiB/s, ${p.disk.measured ? "measured" : "assumed"}):`);
99
+ out.push(table(p.speed.map((s) => [`cache hit rate ${pct(s.hitRate)}`, `at most ${tps(s.maxTokensPerSec)} tokens/s`])));
100
+ }
101
+ for (const w of p.warnings) out.push("", `! ${w}`);
102
+ return out.join("\n");
103
+ }
104
+ function formatGates(g) {
105
+ const mark = { pass: "ok ", warn: "warn", fail: "FAIL", skipped: "-- " };
106
+ return g.map((x) => `${mark[x.status]} ${x.id} ${x.name}: ${x.message}`).join("\n");
107
+ }
108
+ function formatSimulation(base, c) {
109
+ const row = (label, f) => [label, f(base), f(c)];
110
+ return table([
111
+ row("cache hit rate", (m) => pct(m.hitRate)),
112
+ row("read from disk per token", (m) => `${(m.bytesPerToken / 2 ** 30).toFixed(2)} GiB`),
113
+ row("read requests per token", (m) => (m.requests / Math.max(1, m.tokens)).toFixed(0)),
114
+ row("time waiting for disk", (m) => pct(m.stallSec / Math.max(1e-9, m.totalSec))),
115
+ row("modelled speed", (m) => `${tps(m.tokensPerSec)} tokens/s`),
116
+ row("prefetches used / issued", (m) => `${m.prefetchUsed} / ${m.prefetchIssued}`),
117
+ row("experts replaced by stand-ins", (m) => pct(1 - m.exactShare)),
118
+ row("router probability served exactly", (m) => pct(m.exactMassShare))
119
+ ], ["", "baseline (LRU)", "clutor"]);
120
+ }
121
+
122
+ export {
123
+ CONFIG_FILE,
124
+ DEFAULT_PORT,
125
+ ConfigSchema,
126
+ ConfigError,
127
+ loadConfig,
128
+ table,
129
+ pct,
130
+ tps,
131
+ formatPlan,
132
+ formatGates,
133
+ formatSimulation
134
+ };
135
+ //# sourceMappingURL=chunk-U5GHVJJO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/format.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { z } from \"zod\";\n\nexport const CONFIG_FILE = \"clutor.config.json\";\nexport const DEFAULT_PORT = 4320;\n\nexport const ConfigSchema = z\n .object({\n port: z.number().int().min(0).max(65535).default(DEFAULT_PORT),\n host: z.string().min(1).default(\"127.0.0.1\"),\n /** Folder scanned for .gguf files. */\n models: z.string().min(1).default(\"models\"),\n /** What answers chat requests. `none` still serves planning, inspection, and the agent's read-only tools. */\n backend: z\n .object({\n kind: z.enum([\"none\", \"openai\", \"llama-server\"]).default(\"none\"),\n /** `openai`: the base URL of any OpenAI-compatible server, ending in /v1. */\n baseUrl: z.string().url().optional(),\n model: z.string().optional(),\n /** Name of the environment variable holding an API key (never put the key in this file). */\n apiKeyEnv: z.string().optional(),\n llamaServer: z\n .object({\n /** Path to the llama-server binary; found on PATH if omitted. */\n binary: z.string().optional(),\n /** Path to the .gguf file to load. */\n model: z.string().optional(),\n port: z.number().int().min(1).max(65535).default(8081),\n threads: z.number().int().min(1).max(512).optional(),\n extraArgs: z.array(z.string()).default([]),\n })\n .strict()\n .default({ port: 8081, extraArgs: [] }),\n })\n .strict()\n .default({ kind: \"none\", llamaServer: { port: 8081, extraArgs: [] } }),\n memory: z\n .object({\n ramCeilingGiB: z.number().min(1).max(4096).optional(),\n osHeadroomGiB: z.number().min(0).max(512).optional(),\n contextTokens: z.number().int().min(256).max(2_000_000).default(8192),\n kvBits: z.union([z.literal(16), z.literal(8), z.literal(4)]).default(16),\n /** Disk read speed in GB/s if you know it; `clutor bench` measures it. */\n diskGBps: z.number().min(0.01).max(100).optional(),\n })\n .strict()\n .default({ contextTokens: 8192, kvBits: 16 }),\n cache: z\n .object({\n policy: z.enum([\"lru\", \"wtinylfu\", \"adaptive\"]).default(\"adaptive\"),\n smartRoute: z.object({ enabled: z.boolean().default(false), maxSubstitutions: z.number().int().min(0).max(8).default(2), candidates: z.number().int().min(1).max(64).default(12), maxDeltaP: z.number().min(0).max(1).default(0.015) }).strict().default({ enabled: false, maxSubstitutions: 2, candidates: 12, maxDeltaP: 0.015 }),\n })\n .strict()\n .default({ policy: \"adaptive\", smartRoute: { enabled: false, maxSubstitutions: 2, candidates: 12, maxDeltaP: 0.015 } }),\n agent: z\n .object({\n toolMode: z.enum([\"json\", \"native\"]).default(\"json\"),\n maxSteps: z.number().int().min(1).max(200).default(30),\n verifyCommand: z.string().max(500).optional(),\n /** Run commands that execute project code without asking. Off by default; see docs/SECURITY.md. */\n autoApproveProjectCommands: z.boolean().default(false),\n commandTimeoutSeconds: z.number().int().min(5).max(3600).default(120),\n /** Ask the model server to constrain each reply to the tool-call JSON Schema (llama.cpp turns it into a grammar). Replies are still validated. */\n constrainToolCalls: z.boolean().default(true),\n /** With llama-server, save the KV cache of the project-map prefix under .clutor/slots and restore it on the next run. */\n prefixSnapshots: z.boolean().default(true),\n })\n .strict()\n .default({ toolMode: \"json\", maxSteps: 30, autoApproveProjectCommands: false, commandTimeoutSeconds: 120, constrainToolCalls: true, prefixSnapshots: true }),\n /** Web origins allowed to call the server from a browser. The desktop app is always allowed. */\n corsOrigins: z.array(z.string().url()).default([]),\n })\n .strict();\n\nexport type ClutorConfig = z.infer<typeof ConfigSchema>;\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\nexport function loadConfig(dir: string, file?: string): { config: ClutorConfig; path: string | null; root: string } {\n const root = resolve(dir);\n const path = file ? resolve(root, file) : join(root, CONFIG_FILE);\n if (!existsSync(path)) {\n if (file) throw new ConfigError(`${path}: no such file`);\n return { config: ConfigSchema.parse({}), path: null, root };\n }\n let raw: unknown;\n try { raw = JSON.parse(readFileSync(path, \"utf8\")); } catch (e) { throw new ConfigError(`${path}: ${e instanceof Error ? e.message : \"cannot be read\"}`); }\n const parsed = ConfigSchema.safeParse(raw);\n if (!parsed.success) throw new ConfigError(parsed.error.issues.map((i) => `${path}: ${i.path.join(\".\") || \"(root)\"}: ${i.message}`).join(\"\\n\"));\n return { config: parsed.data, path, root };\n}\n","import { fmtGiB, type GateResult, type MemoryPlan, type StreamMetrics } from \"@clutor/core\";\n\nexport function table(rows: string[][], header?: string[]): string {\n const all = header ? [header, ...rows] : rows;\n const widths = all[0]?.map((_, i) => Math.max(...all.map((r) => (r[i] ?? \"\").length))) ?? [];\n const line = (r: string[]): string => r.map((c, i) => c.padEnd(widths[i] ?? 0)).join(\" \").trimEnd();\n return [...(header ? [line(header), widths.map((w) => \"-\".repeat(w)).join(\" \")] : []), ...rows.map(line)].join(\"\\n\");\n}\n\nexport const pct = (x: number): string => `${(x * 100).toFixed(1)}%`;\nexport const tps = (x: number): string => (x === Infinity ? \"no disk limit\" : x >= 10 ? x.toFixed(1) : x >= 0.1 ? x.toFixed(2) : x.toFixed(3));\n\nexport function formatPlan(name: string, quant: string | undefined, p: MemoryPlan): string {\n const out: string[] = [];\n out.push(`${name}${quant ? ` (${quant})` : \"\"}: ${p.fits ? p.strategy.replace(/-/g, \" \") : \"DOES NOT FIT\"} within ${fmtGiB(p.ceilingBytes)}`);\n out.push(\"\", table(p.components.filter((c) => c.bytes > 0).map((c) => [fmtGiB(c.bytes), c.label])));\n if (p.fits && p.strategy !== \"fully-resident\") {\n out.push(\"\", `Expert cache: ${p.expertCache.slots} of ${p.expertCache.totalExperts} experts (${pct(p.expertCache.fractionOfExperts)}); one token uses ${p.expertCache.slotsPerToken}.`);\n out.push(`Per token, from disk: ${fmtGiB(p.perToken.denseStreamBytes)} dense weights + ${fmtGiB(p.perToken.activeExpertBytes)} of active experts (before the cache helps).`);\n out.push(\"\", `Fastest the disk allows (${(p.disk.bytesPerSec / 2 ** 30).toFixed(1)} GiB/s, ${p.disk.measured ? \"measured\" : \"assumed\"}):`);\n out.push(table(p.speed.map((s) => [`cache hit rate ${pct(s.hitRate)}`, `at most ${tps(s.maxTokensPerSec)} tokens/s`])));\n }\n for (const w of p.warnings) out.push(\"\", `! ${w}`);\n return out.join(\"\\n\");\n}\n\nexport function formatGates(g: GateResult[]): string {\n const mark = { pass: \"ok \", warn: \"warn\", fail: \"FAIL\", skipped: \"-- \" } as const;\n return g.map((x) => `${mark[x.status]} ${x.id} ${x.name}: ${x.message}`).join(\"\\n\");\n}\n\nexport function formatSimulation(base: StreamMetrics, c: StreamMetrics): string {\n const row = (label: string, f: (m: StreamMetrics) => string): string[] => [label, f(base), f(c)];\n return table([\n row(\"cache hit rate\", (m) => pct(m.hitRate)),\n row(\"read from disk per token\", (m) => `${(m.bytesPerToken / 2 ** 30).toFixed(2)} GiB`),\n row(\"read requests per token\", (m) => (m.requests / Math.max(1, m.tokens)).toFixed(0)),\n row(\"time waiting for disk\", (m) => pct(m.stallSec / Math.max(1e-9, m.totalSec))),\n row(\"modelled speed\", (m) => `${tps(m.tokensPerSec)} tokens/s`),\n row(\"prefetches used / issued\", (m) => `${m.prefetchUsed} / ${m.prefetchIssued}`),\n row(\"experts replaced by stand-ins\", (m) => pct(1 - m.exactShare)),\n row(\"router probability served exactly\", (m) => pct(m.exactMassShare)),\n ], [\"\", \"baseline (LRU)\", \"clutor\"]);\n}\n"],"mappings":"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","names":[]}