meetsoma 0.1.4 → 0.1.6
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/personality.js +4 -4
- package/dist/postinstall.js +30 -0
- package/dist/thin-cli.js +539 -73
- package/package.json +5 -1
package/dist/personality.js
CHANGED
|
@@ -246,9 +246,9 @@ const TOPICS = {
|
|
|
246
246
|
// ── Practical / instructional ───────────────────────────────────────
|
|
247
247
|
|
|
248
248
|
how_to_install: [
|
|
249
|
-
`{
|
|
249
|
+
`{Press Enter|Just hit Enter|Enter} and {I'll walk you through it|Soma handles the rest|the setup takes about a minute}. It {downloads the runtime|grabs everything you need|installs automatically}, {sets up your API key|walks you through authentication|helps you connect an AI provider}, and {you're ready to go|you can start right away|launches your first session}. {All you need is|Requirements:} {Node.js 20+|Node 20 or newer} and {git|git installed}. {That's it|Nothing else to do|One flow, start to finish}.`,
|
|
250
250
|
|
|
251
|
-
`
|
|
251
|
+
`{Hit Enter|Press Enter|Just Enter} — {Soma walks you through everything|the setup is guided|it's step by step}. {Downloads the runtime|Installs the engine|Gets everything ready}, {helps you set up an API key|handles authentication|connects you to an AI provider}, {done in about a minute|quick setup|takes sixty seconds}. {Need|Requirements:} {Node.js 20+|Node 20 or newer} and {git|git installed}.`,
|
|
252
252
|
],
|
|
253
253
|
|
|
254
254
|
how_to_source: [
|
|
@@ -266,7 +266,7 @@ const TOPICS = {
|
|
|
266
266
|
],
|
|
267
267
|
|
|
268
268
|
how_to_api_key: [
|
|
269
|
-
`{Yes|You'll need one|Required
|
|
269
|
+
`{Yes|You'll need one|Required}, but {Soma walks you through it|the setup handles it|we'll set it up together} when you install. {You bring your own key|It's your key|You get one from Anthropic} — {Soma stores it locally|it stays on your machine|nothing gets sent to us}. {If you have a Claude Pro or Max subscription|Got a Claude subscription?|Claude Pro/Max users}, you can {log in with your account instead|skip the API key entirely|use OAuth — no key needed}. {Press Enter to get started|Hit Enter and I'll walk you through it|Ready? Just press Enter}.`,
|
|
270
270
|
],
|
|
271
271
|
|
|
272
272
|
how_to_model: [
|
|
@@ -274,7 +274,7 @@ const TOPICS = {
|
|
|
274
274
|
],
|
|
275
275
|
|
|
276
276
|
how_to_start: [
|
|
277
|
-
`{
|
|
277
|
+
`{Press Enter|Hit Enter|Just Enter} — {Soma handles everything|the setup is guided|I'll walk you through it}. {Installs the runtime|Downloads what you need|Gets everything ready}, {helps you connect an AI provider|sets up your API key|handles auth}, and {you can launch right away|your first session starts immediately|you're coding in about a minute}. After that, {cd into any project|go to a project directory} and run ${"`soma`"} — {it creates a .soma/ directory|Soma sets up in your project} and {starts learning how you work|begins adapting|picks up your patterns}. {By session five|After a few sessions|Give it a week} — {you'll feel the difference|it knows your workflow|it remembers everything}.`,
|
|
278
278
|
],
|
|
279
279
|
|
|
280
280
|
how_to_try: [
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall.js — runs after `npm install -g meetsoma`
|
|
4
|
+
*
|
|
5
|
+
* If we're in an interactive terminal, launch the welcome/setup flow.
|
|
6
|
+
* If not (CI, piped, scripts), just print a short message.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { execFileSync } from "child_process";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
16
|
+
// Interactive — run the full welcome + setup
|
|
17
|
+
try {
|
|
18
|
+
execFileSync("node", [join(__dirname, "thin-cli.js")], {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
cwd: process.env.HOME || process.env.USERPROFILE || process.cwd(),
|
|
21
|
+
});
|
|
22
|
+
} catch {
|
|
23
|
+
// Non-zero exit is fine (user ctrl+c, etc.)
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
// Non-interactive — just tell them what to do
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(" ✓ Soma installed. Run \x1b[32msoma\x1b[0m to get started.");
|
|
29
|
+
console.log("");
|
|
30
|
+
}
|
package/dist/thin-cli.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* For returning users: detects installed runtime → delegates to it.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
12
12
|
import { join, dirname } from "path";
|
|
13
13
|
import { homedir, platform } from "os";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
@@ -50,7 +50,9 @@ function isInstalled() {
|
|
|
50
50
|
// Check both layouts: soma-beta (dist/extensions) and dev setup (extensions/)
|
|
51
51
|
const hasDist = existsSync(join(CORE_DIR, "dist", "extensions")) && existsSync(join(CORE_DIR, "dist", "core"));
|
|
52
52
|
const hasDev = existsSync(join(CORE_DIR, "extensions")) && existsSync(join(CORE_DIR, "core"));
|
|
53
|
-
|
|
53
|
+
// Check deps exist — Pi is the critical dependency
|
|
54
|
+
const hasDeps = existsSync(join(CORE_DIR, "node_modules", "@mariozechner"));
|
|
55
|
+
return (hasDist || hasDev) && hasDeps;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
// ── Browser ──────────────────────────────────────────────────────────
|
|
@@ -89,6 +91,11 @@ async function confirm(prompt) {
|
|
|
89
91
|
return true;
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
async function confirmYN(prompt) {
|
|
95
|
+
const key = await waitForKey(`${prompt} ${dim("[y/n]")} `);
|
|
96
|
+
return key.toLowerCase() === "y";
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
// ── Typing effect ────────────────────────────────────────────────────
|
|
93
100
|
|
|
94
101
|
function sleep(ms) {
|
|
@@ -281,6 +288,42 @@ function readLine(prompt) {
|
|
|
281
288
|
});
|
|
282
289
|
}
|
|
283
290
|
|
|
291
|
+
async function handleQuestion(input) {
|
|
292
|
+
const match = matchQuestion(input);
|
|
293
|
+
if (match) {
|
|
294
|
+
const answer = voice.ask(match.topic);
|
|
295
|
+
console.log("");
|
|
296
|
+
await typeParagraph(answer);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Edge case routing — detect intent even without a topic match
|
|
301
|
+
const lower = input.toLowerCase();
|
|
302
|
+
const rude = /suck|stupid|dumb|trash|garbage|hate|worst|bad|ugly|boring|lame|waste/.test(lower);
|
|
303
|
+
const impressed = /cool|amazing|wow|nice|love|awesome|brilliant|impressive|neat|sick|fire|goat/.test(lower);
|
|
304
|
+
const meta = /are you|what are you|how do you|who are you|real|alive|ai\b|bot\b/.test(lower);
|
|
305
|
+
const greeting = /^(hi|hey|hello|sup|yo|howdy|hola|greetings|good morning|good evening)\b/.test(lower);
|
|
306
|
+
|
|
307
|
+
console.log("");
|
|
308
|
+
if (greeting) {
|
|
309
|
+
await typeOut(` ${voice.greet()} ${voice.spin("{Ask me anything.|What do you want to know?|I know about 9 topics — pick one.}")}\n`);
|
|
310
|
+
} else if (rude) {
|
|
311
|
+
await typeParagraph(voice.ask("meta_rude"));
|
|
312
|
+
} else if (impressed) {
|
|
313
|
+
await typeParagraph(voice.ask("meta_impressed"));
|
|
314
|
+
} else if (meta) {
|
|
315
|
+
await typeParagraph(voice.ask("meta_self"));
|
|
316
|
+
} else {
|
|
317
|
+
// Anything with a question mark → try harder, then admit we don't know
|
|
318
|
+
if (input.includes("?")) {
|
|
319
|
+
await typeParagraph(voice.ask("meta_nonsense"));
|
|
320
|
+
} else {
|
|
321
|
+
await typeParagraph(voice.ask("meta_nonsense"));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
284
327
|
async function interactiveQ() {
|
|
285
328
|
console.log("");
|
|
286
329
|
console.log(` ${bold("Ask me anything.")}`);
|
|
@@ -290,7 +333,7 @@ async function interactiveQ() {
|
|
|
290
333
|
console.log(` ${dim("•")} Why no compaction? ${dim("•")} Are you AI?`);
|
|
291
334
|
console.log(` ${dim("•")} How does it compare? ${dim("•")} Who made this?`);
|
|
292
335
|
console.log("");
|
|
293
|
-
console.log(` ${dim("...or ask anything.
|
|
336
|
+
console.log(` ${dim("...or ask anything. Press")} ${green("Enter")} ${dim("when you're ready to install.")}`);
|
|
294
337
|
|
|
295
338
|
let rounds = 0;
|
|
296
339
|
const maxRounds = 8;
|
|
@@ -299,56 +342,23 @@ async function interactiveQ() {
|
|
|
299
342
|
console.log("");
|
|
300
343
|
const input = await readLine(` ${cyan("?")} `);
|
|
301
344
|
|
|
345
|
+
// Empty input or quit → exit Q&A, proceed to install
|
|
302
346
|
if (!input || input === "q" || input === "quit" || input === "exit") {
|
|
303
|
-
console.log("");
|
|
304
|
-
await typeOut(` ${voice.say("bye")}\n`);
|
|
305
347
|
break;
|
|
306
348
|
}
|
|
307
349
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const answer = voice.ask(match.topic);
|
|
311
|
-
console.log("");
|
|
312
|
-
await typeParagraph(answer);
|
|
313
|
-
rounds++;
|
|
314
|
-
|
|
315
|
-
if (rounds < maxRounds) {
|
|
316
|
-
console.log("");
|
|
317
|
-
console.log(` ${dim("Ask another, or")} ${green("Enter")} ${dim("to install Soma.")}`);
|
|
318
|
-
}
|
|
319
|
-
} else {
|
|
320
|
-
// Edge case routing — detect intent even without a topic match
|
|
321
|
-
const lower = input.toLowerCase();
|
|
322
|
-
const rude = /suck|stupid|dumb|trash|garbage|hate|worst|bad|ugly|boring|lame|waste/.test(lower);
|
|
323
|
-
const impressed = /cool|amazing|wow|nice|love|awesome|brilliant|impressive|neat|sick|fire|goat/.test(lower);
|
|
324
|
-
const meta = /are you|what are you|how do you|who are you|real|alive|ai\b|bot\b/.test(lower);
|
|
325
|
-
const greeting = /^(hi|hey|hello|sup|yo|howdy|hola|greetings|good morning|good evening)\b/.test(lower);
|
|
350
|
+
await handleQuestion(input);
|
|
351
|
+
rounds++;
|
|
326
352
|
|
|
353
|
+
if (rounds < maxRounds) {
|
|
327
354
|
console.log("");
|
|
328
|
-
|
|
329
|
-
await typeOut(` ${voice.greet()} ${voice.spin("{Ask me anything.|What do you want to know?|I know about 9 topics — pick one.}")}\n`);
|
|
330
|
-
} else if (rude) {
|
|
331
|
-
await typeParagraph(voice.ask("meta_rude"));
|
|
332
|
-
} else if (impressed) {
|
|
333
|
-
await typeParagraph(voice.ask("meta_impressed"));
|
|
334
|
-
} else if (meta) {
|
|
335
|
-
await typeParagraph(voice.ask("meta_self"));
|
|
336
|
-
} else {
|
|
337
|
-
await typeParagraph(voice.ask("meta_nonsense"));
|
|
338
|
-
}
|
|
355
|
+
console.log(` ${dim("Ask another, or")} ${green("Enter")} ${dim("to install Soma.")}`);
|
|
339
356
|
}
|
|
340
357
|
}
|
|
341
358
|
|
|
342
359
|
if (rounds >= maxRounds) {
|
|
343
360
|
console.log("");
|
|
344
|
-
await typeOut(` ${voice.spin("{Curious enough?|Intrigued?|Want to see it in action?}")} ${dim("Let's
|
|
345
|
-
console.log("");
|
|
346
|
-
await confirm(` ${dim("→")} Press ${bold("Enter")} to install Soma`);
|
|
347
|
-
if (openBrowser(SITE_URL)) {
|
|
348
|
-
console.log(` ${green("✓")} Opened ${cyan(SITE_URL)}`);
|
|
349
|
-
} else {
|
|
350
|
-
console.log(` ${dim("→")} Visit: ${cyan(SITE_URL)}`);
|
|
351
|
-
}
|
|
361
|
+
await typeOut(` ${voice.spin("{Curious enough?|Intrigued?|Want to see it in action?}")} ${dim("Let's set you up.")}\n`);
|
|
352
362
|
}
|
|
353
363
|
}
|
|
354
364
|
|
|
@@ -369,6 +379,201 @@ function getGitHubUsername() {
|
|
|
369
379
|
|
|
370
380
|
// ── Commands ─────────────────────────────────────────────────────────
|
|
371
381
|
|
|
382
|
+
// ── Auth helpers ─────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
function hasAnyAuth() {
|
|
385
|
+
const hasEnvKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.GROQ_API_KEY || process.env.XAI_API_KEY);
|
|
386
|
+
if (hasEnvKey) return true;
|
|
387
|
+
try {
|
|
388
|
+
const authData = JSON.parse(readFileSync(join(CORE_DIR, "auth.json"), "utf-8"));
|
|
389
|
+
return Object.keys(authData).length > 0;
|
|
390
|
+
} catch { return false; }
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getShellConfigPath() {
|
|
394
|
+
return process.env.SHELL?.includes("zsh") ? "~/.zshrc" : "~/.bashrc";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getShellConfigAbsPath() {
|
|
398
|
+
const home = homedir();
|
|
399
|
+
return process.env.SHELL?.includes("zsh") ? join(home, ".zshrc") : join(home, ".bashrc");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function detectKeyInShellConfig() {
|
|
403
|
+
// Check if key is in shell config but not loaded (user hasn't restarted terminal)
|
|
404
|
+
try {
|
|
405
|
+
const configContent = readFileSync(getShellConfigAbsPath(), "utf-8");
|
|
406
|
+
const keyPattern = /export\s+(ANTHROPIC_API_KEY|OPENAI_API_KEY|GEMINI_API_KEY)=/;
|
|
407
|
+
const match = configContent.match(keyPattern);
|
|
408
|
+
if (match) return match[1];
|
|
409
|
+
} catch {}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── API key setup wizard ─────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
async function apiKeySetup() {
|
|
416
|
+
console.log(` ${yellow("!")} One more thing — Soma needs an AI provider to work.`);
|
|
417
|
+
console.log("");
|
|
418
|
+
await typeOut(` ${voice.spin("{Do you have an Anthropic API key?|Got a Claude API key?|Have an API key for Claude?}")}\n`);
|
|
419
|
+
console.log("");
|
|
420
|
+
console.log(` ${green("y")} ${dim("Yes, I have a key")}`);
|
|
421
|
+
console.log(` ${green("n")} ${dim("No, I need one")}`);
|
|
422
|
+
console.log(` ${green("s")} ${dim("I have a Claude Pro/Max subscription")}`);
|
|
423
|
+
console.log(` ${green("?")} ${dim("What's an API key?")}`);
|
|
424
|
+
console.log("");
|
|
425
|
+
|
|
426
|
+
const key = await waitForKey(` ${dim("→")} `);
|
|
427
|
+
const choice = key.toLowerCase();
|
|
428
|
+
|
|
429
|
+
if (choice === "y") {
|
|
430
|
+
await apiKeyEntry();
|
|
431
|
+
} else if (choice === "s") {
|
|
432
|
+
await oauthGuide();
|
|
433
|
+
} else if (choice === "?") {
|
|
434
|
+
await apiKeyExplain();
|
|
435
|
+
} else {
|
|
436
|
+
await apiKeyGetOne();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function apiKeyExplain() {
|
|
441
|
+
console.log("");
|
|
442
|
+
await typeParagraph("An API key is like a password that lets Soma talk to an AI model. You get one from Anthropic (the company that makes Claude), paste it into your terminal config, and Soma handles the rest. Your key stays on your machine — Soma never sends it anywhere.");
|
|
443
|
+
console.log("");
|
|
444
|
+
await typeParagraph("If you have a Claude Pro or Max subscription, you don't need a separate key — you can log in with your account instead.");
|
|
445
|
+
console.log("");
|
|
446
|
+
|
|
447
|
+
console.log(` ${green("g")} ${dim("Get a key (I'll show you how)")}`);
|
|
448
|
+
console.log(` ${green("s")} ${dim("I have Claude Pro/Max — log in instead")}`);
|
|
449
|
+
console.log("");
|
|
450
|
+
|
|
451
|
+
const key = await waitForKey(` ${dim("→")} `);
|
|
452
|
+
if (key.toLowerCase() === "s") {
|
|
453
|
+
await oauthGuide();
|
|
454
|
+
} else {
|
|
455
|
+
await apiKeyGetOne();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function apiKeyGetOne() {
|
|
460
|
+
console.log("");
|
|
461
|
+
await typeOut(` ${voice.spin("{Here's how.|Let me walk you through it.|Quick steps.}")}\n`);
|
|
462
|
+
console.log("");
|
|
463
|
+
console.log(` ${cyan("Step 1:")} Open this link to create a key:`);
|
|
464
|
+
console.log("");
|
|
465
|
+
console.log(` ${cyan("https://console.anthropic.com/settings/keys")}`);
|
|
466
|
+
console.log("");
|
|
467
|
+
openBrowser("https://console.anthropic.com/settings/keys");
|
|
468
|
+
console.log(` ${dim("(opened in your browser)")}`);
|
|
469
|
+
console.log("");
|
|
470
|
+
await confirm(` ${dim("→")} Press ${bold("Enter")} when you have your key`);
|
|
471
|
+
await apiKeyEntry();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function readSecret(prompt) {
|
|
475
|
+
return new Promise(resolve => {
|
|
476
|
+
process.stdout.write(prompt);
|
|
477
|
+
if (!process.stdin.isTTY) { resolve(""); return; }
|
|
478
|
+
process.stdin.setRawMode(true);
|
|
479
|
+
process.stdin.resume();
|
|
480
|
+
process.stdin.setEncoding("utf-8");
|
|
481
|
+
let buf = "";
|
|
482
|
+
const onData = chunk => {
|
|
483
|
+
for (const ch of chunk) {
|
|
484
|
+
if (ch === "\r" || ch === "\n") {
|
|
485
|
+
process.stdin.setRawMode(false);
|
|
486
|
+
process.stdin.pause();
|
|
487
|
+
process.stdin.removeListener("data", onData);
|
|
488
|
+
process.stdout.write("\n");
|
|
489
|
+
resolve(buf);
|
|
490
|
+
return;
|
|
491
|
+
} else if (ch === "\u007F" || ch === "\b") {
|
|
492
|
+
// Backspace
|
|
493
|
+
if (buf.length > 0) {
|
|
494
|
+
buf = buf.slice(0, -1);
|
|
495
|
+
process.stdout.write("\b \b");
|
|
496
|
+
}
|
|
497
|
+
} else if (ch === "\u0003") {
|
|
498
|
+
// Ctrl+C
|
|
499
|
+
process.stdout.write("\n");
|
|
500
|
+
process.exit(0);
|
|
501
|
+
} else if (ch >= " ") {
|
|
502
|
+
buf += ch;
|
|
503
|
+
process.stdout.write("•");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
process.stdin.on("data", onData);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function apiKeyEntry() {
|
|
512
|
+
console.log("");
|
|
513
|
+
console.log(` ${cyan("Step 2:")} Paste your key below.`);
|
|
514
|
+
console.log(` ${dim("It starts with")} sk-ant-...`);
|
|
515
|
+
console.log("");
|
|
516
|
+
|
|
517
|
+
const apiKey = await readSecret(` ${dim("Key:")} `);
|
|
518
|
+
|
|
519
|
+
if (!apiKey || !apiKey.startsWith("sk-")) {
|
|
520
|
+
console.log("");
|
|
521
|
+
if (!apiKey) {
|
|
522
|
+
console.log(` ${dim("No key entered. You can set it up later.")}`);
|
|
523
|
+
} else {
|
|
524
|
+
console.log(` ${yellow("!")} That doesn't look like an Anthropic key.`);
|
|
525
|
+
console.log(` ${dim("Keys start with")} sk-ant-...`);
|
|
526
|
+
}
|
|
527
|
+
console.log("");
|
|
528
|
+
const sc = getShellConfigPath();
|
|
529
|
+
console.log(` ${dim("When you have your key, add it to")} ${dim(sc)}${dim(":")}`);
|
|
530
|
+
console.log(` ${green('export ANTHROPIC_API_KEY="your-key-here"')}`);
|
|
531
|
+
console.log(` ${dim("Then restart your terminal and run")} ${green("soma")}`);
|
|
532
|
+
console.log("");
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Write to shell config
|
|
537
|
+
const shellConfigPath = getShellConfigAbsPath();
|
|
538
|
+
const shellConfigName = getShellConfigPath();
|
|
539
|
+
const exportLine = `\nexport ANTHROPIC_API_KEY="${apiKey}"\n`;
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
appendFileSync(shellConfigPath, exportLine);
|
|
543
|
+
console.log("");
|
|
544
|
+
console.log(` ${green("✓")} Key saved to ${dim(shellConfigName)}`);
|
|
545
|
+
console.log("");
|
|
546
|
+
|
|
547
|
+
// Set it for the current process too so we can launch immediately
|
|
548
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
549
|
+
|
|
550
|
+
await typeOut(` ${voice.spin("{You're all set.|Good to go.|Ready.}")} ${dim("Soma can start now.")}\n`);
|
|
551
|
+
console.log("");
|
|
552
|
+
} catch {
|
|
553
|
+
console.log("");
|
|
554
|
+
console.log(` ${yellow("!")} Couldn't write to ${dim(shellConfigName)}.`);
|
|
555
|
+
console.log(` ${dim("Add this line manually:")}`);
|
|
556
|
+
console.log(` ${green(`export ANTHROPIC_API_KEY="${apiKey}"`)}`);
|
|
557
|
+
console.log(` ${dim("Then restart your terminal and run")} ${green("soma")}`);
|
|
558
|
+
console.log("");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function oauthGuide() {
|
|
563
|
+
console.log("");
|
|
564
|
+
await typeParagraph("Nice — with a Pro or Max subscription, you can log in with your Anthropic account. No API key needed.");
|
|
565
|
+
console.log("");
|
|
566
|
+
console.log(` ${dim("When Soma starts, type")} ${green("/login")} ${dim("and follow the prompts.")}`);
|
|
567
|
+
console.log(` ${dim("It'll open your browser to authenticate.")}`);
|
|
568
|
+
console.log("");
|
|
569
|
+
await typeOut(` ${voice.spin("{Let's launch.|Starting up.|Here we go.}")}\n`);
|
|
570
|
+
console.log("");
|
|
571
|
+
// Mark that user chose OAuth so we don't block launch
|
|
572
|
+
process.env._SOMA_OAUTH_PENDING = "1";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ── Welcome / First Run ─────────────────────────────────────────────
|
|
576
|
+
|
|
372
577
|
async function showWelcome() {
|
|
373
578
|
printSigma();
|
|
374
579
|
console.log(` ${bold("Soma")} ${dim("—")} ${white("the AI agent that remembers")}`);
|
|
@@ -381,32 +586,74 @@ async function showWelcome() {
|
|
|
381
586
|
if (ghUser) {
|
|
382
587
|
console.log(` ${green("✓")} ${voice.greetBack(ghUser)}`);
|
|
383
588
|
}
|
|
384
|
-
|
|
589
|
+
|
|
590
|
+
if (!hasAnyAuth()) {
|
|
591
|
+
console.log(` ${green("✓")} Core installed`);
|
|
592
|
+
console.log("");
|
|
593
|
+
|
|
594
|
+
// Check if key exists in shell config but isn't loaded
|
|
595
|
+
const unloadedKey = detectKeyInShellConfig();
|
|
596
|
+
if (unloadedKey) {
|
|
597
|
+
console.log(` ${yellow("!")} Found ${bold(unloadedKey)} in ${dim(getShellConfigPath())} but it's not loaded.`);
|
|
598
|
+
console.log(` ${dim("Restart your terminal and run")} ${green("soma")} ${dim("again.")}`);
|
|
599
|
+
console.log("");
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
await apiKeySetup();
|
|
604
|
+
|
|
605
|
+
// If they set a key or chose OAuth, launch. If not, exit gracefully.
|
|
606
|
+
if (!hasAnyAuth() && !process.env.ANTHROPIC_API_KEY && !process.env._SOMA_OAUTH_PENDING) {
|
|
607
|
+
console.log(` ${dim("No worries.")} ${voice.spin("{Come back when you're ready.|Set up a key and run soma again.|We'll be here.}")}`);
|
|
608
|
+
console.log("");
|
|
609
|
+
console.log(` ${dim(`v${VERSION} · BSL 1.1 · soma.gravicity.ai`)}`);
|
|
610
|
+
console.log("");
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
console.log(` ${green("✓")} Core installed. Starting Soma...`);
|
|
615
|
+
}
|
|
385
616
|
console.log("");
|
|
386
617
|
await delegateToCore();
|
|
387
618
|
return;
|
|
388
619
|
}
|
|
389
620
|
|
|
390
|
-
// Not installed —
|
|
391
|
-
const concept = CONCEPTS[getConceptIndex()];
|
|
392
|
-
const body = getConceptBody(concept.topic);
|
|
621
|
+
// ── Not installed — first time ever ────────────────────────────────
|
|
393
622
|
|
|
394
|
-
|
|
623
|
+
await typeOut(` ${voice.greet()}\n`);
|
|
395
624
|
console.log("");
|
|
396
|
-
await typeParagraph(
|
|
625
|
+
await typeParagraph("Soma is an AI coding agent that remembers across sessions. It learns your patterns, builds its own tools, and picks up where it left off.");
|
|
397
626
|
console.log("");
|
|
398
627
|
console.log(` ${dim("─".repeat(58))}`);
|
|
399
628
|
console.log("");
|
|
400
|
-
console.log(` ${dim("→")} ${green("Enter")}
|
|
401
|
-
console.log(` ${dim("→")} ${green("?")} Ask me something first`);
|
|
629
|
+
console.log(` ${dim("→")} Press ${green("Enter")} to set up, or type a question.`);
|
|
402
630
|
console.log("");
|
|
403
631
|
|
|
404
|
-
const
|
|
632
|
+
const input = await readLine(` ${dim("→")} `);
|
|
405
633
|
|
|
406
|
-
if (
|
|
634
|
+
if (input && input !== "") {
|
|
635
|
+
await handleQuestion(input);
|
|
407
636
|
await interactiveQ();
|
|
408
|
-
}
|
|
409
|
-
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Install the runtime
|
|
640
|
+
await initSoma();
|
|
641
|
+
|
|
642
|
+
// If install succeeded, run the API key setup
|
|
643
|
+
if (isInstalled() && !hasAnyAuth()) {
|
|
644
|
+
await apiKeySetup();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// If they have auth now (or chose OAuth), offer to launch
|
|
648
|
+
if (isInstalled() && (hasAnyAuth() || process.env.ANTHROPIC_API_KEY || process.env._SOMA_OAUTH_PENDING)) {
|
|
649
|
+
console.log(` ${dim("─".repeat(58))}`);
|
|
650
|
+
console.log("");
|
|
651
|
+
const launch = await confirmYN(` ${voice.spin("{Ready to go?|Want to start your first session?|Launch Soma?}")}`);
|
|
652
|
+
if (launch) {
|
|
653
|
+
console.log("");
|
|
654
|
+
await delegateToCore();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
410
657
|
}
|
|
411
658
|
|
|
412
659
|
console.log("");
|
|
@@ -515,7 +762,65 @@ async function initSoma() {
|
|
|
515
762
|
const installDir = join(SOMA_HOME, "agent");
|
|
516
763
|
mkdirSync(SOMA_HOME, { recursive: true });
|
|
517
764
|
|
|
518
|
-
|
|
765
|
+
// Validate existing install — check it's a real git repo with dist/ content
|
|
766
|
+
const isValidInstall = existsSync(installDir)
|
|
767
|
+
&& existsSync(join(installDir, ".git"))
|
|
768
|
+
&& (existsSync(join(installDir, "dist", "extensions")) || existsSync(join(installDir, "extensions")));
|
|
769
|
+
|
|
770
|
+
// Track user files to preserve across repair/reinstall
|
|
771
|
+
let preservedFiles = {};
|
|
772
|
+
|
|
773
|
+
if (existsSync(installDir) && !isValidInstall) {
|
|
774
|
+
// Broken/partial install — try to repair, preserve user files
|
|
775
|
+
console.log(` ${yellow("⚠")} Incomplete installation detected.`);
|
|
776
|
+
console.log(` ${dim("Missing:")} ${!existsSync(join(installDir, ".git")) ? "git repo" : "core files"}`);
|
|
777
|
+
console.log("");
|
|
778
|
+
|
|
779
|
+
// Save user files before touching anything
|
|
780
|
+
const userFileNames = ["auth.json", "models.json"];
|
|
781
|
+
for (const f of userFileNames) {
|
|
782
|
+
const fp = join(installDir, f);
|
|
783
|
+
if (existsSync(fp)) {
|
|
784
|
+
try {
|
|
785
|
+
preservedFiles[f] = readFileSync(fp, "utf-8");
|
|
786
|
+
console.log(` ${dim("→")} Preserving ${f}`);
|
|
787
|
+
} catch {}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Try repair: if it's a git repo, fetch + reset. Otherwise move aside for fresh clone.
|
|
792
|
+
const hasGit = existsSync(join(installDir, ".git"));
|
|
793
|
+
let repaired = false;
|
|
794
|
+
|
|
795
|
+
if (hasGit) {
|
|
796
|
+
console.log(` ${yellow("⏳")} Repairing...`);
|
|
797
|
+
try {
|
|
798
|
+
execSync("git fetch origin", { cwd: installDir, stdio: "ignore", timeout: 30000 });
|
|
799
|
+
execSync("git reset --hard origin/main", { cwd: installDir, stdio: "ignore" });
|
|
800
|
+
console.log(` ${green("✓")} Repaired from remote`);
|
|
801
|
+
repaired = true;
|
|
802
|
+
} catch {
|
|
803
|
+
console.log(` ${yellow("!")} Repair failed — will re-download.`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (!repaired) {
|
|
808
|
+
// Move broken dir aside (never delete), then clone will happen below
|
|
809
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
810
|
+
const backup = join(SOMA_HOME, `agent-backup-${ts}`);
|
|
811
|
+
try {
|
|
812
|
+
execSync(`mv "${installDir}" "${backup}"`, { stdio: "ignore" });
|
|
813
|
+
console.log(` ${dim("Old files saved to")} ${dim(backup.replace(homedir(), "~"))}`);
|
|
814
|
+
} catch {
|
|
815
|
+
console.log(` ${red("✗")} Could not move old installation aside.`);
|
|
816
|
+
console.log(` ${dim("Try:")} mv ~/.soma/agent ~/.soma/agent-old && soma init`);
|
|
817
|
+
console.log("");
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (isValidInstall) {
|
|
519
824
|
console.log(` ${dim("→")} Runtime already installed.`);
|
|
520
825
|
|
|
521
826
|
// Pull latest
|
|
@@ -557,13 +862,30 @@ async function initSoma() {
|
|
|
557
862
|
}
|
|
558
863
|
}
|
|
559
864
|
|
|
560
|
-
//
|
|
865
|
+
// Restore preserved user files (from broken install repair)
|
|
866
|
+
if (Object.keys(preservedFiles).length > 0) {
|
|
867
|
+
for (const [f, content] of Object.entries(preservedFiles)) {
|
|
868
|
+
try {
|
|
869
|
+
writeFileSync(join(installDir, f), content, { mode: 0o600 });
|
|
870
|
+
console.log(` ${green("✓")} Restored ${f}`);
|
|
871
|
+
} catch {}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Verify — gate success on actual working install
|
|
561
876
|
const hasExts = existsSync(join(installDir, "dist", "extensions"));
|
|
562
877
|
const hasCore = existsSync(join(installDir, "dist", "core"));
|
|
563
|
-
|
|
564
|
-
|
|
878
|
+
|
|
879
|
+
if (!hasExts || !hasCore) {
|
|
880
|
+
console.log("");
|
|
881
|
+
console.log(` ${red("✗")} Installation incomplete — core files missing.`);
|
|
882
|
+
console.log(` ${dim("Try:")} rm -rf ~/.soma/agent && soma init`);
|
|
883
|
+
console.log("");
|
|
884
|
+
return;
|
|
565
885
|
}
|
|
566
886
|
|
|
887
|
+
console.log(` ${green("✓")} Extensions and core ready`);
|
|
888
|
+
|
|
567
889
|
// Save config
|
|
568
890
|
const config = readConfig();
|
|
569
891
|
config.installedAt = config.installedAt || new Date().toISOString();
|
|
@@ -574,12 +896,118 @@ async function initSoma() {
|
|
|
574
896
|
console.log("");
|
|
575
897
|
console.log(` ${green("✓")} ${bold("Soma is installed!")}`);
|
|
576
898
|
console.log("");
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
async function checkAndUpdate() {
|
|
902
|
+
printSigma();
|
|
903
|
+
console.log(` ${bold("Soma")} — Status`);
|
|
904
|
+
console.log("");
|
|
905
|
+
|
|
906
|
+
const config = readConfig();
|
|
907
|
+
const installPath = config.installPath || join(SOMA_HOME, "agent");
|
|
908
|
+
|
|
909
|
+
// Check current version
|
|
910
|
+
let currentHash = "";
|
|
911
|
+
try {
|
|
912
|
+
currentHash = execSync("git rev-parse --short HEAD", {
|
|
913
|
+
cwd: installPath, encoding: "utf-8"
|
|
914
|
+
}).trim();
|
|
915
|
+
console.log(` ${green("✓")} Core installed ${dim(`(${currentHash})`)}`);
|
|
916
|
+
} catch {
|
|
917
|
+
console.log(` ${green("✓")} Core installed`);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Check for updates
|
|
921
|
+
let behind = 0;
|
|
922
|
+
try {
|
|
923
|
+
console.log(` ${yellow("⏳")} Checking for updates...`);
|
|
924
|
+
execSync("git fetch origin --quiet", { cwd: installPath, stdio: "ignore", timeout: 15000 });
|
|
925
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
926
|
+
cwd: installPath, encoding: "utf-8"
|
|
927
|
+
}).trim();
|
|
928
|
+
const behindStr = execSync(
|
|
929
|
+
`git rev-list HEAD..origin/${branch} --count`,
|
|
930
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
931
|
+
).trim();
|
|
932
|
+
behind = parseInt(behindStr) || 0;
|
|
933
|
+
} catch {
|
|
934
|
+
console.log(` ${dim("Could not check for updates.")}`);
|
|
935
|
+
console.log("");
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (behind === 0) {
|
|
940
|
+
console.log(` ${green("✓")} Already up to date.`);
|
|
941
|
+
console.log("");
|
|
942
|
+
console.log(` ${dim("Soma is set up and ready.")} Run ${green("soma")} ${dim("in a project to start a session.")}`);
|
|
943
|
+
console.log("");
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
console.log(` ${cyan("⬆")} ${bold(`${behind} update${behind !== 1 ? "s" : ""} available.`)}`);
|
|
948
|
+
console.log("");
|
|
949
|
+
|
|
950
|
+
// Show what changed
|
|
951
|
+
try {
|
|
952
|
+
const log = execSync(
|
|
953
|
+
`git log HEAD..origin/main --oneline --no-decorate -5`,
|
|
954
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
955
|
+
).trim();
|
|
956
|
+
if (log) {
|
|
957
|
+
for (const line of log.split("\n")) {
|
|
958
|
+
console.log(` ${dim("•")} ${line.slice(8)}`);
|
|
959
|
+
}
|
|
960
|
+
if (behind > 5) {
|
|
961
|
+
console.log(` ${dim(`...and ${behind - 5} more`)}`);
|
|
962
|
+
}
|
|
963
|
+
console.log("");
|
|
964
|
+
}
|
|
965
|
+
} catch {}
|
|
966
|
+
|
|
967
|
+
const shouldUpdate = await confirmYN(` ${dim("→")} Update now?`);
|
|
968
|
+
if (!shouldUpdate) {
|
|
969
|
+
console.log("");
|
|
970
|
+
console.log(` ${dim("Skipped. Run")} ${green("soma init")} ${dim("anytime to update.")}`);
|
|
971
|
+
console.log("");
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Pull + reinstall deps
|
|
580
976
|
console.log("");
|
|
581
|
-
|
|
582
|
-
|
|
977
|
+
try {
|
|
978
|
+
execSync("git pull --ff-only", { cwd: installPath, stdio: "ignore" });
|
|
979
|
+
console.log(` ${green("✓")} Updated`);
|
|
980
|
+
} catch {
|
|
981
|
+
console.log(` ${yellow("!")} Pull failed — trying reset...`);
|
|
982
|
+
try {
|
|
983
|
+
execSync("git reset --hard origin/main", { cwd: installPath, stdio: "ignore" });
|
|
984
|
+
console.log(` ${green("✓")} Updated (reset)`);
|
|
985
|
+
} catch {
|
|
986
|
+
console.log(` ${red("✗")} Update failed.`);
|
|
987
|
+
console.log(` ${dim("Try:")} cd ~/.soma/agent && git pull`);
|
|
988
|
+
console.log("");
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Reinstall deps if package.json changed
|
|
994
|
+
try {
|
|
995
|
+
const pkgChanged = execSync(
|
|
996
|
+
`git diff HEAD~${behind} HEAD --name-only -- package.json package-lock.json`,
|
|
997
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
998
|
+
).trim();
|
|
999
|
+
if (pkgChanged) {
|
|
1000
|
+
console.log(` ${yellow("⏳")} Updating dependencies...`);
|
|
1001
|
+
execSync("npm install --omit=dev", { cwd: installPath, stdio: ["ignore", "ignore", "inherit"] });
|
|
1002
|
+
console.log(` ${green("✓")} Dependencies updated`);
|
|
1003
|
+
}
|
|
1004
|
+
} catch {}
|
|
1005
|
+
|
|
1006
|
+
const newHash = execSync("git rev-parse --short HEAD", {
|
|
1007
|
+
cwd: installPath, encoding: "utf-8"
|
|
1008
|
+
}).trim();
|
|
1009
|
+
console.log("");
|
|
1010
|
+
console.log(` ${green("✓")} ${bold("Soma is up to date")} ${dim(`(${currentHash} → ${newHash})`)}`);
|
|
583
1011
|
console.log("");
|
|
584
1012
|
}
|
|
585
1013
|
|
|
@@ -688,11 +1116,18 @@ async function doctor() {
|
|
|
688
1116
|
}
|
|
689
1117
|
}
|
|
690
1118
|
|
|
691
|
-
// API key
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
"
|
|
695
|
-
|
|
1119
|
+
// API key (check env + auth.json + shell config)
|
|
1120
|
+
const hasAuth = hasAnyAuth();
|
|
1121
|
+
if (hasAuth) {
|
|
1122
|
+
console.log(` ${green("✓")} API key configured`);
|
|
1123
|
+
} else {
|
|
1124
|
+
const unloadedKey = detectKeyInShellConfig();
|
|
1125
|
+
if (unloadedKey) {
|
|
1126
|
+
console.log(` ${yellow("⚠")} ${unloadedKey} found in ${dim(getShellConfigPath())} but not loaded — restart your terminal`);
|
|
1127
|
+
} else {
|
|
1128
|
+
console.log(` ${yellow("⚠")} No API key — run ${green("soma")} to set one up`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
696
1131
|
|
|
697
1132
|
// Git
|
|
698
1133
|
try {
|
|
@@ -746,6 +1181,27 @@ function showStatus() {
|
|
|
746
1181
|
// ── Delegation ───────────────────────────────────────────────────────
|
|
747
1182
|
|
|
748
1183
|
async function delegateToCore() {
|
|
1184
|
+
// Pre-flight: verify runtime can start before delegating
|
|
1185
|
+
const piPkg = join(CORE_DIR, "node_modules", "@mariozechner", "pi-coding-agent");
|
|
1186
|
+
if (!existsSync(piPkg)) {
|
|
1187
|
+
console.log(` ${red("✗")} Runtime dependencies missing.`);
|
|
1188
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1189
|
+
console.log("");
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const cliEntry = existsSync(join(CORE_DIR, "dist", "cli.js"))
|
|
1193
|
+
? join(CORE_DIR, "dist", "cli.js")
|
|
1194
|
+
: null;
|
|
1195
|
+
const mainEntry = existsSync(join(CORE_DIR, "dist", "main.js"))
|
|
1196
|
+
? join(CORE_DIR, "dist", "main.js")
|
|
1197
|
+
: null;
|
|
1198
|
+
if (!cliEntry && !mainEntry) {
|
|
1199
|
+
console.log(` ${red("✗")} Runtime entry point missing.`);
|
|
1200
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1201
|
+
console.log("");
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
749
1205
|
const { execFileSync: execF } = await import("child_process");
|
|
750
1206
|
const passArgs = process.argv.slice(2);
|
|
751
1207
|
|
|
@@ -792,7 +1248,15 @@ async function delegateToCore() {
|
|
|
792
1248
|
}
|
|
793
1249
|
return;
|
|
794
1250
|
} catch (err) {
|
|
1251
|
+
// Non-zero exit from session is normal (user quit, ctrl+c)
|
|
795
1252
|
if (err.status) process.exit(err.status);
|
|
1253
|
+
// Module errors = broken install
|
|
1254
|
+
if (err.message && err.message.includes("MODULE_NOT_FOUND")) {
|
|
1255
|
+
console.log("");
|
|
1256
|
+
console.log(` ${red("✗")} Soma failed to start — missing dependencies.`);
|
|
1257
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1258
|
+
console.log("");
|
|
1259
|
+
}
|
|
796
1260
|
return;
|
|
797
1261
|
}
|
|
798
1262
|
}
|
|
@@ -816,17 +1280,19 @@ if (cmd === "--version" || cmd === "-v" || cmd === "-V") {
|
|
|
816
1280
|
} else if (cmd === "about") {
|
|
817
1281
|
await showAbout();
|
|
818
1282
|
} else if (cmd === "init") {
|
|
819
|
-
// If runtime is installed AND (has --template/--orphan args OR no .soma/ in cwd),
|
|
820
|
-
// route to project init via content-cli instead of runtime install
|
|
821
1283
|
const hasProjectArgs = args.includes("--template") || args.includes("--orphan") || args.includes("-o");
|
|
822
1284
|
const runtimeInstalled = isInstalled();
|
|
823
1285
|
const hasSomaDir = existsSync(join(process.cwd(), ".soma"));
|
|
824
1286
|
|
|
825
|
-
if (runtimeInstalled
|
|
826
|
-
//
|
|
1287
|
+
if (!runtimeInstalled) {
|
|
1288
|
+
// Not installed — run full install + setup
|
|
1289
|
+
await initSoma();
|
|
1290
|
+
} else if (hasProjectArgs || !hasSomaDir) {
|
|
1291
|
+
// Installed, project init (new project or --template/--orphan)
|
|
827
1292
|
await delegateToCore();
|
|
828
1293
|
} else {
|
|
829
|
-
|
|
1294
|
+
// Installed + .soma/ exists — check for updates, don't re-run setup
|
|
1295
|
+
await checkAndUpdate();
|
|
830
1296
|
}
|
|
831
1297
|
} else if (cmd === "update") {
|
|
832
1298
|
checkForUpdates();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meetsoma",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Soma \u2014 the AI coding agent with self-growing memory",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/thin-cli.js",
|
|
11
11
|
"dist/personality.js",
|
|
12
|
+
"dist/postinstall.js",
|
|
12
13
|
"LICENSE",
|
|
13
14
|
"README.md",
|
|
14
15
|
"CHANGELOG.md"
|
|
@@ -34,6 +35,9 @@
|
|
|
34
35
|
"type": "git",
|
|
35
36
|
"url": "git+https://github.com/meetsoma/soma-agent.git"
|
|
36
37
|
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"postinstall": "node dist/postinstall.js"
|
|
40
|
+
},
|
|
37
41
|
"engines": {
|
|
38
42
|
"node": ">=20.6.0"
|
|
39
43
|
}
|