offgrid-ai 0.4.1 → 0.4.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.mjs +27 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Privacy-first CLI for running local LLMs — discover, configure, run, benchmark",
5
5
  "author": "Eeshan Srivastava (https://eeshans.com)",
6
6
  "type": "module",
package/src/cli.mjs CHANGED
@@ -9,7 +9,7 @@ import { startServer, stopProfile, waitForReady, serverReady, serverMatchesProfi
9
9
  import { syncPiConfig, removeFromPiConfig, hasPiModel, launchPi, hasPi } from "./harness-pi.mjs";
10
10
  import { tailFriendly } from "./logs.mjs";
11
11
  import { estimateMemory } from "./estimate.mjs";
12
- import { pc, formatBytes, renderRows, renderSection, renderCard, humanCapabilitySummary, statusText, startInteractive, createPrompt, parseOptions } from "./ui.mjs";
12
+ import { pc, formatBytes, renderRows, renderSection, renderCard, humanCapabilitySummary, startInteractive, createPrompt, parseOptions } from "./ui.mjs";
13
13
  import { checkForUpdate, currentPackageVersion, detectInvocation, updateCommand, runUpdateCommand } from "./updates.mjs";
14
14
  import { removeInstallerPathEntries } from "./shell-path.mjs";
15
15
  import { configureLocalProfile } from "./profile-setup.mjs";
@@ -243,13 +243,14 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
243
243
 
244
244
  const fileMissingCount = profiles.filter((p) => isProfileFileMissing(p)).length;
245
245
 
246
+ const summaryBorder = fileMissingCount > 0 ? pc.red : newModels.length > 0 ? pc.yellow : pc.dim;
246
247
  console.log("\n" + renderCard("Your local AI workspace", renderRows([
247
- ["Ready to chat", pc.green(`${profiles.length} saved setup${profiles.length === 1 ? "" : "s"}`)],
248
- ["Need setup", newModels.length > 0 ? pc.yellow(`${newModels.length} downloaded model${newModels.length === 1 ? "" : "s"}`) : pc.green("none")],
249
- ["Running now", runningProfilesNow.length > 0 ? pc.green(String(runningProfilesNow.length)) : pc.dim("none")],
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 }));
248
+ ["Setups", `${profiles.length} saved`],
249
+ ["Need setup", newModels.length > 0 ? pc.yellow(`${newModels.length} model${newModels.length === 1 ? "" : "s"}`) : pc.dim("none")],
250
+ ["Running", runningProfilesNow.length > 0 ? pc.green(String(runningProfilesNow.length)) : pc.dim("none")],
251
+ ["File missing", fileMissingCount > 0 ? pc.red(`${fileMissingCount} setup${fileMissingCount === 1 ? "" : "s"}`) : pc.dim("none")],
252
+ ["Next step", fileMissingCount > 0 ? pc.red("Remove or fix missing setups") : profiles.length > 0 ? "Start chatting" : newModels.length > 0 ? pc.yellow("Set up a downloaded model") : "Download a model"],
253
+ ]), { formatBorder: summaryBorder }));
253
254
 
254
255
  console.log("\n" + pc.bold("Ready to chat"));
255
256
  if (profiles.length === 0) {
@@ -264,9 +265,9 @@ async function printModelCatalog({ profiles, newModels, managedItems }, items =
264
265
  }
265
266
  }
266
267
 
267
- console.log("\n" + pc.bold("Downloaded, needs one-time setup"));
268
+ console.log("\n" + pc.bold("Needs one-time setup"));
268
269
  if (newModels.length === 0) {
269
- console.log(renderCard("All set", "Every downloaded local model already has a saved setup.", { formatBorder: pc.green }));
270
+ console.log(renderCard("All set", "Every downloaded local model already has a saved setup.", { formatBorder: pc.dim }));
270
271
  } else {
271
272
  for (const model of newModels.slice(0, 20)) {
272
273
  const caps = detectCapabilities(model.path, model.mmprojPath);
@@ -303,21 +304,22 @@ function profileCatalogCard(num, profile, { running, piConfigured, fileMissing }
303
304
  if (fileMissing) {
304
305
  status = pc.red("File missing");
305
306
  } else if (running) {
306
- status = statusText("running", "Running now");
307
+ status = pc.green("Running now");
307
308
  } else {
308
- status = statusText("ready", "Ready to chat");
309
+ status = "Ready";
309
310
  }
311
+ const border = fileMissing ? pc.red : running ? pc.green : pc.dim;
310
312
  return renderCard(`${num}. ${profile.label}`, renderRows([
311
313
  ["Status", status],
312
314
  ["Good for", fileMissing ? pc.red("Model file not found") : humanCapabilitySummary(caps)],
313
- ["Pi", piConfigured ? pc.green("Synced") : pc.yellow("Needs sync")],
315
+ ["Pi", piConfigured ? pc.dim("synced") : pc.yellow("Needs sync")],
314
316
  ["Runs with", backend.label],
315
- ]), { formatBorder: fileMissing ? pc.red : running ? pc.green : pc.cyan });
317
+ ]), { formatBorder: border });
316
318
  }
