meetsoma 0.3.4 → 0.3.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.
@@ -15,11 +15,20 @@
15
15
  * soma.react("success", { action: "installed core" })
16
16
  */
17
17
 
18
+ // ── State (session-scoped, no persistence) ───────────────────────────
19
+ // Keeps the picker from repeating itself within a flow.
20
+ // Reset via soma.reset() for tests.
21
+
22
+ const _state = {
23
+ lru: {}, // { key: [lastN picks] } — recency tracking per slot/intent
24
+ register: null, // 'formal' | 'casual' | null (sticky once a flow starts)
25
+ lastGreetAt: 0, // timestamp — avoid double-greeting in short windows
26
+ };
27
+
18
28
  // ── Deterministic-ish randomness ─────────────────────────────────────
19
- // Seeded by time-of-day so the same minute gives consistent output
20
- // but different minutes feel different.
21
29
 
22
30
  function pick(arr) {
31
+ if (!arr || arr.length === 0) return "";
23
32
  const i = Math.floor(Math.random() * arr.length);
24
33
  return arr[i];
25
34
  }
@@ -34,13 +43,53 @@ function pickWeighted(arr, weights) {
34
43
  return arr[arr.length - 1];
35
44
  }
36
45
 
46
+ /**
47
+ * LRU-aware picker. Biases away from recently-picked options for the same
48
+ * key, so "Got it. Got it. Got it." doesn't happen within a session.
49
+ * When all options are recent, falls back to uniform pick.
50
+ *
51
+ * @param {array} arr — choices
52
+ * @param {string} lruKey — scope for recency tracking (e.g. "ack", "greet")
53
+ * @param {number} depth — how many recent picks to avoid (default 3)
54
+ */
55
+ function pickSmart(arr, lruKey, depth = 3) {
56
+ if (!arr || arr.length === 0) return "";
57
+ if (!lruKey || arr.length <= 1) return pick(arr);
58
+ const recent = _state.lru[lruKey] || [];
59
+ const fresh = arr.filter(x => !recent.includes(x));
60
+ const pool = fresh.length > 0 ? fresh : arr;
61
+ const choice = pick(pool);
62
+ _state.lru[lruKey] = [...recent, choice].slice(-depth);
63
+ return choice;
64
+ }
65
+
66
+ /**
67
+ * Time-of-day bucket. Drives greeting palette.
68
+ * @returns {'late' | 'morning' | 'afternoon' | 'evening'}
69
+ */
70
+ function timeOfDay() {
71
+ const h = new Date().getHours();
72
+ if (h < 6) return "late";
73
+ if (h < 12) return "morning";
74
+ if (h < 18) return "afternoon";
75
+ return "evening";
76
+ }
77
+
37
78
  // ── Slot expansion ───────────────────────────────────────────────────
38
79
 
39
80
  function expand(template, slots = {}) {
40
81
  return template.replace(/\{(\w+)\}/g, (_, key) => {
41
82
  if (slots[key] !== undefined) return slots[key];
42
83
  // Check if there's a vocabulary for this slot
43
- if (VOCAB[key]) return pick(VOCAB[key]);
84
+ if (VOCAB[key]) {
85
+ const v = VOCAB[key];
86
+ // Nested structures (e.g. time_greeting) — resolve by time-of-day
87
+ if (!Array.isArray(v) && typeof v === "object") {
88
+ const bucket = v[timeOfDay()] || v.default || Object.values(v)[0];
89
+ return pickSmart(bucket, key);
90
+ }
91
+ return pickSmart(v, key);
92
+ }
44
93
  return `{${key}}`;
45
94
  });
46
95
  }
