ollanet 0.2.2 → 0.4.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 CHANGED
@@ -1,8 +1,16 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Catalyst-Forge-LLC/ollanet/main/assets/ollanet-logo.png" alt="ollanet logo" width="360">
3
+ </p>
4
+
1
5
  # ollanet
2
6
 
3
- Chat with **Ollama** servers on **any network you can reach** — LAN, localhost, Tailscale, VPN, or a raw IP.
7
+ Find the models. Talk to them. Keep the thread.
8
+
9
+ Chat with **Ollama** servers on **any network you can reach** — LAN, localhost, Tailscale, VPN, or a raw IP. CLI for humans, MCP for agents, **Node library for apps**.
10
+
11
+ Scan for models, fire prompts, and continue conversations later by a short hash. No browser UI required. **Node 20+ · zero runtime deps.**
4
12
 
5
- Scan for models, fire prompts, and continue conversations later by a short hash. No browser UI required.
13
+ **Site:** [ollanet.dev](https://ollanet.dev) — Downpress + Cloudflare Pages (`site/`).
6
14
 
7
15
  ## Origin
8
16
 
@@ -17,7 +25,7 @@ Typical loop:
17
25
  1. On the host: `finetuna` → create something like `gemma4-ctx32k`
18
26
  2. From anywhere on the network: `ollanet scan` → `ollanet prompt that-host gemma4-ctx32k "…"`
19
27
 
20
- They share the same Ollama API; Finetuna shapes the models, ollanet finds and chats with them.
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.
21
29
 
22
30
  ## Features
23
31
 
@@ -26,6 +34,28 @@ They share the same Ollama API; Finetuna shapes the models, ollanet finds and ch
26
34
  - **Persist** chats as `responses/<hash>.json` with topic, machine, model, timestamps
27
35
  - **Continue** any thread with `--chat <hash>`
28
36
  - **Configure** per-machine defaults (model, temperature, context, …)
37
+ - **MCP** — `ollanet mcp` exposes scan/prompt/chats as Model Context Protocol tools for agents
38
+ - **Library** — `import { scanNetwork } from "ollanet"` for apps (Node 20+, not the browser)
39
+
40
+ ## Use cases
41
+
42
+ 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:
43
+
44
+ **For agents and automation** — prefer `ollanet mcp` (stdio MCP). The same flows also work via CLI flags:
45
+
46
+ - **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.
47
+ - **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
+ - **A “talk to any Ollama on my network” tool** — `ollanet_prompt` / `prompt … --json --no-stream`; no hard-coded endpoints.
49
+ - **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
+ - **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://github.com/Catalyst-Forge-LLC/finetuna) shapes a GPU-tuned named variant on the host; ollanet discovers and uses it from anywhere on the network.
52
+
53
+ **For humans:**
54
+
55
+ - **Tailscale-native access** — prompt any machine by MagicDNS name from a laptop or phone (Termux) without babysitting IPs or opening a browser.
56
+ - **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.
57
+ - **Multi-GPU lab** — `scan --lan` treats several machines as a pool of inference endpoints; `bench` compares the same model across hardware.
58
+ - **Audit trail / notes** — persisted chat JSON doubles as a research log you can resume from any device.
29
59
 
30
60
  ## Requirements
31
61
 
@@ -39,7 +69,7 @@ Tailscale is optional. If the `tailscale` CLI is present, peers are included aut
39
69
  Run without installing:
40
70
 
41
71
  ```bash
42
- npx ollanet scan # from npm (once published)
72
+ npx ollanet scan
43
73
  npx --allow-git=all github:Catalyst-Forge-LLC/ollanet scan # straight from GitHub
44
74
  ```
45
75
 
@@ -50,7 +80,7 @@ npx --allow-git=all github:Catalyst-Forge-LLC/ollanet scan # straight fro
50
80
  Or install globally, which puts the `ollanet` command on your PATH:
51
81
 
52
82
  ```bash
53
- npm install -g ollanet # from npm (once published)
83
+ npm install -g ollanet
54
84
  npm install -g --allow-git=all github:Catalyst-Forge-LLC/ollanet # from GitHub
55
85
  ollanet scan
56
86
  ```
@@ -97,6 +127,49 @@ ollanet chats
97
127
  ollanet chats --id a1b2c3d4e5f6
98
128
  ```
99
129
 
130
+ ## Use as a library
131
+
132
+ 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.
133
+
134
+ 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`.
135
+
136
+ ```ts
137
+ import { scanNetwork } from "ollanet";
138
+
139
+ const { servers, sources } = await scanNetwork({ lanScan: false });
140
+ for (const s of servers) {
141
+ console.log(s.endpoint, s.models.map((m) => m.name));
142
+ }
143
+ ```
144
+
145
+ | Call | What it does | When apps should use it |
146
+ |---|---|---|
147
+ | `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 |
149
+ | `discoverHosts()` then probe yourself | candidates only, no `/api/tags` | If the app already has its own Ollama client |
150
+
151
+ Pass `config` to skip the file (useful when the app already has its own config):
152
+
153
+ ```ts
154
+ await scanNetwork({
155
+ lanScan: false,
156
+ config: {
157
+ hosts: ["studio.tailnet.ts.net", "192.168.1.50"],
158
+ discovery: { localhost: true, tailscale: true, lan: false },
159
+ },
160
+ });
161
+ ```
162
+
163
+ `runPrompt({ save: false, writeStdout: false })` is a one-shot call that does not write `~/.ollanet/responses/`.
164
+
165
+ Requires **ollanet ≥ 0.4.0**. Bundlers (Vite / SvelteKit SSR):
166
+
167
+ ```ts
168
+ // vite.config.ts
169
+ ssr: { external: ["ollanet"] },
170
+ optimizeDeps: { exclude: ["ollanet"] },
171
+ ```
172
+
100
173
  ## Commands
101
174
 
102
175
  | Command | Description |
@@ -105,6 +178,7 @@ ollanet chats --id a1b2c3d4e5f6
105
178
  | `ollanet prompt …` | Send a prompt / continue a chat |
106
179
  | `ollanet chats` | List or inspect saved transcripts |
107
180
  | `ollanet bench …` | Benchmark models for tok/s + lightweight quality checks |
181
+ | `ollanet mcp` | Stdio [MCP](https://modelcontextprotocol.io) server for agents |
108
182
 
109
183
  ### `scan` options
110
184
 
@@ -142,6 +216,36 @@ Machine can be a discovered name, hostname, FQDN, or IP (`192.168.1.50`, `host:1
142
216
  - `--json` — list summary JSON
143
217
  - `--id <hash>` — show one chat (full JSON with `--json`)
144
218
 
219
+ ### `mcp`
220
+
221
+ ```bash
222
+ ollanet mcp
223
+ ```
224
+
225
+ Runs a **stdio MCP server** (no extra npm deps). Cursor / Claude Desktop / any MCP host can spawn it and call:
226
+
227
+ | Tool | What it does |
228
+ |---|---|
229
+ | `ollanet_scan` | Discover hosts + models (`lan`, `all` optional) |
230
+ | `ollanet_prompt` | Prompt a host or continue with `chat_id` |
231
+ | `ollanet_list_chats` | Summarize saved transcripts |
232
+ | `ollanet_get_chat` | Load one chat (full history) |
233
+
234
+ Example Cursor MCP config (`~/.cursor/mcp.json` or project `.cursor/mcp.json`):
235
+
236
+ ```json
237
+ {
238
+ "mcpServers": {
239
+ "ollanet": {
240
+ "command": "npx",
241
+ "args": ["-y", "ollanet", "mcp"]
242
+ }
243
+ }
244
+ }
245
+ ```
246
+
247
+ 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).
248
+
145
249
  ### `bench`
