ollanet 0.4.1 → 0.6.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.
@@ -50,9 +50,26 @@ export interface OllamaModelInfo {
50
50
  };
51
51
  }
52
52
  export declare function ollamaTags(baseUrl: string, timeoutMs: number): Promise<OllamaModelInfo[]>;
53
- export declare function ollamaShow(baseUrl: string, model: string, timeoutMs: number): Promise<{
53
+ export interface OllamaShowInfo {
54
54
  capabilities?: string[];
55
- }>;
55
+ modelfile?: string;
56
+ parameters?: string;
57
+ template?: string;
58
+ system?: string;
59
+ details?: {
60
+ parent_model?: string;
61
+ format?: string;
62
+ family?: string;
63
+ families?: string[];
64
+ parameter_size?: string;
65
+ quantization_level?: string;
66
+ };
67
+ model_info?: Record<string, unknown>;
68
+ modified_at?: string;
69
+ }
70
+ export declare function ollamaShow(baseUrl: string, model: string, timeoutMs: number): Promise<OllamaShowInfo>;
71
+ /** POST /api/delete — removes the named model from that host's disk. */
72
+ export declare function ollamaDelete(baseUrl: string, model: string, timeoutMs: number): Promise<void>;
56
73
  export declare function ollamaVersion(baseUrl: string, timeoutMs: number): Promise<string | undefined>;
