ollanet 0.6.1 → 0.6.3
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/README.md +8 -5
- package/dist/argv.d.ts +5 -0
- package/dist/argv.js +13 -0
- package/dist/bench-suite.d.ts +8 -1
- package/dist/bench-suite.js +15 -8
- package/dist/bench.d.ts +1 -0
- package/dist/bench.js +92 -34
- package/dist/chats.d.ts +1 -0
- package/dist/chats.js +9 -7
- package/dist/cli.js +29 -1
- package/dist/compare.d.ts +1 -0
- package/dist/compare.js +9 -7
- package/dist/help.d.ts +7 -0
- package/dist/help.js +51 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +24 -1
- package/dist/ollama-chat.d.ts +2 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +9 -7
- package/dist/ps.d.ts +12 -0
- package/dist/ps.js +32 -9
- package/dist/pull.d.ts +1 -0
- package/dist/pull.js +9 -7
- package/dist/rm.d.ts +1 -0
- package/dist/rm.js +9 -7
- package/dist/scan.d.ts +1 -0
- package/dist/scan.js +24 -0
- package/dist/show.d.ts +1 -0
- package/dist/show.js +9 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -192,12 +192,13 @@ optimizeDeps: { exclude: ["ollanet"] },
|
|
|
192
192
|
| `ollanet pull <machine> <model>` | Ask that host to download / update a library model |
|
|
193
193
|
| `ollanet show <machine> <model>` | Inspect Modelfile / params / capabilities (`[tuned]` when it looks like Finetuna) |
|
|
194
194
|
| `ollanet rm <machine> <model> --yes` | Delete a model from that host’s disk |
|
|
195
|
-
| `ollanet ps [machine]` | Models loaded in VRAM (omit machine = every discovered host) |
|
|
195
|
+
| `ollanet ps [machine]` | Models loaded in VRAM, with CPU/GPU % (omit machine = every discovered host) |
|
|
196
196
|
| `ollanet prompt …` | Send a prompt / continue a chat (`--file` for `.txt` / `.md`) |
|
|
197
197
|
| `ollanet compare …` | Same prompt on 2–5 models; summary + `compares/<id>.md` |
|
|
198
198
|
| `ollanet chats` | List or inspect saved transcripts |
|
|
199
199
|
| `ollanet bench …` | Benchmark models for tok/s + lightweight quality checks |
|
|
200
200
|
| `ollanet mcp` | Stdio [MCP](https://modelcontextprotocol.io) server for agents |
|
|
201
|
+
| `ollanet help [command]` | This overview, or one command’s flags (`ollanet help bench`, `ollanet bench help`) |
|
|
201
202
|
|
|
202
203
|
### `scan` options
|
|
203
204
|
|
|
@@ -339,10 +340,12 @@ ollanet bench localhost gemma4:12b --hot --runs 5
|
|
|
339
340
|
```
|
|
340
341
|
|
|
341
342
|
Runs a built-in suite (`quick` default, or `--suite full`) against one or more **completion**
|
|
342
|
-
models: instruction checks once, throughput
|
|
343
|
+
models: instruction checks once, 256-token throughput repeated (`--runs`, default 3) with
|
|
343
344
|
pinned `num_predict` / `seed` / `temperature`. Reports median tok/s + spread.
|
|
344
|
-
`--hot` discards the first
|
|
345
|
-
repeats are already warm (Finetuna steady-state).
|
|
345
|
+
`--hot` discards the first 256-token run and leaves models loaded so the counted
|
|
346
|
+
repeats are already warm (Finetuna steady-state). `--suite full` also runs json/reason
|
|
347
|
+
checks and **one** 1024-token prose generation (`tok/s long`) — closer to a long chat
|
|
348
|
+
reply. Under `--all`,
|
|
346
349
|
skips non-completion models (embeddings); multimodal `completion`+`vision` models stay in
|
|
347
350
|
(early-stop / pass_rate handle text-suite failures). Use `--exclude-vision` to drop them.
|
|
348
351
|
Saves `benchmarks/<id>.json` (or `~/.ollanet/benchmarks/` when installed).
|
|
@@ -350,7 +353,7 @@ See [`docs/bench-spec.md`](docs/bench-spec.md) for measurement rules.
|
|
|
350
353
|
|
|
351
354
|
`quick` has only two scored checks (`ping`, `math`), so `pass` is a coarse
|
|
352
355
|
liveness/sanity gauge (`0/2`, `1/2`, `2/2`) — not “50% broken.” Use `--suite full`
|
|
353
|
-
when you care more about the quality signal.
|
|
356
|
+
when you care more about the quality signal or want the long tok/s column.
|
|
354
357
|
|
|
355
358
|
## Discovery
|
|
356
359
|
|
package/dist/argv.d.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { GenerateSettings } from "./config.js";
|
|
5
5
|
export type UsageFn = () => never;
|
|
6
|
+
export declare function isHelpFlag(arg: string): boolean;
|
|
7
|
+
/** Intentional help: stdout, exit 0. */
|
|
8
|
+
export declare function printHelp(text: string): never;
|
|
9
|
+
/** Usage error: stderr, exit 1. */
|
|
10
|
+
export declare function failUsage(text: string): never;
|
|
6
11
|
export declare function takeValue(args: string[], flag: string, usage: UsageFn): string;
|
|
7
12
|
/** `--name value` or `--name=value`. Returns undefined if `arg` is not this flag. */
|
|
8
13
|
export declare function takeFlag(arg: string, name: string, args: string[], usage: UsageFn): string | undefined;
|
package/dist/argv.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
export function isHelpFlag(arg) {
|
|
2
|
+
return arg === "--help" || arg === "-h";
|
|
3
|
+
}
|
|
4
|
+
/** Intentional help: stdout, exit 0. */
|
|
5
|
+
export function printHelp(text) {
|
|
6
|
+
process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
7
|
+
process.exit(0);
|
|
8
|
+
}
|
|
9
|
+
/** Usage error: stderr, exit 1. */
|
|
10
|
+
export function failUsage(text) {
|
|
11
|
+
console.error(text);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
1
14
|
export function takeValue(args, flag, usage) {
|
|
2
15
|
const value = args.shift();
|
|
3
16
|
if (!value) {
|
package/dist/bench-suite.d.ts
CHANGED
|
@@ -3,14 +3,21 @@ export interface BenchCase {
|
|
|
3
3
|
id: string;
|
|
4
4
|
role: CaseRole;
|
|
5
5
|
prompt: string;
|
|
6
|
-
/** Cap for this case
|
|
6
|
+
/** Cap for this case. Peak throughput uses `--num-predict` when omitted. */
|
|
7
7
|
num_predict?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Throughput attempts. Omit to use `--runs` (and `--hot` discard).
|
|
10
|
+
* Set `1` for a single large shot that does not repeat.
|
|
11
|
+
*/
|
|
12
|
+
repeats?: number;
|
|
8
13
|
format?: "json";
|
|
9
14
|
check?: (content: string) => {
|
|
10
15
|
ok: boolean;
|
|
11
16
|
detail?: string;
|
|
12
17
|
};
|
|
13
18
|
}
|
|
19
|
+
/** Pinned length for `full`'s prose throughput case (`throughput_long`). */
|
|
20
|
+
export declare const LONG_THROUGHPUT_NUM_PREDICT = 1024;
|
|
14
21
|
export type SuiteName = "quick" | "full";
|
|
15
22
|
export declare function getSuiteCases(suite: SuiteName): BenchCase[];
|
|
16
23
|
export declare function suiteRevision(suite: SuiteName): string;
|
package/dist/bench-suite.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Built-in ollanet bench suites, checkers, and revision hashes.
|
|
3
3
|
*/
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
+
/** Pinned length for `full`'s prose throughput case (`throughput_long`). */
|
|
6
|
+
export const LONG_THROUGHPUT_NUM_PREDICT = 1024;
|
|
5
7
|
const MATH_ANSWER = 323;
|
|
6
8
|
const REASON_ANSWER = 42;
|
|
7
9
|
function lastTokenNormalized(content) {
|
|
@@ -104,18 +106,23 @@ const FULL_EXTRA = [
|
|
|
104
106
|
{
|
|
105
107
|
id: "reason",
|
|
106
108
|
role: "check",
|
|
107
|
-
prompt: "
|
|
108
|
-
"How many apples are left? Reply with only the final number.",
|
|
109
|
+
prompt: "Start with 40. Add 7. Subtract 5. Reply with only the final number.",
|
|
109
110
|
num_predict: 48,
|
|
110
|
-
// 10 - 3 + 5 - 6 = 6... wait: gave away 2*3=6, so 10-3+5-6=6. Spec said 42 as placeholder.
|
|
111
|
-
// Use a problem that clearly ends at 42:
|
|
112
|
-
// Actually rewrite for 42: "Start with 40. Add 7. Subtract 5. Reply with only the number." => 42
|
|
113
111
|
check: checkExpectedInt(REASON_ANSWER),
|
|
114
112
|
},
|
|
113
|
+
{
|
|
114
|
+
id: "throughput_long",
|
|
115
|
+
role: "throughput",
|
|
116
|
+
// Chat-shaped tokens (markdown, lists, a table) at a long pin. Anti-EOS
|
|
117
|
+
// so done_reason=length; one shot (not --runs) so full stays usable.
|
|
118
|
+
prompt: "Write a detailed trade-off briefing for a product team choosing a web UI stack. " +
|
|
119
|
+
"Compare at least two options. Use markdown headings, bullet lists, and at least one table. " +
|
|
120
|
+
"Keep adding sections (testing, hiring, migration, failure modes, cost) without wrapping up. " +
|
|
121
|
+
"Do not write a closing recommendation that ends the piece. Do not stop early.",
|
|
122
|
+
num_predict: LONG_THROUGHPUT_NUM_PREDICT,
|
|
123
|
+
repeats: 1,
|
|
124
|
+
},
|
|
115
125
|
];
|
|
116
|
-
// Fix reason prompt to match REASON_ANSWER
|
|
117
|
-
FULL_EXTRA[FULL_EXTRA.length - 1].prompt =
|
|
118
|
-
"Start with 40. Add 7. Subtract 5. Reply with only the final number.";
|
|
119
126
|
export function getSuiteCases(suite) {
|
|
120
127
|
if (suite === "full")
|
|
121
128
|
return [...QUICK_CASES, ...FULL_EXTRA];
|
package/dist/bench.d.ts
CHANGED
package/dist/bench.js
CHANGED
|
@@ -2,46 +2,55 @@
|
|
|
2
2
|
* ollanet bench — speed + lightweight quality suite against Ollama models.
|
|
3
3
|
*/
|
|
4
4
|
import { defaultModelForHost, loadConfig, machineSettingsForHost, mergeSettings, settingsFromEnv, } from "./config.js";
|
|
5
|
-
import { comparabilityKey, getSuiteCases, median, suiteRevision, } from "./bench-suite.js";
|
|
5
|
+
import { comparabilityKey, getSuiteCases, LONG_THROUGHPUT_NUM_PREDICT, median, suiteRevision, } from "./bench-suite.js";
|
|
6
6
|
import { benchmarksDir, newBenchId, saveBenchmark } from "./bench-store.js";
|
|
7
7
|
import { readFile } from "node:fs/promises";
|
|
8
8
|
import { envInt, ollamaBaseUrl, shortName } from "./hosts.js";
|
|
9
|
-
import { consumeSettingsFlag, parseNumberFlag, takeFlag } from "./argv.js";
|
|
9
|
+
import { consumeSettingsFlag, failUsage, isHelpFlag, parseNumberFlag, printHelp, takeFlag } from "./argv.js";
|
|
10
10
|
import { resolveTarget } from "./target.js";
|
|
11
11
|
import { projectPath } from "./paths.js";
|
|
12
12
|
import { contextLengthForModel, isCompletionCapable, isVisionCapable, ollamaChat, ollamaShow, ollamaTags, ollamaUnload, ollamaVersion, ollamaPs, psHasModel, shouldWarnNoThinking, vramForModel, waitUntilUnloaded, } from "./ollama-chat.js";
|
|
13
13
|
const MODEL_COL = 34;
|
|
14
14
|
const BENCH_TIMEOUT_MS = envInt("OLLAMA_BENCH_TIMEOUT_MS", 60_000);
|
|
15
15
|
const UNLOAD_WAIT_MS = envInt("OLLAMA_BENCH_UNLOAD_WAIT_MS", 60_000);
|
|
16
|
-
function
|
|
17
|
-
|
|
16
|
+
export function helpText() {
|
|
17
|
+
return `Usage:
|
|
18
18
|
ollanet bench <machine|ip> [model...] [options]
|
|
19
19
|
ollanet bench <machine|ip> --all [options]
|
|
20
20
|
|
|
21
21
|
Examples:
|
|
22
22
|
ollanet bench studio gemma3:12b --runs 5
|
|
23
23
|
ollanet bench studio gemma3:12b --hot --runs 5
|
|
24
|
+
ollanet help bench
|
|
25
|
+
|
|
26
|
+
Fixed suite (not compare). Checks run once; 256-token throughput repeats --runs times.
|
|
27
|
+
Median tok/s excludes early-stops. Unload is between models, not between repeats.
|
|
28
|
+
full adds json/reason checks plus one 1024-token prose generation (tok/s long).
|
|
24
29
|
|
|
25
30
|
Options:
|
|
26
31
|
--all Every completion-capable model from /api/tags
|
|
27
32
|
--exclude-vision With --all, skip models that advertise vision
|
|
28
33
|
--suite quick|full Prompt suite (default quick)
|
|
29
|
-
--runs <n>
|
|
30
|
-
--hot Discard first throughput run; keep models loaded
|
|
31
|
-
--warmup
|
|
34
|
+
--runs <n> Counted 256-token throughput repeats (default 3)
|
|
35
|
+
--hot Discard first 256-token throughput run; keep models loaded
|
|
36
|
+
--warmup Short discarded call before timed cases (default on)
|
|
37
|
+
--no-warmup Skip the short warmup (--hot implies this)
|
|
32
38
|
--cold-load Measure cold load via unload + /api/ps
|
|
33
|
-
--num-predict <n> Pin throughput length (default 256)
|
|
34
|
-
--num-ctx <n>
|
|
35
|
-
--keep-alive <value>
|
|
36
|
-
--think / --no-think
|
|
37
|
-
--judge
|
|
39
|
+
--num-predict <n> Pin 256-token throughput length (default 256; not the long case)
|
|
40
|
+
--num-ctx <n> Context window override
|
|
41
|
+
--keep-alive <value> Keep model loaded (e.g. 5m, 0, -1)
|
|
42
|
+
--think / --no-think Thinking tokens (default off)
|
|
43
|
+
--judge Second pass: score check answers 1–5
|
|
44
|
+
--judge-model <name> Required with --judge (no default)
|
|
38
45
|
--json Machine-readable stdout
|
|
39
|
-
--save / --no-save
|
|
40
|
-
--fail-fast
|
|
46
|
+
--save / --no-save Persist under benchmarks/ (default on)
|
|
47
|
+
--fail-fast Stop remaining models after the first hard error
|
|
41
48
|
--fail-on-error Exit 2 if any model/case errored
|
|
42
49
|
|
|
43
|
-
See docs/bench-spec.md for measurement rules
|
|
44
|
-
|
|
50
|
+
See docs/bench-spec.md for measurement rules.`;
|
|
51
|
+
}
|
|
52
|
+
function usage() {
|
|
53
|
+
failUsage(helpText());
|
|
45
54
|
}
|
|
46
55
|
function isLoopback(host) {
|
|
47
56
|
return (host.source === "localhost" ||
|
|
@@ -57,6 +66,26 @@ function resolveModelName(tags, query) {
|
|
|
57
66
|
const prefix = tags.find((t) => t.name.toLowerCase().startsWith(`${q}:`) || q.startsWith(`${t.name.toLowerCase()}:`));
|
|
58
67
|
return prefix?.name;
|
|
59
68
|
}
|
|
69
|
+
function pinnedNumPredict(caseDef, throughputNumPredict) {
|
|
70
|
+
if (caseDef.role === "throughput") {
|
|
71
|
+
return caseDef.num_predict ?? throughputNumPredict;
|
|
72
|
+
}
|
|
73
|
+
return caseDef.num_predict ?? 32;
|
|
74
|
+
}
|
|
75
|
+
/** At least the global timeout; longer pins need room to finish at ~8 tok/s. */
|
|
76
|
+
function attemptTimeoutMs(numPredict) {
|
|
77
|
+
return Math.max(BENCH_TIMEOUT_MS, Math.ceil(numPredict / 8) * 1000);
|
|
78
|
+
}
|
|
79
|
+
function caseReps(caseDef, runs, hot) {
|
|
80
|
+
if (caseDef.role !== "throughput")
|
|
81
|
+
return 1;
|
|
82
|
+
if (caseDef.repeats != null)
|
|
83
|
+
return caseDef.repeats;
|
|
84
|
+
return runs + (hot ? 1 : 0);
|
|
85
|
+
}
|
|
86
|
+
function discardFirstThroughput(caseDef, hot) {
|
|
87
|
+
return hot && caseDef.role === "throughput" && caseDef.repeats == null;
|
|
88
|
+
}
|
|
60
89
|
function tokPerSec(chunk) {
|
|
61
90
|
if (chunk.eval_count == null || chunk.eval_duration == null || chunk.eval_duration <= 0) {
|
|
62
91
|
return undefined;
|
|
@@ -90,7 +119,9 @@ function parseArgs(argv) {
|
|
|
90
119
|
const positional = [];
|
|
91
120
|
while (args.length > 0) {
|
|
92
121
|
const arg = args.shift();
|
|
93
|
-
if (arg
|
|
122
|
+
if (isHelpFlag(arg))
|
|
123
|
+
printHelp(helpText());
|
|
124
|
+
if (arg === "--")
|
|
94
125
|
usage();
|
|
95
126
|
if (arg === "--all") {
|
|
96
127
|
all = true;
|
|
@@ -275,9 +306,7 @@ async function runColdLoad(baseUrl, model, keepAlive) {
|
|
|
275
306
|
}
|
|
276
307
|
}
|
|
277
308
|
async function runCaseAttempt(opts) {
|
|
278
|
-
const numPredict = opts.caseDef.
|
|
279
|
-
? opts.throughputNumPredict
|
|
280
|
-
: (opts.caseDef.num_predict ?? 32);
|
|
309
|
+
const numPredict = pinnedNumPredict(opts.caseDef, opts.throughputNumPredict);
|
|
281
310
|
const settings = mergeSettings(opts.settings, {
|
|
282
311
|
temperature: 0,
|
|
283
312
|
seed: 0,
|
|
@@ -293,7 +322,7 @@ async function runCaseAttempt(opts) {
|
|
|
293
322
|
messages: [{ role: "user", content: opts.caseDef.prompt }],
|
|
294
323
|
stream: false,
|
|
295
324
|
writeStdout: false,
|
|
296
|
-
timeoutMs:
|
|
325
|
+
timeoutMs: attemptTimeoutMs(numPredict),
|
|
297
326
|
settings,
|
|
298
327
|
});
|
|
299
328
|
const wallMs = Date.now() - wall0;
|
|
@@ -363,12 +392,23 @@ async function runCaseAttempt(opts) {
|
|
|
363
392
|
function countedAttempts(attempts) {
|
|
364
393
|
return attempts.filter((a) => !a.discarded);
|
|
365
394
|
}
|
|
366
|
-
function
|
|
367
|
-
const
|
|
368
|
-
const counted = countedAttempts(throughput?.attempts ?? []);
|
|
395
|
+
function speedFrom(cases, id) {
|
|
396
|
+
const counted = countedAttempts(cases.find((c) => c.id === id)?.attempts ?? []);
|
|
369
397
|
const fullLen = counted.filter((a) => !a.error && !a.early_stop && a.tok_s != null);
|
|
370
398
|
const toks = fullLen.map((a) => a.tok_s);
|
|
371
|
-
|
|
399
|
+
return {
|
|
400
|
+
median: median(toks) ?? null,
|
|
401
|
+
min: toks.length ? Math.min(...toks) : null,
|
|
402
|
+
max: toks.length ? Math.max(...toks) : null,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function summarizeModel(cases, cold, selfJudge) {
|
|
406
|
+
const peak = speedFrom(cases, "throughput");
|
|
407
|
+
const long = speedFrom(cases, "throughput_long");
|
|
408
|
+
const throughputAttempts = cases
|
|
409
|
+
.filter((c) => c.role === "throughput")
|
|
410
|
+
.flatMap((c) => countedAttempts(c.attempts));
|
|
411
|
+
const earlyStopCount = throughputAttempts.filter((a) => a.early_stop || (a.done_reason && a.done_reason !== "length")).length;
|
|
372
412
|
const checkAttempts = cases
|
|
373
413
|
.filter((c) => c.role === "check")
|
|
374
414
|
.flatMap((c) => c.attempts)
|
|
@@ -376,9 +416,12 @@ function summarizeModel(cases, cold, selfJudge) {
|
|
|
376
416
|
const passed = checkAttempts.filter((a) => a.quality?.pass).length;
|
|
377
417
|
const passRate = checkAttempts.length > 0 ? passed / checkAttempts.length : null;
|
|
378
418
|
return {
|
|
379
|
-
tok_s_median: median
|
|
380
|
-
tok_s_min:
|
|
381
|
-
tok_s_max:
|
|
419
|
+
tok_s_median: peak.median,
|
|
420
|
+
tok_s_min: peak.min,
|
|
421
|
+
tok_s_max: peak.max,
|
|
422
|
+
tok_s_long_median: long.median,
|
|
423
|
+
tok_s_long_min: long.min,
|
|
424
|
+
tok_s_long_max: long.max,
|
|
382
425
|
early_stop_count: earlyStopCount,
|
|
383
426
|
pass_rate: passRate,
|
|
384
427
|
load_ms: cold && cold.error == null && cold.load_ms > 0 ? cold.load_ms : cold?.error ? null : cold?.load_ms ?? null,
|
|
@@ -394,17 +437,22 @@ function printTable(machine, suite, runs, revision, compKey, models, skipped, co
|
|
|
394
437
|
lines.push("Cold-load: on");
|
|
395
438
|
if (hot)
|
|
396
439
|
lines.push("Hot: first throughput run discarded; models stay loaded");
|
|
440
|
+
if (suite === "full")
|
|
441
|
+
lines.push("full: tok/s long is one 1024-token prose generation");
|
|
397
442
|
lines.push("");
|
|
443
|
+
const showLong = suite === "full";
|
|
398
444
|
const header = coldLoad
|
|
399
445
|
? pad("Model", MODEL_COL) +
|
|
400
446
|
pad("tok/s (med)", 12) +
|
|
401
447
|
pad("spread", 12) +
|
|
448
|
+
(showLong ? pad("tok/s long", 12) : "") +
|
|
402
449
|
pad("load", 8) +
|
|
403
450
|
pad("pass", 8) +
|
|
404
451
|
"notes"
|
|
405
452
|
: pad("Model", MODEL_COL) +
|
|
406
453
|
pad("tok/s (med)", 12) +
|
|
407
454
|
pad("spread", 12) +
|
|
455
|
+
(showLong ? pad("tok/s long", 12) : "") +
|
|
408
456
|
pad("pass", 8) +
|
|
409
457
|
"notes";
|
|
410
458
|
lines.push(header);
|
|
@@ -438,6 +486,7 @@ function printTable(machine, suite, runs, revision, compKey, models, skipped, co
|
|
|
438
486
|
const spread = m.summary.tok_s_min != null && m.summary.tok_s_max != null
|
|
439
487
|
? `${m.summary.tok_s_min.toFixed(1)}–${m.summary.tok_s_max.toFixed(1)}`
|
|
440
488
|
: "—";
|
|
489
|
+
const tokLong = m.summary.tok_s_long_median != null ? m.summary.tok_s_long_median.toFixed(1) : "—";
|
|
441
490
|
const pass = m.summary.pass_rate != null
|
|
442
491
|
? `${Math.round(m.summary.pass_rate * checkDenom(m))}/${checkDenom(m)}`
|
|
443
492
|
: "—";
|
|
@@ -457,12 +506,14 @@ function printTable(machine, suite, runs, revision, compKey, models, skipped, co
|
|
|
457
506
|
? pad(m.name, MODEL_COL) +
|
|
458
507
|
pad(tok, 12) +
|
|
459
508
|
pad(spread, 12) +
|
|
509
|
+
(showLong ? pad(tokLong, 12) : "") +
|
|
460
510
|
pad(load, 8) +
|
|
461
511
|
pad(pass, 8) +
|
|
462
512
|
notes.join("; ")
|
|
463
513
|
: pad(m.name, MODEL_COL) +
|
|
464
514
|
pad(tok, 12) +
|
|
465
515
|
pad(spread, 12) +
|
|
516
|
+
(showLong ? pad(tokLong, 12) : "") +
|
|
466
517
|
pad(pass, 8) +
|
|
467
518
|
notes.join("; ");
|
|
468
519
|
lines.push(row);
|
|
@@ -575,10 +626,13 @@ export async function main() {
|
|
|
575
626
|
hot: parsed.hot,
|
|
576
627
|
});
|
|
577
628
|
const suiteCases = getSuiteCases(parsed.suite);
|
|
578
|
-
const throughputReps = parsed.runs + (parsed.hot ? 1 : 0);
|
|
579
629
|
const caseCount = selected.length *
|
|
580
|
-
|
|
581
|
-
|
|
630
|
+
suiteCases.reduce((n, c) => n + caseReps(c, parsed.runs, parsed.hot), 0);
|
|
631
|
+
const worstCaseMs = selected.length *
|
|
632
|
+
suiteCases.reduce((n, c) => {
|
|
633
|
+
const pin = pinnedNumPredict(c, parsed.throughputNumPredict);
|
|
634
|
+
return n + caseReps(c, parsed.runs, parsed.hot) * attemptTimeoutMs(pin);
|
|
635
|
+
}, 0);
|
|
582
636
|
if (!parsed.json) {
|
|
583
637
|
const skipBits = [
|
|
584
638
|
skipped.filter((s) => s.reason === "non-completion").length
|
|
@@ -593,7 +647,10 @@ export async function main() {
|
|
|
593
647
|
` × suite=${parsed.suite} × runs=${parsed.runs}` +
|
|
594
648
|
(parsed.hot ? " (hot: +1 discarded)" : ""));
|
|
595
649
|
console.error(`Cases: ~${caseCount} chat calls timeout: ${BENCH_TIMEOUT_MS}ms/case` +
|
|
596
|
-
|
|
650
|
+
(parsed.suite === "full"
|
|
651
|
+
? ` (long case ≥${attemptTimeoutMs(LONG_THROUGHPUT_NUM_PREDICT)}ms)`
|
|
652
|
+
: "") +
|
|
653
|
+
` worst-case ceiling: ~${Math.ceil(worstCaseMs / 60000)}m if every call times out`);
|
|
597
654
|
if (!parsed.hot && !isLoopback(host) && (selected.length >= 2 || parsed.coldLoad)) {
|
|
598
655
|
const bits = [
|
|
599
656
|
selected.length >= 2 ? "unloads each model before the next" : "",
|
|
@@ -655,8 +712,8 @@ export async function main() {
|
|
|
655
712
|
let modelError;
|
|
656
713
|
for (const caseDef of suiteCases) {
|
|
657
714
|
const attempts = [];
|
|
658
|
-
const discardFirst = parsed.hot
|
|
659
|
-
const reps = caseDef
|
|
715
|
+
const discardFirst = discardFirstThroughput(caseDef, parsed.hot);
|
|
716
|
+
const reps = caseReps(caseDef, parsed.runs, parsed.hot);
|
|
660
717
|
for (let i = 0; i < reps; i += 1) {
|
|
661
718
|
const discarded = discardFirst && i === 0;
|
|
662
719
|
const attempt = await runCaseAttempt({
|
|
@@ -748,6 +805,7 @@ export async function main() {
|
|
|
748
805
|
temperature: 0,
|
|
749
806
|
seed: 0,
|
|
750
807
|
throughput_num_predict: parsed.throughputNumPredict,
|
|
808
|
+
throughput_long_num_predict: parsed.suite === "full" ? LONG_THROUGHPUT_NUM_PREDICT : null,
|
|
751
809
|
num_ctx: baseSettings.num_ctx ?? null,
|
|
752
810
|
warmup: parsed.warmup,
|
|
753
811
|
hot: parsed.hot,
|
package/dist/chats.d.ts
CHANGED
package/dist/chats.js
CHANGED
|
@@ -7,16 +7,18 @@
|
|
|
7
7
|
* ollanet chats --id a1b2c3d4e5f6
|
|
8
8
|
*/
|
|
9
9
|
import { loadChat, listChats, responsesDir, } from "./chat-store.js";
|
|
10
|
-
import { takeFlag } from "./argv.js";
|
|
11
|
-
function
|
|
12
|
-
|
|
10
|
+
import { failUsage, isHelpFlag, printHelp, takeFlag } from "./argv.js";
|
|
11
|
+
export function helpText() {
|
|
12
|
+
return `Usage: ollanet chats [--json] [--id <hash>]
|
|
13
13
|
|
|
14
14
|
Lists transcripts in ${responsesDir()}.
|
|
15
15
|
|
|
16
16
|
Options:
|
|
17
17
|
--json Machine-readable output
|
|
18
|
-
--id <hash> Show one chat (messages included with --json, preview otherwise)
|
|
19
|
-
|
|
18
|
+
--id <hash> Show one chat (messages included with --json, preview otherwise)`;
|
|
19
|
+
}
|
|
20
|
+
function usage() {
|
|
21
|
+
failUsage(helpText());
|
|
20
22
|
}
|
|
21
23
|
function parseArgs(argv) {
|
|
22
24
|
const args = [...argv];
|
|
@@ -26,8 +28,8 @@ function parseArgs(argv) {
|
|
|
26
28
|
const arg = args.shift();
|
|
27
29
|
if (arg === "--")
|
|
28
30
|
continue;
|
|
29
|
-
if (arg
|
|
30
|
-
|
|
31
|
+
if (isHelpFlag(arg))
|
|
32
|
+
printHelp(helpText());
|
|
31
33
|
if (arg === "--json") {
|
|
32
34
|
json = true;
|
|
33
35
|
continue;
|
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ Usage:
|
|
|
28
28
|
ollanet chats [--json] [--id <hash>]
|
|
29
29
|
ollanet bench <machine|ip> [model...] [--all] [options]
|
|
30
30
|
ollanet mcp
|
|
31
|
+
ollanet help [command]
|
|
31
32
|
|
|
32
33
|
Examples:
|
|
33
34
|
ollanet scan
|
|
@@ -46,6 +47,7 @@ Examples:
|
|
|
46
47
|
ollanet bench localhost --all
|
|
47
48
|
ollanet bench localhost llama3.2:1b --runs 3
|
|
48
49
|
ollanet bench localhost gemma3:12b --hot --runs 5
|
|
50
|
+
ollanet help bench # flags for one command (also: ollanet bench help)
|
|
49
51
|
ollanet mcp # stdio MCP server for agents
|
|
50
52
|
|
|
51
53
|
Config: ~/.ollanet/config.json when installed, ./config.json in a checkout
|
|
@@ -58,10 +60,36 @@ async function main() {
|
|
|
58
60
|
// pnpm/npm often forward a bare `--`; ignore it.
|
|
59
61
|
const argv = process.argv.slice(2).filter((arg) => arg !== "--");
|
|
60
62
|
const cmd = argv[0];
|
|
61
|
-
if (!cmd || cmd === "-h" || cmd === "--help"
|
|
63
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
62
64
|
process.stdout.write(HELP);
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
67
|
+
if (cmd === "help") {
|
|
68
|
+
const topic = argv[1];
|
|
69
|
+
if (!topic || topic === "-h" || topic === "--help") {
|
|
70
|
+
process.stdout.write(HELP);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const { commandHelp, commandNames } = await import("./help.js");
|
|
74
|
+
const text = await commandHelp(topic);
|
|
75
|
+
if (!text) {
|
|
76
|
+
console.error(`Unknown command: ${topic}`);
|
|
77
|
+
console.error(`Try: ollanet help (commands: ${commandNames().join(", ")})`);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const rest = argv.slice(1);
|
|
85
|
+
const { isHelpRequest, commandHelp } = await import("./help.js");
|
|
86
|
+
if (isHelpRequest(rest)) {
|
|
87
|
+
const text = await commandHelp(cmd);
|
|
88
|
+
if (text) {
|
|
89
|
+
process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
65
93
|
if (cmd === "-v" || cmd === "--version" || cmd === "version") {
|
|
66
94
|
const { readFile } = await import("node:fs/promises");
|
|
67
95
|
const { projectPath } = await import("./paths.js");
|
package/dist/compare.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { type AppConfig, type GenerateSettings } from "./config.js";
|
|
8
8
|
/** Used when --prompt, --file, and stdin are all omitted. */
|
|
9
9
|
export declare const DEFAULT_COMPARE_PROMPT: string;
|
|
10
|
+
export declare function helpText(): string;
|
|
10
11
|
export interface CompareModelResult {
|
|
11
12
|
model: string;
|
|
12
13
|
content?: string;
|
package/dist/compare.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { configFromPartial, loadConfig, machineSettingsForHost, mergeSettings, settingsFromEnv, } from "./config.js";
|
|
8
8
|
import { envInt, ollamaBaseUrl, shortName } from "./hosts.js";
|
|
9
|
-
import { consumeSettingsFlag, takeFlag, takeValue } from "./argv.js";
|
|
9
|
+
import { consumeSettingsFlag, failUsage, isHelpFlag, printHelp, takeFlag, takeValue } from "./argv.js";
|
|
10
10
|
import { resolveTarget } from "./target.js";
|
|
11
11
|
import { ollamaChat, ollamaTags, ollamaUnload, waitUntilUnloaded, } from "./ollama-chat.js";
|
|
12
12
|
import { assemblePrompt, readPromptFile } from "./prompt-input.js";
|
|
@@ -23,8 +23,8 @@ function compareTimeoutMs() {
|
|
|
23
23
|
function unloadWaitMs() {
|
|
24
24
|
return envInt("OLLAMA_COMPARE_UNLOAD_WAIT_MS", 60_000);
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
26
|
+
export function helpText() {
|
|
27
|
+
return `Usage:
|
|
28
28
|
ollanet compare <machine> <model> <model> [model...]
|
|
29
29
|
ollanet compare <machine> <model> <model> [model...] --prompt <text>
|
|
30
30
|
ollanet compare <machine> <model> <model> [model...] --file <path.txt|.md>
|
|
@@ -49,8 +49,10 @@ Options:
|
|
|
49
49
|
--think / --no-think
|
|
50
50
|
--unload Unload each model before the next (fairer tok/s)
|
|
51
51
|
--no-save Do not write compares/*
|
|
52
|
-
--json Emit the full record on stdout
|
|
53
|
-
|
|
52
|
+
--json Emit the full record on stdout`;
|
|
53
|
+
}
|
|
54
|
+
function usage() {
|
|
55
|
+
failUsage(helpText());
|
|
54
56
|
}
|
|
55
57
|
function parseArgs(argv) {
|
|
56
58
|
const args = [...argv];
|
|
@@ -65,8 +67,8 @@ function parseArgs(argv) {
|
|
|
65
67
|
const arg = args.shift();
|
|
66
68
|
if (arg === "--")
|
|
67
69
|
continue;
|
|
68
|
-
if (arg
|
|
69
|
-
|
|
70
|
+
if (isHelpFlag(arg))
|
|
71
|
+
printHelp(helpText());
|
|
70
72
|
if (arg === "--json") {
|
|
71
73
|
json = true;
|
|
72
74
|
continue;
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-command help: `ollanet help bench` and `ollanet bench help`.
|
|
3
|
+
*/
|
|
4
|
+
export declare function canonicalCommand(name: string): string | undefined;
|
|
5
|
+
export declare function commandNames(): string[];
|
|
6
|
+
export declare function commandHelp(name: string): Promise<string | undefined>;
|
|
7
|
+
export declare function isHelpRequest(args: string[]): boolean;
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-command help: `ollanet help bench` and `ollanet bench help`.
|
|
3
|
+
*/
|
|
4
|
+
const LOADERS = {
|
|
5
|
+
scan: () => import("./scan.js"),
|
|
6
|
+
prompt: () => import("./prompt.js"),
|
|
7
|
+
chats: () => import("./chats.js"),
|
|
8
|
+
pull: () => import("./pull.js"),
|
|
9
|
+
show: () => import("./show.js"),
|
|
10
|
+
rm: () => import("./rm.js"),
|
|
11
|
+
ps: () => import("./ps.js"),
|
|
12
|
+
compare: () => import("./compare.js"),
|
|
13
|
+
bench: () => import("./bench.js"),
|
|
14
|
+
mcp: () => import("./mcp.js"),
|
|
15
|
+
};
|
|
16
|
+
const ALIASES = {
|
|
17
|
+
"ls-servers": "scan",
|
|
18
|
+
ask: "prompt",
|
|
19
|
+
chat: "prompt",
|
|
20
|
+
ls: "chats",
|
|
21
|
+
history: "chats",
|
|
22
|
+
download: "pull",
|
|
23
|
+
inspect: "show",
|
|
24
|
+
delete: "rm",
|
|
25
|
+
loaded: "ps",
|
|
26
|
+
vs: "compare",
|
|
27
|
+
h2h: "compare",
|
|
28
|
+
benchmark: "bench",
|
|
29
|
+
};
|
|
30
|
+
export function canonicalCommand(name) {
|
|
31
|
+
const key = name.trim().toLowerCase();
|
|
32
|
+
if (LOADERS[key])
|
|
33
|
+
return key;
|
|
34
|
+
return ALIASES[key];
|
|
35
|
+
}
|
|
36
|
+
export function commandNames() {
|
|
37
|
+
return Object.keys(LOADERS);
|
|
38
|
+
}
|
|
39
|
+
export async function commandHelp(name) {
|
|
40
|
+
const canon = canonicalCommand(name);
|
|
41
|
+
if (!canon)
|
|
42
|
+
return undefined;
|
|
43
|
+
const load = LOADERS[canon];
|
|
44
|
+
if (!load)
|
|
45
|
+
return undefined;
|
|
46
|
+
const mod = await load();
|
|
47
|
+
return mod.helpText();
|
|
48
|
+
}
|
|
49
|
+
export function isHelpRequest(args) {
|
|
50
|
+
return args.length === 1 && (args[0] === "help" || args[0] === "--help" || args[0] === "-h");
|
|
51
|
+
}
|
package/dist/mcp.d.ts
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -21,6 +21,7 @@ import { removeModel } from "./rm.js";
|
|
|
21
21
|
import { lastScan } from "./scan-store.js";
|
|
22
22
|
import { scanNetwork } from "./scan.js";
|
|
23
23
|
import { showModel } from "./show.js";
|
|
24
|
+
import { isHelpFlag, printHelp } from "./argv.js";
|
|
24
25
|
// Pin to the oldest widely-supported MCP revision. Clients may request a newer
|
|
25
26
|
// date; we still answer with this — legal negotiation, max compatibility.
|
|
26
27
|
const PROTOCOL_VERSION = "2024-11-05";
|
|
@@ -199,7 +200,7 @@ const TOOLS = [
|
|
|
199
200
|
{
|
|
200
201
|
name: "ollanet_ps",
|
|
201
202
|
description: "List models currently loaded in VRAM. Optional machine; omit to probe every discovered host. " +
|
|
202
|
-
"scan is on-disk inventory; ps is what is resident right now
|
|
203
|
+
"scan is on-disk inventory; ps is what is resident right now, with CPU/GPU %.",
|
|
203
204
|
inputSchema: {
|
|
204
205
|
type: "object",
|
|
205
206
|
properties: {
|
|
@@ -487,7 +488,29 @@ async function handleRequest(msg, version) {
|
|
|
487
488
|
fail(id, -32601, `Method not found: ${method}`);
|
|
488
489
|
}
|
|
489
490
|
}
|
|
491
|
+
export function helpText() {
|
|
492
|
+
return `Usage:
|
|
493
|
+
ollanet mcp
|
|
494
|
+
|
|
495
|
+
Stdio MCP server (no extra npm deps). stdout is JSON-RPC; logs go to stderr.
|
|
496
|
+
|
|
497
|
+
Tools:
|
|
498
|
+
ollanet_scan Discover hosts + models (lan, all, last)
|
|
499
|
+
ollanet_prompt Prompt a host or continue with chat_id
|
|
500
|
+
ollanet_compare Same prompt on 2–5 models
|
|
501
|
+
ollanet_pull Pull / update a model on a host
|
|
502
|
+
ollanet_show Inspect a model
|
|
503
|
+
ollanet_rm Delete a model (confirm: true)
|
|
504
|
+
ollanet_ps Models loaded in VRAM
|
|
505
|
+
ollanet_list_chats Saved transcripts
|
|
506
|
+
ollanet_get_chat One chat by id
|
|
507
|
+
|
|
508
|
+
Example Cursor config: { "command": "npx", "args": ["-y", "ollanet", "mcp"] }`;
|
|
509
|
+
}
|
|
490
510
|
export async function main() {
|
|
511
|
+
if (process.argv.slice(2).some((arg) => isHelpFlag(arg))) {
|
|
512
|
+
printHelp(helpText());
|
|
513
|
+
}
|
|
491
514
|
const version = await packageVersion();
|
|
492
515
|
console.error(`ollanet mcp ${version} (stdio) — tools: ${TOOLS.map((t) => t.name).join(", ")}`);
|
|
493
516
|
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
package/dist/ollama-chat.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export declare function ollamaVersion(baseUrl: string, timeoutMs: number): Promi
|
|
|
74
74
|
export interface PsModel {
|
|
75
75
|
name?: string;
|
|
76
76
|
model?: string;
|
|
77
|
+
/** Total resident bytes (VRAM + CPU/RAM). */
|
|
78
|
+
size?: number;
|
|
77
79
|
size_vram?: number;
|
|
78
80
|
context_length?: number;
|
|
79
81
|
digest?: string;
|
package/dist/prompt.d.ts
CHANGED
package/dist/prompt.js
CHANGED
|
@@ -17,14 +17,14 @@ import { configPath, defaultModelForHost, loadConfig, machineSettingsForHost, me
|
|
|
17
17
|
import { envInt, findDiscoveredHost, ollamaBaseUrl, resolveHost, shortName, } from "./hosts.js";
|
|
18
18
|
import { ollamaChat } from "./ollama-chat.js";
|
|
19
19
|
import { assemblePrompt, readPromptFile } from "./prompt-input.js";
|
|
20
|
-
import { consumeSettingsFlag, takeFlag, takeValue } from "./argv.js";
|
|
20
|
+
import { consumeSettingsFlag, failUsage, isHelpFlag, printHelp, takeFlag, takeValue } from "./argv.js";
|
|
21
21
|
import { listTargets, resolveTarget } from "./target.js";
|
|
22
22
|
/** Prompt/chat HTTP timeout (ms). 0 = no timeout. Separate from scan's OLLAMA_TIMEOUT_MS. */
|
|
23
23
|
function promptTimeoutMs() {
|
|
24
24
|
return envInt("OLLAMA_PROMPT_TIMEOUT_MS", 600_000);
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
26
|
+
export function helpText() {
|
|
27
|
+
return `Usage:
|
|
28
28
|
ollanet prompt <machine> [model] <prompt...>
|
|
29
29
|
ollanet prompt --chat <hash> <prompt...>
|
|
30
30
|
ollanet prompt <machine> --chat <hash> <prompt...>
|
|
@@ -57,8 +57,10 @@ Options:
|
|
|
57
57
|
--no-think Disable thinking (default) so tokens go to the reply
|
|
58
58
|
--file <path> Prompt from a .txt or .md file (joins argv / stdin)
|
|
59
59
|
--no-stream Wait for the full response
|
|
60
|
-
--json Emit final response JSON (implies --no-stream)
|
|
61
|
-
|
|
60
|
+
--json Emit final response JSON (implies --no-stream)`;
|
|
61
|
+
}
|
|
62
|
+
function usage() {
|
|
63
|
+
failUsage(helpText());
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* Heuristic for an optional positional model token.
|
|
@@ -136,8 +138,8 @@ function parseArgs(argv) {
|
|
|
136
138
|
const arg = args.shift();
|
|
137
139
|
if (arg === "--")
|
|
138
140
|
continue;
|
|
139
|
-
if (arg
|
|
140
|
-
|
|
141
|
+
if (isHelpFlag(arg))
|
|
142
|
+
printHelp(helpText());
|
|
141
143
|
if (arg === "--no-stream") {
|
|
142
144
|
stream = false;
|
|
143
145
|
continue;
|
package/dist/ps.d.ts
CHANGED
|
@@ -5,9 +5,15 @@
|
|
|
5
5
|
* ollanet ps studio
|
|
6
6
|
*/
|
|
7
7
|
import { type AppConfig } from "./config.js";
|
|
8
|
+
export declare function helpText(): string;
|
|
8
9
|
export interface LoadedModel {
|
|
9
10
|
name: string;
|
|
11
|
+
size?: number;
|
|
10
12
|
size_vram?: number;
|
|
13
|
+
cpu_percent?: number;
|
|
14
|
+
gpu_percent?: number;
|
|
15
|
+
/** Same wording as `ollama ps` PROCESSOR (e.g. "100% GPU", "83%/17% CPU/GPU"). */
|
|
16
|
+
processor?: string;
|
|
11
17
|
context_length?: number;
|
|
12
18
|
digest?: string;
|
|
13
19
|
tuned: boolean;
|
|
@@ -27,5 +33,11 @@ export interface PsOptions {
|
|
|
27
33
|
export interface PsResult {
|
|
28
34
|
hosts: LoadedHost[];
|
|
29
35
|
}
|
|
36
|
+
/** Match `ollama ps` PROCESSOR: CPU% from (size - size_vram) / size. */
|
|
37
|
+
export declare function processorSplit(size?: number, sizeVram?: number): {
|
|
38
|
+
cpu_percent?: number;
|
|
39
|
+
gpu_percent?: number;
|
|
40
|
+
processor?: string;
|
|
41
|
+
};
|
|
30
42
|
export declare function listLoaded(opts?: PsOptions): Promise<PsResult>;
|
|
31
43
|
export declare function main(): Promise<void>;
|
package/dist/ps.js
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
import { configFromPartial, loadConfig } from "./config.js";
|
|
8
8
|
import { discoverHosts, envInt, ollamaBaseUrl, shortName, } from "./hosts.js";
|
|
9
9
|
import { ollamaPs, ollamaPsRequired } from "./ollama-chat.js";
|
|
10
|
-
import { takeFlag } from "./argv.js";
|
|
10
|
+
import { failUsage, isHelpFlag, printHelp, takeFlag } from "./argv.js";
|
|
11
11
|
import { resolveTarget } from "./target.js";
|
|
12
12
|
import { looksTuned } from "./tuned.js";
|
|
13
13
|
function psTimeoutMs() {
|
|
14
14
|
return envInt("OLLAMA_PS_TIMEOUT_MS", 10_000);
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
16
|
+
export function helpText() {
|
|
17
|
+
return `Usage:
|
|
18
18
|
ollanet ps [machine]
|
|
19
19
|
ollanet ps --machine <name>
|
|
20
20
|
|
|
@@ -23,12 +23,15 @@ Examples:
|
|
|
23
23
|
ollanet ps studio
|
|
24
24
|
ollanet ps studio --json
|
|
25
25
|
|
|
26
|
-
scan lists models on disk. ps lists what is loaded
|
|
26
|
+
scan lists models on disk. ps lists what is loaded and the CPU/GPU split
|
|
27
|
+
(same rule as ollama ps: size_vram / size).
|
|
27
28
|
|
|
28
29
|
Options:
|
|
29
30
|
--machine <name> One host (default: every discovered host)
|
|
30
|
-
--json Emit result JSON on stdout
|
|
31
|
-
|
|
31
|
+
--json Emit result JSON on stdout`;
|
|
32
|
+
}
|
|
33
|
+
function usage() {
|
|
34
|
+
failUsage(helpText());
|
|
32
35
|
}
|
|
33
36
|
function parseArgs(argv) {
|
|
34
37
|
const args = [...argv];
|
|
@@ -39,8 +42,8 @@ function parseArgs(argv) {
|
|
|
39
42
|
const arg = args.shift();
|
|
40
43
|
if (arg === "--")
|
|
41
44
|
continue;
|
|
42
|
-
if (arg
|
|
43
|
-
|
|
45
|
+
if (isHelpFlag(arg))
|
|
46
|
+
printHelp(helpText());
|
|
44
47
|
if (arg === "--json") {
|
|
45
48
|
json = true;
|
|
46
49
|
continue;
|
|
@@ -74,11 +77,31 @@ function formatBytes(bytes) {
|
|
|
74
77
|
}
|
|
75
78
|
return `${value.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
|
|
76
79
|
}
|
|
80
|
+
/** Match `ollama ps` PROCESSOR: CPU% from (size - size_vram) / size. */
|
|
81
|
+
export function processorSplit(size, sizeVram) {
|
|
82
|
+
const total = size ?? 0;
|
|
83
|
+
const vram = sizeVram ?? 0;
|
|
84
|
+
if (vram === 0 && total === 0)
|
|
85
|
+
return {};
|
|
86
|
+
if (vram === 0)
|
|
87
|
+
return { cpu_percent: 100, gpu_percent: 0, processor: "100% CPU" };
|
|
88
|
+
if (total > 0 && vram === total) {
|
|
89
|
+
return { cpu_percent: 0, gpu_percent: 100, processor: "100% GPU" };
|
|
90
|
+
}
|
|
91
|
+
if (vram > total || total === 0)
|
|
92
|
+
return { processor: "Unknown" };
|
|
93
|
+
const cpu = Math.round(((total - vram) / total) * 100);
|
|
94
|
+
const gpu = 100 - cpu;
|
|
95
|
+
return { cpu_percent: cpu, gpu_percent: gpu, processor: `${cpu}%/${gpu}% CPU/GPU` };
|
|
96
|
+
}
|
|
77
97
|
function toLoaded(m) {
|
|
78
98
|
const name = (m.name ?? m.model ?? "").trim();
|
|
99
|
+
const split = processorSplit(m.size, m.size_vram);
|
|
79
100
|
return {
|
|
80
101
|
name,
|
|
102
|
+
size: m.size,
|
|
81
103
|
size_vram: m.size_vram,
|
|
104
|
+
...split,
|
|
82
105
|
context_length: m.context_length,
|
|
83
106
|
digest: m.digest,
|
|
84
107
|
tuned: looksTuned(name),
|
|
@@ -141,7 +164,7 @@ function printPs(result) {
|
|
|
141
164
|
const vram = formatBytes(m.size_vram);
|
|
142
165
|
const ctx = m.context_length != null ? `ctx ${m.context_length}` : "";
|
|
143
166
|
const tag = m.tuned ? " [tuned]" : "";
|
|
144
|
-
const meta = [vram ? `VRAM ${vram}` : "", ctx].filter(Boolean).join(" ");
|
|
167
|
+
const meta = [vram ? `VRAM ${vram}` : "", m.processor, ctx].filter(Boolean).join(" ");
|
|
145
168
|
process.stdout.write(meta ? ` ${m.name} ${meta}${tag}\n` : ` ${m.name}${tag}\n`);
|
|
146
169
|
}
|
|
147
170
|
}
|
package/dist/pull.d.ts
CHANGED
package/dist/pull.js
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { envInt, ollamaBaseUrl, shortName } from "./hosts.js";
|
|
10
10
|
import { ollamaPull } from "./ollama-chat.js";
|
|
11
|
-
import { takeFlag } from "./argv.js";
|
|
11
|
+
import { failUsage, isHelpFlag, printHelp, takeFlag } from "./argv.js";
|
|
12
12
|
import { resolveTarget } from "./target.js";
|
|
13
13
|
/** Pull HTTP timeout (ms). 0 = none. Large models can take hours. */
|
|
14
14
|
function pullTimeoutMs() {
|
|
15
15
|
return envInt("OLLAMA_PULL_TIMEOUT_MS", 0);
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
17
|
+
export function helpText() {
|
|
18
|
+
return `Usage:
|
|
19
19
|
ollanet pull <machine> <model>
|
|
20
20
|
ollanet pull --machine <name> --model <name>
|
|
21
21
|
|
|
@@ -33,8 +33,10 @@ Options:
|
|
|
33
33
|
--model <name> Model to pull (e.g. gemma3:12b)
|
|
34
34
|
--insecure Allow HTTP / self-signed model registries
|
|
35
35
|
--no-stream Wait for a single final response (no progress chunks)
|
|
36
|
-
--json Emit result JSON on stdout
|
|
37
|
-
|
|
36
|
+
--json Emit result JSON on stdout`;
|
|
37
|
+
}
|
|
38
|
+
function usage() {
|
|
39
|
+
failUsage(helpText());
|
|
38
40
|
}
|
|
39
41
|
function parseArgs(argv) {
|
|
40
42
|
const args = [...argv];
|
|
@@ -48,8 +50,8 @@ function parseArgs(argv) {
|
|
|
48
50
|
const arg = args.shift();
|
|
49
51
|
if (arg === "--")
|
|
50
52
|
continue;
|
|
51
|
-
if (arg
|
|
52
|
-
|
|
53
|
+
if (isHelpFlag(arg))
|
|
54
|
+
printHelp(helpText());
|
|
53
55
|
if (arg === "--insecure") {
|
|
54
56
|
insecure = true;
|
|
55
57
|
continue;
|
package/dist/rm.d.ts
CHANGED
package/dist/rm.js
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
7
|
import { envInt, ollamaBaseUrl, shortName } from "./hosts.js";
|
|
8
8
|
import { ollamaDelete } from "./ollama-chat.js";
|
|
9
|
-
import { takeFlag } from "./argv.js";
|
|
9
|
+
import { failUsage, isHelpFlag, printHelp, takeFlag } from "./argv.js";
|
|
10
10
|
import { resolveTarget } from "./target.js";
|
|
11
11
|
function rmTimeoutMs() {
|
|
12
12
|
return envInt("OLLAMA_RM_TIMEOUT_MS", 30_000);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
14
|
+
export function helpText() {
|
|
15
|
+
return `Usage:
|
|
16
16
|
ollanet rm <machine> <model> --yes
|
|
17
17
|
ollanet rm --machine <name> --model <name> --yes
|
|
18
18
|
|
|
@@ -27,8 +27,10 @@ Options:
|
|
|
27
27
|
--machine <name> Host (discovered name, MagicDNS, hostname, or IP[:port])
|
|
28
28
|
--model <name> Model to delete
|
|
29
29
|
--yes Required unless stdin is a TTY and you confirm
|
|
30
|
-
--json Emit result JSON on stdout
|
|
31
|
-
|
|
30
|
+
--json Emit result JSON on stdout`;
|
|
31
|
+
}
|
|
32
|
+
function usage() {
|
|
33
|
+
failUsage(helpText());
|
|
32
34
|
}
|
|
33
35
|
function parseArgs(argv) {
|
|
34
36
|
const args = [...argv];
|
|
@@ -41,8 +43,8 @@ function parseArgs(argv) {
|
|
|
41
43
|
const arg = args.shift();
|
|
42
44
|
if (arg === "--")
|
|
43
45
|
continue;
|
|
44
|
-
if (arg
|
|
45
|
-
|
|
46
|
+
if (isHelpFlag(arg))
|
|
47
|
+
printHelp(helpText());
|
|
46
48
|
if (arg === "--yes" || arg === "-y") {
|
|
47
49
|
yes = true;
|
|
48
50
|
continue;
|
package/dist/scan.d.ts
CHANGED
|
@@ -54,4 +54,5 @@ export interface ScanPayload {
|
|
|
54
54
|
}
|
|
55
55
|
/** Programmatic scan used by CLI `--json`, MCP, and `import { scanNetwork } from "ollanet"`. */
|
|
56
56
|
export declare function scanNetwork(options?: ScanOptions): Promise<ScanPayload>;
|
|
57
|
+
export declare function helpText(): string;
|
|
57
58
|
export declare function main(): Promise<void>;
|
package/dist/scan.js
CHANGED
|
@@ -7,6 +7,7 @@ import { configFromPartial, loadConfig } from "./config.js";
|
|
|
7
7
|
import { discoverHosts, envInt, ollamaPort, shortName, } from "./hosts.js";
|
|
8
8
|
import { lastScanPath, loadLastScan, saveLastScan } from "./scan-store.js";
|
|
9
9
|
import { looksTuned } from "./tuned.js";
|
|
10
|
+
import { isHelpFlag, printHelp } from "./argv.js";
|
|
10
11
|
function scanTimeoutMs() {
|
|
11
12
|
return envInt("OLLAMA_TIMEOUT_MS", 2500);
|
|
12
13
|
}
|
|
@@ -184,7 +185,30 @@ export async function scanNetwork(options = {}) {
|
|
|
184
185
|
}
|
|
185
186
|
return payload;
|
|
186
187
|
}
|
|
188
|
+
export function helpText() {
|
|
189
|
+
return `Usage:
|
|
190
|
+
ollanet scan [--json] [--all] [--lan] [--last]
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
ollanet scan
|
|
194
|
+
ollanet scan --lan
|
|
195
|
+
ollanet scan --last
|
|
196
|
+
ollanet scan --json
|
|
197
|
+
|
|
198
|
+
Discovers Ollama hosts (localhost, config, OLLANET_HOSTS, Tailscale) and lists
|
|
199
|
+
models. --lan is an opt-in TCP sweep of local /24s on port 11434.
|
|
200
|
+
|
|
201
|
+
Options:
|
|
202
|
+
--json Machine-readable output
|
|
203
|
+
--all Also probe offline Tailscale peers
|
|
204
|
+
--lan TCP-scan local LAN CIDRs for open Ollama ports
|
|
205
|
+
--last Reprint the last saved scan (no network)
|
|
206
|
+
--cached Same as --last`;
|
|
207
|
+
}
|
|
187
208
|
export async function main() {
|
|
209
|
+
if (process.argv.slice(2).some((arg) => isHelpFlag(arg))) {
|
|
210
|
+
printHelp(helpText());
|
|
211
|
+
}
|
|
188
212
|
const includeOffline = process.argv.includes("--all");
|
|
189
213
|
const jsonOut = process.argv.includes("--json");
|
|
190
214
|
const lanScan = process.argv.includes("--lan");
|
package/dist/show.d.ts
CHANGED
package/dist/show.js
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { envInt, ollamaBaseUrl, shortName } from "./hosts.js";
|
|
7
7
|
import { ollamaShow } from "./ollama-chat.js";
|
|
8
|
-
import { takeFlag } from "./argv.js";
|
|
8
|
+
import { failUsage, isHelpFlag, printHelp, takeFlag } from "./argv.js";
|
|
9
9
|
import { resolveTarget } from "./target.js";
|
|
10
10
|
import { looksTuned } from "./tuned.js";
|
|
11
11
|
function showTimeoutMs() {
|
|
12
12
|
return envInt("OLLAMA_SHOW_TIMEOUT_MS", 30_000);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
14
|
+
export function helpText() {
|
|
15
|
+
return `Usage:
|
|
16
16
|
ollanet show <machine> <model>
|
|
17
17
|
ollanet show --machine <name> --model <name>
|
|
18
18
|
|
|
@@ -26,8 +26,10 @@ Tuned Finetuna-style names are marked [tuned].
|
|
|
26
26
|
Options:
|
|
27
27
|
--machine <name> Host (discovered name, MagicDNS, hostname, or IP[:port])
|
|
28
28
|
--model <name> Model to inspect
|
|
29
|
-
--json Emit result JSON on stdout
|
|
30
|
-
|
|
29
|
+
--json Emit result JSON on stdout`;
|
|
30
|
+
}
|
|
31
|
+
function usage() {
|
|
32
|
+
failUsage(helpText());
|
|
31
33
|
}
|
|
32
34
|
function parseArgs(argv) {
|
|
33
35
|
const args = [...argv];
|
|
@@ -39,8 +41,8 @@ function parseArgs(argv) {
|
|
|
39
41
|
const arg = args.shift();
|
|
40
42
|
if (arg === "--")
|
|
41
43
|
continue;
|
|
42
|
-
if (arg
|
|
43
|
-
|
|
44
|
+
if (isHelpFlag(arg))
|
|
45
|
+
printHelp(helpText());
|
|
44
46
|
if (arg === "--json") {
|
|
45
47
|
json = true;
|
|
46
48
|
continue;
|