pudu-ai 0.2.18 → 0.2.19
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 +133 -132
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -29,7 +29,7 @@ function spawnTracked(file, args2 = [], options = {}) {
|
|
|
29
29
|
installSignalHandlers();
|
|
30
30
|
const subprocess = execa(file, args2, {
|
|
31
31
|
reject: false,
|
|
32
|
-
timeout: options.timeout
|
|
32
|
+
timeout: options.timeout === void 0 ? 3e4 : options.timeout === 0 ? void 0 : options.timeout,
|
|
33
33
|
...options
|
|
34
34
|
});
|
|
35
35
|
children.add(subprocess);
|
|
@@ -71,7 +71,7 @@ var init_process = __esm({
|
|
|
71
71
|
// src/shared/which.ts
|
|
72
72
|
import { access } from "node:fs/promises";
|
|
73
73
|
import { constants } from "node:fs";
|
|
74
|
-
import
|
|
74
|
+
import path from "node:path";
|
|
75
75
|
async function commandExists(name) {
|
|
76
76
|
if (name.includes("/") || name.includes("\\")) {
|
|
77
77
|
try {
|
|
@@ -82,9 +82,9 @@ async function commandExists(name) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
const pathEnv = process.env.PATH ?? "";
|
|
85
|
-
const parts = pathEnv.split(
|
|
85
|
+
const parts = pathEnv.split(path.delimiter);
|
|
86
86
|
for (const dir of parts) {
|
|
87
|
-
const candidate =
|
|
87
|
+
const candidate = path.join(dir, name);
|
|
88
88
|
try {
|
|
89
89
|
await access(candidate, constants.X_OK);
|
|
90
90
|
return candidate;
|
|
@@ -238,31 +238,138 @@ function parseArgs(argv) {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
// src/adapters/ollama/index.ts
|
|
242
|
+
init_process();
|
|
243
|
+
init_which();
|
|
244
|
+
|
|
245
|
+
// src/shared/bytes.ts
|
|
246
|
+
var GiB = 1024 ** 3;
|
|
247
|
+
var MiB = 1024 ** 2;
|
|
248
|
+
function bytesToGiB(bytes) {
|
|
249
|
+
return bytes / GiB;
|
|
250
|
+
}
|
|
251
|
+
function parseSizeToBytes(input) {
|
|
252
|
+
const match = input.trim().match(/^([\d.]+)\s*(B|KB|MB|GB|TB|KiB|MiB|GiB|TiB)$/i);
|
|
253
|
+
if (!match) return void 0;
|
|
254
|
+
const value = Number(match[1]);
|
|
255
|
+
const unit = match[2].toLowerCase();
|
|
256
|
+
const map = {
|
|
257
|
+
b: 1,
|
|
258
|
+
kb: 1e3,
|
|
259
|
+
mb: 1e3 ** 2,
|
|
260
|
+
gb: 1e3 ** 3,
|
|
261
|
+
tb: 1e3 ** 4,
|
|
262
|
+
kib: 1024,
|
|
263
|
+
mib: 1024 ** 2,
|
|
264
|
+
gib: 1024 ** 3,
|
|
265
|
+
tib: 1024 ** 4
|
|
266
|
+
};
|
|
267
|
+
const factor = map[unit];
|
|
268
|
+
if (!factor || Number.isNaN(value)) return void 0;
|
|
269
|
+
return value * factor;
|
|
270
|
+
}
|
|
271
|
+
function formatBytes(bytes, digits = 1) {
|
|
272
|
+
if (!Number.isFinite(bytes)) return "N/A";
|
|
273
|
+
const abs = Math.abs(bytes);
|
|
274
|
+
if (abs >= GiB) return `${(bytes / GiB).toFixed(digits)} GB`;
|
|
275
|
+
if (abs >= MiB) return `${(bytes / MiB).toFixed(digits)} MB`;
|
|
276
|
+
if (abs >= 1024) return `${(bytes / 1024).toFixed(digits)} KB`;
|
|
277
|
+
return `${Math.round(bytes)} B`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/adapters/ollama/parse.ts
|
|
281
|
+
function parseOllamaList(output) {
|
|
282
|
+
const lines = output.split(/\r?\n/).map((l) => l.trimEnd()).filter(Boolean);
|
|
283
|
+
if (lines.length === 0) return [];
|
|
284
|
+
const header = lines[0] ?? "";
|
|
285
|
+
if (!/^NAME\s+/i.test(header)) {
|
|
286
|
+
return lines.flatMap((line) => parseDataLine(line));
|
|
287
|
+
}
|
|
288
|
+
return lines.slice(1).flatMap((line) => parseDataLine(line));
|
|
289
|
+
}
|
|
290
|
+
function parseDataLine(line) {
|
|
291
|
+
const parts = line.trim().split(/\s{2,}/);
|
|
292
|
+
if (parts.length < 2) return [];
|
|
293
|
+
const name = parts[0];
|
|
294
|
+
if (!name || /^NAME$/i.test(name)) return [];
|
|
295
|
+
const digest = parts[1];
|
|
296
|
+
const sizeRaw = parts[2];
|
|
297
|
+
const modified = parts.slice(3).join(" ") || void 0;
|
|
298
|
+
return [
|
|
299
|
+
{
|
|
300
|
+
id: name,
|
|
301
|
+
name,
|
|
302
|
+
source: "ollama",
|
|
303
|
+
digest,
|
|
304
|
+
sizeBytes: sizeRaw ? parseSizeToBytes(sizeRaw.replace(/([A-Z]+)$/i, " $1")) : void 0,
|
|
305
|
+
modifiedAt: modified
|
|
306
|
+
}
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
function parseOllamaModelfile(modelfile) {
|
|
310
|
+
const match = modelfile.match(/^FROM\s+(.+)$/m);
|
|
311
|
+
return { from: match?.[1]?.trim() };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/adapters/ollama/index.ts
|
|
315
|
+
var ollamaAdapter = {
|
|
316
|
+
id: "ollama",
|
|
317
|
+
label: "Ollama",
|
|
318
|
+
async detect() {
|
|
319
|
+
return Boolean(await commandExists("ollama"));
|
|
320
|
+
},
|
|
321
|
+
async version() {
|
|
322
|
+
const result = await runCommand("ollama", ["--version"], { timeout: 8e3 });
|
|
323
|
+
const text = `${result.stdout} ${result.stderr}`;
|
|
324
|
+
const match = text.match(/(\d+\.\d+\.\d+)/);
|
|
325
|
+
return match?.[1];
|
|
326
|
+
},
|
|
327
|
+
async listModels() {
|
|
328
|
+
if (!await this.detect()) return [];
|
|
329
|
+
const result = await runCommand("ollama", ["list"], { timeout: 15e3 });
|
|
330
|
+
return parseOllamaList(result.stdout);
|
|
331
|
+
},
|
|
332
|
+
async resolveModel(id) {
|
|
333
|
+
return resolveOllamaBlob(id);
|
|
334
|
+
},
|
|
335
|
+
async benchmarkCapabilities() {
|
|
336
|
+
return ["llama-bench"];
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
async function resolveOllamaBlob(id) {
|
|
340
|
+
const result = await runCommand("ollama", ["show", "--modelfile", id], { timeout: 15e3 });
|
|
341
|
+
if (result.exitCode !== 0) return void 0;
|
|
342
|
+
const { from } = parseOllamaModelfile(result.stdout);
|
|
343
|
+
if (!from) return void 0;
|
|
344
|
+
const format = from.endsWith(".gguf") || from.includes("sha256-") ? "gguf" : "unknown";
|
|
345
|
+
return { id, path: from, format };
|
|
346
|
+
}
|
|
347
|
+
|
|
241
348
|
// src/storage/config.ts
|
|
242
349
|
import { readFile, writeFile } from "node:fs/promises";
|
|
243
350
|
import { z } from "zod";
|
|
244
351
|
|
|
245
352
|
// src/storage/paths.ts
|
|
246
353
|
import os from "node:os";
|
|
247
|
-
import
|
|
354
|
+
import path2 from "node:path";
|
|
248
355
|
import { mkdir } from "node:fs/promises";
|
|
249
356
|
function homeDir() {
|
|
250
|
-
return
|
|
357
|
+
return path2.join(os.homedir(), ".pudu-ai");
|
|
251
358
|
}
|
|
252
359
|
function configPath() {
|
|
253
|
-
return
|
|
360
|
+
return path2.join(homeDir(), "config.json");
|
|
254
361
|
}
|
|
255
362
|
function benchmarksDir() {
|
|
256
|
-
return
|
|
363
|
+
return path2.join(homeDir(), "benchmarks");
|
|
257
364
|
}
|
|
258
365
|
function telemetryDir() {
|
|
259
|
-
return
|
|
366
|
+
return path2.join(homeDir(), "telemetry");
|
|
260
367
|
}
|
|
261
368
|
function cacheDir() {
|
|
262
|
-
return
|
|
369
|
+
return path2.join(homeDir(), "cache");
|
|
263
370
|
}
|
|
264
371
|
function canirunCachePath() {
|
|
265
|
-
return
|
|
372
|
+
return path2.join(cacheDir(), "canirun-models.json");
|
|
266
373
|
}
|
|
267
374
|
async function ensureStorage() {
|
|
268
375
|
await mkdir(benchmarksDir(), { recursive: true });
|
|
@@ -305,43 +412,6 @@ import os5 from "node:os";
|
|
|
305
412
|
// src/platform/macos/hardware.ts
|
|
306
413
|
init_process();
|
|
307
414
|
import os2 from "node:os";
|
|
308
|
-
|
|
309
|
-
// src/shared/bytes.ts
|
|
310
|
-
var GiB = 1024 ** 3;
|
|
311
|
-
var MiB = 1024 ** 2;
|
|
312
|
-
function bytesToGiB(bytes) {
|
|
313
|
-
return bytes / GiB;
|
|
314
|
-
}
|
|
315
|
-
function parseSizeToBytes(input) {
|
|
316
|
-
const match = input.trim().match(/^([\d.]+)\s*(B|KB|MB|GB|TB|KiB|MiB|GiB|TiB)$/i);
|
|
317
|
-
if (!match) return void 0;
|
|
318
|
-
const value = Number(match[1]);
|
|
319
|
-
const unit = match[2].toLowerCase();
|
|
320
|
-
const map = {
|
|
321
|
-
b: 1,
|
|
322
|
-
kb: 1e3,
|
|
323
|
-
mb: 1e3 ** 2,
|
|
324
|
-
gb: 1e3 ** 3,
|
|
325
|
-
tb: 1e3 ** 4,
|
|
326
|
-
kib: 1024,
|
|
327
|
-
mib: 1024 ** 2,
|
|
328
|
-
gib: 1024 ** 3,
|
|
329
|
-
tib: 1024 ** 4
|
|
330
|
-
};
|
|
331
|
-
const factor = map[unit];
|
|
332
|
-
if (!factor || Number.isNaN(value)) return void 0;
|
|
333
|
-
return value * factor;
|
|
334
|
-
}
|
|
335
|
-
function formatBytes(bytes, digits = 1) {
|
|
336
|
-
if (!Number.isFinite(bytes)) return "N/A";
|
|
337
|
-
const abs = Math.abs(bytes);
|
|
338
|
-
if (abs >= GiB) return `${(bytes / GiB).toFixed(digits)} GB`;
|
|
339
|
-
if (abs >= MiB) return `${(bytes / MiB).toFixed(digits)} MB`;
|
|
340
|
-
if (abs >= 1024) return `${(bytes / 1024).toFixed(digits)} KB`;
|
|
341
|
-
return `${Math.round(bytes)} B`;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// src/platform/macos/hardware.ts
|
|
345
415
|
function parseAppleSilicon(chip) {
|
|
346
416
|
const match = chip.match(/Apple\s+(M\d+)(?:\s+(Pro|Max|Ultra))?/i);
|
|
347
417
|
if (!match) return void 0;
|
|
@@ -515,85 +585,6 @@ async function detectHardware() {
|
|
|
515
585
|
import os7 from "node:os";
|
|
516
586
|
import path5 from "node:path";
|
|
517
587
|
|
|
518
|
-
// src/adapters/ollama/index.ts
|
|
519
|
-
init_process();
|
|
520
|
-
init_which();
|
|
521
|
-
|
|
522
|
-
// src/adapters/ollama/parse.ts
|
|
523
|
-
function parseOllamaList(output) {
|
|
524
|
-
const lines = output.split(/\r?\n/).map((l) => l.trimEnd()).filter(Boolean);
|
|
525
|
-
if (lines.length === 0) return [];
|
|
526
|
-
const header = lines[0] ?? "";
|
|
527
|
-
if (!/^NAME\s+/i.test(header)) {
|
|
528
|
-
return lines.flatMap((line) => parseDataLine(line));
|
|
529
|
-
}
|
|
530
|
-
return lines.slice(1).flatMap((line) => parseDataLine(line));
|
|
531
|
-
}
|
|
532
|
-
function parseDataLine(line) {
|
|
533
|
-
const parts = line.trim().split(/\s{2,}/);
|
|
534
|
-
if (parts.length < 2) return [];
|
|
535
|
-
const name = parts[0];
|
|
536
|
-
if (!name || /^NAME$/i.test(name)) return [];
|
|
537
|
-
const digest = parts[1];
|
|
538
|
-
const sizeRaw = parts[2];
|
|
539
|
-
const modified = parts.slice(3).join(" ") || void 0;
|
|
540
|
-
return [
|
|
541
|
-
{
|
|
542
|
-
id: name,
|
|
543
|
-
name,
|
|
544
|
-
source: "ollama",
|
|
545
|
-
digest,
|
|
546
|
-
sizeBytes: sizeRaw ? parseSizeToBytes(sizeRaw.replace(/([A-Z]+)$/i, " $1")) : void 0,
|
|
547
|
-
modifiedAt: modified
|
|
548
|
-
}
|
|
549
|
-
];
|
|
550
|
-
}
|
|
551
|
-
function parseOllamaModelfile(modelfile) {
|
|
552
|
-
const match = modelfile.match(/^FROM\s+(.+)$/m);
|
|
553
|
-
return { from: match?.[1]?.trim() };
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// src/adapters/ollama/index.ts
|
|
557
|
-
var ollamaAdapter = {
|
|
558
|
-
id: "ollama",
|
|
559
|
-
label: "Ollama",
|
|
560
|
-
async detect() {
|
|
561
|
-
return Boolean(await commandExists("ollama"));
|
|
562
|
-
},
|
|
563
|
-
async version() {
|
|
564
|
-
const result = await runCommand("ollama", ["--version"], { timeout: 8e3 });
|
|
565
|
-
const text = `${result.stdout} ${result.stderr}`;
|
|
566
|
-
const match = text.match(/(\d+\.\d+\.\d+)/);
|
|
567
|
-
return match?.[1];
|
|
568
|
-
},
|
|
569
|
-
async listModels() {
|
|
570
|
-
if (!await this.detect()) return [];
|
|
571
|
-
const result = await runCommand("ollama", ["list"], { timeout: 15e3 });
|
|
572
|
-
const models = parseOllamaList(result.stdout);
|
|
573
|
-
const resolved = await Promise.all(
|
|
574
|
-
models.map(async (model) => {
|
|
575
|
-
const artifact = await resolveOllamaBlob(model.id);
|
|
576
|
-
return { ...model, artifactPath: artifact?.path };
|
|
577
|
-
})
|
|
578
|
-
);
|
|
579
|
-
return resolved;
|
|
580
|
-
},
|
|
581
|
-
async resolveModel(id) {
|
|
582
|
-
return resolveOllamaBlob(id);
|
|
583
|
-
},
|
|
584
|
-
async benchmarkCapabilities() {
|
|
585
|
-
return ["llama-bench"];
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
async function resolveOllamaBlob(id) {
|
|
589
|
-
const result = await runCommand("ollama", ["show", "--modelfile", id], { timeout: 15e3 });
|
|
590
|
-
if (result.exitCode !== 0) return void 0;
|
|
591
|
-
const { from } = parseOllamaModelfile(result.stdout);
|
|
592
|
-
if (!from) return void 0;
|
|
593
|
-
const format = from.endsWith(".gguf") || from.includes("sha256-") ? "gguf" : "unknown";
|
|
594
|
-
return { id, path: from, format };
|
|
595
|
-
}
|
|
596
|
-
|
|
597
588
|
// src/adapters/lmstudio/index.ts
|
|
598
589
|
import os6 from "node:os";
|
|
599
590
|
import path4 from "node:path";
|
|
@@ -1632,7 +1623,7 @@ async function loadSession(options) {
|
|
|
1632
1623
|
const [runtimes2, models, catalogState, history, llamaBench, agents] = await Promise.all([
|
|
1633
1624
|
detectRuntimes(),
|
|
1634
1625
|
discoverLocalModels(),
|
|
1635
|
-
loadCatalog(options.network),
|
|
1626
|
+
options.light ? Promise.resolve({ catalog: [], networkUsed: false }) : loadCatalog(options.network),
|
|
1636
1627
|
listBenchmarks(),
|
|
1637
1628
|
commandExists("llama-bench").then(Boolean),
|
|
1638
1629
|
detectAgents()
|
|
@@ -1640,11 +1631,11 @@ async function loadSession(options) {
|
|
|
1640
1631
|
const rows = [];
|
|
1641
1632
|
for (const local of models) {
|
|
1642
1633
|
const catalog = matchCatalog(catalogState.catalog, local);
|
|
1643
|
-
const compatibility = catalog ? await compatibilityFor(hardware, catalog, options.network) : void 0;
|
|
1634
|
+
const compatibility = catalog && !options.light ? await compatibilityFor(hardware, catalog, options.network) : void 0;
|
|
1644
1635
|
const lastBenchmark = [...history].reverse().find((b) => b.model.id === local.id);
|
|
1645
1636
|
rows.push({ local, catalog, compatibility, lastBenchmark });
|
|
1646
1637
|
}
|
|
1647
|
-
const recommendations = await recommendForMachine(hardware, catalogState.catalog, options.network);
|
|
1638
|
+
const recommendations = options.light ? [] : await recommendForMachine(hardware, catalogState.catalog, options.network);
|
|
1648
1639
|
return {
|
|
1649
1640
|
hardware,
|
|
1650
1641
|
runtimes: runtimes2,
|
|
@@ -2727,7 +2718,10 @@ ${pull.stderr || t("launchPullFail")}` };
|
|
|
2727
2718
|
const args2 = ["launch", def.ollamaLaunch, "--model", decision.ollamaTag];
|
|
2728
2719
|
if (def.id === "openclaw" || def.id === "claude") args2.push("--yes");
|
|
2729
2720
|
lines.push(`ollama ${args2.join(" ")}`);
|
|
2730
|
-
const launched = await runCommand("ollama", args2, {
|
|
2721
|
+
const launched = await runCommand("ollama", args2, {
|
|
2722
|
+
timeout: 0,
|
|
2723
|
+
stdio: "inherit"
|
|
2724
|
+
});
|
|
2731
2725
|
if (launched.exitCode !== 0) {
|
|
2732
2726
|
return {
|
|
2733
2727
|
ok: false,
|
|
@@ -3565,7 +3559,10 @@ async function run(args2) {
|
|
|
3565
3559
|
process.stderr.write(`${t("loading")}
|
|
3566
3560
|
`);
|
|
3567
3561
|
}
|
|
3568
|
-
const session = await loadSession({
|
|
3562
|
+
const session = await loadSession({
|
|
3563
|
+
network: args2.network,
|
|
3564
|
+
light: args2.command === "launch" || args2.command === "doctor" || args2.command === "hardware"
|
|
3565
|
+
});
|
|
3569
3566
|
switch (args2.command) {
|
|
3570
3567
|
case "hardware":
|
|
3571
3568
|
print(
|
|
@@ -3690,6 +3687,10 @@ ${runtimesText(session)}`),
|
|
|
3690
3687
|
return 1;
|
|
3691
3688
|
}
|
|
3692
3689
|
const catalog = session.rows.find((r) => r.local.id === model.id)?.catalog;
|
|
3690
|
+
if (!model.artifactPath && model.source === "ollama") {
|
|
3691
|
+
const artifact = await ollamaAdapter.resolveModel(model.id);
|
|
3692
|
+
model.artifactPath = artifact?.path;
|
|
3693
|
+
}
|
|
3693
3694
|
const result = await runBenchmark({
|
|
3694
3695
|
model,
|
|
3695
3696
|
hardware: session.hardware,
|