57
74
  export interface PsModel {
58
75
  name?: string;
@@ -61,7 +78,34 @@ export interface PsModel {
61
78
  context_length?: number;
62
79
  digest?: string;
63
80
  }
81
+ export interface PullChunk {
82
+ status?: string;
83
+ digest?: string;
84
+ total?: number;
85
+ completed?: number;
86
+ error?: string;
87
+ }
88
+ export interface OllamaPullOptions {
89
+ baseUrl: string;
90
+ model: string;
91
+ /** Allow HTTP / self-signed registries (Ollama `insecure`). */
92
+ insecure?: boolean;
93
+ stream: boolean;
94
+ timeoutMs: number;
95
+ onProgress?: (chunk: PullChunk) => void;
96
+ }
97
+ export interface OllamaPullResult {
98
+ status: string;
99
+ chunks: PullChunk[];
100
+ }
101
+ /**
102
+ * POST /api/pull — the *server* downloads the model. The client only sends the
103
+ * request. Default timeout 0 (none): large pulls can take hours.
104
+ */
105
+ export declare function ollamaPull(opts: OllamaPullOptions): Promise<OllamaPullResult>;
64
106
  export declare function ollamaPs(baseUrl: string, timeoutMs: number): Promise<PsModel[]>;
107
+ /** Like ollamaPs, but unreachable hosts throw instead of looking empty. */
108
+ export declare function ollamaPsRequired(baseUrl: string, timeoutMs: number): Promise<PsModel[]>;
65
109
  export declare function psHasModel(models: PsModel[], name: string): boolean;
66
110
  export declare function vramForModel(models: PsModel[], name: string): number | undefined;
67
111
  export declare function contextLengthForModel(models: PsModel[], name: string): number | undefined;
@@ -164,6 +164,10 @@ export async function ollamaShow(baseUrl, model, timeoutMs) {
164
164
  const url = `${baseUrl}/api/show`;
165
165
  return ollamaPostJson(url, { model }, timeoutMs);
166
166
  }
167
+ /** POST /api/delete — removes the named model from that host's disk. */
168
+ export async function ollamaDelete(baseUrl, model, timeoutMs) {
169
+ await ollamaPostJson(`${baseUrl}/api/delete`, { model }, timeoutMs);
170
+ }
167
171
  export async function ollamaVersion(baseUrl, timeoutMs) {
168
172
  try {
169
173
  const body = await ollamaGetJson(`${baseUrl}/api/version`, timeoutMs);
@@ -173,15 +177,128 @@ export async function ollamaVersion(baseUrl, timeoutMs) {
173
177
  return undefined;
174
178
  }
175
179
  }
180
+ function parsePullChunk(url, raw) {
181
+ try {
182
+ return JSON.parse(raw);
183
+ }
184
+ catch {
185
+ throw new Error(`Non-JSON response from ${url} (is something else on this port?): ${raw.slice(0, 120)}`);
186
+ }
187
+ }
188
+ /**
189
+ * POST /api/pull — the *server* downloads the model. The client only sends the
190
+ * request. Default timeout 0 (none): large pulls can take hours.
191
+ */
192
+ export async function ollamaPull(opts) {
193
+ const url = `${opts.baseUrl}/api/pull`;
194
+ const body = {
195
+ model: opts.model,
196
+ stream: opts.stream,
197
+ };
198
+ if (opts.insecure)
199
+ body.insecure = true;
200
+ const controller = new AbortController();
201
+ const timer = opts.timeoutMs > 0 ? setTimeout(() => controller.abort(), opts.timeoutMs) : undefined;
202
+ let res;
203
+ try {
204
+ res = await fetch(url, {
205
+ method: "POST",
206
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
207
+ body: JSON.stringify(body),
208
+ signal: controller.signal,
209
+ });
210
+ }
211
+ catch (err) {
212
+ if (timer)
213
+ clearTimeout(timer);
214
+ const name = err instanceof Error ? err.name : "";
215
+ if (name === "AbortError") {
216
+ throw new Error(`Pull timed out after ${opts.timeoutMs}ms (${url})`);
217
+ }
218
+ throw err;
219
+ }
220
+ if (!res.ok) {
221
+ if (timer)
222
+ clearTimeout(timer);
223
+ const text = await res.text().catch(() => "");
224
+ let detail = text;
225
+ try {
226
+ const parsed = JSON.parse(text);
227
+ if (parsed.error)
228
+ detail = parsed.error;
229
+ }
230
+ catch {
231
+ // keep raw text
232
+ }
233
+ throw new Error(`Ollama HTTP ${res.status} ${res.statusText}${detail ? `: ${detail}` : ""}`);
234
+ }
235
+ const chunks = [];
236
+ const take = (chunk) => {
237
+ if (chunk.error)
238
+ throw new Error(chunk.error);
239
+ chunks.push(chunk);
240
+ opts.onProgress?.(chunk);
241
+ };
242
+ try {
243
+ if (!opts.stream) {
244
+ take(parsePullChunk(url, await res.text()));
245
+ }
246
+ else {
247
+ if (!res.body)
248
+ throw new Error("No response body from Ollama");
249
+ const reader = res.body.getReader();
250
+ const decoder = new TextDecoder();
251
+ let buffer = "";
252
+ while (true) {
253
+ const { done, value } = await reader.read();
254
+ if (done)
255
+ break;
256
+ buffer += decoder.decode(value, { stream: true });
257
+ const lines = buffer.split("\n");
258
+ buffer = lines.pop() ?? "";
259
+ for (const line of lines) {
260
+ const trimmed = line.trim();
261
+ if (!trimmed)
262
+ continue;
263
+ take(parsePullChunk(url, trimmed));
264
+ }
265
+ }
266
+ const trailing = buffer.trim();
267
+ if (trailing)
268
+ take(parsePullChunk(url, trailing));
269
+ }
270
+ }
271
+ catch (err) {
272
+ const name = err instanceof Error ? err.name : "";
273
+ if (name === "AbortError") {
274
+ throw new Error(`Pull timed out after ${opts.timeoutMs}ms (${url})`);
275
+ }
276
+ throw err;
277
+ }
278
+ finally {
279
+ if (timer)
280
+ clearTimeout(timer);
281
+ }
282
+ const last = chunks.at(-1);
283
+ const status = last?.status ?? "";
284
+ if (status !== "success") {
285
+ throw new Error(status ? `Pull ended: ${status}` : "Pull failed (no success status)");
286
+ }
287
+ return { status, chunks };
288
+ }
176
289
  export async function ollamaPs(baseUrl, timeoutMs) {
177
290
  try {
178
- const body = await ollamaGetJson(`${baseUrl}/api/ps`, timeoutMs);
179
- return body.models ?? [];
291
+ return await ollamaPsRequired(baseUrl, timeoutMs);
180
292
  }
181
293
  catch {
182
294
  return [];
183
295
  }
184
296
  }
297
+ /** Like ollamaPs, but unreachable hosts throw instead of looking empty. */
298
+ export async function ollamaPsRequired(baseUrl, timeoutMs) {
299
+ const body = await ollamaGetJson(`${baseUrl}/api/ps`, timeoutMs);
300
+ return body.models ?? [];
301
+ }
185
302
  function modelKey(name) {
186
303
  return name.trim().toLowerCase();
187
304
  }
@@ -293,6 +410,8 @@ async function ollamaPostJson(url, body, timeoutMs) {
293
410
  throw new Error(`Ollama HTTP ${res.status} ${res.statusText}${text ? `: ${text}` : ""} (${url})`);
294
411
  }
295
412
  const text = await res.text();
413
+ if (!text.trim())
414
+ return {};
296
415
  try {
297
416
  return JSON.parse(text);
298
417
  }
package/dist/paths.d.ts CHANGED
@@ -15,3 +15,7 @@ export declare function defaultConfigPath(): string;
15
15
  export declare function defaultResponsesDir(): string;
16
16
  /** Default benchmark results dir (mirrors responses layout). */
17
17
  export declare function defaultBenchmarksDir(): string;
18
+ /** Last successful scan snapshot (single file). */
19
+ export declare function defaultLastScanPath(): string;
20
+ /** Head-to-head compare records (markdown + JSON). */
21
+ export declare function defaultComparesDir(): string;
package/dist/paths.js CHANGED
@@ -26,3 +26,11 @@ export function defaultResponsesDir() {
26
26
  export function defaultBenchmarksDir() {
27
27
  return IS_INSTALLED ? path.join(USER_DIR, "benchmarks") : projectPath("benchmarks");
28
28
  }
29
+ /** Last successful scan snapshot (single file). */
30
+ export function defaultLastScanPath() {
31
+ return IS_INSTALLED ? path.join(USER_DIR, "last-scan.json") : projectPath("last-scan.json");
32
+ }
33
+ /** Head-to-head compare records (markdown + JSON). */
34
+ export function defaultComparesDir() {
35
+ return IS_INSTALLED ? path.join(USER_DIR, "compares") : projectPath("compares");
36
+ }
@@ -0,0 +1,7 @@
1
+ export declare function assertPromptFilename(filePath: string): string;
2
+ export declare function readPromptFile(filePath: string): Promise<string>;
3
+ export declare function assemblePrompt(parts: {
4
+ argv?: string;
5
+ file?: string;
6
+ stdin?: string;
7
+ }): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared prompt assembly: argv, --file (.txt / .md), and piped stdin.
3
+ */
4
+ import { readFile, stat } from "node:fs/promises";
5
+ import path from "node:path";
6
+ const ALLOWED = new Set([".txt", ".md"]);
7
+ const MAX_BYTES = 1_000_000;
8
+ export function assertPromptFilename(filePath) {
9
+ const ext = path.extname(filePath).toLowerCase();
10
+ if (!ALLOWED.has(ext)) {
11
+ throw new Error(`Prompt file must be .txt or .md (got "${ext || "no extension"}": ${filePath})`);
12
+ }
13
+ return path.resolve(filePath);
14
+ }
15
+ export async function readPromptFile(filePath) {
16
+ const resolved = assertPromptFilename(filePath);
17
+ const info = await stat(resolved);
18
+ if (!info.isFile()) {
19
+ throw new Error(`Prompt path is not a file: ${resolved}`);
20
+ }
21
+ if (info.size > MAX_BYTES) {
22
+ throw new Error(`Prompt file is too large (${info.size} bytes; max ${MAX_BYTES}): ${resolved}`);
23
+ }
24
+ const text = (await readFile(resolved, "utf8")).replace(/^\uFEFF/, "");
25
+ if (!text.trim()) {
26
+ throw new Error(`Prompt file is empty: ${resolved}`);
27
+ }
28
+ return text;
29
+ }
30
+ export function assemblePrompt(parts) {
31
+ return [parts.argv, parts.file, parts.stdin]
32
+ .map((s) => (s ?? "").trim())
33
+ .filter(Boolean)
34
+ .join("\n\n")
35
+ .trim();
36
+ }
package/dist/prompt.js CHANGED
@@ -16,6 +16,7 @@ import { appendMessage, cleanTopic, createChat, loadChat, saveChat, toApiMessage
16
16
  import { configPath, defaultModelForHost, loadConfig, machineSettingsForHost, mergeSettings, parseFormat, settingsFromEnv, settingsSummary, configFromPartial, } from "./config.js";
17
17
  import { discoverHosts, envInt, findDiscoveredHost, ollamaBaseUrl, resolveHost, shortName, } from "./hosts.js";
18
18
  import { ollamaChat } from "./ollama-chat.js";
19
+ import { assemblePrompt, readPromptFile } from "./prompt-input.js";
19
20
  /** Prompt/chat HTTP timeout (ms). 0 = no timeout. Separate from scan's OLLAMA_TIMEOUT_MS. */
20
21
  function promptTimeoutMs() {
21
22
  return envInt("OLLAMA_PROMPT_TIMEOUT_MS", 600_000);
@@ -30,6 +31,7 @@ Examples:
30
31
  ollanet prompt localhost "Explain MagicDNS"
31
32
  ollanet prompt --chat a1b2c3d4e5f6 "Give an example"
32
33
  ollanet prompt localhost --temperature 0.2 --num-predict 64 "2+2?"
34
+ ollanet prompt localhost --file ./notes.md
33
35
  ollanet chats
34
36
 
35
37
  Each reply is saved under responses/<hash>.json and the hash is printed so you
@@ -51,6 +53,7 @@ Options:
51
53
  --format <json|schema> Force JSON mode or a JSON schema string
52
54
  --think Enable model thinking (qwen3 etc.); streams to stderr
53
55
  --no-think Disable thinking (default) so tokens go to the reply
56
+ --file <path> Prompt from a .txt or .md file (joins argv / stdin)
54
57
  --no-stream Wait for the full response
55
58
  --json Emit final response JSON (implies --no-stream)`);
56
59
  process.exit(1);
@@ -137,6 +140,7 @@ function parseArgs(argv) {
137
140
  let machineFlag;
138
141
  let modelFlag;
139
142
  let chatId;
143
+ let file;
140
144
  let stream = true;
141
145
  let json = false;
142
146
  let save = true;
@@ -181,6 +185,10 @@ function parseArgs(argv) {
181
185
  modelFlag = arg.includes("=") ? arg.slice("--model=".length) : takeValue(args, "--model");
182
186
  continue;
183
187
  }
188
+ if (arg === "--file" || arg === "-f" || arg.startsWith("--file=")) {
189
+ file = arg.includes("=") ? arg.slice("--file=".length) : takeValue(args, "--file");
190
+ continue;
191
+ }
184
192
  if (arg === "--system" || arg.startsWith("--system=")) {
185
193
  settings.system = arg.includes("=")
186
194
  ? arg.slice("--system=".length)
@@ -235,7 +243,7 @@ function parseArgs(argv) {
235
243
  }
236
244
  // Continue (--chat): all positionals are the prompt. Optional machine override
237
245
  // via --machine, or a leading discovered-host name peeled in main().
238
- return { machine, model, promptParts, stream, json, save, chatId, settings };
246
+ return { machine, model, promptParts, file, stream, json, save, chatId, settings };
239
247
  }
240
248
  async function readStdinIfPiped() {
241
249
  if (stdinStream.isTTY)
@@ -261,7 +269,7 @@ async function readStdinIfPiped() {
261
269
  const onEnd = () => done(true);
262
270
  stdinStream.once("readable", onReadable);
263
271
  stdinStream.once("end", onEnd);
264
- const timer = setTimeout(() => done(stdinStream.readableLength > 0), 25);
272
+ const timer = setTimeout(() => done(stdinStream.readableLength > 0), 100);
265
273
  });
266
274
  if (!hasData)
267
275
  return "";
@@ -436,6 +444,17 @@ export async function main() {
436
444
  const { stream, json, save } = parsed;
437
445
  let promptParts = parsed.promptParts;
438
446
  const fromStdin = await readStdinIfPiped();
447
+ let fromFile = "";
448
+ if (parsed.file) {
449
+ try {
450
+ fromFile = await readPromptFile(parsed.file);
451
+ }
452
+ catch (err) {
453
+ console.error(err instanceof Error ? err.message : err);
454
+ process.exitCode = 1;
455
+ return;
456
+ }
457
+ }
439
458
  const config = await loadConfig();
440
459
  const { hosts: targets } = await discoverHosts({
441
460
  hosts: config.hosts,
@@ -460,13 +479,17 @@ export async function main() {
460
479
  usage();
461
480
  }
462
481
  const host = resolveHost(targets, machineQuery);
463
- const peeled = await maybePeelModel(host, promptParts, fromStdin);
482
+ const peeled = await maybePeelModel(host, promptParts, fromStdin || fromFile);
464
483
  peeledModel = peeled.model;
465
484
  promptParts = peeled.promptParts;
466
485
  }
467
- const promptText = [promptParts.join(" ").trim(), fromStdin].filter(Boolean).join("\n\n").trim();
486
+ const promptText = assemblePrompt({
487
+ argv: promptParts.join(" "),
488
+ file: fromFile,
489
+ stdin: fromStdin,
490
+ });
468
491
  if (!promptText) {
469
- console.error("No prompt provided (pass args and/or pipe stdin).");
492
+ console.error("No prompt provided (pass args, --file, and/or pipe stdin).");
470
493
  usage();
471
494
  }
472
495
  try {
package/dist/ps.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * List models currently loaded in VRAM on reachable Ollama hosts.
3
+ *
4
+ * ollanet ps
5
+ * ollanet ps studio
6
+ */
7
+ import { type AppConfig } from "./config.js";
8
+ export interface LoadedModel {
9
+ name: string;
10
+ size_vram?: number;
11
+ context_length?: number;
12
+ digest?: string;
13
+ tuned: boolean;
14
+ }
15
+ export interface LoadedHost {
16
+ machine: string;
17
+ endpoint: string;
18
+ ok: boolean;
19
+ error?: string;
20
+ models: LoadedModel[];
21
+ }
22
+ export interface PsOptions {
23
+ machine?: string;
24
+ config?: Partial<AppConfig>;
25
+ timeoutMs?: number;
26
+ }
27
+ export interface PsResult {
28
+ hosts: LoadedHost[];
29
+ }
30
+ export declare function listLoaded(opts?: PsOptions): Promise<PsResult>;
31
+ export declare function main(): Promise<void>;
package/dist/ps.js ADDED
@@ -0,0 +1,165 @@
1
+ /**
2
+ * List models currently loaded in VRAM on reachable Ollama hosts.
3
+ *
4
+ * ollanet ps
5
+ * ollanet ps studio
6
+ */
7
+ import { configFromPartial, loadConfig } from "./config.js";
8
+ import { discoverHosts, envInt, ollamaBaseUrl, shortName, } from "./hosts.js";
9
+ import { ollamaPs, ollamaPsRequired } from "./ollama-chat.js";
10
+ import { resolveTarget } from "./target.js";
11
+ import { looksTuned } from "./tuned.js";
12
+ function psTimeoutMs() {
13
+ return envInt("OLLAMA_PS_TIMEOUT_MS", 10_000);
14
+ }
15
+ function usage() {
16
+ console.error(`Usage:
17
+ ollanet ps [machine]
18
+ ollanet ps --machine <name>
19
+
20
+ Examples:
21
+ ollanet ps
22
+ ollanet ps studio
23
+ ollanet ps studio --json
24
+
25
+ scan lists models on disk. ps lists what is loaded in VRAM right now.
26
+
27
+ Options:
28
+ --machine <name> One host (default: every discovered host)
29
+ --json Emit result JSON on stdout`);
30
+ process.exit(1);
31
+ }
32
+ function takeValue(args, flag) {
33
+ const value = args.shift();
34
+ if (!value) {
35
+ console.error(`${flag} requires a value`);
36
+ usage();
37
+ }
38
+ return value;
39
+ }
40
+ function parseArgs(argv) {
41
+ const args = [...argv];
42
+ let machineFlag;
43
+ let json = false;
44
+ const positional = [];
45
+ while (args.length > 0) {
46
+ const arg = args.shift();
47
+ if (arg === "--")
48
+ continue;
49
+ if (arg === "--help" || arg === "-h")
50
+ usage();
51
+ if (arg === "--json") {
52
+ json = true;
53
+ continue;
54
+ }
55
+ if (arg === "--machine") {
56
+ machineFlag = takeValue(args, "--machine");
57
+ continue;
58
+ }
59
+ if (arg.startsWith("-")) {
60
+ console.error(`Unknown flag: ${arg}`);
61
+ usage();
62
+ }
63
+ positional.push(arg);
64
+ }
65
+ if (positional.length > 1 || (machineFlag && positional.length > 0)) {
66
+ console.error("Too many arguments.");
67
+ usage();
68
+ }
69
+ return { machine: machineFlag ?? positional[0], json };
70
+ }
71
+ function formatBytes(bytes) {
72
+ if (bytes == null || !Number.isFinite(bytes))
73
+ return "";
74
+ const units = ["B", "KB", "MB", "GB", "TB"];
75
+ let value = bytes;
76
+ let unit = 0;
77
+ while (value >= 1024 && unit < units.length - 1) {
78
+ value /= 1024;
79
+ unit += 1;
80
+ }
81
+ return `${value.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
82
+ }
83
+ function toLoaded(m) {
84
+ const name = (m.name ?? m.model ?? "").trim();
85
+ return {
86
+ name,
87
+ size_vram: m.size_vram,
88
+ context_length: m.context_length,
89
+ digest: m.digest,
90
+ tuned: looksTuned(name),
91
+ };
92
+ }
93
+ async function psHost(host, timeoutMs, required) {
94
+ const endpoint = ollamaBaseUrl(host);
95
+ try {
96
+ const models = required
97
+ ? await ollamaPsRequired(endpoint, timeoutMs)
98
+ : await ollamaPs(endpoint, timeoutMs);
99
+ return {
100
+ machine: shortName(host),
101
+ endpoint,
102
+ ok: true,
103
+ models: models.map(toLoaded).filter((m) => m.name),
104
+ };
105
+ }
106
+ catch (err) {
107
+ return {
108
+ machine: shortName(host),
109
+ endpoint,
110
+ ok: false,
111
+ error: err instanceof Error ? err.message : String(err),
112
+ models: [],
113
+ };
114
+ }
115
+ }
116
+ export async function listLoaded(opts = {}) {
117
+ const timeoutMs = opts.timeoutMs ?? psTimeoutMs();
118
+ if (opts.machine?.trim()) {
119
+ const host = await resolveTarget(opts.machine, opts.config);
120
+ const row = await psHost(host, timeoutMs, true);
121
+ if (!row.ok)
122
+ throw new Error(row.error ?? `ps failed on ${row.machine}`);
123
+ return { hosts: [row] };
124
+ }
125
+ const app = opts.config ? configFromPartial(opts.config) : await loadConfig();
126
+ const { hosts } = await discoverHosts({
127
+ hosts: app.hosts,
128
+ discovery: app.discovery,
129
+ });
130
+ const rows = await Promise.all(hosts
131
+ .filter((h) => h.online || h.isSelf || h.source !== "tailscale")
132
+ .map((h) => psHost(h, timeoutMs, false)));
133
+ return { hosts: rows.filter((r) => r.ok) };
134
+ }
135
+ function printPs(result) {
136
+ if (result.hosts.length === 0) {
137
+ process.stdout.write("No hosts reported loaded models.\n");
138
+ return;
139
+ }
140
+ for (const host of result.hosts) {
141
+ process.stdout.write(`${host.machine} ${host.endpoint}\n`);
142
+ if (host.models.length === 0) {
143
+ process.stdout.write(" (nothing loaded)\n");
144
+ }
145
+ else {
146
+ for (const m of host.models) {
147
+ const vram = formatBytes(m.size_vram);
148
+ const ctx = m.context_length != null ? `ctx ${m.context_length}` : "";
149
+ const tag = m.tuned ? " [tuned]" : "";
150
+ const meta = [vram ? `VRAM ${vram}` : "", ctx].filter(Boolean).join(" ");
151
+ process.stdout.write(meta ? ` ${m.name} ${meta}${tag}\n` : ` ${m.name}${tag}\n`);
152
+ }
153
+ }
154
+ process.stdout.write("\n");
155
+ }
156
+ }
157
+ export async function main() {
158
+ const parsed = parseArgs(process.argv.slice(2));
159
+ const result = await listLoaded({ machine: parsed.machine });
160
+ if (parsed.json) {
161
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
162
+ return;
163
+ }
164
+ printPs(result);
165
+ }
package/dist/pull.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Pull a model onto a reachable Ollama host.
3
+ *
4
+ * ollanet pull studio gemma3:12b
5
+ *
6
+ * `studio` is the machine (discovered name, MagicDNS, hostname, or IP).
7
+ * The *server* downloads from the registry; ollanet only sends POST /api/pull.
8
+ */
9
+ import type { AppConfig } from "./config.js";
10
+ import { type PullChunk } from "./ollama-chat.js";
11
+ export interface PullOptions {
12
+ machine: string;
13
+ model: string;
14
+ insecure?: boolean;
15
+ stream?: boolean;
16
+ /** Progress on stderr. Default true for CLI; pass false for MCP / apps. */
17
+ writeStdout?: boolean;
18
+ quiet?: boolean;
19
+ /** In-memory config; skips the config file when set. */
20
+ config?: Partial<AppConfig>;
21
+ timeoutMs?: number;
22
+ onProgress?: (chunk: PullChunk) => void;
23
+ }
24
+ export interface PullResult {
25
+ machine: string;
26
+ model: string;
27
+ status: string;
28
+ endpoint: string;
29
+ next: {
30
+ hint: string;
31
+ site: string;
32
+ };
33
+ }
34
+ /** Programmatic pull used by the CLI, MCP, and apps. */
35
+ export declare function pullModel(opts: PullOptions): Promise<PullResult>;
36
+ export declare function main(): Promise<void>;