pudu-ai 0.2.6 → 0.2.8

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 +129 -38
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -828,6 +828,7 @@ async function loadCatalogCache() {
828
828
  var en = {
829
829
  appTitle: "PUDU-AI",
830
830
  appSubtitle: "Local AI Hardware & Benchmark Lab",
831
+ byline: "by devjaime",
831
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.",
832
833
  aboutHardware: "Detects CPU, GPU, and Unified Memory/RAM. Use it to know what this computer can host before pulling a model.",
833
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.",
@@ -865,6 +866,7 @@ var en = {
865
866
  linkingTool: "Linking {tool} with {model}\u2026",
866
867
  modelPulled: "Pulled {model} with Ollama.",
867
868
  installNone: "No recommended model has grade S\u2013B to install.",
869
+ noOllamaTag: "No Ollama library tag for {model}. Catalog names like Agents-A1 are not pullable. Use qwen3:8b, gemma3:4b, qwen3.5:4b, llama3.1:8b.",
868
870
  credits: "Measured: llama-bench + OS telemetry. Estimated: CanIRun.ai by midudev (https://canirun.ai, https://github.com/midudev/canirun.ai, https://midu.dev). Labelled separately; never mixed.",
869
871
  noModels: "No local models detected",
870
872
  notTested: "Not tested",
@@ -1011,6 +1013,7 @@ Credits:
1011
1013
  var es = {
1012
1014
  appTitle: "PUDU-AI",
1013
1015
  appSubtitle: "Laboratorio local de hardware y benchmarks de IA",
1016
+ byline: "by devjaime",
1014
1017
  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.",
1015
1018
  aboutHardware: "Detecta CPU, GPU y memoria unificada/RAM. Sirve para saber qu\xE9 puede hospedar este equipo antes de descargar un modelo.",
1016
1019
  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.",
@@ -1048,6 +1051,7 @@ var es = {
1048
1051
  linkingTool: "Vinculando {tool} con {model}\u2026",
1049
1052
  modelPulled: "Descargado {model} con Ollama.",
1050
1053
  installNone: "Ning\xFAn modelo recomendado tiene nota S\u2013B para instalar.",
1054
+ noOllamaTag: "No hay tag de Ollama para {model}. Nombres de cat\xE1logo como Agents-A1 no se pueden descargar. Usa qwen3:8b, gemma3:4b, qwen3.5:4b, llama3.1:8b.",
1051
1055
  credits: "Medido: llama-bench + telemetr\xEDa del SO. Estimado: CanIRun.ai de midudev (https://canirun.ai, https://github.com/midudev/canirun.ai, https://midu.dev). Se etiquetan por separado; nunca se mezclan.",
1052
1056
  noModels: "No se detectaron modelos locales",
1053
1057
  notTested: "Sin probar",
@@ -1212,6 +1216,35 @@ function t(id, vars) {
1212
1216
  return text;
1213
1217
  }
1214
1218
 
1219
+ // src/integrations/ollama-tags.ts
1220
+ var RULES = [
1221
+ { test: /qwen\s*3\.5\s*[-:]?\s*8b/i, tag: "qwen3.5:8b" },
1222
+ { test: /qwen\s*3\.5\s*[-:]?\s*4b/i, tag: "qwen3.5:4b" },
1223
+ { test: /qwen\s*3\s*[-:]?\s*14b/i, tag: "qwen3:14b" },
1224
+ { test: /qwen\s*3\s*[-:]?\s*8b/i, tag: "qwen3:8b" },
1225
+ { test: /qwen\s*3\s*[-:]?\s*4b/i, tag: "qwen3:4b" },
1226
+ { test: /qwen\s*2\.5\s*[-:]?\s*coder\s*[-:]?\s*7b/i, tag: "qwen2.5-coder:7b" },
1227
+ { test: /gemma\s*3\s*[-:]?\s*12b/i, tag: "gemma3:12b" },
1228
+ { test: /gemma\s*3\s*[-:]?\s*4b/i, tag: "gemma3:4b" },
1229
+ { test: /gemma\s*3\s*[-:]?\s*1b/i, tag: "gemma3:1b" },
1230
+ { test: /llama\s*3\.2\s*[-:]?\s*3b/i, tag: "llama3.2:3b" },
1231
+ { test: /llama\s*3\.2\s*[-:]?\s*1b/i, tag: "llama3.2:1b" },
1232
+ { test: /llama\s*3\.1\s*[-:]?\s*8b/i, tag: "llama3.1:8b" },
1233
+ { test: /deepseek[- ]r1\s*[-:]?\s*8b|deepseek-r1-distill.*8b/i, tag: "deepseek-r1:8b" },
1234
+ { test: /deepseek[- ]r1\s*[-:]?\s*7b/i, tag: "deepseek-r1:7b" },
1235
+ { test: /mistral\s*[-:]?\s*7b|mistral-nemo/i, tag: "mistral:7b" },
1236
+ { test: /phi\s*4|phi-4/i, tag: "phi4" }
1237
+ ];
1238
+ function resolveOllamaTag(...parts) {
1239
+ const haystack = parts.filter(Boolean).join(" ");
1240
+ if (!haystack.trim()) return void 0;
1241
+ if (/^[a-z0-9._-]+:[a-z0-9._-]+$/i.test(haystack.trim())) return haystack.trim();
1242
+ for (const rule of RULES) {
1243
+ if (rule.test.test(haystack)) return rule.tag;
1244
+ }
1245
+ return void 0;
1246
+ }
1247
+
1215
1248
  // src/compatibility/local.ts
1216
1249
  var GB_PER_BILLION_Q4 = 0.65;
1217
1250
  function estimateModelRamGb(model, quant = "Q4_K_M") {
@@ -1293,8 +1326,9 @@ async function recommendForMachine(hardware, catalog, network) {
1293
1326
  try {
1294
1327
  const rows = [];
1295
1328
  for (const useCase of useCases) {
1296
- const found = await fetchRecommendations(hardware, useCase, 1);
1297
- rows.push(...found.map((item) => ({ ...item, useCase: labelUseCase(useCase) })));
1329
+ const found = await fetchRecommendations(hardware, useCase, 8);
1330
+ const mapped = found.filter((item) => resolveOllamaTag(item.model.id, item.model.name)).slice(0, 1).map((item) => ({ ...item, useCase: labelUseCase(useCase) }));
1331
+ rows.push(...mapped);
1298
1332
  }
1299
1333
  if (rows.length) return rows;
1300
1334
  } catch {
@@ -1306,6 +1340,7 @@ async function recommendForMachine(hardware, catalog, network) {
1306
1340
  for (const useCase of useCases) {
1307
1341
  const hit = ranked.find((row) => {
1308
1342
  if (used.has(row.model.id)) return false;
1343
+ if (!resolveOllamaTag(row.model.id, row.model.name)) return false;
1309
1344
  if (useCase === "chat") return row.model.useCase?.includes("chat") ?? true;
1310
1345
  return row.model.useCase?.some((u) => u.includes(useCase)) ?? false;
1311
1346
  });
@@ -1950,7 +1985,7 @@ function setLogLevel(next) {
1950
1985
  import { render } from "ink";
1951
1986
 
1952
1987
  // src/tui/App.tsx
1953
- import { Box as Box9, Text as Text9, useApp as useApp2, useInput as useInput4 } from "ink";
1988
+ import { Box as Box10, useApp as useApp2, useInput as useInput4 } from "ink";
1954
1989
  import { useMemo as useMemo2, useState as useState4 } from "react";
1955
1990
 
1956
1991
  // src/tui/views/Dashboard.tsx
@@ -2015,11 +2050,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2015
2050
  function Dashboard({ session }) {
2016
2051
  const h = session.hardware;
2017
2052
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2018
- /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2019
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("appTitle") }),
2020
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("appSubtitle") }),
2021
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("aboutDashboard") })
2022
- ] }),
2053
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: t("aboutDashboard") }),
2023
2054
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: t("machine") }),
2024
2055
  /* @__PURE__ */ jsx(Text, { children: h.machineModel ?? t("unknownMachine") }),
