offgrid-ai 0.5.2 → 0.5.3
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/package.json +1 -1
- package/src/cli.mjs +68 -61
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -201,16 +201,15 @@ async function modelCommandCenter(initialCatalog) {
|
|
|
201
201
|
["File missing", fileMissingCount > 0 ? pc.red(`${fileMissingCount} setup${fileMissingCount === 1 ? "" : "s"}`) : pc.dim("none")],
|
|
202
202
|
]), { formatBorder: summaryBorder }));
|
|
203
203
|
|
|
204
|
+
// Show model cards for all items
|
|
205
|
+
printModelCards(allItems, { runningProfilesNow, drafters: normalized.drafters });
|
|
206
|
+
|
|
204
207
|
const options = allItems.map((item) => modelSelectOption(item, { runningProfilesNow, drafters: normalized.drafters }));
|
|
205
208
|
const selected = await prompt.choice("Select a model", options);
|
|
206
209
|
if (!selected) break;
|
|
207
210
|
const item = allItems.find((i) => itemKey(i) === selected);
|
|
208
211
|
if (!item) break;
|
|
209
212
|
|
|
210
|
-
// Show detail card before action menu
|
|
211
|
-
console.log("");
|
|
212
|
-
printItemCard(item, { runningProfilesNow, drafters: normalized.drafters });
|
|
213
|
-
|
|
214
213
|
const actions = actionsForItem(item);
|
|
215
214
|
const action = await prompt.choice(item.label, actions, actions[0].value);
|
|
216
215
|
if (!action) continue;
|
|
@@ -340,65 +339,73 @@ function isProfileFileMissing(profile) {
|
|
|
340
339
|
return !existsSync(profile.modelPath);
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
mtpLabel
|
|
342
|
+
function printModelCards(items, { runningProfilesNow, drafters }) {
|
|
343
|
+
// Group items by type for section headers
|
|
344
|
+
const profiles = items.filter((i) => i.type === "profile");
|
|
345
|
+
const newModels = items.filter((i) => i.type === "new");
|
|
346
|
+
const managed = items.filter((i) => i.type === "managed");
|
|
347
|
+
|
|
348
|
+
if (profiles.length > 0) {
|
|
349
|
+
console.log("\n" + pc.bold("Ready to chat"));
|
|
350
|
+
for (const item of profiles) {
|
|
351
|
+
const { profile } = item;
|
|
352
|
+
const backend = backendFor(profile.backend);
|
|
353
|
+
const running = runningProfilesNow.some((r) => r.id === profile.id);
|
|
354
|
+
const fileMissing = item.fileMissing;
|
|
355
|
+
const caps = profile.capabilities ?? {};
|
|
356
|
+
let status;
|
|
357
|
+
if (fileMissing) status = pc.red("File missing");
|
|
358
|
+
else if (running) status = pc.green("Running now");
|
|
359
|
+
else status = "Ready";
|
|
360
|
+
const border = fileMissing ? pc.red : running ? pc.green : pc.dim;
|
|
361
|
+
// MTP: enabled vs available
|
|
362
|
+
let mtpLabel;
|
|
363
|
+
if (profile.drafterPath) mtpLabel = pc.green("MTP enabled");
|
|
364
|
+
else if (drafters ? matchDrafter(profile.modelPath, drafters) : null) mtpLabel = pc.yellow("MTP available");
|
|
365
|
+
else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP: needs drafter");
|
|
366
|
+
const detailParts = [fileMissing ? pc.red("File not found") : humanCapabilitySummary(caps)];
|
|
367
|
+
if (mtpLabel) detailParts.push(mtpLabel);
|
|
368
|
+
const ctxLabel = profile.flags?.ctxSize ? `${(profile.flags.ctxSize / 1000).toFixed(0)}k ctx` : null;
|
|
369
|
+
if (ctxLabel) detailParts.push(ctxLabel);
|
|
370
|
+
console.log(renderCard(profile.label, renderRows([
|
|
371
|
+
["Status", status],
|
|
372
|
+
["Details", detailParts.join(pc.dim(" · "))],
|
|
373
|
+
["Runs with", backend.label],
|
|
374
|
+
]), { formatBorder: border }));
|
|
364
375
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (newModels.length > 0) {
|
|
379
|
+
console.log("\n" + pc.bold("Needs setup"));
|
|
380
|
+
for (const item of newModels) {
|
|
381
|
+
const { model, drafter } = item;
|
|
382
|
+
const caps = detectCapabilities(model.path, model.mmprojPath);
|
|
383
|
+
const mtpAvailable = caps.mtp || Boolean(drafter);
|
|
384
|
+
const mtpLabel = mtpAvailable
|
|
385
|
+
? pc.green("MTP ✓")
|
|
386
|
+
: (caps.architecture === "gemma4")
|
|
387
|
+
? pc.yellow("MTP: needs drafter")
|
|
388
|
+
: null;
|
|
389
|
+
const detailParts = [humanCapabilitySummary(caps)];
|
|
390
|
+
if (mtpLabel) detailParts.push(mtpLabel);
|
|
391
|
+
detailParts.push(formatBytes(model.sizeBytes));
|
|
392
|
+
console.log(renderCard(model.label, renderRows([
|
|
393
|
+
["Status", pc.yellow("Needs setup")],
|
|
394
|
+
["Details", detailParts.join(pc.dim(" · "))],
|
|
395
|
+
]), { formatBorder: pc.yellow }));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const beId of ["ollama", "omlx"]) {
|
|
400
|
+
const managedForBackend = managed.filter((i) => i.backendId === beId);
|
|
401
|
+
if (managedForBackend.length === 0) continue;
|
|
402
|
+
const be = BACKENDS[beId];
|
|
403
|
+
console.log("\n" + pc.bold(`Via ${be.label}`));
|
|
404
|
+
for (const item of managedForBackend) {
|
|
405
|
+
console.log(renderCard(item.model.label, renderRows([
|
|
406
|
+
["Details", [item.model.id, item.model.quant].filter(Boolean).join(pc.dim(" · "))],
|
|
407
|
+
]), { formatBorder: pc.dim }));
|
|
376
408
|
}
|
|
377
|
-
console.log(renderCard(profile.label, renderRows(rows), { formatBorder: border }));
|
|
378
|
-
} else if (item.type === "new") {
|
|
379
|
-
const { model, drafter } = item;
|
|
380
|
-
const caps = detectCapabilities(model.path, model.mmprojPath);
|
|
381
|
-
const mtpAvailable = caps.mtp || Boolean(drafter);
|
|
382
|
-
const mtpLabel = mtpAvailable
|
|
383
|
-
? pc.green("MTP ✓")
|
|
384
|
-
: (caps.architecture === "gemma4")
|
|
385
|
-
? pc.yellow("MTP: needs drafter")
|
|
386
|
-
: null;
|
|
387
|
-
const detailParts = [humanCapabilitySummary(caps)];
|
|
388
|
-
if (mtpLabel) detailParts.push(mtpLabel);
|
|
389
|
-
detailParts.push(formatBytes(model.sizeBytes));
|
|
390
|
-
console.log(renderCard(model.label, renderRows([
|
|
391
|
-
["Status", pc.yellow("Needs setup")],
|
|
392
|
-
["Details", detailParts.join(pc.dim(" · "))],
|
|
393
|
-
]), { formatBorder: pc.yellow }));
|
|
394
|
-
} else {
|
|
395
|
-
// managed
|
|
396
|
-
const { model, backendId } = item;
|
|
397
|
-
const backend = BACKENDS[backendId];
|
|
398
|
-
console.log(renderCard(model.label, renderRows([
|
|
399
|
-
["Status", pc.dim(`Via ${backend.label}`)],
|
|
400
|
-
["Details", [model.id, model.quant].filter(Boolean).join(pc.dim(" · "))],
|
|
401
|
-
]), { formatBorder: pc.dim }));
|
|
402
409
|
}
|
|
403
410
|
}
|
|
404
411
|
|