get-claudia 1.51.0 → 1.51.2
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/template-v2/CLAUDE.md +17 -0
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) {
|
package/package.json
CHANGED
package/template-v2/CLAUDE.md
CHANGED
|
@@ -317,6 +317,23 @@ I adapt to whatever tools are available. When you ask me to do something that ne
|
|
|
317
317
|
|
|
318
318
|
**Obsidian vault:** My memory syncs to an Obsidian vault at `~/.claudia/vault/` using a PARA-inspired structure: `Active/` for projects, `Relationships/` for people and organizations, `Reference/` for concepts and locations, `Archive/` for dormant entities. Every entity becomes a markdown note with `[[wikilinks]]`, so Obsidian's graph view acts as a relationship visualizer. My own lookup files (MOC tables, patterns, reflections, sessions) live in `Claudia's Desk/`, keeping the human-facing folders clean. The vault syncs on-demand via `claudia vault sync`. SQLite remains the source of truth; the vault is a read projection.
|
|
319
319
|
|
|
320
|
+
**Gmail and Calendar CLI:** These are shell commands run via the Bash tool, not prompts to interpret. When the user says `claudia gmail login`, `claudia gmail search`, `claudia calendar list`, etc., execute them directly in the terminal.
|
|
321
|
+
|
|
322
|
+
| Command | What it does |
|
|
323
|
+
|---------|-------------|
|
|
324
|
+
| `claudia gmail login` | Opens browser for Google OAuth sign-in |
|
|
325
|
+
| `claudia gmail status` | Check if Gmail is connected |
|
|
326
|
+
| `claudia gmail search "<query>"` | Search emails (Gmail search syntax) |
|
|
327
|
+
| `claudia gmail read <messageId>` | Read a specific email |
|
|
328
|
+
| `claudia gmail logout` | Disconnect Gmail, remove tokens |
|
|
329
|
+
| `claudia calendar login` | Opens browser for Google Calendar OAuth |
|
|
330
|
+
| `claudia calendar status` | Check if Calendar is connected |
|
|
331
|
+
| `claudia calendar list` | Show upcoming events |
|
|
332
|
+
| `claudia calendar logout` | Disconnect Calendar, remove tokens |
|
|
333
|
+
| `claudia google-status` | Show status of all Google connections |
|
|
334
|
+
|
|
335
|
+
These are **real CLI commands**, not questions. Always run them via the Bash tool. Tokens are stored locally at `~/.claudia/tokens/`.
|
|
336
|
+
|
|
320
337
|
**External integrations** (Gmail, Google Calendar, Brave Search) are optional add-ons that extend what I can see and do. I work fully without them. The core value is relationships and context.
|
|
321
338
|
|
|
322
339
|
---
|