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 +1 -1
- package/src/commands/models.mjs +4 -4
- package/src/model-presenters.mjs +22 -42
package/package.json
CHANGED
package/src/commands/models.mjs
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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);
|
package/src/model-presenters.mjs
CHANGED
|
@@ -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
|
|
23
|
-
return color ? color(
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
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 (
|
|
127
|
-
if (
|
|
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.
|
|
130
|
-
console.log(pc.dim(`
|
|
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
|
+
}
|