317
319
 
318
320
  function downloadedModelCard(num, model, caps) {
319
321
  return renderCard(`${num}. ${model.label}`, renderRows([
320
- ["Status", statusText("warning", "Needs one-time setup")],
322
+ ["Status", pc.yellow("Needs setup")],
321
323
  ["Good for", humanCapabilitySummary(caps)],
322
324
  ["Size", formatBytes(model.sizeBytes)],
323
325
  ]), { formatBorder: pc.yellow });
@@ -325,11 +327,11 @@ function downloadedModelCard(num, model, caps) {
325
327
 
326
328
  function managedModelCard(num, model, backend) {
327
329
  return renderCard(`${num}. ${model.label}`, renderRows([
328
- ["Status", statusText("info", `Local model via ${backend.label}`)],
330
+ ["Status", pc.dim(`Via ${backend.label}`)],
329
331
  ["Runs with", backend.label],
330
- ["Model ID", pc.cyan(model.id)],
332
+ ["Model ID", model.id],
331
333
  ...(model.quant ? [["Size/type", model.quant]] : []),
332
- ]), { formatBorder: pc.magenta });
334
+ ]), { formatBorder: pc.dim });
333
335
  }
334
336
 
335
337
  function modelCatalogItems({ profiles, newModels, managedItems }) {
@@ -410,19 +412,19 @@ async function printProfileDetails(profile) {
410
412
  const fileMissing = !isManaged && isProfileFileMissing(profile);
411
413
  const statusRow = fileMissing
412
414
  ? pc.red("File missing")
413
- : running ? pc.green("Running now") : pc.green("Ready to chat");
415
+ : running ? pc.green("Running now") : "Ready";
414
416
  console.log("\n" + renderSection("Model overview", renderRows([
415
417
  ["Name", pc.bold(profile.label)],
416
418
  ["Status", statusRow],
417
419
  ["Good for", fileMissing ? pc.red("Model file not found — remove or fix this setup") : humanCapabilitySummary(profile.capabilities ?? {})],
418
- ["Pi", piConfigured ? pc.green("Synced") : pc.yellow("Needs sync")],
419
- ["Server", fileMissing ? pc.red(profile.baseUrl) : pc.green(profile.baseUrl)],
420
+ ["Pi", piConfigured ? pc.dim("synced") : pc.yellow("Needs sync")],
421
+ ["Server", fileMissing ? pc.red(profile.baseUrl) : profile.baseUrl],
420
422
  ])));
421
423
 
422
424
  console.log("\n" + renderSection("Model details", renderRows([
423
- ["Setup ID", pc.cyan(profile.id)],
425
+ ["Setup ID", profile.id],
424
426
  ["Runs with", backend.label],
425
- ["Model alias", pc.cyan(profile.modelAlias)],
427
+ ["Model alias", profile.modelAlias],
426
428
  ...(profile.capabilities ? [["Detected", capabilitySummary(profile.capabilities)]] : []),
427
429
  ...(!isManaged ? [
428
430
  ["Local file", fileMissing ? pc.red(`${profile.modelPath} (not found)`) : profile.modelPath ?? "unknown"],
@@ -659,9 +661,9 @@ async function statusCommand() {
659
661
  if (running.length === 0) {
660
662
  console.log(renderCard("Status", renderRows([
661
663
  ["Running now", pc.dim("none")],
662
- ["Ready setups", profiles.length > 0 ? pc.green(String(profiles.length)) : pc.dim("none")],
663
- ["Next step", profiles.length > 0 ? "Run offgrid-ai to start chatting" : "Run offgrid-ai to set up a model"],
664
- ]), { formatBorder: profiles.length > 0 ? pc.cyan : pc.yellow }));
664
+ ["Ready setups", profiles.length > 0 ? String(profiles.length) : pc.dim("none")],
665
+ ["Next step", profiles.length > 0 ? "Run offgrid-ai to start chatting" : pc.yellow("Run offgrid-ai to set up a model")],
666
+ ]), { formatBorder: pc.dim }));
665
667
  return;
666
668
  }
667
669