meetsoma 0.1.3 → 0.1.5
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/CHANGELOG.md +27 -0
- package/dist/personality.js +4 -4
- package/dist/postinstall.js +30 -0
- package/dist/thin-cli.js +519 -70
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to Soma (`meetsoma` on npm).
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). Versioning follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.1.4] — 2026-03-28
|
|
8
|
+
|
|
9
|
+
Corresponds to Soma agent v0.6.5.
|
|
10
|
+
|
|
11
|
+
### New
|
|
12
|
+
|
|
13
|
+
- **`soma inhale --list`** — see available preloads with age and staleness markers.
|
|
14
|
+
- **`soma inhale <name>`** — load a specific preload by date, session ID, or substring.
|
|
15
|
+
- **`soma inhale --load <path>`** — load any file as a preload.
|
|
16
|
+
- **`soma map <name>`** — run a MAP with prompt-config and targeted preload.
|
|
17
|
+
- **`soma map --list`** — see available MAPs with status and description.
|
|
18
|
+
- **`--preload` flag deprecated** — use `soma inhale` or `soma map` instead (still works).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Settings-driven heat overrides** — per-project AMPS heat control via `settings.heat.overrides`.
|
|
23
|
+
- **Statusline** shows preload status. Smart `/exhale` detects edit vs write mode.
|
|
24
|
+
- **Restart signal** — auto-detects when extensions change, prompts for restart.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Crash on partial settings** — `settings.heat` access now defensive (won't crash with old config files).
|
|
29
|
+
- **Breathe grace period** — was 60s, now correctly 30s matching settings default.
|
|
30
|
+
- **5 auto-breathe UX gaps** — smarter context warnings, resume awareness.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
7
34
|
## [0.1.3] — 2026-03-23
|
|
8
35
|
|
|
9
36
|
### New
|
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";
|
|
@@ -89,6 +89,11 @@ async function confirm(prompt) {
|
|
|
89
89
|
return true;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
async function confirmYN(prompt) {
|
|
93
|
+
const key = await waitForKey(`${prompt} ${dim("[y/n]")} `);
|
|
94
|
+
return key.toLowerCase() === "y";
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
// ── Typing effect ────────────────────────────────────────────────────
|
|
93
98
|
|
|
94
99
|
function sleep(ms) {
|
|
@@ -281,6 +286,42 @@ function readLine(prompt) {
|
|
|
281
286
|
});
|
|
282
287
|
}
|
|
283
288
|
|
|
289
|
+
async function handleQuestion(input) {
|
|
290
|
+
const match = matchQuestion(input);
|
|
291
|
+
if (match) {
|
|
292
|
+
const answer = voice.ask(match.topic);
|
|
293
|
+
console.log("");
|
|
294
|
+
await typeParagraph(answer);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Edge case routing — detect intent even without a topic match
|
|
299
|
+
const lower = input.toLowerCase();
|
|
300
|
+
const rude = /suck|stupid|dumb|trash|garbage|hate|worst|bad|ugly|boring|lame|waste/.test(lower);
|
|
301
|
+
const impressed = /cool|amazing|wow|nice|love|awesome|brilliant|impressive|neat|sick|fire|goat/.test(lower);
|
|
302
|
+
const meta = /are you|what are you|how do you|who are you|real|alive|ai\b|bot\b/.test(lower);
|
|
303
|
+
const greeting = /^(hi|hey|hello|sup|yo|howdy|hola|greetings|good morning|good evening)\b/.test(lower);
|
|
304
|
+
|
|
305
|
+
console.log("");
|
|
306
|
+
if (greeting) {
|
|
307
|
+
await typeOut(` ${voice.greet()} ${voice.spin("{Ask me anything.|What do you want to know?|I know about 9 topics — pick one.}")}\n`);
|
|
308
|
+
} else if (rude) {
|
|
309
|
+
await typeParagraph(voice.ask("meta_rude"));
|
|
310
|
+
} else if (impressed) {
|
|
311
|
+
await typeParagraph(voice.ask("meta_impressed"));
|
|
312
|
+
} else if (meta) {
|
|
313
|
+
await typeParagraph(voice.ask("meta_self"));
|
|
314
|
+
} else {
|
|
315
|
+
// Anything with a question mark → try harder, then admit we don't know
|
|
316
|
+
if (input.includes("?")) {
|
|
317
|
+
await typeParagraph(voice.ask("meta_nonsense"));
|
|
318
|
+
} else {
|
|
319
|
+
await typeParagraph(voice.ask("meta_nonsense"));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
284
325
|
async function interactiveQ() {
|
|
285
326
|
console.log("");
|
|
286
327
|
console.log(` ${bold("Ask me anything.")}`);
|
|
@@ -290,7 +331,7 @@ async function interactiveQ() {
|
|
|
290
331
|
console.log(` ${dim("•")} Why no compaction? ${dim("•")} Are you AI?`);
|
|
291
332
|
console.log(` ${dim("•")} How does it compare? ${dim("•")} Who made this?`);
|
|
292
333
|
console.log("");
|
|
293
|
-
console.log(` ${dim("...or ask anything.
|
|
334
|
+
console.log(` ${dim("...or ask anything. Press")} ${green("Enter")} ${dim("when you're ready to install.")}`);
|
|
294
335
|
|
|
295
336
|
let rounds = 0;
|
|
296
337
|
const maxRounds = 8;
|
|
@@ -299,56 +340,23 @@ async function interactiveQ() {
|
|
|
299
340
|
console.log("");
|
|
300
341
|
const input = await readLine(` ${cyan("?")} `);
|
|
301
342
|
|
|
343
|
+
// Empty input or quit → exit Q&A, proceed to install
|
|
302
344
|
if (!input || input === "q" || input === "quit" || input === "exit") {
|
|
303
|
-
console.log("");
|
|
304
|
-
await typeOut(` ${voice.say("bye")}\n`);
|
|
305
345
|
break;
|
|
306
346
|
}
|
|
307
347
|
|
|
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);
|
|
348
|
+
await handleQuestion(input);
|
|
349
|
+
rounds++;
|
|
326
350
|
|
|
351
|
+
if (rounds < maxRounds) {
|
|
327
352
|
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
|
-
}
|
|
353
|
+
console.log(` ${dim("Ask another, or")} ${green("Enter")} ${dim("to install Soma.")}`);
|
|
339
354
|
}
|
|
340
355
|
}
|
|
341
356
|
|
|
342
357
|
if (rounds >= maxRounds) {
|
|
343
358
|
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
|
-
}
|
|
359
|
+
await typeOut(` ${voice.spin("{Curious enough?|Intrigued?|Want to see it in action?}")} ${dim("Let's set you up.")}\n`);
|
|
352
360
|
}
|
|
353
361
|
}
|
|
354
362
|
|
|
@@ -369,6 +377,201 @@ function getGitHubUsername() {
|
|
|
369
377
|
|
|
370
378
|
// ── Commands ─────────────────────────────────────────────────────────
|
|
371
379
|
|
|
380
|
+
// ── Auth helpers ─────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
function hasAnyAuth() {
|
|
383
|
+
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);
|
|
384
|
+
if (hasEnvKey) return true;
|
|
385
|
+
try {
|
|
386
|
+
const authData = JSON.parse(readFileSync(join(CORE_DIR, "auth.json"), "utf-8"));
|
|
387
|
+
return Object.keys(authData).length > 0;
|
|
388
|
+
} catch { return false; }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getShellConfigPath() {
|
|
392
|
+
return process.env.SHELL?.includes("zsh") ? "~/.zshrc" : "~/.bashrc";
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function getShellConfigAbsPath() {
|
|
396
|
+
const home = homedir();
|
|
397
|
+
return process.env.SHELL?.includes("zsh") ? join(home, ".zshrc") : join(home, ".bashrc");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function detectKeyInShellConfig() {
|
|
401
|
+
// Check if key is in shell config but not loaded (user hasn't restarted terminal)
|
|
402
|
+
try {
|
|
403
|
+
const configContent = readFileSync(getShellConfigAbsPath(), "utf-8");
|
|
404
|
+
const keyPattern = /export\s+(ANTHROPIC_API_KEY|OPENAI_API_KEY|GEMINI_API_KEY)=/;
|
|
405
|
+
const match = configContent.match(keyPattern);
|
|
406
|
+
if (match) return match[1];
|
|
407
|
+
} catch {}
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── API key setup wizard ─────────────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
async function apiKeySetup() {
|
|
414
|
+
console.log(` ${yellow("!")} One more thing — Soma needs an AI provider to work.`);
|
|
415
|
+
console.log("");
|
|
416
|
+
await typeOut(` ${voice.spin("{Do you have an Anthropic API key?|Got a Claude API key?|Have an API key for Claude?}")}\n`);
|
|
417
|
+
console.log("");
|
|
418
|
+
console.log(` ${green("y")} ${dim("Yes, I have a key")}`);
|
|
419
|
+
console.log(` ${green("n")} ${dim("No, I need one")}`);
|
|
420
|
+
console.log(` ${green("s")} ${dim("I have a Claude Pro/Max subscription")}`);
|
|
421
|
+
console.log(` ${green("?")} ${dim("What's an API key?")}`);
|
|
422
|
+
console.log("");
|
|
423
|
+
|
|
424
|
+
const key = await waitForKey(` ${dim("→")} `);
|
|
425
|
+
const choice = key.toLowerCase();
|
|
426
|
+
|
|
427
|
+
if (choice === "y") {
|
|
428
|
+
await apiKeyEntry();
|
|
429
|
+
} else if (choice === "s") {
|
|
430
|
+
await oauthGuide();
|
|
431
|
+
} else if (choice === "?") {
|
|
432
|
+
await apiKeyExplain();
|
|
433
|
+
} else {
|
|
434
|
+
await apiKeyGetOne();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function apiKeyExplain() {
|
|
439
|
+
console.log("");
|
|
440
|
+
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.");
|
|
441
|
+
console.log("");
|
|
442
|
+
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.");
|
|
443
|
+
console.log("");
|
|
444
|
+
|
|
445
|
+
console.log(` ${green("g")} ${dim("Get a key (I'll show you how)")}`);
|
|
446
|
+
console.log(` ${green("s")} ${dim("I have Claude Pro/Max — log in instead")}`);
|
|
447
|
+
console.log("");
|
|
448
|
+
|
|
449
|
+
const key = await waitForKey(` ${dim("→")} `);
|
|
450
|
+
if (key.toLowerCase() === "s") {
|
|
451
|
+
await oauthGuide();
|
|
452
|
+
} else {
|
|
453
|
+
await apiKeyGetOne();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function apiKeyGetOne() {
|
|
458
|
+
console.log("");
|
|
459
|
+
await typeOut(` ${voice.spin("{Here's how.|Let me walk you through it.|Quick steps.}")}\n`);
|
|
460
|
+
console.log("");
|
|
461
|
+
console.log(` ${cyan("Step 1:")} Open this link to create a key:`);
|
|
462
|
+
console.log("");
|
|
463
|
+
console.log(` ${cyan("https://console.anthropic.com/settings/keys")}`);
|
|
464
|
+
console.log("");
|
|
465
|
+
openBrowser("https://console.anthropic.com/settings/keys");
|
|
466
|
+
console.log(` ${dim("(opened in your browser)")}`);
|
|
467
|
+
console.log("");
|
|
468
|
+
await confirm(` ${dim("→")} Press ${bold("Enter")} when you have your key`);
|
|
469
|
+
await apiKeyEntry();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function readSecret(prompt) {
|
|
473
|
+
return new Promise(resolve => {
|
|
474
|
+
process.stdout.write(prompt);
|
|
475
|
+
if (!process.stdin.isTTY) { resolve(""); return; }
|
|
476
|
+
process.stdin.setRawMode(true);
|
|
477
|
+
process.stdin.resume();
|
|
478
|
+
process.stdin.setEncoding("utf-8");
|
|
479
|
+
let buf = "";
|
|
480
|
+
const onData = chunk => {
|
|
481
|
+
for (const ch of chunk) {
|
|
482
|
+
if (ch === "\r" || ch === "\n") {
|
|
483
|
+
process.stdin.setRawMode(false);
|
|
484
|
+
process.stdin.pause();
|
|
485
|
+
process.stdin.removeListener("data", onData);
|
|
486
|
+
process.stdout.write("\n");
|
|
487
|
+
resolve(buf);
|
|
488
|
+
return;
|
|
489
|
+
} else if (ch === "\u007F" || ch === "\b") {
|
|
490
|
+
// Backspace
|
|
491
|
+
if (buf.length > 0) {
|
|
492
|
+
buf = buf.slice(0, -1);
|
|
493
|
+
process.stdout.write("\b \b");
|
|
494
|
+
}
|
|
495
|
+
} else if (ch === "\u0003") {
|
|
496
|
+
// Ctrl+C
|
|
497
|
+
process.stdout.write("\n");
|
|
498
|
+
process.exit(0);
|
|
499
|
+
} else if (ch >= " ") {
|
|
500
|
+
buf += ch;
|
|
501
|
+
process.stdout.write("•");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
process.stdin.on("data", onData);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function apiKeyEntry() {
|
|
510
|
+
console.log("");
|
|
511
|
+
console.log(` ${cyan("Step 2:")} Paste your key below.`);
|
|
512
|
+
console.log(` ${dim("It starts with")} sk-ant-...`);
|
|
513
|
+
console.log("");
|
|
514
|
+
|
|
515
|
+
const apiKey = await readSecret(` ${dim("Key:")} `);
|
|
516
|
+
|
|
517
|
+
if (!apiKey || !apiKey.startsWith("sk-")) {
|
|
518
|
+
console.log("");
|
|
519
|
+
if (!apiKey) {
|
|
520
|
+
console.log(` ${dim("No key entered. You can set it up later.")}`);
|
|
521
|
+
} else {
|
|
522
|
+
console.log(` ${yellow("!")} That doesn't look like an Anthropic key.`);
|
|
523
|
+
console.log(` ${dim("Keys start with")} sk-ant-...`);
|
|
524
|
+
}
|
|
525
|
+
console.log("");
|
|
526
|
+
const sc = getShellConfigPath();
|
|
527
|
+
console.log(` ${dim("When you have your key, add it to")} ${dim(sc)}${dim(":")}`);
|
|
528
|
+
console.log(` ${green('export ANTHROPIC_API_KEY="your-key-here"')}`);
|
|
529
|
+
console.log(` ${dim("Then restart your terminal and run")} ${green("soma")}`);
|
|
530
|
+
console.log("");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Write to shell config
|
|
535
|
+
const shellConfigPath = getShellConfigAbsPath();
|
|
536
|
+
const shellConfigName = getShellConfigPath();
|
|
537
|
+
const exportLine = `\nexport ANTHROPIC_API_KEY="${apiKey}"\n`;
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
appendFileSync(shellConfigPath, exportLine);
|
|
541
|
+
console.log("");
|
|
542
|
+
console.log(` ${green("✓")} Key saved to ${dim(shellConfigName)}`);
|
|
543
|
+
console.log("");
|
|
544
|
+
|
|
545
|
+
// Set it for the current process too so we can launch immediately
|
|
546
|
+
process.env.ANTHROPIC_API_KEY = apiKey;
|
|
547
|
+
|
|
548
|
+
await typeOut(` ${voice.spin("{You're all set.|Good to go.|Ready.}")} ${dim("Soma can start now.")}\n`);
|
|
549
|
+
console.log("");
|
|
550
|
+
} catch {
|
|
551
|
+
console.log("");
|
|
552
|
+
console.log(` ${yellow("!")} Couldn't write to ${dim(shellConfigName)}.`);
|
|
553
|
+
console.log(` ${dim("Add this line manually:")}`);
|
|
554
|
+
console.log(` ${green(`export ANTHROPIC_API_KEY="${apiKey}"`)}`);
|
|
555
|
+
console.log(` ${dim("Then restart your terminal and run")} ${green("soma")}`);
|
|
556
|
+
console.log("");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function oauthGuide() {
|
|
561
|
+
console.log("");
|
|
562
|
+
await typeParagraph("Nice — with a Pro or Max subscription, you can log in with your Anthropic account. No API key needed.");
|
|
563
|
+
console.log("");
|
|
564
|
+
console.log(` ${dim("When Soma starts, type")} ${green("/login")} ${dim("and follow the prompts.")}`);
|
|
565
|
+
console.log(` ${dim("It'll open your browser to authenticate.")}`);
|
|
566
|
+
console.log("");
|
|
567
|
+
await typeOut(` ${voice.spin("{Let's launch.|Starting up.|Here we go.}")}\n`);
|
|
568
|
+
console.log("");
|
|
569
|
+
// Mark that user chose OAuth so we don't block launch
|
|
570
|
+
process.env._SOMA_OAUTH_PENDING = "1";
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ── Welcome / First Run ─────────────────────────────────────────────
|
|
574
|
+
|
|
372
575
|
async function showWelcome() {
|
|
373
576
|
printSigma();
|
|
374
577
|
console.log(` ${bold("Soma")} ${dim("—")} ${white("the AI agent that remembers")}`);
|
|
@@ -381,32 +584,74 @@ async function showWelcome() {
|
|
|
381
584
|
if (ghUser) {
|
|
382
585
|
console.log(` ${green("✓")} ${voice.greetBack(ghUser)}`);
|
|
383
586
|
}
|
|
384
|
-
|
|
587
|
+
|
|
588
|
+
if (!hasAnyAuth()) {
|
|
589
|
+
console.log(` ${green("✓")} Core installed`);
|
|
590
|
+
console.log("");
|
|
591
|
+
|
|
592
|
+
// Check if key exists in shell config but isn't loaded
|
|
593
|
+
const unloadedKey = detectKeyInShellConfig();
|
|
594
|
+
if (unloadedKey) {
|
|
595
|
+
console.log(` ${yellow("!")} Found ${bold(unloadedKey)} in ${dim(getShellConfigPath())} but it's not loaded.`);
|
|
596
|
+
console.log(` ${dim("Restart your terminal and run")} ${green("soma")} ${dim("again.")}`);
|
|
597
|
+
console.log("");
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
await apiKeySetup();
|
|
602
|
+
|
|
603
|
+
// If they set a key or chose OAuth, launch. If not, exit gracefully.
|
|
604
|
+
if (!hasAnyAuth() && !process.env.ANTHROPIC_API_KEY && !process.env._SOMA_OAUTH_PENDING) {
|
|
605
|
+
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.}")}`);
|
|
606
|
+
console.log("");
|
|
607
|
+
console.log(` ${dim(`v${VERSION} · BSL 1.1 · soma.gravicity.ai`)}`);
|
|
608
|
+
console.log("");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
console.log(` ${green("✓")} Core installed. Starting Soma...`);
|
|
613
|
+
}
|
|
385
614
|
console.log("");
|
|
386
615
|
await delegateToCore();
|
|
387
616
|
return;
|
|
388
617
|
}
|
|
389
618
|
|
|
390
|
-
// Not installed —
|
|
391
|
-
const concept = CONCEPTS[getConceptIndex()];
|
|
392
|
-
const body = getConceptBody(concept.topic);
|
|
619
|
+
// ── Not installed — first time ever ────────────────────────────────
|
|
393
620
|
|
|
394
|
-
|
|
621
|
+
await typeOut(` ${voice.greet()}\n`);
|
|
395
622
|
console.log("");
|
|
396
|
-
await typeParagraph(
|
|
623
|
+
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
624
|
console.log("");
|
|
398
625
|
console.log(` ${dim("─".repeat(58))}`);
|
|
399
626
|
console.log("");
|
|
400
|
-
console.log(` ${dim("→")} ${green("Enter")}
|
|
401
|
-
console.log(` ${dim("→")} ${green("?")} Ask me something first`);
|
|
627
|
+
console.log(` ${dim("→")} Press ${green("Enter")} to set up, or type a question.`);
|
|
402
628
|
console.log("");
|
|
403
629
|
|
|
404
|
-
const
|
|
630
|
+
const input = await readLine(` ${dim("→")} `);
|
|
405
631
|
|
|
406
|
-
if (
|
|
632
|
+
if (input && input !== "") {
|
|
633
|
+
await handleQuestion(input);
|
|
407
634
|
await interactiveQ();
|
|
408
|
-
}
|
|
409
|
-
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Install the runtime
|
|
638
|
+
await initSoma();
|
|
639
|
+
|
|
640
|
+
// If install succeeded, run the API key setup
|
|
641
|
+
if (isInstalled() && !hasAnyAuth()) {
|
|
642
|
+
await apiKeySetup();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// If they have auth now (or chose OAuth), offer to launch
|
|
646
|
+
if (isInstalled() && (hasAnyAuth() || process.env.ANTHROPIC_API_KEY || process.env._SOMA_OAUTH_PENDING)) {
|
|
647
|
+
console.log(` ${dim("─".repeat(58))}`);
|
|
648
|
+
console.log("");
|
|
649
|
+
const launch = await confirmYN(` ${voice.spin("{Ready to go?|Want to start your first session?|Launch Soma?}")}`);
|
|
650
|
+
if (launch) {
|
|
651
|
+
console.log("");
|
|
652
|
+
await delegateToCore();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
410
655
|
}
|
|
411
656
|
|
|
412
657
|
console.log("");
|
|
@@ -429,7 +674,10 @@ function showHelp() {
|
|
|
429
674
|
console.log(` ${green("soma")} Start a session`);
|
|
430
675
|
console.log(` ${green("soma focus <keyword>")} Start a focused session`);
|
|
431
676
|
console.log(` ${green("soma inhale")} Resume from last session's preload`);
|
|
432
|
-
console.log(` ${green("soma
|
|
677
|
+
console.log(` ${green("soma inhale <name>")} Load a specific preload by name`);
|
|
678
|
+
console.log(` ${green("soma inhale --list")} Show available preloads`);
|
|
679
|
+
console.log(` ${green("soma map <name>")} Load a specific workflow (MAP)`);
|
|
680
|
+
console.log(` ${green("soma map --list")} Show available MAPs`);
|
|
433
681
|
console.log("");
|
|
434
682
|
console.log(` ${bold("Maintenance")}`);
|
|
435
683
|
console.log(` ${green("soma doctor")} Verify installation health`);
|
|
@@ -512,7 +760,65 @@ async function initSoma() {
|
|
|
512
760
|
const installDir = join(SOMA_HOME, "agent");
|
|
513
761
|
mkdirSync(SOMA_HOME, { recursive: true });
|
|
514
762
|
|
|
515
|
-
|
|
763
|
+
// Validate existing install — check it's a real git repo with dist/ content
|
|
764
|
+
const isValidInstall = existsSync(installDir)
|
|
765
|
+
&& existsSync(join(installDir, ".git"))
|
|
766
|
+
&& (existsSync(join(installDir, "dist", "extensions")) || existsSync(join(installDir, "extensions")));
|
|
767
|
+
|
|
768
|
+
// Track user files to preserve across repair/reinstall
|
|
769
|
+
let preservedFiles = {};
|
|
770
|
+
|
|
771
|
+
if (existsSync(installDir) && !isValidInstall) {
|
|
772
|
+
// Broken/partial install — try to repair, preserve user files
|
|
773
|
+
console.log(` ${yellow("⚠")} Incomplete installation detected.`);
|
|
774
|
+
console.log(` ${dim("Missing:")} ${!existsSync(join(installDir, ".git")) ? "git repo" : "core files"}`);
|
|
775
|
+
console.log("");
|
|
776
|
+
|
|
777
|
+
// Save user files before touching anything
|
|
778
|
+
const userFileNames = ["auth.json", "models.json"];
|
|
779
|
+
for (const f of userFileNames) {
|
|
780
|
+
const fp = join(installDir, f);
|
|
781
|
+
if (existsSync(fp)) {
|
|
782
|
+
try {
|
|
783
|
+
preservedFiles[f] = readFileSync(fp, "utf-8");
|
|
784
|
+
console.log(` ${dim("→")} Preserving ${f}`);
|
|
785
|
+
} catch {}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Try repair: if it's a git repo, fetch + reset. Otherwise move aside for fresh clone.
|
|
790
|
+
const hasGit = existsSync(join(installDir, ".git"));
|
|
791
|
+
let repaired = false;
|
|
792
|
+
|
|
793
|
+
if (hasGit) {
|
|
794
|
+
console.log(` ${yellow("⏳")} Repairing...`);
|
|
795
|
+
try {
|
|
796
|
+
execSync("git fetch origin", { cwd: installDir, stdio: "ignore", timeout: 30000 });
|
|
797
|
+
execSync("git reset --hard origin/main", { cwd: installDir, stdio: "ignore" });
|
|
798
|
+
console.log(` ${green("✓")} Repaired from remote`);
|
|
799
|
+
repaired = true;
|
|
800
|
+
} catch {
|
|
801
|
+
console.log(` ${yellow("!")} Repair failed — will re-download.`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (!repaired) {
|
|
806
|
+
// Move broken dir aside (never delete), then clone will happen below
|
|
807
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
808
|
+
const backup = join(SOMA_HOME, `agent-backup-${ts}`);
|
|
809
|
+
try {
|
|
810
|
+
execSync(`mv "${installDir}" "${backup}"`, { stdio: "ignore" });
|
|
811
|
+
console.log(` ${dim("Old files saved to")} ${dim(backup.replace(homedir(), "~"))}`);
|
|
812
|
+
} catch {
|
|
813
|
+
console.log(` ${red("✗")} Could not move old installation aside.`);
|
|
814
|
+
console.log(` ${dim("Try:")} mv ~/.soma/agent ~/.soma/agent-old && soma init`);
|
|
815
|
+
console.log("");
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (isValidInstall) {
|
|
516
822
|
console.log(` ${dim("→")} Runtime already installed.`);
|
|
517
823
|
|
|
518
824
|
// Pull latest
|
|
@@ -554,13 +860,30 @@ async function initSoma() {
|
|
|
554
860
|
}
|
|
555
861
|
}
|
|
556
862
|
|
|
557
|
-
//
|
|
863
|
+
// Restore preserved user files (from broken install repair)
|
|
864
|
+
if (Object.keys(preservedFiles).length > 0) {
|
|
865
|
+
for (const [f, content] of Object.entries(preservedFiles)) {
|
|
866
|
+
try {
|
|
867
|
+
writeFileSync(join(installDir, f), content, { mode: 0o600 });
|
|
868
|
+
console.log(` ${green("✓")} Restored ${f}`);
|
|
869
|
+
} catch {}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Verify — gate success on actual working install
|
|
558
874
|
const hasExts = existsSync(join(installDir, "dist", "extensions"));
|
|
559
875
|
const hasCore = existsSync(join(installDir, "dist", "core"));
|
|
560
|
-
|
|
561
|
-
|
|
876
|
+
|
|
877
|
+
if (!hasExts || !hasCore) {
|
|
878
|
+
console.log("");
|
|
879
|
+
console.log(` ${red("✗")} Installation incomplete — core files missing.`);
|
|
880
|
+
console.log(` ${dim("Try:")} rm -rf ~/.soma/agent && soma init`);
|
|
881
|
+
console.log("");
|
|
882
|
+
return;
|
|
562
883
|
}
|
|
563
884
|
|
|
885
|
+
console.log(` ${green("✓")} Extensions and core ready`);
|
|
886
|
+
|
|
564
887
|
// Save config
|
|
565
888
|
const config = readConfig();
|
|
566
889
|
config.installedAt = config.installedAt || new Date().toISOString();
|
|
@@ -571,12 +894,118 @@ async function initSoma() {
|
|
|
571
894
|
console.log("");
|
|
572
895
|
console.log(` ${green("✓")} ${bold("Soma is installed!")}`);
|
|
573
896
|
console.log("");
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
async function checkAndUpdate() {
|
|
900
|
+
printSigma();
|
|
901
|
+
console.log(` ${bold("Soma")} — Status`);
|
|
902
|
+
console.log("");
|
|
903
|
+
|
|
904
|
+
const config = readConfig();
|
|
905
|
+
const installPath = config.installPath || join(SOMA_HOME, "agent");
|
|
906
|
+
|
|
907
|
+
// Check current version
|
|
908
|
+
let currentHash = "";
|
|
909
|
+
try {
|
|
910
|
+
currentHash = execSync("git rev-parse --short HEAD", {
|
|
911
|
+
cwd: installPath, encoding: "utf-8"
|
|
912
|
+
}).trim();
|
|
913
|
+
console.log(` ${green("✓")} Core installed ${dim(`(${currentHash})`)}`);
|
|
914
|
+
} catch {
|
|
915
|
+
console.log(` ${green("✓")} Core installed`);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Check for updates
|
|
919
|
+
let behind = 0;
|
|
920
|
+
try {
|
|
921
|
+
console.log(` ${yellow("⏳")} Checking for updates...`);
|
|
922
|
+
execSync("git fetch origin --quiet", { cwd: installPath, stdio: "ignore", timeout: 15000 });
|
|
923
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
924
|
+
cwd: installPath, encoding: "utf-8"
|
|
925
|
+
}).trim();
|
|
926
|
+
const behindStr = execSync(
|
|
927
|
+
`git rev-list HEAD..origin/${branch} --count`,
|
|
928
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
929
|
+
).trim();
|
|
930
|
+
behind = parseInt(behindStr) || 0;
|
|
931
|
+
} catch {
|
|
932
|
+
console.log(` ${dim("Could not check for updates.")}`);
|
|
933
|
+
console.log("");
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (behind === 0) {
|
|
938
|
+
console.log(` ${green("✓")} Already up to date.`);
|
|
939
|
+
console.log("");
|
|
940
|
+
console.log(` ${dim("Soma is set up and ready.")} Run ${green("soma")} ${dim("in a project to start a session.")}`);
|
|
941
|
+
console.log("");
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
console.log(` ${cyan("⬆")} ${bold(`${behind} update${behind !== 1 ? "s" : ""} available.`)}`);
|
|
946
|
+
console.log("");
|
|
947
|
+
|
|
948
|
+
// Show what changed
|
|
949
|
+
try {
|
|
950
|
+
const log = execSync(
|
|
951
|
+
`git log HEAD..origin/main --oneline --no-decorate -5`,
|
|
952
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
953
|
+
).trim();
|
|
954
|
+
if (log) {
|
|
955
|
+
for (const line of log.split("\n")) {
|
|
956
|
+
console.log(` ${dim("•")} ${line.slice(8)}`);
|
|
957
|
+
}
|
|
958
|
+
if (behind > 5) {
|
|
959
|
+
console.log(` ${dim(`...and ${behind - 5} more`)}`);
|
|
960
|
+
}
|
|
961
|
+
console.log("");
|
|
962
|
+
}
|
|
963
|
+
} catch {}
|
|
964
|
+
|
|
965
|
+
const shouldUpdate = await confirmYN(` ${dim("→")} Update now?`);
|
|
966
|
+
if (!shouldUpdate) {
|
|
967
|
+
console.log("");
|
|
968
|
+
console.log(` ${dim("Skipped. Run")} ${green("soma init")} ${dim("anytime to update.")}`);
|
|
969
|
+
console.log("");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Pull + reinstall deps
|
|
974
|
+
console.log("");
|
|
975
|
+
try {
|
|
976
|
+
execSync("git pull --ff-only", { cwd: installPath, stdio: "ignore" });
|
|
977
|
+
console.log(` ${green("✓")} Updated`);
|
|
978
|
+
} catch {
|
|
979
|
+
console.log(` ${yellow("!")} Pull failed — trying reset...`);
|
|
980
|
+
try {
|
|
981
|
+
execSync("git reset --hard origin/main", { cwd: installPath, stdio: "ignore" });
|
|
982
|
+
console.log(` ${green("✓")} Updated (reset)`);
|
|
983
|
+
} catch {
|
|
984
|
+
console.log(` ${red("✗")} Update failed.`);
|
|
985
|
+
console.log(` ${dim("Try:")} cd ~/.soma/agent && git pull`);
|
|
986
|
+
console.log("");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Reinstall deps if package.json changed
|
|
992
|
+
try {
|
|
993
|
+
const pkgChanged = execSync(
|
|
994
|
+
`git diff HEAD~${behind} HEAD --name-only -- package.json package-lock.json`,
|
|
995
|
+
{ cwd: installPath, encoding: "utf-8" }
|
|
996
|
+
).trim();
|
|
997
|
+
if (pkgChanged) {
|
|
998
|
+
console.log(` ${yellow("⏳")} Updating dependencies...`);
|
|
999
|
+
execSync("npm install --omit=dev", { cwd: installPath, stdio: ["ignore", "ignore", "inherit"] });
|
|
1000
|
+
console.log(` ${green("✓")} Dependencies updated`);
|
|
1001
|
+
}
|
|
1002
|
+
} catch {}
|
|
1003
|
+
|
|
1004
|
+
const newHash = execSync("git rev-parse --short HEAD", {
|
|
1005
|
+
cwd: installPath, encoding: "utf-8"
|
|
1006
|
+
}).trim();
|
|
577
1007
|
console.log("");
|
|
578
|
-
console.log(` Soma
|
|
579
|
-
console.log(` and begin learning how you work.`);
|
|
1008
|
+
console.log(` ${green("✓")} ${bold("Soma is up to date")} ${dim(`(${currentHash} → ${newHash})`)}`);
|
|
580
1009
|
console.log("");
|
|
581
1010
|
}
|
|
582
1011
|
|
|
@@ -685,11 +1114,18 @@ async function doctor() {
|
|
|
685
1114
|
}
|
|
686
1115
|
}
|
|
687
1116
|
|
|
688
|
-
// API key
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
"
|
|
692
|
-
|
|
1117
|
+
// API key (check env + auth.json + shell config)
|
|
1118
|
+
const hasAuth = hasAnyAuth();
|
|
1119
|
+
if (hasAuth) {
|
|
1120
|
+
console.log(` ${green("✓")} API key configured`);
|
|
1121
|
+
} else {
|
|
1122
|
+
const unloadedKey = detectKeyInShellConfig();
|
|
1123
|
+
if (unloadedKey) {
|
|
1124
|
+
console.log(` ${yellow("⚠")} ${unloadedKey} found in ${dim(getShellConfigPath())} but not loaded — restart your terminal`);
|
|
1125
|
+
} else {
|
|
1126
|
+
console.log(` ${yellow("⚠")} No API key — run ${green("soma")} to set one up`);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
693
1129
|
|
|
694
1130
|
// Git
|
|
695
1131
|
try {
|
|
@@ -813,7 +1249,20 @@ if (cmd === "--version" || cmd === "-v" || cmd === "-V") {
|
|
|
813
1249
|
} else if (cmd === "about") {
|
|
814
1250
|
await showAbout();
|
|
815
1251
|
} else if (cmd === "init") {
|
|
816
|
-
|
|
1252
|
+
const hasProjectArgs = args.includes("--template") || args.includes("--orphan") || args.includes("-o");
|
|
1253
|
+
const runtimeInstalled = isInstalled();
|
|
1254
|
+
const hasSomaDir = existsSync(join(process.cwd(), ".soma"));
|
|
1255
|
+
|
|
1256
|
+
if (!runtimeInstalled) {
|
|
1257
|
+
// Not installed — run full install + setup
|
|
1258
|
+
await initSoma();
|
|
1259
|
+
} else if (hasProjectArgs || !hasSomaDir) {
|
|
1260
|
+
// Installed, project init (new project or --template/--orphan)
|
|
1261
|
+
await delegateToCore();
|
|
1262
|
+
} else {
|
|
1263
|
+
// Installed + .soma/ exists — check for updates, don't re-run setup
|
|
1264
|
+
await checkAndUpdate();
|
|
1265
|
+
}
|
|
817
1266
|
} else if (cmd === "update") {
|
|
818
1267
|
checkForUpdates();
|
|
819
1268
|
} else if (cmd === "doctor") {
|
|
@@ -825,7 +1274,7 @@ if (cmd === "--version" || cmd === "-v" || cmd === "-V") {
|
|
|
825
1274
|
await delegateToCore();
|
|
826
1275
|
} else {
|
|
827
1276
|
// Check if user typed a known post-install command
|
|
828
|
-
const postInstallCmds = ["focus", "inhale", "content", "install", "list", "--map", "--preload"];
|
|
1277
|
+
const postInstallCmds = ["focus", "inhale", "content", "install", "list", "map", "--map", "--preload"];
|
|
829
1278
|
if (cmd && postInstallCmds.includes(cmd)) {
|
|
830
1279
|
printSigma();
|
|
831
1280
|
console.log(` ${bold("soma " + cmd)} requires the Soma runtime.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meetsoma",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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
|
}
|