146
250
 
147
251
  ```bash
@@ -250,6 +354,7 @@ The first turn asks the model for a short topic title (falls back to the prompt
250
354
  ```text
251
355
  ollanet/
252
356
  src/
357
+ index.ts # public library entry (scan, discover, prompt, ollama helpers)
253
358
  cli.ts # subcommand router (CLI entry)
254
359
  hosts.ts # discovery + host resolution
255
360
  scan.ts
@@ -259,11 +364,13 @@ ollanet/
259
364
  config.ts
260
365
  paths.ts # checkout vs installed path resolution
261
366
  ollama-chat.ts # shared /api/chat (+ tags/show/ps helpers)
367
+ mcp.ts # stdio MCP server
262
368
  bench.ts # ollanet bench
263
369
  bench-suite.ts
264
370
  bench-store.ts
265
371
  dist/ # compiled output (published to npm)
266
372
  docs/bench-spec.md # bench measurement spec
373
+ docs/library-api-spec.md
267
374
  config.json
268
375
  responses/ # gitignored chat history
269
376
  benchmarks/ # gitignored bench results
@@ -0,0 +1,3 @@
1
+ export declare function benchmarksDir(): string;
2
+ export declare function newBenchId(): string;
3
+ export declare function saveBenchmark(result: unknown): Promise<string>;
@@ -0,0 +1,25 @@
1
+ export type CaseRole = "check" | "live" | "throughput";
2
+ export interface BenchCase {
3
+ id: string;
4
+ role: CaseRole;
5
+ prompt: string;
6
+ /** Cap for this case; throughput uses settings.throughput_num_predict. */
7
+ num_predict?: number;
8
+ format?: "json";
9
+ check?: (content: string) => {
10
+ ok: boolean;
11
+ detail?: string;
12
+ };
13
+ }
14
+ export type SuiteName = "quick" | "full";
15
+ export declare function getSuiteCases(suite: SuiteName): BenchCase[];
16
+ export declare function suiteRevision(suite: SuiteName): string;
17
+ export declare function comparabilityKey(opts: {
18
+ suite: SuiteName;
19
+ throughputNumPredict: number;
20
+ seed: number;
21
+ temperature: number;
22
+ think: boolean;
23
+ numCtx: number | null;
24
+ }): string;
25
+ export declare function median(values: number[]): number | undefined;
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;
package/dist/bench.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * ollanet bench — speed + lightweight quality suite against Ollama models.
4
3
  */
