pudu-ai 0.2.10 → 0.2.12

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 +200 -144
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -851,7 +851,7 @@ var en = {
851
851
  setupLinkNow: "installed \u2192 press 1/2/3 to link this model",
852
852
  setupInstallAgent: "not installed \u2192 press 1/2/3 to install via ollama launch (if eligible)",
853
853
  setupKeys: "Enter/I = pull model 1 OpenCode 2 Hermes 3 OpenClaw Esc = back",
854
- setupCta: "[S] setup",
854
+ setupCta: "[S] Setup: install model + OpenCode / Hermes / OpenClaw",
855
855
  machine: "MACHINE",
856
856
  runtimes: "LOCAL AI RUNTIMES",
857
857
  installedModels: "INSTALLED MODELS",
@@ -1036,7 +1036,7 @@ var es = {
1036
1036
  setupLinkNow: "instalado \u2192 pulsa 1/2/3 para vincular este modelo",
1037
1037
  setupInstallAgent: "no instalado \u2192 pulsa 1/2/3 para instalar con ollama launch (si es elegible)",
1038
1038
  setupKeys: "Enter/I = descargar modelo 1 OpenCode 2 Hermes 3 OpenClaw Esc = atr\xE1s",
1039
- setupCta: "[S] setup",
1039
+ setupCta: "[S] Setup: instalar modelo + OpenCode / Hermes / OpenClaw",
1040
1040
  machine: "EQUIPO",
1041
1041
  runtimes: "RUNTIMES DE IA LOCAL",
1042
1042
  installedModels: "MODELOS INSTALADOS",
@@ -1985,7 +1985,7 @@ function setLogLevel(next) {
1985
1985
  import { render } from "ink";
1986
1986
 
1987
1987
  // src/tui/App.tsx
1988
- import { Box as Box10, useApp as useApp2, useInput as useInput4 } from "ink";
1988
+ import { Box as Box11, useApp as useApp2, useInput as useInput4 } from "ink";
1989
1989
  import { useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "react";
1990
1990
 
1991
1991
  // src/tui/views/Dashboard.tsx
@@ -2035,37 +2035,40 @@ function sparkline(values, width = 24) {
2035
2035
  return slice.map((v) => glyphs[Math.min(glyphs.length - 1, Math.floor(v / max * (glyphs.length - 1)))]).join("").padStart(width, glyphs[0]);
2036
2036
  }
2037
2037
 
2038
+ // src/tui/width.ts
2039
+ import { useStdout } from "ink";
2040
+ function useCols() {
2041
+ const { stdout } = useStdout();
2042
+ const cols = stdout?.columns ?? 80;
2043
+ return Math.max(40, Math.min(cols - 2, 100));
2044
+ }
2045
+ function fit(text, width) {
2046
+ if (text.length <= width) return text;
2047
+ if (width <= 1) return "\u2026";
2048
+ return `${text.slice(0, width - 1)}\u2026`;
2049
+ }
2050
+
2038
2051
  // src/tui/views/Dashboard.tsx
2039
2052
  import { jsx, jsxs } from "react/jsx-runtime";
2040
2053
  function Dashboard({ session }) {
2054
+ const cols = useCols();
2041
2055
  const h = session.hardware;
2042
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2043
- /* @__PURE__ */ jsxs(Text, { children: [
2044
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: h.machineModel ?? t("unknownMachine") }),
2045
- /* @__PURE__ */ jsxs(Text, { children: [
2046
- " \xB7 ",
2047
- h.cpu.name ?? t("cpuNa")
2048
- ] }),
2049
- /* @__PURE__ */ jsxs(Text, { children: [
2050
- " \xB7 ",
2051
- h.cpu.physicalCores ?? "?",
2052
- "c"
2053
- ] }),
2054
- /* @__PURE__ */ jsxs(Text, { children: [
2055
- " ",
2056
- "\xB7 ",
2057
- formatBytes(h.memory.totalBytes, 0),
2058
- " ",
2059
- memoryLabel(h)
2060
- ] })
2061
- ] }),
2056
+ const line = fit(
2057
+ `${h.machineModel ?? t("unknownMachine")} \xB7 ${h.cpu.name ?? t("cpuNa")} \xB7 ${formatBytes(h.memory.totalBytes, 0)} ${memoryLabel(h)}`,
2058
+ cols
2059
+ );
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 }),
2062
2063
  /* @__PURE__ */ jsx(Text, { children: session.runtimes.map((r, i) => /* @__PURE__ */ jsxs(Text, { color: r.detected ? "green" : "gray", children: [
2063
- i ? " " : "",
2064
+ i ? " " : "",
2064
2065
  r.detected ? "\u2713" : "\u25CB",
2065
2066
  r.label
2066
2067
  ] }, r.id)) }),
2067
- session.rows.slice(0, 5).map((row) => /* @__PURE__ */ jsxs(Text, { children: [
2068
- /* @__PURE__ */ jsx(Text, { color: "green", children: row.local.name }),
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) }),
2069
2072
  /* @__PURE__ */ jsx(Text, { children: " " }),
2070
2073
  /* @__PURE__ */ jsx(Text, { color: gradeColor(row.compatibility?.grade), children: row.compatibility?.grade ?? "\u2014" }),
2071
2074
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
@@ -2073,16 +2076,17 @@ function Dashboard({ session }) {
2073
2076
  row.lastBenchmark?.benchmark.generationTokensPerSecond ? formatTokensPerSec(row.lastBenchmark.benchmark.generationTokensPerSecond) : t("notTested")
2074
2077
  ] })
