pudu-ai 0.2.13 → 0.2.15

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 +141 -50
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -658,6 +658,11 @@ async function detectRuntimes() {
658
658
  label: "llama-bench",
659
659
  detected: llamaBench
660
660
  });
661
+ statuses.push({
662
+ id: "docker",
663
+ label: "Docker",
664
+ detected: Boolean(await commandExists("docker"))
665
+ });
661
666
  return statuses;
662
667
  }
663
668
 
@@ -836,6 +841,15 @@ var en = {
836
841
  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
842
  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
843
  aboutCompare: "Compares saved measured runs (speed, memory, power). Quality is never inferred from speed. Use it after two or more benchmarks.",
844
+ 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.",
845
+ compareNeedBench: "Need 2+ measured models. Press [B] to benchmark first.",
846
+ reqOllama: "install: https://ollama.com/download or brew install ollama",
847
+ reqLmStudio: "install: https://lmstudio.ai",
848
+ reqDocker: "install: https://docs.docker.com/get-docker/ or brew install --cask docker",
849
+ dockerHint: "press 4 to run ollama/ollama in Docker",
850
+ dockerStarting: "Starting ollama/ollama container\u2026",
851
+ dockerRunOk: "Docker Ollama is up on port 11434 (name: pudu-ollama).",
852
+ dockerRunFail: "docker run failed. Is Docker Desktop running?",
839
853
  aboutHistory: "Shows local benchmark files in ~/.pudu-ai/benchmarks. Use it to replay what this machine actually did.",
840
854
  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
855
  aboutReport: "Writes the latest measured run as Markdown. Use it to paste results into GitHub or notes.",
@@ -1021,6 +1035,15 @@ var es = {
1021
1035
  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
1036
  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
1037
  aboutCompare: "Compara corridas medidas (velocidad, memoria, potencia). La calidad no se infiere de la velocidad. Sirve despu\xE9s de dos o m\xE1s benchmarks.",
1038
+ 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.",
1039
+ compareNeedBench: "Hacen falta 2+ modelos medidos. Pulsa [B] para hacer un benchmark primero.",
1040
+ reqOllama: "instalar: https://ollama.com/download o brew install ollama",
1041
+ reqLmStudio: "instalar: https://lmstudio.ai",
1042
+ reqDocker: "instalar: https://docs.docker.com/get-docker/ o brew install --cask docker",
1043
+ dockerHint: "pulsa 4 para correr ollama/ollama en Docker",
1044
+ dockerStarting: "Arrancando contenedor ollama/ollama\u2026",
1045
+ dockerRunOk: "Ollama en Docker est\xE1 en el puerto 11434 (nombre: pudu-ollama).",
1046
+ dockerRunFail: "fall\xF3 docker run. \xBFDocker Desktop est\xE1 abierto?",
1024
1047
  aboutHistory: "Muestra los JSON locales en ~/.pudu-ai/benchmarks. Sirve para ver qu\xE9 hizo realmente esta m\xE1quina.",
1025
1048
  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
1049
  aboutReport: "Exporta la \xFAltima corrida medida en Markdown. Sirve para pegar resultados en GitHub o notas.",
@@ -1441,9 +1464,27 @@ async function listBenchmarks() {
1441
1464
 
1442
1465
  // src/integrations/agents.ts
1443
1466
  var AGENTS = [
1444
- { id: "opencode", label: "OpenCode", bin: "opencode" },
1445
- { id: "hermes", label: "Hermes", bin: "hermes" },
1446
- { id: "openclaw", label: "OpenClaw", bin: "openclaw" }
1467
+ {
1468
+ id: "opencode",
1469
+ label: "OpenCode",
1470
+ bin: "opencode",
1471
+ launch: "ollama launch opencode",
1472
+ docs: "https://docs.ollama.com/integrations/opencode"
1473
+ },
1474
+ {
1475
+ id: "hermes",
1476
+ label: "Hermes",
1477
+ bin: "hermes",
1478
+ launch: "ollama launch hermes",
1479
+ docs: "https://docs.ollama.com/integrations/hermes"
1480
+ },
1481
+ {
1482
+ id: "openclaw",
1483
+ label: "OpenClaw",
1484
+ bin: "openclaw",
1485
+ launch: "ollama launch openclaw",
1486
+ docs: "https://docs.ollama.com/integrations/openclaw"
1487
+ }
1447
1488
  ];
1448
1489
  async function detectAgents() {
1449
1490
  const out = [];
@@ -1455,6 +1496,7 @@ async function detectAgents() {
1455
1496
  }
1456
1497
  return out;
1457
1498
  }
1499
+ var DOCKER_OLLAMA = "docker run -d --name pudu-ollama -p 11434:11434 -v ollama:/root/.ollama ollama/ollama";
1458
1500
 
1459
1501
  // src/session/load.ts
1460
1502
  async function loadSession(options) {
@@ -2119,6 +2161,11 @@ function Dashboard({ session }) {
2119
2161
  ] }),
2120
2162
  /* @__PURE__ */ jsx2(Text2, { color: gradeColor(rec.grade), children: rec.grade })
2121
2163
  ] }, `${rec.useCase}-${rec.model.id}`)),
