pudu-ai 0.2.12 → 0.2.14

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +263 -191
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -836,6 +836,10 @@ var en = {
836
836
  aboutTasks: "Asks what you want (code, video, image, transcription, chat) and returns small OpenCode-style harnesses per model, in this language. Use it to try a model on one concrete job.",
837
837
  aboutBenchmark: "Runs llama-bench on a local GGUF and records measured t/s plus system telemetry. Use it when you need real numbers, not estimates.",
838
838
  aboutCompare: "Compares saved measured runs (speed, memory, power). Quality is never inferred from speed. Use it after two or more benchmarks.",
839
+ compareHow: "[C] Compare needs two measured llama-bench runs of different models. Press [B], pick a model, wait; repeat with another; then [C]. Quality is not a speed score.",
840
+ compareNeedBench: "Need 2+ measured models. Press [B] to benchmark first.",
841
+ reqOllama: "install: https://ollama.com/download or brew install ollama",
842
+ reqLmStudio: "install: https://lmstudio.ai",
839
843
  aboutHistory: "Shows local benchmark files in ~/.pudu-ai/benchmarks. Use it to replay what this machine actually did.",
840
844
  aboutDoctor: "Checks Node, Apple Silicon/Metal, Ollama, llama-bench, and the CanIRun API. Use it to see what is missing before a bench or launch.",
841
845
  aboutReport: "Writes the latest measured run as Markdown. Use it to paste results into GitHub or notes.",
@@ -1021,6 +1025,10 @@ var es = {
1021
1025
  aboutTasks: "Pregunta qu\xE9 quieres (c\xF3digo, v\xEDdeo, imagen, transcripci\xF3n, chat) y devuelve harnesses peque\xF1os estilo OpenCode por modelo, en este idioma. Sirve para probar un modelo en una tarea concreta.",
1022
1026
  aboutBenchmark: "Ejecuta llama-bench sobre un GGUF local y guarda t/s medidos m\xE1s telemetr\xEDa. Sirve cuando necesitas n\xFAmeros reales, no estimaciones.",
1023
1027
  aboutCompare: "Compara corridas medidas (velocidad, memoria, potencia). La calidad no se infiere de la velocidad. Sirve despu\xE9s de dos o m\xE1s benchmarks.",
1028
+ compareHow: "[C] Comparar necesita dos corridas medidas de llama-bench en modelos distintos. Pulsa [B], elige un modelo, espera; repite con otro; luego [C]. La calidad no es un score de velocidad.",
1029
+ compareNeedBench: "Hacen falta 2+ modelos medidos. Pulsa [B] para hacer un benchmark primero.",
1030
+ reqOllama: "instalar: https://ollama.com/download o brew install ollama",
1031
+ reqLmStudio: "instalar: https://lmstudio.ai",
1024
1032
  aboutHistory: "Muestra los JSON locales en ~/.pudu-ai/benchmarks. Sirve para ver qu\xE9 hizo realmente esta m\xE1quina.",
1025
1033
  aboutDoctor: "Revisa Node, Apple Silicon/Metal, Ollama, llama-bench y la API de CanIRun. Sirve para ver qu\xE9 falta antes de un bench o un launch.",
1026
1034
  aboutReport: "Exporta la \xFAltima corrida medida en Markdown. Sirve para pegar resultados en GitHub o notas.",
@@ -1441,9 +1449,27 @@ async function listBenchmarks() {
1441
1449
 
1442
1450
  // src/integrations/agents.ts
1443
1451
  var AGENTS = [
1444
- { id: "opencode", label: "OpenCode", bin: "opencode" },
1445
- { id: "hermes", label: "Hermes", bin: "hermes" },
1446
- { id: "openclaw", label: "OpenClaw", bin: "openclaw" }
1452
+ {
1453
+ id: "opencode",
1454
+ label: "OpenCode",
1455
+ bin: "opencode",
1456
+ launch: "ollama launch opencode",
1457
+ docs: "https://docs.ollama.com/integrations/opencode"
1458
+ },
1459
+ {
1460
+ id: "hermes",
1461
+ label: "Hermes",
1462
+ bin: "hermes",
1463
+ launch: "ollama launch hermes",
1464
+ docs: "https://docs.ollama.com/integrations/hermes"
1465
+ },
1466
+ {
1467
+ id: "openclaw",
1468
+ label: "OpenClaw",
1469
+ bin: "openclaw",
1470
+ launch: "ollama launch openclaw",
1471
+ docs: "https://docs.ollama.com/integrations/openclaw"
1472
+ }
1447
1473
  ];
1448
1474
  async function detectAgents() {
1449
1475
  const out = [];
@@ -1986,10 +2012,10 @@ import { render } from "ink";
1986
2012
 
1987
2013
  // src/tui/App.tsx
1988
2014
  import { Box as Box11, useApp as useApp2, useInput as useInput4 } from "ink";
1989
- import { useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "react";
2015
+ import { useMemo as useMemo2, useRef as useRef2, useState as useState5 } from "react";
1990
2016
 
1991
2017
  // src/tui/views/Dashboard.tsx
1992
- import { Box, Text } from "ink";
2018
+ import { Box, Text as Text2 } from "ink";
1993
2019
 
1994
2020
  // src/shared/format.ts
1995
2021
  function formatNumber(value, digits = 1) {
@@ -2035,6 +2061,39 @@ function sparkline(values, width = 24) {
2035
2061
  return slice.map((v) => glyphs[Math.min(glyphs.length - 1, Math.floor(v / max * (glyphs.length - 1)))]).join("").padStart(width, glyphs[0]);
2036
2062
  }
2037
2063
 
2064
+ // src/tui/Typewriter.tsx
2065
+ import { Text } from "ink";
2066
+ import { useEffect, useState } from "react";
2067
+ import { jsx, jsxs } from "react/jsx-runtime";
2068
+ var finished = /* @__PURE__ */ new Set();
2069
+ function Typewriter(props) {
2070
+ const start = props.once && finished.has(props.text) ? props.text.length : 0;
2071
+ const [n, setN] = useState(start);
2072
+ useEffect(() => {
2073
+ if (props.once && finished.has(props.text)) {
2074
+ setN(props.text.length);
2075
+ return;
2076
+ }
2077
+ setN(0);
2078
+ }, [props.text, props.once]);
2079
+ useEffect(() => {
2080
+ if (n >= props.text.length) return;
2081
+ const timer = setTimeout(() => {
2082
+ setN((v) => {
2083
+ const next = v + 1;
2084
+ if (props.once && next >= props.text.length) finished.add(props.text);
2085
+ return next;
2086
+ });
2087
+ }, props.ms ?? 16);
2088
+ return () => clearTimeout(timer);
2089
+ }, [n, props.text, props.ms, props.once]);
2090
+ const done = n >= props.text.length;
2091
+ return /* @__PURE__ */ jsxs(Text, { color: props.color, dimColor: props.dimColor, bold: props.bold, children: [
2092
+ props.text.slice(0, n),
2093
+ done ? null : /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u258D" })
2094
+ ] });
2095
+ }
2096
+
2038
2097
  // src/tui/width.ts
2039
2098
  import { useStdout } from "ink";
2040
2099
  function useCols() {
@@ -2049,7 +2108,7 @@ function fit(text, width) {
2049
2108
  }
2050
2109
 
2051
2110
  // src/tui/views/Dashboard.tsx
2052
- import { jsx, jsxs } from "react/jsx-runtime";
2111
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2053
2112
  function Dashboard({ session }) {
2054
2113
  const cols = useCols();
2055
2114
  const h = session.hardware;
@@ -2057,41 +2116,46 @@ function Dashboard({ session }) {
2057
2116
  `${h.machineModel ?? t("unknownMachine")} \xB7 ${h.cpu.name ?? t("cpuNa")} \xB7 ${formatBytes(h.memory.totalBytes, 0)} ${memoryLabel(h)}`,
2058
2117
  cols
2059
2118
  );
2060
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: cols, children: [
2061
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("machine") }),
2062
- /* @__PURE__ */ jsx(Text, { children: line }),
2063
- /* @__PURE__ */ jsx(Text, { children: session.runtimes.map((r, i) => /* @__PURE__ */ jsxs(Text, { color: r.detected ? "green" : "gray", children: [
2119
+ return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", width: cols, children: [
2120
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("machine") }),
2121
+ /* @__PURE__ */ jsx2(Text2, { children: line }),
2122
+ /* @__PURE__ */ jsx2(Text2, { children: session.runtimes.map((r, i) => /* @__PURE__ */ jsxs2(Text2, { color: r.detected ? "green" : "gray", children: [
2064
2123
  i ? " " : "",
2065
2124
  r.detected ? "\u2713" : "\u25CB",
2066
2125
  r.label
2067
2126
  ] }, r.id)) }),
2068
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("installedModels") }),
2069
- session.rows.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("noModels") }),
2070
- session.rows.slice(0, 6).map((row) => /* @__PURE__ */ jsxs(Text, { children: [
2071
- /* @__PURE__ */ jsx(Text, { color: "green", children: fit(row.local.name, 18) }),
2072
- /* @__PURE__ */ jsx(Text, { children: " " }),
2073
- /* @__PURE__ */ jsx(Text, { color: gradeColor(row.compatibility?.grade), children: row.compatibility?.grade ?? "\u2014" }),
2074
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
2127
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("installedModels") }),
2128
+ session.rows.length === 0 && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("noModels") }),
2129
+ session.rows.slice(0, 6).map((row) => /* @__PURE__ */ jsxs2(Text2, { children: [
2130
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: fit(row.local.name, 18) }),
2131
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
2132
+ /* @__PURE__ */ jsx2(Text2, { color: gradeColor(row.compatibility?.grade), children: row.compatibility?.grade ?? "\u2014" }),
2133
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
2075
2134
  " ",
2076
2135
  row.lastBenchmark?.benchmark.generationTokensPerSecond ? formatTokensPerSec(row.lastBenchmark.benchmark.generationTokensPerSecond) : t("notTested")
2077
2136
  ] })
2078
2137
  ] }, row.local.id)),
2079
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("recommended") }),
2080
- session.recommendations.slice(0, 4).map((rec) => /* @__PURE__ */ jsxs(Text, { children: [
2081
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: fit(rec.useCase, 10) }),
2082
- /* @__PURE__ */ jsxs(Text, { children: [
2138
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "cyan", children: t("recommended") }),
2139
+ session.recommendations.slice(0, 4).map((rec) => /* @__PURE__ */ jsxs2(Text2, { children: [
2140
+ /* @__PURE__ */ jsx2(Text2, { color: "magenta", children: fit(rec.useCase, 10) }),
2141
+ /* @__PURE__ */ jsxs2(Text2, { children: [
2083
2142
  " ",
2084
2143
  fit(rec.model.name, 22),
2085
2144
  " "
2086
2145
  ] }),
2087
- /* @__PURE__ */ jsx(Text, { color: gradeColor(rec.grade), children: rec.grade })
2146
+ /* @__PURE__ */ jsx2(Text2, { color: gradeColor(rec.grade), children: rec.grade })
2088
2147
  ] }, `${rec.useCase}-${rec.model.id}`)),
2089
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: fit(t("setupCta"), cols) })
2148
+ /* @__PURE__ */ jsxs2(Text2, { children: [
2149
+ /* @__PURE__ */ jsx2(Text2, { color: session.runtimes.find((r) => r.id === "ollama")?.detected ? "green" : "yellow", children: session.runtimes.find((r) => r.id === "ollama")?.detected ? "\u2713 Ollama" : `\u25CB Ollama ${t("reqOllama")}` }),
2150
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
2151
+ /* @__PURE__ */ jsx2(Text2, { color: session.runtimes.find((r) => r.id === "lmstudio")?.detected ? "green" : "gray", children: session.runtimes.find((r) => r.id === "lmstudio")?.detected ? "\u2713 LM Studio" : `\u25CB LM Studio ${t("reqLmStudio")}` })
2152
+ ] }),
2153
+ /* @__PURE__ */ jsx2(Typewriter, { text: fit(t("setupCta"), cols), ms: 18, color: "yellow" })
2090
2154
  ] });