2075
2078
  ] }, row.local.id)),
2079
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("recommended") }),
2076
2080
  session.recommendations.slice(0, 4).map((rec) => /* @__PURE__ */ jsxs(Text, { children: [
2077
- /* @__PURE__ */ jsx(Text, { color: "magenta", children: rec.useCase }),
2081
+ /* @__PURE__ */ jsx(Text, { color: "magenta", children: fit(rec.useCase, 10) }),
2078
2082
  /* @__PURE__ */ jsxs(Text, { children: [
2079
2083
  " ",
2080
- rec.model.name,
2084
+ fit(rec.model.name, 22),
2081
2085
  " "
2082
2086
  ] }),
2083
2087
  /* @__PURE__ */ jsx(Text, { color: gradeColor(rec.grade), children: rec.grade })
2084
2088
  ] }, `${rec.useCase}-${rec.model.id}`)),
2085
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: t("setupCta") })
2089
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: fit(t("setupCta"), cols) })
2086
2090
  ] });
2087
2091
  }
2088
2092
 
@@ -2146,11 +2150,11 @@ function runtimesText(session) {
2146
2150
  function modelsText(session) {
2147
2151
  const lines = [bold(cyan(t("installedModels"))), dim("MODEL INSTALLED FIT EST. SPEED MEASURED")];
2148
2152
  for (const row of session.rows) {
2149
- const fit = row.compatibility?.grade ?? "\u2014";
2153
+ const fit2 = row.compatibility?.grade ?? "\u2014";
2150
2154
  const est = row.compatibility?.estimatedTokensPerSecond ? `~${formatNumber(row.compatibility.estimatedTokensPerSecond)} t/s` : "\u2014";
2151
2155
  const measured = row.lastBenchmark?.benchmark.generationTokensPerSecond ? formatTokensPerSec(row.lastBenchmark.benchmark.generationTokensPerSecond) : t("notTested");
2152
2156
  lines.push(
2153
- `${green(row.local.name.padEnd(22))} \u2713 ${gradeAnsi(fit).padEnd(9)} ${est.padEnd(14)} ${measured}`
2157
+ `${green(row.local.name.padEnd(22))} \u2713 ${gradeAnsi(fit2).padEnd(9)} ${est.padEnd(14)} ${measured}`
2154
2158
  );
2155
2159
  }
2156
2160
  lines.push("", t("compatible"));
@@ -2311,7 +2315,36 @@ function HardwareView({ session }) {
2311
2315
  }
2312
2316
 
2313
2317
  // src/tui/views/Recommend.tsx
2314
- import { Box as Box4, Text as Text4, useApp, useInput } from "ink";
2318
+ import { Box as Box4, Text as Text4 } from "ink";
2319
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2320
+ function RecommendView({ session }) {
2321
+ 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: [
2328
+ " ",
2329
+ fit(rec.model.name, 22),
2330
+ " "
2331
+ ] }),
2332
+ /* @__PURE__ */ jsx4(Text4, { color: gradeColor(rec.grade), children: rec.grade }),
2333
+ /* @__PURE__ */ jsxs4(Text4, { children: [
2334
+ " ",
2335
+ GRADE_MEANING[rec.grade]
2336
+ ] }),
2337
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2338
+ " ",
2339
+ resolveOllamaTag(rec.model.id, rec.model.name) ?? "\u2014"
2340
+ ] })
2341
+ ] }, `${rec.useCase}-${rec.model.id}`)),
2342
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: fit(t("setupCta"), cols) })
2343
+ ] });
2344
+ }
2345
+
2346
+ // src/tui/views/Setup.tsx
2347
+ import { Box as Box5, Text as Text5, useApp, useInput } from "ink";
2315
2348
  import { useState } from "react";
