offgrid-ai 0.6.4 → 0.6.6

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.4",
3
+ "version": "0.6.6",
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/backends.mjs CHANGED
@@ -117,16 +117,18 @@ async function scanOmlxModels() {
117
117
  if (!response.ok) return [];
118
118
  const body = await response.json();
119
119
  if (!Array.isArray(body?.data)) return [];
120
- return body.data.map((model) => ({
121
- id: model.id,
122
- label: omlxLabel(model.id),
123
- aliasSuggestion: model.id,
124
- sizeBytes: 0,
125
- quant: null,
126
- family: null,
127
- backend: "omlx",
128
- source: "omlx",
129
- })).sort((a, b) => a.label.localeCompare(b.label));
120
+ return body.data
121
+ .filter((model) => isChatOmlxModel(model))
122
+ .map((model) => ({
123
+ id: model.id,
124
+ label: omlxLabel(model.id),
125
+ aliasSuggestion: model.id,
126
+ sizeBytes: 0,
127
+ quant: null,
128
+ family: null,
129
+ backend: "omlx",
130
+ source: "omlx",
131
+ })).sort((a, b) => a.label.localeCompare(b.label));
130
132
  } catch {
131
133
  return [];
132
134
  }
@@ -141,6 +143,14 @@ function isLocalOllamaModel(model) {
141
143
  return true;
142
144
  }
143
145
 
146
+ function isChatOmlxModel(model) {
147
+ if (typeof model?.id !== "string" || !model.id.trim()) return false;
148
+ const type = String(model.type ?? model.model_type ?? "").toLowerCase();
149
+ if (["embedding", "embeddings", "reranker", "tool", "converter"].includes(type)) return false;
150
+ if (Object.hasOwn(model, "max_model_len") && model.max_model_len === null) return false;
151
+ return true;
152
+ }
153
+
144
154
  function ollamaLabel(name) {
145
155
  return name.replace(/[-_]/g, " ").replace(/^gemma\b/i, "Gemma").replace(/^qwen/i, "Qwen");
146
156
  }
package/src/cli.mjs CHANGED
@@ -284,7 +284,7 @@ function optionTag(text, color, width) {
284
284
  function optionStatusTag(kind) {
285
285
  const statuses = {
286
286
  running: ["RUNNING", pc.green],
287
- ready: ["SET UP", pc.green],
287
+ ready: ["READY", pc.green],
288
288
  missing: ["FILE MISSING", pc.red],
289
289
  setup: ["NEEDS SETUP", pc.yellow],
290
290
  };
@@ -307,11 +307,7 @@ function optionLabel({ status, source, name, details = [] }) {
307
307
  return [status, source, pc.bold(name), ...details].filter(Boolean).join(OPTION_SEPARATOR);
308
308
  }
309
309
 
310
- function optionHint(parts) {
311
- return parts.filter(Boolean).join(" · ");
312
- }
313
-
314
- function modelSelectOption(item, { runningProfilesNow, drafters }) {
310
+ function modelSelectOption(item, { runningProfilesNow }) {
315
311
  if (item.type === "profile") {
316
312
  const { profile } = item;
317
313
  const backend = backendFor(profile.backend);
@@ -319,41 +315,27 @@ function modelSelectOption(item, { runningProfilesNow, drafters }) {
319
315
  const fileMissing = item.fileMissing;
320
316
  const status = optionStatusTag(fileMissing ? "missing" : running ? "running" : "ready");
321
317
  const source = optionSourceTag(profile.backend, backend.label);
322
- const caps = profile.capabilities ?? {};
323
- let mtpLabel;
324
- if (profile.drafterPath) mtpLabel = pc.green("MTP on");
325
- else if (drafters ? matchDrafter(profile.modelPath, drafters) : null) mtpLabel = pc.yellow("MTP available");
326
- else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP needs drafter");
327
- const ctxLabel = profile.flags?.ctxSize ? pc.dim(`${(profile.flags.ctxSize / 1000).toFixed(0)}k ctx`) : null;
328
- const label = optionLabel({ status, source, name: profile.label, details: [ctxLabel, mtpLabel] });
329
- return { value: itemKey(item), label, hint: optionHint([profile.modelAlias, humanCapabilitySummary(caps), profile.baseUrl]) };
318
+ const label = optionLabel({ status, source, name: profile.label });
319
+ return { value: itemKey(item), label };
330
320
  }
331
321
  if (item.type === "new") {
332
- const { model, drafter } = item;
333
- const caps = detectCapabilities(model.path, model.mmprojPath);
334
- const mtpAvailable = caps.mtp || Boolean(drafter);
335
- let mtpLabel;
336
- if (mtpAvailable) mtpLabel = pc.green("MTP available");
337
- else if (caps.architecture === "gemma4") mtpLabel = pc.yellow("MTP needs drafter");
322
+ const { model } = item;
338
323
  const label = optionLabel({
339
324
  status: optionStatusTag("setup"),
340
325
  source: optionSourceTag("gguf", "GGUF file"),
341
326
  name: model.label,
342
- details: [pc.dim(formatBytes(model.sizeBytes)), mtpLabel],
343
327
  });
344
- return { value: itemKey(item), label, hint: optionHint([basename(model.path), humanCapabilitySummary(caps), model.quant]) };
328
+ return { value: itemKey(item), label };
345
329
  }
346
330
  // managed
347
331
  const { model, backendId } = item;
348
332
  const backend = BACKENDS[backendId];
349
- const details = [model.quant ? pc.dim(model.quant) : null, model.sizeBytes ? pc.dim(formatBytes(model.sizeBytes)) : null];
350
333
  const label = optionLabel({
351
334
  status: optionStatusTag("setup"),
352
335
  source: optionSourceTag(backendId, backend.label),
353
336
  name: model.label,
354
- details,
355
337
  });
356
- return { value: itemKey(item), label, hint: optionHint([model.id, "available through local backend"]) };
338
+ return { value: itemKey(item), label };
357
339
  }
358
340
 
359
341
  function actionsForItem(item) {
package/src/profiles.mjs CHANGED
@@ -150,6 +150,7 @@ export async function createProfileFromModel(model, backendId, drafterPath) {
150
150
  backend,
151
151
  providerId: backend,
152
152
  modelAlias: model.aliasSuggestion,
153
+ source: model.source,
153
154
  modelPath: model.path,
154
155
  mmprojPath: model.mmprojPath,
155
156
  drafterPath: drafterPath ?? null,
package/src/scan.mjs CHANGED
@@ -158,6 +158,7 @@ function quantFromName(name) {
158
158
  return name.match(/(Q\d_K_[A-Z]+|Q\d_[01]|UD-[A-Z0-9_]+)/)?.[1];
159
159
  }
160
160
 
161
+
161
162
  function safeReadGgufMetadata(path) {
162
163
  try {
163
164
  return readGgufMetadata(path);