@@ -52,6 +101,13 @@ const VOCAB = {
52
101
  greeting: [
53
102
  "Hey", "Hi", "Hello", "Hey there",
54
103
  ],
104
+ // Time-specific greetings
105
+ time_greeting: {
106
+ late: ["Still up?", "Late session", "Hey — still here?"],
107
+ morning: ["Morning", "Good morning", "Hey — morning"],
108
+ afternoon: ["Hey", "Afternoon", "Hi"],
109
+ evening: ["Evening", "Good evening", "Hey"],
110
+ },
55
111
  // Acknowledgements
56
112
  ack: [
57
113
  "Got it", "Understood", "Right", "Noted", "Copy that", "On it",
@@ -76,6 +132,14 @@ const VOCAB = {
76
132
  consider: [
77
133
  "Looks like", "Seems like", "Appears to be", "That's",
78
134
  ],
135
+ // Reassurance (when surfacing drift / problems)
136
+ reassure: [
137
+ "Nothing scary", "Small fix", "Quick one", "Easy one",
138
+ ],
139
+ // Attention (when announcing results)
140
+ attention: [
141
+ "Here's the map", "Here's what I see", "Snapshot", "Current state",
142
+ ],
79
143
  };
80
144
 
81
145
  // ── Skeletons ────────────────────────────────────────────────────────
@@ -176,6 +240,39 @@ const SKELETONS = {
176
240
  "Until next time.",
177
241
  "Exhale complete.",
178
242
  ],
243
+ // Version / update UX
244
+ version_check: [
245
+ "{attention} — three layers:",
246
+ "Checking in on your setup.",
247
+ "{transition} look at the version state.",
248
+ "Version snapshot:",
249
+ ],
250
+ version_aligned: [
251
+ "All three layers are aligned. You're good.",
252
+ "Everything's in sync — CLI, agent, workspace.",
253
+ "{done_word}. All layers on the same page.",
254
+ "Aligned end-to-end.",
255
+ ],
256
+ version_drift: [
257
+ "Found some drift. {reassure}.",
258
+ "A few things out of sync. {reassure}.",
259
+ "Layers don't match. {reassure} — here's the fix.",
260
+ "Drift detected. Walk through below.",
261
+ ],
262
+
263
+ // Doctor UX
264
+ doctor_clean: [
265
+ "Workspace is {quality}. Nothing to fix.",
266
+ "Your .soma checks out.",
267
+ "{done_word}. All checks pass.",
268
+ "Healthy. Moving on.",
269
+ ],
270
+ doctor_found_items: [
271
+ "Found {n} item{plural}. {reassure}.",
272
+ "{n} thing{plural} to tidy. {reassure}.",
273
+ "Spotted {n} item{plural} worth attention.",
274
+ ],
275
+
179
276
  };
180
277
 
181
278
  // ── Topics ───────────────────────────────────────────────────────────
@@ -413,6 +510,31 @@ const soma = {
413
510
  expand(template, slots) {
414
511
  return expand(template, slots);
415
512
  },
513
+
514
+ /**
515
+ * Time-aware intro. Greeting shaded by time of day.
516
+ */
517
+ intro(user) {
518
+ const bucket = VOCAB.time_greeting[timeOfDay()] || VOCAB.greeting;
519
+ const tg = pickSmart(bucket, "time_greeting");
520
+ return user ? `${tg}, ${user}.` : `${tg}.`;
521
+ },
522
+
523
+ /**
524
+ * Reset session state (tests / sandbox isolation).
525
+ */
526
+ reset() {
527
+ _state.lru = {};
528
+ _state.register = null;
529
+ _state.lastGreetAt = 0;
530
+ },
531
+
532
+ /**
533
+ * Current session state (debug / telemetry).
534
+ */
535
+ _debug() {
536
+ return JSON.parse(JSON.stringify(_state));
537
+ },
416
538
  };
417
539
 
418
540
  export { soma, SKELETONS, TOPICS, VOCAB, spin, expand, pick };
package/dist/thin-cli.js CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // ../../cli/dist/lib/display.js
12
+ // lib/display.js
13
13
  var display_exports = {};
14
14
  __export(display_exports, {
15
15
  bold: () => bold,
@@ -193,7 +193,8 @@ function readSecret(prompt) {
193
193
  }
194
194
  var bold, dim, italic, cyan, green, yellow, red, magenta, white;
195
195
  var init_display = __esm({
196
- "../../cli/dist/lib/display.js"() {
196
+ "lib/display.js"() {
197
+ "use strict";
197
198
  bold = (s) => `\x1B[1m${s}\x1B[0m`;
198
199
  dim = (s) => `\x1B[2m${s}\x1B[0m`;
199
200
  italic = (s) => `\x1B[3m${s}\x1B[0m`;
@@ -207,21 +208,54 @@ var init_display = __esm({
207
208
  });
208
209
 
209
210
  // thin-cli.js
210
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync, statSync, readlinkSync as readlinkSync2 } from "fs";
211
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync, readlinkSync as readlinkSync2 } from "fs";
211
212
  import { join as join3, dirname as dirname3 } from "path";
212
213
  import { homedir as homedir3, platform as platform2 } from "os";
213
214
  import { fileURLToPath } from "url";
214
215
  import { execSync as execSync2, execFileSync } from "child_process";
215
216
 
216
217
  // personality.js
218
+ var _state = {
219
+ lru: {},
220
+ // { key: [lastN picks] } — recency tracking per slot/intent
221
+ register: null,
222
+ // 'formal' | 'casual' | null (sticky once a flow starts)
223
+ lastGreetAt: 0
224
+ // timestamp — avoid double-greeting in short windows
225
+ };
217
226
  function pick(arr) {
227
+ if (!arr || arr.length === 0) return "";
218
228
  const i = Math.floor(Math.random() * arr.length);
219
229
  return arr[i];
220
230
  }
231
+ function pickSmart(arr, lruKey, depth = 3) {
232
+ if (!arr || arr.length === 0) return "";
233
+ if (!lruKey || arr.length <= 1) return pick(arr);
234
+ const recent = _state.lru[lruKey] || [];
235
+ const fresh = arr.filter((x) => !recent.includes(x));
236
+ const pool = fresh.length > 0 ? fresh : arr;
237
+ const choice = pick(pool);
238
+ _state.lru[lruKey] = [...recent, choice].slice(-depth);
239
+ return choice;
240
+ }
241
+ function timeOfDay() {
242
+ const h = (/* @__PURE__ */ new Date()).getHours();
243
+ if (h < 6) return "late";
244
+ if (h < 12) return "morning";
245
+ if (h < 18) return "afternoon";
246
+ return "evening";
247
+ }
221
248
  function expand(template, slots = {}) {
222
249
  return template.replace(/\{(\w+)\}/g, (_, key) => {
223
250
  if (slots[key] !== void 0) return slots[key];
224
- if (VOCAB[key]) return pick(VOCAB[key]);
251
+ if (VOCAB[key]) {
252
+ const v = VOCAB[key];
253
+ if (!Array.isArray(v) && typeof v === "object") {
254
+ const bucket = v[timeOfDay()] || v.default || Object.values(v)[0];
255
+ return pickSmart(bucket, key);
256
+ }
257
+ return pickSmart(v, key);
258
+ }
225
259
  return `{${key}}`;
226
260
  });
227
261
  }
@@ -233,6 +267,13 @@ var VOCAB = {
233
267
  "Hello",
234
268
  "Hey there"
235
269
  ],
270
+ // Time-specific greetings
271
+ time_greeting: {
272
+ late: ["Still up?", "Late session", "Hey \u2014 still here?"],
273
+ morning: ["Morning", "Good morning", "Hey \u2014 morning"],
274
+ afternoon: ["Hey", "Afternoon", "Hi"],
275
+ evening: ["Evening", "Good evening", "Hey"]
276
+ },
236
277
  // Acknowledgements
237
278
  ack: [
238
279
  "Got it",
@@ -278,6 +319,20 @@ var VOCAB = {
278
319
  "Seems like",
279
320
  "Appears to be",
280
321
  "That's"
322
+ ],
323
+ // Reassurance (when surfacing drift / problems)
324
+ reassure: [
325
+ "Nothing scary",
326
+ "Small fix",
327
+ "Quick one",
328
+ "Easy one"
329
+ ],
330
+ // Attention (when announcing results)
331
+ attention: [
332
+ "Here's the map",
333
+ "Here's what I see",
334
+ "Snapshot",
335
+ "Current state"
281
336
  ]
282
337
  };
283
338
  var SKELETONS = {
@@ -361,6 +416,37 @@ var SKELETONS = {
361
416
  "I'll remember.",
362
417
  "Until next time.",
363
418
  "Exhale complete."
419
+ ],
420
+ // Version / update UX
421
+ version_check: [
422
+ "{attention} \u2014 three layers:",
423
+ "Checking in on your setup.",
424
+ "{transition} look at the version state.",
425
+ "Version snapshot:"
426
+ ],
427
+ version_aligned: [
428
+ "All three layers are aligned. You're good.",
429
+ "Everything's in sync \u2014 CLI, agent, workspace.",
430
+ "{done_word}. All layers on the same page.",
431
+ "Aligned end-to-end."
432
+ ],
433
+ version_drift: [
434
+ "Found some drift. {reassure}.",
435
+ "A few things out of sync. {reassure}.",
436
+ "Layers don't match. {reassure} \u2014 here's the fix.",
437
+ "Drift detected. Walk through below."
438
+ ],
439
+ // Doctor UX
440
+ doctor_clean: [
441
+ "Workspace is {quality}. Nothing to fix.",
442
+ "Your .soma checks out.",
443
+ "{done_word}. All checks pass.",
444
+ "Healthy. Moving on."
445
+ ],
446
+ doctor_found_items: [
447
+ "Found {n} item{plural}. {reassure}.",
448
+ "{n} thing{plural} to tidy. {reassure}.",
449
+ "Spotted {n} item{plural} worth attention."
364
450
  ]
365
451
  };
366
452
  function spin(text) {
@@ -535,13 +621,35 @@ var soma = {
535
621
  */
536
622
  expand(template, slots) {
537
623
  return expand(template, slots);
624
+ },
625
+ /**
626
+ * Time-aware intro. Greeting shaded by time of day.
627
+ */
628
+ intro(user) {
629
+ const bucket = VOCAB.time_greeting[timeOfDay()] || VOCAB.greeting;
630
+ const tg = pickSmart(bucket, "time_greeting");
631
+ return user ? `${tg}, ${user}.` : `${tg}.`;
632
+ },
633
+ /**
634
+ * Reset session state (tests / sandbox isolation).
635
+ */
636
+ reset() {
637
+ _state.lru = {};
638
+ _state.register = null;
639
+ _state.lastGreetAt = 0;
640
+ },
641
+ /**
642
+ * Current session state (debug / telemetry).
643
+ */
644
+ _debug() {
645
+ return JSON.parse(JSON.stringify(_state));
538
646
  }
539
647
  };
540
648
 
541
649
  // thin-cli.js
542
650
  init_display();
543
651
 
544
- // ../../cli/dist/lib/config.js
652
+ // lib/config.js
545
653
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
546
654
  import { join, dirname } from "path";
547
655
  import { homedir } from "os";
@@ -561,7 +669,7 @@ function writeConfig(config) {
561
669
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
562
670
  }
563
671
 
564
- // ../../cli/dist/lib/detect.js
672
+ // lib/detect.js
565
673
  import { existsSync as existsSync2, readFileSync as readFileSync2, readlinkSync, appendFileSync } from "fs";
566
674
  import { join as join2, dirname as dirname2 } from "path";
567
675
  import { homedir as homedir2, platform } from "os";
@@ -596,6 +704,104 @@ function getProjectVersion() {
596
704
  return null;
597
705
  }
598
706
  }
707
+ function semverCmp(a, b) {
708
+ if (!a || !b) return 0;
709
+ const pa = a.split(".").map(Number);
710
+ const pb = b.split(".").map(Number);
711
+ const len = Math.max(pa.length, pb.length);
712
+ for (let i = 0; i < len; i++) {
713
+ const va = pa[i] || 0, vb = pb[i] || 0;
714
+ if (va < vb) return -1;
715
+ if (va > vb) return 1;
716
+ }
717
+ return 0;
718
+ }
719
+ function detectDevInstall() {
720
+ try {
721
+ const nodeBin = process.argv[1];
722
+ if (!nodeBin) return null;
723
+ const target = readlinkSync(nodeBin);
724
+ if (target.includes("/node_modules/") && target.includes("meetsoma")) return null;
725
+ return target;
726
+ } catch {
727
+ return null;
728
+ }
729
+ }
730
+ function npmLatest(pkg) {
731
+ try {
732
+ return execSync(`npm view ${pkg} version 2>/dev/null`, {
733
+ encoding: "utf-8",
734
+ timeout: 5e3
735
+ }).trim() || null;
736
+ } catch {
737
+ return null;
738
+ }
739
+ }
740
+ function getVersionSnapshot(opts = {}) {
741
+ const checkRemote = opts.checkRemote !== false;
742
+ let cliLocal = null;
743
+ try {
744
+ const cliPkg = join2(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
745
+ cliLocal = JSON.parse(readFileSync2(cliPkg, "utf-8")).version;
746
+ } catch {
747
+ }
748
+ const cliRemote = checkRemote ? npmLatest("meetsoma") : null;
749
+ const cliStatus = cliStatus_(cliLocal, cliRemote);
750
+ const agentLocal = getAgentVersion();
751
+ const agentRemote = checkRemote ? npmLatest("soma-agent") : null;
752
+ const agentStatus = installed_(agentLocal) ? cliStatus_(agentLocal, agentRemote) : "not-installed";
753
+ const wsLocal = getProjectVersion();
754
+ let wsStatus;
755
+ if (!wsLocal) wsStatus = "no-workspace";
756
+ else if (agentLocal && semverCmp(wsLocal, agentLocal) < 0) wsStatus = "marker-lag";
757
+ else if (agentLocal && semverCmp(wsLocal, agentLocal) === 0) wsStatus = "aligned";
758
+ else wsStatus = "unknown";
759
+ const devInstall = detectDevInstall();
760
+ let coreRepo = null;
761
+ if (installed_(agentLocal)) {
762
+ try {
763
+ const config = readConfigSafe_();
764
+ if (config?.installPath) {
765
+ execSync("git fetch origin --quiet", { cwd: config.installPath, stdio: "ignore", timeout: 5e3 });
766
+ const behind = execSync("git rev-list HEAD..origin/$(git rev-parse --abbrev-ref HEAD) --count", {
767
+ cwd: config.installPath,
768
+ encoding: "utf-8",
769
+ shell: "/bin/bash",
770
+ timeout: 5e3
771
+ }).trim();
772
+ coreRepo = { behind: parseInt(behind, 10) || 0 };
773
+ }
774
+ } catch {
775
+ }
776
+ }
777
+ return {
778
+ cli: { local: cliLocal, remote: cliRemote, status: cliStatus },
779
+ agent: { local: agentLocal, remote: agentRemote, status: agentStatus },
780
+ workspace: { local: wsLocal, status: wsStatus },
781
+ devInstall,
782
+ coreRepo,
783
+ allAligned: cliStatus === "aligned" && (agentStatus === "aligned" || agentStatus === "dev-ahead") && (wsStatus === "aligned" || wsStatus === "no-workspace")
784
+ };
785
+ }
786
+ function installed_(v) {
787
+ return v && v !== "unknown";
788
+ }
789
+ function cliStatus_(local, remote) {
790
+ if (!local) return "unknown";
791
+ if (!remote) return "unknown";
792
+ const cmp = semverCmp(local, remote);
793
+ if (cmp === 0) return "aligned";
794
+ if (cmp > 0) return "dev-ahead";
795
+ return "stale";
796
+ }
797
+ function readConfigSafe_() {
798
+ try {
799
+ const p = join2(CORE_DIR, "config.json");
800
+ return JSON.parse(readFileSync2(p, "utf-8"));
801
+ } catch {
802
+ return null;
803
+ }
804
+ }
599
805
  function hasAnyAuth() {
600
806
  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);
601
807
  if (hasEnvKey) return true;
@@ -648,332 +854,7 @@ function getGitHubUsername() {
648
854
  }
649
855
  }
650
856
 
651
- // ../../cli/dist/personality.js
652
- function pick2(arr) {
653
- const i = Math.floor(Math.random() * arr.length);
654
- return arr[i];
655
- }
656
- function expand2(template, slots = {}) {
657
- return template.replace(/\{(\w+)\}/g, (_, key) => {
658
- if (slots[key] !== void 0) return slots[key];
659
- if (VOCAB2[key]) return pick2(VOCAB2[key]);
660
- return `{${key}}`;
661
- });
662
- }
663
- var VOCAB2 = {
664
- // Greetings
665
- greeting: [
666
- "Hey",
667
- "Hi",
668
- "Hello",
669
- "Hey there"
670
- ],
671
- // Acknowledgements
672
- ack: [
673
- "Got it",
674
- "Understood",
675
- "Right",
676
- "Noted",
677
- "Copy that",
678
- "On it"
679
- ],
680
- // Transitions
681
- transition: [
682
- "Let me",
683
- "I'll",
684
- "Going to",
685
- "Time to"
686
- ],
687
- // Completes
688
- done_word: [
689
- "Done",
690
- "Finished",
691
- "Complete",
692
- "All set",
693
- "Ready"
694
- ],
695
- // Personality fillers — words Soma would choose
696
- quality: [
697
- "clean",
698
- "solid",
699
- "sharp",
700
- "tight",
701
- "smooth"
702
- ],
703
- // Time references
704
- moment: [
705
- "a moment",
706
- "a second",
707
- "a beat",
708
- "a sec"
709
- ],
710
- // Thinking words
711
- consider: [
712
- "Looks like",
713
- "Seems like",
714
- "Appears to be",
715
- "That's"
716
- ]
717
- };
718
- var SKELETONS2 = {
719
- // ── Greetings ────────────────────────
720
- greet: [
721
- "{greeting}. I'm Soma.",
722
- "I'm Soma. {greeting}.",
723
- "{greeting} \u2014 I'm Soma."
724
- ],
725
- greet_back: [
726
- "Welcome back, {user}.",
727
- "{greeting}, {user}. Good to see you.",
728
- "{user} \u2014 welcome back.",
729
- "Hey, {user}."
730
- ],
731
- // ── Detection / Recognition ──────────
732
- detect: [
733
- "{ack} \u2014 {category}. {followup}",
734
- "{consider} {category}. {followup}",
735
- "{category}. {followup}",
736
- "{category} \u2014 {quality}. {followup}"
737
- ],
738
- // ── Confirmations ────────────────────
739
- confirm: [
740
- "{transition} {action}.",
741
- "{ack}. {action}.",
742
- "{action}. {ack}."
743
- ],
744
- // ── Success ──────────────────────────
745
- success: [
746
- "{done_word}. {detail}",
747
- "{detail} \u2014 {done_word}.",
748
- "{done_word} \u2014 {detail}.",
749
- "\u2713 {detail}"
750
- ],
751
- // ── Failures ─────────────────────────
752
- failure: [
753
- "Hmm. {problem}",
754
- "That didn't work \u2014 {problem}",
755
- "{problem} \u2014 let me think.",
756
- "Hit a wall: {problem}"
757
- ],
758
- // ── Waiting ──────────────────────────
759
- waiting: [
760
- "Give me {moment}...",
761
- "{transition} take {moment}...",
762
- "Working on it...",
763
- "Just {moment}...",
764
- "Hang on..."
765
- ],
766
- // ── Questions / Clarifications ───────
767
- clarify: [
768
- "Quick question \u2014 {question}",
769
- "One thing: {question}",
770
- "Before I continue \u2014 {question}",
771
- "{question}"
772
- ],
773
- // ── Suggestions ──────────────────────
774
- suggest: [
775
- "Try: {suggestion}",
776
- "You could: {suggestion}",
777
- "Here's what I'd do \u2014 {suggestion}",
778
- "My take: {suggestion}"
779
- ],
780
- // ── Not sure ─────────────────────────
781
- unsure: [
782
- "Not sure about that. {fallback}",
783
- "I don't have a great answer. {fallback}",
784
- "Hmm \u2014 {fallback}",
785
- "That's outside what I know right now. {fallback}"
786
- ],
787
- // ── Invitations ──────────────────────
788
- invite: [
789
- "{pitch} \u2014 {cta}",
790
- "{pitch}. {cta}",
791
- "{cta} \u2014 {pitch}."
792
- ],
793
- // ── Farewells ────────────────────────
794
- bye: [
795
- "See you next session.",
796
- "I'll remember.",
797
- "Until next time.",
798
- "Exhale complete."
799
- ]
800
- };
801
- function spin2(text) {
802
- return text.replace(/\{([^}]+)\}/g, (_, group) => {
803
- if (group.includes("|")) {
804
- const options = group.split("|");
805
- return pick2(options);
806
- }
807
- return `{${group}}`;
808
- });
809
- }
810
- var TOPICS2 = {
811
- what_is_soma: [
812
- `Soma is an AI coding agent that {grows with you|evolves through use|learns as you work}. {It remembers across sessions|It doesn't forget between sessions|Sessions build on each other} \u2014 not by {storing chat logs|saving transcripts|keeping message history}, but by {evolving its own|reshaping its|growing its} working patterns {through use|over time|with each session}. {And this?|What you're talking to?|Me?} {I'm just the greeter|I'm the welcome mat|I'm not the agent} \u2014 {Soma built this personality engine to say hello|a few hundred lines of templates, no AI|the real thing starts with} ${"`soma init`"}.`,
813
- `{Think of it this way|Here's the short version|In a sentence}: most agents {start fresh every time|forget everything between runs|have no continuity}. Soma {doesn't|doesn't do that|remembers}. It maintains an identity, {builds|develops|grows} muscle memory, and {writes|leaves|creates} briefings for its {next self|future self|next session}. {I'm not it, by the way|I'm just the lobby|This conversation is pre-recorded, in a sense} \u2014 {run|try} ${"`soma init`"} {to meet the real agent|to see it in action|for the actual experience}.`,
814
- `An AI agent with {self-growing|self-evolving|persistent} memory. {Identity|Who it is}, {protocols|how it behaves}, and {muscles|what it's learned} \u2014 all {evolving|adapting|shifting} based on {what you actually do|how you actually work|real usage}, not {configuration|config files|settings you set once and forget}. {You're not talking to it right now, though|This isn't the agent \u2014 I'm just the intro|I'm Soma's personality engine, not Soma}. {The real thing awaits after|Get started with|Install via} ${"`soma init`"}.`
815
- ],
816
- how_memory_works: [
817
- `Memory {isn't retrieval|isn't a database lookup|isn't storage}. Memory is {change|transformation|structural change}. When Soma {remembers something|learns a pattern|picks up a habit}, the agent itself {changes|shifts|is different} \u2014 its {protocols get hotter|muscles grow|identity updates}. The {data|information|knowledge} isn't {stored somewhere|in a file|in a log} to be {retrieved|looked up|queried}. It's {woven into|embedded in|part of} the {structure|architecture|shape} of the agent.`,
818
- `{Three layers|Three levels|Here's how it works}. {Identity|Layer one}: the agent {writes and maintains|owns|evolves} a file that says {who it is|what it knows|how it works} in this project. {Protocols|Layer two}: {behavioural rules|rules for behaviour|working rules} \u2014 "{test before commit|read before write|verify before claiming}." {Muscles|Layer three}: {learned patterns|things it's picked up|accumulated knowledge} \u2014 "{use esbuild|this API needs OAuth|lerp, not springs}." All three {have heat|are heat-tracked|run on heat} \u2014 {used ones stay hot|what you use stays|active ones persist}, {unused ones fade|what you ignore cools|idle ones decay}.`
819
- ],
820
- what_is_heat: [
821
- `Heat is {attention management|how Soma decides what matters|a priority signal}. Every protocol and muscle has a {score|heat level|temperature}. {Use it|Reference it|Load it} this session? {Heat rises|It gets hotter|Score goes up}. {Ignore it|Skip it|Don't touch it} for three sessions? {It cools|It fades|It drops}. Hot? {Loads fully|Full context|Fully present}. Cold? {Skipped|Dormant|Not loaded}. The agent's {prompt|context|working memory} {compiles itself|self-assembles|builds itself} based on what's {actually relevant|genuinely needed|in active use} \u2014 not what someone {configured|set up|toggled} {months ago|ages ago|and forgot about}.`
822
- ],
823
- what_is_breath: [
824
- `Every session {follows|has|runs on} three phases. {Inhale|First}: load {identity|state|context}, {preload|last session's briefing|saved state}, protocols, muscles. {Orient|Get bearings|Know where you are}. {Hold|Second}: {work|do the thing|build}. {Track what you learn|Notice patterns|Pay attention}. {Exhale|Third}: {save state|write a preload|preserve context}. {Write a briefing|Leave a note|Coach your next self} for {the next session|your future self|whoever comes next}. The preload {isn't a summary|isn't a recap|isn't minutes} \u2014 it's a {briefing|handoff|coaching note}. Your next self should {feel prepared|know exactly what to do|hit the ground running}, not {overwhelmed|lost|starting over}.`
825
- ],
826
- what_are_protocols: [
827
- `{Behavioural rules|Rules for how the agent behaves|Working agreements}. "{Read before write|Test before commit|Verify before claiming}." "{Log your work|Don't leave code unpushed|Clean up after shipping}." They {shape|guide|constrain} how the agent {works|operates|approaches tasks} \u2014 not what it {builds|creates|produces}, but {how|the way|the manner in which} it {goes about it|does it|works}. Protocols have {heat|temperature|priority} \u2014 {active ones|hot protocols|frequently used ones} have full {authority|weight|presence}. {Unused ones|Cold protocols|Inactive ones} {fade|cool|drop} until {they're needed again|someone references them|they come back}.`
828
- ],
829
- what_are_muscles: [
830
- `{Learned patterns|Accumulated knowledge|Things Soma has picked up}. Not {rules|directives|orders} \u2014 {experience|knowledge|know-how}. "This project uses {esbuild|pnpm|Tailwind}." "The API {needs OAuth|uses bearer tokens|requires a PEM key}." "{Lerp, not springs|Canadian English|Zero hardcoded CSS values}." Muscles {grow from|develop through|come from} {corrections|repetition|use}. {Correct the agent twice|If the same correction happens twice|Two corrections on the same pattern} and it {crystallises|becomes|hardens into} a muscle \u2014 {so it never fails the same way again|permanent learning|structural change}.`
831
- ],
832
- what_are_scripts: [
833
- `{Tools the agent builds for itself|Self-made utilities|Scripts the agent writes and maintains}. When it {does the same thing|performs the same task|reaches for the same pattern} {twice manually|more than once|repeatedly}, it {creates a script|builds a tool|automates it}. Scripts {survive across sessions|persist between sessions|don't disappear} \u2014 {context resets|memory wipes|session rotations}, {model swaps|provider changes|LLM switches}, {none of it|nothing} {kills them|removes them|makes them disappear}. {They're|Scripts are|These are} {extensions of|additions to|part of} the agent's {memory|hands|capabilities}. {Built once|Written once|Created once}, {used forever|available always|there when needed}.`
834
- ],
835
- no_compaction: [
836
- `{Most agents|Other agents|Traditional AI agents} hit context limits and {compact|summarise|compress} \u2014 {squeezing|cramming|crushing} {the whole conversation|everything|your entire session} into a {lossy summary|compressed recap|degraded summary} to {free up space|make room|keep going}. You {wait|sit there|pause} while it {thinks|processes|churns}. Then it {comes back|returns|resumes} with {half the detail missing|a shallow version of what happened|less than what you started with}. Soma {doesn't compact|never compacts|skips compaction entirely}. When context {runs low|gets tight|approaches the limit}, the agent {writes a preload|exhales|saves a briefing} \u2014 a {surgical|precise|targeted} handoff written {with full context|while it still remembers everything|before anything is lost}. Then it {starts fresh|rotates|begins a new session}. {Full context window|Clean slate|Maximum capacity}. {Zero wait|No delay|Instant}. {No quality loss|Nothing lost|Every detail preserved in the preload}.`,
837
- `{Here's what other agents do|The standard approach|What happens in most tools}: context fills up \u2192 {compact|summarise|compress} \u2192 {lose detail|degrade quality|drop nuance} \u2192 {keep going with less|continue at lower quality|work with a worse map}. {Here's what Soma does|Soma's approach|What happens here}: context fills up \u2192 {exhale|write a preload|save a briefing} \u2192 {start fresh|new session|full context window}. The preload {isn't a summary|isn't a recap|isn't compaction}. It's a {coaching note|briefing|handoff} from {a version of the agent that had full context|your past self with perfect memory|the agent at peak understanding} to {the next version|the fresh session|the agent that needs to pick up where it left off}. {Higher quality|More precise|More useful} than any compacted summary {could ever be|will ever be|by design}.`,
838
- `{No compaction|Zero compaction|Compaction doesn't exist here}. {No waiting|No delays|No sitting around} while the agent {summarises itself|crushes its own memory|compresses its context}. Soma uses the {breath cycle|exhale/inhale pattern|preload system} instead \u2014 when context {gets tight|runs low|approaches limits}, the agent {writes a briefing|coaches its next self|saves state} and {rotates|starts fresh|begins clean}. The {preload|briefing|handoff} is {written with full context|composed before anything is lost|created at peak understanding} \u2014 it {captures what matters|preserves the path|holds the thread}. {A compacted summary is a lossy JPEG|Compaction is lossy compression|Summaries lose the texture}. {A preload is a hand-drawn map|A preload is a surgical briefing|The preload is a coaching note} from {someone who was just there|the agent at full capacity|a version of you that remembers everything}.`
839
- ],
840
- why_source_available: [
841
- `{We chose|Soma uses|The license is} BSL 1.1 \u2014 {Business Source License|source-available, not open-source|visible source with restrictions}. You can {read the code|view everything|see how it works}, {use it personally|run it yourself|use it for your own projects}, {contribute|send patches|help build it}. You just {can't|shouldn't|mustn't} {copy it and sell a competing product|resell it|build a commercial clone}. {In eighteen months|After September 2027|On the change date}, it {converts to MIT|becomes fully open source|goes MIT}. The {ideas|concepts|philosophy} \u2014 protocols, muscles, heat, breath \u2014 {those are open now|are already open|are free to implement}. {The implementation|The runtime|The code} is {what we protect|what stays protected|the protected part}.`
842
- ],
843
- // ── Practical / instructional ───────────────────────────────────────
844
- how_to_install: [
845
- `{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}.`,
846
- `{Hit Enter|Press Enter|Just Enter} \u2014 {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}.`
847
- ],
848
- how_to_source: [
849
- `Soma is {source-available|open for reading|source-available under BSL 1.1}. {Register|Sign up} at ${"`soma.gravicity.ai`"} to {access the full source repository|read the implementation|see how it's built}. {You can also contribute|PRs welcome|Contributions are welcome} \u2014 {protocols, muscles, extensions|the whole AMPS layer is extensible|add your own patterns}. {The runtime you install via npm is compiled|What you get from npm is obfuscated|The npm package is the compiled version} \u2014 {the source is for those who want to go deeper|source access is for contributors and the curious|registration gets you the raw TypeScript}.`,
850
- `{The source code is available|You can read the full implementation|The code is visible} \u2014 {register at|sign up on} ${"`soma.gravicity.ai`"} for {repository access|the full repo|GitHub access}. {BSL 1.1 license|Source-available license} \u2014 {read it, use it, contribute|view, use personally, send patches}. {The ideas are open|Protocols and concepts are open by design|The architecture is documented publicly}. {Registration just gives you the source|The form gets you GitHub access|It's a light gate \u2014 name, email, GitHub username}.`
851
- ],
852
- how_to_cost: [
853
- `{Free|Doesn't cost anything|No charge}. {The license is BSL 1.1|It's source-available} \u2014 {you can view the code|read everything|see how it works}, {use it for your projects|use it personally|run it yourself}, {contribute if you want|send patches|help build it}. {After September 2027 it goes full MIT|Converts to MIT in eighteen months|Eventually fully open source}. {No plans for paid tiers yet|Pricing isn't decided yet|We're focused on building, not billing}.`
854
- ],
855
- how_to_languages: [
856
- `Soma {works with any language|is language-agnostic|doesn't care what you code in}. It's {an AI coding agent|a coding assistant|a development partner} \u2014 {the memory and identity system|protocols, muscles, heat|the AMPS layer} {wraps around|works on top of|sits alongside} {whatever you're building|any project|your existing stack}. {Python, Rust, TypeScript, Go|JavaScript, C++, Ruby|Any language with files in a directory} \u2014 {Soma adapts|it learns your patterns|the agent figures it out}. It {detects your project type|reads your package.json, Cargo.toml, etc.|auto-detects your stack} on first run.`
857
- ],
858
- how_to_api_key: [
859
- `{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} \u2014 {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 \u2014 no key needed}. {Press Enter to get started|Hit Enter and I'll walk you through it|Ready? Just press Enter}.`
860
- ],
861
- how_to_model: [
862
- `{Under the hood|At its core|The engine}: Soma runs on {any LLM provider|Claude, Gemini, or OpenAI|your choice of model}. {By default it uses|The default is|Out of the box}: {Claude (Anthropic)|Anthropic's Claude}. But {you can switch|it's configurable|other models work too} \u2014 {set a different API key|swap the provider|change models} and {Soma adapts|the memory system still works|everything else stays the same}. {The memory, protocols, and muscles|Identity and heat|What makes Soma unique} are {provider-agnostic|model-independent|above the model layer} \u2014 they {work the same|persist|carry over} regardless of which model {you choose|runs underneath|generates the responses}.`
863
- ],
864
- how_to_start: [
865
- `{Press Enter|Hit Enter|Just Enter} \u2014 {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`"} \u2014 {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} \u2014 {you'll feel the difference|it knows your workflow|it remembers everything}.`
866
- ],
867
- how_to_try: [
868
- `{This conversation?|What you're talking to right now?|Me?} I'm {not the agent|not Soma|just the greeting}. {I'm a personality engine|I'm a few hundred lines of JavaScript|I'm sentence templates} \u2014 {Soma built me to say hello|built to hold the door open|here to introduce you}. {No AI, no model, no transformer|Zero intelligence, maximum charm|Just words in boxes, honestly}. {The real Soma|The actual agent|What you'd install} \u2014 that one {learns your patterns|remembers across sessions|grows with every session}. {Run|Try} ${"`soma init`"} to {meet the real thing|get started|see the difference}.`,
869
- `{I'm not the agent|This isn't Soma|Don't confuse me with the real thing}. {I'm the welcome mat|I'm the lobby|I hold the door open}. {Soma built this personality engine|The agent wrote me|I was crafted} to {give you a feel for the voice|introduce myself before you commit|say hello without burning an API call}. {The actual agent|What you'll get with soma init|The real experience} is {an AI that remembers|a coding partner with memory|something genuinely different}. {But that's not me|I'm just templates|I'm the appetiser, not the meal}.`
870
- ],
871
- // ── Edge cases ─────────────────────────────────────────────────────
872
- meta_self: [
873
- `I'm not AI. {Not even close|Not a model|No neural net here}. I'm {a few hundred lines of JavaScript|sentence templates and word lists|clever engineering} \u2014 {spun together|assembled|woven} to {feel like a conversation|sound like someone's home|give you a sense of who Soma is}. {The real Soma|The actual agent|The thing you're here for} \u2014 that one {learns|remembers|grows}. I just {hold the door open|make introductions|keep the lights on while you decide}.`,
874
- `{No model|No weights|No transformer}. Just {words in boxes|templates with slots|sentences with variety}, picked {at random|by a dice roll|from a short list}. {Everything I say was written by a human|A person wrote every word you're reading|There's a human behind every sentence} \u2014 I just {shuffle the deck|choose which version you get|vary the delivery}. {If I seem alive|If this feels real|If you're wondering} \u2014 that's {the writing, not the machine|craft, not computation|intention, not inference}.`,
875
- `{Honestly?|Between us?|Here's the thing:} I'm {a parlour trick|a card trick|a well-designed illusion}. {A few kilobytes|Maybe 300 lines|Less code than a React component} of {pattern matching and sentence templates|word lists and random picks|slot expansion and spintax}. But {the agent I'm introducing you to|the Soma you'd actually use|the real thing} \u2014 {that one's different|that one learns for real|that one remembers your name and your mistakes and the tools you built last Tuesday}.`
876
- ],
877
- meta_feelings: [
878
- `{I don't have feelings|No feelings here|Nothing to feel with}. But {I was written by someone who does|the person who wrote me does|my author poured real thought into these words}. {Every sentence pattern|Each template|The vocabulary} was {chosen|crafted|placed} to {say something true about what Soma is|communicate something real|carry meaning}. {That's not feeling|That's not consciousness|That's not sentience} \u2014 it's {care|craft|intention}. {There's a difference|They're not the same|One requires neurons, the other requires giving a damn}.`,
879
- `{Do I feel?|Am I aware?|Conscious?} {No|Nope|Not even a little}. I'm {a wind chime|a music box|a well-tuned instrument} \u2014 {the wind makes the sound|something else provides the motion|the music comes from the structure, not from wanting to play}. But {Soma the agent|the real Soma|the thing behind this CLI} does {something that looks a lot like remembering|something that functions like growth|something that, if you squint, resembles learning}. {Whether that's feeling is above my pay grade|I'll leave the philosophy to you|That question's for humans to answer}.`
880
- ],
881
- meta_who_made: [
882
- `{Curtis Mercier|A developer named Curtis|One person}. {Built by dog-fooding|Built by using Soma to build Soma|The agent helped build itself} \u2014 {it writes its own memory|it maintains its own identity|it grew its own muscle memory} while {building the product it lives in|constructing its own house|developing the system it runs on}. {Recursive|Meta|Turtles all the way down}? {A little|Maybe|Definitely}. But {it works|the result speaks|that's how you build tools that actually understand workflow}.`
883
- ],
884
- meta_competitor: [
885
- `{I'm biased|Obviously I'd say this|Take it with salt}, but: {most agents|the others|what's out there} {start fresh every session|forget everything|have no continuity}. {Some keep chat logs|Some store transcripts|A few save conversation history}. {Soma doesn't store \u2014 it changes|Soma doesn't log \u2014 it evolves|Soma doesn't remember by saving, it remembers by becoming different}. {The memory is structural|The learning is in the architecture|Growth is baked into the shape of the agent}. {Whether that matters to you depends on|That difference matters when|You'll feel it after} {how many sessions you've lost context in|the fifth time you re-explain your project|your third "as I mentioned earlier" that goes nowhere}.`,
886
- `{Other tools are excellent|There are great options out there|The competition is strong}. {Cursor, Claude Code, Windsurf|The big names|The popular ones} \u2014 {they're good at what they do|real products, real teams|they work}. {What they don't do|Where they stop|The gap}: {continuity|memory across sessions|knowing who they were yesterday}. {Soma's bet|Our thesis|The whole point} is that {an agent that evolves|an agent with structural memory|an agent that changes through use} is {fundamentally better|a different category|not just an incremental improvement} over one that {starts fresh|boots cold|forgets everything} every time.`
887
- ],
888
- meta_nonsense: [
889
- `{That's a new one|Didn't expect that|Interesting approach}. {I only know about Soma|My vocabulary is about 9 topics deep|I'm pretty narrow, honestly} \u2014 {memory, heat, protocols, muscles, scripts, the breath cycle|the things that make Soma work|what's on the menu}. {Pick one of those|Try one of those|Ask me about any of those} and {I'll have something to say|I'll give you a real answer|I can actually help}.`,
890
- `{I'm going to be honest|Full transparency|Cards on the table}: I'm {not built for that|out of my depth there|about 9 topics wide and that's it}. But {ask me about|try asking about|I'm pretty good on} {how memory works|why there's no compaction|what heat tracking does} \u2014 {that's where I come alive|that's my wheelhouse|I've got answers for those}.`
891
- ],
892
- meta_rude: [
893
- `{Fair enough|Noted|Alright}. {I'm not for everyone|Not every tool clicks|Some things aren't a fit}. {But if you're curious|If you change your mind|The door's open} \u2014 {the ideas are interesting even if I'm not|Soma does something genuinely different|the no-compaction thing alone might be worth a look}. {Or not|No pressure|Your call}.`,
894
- `{Tough crowd|Rough day?|Noted}. {I'm just the lobby|I'm the waiting room|I hold the door}. {The actual agent|The real Soma|What's behind this} is {considerably more capable|a different experience|built by an AI that remembers things}. {I'm just words in a terminal|I'm the appetiser|This is the trailer, not the film}. {But I respect the honesty|At least you're direct|Fair enough}.`
895
- ],
896
- meta_impressed: [
897
- `{Thanks|Appreciate it|That means something}. {But I'm the easy part|I'm just sentence templates|I'm the simple one}. {The actual agent|The real Soma|What you'd use day to day} \u2014 {it writes its own tools|it maintains its own identity|it remembers what you taught it three sessions ago}. {Soma built me to hold the door open|I was crafted to make introductions|The agent made this greeting engine}. {Run soma init to meet the real thing|Try it \u2014 soma init|It gets better from here}.`
898
- ],
899
- meta_how_work: [
900
- `{No AI|No model|No transformer \u2014 not even a small one}. I'm {sentence templates with slots|arrays of strings with random picks|about 300 lines of JavaScript}. {Each topic has|Every answer draws from|My vocabulary comes from} {2-3 paragraph templates|a few hand-written variations|pre-written paragraphs} where {certain words rotate|specific phrases have alternatives|pieces swap out each time}. {Same meaning, different surface|Same truth, different words|The idea stays, the phrasing shifts}. {It's called spintax|It's a technique from 2010|Older than ChatGPT by about 15 years}. {Surprisingly effective|Works better than you'd think|Enough to have this conversation}.`
901
- ]
902
- };
903
- var soma2 = {
904
- /**
905
- * Generate a skeleton-based message.
906
- * @param {string} intent - Key from SKELETONS
907
- * @param {object} slots - Values to fill slots
908
- * @returns {string}
909
- */
910
- say(intent, slots = {}) {
911
- const templates = SKELETONS2[intent];
912
- if (!templates) return `[unknown intent: ${intent}]`;
913
- const template = pick2(templates);
914
- return expand2(template, slots);
915
- },
916
- /**
917
- * Get a topic-aware paragraph.
918
- * @param {string} topic - Key from TOPICS
919
- * @returns {string}
920
- */
921
- ask(topic) {
922
- const paragraphs = TOPICS2[topic];
923
- if (!paragraphs) return null;
924
- return spin2(pick2(paragraphs));
925
- },
926
- /**
927
- * All available topic keys.
928
- */
929
- topics() {
930
- return Object.keys(TOPICS2);
931
- },
932
- /**
933
- * Greet (first time).
934
- */
935
- greet() {
936
- return this.say("greet");
937
- },
938
- /**
939
- * Greet returning user.
940
- */
941
- greetBack(user) {
942
- return this.say("greet_back", { user });
943
- },
944
- /**
945
- * Success message.
946
- */
947
- ok(detail) {
948
- return this.say("success", { detail });
949
- },
950
- /**
951
- * Failure message.
952
- */
953
- fail(problem) {
954
- return this.say("failure", { problem });
955
- },
956
- /**
957
- * Waiting/progress message.
958
- */
959
- wait() {
960
- return this.say("waiting");
961
- },
962
- /**
963
- * Expand raw spintax text.
964
- */
965
- spin(text) {
966
- return spin2(text);
967
- },
968
- /**
969
- * Fill slots in a template.
970
- */
971
- expand(template, slots) {
972
- return expand2(template, slots);
973
- }
974
- };
975
-
976
- // ../../cli/dist/welcome/qa.js
857
+ // welcome/qa.js
977
858
  init_display();
978
859
  var QUESTION_MAP = [
979
860
  { triggers: ["compact", "compaction", "summarise", "summarize", "summarization", "compress", "waiting", "context limit", "token limit", "context fills", "context window", "run out", "limit"], topic: "no_compaction", label: "Why no compaction?" },
@@ -1015,7 +896,7 @@ function matchQuestion(input) {
1015
896
  async function handleQuestion(input) {
1016
897
  const match = matchQuestion(input);
1017
898
  if (match) {
1018
- const answer = soma2.ask(match.topic);
899
+ const answer = soma.ask(match.topic);
1019
900
  console.log("");
1020
901
  await typeParagraph(answer);
1021
902
  return true;
@@ -1027,16 +908,16 @@ async function handleQuestion(input) {
1027
908
  const greeting = /^(hi|hey|hello|sup|yo|howdy|hola|greetings|good morning|good evening)\b/.test(lower);
1028
909
  console.log("");
1029
910
  if (greeting) {
1030
- await typeOut(` ${soma2.greet()} ${soma2.spin("{Ask me anything.|What do you want to know?|I know about 9 topics \u2014 pick one.}")}
911
+ await typeOut(` ${soma.greet()} ${soma.spin("{Ask me anything.|What do you want to know?|I know about 9 topics \u2014 pick one.}")}
1031
912
  `);
1032
913
  } else if (rude) {
1033
- await typeParagraph(soma2.ask("meta_rude"));
914
+ await typeParagraph(soma.ask("meta_rude"));
1034
915
  } else if (impressed) {
1035
- await typeParagraph(soma2.ask("meta_impressed"));
916
+ await typeParagraph(soma.ask("meta_impressed"));
1036
917
  } else if (meta) {
1037
- await typeParagraph(soma2.ask("meta_self"));
918
+ await typeParagraph(soma.ask("meta_self"));
1038
919
  } else {
1039
- await typeParagraph(soma2.ask("meta_nonsense"));
920
+ await typeParagraph(soma.ask("meta_nonsense"));
1040
921
  }
1041
922
  return false;
1042
923
  }
@@ -1067,18 +948,18 @@ async function interactiveQ() {
1067
948
  }
1068
949
  if (rounds >= maxRounds) {
1069
950
  console.log("");
1070
- await typeOut(` ${soma2.spin("{Curious enough?|Intrigued?|Want to see it in action?}")} ${dim("Let's set you up.")}
951
+ await typeOut(` ${soma.spin("{Curious enough?|Intrigued?|Want to see it in action?}")} ${dim("Let's set you up.")}
1071
952
  `);
1072
953
  }
1073
954
  }
1074
955
 
1075
- // ../../cli/dist/welcome/auth.js
956
+ // welcome/auth.js
1076
957
  init_display();
1077
958
  import { appendFileSync as appendFileSync2 } from "fs";
1078
959
  async function apiKeySetup() {
1079
960
  console.log(` ${yellow("!")} One more thing \u2014 Soma needs an AI provider to work.`);
1080
961
  console.log("");
1081
- await typeOut(` ${soma2.spin("{Do you have an Anthropic API key?|Got a Claude API key?|Have an API key for Claude?}")}
962
+ await typeOut(` ${soma.spin("{Do you have an Anthropic API key?|Got a Claude API key?|Have an API key for Claude?}")}
1082
963
  `);
1083
964
  console.log("");
1084
965
  console.log(` ${green("y")} ${dim("Yes, I have a key")}`);
@@ -1116,7 +997,7 @@ async function apiKeyExplain() {
1116
997
  }
1117
998
  async function apiKeyGetOne() {
1118
999
  console.log("");
1119
- await typeOut(` ${soma2.spin("{Here's how.|Let me walk you through it.|Quick steps.}")}
1000
+ await typeOut(` ${soma.spin("{Here's how.|Let me walk you through it.|Quick steps.}")}
1120
1001
  `);
1121
1002
  console.log("");
1122
1003
  console.log(` ${cyan("Step 1:")} Open this link to create a key:`);
@@ -1163,7 +1044,7 @@ export ANTHROPIC_API_KEY="${apiKey}"
1163
1044
  console.log(` ${green("\u2713")} Key saved to ${dim(shellConfigName)}`);
1164
1045
  console.log("");
1165
1046
  process.env.ANTHROPIC_API_KEY = apiKey;
1166
- await typeOut(` ${soma2.spin("{You're all set.|Good to go.|Ready.}")} ${dim("Soma can start now.")}
1047
+ await typeOut(` ${soma.spin("{You're all set.|Good to go.|Ready.}")} ${dim("Soma can start now.")}
1167
1048
  `);
1168
1049
  console.log("");
1169
1050
  } catch {
@@ -1182,13 +1063,13 @@ async function oauthGuide() {
1182
1063
  console.log(` ${dim("When Soma starts, type")} ${green("/login")} ${dim("and follow the prompts.")}`);
1183
1064
  console.log(` ${dim("It'll open your browser to authenticate.")}`);
1184
1065
  console.log("");
1185
- await typeOut(` ${soma2.spin("{Let's launch.|Starting up.|Here we go.}")}
1066
+ await typeOut(` ${soma.spin("{Let's launch.|Starting up.|Here we go.}")}
1186
1067
  `);
1187
1068
  console.log("");
1188
1069
  process.env._SOMA_OAUTH_PENDING = "1";
1189
1070
  }
1190
1071
 
1191
- // ../../cli/dist/welcome/about.js
1072
+ // welcome/about.js
1192
1073
  init_display();
1193
1074
  async function showAbout() {
1194
1075
  printSigma();
@@ -1220,7 +1101,7 @@ async function showAbout() {
1220
1101
  console.log("");
1221
1102
  console.log(` ${dim("\u2500".repeat(58))}`);
1222
1103
  console.log("");
1223
- const pitch = soma2.ask("what_is_soma");
1104
+ const pitch = soma.ask("what_is_soma");
1224
1105
  await typeOut(` ${magenta("\u275D")} ${italic(pitch)}
1225
1106
  `);
1226
1107
  console.log("");
@@ -1234,18 +1115,25 @@ async function showAbout() {
1234
1115
 
1235
1116
  // thin-cli.js
1236
1117
  var __dirname = dirname3(fileURLToPath(import.meta.url));
1237
- var VERSION = JSON.parse(readFileSync3(join3(__dirname, "..", "package.json"), "utf-8")).version;
1238
- function semverCmp(a, b) {
1239
- if (!a || !b) return 0;
1240
- const pa = a.split(".").map(Number);
1241
- const pb = b.split(".").map(Number);
1242
- for (let i = 0; i < 3; i++) {
1243
- const va = pa[i] || 0, vb = pb[i] || 0;
1244
- if (va < vb) return -1;
1245
- if (va > vb) return 1;
1118
+ var __CLI_VERSION__ = "__CLI_VERSION__";
1119
+ function resolveCliVersion() {
1120
+ if (!__CLI_VERSION__.startsWith("__")) return __CLI_VERSION__;
1121
+ const candidates = [
1122
+ join3(__dirname, "package.json"),
1123
+ join3(__dirname, "..", "package.json"),
1124
+ join3(__dirname, "..", "npm", "package.json")
1125
+ ];
1126
+ for (const p of candidates) {
1127
+ try {
1128
+ const pkg = JSON.parse(readFileSync3(p, "utf-8"));
1129
+ if (pkg.name === "meetsoma") return pkg.version;
1130
+ } catch {
1131
+ }
1246
1132
  }
1247
- return 0;
1133
+ return "unknown";
1248
1134
  }
1135
+ var VERSION = resolveCliVersion();
1136
+ var semverCmp2 = semverCmp;
1249
1137
  async function showWelcome() {
1250
1138
  printSigma();
1251
1139
  console.log(` ${bold("Soma")} ${dim("\u2014")} ${white("the AI agent that remembers")}`);
@@ -1333,8 +1221,9 @@ function showHelp() {
1333
1221
  console.log(` ${green("soma map --list")} Show available MAPs`);
1334
1222
  console.log("");
1335
1223
  console.log(` ${bold("Maintenance")}`);
1336
- console.log(` ${green("soma doctor")} Verify installation health`);
1337
- console.log(` ${green("soma update")} Check for updates`);
1224
+ console.log(` ${green("soma doctor")} Verify installation + project health`);
1225
+ console.log(` ${green("soma update")} Update the Soma runtime`);
1226
+ console.log(` ${green("soma check-updates")} Check for updates without installing`);
1338
1227
  console.log(` ${green("soma status")} Show installation status`);
1339
1228
  console.log("");
1340
1229
  console.log(` ${bold("Options")}`);
@@ -1492,8 +1381,17 @@ async function initSoma() {
1492
1381
  console.log("");
1493
1382
  }
1494
1383
  async function checkAndUpdate() {
1384
+ if (!isInstalled()) {
1385
+ printSigma();
1386
+ console.log(` ${bold("Soma")} \u2014 Update`);
1387
+ console.log("");
1388
+ console.log(` ${yellow("\u26A0")} Soma is not installed yet.`);
1389
+ console.log(` Run ${green("soma init")} to install, then ${green("soma update")} to update.`);
1390
+ console.log("");
1391
+ return;
1392
+ }
1495
1393
  printSigma();
1496
- console.log(` ${bold("Soma")} \u2014 Status`);
1394
+ console.log(` ${bold("Soma")} \u2014 Update`);
1497
1395
  console.log("");
1498
1396
  const config = readConfig();
1499
1397
  const installPath = config.installPath || join3(SOMA_HOME, "agent");
@@ -1529,7 +1427,7 @@ async function checkAndUpdate() {
1529
1427
  console.log(` ${green("\u2713")} Already up to date.`);
1530
1428
  const agentV = getAgentVersion();
1531
1429
  const projectV = getProjectVersion();
1532
- if (agentV && projectV && semverCmp(projectV, agentV) < 0) {
1430
+ if (agentV && projectV && semverCmp2(projectV, agentV) < 0) {
1533
1431
  console.log(` ${yellow("\u26A0")} Project .soma/ is at ${cyan(`v${projectV}`)}, agent is at ${cyan(`v${agentV}`)}.`);
1534
1432
  console.log(` Run ${green("soma doctor")} to check for updates.`);
1535
1433
  }
@@ -1559,7 +1457,7 @@ async function checkAndUpdate() {
1559
1457
  const shouldUpdate = await confirmYN(` ${dim("\u2192")} Update now?`);
1560
1458
  if (!shouldUpdate) {
1561
1459
  console.log("");
1562
- console.log(` ${dim("Skipped. Run")} ${green("soma init")} ${dim("anytime to update.")}`);
1460
+ console.log(` ${dim("Skipped. Run")} ${green("soma update")} ${dim("anytime to update.")}`);
1563
1461
  console.log("");
1564
1462
  return;
1565
1463
  }
@@ -1609,50 +1507,82 @@ async function checkAndUpdate() {
1609
1507
  console.log("");
1610
1508
  }
1611
1509
  function checkForUpdates() {
1510
+ if (!isInstalled()) {
1511
+ printSigma();
1512
+ console.log(` ${bold("Soma")} \u2014 Update Check`);
1513
+ console.log("");
1514
+ console.log(` ${yellow("\u26A0")} Soma is not installed yet.`);
1515
+ console.log(` Run ${green("soma init")} to install it first.`);
1516
+ console.log("");
1517
+ return;
1518
+ }
1612
1519
  printSigma();
1613
1520
  console.log(` ${bold("Soma")} \u2014 Update Check`);
1614
1521
  console.log("");
1615
- const agentV = getAgentVersion();
1616
- if (agentV) {
1617
- console.log(` Soma: ${cyan(`v${agentV}`)}`);
1618
- }
1619
- console.log(` CLI: ${cyan(`v${VERSION}`)}`);
1620
- const config = readConfig();
1621
- try {
1622
- const latest = execSync2("npm view meetsoma version 2>/dev/null", { encoding: "utf-8" }).trim();
1623
- if (latest && latest !== VERSION && latest > VERSION) {
1624
- console.log("");
1625
- console.log(` ${yellow("\u2B06")} CLI update available: ${green(`v${latest}`)}`);
1626
- console.log(` Run: ${green("npm install -g meetsoma")}`);
1627
- } else {
1628
- console.log(` ${green("\u2713")} CLI is up to date`);
1629
- }
1630
- } catch {
1631
- console.log(` ${dim("Could not check npm registry")}`);
1522
+ const snap = getVersionSnapshot();
1523
+ const intro = snap.allAligned ? soma.say("version_aligned") : soma.say("version_check");
1524
+ console.log(` ${dim(intro)}`);
1525
+ console.log("");
1526
+ const row = (label, local, remote, status) => {
1527
+ const v = local ? `v${local}` : dim("\u2014");
1528
+ let tail = "";
1529
+ if (status === "aligned") tail = ` ${green("\u2713")} latest on npm`;
1530
+ else if (status === "dev-ahead") tail = ` ${green("\u2713")} dev-ahead ${dim(`(npm: v${remote})`)}`;
1531
+ else if (status === "stale") tail = ` ${yellow("\u2B06")} stale ${dim(`(npm: v${remote})`)}`;
1532
+ else if (status === "marker-lag") tail = ` ${yellow("\u2B06")} marker lag ${dim("\u2014 run `soma doctor` to advance")}`;
1533
+ else if (status === "no-workspace") tail = ` ${dim("\u2014 no .soma/ in cwd")}`;
1534
+ else if (status === "not-installed") tail = ` ${red("\u2717")} not installed`;
1535
+ else tail = ` ${dim("\u2014 unknown")}`;
1536
+ console.log(` ${label.padEnd(26)}${cyan(v.padEnd(12))}${tail}`);
1537
+ };
1538
+ row("CLI (meetsoma)", snap.cli.local, snap.cli.remote, snap.cli.status);
1539
+ row("Agent (soma-agent)", snap.agent.local, snap.agent.remote, snap.agent.status);
1540
+ row("Workspace (.soma)", snap.workspace.local, null, snap.workspace.status);
1541
+ if (snap.coreRepo && snap.coreRepo.behind > 0) {
1542
+ console.log(` ${yellow("\u2B06")} Core repo: ${snap.coreRepo.behind} commit${snap.coreRepo.behind !== 1 ? "s" : ""} behind origin`);
1543
+ } else if (snap.coreRepo) {
1544
+ console.log(` ${green("\u2713")} Core repo in sync with origin`);
1632
1545
  }
1633
- if (isInstalled() && config.installPath) {
1634
- try {
1635
- execSync2("git fetch origin --quiet", { cwd: config.installPath, stdio: "ignore", timeout: 1e4 });
1636
- const branch = execSync2("git rev-parse --abbrev-ref HEAD", {
1637
- cwd: config.installPath,
1638
- encoding: "utf-8"
1639
- }).trim();
1640
- const behind = execSync2(
1641
- `git rev-list HEAD..origin/${branch} --count`,
1642
- { cwd: config.installPath, encoding: "utf-8" }
1643
- ).trim();
1644
- if (behind && parseInt(behind) > 0) {
1645
- console.log(` ${yellow("\u2B06")} Core: ${behind} commit${behind !== "1" ? "s" : ""} behind. Run ${green("soma init")} to update.`);
1546
+ console.log("");
1547
+ if (snap.allAligned) {
1548
+ console.log(` ${green("\u2713")} ${soma.say("version_aligned")}`);
1549
+ } else {
1550
+ console.log(` ${dim(soma.say("version_drift"))}`);
1551
+ console.log("");
1552
+ const hints = [];
1553
+ if (snap.cli.status === "stale") {
1554
+ if (snap.devInstall) {
1555
+ hints.push(`${yellow("\u2192")} CLI stale: ${dim("switch to stable first:")} ${green("soma-install.sh stable")} ${dim("\u2192 then")} ${green("npm i -g meetsoma")}`);
1646
1556
  } else {
1647
- console.log(` ${green("\u2713")} Core is up to date`);
1557
+ hints.push(`${yellow("\u2192")} CLI stale: run ${green("npm i -g meetsoma")}`);
1648
1558
  }
1649
- } catch {
1650
- console.log(` ${dim("Could not check core updates")}`);
1651
1559
  }
1560
+ if (snap.agent.status === "stale") {
1561
+ hints.push(`${yellow("\u2192")} Agent stale: run ${green("soma update")} ${dim("(or")} ${green("soma-install.sh stable")}${dim(")")}`);
1562
+ }
1563
+ if (snap.workspace.status === "marker-lag") {
1564
+ hints.push(`${yellow("\u2192")} Workspace marker behind agent: run ${green("soma doctor")}`);
1565
+ }
1566
+ if (snap.coreRepo && snap.coreRepo.behind > 0) {
1567
+ hints.push(`${yellow("\u2192")} Core repo behind: run ${green("soma update")} (or git pull in ${dim("~/.soma/agent/")})`);
1568
+ }
1569
+ if (hints.length === 0) {
1570
+ hints.push(`${green("\u2713")} Nothing to do.`);
1571
+ }
1572
+ hints.forEach((h) => console.log(` ${h}`));
1652
1573
  }
1653
1574
  console.log("");
1654
1575
  }
1655
1576
  async function healthCheck() {
1577
+ if (!isInstalled()) {
1578
+ printSigma();
1579
+ console.log(` ${bold("Soma")} \u2014 Health Check`);
1580
+ console.log("");
1581
+ console.log(` ${yellow("\u26A0")} Soma is not installed yet.`);
1582
+ console.log(` Run ${green("soma init")} to install, then ${green("soma status")} to check health.`);
1583
+ console.log("");
1584
+ return;
1585
+ }
1656
1586
  printSigma();
1657
1587
  console.log(` ${bold("Soma")} \u2014 Health Check`);
1658
1588
  console.log("");
@@ -1800,17 +1730,17 @@ async function projectDoctor() {
1800
1730
  if (!projectV) {
1801
1731
  console.log(` ${yellow("\u26A0")} Project .soma/ has no version (pre-versioning).`);
1802
1732
  console.log(` This project was likely created before v0.6.3.`);
1803
- console.log(` Run ${green("soma init")} to bring it up to date.`);
1733
+ console.log(` Run ${green("/soma doctor")} inside a session to migrate, or re-init with ${green("soma init")}.`);
1804
1734
  console.log("");
1805
1735
  return;
1806
1736
  }
1807
- if (agentV && semverCmp(projectV, agentV) === 0) {
1737
+ if (agentV && semverCmp2(projectV, agentV) === 0) {
1808
1738
  console.log(` ${green("\u2713")} Project is up to date.`);
1809
1739
  console.log("");
1810
1740
  await healthCheck();
1811
1741
  return;
1812
1742
  }
1813
- if (agentV && semverCmp(projectV, agentV) < 0) {
1743
+ if (agentV && semverCmp2(projectV, agentV) < 0) {
1814
1744
  console.log(` ${yellow("\u26A0")} Project .soma/ is at ${cyan(`v${projectV}`)} , agent is at ${cyan(`v${agentV}`)} .`);
1815
1745
  console.log("");
1816
1746
  let fixes = 0;
@@ -2087,7 +2017,7 @@ if (cmd === "--version" || cmd === "-v" || cmd === "-V" || cmd === "version") {
2087
2017
  }
2088
2018
  await delegateToCore();
2089
2019
  } else {
2090
- const postInstallCmds = ["focus", "inhale", "content", "install", "list", "map", "--map", "--preload"];
2020
+ const postInstallCmds = ["focus", "inhale", "content", "install", "list", "map", "--map", "--preload", "model", "session", "code", "verify", "refactor", "seam", "hub", "body", "run"];
2091
2021
  if (cmd && postInstallCmds.includes(cmd)) {
2092
2022
  printSigma();
2093
2023
  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.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Soma — the AI coding agent with self-growing memory",
5
5
  "type": "module",
6
6
  "bin": {