@@ -0,0 +1,50 @@
1
+ export type ChatRole = "system" | "user" | "assistant";
2
+ export interface ChatMessage {
3
+ role: ChatRole;
4
+ content: string;
5
+ /** Present for thinking models when reasoning was returned separately. */
6
+ thinking?: string;
7
+ timestamp: string;
8
+ machine?: string;
9
+ model?: string;
10
+ stats?: {
11
+ eval_count?: number;
12
+ eval_duration?: number;
13
+ total_duration?: number;
14
+ prompt_eval_count?: number;
15
+ };
16
+ }
17
+ export interface ChatTranscript {
18
+ id: string;
19
+ topic: string;
20
+ created_at: string;
21
+ updated_at: string;
22
+ machine: string;
23
+ model: string;
24
+ system?: string;
25
+ messages: ChatMessage[];
26
+ }
27
+ export declare function responsesDir(): string;
28
+ export declare function newChatId(): string;
29
+ export declare function chatPath(id: string): string;
30
+ export declare function normalizeChatId(id: string): string;
31
+ export declare function ensureResponsesDir(): Promise<string>;
32
+ export declare function loadChat(id: string): Promise<ChatTranscript>;
33
+ export declare function saveChat(chat: ChatTranscript): Promise<string>;
34
+ export declare function createChat(opts: {
35
+ machine: string;
36
+ model: string;
37
+ system?: string;
38
+ topic?: string;
39
+ }): ChatTranscript;
40
+ export declare function appendMessage(chat: ChatTranscript, message: Omit<ChatMessage, "timestamp"> & {
41
+ timestamp?: string;
42
+ }): ChatMessage;
43
+ export declare function listChats(): Promise<ChatTranscript[]>;
44
+ /** Fallback topic from the first user message when model summarization fails. */
45
+ export declare function topicFromPrompt(prompt: string): string;
46
+ export declare function cleanTopic(raw: string, fallback: string): string;
47
+ export declare function toApiMessages(chat: ChatTranscript): Array<{
48
+ role: ChatRole;
49
+ content: string;
50
+ }>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * List saved ollanet chats.
3
+ *
4
+ * Usage:
5
+ * ollanet chats
6
+ * ollanet chats --json
7
+ * ollanet chats --id a1b2c3d4e5f6
8
+ */
9
+ export declare function main(): Promise<void>;
package/dist/chats.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * List saved ollanet chats.
4
3
  *
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ollanet — chat with Ollama servers on any reachable network.
4
+ *
5
+ * ollanet scan
6
+ * ollanet prompt <machine-or-ip> "hello"
7
+ * ollanet chats
8
+ */
9
+ export {};
package/dist/cli.js CHANGED
@@ -17,6 +17,7 @@ Usage:
17
17
  ollanet prompt --chat <hash> <prompt...>
