pudu-ai 0.2.5 → 0.2.6

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;
@@ -839,7 +840,17 @@ var en = {
839
840
  aboutReport: "Writes the latest measured run as Markdown. Use it to paste results into GitHub or notes.",
840
841
  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
842
  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",
843
+ nav: "[S] Setup/Install [B] Benchmark [M] Models [R] Recommend [T] Tasks [H] Hardware [C] Compare [L] History [Q] Quit",
844
+ setupTitle: "SETUP \u2014 INSTALL MODELS & AGENTS",
845
+ setupIntro: "Pick a recommended model (grade S\u2013B), install it with Ollama, then link or install OpenCode, Hermes, or OpenClaw.",
846
+ setupStep1: "1. Recommended models (\u2191\u2193 to choose)",
847
+ setupStep2: "2. Install the selected model",
848
+ setupInstallModel: "Press Enter or I \u2192 ollama pull (only if grade is S, A, or B)",
849
+ setupStep3: "3. Coding agent \u2014 link if present, install if missing",
850
+ setupLinkNow: "installed \u2192 press 1/2/3 to link this model",
851
+ setupInstallAgent: "not installed \u2192 press 1/2/3 to install via ollama launch (if eligible)",
852
+ setupKeys: "Enter/I = pull model 1 OpenCode 2 Hermes 3 OpenClaw Esc = back",
853
+ setupCta: "\u2192 Press [S] or [I] to INSTALL a recommended model and link OpenCode / Hermes / OpenClaw",
843
854
  machine: "MACHINE",
844
855
  runtimes: "LOCAL AI RUNTIMES",
845
856
  installedModels: "INSTALLED MODELS",
@@ -960,6 +971,7 @@ Usage:
960
971
  npx pudu-ai hardware
961
972
  npx pudu-ai models
962
973
  npx pudu-ai models add-path ~/Models
974
+ npx pudu-ai setup
963
975
  npx pudu-ai recommend
964
976
  npx pudu-ai recommend --install --yes
965
977
  npx pudu-ai recommend --link opencode --yes
@@ -1011,7 +1023,17 @@ var es = {
1011
1023
  aboutReport: "Exporta la \xFAltima corrida medida en Markdown. Sirve para pegar resultados en GitHub o notas.",
1012
1024
  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
1025
  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",
1026
+ nav: "[S] Setup/Instalar [B] Benchmark [M] Modelos [R] Recomendaciones [T] Tareas [H] Hardware [C] Comparar [L] Historial [Q] Salir",
1027
+ setupTitle: "SETUP \u2014 INSTALAR MODELOS Y AGENTES",
1028
+ setupIntro: "Elige un modelo recomendado (nota S\u2013B), inst\xE1lalo con Ollama y luego vincula o instala OpenCode, Hermes u OpenClaw.",
1029
+ setupStep1: "1. Modelos recomendados (\u2191\u2193 para elegir)",
1030
+ setupStep2: "2. Instalar el modelo seleccionado",
1031
+ setupInstallModel: "Enter o I \u2192 ollama pull (solo si la nota es S, A o B)",
1032
+ setupStep3: "3. Agente de c\xF3digo \u2014 vincular si existe, instalar si falta",
1033
+ setupLinkNow: "instalado \u2192 pulsa 1/2/3 para vincular este modelo",
1034
+ setupInstallAgent: "no instalado \u2192 pulsa 1/2/3 para instalar con ollama launch (si es elegible)",
1035
+ setupKeys: "Enter/I = descargar modelo 1 OpenCode 2 Hermes 3 OpenClaw Esc = atr\xE1s",
1036
+ setupCta: "\u2192 Pulsa [S] o [I] para INSTALAR un modelo recomendado y vincular OpenCode / Hermes / OpenClaw",
1015
1037
  machine: "EQUIPO",
1016
1038
  runtimes: "RUNTIMES DE IA LOCAL",
1017
1039
  installedModels: "MODELOS INSTALADOS",
@@ -1132,6 +1154,7 @@ Uso:
1132
1154
  npx pudu-ai hardware
1133
1155
  npx pudu-ai models
1134
1156
  npx pudu-ai models add-path ~/Models
1157
+ npx pudu-ai setup
1135
1158
  npx pudu-ai recommend
1136
1159
  npx pudu-ai recommend --install --yes
1137
1160
  npx pudu-ai recommend --link opencode --yes
@@ -2059,7 +2082,8 @@ function Dashboard({ session }) {
2059
2082
  " ",
2060
2083
  GRADE_MEANING[rec.grade]
2061
2084
  ] }, `${rec.useCase}-${rec.model.id}`)),
2062
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("credits") })
2085
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("credits") }),
2086
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: t("setupCta") })
2063
2087
  ] })
2064
2088
  ] });
2065
2089
  }
