ollanet 0.4.0 → 0.5.0
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 +59 -7
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +36 -0
- package/dist/hosts.d.ts +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +6 -1
- package/dist/mcp.js +118 -1
- package/dist/ollama-chat.d.ts +46 -2
- package/dist/ollama-chat.js +121 -2
- package/dist/ps.d.ts +31 -0
- package/dist/ps.js +165 -0
- package/dist/pull.d.ts +36 -0
- package/dist/pull.js +198 -0
- package/dist/rm.d.ts +22 -0
- package/dist/rm.js +134 -0
- package/dist/scan.d.ts +3 -1
- package/dist/scan.js +4 -1
- package/dist/show.d.ts +28 -0
- package/dist/show.js +131 -0
- package/dist/target.d.ts +4 -0
- package/dist/target.js +18 -0
- package/dist/tuned.d.ts +8 -0
- package/dist/tuned.js +17 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Chat with **Ollama** servers on **any network you can reach** — LAN, localhost
|
|
|
10
10
|
|
|
11
11
|
Scan for models, fire prompts, and continue conversations later by a short hash. No browser UI required. **Node 20+ · zero runtime deps.**
|
|
12
12
|
|
|
13
|
-
**Site:** [ollanet.dev](https://ollanet.dev) —
|
|
13
|
+
**Site:** [ollanet.dev](https://ollanet.dev) — FilePress + Cloudflare Pages (`site/`).
|
|
14
14
|
|
|
15
15
|
## Origin
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ This started as a personal little script. I had Ollama running on a laptop on my
|
|
|
18
18
|
|
|
19
19
|
## Related: Finetuna
|
|
20
20
|
|
|
21
|
-
**ollanet** is the client-side / network tool. On the machine that *runs* Ollama, use **[Finetuna](https://
|
|
21
|
+
**ollanet** is the client-side / network tool. On the machine that *runs* Ollama, use **[Finetuna](https://finetuna.net)** to turn a stock model into a GPU-tuned, named variant (context, batch, Modelfile, auto-tune).
|
|
22
22
|
|
|
23
23
|
Typical loop:
|
|
24
24
|
|
|
@@ -29,12 +29,14 @@ They share the same Ollama API; Finetuna shapes the models, ollanet finds and ch
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
- **Discover** Ollama hosts from localhost, `config.hosts`, `OLLANET_HOSTS`, optional Tailscale, and optional LAN scan
|
|
32
|
+
- **Discover** Ollama hosts from localhost, `config.hosts`, `OLLANET_HOSTS`, optional Tailscale, and optional LAN scan (`--lan`, off by default)
|
|
33
|
+
- **Pull** a library model onto a named host (`ollanet pull studio gemma3:12b`) — the server downloads; re-pull updates
|
|
34
|
+
- **Show / rm / ps** — inspect a Modelfile, delete a model (`--yes`), see what’s in VRAM. Scan marks Finetuna-style `[tuned]` names
|
|
33
35
|
- **Prompt** any hostname/IP/model with streaming replies
|
|
34
36
|
- **Persist** chats as `responses/<hash>.json` with topic, machine, model, timestamps
|
|
35
37
|
- **Continue** any thread with `--chat <hash>`
|
|
36
38
|
- **Configure** per-machine defaults (model, temperature, context, …)
|
|
37
|
-
- **MCP** — `ollanet mcp` exposes scan/prompt/chats as Model Context Protocol tools for agents
|
|
39
|
+
- **MCP** — `ollanet mcp` exposes scan/prompt/pull/show/rm/ps/chats as Model Context Protocol tools for agents
|
|
38
40
|
- **Library** — `import { scanNetwork } from "ollanet"` for apps (Node 20+, not the browser)
|
|
39
41
|
|
|
40
42
|
## Use cases
|
|
@@ -44,11 +46,12 @@ The core idea: turn “I have to remember which IP has which models loaded” in
|
|
|
44
46
|
**For agents and automation** — prefer `ollanet mcp` (stdio MCP). The same flows also work via CLI flags:
|
|
45
47
|
|
|
46
48
|
- **Live model inventory / routing** — `ollanet_scan` (or `scan --json`) lists reachable hosts and models. Route each sub-task to the best host: the big-VRAM box for reasoning, the laptop for quick lookups.
|
|
49
|
+
- **Put a new model on a box** — `ollanet_pull` (or `pull studio gemma3:12b`) asks that host to download from the registry. Re-pull updates. Then `show` / `ps` / `rm` manage what’s on that disk and in VRAM.
|
|
47
50
|
- **Conversations that survive machines** — chats are stored by short hash with full history. Start a long thread on the GPU box, continue it later with `ollanet_prompt` + `chat_id` (or `--chat <hash>`).
|
|
48
51
|
- **A “talk to any Ollama on my network” tool** — `ollanet_prompt` / `prompt … --json --no-stream`; no hard-coded endpoints.
|
|
49
52
|
- **Embed in an app** — same scan + pick + call as a library. The app does not own the GPUs; it discovers them (FilePress, CLIs, Electron, a local companion for a hosted product).
|
|
50
53
|
- **Fleet health and speed checks** — periodic `ollanet bench <host> --json` builds a record of which model on which machine is currently fastest (or has silently gone offline). Saved results live in `benchmarks/`.
|
|
51
|
-
- **The Finetuna loop** — [Finetuna](https://
|
|
54
|
+
- **The Finetuna loop** — [Finetuna](https://finetuna.net) shapes a GPU-tuned named variant on the host; ollanet `scan` / `show` / `prompt` it from anywhere on the network. After `pull`, ollanet prints the next step (`finetuna --model …`).
|
|
52
55
|
|
|
53
56
|
**For humans:**
|
|
54
57
|
|
|
@@ -115,6 +118,11 @@ ollanet scan
|
|
|
115
118
|
# Also TCP-scan your local /24s for open Ollama ports
|
|
116
119
|
ollanet scan --lan
|
|
117
120
|
|
|
121
|
+
# Put a library model on a named machine (that host downloads it)
|
|
122
|
+
ollanet pull studio gemma3:12b
|
|
123
|
+
ollanet show studio gemma3:12b
|
|
124
|
+
ollanet ps studio
|
|
125
|
+
|
|
118
126
|
# Talk to a machine by name, MagicDNS name, or IP
|
|
119
127
|
ollanet prompt localhost "What is MagicDNS?"
|
|
120
128
|
ollanet prompt 192.168.1.50 gemma4:12b "Hello from the LAN"
|
|
@@ -145,7 +153,7 @@ for (const s of servers) {
|
|
|
145
153
|
| Call | What it does | When apps should use it |
|
|
146
154
|
|---|---|---|
|
|
147
155
|
| `scanNetwork()` | localhost + config + `OLLANET_HOSTS` + Tailscale | Button / first “find servers” |
|
|
148
|
-
| `scanNetwork({ lanScan: true })` | plus TCP sweep of local `/24`s | Checkbox, never on every health check |
|
|
156
|
+
| `scanNetwork({ lanScan: true })` | plus TCP sweep of local `/24`s on port 11434 | Checkbox; never the default scan, never on every health check |
|
|
149
157
|
| `discoverHosts()` then probe yourself | candidates only, no `/api/tags` | If the app already has its own Ollama client |
|
|
150
158
|
|
|
151
159
|
Pass `config` to skip the file (useful when the app already has its own config):
|
|
@@ -162,7 +170,9 @@ await scanNetwork({
|
|
|
162
170
|
|
|
163
171
|
`runPrompt({ save: false, writeStdout: false })` is a one-shot call that does not write `~/.ollanet/responses/`.
|
|
164
172
|
|
|
165
|
-
|
|
173
|
+
`pullModel({ machine, model })` asks that host to download (or update) a library model. The bits never transit ollanet. `showModel`, `removeModel({ yes: true })`, and `listLoaded` are the inspect / delete / VRAM trio.
|
|
174
|
+
|
|
175
|
+
Requires **ollanet ≥ 0.4.0** (library). **Model management (`pull` / `show` / `rm` / `ps`) needs ≥ 0.5.0.** Bundlers (Vite / SvelteKit SSR):
|
|
166
176
|
|
|
167
177
|
```ts
|
|
168
178
|
// vite.config.ts
|
|
@@ -175,6 +185,10 @@ optimizeDeps: { exclude: ["ollanet"] },
|
|
|
175
185
|
| Command | Description |
|
|
176
186
|
|---|---|
|
|
177
187
|
| `ollanet scan` | Probe known/discovered hosts for Ollama + list models |
|
|
188
|
+
| `ollanet pull <machine> <model>` | Ask that host to download / update a library model |
|
|
189
|
+
| `ollanet show <machine> <model>` | Inspect Modelfile / params / capabilities (`[tuned]` when it looks like Finetuna) |
|
|
190
|
+
| `ollanet rm <machine> <model> --yes` | Delete a model from that host’s disk |
|
|
191
|
+
| `ollanet ps [machine]` | Models loaded in VRAM (omit machine = every discovered host) |
|
|
178
192
|
| `ollanet prompt …` | Send a prompt / continue a chat |
|
|
179
193
|
| `ollanet chats` | List or inspect saved transcripts |
|
|
180
194
|
| `ollanet bench …` | Benchmark models for tok/s + lightweight quality checks |
|
|
@@ -211,6 +225,40 @@ ollanet prompt --chat <hash> <prompt...>
|
|
|
211
225
|
|
|
212
226
|
Machine can be a discovered name, hostname, FQDN, or IP (`192.168.1.50`, `host:11434`). Direct addresses work even if they were never scanned.
|
|
213
227
|
|
|
228
|
+
### `pull` options
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
ollanet pull <machine|ip> <model>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The named machine downloads from the Ollama registry onto **its** disk. Re-pulling the same name updates it. This is not a file upload and not a Finetuna runtime tune.
|
|
235
|
+
|
|
236
|
+
| Flag | Meaning |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `--machine <name>` | Host (same resolution as `prompt`) |
|
|
239
|
+
| `--model <name>` | Model to pull |
|
|
240
|
+
| `--insecure` | Allow HTTP / self-signed registries |
|
|
241
|
+
| `--no-stream` | Single final response (no progress chunks) |
|
|
242
|
+
| `--json` | Result JSON on stdout |
|
|
243
|
+
|
|
244
|
+
Timeout default is none (large pulls can take hours). Override with `OLLAMA_PULL_TIMEOUT_MS`. After a successful pull, stderr points at [Finetuna](https://finetuna.net) for a host-side named variant.
|
|
245
|
+
|
|
246
|
+
### `show` / `rm` / `ps`
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
ollanet show <machine|ip> <model>
|
|
250
|
+
ollanet rm <machine|ip> <model> --yes
|
|
251
|
+
ollanet ps [machine]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
| Flag | Meaning |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `--yes` / `-y` | Required for `rm` unless you confirm on a TTY |
|
|
257
|
+
| `--json` | Result JSON on stdout |
|
|
258
|
+
| `--machine` / `--model` | Same host resolution as `prompt` |
|
|
259
|
+
|
|
260
|
+
`scan` is on-disk inventory and marks `*-ctx32k` / `*-flash` / `*finetuna*` as `[tuned]`. `ps` is what’s resident. `show` reads the Modelfile so you can see a Finetuna bake from another machine.
|
|
261
|
+
|
|
214
262
|
### `chats` options
|
|
215
263
|
|
|
216
264
|
- `--json` — list summary JSON
|
|
@@ -228,6 +276,10 @@ Runs a **stdio MCP server** (no extra npm deps). Cursor / Claude Desktop / any M
|
|
|
228
276
|
|---|---|
|
|
229
277
|
| `ollanet_scan` | Discover hosts + models (`lan`, `all` optional) |
|
|
230
278
|
| `ollanet_prompt` | Prompt a host or continue with `chat_id` |
|
|
279
|
+
| `ollanet_pull` | Pull / update a model on a host (`machine`, `model`) |
|
|
280
|
+
| `ollanet_show` | Inspect a model (`machine`, `model`) |
|
|
281
|
+
| `ollanet_rm` | Delete a model (`machine`, `model`, `confirm: true`) |
|
|
282
|
+
| `ollanet_ps` | Loaded models (`machine` optional) |
|
|
231
283
|
| `ollanet_list_chats` | Summarize saved transcripts |
|
|
232
284
|
| `ollanet_get_chat` | Load one chat (full history) |
|
|
233
285
|
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* ollanet scan
|
|
6
6
|
* ollanet prompt <machine-or-ip> "hello"
|
|
7
|
+
* ollanet pull <machine> <model>
|
|
8
|
+
* ollanet show <machine> <model>
|
|
9
|
+
* ollanet rm <machine> <model> --yes
|
|
10
|
+
* ollanet ps [machine]
|
|
7
11
|
* ollanet chats
|
|
8
12
|
*/
|
|
9
13
|
const HELP = `ollanet — Ollama over your network
|
|
@@ -15,6 +19,10 @@ Usage:
|
|
|
15
19
|
ollanet scan [--json] [--all] [--lan]
|
|
16
20
|
ollanet prompt <machine|ip> [model] <prompt...>
|
|
17
21
|
ollanet prompt --chat <hash> <prompt...>
|
|
22
|
+
ollanet pull <machine|ip> <model>
|
|
23
|
+
ollanet show <machine|ip> <model>
|
|
24
|
+
ollanet rm <machine|ip> <model> --yes
|
|
25
|
+
ollanet ps [machine]
|
|
18
26
|
ollanet chats [--json] [--id <hash>]
|
|
19
27
|
ollanet bench <machine|ip> [model...] [--all] [options]
|
|
20
28
|
ollanet mcp
|
|
@@ -25,6 +33,10 @@ Examples:
|
|
|
25
33
|
ollanet prompt localhost "What is MagicDNS?"
|
|
26
34
|
ollanet prompt 192.168.1.50 gemma4:12b "Hello"
|
|
27
35
|
ollanet prompt --chat a1b2c3d4e5f6 "Tell me more"
|
|
36
|
+
ollanet pull studio gemma3:12b
|
|
37
|
+
ollanet show studio gemma4-ctx32k
|
|
38
|
+
ollanet rm studio llama3.2:1b --yes
|
|
39
|
+
ollanet ps studio
|
|
28
40
|
ollanet chats
|
|
29
41
|
ollanet bench localhost --all
|
|
30
42
|
ollanet bench localhost llama3.2:1b --runs 3
|
|
@@ -73,6 +85,30 @@ async function main() {
|
|
|
73
85
|
await run();
|
|
74
86
|
return;
|
|
75
87
|
}
|
|
88
|
+
case "pull":
|
|
89
|
+
case "download": {
|
|
90
|
+
const { main: run } = await import("./pull.js");
|
|
91
|
+
await run();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
case "show":
|
|
95
|
+
case "inspect": {
|
|
96
|
+
const { main: run } = await import("./show.js");
|
|
97
|
+
await run();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
case "rm":
|
|
101
|
+
case "delete": {
|
|
102
|
+
const { main: run } = await import("./rm.js");
|
|
103
|
+
await run();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
case "ps":
|
|
107
|
+
case "loaded": {
|
|
108
|
+
const { main: run } = await import("./ps.js");
|
|
109
|
+
await run();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
76
112
|
case "bench":
|
|
77
113
|
case "benchmark": {
|
|
78
114
|
const { main: run } = await import("./bench.js");
|
package/dist/hosts.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface DiscoverOptions {
|
|
|
39
39
|
discovery?: DiscoveryConfig;
|
|
40
40
|
/** Include Tailscale peers marked offline. */
|
|
41
41
|
includeOffline?: boolean;
|
|
42
|
-
/** Probe local LAN CIDRs for open Ollama ports. */
|
|
42
|
+
/** Probe local LAN CIDRs for open Ollama ports. Off unless true. */
|
|
43
43
|
lanScan?: boolean;
|
|
44
44
|
}
|
|
45
45
|
export declare function shortName(host: HostTarget): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,4 +15,14 @@ export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, } fr
|
|
|
15
15
|
export type { AppConfig, DiscoveryConfig, GenerateSettings } from "./config.js";
|
|
16
16
|
export { runPrompt } from "./prompt.js";
|
|
17
17
|
export type { PromptRunOptions, PromptRunResult } from "./prompt.js";
|
|
18
|
-
export {
|
|
18
|
+
export { pullModel } from "./pull.js";
|
|
19
|
+
export type { PullOptions, PullResult } from "./pull.js";
|
|
20
|
+
export { showModel } from "./show.js";
|
|
21
|
+
export type { ShowOptions, ShowResult } from "./show.js";
|
|
22
|
+
export { removeModel } from "./rm.js";
|
|
23
|
+
export type { RemoveOptions, RemoveResult } from "./rm.js";
|
|
24
|
+
export { listLoaded } from "./ps.js";
|
|
25
|
+
export type { PsOptions, PsResult, LoadedHost, LoadedModel } from "./ps.js";
|
|
26
|
+
export { looksTuned } from "./tuned.js";
|
|
27
|
+
export { ollamaChat, ollamaTags, ollamaShow, ollamaPs, ollamaPsRequired, ollamaPull, ollamaDelete, } from "./ollama-chat.js";
|
|
28
|
+
export type { PullChunk, OllamaPullOptions, OllamaPullResult, OllamaShowInfo, } from "./ollama-chat.js";
|
package/dist/index.js
CHANGED
|
@@ -11,4 +11,9 @@ export { scanNetwork } from "./scan.js";
|
|
|
11
11
|
export { discoverHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
|
|
12
12
|
export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, } from "./config.js";
|
|
13
13
|
export { runPrompt } from "./prompt.js";
|
|
14
|
-
export {
|
|
14
|
+
export { pullModel } from "./pull.js";
|
|
15
|
+
export { showModel } from "./show.js";
|
|
16
|
+
export { removeModel } from "./rm.js";
|
|
17
|
+
export { listLoaded } from "./ps.js";
|
|
18
|
+
export { looksTuned } from "./tuned.js";
|
|
19
|
+
export { ollamaChat, ollamaTags, ollamaShow, ollamaPs, ollamaPsRequired, ollamaPull, ollamaDelete, } from "./ollama-chat.js";
|
package/dist/mcp.js
CHANGED
|
@@ -12,8 +12,12 @@ import { createInterface } from "node:readline";
|
|
|
12
12
|
import { readFile } from "node:fs/promises";
|
|
13
13
|
import { listChats, loadChat } from "./chat-store.js";
|
|
14
14
|
import { projectPath } from "./paths.js";
|
|
15
|
+
import { listLoaded } from "./ps.js";
|
|
16
|
+
import { pullModel } from "./pull.js";
|
|
15
17
|
import { runPrompt } from "./prompt.js";
|
|
18
|
+
import { removeModel } from "./rm.js";
|
|
16
19
|
import { scanNetwork } from "./scan.js";
|
|
20
|
+
import { showModel } from "./show.js";
|
|
17
21
|
// Pin to the oldest widely-supported MCP revision. Clients may request a newer
|
|
18
22
|
// date; we still answer with this — legal negotiation, max compatibility.
|
|
19
23
|
const PROTOCOL_VERSION = "2024-11-05";
|
|
@@ -22,7 +26,7 @@ const TOOLS = [
|
|
|
22
26
|
name: "ollanet_scan",
|
|
23
27
|
description: "Discover reachable Ollama hosts and list their models. " +
|
|
24
28
|
"Use this before routing work to pick a host/model. " +
|
|
25
|
-
"Optional LAN TCP scan (
|
|
29
|
+
"Optional LAN TCP scan (off unless lan=true; equivalent to CLI --lan) and include offline Tailscale peers.",
|
|
26
30
|
inputSchema: {
|
|
27
31
|
type: "object",
|
|
28
32
|
properties: {
|
|
@@ -85,6 +89,78 @@ const TOOLS = [
|
|
|
85
89
|
additionalProperties: false,
|
|
86
90
|
},
|
|
87
91
|
},
|
|
92
|
+
{
|
|
93
|
+
name: "ollanet_pull",
|
|
94
|
+
description: "Pull (or update) a model onto a reachable Ollama host. " +
|
|
95
|
+
"The named machine downloads from the registry onto its own disk — " +
|
|
96
|
+
"ollanet does not upload weights. Machine is a discovered name, MagicDNS name, hostname, or IP[:port].",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
machine: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Host name/IP that should download the model",
|
|
103
|
+
},
|
|
104
|
+
model: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Model to pull (e.g. gemma3:12b)",
|
|
107
|
+
},
|
|
108
|
+
insecure: {
|
|
109
|
+
type: "boolean",
|
|
110
|
+
description: "Allow HTTP / self-signed model registries (default false)",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["machine", "model"],
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "ollanet_show",
|
|
119
|
+
description: "Inspect a model on a reachable Ollama host (Modelfile, parameters, capabilities). " +
|
|
120
|
+
"Marks Finetuna-style tuned names. Machine is a discovered name, MagicDNS name, hostname, or IP[:port].",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
machine: { type: "string", description: "Host name/IP" },
|
|
125
|
+
model: { type: "string", description: "Model to inspect" },
|
|
126
|
+
},
|
|
127
|
+
required: ["machine", "model"],
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "ollanet_rm",
|
|
133
|
+
description: "Delete a model from a reachable Ollama host's disk. Destructive. " +
|
|
134
|
+
"Requires confirm=true. Machine is a discovered name, MagicDNS name, hostname, or IP[:port].",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
machine: { type: "string", description: "Host name/IP" },
|
|
139
|
+
model: { type: "string", description: "Model to delete" },
|
|
140
|
+
confirm: {
|
|
141
|
+
type: "boolean",
|
|
142
|
+
description: "Must be true to actually delete (default false)",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["machine", "model", "confirm"],
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "ollanet_ps",
|
|
151
|
+
description: "List models currently loaded in VRAM. Optional machine; omit to probe every discovered host. " +
|
|
152
|
+
"scan is on-disk inventory; ps is what is resident right now.",
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: "object",
|
|
155
|
+
properties: {
|
|
156
|
+
machine: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Host name/IP (optional; omit for all discovered hosts)",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
additionalProperties: false,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
88
164
|
{
|
|
89
165
|
name: "ollanet_list_chats",
|
|
90
166
|
description: "List saved ollanet chat transcripts (id, topic, machine, model, message count).",
|
|
@@ -187,6 +263,47 @@ async function callTool(name, args) {
|
|
|
187
263
|
});
|
|
188
264
|
return textResult(result);
|
|
189
265
|
}
|
|
266
|
+
case "ollanet_pull": {
|
|
267
|
+
const machine = typeof args.machine === "string" ? args.machine : "";
|
|
268
|
+
const model = typeof args.model === "string" ? args.model : "";
|
|
269
|
+
if (!machine.trim())
|
|
270
|
+
return textResult({ error: "machine is required" }, true);
|
|
271
|
+
if (!model.trim())
|
|
272
|
+
return textResult({ error: "model is required" }, true);
|
|
273
|
+
const result = await pullModel({
|
|
274
|
+
machine,
|
|
275
|
+
model,
|
|
276
|
+
insecure: args.insecure === true,
|
|
277
|
+
writeStdout: false,
|
|
278
|
+
quiet: true,
|
|
279
|
+
});
|
|
280
|
+
return textResult(result);
|
|
281
|
+
}
|
|
282
|
+
case "ollanet_show": {
|
|
283
|
+
const machine = typeof args.machine === "string" ? args.machine : "";
|
|
284
|
+
const model = typeof args.model === "string" ? args.model : "";
|
|
285
|
+
if (!machine.trim())
|
|
286
|
+
return textResult({ error: "machine is required" }, true);
|
|
287
|
+
if (!model.trim())
|
|
288
|
+
return textResult({ error: "model is required" }, true);
|
|
289
|
+
return textResult(await showModel({ machine, model }));
|
|
290
|
+
}
|
|
291
|
+
case "ollanet_rm": {
|
|
292
|
+
const machine = typeof args.machine === "string" ? args.machine : "";
|
|
293
|
+
const model = typeof args.model === "string" ? args.model : "";
|
|
294
|
+
if (!machine.trim())
|
|
295
|
+
return textResult({ error: "machine is required" }, true);
|
|
296
|
+
if (!model.trim())
|
|
297
|
+
return textResult({ error: "model is required" }, true);
|
|
298
|
+
if (args.confirm !== true) {
|
|
299
|
+
return textResult({ error: "confirm must be true to delete a model" }, true);
|
|
300
|
+
}
|
|
301
|
+
return textResult(await removeModel({ machine, model, yes: true }));
|
|
302
|
+
}
|
|
303
|
+
case "ollanet_ps": {
|
|
304
|
+
const machine = typeof args.machine === "string" ? args.machine : undefined;
|
|
305
|
+
return textResult(await listLoaded({ machine }));
|
|
306
|
+
}
|
|
190
307
|
case "ollanet_list_chats": {
|
|
191
308
|
const chats = await listChats();
|
|
192
309
|
return textResult(chats.map((c) => ({
|
package/dist/ollama-chat.d.ts
CHANGED
|
@@ -50,9 +50,26 @@ export interface OllamaModelInfo {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
export declare function ollamaTags(baseUrl: string, timeoutMs: number): Promise<OllamaModelInfo[]>;
|
|
53
|
-
export
|
|
53
|
+
export interface OllamaShowInfo {
|
|
54
54
|
capabilities?: string[];
|
|
55
|
-
|
|
55
|
+
modelfile?: string;
|
|
56
|
+
parameters?: string;
|
|
57
|
+
template?: string;
|
|
58
|
+
system?: string;
|
|
59
|
+
details?: {
|
|
60
|
+
parent_model?: string;
|
|
61
|
+
format?: string;
|
|
62
|
+
family?: string;
|
|
63
|
+
families?: string[];
|
|
64
|
+
parameter_size?: string;
|
|
65
|
+
quantization_level?: string;
|
|
66
|
+
};
|
|
67
|
+
model_info?: Record<string, unknown>;
|
|
68
|
+
modified_at?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function ollamaShow(baseUrl: string, model: string, timeoutMs: number): Promise<OllamaShowInfo>;
|
|
71
|
+
/** POST /api/delete — removes the named model from that host's disk. */
|
|
72
|
+
export declare function ollamaDelete(baseUrl: string, model: string, timeoutMs: number): Promise<void>;
|
|
56
73
|
export declare function ollamaVersion(baseUrl: string, timeoutMs: number): Promise<string | undefined>;
|
|
57
74
|
export interface PsModel {
|
|
58
75
|
name?: string;
|
|
@@ -61,7 +78,34 @@ export interface PsModel {
|
|
|
61
78
|
context_length?: number;
|
|
62
79
|
digest?: string;
|
|
63
80
|
}
|
|
81
|
+
export interface PullChunk {
|
|
82
|
+
status?: string;
|
|
83
|
+
digest?: string;
|
|
84
|
+
total?: number;
|
|
85
|
+
completed?: number;
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface OllamaPullOptions {
|
|
89
|
+
baseUrl: string;
|
|
90
|
+
model: string;
|
|
91
|
+
/** Allow HTTP / self-signed registries (Ollama `insecure`). */
|
|
92
|
+
insecure?: boolean;
|
|
93
|
+
stream: boolean;
|
|
94
|
+
timeoutMs: number;
|
|
95
|
+
onProgress?: (chunk: PullChunk) => void;
|
|
96
|
+
}
|
|
97
|
+
export interface OllamaPullResult {
|
|
98
|
+
status: string;
|
|
99
|
+
chunks: PullChunk[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* POST /api/pull — the *server* downloads the model. The client only sends the
|
|
103
|
+
* request. Default timeout 0 (none): large pulls can take hours.
|
|
104
|
+
*/
|
|
105
|
+
export declare function ollamaPull(opts: OllamaPullOptions): Promise<OllamaPullResult>;
|
|
64
106
|
export declare function ollamaPs(baseUrl: string, timeoutMs: number): Promise<PsModel[]>;
|
|
107
|
+
/** Like ollamaPs, but unreachable hosts throw instead of looking empty. */
|
|
108
|
+
export declare function ollamaPsRequired(baseUrl: string, timeoutMs: number): Promise<PsModel[]>;
|
|
65
109
|
export declare function psHasModel(models: PsModel[], name: string): boolean;
|
|
66
110
|
export declare function vramForModel(models: PsModel[], name: string): number | undefined;
|
|
67
111
|
export declare function contextLengthForModel(models: PsModel[], name: string): number | undefined;
|
package/dist/ollama-chat.js
CHANGED
|
@@ -164,6 +164,10 @@ export async function ollamaShow(baseUrl, model, timeoutMs) {
|
|
|
164
164
|
const url = `${baseUrl}/api/show`;
|
|
165
165
|
return ollamaPostJson(url, { model }, timeoutMs);
|
|
166
166
|
}
|
|
167
|
+
/** POST /api/delete — removes the named model from that host's disk. */
|
|
168
|
+
export async function ollamaDelete(baseUrl, model, timeoutMs) {
|
|
169
|
+
await ollamaPostJson(`${baseUrl}/api/delete`, { model }, timeoutMs);
|
|
170
|
+
}
|
|
167
171
|
export async function ollamaVersion(baseUrl, timeoutMs) {
|
|
168
172
|
try {
|
|
169
173
|
const body = await ollamaGetJson(`${baseUrl}/api/version`, timeoutMs);
|
|
@@ -173,15 +177,128 @@ export async function ollamaVersion(baseUrl, timeoutMs) {
|
|
|
173
177
|
return undefined;
|
|
174
178
|
}
|
|
175
179
|
}
|
|
180
|
+
function parsePullChunk(url, raw) {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(raw);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
throw new Error(`Non-JSON response from ${url} (is something else on this port?): ${raw.slice(0, 120)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* POST /api/pull — the *server* downloads the model. The client only sends the
|
|
190
|
+
* request. Default timeout 0 (none): large pulls can take hours.
|
|
191
|
+
*/
|
|
192
|
+
export async function ollamaPull(opts) {
|
|
193
|
+
const url = `${opts.baseUrl}/api/pull`;
|
|
194
|
+
const body = {
|
|
195
|
+
model: opts.model,
|
|
196
|
+
stream: opts.stream,
|
|
197
|
+
};
|
|
198
|
+
if (opts.insecure)
|
|
199
|
+
body.insecure = true;
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
const timer = opts.timeoutMs > 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : undefined;
|
|
202
|
+
let res;
|
|
203
|
+
try {
|
|
204
|
+
res = await fetch(url, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
207
|
+
body: JSON.stringify(body),
|
|
208
|
+
signal: controller.signal,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
if (timer)
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
const name = err instanceof Error ? err.name : "";
|
|
215
|
+
if (name === "AbortError") {
|
|
216
|
+
throw new Error(`Pull timed out after ${opts.timeoutMs}ms (${url})`);
|
|
217
|
+
}
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
if (timer)
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
const text = await res.text().catch(() => "");
|
|
224
|
+
let detail = text;
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(text);
|
|
227
|
+
if (parsed.error)
|
|
228
|
+
detail = parsed.error;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// keep raw text
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`Ollama HTTP ${res.status} ${res.statusText}${detail ? `: ${detail}` : ""}`);
|
|
234
|
+
}
|
|
235
|
+
const chunks = [];
|
|
236
|
+
const take = (chunk) => {
|
|
237
|
+
if (chunk.error)
|
|
238
|
+
throw new Error(chunk.error);
|
|
239
|
+
chunks.push(chunk);
|
|
240
|
+
opts.onProgress?.(chunk);
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
if (!opts.stream) {
|
|
244
|
+
take(parsePullChunk(url, await res.text()));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
if (!res.body)
|
|
248
|
+
throw new Error("No response body from Ollama");
|
|
249
|
+
const reader = res.body.getReader();
|
|
250
|
+
const decoder = new TextDecoder();
|
|
251
|
+
let buffer = "";
|
|
252
|
+
while (true) {
|
|
253
|
+
const { done, value } = await reader.read();
|
|
254
|
+
if (done)
|
|
255
|
+
break;
|
|
256
|
+
buffer += decoder.decode(value, { stream: true });
|
|
257
|
+
const lines = buffer.split("\n");
|
|
258
|
+
buffer = lines.pop() ?? "";
|
|
259
|
+
for (const line of lines) {
|
|
260
|
+
const trimmed = line.trim();
|
|
261
|
+
if (!trimmed)
|
|
262
|
+
continue;
|
|
263
|
+
take(parsePullChunk(url, trimmed));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const trailing = buffer.trim();
|
|
267
|
+
if (trailing)
|
|
268
|
+
take(parsePullChunk(url, trailing));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
const name = err instanceof Error ? err.name : "";
|
|
273
|
+
if (name === "AbortError") {
|
|
274
|
+
throw new Error(`Pull timed out after ${opts.timeoutMs}ms (${url})`);
|
|
275
|
+
}
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
if (timer)
|
|
280
|
+
clearTimeout(timer);
|
|
281
|
+
}
|
|
282
|
+
const last = chunks.at(-1);
|
|
283
|
+
const status = last?.status ?? "";
|
|
284
|
+
if (status !== "success") {
|
|
285
|
+
throw new Error(status ? `Pull ended: ${status}` : "Pull failed (no success status)");
|
|
286
|
+
}
|
|
287
|
+
return { status, chunks };
|
|
288
|
+
}
|
|
176
289
|
export async function ollamaPs(baseUrl, timeoutMs) {
|
|
177
290
|
try {
|
|
178
|
-
|
|
179
|
-
return body.models ?? [];
|
|
291
|
+
return await ollamaPsRequired(baseUrl, timeoutMs);
|
|
180
292
|
}
|
|
181
293
|
catch {
|
|
182
294
|
return [];
|
|
183
295
|
}
|
|
184
296
|
}
|
|
297
|
+
/** Like ollamaPs, but unreachable hosts throw instead of looking empty. */
|
|
298
|
+
export async function ollamaPsRequired(baseUrl, timeoutMs) {
|
|
299
|
+
const body = await ollamaGetJson(`${baseUrl}/api/ps`, timeoutMs);
|
|
300
|
+
return body.models ?? [];
|
|
301
|
+
}
|
|
185
302
|
function modelKey(name) {
|
|
186
303
|
return name.trim().toLowerCase();
|
|
187
304
|
}
|
|
@@ -293,6 +410,8 @@ async function ollamaPostJson(url, body, timeoutMs) {
|
|
|
293
410
|
throw new Error(`Ollama HTTP ${res.status} ${res.statusText}${text ? `: ${text}` : ""} (${url})`);
|
|
294
411
|
}
|
|
295
412
|
const text = await res.text();
|
|
413
|
+
if (!text.trim())
|
|
414
|
+
return {};
|
|
296
415
|
try {
|
|
297
416
|
return JSON.parse(text);
|
|
298
417
|
}
|
package/dist/ps.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List models currently loaded in VRAM on reachable Ollama hosts.
|
|
3
|
+
*
|
|
4
|
+
* ollanet ps
|
|
5
|
+
* ollanet ps studio
|
|
6
|
+
*/
|
|
7
|
+
import { type AppConfig } from "./config.js";
|
|
8
|
+
export interface LoadedModel {
|
|
9
|
+
name: string;
|
|
10
|
+
size_vram?: number;
|
|
11
|
+
context_length?: number;
|
|
12
|
+
digest?: string;
|
|
13
|
+
tuned: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface LoadedHost {
|
|
16
|
+
machine: string;
|
|
17
|
+
endpoint: string;
|
|
18
|
+
ok: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
models: LoadedModel[];
|
|
21
|
+
}
|
|
22
|
+
export interface PsOptions {
|
|
23
|
+
machine?: string;
|
|
24
|
+
config?: Partial<AppConfig>;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface PsResult {
|
|
28
|
+
hosts: LoadedHost[];
|
|
29
|
+
}
|
|
30
|
+
export declare function listLoaded(opts?: PsOptions): Promise<PsResult>;
|
|
31
|
+
export declare function main(): Promise<void>;
|