offgrid-ai 0.4.0 → 0.4.1
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 +39 -13
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -241,12 +241,15 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
|
|
|
241
241
|
if (await isProfileRunning(profile)) runningProfilesNow.push(profile);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
const fileMissingCount = profiles.filter((p) => isProfileFileMissing(p)).length;
|
|
245
|
+
|
|
244
246
|
console.log("\n" + renderCard("Your local AI workspace", renderRows([
|
|
245
247
|
["Ready to chat", pc.green(`${profiles.length} saved setup${profiles.length === 1 ? "" : "s"}`)],
|
|
246
248
|
["Need setup", newModels.length > 0 ? pc.yellow(`${newModels.length} downloaded model${newModels.length === 1 ? "" : "s"}`) : pc.green("none")],
|
|
247
249
|
["Running now", runningProfilesNow.length > 0 ? pc.green(String(runningProfilesNow.length)) : pc.dim("none")],
|
|
248
|
-
["
|
|
249
|
-
|
|
250
|
+
["File missing", fileMissingCount > 0 ? pc.red(`${fileMissingCount} setup${fileMissingCount === 1 ? "" : "s"}`) : pc.green("none")],
|
|
251
|
+
["Next step", fileMissingCount > 0 ? pc.red("Remove or fix missing setups") : profiles.length > 0 ? "Start chatting" : newModels.length > 0 ? "Set up a downloaded model" : "Download a model"],
|
|
252
|
+
]), { formatBorder: fileMissingCount > 0 ? pc.yellow : pc.cyan }));
|
|
250
253
|
|
|
251
254
|
console.log("\n" + pc.bold("Ready to chat"));
|
|
252
255
|
if (profiles.length === 0) {
|
|
@@ -255,8 +258,9 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
|
|
|
255
258
|
for (const profile of profiles) {
|
|
256
259
|
const running = runningProfilesNow.some((item) => item.id === profile.id);
|
|
257
260
|
const piConfigured = await hasPiModel(profile);
|
|
261
|
+
const fileMissing = isProfileFileMissing(profile);
|
|
258
262
|
const num = itemNumber((item) => item.type === "profile" && item.profile.id === profile.id);
|
|
259
|
-
console.log(profileCatalogCard(num, profile, { running, piConfigured }));
|
|
263
|
+
console.log(profileCatalogCard(num, profile, { running, piConfigured, fileMissing }));
|
|
260
264
|
}
|
|
261
265
|
}
|
|
262
266
|
|
|
@@ -285,16 +289,30 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
|
|
|
285
289
|
}
|
|
286
290
|
}
|
|
287
291
|
|
|
288
|
-
function
|
|
292
|
+
function isProfileFileMissing(profile) {
|
|
293
|
+
const backend = backendFor(profile.backend);
|
|
294
|
+
if (backend.type === "managed-server") return false;
|
|
295
|
+
if (!profile.modelPath) return true; // no path recorded means we can't verify
|
|
296
|
+
return !existsSync(profile.modelPath);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function profileCatalogCard(num, profile, { running, piConfigured, fileMissing }) {
|
|
289
300
|
const backend = backendFor(profile.backend);
|
|
290
301
|
const caps = profile.capabilities ?? {};
|
|
291
|
-
|
|
302
|
+
let status;
|
|
303
|
+
if (fileMissing) {
|
|
304
|
+
status = pc.red("File missing");
|
|
305
|
+
} else if (running) {
|
|
306
|
+
status = statusText("running", "Running now");
|
|
307
|
+
} else {
|
|
308
|
+
status = statusText("ready", "Ready to chat");
|
|
309
|
+
}
|
|
292
310
|
return renderCard(`${num}. ${profile.label}`, renderRows([
|
|
293
311
|
["Status", status],
|
|
294
|
-
["Good for", humanCapabilitySummary(caps)],
|
|
312
|
+
["Good for", fileMissing ? pc.red("Model file not found") : humanCapabilitySummary(caps)],
|
|
295
313
|
["Pi", piConfigured ? pc.green("Synced") : pc.yellow("Needs sync")],
|
|
296
314
|
["Runs with", backend.label],
|
|
297
|
-
]), { formatBorder: running ? pc.green : pc.cyan });
|
|
315
|
+
]), { formatBorder: fileMissing ? pc.red : running ? pc.green : pc.cyan });
|
|
298
316
|
}
|
|
299
317
|
|
|
300
318
|
function downloadedModelCard(num, model, caps) {
|
|
@@ -316,7 +334,7 @@ function managedModelCard(num, model, backend) {
|
|
|
316
334
|
|
|
317
335
|
function modelCatalogItems({ profiles, newModels, managedItems }) {
|
|
318
336
|
return [
|
|
319
|
-
...profiles.map((profile) => ({ type: "profile", profile, label: profile.label, hint: `${profile.modelAlias} · ${profile.baseUrl}
|
|
337
|
+
...profiles.map((profile) => ({ type: "profile", profile, label: profile.label, hint: `${profile.modelAlias} · ${profile.baseUrl}`, fileMissing: isProfileFileMissing(profile) })),
|
|
320
338
|
...newModels.map((model) => ({ type: "new", model, label: model.label, hint: `${model.quant ?? "GGUF"} · ${formatBytes(model.sizeBytes)}` })),
|
|
321
339
|
...managedItems.map(({ model, backendId }) => ({ type: "managed", model, backendId, label: model.label, hint: BACKENDS[backendId].label })),
|
|
322
340
|
];
|
|
@@ -389,12 +407,16 @@ async function printProfileDetails(profile) {
|
|
|
389
407
|
const isManaged = backend.type === "managed-server";
|
|
390
408
|
const piConfigured = await hasPiModel(profile);
|
|
391
409
|
const running = await isProfileRunning(profile);
|
|
410
|
+
const fileMissing = !isManaged && isProfileFileMissing(profile);
|
|
411
|
+
const statusRow = fileMissing
|
|
412
|
+
? pc.red("File missing")
|
|
413
|
+
: running ? pc.green("Running now") : pc.green("Ready to chat");
|
|
392
414
|
console.log("\n" + renderSection("Model overview", renderRows([
|
|
393
415
|
["Name", pc.bold(profile.label)],
|
|
394
|
-
["Status",
|
|
395
|
-
["Good for", humanCapabilitySummary(profile.capabilities ?? {})],
|
|
416
|
+
["Status", statusRow],
|
|
417
|
+
["Good for", fileMissing ? pc.red("Model file not found — remove or fix this setup") : humanCapabilitySummary(profile.capabilities ?? {})],
|
|
396
418
|
["Pi", piConfigured ? pc.green("Synced") : pc.yellow("Needs sync")],
|
|
397
|
-
["Server", pc.green(profile.baseUrl)],
|
|
419
|
+
["Server", fileMissing ? pc.red(profile.baseUrl) : pc.green(profile.baseUrl)],
|
|
398
420
|
])));
|
|
399
421
|
|
|
400
422
|
console.log("\n" + renderSection("Model details", renderRows([
|
|
@@ -403,12 +425,16 @@ async function printProfileDetails(profile) {
|
|
|
403
425
|
["Model alias", pc.cyan(profile.modelAlias)],
|
|
404
426
|
...(profile.capabilities ? [["Detected", capabilitySummary(profile.capabilities)]] : []),
|
|
405
427
|
...(!isManaged ? [
|
|
406
|
-
["Local file", profile.modelPath ?? "unknown"],
|
|
407
|
-
["Vision file", profile.mmprojPath
|
|
428
|
+
["Local file", fileMissing ? pc.red(`${profile.modelPath} (not found)`) : profile.modelPath ?? "unknown"],
|
|
429
|
+
["Vision file", profile.mmprojPath ? (existsSync(profile.mmprojPath) ? profile.mmprojPath : pc.red(`${profile.mmprojPath} (not found)`)) : "none"],
|
|
408
430
|
["Model size", profile.modelPath && existsSync(profile.modelPath) ? formatBytes(statSync(profile.modelPath).size) : "unknown"],
|
|
409
431
|
] : []),
|
|
410
432
|
]), { columns: 110 }));
|
|
411
433
|
|
|
434
|
+
if (fileMissing) {
|
|
435
|
+
console.log("\n" + pc.red("⚠ This model's file is no longer on disk. Remove this setup or move the file back."));
|
|
436
|
+
}
|
|
437
|
+
|
|
412
438
|
if (!isManaged && profile.commandArgv) {
|
|
413
439
|
const commandArgv = await readCommandArgv(profile);
|
|
414
440
|
console.log("\n" + renderSection("llama-server command", pc.dim(buildPrettyCommand({ ...profile, commandArgv })), { columns: 120 }));
|