@@ -2471,19 +2495,23 @@ async function pullOllamaModel(modelId) {
2471
2495
 
2472
2496
  // src/tui/views/Recommend.tsx
2473
2497
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2498
+ var TOOLS = [
2499
+ { key: "1", id: "opencode", label: "OpenCode" },
2500
+ { key: "2", id: "hermes", label: "Hermes" },
2501
+ { key: "3", id: "openclaw", label: "OpenClaw" }
2502
+ ];
2474
2503
  function RecommendView({ session }) {
2475
2504
  const { exit } = useApp();
2476
2505
  const [cursor, setCursor] = useState(0);
2477
2506
  const [log, setLog] = useState("");
2478
2507
  const [busy, setBusy] = useState(false);
2479
2508
  const recs = session.recommendations;
2509
+ const rec = recs[cursor];
2480
2510
  useInput((input, key) => {
2481
2511
  if (busy) return;
2482
- if (key.escape) return;
2483
2512
  if (key.upArrow) setCursor((c) => Math.max(0, c - 1));
2484
2513
  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) {
2514
+ if ((input.toLowerCase() === "i" || key.return) && rec) {
2487
2515
  if (!canInstallGrade(rec.grade)) {
2488
2516
  setLog(t("installGradeBlock", { grade: rec.grade }));
2489
2517
  return;
@@ -2495,8 +2523,7 @@ function RecommendView({ session }) {
2495
2523
  setBusy(false);
2496
2524
  });
2497
2525
  }
2498
- const map = { o: "opencode", e: "hermes", w: "openclaw" };
2499
- const tool = map[input.toLowerCase()];
2526
+ const tool = TOOLS.find((item) => item.key === input)?.id;
2500
2527
  if (tool && rec) {
2501
2528
  const decision = decideLaunch(session, tool);
2502
2529
  if (!decision.eligible) {
@@ -2513,29 +2540,40 @@ function RecommendView({ session }) {
2513
2540
  }
2514
2541
  });
2515
2542
  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: [
2543
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2544
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("setupTitle") }),
2545
+ /* @__PURE__ */ jsx4(Text4, { children: t("setupIntro") })
2546
+ ] }),
2547
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: t("setupStep1") }),
2548
+ recs.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("installNone") }),
2549
+ recs.map((item, i) => /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
2520
2550
  i === cursor ? "\u276F " : " ",
2521
- /* @__PURE__ */ jsx4(Text4, { color: "magenta", children: rec.useCase.padEnd(12) }),
2522
- rec.model.name.padEnd(22),
2551
+ /* @__PURE__ */ jsx4(Text4, { color: "magenta", children: item.useCase.padEnd(12) }),
2552
+ item.model.name.padEnd(22),
2523
2553
  " ",
2524
- /* @__PURE__ */ jsx4(Text4, { color: gradeColor(rec.grade), bold: true, children: rec.grade }),
2554
+ /* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), bold: true, children: item.grade }),
2525
2555
  " ",
2526
- GRADE_MEANING[rec.grade]
2527
- ] }, `${rec.useCase}-${rec.model.id}`)),
2556
+ GRADE_MEANING[item.grade]
2557
+ ] }, `${item.useCase}-${item.model.id}`)),
2528
2558
  /* @__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: [
2559
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: t("setupStep2") }),
2560
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: t("setupInstallModel") })
2561
+ ] }),
2562
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2563
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("setupStep3") }),
2564
+ session.agents.map((agent, index) => /* @__PURE__ */ jsxs4(Text4, { color: agent.detected ? "green" : "yellow", children: [
2565
+ "[",
2566
+ index + 1,
2567
+ "] ",
2531
2568
  agent.detected ? "\u2713" : "\u25CB",
2532
2569
  " ",
2533
2570
  agent.label.padEnd(12),
2534
2571
  " ",
2535
- agent.detected ? t("detected") : t("agentMissing")
2572
+ agent.detected ? t("setupLinkNow") : t("setupInstallAgent")
2536
2573
  ] }, agent.id))
2537
2574
  ] }),
2538
- log ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: log }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("credits") })
2575
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("setupKeys") }) }),
2576
+ log ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: log }) : null
2539
2577
  ] });
2540
2578
  }
2541
2579
 
@@ -3016,6 +3054,7 @@ function handleAppKey(screen, input, key) {
3016
3054
  if (letter === "c") return { type: "screen", screen: "compare" };
3017
3055
  if (letter === "l") return { type: "screen", screen: "history" };
3018
3056
  if (letter === "t") return { type: "screen", screen: "tasks" };
3057
+ if (letter === "s" || letter === "i") return { type: "screen", screen: "recommend" };
3019
3058
  if (key.return && screen === "home") return { type: "screen", screen: "benchmark" };
3020
3059
  return { type: "ignore" };
3021
3060
  }
@@ -3024,7 +3063,7 @@ function handleAppKey(screen, input, key) {
3024
3063
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3025
3064
  function App(props) {
3026
3065
  const { exit } = useApp2();
3027
- const [screen, setScreen] = useState4("home");
3066
+ const [screen, setScreen] = useState4(props.start ?? "home");
3028
3067
  const [selected, setSelected] = useState4(0);
3029
3068
  const benchable = useMemo2(
3030
3069
  () => props.session.models.filter((m) => Boolean(m.artifactPath)),
@@ -3061,8 +3100,8 @@ function App(props) {
3061
3100
 
3062
3101
  // src/tui/render.tsx
3063
3102
  import { jsx as jsx10 } from "react/jsx-runtime";
3064
- async function renderDashboard(session, preset) {
3065
- const instance = render(/* @__PURE__ */ jsx10(App, { session, preset }));
3103
+ async function renderDashboard(session, preset, start) {
3104
+ const instance = render(/* @__PURE__ */ jsx10(App, { session, preset, start }));
3066
3105
  await instance.waitUntilExit();
3067
3106
  }
3068
3107
 
@@ -3124,6 +3163,13 @@ ${runtimesText(session)}`),
3124
3163
  case "models":
3125
3164
  print(args2.json ? session.rows : explained("aboutModels", modelsText(session)), args2.json);
3126
3165
  return 0;
3166
+ case "setup":
3167
+ if (args2.json) {
3168
+ print({ recommendations: session.recommendations, agents: session.agents }, true);
3169
+ return 0;
3170
+ }
3171
+ await renderDashboard(session, args2.preset, "recommend");
3172
+ return 0;
3127
3173
  case "recommend": {
3128
3174
  if (args2.install && args2.yes) {
3129
3175
  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.6",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",