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