pudu-ai 0.2.5 → 0.2.7

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.
package/README.md CHANGED
@@ -42,6 +42,7 @@ npx pudu-ai
42
42
  npx pudu-ai hardware
43
43
  npx pudu-ai models
44
44
  npx pudu-ai models add-path ~/Models
45
+ npx pudu-ai setup
45
46
  npx pudu-ai recommend
46
47
  npx pudu-ai recommend --install --yes
47
48
  npx pudu-ai recommend --link opencode --yes
package/dist/cli/index.js CHANGED
@@ -17,7 +17,8 @@ function parseArgs(argv) {
17
17
  "doctor",
18
18
  "report",
19
19
  "tasks",
20
- "launch"
20
+ "launch",
21
+ "setup"
21
22
  ];
22
23
  const isKnown = known.includes(command);
23
24
  let addPath;
@@ -827,6 +828,7 @@ async function loadCatalogCache() {
827
828
  var en = {
828
829
  appTitle: "PUDU-AI",
829
830
  appSubtitle: "Local AI Hardware & Benchmark Lab",
831
+ byline: "by devjaime",
830
832
  aboutDashboard: "Opens the live lab: your machine, installed models, and estimates. Use it to see what can run here before you benchmark or launch a tool.",
831
833
  aboutHardware: "Detects CPU, GPU, and Unified Memory/RAM. Use it to know what this computer can host before pulling a model.",
832
834
  aboutModels: "Lists installed models vs catalog fits. FIT/EST. SPEED are estimated (CanIRun.ai by midudev); MEASURED is llama-bench history. Use it to separate \u201Calready here\u201D from \u201Cmight run\u201D.",
@@ -839,7 +841,17 @@ var en = {
839
841
  aboutReport: "Writes the latest measured run as Markdown. Use it to paste results into GitHub or notes.",
840
842
  aboutLaunch: "Explains Ollama integrations (OpenCode, OpenClaw, Hermes, Claude Code). Pull/launch only with --yes if the model grade is S\u2013B and speed is enough. Use it to wire a coding agent only when this hardware can carry it.",
841
843
  aboutAddPath: "Adds a GGUF folder to scan (not the whole disk). Use it so llama-bench can find models outside Ollama.",
842
- nav: "[B] Benchmark [M] Models [R] Recommend [T] Tasks [H] Hardware [C] Compare [L] History [Q] Quit",
844
+ nav: "[S] Setup/Install [B] Benchmark [M] Models [R] Recommend [T] Tasks [H] Hardware [C] Compare [L] History [Q] Quit",
845
+ setupTitle: "SETUP \u2014 INSTALL MODELS & AGENTS",
846
+ setupIntro: "Pick a recommended model (grade S\u2013B), install it with Ollama, then link or install OpenCode, Hermes, or OpenClaw.",
847
+ setupStep1: "1. Recommended models (\u2191\u2193 to choose)",
848
+ setupStep2: "2. Install the selected model",
849
+ setupInstallModel: "Press Enter or I \u2192 ollama pull (only if grade is S, A, or B)",
850
+ setupStep3: "3. Coding agent \u2014 link if present, install if missing",
851
+ setupLinkNow: "installed \u2192 press 1/2/3 to link this model",
852
+ setupInstallAgent: "not installed \u2192 press 1/2/3 to install via ollama launch (if eligible)",
853
+ setupKeys: "Enter/I = pull model 1 OpenCode 2 Hermes 3 OpenClaw Esc = back",
854
+ setupCta: "\u2192 Press [S] or [I] to INSTALL a recommended model and link OpenCode / Hermes / OpenClaw",
843
855
  machine: "MACHINE",
844
856
  runtimes: "LOCAL AI RUNTIMES",
845
857
  installedModels: "INSTALLED MODELS",
@@ -960,6 +972,7 @@ Usage:
960
972
  npx pudu-ai hardware
961
973
  npx pudu-ai models
962
974
  npx pudu-ai models add-path ~/Models
975
+ npx pudu-ai setup
963
976
  npx pudu-ai recommend
964
977
  npx pudu-ai recommend --install --yes
965
978
  npx pudu-ai recommend --link opencode --yes
@@ -999,6 +1012,7 @@ Credits:
999
1012
  var es = {
1000
1013
  appTitle: "PUDU-AI",
1001
1014
  appSubtitle: "Laboratorio local de hardware y benchmarks de IA",
1015
+ byline: "by devjaime",
1002
1016
  aboutDashboard: "Abre el laboratorio en vivo: equipo, modelos instalados y estimaciones. Sirve para ver qu\xE9 puede correr aqu\xED antes de medir o lanzar una herramienta.",
1003
1017
  aboutHardware: "Detecta CPU, GPU y memoria unificada/RAM. Sirve para saber qu\xE9 puede hospedar este equipo antes de descargar un modelo.",
1004
1018
  aboutModels: "Lista instalados frente al cat\xE1logo. FIT/EST. SPEED son estimados (CanIRun.ai de midudev); MEASURED es historial de llama-bench. Sirve para separar \u201Cya est\xE1\u201D de \u201Cpodr\xEDa correr\u201D.",
@@ -1011,7 +1025,17 @@ var es = {
1011
1025
  aboutReport: "Exporta la \xFAltima corrida medida en Markdown. Sirve para pegar resultados en GitHub o notas.",
1012
1026
  aboutLaunch: "Explica las integraciones de Ollama (OpenCode, OpenClaw, Hermes, Claude Code). Pull/launch solo con --yes si la nota es S\u2013B y hay velocidad suficiente. Sirve para conectar un agente de c\xF3digo solo cuando el hardware lo aguanta.",
1013
1027
  aboutAddPath: "A\xF1ade una carpeta GGUF a escanear (no todo el disco). Sirve para que llama-bench encuentre modelos fuera de Ollama.",
1014
- nav: "[B] Benchmark [M] Modelos [R] Recomendaciones [T] Tareas [H] Hardware [C] Comparar [L] Historial [Q] Salir",
1028
+ nav: "[S] Setup/Instalar [B] Benchmark [M] Modelos [R] Recomendaciones [T] Tareas [H] Hardware [C] Comparar [L] Historial [Q] Salir",
1029
+ setupTitle: "SETUP \u2014 INSTALAR MODELOS Y AGENTES",
1030
+ setupIntro: "Elige un modelo recomendado (nota S\u2013B), inst\xE1lalo con Ollama y luego vincula o instala OpenCode, Hermes u OpenClaw.",
1031
+ setupStep1: "1. Modelos recomendados (\u2191\u2193 para elegir)",
1032
+ setupStep2: "2. Instalar el modelo seleccionado",
1033
+ setupInstallModel: "Enter o I \u2192 ollama pull (solo si la nota es S, A o B)",
1034
+ setupStep3: "3. Agente de c\xF3digo \u2014 vincular si existe, instalar si falta",
1035
+ setupLinkNow: "instalado \u2192 pulsa 1/2/3 para vincular este modelo",
1036
+ setupInstallAgent: "no instalado \u2192 pulsa 1/2/3 para instalar con ollama launch (si es elegible)",
1037
+ setupKeys: "Enter/I = descargar modelo 1 OpenCode 2 Hermes 3 OpenClaw Esc = atr\xE1s",
1038
+ setupCta: "\u2192 Pulsa [S] o [I] para INSTALAR un modelo recomendado y vincular OpenCode / Hermes / OpenClaw",
1015
1039
  machine: "EQUIPO",
1016
1040
  runtimes: "RUNTIMES DE IA LOCAL",
1017
1041
  installedModels: "MODELOS INSTALADOS",
@@ -1132,6 +1156,7 @@ Uso:
1132
1156
  npx pudu-ai hardware
1133
1157
  npx pudu-ai models
1134
1158
  npx pudu-ai models add-path ~/Models
1159
+ npx pudu-ai setup
1135
1160
  npx pudu-ai recommend
1136
1161
  npx pudu-ai recommend --install --yes
1137
1162
  npx pudu-ai recommend --link opencode --yes
@@ -1927,7 +1952,7 @@ function setLogLevel(next) {
1927
1952
  import { render } from "ink";
1928
1953
 
1929
1954
  // src/tui/App.tsx
1930
- import { Box as Box9, Text as Text9, useApp as useApp2, useInput as useInput4 } from "ink";
1955
+ import { Box as Box10, useApp as useApp2, useInput as useInput4 } from "ink";
1931
1956
  import { useMemo as useMemo2, useState as useState4 } from "react";
1932
1957
 
1933
1958
  // src/tui/views/Dashboard.tsx
@@ -1992,11 +2017,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
1992
2017
  function Dashboard({ session }) {
1993
2018
  const h = session.hardware;
1994
2019
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1995
- /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1996
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("appTitle") }),
1997
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("appSubtitle") }),
1998
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("aboutDashboard") })
1999
- ] }),
2020
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("aboutDashboard") }),
2000
2021
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("machine") }),
2001
2022
  /* @__PURE__ */ jsx(Text, { children: h.machineModel ?? t("unknownMachine") }),