2091
2155
  }
2092
2156
 
2093
2157
  // src/tui/views/Models.tsx
2094
- import { Box as Box2, Text as Text2 } from "ink";
2158
+ import { Box as Box2, Text as Text3 } from "ink";
2095
2159
 
2096
2160
  // src/compatibility/types.ts
2097
2161
  var GRADE_MEANING = {
@@ -2214,7 +2278,11 @@ function doctorText(session) {
2214
2278
  `${session.hardware.gpu.metal ? "\u2713" : "\u25CB"} Metal`,
2215
2279
  ...session.runtimes.map((r) => `${r.detected ? "\u2713" : "\u25CB"} ${r.label.padEnd(14)} ${r.version ?? ""}`.trimEnd()),
2216
2280
  `${session.networkUsed ? "\u2713" : "\u25CB"} CanIRun.ai (midudev)`,
2217
- ...session.agents.map((a) => `${a.detected ? "\u2713" : "\u25CB"} ${a.label.padEnd(14)} ${a.detected ? t("detected") : t("agentMissing")}`),
2281
+ ...session.agents.map(
2282
+ (a) => `${a.detected ? "\u2713" : "\u25CB"} ${a.label.padEnd(14)} ${a.detected ? t("detected") : a.launch}`
2283
+ ),
2284
+ ...session.runtimes.find((r) => r.id === "ollama")?.detected ? [] : [`\u25CB Ollama ${t("reqOllama")}`],
2285
+ ...session.runtimes.find((r) => r.id === "lmstudio")?.detected ? [] : [`\u25CB LM Studio ${t("reqLmStudio")}`],
2218
2286
  "",
2219
2287
  session.llamaBench ? t("doctorReady", { count: session.models.filter((m) => m.artifactPath).length }) : t("doctorNoBench")
2220
2288
  ];
@@ -2295,57 +2363,57 @@ function resultText(record, assessment) {
2295
2363
  }
2296
2364
 
2297
2365
  // src/tui/views/Models.tsx
2298
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2366
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2299
2367
  function ModelsView({ session }) {
2300
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2301
- /* @__PURE__ */ jsx2(Text2, { children: modelsText(session) }),
2302
- /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: t("modelsHint") })
2368
+ return /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", children: [
2369
+ /* @__PURE__ */ jsx3(Text3, { children: modelsText(session) }),
2370
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: t("modelsHint") })
2303
2371
  ] });