2316
2349
 
2317
2350
  // src/integrations/catalog.ts
@@ -2383,8 +2416,8 @@ function decideLaunch(session, id) {
2383
2416
  if (!candidates.length) {
2384
2417
  for (const model of session.catalog) {
2385
2418
  if (!useCaseOk(model.useCase, def.useCases)) continue;
2386
- const fit = localCompatibility(session.hardware, model);
2387
- if (!def.allowedGrades.includes(fit.grade)) continue;
2419
+ const fit2 = localCompatibility(session.hardware, model);
2420
+ if (!def.allowedGrades.includes(fit2.grade)) continue;
2388
2421
  const tag = resolveOllamaTag(model.id, model.name);
2389
2422
  if (!tag) continue;
2390
2423
  candidates.push({
@@ -2392,7 +2425,7 @@ function decideLaunch(session, id) {
2392
2425
  ollamaTag: tag,
2393
2426
  installed: false,
2394
2427
  origin: "estimated",
2395
- grade: fit.grade
2428
+ grade: fit2.grade
2396
2429
  });
2397
2430
  }
2398
2431
  }
@@ -2501,14 +2534,15 @@ async function pullOllamaModel(modelId, extraName) {
2501
2534
  return { ok: true, log: t("modelPulled", { model: tag }), tag };
2502
2535
  }
2503
2536
 
2504
- // src/tui/views/Recommend.tsx
2505
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2537
+ // src/tui/views/Setup.tsx
2538
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2506
2539
  var TOOLS = [
2507
2540
  { key: "1", id: "opencode", label: "OpenCode" },
2508
2541
  { key: "2", id: "hermes", label: "Hermes" },
2509
2542
  { key: "3", id: "openclaw", label: "OpenClaw" }
2510
2543
  ];
2511
- function RecommendView({ session }) {
2544
+ function SetupView({ session }) {
2545
+ const cols = useCols();
2512
2546
  const { exit } = useApp();
2513
2547
  const [cursor, setCursor] = useState(0);
2514
2548
  const [log, setLog] = useState("");
@@ -2548,19 +2582,19 @@ function RecommendView({ session }) {
2548
2582
  }
2549
2583
  });
2550
2584
  const tag = rec ? resolveOllamaTag(rec.model.id, rec.model.name) : void 0;
2551
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2552
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("setupTitle") }),
2553
- recs.map((item, i) => /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
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: [
2554
2588
  i === cursor ? "\u276F " : " ",
2555
- item.model.name,
2589
+ fit(item.model.name, 20),
2556
2590
  " ",
2557
- /* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), children: item.grade }),
2558
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2591
+ /* @__PURE__ */ jsx5(Text5, { color: gradeColor(item.grade), children: item.grade }),
2592
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
2559
2593
  " ",
2560
2594
  resolveOllamaTag(item.model.id, item.model.name) ?? "\u2014"
2561
2595
  ] })
2562
2596
  ] }, `${item.useCase}-${item.model.id}`)),
2563
- /* @__PURE__ */ jsx4(Text4, { children: session.agents.map((agent, index) => /* @__PURE__ */ jsxs4(Text4, { color: agent.detected ? "green" : "gray", children: [
2597
+ /* @__PURE__ */ jsx5(Text5, { children: session.agents.map((agent, index) => /* @__PURE__ */ jsxs5(Text5, { color: agent.detected ? "green" : "gray", children: [
2564
2598
  index ? " " : "",
2565
2599
  "[",
2566
2600
  index + 1,
@@ -2568,26 +2602,22 @@ function RecommendView({ session }) {
2568
2602
  agent.detected ? "\u2713" : "\u25CB",
2569
2603
  agent.label
2570
2604
  ] }, agent.id)) }),
2571
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
2572
- "Enter=pull ",
2573
- tag ?? "",
2574
- " \xB7 1/2/3=agent"
2575
- ] }),
2576
- log ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: log }) : null
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
2577
2607
  ] });
