get-claudia 1.51.0 → 1.51.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/bin/index.js +185 -7
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -202,6 +202,145 @@ class ProgressRenderer {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// ─── Ollama helpers ──────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
/** Check if Ollama CLI is installed (on PATH or in common locations). */
|
|
208
|
+
async function isOllamaInstalled() {
|
|
209
|
+
// Check PATH
|
|
210
|
+
const which = isWindows ? 'where' : 'which';
|
|
211
|
+
const found = await new Promise((resolve) => {
|
|
212
|
+
const proc = spawn(which, ['ollama'], { stdio: 'pipe', timeout: 5000 });
|
|
213
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
214
|
+
proc.on('error', () => resolve(false));
|
|
215
|
+
});
|
|
216
|
+
if (found) return true;
|
|
217
|
+
|
|
218
|
+
// Check common install locations
|
|
219
|
+
if (process.platform === 'darwin') {
|
|
220
|
+
return existsSync('/usr/local/bin/ollama') || existsSync('/opt/homebrew/bin/ollama');
|
|
221
|
+
} else if (!isWindows) {
|
|
222
|
+
return existsSync('/usr/local/bin/ollama') || existsSync('/usr/bin/ollama');
|
|
223
|
+
}
|
|
224
|
+
return existsSync(join(process.env.LOCALAPPDATA || '', 'Ollama', 'ollama.exe'));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Install Ollama automatically.
|
|
229
|
+
* macOS: uses brew if available, otherwise curl installer
|
|
230
|
+
* Linux: uses official curl installer
|
|
231
|
+
* Windows: skip (requires manual download from ollama.com)
|
|
232
|
+
*/
|
|
233
|
+
async function installOllama() {
|
|
234
|
+
if (isWindows) return false; // Windows needs manual install from ollama.com
|
|
235
|
+
|
|
236
|
+
if (process.platform === 'darwin') {
|
|
237
|
+
// Try Homebrew first
|
|
238
|
+
const hasBrew = await new Promise((resolve) => {
|
|
239
|
+
const proc = spawn('which', ['brew'], { stdio: 'pipe', timeout: 5000 });
|
|
240
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
241
|
+
proc.on('error', () => resolve(false));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (hasBrew) {
|
|
245
|
+
return new Promise((resolve) => {
|
|
246
|
+
const proc = spawn('brew', ['install', 'ollama'], { stdio: 'pipe', timeout: 120000 });
|
|
247
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
248
|
+
proc.on('error', () => resolve(false));
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Linux and macOS fallback: official install script
|
|
254
|
+
return new Promise((resolve) => {
|
|
255
|
+
const proc = spawn('sh', ['-c', 'curl -fsSL https://ollama.com/install.sh | sh'], {
|
|
256
|
+
stdio: 'pipe',
|
|
257
|
+
timeout: 120000
|
|
258
|
+
});
|
|
259
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
260
|
+
proc.on('error', () => resolve(false));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Start the Ollama service and wait for it to respond.
|
|
266
|
+
* On macOS: open the Ollama app or run `ollama serve` in background.
|
|
267
|
+
* On Linux: run `ollama serve` in background.
|
|
268
|
+
* Returns true if Ollama API responds within ~15 seconds.
|
|
269
|
+
*/
|
|
270
|
+
async function startOllama() {
|
|
271
|
+
try {
|
|
272
|
+
if (process.platform === 'darwin') {
|
|
273
|
+
// Try macOS app first (installed by brew cask or .dmg), fall back to serve
|
|
274
|
+
const appExists = existsSync('/Applications/Ollama.app');
|
|
275
|
+
if (appExists) {
|
|
276
|
+
spawn('open', ['-a', 'Ollama'], { stdio: 'pipe', detached: true }).unref();
|
|
277
|
+
} else {
|
|
278
|
+
spawn('ollama', ['serve'], { stdio: 'pipe', detached: true }).unref();
|
|
279
|
+
}
|
|
280
|
+
} else if (!isWindows) {
|
|
281
|
+
spawn('ollama', ['serve'], { stdio: 'pipe', detached: true }).unref();
|
|
282
|
+
} else {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Poll until API responds (up to 15 seconds)
|
|
290
|
+
for (let i = 0; i < 15; i++) {
|
|
291
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
292
|
+
try {
|
|
293
|
+
const resp = await fetch('http://127.0.0.1:11434/api/version');
|
|
294
|
+
if (resp.ok) return true;
|
|
295
|
+
} catch { /* not ready yet */ }
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Ensure Ollama's Ed25519 identity key exists at ~/.ollama/id_ed25519.
|
|
302
|
+
* A fresh Ollama install sometimes creates ~/.ollama/ without the key file,
|
|
303
|
+
* causing registry pull requests to fail silently. We generate one with
|
|
304
|
+
* ssh-keygen (available on macOS, Linux, and Windows with Git).
|
|
305
|
+
*/
|
|
306
|
+
async function ensureOllamaKey() {
|
|
307
|
+
const ollamaDir = join(homedir(), '.ollama');
|
|
308
|
+
const keyPath = join(ollamaDir, 'id_ed25519');
|
|
309
|
+
if (existsSync(keyPath)) return;
|
|
310
|
+
|
|
311
|
+
mkdirSync(ollamaDir, { recursive: true });
|
|
312
|
+
try {
|
|
313
|
+
await new Promise((resolve, reject) => {
|
|
314
|
+
const proc = spawn('ssh-keygen', ['-t', 'ed25519', '-f', keyPath, '-N', '', '-q'], {
|
|
315
|
+
stdio: 'pipe',
|
|
316
|
+
timeout: 10000
|
|
317
|
+
});
|
|
318
|
+
proc.on('close', (code) => code === 0 ? resolve() : reject(new Error(`ssh-keygen exited ${code}`)));
|
|
319
|
+
proc.on('error', reject);
|
|
320
|
+
});
|
|
321
|
+
} catch {
|
|
322
|
+
// ssh-keygen unavailable or failed; Ollama will need a restart to self-generate.
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Restart Ollama so it regenerates missing config (identity keys, etc.).
|
|
328
|
+
* Kills the running process, waits, then delegates to startOllama().
|
|
329
|
+
*/
|
|
330
|
+
async function restartOllama() {
|
|
331
|
+
try {
|
|
332
|
+
const killCmd = isWindows ? 'taskkill' : 'pkill';
|
|
333
|
+
const killArgs = isWindows ? ['/f', '/im', 'ollama.exe'] : ['-f', 'ollama'];
|
|
334
|
+
await new Promise((resolve) => {
|
|
335
|
+
const proc = spawn(killCmd, killArgs, { stdio: 'pipe', timeout: 5000 });
|
|
336
|
+
proc.on('close', () => resolve());
|
|
337
|
+
proc.on('error', () => resolve());
|
|
338
|
+
});
|
|
339
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
340
|
+
} catch { /* ignore */ }
|
|
341
|
+
return startOllama();
|
|
342
|
+
}
|
|
343
|
+
|
|
205
344
|
// ─── Main ───────────────────────────────────────────────────────────────
|
|
206
345
|
|
|
207
346
|
async function main() {
|
|
@@ -314,7 +453,7 @@ async function main() {
|
|
|
314
453
|
let memoryOk = false;
|
|
315
454
|
|
|
316
455
|
try {
|
|
317
|
-
// Step 1: Environment -- check Node.js version
|
|
456
|
+
// Step 1: Environment -- check Node.js version, detect/install/start Ollama
|
|
318
457
|
renderer.update('environment', 'active', 'checking...');
|
|
319
458
|
const nodeVersion = process.versions.node;
|
|
320
459
|
const nodeMajor = parseInt(nodeVersion.split('.')[0], 10);
|
|
@@ -324,15 +463,32 @@ async function main() {
|
|
|
324
463
|
throw new Error('Node 18+ required');
|
|
325
464
|
}
|
|
326
465
|
|
|
327
|
-
//
|
|
466
|
+
// Phase 1: Is Ollama running?
|
|
328
467
|
let ollamaOk = false;
|
|
329
468
|
try {
|
|
330
469
|
const resp = await fetch('http://127.0.0.1:11434/api/version');
|
|
331
|
-
if (resp.ok)
|
|
332
|
-
|
|
470
|
+
if (resp.ok) ollamaOk = true;
|
|
471
|
+
} catch { /* not running */ }
|
|
472
|
+
|
|
473
|
+
// Phase 2: If not running, is it installed?
|
|
474
|
+
if (!ollamaOk) {
|
|
475
|
+
const ollamaInstalled = await isOllamaInstalled();
|
|
476
|
+
|
|
477
|
+
if (!ollamaInstalled) {
|
|
478
|
+
// Phase 3: Not installed at all. Install it.
|
|
479
|
+
renderer.update('environment', 'active', 'installing Ollama...');
|
|
480
|
+
const installed = await installOllama();
|
|
481
|
+
if (!installed) {
|
|
482
|
+
renderer.update('environment', 'warn', `Node ${nodeVersion}, no Ollama`);
|
|
483
|
+
if (!supportsInPlace) renderer.appendLine('environment', 'warn', `Node ${nodeVersion}, no Ollama`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Phase 4: Installed (or just installed). Try starting it.
|
|
488
|
+
if (!ollamaOk) {
|
|
489
|
+
renderer.update('environment', 'active', 'starting Ollama...');
|
|
490
|
+
ollamaOk = await startOllama();
|
|
333
491
|
}
|
|
334
|
-
} catch {
|
|
335
|
-
// Ollama not running
|
|
336
492
|
}
|
|
337
493
|
|
|
338
494
|
if (ollamaOk) {
|
|
@@ -358,15 +514,37 @@ async function main() {
|
|
|
358
514
|
} catch { /* ignore */ }
|
|
359
515
|
|
|
360
516
|
if (!modelReady) {
|
|
517
|
+
// Ensure Ollama's identity key exists (required for registry pulls).
|
|
518
|
+
// A fresh Ollama install may have ~/.ollama/ but no key file,
|
|
519
|
+
// causing silent pull failures. Generate one with ssh-keygen if missing.
|
|
520
|
+
await ensureOllamaKey();
|
|
521
|
+
|
|
361
522
|
renderer.update('models', 'active', 'pulling all-minilm:l6-v2...');
|
|
523
|
+
let pullOk = false;
|
|
362
524
|
try {
|
|
363
525
|
const pullResp = await fetch('http://127.0.0.1:11434/api/pull', {
|
|
364
526
|
method: 'POST',
|
|
365
527
|
headers: { 'Content-Type': 'application/json' },
|
|
366
528
|
body: JSON.stringify({ name: 'all-minilm:l6-v2', stream: false })
|
|
367
529
|
});
|
|
368
|
-
|
|
530
|
+
pullOk = pullResp.ok;
|
|
369
531
|
} catch { /* ignore */ }
|
|
532
|
+
|
|
533
|
+
// If pull failed, restart Ollama (regenerates keys) and retry once
|
|
534
|
+
if (!pullOk) {
|
|
535
|
+
renderer.update('models', 'active', 'retrying pull...');
|
|
536
|
+
await restartOllama();
|
|
537
|
+
try {
|
|
538
|
+
const retryResp = await fetch('http://127.0.0.1:11434/api/pull', {
|
|
539
|
+
method: 'POST',
|
|
540
|
+
headers: { 'Content-Type': 'application/json' },
|
|
541
|
+
body: JSON.stringify({ name: 'all-minilm:l6-v2', stream: false })
|
|
542
|
+
});
|
|
543
|
+
pullOk = retryResp.ok;
|
|
544
|
+
} catch { /* ignore */ }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
modelReady = pullOk;
|
|
370
548
|
}
|
|
371
549
|
|
|
372
550
|
if (modelReady) {
|