2164
+ /* @__PURE__ */ jsxs2(Text2, { children: [
2165
+ /* @__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")}` }),
2166
+ /* @__PURE__ */ jsx2(Text2, { children: " " }),
2167
+ /* @__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")}` })
2168
+ ] }),
2122
2169
  /* @__PURE__ */ jsx2(Typewriter, { text: fit(t("setupCta"), cols), ms: 18, color: "yellow" })
2123
2170
  ] });
2124
2171
  }
@@ -2247,7 +2294,12 @@ function doctorText(session) {
2247
2294
  `${session.hardware.gpu.metal ? "\u2713" : "\u25CB"} Metal`,
2248
2295
  ...session.runtimes.map((r) => `${r.detected ? "\u2713" : "\u25CB"} ${r.label.padEnd(14)} ${r.version ?? ""}`.trimEnd()),
2249
2296
  `${session.networkUsed ? "\u2713" : "\u25CB"} CanIRun.ai (midudev)`,
2250
- ...session.agents.map((a) => `${a.detected ? "\u2713" : "\u25CB"} ${a.label.padEnd(14)} ${a.detected ? t("detected") : t("agentMissing")}`),
2297
+ ...session.agents.map(
2298
+ (a) => `${a.detected ? "\u2713" : "\u25CB"} ${a.label.padEnd(14)} ${a.detected ? t("detected") : a.launch}`
2299
+ ),
2300
+ ...session.runtimes.find((r) => r.id === "ollama")?.detected ? [] : [`\u25CB Ollama ${t("reqOllama")}`],
2301
+ ...session.runtimes.find((r) => r.id === "lmstudio")?.detected ? [] : [`\u25CB LM Studio ${t("reqLmStudio")}`],
2302
+ ...session.runtimes.find((r) => r.id === "docker")?.detected ? [`\u2713 Docker ${t("dockerHint")}`] : [`\u25CB Docker ${t("reqDocker")}`],
2251
2303
  "",
2252
2304
  session.llamaBench ? t("doctorReady", { count: session.models.filter((m) => m.artifactPath).length }) : t("doctorNoBench")
2253
2305
  ];
@@ -2549,6 +2601,14 @@ ${launched.stderr || launched.stdout || t("launchExecFail")}`
2549
2601
  return { ok: true, log: `${lines.join("\n")}
