ollanet 0.6.3 → 0.6.5
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 +19 -442
- package/dist/alias.d.ts +9 -0
- package/dist/alias.js +147 -0
- package/dist/bench.js +14 -5
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +16 -3
- package/dist/compare.js +12 -3
- package/dist/config.d.ts +23 -0
- package/dist/config.js +73 -1
- package/dist/help.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +2 -1
- package/dist/prompt.js +22 -3
- package/dist/ps.js +8 -3
- package/dist/pull.js +11 -5
- package/dist/rm.d.ts +1 -1
- package/dist/rm.js +11 -4
- package/dist/show.d.ts +1 -1
- package/dist/show.js +13 -3
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -6,478 +6,55 @@
|
|
|
6
6
|
|
|
7
7
|
Find the models. Talk to them. Keep the thread.
|
|
8
8
|
|
|
9
|
-
Chat with **Ollama** servers on **any network you can reach** — LAN, localhost, Tailscale, VPN, or a raw IP.
|
|
9
|
+
Chat with **Ollama** servers on **any network you can reach** — LAN, localhost, Tailscale, VPN, or a raw IP.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
**Site:** [ollanet.dev](https://ollanet.dev) — FilePress + Cloudflare Pages (`site/`).
|
|
14
|
-
|
|
15
|
-
## Origin
|
|
16
|
-
|
|
17
|
-
This started as a personal little script. I had Ollama running on a laptop on my Tailscale network and wanted an easy way to discover it, poke at the models, and actually talk to them from another machine — without babysitting IPs or opening a browser. One scan/prompt tool later, chats by hash showed up, LAN discovery crept in, and the thing grew into **ollanet**: a tiny CLI for using Ollama over whatever network you already have.
|
|
18
|
-
|
|
19
|
-
## Related: Finetuna
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
Typical loop:
|
|
24
|
-
|
|
25
|
-
1. On the host: `finetuna` → create something like `gemma4-ctx32k`
|
|
26
|
-
2. From anywhere on the network: `ollanet scan` → `ollanet prompt that-host gemma4-ctx32k "…"`
|
|
27
|
-
|
|
28
|
-
They share the same Ollama API; Finetuna shapes the models, ollanet finds and chats with them. Host tunes the model. Network finds and uses it. Same API, closed loop.
|
|
29
|
-
|
|
30
|
-
## Features
|
|
31
|
-
|
|
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
|
|
35
|
-
- **Prompt** any hostname/IP/model with streaming replies (`--file` for `.txt` / `.md`)
|
|
36
|
-
- **Compare** 2–5 models on one host with the same prompt; summary table + `compares/<id>.md`
|
|
37
|
-
- **Persist** chats as `responses/<hash>.json` with topic, machine, model, timestamps
|
|
38
|
-
- **Continue** any thread with `--chat <hash>`
|
|
39
|
-
- **Configure** per-machine defaults (model, temperature, context, …)
|
|
40
|
-
- **MCP** — `ollanet mcp` exposes scan/prompt/compare/pull/show/rm/ps/chats as Model Context Protocol tools for agents
|
|
41
|
-
- **Library** — `import { scanNetwork } from "ollanet"` for apps (Node 20+, not the browser)
|
|
42
|
-
|
|
43
|
-
## Use cases
|
|
44
|
-
|
|
45
|
-
The core idea: turn “I have to remember which IP has which models loaded” into a discoverable, stateful, network-native primitive. That unlocks more than interactive chat:
|
|
46
|
-
|
|
47
|
-
**For agents and automation** — prefer `ollanet mcp` (stdio MCP). The same flows also work via CLI flags:
|
|
11
|
+
**CLI** for humans · **MCP** for agents · **Node** for apps. Same mesh.
|
|
48
12
|
|
|
49
|
-
|
|
50
|
-
- **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.
|
|
51
|
-
- **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>`).
|
|
52
|
-
- **A “talk to any Ollama on my network” tool** — `ollanet_prompt` / `prompt … --json --no-stream`; no hard-coded endpoints.
|
|
53
|
-
- **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).
|
|
54
|
-
- **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/`.
|
|
55
|
-
- **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 …`).
|
|
56
|
-
|
|
57
|
-
**For humans:**
|
|
58
|
-
|
|
59
|
-
- **Tailscale-native access** — prompt any machine by MagicDNS name from a laptop or phone (Termux) without babysitting IPs or opening a browser.
|
|
60
|
-
- **A private model mesh** — everyone on a shared tailnet or LAN can discover and use the same Ollama hosts, no public exposure, no central UI.
|
|
61
|
-
- **Multi-GPU lab** — `scan --lan` treats several machines as a pool of inference endpoints; `bench` compares the same model across hardware.
|
|
62
|
-
- **Audit trail / notes** — persisted chat JSON doubles as a research log you can resume from any device.
|
|
63
|
-
|
|
64
|
-
## Requirements
|
|
65
|
-
|
|
66
|
-
- Node.js 20+
|
|
67
|
-
- One or more [Ollama](https://ollama.com) servers reachable on port `11434` (or a custom port)
|
|
13
|
+
Scan for models, fire prompts, and continue conversations later by a short hash. No browser UI required. **Node 20+ · zero runtime deps.**
|
|
68
14
|
|
|
69
|
-
|
|
15
|
+
**Docs:** [ollanet.dev/docs](https://ollanet.dev/docs) · **Site:** [ollanet.dev](https://ollanet.dev)
|
|
70
16
|
|
|
71
17
|
## Install
|
|
72
18
|
|
|
73
|
-
Run without installing:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
npx ollanet scan
|
|
77
|
-
npx --allow-git=all github:Catalyst-Forge-LLC/ollanet scan # straight from GitHub
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
> npm 12+ blocks git dependencies by default, hence `--allow-git=all` for the
|
|
81
|
-
> GitHub form (npm ≤11 doesn't need it). Note that npx runs a one-off command —
|
|
82
|
-
> it does not put `ollanet` on your PATH.
|
|
83
|
-
|
|
84
|
-
Or install globally, which puts the `ollanet` command on your PATH:
|
|
85
|
-
|
|
86
19
|
```bash
|
|
87
20
|
npm install -g ollanet
|
|
88
|
-
npm install -g --allow-git=all github:Catalyst-Forge-LLC/ollanet # from GitHub
|
|
89
21
|
ollanet scan
|
|
90
22
|
```
|
|
91
23
|
|
|
92
|
-
|
|
93
|
-
saved to `~/.ollanet/responses/`.
|
|
94
|
-
|
|
95
|
-
Releases can publish from GitHub Actions via npm **trusted publishing** (OIDC + provenance). Setup notes: [docs/trusted-publishing.md](docs/trusted-publishing.md).
|
|
96
|
-
|
|
97
|
-
### From a checkout (development)
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
cd ollanet
|
|
101
|
-
pnpm install # also builds dist/ via the prepare hook
|
|
102
|
-
pnpm link --global # optional: put `ollanet` on your PATH
|
|
103
|
-
|
|
104
|
-
# Or without a global link:
|
|
105
|
-
pnpm ollanet -- help
|
|
106
|
-
pnpm scan
|
|
107
|
-
pnpm prompt -- localhost "hello"
|
|
108
|
-
pnpm chats
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
A checkout uses the repo-local `config.json` and `responses/` instead of `~/.ollanet/`.
|
|
24
|
+
Or one-off: `npx ollanet scan`
|
|
112
25
|
|
|
113
26
|
## Quick start
|
|
114
27
|
|
|
115
28
|
```bash
|
|
116
|
-
# Find Ollama servers (localhost + configured hosts + Tailscale if available)
|
|
117
29
|
ollanet scan
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
ollanet
|
|
121
|
-
|
|
122
|
-
# Put a library model on a named machine (that host downloads it)
|
|
123
|
-
ollanet pull studio gemma3:12b
|
|
124
|
-
ollanet show studio gemma3:12b
|
|
125
|
-
ollanet ps studio
|
|
126
|
-
|
|
127
|
-
# Talk to a machine by name, MagicDNS name, or IP
|
|
128
|
-
ollanet prompt localhost "What is MagicDNS?"
|
|
129
|
-
ollanet prompt localhost --file ./notes.md
|
|
130
|
-
ollanet compare studio gemma3:12b llama3.2:3b --prompt "Explain MagicDNS"
|
|
131
|
-
ollanet prompt 192.168.1.50 gemma4:12b "Hello from the LAN"
|
|
132
|
-
|
|
133
|
-
# Continue later
|
|
134
|
-
ollanet prompt --chat a1b2c3d4e5f6 "Give a concrete example"
|
|
135
|
-
|
|
136
|
-
# Browse saved chats
|
|
137
|
-
ollanet chats
|
|
138
|
-
ollanet chats --id a1b2c3d4e5f6
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Use as a library
|
|
142
|
-
|
|
143
|
-
ollanet is a **Node 20+ library that happens to have a CLI**. Not for the browser — discovery uses `node:net`, `child_process` (`tailscale status`), and `os.networkInterfaces()`. Call it from a dev server, CLI, Electron main, or a Worker with Node, never from Svelte/Vite client code.
|
|
144
|
-
|
|
145
|
-
Hard-coding `http://127.0.0.1:11434` treats Ollama like a local daemon. This treats it like a **private inference mesh**: any machine you can already reach. The app does not own the GPUs; it discovers them. LAN scan is opt-in so you do not look like a port scanner. Tailscale is automatic if the CLI exists. Config lives in `~/.ollanet` unless you pass `config`.
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import { scanNetwork } from "ollanet";
|
|
149
|
-
|
|
150
|
-
const { servers, sources } = await scanNetwork({ lanScan: false });
|
|
151
|
-
for (const s of servers) {
|
|
152
|
-
console.log(s.endpoint, s.models.map((m) => m.name));
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
| Call | What it does | When apps should use it |
|
|
157
|
-
|---|---|---|
|
|
158
|
-
| `scanNetwork()` | localhost + config + `OLLANET_HOSTS` + Tailscale | Button / first “find servers” |
|
|
159
|
-
| `scanNetwork({ lanScan: true })` | plus TCP sweep of local `/24`s on port 11434 | Checkbox; never the default scan, never on every health check |
|
|
160
|
-
| `lastScan()` | last saved snapshot, no network | UI “show last inventory” |
|
|
161
|
-
| `discoverHosts()` then probe yourself | candidates only, no `/api/tags` | If the app already has its own Ollama client |
|
|
162
|
-
|
|
163
|
-
Pass `config` to skip the file (useful when the app already has its own config):
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
await scanNetwork({
|
|
167
|
-
lanScan: false,
|
|
168
|
-
config: {
|
|
169
|
-
hosts: ["studio.tailnet.ts.net", "192.168.1.50"],
|
|
170
|
-
discovery: { localhost: true, tailscale: true, lan: false },
|
|
171
|
-
},
|
|
172
|
-
});
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
`runPrompt({ save: false, writeStdout: false })` is a one-shot call that does not write `~/.ollanet/responses/`.
|
|
176
|
-
|
|
177
|
-
`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.
|
|
178
|
-
|
|
179
|
-
Requires **ollanet ≥ 0.4.0** (library). **Model management (`pull` / `show` / `rm` / `ps`) needs ≥ 0.5.0.** **Compare, `lastScan`, and bench `--hot` need ≥ 0.6.0.** **`resolveTarget` / `listTargets` need ≥ 0.6.1.** Bundlers (Vite / SvelteKit SSR):
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
// vite.config.ts
|
|
183
|
-
ssr: { external: ["ollanet"] },
|
|
184
|
-
optimizeDeps: { exclude: ["ollanet"] },
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
## Commands
|
|
188
|
-
|
|
189
|
-
| Command | Description |
|
|
190
|
-
|---|---|
|
|
191
|
-
| `ollanet scan` | Probe known/discovered hosts for Ollama + list models |
|
|
192
|
-
| `ollanet pull <machine> <model>` | Ask that host to download / update a library model |
|
|
193
|
-
| `ollanet show <machine> <model>` | Inspect Modelfile / params / capabilities (`[tuned]` when it looks like Finetuna) |
|
|
194
|
-
| `ollanet rm <machine> <model> --yes` | Delete a model from that host’s disk |
|
|
195
|
-
| `ollanet ps [machine]` | Models loaded in VRAM, with CPU/GPU % (omit machine = every discovered host) |
|
|
196
|
-
| `ollanet prompt …` | Send a prompt / continue a chat (`--file` for `.txt` / `.md`) |
|
|
197
|
-
| `ollanet compare …` | Same prompt on 2–5 models; summary + `compares/<id>.md` |
|
|
198
|
-
| `ollanet chats` | List or inspect saved transcripts |
|
|
199
|
-
| `ollanet bench …` | Benchmark models for tok/s + lightweight quality checks |
|
|
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`) |
|
|
202
|
-
|
|
203
|
-
### `scan` options
|
|
204
|
-
|
|
205
|
-
- `--json` — machine-readable output
|
|
206
|
-
- `--all` — also probe offline Tailscale peers
|
|
207
|
-
- `--lan` — scan local LAN CIDRs for open Ollama ports
|
|
208
|
-
- `--last` / `--cached` — print the last saved scan (no network). Written to `~/.ollanet/last-scan.json` when installed, or `./last-scan.json` in a checkout (`OLLANET_LAST_SCAN` overrides).
|
|
209
|
-
|
|
210
|
-
### `prompt` options
|
|
211
|
-
|
|
212
|
-
```text
|
|
213
|
-
ollanet prompt <machine|ip> [model] <prompt...>
|
|
214
|
-
ollanet prompt --chat <hash> <prompt...>
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
| Flag | Meaning |
|
|
218
|
-
|---|---|
|
|
219
|
-
| `--chat <hash>` | Continue a saved chat |
|
|
220
|
-
| `--machine <name>` | Explicit machine (or override when continuing) |
|
|
221
|
-
| `--model <name>` | Model override |
|
|
222
|
-
| `--system <text>` | System prompt |
|
|
223
|
-
| `--temperature <n>` | Sampling temperature |
|
|
224
|
-
| `--num-predict <n>` | Max tokens to generate |
|
|
225
|
-
| `--num-ctx <n>` | Context window |
|
|
226
|
-
| `--keep-alive <value>` | Keep model loaded (`5m`, `0`, `-1`, …) |
|
|
227
|
-
| `--format json\|<schema>` | JSON mode / schema |
|
|
228
|
-
| `--think` / `--no-think` | Enable/disable model thinking (default **off** so replies aren’t empty on qwen3-style models) |
|
|
229
|
-
| `--file <path>` | Prompt from a `.txt` or `.md` file (joins argv / stdin) |
|
|
230
|
-
| `--no-stream` | Buffer the full reply |
|
|
231
|
-
| `--no-save` | Do not write a transcript |
|
|
232
|
-
| `--json` | Emit JSON (includes chat id when saved) |
|
|
233
|
-
|
|
234
|
-
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.
|
|
235
|
-
|
|
236
|
-
### `compare` options
|
|
237
|
-
|
|
238
|
-
```text
|
|
239
|
-
ollanet compare <machine> <model> <model> [model...]
|
|
240
|
-
ollanet compare <machine> <model> <model> [model...] --prompt <text>
|
|
241
|
-
ollanet compare <machine> <model> <model> [model...] --file <path.txt|.md>
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Same prompt on **2–5 models** on one host. Prints a tok/s table and writes `compares/<id>.md` (readable) plus `.json`. This is not `bench` (no fixed suite / repeats). Omit `--prompt` / `--file` to use the built-in mesh-host tasting prompt (printed on stderr when used).
|
|
245
|
-
|
|
246
|
-
| Flag | Meaning |
|
|
247
|
-
|---|---|
|
|
248
|
-
| `--prompt` / `-p` | Prompt text (default: built-in) |
|
|
249
|
-
| `--file` / `-f` | Prompt from `.txt` or `.md` |
|
|
250
|
-
| `--system` | System prompt |
|
|
251
|
-
| `--temperature` / `--num-predict` / `--num-ctx` / `--keep-alive` | Same as `prompt` |
|
|
252
|
-
| `--think` / `--no-think` | Thinking (default off) |
|
|
253
|
-
| `--unload` | Unload each model before the next (fairer tok/s) |
|
|
254
|
-
| `--no-save` | Do not write `compares/*` |
|
|
255
|
-
| `--json` | Full record on stdout |
|
|
256
|
-
|
|
257
|
-
Override dir with `OLLANET_COMPARES_DIR`.
|
|
258
|
-
|
|
259
|
-
### `pull` options
|
|
260
|
-
|
|
261
|
-
```text
|
|
262
|
-
ollanet pull <machine|ip> <model>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
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.
|
|
266
|
-
|
|
267
|
-
| Flag | Meaning |
|
|
268
|
-
|---|---|
|
|
269
|
-
| `--machine <name>` | Host (same resolution as `prompt`) |
|
|
270
|
-
| `--model <name>` | Model to pull |
|
|
271
|
-
| `--insecure` | Allow HTTP / self-signed registries |
|
|
272
|
-
| `--no-stream` | Single final response (no progress chunks) |
|
|
273
|
-
| `--json` | Result JSON on stdout |
|
|
274
|
-
|
|
275
|
-
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.
|
|
276
|
-
|
|
277
|
-
### `show` / `rm` / `ps`
|
|
278
|
-
|
|
279
|
-
```text
|
|
280
|
-
ollanet show <machine|ip> <model>
|
|
281
|
-
ollanet rm <machine|ip> <model> --yes
|
|
282
|
-
ollanet ps [machine]
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
| Flag | Meaning |
|
|
286
|
-
|---|---|
|
|
287
|
-
| `--yes` / `-y` | Required for `rm` unless you confirm on a TTY |
|
|
288
|
-
| `--json` | Result JSON on stdout |
|
|
289
|
-
| `--machine` / `--model` | Same host resolution as `prompt` |
|
|
290
|
-
|
|
291
|
-
`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.
|
|
292
|
-
|
|
293
|
-
### `chats` options
|
|
294
|
-
|
|
295
|
-
- `--json` — list summary JSON
|
|
296
|
-
- `--id <hash>` — show one chat (full JSON with `--json`)
|
|
297
|
-
|
|
298
|
-
### `mcp`
|
|
299
|
-
|
|
300
|
-
```bash
|
|
30
|
+
ollanet alias add desk studio gemma3:12b
|
|
31
|
+
ollanet prompt desk "What is MagicDNS?"
|
|
32
|
+
ollanet bench desk --hot
|
|
301
33
|
ollanet mcp
|
|
302
34
|
```
|
|
303
35
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
| Tool | What it does |
|
|
307
|
-
|---|---|
|
|
308
|
-
| `ollanet_scan` | Discover hosts + models (`lan`, `all`, `last` optional) |
|
|
309
|
-
| `ollanet_prompt` | Prompt a host or continue with `chat_id` (`file` optional) |
|
|
310
|
-
| `ollanet_compare` | Same prompt on 2–5 models (`machine`, `models`; `prompt` / `file` optional) |
|
|
311
|
-
| `ollanet_pull` | Pull / update a model on a host (`machine`, `model`) |
|
|
312
|
-
| `ollanet_show` | Inspect a model (`machine`, `model`) |
|
|
313
|
-
| `ollanet_rm` | Delete a model (`machine`, `model`, `confirm: true`) |
|
|
314
|
-
| `ollanet_ps` | Loaded models (`machine` optional) |
|
|
315
|
-
| `ollanet_list_chats` | Summarize saved transcripts |
|
|
316
|
-
| `ollanet_get_chat` | Load one chat (full history) |
|
|
317
|
-
|
|
318
|
-
Example Cursor MCP config (`~/.cursor/mcp.json` or project `.cursor/mcp.json`):
|
|
319
|
-
|
|
320
|
-
```json
|
|
321
|
-
{
|
|
322
|
-
"mcpServers": {
|
|
323
|
-
"ollanet": {
|
|
324
|
-
"command": "npx",
|
|
325
|
-
"args": ["-y", "ollanet", "mcp"]
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
Or, from a checkout: `"command": "pnpm"`, `"args": ["ollanet", "--", "mcp"]` (with `cwd` set to the repo). Uses the same config/chats dirs as the CLI (`~/.ollanet/` when installed).
|
|
332
|
-
|
|
333
|
-
### `bench`
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
ollanet bench <machine|ip> [model...]
|
|
337
|
-
ollanet bench <machine|ip> --all
|
|
338
|
-
ollanet bench localhost gemma4:12b --runs 5 --cold-load --json
|
|
339
|
-
ollanet bench localhost gemma4:12b --hot --runs 5
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Runs a built-in suite (`quick` default, or `--suite full`) against one or more **completion**
|
|
343
|
-
models: instruction checks once, 256-token throughput repeated (`--runs`, default 3) with
|
|
344
|
-
pinned `num_predict` / `seed` / `temperature`. Reports median tok/s + spread.
|
|
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`,
|
|
349
|
-
skips non-completion models (embeddings); multimodal `completion`+`vision` models stay in
|
|
350
|
-
(early-stop / pass_rate handle text-suite failures). Use `--exclude-vision` to drop them.
|
|
351
|
-
Saves `benchmarks/<id>.json` (or `~/.ollanet/benchmarks/` when installed).
|
|
352
|
-
See [`docs/bench-spec.md`](docs/bench-spec.md) for measurement rules.
|
|
36
|
+
Host-first: `ollanet <cmd> <machine> …` — the machine is MagicDNS / config name / IP, or an alias for a machine+model pair.
|
|
353
37
|
|
|
354
|
-
|
|
355
|
-
liveness/sanity gauge (`0/2`, `1/2`, `2/2`) — not “50% broken.” Use `--suite full`
|
|
356
|
-
when you care more about the quality signal or want the long tok/s column.
|
|
38
|
+
## What you get
|
|
357
39
|
|
|
358
|
-
|
|
40
|
+
Discover hosts · pull / show / rm / ps · aliases · prompt + hash-addressed chats · compare · bench · MCP · Node library. Full flags and configuration live in the [docs](https://ollanet.dev/docs).
|
|
359
41
|
|
|
360
|
-
|
|
42
|
+
## Finetuna
|
|
361
43
|
|
|
362
|
-
|
|
363
|
-
|---|---|
|
|
364
|
-
| localhost | Always (unless disabled) |
|
|
365
|
-
| `config.hosts` | Always when configured |
|
|
366
|
-
| `OLLANET_HOSTS` | Env comma/space list of hosts/IPs |
|
|
367
|
-
| Tailscale | If `tailscale status --json` works |
|
|
368
|
-
| LAN scan | `ollanet scan --lan` or `discovery.lan: true` |
|
|
44
|
+
On the machine that *runs* Ollama, use **[Finetuna](https://finetuna.net)** to shape a GPU-tuned named variant. ollanet finds and chats with it from anywhere on the mesh.
|
|
369
45
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
Edit `~/.ollanet/config.json` (installed) or `config.json` (checkout) — or point
|
|
373
|
-
`OLLANET_CONFIG` at another file:
|
|
374
|
-
|
|
375
|
-
```json
|
|
376
|
-
{
|
|
377
|
-
"hosts": [
|
|
378
|
-
"192.168.1.50",
|
|
379
|
-
{ "name": "studio", "host": "studio.local", "port": 11434 }
|
|
380
|
-
],
|
|
381
|
-
"discovery": {
|
|
382
|
-
"localhost": true,
|
|
383
|
-
"tailscale": true,
|
|
384
|
-
"lan": false,
|
|
385
|
-
"cidrs": ["192.168.1.0/24"]
|
|
386
|
-
},
|
|
387
|
-
"defaultModels": {
|
|
388
|
-
"localhost": "llama3.2:1b",
|
|
389
|
-
"studio": "gemma4:12b"
|
|
390
|
-
},
|
|
391
|
-
"defaults": {
|
|
392
|
-
"temperature": 0.7,
|
|
393
|
-
"num_predict": 512,
|
|
394
|
-
"keep_alive": "5m",
|
|
395
|
-
"think": false
|
|
396
|
-
},
|
|
397
|
-
"machineDefaults": {
|
|
398
|
-
"studio": { "num_ctx": 16384 }
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
**Precedence:** CLI flags → env vars → `machineDefaults` → `defaults`.
|
|
404
|
-
|
|
405
|
-
### Environment
|
|
406
|
-
|
|
407
|
-
| Variable | Purpose |
|
|
408
|
-
|---|---|
|
|
409
|
-
| `OLLANET_CONFIG` | Path to config JSON |
|
|
410
|
-
| `OLLANET_RESPONSES_DIR` | Chat transcript directory |
|
|
411
|
-
| `OLLANET_HOSTS` | Extra hosts/IPs to include |
|
|
412
|
-
| `OLLANET_LAN_TIMEOUT_MS` | Per-IP LAN probe timeout (default `200`) |
|
|
413
|
-
| `OLLANET_LAN_CONCURRENCY` | LAN probe concurrency (default `64`) |
|
|
414
|
-
| `OLLAMA_PORT` | Default Ollama port (`11434`) |
|
|
415
|
-
| `OLLAMA_TEMPERATURE` | Default temperature |
|
|
416
|
-
| `OLLAMA_NUM_PREDICT` | Default max tokens |
|
|
417
|
-
| `OLLAMA_NUM_CTX` | Default context size |
|
|
418
|
-
| `OLLAMA_KEEP_ALIVE` | Default keep-alive |
|
|
419
|
-
| `OLLAMA_FORMAT` | Default format (`json` or schema JSON) |
|
|
420
|
-
| `OLLAMA_SYSTEM` | Default system prompt |
|
|
421
|
-
| `OLLANET_THINK` / `OLLAMA_THINK` | Enable thinking (`true`/`false`; default off) |
|
|
422
|
-
| `OLLAMA_TIMEOUT_MS` | HTTP scan/probe timeout |
|
|
423
|
-
| `OLLAMA_PROMPT_TIMEOUT_MS` | Prompt/chat HTTP timeout (default `600000`; `0` = none) |
|
|
424
|
-
| `OLLAMA_BENCH_TIMEOUT_MS` | Per-case bench timeout (default `60000`) |
|
|
425
|
-
| `OLLANET_BENCHMARKS_DIR` | Benchmark result directory |
|
|
426
|
-
| `OLLAMA_CONCURRENCY` | Scan concurrency |
|
|
427
|
-
|
|
428
|
-
## Chat transcripts
|
|
429
|
-
|
|
430
|
-
Each saved chat is a JSON file:
|
|
431
|
-
|
|
432
|
-
```text
|
|
433
|
-
~/.ollanet/responses/<hash>.json # installed
|
|
434
|
-
responses/<hash>.json # checkout
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
Fields include `id`, `topic`, `machine`, `model`, `system`, `created_at`, `updated_at`, and a `messages[]` array (`role`, `content`, `timestamp`, plus assistant `stats`).
|
|
438
|
-
|
|
439
|
-
The first turn asks the model for a short topic title (falls back to the prompt text).
|
|
440
|
-
|
|
441
|
-
## Project layout
|
|
442
|
-
|
|
443
|
-
```text
|
|
444
|
-
ollanet/
|
|
445
|
-
src/
|
|
446
|
-
index.ts # public library entry (scan, discover, prompt, ollama helpers)
|
|
447
|
-
cli.ts # subcommand router (CLI entry)
|
|
448
|
-
hosts.ts # discovery + host resolution
|
|
449
|
-
scan.ts
|
|
450
|
-
prompt.ts
|
|
451
|
-
chats.ts
|
|
452
|
-
chat-store.ts
|
|
453
|
-
config.ts
|
|
454
|
-
paths.ts # checkout vs installed path resolution
|
|
455
|
-
ollama-chat.ts # shared /api/chat (+ tags/show/ps helpers)
|
|
456
|
-
mcp.ts # stdio MCP server
|
|
457
|
-
bench.ts # ollanet bench
|
|
458
|
-
bench-suite.ts
|
|
459
|
-
bench-store.ts
|
|
460
|
-
dist/ # compiled output (published to npm)
|
|
461
|
-
docs/bench-spec.md # bench measurement spec
|
|
462
|
-
docs/library-api-spec.md
|
|
463
|
-
config.json
|
|
464
|
-
responses/ # gitignored chat history
|
|
465
|
-
benchmarks/ # gitignored bench results
|
|
466
|
-
```
|
|
46
|
+
Host tunes the model. Network finds and uses it.
|
|
467
47
|
|
|
468
48
|
## Development
|
|
469
49
|
|
|
470
50
|
```bash
|
|
471
|
-
pnpm install
|
|
472
|
-
pnpm
|
|
473
|
-
pnpm
|
|
474
|
-
pnpm ollanet -- scan # runs from source via tsx
|
|
475
|
-
pnpm build # compile dist/ manually
|
|
51
|
+
pnpm install
|
|
52
|
+
pnpm test
|
|
53
|
+
pnpm ollanet -- help
|
|
476
54
|
```
|
|
477
55
|
|
|
478
|
-
|
|
479
|
-
in-process mock Ollama server — no real models or Tailscale required.
|
|
56
|
+
Site (FilePress + docs mount): `pnpm --dir site ship`
|
|
480
57
|
|
|
481
58
|
## License
|
|
482
59
|
|
|
483
|
-
MIT
|
|
60
|
+
MIT · [Catalyst Forge LLC](https://www.catalystforge.com)
|
package/dist/alias.d.ts
ADDED
package/dist/alias.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage machine+model aliases in config.
|
|
3
|
+
*
|
|
4
|
+
* ollanet alias list
|
|
5
|
+
* ollanet alias add <name> <machine> <model>
|
|
6
|
+
* ollanet alias rm <name>
|
|
7
|
+
*/
|
|
8
|
+
import { configPath, isValidAliasName, loadConfig, mutateConfigFile, } from "./config.js";
|
|
9
|
+
import { failUsage, isHelpFlag, printHelp } from "./argv.js";
|
|
10
|
+
export function helpText() {
|
|
11
|
+
return `Usage:
|
|
12
|
+
ollanet alias list
|
|
13
|
+
ollanet alias add <name> <machine> <model>
|
|
14
|
+
ollanet alias rm <name>
|
|
15
|
+
|
|
16
|
+
Shortcuts for frequent machine + model pairs. After adding:
|
|
17
|
+
|
|
18
|
+
ollanet prompt <name> "hello"
|
|
19
|
+
ollanet pull <name>
|
|
20
|
+
ollanet show <name>
|
|
21
|
+
ollanet bench <name>
|
|
22
|
+
|
|
23
|
+
An explicit model still overrides the alias (e.g. \`ollanet prompt desk other:7b "hi"\`).
|
|
24
|
+
Alias names are case-insensitive (stored lowercase). They take priority over a
|
|
25
|
+
host with the same short name when used as the first token.
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--json Machine-readable output (list only)`;
|
|
29
|
+
}
|
|
30
|
+
function usage() {
|
|
31
|
+
failUsage(helpText());
|
|
32
|
+
}
|
|
33
|
+
function pad(value, width) {
|
|
34
|
+
if (value.length >= width)
|
|
35
|
+
return `${value.slice(0, width - 1)}…`;
|
|
36
|
+
return value.padEnd(width);
|
|
37
|
+
}
|
|
38
|
+
function printTable(aliases) {
|
|
39
|
+
const names = Object.keys(aliases).sort();
|
|
40
|
+
if (names.length === 0) {
|
|
41
|
+
console.log(`No aliases in ${configPath()}`);
|
|
42
|
+
console.log(`Add one with: ollanet alias add <name> <machine> <model>`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(`${pad("ALIAS", 16)} ${pad("MACHINE", 20)} MODEL`);
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
const a = aliases[name];
|
|
48
|
+
console.log(`${pad(name, 16)} ${pad(a.machine, 20)} ${a.model}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function cmdList(json) {
|
|
52
|
+
const config = await loadConfig();
|
|
53
|
+
if (json) {
|
|
54
|
+
console.log(JSON.stringify(config.aliases, null, 2));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
printTable(config.aliases);
|
|
58
|
+
}
|
|
59
|
+
async function cmdAdd(name, machine, model) {
|
|
60
|
+
const key = name.trim().toLowerCase();
|
|
61
|
+
if (!isValidAliasName(key)) {
|
|
62
|
+
throw new Error(`Invalid alias name "${name}". Use letters, digits, _ or - (must start with a letter).`);
|
|
63
|
+
}
|
|
64
|
+
const machineTrim = machine.trim();
|
|
65
|
+
const modelTrim = model.trim();
|
|
66
|
+
if (!machineTrim || !modelTrim) {
|
|
67
|
+
throw new Error("Machine and model are required.");
|
|
68
|
+
}
|
|
69
|
+
await mutateConfigFile((file) => {
|
|
70
|
+
const prev = file.aliases && typeof file.aliases === "object" && !Array.isArray(file.aliases)
|
|
71
|
+
? { ...file.aliases }
|
|
72
|
+
: {};
|
|
73
|
+
prev[key] = { machine: machineTrim, model: modelTrim };
|
|
74
|
+
file.aliases = prev;
|
|
75
|
+
});
|
|
76
|
+
console.log(`Alias "${key}" → ${machineTrim} / ${modelTrim}`);
|
|
77
|
+
console.log(`Saved ${configPath()}`);
|
|
78
|
+
}
|
|
79
|
+
async function cmdRm(name) {
|
|
80
|
+
const key = name.trim().toLowerCase();
|
|
81
|
+
if (!key)
|
|
82
|
+
usage();
|
|
83
|
+
let removed = false;
|
|
84
|
+
await mutateConfigFile((file) => {
|
|
85
|
+
if (!file.aliases || typeof file.aliases !== "object" || Array.isArray(file.aliases)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const map = file.aliases;
|
|
89
|
+
if (key in map) {
|
|
90
|
+
delete map[key];
|
|
91
|
+
removed = true;
|
|
92
|
+
}
|
|
93
|
+
file.aliases = map;
|
|
94
|
+
});
|
|
95
|
+
if (!removed) {
|
|
96
|
+
throw new Error(`No alias named "${key}" in ${configPath()}`);
|
|
97
|
+
}
|
|
98
|
+
console.log(`Removed alias "${key}"`);
|
|
99
|
+
console.log(`Saved ${configPath()}`);
|
|
100
|
+
}
|
|
101
|
+
export async function main() {
|
|
102
|
+
const args = process.argv.slice(2).filter((a) => a !== "--");
|
|
103
|
+
let json = false;
|
|
104
|
+
const positional = [];
|
|
105
|
+
while (args.length > 0) {
|
|
106
|
+
const arg = args.shift();
|
|
107
|
+
if (isHelpFlag(arg))
|
|
108
|
+
printHelp(helpText());
|
|
109
|
+
if (arg === "--json") {
|
|
110
|
+
json = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg.startsWith("-")) {
|
|
114
|
+
console.error(`Unknown flag: ${arg}`);
|
|
115
|
+
usage();
|
|
116
|
+
}
|
|
117
|
+
positional.push(arg);
|
|
118
|
+
}
|
|
119
|
+
const sub = (positional[0] ?? "list").toLowerCase();
|
|
120
|
+
const rest = positional.slice(1);
|
|
121
|
+
if (sub === "list" || sub === "ls") {
|
|
122
|
+
if (rest.length > 0) {
|
|
123
|
+
console.error("Unexpected arguments after list.");
|
|
124
|
+
usage();
|
|
125
|
+
}
|
|
126
|
+
await cmdList(json);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (sub === "add" || sub === "set") {
|
|
130
|
+
if (rest.length !== 3) {
|
|
131
|
+
console.error("Usage: ollanet alias add <name> <machine> <model>");
|
|
132
|
+
usage();
|
|
133
|
+
}
|
|
134
|
+
await cmdAdd(rest[0], rest[1], rest[2]);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (sub === "rm" || sub === "remove" || sub === "delete") {
|
|
138
|
+
if (rest.length !== 1) {
|
|
139
|
+
console.error("Usage: ollanet alias rm <name>");
|
|
140
|
+
usage();
|
|
141
|
+
}
|
|
142
|
+
await cmdRm(rest[0]);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
console.error(`Unknown alias subcommand: ${sub}`);
|
|
146
|
+
usage();
|
|
147
|
+
}
|