2304
2372
  }
2305
2373
 
2306
2374
  // src/tui/views/Hardware.tsx
2307
- import { Box as Box3, Text as Text3 } from "ink";
2308
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2375
+ import { Box as Box3, Text as Text4 } from "ink";
2376
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2309
2377
  function HardwareView({ session }) {
2310
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2311
- /* @__PURE__ */ jsx3(Text3, { children: hardwareText(session) }),
2312
- /* @__PURE__ */ jsx3(Text3, { children: runtimesText(session) }),
2313
- /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: t("hardwareHint") })
2378
+ return /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", children: [
2379
+ /* @__PURE__ */ jsx4(Text4, { children: hardwareText(session) }),
2380
+ /* @__PURE__ */ jsx4(Text4, { children: runtimesText(session) }),
2381
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: t("hardwareHint") })
2314
2382
  ] });
2315
2383
  }
2316
2384
 
2317
2385
  // src/tui/views/Recommend.tsx
2318
- import { Box as Box4, Text as Text4 } from "ink";
2319
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2386
+ import { Box as Box4, Text as Text5 } from "ink";
2387
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2320
2388
  function RecommendView({ session }) {
2321
2389
  const cols = useCols();
2322
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width: cols, children: [
2323
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "magenta", children: t("recommended") }),
2324
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: fit(t("aboutRecommend"), cols) }),
2325
- session.recommendations.map((rec) => /* @__PURE__ */ jsxs4(Text4, { children: [
2326
- /* @__PURE__ */ jsx4(Text4, { color: "magenta", children: fit(rec.useCase, 10) }),
2327
- /* @__PURE__ */ jsxs4(Text4, { children: [
2390
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", width: cols, children: [
2391
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: "magenta", children: t("recommended") }),
2392
+ /* @__PURE__ */ jsx5(Typewriter, { text: fit(t("aboutRecommend"), cols), ms: 12, dimColor: true }),
2393
+ session.recommendations.map((rec) => /* @__PURE__ */ jsxs5(Text5, { children: [
2394
+ /* @__PURE__ */ jsx5(Text5, { color: "magenta", children: fit(rec.useCase, 10) }),
2395
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2328
2396
  " ",
2329
2397
  fit(rec.model.name, 22),
2330
2398
  " "
2331
2399
  ] }),
2332
- /* @__PURE__ */ jsx4(Text4, { color: gradeColor(rec.grade), children: rec.grade }),
2333
- /* @__PURE__ */ jsxs4(Text4, { children: [
2400
+ /* @__PURE__ */ jsx5(Text5, { color: gradeColor(rec.grade), children: rec.grade }),
2401
+ /* @__PURE__ */ jsxs5(Text5, { children: [
2334
2402
  " ",
2335
2403
  GRADE_MEANING[rec.grade]
2336
2404
  ] }),
2337
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2405
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
2338
2406
  " ",
2339
2407
  resolveOllamaTag(rec.model.id, rec.model.name) ?? "\u2014"
2340
2408
  ] })
2341
2409
  ] }, `${rec.useCase}-${rec.model.id}`)),
2342
- /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: fit(t("setupCta"), cols) })
2410
+ /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: fit(t("setupCta"), cols) })
2343
2411
  ] });
