ollanet 0.3.0 → 0.4.1
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 +57 -7
- package/dist/bench-store.d.ts +3 -0
- package/dist/bench-suite.d.ts +25 -0
- package/dist/bench.d.ts +1 -0
- package/dist/bench.js +0 -1
- package/dist/chat-store.d.ts +50 -0
- package/dist/chats.d.ts +9 -0
- package/dist/chats.js +0 -1
- package/dist/cli.d.ts +9 -0
- package/dist/config.d.ts +45 -0
- package/dist/config.js +11 -0
- package/dist/hosts.d.ts +55 -0
- package/dist/hosts.js +9 -6
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +3 -1
- package/dist/ollama-chat.d.ts +87 -0
- package/dist/paths.d.ts +17 -0
- package/dist/prompt.d.ts +41 -0
- package/dist/prompt.js +8 -7
- package/dist/scan.d.ts +44 -0
- package/dist/scan.js +16 -12
- package/package.json +19 -6
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# ollanet
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Find the models. Talk to them. Keep the thread.
|
|
8
8
|
|
|
9
|
-
|
|
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
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
|
+
|
|
13
|
+
**Site:** [ollanet.dev](https://ollanet.dev) — FilePress + Cloudflare Pages (`site/`).
|
|
12
14
|
|
|
13
15
|
## Origin
|
|
14
16
|
|
|
@@ -23,16 +25,17 @@ Typical loop:
|
|
|
23
25
|
1. On the host: `finetuna` → create something like `gemma4-ctx32k`
|
|
24
26
|
2. From anywhere on the network: `ollanet scan` → `ollanet prompt that-host gemma4-ctx32k "…"`
|
|
25
27
|
|
|
26
|
-
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.
|
|
27
29
|
|
|
28
30
|
## Features
|
|
29
31
|
|
|
30
|
-
- **Discover** Ollama hosts from localhost, `config.hosts`, `OLLANET_HOSTS`, optional Tailscale, and optional LAN scan
|
|
32
|
+
- **Discover** Ollama hosts from localhost, `config.hosts`, `OLLANET_HOSTS`, optional Tailscale, and optional LAN scan (`--lan`, off by default)
|
|
31
33
|
- **Prompt** any hostname/IP/model with streaming replies
|
|
32
34
|
- **Persist** chats as `responses/<hash>.json` with topic, machine, model, timestamps
|
|
33
35
|
- **Continue** any thread with `--chat <hash>`
|
|
34
36
|
- **Configure** per-machine defaults (model, temperature, context, …)
|
|
35
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)
|
|
36
39
|
|
|
37
40
|
## Use cases
|
|
38
41
|
|
|
@@ -43,6 +46,7 @@ The core idea: turn “I have to remember which IP has which models loaded” in
|
|
|
43
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.
|
|
44
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>`).
|
|
45
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).
|
|
46
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/`.
|
|
47
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.
|
|
48
52
|
|
|
@@ -65,7 +69,7 @@ Tailscale is optional. If the `tailscale` CLI is present, peers are included aut
|
|
|
65
69
|
Run without installing:
|
|
66
70
|
|
|
67
71
|
```bash
|
|
68
|
-
npx ollanet scan
|
|
72
|
+
npx ollanet scan
|
|
69
73
|
npx --allow-git=all github:Catalyst-Forge-LLC/ollanet scan # straight from GitHub
|
|
70
74
|
```
|
|
71
75
|
|
|
@@ -76,7 +80,7 @@ npx --allow-git=all github:Catalyst-Forge-LLC/ollanet scan # straight fro
|
|
|
76
80
|
Or install globally, which puts the `ollanet` command on your PATH:
|
|
77
81
|
|
|
78
82
|
```bash
|
|
79
|
-
npm install -g ollanet
|
|
83
|
+
npm install -g ollanet
|
|
80
84
|
npm install -g --allow-git=all github:Catalyst-Forge-LLC/ollanet # from GitHub
|
|
81
85
|
ollanet scan
|
|
82
86
|
```
|
|
@@ -123,6 +127,49 @@ ollanet chats
|
|
|
123
127
|
ollanet chats --id a1b2c3d4e5f6
|
|
124
128
|
```
|
|
125
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 on port 11434 | Checkbox; never the default scan, 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
|
+
|
|
126
173
|
## Commands
|
|
127
174
|
|
|
128
175
|
| Command | Description |
|
|
@@ -307,6 +354,7 @@ The first turn asks the model for a short topic title (falls back to the prompt
|
|
|
307
354
|
```text
|
|
308
355
|
ollanet/
|
|
309
356
|
src/
|
|
357
|
+
index.ts # public library entry (scan, discover, prompt, ollama helpers)
|
|
310
358
|
cli.ts # subcommand router (CLI entry)
|
|
311
359
|
hosts.ts # discovery + host resolution
|
|
312
360
|
scan.ts
|
|
@@ -316,11 +364,13 @@ ollanet/
|
|
|
316
364
|
config.ts
|
|
317
365
|
paths.ts # checkout vs installed path resolution
|
|
318
366
|
ollama-chat.ts # shared /api/chat (+ tags/show/ps helpers)
|
|
367
|
+
mcp.ts # stdio MCP server
|
|
319
368
|
bench.ts # ollanet bench
|
|
320
369
|
bench-suite.ts
|
|
321
370
|
bench-store.ts
|
|
322
371
|
dist/ # compiled output (published to npm)
|
|
323
372
|
docs/bench-spec.md # bench measurement spec
|
|
373
|
+
docs/library-api-spec.md
|
|
324
374
|
config.json
|
|
325
375
|
responses/ # gitignored chat history
|
|
326
376
|
benchmarks/ # gitignored bench results
|
|
@@ -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;
|
package/dist/bench.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
package/dist/bench.js
CHANGED
|
@@ -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
|
+
}>;
|
package/dist/chats.d.ts
ADDED
package/dist/chats.js
CHANGED
package/dist/cli.d.ts
ADDED
package/dist/config.d.ts
ADDED
|
@@ -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();
|
package/dist/hosts.d.ts
ADDED
|
@@ -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. Off unless true. */
|
|
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
|
-
|
|
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 ||
|
|
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 ??
|
|
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 :
|
|
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 =
|
|
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,
|
|
371
|
+
const lanHosts = await lanScanTargets(cidrs, ollamaPort());
|
|
369
372
|
if (lanHosts.length > 0) {
|
|
370
373
|
collected.push(...lanHosts);
|
|
371
374
|
sources.push("lan");
|
package/dist/index.d.ts
ADDED
|
@@ -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>;
|
package/dist/mcp.js
CHANGED
|
@@ -14,13 +14,15 @@ import { listChats, loadChat } from "./chat-store.js";
|
|
|
14
14
|
import { projectPath } from "./paths.js";
|
|
15
15
|
import { runPrompt } from "./prompt.js";
|
|
16
16
|
import { scanNetwork } from "./scan.js";
|
|
17
|
+
// Pin to the oldest widely-supported MCP revision. Clients may request a newer
|
|
18
|
+
// date; we still answer with this — legal negotiation, max compatibility.
|
|
17
19
|
const PROTOCOL_VERSION = "2024-11-05";
|
|
18
20
|
const TOOLS = [
|
|
19
21
|
{
|
|
20
22
|
name: "ollanet_scan",
|
|
21
23
|
description: "Discover reachable Ollama hosts and list their models. " +
|
|
22
24
|
"Use this before routing work to pick a host/model. " +
|
|
23
|
-
"Optional LAN TCP scan (
|
|
25
|
+
"Optional LAN TCP scan (off unless lan=true; equivalent to CLI --lan) and include offline Tailscale peers.",
|
|
24
26
|
inputSchema: {
|
|
25
27
|
type: "object",
|
|
26
28
|
properties: {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Ollama /api/chat client used by prompt and bench.
|
|
3
|
+
*/
|
|
4
|
+
import type { GenerateSettings } from "./config.js";
|
|
5
|
+
export interface ChatChunk {
|
|
6
|
+
model?: string;
|
|
7
|
+
message?: {
|
|
8
|
+
role?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
thinking?: string;
|
|
11
|
+
};
|
|
12
|
+
done?: boolean;
|
|
13
|
+
done_reason?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
total_duration?: number;
|
|
16
|
+
load_duration?: number;
|
|
17
|
+
prompt_eval_count?: number;
|
|
18
|
+
eval_count?: number;
|
|
19
|
+
eval_duration?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface OllamaChatOptions {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
model: string;
|
|
24
|
+
messages: Array<{
|
|
25
|
+
role: string;
|
|
26
|
+
content: string;
|
|
27
|
+
}>;
|
|
28
|
+
stream: boolean;
|
|
29
|
+
settings: GenerateSettings;
|
|
30
|
+
/** Write assistant content to stdout / thinking to stderr when streaming. */
|
|
31
|
+
writeStdout: boolean;
|
|
32
|
+
/** Timeout in ms; 0 = none. */
|
|
33
|
+
timeoutMs: number;
|
|
34
|
+
}
|
|
35
|
+
export interface OllamaChatResult {
|
|
36
|
+
content: string;
|
|
37
|
+
thinking: string;
|
|
38
|
+
chunk: ChatChunk;
|
|
39
|
+
}
|
|
40
|
+
/** POST /api/chat with optional streaming and AbortSignal timeout. */
|
|
41
|
+
export declare function ollamaChat(opts: OllamaChatOptions): Promise<OllamaChatResult>;
|
|
42
|
+
export interface OllamaModelInfo {
|
|
43
|
+
name: string;
|
|
44
|
+
digest?: string;
|
|
45
|
+
size?: number;
|
|
46
|
+
details?: {
|
|
47
|
+
parameter_size?: string;
|
|
48
|
+
quantization_level?: string;
|
|
49
|
+
family?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function ollamaTags(baseUrl: string, timeoutMs: number): Promise<OllamaModelInfo[]>;
|
|
53
|
+
export declare function ollamaShow(baseUrl: string, model: string, timeoutMs: number): Promise<{
|
|
54
|
+
capabilities?: string[];
|
|
55
|
+
}>;
|
|
56
|
+
export declare function ollamaVersion(baseUrl: string, timeoutMs: number): Promise<string | undefined>;
|
|
57
|
+
export interface PsModel {
|
|
58
|
+
name?: string;
|
|
59
|
+
model?: string;
|
|
60
|
+
size_vram?: number;
|
|
61
|
+
context_length?: number;
|
|
62
|
+
digest?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function ollamaPs(baseUrl: string, timeoutMs: number): Promise<PsModel[]>;
|
|
65
|
+
export declare function psHasModel(models: PsModel[], name: string): boolean;
|
|
66
|
+
export declare function vramForModel(models: PsModel[], name: string): number | undefined;
|
|
67
|
+
export declare function contextLengthForModel(models: PsModel[], name: string): number | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Capability filter: omitempty / empty means "unknown → treat as completion".
|
|
70
|
+
* Only skip when a non-empty capabilities array is present and lacks completion.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isCompletionCapable(capabilities: string[] | undefined | null): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Vision-capable models (moondream, llava, …) often fail a text-only suite.
|
|
75
|
+
* Only true when capabilities are present and include vision — absent/empty → false
|
|
76
|
+
* (don't skip unknown older servers).
|
|
77
|
+
*/
|
|
78
|
+
export declare function isVisionCapable(capabilities: string[] | undefined | null): boolean;
|
|
79
|
+
/** True when capabilities are known and lack thinking (absent/empty → no warning). */
|
|
80
|
+
export declare function shouldWarnNoThinking(capabilities: string[] | undefined | null): boolean;
|
|
81
|
+
/** Poll /api/ps until model is absent (or timeout). Returns elapsed ms. */
|
|
82
|
+
export declare function waitUntilUnloaded(baseUrl: string, model: string, timeoutMs: number, pollMs?: number): Promise<{
|
|
83
|
+
unloaded: boolean;
|
|
84
|
+
waitedMs: number;
|
|
85
|
+
}>;
|
|
86
|
+
/** Request unload via keep_alive: 0 (no-op-ish if already unloaded after a short chat). */
|
|
87
|
+
export declare function ollamaUnload(baseUrl: string, model: string, timeoutMs: number): Promise<void>;
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Package root: parent of `src/` in a checkout, parent of `dist/` when installed. */
|
|
2
|
+
export declare const PROJECT_ROOT: string;
|
|
3
|
+
export declare function projectPath(...parts: string[]): string;
|
|
4
|
+
/**
|
|
5
|
+
* True when running from an installed copy (npx cache, global install, or as a
|
|
6
|
+
* dependency) rather than a source checkout. Installed copies always live under
|
|
7
|
+
* a `node_modules` directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare const IS_INSTALLED: boolean;
|
|
10
|
+
/** Per-user data dir used when installed (config + chat transcripts). */
|
|
11
|
+
export declare const USER_DIR: string;
|
|
12
|
+
/** Default config location: repo-local in a checkout, `~/.ollanet/` when installed. */
|
|
13
|
+
export declare function defaultConfigPath(): string;
|
|
14
|
+
/** Default transcript dir: repo-local in a checkout, `~/.ollanet/` when installed. */
|
|
15
|
+
export declare function defaultResponsesDir(): string;
|
|
16
|
+
/** Default benchmark results dir (mirrors responses layout). */
|
|
17
|
+
export declare function defaultBenchmarksDir(): string;
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a prompt / continue a chat against an Ollama server on the network.
|
|
3
|
+
*
|
|
4
|
+
* New chat (saved by default):
|
|
5
|
+
* ollanet prompt localhost "What is Tailscale?"
|
|
6
|
+
* ollanet prompt 192.168.1.50 "Hello"
|
|
7
|
+
*
|
|
8
|
+
* Continue:
|
|
9
|
+
* ollanet prompt --chat a1b2c3d4e5f6 "Tell me more"
|
|
10
|
+
*
|
|
11
|
+
* List chats:
|
|
12
|
+
* ollanet chats
|
|
13
|
+
*/
|
|
14
|
+
import { type AppConfig, type GenerateSettings } from "./config.js";
|
|
15
|
+
export interface PromptRunOptions {
|
|
16
|
+
machine?: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
chatId?: string;
|
|
20
|
+
/** Persist transcript. Default true; pass false for one-shot app calls. */
|
|
21
|
+
save?: boolean;
|
|
22
|
+
settings?: GenerateSettings;
|
|
23
|
+
/** When false, never write the reply to stdout (MCP / programmatic). Default true for CLI. */
|
|
24
|
+
writeStdout?: boolean;
|
|
25
|
+
stream?: boolean;
|
|
26
|
+
quiet?: boolean;
|
|
27
|
+
/** In-memory config; skips the config file when set. */
|
|
28
|
+
config?: Partial<AppConfig>;
|
|
29
|
+
}
|
|
30
|
+
export interface PromptRunResult {
|
|
31
|
+
chat_id: string | null;
|
|
32
|
+
topic: string;
|
|
33
|
+
content: string;
|
|
34
|
+
thinking: string | null;
|
|
35
|
+
machine: string;
|
|
36
|
+
model: string;
|
|
37
|
+
ollama: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/** Programmatic prompt used by CLI `--json` and the MCP server. */
|
|
40
|
+
export declare function runPrompt(opts: PromptRunOptions): Promise<PromptRunResult>;
|
|
41
|
+
export declare function main(): Promise<void>;
|
package/dist/prompt.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Run a prompt / continue a chat against an Ollama server on the network.
|
|
4
3
|
*
|
|
@@ -14,11 +13,13 @@
|
|
|
14
13
|
*/
|
|
15
14
|
import { stdin as stdinStream } from "node:process";
|
|
16
15
|
import { appendMessage, cleanTopic, createChat, loadChat, saveChat, toApiMessages, topicFromPrompt, } from "./chat-store.js";
|
|
17
|
-
import { configPath, defaultModelForHost, loadConfig, machineSettingsForHost, mergeSettings, parseFormat, settingsFromEnv, settingsSummary, } from "./config.js";
|
|
16
|
+
import { configPath, defaultModelForHost, loadConfig, machineSettingsForHost, mergeSettings, parseFormat, settingsFromEnv, settingsSummary, configFromPartial, } from "./config.js";
|
|
18
17
|
import { discoverHosts, envInt, findDiscoveredHost, ollamaBaseUrl, resolveHost, shortName, } from "./hosts.js";
|
|
19
18
|
import { ollamaChat } from "./ollama-chat.js";
|
|
20
19
|
/** Prompt/chat HTTP timeout (ms). 0 = no timeout. Separate from scan's OLLAMA_TIMEOUT_MS. */
|
|
21
|
-
|
|
20
|
+
function promptTimeoutMs() {
|
|
21
|
+
return envInt("OLLAMA_PROMPT_TIMEOUT_MS", 600_000);
|
|
22
|
+
}
|
|
22
23
|
function usage() {
|
|
23
24
|
console.error(`Usage:
|
|
24
25
|
ollanet prompt <machine> [model] <prompt...>
|
|
@@ -77,7 +78,7 @@ function modelNameMatches(available, candidate) {
|
|
|
77
78
|
async function listModelNames(host) {
|
|
78
79
|
const url = `${ollamaBaseUrl(host)}/api/tags`;
|
|
79
80
|
const controller = new AbortController();
|
|
80
|
-
const timer = setTimeout(() => controller.abort(), Math.min(
|
|
81
|
+
const timer = setTimeout(() => controller.abort(), Math.min(promptTimeoutMs() || 2500, 5000));
|
|
81
82
|
try {
|
|
82
83
|
const res = await fetch(url, {
|
|
83
84
|
signal: controller.signal,
|
|
@@ -278,7 +279,7 @@ async function generateTopic(opts) {
|
|
|
278
279
|
model: opts.model,
|
|
279
280
|
stream: false,
|
|
280
281
|
writeStdout: false,
|
|
281
|
-
timeoutMs:
|
|
282
|
+
timeoutMs: promptTimeoutMs(),
|
|
282
283
|
settings: mergeSettings(opts.settings, {
|
|
283
284
|
temperature: 0.2,
|
|
284
285
|
num_predict: 24,
|
|
@@ -315,7 +316,7 @@ export async function runPrompt(opts) {
|
|
|
315
316
|
const quiet = opts.quiet === true || opts.writeStdout === false;
|
|
316
317
|
const writeStdout = opts.writeStdout !== false;
|
|
317
318
|
let promptParts = opts.prompt ? [opts.prompt] : [];
|
|
318
|
-
const config = await loadConfig();
|
|
319
|
+
const config = opts.config ? configFromPartial(opts.config) : await loadConfig();
|
|
319
320
|
const { hosts: targets } = await discoverHosts({
|
|
320
321
|
hosts: config.hosts,
|
|
321
322
|
discovery: config.discovery,
|
|
@@ -388,7 +389,7 @@ export async function runPrompt(opts) {
|
|
|
388
389
|
messages: toApiMessages(chat),
|
|
389
390
|
stream: writeStdout ? stream : false,
|
|
390
391
|
writeStdout,
|
|
391
|
-
timeoutMs:
|
|
392
|
+
timeoutMs: promptTimeoutMs(),
|
|
392
393
|
settings,
|
|
393
394
|
});
|
|
394
395
|
appendMessage(chat, {
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan reachable networks for Ollama servers and list available models.
|
|
3
|
+
*
|
|
4
|
+
* Usage: ollanet scan [--lan] [--json] [--all]
|
|
5
|
+
*/
|
|
6
|
+
import { type AppConfig } from "./config.js";
|
|
7
|
+
export interface ScanOptions {
|
|
8
|
+
includeOffline?: boolean;
|
|
9
|
+
/** TCP-scan local /24s for Ollama (port 11434 by default). Off unless set. */
|
|
10
|
+
lanScan?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* In-memory config. When set, the config file (`~/.ollanet` / `OLLANET_CONFIG`)
|
|
13
|
+
* is not read. Omit to keep CLI/MCP file behavior.
|
|
14
|
+
*/
|
|
15
|
+
config?: Partial<AppConfig>;
|
|
16
|
+
}
|
|
17
|
+
export interface ScannedServer {
|
|
18
|
+
hostname: string;
|
|
19
|
+
dnsName: string;
|
|
20
|
+
ip: string;
|
|
21
|
+
port: number;
|
|
22
|
+
os: string;
|
|
23
|
+
source: string;
|
|
24
|
+
self: boolean;
|
|
25
|
+
endpoint: string;
|
|
26
|
+
models: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
size?: number;
|
|
29
|
+
modified_at?: string;
|
|
30
|
+
parameter_size?: string;
|
|
31
|
+
quantization_level?: string;
|
|
32
|
+
family?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface ScanPayload {
|
|
36
|
+
network: string;
|
|
37
|
+
sources: string[];
|
|
38
|
+
port: number;
|
|
39
|
+
scanned: number;
|
|
40
|
+
servers: ScannedServer[];
|
|
41
|
+
}
|
|
42
|
+
/** Programmatic scan used by CLI `--json`, MCP, and `import { scanNetwork } from "ollanet"`. */
|
|
43
|
+
export declare function scanNetwork(options?: ScanOptions): Promise<ScanPayload>;
|
|
44
|
+
export declare function main(): Promise<void>;
|
package/dist/scan.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
/**
|
|
3
2
|
* Scan reachable networks for Ollama servers and list available models.
|
|
4
3
|
*
|
|
5
4
|
* Usage: ollanet scan [--lan] [--json] [--all]
|
|
6
5
|
*/
|
|
7
|
-
import { loadConfig } from "./config.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { configFromPartial, loadConfig } from "./config.js";
|
|
7
|
+
import { discoverHosts, envInt, ollamaPort, shortName, } from "./hosts.js";
|
|
8
|
+
function scanTimeoutMs() {
|
|
9
|
+
return envInt("OLLAMA_TIMEOUT_MS", 2500);
|
|
10
|
+
}
|
|
11
|
+
function scanConcurrency() {
|
|
12
|
+
return Math.max(1, envInt("OLLAMA_CONCURRENCY", 16));
|
|
13
|
+
}
|
|
11
14
|
function formatBytes(bytes) {
|
|
12
15
|
if (bytes == null || !Number.isFinite(bytes))
|
|
13
16
|
return "";
|
|
@@ -22,8 +25,9 @@ function formatBytes(bytes) {
|
|
|
22
25
|
}
|
|
23
26
|
async function fetchModels(host) {
|
|
24
27
|
const url = `http://${host.ip.includes(":") ? `[${host.ip}]` : host.ip}:${host.port}/api/tags`;
|
|
28
|
+
const timeoutMs = scanTimeoutMs();
|
|
25
29
|
const controller = new AbortController();
|
|
26
|
-
const timer = setTimeout(() => controller.abort(),
|
|
30
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
31
|
try {
|
|
28
32
|
const res = await fetch(url, {
|
|
29
33
|
signal: controller.signal,
|
|
@@ -66,7 +70,7 @@ async function scanHost(host) {
|
|
|
66
70
|
const name = err instanceof Error ? err.name : "";
|
|
67
71
|
const message = err instanceof Error ? err.message : String(err);
|
|
68
72
|
const error = name === "AbortError" || message.toLowerCase().includes("abort")
|
|
69
|
-
? `timeout after ${
|
|
73
|
+
? `timeout after ${scanTimeoutMs()}ms`
|
|
70
74
|
: message;
|
|
71
75
|
return { host, ok: false, models: [], error, url };
|
|
72
76
|
}
|
|
@@ -117,7 +121,7 @@ function toPayload(results, networkLabel, sources) {
|
|
|
117
121
|
return {
|
|
118
122
|
network: networkLabel,
|
|
119
123
|
sources,
|
|
120
|
-
port:
|
|
124
|
+
port: ollamaPort(),
|
|
121
125
|
scanned: results.length,
|
|
122
126
|
servers: results
|
|
123
127
|
.filter((r) => r.ok)
|
|
@@ -141,9 +145,9 @@ function toPayload(results, networkLabel, sources) {
|
|
|
141
145
|
})),
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
|
-
/** Programmatic scan used by CLI `--json
|
|
148
|
+
/** Programmatic scan used by CLI `--json`, MCP, and `import { scanNetwork } from "ollanet"`. */
|
|
145
149
|
export async function scanNetwork(options = {}) {
|
|
146
|
-
const config = await loadConfig();
|
|
150
|
+
const config = options.config ? configFromPartial(options.config) : await loadConfig();
|
|
147
151
|
const { hosts, sources, networkLabel } = await discoverHosts({
|
|
148
152
|
hosts: config.hosts,
|
|
149
153
|
discovery: config.discovery,
|
|
@@ -153,7 +157,7 @@ export async function scanNetwork(options = {}) {
|
|
|
153
157
|
if (hosts.length === 0) {
|
|
154
158
|
throw new Error("No hosts to scan. Add config.hosts, set OLLANET_HOSTS, or enable Tailscale/LAN discovery.");
|
|
155
159
|
}
|
|
156
|
-
const results = await mapPool(hosts,
|
|
160
|
+
const results = await mapPool(hosts, scanConcurrency(), scanHost);
|
|
157
161
|
return toPayload(results, networkLabel, sources);
|
|
158
162
|
}
|
|
159
163
|
export async function main() {
|
|
@@ -172,7 +176,7 @@ export async function main() {
|
|
|
172
176
|
process.exitCode = 1;
|
|
173
177
|
return;
|
|
174
178
|
}
|
|
175
|
-
const results = await mapPool(hosts,
|
|
179
|
+
const results = await mapPool(hosts, scanConcurrency(), scanHost);
|
|
176
180
|
if (jsonOut) {
|
|
177
181
|
console.log(JSON.stringify(toPayload(results, networkLabel, sources), null, 2));
|
|
178
182
|
return;
|
package/package.json
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ollanet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Discover and talk to Ollama on any reachable network — CLI, MCP, and a Node library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ollanet": "./dist/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./scan": {
|
|
15
|
+
"types": "./dist/scan.d.ts",
|
|
16
|
+
"import": "./dist/scan.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
9
21
|
"files": [
|
|
10
22
|
"dist",
|
|
11
23
|
"README.md",
|
|
12
24
|
"LICENSE"
|
|
13
25
|
],
|
|
14
26
|
"scripts": {
|
|
15
|
-
"build": "tsc -p tsconfig.build.json",
|
|
16
|
-
"prepare": "tsc -p tsconfig.build.json",
|
|
27
|
+
"build": "tsc -p tsconfig.build.json && node scripts/fix-dts-extensions.mjs",
|
|
28
|
+
"prepare": "tsc -p tsconfig.build.json && node scripts/fix-dts-extensions.mjs",
|
|
17
29
|
"ollanet": "tsx src/cli.ts",
|
|
18
30
|
"scan": "tsx src/cli.ts scan",
|
|
19
31
|
"prompt": "tsx src/cli.ts prompt",
|
|
@@ -22,7 +34,7 @@
|
|
|
22
34
|
"mcp": "tsx src/cli.ts mcp",
|
|
23
35
|
"start": "tsx src/cli.ts scan",
|
|
24
36
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"test": "tsc -p tsconfig.build.json && node --test test/*.test.mjs",
|
|
37
|
+
"test": "tsc -p tsconfig.build.json && node scripts/fix-dts-extensions.mjs && node --test test/*.test.mjs",
|
|
26
38
|
"test:only": "node --test test/*.test.mjs",
|
|
27
39
|
"site:dev": "pnpm --dir site dev",
|
|
28
40
|
"site:build": "pnpm --dir site build",
|
|
@@ -37,7 +49,8 @@
|
|
|
37
49
|
"tailscale",
|
|
38
50
|
"network",
|
|
39
51
|
"mcp",
|
|
40
|
-
"model-context-protocol"
|
|
52
|
+
"model-context-protocol",
|
|
53
|
+
"library"
|
|
41
54
|
],
|
|
42
55
|
"author": "Catalyst Forge LLC",
|
|
43
56
|
"license": "MIT",
|