18
18
  ollanet chats [--json] [--id <hash>]
19
19
  ollanet bench <machine|ip> [model...] [--all] [options]
20
+ ollanet mcp
20
21
 
21
22
  Examples:
22
23
  ollanet scan
@@ -27,6 +28,7 @@ Examples:
27
28
  ollanet chats
28
29
  ollanet bench localhost --all
29
30
  ollanet bench localhost llama3.2:1b --runs 3
31
+ ollanet mcp # stdio MCP server for agents
30
32
 
31
33
  Config: ~/.ollanet/config.json when installed, ./config.json in a checkout
32
34
  (override with OLLANET_CONFIG)
@@ -77,6 +79,11 @@ async function main() {
77
79
  await run();
78
80
  return;
79
81
  }
82
+ case "mcp": {
83
+ const { main: run } = await import("./mcp.js");
84
+ await run();
85
+ return;
86
+ }
80
87
  default:
81
88
  console.error(`Unknown command: ${cmd}\n`);
82
89
  process.stderr.write(HELP);
@@ -0,0 +1,45 @@
1
+ import { type DiscoveryConfig, type HostConfigEntry, type HostTarget } from "./hosts.js";
2
+ export type { DiscoveryConfig, HostConfigEntry };
3
+ /** Runtime knobs sent to Ollama `/api/generate`. */
4
+ export interface GenerateSettings {
5
+ temperature?: number;
6
+ num_predict?: number;
7
+ num_ctx?: number;
8
+ /** Ollama `options.seed` (-1 = random). */
9
+ seed?: number;
10
+ keep_alive?: string | number;
11
+ /** `"json"` or a JSON-schema object */
12
+ format?: "json" | Record<string, unknown>;
13
+ system?: string;
14
+ /**
15
+ * Ollama "thinking" models (e.g. qwen3). When false/omitted, requests send
16
+ * `think: false` so tokens go to the visible reply instead of hidden reasoning.
17
+ */
18
+ think?: boolean;
19
+ }
20
+ export interface AppConfig {
21
+ /** Explicit hosts/IPs to probe (LAN, VPN, MagicDNS, etc.). */
22
+ hosts: HostConfigEntry[];
23
+ discovery: DiscoveryConfig;
24
+ defaultModels: Record<string, string>;
25
+ /** Global defaults applied to every prompt. */
26
+ defaults: GenerateSettings;
27
+ /** Per-machine overrides (keyed by short name / hostname / DNS / IP). */
28
+ machineDefaults: Record<string, GenerateSettings & {
29
+ model?: string;
30
+ }>;
31
+ }
32
+ /** Build an AppConfig from an in-memory partial (no file I/O). */
33
+ export declare function configFromPartial(partial?: Partial<AppConfig>): AppConfig;
34
+ export declare function loadConfig(): Promise<AppConfig>;
35
+ export declare function normalizeSettings(input: unknown): GenerateSettings;
36
+ export declare function parseFormat(value: unknown): GenerateSettings["format"] | undefined;
37
+ export declare function hostLookupKeys(host: HostTarget): string[];
38
+ /** Resolve a default model for a host using short name, hostname, DNS, or IP keys. */
39
+ export declare function defaultModelForHost(config: AppConfig, host: HostTarget): string | undefined;
40
+ export declare function machineSettingsForHost(config: AppConfig, host: HostTarget): GenerateSettings;
41
+ /** Later layers win. Undefined values are skipped. */
42
+ export declare function mergeSettings(...layers: Array<GenerateSettings | undefined>): GenerateSettings;
43
+ export declare function settingsFromEnv(): GenerateSettings;
44
+ export declare function settingsSummary(settings: GenerateSettings): string;
45
+ export declare function configPath(): string;
package/dist/config.js CHANGED
@@ -10,6 +10,17 @@ function emptyConfig() {
10
10
  machineDefaults: {},
11
11
  };
12
12
  }