2344
2412
  }
2345
2413
 
2346
2414
  // src/tui/views/Setup.tsx
2347
- import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
2348
- import { useState } from "react";
2415
+ import { Box as Box5, Text as Text6, useApp, useInput } from "ink";
2416
+ import { useState as useState2 } from "react";
2349
2417
 
2350
2418
  // src/integrations/catalog.ts
2351
2419
  var INTEGRATIONS = {
@@ -2535,7 +2603,7 @@ async function pullOllamaModel(modelId, extraName) {
2535
2603
  }
2536
2604
 
2537
2605
  // src/tui/views/Setup.tsx
2538
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2606
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2539
2607
  var TOOLS = [
2540
2608
  { key: "1", id: "opencode", label: "OpenCode" },
2541
2609
  { key: "2", id: "hermes", label: "Hermes" },
@@ -2544,9 +2612,9 @@ var TOOLS = [
2544
2612
  function SetupView({ session }) {
2545
2613
  const cols = useCols();
2546
2614
  const { exit } = useApp();
2547
- const [cursor, setCursor] = useState(0);
2548
- const [log, setLog] = useState("");
2549
- const [busy, setBusy] = useState(false);
2615
+ const [cursor, setCursor] = useState2(0);
2616
+ const [log, setLog] = useState2("");
2617
+ const [busy, setBusy] = useState2(false);
2550
2618
  const recs = session.recommendations;
2551
2619
  const rec = recs[cursor];
2552
2620
  useInput((input, key) => {
@@ -2582,48 +2650,59 @@ function SetupView({ session }) {
2582
2650
  }
2583
2651
  });
2584
2652
  const tag = rec ? resolveOllamaTag(rec.model.id, rec.model.name) : void 0;
2585
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width: cols, children: [
2586
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "yellow", children: fit(t("setupTitle"), cols) }),
2587
- recs.map((item, i) => /* @__PURE__ */ jsxs5(Text5, { color: i === cursor ? "cyan" : void 0, children: [
2653
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", width: cols, children: [
2654
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: fit(t("setupTitle"), cols) }),
2655
+ recs.map((item, i) => /* @__PURE__ */ jsxs6(Text6, { color: i === cursor ? "cyan" : void 0, children: [
2588
2656
  i === cursor ? "\u276F " : " ",
2589
2657
  fit(item.model.name, 20),
2590
2658
  " ",
2591
- /* @__PURE__ */ jsx5(Text5, { color: gradeColor(item.grade), children: item.grade }),
2592
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
2659
+ /* @__PURE__ */ jsx6(Text6, { color: gradeColor(item.grade), children: item.grade }),
2660
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
2593
2661
  " ",
2594
2662
  resolveOllamaTag(item.model.id, item.model.name) ?? "\u2014"
2595
2663
  ] })
2596
2664
  ] }, `${item.useCase}-${item.model.id}`)),
2597
- /* @__PURE__ */ jsx5(Text5, { children: session.agents.map((agent, index) => /* @__PURE__ */ jsxs5(Text5, { color: agent.detected ? "green" : "gray", children: [
2598
- index ? " " : "",
2665
+ session.agents.map((agent, index) => /* @__PURE__ */ jsxs6(Text6, { color: agent.detected ? "green" : "yellow", children: [
2599
2666
  "[",
2600
2667
  index + 1,
2601
- "]",
2668
+ "] ",
2602
2669
  agent.detected ? "\u2713" : "\u25CB",
2603
- agent.label
2604
- ] }, agent.id)) }),
2605
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: fit(`Enter=pull ${tag ?? ""} 1/2/3=agent Esc=back`, cols) }),
2606
- log ? /* @__PURE__ */ jsx5(Text5, { color: "green", children: fit(log, cols) }) : null
2670
+ " ",
2671
+ agent.label,
2672
+ agent.detected ? "" : ` \u2192 ${agent.launch}`
2673
+ ] }, agent.id)),
2674
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: fit(
2675
+ `Ollama: ${session.runtimes.find((r) => r.id === "ollama")?.detected ? "\u2713" : t("reqOllama")} LM Studio: ${session.runtimes.find((r) => r.id === "lmstudio")?.detected ? "\u2713" : t("reqLmStudio")}`,
2676
+ cols
2677
+ ) }),
2678
+ /* @__PURE__ */ jsx6(Typewriter, { text: fit(`Enter=pull ${tag ?? ""} 1/2/3=agent Esc=back`, cols), ms: 14, dimColor: true }),
2679
+ log ? /* @__PURE__ */ jsx6(Text6, { color: "green", children: fit(log, cols) }) : null
2607
2680
  ] });