2578
2608
  }
2579
2609
 
2580
2610
  // src/tui/views/Compare.tsx
2581
- import { Box as Box5, Text as Text5 } from "ink";
2582
- import { jsx as jsx5 } from "react/jsx-runtime";
2611
+ import { Box as Box6, Text as Text6 } from "ink";
2612
+ import { jsx as jsx6 } from "react/jsx-runtime";
2583
2613
  function CompareView({ session }) {
2584
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx5(Text5, { children: compareText(session.history) }) });
2614
+ return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx6(Text6, { children: compareText(session.history) }) });
2585
2615
  }
2586
2616
 
2587
2617
  // src/tui/views/Benchmark.tsx
2588
- import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
2618
+ import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
2589
2619
  import { useEffect, useRef, useState as useState2 } from "react";
2590
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2620
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2591
2621
  function BenchmarkView(props) {
2592
2622
  const [phase, setPhase] = useState2(props.models.length ? "select" : "error");
2593
2623
  const [sample, setSample] = useState2();
@@ -2647,19 +2677,19 @@ function BenchmarkView(props) {
2647
2677
  const h = props.session.hardware;
2648
2678
  const model = props.models[props.selected];
2649
2679
  if (phase === "select") {
2650
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2651
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: t("selectBenchmark") }),
2652
- props.models.map((m, i) => /* @__PURE__ */ jsxs5(Text6, { color: i === props.selected ? "cyan" : void 0, children: [
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: [
2653
2683
  i === props.selected ? "\u276F " : " ",
2654
2684
  m.name
2655
2685
  ] }, m.id)),
2656
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("enterToRun") })
2686
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("enterToRun") })
2657
2687
  ] });
2658
2688
  }
2659
2689
  if (phase === "done" && result) {
2660
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2661
- /* @__PURE__ */ jsx6(Text6, { children: resultText(result.record, result.assessment) }),
2662
- /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
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: [
2663
2693
  t("saved"),
2664
2694
  " ",
2665
2695
  result.path
@@ -2667,17 +2697,17 @@ function BenchmarkView(props) {
2667
2697
  ] });
2668
2698
  }
2669
2699
  if (phase === "error") {
2670
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx6(Text6, { color: "red", children: error }) });
2700
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text7, { color: "red", children: error }) });
2671
2701
  }
2672
2702
  const cpu = sample?.cpu?.utilizationPercent;
2673
2703
  const gpu = sample?.gpu?.utilizationPercent;
2674
2704
  const memUsed = sample?.memory.usedBytes;
2675
2705
  const memTotal = h.memory.totalBytes;
2676
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
2677
- /* @__PURE__ */ jsxs5(Box6, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [
2678
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: t("benchmarkTitle") }),
2679
- /* @__PURE__ */ jsx6(Text6, { children: model?.name }),
2680
- /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
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: [
2681
2711
  h.cpu.name,
2682
2712
  " \xB7 ",
2683
2713
  formatBytes(h.memory.totalBytes, 0),
@@ -2685,31 +2715,31 @@ function BenchmarkView(props) {
2685
2715
  memoryLabel(h)
2686
2716
  ] })
2687
2717
  ] }),
2688
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2689
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: t("performance") }),
2690
- /* @__PURE__ */ jsxs5(Text6, { children: [
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: [
2691
2721
  t("elapsed"),
2692
2722
  " ",
2693
2723
  elapsed.toFixed(1),
2694
2724
  " s"
2695
2725
  ] }),
2696
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("measuredAfter") })
2726
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("measuredAfter") })
2697
2727
  ] }),