2025
2056
  /* @__PURE__ */ jsx(Text, { children: h.cpu.name ?? t("cpuNa") }),
@@ -2287,7 +2318,7 @@ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2287
2318
  function ModelsView({ session }) {
2288
2319
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2289
2320
  /* @__PURE__ */ jsx2(Text2, { children: modelsText(session) }),
2290
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t("modelsHint") })
2321
+ /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: t("modelsHint") })
2291
2322
  ] });
2292
2323
  }
2293
2324
 
@@ -2298,7 +2329,7 @@ function HardwareView({ session }) {
2298
2329
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2299
2330
  /* @__PURE__ */ jsx3(Text3, { children: hardwareText(session) }),
2300
2331
  /* @__PURE__ */ jsx3(Text3, { children: runtimesText(session) }),
2301
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("hardwareHint") })
2332
+ /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: t("hardwareHint") })
2302
2333
  ] });
2303
2334
  }
2304
2335
 
@@ -2347,13 +2378,6 @@ var INTEGRATION_IDS = ["opencode", "openclaw", "hermes", "claude"];
2347
2378
 
2348
2379
  // src/integrations/decide.ts
2349
2380
  var GRADE_RANK = { S: 5, A: 4, B: 3, C: 2, D: 1, F: 0 };
2350
- function toOllamaTag(id) {
2351
- const compact = id.toLowerCase().replace(/_/g, "-");
2352
- const match = compact.match(/^([a-z0-9.]+(?:-[a-z0-9.]+)*)-(\d+(?:\.\d+)?b)$/i);
2353
- if (match) return `${match[1]}:${match[2]}`;
2354
- if (compact.includes(":")) return compact;
2355
- return compact;
2356
- }
2357
2381
  function useCaseOk(useCases, needed) {
2358
2382
  if (!useCases?.length) return needed.includes("chat");
2359
2383
  return useCases.some((u) => needed.some((n) => u.includes(n)));
@@ -2367,10 +2391,12 @@ function decideLaunch(session, id) {
2367
2391
  for (const row of session.rows) {
2368
2392
  const useCases = row.catalog?.useCase ?? ["chat", "code"];
2369
2393
  if (!useCaseOk(useCases, def.useCases)) continue;
2394
+ const tag = resolveOllamaTag(row.local.id, row.local.name, row.catalog?.id, row.catalog?.name);
2395
+ if (!tag) continue;
2370
2396
  const tps = row.lastBenchmark?.benchmark.generationTokensPerSecond;
2371
2397
  candidates.push({
2372
2398
  modelId: row.local.id,
2373
- ollamaTag: row.local.id.includes(":") ? row.local.id : toOllamaTag(row.local.id),
2399
+ ollamaTag: tag,
2374
2400
  installed: true,
2375
2401
  origin: row.lastBenchmark ? "measured" : "estimated",
2376
2402
  grade: row.compatibility?.grade,
@@ -2382,9 +2408,11 @@ function decideLaunch(session, id) {
2382
2408
  if (!useCaseOk(model.useCase, def.useCases)) continue;
2383
2409
  const fit = localCompatibility(session.hardware, model);
2384
2410
  if (!def.allowedGrades.includes(fit.grade)) continue;
2411
+ const tag = resolveOllamaTag(model.id, model.name);
2412
+ if (!tag) continue;
2385
2413
  candidates.push({
2386
2414
  modelId: model.id,
2387
- ollamaTag: toOllamaTag(model.id),
2415
+ ollamaTag: tag,
2388
2416
  installed: false,
2389
2417
  origin: "estimated",
2390
2418
  grade: fit.grade
@@ -2484,8 +2512,11 @@ var INSTALLABLE = ["S", "A", "B"];
2484
2512
  function canInstallGrade(grade) {
2485
2513
  return Boolean(grade && INSTALLABLE.includes(grade));
2486
2514
  }
2487
- async function pullOllamaModel(modelId) {
2488
- const tag = toOllamaTag(modelId);
2515
+ async function pullOllamaModel(modelId, extraName) {
2516
+ const tag = resolveOllamaTag(modelId, extraName);
2517
+ if (!tag) {
2518
+ return { ok: false, log: t("noOllamaTag", { model: extraName ?? modelId }), tag: modelId };
2519
+ }
2489
2520
  const result = await runCommand("ollama", ["pull", tag], { timeout: 30 * 6e4 });
2490
2521
  if (result.exitCode !== 0) {
2491
2522
  return { ok: false, log: result.stderr || t("launchPullFail"), tag };
@@ -2517,8 +2548,8 @@ function RecommendView({ session }) {
2517
2548
  return;
2518
2549
  }
2519
2550
  setBusy(true);
2520
- setLog(t("launchPulling", { model: rec.model.id }));
2521
- void pullOllamaModel(rec.model.id).then((result) => {
2551
+ setLog(t("launchPulling", { model: rec.model.name }));
2552
+ void pullOllamaModel(rec.model.id, rec.model.name).then((result) => {
2522
2553
  setLog(result.log);
2523
2554
  setBusy(false);
2524
2555
  });
@@ -2553,7 +2584,9 @@ function RecommendView({ session }) {
2553
2584
  " ",
2554
2585
  /* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), bold: true, children: item.grade }),
2555
2586
  " ",
2556
- GRADE_MEANING[item.grade]
2587
+ GRADE_MEANING[item.grade],
2588
+ " ",
2589
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: resolveOllamaTag(item.model.id, item.model.name) ?? t("noOllamaTag", { model: item.model.name }) })
2557
2590
  ] }, `${item.useCase}-${item.model.id}`)),
2558
2591
  /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
2559
2592
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: t("setupStep2") }),
@@ -3059,8 +3092,65 @@ function handleAppKey(screen, input, key) {
3059
3092
  return { type: "ignore" };
3060
3093
  }
3061
3094
 
3062
- // src/tui/App.tsx
3095
+ // src/tui/Welcome.tsx
3096
+ import { Box as Box9, Text as Text9 } from "ink";
3063
3097
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3098
+ function Welcome({ compact = false }) {
3099
+ if (compact) {
3100
+ return /* @__PURE__ */ jsxs8(Box9, { marginBottom: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
3101
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("appTitle") }),
3102
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: " \xB7 " }),
3103
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", children: t("byline") })
3104
+ ] });
3105
+ }
3106
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "row", marginBottom: 1, children: [
3107
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginRight: 2, children: [
3108
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " \u2572" }),
3109
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3110
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \u256D\u2500" }),
3111
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: "\u25CF \u25CF" }),
3112
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "\u2500\u256E" })
3113
+ ] }),
3114
+ /* @__PURE__ */ jsxs8(Text9, { children: [
3115
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \u2502 " }),
3116
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u25BD" }),
3117
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \u2502" })
3118
+ ] }),
3119
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " \u2570\u252C\u2500\u2500\u2500\u252C\u256F" }),
3120
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " uu uu" })
3121
+ ] }),
3122
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", justifyContent: "center", children: [
3123
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("appTitle") }),
3124
+ /* @__PURE__ */ jsx9(Text9, { color: "white", children: t("appSubtitle") }),
3125
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", children: t("byline") })
3126
+ ] })
3127
+ ] });
3128
+ }
3129
+ function ColorNav() {
3130
+ return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
3131
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", bold: true, children: "[S]" }),
3132
+ /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " Setup " }),
3133
+ /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "[B]" }),
3134
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " Bench " }),
3135
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "[M]" }),
3136
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " Models " }),
3137
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", bold: true, children: "[R]" }),
3138
+ /* @__PURE__ */ jsx9(Text9, { color: "magenta", children: " Rec " }),
3139
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", bold: true, children: "[T]" }),
3140
+ /* @__PURE__ */ jsx9(Text9, { color: "blue", children: " Tasks " }),
3141
+ /* @__PURE__ */ jsx9(Text9, { color: "white", bold: true, children: "[H]" }),
3142
+ /* @__PURE__ */ jsx9(Text9, { children: " HW " }),
3143
+ /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "[C]" }),
3144
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: " Cmp " }),
3145
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "[L]" }),
3146
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: " Log " }),
3147
+ /* @__PURE__ */ jsx9(Text9, { color: "red", bold: true, children: "[Q]" }),
3148
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: " Quit" })
3149
+ ] }) });
3150
+ }
3151
+
3152
+ // src/tui/App.tsx
3153
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3064
3154
  function App(props) {
3065
3155
  const { exit } = useApp2();
3066
3156
  const [screen, setScreen] = useState4(props.start ?? "home");
@@ -3075,15 +3165,16 @@ function App(props) {
3075
3165
  if (result.type === "home") setScreen("home");
3076
3166
  if (result.type === "screen") setScreen(result.screen);
3077
3167
  });