2608
2681
  }
2609
2682
 
2610
2683
  // src/tui/views/Compare.tsx
2611
- import { Box as Box6, Text as Text6 } from "ink";
2612
- import { jsx as jsx6 } from "react/jsx-runtime";
2684
+ import { Box as Box6, Text as Text7 } from "ink";
2685
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2613
2686
  function CompareView({ session }) {
2614
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx6(Text6, { children: compareText(session.history) }) });
2687
+ const cols = useCols();
2688
+ const n = new Set(session.history.map((h) => h.model.id)).size;
2689
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", width: cols, children: [
2690
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "green", children: t("compareTitle") }),
2691
+ /* @__PURE__ */ jsx7(Typewriter, { text: fit(t("compareHow"), cols), ms: 12, dimColor: true }),
2692
+ n < 2 ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: fit(t("compareNeedBench"), cols) }) : /* @__PURE__ */ jsx7(Text7, { children: compareText(session.history) })
2693
+ ] });
2615
2694
  }
2616
2695
 
2617
2696
  // src/tui/views/Benchmark.tsx
2618
- import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
2619
- import { useEffect, useRef, useState as useState2 } from "react";
2620
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2697
+ import { Box as Box7, Text as Text8, useInput as useInput2 } from "ink";
2698
+ import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
2699
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2621
2700
  function BenchmarkView(props) {
2622
- const [phase, setPhase] = useState2(props.models.length ? "select" : "error");
2623
- const [sample, setSample] = useState2();
2624
- const [elapsed, setElapsed] = useState2(0);
2625
- const [result, setResult] = useState2();
2626
- const [error, setError] = useState2(props.models.length ? "" : t("noGguf"));
2701
+ const [phase, setPhase] = useState3(props.models.length ? "select" : "error");
2702
+ const [sample, setSample] = useState3();
2703
+ const [elapsed, setElapsed] = useState3(0);
2704
+ const [result, setResult] = useState3();
2705
+ const [error, setError] = useState3(props.models.length ? "" : t("noGguf"));
2627
2706
  const cpuHist = useRef([]);
2628
2707
  const memHist = useRef([]);
2629
2708
  const abortRef = useRef(void 0);
@@ -2639,7 +2718,7 @@ function BenchmarkView(props) {
2639
2718
  props.onBack();
2640
2719
  }
2641
2720
  });
2642
- useEffect(() => {
2721
+ useEffect2(() => {
2643
2722
  if (phase !== "running") return;
2644
2723
  const model2 = props.models[props.selected];
2645
2724
  if (!model2) return;
@@ -2677,19 +2756,19 @@ function BenchmarkView(props) {
2677
2756
  const h = props.session.hardware;
2678
2757
  const model = props.models[props.selected];
2679
2758
  if (phase === "select") {
2680
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2681
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: t("selectBenchmark") }),
2682
- props.models.map((m, i) => /* @__PURE__ */ jsxs6(Text7, { color: i === props.selected ? "cyan" : void 0, children: [
2759
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2760
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: t("selectBenchmark") }),
2761
+ props.models.map((m, i) => /* @__PURE__ */ jsxs8(Text8, { color: i === props.selected ? "cyan" : void 0, children: [
2683
2762
  i === props.selected ? "\u276F " : " ",
2684
2763
  m.name
2685
2764
  ] }, m.id)),
2686
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("enterToRun") })
2765
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("enterToRun") })
2687
2766
  ] });
2688
2767
  }
2689
2768
  if (phase === "done" && result) {
2690
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2691
- /* @__PURE__ */ jsx7(Text7, { children: resultText(result.record, result.assessment) }),
2692
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
2769
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2770
+ /* @__PURE__ */ jsx8(Text8, { children: resultText(result.record, result.assessment) }),
2771
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2693
2772
  t("saved"),
2694
2773
  " ",
2695
2774
  result.path
@@ -2697,17 +2776,17 @@ function BenchmarkView(props) {
2697
2776
  ] });
2698
2777
  }
2699
2778
  if (phase === "error") {
2700
- return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { color: "red", children: error }) });
2779
+ return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx8(Text8, { color: "red", children: error }) });
2701
2780
  }
2702
2781
  const cpu = sample?.cpu?.utilizationPercent;
2703
2782
  const gpu = sample?.gpu?.utilizationPercent;
2704
2783
  const memUsed = sample?.memory.usedBytes;
2705
2784
  const memTotal = h.memory.totalBytes;
2706
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2707
- /* @__PURE__ */ jsxs6(Box7, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [
2708
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: t("benchmarkTitle") }),
2709
- /* @__PURE__ */ jsx7(Text7, { children: model?.name }),
2710
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
2785
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2786
+ /* @__PURE__ */ jsxs8(Box7, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [
2787
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: t("benchmarkTitle") }),
2788
+ /* @__PURE__ */ jsx8(Text8, { children: model?.name }),
2789
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2711
2790
  h.cpu.name,
2712
2791
  " \xB7 ",
2713
2792
  formatBytes(h.memory.totalBytes, 0),
@@ -2715,31 +2794,31 @@ function BenchmarkView(props) {
2715
2794
  memoryLabel(h)
2716
2795
  ] })
2717
2796
  ] }),
