codemaxxing 0.4.10 → 0.4.11
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/dist/utils/ollama.d.ts +2 -2
- package/dist/utils/ollama.js +83 -5
- package/package.json +1 -1
- package/src/utils/ollama.ts +85 -5
package/dist/utils/ollama.d.ts
CHANGED
|
@@ -13,9 +13,9 @@ export interface PullProgress {
|
|
|
13
13
|
percent: number;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Pull a model from Ollama registry.
|
|
16
|
+
* Pull a model from Ollama registry via HTTP API.
|
|
17
|
+
* Falls back to CLI if API fails.
|
|
17
18
|
* Calls onProgress with download updates.
|
|
18
|
-
* Returns a promise that resolves when complete.
|
|
19
19
|
*/
|
|
20
20
|
export declare function pullModel(modelId: string, onProgress?: (progress: PullProgress) => void): Promise<void>;
|
|
21
21
|
export interface OllamaModelInfo {
|
package/dist/utils/ollama.js
CHANGED
|
@@ -69,22 +69,98 @@ export function getOllamaInstallCommand(os) {
|
|
|
69
69
|
case "windows": return "winget install Ollama.Ollama";
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
/** Find the ollama binary path */
|
|
73
|
+
function findOllamaBinary() {
|
|
74
|
+
// Try PATH first
|
|
75
|
+
try {
|
|
76
|
+
const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
|
|
77
|
+
return execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 }).toString().trim().split("\n")[0];
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
// Check known Windows paths
|
|
81
|
+
if (process.platform === "win32") {
|
|
82
|
+
for (const p of getWindowsOllamaPaths()) {
|
|
83
|
+
if (existsSync(p))
|
|
84
|
+
return p;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return "ollama"; // fallback, hope for the best
|
|
88
|
+
}
|
|
72
89
|
/** Start ollama serve in background */
|
|
73
90
|
export function startOllama() {
|
|
74
|
-
const
|
|
91
|
+
const bin = findOllamaBinary();
|
|
92
|
+
const child = spawn(bin, ["serve"], {
|
|
75
93
|
detached: true,
|
|
76
94
|
stdio: "ignore",
|
|
77
95
|
});
|
|
78
96
|
child.unref();
|
|
79
97
|
}
|
|
80
98
|
/**
|
|
81
|
-
* Pull a model from Ollama registry.
|
|
99
|
+
* Pull a model from Ollama registry via HTTP API.
|
|
100
|
+
* Falls back to CLI if API fails.
|
|
82
101
|
* Calls onProgress with download updates.
|
|
83
|
-
* Returns a promise that resolves when complete.
|
|
84
102
|
*/
|
|
85
103
|
export function pullModel(modelId, onProgress) {
|
|
104
|
+
// Try HTTP API first (works even when CLI isn't on PATH)
|
|
105
|
+
return pullModelViaAPI(modelId, onProgress).catch(() => {
|
|
106
|
+
// Fallback to CLI
|
|
107
|
+
return pullModelViaCLI(modelId, onProgress);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function pullModelViaAPI(modelId, onProgress) {
|
|
111
|
+
return new Promise(async (resolve, reject) => {
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch("http://localhost:11434/api/pull", {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: JSON.stringify({ name: modelId, stream: true }),
|
|
117
|
+
});
|
|
118
|
+
if (!res.ok || !res.body) {
|
|
119
|
+
reject(new Error(`Ollama API returned ${res.status}`));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const reader = res.body.getReader();
|
|
123
|
+
const decoder = new TextDecoder();
|
|
124
|
+
let buffer = "";
|
|
125
|
+
while (true) {
|
|
126
|
+
const { done, value } = await reader.read();
|
|
127
|
+
if (done)
|
|
128
|
+
break;
|
|
129
|
+
buffer += decoder.decode(value, { stream: true });
|
|
130
|
+
const lines = buffer.split("\n");
|
|
131
|
+
buffer = lines.pop() || "";
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
if (!line.trim())
|
|
134
|
+
continue;
|
|
135
|
+
try {
|
|
136
|
+
const data = JSON.parse(line);
|
|
137
|
+
if (data.status === "success") {
|
|
138
|
+
onProgress?.({ status: "success", percent: 100 });
|
|
139
|
+
resolve();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (data.total && data.completed) {
|
|
143
|
+
const percent = Math.round((data.completed / data.total) * 100);
|
|
144
|
+
onProgress?.({ status: "downloading", total: data.total, completed: data.completed, percent });
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
onProgress?.({ status: data.status || "working...", percent: 0 });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch { }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
resolve();
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
reject(err);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function pullModelViaCLI(modelId, onProgress) {
|
|
86
161
|
return new Promise((resolve, reject) => {
|
|
87
|
-
const
|
|
162
|
+
const bin = findOllamaBinary();
|
|
163
|
+
const child = spawn(bin, ["pull", modelId], {
|
|
88
164
|
stdio: ["pipe", "pipe", "pipe"],
|
|
89
165
|
});
|
|
90
166
|
let lastOutput = "";
|
|
@@ -172,7 +248,9 @@ export async function stopOllama() {
|
|
|
172
248
|
try {
|
|
173
249
|
// First unload all models from memory
|
|
174
250
|
try {
|
|
175
|
-
|
|
251
|
+
const bin = findOllamaBinary();
|
|
252
|
+
const { spawnSync } = require("child_process");
|
|
253
|
+
spawnSync(bin, ["stop"], { stdio: "pipe", timeout: 5000 });
|
|
176
254
|
}
|
|
177
255
|
catch { /* may fail if no models loaded */ }
|
|
178
256
|
// Kill the server process
|
package/package.json
CHANGED
package/src/utils/ollama.ts
CHANGED
|
@@ -72,9 +72,26 @@ export function getOllamaInstallCommand(os: "macos" | "linux" | "windows"): stri
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/** Find the ollama binary path */
|
|
76
|
+
function findOllamaBinary(): string {
|
|
77
|
+
// Try PATH first
|
|
78
|
+
try {
|
|
79
|
+
const cmd = process.platform === "win32" ? "where ollama" : "which ollama";
|
|
80
|
+
return execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], timeout: 3000 }).toString().trim().split("\n")[0];
|
|
81
|
+
} catch {}
|
|
82
|
+
// Check known Windows paths
|
|
83
|
+
if (process.platform === "win32") {
|
|
84
|
+
for (const p of getWindowsOllamaPaths()) {
|
|
85
|
+
if (existsSync(p)) return p;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return "ollama"; // fallback, hope for the best
|
|
89
|
+
}
|
|
90
|
+
|
|
75
91
|
/** Start ollama serve in background */
|
|
76
92
|
export function startOllama(): void {
|
|
77
|
-
const
|
|
93
|
+
const bin = findOllamaBinary();
|
|
94
|
+
const child = spawn(bin, ["serve"], {
|
|
78
95
|
detached: true,
|
|
79
96
|
stdio: "ignore",
|
|
80
97
|
});
|
|
@@ -89,16 +106,77 @@ export interface PullProgress {
|
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
/**
|
|
92
|
-
* Pull a model from Ollama registry.
|
|
109
|
+
* Pull a model from Ollama registry via HTTP API.
|
|
110
|
+
* Falls back to CLI if API fails.
|
|
93
111
|
* Calls onProgress with download updates.
|
|
94
|
-
* Returns a promise that resolves when complete.
|
|
95
112
|
*/
|
|
96
113
|
export function pullModel(
|
|
97
114
|
modelId: string,
|
|
98
115
|
onProgress?: (progress: PullProgress) => void
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
// Try HTTP API first (works even when CLI isn't on PATH)
|
|
118
|
+
return pullModelViaAPI(modelId, onProgress).catch(() => {
|
|
119
|
+
// Fallback to CLI
|
|
120
|
+
return pullModelViaCLI(modelId, onProgress);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function pullModelViaAPI(
|
|
125
|
+
modelId: string,
|
|
126
|
+
onProgress?: (progress: PullProgress) => void
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
return new Promise(async (resolve, reject) => {
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch("http://localhost:11434/api/pull", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: { "Content-Type": "application/json" },
|
|
133
|
+
body: JSON.stringify({ name: modelId, stream: true }),
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok || !res.body) {
|
|
136
|
+
reject(new Error(`Ollama API returned ${res.status}`));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const reader = res.body.getReader();
|
|
140
|
+
const decoder = new TextDecoder();
|
|
141
|
+
let buffer = "";
|
|
142
|
+
while (true) {
|
|
143
|
+
const { done, value } = await reader.read();
|
|
144
|
+
if (done) break;
|
|
145
|
+
buffer += decoder.decode(value, { stream: true });
|
|
146
|
+
const lines = buffer.split("\n");
|
|
147
|
+
buffer = lines.pop() || "";
|
|
148
|
+
for (const line of lines) {
|
|
149
|
+
if (!line.trim()) continue;
|
|
150
|
+
try {
|
|
151
|
+
const data = JSON.parse(line);
|
|
152
|
+
if (data.status === "success") {
|
|
153
|
+
onProgress?.({ status: "success", percent: 100 });
|
|
154
|
+
resolve();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (data.total && data.completed) {
|
|
158
|
+
const percent = Math.round((data.completed / data.total) * 100);
|
|
159
|
+
onProgress?.({ status: "downloading", total: data.total, completed: data.completed, percent });
|
|
160
|
+
} else {
|
|
161
|
+
onProgress?.({ status: data.status || "working...", percent: 0 });
|
|
162
|
+
}
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
resolve();
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
reject(err);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function pullModelViaCLI(
|
|
174
|
+
modelId: string,
|
|
175
|
+
onProgress?: (progress: PullProgress) => void
|
|
99
176
|
): Promise<void> {
|
|
100
177
|
return new Promise((resolve, reject) => {
|
|
101
|
-
const
|
|
178
|
+
const bin = findOllamaBinary();
|
|
179
|
+
const child = spawn(bin, ["pull", modelId], {
|
|
102
180
|
stdio: ["pipe", "pipe", "pipe"],
|
|
103
181
|
});
|
|
104
182
|
|
|
@@ -198,7 +276,9 @@ export async function stopOllama(): Promise<{ ok: boolean; message: string }> {
|
|
|
198
276
|
try {
|
|
199
277
|
// First unload all models from memory
|
|
200
278
|
try {
|
|
201
|
-
|
|
279
|
+
const bin = findOllamaBinary();
|
|
280
|
+
const { spawnSync } = require("child_process");
|
|
281
|
+
spawnSync(bin, ["stop"], { stdio: "pipe", timeout: 5000 });
|
|
202
282
|
} catch { /* may fail if no models loaded */ }
|
|
203
283
|
|
|
204
284
|
// Kill the server process
|