2550
2602
  ${launched.stdout}`.trim() };
2551
2603
  }
2604
+ async function executeDockerOllama() {
2605
+ const args2 = DOCKER_OLLAMA.split(" ").slice(1);
2606
+ const result = await runCommand("docker", args2, { timeout: 10 * 6e4 });
2607
+ if (result.exitCode !== 0) {
2608
+ return { ok: false, log: result.stderr || t("dockerRunFail") };
2609
+ }
2610
+ return { ok: true, log: t("dockerRunOk") };
2611
+ }
2552
2612
 
2553
2613
  // src/integrations/pull.ts
2554
2614
  var INSTALLABLE = ["S", "A", "B"];
@@ -2598,6 +2658,20 @@ function SetupView({ session }) {
2598
2658
  setBusy(false);
2599
2659
  });
2600
2660
  }
2661
+ if (input === "4") {
2662
+ const docker = session.runtimes.find((r) => r.id === "docker")?.detected;
2663
+ if (!docker) {
2664
+ setLog(t("reqDocker"));
2665
+ return;
2666
+ }
2667
+ setBusy(true);
2668
+ setLog(t("dockerStarting"));
2669
+ void executeDockerOllama().then((result) => {
2670
+ setLog(result.log);
2671
+ setBusy(false);
2672
+ });
2673
+ return;
2674
+ }
2601
2675
  const tool = TOOLS.find((item) => item.key === input)?.id;
2602
2676
  if (tool && rec) {
2603
2677
  const decision = decideLaunch(session, tool);
@@ -2627,14 +2701,25 @@ function SetupView({ session }) {
2627
2701
  resolveOllamaTag(item.model.id, item.model.name) ?? "\u2014"
2628
2702
  ] })
2629
2703
  ] }, `${item.useCase}-${item.model.id}`)),
2630
- /* @__PURE__ */ jsx6(Text6, { children: session.agents.map((agent, index) => /* @__PURE__ */ jsxs6(Text6, { color: agent.detected ? "green" : "gray", children: [
2631
- index ? " " : "",
2704
+ session.agents.map((agent, index) => /* @__PURE__ */ jsxs6(Text6, { color: agent.detected ? "green" : "yellow", children: [
2632
2705
  "[",
2633
2706
  index + 1,
2634
- "]",
2707
+ "] ",
2635
2708
  agent.detected ? "\u2713" : "\u25CB",
2636
- agent.label
2637
- ] }, agent.id)) }),
2709
+ " ",
2710
+ agent.label,
2711
+ agent.detected ? "" : ` \u2192 ${agent.launch}`
2712
+ ] }, agent.id)),
2713
+ /* @__PURE__ */ jsxs6(Text6, { color: session.runtimes.find((r) => r.id === "docker")?.detected ? "green" : "yellow", children: [
2714
+ "[4] ",
2715
+ session.runtimes.find((r) => r.id === "docker")?.detected ? "\u2713" : "\u25CB",
2716
+ " Docker",
2717
+ session.runtimes.find((r) => r.id === "docker")?.detected ? ` \u2192 ${t("dockerHint")}` : ` \u2192 ${t("reqDocker")}`
2718
+ ] }),
2719
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: fit(
2720
+ `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")}`,
2721
+ cols
2722
+ ) }),
2638
2723
  /* @__PURE__ */ jsx6(Typewriter, { text: fit(`Enter=pull ${tag ?? ""} 1/2/3=agent Esc=back`, cols), ms: 14, dimColor: true }),
2639
2724
  log ? /* @__PURE__ */ jsx6(Text6, { color: "green", children: fit(log, cols) }) : null
2640
2725
  ] });
@@ -2642,15 +2727,21 @@ function SetupView({ session }) {
2642
2727
 
2643
2728
  // src/tui/views/Compare.tsx
2644
2729
  import { Box as Box6, Text as Text7 } from "ink";
2645
- import { jsx as jsx7 } from "react/jsx-runtime";
2730
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2646
2731
  function CompareView({ session }) {
2647
- return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { children: compareText(session.history) }) });
2732
+ const cols = useCols();
2733
+ const n = new Set(session.history.map((h) => h.model.id)).size;
2734
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", width: cols, children: [
2735
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "green", children: t("compareTitle") }),
2736
+ /* @__PURE__ */ jsx7(Typewriter, { text: fit(t("compareHow"), cols), ms: 12, dimColor: true }),
2737
+ n < 2 ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: fit(t("compareNeedBench"), cols) }) : /* @__PURE__ */ jsx7(Text7, { children: compareText(session.history) })
2738
+ ] });
2648
2739
  }
2649
2740
 
2650
2741
  // src/tui/views/Benchmark.tsx
2651
2742
  import { Box as Box7, Text as Text8, useInput as useInput2 } from "ink";
2652
2743
  import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
