ollanet 0.6.8 → 0.6.10
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 +45 -7
- package/dist/cli.js +2 -1
- package/dist/config.js +2 -0
- package/dist/hosts.d.ts +11 -0
- package/dist/hosts.js +98 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.js +4 -3
- package/dist/scan.d.ts +2 -0
- package/dist/scan.js +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
# ollanet
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Find, manage, and use Ollama models on the hosts you choose.
|
|
8
8
|
|
|
9
9
|
**CLI** for humans · **MCP** for agents · **Node** for apps.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Discover hosts, give models convenient aliases, and prompt or compare them. A reachable host is not a trusted host. LAN scan is opt-in. Node 20+, zero runtime deps.
|
|
12
12
|
|
|
13
13
|
**Docs:** [ollanet.dev/docs](https://ollanet.dev/docs) · **Site:** [ollanet.dev](https://ollanet.dev)
|
|
14
14
|
|
|
@@ -27,19 +27,57 @@ Or one-off: `npx ollanet scan`
|
|
|
27
27
|
ollanet scan
|
|
28
28
|
ollanet alias add desk studio gemma3:12b
|
|
29
29
|
ollanet prompt desk "What is MagicDNS?"
|
|
30
|
-
ollanet bench desk --hot
|
|
31
|
-
ollanet mcp
|
|
32
30
|
```
|
|
33
31
|
|
|
32
|
+
`alias` writes local config. `prompt` sends that text to the selected host. `pull` and `rm` change models on the remote host. `scan`, `show`, `ps`, and `alias` do not install or delete models.
|
|
33
|
+
|
|
34
34
|
Host-first: `ollanet <cmd> <machine> …`. The machine is a MagicDNS name, a config name, an IP, or an alias for a machine + model pair.
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## Three jobs
|
|
37
|
+
|
|
38
|
+
**Discover.** Probe localhost, config, env, and Tailscale. `--lan` is an opt-in TCP sweep of your subnet on port `11434`.
|
|
39
|
+
|
|
40
|
+
**Manage.** `pull`, `show`, `rm`, and `ps` on a named machine. Comparisons and benches are deeper workflows on [ollanet.dev/docs](https://ollanet.dev/docs).
|
|
41
|
+
|
|
42
|
+
**Use.** Prompt, continue a chat by hash, or call the same inventory from MCP and the Node library.
|
|
43
|
+
|
|
44
|
+
## Reachability and trust
|
|
45
|
+
|
|
46
|
+
ollanet talks to Ollama endpoints you configure or select. A scan that gets an answer means the port responded. It does not mean you should send prompts or pull models there. Authentication and network protection depend on how that Ollama host is deployed. Discovery is not a security boundary, and a LAN is not inherently safe.
|
|
47
|
+
|
|
48
|
+
## Network effects
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
This package does not include vendor telemetry. Commands still cause network traffic to the hosts you name.
|
|
51
|
+
|
|
52
|
+
| Operation | Client sends | Remote host may do | Stored where |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| `scan` | Health and model-list probes to discovered or configured hosts | Answer with tags | Last scan on this machine (`last-scan.json`) |
|
|
55
|
+
| `scan --lan` | Extra TCP probes on local `/24`s, port `11434` | Same, if something answers | Same |
|
|
56
|
+
| `prompt`, `compare`, `bench` | Prompt text, and prior turns when you continue a chat | Run inference | Chats, compares, or benches on this machine unless you pass `--no-save` |
|
|
57
|
+
| `pull` | `POST /api/pull` | Fetch the model from the Ollama registry onto that host | Model files on the remote host |
|
|
58
|
+
| `show`, `ps` | Read requests | Return metadata or loaded-model rows | Nothing new on disk |
|
|
59
|
+
| `rm` | Delete request (`--yes`) | Remove that model from the host | Model removed on the remote host |
|
|
60
|
+
| `alias` | None to Ollama | None | Local `config.json` |
|
|
61
|
+
| `chats` | None to Ollama | None | Reads local transcripts |
|
|
62
|
+
|
|
63
|
+
Other machines see a saved chat only if they share the responses directory (or the same `~/.ollanet/` when installed). This table covers the operations inspected in this repo. It does not prove every possible data path is listed.
|
|
39
64
|
|
|
40
65
|
## Finetuna
|
|
41
66
|
|
|
42
|
-
|
|
67
|
+
[Finetuna](https://finetuna.net) can shape a GPU-tuned named variant on the machine that runs Ollama. That pairing is optional. Each tool works alone.
|
|
68
|
+
|
|
69
|
+
| Role | Tool | Where |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| Host-side runtime settings | Finetuna | The GPU box |
|
|
72
|
+
| Client-side discover, manage, use | ollanet | Any machine that can reach that host |
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ollanet show studio gemma4-ctx32k
|
|
76
|
+
ollanet prompt studio gemma4-ctx32k "What is MagicDNS?"
|
|
77
|
+
ollanet bench studio gemma4-ctx32k --hot --runs 5
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`bench` `quick` reports median tok/s on a 256-token counted decode, plus ping, math, and haiku checks, for that host, model, suite, and run count. Those checks are not a general quality ranking and do not compare across hardware without the same conditions.
|
|
43
81
|
|
|
44
82
|
<!-- xfacts-nutrition-label -->
|
|
45
83
|
|
package/dist/cli.js
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* ollanet chats
|
|
14
14
|
* ollanet mcp
|
|
15
15
|
*/
|
|
16
|
-
const HELP = `ollanet
|
|
16
|
+
const HELP = `ollanet: find, manage, and use Ollama on hosts you choose
|
|
17
17
|
|
|
18
18
|
Discover hosts, manage models, prompt and compare, bench tok/s, and continue
|
|
19
19
|
chats by hash. CLI for humans, MCP for agents, Node for apps.
|
|
20
|
+
A reachable host is not a trusted host. pull and rm change the remote host.
|
|
20
21
|
|
|
21
22
|
Usage:
|
|
22
23
|
ollanet scan [--json] [--all] [--lan] [--last]
|
package/dist/config.js
CHANGED
package/dist/hosts.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ export interface HostTarget {
|
|
|
23
23
|
hostname: string;
|
|
24
24
|
dnsName: string;
|
|
25
25
|
ip: string;
|
|
26
|
+
/**
|
|
27
|
+
* Other IPs that reach this same instance. Used when loopback and Tailscale
|
|
28
|
+
* Self (or a LAN NIC) are the same machine — we probe once and keep both
|
|
29
|
+
* addresses for resolve / display.
|
|
30
|
+
*/
|
|
31
|
+
also?: string[];
|
|
26
32
|
online: boolean;
|
|
27
33
|
os: string;
|
|
28
34
|
isSelf: boolean;
|
|
@@ -44,6 +50,11 @@ export interface DiscoverOptions {
|
|
|
44
50
|
}
|
|
45
51
|
export declare function shortName(host: HostTarget): string;
|
|
46
52
|
export declare function ollamaBaseUrl(host: HostTarget, port?: number): string;
|
|
53
|
+
/**
|
|
54
|
+
* Collapse loopback, Tailscale Self, and this machine's NIC IPs into one host
|
|
55
|
+
* per port so scan does not list the same Ollama twice.
|
|
56
|
+
*/
|
|
57
|
+
export declare function foldLocalHosts(hosts: HostTarget[]): HostTarget[];
|
|
47
58
|
/** Discover candidate Ollama hosts from localhost, config, env, Tailscale, and optional LAN scan. */
|
|
48
59
|
export declare function discoverHosts(opts?: DiscoverOptions): Promise<DiscoveryResult>;
|
|
49
60
|
/**
|
package/dist/hosts.js
CHANGED
|
@@ -37,10 +37,12 @@ export function ollamaBaseUrl(host, port = host.port || ollamaPort()) {
|
|
|
37
37
|
return `http://${hostPart}:${port}`;
|
|
38
38
|
}
|
|
39
39
|
function makeHost(partial) {
|
|
40
|
+
const also = [...new Set((partial.also ?? []).filter((ip) => ip && ip !== partial.ip))];
|
|
40
41
|
return {
|
|
41
42
|
hostname: partial.hostname,
|
|
42
43
|
dnsName: partial.dnsName ?? "",
|
|
43
44
|
ip: partial.ip,
|
|
45
|
+
...(also.length > 0 ? { also } : {}),
|
|
44
46
|
online: partial.online ?? true,
|
|
45
47
|
os: partial.os ?? "unknown",
|
|
46
48
|
isSelf: partial.isSelf ?? false,
|
|
@@ -71,6 +73,7 @@ function mergeHosts(hosts) {
|
|
|
71
73
|
os: existing.os !== "unknown" ? existing.os : host.os,
|
|
72
74
|
isSelf: existing.isSelf || host.isSelf,
|
|
73
75
|
source: existing.source === "direct" ? host.source : existing.source,
|
|
76
|
+
also: [...new Set([...(existing.also ?? []), ...(host.also ?? [])])].filter((ip) => ip !== existing.ip && ip !== host.ip),
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
return [...byKey.values()].sort((a, b) => {
|
|
@@ -79,6 +82,96 @@ function mergeHosts(hosts) {
|
|
|
79
82
|
return shortName(a).localeCompare(shortName(b));
|
|
80
83
|
});
|
|
81
84
|
}
|
|
85
|
+
function isIpv4Literal(value) {
|
|
86
|
+
return /^\d{1,3}(\.\d{1,3}){3}$/.test(value);
|
|
87
|
+
}
|
|
88
|
+
function localIdentityIps() {
|
|
89
|
+
const ips = new Set(["127.0.0.1", "::1"]);
|
|
90
|
+
for (const entries of Object.values(os.networkInterfaces())) {
|
|
91
|
+
for (const entry of entries ?? []) {
|
|
92
|
+
if (entry.family === "IPv4")
|
|
93
|
+
ips.add(entry.address);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return ips;
|
|
97
|
+
}
|
|
98
|
+
function isThisDevice(host, localIps) {
|
|
99
|
+
return host.isSelf || localIps.has(host.ip);
|
|
100
|
+
}
|
|
101
|
+
function nameQuality(host) {
|
|
102
|
+
const n = host.hostname;
|
|
103
|
+
if (!n || isIpv4Literal(n) || n.toLowerCase() === "localhost")
|
|
104
|
+
return 0;
|
|
105
|
+
if (host.source === "config" || host.source === "env")
|
|
106
|
+
return 3;
|
|
107
|
+
if (host.source === "tailscale")
|
|
108
|
+
return 2;
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
function dnsQuality(dns) {
|
|
112
|
+
if (!dns || dns.toLowerCase() === "localhost" || isIpv4Literal(dns))
|
|
113
|
+
return 0;
|
|
114
|
+
if (dns.includes("."))
|
|
115
|
+
return 2;
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
function pickFoldSource(group, probeIsLoopback) {
|
|
119
|
+
const sources = new Set(group.map((h) => h.source));
|
|
120
|
+
if (sources.has("config"))
|
|
121
|
+
return "config";
|
|
122
|
+
if (sources.has("env"))
|
|
123
|
+
return "env";
|
|
124
|
+
if (sources.has("localhost") && probeIsLoopback)
|
|
125
|
+
return "localhost";
|
|
126
|
+
if (sources.has("tailscale"))
|
|
127
|
+
return "tailscale";
|
|
128
|
+
return group[0].source;
|
|
129
|
+
}
|
|
130
|
+
function mergeThisDevice(group) {
|
|
131
|
+
const ips = [...new Set(group.flatMap((h) => [h.ip, ...(h.also ?? [])]))];
|
|
132
|
+
const loopback = ips.find((ip) => ip === "127.0.0.1" || ip === "::1");
|
|
133
|
+
const ip = loopback ?? ips[0];
|
|
134
|
+
const named = [...group].sort((a, b) => nameQuality(b) - nameQuality(a))[0];
|
|
135
|
+
const dnsHost = [...group].sort((a, b) => dnsQuality(b.dnsName) - dnsQuality(a.dnsName))[0];
|
|
136
|
+
return makeHost({
|
|
137
|
+
hostname: named.hostname,
|
|
138
|
+
dnsName: dnsQuality(dnsHost.dnsName) > 0 ? dnsHost.dnsName : named.dnsName,
|
|
139
|
+
ip,
|
|
140
|
+
also: ips.filter((addr) => addr !== ip),
|
|
141
|
+
online: group.some((h) => h.online),
|
|
142
|
+
os: group.find((h) => h.os !== "unknown")?.os ?? named.os,
|
|
143
|
+
isSelf: true,
|
|
144
|
+
source: pickFoldSource(group, Boolean(loopback)),
|
|
145
|
+
port: group[0].port,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Collapse loopback, Tailscale Self, and this machine's NIC IPs into one host
|
|
150
|
+
* per port so scan does not list the same Ollama twice.
|
|
151
|
+
*/
|
|
152
|
+
export function foldLocalHosts(hosts) {
|
|
153
|
+
const localIps = localIdentityIps();
|
|
154
|
+
const groups = new Map();
|
|
155
|
+
const rest = [];
|
|
156
|
+
for (const host of hosts) {
|
|
157
|
+
if (!isThisDevice(host, localIps)) {
|
|
158
|
+
rest.push(host);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const list = groups.get(host.port) ?? [];
|
|
162
|
+
list.push(host);
|
|
163
|
+
groups.set(host.port, list);
|
|
164
|
+
}
|
|
165
|
+
const folded = [];
|
|
166
|
+
for (const group of groups.values()) {
|
|
167
|
+
folded.push(group.length === 1 ? { ...group[0], isSelf: true } : mergeThisDevice(group));
|
|
168
|
+
}
|
|
169
|
+
return [...folded, ...rest].sort((a, b) => {
|
|
170
|
+
if (a.isSelf !== b.isSelf)
|
|
171
|
+
return a.isSelf ? -1 : 1;
|
|
172
|
+
return shortName(a).localeCompare(shortName(b));
|
|
173
|
+
});
|
|
174
|
+
}
|
|
82
175
|
function localhostTargets() {
|
|
83
176
|
const hostname = os.hostname() || "localhost";
|
|
84
177
|
return [
|
|
@@ -378,7 +471,7 @@ export async function discoverHosts(opts = {}) {
|
|
|
378
471
|
sources.push("lan");
|
|
379
472
|
}
|
|
380
473
|
}
|
|
381
|
-
const hosts = mergeHosts(collected);
|
|
474
|
+
const hosts = foldLocalHosts(mergeHosts(collected));
|
|
382
475
|
const networkLabel = labels[0] ??
|
|
383
476
|
(sources.includes("tailscale")
|
|
384
477
|
? "tailscale"
|
|
@@ -388,7 +481,10 @@ export async function discoverHosts(opts = {}) {
|
|
|
388
481
|
return { hosts, sources, networkLabel };
|
|
389
482
|
}
|
|
390
483
|
function hostNameMatches(host, q) {
|
|
391
|
-
const names = [host.hostname, host.dnsName, shortName(host), host.ip].map((n) => n.toLowerCase());
|
|
484
|
+
const names = [host.hostname, host.dnsName, shortName(host), host.ip, ...(host.also ?? [])].map((n) => n.toLowerCase());
|
|
485
|
+
if (host.isSelf || host.ip === "127.0.0.1" || (host.also ?? []).includes("127.0.0.1")) {
|
|
486
|
+
names.push("localhost");
|
|
487
|
+
}
|
|
392
488
|
return names.includes(q) || names.some((n) => n.startsWith(`${q}.`));
|
|
393
489
|
}
|
|
394
490
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ollanet
|
|
3
|
-
*
|
|
2
|
+
* ollanet: Node library for discovering and talking to Ollama on hosts
|
|
3
|
+
* you configure or select (loopback, LAN, Tailscale, VPN).
|
|
4
4
|
*
|
|
5
5
|
* Node 20+ only. Not for the browser. LAN scan is opt-in.
|
|
6
6
|
*
|
|
@@ -11,7 +11,7 @@ export { scanNetwork } from "./scan.js";
|
|
|
11
11
|
export type { ScanOptions, ScanPayload, ScannedServer, ScanFailure } from "./scan.js";
|
|
12
12
|
export { lastScan, lastScanPath, loadLastScan } from "./scan-store.js";
|
|
13
13
|
export type { StoredScan } from "./scan-store.js";
|
|
14
|
-
export { discoverHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
|
|
14
|
+
export { discoverHosts, foldLocalHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
|
|
15
15
|
export type { DiscoverOptions, DiscoveryResult, HostTarget, HostSource, HostConfigEntry, } from "./hosts.js";
|
|
16
16
|
export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, lookupAlias, expandMachineModel, } from "./config.js";
|
|
17
17
|
export type { AppConfig, DiscoveryConfig, GenerateSettings, ModelAlias } from "./config.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ollanet
|
|
3
|
-
*
|
|
2
|
+
* ollanet: Node library for discovering and talking to Ollama on hosts
|
|
3
|
+
* you configure or select (loopback, LAN, Tailscale, VPN).
|
|
4
4
|
*
|
|
5
5
|
* Node 20+ only. Not for the browser. LAN scan is opt-in.
|
|
6
6
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export { scanNetwork } from "./scan.js";
|
|
11
11
|
export { lastScan, lastScanPath, loadLastScan } from "./scan-store.js";
|
|
12
|
-
export { discoverHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
|
|
12
|
+
export { discoverHosts, foldLocalHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
|
|
13
13
|
export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, lookupAlias, expandMachineModel, } from "./config.js";
|
|
14
14
|
export { runPrompt } from "./prompt.js";
|
|
15
15
|
export { runCompare, DEFAULT_COMPARE_PROMPT } from "./compare.js";
|
package/dist/mcp.js
CHANGED
|
@@ -29,9 +29,10 @@ const PROTOCOL_VERSION = "2024-11-05";
|
|
|
29
29
|
const TOOLS = [
|
|
30
30
|
{
|
|
31
31
|
name: "ollanet_scan",
|
|
32
|
-
description: "Discover
|
|
33
|
-
"
|
|
34
|
-
"Optional LAN TCP scan
|
|
32
|
+
description: "Discover configured or selected Ollama hosts and list their models. " +
|
|
33
|
+
"A responding host is reachable, not automatically trusted. " +
|
|
34
|
+
"Optional LAN TCP scan is off unless lan=true (CLI --lan). " +
|
|
35
|
+
"all=true also probes offline Tailscale peers.",
|
|
35
36
|
inputSchema: {
|
|
36
37
|
type: "object",
|
|
37
38
|
properties: {
|
package/dist/scan.d.ts
CHANGED
package/dist/scan.js
CHANGED
|
@@ -105,8 +105,9 @@ function printPayload(payload, cached) {
|
|
|
105
105
|
for (const server of payload.servers) {
|
|
106
106
|
const label = server.dnsName || server.hostname;
|
|
107
107
|
const selfTag = server.self ? " [this device]" : "";
|
|
108
|
+
const also = server.also?.length ? ` also: ${server.also.join(", ")}` : "";
|
|
108
109
|
console.log(`${label}${selfTag}`);
|
|
109
|
-
console.log(` IP: ${server.ip} source: ${server.source} OS: ${server.os}`);
|
|
110
|
+
console.log(` IP: ${server.ip}${also} source: ${server.source} OS: ${server.os}`);
|
|
110
111
|
console.log(` Endpoint: ${server.endpoint}`);
|
|
111
112
|
if (server.models.length === 0) {
|
|
112
113
|
console.log(" Models: (none)");
|
|
@@ -138,6 +139,7 @@ function toPayload(results, networkLabel, sources) {
|
|
|
138
139
|
hostname: r.host.hostname,
|
|
139
140
|
dnsName: r.host.dnsName,
|
|
140
141
|
ip: r.host.ip,
|
|
142
|
+
...(r.host.also?.length ? { also: r.host.also } : {}),
|
|
141
143
|
port: r.host.port,
|
|
142
144
|
os: r.host.os,
|
|
143
145
|
source: r.host.source,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ollanet",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.10",
|
|
4
|
+
"description": "Find, manage, and use Ollama models on the hosts you choose. CLI for humans, MCP for agents, Node for apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ollanet": "./dist/cli.js"
|