2718
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
2719
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: t("performance") }),
2720
- /* @__PURE__ */ jsxs6(Text7, { children: [
2797
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2798
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("performance") }),
2799
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2721
2800
  t("elapsed"),
2722
2801
  " ",
2723
2802
  elapsed.toFixed(1),
2724
2803
  " s"
2725
2804
  ] }),
2726
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("measuredAfter") })
2805
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("measuredAfter") })
2727
2806
  ] }),
2728
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
2729
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: t("system") }),
2730
- /* @__PURE__ */ jsxs6(Text7, { children: [
2807
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2808
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("system") }),
2809
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2731
2810
  "CPU ",
2732
2811
  bar(cpu),
2733
2812
  " ",
2734
2813
  formatPercent(cpu)
2735
2814
  ] }),
2736
- /* @__PURE__ */ jsxs6(Text7, { children: [
2815
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2737
2816
  "GPU ",
2738
2817
  bar(gpu),
2739
2818
  " ",
2740
2819
  formatPercent(gpu)
2741
2820
  ] }),
2742
- /* @__PURE__ */ jsxs6(Text7, { children: [
2821
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2743
2822
  "Memory ",
2744
2823
  bar(memUsed && memTotal ? memUsed / memTotal * 100 : void 0),
2745
2824
  " ",
@@ -2747,56 +2826,56 @@ function BenchmarkView(props) {
2747
2826
  " / ",
2748
2827
  formatBytes(memTotal, 0)
2749
2828
  ] }),
2750
- /* @__PURE__ */ jsxs6(Text7, { children: [
2829
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2751
2830
  "Swap ",
2752
2831
  bar(0),
2753
2832
  " ",
2754
2833
  sample ? formatBytes(sample.memory.swapUsedBytes) : "N/A"
2755
2834
  ] })
2756
2835
  ] }),
2757
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
2758
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: t("powerThermals") }),
2759
- /* @__PURE__ */ jsxs6(Text7, { children: [
2836
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2837
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("powerThermals") }),
2838
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2760
2839
  "Package ",
2761
2840
  sample?.packagePowerWatts ?? "N/A",
2762
2841
  " W"
2763
2842
  ] }),
2764
- /* @__PURE__ */ jsxs6(Text7, { children: [
2843
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2765
2844
  "Temperature ",
2766
2845
  sample?.thermal?.temperatureC ?? "N/A",
2767
2846
  " \xB0C"
2768
2847
  ] }),
2769
- /* @__PURE__ */ jsxs6(Text7, { children: [
2848
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2770
2849
  "Thermal Pressure ",
2771
2850
  sample?.thermal?.pressure ?? "N/A"
2772
2851
  ] }),
2773
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("permissionsHint") })
2852
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("permissionsHint") })
2774
2853
  ] }),
2775
- /* @__PURE__ */ jsxs6(Text7, { children: [
2854
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2776
2855
  sparkline(cpuHist.current),
2777
2856
  " CPU"
2778
2857
  ] }),
2779
- /* @__PURE__ */ jsxs6(Text7, { children: [
2858
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2780
2859
  sparkline(memHist.current.map((v) => v / 1024 ** 3)),
2781
2860
  " Memory"
2782
2861
  ] }),
2783
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("cancelHint") })
2862
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("cancelHint") })
2784
2863
  ] });
2785
2864
  }
2786
2865
 
2787
2866
  // src/tui/views/History.tsx
2788
- import { Box as Box8, Text as Text8 } from "ink";
2789
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2867
+ import { Box as Box8, Text as Text9 } from "ink";
2868
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2790
2869
  function HistoryView({ session }) {
2791
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
2792
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("measured") }),
2793
- /* @__PURE__ */ jsx8(Text8, { children: historyText(session.history) })
2870
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
2871
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("measured") }),
2872
+ /* @__PURE__ */ jsx9(Text9, { children: historyText(session.history) })
2794
2873
  ] });
2795
2874
  }
2796
2875
 
2797
2876
  // src/tui/views/Tasks.tsx
2798
- import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
2799
- import { useMemo, useState as useState3 } from "react";
2877
+ import { Box as Box9, Text as Text10, useInput as useInput3 } from "ink";
2878
+ import { useMemo, useState as useState4 } from "react";
2800
2879
 
2801
2880
  // src/tasks/catalog.ts
2802
2881
  var TASK_CATALOG = [
@@ -2991,7 +3070,7 @@ function tasksText(plans, answers) {
2991
3070
  }
2992
3071
 
2993
3072
  // src/tui/views/Tasks.tsx
2994
- import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3073
+ import { Fragment, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2995
3074
  var KIND_LABEL = {
2996
3075
  code: "tasksOptCode",
2997
3076
  video: "tasksOptVideo",
@@ -3000,11 +3079,11 @@ var KIND_LABEL = {
3000
3079
  chat: "tasksOptChat"
3001
3080
  };
3002
3081
  function TasksView({ session }) {
3003
- const [step, setStep] = useState3(0);
3004
- const [cursor, setCursor] = useState3(0);
3005
- const [kinds, setKinds] = useState3(["code", "chat"]);
3006
- const [scope, setScope] = useState3("installed");
3007
- const [priority, setPriority] = useState3("balanced");
3082
+ const [step, setStep] = useState4(0);
3083
+ const [cursor, setCursor] = useState4(0);
3084
+ const [kinds, setKinds] = useState4(["code", "chat"]);
3085
+ const [scope, setScope] = useState4("installed");
3086
+ const [priority, setPriority] = useState4("balanced");
3008
3087
  const plans = useMemo(
3009
3088
  () => planTasks(session, { kinds, scope, priority }),
3010
3089
  [session, kinds, scope, priority]
@@ -3032,13 +3111,13 @@ function TasksView({ session }) {
3032
3111
  }
3033
3112
  });
3034
3113
  if (step === 3) {
3035
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Text9, { children: tasksText(plans, { kinds, scope, priority }) }) });
3114
+ return /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { children: tasksText(plans, { kinds, scope, priority }) }) });
3036
3115
  }
