pudu-ai 0.2.4 → 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 +1 -0
- package/dist/cli/index.js +96 -41
- package/package.json +1 -1
package/README.md
CHANGED
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
|
-
|
|
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
|
|
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__ */
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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:
|
|
2522
|
-
|
|
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(
|
|
2554
|
+
/* @__PURE__ */ jsx4(Text4, { color: gradeColor(item.grade), bold: true, children: item.grade }),
|
|
2525
2555
|
" ",
|
|
2526
|
-
GRADE_MEANING[
|
|
2527
|
-
] }, `${
|
|
2556
|
+
GRADE_MEANING[item.grade]
|
|
2557
|
+
] }, `${item.useCase}-${item.model.id}`)),
|
|
2558
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2559
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: t("setupStep2") }),
|
|
2560
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: t("setupInstallModel") })
|
|
2561
|
+
] }),
|
|
2528
2562
|
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
2529
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: t("
|
|
2530
|
-
session.agents.map((agent) => /* @__PURE__ */ jsxs4(Text4, { color: agent.detected ? "green" : "
|
|
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("
|
|
2572
|
+
agent.detected ? t("setupLinkNow") : t("setupInstallAgent")
|
|
2536
2573
|
] }, agent.id))
|
|
2537
2574
|
] }),
|
|
2538
|
-
|
|
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
|
|
|
@@ -2999,33 +3037,43 @@ function TasksView({ session }) {
|
|
|
2999
3037
|
] });
|
|
3000
3038
|
}
|
|
3001
3039
|
|
|
3040
|
+
// src/tui/keys.ts
|
|
3041
|
+
function handleAppKey(screen, input, key) {
|
|
3042
|
+
const letter = input.toLowerCase();
|
|
3043
|
+
if (screen === "benchmark") return { type: "ignore" };
|
|
3044
|
+
if (screen === "recommend" || screen === "tasks") {
|
|
3045
|
+
if (letter === "q") return { type: "quit" };
|
|
3046
|
+
if (key.escape) return { type: "home" };
|
|
3047
|
+
return { type: "ignore" };
|
|
3048
|
+
}
|
|
3049
|
+
if (letter === "q" || key.escape) return { type: "quit" };
|
|
3050
|
+
if (letter === "b") return { type: "screen", screen: "benchmark" };
|
|
3051
|
+
if (letter === "m") return { type: "screen", screen: "models" };
|
|
3052
|
+
if (letter === "r") return { type: "screen", screen: "recommend" };
|
|
3053
|
+
if (letter === "h") return { type: "screen", screen: "hardware" };
|
|
3054
|
+
if (letter === "c") return { type: "screen", screen: "compare" };
|
|
3055
|
+
if (letter === "l") return { type: "screen", screen: "history" };
|
|
3056
|
+
if (letter === "t") return { type: "screen", screen: "tasks" };
|
|
3057
|
+
if (letter === "s" || letter === "i") return { type: "screen", screen: "recommend" };
|
|
3058
|
+
if (key.return && screen === "home") return { type: "screen", screen: "benchmark" };
|
|
3059
|
+
return { type: "ignore" };
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3002
3062
|
// src/tui/App.tsx
|
|
3003
3063
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3004
3064
|
function App(props) {
|
|
3005
3065
|
const { exit } = useApp2();
|
|
3006
|
-
const [screen, setScreen] = useState4("home");
|
|
3066
|
+
const [screen, setScreen] = useState4(props.start ?? "home");
|
|
3007
3067
|
const [selected, setSelected] = useState4(0);
|
|
3008
3068
|
const benchable = useMemo2(
|
|
3009
3069
|
() => props.session.models.filter((m) => Boolean(m.artifactPath)),
|
|
3010
3070
|
[props.session.models]
|
|
3011
3071
|
);
|
|
3012
3072
|
useInput4((input, key) => {
|
|
3013
|
-
|
|
3014
|
-
if (
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
return;
|
|
3018
|
-
}
|
|
3019
|
-
const letter = input.toLowerCase();
|
|
3020
|
-
if (letter === "q" || key.escape) exit();
|
|
3021
|
-
if (letter === "b") setScreen("benchmark");
|
|
3022
|
-
if (letter === "m") setScreen("models");
|
|
3023
|
-
if (letter === "r") setScreen("recommend");
|
|
3024
|
-
if (letter === "h") setScreen("hardware");
|
|
3025
|
-
if (letter === "c") setScreen("compare");
|
|
3026
|
-
if (letter === "l") setScreen("history");
|
|
3027
|
-
if (letter === "t") setScreen("tasks");
|
|
3028
|
-
if (key.return && screen === "home") setScreen("benchmark");
|
|
3073
|
+
const result = handleAppKey(screen, input, key);
|
|
3074
|
+
if (result.type === "quit") exit();
|
|
3075
|
+
if (result.type === "home") setScreen("home");
|
|
3076
|
+
if (result.type === "screen") setScreen(result.screen);
|
|
3029
3077
|
});
|
|
3030
3078
|
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", padding: 1, children: [
|
|
3031
3079
|
screen === "home" && /* @__PURE__ */ jsx9(Dashboard, { session: props.session }),
|
|
@@ -3052,8 +3100,8 @@ function App(props) {
|
|
|
3052
3100
|
|
|
3053
3101
|
// src/tui/render.tsx
|
|
3054
3102
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3055
|
-
async function renderDashboard(session, preset) {
|
|
3056
|
-
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 }));
|
|
3057
3105
|
await instance.waitUntilExit();
|
|
3058
3106
|
}
|
|
3059
3107
|
|
|
@@ -3115,6 +3163,13 @@ ${runtimesText(session)}`),
|
|
|
3115
3163
|
case "models":
|
|
3116
3164
|
print(args2.json ? session.rows : explained("aboutModels", modelsText(session)), args2.json);
|
|
3117
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;
|
|
3118
3173
|
case "recommend": {
|
|
3119
3174
|
if (args2.install && args2.yes) {
|
|
3120
3175
|
const rec = session.recommendations.find((r) => canInstallGrade(r.grade));
|