pudu-ai 0.2.18 → 0.2.21

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.
@@ -0,0 +1,1597 @@
1
+ import {
2
+ formatNumber,
3
+ formatTokensPerSec,
4
+ na,
5
+ t
6
+ } from "./chunk-QGEXKMQ5.js";
7
+ import {
8
+ commandExists,
9
+ runCommand,
10
+ spawnTracked,
11
+ toText
12
+ } from "./chunk-SS7OXXQQ.js";
13
+
14
+ // src/storage/benchmarks.ts
15
+ import { readdir, readFile, writeFile } from "node:fs/promises";
16
+ import path2 from "node:path";
17
+ import { z } from "zod";
18
+
19
+ // src/storage/paths.ts
20
+ import os from "node:os";
21
+ import path from "node:path";
22
+ import { mkdir } from "node:fs/promises";
23
+ function homeDir() {
24
+ return path.join(os.homedir(), ".pudu-ai");
25
+ }
26
+ function configPath() {
27
+ return path.join(homeDir(), "config.json");
28
+ }
29
+ function benchmarksDir() {
30
+ return path.join(homeDir(), "benchmarks");
31
+ }
32
+ function telemetryDir() {
33
+ return path.join(homeDir(), "telemetry");
34
+ }
35
+ function cacheDir() {
36
+ return path.join(homeDir(), "cache");
37
+ }
38
+ function canirunCachePath() {
39
+ return path.join(cacheDir(), "canirun-models.json");
40
+ }
41
+ async function ensureStorage() {
42
+ await mkdir(benchmarksDir(), { recursive: true });
43
+ await mkdir(telemetryDir(), { recursive: true });
44
+ await mkdir(cacheDir(), { recursive: true });
45
+ }
46
+
47
+ // src/storage/benchmarks.ts
48
+ var benchmarkRecordSchema = z.object({
49
+ schemaVersion: z.literal(1),
50
+ timestamp: z.string(),
51
+ machine: z.record(z.unknown()),
52
+ model: z.object({
53
+ id: z.string(),
54
+ name: z.string().optional(),
55
+ source: z.string().optional(),
56
+ path: z.string().optional()
57
+ }),
58
+ runtime: z.object({
59
+ name: z.string(),
60
+ command: z.array(z.string()).optional()
61
+ }),
62
+ benchmark: z.object({
63
+ promptTokens: z.number(),
64
+ generationTokens: z.number(),
65
+ repetitions: z.number(),
66
+ promptTokensPerSecond: z.number().optional(),
67
+ generationTokensPerSecond: z.number().optional(),
68
+ elapsedSeconds: z.number().optional()
69
+ }),
70
+ resources: z.object({
71
+ peakMemoryGb: z.number().optional(),
72
+ peakSwapGb: z.number().optional(),
73
+ avgGpuPercent: z.number().optional(),
74
+ avgCpuPercent: z.number().optional(),
75
+ peakGpuPercent: z.number().optional(),
76
+ avgPackagePowerWatts: z.number().optional(),
77
+ peakPackagePowerWatts: z.number().optional(),
78
+ avgTemperatureC: z.number().optional(),
79
+ peakTemperatureC: z.number().optional(),
80
+ tokensPerSecondPerWatt: z.number().optional(),
81
+ peakProcessRssGb: z.number().optional()
82
+ }),
83
+ score: z.object({
84
+ total: z.number(),
85
+ speed: z.string().optional(),
86
+ memory: z.string().optional(),
87
+ energy: z.string().optional(),
88
+ thermal: z.string().optional(),
89
+ swap: z.string().optional()
90
+ }).optional(),
91
+ origin: z.enum(["measured"]).default("measured")
92
+ });
93
+ async function saveBenchmark(record) {
94
+ await ensureStorage();
95
+ const safeName = record.model.id.replace(/[^a-zA-Z0-9._-]+/g, "-");
96
+ const file = path2.join(benchmarksDir(), `${record.timestamp.replace(/[:.]/g, "-")}-${safeName}.json`);
97
+ await writeFile(file, `${JSON.stringify(record, null, 2)}
98
+ `, "utf8");
99
+ return file;
100
+ }
101
+ async function listBenchmarks() {
102
+ await ensureStorage();
103
+ let files = [];
104
+ try {
105
+ files = (await readdir(benchmarksDir())).filter((f) => f.endsWith(".json"));
106
+ } catch {
107
+ return [];
108
+ }
109
+ const records = [];
110
+ for (const file of files.sort()) {
111
+ try {
112
+ const raw = await readFile(path2.join(benchmarksDir(), file), "utf8");
113
+ records.push(benchmarkRecordSchema.parse(JSON.parse(raw)));
114
+ } catch {
115
+ continue;
116
+ }
117
+ }
118
+ return records;
119
+ }
120
+
121
+ // src/shared/bytes.ts
122
+ var GiB = 1024 ** 3;
123
+ var MiB = 1024 ** 2;
124
+ function bytesToGiB(bytes) {
125
+ return bytes / GiB;
126
+ }
127
+ function parseSizeToBytes(input) {
128
+ const match = input.trim().match(/^([\d.]+)\s*(B|KB|MB|GB|TB|KiB|MiB|GiB|TiB)$/i);
129
+ if (!match) return void 0;
130
+ const value = Number(match[1]);
131
+ const unit = match[2].toLowerCase();
132
+ const map = {
133
+ b: 1,
134
+ kb: 1e3,
135
+ mb: 1e3 ** 2,
136
+ gb: 1e3 ** 3,
137
+ tb: 1e3 ** 4,
138
+ kib: 1024,
139
+ mib: 1024 ** 2,
140
+ gib: 1024 ** 3,
141
+ tib: 1024 ** 4
142
+ };
143
+ const factor = map[unit];
144
+ if (!factor || Number.isNaN(value)) return void 0;
145
+ return value * factor;
146
+ }
147
+ function formatBytes(bytes, digits = 1) {
148
+ if (!Number.isFinite(bytes)) return "N/A";
149
+ const abs = Math.abs(bytes);
150
+ if (abs >= GiB) return `${(bytes / GiB).toFixed(digits)} GB`;
151
+ if (abs >= MiB) return `${(bytes / MiB).toFixed(digits)} MB`;
152
+ if (abs >= 1024) return `${(bytes / 1024).toFixed(digits)} KB`;
153
+ return `${Math.round(bytes)} B`;
154
+ }
155
+
156
+ // src/hardware/detect.ts
157
+ import os5 from "node:os";
158
+
159
+ // src/platform/macos/hardware.ts
160
+ import os2 from "node:os";
161
+ function parseAppleSilicon(chip) {
162
+ const match = chip.match(/Apple\s+(M\d+)(?:\s+(Pro|Max|Ultra))?/i);
163
+ if (!match) return void 0;
164
+ const generation = match[1].toUpperCase().replace(/^M/, "M");
165
+ const variant = match[2] ?? "base";
166
+ return { generation, variant };
167
+ }
168
+ function parseVmStat(output, pageSize) {
169
+ const num = (label) => {
170
+ const row = output.match(new RegExp(`${label}:\\s+([\\d.]+)`));
171
+ if (!row) return void 0;
172
+ return Number(row[1]);
173
+ };
174
+ const free = num("Pages free") ?? 0;
175
+ const speculative = num("Pages speculative") ?? 0;
176
+ const inactive = num("Pages inactive") ?? 0;
177
+ const purgeable = num("Pages purgeable") ?? 0;
178
+ const availableBytes = (free + speculative + inactive + purgeable) * pageSize;
179
+ return { availableBytes };
180
+ }
181
+ function parseMemoryPressure(output) {
182
+ const match = output.match(/System-wide memory free percentage:\s+(\d+)/i);
183
+ if (match) {
184
+ const free = Number(match[1]);
185
+ if (free >= 50) return "Nominal";
186
+ if (free >= 25) return "Warn";
187
+ return "Critical";
188
+ }
189
+ if (/warn/i.test(output)) return "Warn";
190
+ if (/critical/i.test(output)) return "Critical";
191
+ if (/nominal/i.test(output)) return "Nominal";
192
+ return void 0;
193
+ }
194
+ function parseMacosSwapUsage(output) {
195
+ const match = output.match(/used\s*=\s*([\d.]+)\s*([KMGT])?/i);
196
+ if (!match) return void 0;
197
+ const value = Number(match[1]);
198
+ if (Number.isNaN(value)) return void 0;
199
+ const unit = (match[2] ?? "M").toUpperCase();
200
+ const factor = { K: 1024, M: 1024 ** 2, G: 1024 ** 3, T: 1024 ** 4 }[unit] ?? 1024 ** 2;
201
+ return value * factor;
202
+ }
203
+ async function detectMacosHardware() {
204
+ const [brand, physical, logical, memsize, pagesize, perf, eff, hwModel, swVers, profiler, vmstat, pressure, swap] = await Promise.all([
205
+ runCommand("sysctl", ["-n", "machdep.cpu.brand_string"], { timeout: 5e3 }),
206
+ runCommand("sysctl", ["-n", "hw.physicalcpu"], { timeout: 5e3 }),
207
+ runCommand("sysctl", ["-n", "hw.logicalcpu"], { timeout: 5e3 }),
208
+ runCommand("sysctl", ["-n", "hw.memsize"], { timeout: 5e3 }),
209
+ runCommand("sysctl", ["-n", "hw.pagesize"], { timeout: 5e3 }),
210
+ runCommand("sysctl", ["-n", "hw.perflevel0.physicalcpu"], { timeout: 5e3 }),
211
+ runCommand("sysctl", ["-n", "hw.perflevel1.physicalcpu"], { timeout: 5e3 }),
212
+ runCommand("sysctl", ["-n", "hw.model"], { timeout: 5e3 }),
213
+ runCommand("sw_vers", ["-productVersion"], { timeout: 5e3 }),
214
+ runCommand("system_profiler", ["SPHardwareDataType"], { timeout: 15e3 }),
215
+ runCommand("vm_stat", [], { timeout: 5e3 }),
216
+ runCommand("memory_pressure", [], { timeout: 5e3 }),
217
+ runCommand("sysctl", ["-n", "vm.swapusage"], { timeout: 5e3 })
218
+ ]);
219
+ const profilerText = profiler.stdout;
220
+ const chip = profilerText.match(/Chip:\s+(.+)/)?.[1]?.trim() ?? brand.stdout.trim() ?? os2.cpus()[0]?.model;
221
+ const modelName = profilerText.match(/Model Name:\s+(.+)/)?.[1]?.trim();
222
+ const memoryLine = profilerText.match(/Memory:\s+(.+)/)?.[1]?.trim();
223
+ const totalFromProfiler = memoryLine ? parseSizeToBytes(memoryLine.replace("GB", "GiB")) : void 0;
224
+ const totalBytes = totalFromProfiler ?? Number(memsize.stdout.trim()) ?? os2.totalmem();
225
+ const pageSize = Number(pagesize.stdout.trim()) || 16384;
226
+ const vm = parseVmStat(vmstat.stdout, pageSize);
227
+ const appleSilicon = chip ? parseAppleSilicon(chip) : void 0;
228
+ return {
229
+ os: "macos",
230
+ osVersion: swVers.stdout.trim() || void 0,
231
+ arch: os2.arch(),
232
+ machineModel: modelName ?? hwModel.stdout.trim() ?? void 0,
233
+ cpu: {
234
+ name: chip,
235
+ physicalCores: Number(physical.stdout.trim()) || os2.cpus().length,
236
+ logicalCores: Number(logical.stdout.trim()) || os2.cpus().length,
237
+ performanceCores: Number(perf.stdout.trim()) || void 0,
238
+ efficiencyCores: Number(eff.stdout.trim()) || void 0,
239
+ appleSilicon
240
+ },
241
+ gpu: {
242
+ name: chip,
243
+ metal: Boolean(appleSilicon) || os2.arch() === "arm64"
244
+ },
245
+ memory: {
246
+ totalBytes,
247
+ availableBytes: vm.availableBytes ?? os2.freemem(),
248
+ unified: Boolean(appleSilicon) || os2.arch() === "arm64",
249
+ swapUsedBytes: parseMacosSwapUsage(swap.stdout),
250
+ pressure: parseMemoryPressure(pressure.stdout + pressure.stderr)
251
+ }
252
+ };
253
+ }
254
+ function parseMacosMemoryPressure(output) {
255
+ return parseMemoryPressure(output);
256
+ }
257
+ function parseMacosVmStat(output, pageSize) {
258
+ return parseVmStat(output, pageSize);
259
+ }
260
+
261
+ // src/platform/linux/hardware.ts
262
+ import os3 from "node:os";
263
+ async function detectLinuxHardware() {
264
+ return {
265
+ os: "linux",
266
+ arch: os3.arch(),
267
+ cpu: {
268
+ name: os3.cpus()[0]?.model,
269
+ logicalCores: os3.cpus().length
270
+ },
271
+ gpu: {},
272
+ memory: {
273
+ totalBytes: os3.totalmem(),
274
+ availableBytes: os3.freemem(),
275
+ unified: false
276
+ }
277
+ };
278
+ }
279
+
280
+ // src/platform/windows/hardware.ts
281
+ import os4 from "node:os";
282
+ async function detectWindowsHardware() {
283
+ return {
284
+ os: "windows",
285
+ arch: os4.arch(),
286
+ cpu: {
287
+ name: os4.cpus()[0]?.model,
288
+ logicalCores: os4.cpus().length
289
+ },
290
+ gpu: {},
291
+ memory: {
292
+ totalBytes: os4.totalmem(),
293
+ availableBytes: os4.freemem(),
294
+ unified: false
295
+ }
296
+ };
297
+ }
298
+
299
+ // src/hardware/detect.ts
300
+ function detectOs() {
301
+ switch (process.platform) {
302
+ case "darwin":
303
+ return "macos";
304
+ case "linux":
305
+ return "linux";
306
+ case "win32":
307
+ return "windows";
308
+ default:
309
+ return "unknown";
310
+ }
311
+ }
312
+ async function detectHardware() {
313
+ const osName = detectOs();
314
+ if (osName === "macos") return detectMacosHardware();
315
+ if (osName === "linux") return detectLinuxHardware();
316
+ if (osName === "windows") return detectWindowsHardware();
317
+ return {
318
+ os: "unknown",
319
+ arch: os5.arch(),
320
+ cpu: { name: os5.cpus()[0]?.model, logicalCores: os5.cpus().length },
321
+ gpu: {},
322
+ memory: {
323
+ totalBytes: os5.totalmem(),
324
+ availableBytes: os5.freemem(),
325
+ unified: false
326
+ }
327
+ };
328
+ }
329
+
330
+ // src/telemetry/macos.ts
331
+ import os7 from "node:os";
332
+
333
+ // src/telemetry/fallback.ts
334
+ import os6 from "node:os";
335
+ var previous = os6.cpus();
336
+ function cpuPercent() {
337
+ const current = os6.cpus();
338
+ let idle = 0;
339
+ let total = 0;
340
+ for (let i = 0; i < current.length; i += 1) {
341
+ const c = current[i];
342
+ const p = previous[i] ?? c;
343
+ const idleDelta = c.times.idle - p.times.idle;
344
+ const totalDelta = c.times.user + c.times.nice + c.times.sys + c.times.idle + c.times.irq - (p.times.user + p.times.nice + p.times.sys + p.times.idle + p.times.irq);
345
+ idle += idleDelta;
346
+ total += totalDelta;
347
+ }
348
+ previous = current;
349
+ if (total <= 0) return 0;
350
+ return Number((100 * (1 - idle / total)).toFixed(1));
351
+ }
352
+ var nodeTelemetry = {
353
+ async start() {
354
+ previous = os6.cpus();
355
+ },
356
+ async sample() {
357
+ const total = os6.totalmem();
358
+ const free = os6.freemem();
359
+ const sample = {
360
+ timestamp: Date.now(),
361
+ cpu: { utilizationPercent: cpuPercent() },
362
+ memory: {
363
+ usedBytes: total - free,
364
+ availableBytes: free,
365
+ swapUsedBytes: 0
366
+ }
367
+ };
368
+ return sample;
369
+ },
370
+ async stop() {
371
+ return;
372
+ }
373
+ };
374
+
375
+ // src/telemetry/macos.ts
376
+ async function sampleProcess(pid) {
377
+ if (!pid) return void 0;
378
+ const result = await runCommand("ps", ["-o", "pid=,%cpu=,rss=", "-p", String(pid)], { timeout: 3e3 });
379
+ const parts = result.stdout.trim().split(/\s+/);
380
+ if (parts.length < 3) return { pid };
381
+ return {
382
+ pid,
383
+ cpuPercent: Number(parts[1]),
384
+ rssBytes: Number(parts[2]) * 1024
385
+ };
386
+ }
387
+ function createMacosTelemetry(pid) {
388
+ return {
389
+ async start() {
390
+ await nodeTelemetry.start();
391
+ },
392
+ async sample() {
393
+ const base = await nodeTelemetry.sample();
394
+ const [vm, pressure, pagesize, swap] = await Promise.all([
395
+ runCommand("vm_stat", [], { timeout: 3e3 }),
396
+ runCommand("memory_pressure", [], { timeout: 3e3 }),
397
+ runCommand("sysctl", ["-n", "hw.pagesize"], { timeout: 3e3 }),
398
+ runCommand("sysctl", ["-n", "vm.swapusage"], { timeout: 3e3 })
399
+ ]);
400
+ const pageSize = Number(pagesize.stdout.trim()) || 16384;
401
+ const parsed = parseMacosVmStat(vm.stdout, pageSize);
402
+ const total = os7.totalmem();
403
+ const available = parsed.availableBytes ?? os7.freemem();
404
+ const sample = {
405
+ ...base,
406
+ memory: {
407
+ usedBytes: Math.max(0, total - available),
408
+ availableBytes: available,
409
+ swapUsedBytes: parseMacosSwapUsage(swap.stdout) ?? 0
410
+ },
411
+ thermal: {
412
+ pressure: parseMacosMemoryPressure(pressure.stdout + pressure.stderr)
413
+ },
414
+ process: await sampleProcess(pid),
415
+ gpu: {
416
+ utilizationPercent: void 0,
417
+ powerWatts: void 0
418
+ }
419
+ };
420
+ return sample;
421
+ },
422
+ async stop() {
423
+ await nodeTelemetry.stop();
424
+ }
425
+ };
426
+ }
427
+
428
+ // src/telemetry/collector.ts
429
+ function createTelemetry(pid) {
430
+ if (detectOs() === "macos") return createMacosTelemetry(pid);
431
+ return nodeTelemetry;
432
+ }
433
+ var TelemetryCollector = class {
434
+ provider;
435
+ timer;
436
+ samples = [];
437
+ running = false;
438
+ constructor(pid) {
439
+ this.provider = createTelemetry(pid);
440
+ }
441
+ async start(intervalMs = 750) {
442
+ await this.provider.start();
443
+ this.running = true;
444
+ const tick = async () => {
445
+ if (!this.running) return;
446
+ try {
447
+ const sample = await this.provider.sample();
448
+ this.samples.push(sample);
449
+ this.onSample?.(sample);
450
+ } catch {
451
+ }
452
+ };
453
+ await tick();
454
+ this.timer = setInterval(() => {
455
+ void tick();
456
+ }, intervalMs);
457
+ }
458
+ onSample;
459
+ latest() {
460
+ return this.samples.at(-1);
461
+ }
462
+ history() {
463
+ return this.samples;
464
+ }
465
+ async stop() {
466
+ this.running = false;
467
+ if (this.timer) clearInterval(this.timer);
468
+ await this.provider.stop();
469
+ return summarize(this.samples);
470
+ }
471
+ };
472
+ function summarize(samples) {
473
+ if (samples.length === 0) return { samples: 0 };
474
+ const avg = (values) => {
475
+ const nums = values.filter((v) => v !== void 0 && !Number.isNaN(v));
476
+ if (!nums.length) return void 0;
477
+ return Number((nums.reduce((a, b) => a + b, 0) / nums.length).toFixed(1));
478
+ };
479
+ const peak = (values) => {
480
+ const nums = values.filter((v) => v !== void 0 && !Number.isNaN(v));
481
+ if (!nums.length) return void 0;
482
+ return Number(Math.max(...nums).toFixed(2));
483
+ };
484
+ const GiB2 = 1024 ** 3;
485
+ return {
486
+ samples: samples.length,
487
+ avgCpuPercent: avg(samples.map((s) => s.cpu?.utilizationPercent)),
488
+ avgGpuPercent: avg(samples.map((s) => s.gpu?.utilizationPercent)),
489
+ peakGpuPercent: peak(samples.map((s) => s.gpu?.utilizationPercent)),
490
+ peakMemoryGb: peak(samples.map((s) => s.memory.usedBytes / GiB2)),
491
+ peakSwapGb: peak(samples.map((s) => s.memory.swapUsedBytes / GiB2)),
492
+ avgPackagePowerWatts: avg(samples.map((s) => s.packagePowerWatts ?? s.cpu?.powerWatts)),
493
+ peakPackagePowerWatts: peak(samples.map((s) => s.packagePowerWatts ?? s.cpu?.powerWatts)),
494
+ avgTemperatureC: avg(samples.map((s) => s.thermal?.temperatureC)),
495
+ peakTemperatureC: peak(samples.map((s) => s.thermal?.temperatureC)),
496
+ peakProcessRssGb: peak(samples.map((s) => (s.process?.rssBytes ?? 0) / GiB2))
497
+ };
498
+ }
499
+
500
+ // src/benchmark/parse-llama-bench.ts
501
+ import { z as z2 } from "zod";
502
+ var jsonRowSchema = z2.object({
503
+ n_prompt: z2.number().optional(),
504
+ n_gen: z2.number().optional(),
505
+ avg_ts: z2.number().optional(),
506
+ model_type: z2.string().optional(),
507
+ model_size: z2.number().optional(),
508
+ backends: z2.string().optional(),
509
+ test: z2.string().optional()
510
+ }).passthrough();
511
+ function parseLlamaBenchJson(text) {
512
+ const parsed = JSON.parse(text);
513
+ const rows = Array.isArray(parsed) ? parsed.map((row) => jsonRowSchema.parse(row)) : [jsonRowSchema.parse(parsed)];
514
+ return metricsFromRows(rows, parsed);
515
+ }
516
+ function metricsFromRows(rows, raw) {
517
+ let promptTokensPerSecond;
518
+ let generationTokensPerSecond;
519
+ for (const row of rows) {
520
+ const test = row.test ?? "";
521
+ const avg = row.avg_ts;
522
+ if (avg === void 0) continue;
523
+ if (/^pp/i.test(test) || (row.n_prompt ?? 0) > 0 && (row.n_gen ?? 0) === 0) {
524
+ promptTokensPerSecond = avg;
525
+ } else if (/^tg/i.test(test) || (row.n_gen ?? 0) > 0 && (row.n_prompt ?? 0) === 0) {
526
+ generationTokensPerSecond = avg;
527
+ }
528
+ }
529
+ const first = rows[0];
530
+ return {
531
+ promptTokensPerSecond,
532
+ generationTokensPerSecond,
533
+ modelType: first?.model_type,
534
+ modelSizeBytes: first?.model_size,
535
+ backend: first?.backends,
536
+ raw
537
+ };
538
+ }
539
+ function parseLlamaBenchMarkdown(text) {
540
+ const rows = [];
541
+ for (const line of text.split(/\r?\n/)) {
542
+ if (!line.includes("|")) continue;
543
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
544
+ if (cells.length < 6) continue;
545
+ if (/^model$/i.test(cells[0] ?? "") || /^-+$/.test(cells[0] ?? "")) continue;
546
+ const test = cells[cells.length - 2] ?? "";
547
+ const tsCell = cells[cells.length - 1] ?? "";
548
+ const tsMatch = tsCell.match(/([\d.]+)/);
549
+ if (!tsMatch) continue;
550
+ rows.push({ test, ts: Number(tsMatch[1]), modelType: cells[0] });
551
+ }
552
+ let promptTokensPerSecond;
553
+ let generationTokensPerSecond;
554
+ for (const row of rows) {
555
+ if (/^pp/i.test(row.test)) promptTokensPerSecond = row.ts;
556
+ if (/^tg/i.test(row.test)) generationTokensPerSecond = row.ts;
557
+ }
558
+ return {
559
+ promptTokensPerSecond,
560
+ generationTokensPerSecond,
561
+ modelType: rows[0]?.modelType,
562
+ raw: { format: "markdown", rows }
563
+ };
564
+ }
565
+ function parseLlamaBenchOutput(text) {
566
+ const trimmed = text.trim();
567
+ const jsonStart = trimmed.indexOf("[");
568
+ const jsonObjStart = trimmed.indexOf("{");
569
+ const start = jsonStart >= 0 && (jsonObjStart < 0 || jsonStart < jsonObjStart) ? jsonStart : jsonObjStart;
570
+ if (start >= 0) {
571
+ const slice = trimmed.slice(start);
572
+ try {
573
+ return parseLlamaBenchJson(slice);
574
+ } catch {
575
+ }
576
+ }
577
+ return parseLlamaBenchMarkdown(text);
578
+ }
579
+ function tokensPerSecondPerWatt(tokensPerSecond, watts) {
580
+ if (!tokensPerSecond || !watts || watts <= 0) return void 0;
581
+ return Number((tokensPerSecond / watts).toFixed(2));
582
+ }
583
+
584
+ // src/benchmark/presets.ts
585
+ var PRESETS = {
586
+ quick: { name: "quick", promptTokens: 512, generationTokens: 128, repetitions: 3 },
587
+ standard: { name: "standard", promptTokens: 2048, generationTokens: 256, repetitions: 5 },
588
+ stress: { name: "stress", promptTokens: 4096, generationTokens: 512, repetitions: 10 }
589
+ };
590
+ function resolvePreset(name) {
591
+ if (name === "standard" || name === "stress" || name === "quick") return PRESETS[name];
592
+ return PRESETS.quick;
593
+ }
594
+
595
+ // src/compatibility/grades.ts
596
+ function gradeFromThresholds(score, thresholds) {
597
+ if (score >= thresholds.S) return "S";
598
+ if (score >= thresholds.A) return "A";
599
+ if (score >= thresholds.B) return "B";
600
+ if (score >= thresholds.C) return "C";
601
+ if (score >= thresholds.D) return "D";
602
+ return "F";
603
+ }
604
+ function gradeToScore(grade) {
605
+ return { S: 100, A: 85, B: 70, C: 55, D: 40, F: 20 }[grade];
606
+ }
607
+
608
+ // src/benchmark/score.ts
609
+ function computeLocalMeterScore(generationTokensPerSecond, resources, totalMemoryGb) {
610
+ const dimensions = [
611
+ {
612
+ key: "speed",
613
+ measured: generationTokensPerSecond !== void 0,
614
+ grade: generationTokensPerSecond === void 0 ? void 0 : gradeFromThresholds(generationTokensPerSecond, { S: 60, A: 40, B: 25, C: 12, D: 5, F: 0 }),
615
+ weight: 35
616
+ },
617
+ {
618
+ key: "memory",
619
+ measured: resources.peakMemoryGb !== void 0 && totalMemoryGb > 0,
620
+ grade: resources.peakMemoryGb === void 0 || totalMemoryGb <= 0 ? void 0 : gradeFromThresholds(100 - resources.peakMemoryGb / totalMemoryGb * 100, {
621
+ S: 50,
622
+ A: 30,
623
+ B: 15,
624
+ C: 5,
625
+ D: 0,
626
+ F: -100
627
+ }),
628
+ weight: 25
629
+ },
630
+ {
631
+ key: "energy",
632
+ measured: resources.avgPackagePowerWatts !== void 0 && generationTokensPerSecond !== void 0,
633
+ grade: energyGrade(generationTokensPerSecond, resources.avgPackagePowerWatts),
634
+ weight: 20
635
+ },
636
+ {
637
+ key: "thermal",
638
+ measured: resources.peakTemperatureC !== void 0,
639
+ grade: resources.peakTemperatureC === void 0 ? void 0 : gradeFromThresholds(100 - resources.peakTemperatureC, { S: 30, A: 20, B: 10, C: 0, D: -10, F: -100 }),
640
+ weight: 10
641
+ },
642
+ {
643
+ key: "swap",
644
+ measured: resources.peakSwapGb !== void 0,
645
+ grade: resources.peakSwapGb === void 0 ? void 0 : gradeFromThresholds(4 - resources.peakSwapGb, { S: 4, A: 3.75, B: 3, C: 2, D: 0, F: -100 }),
646
+ weight: 10
647
+ }
648
+ ];
649
+ const measured = dimensions.filter((d) => d.measured && d.grade);
650
+ const weightSum = measured.reduce((sum, d) => sum + d.weight, 0);
651
+ if (!measured.length || weightSum === 0) return { dimensions };
652
+ const total = measured.reduce((sum, d) => sum + gradeToScore(d.grade) * (d.weight / weightSum), 0);
653
+ return { total: Math.round(total), dimensions };
654
+ }
655
+ function energyGrade(tps, watts) {
656
+ if (!tps || !watts || watts <= 0) return void 0;
657
+ const efficiency = tps / watts;
658
+ return gradeFromThresholds(efficiency, { S: 3, A: 2, B: 1.2, C: 0.6, D: 0.3, F: 0 });
659
+ }
660
+
661
+ // src/benchmark/assess.ts
662
+ function assessRun(input) {
663
+ const lines = [];
664
+ const { generationTokensPerSecond, resources, catalog } = input;
665
+ if (generationTokensPerSecond !== void 0 && generationTokensPerSecond >= 25) {
666
+ lines.push("Runs comfortably on this machine");
667
+ } else if (generationTokensPerSecond !== void 0 && generationTokensPerSecond >= 10) {
668
+ lines.push("Runs, but generation is constrained");
669
+ } else if (generationTokensPerSecond !== void 0) {
670
+ lines.push("Generation is too slow for interactive use");
671
+ }
672
+ if ((resources.peakSwapGb ?? 0) < 0.05) lines.push("No meaningful swap detected");
673
+ else lines.push("Swap activity detected \u2014 memory is tight");
674
+ if (resources.avgGpuPercent !== void 0 && resources.avgGpuPercent >= 80) {
675
+ lines.push("GPU remains highly utilized (system-wide)");
676
+ }
677
+ if (generationTokensPerSecond !== void 0 && generationTokensPerSecond >= 35) {
678
+ lines.push("Good sustained generation performance");
679
+ }
680
+ if (resources.avgPackagePowerWatts !== void 0 && generationTokensPerSecond) {
681
+ const eff = generationTokensPerSecond / resources.avgPackagePowerWatts;
682
+ if (eff >= 2) lines.push("Excellent energy efficiency");
683
+ }
684
+ if (catalog?.useCase?.length) {
685
+ lines.push(`This model is suitable for: ${catalog.useCase.join(", ")}`);
686
+ }
687
+ return lines;
688
+ }
689
+
690
+ // src/benchmark/engine.ts
691
+ async function runBenchmark(input) {
692
+ const bench = await commandExists("llama-bench");
693
+ if (!bench) {
694
+ throw new Error("llama-bench not found. Install llama.cpp (e.g. brew install llama.cpp).");
695
+ }
696
+ const artifact = input.model.artifactPath;
697
+ if (!artifact) {
698
+ throw new Error(`No GGUF path for ${input.model.id}. Cannot run llama-bench.`);
699
+ }
700
+ const preset = resolvePreset(input.preset);
701
+ const started = Date.now();
702
+ const args = [
703
+ "-m",
704
+ artifact,
705
+ "-p",
706
+ String(preset.promptTokens),
707
+ "-n",
708
+ String(preset.generationTokens),
709
+ "-r",
710
+ String(preset.repetitions),
711
+ "-o",
712
+ "json"
713
+ ];
714
+ const subprocess = spawnTracked(bench, args, { timeout: 30 * 6e4 });
715
+ const collector = new TelemetryCollector(subprocess.pid);
716
+ collector.onSample = (sample) => {
717
+ input.onProgress?.({ sample, elapsedSeconds: (Date.now() - started) / 1e3, status: "running" });
718
+ };
719
+ await collector.start(750);
720
+ const abort = () => {
721
+ subprocess.kill("SIGTERM");
722
+ };
723
+ input.signal?.addEventListener("abort", abort, { once: true });
724
+ const result = await subprocess;
725
+ input.signal?.removeEventListener("abort", abort);
726
+ const resources = await collector.stop();
727
+ if (input.signal?.aborted) {
728
+ throw new Error("Benchmark cancelled");
729
+ }
730
+ const stdout = toText(result.stdout);
731
+ const stderr = toText(result.stderr);
732
+ if (result.exitCode !== 0) {
733
+ throw new Error(stderr || `llama-bench exited with ${result.exitCode}`);
734
+ }
735
+ const metrics = parseLlamaBenchOutput(stdout || stderr);
736
+ const score = computeLocalMeterScore(
737
+ metrics.generationTokensPerSecond,
738
+ resources,
739
+ bytesToGiB(input.hardware.memory.totalBytes)
740
+ );
741
+ const efficiency = tokensPerSecondPerWatt(
742
+ metrics.generationTokensPerSecond,
743
+ resources.avgPackagePowerWatts
744
+ );
745
+ const record = {
746
+ schemaVersion: 1,
747
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
748
+ machine: {
749
+ os: input.hardware.os,
750
+ arch: input.hardware.arch,
751
+ model: input.hardware.machineModel,
752
+ cpu: input.hardware.cpu.name,
753
+ memoryGb: Number(bytesToGiB(input.hardware.memory.totalBytes).toFixed(1)),
754
+ unified: input.hardware.memory.unified
755
+ },
756
+ model: {
757
+ id: input.model.id,
758
+ name: input.model.name,
759
+ source: input.model.source,
760
+ path: input.model.artifactPath
761
+ },
762
+ runtime: { name: "llama-bench", command: [bench, ...args] },
763
+ benchmark: {
764
+ promptTokens: preset.promptTokens,
765
+ generationTokens: preset.generationTokens,
766
+ repetitions: preset.repetitions,
767
+ promptTokensPerSecond: metrics.promptTokensPerSecond,
768
+ generationTokensPerSecond: metrics.generationTokensPerSecond,
769
+ elapsedSeconds: (Date.now() - started) / 1e3
770
+ },
771
+ resources: {
772
+ ...resourceFields(resources),
773
+ tokensPerSecondPerWatt: efficiency
774
+ },
775
+ score: {
776
+ total: score.total ?? 0,
777
+ speed: score.dimensions.find((d) => d.key === "speed")?.grade,
778
+ memory: score.dimensions.find((d) => d.key === "memory")?.grade,
779
+ energy: score.dimensions.find((d) => d.key === "energy")?.grade,
780
+ thermal: score.dimensions.find((d) => d.key === "thermal")?.grade,
781
+ swap: score.dimensions.find((d) => d.key === "swap")?.grade
782
+ },
783
+ origin: "measured"
784
+ };
785
+ const path3 = await saveBenchmark(record);
786
+ input.onProgress?.({
787
+ elapsedSeconds: record.benchmark.elapsedSeconds ?? 0,
788
+ status: "completed"
789
+ });
790
+ return {
791
+ record,
792
+ assessment: assessRun({
793
+ generationTokensPerSecond: metrics.generationTokensPerSecond,
794
+ resources,
795
+ catalog: input.catalog
796
+ }),
797
+ path: path3,
798
+ stdout
799
+ };
800
+ }
801
+ function resourceFields(resources) {
802
+ return {
803
+ peakMemoryGb: resources.peakMemoryGb,
804
+ peakSwapGb: resources.peakSwapGb,
805
+ avgGpuPercent: resources.avgGpuPercent,
806
+ avgCpuPercent: resources.avgCpuPercent,
807
+ peakGpuPercent: resources.peakGpuPercent,
808
+ avgPackagePowerWatts: resources.avgPackagePowerWatts,
809
+ peakPackagePowerWatts: resources.peakPackagePowerWatts,
810
+ avgTemperatureC: resources.avgTemperatureC,
811
+ peakTemperatureC: resources.peakTemperatureC,
812
+ peakProcessRssGb: resources.peakProcessRssGb
813
+ };
814
+ }
815
+
816
+ // src/compatibility/local.ts
817
+ var GB_PER_BILLION_Q4 = 0.65;
818
+ function estimateModelRamGb(model, quant = "Q4_K_M") {
819
+ const params = model.paramsBillions ?? 8;
820
+ const quantFactor = {
821
+ Q2_K: 0.4,
822
+ Q3_K_M: 0.5,
823
+ Q4_K_M: 0.65,
824
+ Q5_K_M: 0.8,
825
+ Q6_K: 0.9,
826
+ Q8_0: 1.1,
827
+ F16: 2
828
+ };
829
+ return params * (quantFactor[quant] ?? GB_PER_BILLION_Q4);
830
+ }
831
+ function localCompatibility(hardware, model) {
832
+ const ramGb = bytesToGiB(hardware.memory.totalBytes);
833
+ const required = estimateModelRamGb(model);
834
+ const ratio = required / ramGb;
835
+ let grade;
836
+ if (ratio <= 0.35) grade = "S";
837
+ else if (ratio <= 0.5) grade = "A";
838
+ else if (ratio <= 0.7) grade = "B";
839
+ else if (ratio <= 0.9) grade = "C";
840
+ else if (ratio <= 1.15) grade = "D";
841
+ else grade = "F";
842
+ const bandwidthGuess = hardware.cpu.appleSilicon ? 100 : 40;
843
+ const estimatedTokensPerSecond = grade === "F" ? void 0 : Math.max(4, bandwidthGuess / Math.max(required, 1));
844
+ return {
845
+ modelId: model.id,
846
+ source: "estimated",
847
+ grade,
848
+ estimatedRamGb: Number(required.toFixed(2)),
849
+ estimatedTokensPerSecond: estimatedTokensPerSecond ? Number(estimatedTokensPerSecond.toFixed(1)) : void 0,
850
+ notes: [`Local estimate from ${required.toFixed(1)} GB Q4 vs ${ramGb.toFixed(1)} GB ${hardware.memory.unified ? "unified memory" : "RAM"}`]
851
+ };
852
+ }
853
+
854
+ // src/models/match.ts
855
+ function normalizeModelId(id) {
856
+ return id.toLowerCase().replace(/[:/]/g, "-").replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
857
+ }
858
+ function idsLikelyMatch(a, b) {
859
+ const na2 = normalizeModelId(a);
860
+ const nb = normalizeModelId(b);
861
+ return na2 === nb || na2.includes(nb) || nb.includes(na2);
862
+ }
863
+
864
+ // src/tasks/catalog.ts
865
+ var TASK_CATALOG = [
866
+ {
867
+ id: "code-review",
868
+ kind: "code",
869
+ useCases: ["code"],
870
+ titleKey: "taskCodeReviewTitle",
871
+ promptKey: "taskCodeReviewPrompt",
872
+ harnessId: "H-CODE-REVIEW"
873
+ },
874
+ {
875
+ id: "code-tests",
876
+ kind: "code",
877
+ useCases: ["code"],
878
+ titleKey: "taskCodeTestsTitle",
879
+ promptKey: "taskCodeTestsPrompt",
880
+ harnessId: "H-CODE-TESTS"
881
+ },
882
+ {
883
+ id: "chat-plan",
884
+ kind: "chat",
885
+ useCases: ["chat"],
886
+ titleKey: "taskChatPlanTitle",
887
+ promptKey: "taskChatPlanPrompt",
888
+ harnessId: "H-CHAT-PLAN"
889
+ },
890
+ {
891
+ id: "chat-agent",
892
+ kind: "chat",
893
+ useCases: ["chat", "code"],
894
+ titleKey: "taskChatAgentTitle",
895
+ promptKey: "taskChatAgentPrompt",
896
+ harnessId: "H-CHAT-AGENT"
897
+ },
898
+ {
899
+ id: "image-caption",
900
+ kind: "image",
901
+ useCases: ["image", "vision"],
902
+ titleKey: "taskImageCaptionTitle",
903
+ promptKey: "taskImageCaptionPrompt",
904
+ harnessId: "H-IMAGE-CAPTION"
905
+ },
906
+ {
907
+ id: "image-edit-brief",
908
+ kind: "image",
909
+ useCases: ["image", "vision"],
910
+ titleKey: "taskImageBriefTitle",
911
+ promptKey: "taskImageBriefPrompt",
912
+ harnessId: "H-IMAGE-BRIEF"
913
+ },
914
+ {
915
+ id: "video-storyboard",
916
+ kind: "video",
917
+ useCases: ["video"],
918
+ titleKey: "taskVideoBoardTitle",
919
+ promptKey: "taskVideoBoardPrompt",
920
+ harnessId: "H-VIDEO-BOARD"
921
+ },
922
+ {
923
+ id: "video-shotlist",
924
+ kind: "video",
925
+ useCases: ["video"],
926
+ titleKey: "taskVideoShotTitle",
927
+ promptKey: "taskVideoShotPrompt",
928
+ harnessId: "H-VIDEO-SHOTS"
929
+ },
930
+ {
931
+ id: "transcribe-clean",
932
+ kind: "transcription",
933
+ useCases: ["chat", "multilingual"],
934
+ titleKey: "taskTranscribeCleanTitle",
935
+ promptKey: "taskTranscribeCleanPrompt",
936
+ harnessId: "H-ASR-CLEAN"
937
+ },
938
+ {
939
+ id: "transcribe-actions",
940
+ kind: "transcription",
941
+ useCases: ["chat", "code"],
942
+ titleKey: "taskTranscribeActionsTitle",
943
+ promptKey: "taskTranscribeActionsPrompt",
944
+ harnessId: "H-ASR-ACTIONS"
945
+ }
946
+ ];
947
+
948
+ // src/tasks/types.ts
949
+ var WORK_KINDS = ["code", "video", "image", "transcription", "chat"];
950
+
951
+ // src/tasks/plan.ts
952
+ function parseKinds(raw) {
953
+ if (!raw) return [...WORK_KINDS];
954
+ const parts = raw.split(",").map((p) => p.trim().toLowerCase());
955
+ const kinds = WORK_KINDS.filter((k) => parts.includes(k));
956
+ return kinds.length ? kinds : [...WORK_KINDS];
957
+ }
958
+ function parseAnswers(input) {
959
+ return {
960
+ kinds: parseKinds(input.for),
961
+ scope: input.scope === "all" ? "all" : "installed",
962
+ priority: input.priority === "quality" || input.priority === "speed" ? input.priority : "balanced"
963
+ };
964
+ }
965
+ function kindMatches(kind, useCases) {
966
+ if (kind === "code") return useCases.some((u) => u.includes("code"));
967
+ if (kind === "image") return useCases.some((u) => u.includes("image") || u.includes("vision"));
968
+ if (kind === "video") return useCases.some((u) => u.includes("video"));
969
+ if (kind === "transcription") return useCases.some((u) => u.includes("chat") || u.includes("multilingual"));
970
+ return useCases.some((u) => u.includes("chat") || u.includes("reasoning") || u.includes("code"));
971
+ }
972
+ function planTasks(session, answers) {
973
+ const wanted = new Set(answers.kinds);
974
+ const defs = TASK_CATALOG.filter((task) => wanted.has(task.kind));
975
+ const plans = [];
976
+ const installed = session.rows.map((row) => {
977
+ const useCases = row.catalog?.useCase ?? ["chat", "code"];
978
+ const kinds = answers.kinds.filter((kind) => kindMatches(kind, useCases));
979
+ return {
980
+ modelId: row.local.id,
981
+ modelName: row.local.name,
982
+ installed: true,
983
+ origin: row.lastBenchmark ? "measured" : "estimated",
984
+ grade: row.compatibility?.grade,
985
+ useCases,
986
+ kinds
987
+ };
988
+ });
989
+ const catalogExtras = answers.scope === "all" ? session.catalog.filter((model) => !session.rows.some((row) => idsLikelyMatch(row.local.id, model.id))).map((model) => {
990
+ const useCases = model.useCase ?? [];
991
+ const kinds = answers.kinds.filter((kind) => kindMatches(kind, useCases));
992
+ const fit = localCompatibility(session.hardware, model);
993
+ return {
994
+ modelId: model.id,
995
+ modelName: model.name,
996
+ installed: false,
997
+ origin: "estimated",
998
+ grade: fit.grade,
999
+ useCases,
1000
+ kinds
1001
+ };
1002
+ }).filter((row) => row.kinds.length && row.grade !== "F") : [];
1003
+ const ranked = [...installed, ...catalogExtras].filter((row) => row.kinds.length);
1004
+ ranked.sort((a, b) => {
1005
+ if (a.installed !== b.installed) return a.installed ? -1 : 1;
1006
+ if (answers.priority === "speed") return (a.grade ?? "C").localeCompare(b.grade ?? "C");
1007
+ if (answers.priority === "quality") return (b.grade ?? "C").localeCompare(a.grade ?? "C");
1008
+ return 0;
1009
+ });
1010
+ for (const row of ranked.slice(0, 8)) {
1011
+ const tasks = defs.filter((def) => row.kinds.includes(def.kind) && def.useCases.some((u) => row.useCases.includes(u) || kindMatches(def.kind, row.useCases))).slice(0, 3).map((def) => ({
1012
+ id: def.id,
1013
+ harnessId: def.harnessId,
1014
+ kind: def.kind,
1015
+ title: t(def.titleKey),
1016
+ prompt: t(def.promptKey)
1017
+ }));
1018
+ if (!tasks.length) continue;
1019
+ plans.push({
1020
+ modelId: row.modelId,
1021
+ modelName: row.modelName,
1022
+ installed: row.installed,
1023
+ origin: row.origin,
1024
+ grade: row.grade,
1025
+ kinds: row.kinds,
1026
+ tasks
1027
+ });
1028
+ }
1029
+ return plans;
1030
+ }
1031
+ function tasksText(plans, answers) {
1032
+ const header = [
1033
+ t("tasksTitle"),
1034
+ t("tasksHint"),
1035
+ `${t("tasksKinds")}: ${answers.kinds.join(", ")}`,
1036
+ `${t("tasksScope")}: ${answers.scope}`,
1037
+ `${t("tasksPriority")}: ${answers.priority}`,
1038
+ t("credits"),
1039
+ ""
1040
+ ];
1041
+ if (!plans.length) return [...header, t("tasksEmpty")].join("\n");
1042
+ const blocks = plans.map((plan) => {
1043
+ const inst = plan.installed ? t("tasksInstalled") : t("tasksNotInstalled");
1044
+ const origin = plan.origin === "measured" ? t("measured") : t("estimated");
1045
+ const lines = [
1046
+ `${plan.modelName} [${inst}] ${plan.grade ?? "\u2014"} ${origin}`,
1047
+ ` ${plan.kinds.join(", ")}`,
1048
+ ...plan.tasks.map((task) => ` ${task.harnessId} ${task.title}
1049
+ ${task.prompt}`)
1050
+ ];
1051
+ return lines.join("\n");
1052
+ });
1053
+ return [...header, ...blocks].join("\n\n");
1054
+ }
1055
+
1056
+ // src/integrations/catalog.ts
1057
+ var INTEGRATIONS = {
1058
+ opencode: {
1059
+ id: "opencode",
1060
+ docsUrl: "https://docs.ollama.com/integrations/opencode",
1061
+ ollamaLaunch: "opencode",
1062
+ minGenerationTps: 12,
1063
+ allowedGrades: ["S", "A", "B"],
1064
+ useCases: ["code", "chat", "reasoning"]
1065
+ },
1066
+ openclaw: {
1067
+ id: "openclaw",
1068
+ docsUrl: "https://docs.ollama.com/integrations/openclaw",
1069
+ ollamaLaunch: "openclaw",
1070
+ minGenerationTps: 12,
1071
+ allowedGrades: ["S", "A", "B"],
1072
+ useCases: ["code", "chat", "reasoning"]
1073
+ },
1074
+ hermes: {
1075
+ id: "hermes",
1076
+ docsUrl: "https://docs.ollama.com/integrations/hermes",
1077
+ ollamaLaunch: "hermes",
1078
+ minGenerationTps: 12,
1079
+ allowedGrades: ["S", "A"],
1080
+ useCases: ["code", "chat", "reasoning"]
1081
+ },
1082
+ claude: {
1083
+ id: "claude",
1084
+ docsUrl: "https://docs.ollama.com/integrations/claude-code",
1085
+ ollamaLaunch: "claude",
1086
+ minGenerationTps: 12,
1087
+ allowedGrades: ["S", "A", "B"],
1088
+ useCases: ["code", "chat", "reasoning"]
1089
+ }
1090
+ };
1091
+
1092
+ // src/integrations/ollama-tags.ts
1093
+ var RULES = [
1094
+ { test: /qwen\s*3\.5\s*[-:]?\s*8b/i, tag: "qwen3.5:8b" },
1095
+ { test: /qwen\s*3\.5\s*[-:]?\s*4b/i, tag: "qwen3.5:4b" },
1096
+ { test: /qwen\s*3\s*[-:]?\s*14b/i, tag: "qwen3:14b" },
1097
+ { test: /qwen\s*3\s*[-:]?\s*8b/i, tag: "qwen3:8b" },
1098
+ { test: /qwen\s*3\s*[-:]?\s*4b/i, tag: "qwen3:4b" },
1099
+ { test: /qwen\s*2\.5\s*[-:]?\s*coder\s*[-:]?\s*7b/i, tag: "qwen2.5-coder:7b" },
1100
+ { test: /gemma\s*3\s*[-:]?\s*12b/i, tag: "gemma3:12b" },
1101
+ { test: /gemma\s*3\s*[-:]?\s*4b/i, tag: "gemma3:4b" },
1102
+ { test: /gemma\s*3\s*[-:]?\s*1b/i, tag: "gemma3:1b" },
1103
+ { test: /llama\s*3\.2\s*[-:]?\s*3b/i, tag: "llama3.2:3b" },
1104
+ { test: /llama\s*3\.2\s*[-:]?\s*1b/i, tag: "llama3.2:1b" },
1105
+ { test: /llama\s*3\.1\s*[-:]?\s*8b/i, tag: "llama3.1:8b" },
1106
+ { test: /deepseek[- ]r1\s*[-:]?\s*8b|deepseek-r1-distill.*8b/i, tag: "deepseek-r1:8b" },
1107
+ { test: /deepseek[- ]r1\s*[-:]?\s*7b/i, tag: "deepseek-r1:7b" },
1108
+ { test: /mistral\s*[-:]?\s*7b|mistral-nemo/i, tag: "mistral:7b" },
1109
+ { test: /phi\s*4|phi-4/i, tag: "phi4" }
1110
+ ];
1111
+ function resolveOllamaTag(...parts) {
1112
+ const haystack = parts.filter(Boolean).join(" ");
1113
+ if (!haystack.trim()) return void 0;
1114
+ if (/^[a-z0-9._-]+:[a-z0-9._-]+$/i.test(haystack.trim())) return haystack.trim();
1115
+ for (const rule of RULES) {
1116
+ if (rule.test.test(haystack)) return rule.tag;
1117
+ }
1118
+ return void 0;
1119
+ }
1120
+
1121
+ // src/integrations/types.ts
1122
+ var INTEGRATION_IDS = ["opencode", "openclaw", "hermes", "claude"];
1123
+
1124
+ // src/integrations/decide.ts
1125
+ var GRADE_RANK = { S: 5, A: 4, B: 3, C: 2, D: 1, F: 0 };
1126
+ function useCaseOk(useCases, needed) {
1127
+ if (!useCases?.length) return needed.includes("chat");
1128
+ return useCases.some((u) => needed.some((n) => u.includes(n)));
1129
+ }
1130
+ function decideLaunch(session, id) {
1131
+ const def = INTEGRATIONS[id];
1132
+ const reasons = [];
1133
+ const ollama = session.runtimes.find((r) => r.id === "ollama")?.detected;
1134
+ if (!ollama) reasons.push(t("launchNeedOllama"));
1135
+ const candidates = [];
1136
+ for (const row of session.rows) {
1137
+ const useCases = row.catalog?.useCase ?? ["chat", "code"];
1138
+ if (!useCaseOk(useCases, def.useCases)) continue;
1139
+ const tag = resolveOllamaTag(row.local.id, row.local.name, row.catalog?.id, row.catalog?.name);
1140
+ if (!tag) continue;
1141
+ const tps = row.lastBenchmark?.benchmark.generationTokensPerSecond;
1142
+ candidates.push({
1143
+ modelId: row.local.id,
1144
+ ollamaTag: tag,
1145
+ installed: true,
1146
+ origin: row.lastBenchmark ? "measured" : "estimated",
1147
+ grade: row.compatibility?.grade,
1148
+ tps
1149
+ });
1150
+ }
1151
+ if (!candidates.length) {
1152
+ for (const model of session.catalog) {
1153
+ if (!useCaseOk(model.useCase, def.useCases)) continue;
1154
+ const fit = localCompatibility(session.hardware, model);
1155
+ if (!def.allowedGrades.includes(fit.grade)) continue;
1156
+ const tag = resolveOllamaTag(model.id, model.name);
1157
+ if (!tag) continue;
1158
+ candidates.push({
1159
+ modelId: model.id,
1160
+ ollamaTag: tag,
1161
+ installed: false,
1162
+ origin: "estimated",
1163
+ grade: fit.grade
1164
+ });
1165
+ }
1166
+ }
1167
+ candidates.sort((a, b) => {
1168
+ if (a.installed !== b.installed) return a.installed ? -1 : 1;
1169
+ return (GRADE_RANK[b.grade ?? "F"] ?? 0) - (GRADE_RANK[a.grade ?? "F"] ?? 0);
1170
+ });
1171
+ const pick = candidates[0];
1172
+ if (!pick) reasons.push(t("launchNoModel"));
1173
+ if (pick?.grade && !def.allowedGrades.includes(pick.grade)) {
1174
+ reasons.push(t("launchGradeFail", { grade: pick.grade, allowed: def.allowedGrades.join(",") }));
1175
+ }
1176
+ if (pick?.origin === "measured" && pick.tps !== void 0 && pick.tps < def.minGenerationTps) {
1177
+ reasons.push(t("launchSlowFail", { tps: pick.tps.toFixed(1), min: def.minGenerationTps }));
1178
+ }
1179
+ if (pick && pick.origin === "estimated" && (!pick.grade || GRADE_RANK[pick.grade] < GRADE_RANK.B)) {
1180
+ reasons.push(t("launchEstimateWeak"));
1181
+ }
1182
+ const eligible = Boolean(ollama && pick && reasons.length === 0);
1183
+ const command = pick ? `ollama launch ${def.ollamaLaunch} --model ${pick.ollamaTag}` : `ollama launch ${def.ollamaLaunch}`;
1184
+ return {
1185
+ integration: id,
1186
+ docsUrl: def.docsUrl,
1187
+ eligible,
1188
+ reasons: eligible ? [t("launchOk")] : reasons,
1189
+ modelId: pick?.modelId,
1190
+ ollamaTag: pick?.ollamaTag,
1191
+ installed: pick?.installed ?? false,
1192
+ origin: pick?.origin,
1193
+ grade: pick?.grade,
1194
+ command
1195
+ };
1196
+ }
1197
+ function decideAll(session) {
1198
+ return INTEGRATION_IDS.map((id) => decideLaunch(session, id));
1199
+ }
1200
+ function launchText(decisions) {
1201
+ const lines = [t("launchTitle"), t("launchHint"), ""];
1202
+ for (const d of decisions) {
1203
+ lines.push(`${d.integration} ${d.docsUrl}`);
1204
+ lines.push(` ${d.eligible ? "\u2713" : "\u25CB"} ${d.reasons.join(" ")}`);
1205
+ if (d.modelId) {
1206
+ lines.push(
1207
+ ` ${t("launchModel")}: ${d.modelId} ${d.grade ?? "\u2014"} ${d.origin === "measured" ? t("measured") : t("estimated")} ${d.installed ? t("tasksInstalled") : t("tasksNotInstalled")}`
1208
+ );
1209
+ }
1210
+ lines.push(` ${d.command}`);
1211
+ if (d.eligible) lines.push(` ${t("launchRunHint", { tool: d.integration })}`);
1212
+ else lines.push(` ${t("launchBlocked")}`);
1213
+ lines.push("");
1214
+ }
1215
+ return lines.join("\n");
1216
+ }
1217
+ function parseIntegration(raw) {
1218
+ if (!raw) return void 0;
1219
+ const id = raw.toLowerCase();
1220
+ if (id === "claude-code") return "claude";
1221
+ return INTEGRATION_IDS.find((item) => item === id);
1222
+ }
1223
+
1224
+ // src/integrations/agents.ts
1225
+ var AGENTS = [
1226
+ {
1227
+ id: "opencode",
1228
+ label: "OpenCode",
1229
+ bin: "opencode",
1230
+ launch: "ollama launch opencode",
1231
+ docs: "https://docs.ollama.com/integrations/opencode"
1232
+ },
1233
+ {
1234
+ id: "hermes",
1235
+ label: "Hermes",
1236
+ bin: "hermes",
1237
+ launch: "ollama launch hermes",
1238
+ docs: "https://docs.ollama.com/integrations/hermes"
1239
+ },
1240
+ {
1241
+ id: "openclaw",
1242
+ label: "OpenClaw",
1243
+ bin: "openclaw",
1244
+ launch: "ollama launch openclaw",
1245
+ docs: "https://docs.ollama.com/integrations/openclaw"
1246
+ }
1247
+ ];
1248
+ async function detectAgents() {
1249
+ const out = [];
1250
+ for (const agent of AGENTS) {
1251
+ out.push({
1252
+ ...agent,
1253
+ detected: Boolean(await commandExists(agent.bin))
1254
+ });
1255
+ }
1256
+ return out;
1257
+ }
1258
+ var DOCKER_OLLAMA = "docker run -d --name pudu-ollama -p 11434:11434 -v ollama:/root/.ollama ollama/ollama";
1259
+
1260
+ // src/integrations/execute.ts
1261
+ async function executeLaunch(decision) {
1262
+ if (!decision.eligible || !decision.ollamaTag) {
1263
+ return { ok: false, log: t("launchBlocked") };
1264
+ }
1265
+ const def = INTEGRATIONS[decision.integration];
1266
+ if (!def) return { ok: false, log: t("launchUnknown") };
1267
+ const lines = [];
1268
+ if (!decision.installed) {
1269
+ lines.push(t("launchPulling", { model: decision.ollamaTag }));
1270
+ const pull = await runCommand("ollama", ["pull", decision.ollamaTag], { timeout: 30 * 6e4 });
1271
+ if (pull.exitCode !== 0) {
1272
+ return { ok: false, log: `${lines.join("\n")}
1273
+ ${pull.stderr || t("launchPullFail")}` };
1274
+ }
1275
+ }
1276
+ const args = ["launch", def.ollamaLaunch, "--model", decision.ollamaTag];
1277
+ if (def.id === "openclaw" || def.id === "claude") args.push("--yes");
1278
+ lines.push(`ollama ${args.join(" ")}`);
1279
+ const launched = await runCommand("ollama", args, {
1280
+ timeout: 0,
1281
+ stdio: "inherit"
1282
+ });
1283
+ if (launched.exitCode !== 0) {
1284
+ return {
1285
+ ok: false,
1286
+ log: `${lines.join("\n")}
1287
+ ${launched.stderr || launched.stdout || t("launchExecFail")}`
1288
+ };
1289
+ }
1290
+ return { ok: true, log: `${lines.join("\n")}
1291
+ ${launched.stdout}`.trim() };
1292
+ }
1293
+ async function installWithBrew(kind) {
1294
+ const { brewInstall } = await import("./brew-ZRR7D4XL.js");
1295
+ if (kind === "ollama") {
1296
+ const result = await brewInstall(["install", "ollama"]);
1297
+ if (result.ok) {
1298
+ void runCommand("ollama", ["serve"], { timeout: 4e3 });
1299
+ }
1300
+ return result;
1301
+ }
1302
+ return brewInstall(["install", "--cask", "lm-studio"]);
1303
+ }
1304
+ async function executeDockerOllama() {
1305
+ const { brewInstallCask, dockerReady, startDockerDesktop, waitForDocker } = await import("./brew-ZRR7D4XL.js");
1306
+ const notes = [];
1307
+ if (!await dockerReady()) {
1308
+ notes.push(t("dockerBrewInstall"));
1309
+ const installed = await brewInstallCask("docker");
1310
+ if (!installed.ok) {
1311
+ return { ok: false, log: `${t("reqDocker")}
1312
+ ${installed.log}` };
1313
+ }
1314
+ notes.push(t("dockerDesktopWait"));
1315
+ await startDockerDesktop();
1316
+ const up = await waitForDocker();
1317
+ if (!up) return { ok: false, log: `${notes.join("\n")}
1318
+ ${t("dockerRunFail")}` };
1319
+ }
1320
+ const start = await runCommand("docker", ["start", "pudu-ollama"], { timeout: 3e4 });
1321
+ if (start.exitCode === 0) return { ok: true, log: t("dockerRunOk") };
1322
+ const args = DOCKER_OLLAMA.split(" ").slice(1);
1323
+ const result = await runCommand("docker", args, { timeout: 10 * 6e4 });
1324
+ if (result.exitCode !== 0) {
1325
+ return { ok: false, log: result.stderr || t("dockerRunFail") };
1326
+ }
1327
+ return { ok: true, log: t("dockerRunOk") };
1328
+ }
1329
+
1330
+ // src/integrations/pull.ts
1331
+ var INSTALLABLE = ["S", "A", "B"];
1332
+ function canInstallGrade(grade) {
1333
+ return Boolean(grade && INSTALLABLE.includes(grade));
1334
+ }
1335
+ async function pullOllamaModel(modelId, extraName) {
1336
+ const tag = resolveOllamaTag(modelId, extraName);
1337
+ if (!tag) {
1338
+ return { ok: false, log: t("noOllamaTag", { model: extraName ?? modelId }), tag: modelId };
1339
+ }
1340
+ const result = await runCommand("ollama", ["pull", tag], { timeout: 30 * 6e4 });
1341
+ if (result.exitCode !== 0) {
1342
+ return { ok: false, log: result.stderr || t("launchPullFail"), tag };
1343
+ }
1344
+ return { ok: true, log: t("modelPulled", { model: tag }), tag };
1345
+ }
1346
+
1347
+ // src/hardware/types.ts
1348
+ function memoryLabel(profile) {
1349
+ return profile.memory.unified ? "Unified Memory" : "RAM";
1350
+ }
1351
+
1352
+ // src/compatibility/types.ts
1353
+ var GRADE_MEANING = {
1354
+ S: "Excellent",
1355
+ A: "Recommended",
1356
+ B: "Good",
1357
+ C: "Tight",
1358
+ D: "CPU/offload",
1359
+ F: "Not recommended"
1360
+ };
1361
+
1362
+ // src/cli/color.ts
1363
+ var enabled = () => !process.env.NO_COLOR && process.stdout.isTTY;
1364
+ var wrap = (open, close = 39) => (text) => enabled() ? `\x1B[${open}m${text}\x1B[${close}m` : text;
1365
+ var cyan = wrap(36);
1366
+ var green = wrap(32);
1367
+ var yellow = wrap(33);
1368
+ var red = wrap(31);
1369
+ var magenta = wrap(35);
1370
+ var bold = wrap(1, 22);
1371
+ var dim = wrap(2, 22);
1372
+ function gradeAnsi(grade) {
1373
+ if (grade === "S" || grade === "A") return green(grade);
1374
+ if (grade === "B") return yellow(grade);
1375
+ if (grade === "C") return magenta(grade);
1376
+ if (grade === "D" || grade === "F") return red(grade);
1377
+ return grade;
1378
+ }
1379
+
1380
+ // src/cli/text.ts
1381
+ function hardwareText(session) {
1382
+ const h = session.hardware;
1383
+ const ram = formatBytes(h.memory.totalBytes, 0);
1384
+ const avail = h.memory.availableBytes ? formatBytes(h.memory.availableBytes) : "N/A";
1385
+ return [
1386
+ bold(cyan(t("machine"))),
1387
+ ` ${h.machineModel ?? t("unknownMachine")}`,
1388
+ ` ${h.cpu.name ?? t("cpuNa")}`,
1389
+ ` CPU ${h.cpu.physicalCores ?? "N/A"} cores (${h.cpu.performanceCores ?? "?"}P / ${h.cpu.efficiencyCores ?? "?"}E)`,
1390
+ ` GPU ${h.gpu.name ?? "N/A"}`,
1391
+ ` ${memoryLabel(h).padEnd(13)} ${ram}`,
1392
+ ` Available ${avail}`,
1393
+ ` ${h.os} ${h.osVersion ?? h.arch}`
1394
+ ].join("\n");
1395
+ }
1396
+ function runtimesText(session) {
1397
+ return [
1398
+ bold(cyan(t("runtimes"))),
1399
+ ...session.runtimes.map((r) => {
1400
+ const mark = r.detected ? green("\u2713") : dim("\u25CB");
1401
+ const state = r.detected ? green(r.version ?? t("detected")) : dim(t("notDetected"));
1402
+ return ` ${mark} ${r.label.padEnd(14)} ${state}`;
1403
+ })
1404
+ ].join("\n");
1405
+ }
1406
+ function modelsText(session) {
1407
+ const lines = [bold(cyan(t("installedModels"))), dim("MODEL INSTALLED FIT EST. SPEED MEASURED")];
1408
+ for (const row of session.rows) {
1409
+ const fit = row.compatibility?.grade ?? "\u2014";
1410
+ const est = row.compatibility?.estimatedTokensPerSecond ? `~${formatNumber(row.compatibility.estimatedTokensPerSecond)} t/s` : "\u2014";
1411
+ const measured = row.lastBenchmark?.benchmark.generationTokensPerSecond ? formatTokensPerSec(row.lastBenchmark.benchmark.generationTokensPerSecond) : t("notTested");
1412
+ lines.push(
1413
+ `${green(row.local.name.padEnd(22))} \u2713 ${gradeAnsi(fit).padEnd(9)} ${est.padEnd(14)} ${measured}`
1414
+ );
1415
+ }
1416
+ lines.push("", t("compatible"));
1417
+ const extras = session.catalog.filter((model) => !session.rows.some((row) => idsLikelyMatch(row.local.id, model.id))).map((model) => ({ model, fit: localCompatibility(session.hardware, model) })).sort((a, b) => a.fit.grade.localeCompare(b.fit.grade)).slice(0, 12);
1418
+ for (const extra of extras) {
1419
+ const est = extra.fit.estimatedTokensPerSecond ? `~${formatNumber(extra.fit.estimatedTokensPerSecond)} t/s` : "\u2014";
1420
+ lines.push(`${extra.model.name.padEnd(22)} \u2014 ${extra.fit.grade.padEnd(9)} ${est.padEnd(14)} ${t("estimated")}`);
1421
+ }
1422
+ return lines.join("\n");
1423
+ }
1424
+ function recommendText(session) {
1425
+ const lines = [bold(cyan(t("recommended"))), dim(t("recommendedHint")), "", dim(t("credits")), ""];
1426
+ for (const rec of session.recommendations) {
1427
+ lines.push(
1428
+ `${magenta(rec.useCase.padEnd(12))} ${rec.model.name.padEnd(22)} ${gradeAnsi(rec.grade)} ${GRADE_MEANING[rec.grade]} ~${na(rec.estimatedTokensPerSecond)} t/s est.`
1429
+ );
1430
+ }
1431
+ lines.push("", bold(yellow(t("agentsTitle"))));
1432
+ for (const agent of session.agents) {
1433
+ lines.push(
1434
+ ` ${agent.detected ? green("\u2713") : dim("\u25CB")} ${agent.label.padEnd(12)} ${agent.detected ? green(t("detected")) : dim(t("agentMissing"))}`
1435
+ );
1436
+ }
1437
+ lines.push("", dim(t("recommendCliHint")));
1438
+ return lines.join("\n");
1439
+ }
1440
+ function historyText(records) {
1441
+ if (!records.length) return t("historyEmpty");
1442
+ return records.map((r) => {
1443
+ const gen = r.benchmark.generationTokensPerSecond;
1444
+ return `${r.timestamp} ${r.model.id.padEnd(18)} ${gen ? formatTokensPerSec(gen) : "N/A"} score ${r.score?.total ?? "N/A"}`;
1445
+ }).join("\n");
1446
+ }
1447
+ function historyCsv(records) {
1448
+ const header = "timestamp,model,prompt_tps,generation_tps,peak_memory_gb,avg_cpu,score";
1449
+ const rows = records.map(
1450
+ (r) => [
1451
+ r.timestamp,
1452
+ r.model.id,
1453
+ r.benchmark.promptTokensPerSecond ?? "",
1454
+ r.benchmark.generationTokensPerSecond ?? "",
1455
+ r.resources.peakMemoryGb ?? "",
1456
+ r.resources.avgCpuPercent ?? "",
1457
+ r.score?.total ?? ""
1458
+ ].join(",")
1459
+ );
1460
+ return [header, ...rows].join("\n");
1461
+ }
1462
+ function doctorText(session) {
1463
+ const node = process.version.replace(/^v/, "");
1464
+ const apple = session.hardware.cpu.appleSilicon ? `${session.hardware.cpu.appleSilicon.generation} ${session.hardware.cpu.appleSilicon.variant}` : "no";
1465
+ const lines = [
1466
+ t("doctorTitle"),
1467
+ "",
1468
+ `\u2713 Node.js ${node}`,
1469
+ `${session.hardware.cpu.appleSilicon ? "\u2713" : "\u25CB"} Apple Silicon ${apple}`,
1470
+ `${session.hardware.gpu.metal ? "\u2713" : "\u25CB"} Metal`,
1471
+ ...session.runtimes.map((r) => `${r.detected ? "\u2713" : "\u25CB"} ${r.label.padEnd(14)} ${r.version ?? ""}`.trimEnd()),
1472
+ `${session.networkUsed ? "\u2713" : "\u25CB"} CanIRun.ai (midudev)`,
1473
+ ...session.agents.map(
1474
+ (a) => `${a.detected ? "\u2713" : "\u25CB"} ${a.label.padEnd(14)} ${a.detected ? t("detected") : a.launch}`
1475
+ ),
1476
+ ...session.runtimes.find((r) => r.id === "ollama")?.detected ? [] : [`\u25CB Ollama ${t("reqOllama")}`],
1477
+ ...session.runtimes.find((r) => r.id === "lmstudio")?.detected ? [] : [`\u25CB LM Studio ${t("reqLmStudio")}`],
1478
+ ...session.runtimes.find((r) => r.id === "docker")?.detected ? [`\u2713 Docker ${t("dockerHint")}`] : [`\u25CB Docker ${t("reqDocker")}`],
1479
+ "",
1480
+ session.llamaBench ? t("doctorReady", { count: session.models.filter((m) => m.artifactPath).length }) : t("doctorNoBench")
1481
+ ];
1482
+ return lines.join("\n");
1483
+ }
1484
+ function reportMarkdown(record) {
1485
+ const machine = `${record.machine.cpu ?? "unknown"} / ${record.machine.memoryGb ?? "?"} GB`;
1486
+ return `## ${t("reportTitle")}
1487
+
1488
+ **Machine:** ${machine}
1489
+ **Model:** ${record.model.id}
1490
+
1491
+ | Metric | Result |
1492
+ |---|---:|
1493
+ | Prompt processing | ${record.benchmark.promptTokensPerSecond ?? "N/A"} t/s |
1494
+ | Generation | ${record.benchmark.generationTokensPerSecond ?? "N/A"} t/s |
1495
+ | Peak memory | ${record.resources.peakMemoryGb ?? "N/A"} GB |
1496
+ | Average GPU | ${record.resources.avgGpuPercent ?? "N/A"}% |
1497
+ | Average power | ${record.resources.avgPackagePowerWatts ?? "N/A"} W |
1498
+ | Efficiency | ${record.resources.tokensPerSecondPerWatt ?? "N/A"} t/s/W |
1499
+ | ${t("scoreLabel")} | ${record.score?.total ?? "N/A"} / 100 |
1500
+ `;
1501
+ }
1502
+ function compareText(records) {
1503
+ const latestByModel = /* @__PURE__ */ new Map();
1504
+ for (const record of records) latestByModel.set(record.model.id, record);
1505
+ const list = [...latestByModel.values()].slice(0, 4);
1506
+ if (list.length < 2) return t("compareNeedTwo");
1507
+ const names = list.map((r) => r.model.id);
1508
+ const row = (label, pick) => `${label.padEnd(20)}${list.map((r) => pick(r).padStart(14)).join("")}`;
1509
+ const winner = (metric, higher = true) => {
1510
+ let best;
1511
+ for (const r of list) {
1512
+ const v = metric(r);
1513
+ if (v === void 0) continue;
1514
+ if (!best) best = r;
1515
+ else {
1516
+ const b = metric(best);
1517
+ if (b === void 0) best = r;
1518
+ else if (higher ? v > b : v < b) best = r;
1519
+ }
1520
+ }
1521
+ return best?.model.id ?? "N/A";
1522
+ };
1523
+ return [
1524
+ t("compareTitle"),
1525
+ names.join(" vs "),
1526
+ row("Generation t/s", (r) => formatNumber(r.benchmark.generationTokensPerSecond)),
1527
+ row("Prompt t/s", (r) => formatNumber(r.benchmark.promptTokensPerSecond)),
1528
+ row("Peak RAM", (r) => r.resources.peakMemoryGb ? `${r.resources.peakMemoryGb} GB` : "N/A"),
1529
+ row("Avg CPU", (r) => r.resources.avgCpuPercent ? `${r.resources.avgCpuPercent}%` : "N/A"),
1530
+ row("Power", (r) => r.resources.avgPackagePowerWatts ? `${r.resources.avgPackagePowerWatts} W` : "N/A"),
1531
+ row("t/s/W", (r) => formatNumber(r.resources.tokensPerSecondPerWatt)),
1532
+ "",
1533
+ t("winner"),
1534
+ `Speed ${winner((r) => r.benchmark.generationTokensPerSecond)}`,
1535
+ `Memory ${winner((r) => r.resources.peakMemoryGb, false)}`,
1536
+ `Efficiency ${winner((r) => r.resources.tokensPerSecondPerWatt)}`,
1537
+ t("qualityNote")
1538
+ ].join("\n");
1539
+ }
1540
+ function resultText(record, assessment) {
1541
+ return [
1542
+ record.model.id,
1543
+ "",
1544
+ `Prompt processing ${record.benchmark.promptTokensPerSecond ?? "N/A"} t/s`,
1545
+ `Generation ${record.benchmark.generationTokensPerSecond ?? "N/A"} t/s`,
1546
+ `Peak memory ${record.resources.peakMemoryGb ?? "N/A"} GB`,
1547
+ `Average GPU ${record.resources.avgGpuPercent ?? "N/A"} %`,
1548
+ `Average CPU ${record.resources.avgCpuPercent ?? "N/A"} %`,
1549
+ `Average power ${record.resources.avgPackagePowerWatts ?? "N/A"} W`,
1550
+ `Efficiency ${record.resources.tokensPerSecondPerWatt ?? "N/A"} t/s/W`,
1551
+ "",
1552
+ `${t("scoreLabel")} ${record.score?.total ?? "N/A"}/100`,
1553
+ "",
1554
+ ...assessment.map((line) => `\u2713 ${line}`)
1555
+ ].join("\n");
1556
+ }
1557
+
1558
+ export {
1559
+ bytesToGiB,
1560
+ parseSizeToBytes,
1561
+ formatBytes,
1562
+ configPath,
1563
+ canirunCachePath,
1564
+ ensureStorage,
1565
+ detectHardware,
1566
+ idsLikelyMatch,
1567
+ resolveOllamaTag,
1568
+ localCompatibility,
1569
+ listBenchmarks,
1570
+ detectAgents,
1571
+ runBenchmark,
1572
+ WORK_KINDS,
1573
+ parseAnswers,
1574
+ planTasks,
1575
+ tasksText,
1576
+ decideLaunch,
1577
+ decideAll,
1578
+ launchText,
1579
+ parseIntegration,
1580
+ executeLaunch,
1581
+ installWithBrew,
1582
+ executeDockerOllama,
1583
+ canInstallGrade,
1584
+ pullOllamaModel,
1585
+ memoryLabel,
1586
+ GRADE_MEANING,
1587
+ hardwareText,
1588
+ runtimesText,
1589
+ modelsText,
1590
+ recommendText,
1591
+ historyText,
1592
+ historyCsv,
1593
+ doctorText,
1594
+ reportMarkdown,
1595
+ compareText,
1596
+ resultText
1597
+ };