2653
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2744
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2654
2745
  function BenchmarkView(props) {
2655
2746
  const [phase, setPhase] = useState3(props.models.length ? "select" : "error");
2656
2747
  const [sample, setSample] = useState3();
@@ -2710,9 +2801,9 @@ function BenchmarkView(props) {
2710
2801
  const h = props.session.hardware;
2711
2802
  const model = props.models[props.selected];
2712
2803
  if (phase === "select") {
2713
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2804
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2714
2805
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: t("selectBenchmark") }),
2715
- props.models.map((m, i) => /* @__PURE__ */ jsxs7(Text8, { color: i === props.selected ? "cyan" : void 0, children: [
2806
+ props.models.map((m, i) => /* @__PURE__ */ jsxs8(Text8, { color: i === props.selected ? "cyan" : void 0, children: [
2716
2807
  i === props.selected ? "\u276F " : " ",
2717
2808
  m.name
2718
2809
  ] }, m.id)),
@@ -2720,9 +2811,9 @@ function BenchmarkView(props) {
2720
2811
  ] });
2721
2812
  }
2722
2813
  if (phase === "done" && result) {
2723
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2814
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2724
2815
  /* @__PURE__ */ jsx8(Text8, { children: resultText(result.record, result.assessment) }),
2725
- /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
2816
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2726
2817
  t("saved"),
2727
2818
  " ",
2728
2819
  result.path
@@ -2736,11 +2827,11 @@ function BenchmarkView(props) {
2736
2827
  const gpu = sample?.gpu?.utilizationPercent;
2737
2828
  const memUsed = sample?.memory.usedBytes;
2738
2829
  const memTotal = h.memory.totalBytes;
2739
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2740
- /* @__PURE__ */ jsxs7(Box7, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [
2830
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2831
+ /* @__PURE__ */ jsxs8(Box7, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [
2741
2832
  /* @__PURE__ */ jsx8(Text8, { bold: true, children: t("benchmarkTitle") }),
2742
2833
  /* @__PURE__ */ jsx8(Text8, { children: model?.name }),
2743
- /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
2834
+ /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
2744
2835
  h.cpu.name,
2745
2836
  " \xB7 ",
2746
2837
  formatBytes(h.memory.totalBytes, 0),
@@ -2748,9 +2839,9 @@ function BenchmarkView(props) {
2748
2839
  memoryLabel(h)
2749
2840
  ] })
2750
2841
  ] }),
2751
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2842
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2752
2843
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("performance") }),
2753
- /* @__PURE__ */ jsxs7(Text8, { children: [
2844
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2754
2845
  t("elapsed"),
2755
2846
  " ",
2756
2847
  elapsed.toFixed(1),
@@ -2758,21 +2849,21 @@ function BenchmarkView(props) {
2758
2849
  ] }),
2759
2850
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("measuredAfter") })
2760
2851
  ] }),
2761
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2852
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2762
2853
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("system") }),
2763
- /* @__PURE__ */ jsxs7(Text8, { children: [
2854
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2764
2855
  "CPU ",
2765
2856
  bar(cpu),
2766
2857
  " ",
2767
2858
  formatPercent(cpu)
2768
2859
  ] }),
2769
- /* @__PURE__ */ jsxs7(Text8, { children: [
2860
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2770
2861
  "GPU ",
2771
2862
  bar(gpu),
2772
2863
  " ",
2773
2864
  formatPercent(gpu)
2774
2865
  ] }),
2775
- /* @__PURE__ */ jsxs7(Text8, { children: [
2866
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2776
2867
  "Memory ",
2777
2868
  bar(memUsed && memTotal ? memUsed / memTotal * 100 : void 0),
2778
2869
  " ",
@@ -2780,36 +2871,36 @@ function BenchmarkView(props) {
2780
2871
  " / ",
2781
2872
  formatBytes(memTotal, 0)
2782
2873
  ] }),
2783
- /* @__PURE__ */ jsxs7(Text8, { children: [
2874
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2784
2875
  "Swap ",
2785
2876
  bar(0),
2786
2877
  " ",
2787
2878
  sample ? formatBytes(sample.memory.swapUsedBytes) : "N/A"
2788
2879
  ] })
2789
2880
  ] }),