2002
2023
  /* @__PURE__ */ jsx(Text, { children: h.cpu.name ?? t("cpuNa") }),
@@ -2059,7 +2080,8 @@ function Dashboard({ session }) {
2059
2080
  " ",
2060
2081
  GRADE_MEANING[rec.grade]
2061
2082
  ] }, `${rec.useCase}-${rec.model.id}`)),
2062
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("credits") })
2083
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("credits") }),
2084
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: t("setupCta") })
2063
2085
  ] })
2064
2086
  ] });
2065
2087
  }
@@ -2263,7 +2285,7 @@ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2263
2285
  function ModelsView({ session }) {
2264
2286
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2265
2287
  /* @__PURE__ */ jsx2(Text2, { children: modelsText(session) }),
2266
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("modelsHint") })
2288
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: t("modelsHint") })
2267
2289
  ] });
2268
2290
  }
2269
2291
 
@@ -2274,7 +2296,7 @@ function HardwareView({ session }) {
2274
2296
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2275
2297
  /* @__PURE__ */ jsx3(Text3, { children: hardwareText(session) }),
2276
2298
  /* @__PURE__ */ jsx3(Text3, { children: runtimesText(session) }),
2277
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("hardwareHint") })
2299
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: t("hardwareHint") })
2278
2300
  ] });
