meetsoma 0.1.5 → 0.2.0
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 +26 -0
- package/README.md +74 -22
- package/dist/thin-cli.js +401 -39
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ 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.2.0] — 2026-04-02
|
|
8
|
+
|
|
9
|
+
Corresponds to Soma agent v0.8.0.
|
|
10
|
+
|
|
11
|
+
### New
|
|
12
|
+
|
|
13
|
+
- **`soma doctor`** — check project health, auto-fix stale settings/body/protocols, run migrations.
|
|
14
|
+
- **`soma status` / `soma health`** — quick project health check (renamed from old `soma doctor`).
|
|
15
|
+
- **`soma --version`** — shows both agent and CLI versions.
|
|
16
|
+
- **`soma --help`** — delegates to core agent for full help output.
|
|
17
|
+
- **`--scan` / `--all` flags** — doctor multi-project support (delegates to core).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **CLI routing overhaul** — doctor/status/health/update/version all route through thin-cli properly.
|
|
22
|
+
- **Stale protocol detection** — doctor identifies outdated bundled protocols and offers to update them.
|
|
23
|
+
- **Version-aware pending file** — `_doctor-pending.md` injected on boot when migration is available.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **Help header** — shows "CLI v0.2.0" instead of bare version number.
|
|
28
|
+
- **Orphaned `showStatus()`** — removed dead code from thin-cli.
|
|
29
|
+
- **Placeholder text** — replaced thin-cli stubs with real output.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
7
33
|
## [0.1.4] — 2026-03-28
|
|
8
34
|
|
|
9
35
|
Corresponds to Soma agent v0.6.5.
|
package/README.md
CHANGED
|
@@ -25,36 +25,83 @@ An AI coding agent that grows with you. Identity, protocols, and muscle memory
|
|
|
25
25
|
|
|
26
26
|
Most agents forget everything between sessions. Soma doesn't.
|
|
27
27
|
|
|
28
|
-
##
|
|
29
|
-
|
|
30
|
-
On first run, you'll meet the personality engine — no AI, just sentence templates. It introduces Soma and lets you ask questions before committing. Press Enter to install.
|
|
28
|
+
## Install
|
|
31
29
|
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g meetsoma
|
|
32
32
|
```
|
|
33
|
-
σ
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
This installs the Soma CLI — a thin bootstrap layer. On first run, it downloads the full agent runtime (~50MB) from GitHub.
|
|
35
|
+
|
|
36
|
+
### What Happens
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
npm install -g meetsoma ← installs CLI only (~50KB)
|
|
40
|
+
soma ← first run: downloads agent runtime
|
|
41
|
+
soma ← every run after: starts a session
|
|
42
|
+
```
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
**Requirements:** Node.js ≥ 20.6, git, an API key (Anthropic, Google, or OpenAI).
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
## First Run
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
```bash
|
|
49
|
+
cd your-project
|
|
50
|
+
soma
|
|
43
51
|
```
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
Soma detects your project (language, framework, package manager, monorepo signals), creates `.soma/`, and writes its own identity. By session two, it remembers.
|
|
46
54
|
|
|
47
55
|
## Commands
|
|
48
56
|
|
|
57
|
+
### CLI (from your shell)
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
soma # fresh session
|
|
61
|
+
soma inhale # resume from last session's preload
|
|
62
|
+
soma -c # continue last session (full history)
|
|
63
|
+
soma -r # pick a session to resume
|
|
64
|
+
soma focus <keyword> # prime for a topic
|
|
65
|
+
soma map <name> # boot with a workflow MAP
|
|
66
|
+
|
|
67
|
+
soma doctor # project health + migration check
|
|
68
|
+
soma status # infrastructure health check
|
|
69
|
+
soma --version # show agent + CLI versions
|
|
70
|
+
soma --help # full command reference
|
|
71
|
+
soma --help scripts # list installed scripts
|
|
72
|
+
soma --help commands # all CLI + TUI commands
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Session (inside the TUI)
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
/exhale save state + write preload for next session
|
|
79
|
+
/breathe save + rotate into fresh session
|
|
80
|
+
/inhale check preload status
|
|
81
|
+
/rest disable keepalive + exhale
|
|
82
|
+
/pin <name> keep a muscle/protocol hot
|
|
83
|
+
/kill <name> drop to cold
|
|
84
|
+
/hub install install from the community hub
|
|
85
|
+
/soma doctor run migration analysis
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Two Versions
|
|
89
|
+
|
|
90
|
+
Soma has two independently-versioned layers:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
σ Soma v0.7.1 ← agent version (the runtime — features, protocols, templates)
|
|
94
|
+
CLI v0.2.0 ← CLI version (this npm package — install flow, delegation)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The **agent version** is what matters for features. The **CLI version** is the bootstrap.
|
|
98
|
+
|
|
99
|
+
### Updating
|
|
100
|
+
|
|
49
101
|
```bash
|
|
50
|
-
soma
|
|
51
|
-
|
|
52
|
-
soma
|
|
53
|
-
soma --map <name> # load a workflow
|
|
54
|
-
|
|
55
|
-
soma init # install the runtime
|
|
56
|
-
soma doctor # check health
|
|
57
|
-
soma update # check for updates
|
|
102
|
+
soma init # update the agent runtime
|
|
103
|
+
npm install -g meetsoma # update the CLI (rare)
|
|
104
|
+
soma doctor # check if project .soma/ needs migration
|
|
58
105
|
```
|
|
59
106
|
|
|
60
107
|
## The Five Ideas
|
|
@@ -69,11 +116,16 @@ soma update # check for updates
|
|
|
69
116
|
|
|
70
117
|
**Heat** — attention management. Used things stay. Unused things cool.
|
|
71
118
|
|
|
72
|
-
##
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
~/.soma/agent/ ← global runtime (git clone, updated via soma init)
|
|
123
|
+
your-project/.soma/ ← per-project memory (identity, protocols, sessions)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The CLI delegates to the runtime. The runtime loads your project's `.soma/`, resolves the identity chain (project → parent → global), and boots the agent with your accumulated context.
|
|
73
127
|
|
|
74
|
-
-
|
|
75
|
-
- git
|
|
76
|
-
- API key (Anthropic, Google, or OpenAI)
|
|
128
|
+
On boot, Soma silently adds any missing settings or protocols from newer versions (Tier 1 auto-fix). Bigger migrations are handled by `/soma doctor` inside the TUI.
|
|
77
129
|
|
|
78
130
|
---
|
|
79
131
|
|
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, appendFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync, readdirSync, statSync, readlinkSync } from "fs";
|
|
12
12
|
import { join, dirname } from "path";
|
|
13
13
|
import { homedir, platform } from "os";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
@@ -50,7 +50,32 @@ function isInstalled() {
|
|
|
50
50
|
// Check both layouts: soma-beta (dist/extensions) and dev setup (extensions/)
|
|
51
51
|
const hasDist = existsSync(join(CORE_DIR, "dist", "extensions")) && existsSync(join(CORE_DIR, "dist", "core"));
|
|
52
52
|
const hasDev = existsSync(join(CORE_DIR, "extensions")) && existsSync(join(CORE_DIR, "core"));
|
|
53
|
-
|
|
53
|
+
// Check deps exist — Pi is the critical dependency
|
|
54
|
+
const hasDeps = existsSync(join(CORE_DIR, "node_modules", "@mariozechner"));
|
|
55
|
+
return (hasDist || hasDev) && hasDeps;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getAgentVersion() {
|
|
59
|
+
try {
|
|
60
|
+
// In dev mode, core/ is symlinked — resolve to find the real repo's package.json
|
|
61
|
+
let pkgPath = join(CORE_DIR, "package.json");
|
|
62
|
+
try {
|
|
63
|
+
const realCore = readlinkSync(join(CORE_DIR, "core"));
|
|
64
|
+
if (realCore) {
|
|
65
|
+
// core/ symlink → repos/agent/core → go up one level for package.json
|
|
66
|
+
const devPkg = join(dirname(realCore), "package.json");
|
|
67
|
+
if (existsSync(devPkg)) pkgPath = devPkg;
|
|
68
|
+
}
|
|
69
|
+
} catch { /* not a symlink, use default */ }
|
|
70
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
71
|
+
} catch { return null; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getProjectVersion() {
|
|
75
|
+
try {
|
|
76
|
+
const settingsPath = join(process.cwd(), ".soma", "settings.json");
|
|
77
|
+
return JSON.parse(readFileSync(settingsPath, "utf-8")).version || null;
|
|
78
|
+
} catch { return null; }
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
// ── Browser ──────────────────────────────────────────────────────────
|
|
@@ -693,7 +718,13 @@ function showHelp() {
|
|
|
693
718
|
}
|
|
694
719
|
|
|
695
720
|
function showVersion() {
|
|
696
|
-
|
|
721
|
+
const agentV = getAgentVersion();
|
|
722
|
+
if (agentV) {
|
|
723
|
+
console.log(`σ Soma v${agentV}`);
|
|
724
|
+
console.log(` CLI v${VERSION}`);
|
|
725
|
+
} else {
|
|
726
|
+
console.log(`soma v${VERSION}`);
|
|
727
|
+
}
|
|
697
728
|
}
|
|
698
729
|
|
|
699
730
|
async function showAbout() {
|
|
@@ -887,7 +918,7 @@ async function initSoma() {
|
|
|
887
918
|
// Save config
|
|
888
919
|
const config = readConfig();
|
|
889
920
|
config.installedAt = config.installedAt || new Date().toISOString();
|
|
890
|
-
config.coreVersion = VERSION;
|
|
921
|
+
config.coreVersion = getAgentVersion() || VERSION;
|
|
891
922
|
config.installPath = installDir;
|
|
892
923
|
writeConfig(config);
|
|
893
924
|
|
|
@@ -936,6 +967,15 @@ async function checkAndUpdate() {
|
|
|
936
967
|
|
|
937
968
|
if (behind === 0) {
|
|
938
969
|
console.log(` ${green("✓")} Already up to date.`);
|
|
970
|
+
|
|
971
|
+
// Check project version staleness even when runtime is current
|
|
972
|
+
const agentV = getAgentVersion();
|
|
973
|
+
const projectV = getProjectVersion();
|
|
974
|
+
if (agentV && projectV && projectV < agentV) {
|
|
975
|
+
console.log(` ${yellow("⚠")} Project .soma/ is at ${cyan(`v${projectV}`)}, agent is at ${cyan(`v${agentV}`)}.`);
|
|
976
|
+
console.log(` Run ${green("soma doctor")} to check for updates.`);
|
|
977
|
+
}
|
|
978
|
+
|
|
939
979
|
console.log("");
|
|
940
980
|
console.log(` ${dim("Soma is set up and ready.")} Run ${green("soma")} ${dim("in a project to start a session.")}`);
|
|
941
981
|
console.log("");
|
|
@@ -1013,12 +1053,13 @@ function checkForUpdates() {
|
|
|
1013
1053
|
printSigma();
|
|
1014
1054
|
console.log(` ${bold("Soma")} — Update Check`);
|
|
1015
1055
|
console.log("");
|
|
1016
|
-
|
|
1056
|
+
const agentV = getAgentVersion();
|
|
1057
|
+
if (agentV) {
|
|
1058
|
+
console.log(` Soma: ${cyan(`v${agentV}`)}`);
|
|
1059
|
+
}
|
|
1060
|
+
console.log(` CLI: ${cyan(`v${VERSION}`)}`);
|
|
1017
1061
|
|
|
1018
1062
|
const config = readConfig();
|
|
1019
|
-
if (config.coreVersion) {
|
|
1020
|
-
console.log(` Core version: ${cyan(`v${config.coreVersion}`)}`);
|
|
1021
|
-
}
|
|
1022
1063
|
|
|
1023
1064
|
// Check npm for CLI updates
|
|
1024
1065
|
try {
|
|
@@ -1059,7 +1100,7 @@ function checkForUpdates() {
|
|
|
1059
1100
|
console.log("");
|
|
1060
1101
|
}
|
|
1061
1102
|
|
|
1062
|
-
async function
|
|
1103
|
+
async function healthCheck() {
|
|
1063
1104
|
printSigma();
|
|
1064
1105
|
console.log(` ${bold("Soma")} — Health Check`);
|
|
1065
1106
|
console.log("");
|
|
@@ -1105,12 +1146,26 @@ async function doctor() {
|
|
|
1105
1146
|
: join(CORE_DIR, "core");
|
|
1106
1147
|
check(existsSync(coreDir), "Core modules present", "Core modules missing");
|
|
1107
1148
|
|
|
1108
|
-
// Git repo health
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1149
|
+
// Git repo health (skip in dev mode — no .git when using symlinks)
|
|
1150
|
+
if (existsSync(join(CORE_DIR, ".git"))) {
|
|
1151
|
+
try {
|
|
1152
|
+
execSync("git status --porcelain", { cwd: CORE_DIR, stdio: "ignore" });
|
|
1153
|
+
check(true, "Git repo healthy", "");
|
|
1154
|
+
} catch {
|
|
1155
|
+
warn(false, "", "Core git repo has issues");
|
|
1156
|
+
}
|
|
1157
|
+
} else {
|
|
1158
|
+
// Dev mode — core/ is symlinked, no .git expected
|
|
1159
|
+
try {
|
|
1160
|
+
const realCore = readlinkSync(join(CORE_DIR, "core"));
|
|
1161
|
+
if (realCore) {
|
|
1162
|
+
check(true, "Dev mode (symlinked)", "");
|
|
1163
|
+
} else {
|
|
1164
|
+
warn(false, "", "Core git repo missing — run soma init");
|
|
1165
|
+
}
|
|
1166
|
+
} catch {
|
|
1167
|
+
warn(false, "", "Core git repo missing — run soma init");
|
|
1168
|
+
}
|
|
1114
1169
|
}
|
|
1115
1170
|
}
|
|
1116
1171
|
|
|
@@ -1146,39 +1201,333 @@ async function doctor() {
|
|
|
1146
1201
|
console.log("");
|
|
1147
1202
|
}
|
|
1148
1203
|
|
|
1149
|
-
function
|
|
1204
|
+
async function projectDoctor() {
|
|
1205
|
+
const doctorArgs = args.slice(1);
|
|
1206
|
+
const wantsScan = doctorArgs.includes("--scan");
|
|
1207
|
+
const wantsAll = doctorArgs.includes("--all");
|
|
1208
|
+
|
|
1209
|
+
// --scan and --all need the runtime — delegate to core
|
|
1210
|
+
if ((wantsScan || wantsAll) && isInstalled()) {
|
|
1211
|
+
await delegateToCore();
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1150
1215
|
printSigma();
|
|
1151
|
-
|
|
1216
|
+
const agentV = getAgentVersion();
|
|
1217
|
+
const projectV = getProjectVersion();
|
|
1218
|
+
const installed = isInstalled();
|
|
1219
|
+
|
|
1220
|
+
console.log(` ${bold("Soma")} — Doctor`);
|
|
1152
1221
|
console.log("");
|
|
1153
1222
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1223
|
+
if (!installed) {
|
|
1224
|
+
console.log(` ${red("✗")} Soma not installed. Run ${green("soma init")} first.`);
|
|
1225
|
+
console.log("");
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1156
1228
|
|
|
1157
|
-
|
|
1158
|
-
console.log(`
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
console.log(` Since: ${dim(config.installedAt.split("T")[0])}`);
|
|
1229
|
+
// Version overview
|
|
1230
|
+
console.log(` Agent: ${cyan(`v${agentV || "unknown"}`)}`);
|
|
1231
|
+
if (projectV) {
|
|
1232
|
+
console.log(` Project: ${cyan(`v${projectV}`)}`);
|
|
1162
1233
|
}
|
|
1234
|
+
console.log(` CLI: ${dim(`v${VERSION}`)}`);
|
|
1235
|
+
console.log("");
|
|
1163
1236
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1237
|
+
const hasSomaDir = existsSync(join(process.cwd(), ".soma"));
|
|
1238
|
+
|
|
1239
|
+
if (!hasSomaDir) {
|
|
1240
|
+
console.log(` ${yellow("⚠")} No .soma/ in current directory.`);
|
|
1241
|
+
console.log(` Run ${green("soma init")} to set up this project, or ${green("soma doctor --scan")} to find projects.`);
|
|
1242
|
+
console.log("");
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (!projectV) {
|
|
1247
|
+
console.log(` ${yellow("⚠")} Project .soma/ has no version (pre-versioning).`);
|
|
1248
|
+
console.log(` This project was likely created before v0.6.3.`);
|
|
1249
|
+
console.log(` Run ${green("soma init")} to bring it up to date.`);
|
|
1250
|
+
console.log("");
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
if (agentV && projectV === agentV) {
|
|
1255
|
+
console.log(` ${green("\u2713")} Project is up to date.`);
|
|
1256
|
+
console.log("");
|
|
1257
|
+
await healthCheck();
|
|
1258
|
+
return;
|
|
1174
1259
|
}
|
|
1260
|
+
|
|
1261
|
+
if (agentV && projectV < agentV) {
|
|
1262
|
+
console.log(` ${yellow("\u26a0")} Project .soma/ is at ${cyan(`v${projectV}`)} , agent is at ${cyan(`v${agentV}`)} .`);
|
|
1263
|
+
console.log("");
|
|
1264
|
+
|
|
1265
|
+
// Tier 1: Auto-fix safe things right now
|
|
1266
|
+
let fixes = 0;
|
|
1267
|
+
const somaDir = join(process.cwd(), ".soma");
|
|
1268
|
+
|
|
1269
|
+
// Add missing settings keys
|
|
1270
|
+
const settingsPath = join(somaDir, "settings.json");
|
|
1271
|
+
if (existsSync(settingsPath)) {
|
|
1272
|
+
try {
|
|
1273
|
+
const current = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
1274
|
+
let changed = false;
|
|
1275
|
+
const add = (k, v) => { if (!(k in current)) { current[k] = v; changed = true; fixes++; } };
|
|
1276
|
+
add("doctor", { autoUpdate: true, declinedVersion: null });
|
|
1277
|
+
add("breathe", { auto: false, triggerAt: 50, rotateAt: 70, graceSeconds: 30 });
|
|
1278
|
+
add("context", { notifyAt: 50, warnAt: 70, urgentAt: 80, autoExhaleAt: 85 });
|
|
1279
|
+
add("preload", { staleAfterHours: 48, lastSessionLogs: 0 });
|
|
1280
|
+
add("scratch", { autoInject: false });
|
|
1281
|
+
add("guard", { coreFiles: "warn", bashCommands: "warn", gitIdentity: null });
|
|
1282
|
+
add("checkpoints", { enabled: true, intervalMinutes: 5, squashOnPush: true });
|
|
1283
|
+
add("persona", { name: null, emoji: "\u03c3" });
|
|
1284
|
+
add("inherit", { identity: true, protocols: true, muscles: true, tools: true });
|
|
1285
|
+
if (changed) writeFileSync(settingsPath, JSON.stringify(current, null, "\t") + "\n");
|
|
1286
|
+
} catch {}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Add missing body/ + templates
|
|
1290
|
+
const bodyDir = join(somaDir, "body");
|
|
1291
|
+
// Resolve agent root — follow dev symlinks if needed
|
|
1292
|
+
let agentRoot = CORE_DIR;
|
|
1293
|
+
try {
|
|
1294
|
+
const realCore = readlinkSync(join(CORE_DIR, "core"));
|
|
1295
|
+
if (realCore) {
|
|
1296
|
+
const devRoot = dirname(realCore);
|
|
1297
|
+
if (existsSync(join(devRoot, "body", "_public"))) agentRoot = devRoot;
|
|
1298
|
+
}
|
|
1299
|
+
} catch {}
|
|
1300
|
+
// Also check dist/ parent (prod layout)
|
|
1301
|
+
if (!existsSync(join(agentRoot, "body", "_public"))) {
|
|
1302
|
+
const parent = dirname(agentRoot);
|
|
1303
|
+
if (existsSync(join(parent, "body", "_public"))) agentRoot = parent;
|
|
1304
|
+
}
|
|
1305
|
+
const bundledBody = join(agentRoot, "body", "_public");
|
|
1306
|
+
if (existsSync(bundledBody)) {
|
|
1307
|
+
try {
|
|
1308
|
+
if (!existsSync(bodyDir)) mkdirSync(bodyDir, { recursive: true });
|
|
1309
|
+
for (const f of readdirSync(bundledBody).filter(f => f.endsWith(".md"))) {
|
|
1310
|
+
const dest = join(bodyDir, f);
|
|
1311
|
+
if (!existsSync(dest)) { writeFileSync(dest, readFileSync(join(bundledBody, f), "utf-8")); fixes++; }
|
|
1312
|
+
}
|
|
1313
|
+
} catch {}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Add missing protocols
|
|
1317
|
+
const protoDir = join(somaDir, "amps", "protocols");
|
|
1318
|
+
const bundledProtos = existsSync(join(CORE_DIR, "dist", "content", "protocols"))
|
|
1319
|
+
? join(CORE_DIR, "dist", "content", "protocols")
|
|
1320
|
+
: existsSync(join(CORE_DIR, "content", "protocols"))
|
|
1321
|
+
? join(CORE_DIR, "content", "protocols") : null;
|
|
1322
|
+
if (bundledProtos) {
|
|
1323
|
+
if (!existsSync(protoDir)) mkdirSync(protoDir, { recursive: true });
|
|
1324
|
+
for (const f of readdirSync(bundledProtos).filter(f => f.endsWith(".md") && f !== "_template.md" && f !== "README.md")) {
|
|
1325
|
+
const dest = join(protoDir, f);
|
|
1326
|
+
if (!existsSync(dest)) { writeFileSync(dest, readFileSync(join(bundledProtos, f), "utf-8")); fixes++; }
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Add missing bundled scripts
|
|
1331
|
+
const scriptsDir = join(somaDir, "amps", "scripts");
|
|
1332
|
+
const bundledScripts = existsSync(join(agentRoot, "dist", "content", "scripts"))
|
|
1333
|
+
? join(agentRoot, "dist", "content", "scripts")
|
|
1334
|
+
: existsSync(join(agentRoot, "content", "scripts"))
|
|
1335
|
+
? join(agentRoot, "content", "scripts")
|
|
1336
|
+
: existsSync(join(agentRoot, "scripts"))
|
|
1337
|
+
? join(agentRoot, "scripts") : null;
|
|
1338
|
+
if (bundledScripts) {
|
|
1339
|
+
if (!existsSync(scriptsDir)) mkdirSync(scriptsDir, { recursive: true });
|
|
1340
|
+
for (const f of readdirSync(bundledScripts).filter(f => f.endsWith(".sh"))) {
|
|
1341
|
+
const dest = join(scriptsDir, f);
|
|
1342
|
+
if (!existsSync(dest)) {
|
|
1343
|
+
writeFileSync(dest, readFileSync(join(bundledScripts, f), "utf-8"), { mode: 0o755 });
|
|
1344
|
+
fixes++;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1175
1348
|
|
|
1349
|
+
// Bump version after fixes
|
|
1350
|
+
if (fixes > 0) {
|
|
1351
|
+
try {
|
|
1352
|
+
const s = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
1353
|
+
s.version = agentV;
|
|
1354
|
+
writeFileSync(settingsPath, JSON.stringify(s, null, "\t") + "\n");
|
|
1355
|
+
} catch {}
|
|
1356
|
+
console.log(` ${green("\u2713")} Applied ${fixes} automatic fixes`);
|
|
1357
|
+
const bc = existsSync(join(somaDir, "body")) ? readdirSync(join(somaDir, "body")).filter(f => f.endsWith(".md")).length : 0;
|
|
1358
|
+
const pc = existsSync(protoDir) ? readdirSync(protoDir).filter(f => f.endsWith(".md")).length : 0;
|
|
1359
|
+
console.log(` ${bc} body files, ${pc} protocols, settings updated`);
|
|
1360
|
+
console.log(` Version bumped to ${cyan(`v${agentV}`)}`);
|
|
1361
|
+
|
|
1362
|
+
// Scan + fix stale protocols (exist but differ from bundled)
|
|
1363
|
+
let staleUpdated = 0;
|
|
1364
|
+
let staleSkipped = [];
|
|
1365
|
+
if (bundledProtos && existsSync(protoDir)) {
|
|
1366
|
+
for (const f of readdirSync(protoDir).filter(f => f.endsWith(".md") && f !== "_template.md" && f !== "README.md")) {
|
|
1367
|
+
const bundledFile = join(bundledProtos, f);
|
|
1368
|
+
if (!existsSync(bundledFile)) continue;
|
|
1369
|
+
const projRaw = readFileSync(join(protoDir, f), "utf-8");
|
|
1370
|
+
const bundledRaw = readFileSync(bundledFile, "utf-8");
|
|
1371
|
+
const strip = s => s.replace(/^(heat|loads|runs|last-run|heat-default):.*\n?/gm, "").trim();
|
|
1372
|
+
if (strip(projRaw) === strip(bundledRaw)) continue;
|
|
1373
|
+
// Preserve user's runtime fields (heat, loads) when updating content
|
|
1374
|
+
const heatLine = projRaw.match(/^heat:.*$/m);
|
|
1375
|
+
const loadsLine = projRaw.match(/^loads:.*$/m);
|
|
1376
|
+
let updated = bundledRaw;
|
|
1377
|
+
if (heatLine) updated = updated.replace(/^heat:.*$/m, heatLine[0]);
|
|
1378
|
+
if (loadsLine) updated = updated.replace(/^loads:.*$/m, loadsLine[0]);
|
|
1379
|
+
writeFileSync(join(protoDir, f), updated);
|
|
1380
|
+
staleUpdated++;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
console.log("");
|
|
1385
|
+
if (staleUpdated > 0) {
|
|
1386
|
+
console.log(` ${green("\u2713")} ${staleUpdated} protocols updated to latest version`);
|
|
1387
|
+
console.log(` ${dim("Heat and load counts preserved. Content updated (coaching-voice rewrites, new TL;DR).")}`);
|
|
1388
|
+
}
|
|
1389
|
+
if (staleSkipped.length > 0) {
|
|
1390
|
+
console.log(` ${yellow("\u26a0")} ${staleSkipped.length} protocols skipped (may be customized)`);
|
|
1391
|
+
}
|
|
1392
|
+
console.log("");
|
|
1393
|
+
const totalRemaining = staleSkipped.length;
|
|
1394
|
+
if (totalRemaining > 0) {
|
|
1395
|
+
console.log(` ${dim("CLI handled: files, scripts, settings, protocols, version bump.")}`);
|
|
1396
|
+
console.log(` ${dim("Remaining: " + totalRemaining + " items need review.")}`);
|
|
1397
|
+
console.log(` ${dim("For full migration:")} ${green("soma")} ${dim("then")} ${green("/soma doctor")}`);
|
|
1398
|
+
} else {
|
|
1399
|
+
console.log(` ${green("\u2713")} Full migration complete from CLI.`);
|
|
1400
|
+
console.log(` ${dim("No TUI session needed — all updates applied.")}`);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Write _doctor-pending.md — only if there are remaining items for the agent
|
|
1404
|
+
// If CLI handled everything, write a brief "complete" note instead
|
|
1405
|
+
try {
|
|
1406
|
+
const pendingPath = join(somaDir, "body", "_doctor-pending.md");
|
|
1407
|
+
if (staleSkipped.length === 0) {
|
|
1408
|
+
// Full migration done — write brief completion note
|
|
1409
|
+
const done = [
|
|
1410
|
+
"---",
|
|
1411
|
+
"type: template",
|
|
1412
|
+
"name: doctor-pending",
|
|
1413
|
+
"status: complete",
|
|
1414
|
+
`created: ${new Date().toISOString().split("T")[0]}`,
|
|
1415
|
+
"description: CLI doctor completed full migration — verify with /soma doctor then delete this file",
|
|
1416
|
+
"---",
|
|
1417
|
+
"",
|
|
1418
|
+
"# Doctor Update — Complete",
|
|
1419
|
+
"",
|
|
1420
|
+
`The CLI doctor migrated this project from v${projectV} to v${agentV} on ${new Date().toISOString().split("T")[0]}.`,
|
|
1421
|
+
"",
|
|
1422
|
+
`Applied: ${fixes} file fixes + ${staleUpdated} protocol updates. All settings, body, scripts, protocols current.`,
|
|
1423
|
+
"",
|
|
1424
|
+
"Run \`/soma doctor\` to verify, then delete this file.",
|
|
1425
|
+
];
|
|
1426
|
+
writeFileSync(pendingPath, done.join("\n"));
|
|
1427
|
+
} else {
|
|
1428
|
+
const pending = [
|
|
1429
|
+
"---",
|
|
1430
|
+
"type: template",
|
|
1431
|
+
"name: doctor-pending",
|
|
1432
|
+
"status: active",
|
|
1433
|
+
`created: ${new Date().toISOString().split("T")[0]}`,
|
|
1434
|
+
"description: Written by soma doctor CLI — tells the agent what was fixed and what needs attention",
|
|
1435
|
+
"---",
|
|
1436
|
+
"",
|
|
1437
|
+
"# Doctor Update — Pending",
|
|
1438
|
+
"",
|
|
1439
|
+
`The CLI doctor ran on ${new Date().toISOString().split("T")[0]} and updated this project from v${projectV} to v${agentV}.`,
|
|
1440
|
+
"",
|
|
1441
|
+
"## What the CLI fixed (Tier 1 — automatic, safe)",
|
|
1442
|
+
"",
|
|
1443
|
+
`- ${bc} body template files (added missing ones, preserved your customized files)`,
|
|
1444
|
+
`- ${pc} protocols present (added missing bundled protocols)`,
|
|
1445
|
+
`- Settings: added missing configuration sections with safe defaults`,
|
|
1446
|
+
`- Version bumped to v${agentV}`,
|
|
1447
|
+
"",
|
|
1448
|
+
`## What the CLI also fixed`,
|
|
1449
|
+
"",
|
|
1450
|
+
`- ${staleUpdated} protocols updated to latest version (heat/loads preserved)`,
|
|
1451
|
+
"",
|
|
1452
|
+
staleSkipped.length > 0 ? `## What still needs attention (${staleSkipped.length} items)` : "## Status: Complete",
|
|
1453
|
+
"",
|
|
1454
|
+
staleSkipped.length > 0
|
|
1455
|
+
? `**${staleSkipped.length} protocols** may be customized and were not auto-updated. The agent needs to:\n- Diff each against bundled version\n- Decide whether to update or preserve`
|
|
1456
|
+
: "All mechanical updates applied by CLI. The agent can verify with /soma doctor.",
|
|
1457
|
+
"",
|
|
1458
|
+
"**To complete the migration:**",
|
|
1459
|
+
"",
|
|
1460
|
+
"Read the migration phases that apply to your version jump:",
|
|
1461
|
+
"```",
|
|
1462
|
+
];
|
|
1463
|
+
// Find which phases apply (only from projectV onward)
|
|
1464
|
+
let agentRoot2 = CORE_DIR;
|
|
1465
|
+
try {
|
|
1466
|
+
const rc = readlinkSync(join(CORE_DIR, "core"));
|
|
1467
|
+
if (rc) { const dr = dirname(rc); if (existsSync(join(dr, "migrations", "phases"))) agentRoot2 = dr; }
|
|
1468
|
+
} catch {}
|
|
1469
|
+
if (!existsSync(join(agentRoot2, "migrations", "phases"))) {
|
|
1470
|
+
const pr = dirname(agentRoot2);
|
|
1471
|
+
if (existsSync(join(pr, "migrations", "phases"))) agentRoot2 = pr;
|
|
1472
|
+
}
|
|
1473
|
+
const phasesDir2 = join(agentRoot2, "migrations", "phases");
|
|
1474
|
+
if (existsSync(phasesDir2)) {
|
|
1475
|
+
const allPhases = readdirSync(phasesDir2).filter(f => f.endsWith(".md")).sort();
|
|
1476
|
+
const relevant = allPhases.filter(f => {
|
|
1477
|
+
const m = f.match(/^v([\d.]+)-to-v/);
|
|
1478
|
+
return m && m[1] >= projectV;
|
|
1479
|
+
});
|
|
1480
|
+
pending.push(`${relevant.length} phases apply (v${projectV} \u2192 v${agentV}):`);
|
|
1481
|
+
pending.push("\`\`\`");
|
|
1482
|
+
for (const f of relevant) {
|
|
1483
|
+
pending.push(" " + join(phasesDir2, f));
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
pending.push("```");
|
|
1487
|
+
pending.push("");
|
|
1488
|
+
pending.push("Execute each phase's **Actions** section in order.");
|
|
1489
|
+
pending.push("After completing all phases, delete this file — it's no longer needed.");
|
|
1490
|
+
pending.push("");
|
|
1491
|
+
writeFileSync(pendingPath, pending.join("\n"));
|
|
1492
|
+
}
|
|
1493
|
+
} catch { /* non-fatal */ }
|
|
1494
|
+
} else {
|
|
1495
|
+
console.log(` ${green("\u2713")} Version bumped to ${cyan(`v${agentV}`)}`);
|
|
1496
|
+
console.log(` ${dim("No file changes needed — project structure is current.")}`);
|
|
1497
|
+
}
|
|
1498
|
+
} else {
|
|
1499
|
+
console.log(` ${green("\u2713")} Project version: ${cyan(`v${projectV}`)}`);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1176
1502
|
console.log("");
|
|
1503
|
+
await healthCheck();
|
|
1177
1504
|
}
|
|
1178
1505
|
|
|
1506
|
+
|
|
1179
1507
|
// ── Delegation ───────────────────────────────────────────────────────
|
|
1180
1508
|
|
|
1181
1509
|
async function delegateToCore() {
|
|
1510
|
+
// Pre-flight: verify runtime can start before delegating
|
|
1511
|
+
const piPkg = join(CORE_DIR, "node_modules", "@mariozechner", "pi-coding-agent");
|
|
1512
|
+
if (!existsSync(piPkg)) {
|
|
1513
|
+
console.log(` ${red("✗")} Runtime dependencies missing.`);
|
|
1514
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1515
|
+
console.log("");
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
const cliEntry = existsSync(join(CORE_DIR, "dist", "cli.js"))
|
|
1519
|
+
? join(CORE_DIR, "dist", "cli.js")
|
|
1520
|
+
: null;
|
|
1521
|
+
const mainEntry = existsSync(join(CORE_DIR, "dist", "main.js"))
|
|
1522
|
+
? join(CORE_DIR, "dist", "main.js")
|
|
1523
|
+
: null;
|
|
1524
|
+
if (!cliEntry && !mainEntry) {
|
|
1525
|
+
console.log(` ${red("✗")} Runtime entry point missing.`);
|
|
1526
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1527
|
+
console.log("");
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1182
1531
|
const { execFileSync: execF } = await import("child_process");
|
|
1183
1532
|
const passArgs = process.argv.slice(2);
|
|
1184
1533
|
|
|
@@ -1225,7 +1574,15 @@ async function delegateToCore() {
|
|
|
1225
1574
|
}
|
|
1226
1575
|
return;
|
|
1227
1576
|
} catch (err) {
|
|
1577
|
+
// Non-zero exit from session is normal (user quit, ctrl+c)
|
|
1228
1578
|
if (err.status) process.exit(err.status);
|
|
1579
|
+
// Module errors = broken install
|
|
1580
|
+
if (err.message && err.message.includes("MODULE_NOT_FOUND")) {
|
|
1581
|
+
console.log("");
|
|
1582
|
+
console.log(` ${red("✗")} Soma failed to start — missing dependencies.`);
|
|
1583
|
+
console.log(` ${dim("Run")} ${green("soma init")} ${dim("to repair the installation.")}`);
|
|
1584
|
+
console.log("");
|
|
1585
|
+
}
|
|
1229
1586
|
return;
|
|
1230
1587
|
}
|
|
1231
1588
|
}
|
|
@@ -1245,7 +1602,12 @@ const cmd = args[0];
|
|
|
1245
1602
|
if (cmd === "--version" || cmd === "-v" || cmd === "-V") {
|
|
1246
1603
|
showVersion();
|
|
1247
1604
|
} else if (cmd === "--help" || cmd === "-h") {
|
|
1248
|
-
|
|
1605
|
+
if (isInstalled()) {
|
|
1606
|
+
// Delegate to core — richer help with scripts, commands, hub
|
|
1607
|
+
await delegateToCore();
|
|
1608
|
+
} else {
|
|
1609
|
+
showHelp();
|
|
1610
|
+
}
|
|
1249
1611
|
} else if (cmd === "about") {
|
|
1250
1612
|
await showAbout();
|
|
1251
1613
|
} else if (cmd === "init") {
|
|
@@ -1260,15 +1622,15 @@ if (cmd === "--version" || cmd === "-v" || cmd === "-V") {
|
|
|
1260
1622
|
// Installed, project init (new project or --template/--orphan)
|
|
1261
1623
|
await delegateToCore();
|
|
1262
1624
|
} else {
|
|
1263
|
-
// Installed + .soma/ exists — check for updates
|
|
1625
|
+
// Installed + .soma/ exists — check for updates + project staleness
|
|
1264
1626
|
await checkAndUpdate();
|
|
1265
1627
|
}
|
|
1266
1628
|
} else if (cmd === "update") {
|
|
1267
1629
|
checkForUpdates();
|
|
1268
1630
|
} else if (cmd === "doctor") {
|
|
1269
|
-
await
|
|
1270
|
-
} else if (cmd === "status") {
|
|
1271
|
-
|
|
1631
|
+
await projectDoctor();
|
|
1632
|
+
} else if (cmd === "status" || cmd === "health") {
|
|
1633
|
+
await healthCheck();
|
|
1272
1634
|
} else if (isInstalled()) {
|
|
1273
1635
|
// Core installed — delegate to runtime
|
|
1274
1636
|
await delegateToCore();
|