13
+ /** Build an AppConfig from an in-memory partial (no file I/O). */
14
+ export function configFromPartial(partial = {}) {
15
+ const empty = emptyConfig();
16
+ return {
17
+ hosts: Array.isArray(partial.hosts) ? partial.hosts : empty.hosts,
18
+ discovery: partial.discovery ?? empty.discovery,
19
+ defaultModels: partial.defaultModels ?? empty.defaultModels,
20
+ defaults: partial.defaults ?? empty.defaults,
21
+ machineDefaults: partial.machineDefaults ?? empty.machineDefaults,
22
+ };
23
+ }
13
24
  const CONFIG_PATH = process.env.OLLANET_CONFIG ??
14
25
  process.env.OLLAMA_CONFIG ??
15
26
  defaultConfigPath();
@@ -0,0 +1,55 @@
1
+ /** Parse a required numeric env var; invalid values throw instead of becoming NaN. */
2
+ export declare function envInt(name: string, fallback: number): number;
3
+ /** Default Ollama port. Read at call time so a bad env var cannot break `import`. */
4
+ export declare function ollamaPort(): number;
5
+ export type HostSource = "localhost" | "config" | "env" | "tailscale" | "lan" | "direct";
6
+ export type HostConfigEntry = string | {
7
+ name?: string;
8
+ host?: string;
9
+ ip?: string;
10
+ port?: number;
11
+ };
12
+ export interface DiscoveryConfig {
13
+ /** Always include localhost / 127.0.0.1 (default true). */
14
+ localhost?: boolean;
15
+ /** Use Tailscale peers when the CLI is available (default true). */
16
+ tailscale?: boolean;
17
+ /** TCP-scan local LAN CIDRs for open Ollama ports (default false). */
18
+ lan?: boolean;
19
+ /** Explicit CIDRs for LAN scan (default: guessed /24s from local interfaces). */
20
+ cidrs?: string[];
21
+ }
22
+ export interface HostTarget {
23
+ hostname: string;
24
+ dnsName: string;
25
+ ip: string;
26
+ online: boolean;
27
+ os: string;
28
+ isSelf: boolean;
29
+ source: HostSource;
30
+ port: number;
31
+ }
32
+ export interface DiscoveryResult {
33
+ hosts: HostTarget[];
34
+ sources: string[];
35
+ networkLabel: string;
36
+ }
37
+ export interface DiscoverOptions {
38
+ hosts?: HostConfigEntry[];
39
+ discovery?: DiscoveryConfig;
40
+ /** Include Tailscale peers marked offline. */
41
+ includeOffline?: boolean;
42
+ /** Probe local LAN CIDRs for open Ollama ports. */
43
+ lanScan?: boolean;
44
+ }
45
+ export declare function shortName(host: HostTarget): string;
46
+ export declare function ollamaBaseUrl(host: HostTarget, port?: number): string;
47
+ /** Discover candidate Ollama hosts from localhost, config, env, Tailscale, and optional LAN scan. */
48
+ export declare function discoverHosts(opts?: DiscoverOptions): Promise<DiscoveryResult>;
49
+ /**
50
+ * Match a query against already-discovered hosts only (no direct-address fallback).
51
+ * Used when peeling an optional leading hostname before a prompt.
52
+ */
53
+ export declare function findDiscoveredHost(targets: HostTarget[], query: string): HostTarget | undefined;
54
+ /** Resolve a machine name against discovered hosts, or treat it as a direct address. */
55
+ export declare function resolveHost(targets: HostTarget[], query: string): HostTarget;
package/dist/hosts.js CHANGED
@@ -14,7 +14,10 @@ export function envInt(name, fallback) {
14
14
  }
15
15
  return Math.trunc(n);
16
16
  }
17
- export const OLLAMA_PORT = envInt("OLLAMA_PORT", 11434);
17
+ /** Default Ollama port. Read at call time so a bad env var cannot break `import`. */
18
+ export function ollamaPort() {
19
+ return envInt("OLLAMA_PORT", 11434);
20
+ }
18
21
  function cleanDnsName(dnsName) {
19
22
  if (!dnsName)
20
23
  return "";
@@ -29,7 +32,7 @@ export function shortName(host) {
29
32
  }
30
33
  return host.hostname;
31
34
  }
32
- export function ollamaBaseUrl(host, port = host.port || OLLAMA_PORT) {
35
+ export function ollamaBaseUrl(host, port = host.port || ollamaPort()) {
33
36
  const hostPart = host.ip.includes(":") ? `[${host.ip}]` : host.ip;
34
37
  return `http://${hostPart}:${port}`;
35
38
  }
@@ -42,7 +45,7 @@ function makeHost(partial) {
42
45
  os: partial.os ?? "unknown",
43
46
  isSelf: partial.isSelf ?? false,
44
47
  source: partial.source,
45
- port: partial.port ?? OLLAMA_PORT,
48
+ port: partial.port ?? ollamaPort(),
46
49
  };
47
50
  }