2279
2301
  }
2280
2302
 
@@ -2471,19 +2493,23 @@ async function pullOllamaModel(modelId) {
2471
2493
 
2472
2494
  // src/tui/views/Recommend.tsx
2473
2495
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2496
+ var TOOLS = [
2497
+ { key: "1", id: "opencode", label: "OpenCode" },
2498
+ { key: "2", id: "hermes", label: "Hermes" },
2499
+ { key: "3", id: "openclaw", label: "OpenClaw" }
2500
+ ];
2474
2501
  function RecommendView({ session }) {
2475
2502
  const { exit } = useApp();
2476
2503
  const [cursor, setCursor] = useState(0);
2477
2504
  const [log, setLog] = useState("");
2478
2505
  const [busy, setBusy] = useState(false);
2479
2506
  const recs = session.recommendations;
2507
+ const rec = recs[cursor];
2480
2508
  useInput((input, key) => {
2481
2509
  if (busy) return;
2482
- if (key.escape) return;
2483
2510
  if (key.upArrow) setCursor((c) => Math.max(0, c - 1));
2484
2511
  if (key.downArrow) setCursor((c) => Math.min(Math.max(recs.length - 1, 0), c + 1));
2485
- const rec = recs[cursor];
2486
- if (input.toLowerCase() === "i" && rec) {
2512
+ if ((input.toLowerCase() === "i" || key.return) && rec) {
2487
2513
  if (!canInstallGrade(rec.grade)) {
2488
2514
  setLog(t("installGradeBlock", { grade: rec.grade }));
2489
2515
  return;
@@ -2495,8 +2521,7 @@ function RecommendView({ session }) {
2495
2521
  setBusy(false);
2496
2522
  });
2497
2523
  }
2498
- const map = { o: "opencode", e: "hermes", w: "openclaw" };
2499
- const tool = map[input.toLowerCase()];
2524
+ const tool = TOOLS.find((item) => item.key === input)?.id;
2500
2525
  if (tool && rec) {
2501
2526
  const decision = decideLaunch(session, tool);
2502
2527
  if (!decision.eligible) {
@@ -2513,29 +2538,40 @@ function RecommendView({ session }) {
2513
2538
  }
2514
2539
  });
2515
2540
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2516
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: t("recommended") }),
2517
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("aboutRecommend") }),
2518
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("recommendKeys") }),
2519
- recs.map((rec, i) => /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
2541
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2542
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("setupTitle") }),
2543
+ /* @__PURE__ */ jsx4(Text4, { children: t("setupIntro") })
2544
+ ] }),
2545
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: t("setupStep1") }),
2546
+ recs.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("installNone") }),
2547
+ recs.map((item, i) => /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
2520
2548
  i === cursor ? "\u276F " : " ",
2521
- /* @__PURE__ */ jsx4(Text4, { color: "magenta", children: rec.useCase.padEnd(12) }),
2522
- rec.model.name.padEnd(22),
2549
+ /* @__PURE__ */ jsx4(Text4, { color: "magenta", children: item.useCase.padEnd(12) }),
2550
+ item.model.name.padEnd(22),
2523
2551
  " ",
2524
- /* @__PURE__ */ jsx4(Text4, { color: gradeColor(rec.grade), bold: true, children: rec.grade }),
2552
+ /* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), bold: true, children: item.grade }),
2525
2553
  " ",
2526
- GRADE_MEANING[rec.grade]
2527
- ] }, `${rec.useCase}-${rec.model.id}`)),
2554
+ GRADE_MEANING[item.grade]
2555
+ ] }, `${item.useCase}-${item.model.id}`)),
2528
2556
  /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2529
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("agentsTitle") }),
2530
- session.agents.map((agent) => /* @__PURE__ */ jsxs4(Text4, { color: agent.detected ? "green" : "gray", children: [
2557
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: t("setupStep2") }),
2558
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: t("setupInstallModel") })
2559
+ ] }),
2560
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2561
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("setupStep3") }),
2562
+ session.agents.map((agent, index) => /* @__PURE__ */ jsxs4(Text4, { color: agent.detected ? "green" : "yellow", children: [
2563
+ "[",
2564
+ index + 1,
2565
+ "] ",
2531
2566
  agent.detected ? "\u2713" : "\u25CB",
2532
2567
  " ",
2533
2568
  agent.label.padEnd(12),
2534
2569
  " ",
2535
- agent.detected ? t("detected") : t("agentMissing")
2570
+ agent.detected ? t("setupLinkNow") : t("setupInstallAgent")
2536
2571
  ] }, agent.id))