2790
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2881
+ /* @__PURE__ */ jsxs8(Box7, { marginTop: 1, flexDirection: "column", children: [
2791
2882
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("powerThermals") }),
2792
- /* @__PURE__ */ jsxs7(Text8, { children: [
2883
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2793
2884
  "Package ",
2794
2885
  sample?.packagePowerWatts ?? "N/A",
2795
2886
  " W"
2796
2887
  ] }),
2797
- /* @__PURE__ */ jsxs7(Text8, { children: [
2888
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2798
2889
  "Temperature ",
2799
2890
  sample?.thermal?.temperatureC ?? "N/A",
2800
2891
  " \xB0C"
2801
2892
  ] }),
2802
- /* @__PURE__ */ jsxs7(Text8, { children: [
2893
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2803
2894
  "Thermal Pressure ",
2804
2895
  sample?.thermal?.pressure ?? "N/A"
2805
2896
  ] }),
2806
2897
  /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: t("permissionsHint") })
2807
2898
  ] }),
2808
- /* @__PURE__ */ jsxs7(Text8, { children: [
2899
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2809
2900
  sparkline(cpuHist.current),
2810
2901
  " CPU"
2811
2902
  ] }),
2812
- /* @__PURE__ */ jsxs7(Text8, { children: [
2903
+ /* @__PURE__ */ jsxs8(Text8, { children: [
2813
2904
  sparkline(memHist.current.map((v) => v / 1024 ** 3)),
2814
2905
  " Memory"
2815
2906
  ] }),
@@ -2819,9 +2910,9 @@ function BenchmarkView(props) {
2819
2910
 
2820
2911
  // src/tui/views/History.tsx
2821
2912
  import { Box as Box8, Text as Text9 } from "ink";
2822
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2913
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2823
2914
  function HistoryView({ session }) {
2824
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2915
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
2825
2916
  /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("measured") }),
2826
2917
  /* @__PURE__ */ jsx9(Text9, { children: historyText(session.history) })
2827
2918
  ] });
@@ -3024,7 +3115,7 @@ function tasksText(plans, answers) {
3024
3115
  }
3025
3116
 
3026
3117
  // src/tui/views/Tasks.tsx