2698
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2699
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: t("system") }),
2700
- /* @__PURE__ */ jsxs5(Text6, { children: [
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: [
2701
2731
  "CPU ",
2702
2732
  bar(cpu),
2703
2733
  " ",
2704
2734
  formatPercent(cpu)
2705
2735
  ] }),
2706
- /* @__PURE__ */ jsxs5(Text6, { children: [
2736
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2707
2737
  "GPU ",
2708
2738
  bar(gpu),
2709
2739
  " ",
2710
2740
  formatPercent(gpu)
2711
2741
  ] }),
2712
- /* @__PURE__ */ jsxs5(Text6, { children: [
2742
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2713
2743
  "Memory ",
2714
2744
  bar(memUsed && memTotal ? memUsed / memTotal * 100 : void 0),
2715
2745
  " ",
@@ -2717,55 +2747,55 @@ function BenchmarkView(props) {
2717
2747
  " / ",
2718
2748
  formatBytes(memTotal, 0)
2719
2749
  ] }),
2720
- /* @__PURE__ */ jsxs5(Text6, { children: [
2750
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2721
2751
  "Swap ",
2722
2752
  bar(0),
2723
2753
  " ",
2724
2754
  sample ? formatBytes(sample.memory.swapUsedBytes) : "N/A"
2725
2755
  ] })
2726
2756
  ] }),
2727
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
2728
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: t("powerThermals") }),
2729
- /* @__PURE__ */ jsxs5(Text6, { children: [
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: [
2730
2760
  "Package ",
2731
2761
  sample?.packagePowerWatts ?? "N/A",
2732
2762
  " W"
2733
2763
  ] }),
2734
- /* @__PURE__ */ jsxs5(Text6, { children: [
2764
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2735
2765
  "Temperature ",
2736
2766
  sample?.thermal?.temperatureC ?? "N/A",
2737
2767
  " \xB0C"
2738
2768
  ] }),
2739
- /* @__PURE__ */ jsxs5(Text6, { children: [
2769
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2740
2770
  "Thermal Pressure ",
2741
2771
  sample?.thermal?.pressure ?? "N/A"
2742
2772
  ] }),
2743
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("permissionsHint") })
2773
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("permissionsHint") })
2744
2774
  ] }),
2745
- /* @__PURE__ */ jsxs5(Text6, { children: [
2775
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2746
2776
  sparkline(cpuHist.current),
2747
2777
  " CPU"
2748
2778
  ] }),
2749
- /* @__PURE__ */ jsxs5(Text6, { children: [
2779
+ /* @__PURE__ */ jsxs6(Text7, { children: [
2750
2780
  sparkline(memHist.current.map((v) => v / 1024 ** 3)),
2751
2781
  " Memory"
2752
2782
  ] }),
2753
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: t("cancelHint") })
2783
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: t("cancelHint") })
2754
2784
  ] });
2755
2785
  }
2756
2786
 
2757
2787
  // src/tui/views/History.tsx
2758
- import { Box as Box7, Text as Text7 } from "ink";
2759
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2788
+ import { Box as Box8, Text as Text8 } from "ink";
2789
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2760
2790
  function HistoryView({ session }) {
2761
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2762
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: t("measured") }),
2763
- /* @__PURE__ */ jsx7(Text7, { children: historyText(session.history) })
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) })
2764
2794
  ] });
2765
2795
  }
2766
2796
 
2767
2797
  // src/tui/views/Tasks.tsx
2768
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
2798
+ import { Box as Box9, Text as Text9, useInput as useInput3 } from "ink";
2769
2799
  import { useMemo, useState as useState3 } from "react";
2770
2800
 
2771
2801
  // src/tasks/catalog.ts
@@ -2896,13 +2926,13 @@ function planTasks(session, answers) {
2896
2926
  const catalogExtras = answers.scope === "all" ? session.catalog.filter((model) => !session.rows.some((row) => idsLikelyMatch(row.local.id, model.id))).map((model) => {
2897
2927
  const useCases = model.useCase ?? [];
2898
2928
  const kinds = answers.kinds.filter((kind) => kindMatches(kind, useCases));
2899
- const fit = localCompatibility(session.hardware, model);
2929
+ const fit2 = localCompatibility(session.hardware, model);
2900
2930
  return {
2901
2931
  modelId: model.id,
2902
2932
  modelName: model.name,
2903
2933
  installed: false,
2904
2934
  origin: "estimated",
2905
- grade: fit.grade,
2935
+ grade: fit2.grade,
2906
2936
  useCases,
2907
2937
  kinds
2908
2938
  };
@@ -2961,7 +2991,7 @@ function tasksText(plans, answers) {
2961
2991
  }
2962
2992
 
2963
2993
  // src/tui/views/Tasks.tsx
2964
- import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2994
+ import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2965
2995
  var KIND_LABEL = {
2966
2996
  code: "tasksOptCode",
2967
2997
  video: "tasksOptVideo",
@@ -3002,13 +3032,13 @@ function TasksView({ session }) {
3002
3032
  }
3003
3033
  });
3004
3034
  if (step === 3) {
3005
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx8(Text8, { children: tasksText(plans, { kinds, scope, priority }) }) });
3035
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Text9, { children: tasksText(plans, { kinds, scope, priority }) }) });
3006
3036
  }