3078
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", padding: 1, children: [
3079
- screen === "home" && /* @__PURE__ */ jsx9(Dashboard, { session: props.session }),
3080
- screen === "models" && /* @__PURE__ */ jsx9(ModelsView, { session: props.session }),
3081
- screen === "hardware" && /* @__PURE__ */ jsx9(HardwareView, { session: props.session }),
3082
- screen === "recommend" && /* @__PURE__ */ jsx9(RecommendView, { session: props.session }),
3083
- screen === "compare" && /* @__PURE__ */ jsx9(CompareView, { session: props.session }),
3084
- screen === "history" && /* @__PURE__ */ jsx9(HistoryView, { session: props.session }),
3085
- screen === "tasks" && /* @__PURE__ */ jsx9(TasksView, { session: props.session }),
3086
- screen === "benchmark" && /* @__PURE__ */ jsx9(
3168
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", padding: 1, children: [
3169
+ screen !== "benchmark" && /* @__PURE__ */ jsx10(Welcome, { compact: screen !== "home" }),
3170
+ screen === "home" && /* @__PURE__ */ jsx10(Dashboard, { session: props.session }),
3171
+ screen === "models" && /* @__PURE__ */ jsx10(ModelsView, { session: props.session }),
3172
+ screen === "hardware" && /* @__PURE__ */ jsx10(HardwareView, { session: props.session }),
3173
+ screen === "recommend" && /* @__PURE__ */ jsx10(RecommendView, { session: props.session }),
3174
+ screen === "compare" && /* @__PURE__ */ jsx10(CompareView, { session: props.session }),
3175
+ screen === "history" && /* @__PURE__ */ jsx10(HistoryView, { session: props.session }),
3176
+ screen === "tasks" && /* @__PURE__ */ jsx10(TasksView, { session: props.session }),
3177
+ screen === "benchmark" && /* @__PURE__ */ jsx10(
3087
3178
  BenchmarkView,
3088
3179
  {
3089
3180
  session: props.session,
@@ -3094,14 +3185,14 @@ function App(props) {
3094
3185
  onBack: () => setScreen("home")
3095
3186
  }
3096
3187
  ),
3097
- screen !== "benchmark" && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: t("nav") }) })
3188
+ screen !== "benchmark" && /* @__PURE__ */ jsx10(ColorNav, {})
3098
3189
  ] });
3099
3190
  }
3100
3191
 
3101
3192
  // src/tui/render.tsx
3102
- import { jsx as jsx10 } from "react/jsx-runtime";
3193
+ import { jsx as jsx11 } from "react/jsx-runtime";
3103
3194
  async function renderDashboard(session, preset, start) {
3104
- const instance = render(/* @__PURE__ */ jsx10(App, { session, preset, start }));
3195
+ const instance = render(/* @__PURE__ */ jsx11(App, { session, preset, start }));
3105
3196
  await instance.waitUntilExit();
3106
3197
  }
3107
3198
 
@@ -3177,7 +3268,7 @@ ${runtimesText(session)}`),
3177
3268
  print(t("installNone"), args2.json);
3178
3269
  return 1;
3179
3270
  }
3180
- const pulled = await pullOllamaModel(rec.model.id);
3271
+ const pulled = await pullOllamaModel(rec.model.id, rec.model.name);
3181
3272
  print(args2.json ? pulled : explained("aboutRecommend", pulled.log), args2.json);
3182
3273
  return pulled.ok ? 0 : 1;
3183
3274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",