2537
2572
  ] }),
2538
- log ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: log }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("credits") })
2573
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("setupKeys") }) }),
2574
+ log ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: log }) : null
2539
2575
  ] });
2540
2576
  }
2541
2577
 
@@ -3016,15 +3052,81 @@ function handleAppKey(screen, input, key) {
3016
3052
  if (letter === "c") return { type: "screen", screen: "compare" };
3017
3053
  if (letter === "l") return { type: "screen", screen: "history" };
3018
3054
  if (letter === "t") return { type: "screen", screen: "tasks" };
3055
+ if (letter === "s" || letter === "i") return { type: "screen", screen: "recommend" };
3019
3056
  if (key.return && screen === "home") return { type: "screen", screen: "benchmark" };
3020
3057
  return { type: "ignore" };
3021
3058
  }
3022
3059
 
3023
- // src/tui/App.tsx
3060
+ // src/tui/Welcome.tsx
3061
+ import { Box as Box9, Text as Text9 } from "ink";
3024
3062
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3063
+ function Welcome({ compact = false }) {
3064
+ if (compact) {
3065
+ return /* @__PURE__ */ jsxs8(Box9, { marginBottom: 1, children: [
3066
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u{1F98C} " }),
3067
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("appTitle") }),
3068
+ /* @__PURE__ */ jsxs8(Text9, { color: "magenta", children: [
3069
+ " ",
3070
+ t("byline")
3071
+ ] })
3072
+ ] });
3073
+ }
3074
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginBottom: 1, children: [
3075
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " \\/ \\/" }),
3076
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3077
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " " }),
3078
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " (\\_ _/)" })
3079
+ ] }),
3080
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3081
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " / " }),
3082
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: "o o" }),
3083
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \\" }),
3084
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
3085
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "green", children: t("appTitle") })
3086
+ ] }),
3087
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3088
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \\ " }),
3089
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: "^" }),
3090
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " /" }),
3091
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
3092
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: t("appSubtitle") })
3093
+ ] }),
3094
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3095
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " /| |\\" }),
3096
+ /* @__PURE__ */ jsx9(Text9, { children: " " }),
3097
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "magenta", children: t("byline") })
3098
+ ] }),
3099
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " U U" })
3100
+ ] });
3101
+ }
3102
+ function ColorNav() {
3103
+ return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
3104
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", bold: true, children: "[S]" }),
3105
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " Setup " }),
3106
+ /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "[B]" }),
3107
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " Bench " }),
3108
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "[M]" }),
3109
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " Models " }),
3110
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", bold: true, children: "[R]" }),
3111
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", children: " Rec " }),
3112
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", bold: true, children: "[T]" }),
3113
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: " Tasks " }),
3114
+ /* @__PURE__ */ jsx9(Text9, { color: "white", bold: true, children: "[H]" }),
3115
+ /* @__PURE__ */ jsx9(Text9, { children: " HW " }),
3116
+ /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "[C]" }),
3117
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " Cmp " }),
3118
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "[L]" }),
3119
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " Log " }),
3120
+ /* @__PURE__ */ jsx9(Text9, { color: "red", bold: true, children: "[Q]" }),
3121
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: " Quit" })
3122
+ ] }) });
3123
+ }
3124
+
3125
+ // src/tui/App.tsx
3126
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3025
3127
  function App(props) {
3026
3128
  const { exit } = useApp2();
3027
- const [screen, setScreen] = useState4("home");
3129
+ const [screen, setScreen] = useState4(props.start ?? "home");
3028
3130
  const [selected, setSelected] = useState4(0);
3029
3131
  const benchable = useMemo2(
3030
3132
  () => props.session.models.filter((m) => Boolean(m.artifactPath)),
@@ -3036,15 +3138,16 @@ function App(props) {
3036
3138
  if (result.type === "home") setScreen("home");
3037
3139
  if (result.type === "screen") setScreen(result.screen);
3038
3140
  });
3039
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", padding: 1, children: [
3040
- screen === "home" && /* @__PURE__ */ jsx9(Dashboard, { session: props.session }),
3041
- screen === "models" && /* @__PURE__ */ jsx9(ModelsView, { session: props.session }),
3042
- screen === "hardware" && /* @__PURE__ */ jsx9(HardwareView, { session: props.session }),
3043
- screen === "recommend" && /* @__PURE__ */ jsx9(RecommendView, { session: props.session }),
3044
- screen === "compare" && /* @__PURE__ */ jsx9(CompareView, { session: props.session }),
3045
- screen === "history" && /* @__PURE__ */ jsx9(HistoryView, { session: props.session }),
3046
- screen === "tasks" && /* @__PURE__ */ jsx9(TasksView, { session: props.session }),
3047
- screen === "benchmark" && /* @__PURE__ */ jsx9(
3141
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", padding: 1, children: [
3142
+ screen !== "benchmark" && /* @__PURE__ */ jsx10(Welcome, { compact: screen !== "home" }),
3143
+ screen === "home" && /* @__PURE__ */ jsx10(Dashboard, { session: props.session }),
3144
+ screen === "models" && /* @__PURE__ */ jsx10(ModelsView, { session: props.session }),
3145
+ screen === "hardware" && /* @__PURE__ */ jsx10(HardwareView, { session: props.session }),
3146
+ screen === "recommend" && /* @__PURE__ */ jsx10(RecommendView, { session: props.session }),
3147
+ screen === "compare" && /* @__PURE__ */ jsx10(CompareView, { session: props.session }),
3148
+ screen === "history" && /* @__PURE__ */ jsx10(HistoryView, { session: props.session }),
3149
+ screen === "tasks" && /* @__PURE__ */ jsx10(TasksView, { session: props.session }),
3150
+ screen === "benchmark" && /* @__PURE__ */ jsx10(
3048
3151
  BenchmarkView,
3049
3152
  {
3050
3153
  session: props.session,
@@ -3055,14 +3158,14 @@ function App(props) {
3055
3158
  onBack: () => setScreen("home")
3056
3159
  }
3057
3160
  ),
3058
- screen !== "benchmark" && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: t("nav") }) })
3161
+ screen !== "benchmark" && /* @__PURE__ */ jsx10(ColorNav, {})
3059
3162
  ] });
