pudu-ai 0.2.7 → 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.
- package/dist/cli/index.js +76 -49
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -866,6 +866,7 @@ var en = {
|
|
|
866
866
|
linkingTool: "Linking {tool} with {model}\u2026",
|
|
867
867
|
modelPulled: "Pulled {model} with Ollama.",
|
|
868
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.",
|
|
869
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.",
|
|
870
871
|
noModels: "No local models detected",
|
|
871
872
|
notTested: "Not tested",
|
|
@@ -1050,6 +1051,7 @@ var es = {
|
|
|
1050
1051
|
linkingTool: "Vinculando {tool} con {model}\u2026",
|
|
1051
1052
|
modelPulled: "Descargado {model} con Ollama.",
|
|
1052
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.",
|
|
1053
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.",
|
|
1054
1056
|
noModels: "No se detectaron modelos locales",
|
|
1055
1057
|
notTested: "Sin probar",
|
|
@@ -1214,6 +1216,35 @@ function t(id, vars) {
|
|
|
1214
1216
|
return text;
|
|
1215
1217
|
}
|
|
1216
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
|
+
|
|
1217
1248
|
// src/compatibility/local.ts
|
|
1218
1249
|
var GB_PER_BILLION_Q4 = 0.65;
|
|
1219
1250
|
function estimateModelRamGb(model, quant = "Q4_K_M") {
|
|
@@ -1295,8 +1326,9 @@ async function recommendForMachine(hardware, catalog, network) {
|
|
|
1295
1326
|
try {
|
|
1296
1327
|
const rows = [];
|
|
1297
1328
|
for (const useCase of useCases) {
|
|
1298
|
-
const found = await fetchRecommendations(hardware, useCase,
|
|
1299
|
-
|
|
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);
|
|
1300
1332
|
}
|
|
1301
1333
|
if (rows.length) return rows;
|
|
1302
1334
|
} catch {
|
|
@@ -1308,6 +1340,7 @@ async function recommendForMachine(hardware, catalog, network) {
|
|
|
1308
1340
|
for (const useCase of useCases) {
|
|
1309
1341
|
const hit = ranked.find((row) => {
|
|
1310
1342
|
if (used.has(row.model.id)) return false;
|
|
1343
|
+
if (!resolveOllamaTag(row.model.id, row.model.name)) return false;
|
|
1311
1344
|
if (useCase === "chat") return row.model.useCase?.includes("chat") ?? true;
|
|
1312
1345
|
return row.model.useCase?.some((u) => u.includes(useCase)) ?? false;
|
|
1313
1346
|
});
|
|
@@ -2345,13 +2378,6 @@ var INTEGRATION_IDS = ["opencode", "openclaw", "hermes", "claude"];
|
|
|
2345
2378
|
|
|
2346
2379
|
// src/integrations/decide.ts
|
|
2347
2380
|
var GRADE_RANK = { S: 5, A: 4, B: 3, C: 2, D: 1, F: 0 };
|
|
2348
|
-
function toOllamaTag(id) {
|
|
2349
|
-
const compact = id.toLowerCase().replace(/_/g, "-");
|
|
2350
|
-
const match = compact.match(/^([a-z0-9.]+(?:-[a-z0-9.]+)*)-(\d+(?:\.\d+)?b)$/i);
|
|
2351
|
-
if (match) return `${match[1]}:${match[2]}`;
|
|
2352
|
-
if (compact.includes(":")) return compact;
|
|
2353
|
-
return compact;
|
|
2354
|
-
}
|
|
2355
2381
|
function useCaseOk(useCases, needed) {
|
|
2356
2382
|
if (!useCases?.length) return needed.includes("chat");
|
|
2357
2383
|
return useCases.some((u) => needed.some((n) => u.includes(n)));
|
|
@@ -2365,10 +2391,12 @@ function decideLaunch(session, id) {
|
|
|
2365
2391
|
for (const row of session.rows) {
|
|
2366
2392
|
const useCases = row.catalog?.useCase ?? ["chat", "code"];
|
|
2367
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;
|
|
2368
2396
|
const tps = row.lastBenchmark?.benchmark.generationTokensPerSecond;
|
|
2369
2397
|
candidates.push({
|
|
2370
2398
|
modelId: row.local.id,
|
|
2371
|
-
ollamaTag:
|
|
2399
|
+
ollamaTag: tag,
|
|
2372
2400
|
installed: true,
|
|
2373
2401
|
origin: row.lastBenchmark ? "measured" : "estimated",
|
|
2374
2402
|
grade: row.compatibility?.grade,
|
|
@@ -2380,9 +2408,11 @@ function decideLaunch(session, id) {
|
|
|
2380
2408
|
if (!useCaseOk(model.useCase, def.useCases)) continue;
|
|
2381
2409
|
const fit = localCompatibility(session.hardware, model);
|
|
2382
2410
|
if (!def.allowedGrades.includes(fit.grade)) continue;
|
|
2411
|
+
const tag = resolveOllamaTag(model.id, model.name);
|
|
2412
|
+
if (!tag) continue;
|
|
2383
2413
|
candidates.push({
|
|
2384
2414
|
modelId: model.id,
|
|
2385
|
-
ollamaTag:
|
|
2415
|
+
ollamaTag: tag,
|
|
2386
2416
|
installed: false,
|
|
2387
2417
|
origin: "estimated",
|
|
2388
2418
|
grade: fit.grade
|
|
@@ -2482,8 +2512,11 @@ var INSTALLABLE = ["S", "A", "B"];
|
|
|
2482
2512
|
function canInstallGrade(grade) {
|
|
2483
2513
|
return Boolean(grade && INSTALLABLE.includes(grade));
|
|
2484
2514
|
}
|
|
2485
|
-
async function pullOllamaModel(modelId) {
|
|
2486
|
-
const tag =
|
|
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
|
+
}
|
|
2487
2520
|
const result = await runCommand("ollama", ["pull", tag], { timeout: 30 * 6e4 });
|
|
2488
2521
|
if (result.exitCode !== 0) {
|
|
2489
2522
|
return { ok: false, log: result.stderr || t("launchPullFail"), tag };
|
|
@@ -2515,8 +2548,8 @@ function RecommendView({ session }) {
|
|
|
2515
2548
|
return;
|
|
2516
2549
|
}
|
|
2517
2550
|
setBusy(true);
|
|
2518
|
-
setLog(t("launchPulling", { model: rec.model.
|
|
2519
|
-
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) => {
|
|
2520
2553
|
setLog(result.log);
|
|
2521
2554
|
setBusy(false);
|
|
2522
2555
|
});
|
|
@@ -2551,7 +2584,9 @@ function RecommendView({ session }) {
|
|
|
2551
2584
|
" ",
|
|
2552
2585
|
/* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), bold: true, children: item.grade }),
|
|
2553
2586
|
" ",
|
|
2554
|
-
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 }) })
|
|
2555
2590
|
] }, `${item.useCase}-${item.model.id}`)),
|
|
2556
2591
|
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2557
2592
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: t("setupStep2") }),
|
|
@@ -3062,45 +3097,37 @@ import { Box as Box9, Text as Text9 } from "ink";
|
|
|
3062
3097
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3063
3098
|
function Welcome({ compact = false }) {
|
|
3064
3099
|
if (compact) {
|
|
3065
|
-
return /* @__PURE__ */ jsxs8(Box9, { marginBottom: 1, children: [
|
|
3066
|
-
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u{1F98C} " }),
|
|
3100
|
+
return /* @__PURE__ */ jsxs8(Box9, { marginBottom: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
3067
3101
|
/* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: t("appTitle") }),
|
|
3068
|
-
/* @__PURE__ */
|
|
3069
|
-
|
|
3070
|
-
t("byline")
|
|
3071
|
-
] })
|
|
3102
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", children: " \xB7 " }),
|
|
3103
|
+
/* @__PURE__ */ jsx9(Text9, { color: "magenta", children: t("byline") })
|
|
3072
3104
|
] });
|
|
3073
3105
|
}
|
|
3074
|
-
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "
|
|
3075
|
-
/* @__PURE__ */
|
|
3076
|
-
|
|
3077
|
-
/* @__PURE__ */
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
/* @__PURE__ */
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
/* @__PURE__ */ jsx9(Text9, { color: "
|
|
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") })
|
|
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" })
|
|
3098
3121
|
] }),
|
|
3099
|
-
/* @__PURE__ */
|
|
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
|
+
] })
|
|
3100
3127
|
] });
|
|
3101
3128
|
}
|
|
3102
3129
|
function ColorNav() {
|
|
3103
|
-
return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
3130
|
+
return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
3104
3131
|
/* @__PURE__ */ jsx9(Text9, { color: "yellow", bold: true, children: "[S]" }),
|
|
3105
3132
|
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: " Setup " }),
|
|
3106
3133
|
/* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "[B]" }),
|
|
@@ -3241,7 +3268,7 @@ ${runtimesText(session)}`),
|
|
|
3241
3268
|
print(t("installNone"), args2.json);
|
|
3242
3269
|
return 1;
|
|
3243
3270
|
}
|
|
3244
|
-
const pulled = await pullOllamaModel(rec.model.id);
|
|
3271
|
+
const pulled = await pullOllamaModel(rec.model.id, rec.model.name);
|
|
3245
3272
|
print(args2.json ? pulled : explained("aboutRecommend", pulled.log), args2.json);
|
|
3246
3273
|
return pulled.ok ? 0 : 1;
|
|
3247
3274
|
}
|