pudu-ai 0.2.15 → 0.2.16

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 +218 -81
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -1,4 +1,178 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/shared/process.ts
18
+ import { execa } from "execa";
19
+ function installSignalHandlers() {
20
+ if (installed) return;
21
+ installed = true;
22
+ const stop = () => {
23
+ void killAll("SIGTERM");
24
+ };
25
+ process.once("SIGINT", stop);
26
+ process.once("SIGTERM", stop);
27
+ }
28
+ function spawnTracked(file, args2 = [], options = {}) {
29
+ installSignalHandlers();
30
+ const subprocess = execa(file, args2, {
31
+ reject: false,
32
+ timeout: options.timeout ?? 3e4,
33
+ ...options
34
+ });
35
+ children.add(subprocess);
36
+ void subprocess.finally(() => {
37
+ children.delete(subprocess);
38
+ });
39
+ return subprocess;
40
+ }
41
+ function toText(value) {
42
+ if (typeof value === "string") return value;
43
+ if (value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
44
+ if (Array.isArray(value)) return value.map(String).join("\n");
45
+ if (value == null) return "";
46
+ return String(value);
47
+ }
48
+ async function runCommand(file, args2 = [], options = {}) {
49
+ const result = await spawnTracked(file, args2, options);
50
+ return {
51
+ stdout: toText(result.stdout),
52
+ stderr: toText(result.stderr),
53
+ exitCode: result.exitCode,
54
+ timedOut: Boolean(result.timedOut)
55
+ };
56
+ }
57
+ async function killAll(signal = "SIGTERM") {
58
+ for (const child of [...children]) {
59
+ child.kill(signal);
60
+ }
61
+ }
62
+ var children, installed;
63
+ var init_process = __esm({
64
+ "src/shared/process.ts"() {
65
+ "use strict";
66
+ children = /* @__PURE__ */ new Set();
67
+ installed = false;
68
+ }
69
+ });
70
+
71
+ // src/shared/which.ts
72
+ import { access } from "node:fs/promises";
73
+ import { constants } from "node:fs";
74
+ import path2 from "node:path";
75
+ async function commandExists(name) {
76
+ if (name.includes("/") || name.includes("\\")) {
77
+ try {
78
+ await access(name, constants.X_OK);
79
+ return name;
80
+ } catch {
81
+ return void 0;
82
+ }
83
+ }
84
+ const pathEnv = process.env.PATH ?? "";
85
+ const parts = pathEnv.split(path2.delimiter);
86
+ for (const dir of parts) {
87
+ const candidate = path2.join(dir, name);
88
+ try {
89
+ await access(candidate, constants.X_OK);
90
+ return candidate;
91
+ } catch {
92
+ continue;
93
+ }
94
+ }
95
+ return void 0;
96
+ }
97
+ var init_which = __esm({
98
+ "src/shared/which.ts"() {
99
+ "use strict";
100
+ }
101
+ });
102
+
103
+ // src/integrations/brew.ts
104
+ var brew_exports = {};
105
+ __export(brew_exports, {
106
+ brewInstallCask: () => brewInstallCask,
107
+ dockerReady: () => dockerReady,
108
+ ensureBrew: () => ensureBrew,
109
+ resolveBrew: () => resolveBrew,
110
+ startDockerDesktop: () => startDockerDesktop,
111
+ waitForDocker: () => waitForDocker
112
+ });
113
+ async function resolveBrew() {
114
+ for (const bin of BREW_CANDIDATES) {
115
+ const found = await commandExists(bin);
116
+ if (found) return found;
117
+ }
118
+ return void 0;
119
+ }
120
+ async function dockerReady() {
121
+ const docker = await commandExists("docker");
122
+ if (!docker) return false;
123
+ const info = await runCommand("docker", ["info"], { timeout: 8e3 });
124
+ return info.exitCode === 0;
125
+ }
126
+ async function ensureBrew() {
127
+ const existing = await resolveBrew();
128
+ if (existing) return { ok: true, brew: existing, log: existing };
129
+ const install = await runCommand(
130
+ "/bin/bash",
131
+ ["-c", 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'],
132
+ { timeout: 15 * 6e4 }
133
+ );
134
+ const after = await resolveBrew();
135
+ if (after) return { ok: true, brew: after, log: after };
136
+ return {
137
+ ok: false,
138
+ log: install.stderr || "Homebrew missing. Install: https://brew.sh"
139
+ };
140
+ }
141
+ async function brewInstallCask(name) {
142
+ const brew = await resolveBrew();
143
+ if (!brew) {
144
+ const ensured = await ensureBrew();
145
+ if (!ensured.ok || !ensured.brew) return { ok: false, log: ensured.log };
146
+ const result2 = await runCommand(ensured.brew, ["install", "--cask", name], { timeout: 15 * 6e4 });
147
+ return { ok: result2.exitCode === 0, log: result2.stderr || result2.stdout };
148
+ }
149
+ const result = await runCommand(brew, ["install", "--cask", name], { timeout: 15 * 6e4 });
150
+ if (result.exitCode === 0) return { ok: true, log: result.stdout };
151
+ const formula = await runCommand(brew, ["install", name], { timeout: 15 * 6e4 });
152
+ return { ok: formula.exitCode === 0, log: formula.stderr || formula.stdout };
153
+ }
154
+ async function startDockerDesktop() {
155
+ if (process.platform === "darwin") {
156
+ await runCommand("open", ["-a", "Docker"], { timeout: 15e3 });
157
+ }
158
+ }
159
+ async function waitForDocker(ms = 9e4) {
160
+ const start = Date.now();
161
+ while (Date.now() - start < ms) {
162
+ if (await dockerReady()) return true;
163
+ await new Promise((resolve) => setTimeout(resolve, 2500));
164
+ }
165
+ return dockerReady();
166
+ }
167
+ var BREW_CANDIDATES;
168
+ var init_brew = __esm({
169
+ "src/integrations/brew.ts"() {
170
+ "use strict";
171
+ init_which();
172
+ init_process();
173
+ BREW_CANDIDATES = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew", "brew"];
174
+ }
175
+ });
2
176
 
3
177
  // src/cli/parse-args.ts
4
178
  function parseArgs(argv) {
@@ -118,56 +292,9 @@ async function addModelPath(dir) {
118
292
  import os5 from "node:os";
119
293
 
120
294
  // src/platform/macos/hardware.ts
295
+ init_process();
121
296
  import os2 from "node:os";
122
297
 
123
- // src/shared/process.ts
124
- import { execa } from "execa";
125
- var children = /* @__PURE__ */ new Set();
126
- var installed = false;
127
- function installSignalHandlers() {
128
- if (installed) return;
129
- installed = true;
130
- const stop = () => {
131
- void killAll("SIGTERM");
132
- };
133
- process.once("SIGINT", stop);
134
- process.once("SIGTERM", stop);
135
- }
136
- function spawnTracked(file, args2 = [], options = {}) {
137
- installSignalHandlers();
138
- const subprocess = execa(file, args2, {
139
- reject: false,
140
- timeout: options.timeout ?? 3e4,
141
- ...options
142
- });
143
- children.add(subprocess);
144
- void subprocess.finally(() => {
145
- children.delete(subprocess);
146
- });
147
- return subprocess;
148
- }
149
- function toText(value) {
150
- if (typeof value === "string") return value;
151
- if (value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
152
- if (Array.isArray(value)) return value.map(String).join("\n");
153
- if (value == null) return "";
154
- return String(value);
155
- }
156
- async function runCommand(file, args2 = [], options = {}) {
157
- const result = await spawnTracked(file, args2, options);
158
- return {
159
- stdout: toText(result.stdout),
160
- stderr: toText(result.stderr),
161
- exitCode: result.exitCode,
162
- timedOut: Boolean(result.timedOut)
163
- };
164
- }
165
- async function killAll(signal = "SIGTERM") {
166
- for (const child of [...children]) {
167
- child.kill(signal);
168
- }
169
- }
170
-
171
298
  // src/shared/bytes.ts
172
299
  var GiB = 1024 ** 3;
173
300
  var MiB = 1024 ** 2;
@@ -377,32 +504,9 @@ async function detectHardware() {
377
504
  import os7 from "node:os";
378
505
  import path5 from "node:path";
379
506
 
380
- // src/shared/which.ts
381
- import { access } from "node:fs/promises";
382
- import { constants } from "node:fs";
383
- import path2 from "node:path";
384
- async function commandExists(name) {
385
- if (name.includes("/") || name.includes("\\")) {
386
- try {
387
- await access(name, constants.X_OK);
388
- return name;
389
- } catch {
390
- return void 0;
391
- }
392
- }
393
- const pathEnv = process.env.PATH ?? "";
394
- const parts = pathEnv.split(path2.delimiter);
395
- for (const dir of parts) {
396
- const candidate = path2.join(dir, name);
397
- try {
398
- await access(candidate, constants.X_OK);
399
- return candidate;
400
- } catch {
401
- continue;
402
- }
403
- }
404
- return void 0;
405
- }
507
+ // src/adapters/ollama/index.ts
508
+ init_process();
509
+ init_which();
406
510
 
407
511
  // src/adapters/ollama/parse.ts
408
512
  function parseOllamaList(output) {
@@ -485,6 +589,7 @@ import path4 from "node:path";
485
589
  import { access as access2 } from "node:fs/promises";
486
590
 
487
591
  // src/adapters/llamacpp/index.ts
592
+ init_which();
488
593
  import { readdir, stat } from "node:fs/promises";
489
594
  import path3 from "node:path";
490
595
  var BINARIES = ["llama-bench", "llama-cli", "llama-server"];
@@ -623,6 +728,7 @@ async function discoverLocalModels() {
623
728
  }
624
729
 
625
730
  // src/adapters/mlx/index.ts
731
+ init_which();
626
732
  var mlxAdapter = {
627
733
  id: "mlx",
628
734
  label: "MLX",
@@ -644,6 +750,7 @@ var mlxAdapter = {
644
750
  };
645
751
 
646
752
  // src/runtimes/registry.ts
753
+ init_which();
647
754
  var runtimes = [ollamaAdapter, llamaCppAdapter, lmStudioAdapter, mlxAdapter];
648
755
  async function detectRuntimes() {
649
756
  const llamaBench = Boolean(await commandExists("llama-bench"));
@@ -849,7 +956,9 @@ var en = {
849
956
  dockerHint: "press 4 to run ollama/ollama in Docker",
850
957
  dockerStarting: "Starting ollama/ollama container\u2026",
851
958
  dockerRunOk: "Docker Ollama is up on port 11434 (name: pudu-ollama).",
852
- dockerRunFail: "docker run failed. Is Docker Desktop running?",
959
+ dockerRunFail: "Docker daemon is not running. Open Docker Desktop, wait until it is idle, press 4 again.",
960
+ dockerBrewInstall: "Installing Docker with Homebrew (brew install --cask docker)\u2026",
961
+ dockerDesktopWait: "Starting Docker Desktop and waiting for the daemon\u2026",
853
962
  aboutHistory: "Shows local benchmark files in ~/.pudu-ai/benchmarks. Use it to replay what this machine actually did.",
854
963
  aboutDoctor: "Checks Node, Apple Silicon/Metal, Ollama, llama-bench, and the CanIRun API. Use it to see what is missing before a bench or launch.",
855
964
  aboutReport: "Writes the latest measured run as Markdown. Use it to paste results into GitHub or notes.",
@@ -1043,7 +1152,9 @@ var es = {
1043
1152
  dockerHint: "pulsa 4 para correr ollama/ollama en Docker",
1044
1153
  dockerStarting: "Arrancando contenedor ollama/ollama\u2026",
1045
1154
  dockerRunOk: "Ollama en Docker est\xE1 en el puerto 11434 (nombre: pudu-ollama).",
1046
- dockerRunFail: "fall\xF3 docker run. \xBFDocker Desktop est\xE1 abierto?",
1155
+ dockerRunFail: "El daemon de Docker no corre. Abre Docker Desktop, espera a que est\xE9 listo y pulsa 4 otra vez.",
1156
+ dockerBrewInstall: "Instalando Docker con Homebrew (brew install --cask docker)\u2026",
1157
+ dockerDesktopWait: "Arrancando Docker Desktop y esperando al daemon\u2026",
1047
1158
  aboutHistory: "Muestra los JSON locales en ~/.pudu-ai/benchmarks. Sirve para ver qu\xE9 hizo realmente esta m\xE1quina.",
1048
1159
  aboutDoctor: "Revisa Node, Apple Silicon/Metal, Ollama, llama-bench y la API de CanIRun. Sirve para ver qu\xE9 falta antes de un bench o un launch.",
1049
1160
  aboutReport: "Exporta la \xFAltima corrida medida en Markdown. Sirve para pegar resultados en GitHub o notas.",
@@ -1462,7 +1573,11 @@ async function listBenchmarks() {
1462
1573
  return records;
1463
1574
  }
1464
1575
 
1576
+ // src/session/load.ts
1577
+ init_which();
1578
+
1465
1579
  // src/integrations/agents.ts
1580
+ init_which();
1466
1581
  var AGENTS = [
1467
1582
  {
1468
1583
  id: "opencode",
@@ -1531,7 +1646,12 @@ async function loadSession(options) {
1531
1646
  };
1532
1647
  }
1533
1648
 
1649
+ // src/benchmark/engine.ts
1650
+ init_process();
1651
+ init_which();
1652
+
1534
1653
  // src/telemetry/macos.ts
1654
+ init_process();
1535
1655
  import os9 from "node:os";
1536
1656
 
1537
1657
  // src/telemetry/fallback.ts
@@ -2017,6 +2137,9 @@ function resourceFields(resources) {
2017
2137
  };
2018
2138
  }
2019
2139
 
2140
+ // src/cli/run.ts
2141
+ init_process();
2142
+
2020
2143
  // src/shared/logger.ts
2021
2144
  var level = "info";
2022
2145
  function setLogLevel(next) {
@@ -2572,6 +2695,7 @@ function parseIntegration(raw) {
2572
2695
  }
2573
2696
 
2574
2697
  // src/integrations/execute.ts
2698
+ init_process();
2575
2699
  async function executeLaunch(decision) {
2576
2700
  if (!decision.eligible || !decision.ollamaTag) {
2577
2701
  return { ok: false, log: t("launchBlocked") };
@@ -2602,6 +2726,23 @@ ${launched.stderr || launched.stdout || t("launchExecFail")}`
2602
2726
  ${launched.stdout}`.trim() };
2603
2727
  }
2604
2728
  async function executeDockerOllama() {
2729
+ const { brewInstallCask: brewInstallCask2, dockerReady: dockerReady2, startDockerDesktop: startDockerDesktop2, waitForDocker: waitForDocker2 } = await Promise.resolve().then(() => (init_brew(), brew_exports));
2730
+ const notes = [];
2731
+ if (!await dockerReady2()) {
2732
+ notes.push(t("dockerBrewInstall"));
2733
+ const installed2 = await brewInstallCask2("docker");
2734
+ if (!installed2.ok) {
2735
+ return { ok: false, log: `${t("reqDocker")}
2736
+ ${installed2.log}` };
2737
+ }
2738
+ notes.push(t("dockerDesktopWait"));
2739
+ await startDockerDesktop2();
2740
+ const up = await waitForDocker2();
2741
+ if (!up) return { ok: false, log: `${notes.join("\n")}
2742
+ ${t("dockerRunFail")}` };
2743
+ }
2744
+ const start = await runCommand("docker", ["start", "pudu-ollama"], { timeout: 3e4 });
2745
+ if (start.exitCode === 0) return { ok: true, log: t("dockerRunOk") };
2605
2746
  const args2 = DOCKER_OLLAMA.split(" ").slice(1);
2606
2747
  const result = await runCommand("docker", args2, { timeout: 10 * 6e4 });
2607
2748
  if (result.exitCode !== 0) {
@@ -2611,6 +2752,7 @@ async function executeDockerOllama() {
2611
2752
  }
2612
2753
 
2613
2754
  // src/integrations/pull.ts
2755
+ init_process();
2614
2756
  var INSTALLABLE = ["S", "A", "B"];
2615
2757
  function canInstallGrade(grade) {
2616
2758
  return Boolean(grade && INSTALLABLE.includes(grade));
@@ -2659,11 +2801,6 @@ function SetupView({ session }) {
2659
2801
  });
2660
2802
  }
2661
2803
  if (input === "4") {
2662
- const docker = session.runtimes.find((r) => r.id === "docker")?.detected;
2663
- if (!docker) {
2664
- setLog(t("reqDocker"));
2665
- return;
2666
- }
2667
2804
  setBusy(true);
2668
2805
  setLog(t("dockerStarting"));
2669
2806
  void executeDockerOllama().then((result) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pudu-ai",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Pudu-AI — local AI hardware & benchmark lab for the terminal",
5
5
  "keywords": [
6
6
  "ai",