3007
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
3008
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: "cyan", children: t("tasksTitle") }),
3009
- step === 0 && /* @__PURE__ */ jsxs7(Fragment, { children: [
3010
- /* @__PURE__ */ jsx8(Text8, { children: t("tasksQ1") }),
3011
- WORK_KINDS.map((kind, i) => /* @__PURE__ */ jsxs7(Text8, { color: i === cursor ? "cyan" : void 0, children: [
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: [
3012
3042
  i === cursor ? "\u276F " : " ",
3013
3043
  "[",
3014
3044
  kinds.includes(kind) ? "x" : " ",
@@ -3016,20 +3046,20 @@ function TasksView({ session }) {
3016
3046
  t(KIND_LABEL[kind])
3017
3047
  ] }, kind))
3018
3048
  ] }),
3019
- step === 1 && /* @__PURE__ */ jsxs7(Fragment, { children: [
3020
- /* @__PURE__ */ jsx8(Text8, { children: t("tasksQ2") }),
3021
- /* @__PURE__ */ jsxs7(Text8, { color: scope === "installed" ? "cyan" : void 0, children: [
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: [
3022
3052
  scope === "installed" ? "\u276F " : " ",
3023
3053
  t("tasksOptInstalled")
3024
3054
  ] }),
3025
- /* @__PURE__ */ jsxs7(Text8, { color: scope === "all" ? "cyan" : void 0, children: [
3055
+ /* @__PURE__ */ jsxs8(Text9, { color: scope === "all" ? "cyan" : void 0, children: [
3026
3056
  scope === "all" ? "\u276F " : " ",
3027
3057
  t("tasksOptAll")
3028
3058
  ] })
3029
3059
  ] }),
3030
- step === 2 && /* @__PURE__ */ jsxs7(Fragment, { children: [
3031
- /* @__PURE__ */ jsx8(Text8, { children: t("tasksQ3") }),
3032
- ["speed", "balanced", "quality"].map((item) => /* @__PURE__ */ jsxs7(Text8, { color: priority === item ? "cyan" : void 0, children: [
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: [
3033
3063
  priority === item ? "\u276F " : " ",
3034
3064
  item === "speed" ? t("tasksOptSpeed") : item === "quality" ? t("tasksOptQuality") : t("tasksOptBalanced")
3035
3065
  ] }, item))
@@ -3046,7 +3076,7 @@ function globalNav(letter) {
3046
3076
  if (letter === "c") return { type: "screen", screen: "compare" };
3047
3077
  if (letter === "l") return { type: "screen", screen: "history" };
3048
3078
  if (letter === "t") return { type: "screen", screen: "tasks" };
3049
- if (letter === "s") return { type: "screen", screen: "recommend" };
3079
+ if (letter === "s") return { type: "screen", screen: "setup" };
3050
3080
  return void 0;
3051
3081
  }
3052
3082
  function handleAppKey(screen, input, key) {
@@ -3054,41 +3084,66 @@ function handleAppKey(screen, input, key) {
3054
3084
  if (screen === "benchmark") return { type: "ignore" };
3055
3085
  if (letter === "q") return { type: "quit" };
3056
3086
  if (key.escape) return screen === "home" ? { type: "quit" } : { type: "home" };
3057
- if (screen === "recommend") {
3087
+ if (screen === "setup") {
3058
3088
  if (letter === "i" || letter === "1" || letter === "2" || letter === "3") return { type: "ignore" };
3059
3089
  }
3060
3090
  const nav = globalNav(letter);
3061
3091
  if (nav) return nav;
3062
- if (letter === "i" && screen === "home") return { type: "screen", screen: "recommend" };
3092
+ if (letter === "i" && screen === "home") return { type: "screen", screen: "setup" };
3063
3093
  if (key.return && screen === "home") return { type: "screen", screen: "benchmark" };
3064
3094
  return { type: "ignore" };
3065
3095
  }
3066
3096
 
3067
3097
  // src/tui/Welcome.tsx
3068
- import { Box as Box9, Text as Text9 } from "ink";
3069
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
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";
3070
3100
  function Chrome() {
3071
- return /* @__PURE__ */ jsxs8(Box9, { children: [
3072
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("appTitle") }),
3073
- /* @__PURE__ */ jsxs8(Text9, { color: "magenta", children: [
3074
- " ",
3075
- t("byline"),
3076
- " "
3101
+ const cols = useCols();
3102
+ 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
3077
3112
  ] }),
3078
- /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "[S]" }),
3079
- /* @__PURE__ */ jsx9(Text9, { color: "green", children: "[B]" }),
3080
- /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "[M]" }),
3081
- /* @__PURE__ */ jsx9(Text9, { color: "magenta", children: "[R]" }),
3082
- /* @__PURE__ */ jsx9(Text9, { color: "blue", children: "[T]" }),
3083
- /* @__PURE__ */ jsx9(Text9, { children: "[H]" }),
3084
- /* @__PURE__ */ jsx9(Text9, { color: "green", children: "[C]" }),
3085
- /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "[L]" }),
3086
- /* @__PURE__ */ jsx9(Text9, { color: "red", children: "[Q]" })
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]" })
3141
+ ] }) })
3087
3142
  ] });