48
51
  function dedupeKey(host) {
@@ -114,7 +117,7 @@ function parseHostEntry(entry) {
114
117
  ip: address,
115
118
  source: "config",
116
119
  online: true,
117
- port: typeof entry.port === "number" ? entry.port : OLLAMA_PORT,
120
+ port: typeof entry.port === "number" ? entry.port : ollamaPort(),
118
121
  });
119
122
  }
120
123
  function envHostTargets() {
@@ -298,7 +301,7 @@ function parseDirectAddress(query) {
298
301
  return null;
299
302
  let hostname = q;
300
303
  let ip = q;
301
- let port = OLLAMA_PORT;
304
+ let port = ollamaPort();
302
305
  const ipv4WithPort = q.match(/^(\d{1,3}(?:\.\d{1,3}){3}):(\d+)$/);
303
306
  if (ipv4WithPort) {
304
307
  ip = ipv4WithPort[1];
@@ -365,7 +368,7 @@ export async function discoverHosts(opts = {}) {
365
368
  const lanEnabled = opts.lanScan ?? discovery.lan === true;
366
369
  if (lanEnabled) {
367
370
  const cidrs = discovery.cidrs && discovery.cidrs.length > 0 ? discovery.cidrs : guessLocalCidrs();
368
- const lanHosts = await lanScanTargets(cidrs, OLLAMA_PORT);
371
+ const lanHosts = await lanScanTargets(cidrs, ollamaPort());
369
372
  if (lanHosts.length > 0) {
370
373
  collected.push(...lanHosts);
371
374
  sources.push("lan");
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ollanet — Node library for discovering and talking to Ollama on any
3
+ * reachable network (loopback, LAN, Tailscale, VPN).
4
+ *
5
+ * Node 20+ only. Not for the browser. LAN scan is opt-in.
6
+ *
7
+ * import { scanNetwork } from "ollanet";
8
+ * const { servers } = await scanNetwork({ lanScan: false });
9
+ */
10
+ export { scanNetwork } from "./scan.js";
11
+ export type { ScanOptions, ScanPayload, ScannedServer } from "./scan.js";
12
+ export { discoverHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
13
+ export type { DiscoverOptions, DiscoveryResult, HostTarget, HostSource, HostConfigEntry, } from "./hosts.js";
14
+ export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, } from "./config.js";
15
+ export type { AppConfig, DiscoveryConfig, GenerateSettings } from "./config.js";
16
+ export { runPrompt } from "./prompt.js";
17
+ export type { PromptRunOptions, PromptRunResult } from "./prompt.js";
18
+ export { ollamaChat, ollamaTags, ollamaShow, ollamaPs } from "./ollama-chat.js";
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ollanet — Node library for discovering and talking to Ollama on any
3
+ * reachable network (loopback, LAN, Tailscale, VPN).
4
+ *
5
+ * Node 20+ only. Not for the browser. LAN scan is opt-in.
6
+ *
7
+ * import { scanNetwork } from "ollanet";
8
+ * const { servers } = await scanNetwork({ lanScan: false });
9
+ */
10
+ export { scanNetwork } from "./scan.js";
11
+ export { discoverHosts, resolveHost, ollamaBaseUrl, shortName, ollamaPort, } from "./hosts.js";
12
+ export { loadConfig, defaultModelForHost, mergeSettings, configFromPartial, } from "./config.js";
13
+ export { runPrompt } from "./prompt.js";
14
+ export { ollamaChat, ollamaTags, ollamaShow, ollamaPs } from "./ollama-chat.js";
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ollanet mcp — stdio MCP server (zero deps).
3
+ *
4
+ * Speaks the Model Context Protocol over stdin/stdout so agents (Cursor,
5
+ * Claude Desktop, etc.) can discover hosts, prompt models, and continue chats.
6
+ *
7
+ * ollanet mcp
8
+ *
9
+ * stdout is reserved for JSON-RPC; log only to stderr.
10
+ */
11
+ export declare function main(): Promise<void>;