3060
3163
  }
3061
3164
 
3062
3165
  // src/tui/render.tsx
3063
- import { jsx as jsx10 } from "react/jsx-runtime";
3064
- async function renderDashboard(session, preset) {
3065
- const instance = render(/* @__PURE__ */ jsx10(App, { session, preset }));
3166
+ import { jsx as jsx11 } from "react/jsx-runtime";
3167
+ async function renderDashboard(session, preset, start) {
3168
+ const instance = render(/* @__PURE__ */ jsx11(App, { session, preset, start }));
3066
3169
  await instance.waitUntilExit();
3067
3170
  }
3068
3171
 
@@ -3124,6 +3227,13 @@ ${runtimesText(session)}`),
3124
3227
  case "models":
3125
3228
  print(args2.json ? session.rows : explained("aboutModels", modelsText(session)), args2.json);
3126
3229
  return 0;
3230
+ case "setup":
3231
+ if (args2.json) {
3232
+ print({ recommendations: session.recommendations, agents: session.agents }, true);
3233
+ return 0;
3234
+ }
3235
+ await renderDashboard(session, args2.preset, "recommend");
3236
+ return 0;
3127
3237
  case "recommend": {
3128
3238
  if (args2.install && args2.yes) {
3129
3239
  const rec = session.recommendations.find((r) => canInstallGrade(r.grade));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",