ollanet 0.6.3 → 0.6.4

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 CHANGED
@@ -10,474 +10,49 @@ 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) — 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:
48
-
49
- - **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.
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)
68
-
69
- Tailscale is optional. If the `tailscale` CLI is present, peers are included automatically.
13
+ **Docs:** [ollanet.dev/docs](https://ollanet.dev/docs) · **Site:** [ollanet.dev](https://ollanet.dev)
70
14
 
71
15
  ## Install
72
16
 
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
17
  ```bash
87
18
  npm install -g ollanet
88
- npm install -g --allow-git=all github:Catalyst-Forge-LLC/ollanet # from GitHub
89
19
  ollanet scan
90
20
  ```
91
21
 
92
- When installed this way, config lives at `~/.ollanet/config.json` and chats are
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/`.
22
+ Or one-off: `npx ollanet scan`
112
23
 
113
24
  ## Quick start
114
25
 
115
26
  ```bash
116
- # Find Ollama servers (localhost + configured hosts + Tailscale if available)
117
27
  ollanet scan
118
-
119
- # Also TCP-scan your local /24s for open Ollama ports
120
- ollanet scan --lan
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
28
+ ollanet alias add desk studio gemma3:12b
29
+ ollanet prompt desk "What is MagicDNS?"
30
+ ollanet bench desk --hot
301
31
  ollanet mcp
302
32
  ```
303
33
 
304
- Runs a **stdio MCP server** (no extra npm deps). Cursor / Claude Desktop / any MCP host can spawn it and call:
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.
34
+ Host-first: `ollanet <cmd> <machine> …` — the machine is MagicDNS / config name / IP, or an alias for a machine+model pair.
353
35
 
354
- `quick` has only two scored checks (`ping`, `math`), so `pass` is a coarse
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.
36
+ ## What you get
357
37
 
358
- ## Discovery
38
+ 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
39
 
360
- ollanet combines several sources (deduped by `ip:port`):
40
+ ## Finetuna
361
41
 
362
- | Source | When |
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` |
42
+ 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
43
 
370
- ## Configuration
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
- ```
44
+ Host tunes the model. Network finds and uses it.
467
45
 
468
46
  ## Development
469
47
 
470
48
  ```bash
471
- pnpm install # builds dist/ via prepare
472
- pnpm typecheck
473
- pnpm test # build dist/, then run node:test suite
474
- pnpm ollanet -- scan # runs from source via tsx
475
- pnpm build # compile dist/ manually
49
+ pnpm install
50
+ pnpm test
51
+ pnpm ollanet -- help
476
52
  ```
477
53
 
478
- Tests use Node’s built-in test runner against `dist/` (what gets published), with an
479
- in-process mock Ollama server — no real models or Tailscale required.
54
+ Site (FilePress + docs mount): `pnpm --dir site ship`
480
55
 
481
56
  ## License
482
57
 
483
- MIT
58
+ MIT · [Catalyst Forge LLC](https://www.catalystforge.com)
@@ -0,0 +1,9 @@
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
+ export declare function helpText(): string;
9
+ export declare function main(): Promise<void>;
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
+ }