offgrid-ai 0.6.9 → 0.6.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
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",
@@ -6,7 +6,7 @@ import { syncPiConfig, removeFromPiConfig } from "../harness-pi.mjs";
6
6
  import { configureLocalProfile } from "../profile-setup.mjs";
7
7
  import { pc, startInteractive, createPrompt } from "../ui.mjs";
8
8
  import { buildCatalogItems, createManagedProfile, itemKey, loadModelCatalog, normalizeCatalog } from "../model-catalog.mjs";
9
- import { modelSelectOption, printGgufModelDetails, printManagedModelDetails, printWorkspaceHeader, printBenchmarkLine, printTableHeader, printTableFooter, printProfileDetails } from "../model-presenters.mjs";
9
+ import { modelSelectOption, modelNameWidth, printGgufModelDetails, printManagedModelDetails, printWorkspaceHeader, printBenchmarkLine, printProfileDetails } from "../model-presenters.mjs";
10
10
  import { runProfile } from "./run.mjs";
11
11
 
12
12
  export async function modelsCommand(argv) {
@@ -43,15 +43,15 @@ export async function modelCommandCenter(initialCatalog) {
43
43
  }
44
44
  printWorkspaceHeader(normalized, runningProfilesNow);
45
45
  await printBenchmarkLine();
46
- printTableHeader();
46
+
47
+ const nameWidth = modelNameWidth(allItems);
47
48
 
48
49
  const prompt = createPrompt();
49
50
  try {
50
- const selected = await prompt.choice("Select a model", allItems.map((item) => modelSelectOption(item, { runningProfilesNow })));
51
+ const selected = await prompt.choice("Select a model", allItems.map((item) => modelSelectOption(item, { runningProfilesNow, nameWidth })));
51
52
  if (!selected) return;
52
53
  const item = allItems.find((candidate) => itemKey(candidate) === selected);
53
54
  if (!item) return;
54
- printTableFooter();
55
55
 
56
56
  const actions = actionsForItem(item);
57
57
  const action = await prompt.choice(item.label, actions, actions[0].value);
@@ -12,15 +12,13 @@ import { findBenchmarkRepo } from "./benchmark.mjs";
12
12
  const OPTION_SEPARATOR = pc.dim(" │ ");
13
13
  const OPTION_STATUS_WIDTH = 10;
14
14
  const OPTION_SOURCE_WIDTH = 14;
15
- const OPTION_MODEL_WIDTH = 26;
16
- const OPTION_CTX_WIDTH = 5;
17
15
 
18
16
  const { stripVTControlCharacters } = await import("node:util");
19
17
 
20
18
  function optionPad(text, color, width) {
21
19
  const visible = stripVTControlCharacters(String(text)).length;
22
- const padded = String(text).padEnd(width);
23
- return color ? color(padded.slice(0, padded.length - Math.max(0, visible - width))) : padded;
20
+ const padding = Math.max(1, width - visible);
21
+ return (color ? color(String(text)) : String(text)) + " ".repeat(padding);
24
22
  }
25
23
 
26
24
  function optionStatusTag(kind) {
@@ -69,11 +67,18 @@ function optionSizeLabel(item) {
69
67
  return "—";
70
68
  }
71
69
 
72
- function optionLabel({ status, source, name, ctx, size }) {
73
- return [status, source, pc.bold(optionPad(name, null, OPTION_MODEL_WIDTH)), ctx, pc.dim(size)].filter(Boolean).join(OPTION_SEPARATOR);
70
+ export function modelNameWidth(items) {
71
+ const maxName = Math.max(...items.map((item) =>
72
+ stripVTControlCharacters(item.label ?? item.model?.label ?? item.profile?.label ?? "").length,
73
+ ));
74
+ return Math.max(20, maxName + 2);
74
75
  }
75
76
 
76
- export function modelSelectOption(item, { runningProfilesNow }) {
77
+ function optionLabel({ status, source, name, ctx, size, nameWidth }) {
78
+ return [status, source, pc.bold(optionPad(name, null, nameWidth)), ctx, pc.dim(size)].filter(Boolean).join(OPTION_SEPARATOR);
79
+ }
80
+
81
+ export function modelSelectOption(item, { runningProfilesNow, nameWidth }) {
77
82
  if (item.type === "profile") {
78
83
  const backend = backendFor(item.profile.backend);
79
84
  const running = runningProfilesNow.some((profile) => profile.id === item.profile.id);
@@ -83,6 +88,7 @@ export function modelSelectOption(item, { runningProfilesNow }) {
83
88
  status: optionStatusTag(item.fileMissing ? "missing" : running ? "running" : "ready"),
84
89
  source: optionSourceTag(item.profile.backend, backend.label),
85
90
  name: item.profile.label,
91
+ nameWidth,
86
92
  ctx: optionCtxLabel(item),
87
93
  size: optionSizeLabel(item),
88
94
  }),
@@ -95,6 +101,7 @@ export function modelSelectOption(item, { runningProfilesNow }) {
95
101
  status: optionStatusTag("setup"),
96
102
  source: optionSourceTag("gguf", "GGUF file"),
97
103
  name: item.model.label,
104
+ nameWidth,
98
105
  ctx: optionCtxLabel(item),
99
106
  size: optionSizeLabel(item),
100
107
  }),
@@ -107,6 +114,7 @@ export function modelSelectOption(item, { runningProfilesNow }) {
107
114
  status: optionStatusTag("setup"),
108
115
  source: optionSourceTag(item.backendId, backend.label),
109
116
  name: item.model.label,
117
+ nameWidth,
110
118
  ctx: optionCtxLabel(item),
111
119
  size: optionSizeLabel(item),
112
120
  }),
@@ -121,13 +129,13 @@ export function printWorkspaceHeader(normalized, runningProfilesNow) {
121
129
  const setupCount = normalized.newModels.length + normalized.managedItems.length;
122
130
 
123
131
  const countParts = [];
124
- if (readyCount > 0) countParts.push(`${readyCount} ready`);
125
132
  if (runningCount > 0) countParts.push(`${runningCount} running`);
126
- if (missingCount > 0) countParts.push(`${missingCount} missing`);
127
- if (setupCount > 0) countParts.push(`${setupCount} need setup`);
133
+ if (readyCount > 0) countParts.push(pc.green(`${readyCount} model${readyCount === 1 ? "" : "s"} ready`));
134
+ if (missingCount > 0) countParts.push(pc.red(`${missingCount} model${missingCount === 1 ? "" : "s"} missing`));
135
+ if (setupCount > 0) countParts.push(pc.yellow(`${setupCount} model${setupCount === 1 ? "" : "s"} need${setupCount === 1 ? "s" : ""} setup`));
128
136
 
129
- console.log(pc.bold(" offgrid-ai"));
130
- console.log(pc.dim(` ${countParts.join(" · ")} · ${DATA_DIR}`));
137
+ console.log(pc.dim(` ${countParts.join(" · ")}`));
138
+ console.log(pc.dim(` Profiles: ${DATA_DIR}`));
131
139
  }
132
140
 
133
141
  export async function printBenchmarkLine() {
@@ -139,34 +147,6 @@ export async function printBenchmarkLine() {
139
147
  }
140
148
  }
141
149
 
142
- function tableWidth() {
143
- const header = [
144
- optionPad("Status", null, OPTION_STATUS_WIDTH),
145
- optionPad("Backend", null, OPTION_SOURCE_WIDTH),
146
- optionPad("Model", null, OPTION_MODEL_WIDTH),
147
- optionPad("Ctx", null, OPTION_CTX_WIDTH),
148
- "Size",
149
- ].join(" \u2502 ");
150
- return stripVTControlCharacters(header).length;
151
- }
152
-
153
- export function printTableHeader() {
154
- console.log("");
155
- const header = [
156
- optionPad("Status", pc.dim, OPTION_STATUS_WIDTH),
157
- optionPad("Backend", pc.dim, OPTION_SOURCE_WIDTH),
158
- optionPad("Model", pc.dim, OPTION_MODEL_WIDTH),
159
- optionPad("Ctx", pc.dim, OPTION_CTX_WIDTH),
160
- pc.dim("Size"),
161
- ].join(OPTION_SEPARATOR);
162
- console.log(header);
163
- console.log(pc.dim("─".repeat(tableWidth())));
164
- }
165
-
166
- export function printTableFooter() {
167
- console.log(pc.dim("─".repeat(tableWidth())));
168
- }
169
-
170
150
  export async function printProfileDetails(profile) {
171
151
  const backend = backendFor(profile.backend);
172
152
  const isManaged = backend.type === "managed-server";
@@ -207,7 +187,7 @@ export async function printProfileDetails(profile) {
207
187
 
208
188
  export function printGgufModelDetails(model, drafter) {
209
189
  const { caps, parts } = ggufDetailParts(model, drafter);
210
- parts.push(formatBytes(model.sizeBytes));
190
+ parts.push(formatBytes(model.model.sizeBytes));
211
191
  console.log("\n" + renderSection("Downloaded model", renderRows([
212
192
  ["Name", pc.bold(model.label)],
213
193
  ["Status", pc.yellow("Needs one-time setup")],
@@ -231,4 +211,4 @@ export function printManagedModelDetails(model, backend) {
231
211
  ["Quant", model.quant ?? "unknown"],
232
212
  ["Family", model.family ?? "unknown"],
233
213
  ])));
234
- }
214
+ }