3037
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
3038
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("tasksTitle") }),
3039
- step === 0 && /* @__PURE__ */ jsxs8(Fragment, { children: [
3040
- /* @__PURE__ */ jsx9(Text9, { children: t("tasksQ1") }),
3041
- WORK_KINDS.map((kind, i) => /* @__PURE__ */ jsxs8(Text9, { color: i === cursor ? "cyan" : void 0, children: [
3116
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
3117
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: t("tasksTitle") }),
3118
+ step === 0 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3119
+ /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ1") }),
3120
+ WORK_KINDS.map((kind, i) => /* @__PURE__ */ jsxs10(Text10, { color: i === cursor ? "cyan" : void 0, children: [
3042
3121
  i === cursor ? "\u276F " : " ",
3043
3122
  "[",
3044
3123
  kinds.includes(kind) ? "x" : " ",
@@ -3046,20 +3125,20 @@ function TasksView({ session }) {
3046
3125
  t(KIND_LABEL[kind])
3047
3126
  ] }, kind))
3048
3127
  ] }),
3049
- step === 1 && /* @__PURE__ */ jsxs8(Fragment, { children: [
3050
- /* @__PURE__ */ jsx9(Text9, { children: t("tasksQ2") }),
3051
- /* @__PURE__ */ jsxs8(Text9, { color: scope === "installed" ? "cyan" : void 0, children: [
3128
+ step === 1 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3129
+ /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ2") }),
3130
+ /* @__PURE__ */ jsxs10(Text10, { color: scope === "installed" ? "cyan" : void 0, children: [
3052
3131
  scope === "installed" ? "\u276F " : " ",
3053
3132
  t("tasksOptInstalled")
3054
3133
  ] }),
3055
- /* @__PURE__ */ jsxs8(Text9, { color: scope === "all" ? "cyan" : void 0, children: [
3134
+ /* @__PURE__ */ jsxs10(Text10, { color: scope === "all" ? "cyan" : void 0, children: [
3056
3135
  scope === "all" ? "\u276F " : " ",
3057
3136
  t("tasksOptAll")
3058
3137
  ] })
3059
3138
  ] }),
3060
- step === 2 && /* @__PURE__ */ jsxs8(Fragment, { children: [
3061
- /* @__PURE__ */ jsx9(Text9, { children: t("tasksQ3") }),
3062
- ["speed", "balanced", "quality"].map((item) => /* @__PURE__ */ jsxs8(Text9, { color: priority === item ? "cyan" : void 0, children: [
3139
+ step === 2 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3140
+ /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ3") }),
3141
+ ["speed", "balanced", "quality"].map((item) => /* @__PURE__ */ jsxs10(Text10, { color: priority === item ? "cyan" : void 0, children: [
3063
3142
  priority === item ? "\u276F " : " ",
3064
3143
  item === "speed" ? t("tasksOptSpeed") : item === "quality" ? t("tasksOptQuality") : t("tasksOptBalanced")
3065
3144
  ] }, item))
@@ -3095,61 +3174,54 @@ function handleAppKey(screen, input, key) {
3095
3174
  }
3096
3175
 
3097
3176
  // src/tui/Welcome.tsx
3098
- import { Box as Box10, Text as Text10 } from "ink";
3099
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3177
+ import { Box as Box10, Text as Text11 } from "ink";
3178
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3100
3179
  function Chrome() {
3101
3180
  const cols = useCols();
3102
3181
  const wide = cols >= 72;
3103
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", width: cols, marginBottom: 1, children: [
3104
- /* @__PURE__ */ jsxs9(Text10, { children: [
3105
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: t("appTitle") }),
3106
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: " \xB7 " }),
3107
- /* @__PURE__ */ jsx10(Text10, { color: "magenta", children: t("byline") }),
3108
- wide ? /* @__PURE__ */ jsxs9(Text10, { color: "gray", children: [
3109
- " \xB7 ",
3110
- fit(t("appSubtitle"), cols - 28)
3111
- ] }) : null
3112
- ] }),
3113
- /* @__PURE__ */ jsx10(Text10, { children: wide ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
3114
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "[S] Setup" }),
3115
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3116
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "[B] Bench" }),
3117
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3118
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "[M] Models" }),
3119
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3120
- /* @__PURE__ */ jsx10(Text10, { color: "magenta", children: "[R] Rec" }),
3121
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3122
- /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "[T] Tasks" }),
3123
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3124
- /* @__PURE__ */ jsx10(Text10, { children: "[H] HW" }),
3125
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3126
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "[C] Cmp" }),
3127
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3128
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "[L] Log" }),
3129
- /* @__PURE__ */ jsx10(Text10, { children: " " }),
3130
- /* @__PURE__ */ jsx10(Text10, { color: "red", children: "[Q] Quit" })
3131
- ] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
3132
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "[S]" }),
3133
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "[B]" }),
3134
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "[M]" }),
3135
- /* @__PURE__ */ jsx10(Text10, { color: "magenta", children: "[R]" }),
3136
- /* @__PURE__ */ jsx10(Text10, { color: "blue", children: "[T]" }),
3137
- /* @__PURE__ */ jsx10(Text10, { children: "[H]" }),
3138
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "[C]" }),
3139
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "[L]" }),
3140
- /* @__PURE__ */ jsx10(Text10, { color: "red", children: "[Q]" })
3182
+ const brand = `${t("appTitle")} ${t("byline")}`;
3183
+ return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", width: cols, marginBottom: 1, children: [
3184
+ /* @__PURE__ */ jsx11(Typewriter, { text: brand, ms: 28, color: "cyan", bold: true, once: true }),
3185
+ /* @__PURE__ */ jsx11(Text11, { children: wide ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
3186
+ /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "[S] Setup" }),
3187
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3188
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[B] Bench" }),
3189
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3190
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[M] Models" }),
3191
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3192
+ /* @__PURE__ */ jsx11(Text11, { color: "magenta", children: "[R] Rec" }),
3193
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3194
+ /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "[T] Tasks" }),
3195
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3196
+ /* @__PURE__ */ jsx11(Text11, { children: "[H] HW" }),
3197
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3198
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[C] Cmp" }),
3199
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3200
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[L] Log" }),
3201
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3202
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: "[Q] Quit" })
3203
+ ] }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
3204
+ /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "[S]" }),
3205
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[B]" }),
3206
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[M]" }),
3207
+ /* @__PURE__ */ jsx11(Text11, { color: "magenta", children: "[R]" }),
3208
+ /* @__PURE__ */ jsx11(Text11, { color: "blue", children: "[T]" }),
3209
+ /* @__PURE__ */ jsx11(Text11, { children: "[H]" }),
3210
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[C]" }),
3211
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[L]" }),
3212
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: "[Q]" })
3141
3213
  ] }) })
