offgrid-ai 0.3.12 → 0.3.13
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 +68 -56
- package/src/command.mjs +21 -0
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { pc, formatBytes, renderRows, renderSection, startInteractive, createPro
|
|
|
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";
|
|
16
|
+
import { buildPrettyCommand } from "./command.mjs";
|
|
16
17
|
|
|
17
18
|
// ── Entry point ────────────────────────────────────────────────────────────
|
|
18
19
|
|
|
@@ -201,15 +202,21 @@ export async function mainFlow() {
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
// Pick what to do
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
205
|
+
while (true) {
|
|
206
|
+
const action = await prompt.choice("What next?", [
|
|
207
|
+
{ value: "run", label: "Run a model", hint: "Start server and launch Pi" },
|
|
208
|
+
...(profiles.length > 0 ? [{ value: "manage", label: "Manage profiles", hint: "Sync, remove, or inspect" }] : []),
|
|
209
|
+
{ value: "benchmark", label: "Benchmark", hint: "Run a benchmark prompt" },
|
|
210
|
+
], "run");
|
|
211
|
+
|
|
212
|
+
if (action === "run") return await pickAndRun(prompt, profiles, newModels, managedItems);
|
|
213
|
+
if (action === "manage") {
|
|
214
|
+
const result = await manageProfiles(prompt, profiles);
|
|
215
|
+
if (result === "back") continue;
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
if (action === "benchmark") return await benchmarkFlow(prompt, profiles);
|
|
219
|
+
}
|
|
213
220
|
} finally {
|
|
214
221
|
prompt.close();
|
|
215
222
|
}
|
|
@@ -384,58 +391,63 @@ async function runProfile(profile, options = {}) {
|
|
|
384
391
|
// ── Manage profiles ─────────────────────────────────────────────────────────
|
|
385
392
|
|
|
386
393
|
async function manageProfiles(prompt, profiles) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
394
|
+
while (true) {
|
|
395
|
+
const choices = profiles.map((p) => ({
|
|
396
|
+
value: p.id,
|
|
397
|
+
label: p.label,
|
|
398
|
+
hint: `${p.modelAlias} · ${p.baseUrl}`,
|
|
399
|
+
}));
|
|
400
|
+
choices.push({ value: "__back", label: "← Back" });
|
|
393
401
|
|
|
394
|
-
|
|
395
|
-
|
|
402
|
+
const selected = await prompt.choice("Which profile?", choices, choices[0].value);
|
|
403
|
+
if (selected === "__back") return "back";
|
|
396
404
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
405
|
+
const profile = await readProfile(selected);
|
|
406
|
+
const backend = backendFor(profile.backend);
|
|
407
|
+
const isManaged = backend.type === "managed-server";
|
|
408
|
+
const piConfigured = await hasPiModel(profile);
|
|
400
409
|
|
|
401
|
-
|
|
402
|
-
console.log("");
|
|
403
|
-
console.log(renderSection("Profile", renderRows([
|
|
404
|
-
["ID", pc.cyan(profile.id)],
|
|
405
|
-
["Label", pc.bold(profile.label)],
|
|
406
|
-
["Backend", backend.label],
|
|
407
|
-
["Endpoint", pc.green(profile.baseUrl)],
|
|
408
|
-
...(!isManaged ? [
|
|
409
|
-
["Model", profile.modelPath ?? "unknown"],
|
|
410
|
-
["MMProj", profile.mmprojPath ?? "none"],
|
|
411
|
-
["Memory", existsSync(profile.modelPath) ? formatBytes(statSync(profile.modelPath).size) : "unknown"],
|
|
412
|
-
] : []),
|
|
413
|
-
["Alias", pc.cyan(profile.modelAlias)],
|
|
414
|
-
["Pi", (await hasPiModel(profile)) ? pc.green("configured") : pc.yellow("not synced")],
|
|
415
|
-
])));
|
|
416
|
-
|
|
417
|
-
if (!isManaged && profile.commandArgv) {
|
|
410
|
+
// Show profile details
|
|
418
411
|
console.log("");
|
|
419
|
-
console.log(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
412
|
+
console.log(renderSection("Profile", renderRows([
|
|
413
|
+
["ID", pc.cyan(profile.id)],
|
|
414
|
+
["Label", pc.bold(profile.label)],
|
|
415
|
+
["Backend", backend.label],
|
|
416
|
+
["Endpoint", pc.green(profile.baseUrl)],
|
|
417
|
+
...(!isManaged ? [
|
|
418
|
+
["Model", profile.modelPath ?? "unknown"],
|
|
419
|
+
["MMProj", profile.mmprojPath ?? "none"],
|
|
420
|
+
["Memory", existsSync(profile.modelPath) ? formatBytes(statSync(profile.modelPath).size) : "unknown"],
|
|
421
|
+
] : []),
|
|
422
|
+
["Alias", pc.cyan(profile.modelAlias)],
|
|
423
|
+
["Pi", piConfigured ? pc.green("configured") : pc.yellow("not synced")],
|
|
424
|
+
])));
|
|
425
|
+
|
|
426
|
+
if (!isManaged && profile.commandArgv) {
|
|
427
|
+
console.log("");
|
|
428
|
+
console.log(pc.bold("llama-server command"));
|
|
429
|
+
console.log(pc.dim(buildPrettyCommand(profile)));
|
|
430
|
+
}
|
|
430
431
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
432
|
+
const action = await prompt.choice("Action", [
|
|
433
|
+
{ value: "sync", label: piConfigured ? `${pc.green("✓")} Pi config synced` : "Sync Pi config", hint: piConfigured ? "Already in ~/.pi/agent/models.json" : "Update ~/.pi/agent/models.json" },
|
|
434
|
+
{ value: "run", label: "Run", hint: "Start server + Pi" },
|
|
435
|
+
...(isManaged ? [] : [{ value: "server", label: "Server only", hint: "Start server, no harness" }]),
|
|
436
|
+
{ value: "remove", label: "Remove", hint: "Delete profile + Pi config" },
|
|
437
|
+
{ value: "__back", label: "← Back", hint: "Choose another profile" },
|
|
438
|
+
], "sync");
|
|
439
|
+
|
|
440
|
+
if (action === "__back") continue;
|
|
441
|
+
if (action === "sync") {
|
|
442
|
+
await syncPiConfig(profile);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (action === "run") return await runProfile(profile);
|
|
446
|
+
if (action === "server") return await runProfile(profile, { with: "server" });
|
|
447
|
+
if (action === "remove") {
|
|
448
|
+
await removeProfileInteractive(profile.id);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
439
451
|
}
|
|
440
452
|
}
|
|
441
453
|
|
package/src/command.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function buildPrettyCommand(profile, binary = "llama-server") {
|
|
2
|
+
const argv = profile.commandArgv ?? [];
|
|
3
|
+
const lines = [`${quoteShell(binary)} \\`];
|
|
4
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5
|
+
const arg = argv[i];
|
|
6
|
+
const next = argv[i + 1];
|
|
7
|
+
const hasValue = arg.startsWith("--") && next && !next.startsWith("--");
|
|
8
|
+
if (hasValue) {
|
|
9
|
+
lines.push(` ${arg} ${quoteShell(next)}${i + 2 < argv.length ? " \\" : ""}`);
|
|
10
|
+
i += 1;
|
|
11
|
+
} else {
|
|
12
|
+
lines.push(` ${arg}${i + 1 < argv.length ? " \\" : ""}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function quoteShell(value) {
|
|
19
|
+
const text = String(value);
|
|
20
|
+
return /^[A-Za-z0-9_/@%+=:,.-]+$/u.test(text) ? text : `'${text.replace(/'/gu, `'"'"'`)}'`;
|
|
21
|
+
}
|