3027
- import { Fragment, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3118
+ import { Fragment, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
3028
3119
  var KIND_LABEL = {
3029
3120
  code: "tasksOptCode",
3030
3121
  video: "tasksOptVideo",
@@ -3067,11 +3158,11 @@ function TasksView({ session }) {
3067
3158
  if (step === 3) {
3068
3159
  return /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx10(Text10, { children: tasksText(plans, { kinds, scope, priority }) }) });
3069
3160
  }
3070
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3161
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
3071
3162
  /* @__PURE__ */ jsx10(Text10, { bold: true, color: "cyan", children: t("tasksTitle") }),
3072
- step === 0 && /* @__PURE__ */ jsxs9(Fragment, { children: [
3163
+ step === 0 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3073
3164
  /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ1") }),
3074
- WORK_KINDS.map((kind, i) => /* @__PURE__ */ jsxs9(Text10, { color: i === cursor ? "cyan" : void 0, children: [
3165
+ WORK_KINDS.map((kind, i) => /* @__PURE__ */ jsxs10(Text10, { color: i === cursor ? "cyan" : void 0, children: [
3075
3166
  i === cursor ? "\u276F " : " ",
3076
3167
  "[",
3077
3168
  kinds.includes(kind) ? "x" : " ",
@@ -3079,20 +3170,20 @@ function TasksView({ session }) {
3079
3170
  t(KIND_LABEL[kind])
3080
3171
  ] }, kind))
3081
3172
  ] }),
3082
- step === 1 && /* @__PURE__ */ jsxs9(Fragment, { children: [
3173
+ step === 1 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3083
3174
  /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ2") }),
3084
- /* @__PURE__ */ jsxs9(Text10, { color: scope === "installed" ? "cyan" : void 0, children: [
3175
+ /* @__PURE__ */ jsxs10(Text10, { color: scope === "installed" ? "cyan" : void 0, children: [
3085
3176
  scope === "installed" ? "\u276F " : " ",
3086
3177
  t("tasksOptInstalled")
3087
3178
  ] }),
3088
- /* @__PURE__ */ jsxs9(Text10, { color: scope === "all" ? "cyan" : void 0, children: [
3179
+ /* @__PURE__ */ jsxs10(Text10, { color: scope === "all" ? "cyan" : void 0, children: [
3089
3180
  scope === "all" ? "\u276F " : " ",
3090
3181
  t("tasksOptAll")
3091
3182
  ] })
3092
3183
  ] }),
3093
- step === 2 && /* @__PURE__ */ jsxs9(Fragment, { children: [
3184
+ step === 2 && /* @__PURE__ */ jsxs10(Fragment, { children: [
3094
3185
  /* @__PURE__ */ jsx10(Text10, { children: t("tasksQ3") }),
3095
- ["speed", "balanced", "quality"].map((item) => /* @__PURE__ */ jsxs9(Text10, { color: priority === item ? "cyan" : void 0, children: [
3186
+ ["speed", "balanced", "quality"].map((item) => /* @__PURE__ */ jsxs10(Text10, { color: priority === item ? "cyan" : void 0, children: [
3096
3187
  priority === item ? "\u276F " : " ",
3097
3188
  item === "speed" ? t("tasksOptSpeed") : item === "quality" ? t("tasksOptQuality") : t("tasksOptBalanced")
3098
3189
  ] }, item))
@@ -3118,7 +3209,7 @@ function handleAppKey(screen, input, key) {
3118
3209
  if (letter === "q") return { type: "quit" };
3119
3210
  if (key.escape) return screen === "home" ? { type: "quit" } : { type: "home" };
3120
3211
  if (screen === "setup") {
3121
- if (letter === "i" || letter === "1" || letter === "2" || letter === "3") return { type: "ignore" };
3212
+ if (letter === "i" || letter === "1" || letter === "2" || letter === "3" || letter === "4") return { type: "ignore" };
3122
3213
  }
3123
3214
  const nav = globalNav(letter);
3124
3215
  if (nav) return nav;
@@ -3129,14 +3220,14 @@ function handleAppKey(screen, input, key) {
3129
3220
 
3130
3221
  // src/tui/Welcome.tsx
3131
3222
  import { Box as Box10, Text as Text11 } from "ink";
3132
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3223
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
3133
3224
  function Chrome() {
3134
3225
  const cols = useCols();
3135
3226
  const wide = cols >= 72;
3136
3227
  const brand = `${t("appTitle")} ${t("byline")}`;
3137
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, marginBottom: 1, children: [
3228
+ return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", width: cols, marginBottom: 1, children: [
3138
3229
  /* @__PURE__ */ jsx11(Typewriter, { text: brand, ms: 28, color: "cyan", bold: true, once: true }),
3139
- /* @__PURE__ */ jsx11(Text11, { children: wide ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
3230
+ /* @__PURE__ */ jsx11(Text11, { children: wide ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
3140
3231
  /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "[S] Setup" }),
3141
3232
  /* @__PURE__ */ jsx11(Text11, { children: " " }),
3142
3233
  /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[B] Bench" }),
@@ -3154,7 +3245,7 @@ function Chrome() {
3154
3245
  /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[L] Log" }),
3155
3246
  /* @__PURE__ */ jsx11(Text11, { children: " " }),
3156
3247
  /* @__PURE__ */ jsx11(Text11, { color: "red", children: "[Q] Quit" })
3157
- ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
3248
+ ] }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
3158
3249
  /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "[S]" }),
3159
3250
  /* @__PURE__ */ jsx11(Text11, { color: "green", children: "[B]" }),
3160
3251
  /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: "[M]" }),
@@ -3169,7 +3260,7 @@ function Chrome() {
3169
3260
  }
3170
3261
 
3171
3262
  // src/tui/App.tsx
3172
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3263
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3173
3264
  function App(props) {
3174
3265
  const { exit } = useApp2();
3175
3266
  const [screen, setScreen] = useState5(props.start ?? "home");
@@ -3186,7 +3277,7 @@ function App(props) {
3186
3277
  if (result.type === "home") setScreen("home");
3187
3278
  if (result.type === "screen") setScreen(result.screen);
3188
3279
  });
3189
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
3280
+ return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", paddingX: 1, children: [
3190
3281
  screen !== "benchmark" && /* @__PURE__ */ jsx12(Chrome, {}),
3191
3282
  screen === "home" && /* @__PURE__ */ jsx12(Dashboard, { session: props.session }),
3192
3283
  screen === "models" && /* @__PURE__ */ jsx12(ModelsView, { session: props.session }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",