@clutor/cli 0.1.1 → 0.2.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.
@@ -6,6 +6,38 @@ import {
6
6
 
7
7
  // src/repl.ts
8
8
  import { TOOLS } from "@clutor/core";
9
+
10
+ // src/banner.ts
11
+ var GLYPHS = {
12
+ C: [" \u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D", "\u2588\u2588\u2551 ", "\u2588\u2588\u2551 ", "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557", " \u255A\u2550\u2550\u2550\u2550\u2550\u255D"],
13
+ L: ["\u2588\u2588\u2557 ", "\u2588\u2588\u2551 ", "\u2588\u2588\u2551 ", "\u2588\u2588\u2551 ", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"],
14
+ U: ["\u2588\u2588\u2557 \u2588\u2588\u2557", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", " \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],
15
+ T: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D", " \u2588\u2588\u2551 ", " \u2588\u2588\u2551 ", " \u2588\u2588\u2551 ", " \u255A\u2550\u255D "],
16
+ O: [" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", " \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],
17
+ R: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2550\u255D \u255A\u2550\u255D"]
18
+ };
19
+ var WORDMARK = Array.from({ length: 6 }, (_, row) => [..."CLUTOR"].map((ch) => GLYPHS[ch]?.[row] ?? "").join(""));
20
+ var WORDMARK_WIDTH = Math.max(...WORDMARK.map((r) => r.length));
21
+ var GRADIENT = [51, 45, 39, 33, 63, 99];
22
+ function useColor(env = process.env, isTTY = !!process.stdout.isTTY) {
23
+ return isTTY && !("NO_COLOR" in env) && env["TERM"] !== "dumb";
24
+ }
25
+ function banner(o) {
26
+ const paint = (code, s) => o.color ? `\x1B[${typeof code === "number" ? `38;5;${code}` : code}m${s}\x1B[0m` : s;
27
+ const dim = (s) => paint("2", s);
28
+ const wide = (o.columns ?? 80) >= WORDMARK_WIDTH + 2;
29
+ const lines = [""];
30
+ if (wide) lines.push(...WORDMARK.map((r, i) => paint(GRADIENT[i] ?? 99, r)));
31
+ else lines.push(paint(45, "C L U T O R"));
32
+ lines.push("", `${paint("1", "clutor interactive session")} ${dim(`v${o.version}`)}`, dim("big models on small machines, with honest numbers"), "");
33
+ lines.push(` ${dim("folder")} ${o.folder}`, ` ${dim("model")} ${o.model}`, "");
34
+ lines.push(` ${dim("tips")} type a task in plain words and the agent works on this folder`);
35
+ lines.push(` its changes are held aside; nothing is written without your approval`);
36
+ lines.push(` /help lists commands \xB7 /plan <model> \xB7 /cache \xB7 /exit`, "");
37
+ return lines.join("\n");
38
+ }
39
+
40
+ // src/repl.ts
9
41
  var FORWARD = {
10
42
  status: { usage: "/status", help: "machine, model backend, memory plan, receipts" },
11
43
  doctor: { usage: "/doctor", help: "check this machine and the configuration" },
@@ -60,7 +92,7 @@ function helpText() {
60
92
 
61
93
  ${table(rows)}`;
62
94
  }
63
- async function runRepl(io, run, engine) {
95
+ async function runRepl(io, run, engine, version = "dev") {
64
96
  const ask = io.prompt;
65
97
  const session = { auto: false, notes: [], usage: { promptTokens: 0, completionTokens: 0 } };
66
98
  const load = () => {
@@ -73,10 +105,7 @@ async function runRepl(io, run, engine) {
73
105
  };
74
106
  const cfg = load();
75
107
  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.`);
108
+ io.out(banner({ version, folder: io.cwd, model: model(cfg), columns: process.stdout.columns ?? 80, color: useColor() }));
80
109
  try {
81
110
  for (; ; ) {
82
111
  const line = (await ask("clt> ")).trim();
@@ -175,4 +204,4 @@ export {
175
204
  splitWords,
176
205
  runRepl
177
206
  };
178
- //# sourceMappingURL=chunk-RBSFKJ6L.js.map
207
+ //# sourceMappingURL=chunk-BZZUO7XX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/repl.ts","../src/banner.ts"],"sourcesContent":["import { TOOLS } from \"@clutor/core\";\nimport { loadConfig, ConfigError, type ClutorConfig } from \"./config\";\nimport { banner, useColor } from \"./banner\";\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, version = \"dev\"): 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(banner({ version, folder: io.cwd, model: model(cfg), columns: process.stdout.columns ?? 80, color: useColor() }));\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","/**\n * The welcome screen of the interactive session: the name in large block letters, then where you are and what to type.\n * Colour is used only on a terminal that asked for it (not when piped, not with NO_COLOR); the text is the same either way.\n */\n\nconst GLYPHS: Record<string, string[]> = {\n C: [\" ██████╗\", \"██╔════╝\", \"██║ \", \"██║ \", \"╚██████╗\", \" ╚═════╝\"],\n L: [\"██╗ \", \"██║ \", \"██║ \", \"██║ \", \"███████╗\", \"╚══════╝\"],\n U: [\"██╗ ██╗\", \"██║ ██║\", \"██║ ██║\", \"██║ ██║\", \"╚██████╔╝\", \" ╚═════╝ \"],\n T: [\"████████╗\", \"╚══██╔══╝\", \" ██║ \", \" ██║ \", \" ██║ \", \" ╚═╝ \"],\n O: [\" ██████╗ \", \"██╔═══██╗\", \"██║ ██║\", \"██║ ██║\", \"╚██████╔╝\", \" ╚═════╝ \"],\n R: [\"██████╗ \", \"██╔══██╗\", \"██████╔╝\", \"██╔══██╗\", \"██║ ██║\", \"╚═╝ ╚═╝\"],\n};\n\n/** The six rows of \"CLUTOR\" in block letters. */\nexport const WORDMARK: string[] = Array.from({ length: 6 }, (_, row) => [...\"CLUTOR\"].map((ch) => GLYPHS[ch]?.[row] ?? \"\").join(\"\"));\nexport const WORDMARK_WIDTH = Math.max(...WORDMARK.map((r) => r.length));\n\n/** Top to bottom: cyan, through blue, to violet (xterm 256-colour codes). */\nconst GRADIENT = [51, 45, 39, 33, 63, 99];\n\nexport interface BannerOptions {\n version: string;\n folder: string;\n model: string;\n /** Width of the terminal in columns; a narrower terminal than the block letters gets a one-line title. */\n columns?: number;\n color?: boolean;\n}\n\nexport function useColor(env: NodeJS.ProcessEnv = process.env, isTTY: boolean = !!process.stdout.isTTY): boolean {\n return isTTY && !(\"NO_COLOR\" in env) && env[\"TERM\"] !== \"dumb\";\n}\n\nexport function banner(o: BannerOptions): string {\n const paint = (code: number | string, s: string): string => (o.color ? `\\x1b[${typeof code === \"number\" ? `38;5;${code}` : code}m${s}\\x1b[0m` : s);\n const dim = (s: string): string => paint(\"2\", s);\n const wide = (o.columns ?? 80) >= WORDMARK_WIDTH + 2;\n const lines: string[] = [\"\"];\n if (wide) lines.push(...WORDMARK.map((r, i) => paint(GRADIENT[i] ?? 99, r)));\n else lines.push(paint(45, \"C L U T O R\"));\n lines.push(\"\", `${paint(\"1\", \"clutor interactive session\")} ${dim(`v${o.version}`)}`, dim(\"big models on small machines, with honest numbers\"), \"\");\n lines.push(` ${dim(\"folder\")} ${o.folder}`, ` ${dim(\"model\")} ${o.model}`, \"\");\n lines.push(` ${dim(\"tips\")} type a task in plain words and the agent works on this folder`);\n lines.push(` its changes are held aside; nothing is written without your approval`);\n lines.push(` /help lists commands · /plan <model> · /cache · /exit`, \"\");\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -211,16 +211,13 @@ interface LocalModel {
211
211
  bytes: number;
212
212
  }
213
213
  declare function listLocalModels(dir: string): Promise<LocalModel[]>;
214
- /**
215
- * Real models small enough to download and test with. Sizes are the file sizes Hugging Face reported on 2026-10-30; the download
216
- * is checked against the size the server announces, and nothing is fetched unless you run `clutor pull`.
217
- */
218
214
  interface Download {
219
215
  id: string;
220
216
  catalogId: string;
221
217
  repo: string;
222
218
  file: string;
223
219
  bytes: number;
220
+ sha256: string;
224
221
  note: string;
225
222
  }
226
223
  declare const DOWNLOADS: Download[];
@@ -228,10 +225,15 @@ declare const downloadUrl: (d: Download) => string;
228
225
  declare class DownloadError extends Error {
229
226
  constructor(message: string);
230
227
  }
231
- /** Downloads `url` to `dest` through a `.part` file, refusing to start without room for it and refusing a size the server did not announce. */
228
+ /**
229
+ * Downloads `url` to `dest` through a `.part` file. An interrupted download keeps its `.part` and the next call resumes it with a Range request. It refuses
230
+ * to start without room for the rest, refuses a size the server did not announce, and (with `expectedSha256`) deletes the file and fails unless the whole
231
+ * file hashes to the published value.
232
+ */
232
233
  declare function downloadFile(url: string, dest: string, opts?: {
233
234
  fetch?: typeof fetch;
234
235
  expectedBytes?: number;
236
+ expectedSha256?: string;
235
237
  onProgress?: (done: number, total: number) => void;
236
238
  }): Promise<number>;
237
239
 
@@ -389,6 +391,7 @@ interface Rpc {
389
391
  }
390
392
  declare function handleMcp(req: Rpc, ctx: {
391
393
  isRoot(dir: string): Promise<string | null>;
394
+ version?: string;
392
395
  }): Promise<{
393
396
  status: number;
394
397
  body: unknown;
@@ -396,6 +399,8 @@ declare function handleMcp(req: Rpc, ctx: {
396
399
 
397
400
  interface Io {
398
401
  out(line: string): void;
402
+ /** Writes text with no newline added (a model's reply as it is generated). Falls back to `out` when absent. */
403
+ write?(text: string): void;
399
404
  err(line: string): void;
400
405
  cwd: string;
401
406
  /** Asks the user a question and returns the answer. Absent when there is no terminal. */
@@ -436,7 +441,7 @@ type Run = (argv: string[], session: SessionState) => Promise<number>;
436
441
  type EngineView = (config: ClutorConfig, what: "layers" | "cache") => Promise<void>;
437
442
  /** Splits a line into words, honouring single and double quotes. */
438
443
  declare function splitWords(line: string): string[];
439
- declare function runRepl(io: Io, run: Run, engine: EngineView): Promise<number>;
444
+ declare function runRepl(io: Io, run: Run, engine: EngineView, version?: string): Promise<number>;
440
445
 
441
446
  /** Where llama-server saves KV-cache snapshots. They contain the project map the model read, so they stay in the user's own home folder. */
442
447
  declare const slotDir: () => string;
@@ -508,6 +513,8 @@ interface StreamOptions {
508
513
  computeUs?: number;
509
514
  windowPct?: number;
510
515
  noOsCache?: boolean;
516
+ /** A route trace recorded by `generate --trace`: the model's real router scores, replayed instead of the synthetic router. */
517
+ trace?: string;
511
518
  /** auto: keep the dense weights in RAM when they fit in a quarter of it; ring: re-read two layers at a time every token. */
512
519
  dense?: "auto" | "resident" | "ring";
513
520
  /** kernel: a Q4_0 x Q8_0 dot-product kernel over every loaded byte (default); checksum: a cheap hash, so the run measures I/O alone. */
@@ -610,6 +617,18 @@ interface StreamReport {
610
617
  }
611
618
  declare function engineInfo(bin: string, file: string): Promise<EngineInfo>;
612
619
  declare function engineStream(bin: string, file: string, o?: StreamOptions, timeoutMs?: number): Promise<StreamReport>;
620
+ /** Runs an engine command whose output should reach the user as it is produced (`generate` writes each token as it is chosen). */
621
+ declare function engineRun(bin: string, args: string[], onOut: (chunk: string) => void, timeoutMs?: number): Promise<{
622
+ code: number;
623
+ stderr: string;
624
+ }>;
625
+ /** The platform name used in release asset names, or undefined where no prebuilt binary is published. */
626
+ declare function enginePlatform(): string | undefined;
627
+ /**
628
+ * Installs the prebuilt engine for this platform from the GitHub release of `version`: downloads SHA256SUMS and the tarball, refuses it unless the
629
+ * SHA-256 matches, unpacks it and puts the binary in `dest` (default ~/.clutor/bin). Returns the path of the installed binary.
630
+ */
631
+ declare function installPrebuilt(version: string, dest?: string): Promise<string>;
613
632
 
614
- export { AgentSession, BackendManager, CONFIG_FILE, ConfigError, ConfigSchema, DEFAULT_PORT, DOWNLOADS, DecideError, DownloadError, EngineError, ModelError, SlotSnapshots, UsageError, agentLlm, buildEngine, buildLlamaArgs, createClutorServer, downloadFile, downloadUrl, engineInfo, engineSourceDir, engineStream, ensureSlotDir, findEngine, findLlamaServer, flagBool, flagInt, flagString, formatGates, formatPlan, formatSimulation, fromOpenAiReply, handleMcp, isInside, listLocalModels, loadConfig, main, normaliseRoot, parseArgs, parseSse, pct, probeLlamaServer, resolveModel, run, runDecide, runRepl, sealDecision, slotDir, splitWords, startBackendIfConfigured, streamToOllama, table, toOpenAiBody, tps, version, withPrefixSnapshots };
633
+ export { AgentSession, BackendManager, CONFIG_FILE, ConfigError, ConfigSchema, DEFAULT_PORT, DOWNLOADS, DecideError, DownloadError, EngineError, ModelError, SlotSnapshots, UsageError, agentLlm, buildEngine, buildLlamaArgs, createClutorServer, downloadFile, downloadUrl, engineInfo, enginePlatform, engineRun, engineSourceDir, engineStream, ensureSlotDir, findEngine, findLlamaServer, flagBool, flagInt, flagString, formatGates, formatPlan, formatSimulation, fromOpenAiReply, handleMcp, installPrebuilt, isInside, listLocalModels, loadConfig, main, normaliseRoot, parseArgs, parseSse, pct, probeLlamaServer, resolveModel, run, runDecide, runRepl, sealDecision, slotDir, splitWords, startBackendIfConfigured, streamToOllama, table, toOpenAiBody, tps, version, withPrefixSnapshots };
615
634
  export type { BackendState, ClutorConfig, ClutorServer, DecideEndpoint, DecideResult, Download, EngineInfo, Io, LlamaArgsInput, LlamaCapabilities, LocalModel, OllamaChatRequest, OllamaMessage, ParsedArgs, ResolvedModel, ServerOptions, SessionEvent, SessionInfo, SessionState, StreamOptions, StreamReport };
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  downloadFile,
17
17
  downloadUrl,
18
18
  engineInfo,
19
+ enginePlatform,
20
+ engineRun,
19
21
  engineSourceDir,
20
22
  engineStream,
21
23
  ensureSlotDir,
@@ -26,6 +28,7 @@ import {
26
28
  flagString,
27
29
  fromOpenAiReply,
28
30
  handleMcp,
31
+ installPrebuilt,
29
32
  isInside,
30
33
  listLocalModels,
31
34
  main,
@@ -43,11 +46,11 @@ import {
43
46
  toOpenAiBody,
44
47
  version,
45
48
  withPrefixSnapshots
46
- } from "./chunk-YL63VENN.js";
49
+ } from "./chunk-2FBPYNHP.js";
47
50
  import {
48
51
  runRepl,
49
52
  splitWords
50
- } from "./chunk-RBSFKJ6L.js";
53
+ } from "./chunk-BZZUO7XX.js";
51
54
  import {
52
55
  CONFIG_FILE,
53
56
  ConfigError,
@@ -83,6 +86,8 @@ export {
83
86
  downloadFile,
84
87
  downloadUrl,
85
88
  engineInfo,
89
+ enginePlatform,
90
+ engineRun,
86
91
  engineSourceDir,
87
92
  engineStream,
88
93
  ensureSlotDir,
@@ -96,6 +101,7 @@ export {
96
101
  formatSimulation,
97
102
  fromOpenAiReply,
98
103
  handleMcp,
104
+ installPrebuilt,
99
105
  isInside,
100
106
  listLocalModels,
101
107
  loadConfig,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  runRepl,
3
3
  splitWords
4
- } from "./chunk-RBSFKJ6L.js";
4
+ } from "./chunk-BZZUO7XX.js";
5
5
  import "./chunk-U5GHVJJO.js";
6
6
  export {
7
7
  runRepl,
8
8
  splitWords
9
9
  };
10
- //# sourceMappingURL=repl-OVODHQI5.js.map
10
+ //# sourceMappingURL=repl-DOWMDXFR.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clutor/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "The clutor command and local server: plan whether a model fits your machine, inspect GGUF files, simulate expert streaming, run a sandboxed coding agent, and serve an OpenAI- and Ollama-compatible API on localhost in front of llama.cpp.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,6 +13,6 @@
13
13
  "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
14
14
  "files": ["dist", "c/src", "c/Makefile", "README.md", "LICENSE"],
15
15
  "engines": { "node": ">=20" },
16
- "dependencies": { "@clutor/core": "^0.1.1", "zod": "^4.0.0" },
16
+ "dependencies": { "@clutor/core": "^0.2.0", "zod": "^4.0.0" },
17
17
  "scripts": { "prepack": "node ../../scripts/pack-engine.mjs", "postpack": "node ../../scripts/pack-engine.mjs clean", "build": "tsup", "typecheck": "tsc --noEmit", "test": "vitest run" }
18
18
  }
@@ -1 +0,0 @@
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":[]}