3142
3214
  ] });
3143
3215
  }
3144
3216
 
3145
3217
  // src/tui/App.tsx
3146
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3218
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3147
3219
  function App(props) {
3148
3220
  const { exit } = useApp2();
3149
- const [screen, setScreen] = useState4(props.start ?? "home");
3221
+ const [screen, setScreen] = useState5(props.start ?? "home");
3150
3222
  const screenRef = useRef2(screen);
3151
3223
  screenRef.current = screen;
3152
- const [selected, setSelected] = useState4(0);
3224
+ const [selected, setSelected] = useState5(0);
3153
3225
  const benchable = useMemo2(
3154
3226
  () => props.session.models.filter((m) => Boolean(m.artifactPath)),
3155
3227
  [props.session.models]
@@ -3160,17 +3232,17 @@ function App(props) {
3160
3232
  if (result.type === "home") setScreen("home");
3161
3233
  if (result.type === "screen") setScreen(result.screen);
3162
3234
  });
3163
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingX: 1, children: [
3164
- screen !== "benchmark" && /* @__PURE__ */ jsx11(Chrome, {}),
3165
- screen === "home" && /* @__PURE__ */ jsx11(Dashboard, { session: props.session }),
3166
- screen === "models" && /* @__PURE__ */ jsx11(ModelsView, { session: props.session }),
3167
- screen === "hardware" && /* @__PURE__ */ jsx11(HardwareView, { session: props.session }),
3168
- screen === "recommend" && /* @__PURE__ */ jsx11(RecommendView, { session: props.session }),
3169
- screen === "setup" && /* @__PURE__ */ jsx11(SetupView, { session: props.session }),
3170
- screen === "compare" && /* @__PURE__ */ jsx11(CompareView, { session: props.session }),
3171
- screen === "history" && /* @__PURE__ */ jsx11(HistoryView, { session: props.session }),
3172
- screen === "tasks" && /* @__PURE__ */ jsx11(TasksView, { session: props.session }),
3173
- screen === "benchmark" && /* @__PURE__ */ jsx11(
3235
+ return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", paddingX: 1, children: [
3236
+ screen !== "benchmark" && /* @__PURE__ */ jsx12(Chrome, {}),
3237
+ screen === "home" && /* @__PURE__ */ jsx12(Dashboard, { session: props.session }),
3238
+ screen === "models" && /* @__PURE__ */ jsx12(ModelsView, { session: props.session }),
3239
+ screen === "hardware" && /* @__PURE__ */ jsx12(HardwareView, { session: props.session }),
3240
+ screen === "recommend" && /* @__PURE__ */ jsx12(RecommendView, { session: props.session }),
3241
+ screen === "setup" && /* @__PURE__ */ jsx12(SetupView, { session: props.session }),
3242
+ screen === "compare" && /* @__PURE__ */ jsx12(CompareView, { session: props.session }),
3243
+ screen === "history" && /* @__PURE__ */ jsx12(HistoryView, { session: props.session }),
3244
+ screen === "tasks" && /* @__PURE__ */ jsx12(TasksView, { session: props.session }),
3245
+ screen === "benchmark" && /* @__PURE__ */ jsx12(
3174
3246
  BenchmarkView,
3175
3247
  {
3176
3248
  session: props.session,
@@ -3185,9 +3257,9 @@ function App(props) {
3185
3257
  }
3186
3258
 
3187
3259
  // src/tui/render.tsx
3188
- import { jsx as jsx12 } from "react/jsx-runtime";
3260
+ import { jsx as jsx13 } from "react/jsx-runtime";
3189
3261
  async function renderDashboard(session, preset, start) {
3190
- const instance = render(/* @__PURE__ */ jsx12(App, { session, preset, start }));
3262
+ const instance = render(/* @__PURE__ */ jsx13(App, { session, preset, start }));
3191
3263
  await instance.waitUntilExit();
3192
3264
  }
3193
3265
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",