3088
3143
  }
3089
3144
 
3090
3145
  // src/tui/App.tsx
3091
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3146
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3092
3147
  function App(props) {
3093
3148
  const { exit } = useApp2();
3094
3149
  const [screen, setScreen] = useState4(props.start ?? "home");
@@ -3105,16 +3160,17 @@ function App(props) {
3105
3160
  if (result.type === "home") setScreen("home");
3106
3161
  if (result.type === "screen") setScreen(result.screen);
3107
3162
  });
3108
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingX: 1, children: [
3109
- screen !== "benchmark" && /* @__PURE__ */ jsx10(Chrome, {}),
3110
- screen === "home" && /* @__PURE__ */ jsx10(Dashboard, { session: props.session }),
3111
- screen === "models" && /* @__PURE__ */ jsx10(ModelsView, { session: props.session }),
3112
- screen === "hardware" && /* @__PURE__ */ jsx10(HardwareView, { session: props.session }),
3113
- screen === "recommend" && /* @__PURE__ */ jsx10(RecommendView, { session: props.session }),
3114
- screen === "compare" && /* @__PURE__ */ jsx10(CompareView, { session: props.session }),
3115
- screen === "history" && /* @__PURE__ */ jsx10(HistoryView, { session: props.session }),
3116
- screen === "tasks" && /* @__PURE__ */ jsx10(TasksView, { session: props.session }),
3117
- screen === "benchmark" && /* @__PURE__ */ jsx10(
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(
3118
3174
  BenchmarkView,
3119
3175
  {
3120
3176
  session: props.session,
@@ -3129,9 +3185,9 @@ function App(props) {
3129
3185
  }
3130
3186
 
3131
3187
  // src/tui/render.tsx
3132
- import { jsx as jsx11 } from "react/jsx-runtime";
3188
+ import { jsx as jsx12 } from "react/jsx-runtime";
3133
3189
  async function renderDashboard(session, preset, start) {
3134
- const instance = render(/* @__PURE__ */ jsx11(App, { session, preset, start }));
3190
+ const instance = render(/* @__PURE__ */ jsx12(App, { session, preset, start }));
3135
3191
  await instance.waitUntilExit();
3136
3192
  }
3137
3193
 
@@ -3198,7 +3254,7 @@ ${runtimesText(session)}`),
3198
3254
  print({ recommendations: session.recommendations, agents: session.agents }, true);
3199
3255
  return 0;
3200
3256
  }
3201
- await renderDashboard(session, args2.preset, "recommend");
3257
+ await renderDashboard(session, args2.preset, "setup");
3202
3258
  return 0;
3203
3259
  case "recommend": {
3204
3260
  if (args2.install && args2.yes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",