mover-os 4.4.2 → 4.5.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/README.md +1 -1
- package/install.js +1067 -780
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -89,8 +89,28 @@ function waveGradient(text, frame, totalFrames) {
|
|
|
89
89
|
}).join("") + S.reset;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// ─── TUI: Alternate screen buffer ────────────────────────────────────────────
|
|
93
|
+
let _inAltScreen = false;
|
|
94
|
+
function enterAltScreen() {
|
|
95
|
+
if (!IS_TTY || _inAltScreen) return;
|
|
96
|
+
w("\x1b[?1049h"); // Enter alternate screen
|
|
97
|
+
w("\x1b[2J\x1b[H"); // Clear + cursor home
|
|
98
|
+
w(S.hide);
|
|
99
|
+
_inAltScreen = true;
|
|
100
|
+
}
|
|
101
|
+
function exitAltScreen() {
|
|
102
|
+
if (!IS_TTY || !_inAltScreen) return;
|
|
103
|
+
w("\x1b[?1049l"); // Restore original screen
|
|
104
|
+
w(S.show);
|
|
105
|
+
_inAltScreen = false;
|
|
106
|
+
}
|
|
107
|
+
function clearContent() {
|
|
108
|
+
w("\x1b[2J\x1b[H"); // Clear screen + cursor to top
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
// ─── Terminal cleanup ────────────────────────────────────────────────────────
|
|
93
112
|
function cleanup() {
|
|
113
|
+
exitAltScreen();
|
|
94
114
|
w(S.show);
|
|
95
115
|
try { if (process.stdin.isTTY && process.stdin.isRaw) process.stdin.setRawMode(false); } catch {}
|
|
96
116
|
}
|
|
@@ -182,7 +202,17 @@ async function printHeader(animate = IS_TTY) {
|
|
|
182
202
|
}
|
|
183
203
|
} catch {}
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
const pkgVer = require("./package.json").version;
|
|
206
|
+
ln(` ${dim(`v${pkgVer}`)} ${gray("the agentic operating system for obsidian")}${infoRight ? ` ${infoRight}` : ""}`);
|
|
207
|
+
|
|
208
|
+
// Non-blocking update check
|
|
209
|
+
try {
|
|
210
|
+
const latest = execSync("npm view mover-os version", { encoding: "utf8", timeout: 5000 }).trim();
|
|
211
|
+
if (latest && latest !== pkgVer && compareVersions(latest, pkgVer) > 0) {
|
|
212
|
+
ln(` ${yellow(`Update available: v${pkgVer} → v${latest}`)} ${dim(`run ${bold("moveros update")}`)}`);
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
|
|
186
216
|
ln();
|
|
187
217
|
ln(gray(" ─────────────────────────────────────────────"));
|
|
188
218
|
ln();
|
|
@@ -242,6 +272,127 @@ function statusLine(icon, label, detail = "") {
|
|
|
242
272
|
barLn(`${iconMap[icon] || icon} ${label}${detail ? ` ${dim(detail)}` : ""}`);
|
|
243
273
|
}
|
|
244
274
|
|
|
275
|
+
// ─── TUI: Compact header ────────────────────────────────────────────────────
|
|
276
|
+
function compactHeader() {
|
|
277
|
+
const pkgVer = require("./package.json").version;
|
|
278
|
+
let info = "";
|
|
279
|
+
try {
|
|
280
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
281
|
+
if (fs.existsSync(cfgPath)) {
|
|
282
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
283
|
+
const parts = [];
|
|
284
|
+
if (cfg.vaultPath) parts.push(path.basename(cfg.vaultPath));
|
|
285
|
+
if (cfg.agents?.length) parts.push(`${cfg.agents.length} agents`);
|
|
286
|
+
info = parts.length ? `${S.gray} · ${parts.join(" · ")}${S.reset}` : "";
|
|
287
|
+
}
|
|
288
|
+
} catch {}
|
|
289
|
+
return ` ${S.bold}moveros${S.reset} ${S.dim}v${pkgVer}${S.reset}${info}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ─── TUI: Screen transitions ────────────────────────────────────────────────
|
|
293
|
+
async function wipeDown(lineCount) {
|
|
294
|
+
if (!IS_TTY) return;
|
|
295
|
+
const rows = process.stdout.rows || 24;
|
|
296
|
+
const count = Math.min(lineCount || rows, rows);
|
|
297
|
+
w("\x1b[H");
|
|
298
|
+
for (let i = 0; i < count; i++) {
|
|
299
|
+
w(`\x1b[${i + 1};1H\x1b[2K`);
|
|
300
|
+
await sleep(8);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function fadeOut() {
|
|
305
|
+
if (!IS_TTY) return;
|
|
306
|
+
const rows = process.stdout.rows || 24;
|
|
307
|
+
for (let i = 1; i <= rows; i++) {
|
|
308
|
+
w(`\x1b[${i};1H\x1b[2K`);
|
|
309
|
+
if (i % 3 === 0) await sleep(6);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function slideIn(lines) {
|
|
314
|
+
if (!IS_TTY) { lines.forEach(l => ln(l)); return; }
|
|
315
|
+
for (const line of lines) {
|
|
316
|
+
ln(line);
|
|
317
|
+
await sleep(12);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function glowPulse(text, x, y) {
|
|
322
|
+
if (!IS_TTY) { ln(text); return; }
|
|
323
|
+
const frames = [255, 252, 255, 250, 248, 246];
|
|
324
|
+
for (const brightness of frames) {
|
|
325
|
+
if (y) w(`\x1b[${y};${x}H`);
|
|
326
|
+
w(`\x1b[2K${S.fg(brightness)}${strip(text)}${S.reset}`);
|
|
327
|
+
await sleep(40);
|
|
328
|
+
}
|
|
329
|
+
if (y) w(`\x1b[${y};${x}H`);
|
|
330
|
+
w(`\x1b[2K${text}\n`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function shimmerText(text, duration = 400) {
|
|
334
|
+
if (!IS_TTY) { ln(text); return; }
|
|
335
|
+
const totalFrames = Math.floor(duration / 20);
|
|
336
|
+
const raw = strip(text);
|
|
337
|
+
for (let f = 0; f <= totalFrames; f++) {
|
|
338
|
+
w(`\r\x1b[2K ${waveGradient(raw, f, totalFrames)}`);
|
|
339
|
+
await sleep(20);
|
|
340
|
+
}
|
|
341
|
+
w(`\r\x1b[2K ${text}\n`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── TUI: waitForEsc ────────────────────────────────────────────────────────
|
|
345
|
+
function waitForEsc() {
|
|
346
|
+
return new Promise((r) => {
|
|
347
|
+
if (!IS_TTY) { r(); return; }
|
|
348
|
+
const { stdin } = process;
|
|
349
|
+
stdin.setRawMode(true);
|
|
350
|
+
stdin.resume();
|
|
351
|
+
stdin.setEncoding("utf8");
|
|
352
|
+
const h = (data) => {
|
|
353
|
+
if (data === "\x1b" || data === "\r" || data === "\n" || data === "\x03") {
|
|
354
|
+
stdin.removeListener("data", h);
|
|
355
|
+
stdin.setRawMode(false);
|
|
356
|
+
stdin.pause();
|
|
357
|
+
if (data === "\x03") { cleanup(); ln(); process.exit(0); }
|
|
358
|
+
r();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
stdin.on("data", h);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ─── TUI: Animated progress for install/update ──────────────────────────────
|
|
366
|
+
async function installProgress(steps) {
|
|
367
|
+
const total = steps.length;
|
|
368
|
+
for (let i = 0; i < total; i++) {
|
|
369
|
+
const step = steps[i];
|
|
370
|
+
w(`\x1b[2K\r ${progressBar(i, total, 30)} ${S.dim}${step.label}${S.reset}`);
|
|
371
|
+
await step.fn();
|
|
372
|
+
w(`\x1b[2K\r ${progressBar(i + 1, total, 30)} ${S.green}\u2713${S.reset} ${step.label}\n`);
|
|
373
|
+
await sleep(60);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ─── TUI: Success animation ─────────────────────────────────────────────────
|
|
378
|
+
async function successAnimation(message) {
|
|
379
|
+
if (!IS_TTY) { ln(message); return; }
|
|
380
|
+
const check = [
|
|
381
|
+
" \u2713",
|
|
382
|
+
" \u2713",
|
|
383
|
+
" \u2713",
|
|
384
|
+
" \u2713 \u2713",
|
|
385
|
+
" \u2713",
|
|
386
|
+
];
|
|
387
|
+
ln();
|
|
388
|
+
for (const line of check) {
|
|
389
|
+
await shimmerText(green(line), 200);
|
|
390
|
+
}
|
|
391
|
+
ln();
|
|
392
|
+
await shimmerText(bold(message), 400);
|
|
393
|
+
ln();
|
|
394
|
+
}
|
|
395
|
+
|
|
245
396
|
// ─── Clack-style frame ──────────────────────────────────────────────────────
|
|
246
397
|
const BAR_COLOR = S.cyan;
|
|
247
398
|
const bar = () => w(`${BAR_COLOR}│${S.reset}`);
|
|
@@ -411,10 +562,12 @@ function interactiveSelect(items, { multi = false, preSelected = [], defaultInde
|
|
|
411
562
|
|
|
412
563
|
const icon = multi
|
|
413
564
|
? (checked ? green("◼") : dim("◻"))
|
|
414
|
-
: (active ?
|
|
415
|
-
const label = active
|
|
565
|
+
: (active ? `${S.bold}${S.fg(255)}›${S.reset}` : dim(" "));
|
|
566
|
+
const label = active
|
|
567
|
+
? `${S.bold}${S.fg(255)}${item.name}${S.reset}`
|
|
568
|
+
: `${S.fg(245)}${item.name}${S.reset}`;
|
|
416
569
|
const padded = strip(label).padEnd(20);
|
|
417
|
-
const styledPadded = active ? bold(padded
|
|
570
|
+
const styledPadded = active ? `${S.bold}${S.fg(255)}${padded}${S.reset}` : `${S.fg(245)}${padded}${S.reset}`;
|
|
418
571
|
const tag = item._detected ? dim("(detected)") : "";
|
|
419
572
|
|
|
420
573
|
w(`\x1b[2K${BAR_COLOR}│${S.reset} ${icon} ${styledPadded}${tag}\n`);
|
|
@@ -645,14 +798,69 @@ async function downloadPayload(key) {
|
|
|
645
798
|
return tmpDir;
|
|
646
799
|
}
|
|
647
800
|
|
|
801
|
+
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
802
|
+
function getSessionDate() {
|
|
803
|
+
const now = new Date();
|
|
804
|
+
if (now.getHours() < 5) return new Date(now.getTime() - 86400000);
|
|
805
|
+
return now;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function resolveDailyNotePath(engineDir, date) {
|
|
809
|
+
if (!date) date = getSessionDate();
|
|
810
|
+
const yyyy = date.getFullYear();
|
|
811
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
812
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
813
|
+
const ymd = `${yyyy}-${mm}-${dd}`;
|
|
814
|
+
// Primary: month subfolder (Mover OS convention)
|
|
815
|
+
const monthPath = path.join(engineDir, "Dailies", `${yyyy}-${mm}`, `Daily - ${ymd}.md`);
|
|
816
|
+
if (fs.existsSync(monthPath)) return monthPath;
|
|
817
|
+
// Fallback: flat Dailies folder (older vaults)
|
|
818
|
+
const flatPath = path.join(engineDir, "Dailies", `Daily - ${ymd}.md`);
|
|
819
|
+
if (fs.existsSync(flatPath)) return flatPath;
|
|
820
|
+
return monthPath; // default for creation
|
|
821
|
+
}
|
|
822
|
+
|
|
648
823
|
// ─── CLI ─────────────────────────────────────────────────────────────────────
|
|
824
|
+
// ─── Frecency Tracking ──────────────────────────────────────────────────────
|
|
825
|
+
function loadCliUsage() {
|
|
826
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
827
|
+
if (!fs.existsSync(cfgPath)) return {};
|
|
828
|
+
try {
|
|
829
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
830
|
+
return cfg.cli_usage || {};
|
|
831
|
+
} catch { return {}; }
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function recordCliUsage(cmd) {
|
|
835
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
836
|
+
try {
|
|
837
|
+
const cfg = fs.existsSync(cfgPath) ? JSON.parse(fs.readFileSync(cfgPath, "utf8")) : {};
|
|
838
|
+
if (!cfg.cli_usage) cfg.cli_usage = {};
|
|
839
|
+
const entry = cfg.cli_usage[cmd] || { count: 0, last: 0 };
|
|
840
|
+
entry.count++;
|
|
841
|
+
entry.last = Date.now();
|
|
842
|
+
cfg.cli_usage[cmd] = entry;
|
|
843
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
844
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
845
|
+
} catch {}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function frecencyScore(usage, cmd) {
|
|
849
|
+
const e = usage[cmd];
|
|
850
|
+
if (!e || !e.count) return 0;
|
|
851
|
+
const ageHours = (Date.now() - (e.last || 0)) / 3600000;
|
|
852
|
+
const recency = ageHours < 1 ? 4 : ageHours < 24 ? 3 : ageHours < 168 ? 2 : 1;
|
|
853
|
+
return e.count * recency;
|
|
854
|
+
}
|
|
855
|
+
|
|
649
856
|
// ─── CLI Commands ────────────────────────────────────────────────────────────
|
|
650
857
|
const CLI_COMMANDS = {
|
|
651
|
-
install:
|
|
652
|
-
update:
|
|
858
|
+
install: { desc: "Fresh install wizard", alias: [] },
|
|
859
|
+
update: { desc: "Comprehensive update — agents, rules, skills", alias: ["-u"] },
|
|
860
|
+
uninstall: { desc: "Remove Mover OS from vault and agents", alias: [] },
|
|
653
861
|
doctor: { desc: "Health check across all installed agents", alias: [] },
|
|
654
862
|
pulse: { desc: "Terminal dashboard — energy, tasks, streaks",alias: [] },
|
|
655
|
-
warm
|
|
863
|
+
// warm removed — hooks + rules + /morning already handle session priming
|
|
656
864
|
capture: { desc: "Quick capture — tasks, links, ideas", alias: [] },
|
|
657
865
|
who: { desc: "Entity memory lookup", alias: [] },
|
|
658
866
|
diff: { desc: "Engine file evolution viewer", alias: [] },
|
|
@@ -733,8 +941,8 @@ function detectObsidianVaults() {
|
|
|
733
941
|
}
|
|
734
942
|
|
|
735
943
|
function compareVersions(a, b) {
|
|
736
|
-
const pa = a.split(".").map(Number);
|
|
737
|
-
const pb = b.split(".").map(Number);
|
|
944
|
+
const pa = String(a).replace(/^[vV]/, "").split(".").map(Number);
|
|
945
|
+
const pb = String(b).replace(/^[vV]/, "").split(".").map(Number);
|
|
738
946
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
739
947
|
const va = pa[i] || 0, vb = pb[i] || 0;
|
|
740
948
|
if (va > vb) return 1;
|
|
@@ -1563,7 +1771,7 @@ Stuck: /debug-resistance
|
|
|
1563
1771
|
|
|
1564
1772
|
## CLI Utility
|
|
1565
1773
|
|
|
1566
|
-
\`moveros\` provides system-level terminal operations: pulse (dashboard),
|
|
1774
|
+
\`moveros\` provides system-level terminal operations: pulse (dashboard), sync (update agents), doctor (health check), who (entity lookup), capture (quick inbox). Use native agent capabilities first — the CLI supplements, never replaces.
|
|
1567
1775
|
`;
|
|
1568
1776
|
}
|
|
1569
1777
|
|
|
@@ -2705,7 +2913,7 @@ function preflight() {
|
|
|
2705
2913
|
// ─── CLI Command Handlers (stubs — implemented progressively) ────────────────
|
|
2706
2914
|
const CLI_HANDLERS = {
|
|
2707
2915
|
pulse: async (opts) => { await cmdPulse(opts); },
|
|
2708
|
-
warm
|
|
2916
|
+
// warm removed
|
|
2709
2917
|
capture: async (opts) => { await cmdCapture(opts); },
|
|
2710
2918
|
who: async (opts) => { await cmdWho(opts); },
|
|
2711
2919
|
diff: async (opts) => { await cmdDiff(opts); },
|
|
@@ -2715,10 +2923,11 @@ const CLI_HANDLERS = {
|
|
|
2715
2923
|
settings: async (opts) => { await cmdSettings(opts); },
|
|
2716
2924
|
backup: async (opts) => { await cmdBackup(opts); },
|
|
2717
2925
|
restore: async (opts) => { await cmdRestore(opts); },
|
|
2718
|
-
doctor:
|
|
2719
|
-
prayer:
|
|
2720
|
-
help:
|
|
2721
|
-
|
|
2926
|
+
doctor: async (opts) => { await cmdDoctor(opts); },
|
|
2927
|
+
prayer: async (opts) => { await cmdPrayer(opts); },
|
|
2928
|
+
help: async (opts) => { await cmdHelp(opts); },
|
|
2929
|
+
uninstall: async (opts) => { await runUninstall(resolveVaultPath(opts.vault)); },
|
|
2930
|
+
test: async (opts) => { await cmdTest(opts); },
|
|
2722
2931
|
};
|
|
2723
2932
|
|
|
2724
2933
|
// ─── moveros doctor ─────────────────────────────────────────────────────────
|
|
@@ -2849,10 +3058,7 @@ async function cmdPulse(opts) {
|
|
|
2849
3058
|
|
|
2850
3059
|
// Today's tasks from Daily Note
|
|
2851
3060
|
barLn();
|
|
2852
|
-
const
|
|
2853
|
-
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2854
|
-
const month = ymd.substring(0, 7);
|
|
2855
|
-
const dailyPath = path.join(engineDir, "Dailies", month, `Daily - ${ymd}.md`);
|
|
3061
|
+
const dailyPath = resolveDailyNotePath(engineDir);
|
|
2856
3062
|
if (fs.existsSync(dailyPath)) {
|
|
2857
3063
|
const daily = fs.readFileSync(dailyPath, "utf8");
|
|
2858
3064
|
const taskSection = daily.match(/##\s*Tasks[\s\S]*?(?=\n##|\n---)/i);
|
|
@@ -2907,91 +3113,25 @@ async function cmdPulse(opts) {
|
|
|
2907
3113
|
statusLine(streak >= 3 ? "ok" : "info", "Daily streak", `${streak} day${streak !== 1 ? "s" : ""}`);
|
|
2908
3114
|
}
|
|
2909
3115
|
|
|
2910
|
-
// Last session log time
|
|
2911
|
-
const
|
|
2912
|
-
if (fs.existsSync(
|
|
3116
|
+
// Last session log time — read from Active_Context.md Workflow State
|
|
3117
|
+
const acLogPath = path.join(engineDir, "Active_Context.md");
|
|
3118
|
+
if (fs.existsSync(acLogPath)) {
|
|
2913
3119
|
try {
|
|
2914
|
-
const
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3120
|
+
const acContent = fs.readFileSync(acLogPath, "utf8");
|
|
3121
|
+
const logMatch = acContent.match(/log_last_run:\s*(\S+)/);
|
|
3122
|
+
if (logMatch) {
|
|
3123
|
+
const agoMs = Date.now() - new Date(logMatch[1]).getTime();
|
|
3124
|
+
const agoMin = Math.round(agoMs / 60000);
|
|
3125
|
+
const agoH = Math.round(agoMs / 3600000);
|
|
3126
|
+
const agoStr = agoMin < 60 ? `${agoMin}m ago` : `${agoH}h ago`;
|
|
3127
|
+
statusLine(agoH < 8 ? "ok" : agoH < 24 ? "info" : "warn", "Last /log", agoStr);
|
|
2918
3128
|
}
|
|
2919
3129
|
} catch {}
|
|
2920
3130
|
}
|
|
2921
3131
|
barLn();
|
|
2922
3132
|
}
|
|
2923
3133
|
|
|
2924
|
-
//
|
|
2925
|
-
async function cmdWarm(opts) {
|
|
2926
|
-
const vault = resolveVaultPath(opts.vault);
|
|
2927
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
2928
|
-
const agent = opts.rest[0] || "claude";
|
|
2929
|
-
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
2930
|
-
const home = os.homedir();
|
|
2931
|
-
|
|
2932
|
-
// Build context primer
|
|
2933
|
-
const sections = [];
|
|
2934
|
-
sections.push("# Session Context Primer");
|
|
2935
|
-
sections.push(`Generated: ${new Date().toISOString()}\n`);
|
|
2936
|
-
|
|
2937
|
-
// Active Context snapshot
|
|
2938
|
-
const acPath = path.join(engineDir, "Active_Context.md");
|
|
2939
|
-
if (fs.existsSync(acPath)) {
|
|
2940
|
-
const ac = fs.readFileSync(acPath, "utf8");
|
|
2941
|
-
// Extract key sections (first 2000 chars)
|
|
2942
|
-
sections.push("## Active Context\n" + ac.substring(0, 2000));
|
|
2943
|
-
}
|
|
2944
|
-
|
|
2945
|
-
// Current project state
|
|
2946
|
-
const cwd = process.cwd();
|
|
2947
|
-
const planPath = path.join(cwd, "dev", "plan.md");
|
|
2948
|
-
if (fs.existsSync(planPath)) {
|
|
2949
|
-
const plan = fs.readFileSync(planPath, "utf8");
|
|
2950
|
-
// Find last active phase
|
|
2951
|
-
const phases = plan.match(/### Phase \d+[\s\S]*?(?=### Phase|\Z)/g);
|
|
2952
|
-
if (phases) {
|
|
2953
|
-
const active = phases[phases.length - 1].substring(0, 1500);
|
|
2954
|
-
sections.push("## Current Plan (last phase)\n" + active);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
|
-
// Today's daily note tasks
|
|
2959
|
-
const now = new Date();
|
|
2960
|
-
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
2961
|
-
const dailyPath = path.join(engineDir, "Dailies", ymd.substring(0, 7), `Daily - ${ymd}.md`);
|
|
2962
|
-
if (fs.existsSync(dailyPath)) {
|
|
2963
|
-
const daily = fs.readFileSync(dailyPath, "utf8");
|
|
2964
|
-
const taskSection = daily.match(/##\s*Tasks[\s\S]*?(?=\n##)/i);
|
|
2965
|
-
if (taskSection) sections.push("## Today's Tasks\n" + taskSection[0]);
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
const primer = sections.join("\n\n---\n\n") + "\n";
|
|
2969
|
-
|
|
2970
|
-
// Write to agent-specific location
|
|
2971
|
-
const targets = {
|
|
2972
|
-
claude: path.join(home, ".claude", "tmp", "session-primer.md"),
|
|
2973
|
-
cursor: path.join(vault, ".cursor", "rules", "session-primer.mdc"),
|
|
2974
|
-
gemini: path.join(home, ".gemini", "session-primer.md"),
|
|
2975
|
-
codex: path.join(home, ".codex", "skills", "session-primer", "SKILL.md"),
|
|
2976
|
-
windsurf: path.join(vault, ".windsurf", "rules", "session-primer.md"),
|
|
2977
|
-
cline: path.join(vault, ".clinerules", "session-primer.md"),
|
|
2978
|
-
};
|
|
2979
|
-
|
|
2980
|
-
const dest = targets[agent] || targets.claude;
|
|
2981
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
2982
|
-
|
|
2983
|
-
let content = primer;
|
|
2984
|
-
if (agent === "cursor") {
|
|
2985
|
-
content = `---\ndescription: "Session context primer — auto-generated by moveros warm"\nglobs:\nalwaysApply: true\n---\n\n${primer}`;
|
|
2986
|
-
} else if (agent === "codex") {
|
|
2987
|
-
content = `---\nname: session-primer\ndescription: "Auto-generated session context from moveros warm"\n---\n\n${primer}`;
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
fs.writeFileSync(dest, content, "utf8");
|
|
2991
|
-
statusLine("ok", "Warm", `${agent} primer written`);
|
|
2992
|
-
barLn(dim(` ${dest}`));
|
|
2993
|
-
barLn();
|
|
2994
|
-
}
|
|
3134
|
+
// cmdWarm removed — hooks + rules + /morning handle session priming
|
|
2995
3135
|
|
|
2996
3136
|
// ─── moveros capture ────────────────────────────────────────────────────────
|
|
2997
3137
|
async function cmdCapture(opts) {
|
|
@@ -3001,7 +3141,19 @@ async function cmdCapture(opts) {
|
|
|
3001
3141
|
const now = new Date();
|
|
3002
3142
|
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
3003
3143
|
const ts = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
3004
|
-
|
|
3144
|
+
|
|
3145
|
+
// Find existing capture file — prefer today's, fallback to most recent, create new if none
|
|
3146
|
+
const inboxDir = path.join(vault, "00_Inbox");
|
|
3147
|
+
fs.mkdirSync(inboxDir, { recursive: true });
|
|
3148
|
+
const existingCaptures = fs.readdirSync(inboxDir)
|
|
3149
|
+
.filter(f => f.toLowerCase().startsWith("capture") && f.endsWith(".md"))
|
|
3150
|
+
.sort();
|
|
3151
|
+
const todayCapture = existingCaptures.find(f => f.includes(ymd));
|
|
3152
|
+
const capturePath = todayCapture
|
|
3153
|
+
? path.join(inboxDir, todayCapture)
|
|
3154
|
+
: existingCaptures.length > 0
|
|
3155
|
+
? path.join(inboxDir, existingCaptures[existingCaptures.length - 1])
|
|
3156
|
+
: path.join(inboxDir, `Capture - ${ymd}.md`);
|
|
3005
3157
|
|
|
3006
3158
|
// Determine type from flags
|
|
3007
3159
|
let type = null, content = "";
|
|
@@ -3047,7 +3199,6 @@ async function cmdCapture(opts) {
|
|
|
3047
3199
|
else entry = `- ${content} *(${ts})*`;
|
|
3048
3200
|
|
|
3049
3201
|
// Append to capture file
|
|
3050
|
-
fs.mkdirSync(path.dirname(capturePath), { recursive: true });
|
|
3051
3202
|
if (!fs.existsSync(capturePath)) {
|
|
3052
3203
|
fs.writeFileSync(capturePath, `# Capture — ${ymd}\n\n${entry}\n`, "utf8");
|
|
3053
3204
|
} else {
|
|
@@ -3253,13 +3404,20 @@ async function cmdReplay(opts) {
|
|
|
3253
3404
|
const vault = resolveVaultPath(opts.vault);
|
|
3254
3405
|
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3255
3406
|
|
|
3407
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3256
3408
|
let dateStr = opts.rest.find((a) => a.startsWith("--date="))?.split("=")[1];
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3409
|
+
let targetDate;
|
|
3410
|
+
if (dateStr) {
|
|
3411
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
3412
|
+
targetDate = new Date(y, m - 1, d);
|
|
3413
|
+
} else {
|
|
3414
|
+
targetDate = getSessionDate();
|
|
3415
|
+
const y = targetDate.getFullYear();
|
|
3416
|
+
const m = String(targetDate.getMonth() + 1).padStart(2, "0");
|
|
3417
|
+
const d = String(targetDate.getDate()).padStart(2, "0");
|
|
3418
|
+
dateStr = `${y}-${m}-${d}`;
|
|
3260
3419
|
}
|
|
3261
|
-
const
|
|
3262
|
-
const dailyPath = path.join(vault, "02_Areas", "Engine", "Dailies", month, `Daily - ${dateStr}.md`);
|
|
3420
|
+
const dailyPath = resolveDailyNotePath(engineDir, targetDate);
|
|
3263
3421
|
|
|
3264
3422
|
barLn(bold(` Session Replay — ${dateStr}`));
|
|
3265
3423
|
barLn();
|
|
@@ -3636,6 +3794,19 @@ async function cmdPrayer(opts) {
|
|
|
3636
3794
|
}
|
|
3637
3795
|
|
|
3638
3796
|
// ─── moveros settings ───────────────────────────────────────────────────────
|
|
3797
|
+
const KNOWN_SETTINGS = {
|
|
3798
|
+
review_day: { type: "string", desc: "Weekly review day", defaults: "Sunday" },
|
|
3799
|
+
evening_zone_start: { type: "number", desc: "Evening starts at (hour)", defaults: 18 },
|
|
3800
|
+
midnight_cutoff: { type: "number", desc: "Before this hour = yesterday", defaults: 5 },
|
|
3801
|
+
deep_work_hours: { type: "number", desc: "Daily deep work target (hours)", defaults: 8 },
|
|
3802
|
+
sleep_target: { type: "number", desc: "Sleep target (hours)", defaults: 8 },
|
|
3803
|
+
track_food: { type: "boolean", desc: "Track food in daily notes", defaults: true },
|
|
3804
|
+
track_sleep: { type: "boolean", desc: "Track sleep in daily notes", defaults: true },
|
|
3805
|
+
show_prayer_times: { type: "boolean", desc: "Show prayer times in statusline", defaults: false },
|
|
3806
|
+
backup_retention: { type: "number", desc: "Max backups to keep", defaults: 5 },
|
|
3807
|
+
friction_level: { type: "number", desc: "Max friction level (1-4)", defaults: 3 },
|
|
3808
|
+
};
|
|
3809
|
+
|
|
3639
3810
|
async function cmdSettings(opts) {
|
|
3640
3811
|
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
3641
3812
|
|
|
@@ -3644,17 +3815,15 @@ async function cmdSettings(opts) {
|
|
|
3644
3815
|
return;
|
|
3645
3816
|
}
|
|
3646
3817
|
|
|
3647
|
-
|
|
3818
|
+
let cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
3648
3819
|
|
|
3649
|
-
// moveros settings set <key> <value>
|
|
3820
|
+
// moveros settings set <key> <value> — CLI mode
|
|
3650
3821
|
if (opts.rest[0] === "set" && opts.rest[1]) {
|
|
3651
3822
|
const key = opts.rest[1];
|
|
3652
3823
|
let val = opts.rest.slice(2).join(" ");
|
|
3653
|
-
// Auto-type conversion
|
|
3654
3824
|
if (val === "true") val = true;
|
|
3655
3825
|
else if (val === "false") val = false;
|
|
3656
3826
|
else if (/^\d+$/.test(val)) val = parseInt(val, 10);
|
|
3657
|
-
|
|
3658
3827
|
if (!cfg.settings) cfg.settings = {};
|
|
3659
3828
|
cfg.settings[key] = val;
|
|
3660
3829
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
@@ -3662,23 +3831,51 @@ async function cmdSettings(opts) {
|
|
|
3662
3831
|
return;
|
|
3663
3832
|
}
|
|
3664
3833
|
|
|
3665
|
-
//
|
|
3834
|
+
// Interactive mode — show settings as a selectable list
|
|
3666
3835
|
barLn(bold(" Settings"));
|
|
3667
3836
|
barLn();
|
|
3668
3837
|
barLn(` ${dim("Vault:")} ${cfg.vaultPath || dim("not set")}`);
|
|
3669
3838
|
barLn(` ${dim("Key:")} ${cfg.licenseKey ? cyan(cfg.licenseKey.substring(0, 12) + "...") : dim("not set")}`);
|
|
3670
3839
|
barLn(` ${dim("Agents:")} ${(cfg.agents || []).join(", ") || dim("none")}`);
|
|
3671
|
-
barLn(` ${dim("Version:")} ${cfg.version || dim("unknown")}`);
|
|
3672
3840
|
barLn();
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3841
|
+
|
|
3842
|
+
const settings = cfg.settings || {};
|
|
3843
|
+
const items = Object.entries(KNOWN_SETTINGS).map(([key, meta]) => {
|
|
3844
|
+
const current = settings[key] !== undefined ? settings[key] : meta.defaults;
|
|
3845
|
+
const display = typeof current === "boolean" ? (current ? green("on") : red("off")) : String(current);
|
|
3846
|
+
return {
|
|
3847
|
+
id: key,
|
|
3848
|
+
name: `${key.padEnd(22)} ${display.padEnd(8)} ${dim(meta.desc)}`,
|
|
3849
|
+
tier: `Current: ${JSON.stringify(current)} Default: ${JSON.stringify(meta.defaults)}`,
|
|
3850
|
+
};
|
|
3851
|
+
});
|
|
3852
|
+
|
|
3853
|
+
question(dim(" Select a setting to edit (esc to go back)"));
|
|
3854
|
+
barLn();
|
|
3855
|
+
const picked = await interactiveSelect(items);
|
|
3856
|
+
if (!picked) return;
|
|
3857
|
+
|
|
3858
|
+
const meta = KNOWN_SETTINGS[picked];
|
|
3859
|
+
const current = settings[picked] !== undefined ? settings[picked] : meta.defaults;
|
|
3860
|
+
|
|
3861
|
+
if (meta.type === "boolean") {
|
|
3862
|
+
// Toggle immediately
|
|
3863
|
+
const newVal = !current;
|
|
3864
|
+
if (!cfg.settings) cfg.settings = {};
|
|
3865
|
+
cfg.settings[picked] = newVal;
|
|
3866
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
3867
|
+
statusLine("ok", picked, newVal ? "on" : "off");
|
|
3868
|
+
} else {
|
|
3869
|
+
// Prompt for new value
|
|
3870
|
+
const answer = await textInput({ label: `${picked}`, initial: String(current) });
|
|
3871
|
+
if (answer === null || answer.trim() === "" || answer === String(current)) return;
|
|
3872
|
+
let val = answer.trim();
|
|
3873
|
+
if (meta.type === "number") val = parseInt(val, 10);
|
|
3874
|
+
if (!cfg.settings) cfg.settings = {};
|
|
3875
|
+
cfg.settings[picked] = val;
|
|
3876
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
3877
|
+
statusLine("ok", picked, JSON.stringify(val));
|
|
3679
3878
|
}
|
|
3680
|
-
barLn(dim(" Edit: moveros settings set <key> <value>"));
|
|
3681
|
-
barLn(dim(` File: ${cfgPath}`));
|
|
3682
3879
|
barLn();
|
|
3683
3880
|
}
|
|
3684
3881
|
|
|
@@ -3847,230 +4044,267 @@ async function cmdRestore(opts) {
|
|
|
3847
4044
|
|
|
3848
4045
|
// ─── moveros help ──────────────────────────────────────────────────────────
|
|
3849
4046
|
async function cmdHelp(opts) {
|
|
3850
|
-
//
|
|
4047
|
+
// Animated typing helper
|
|
4048
|
+
const typeOut = async (text, speed = 12) => {
|
|
4049
|
+
if (!IS_TTY) { ln(text); return; }
|
|
4050
|
+
const raw = strip(text);
|
|
4051
|
+
let rawIdx = 0, ansiIdx = 0;
|
|
4052
|
+
// Build mapping of raw char positions to the styled string slices
|
|
4053
|
+
while (rawIdx < raw.length) {
|
|
4054
|
+
w(text[ansiIdx] || "");
|
|
4055
|
+
if (text[ansiIdx] && text[ansiIdx] !== "\x1b" && !text.slice(Math.max(0, ansiIdx - 10), ansiIdx).match(/\x1b\[[^m]*$/)) {
|
|
4056
|
+
rawIdx++;
|
|
4057
|
+
}
|
|
4058
|
+
ansiIdx++;
|
|
4059
|
+
if (rawIdx % 2 === 0) await sleep(speed);
|
|
4060
|
+
}
|
|
4061
|
+
// flush remaining ANSI codes
|
|
4062
|
+
while (ansiIdx < text.length) { w(text[ansiIdx]); ansiIdx++; }
|
|
4063
|
+
w("\n");
|
|
4064
|
+
};
|
|
4065
|
+
|
|
4066
|
+
const typeLine = async (text, speed = 8) => {
|
|
4067
|
+
if (!IS_TTY) { ln(` ${text}`); return; }
|
|
4068
|
+
w(" ");
|
|
4069
|
+
for (let i = 0; i < text.length; i++) {
|
|
4070
|
+
w(text[i]);
|
|
4071
|
+
if (text[i] !== "\x1b" && i % 3 === 0) await sleep(speed);
|
|
4072
|
+
}
|
|
4073
|
+
w("\n");
|
|
4074
|
+
};
|
|
4075
|
+
|
|
3851
4076
|
const pages = [
|
|
3852
4077
|
{
|
|
3853
|
-
title: "
|
|
4078
|
+
title: "What is Mover OS?",
|
|
3854
4079
|
body: [
|
|
3855
|
-
`${bold("
|
|
4080
|
+
`${bold("Your second brain, but it actually works.")}`,
|
|
4081
|
+
"",
|
|
4082
|
+
"Most productivity systems fail because you have to maintain them.",
|
|
4083
|
+
"Mover OS is different — your AI agents maintain it for you.",
|
|
3856
4084
|
"",
|
|
3857
|
-
"
|
|
3858
|
-
"It
|
|
3859
|
-
"Copilot, Codex, and more. Same brain, every editor.",
|
|
4085
|
+
"You tell your AI who you are, what you're building, and where",
|
|
4086
|
+
"you're going. It remembers across every session, every editor.",
|
|
3860
4087
|
"",
|
|
3861
|
-
`${dim("
|
|
3862
|
-
` ${cyan("1.")}
|
|
3863
|
-
` ${cyan("2.")} ${bold("Workflows")} run your day
|
|
3864
|
-
` ${cyan("3.")} ${bold("Skills")}
|
|
3865
|
-
` ${cyan("4.")} The system ${bold("learns")} from your behavior and adapts`,
|
|
4088
|
+
`${dim("Three things make it work:")}`,
|
|
4089
|
+
` ${cyan("1.")} The ${bold("Engine")} — files that store your identity, strategy, goals`,
|
|
4090
|
+
` ${cyan("2.")} ${bold("Workflows")} — 23 commands that run your day (plan, build, log, repeat)`,
|
|
4091
|
+
` ${cyan("3.")} ${bold("Skills")} — 61 packs that make your AI genuinely useful`,
|
|
3866
4092
|
"",
|
|
3867
|
-
|
|
4093
|
+
`Works across 16 agents. Claude Code, Cursor, Gemini, all of them.`,
|
|
4094
|
+
`Same context, every editor. ${dim("Press → to continue.")}`,
|
|
3868
4095
|
],
|
|
3869
4096
|
},
|
|
3870
4097
|
{
|
|
3871
|
-
title: "The Engine
|
|
4098
|
+
title: "The Engine",
|
|
3872
4099
|
body: [
|
|
3873
|
-
`${dim("
|
|
4100
|
+
`${dim("Your identity, stored as markdown. Read by every AI session.")}`,
|
|
3874
4101
|
"",
|
|
3875
|
-
|
|
4102
|
+
` ${cyan("Identity_Prime.md")} Who you are — values, strengths, anti-patterns`,
|
|
4103
|
+
` ${cyan("Strategy.md")} What you're betting on right now`,
|
|
4104
|
+
` ${cyan("Active_Context.md")} What's happening today — focus, blockers, energy`,
|
|
4105
|
+
` ${cyan("Goals.md")} Where you're heading — 90 days to 10 years`,
|
|
4106
|
+
` ${cyan("Mover_Dossier.md")} What you've got — skills, audience, assets`,
|
|
4107
|
+
` ${cyan("Auto_Learnings.md")} What the AI has noticed about your behavior`,
|
|
3876
4108
|
"",
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
` ${cyan("Active_Context.md")} What's happening NOW — blockers, focus, state`,
|
|
3880
|
-
` ${cyan("Goals.md")} Where you're going — 90d, 1yr, 10yr targets`,
|
|
3881
|
-
` ${cyan("Mover_Dossier.md")} What you have — skills, capital, network`,
|
|
3882
|
-
` ${cyan("Auto_Learnings.md")} What the AI notices — behavioral patterns`,
|
|
4109
|
+
"These files are yours. The system never overwrites them.",
|
|
4110
|
+
"Every workflow reads them. They evolve as you do.",
|
|
3883
4111
|
"",
|
|
3884
|
-
|
|
3885
|
-
"Every AI session reads them. Every workflow updates them.",
|
|
3886
|
-
`Your Engine ${bold("evolves")} as you do.`,
|
|
4112
|
+
`${dim("Location:")} 02_Areas/Engine/`,
|
|
3887
4113
|
],
|
|
3888
4114
|
},
|
|
3889
4115
|
{
|
|
3890
|
-
title: "Daily
|
|
4116
|
+
title: "Your Daily Loop",
|
|
3891
4117
|
body: [
|
|
3892
|
-
`${
|
|
4118
|
+
`${dim("The rhythm that makes it stick:")}`,
|
|
3893
4119
|
"",
|
|
3894
|
-
` ${green("→")} ${bold("/morning")}
|
|
3895
|
-
` ${green("→")} ${bold("[WORK]")} Build, ship, create
|
|
3896
|
-
` ${green("→")} ${bold("/log")} Capture what happened
|
|
3897
|
-
` ${green("→")} ${bold("/analyse-day")}
|
|
3898
|
-
` ${green("→")} ${bold("/plan-tomorrow")}
|
|
4120
|
+
` ${green("→")} ${bold("/morning")} Check in. Set your one thing for the day.`,
|
|
4121
|
+
` ${green("→")} ${bold("[WORK]")} Build, ship, create.`,
|
|
4122
|
+
` ${green("→")} ${bold("/log")} Capture what happened. Plan syncs automatically.`,
|
|
4123
|
+
` ${green("→")} ${bold("/analyse-day")} Honest audit. What worked, what didn't.`,
|
|
4124
|
+
` ${green("→")} ${bold("/plan-tomorrow")} Set up tomorrow before you close the laptop.`,
|
|
3899
4125
|
"",
|
|
3900
|
-
|
|
3901
|
-
` ${green("→")} ${bold("/review-week")} Sunday deep review + strategy validation`,
|
|
4126
|
+
` ${dim("Sundays:")} ${bold("/review-week")} — zoom out, validate strategy, clean the system.`,
|
|
3902
4127
|
"",
|
|
3903
|
-
|
|
3904
|
-
"
|
|
3905
|
-
`Every workflow hands off to the next — ${bold("no dead ends")}.`,
|
|
4128
|
+
"Miss a day, the system notices. Miss three, /reboot kicks in.",
|
|
4129
|
+
"Each workflow hands off to the next. No dead ends.",
|
|
3906
4130
|
],
|
|
3907
4131
|
},
|
|
3908
4132
|
{
|
|
3909
|
-
title: "
|
|
4133
|
+
title: "23 Workflows",
|
|
3910
4134
|
body: [
|
|
3911
|
-
`${
|
|
3912
|
-
`${bold("Build:")} /ignite /overview /refactor-plan /capture /debrief`,
|
|
3913
|
-
`${bold("Think:")} /debug-resistance /pivot-strategy /mover-ideas /screenshot`,
|
|
3914
|
-
`${bold("Grow:")} /harvest /history /reboot`,
|
|
3915
|
-
`${bold("Meta:")} /setup /update /walkthrough /migrate /mover-check /mover-report`,
|
|
4135
|
+
`${dim("Slash commands inside your AI agent. Type / and go.")}`,
|
|
3916
4136
|
"",
|
|
3917
|
-
`${
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
` ${cyan("•")} ${bold("/screenshot")} does meta-analysis of your AI session patterns`,
|
|
4137
|
+
`${bold("Daily:")} /morning /log /analyse-day /plan-tomorrow /review-week`,
|
|
4138
|
+
`${bold("Projects:")} /ignite /overview /refactor-plan /capture /debrief`,
|
|
4139
|
+
`${bold("Strategy:")} /debug-resistance /pivot-strategy /mover-ideas /screenshot`,
|
|
4140
|
+
`${bold("Growth:")} /harvest /history /reboot`,
|
|
4141
|
+
`${bold("System:")} /setup /update /walkthrough /migrate /mover-check`,
|
|
3923
4142
|
"",
|
|
3924
|
-
"
|
|
4143
|
+
`${dim("Highlights:")}`,
|
|
4144
|
+
` ${bold("/ignite")} Starts any project — interrogation, brief, plan`,
|
|
4145
|
+
` ${bold("/debug-resistance")} Figures out WHY you're avoiding something`,
|
|
4146
|
+
` ${bold("/harvest")} Turns conversations into permanent knowledge`,
|
|
4147
|
+
` ${bold("/screenshot")} Meta-analysis of how you actually use AI`,
|
|
3925
4148
|
],
|
|
3926
4149
|
},
|
|
3927
4150
|
{
|
|
3928
|
-
title: "
|
|
4151
|
+
title: "61 Skill Packs",
|
|
3929
4152
|
body: [
|
|
3930
|
-
`${
|
|
4153
|
+
`${dim("Specialized knowledge your AI loads automatically.")}`,
|
|
3931
4154
|
"",
|
|
3932
|
-
|
|
3933
|
-
` ${cyan("
|
|
3934
|
-
` ${cyan("
|
|
3935
|
-
` ${cyan("
|
|
3936
|
-
` ${cyan("
|
|
3937
|
-
` ${cyan("seo")} Audits, schema markup, programmatic SEO, content`,
|
|
3938
|
-
` ${cyan("design")} UI/UX, frontend design, Obsidian markdown/canvas`,
|
|
3939
|
-
` ${cyan("obsidian")} JSON Canvas, Bases, Obsidian CLI, markdown`,
|
|
4155
|
+
` ${cyan("dev")} TDD, debugging, refactoring, error handling, React`,
|
|
4156
|
+
` ${cyan("marketing")} Copywriting, SEO, CRO, social media, email sequences`,
|
|
4157
|
+
` ${cyan("strategy")} Pricing, launch strategy, competitor analysis`,
|
|
4158
|
+
` ${cyan("design")} UI/UX, frontend patterns, accessibility`,
|
|
4159
|
+
` ${cyan("obsidian")} Canvas, Bases, markdown, CLI automation`,
|
|
3940
4160
|
"",
|
|
3941
|
-
`${dim("System skills (always
|
|
3942
|
-
` friction-enforcer
|
|
3943
|
-
`
|
|
4161
|
+
`${dim("System skills (always watching):")}`,
|
|
4162
|
+
` ${bold("friction-enforcer")} Pushes back when you drift from your plan`,
|
|
4163
|
+
` ${bold("pattern-detector")} Spots recurring behavior across sessions`,
|
|
4164
|
+
` ${bold("plan-md-guardian")} Protects your roadmap from corruption`,
|
|
4165
|
+
` ${bold("workflow-router")} Suggests the right workflow for the moment`,
|
|
3944
4166
|
"",
|
|
3945
|
-
"
|
|
4167
|
+
"You choose categories during install. Skills activate on context.",
|
|
3946
4168
|
],
|
|
3947
4169
|
},
|
|
3948
4170
|
{
|
|
3949
4171
|
title: "The Friction System",
|
|
3950
4172
|
body: [
|
|
3951
|
-
`${
|
|
4173
|
+
`${dim("Your AI has opinions. That's the point.")}`,
|
|
3952
4174
|
"",
|
|
3953
|
-
` ${dim("Level 1")} ${cyan("Surface")}
|
|
3954
|
-
` ${dim("Level 2")} ${yellow("Justify")}
|
|
3955
|
-
` ${dim("Level 3")} ${red("Earn It")}
|
|
3956
|
-
` ${dim("Level 4")} ${red("Hard Block")}
|
|
4175
|
+
` ${dim("Level 1")} ${cyan("Surface")} "Your plan says X. You're doing Y. Intentional?"`,
|
|
4176
|
+
` ${dim("Level 2")} ${yellow("Justify")} "Why is this more important than your Single Test?"`,
|
|
4177
|
+
` ${dim("Level 3")} ${red("Earn It")} Stops helping with off-plan work entirely.`,
|
|
4178
|
+
` ${dim("Level 4")} ${red("Hard Block")} Won't delete Engine files without a reason.`,
|
|
3957
4179
|
"",
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
"when you're avoiding the hard thing.",
|
|
4180
|
+
"You can always push through. It's awareness, not a wall.",
|
|
4181
|
+
"But if you're avoiding the hard thing, the AI won't pretend.",
|
|
3961
4182
|
"",
|
|
3962
|
-
`${dim("
|
|
3963
|
-
`
|
|
4183
|
+
`${dim("Smart gate:")} If the work is genuinely useful (compound value,`,
|
|
4184
|
+
`exploration), it logs ${dim("[COMPOUND]")} and doesn't escalate.`,
|
|
3964
4185
|
],
|
|
3965
4186
|
},
|
|
3966
4187
|
{
|
|
3967
|
-
title: "
|
|
4188
|
+
title: "What is this CLI?",
|
|
3968
4189
|
body: [
|
|
3969
|
-
`${bold("
|
|
4190
|
+
`${bold("Terminal utilities that don't need an AI session.")}`,
|
|
3970
4191
|
"",
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
` ${cyan("3.")} Patterns surface proactively in workflows`,
|
|
3975
|
-
` ${cyan("4.")} You confirm or dismiss — the system adapts`,
|
|
4192
|
+
"Your AI agents handle the complex stuff — planning, analysis,",
|
|
4193
|
+
"writing. This CLI handles everything else: quick lookups, status",
|
|
4194
|
+
"checks, maintenance, things you want instantly.",
|
|
3976
4195
|
"",
|
|
3977
|
-
`${dim("
|
|
3978
|
-
` ${
|
|
3979
|
-
` ${
|
|
3980
|
-
` ${
|
|
3981
|
-
` ${yellow("Scope Creep")} Tasks growing beyond plan boundaries`,
|
|
3982
|
-
` ${yellow("Energy Cycles")} Performance tied to sleep/food/time`,
|
|
4196
|
+
`${dim("How it fits together:")}`,
|
|
4197
|
+
` ${cyan("Workflows")} Run inside AI agents (/morning, /log, /ignite)`,
|
|
4198
|
+
` ${cyan("Skills")} Loaded by AI agents automatically`,
|
|
4199
|
+
` ${cyan("CLI")} Runs in your terminal, no AI needed`,
|
|
3983
4200
|
"",
|
|
3984
|
-
|
|
4201
|
+
"The CLI supplements your agents. It never replaces them.",
|
|
4202
|
+
`Think of it as ${bold("htop for your productivity")} — instant, free, always there.`,
|
|
3985
4203
|
],
|
|
3986
4204
|
},
|
|
3987
4205
|
{
|
|
3988
|
-
title: "
|
|
4206
|
+
title: "CLI — Status & Insight",
|
|
3989
4207
|
body: [
|
|
3990
|
-
`${
|
|
4208
|
+
`${dim("See what's happening without opening an AI session.")}`,
|
|
3991
4209
|
"",
|
|
3992
|
-
|
|
3993
|
-
`
|
|
3994
|
-
` Copilot Amazon Q OpenCode Kilo Code`,
|
|
4210
|
+
` ${cyan("pulse")} Dashboard — tasks, streaks, energy, blockers`,
|
|
4211
|
+
` ${dim("Like htop for your day. One glance.")}`,
|
|
3995
4212
|
"",
|
|
3996
|
-
|
|
3997
|
-
`
|
|
4213
|
+
` ${cyan("replay")} Session replay — what you did, when, drift analysis`,
|
|
4214
|
+
` ${dim("\"You planned 4 tasks, completed 2, drifted into 3.\"")}`,
|
|
3998
4215
|
"",
|
|
3999
|
-
|
|
4000
|
-
`
|
|
4216
|
+
` ${cyan("diff")} Engine evolution — how your strategy changed over time`,
|
|
4217
|
+
` ${dim("Git-powered. Shows exactly when and why.")}`,
|
|
4218
|
+
"",
|
|
4219
|
+
` ${cyan("context")} What each agent sees — loaded rules, skills, token count`,
|
|
4220
|
+
` ${dim("Debug why an agent is behaving differently.")}`,
|
|
4221
|
+
"",
|
|
4222
|
+
` ${cyan("doctor")} Health check — are all agents configured correctly?`,
|
|
4223
|
+
],
|
|
4224
|
+
},
|
|
4225
|
+
{
|
|
4226
|
+
title: "CLI — Quick Actions",
|
|
4227
|
+
body: [
|
|
4228
|
+
`${dim("Do things fast without starting a session.")}`,
|
|
4001
4229
|
"",
|
|
4002
|
-
"
|
|
4003
|
-
`
|
|
4004
|
-
|
|
4230
|
+
` ${cyan("capture")} Quick capture — task, idea, link, brain dump`,
|
|
4231
|
+
` ${dim("moveros capture --task \"Fix the login bug\"")}`,
|
|
4232
|
+
"",
|
|
4233
|
+
` ${cyan("who")} Entity lookup — search People, Orgs, Places`,
|
|
4234
|
+
` ${dim("moveros who \"Ishaaq\" → everything you know about them")}`,
|
|
4235
|
+
"",
|
|
4236
|
+
` ${cyan("prayer")} Mosque timetable — next prayer in your status line`,
|
|
4237
|
+
` ${dim("Paste or fetch. Shows countdown. Optional.")}`,
|
|
4005
4238
|
],
|
|
4006
4239
|
},
|
|
4007
4240
|
{
|
|
4008
|
-
title: "CLI
|
|
4241
|
+
title: "CLI — Maintenance",
|
|
4009
4242
|
body: [
|
|
4010
|
-
`${
|
|
4243
|
+
`${dim("Keep everything in sync without thinking about it.")}`,
|
|
4244
|
+
"",
|
|
4245
|
+
` ${cyan("sync")} Update all agents to latest rules and skills`,
|
|
4246
|
+
` ${dim("One command updates 16 agents. Shows what changed.")}`,
|
|
4247
|
+
"",
|
|
4248
|
+
` ${cyan("backup")} Backup Engine, Areas, or agent configs`,
|
|
4249
|
+
` ${dim("With manifest. Knows what's in each backup.")}`,
|
|
4011
4250
|
"",
|
|
4012
|
-
` ${cyan("
|
|
4013
|
-
`
|
|
4014
|
-
|
|
4015
|
-
` ${cyan("
|
|
4016
|
-
`
|
|
4017
|
-
|
|
4018
|
-
` ${cyan("
|
|
4019
|
-
`
|
|
4020
|
-
` ${cyan("moveros settings")} View/edit config — ${dim("settings set <key> <val>")}`,
|
|
4021
|
-
` ${cyan("moveros backup")} Manual backup wizard (engine, areas, agents)`,
|
|
4022
|
-
` ${cyan("moveros restore")} Restore from backup`,
|
|
4023
|
-
` ${cyan("moveros warm")} Pre-warm an AI session with context`,
|
|
4251
|
+
` ${cyan("restore")} Restore from any backup — selective, safe`,
|
|
4252
|
+
` ${dim("Creates a safety backup before restoring.")}`,
|
|
4253
|
+
"",
|
|
4254
|
+
` ${cyan("settings")} View/edit config from terminal`,
|
|
4255
|
+
` ${dim("moveros settings set review_day sunday")}`,
|
|
4256
|
+
"",
|
|
4257
|
+
` ${cyan("update")} Update CLI + workflows + skills in one go`,
|
|
4258
|
+
` ${dim("Self-updates the CLI, then pulls latest payload.")}`,
|
|
4024
4259
|
],
|
|
4025
4260
|
},
|
|
4026
4261
|
{
|
|
4027
|
-
title: "
|
|
4262
|
+
title: "16 Agents, One Brain",
|
|
4028
4263
|
body: [
|
|
4029
|
-
`${
|
|
4264
|
+
`${dim("Install once. Every agent gets the same context.")}`,
|
|
4265
|
+
"",
|
|
4266
|
+
`${dim("Full tier")} ${dim("(rules + skills + commands + hooks):")}`,
|
|
4267
|
+
` Claude Code Cursor Cline Windsurf Gemini CLI`,
|
|
4268
|
+
` Copilot Amazon Q OpenCode Kilo Code`,
|
|
4030
4269
|
"",
|
|
4031
|
-
|
|
4032
|
-
`
|
|
4033
|
-
` ${dim("Line 3:")} Rate limits (5hr + 7day + extra usage)`,
|
|
4270
|
+
`${dim("Enhanced tier")} ${dim("(rules + skills):")}`,
|
|
4271
|
+
` Codex Amp Roo Code Antigravity`,
|
|
4034
4272
|
"",
|
|
4035
|
-
`${dim("
|
|
4036
|
-
`
|
|
4037
|
-
` ${cyan("•")} Strategic task count (vitality excluded)`,
|
|
4038
|
-
` ${cyan("•")} Vitality slot machine — rotates reminders every minute`,
|
|
4039
|
-
` ${cyan("•")} Next prayer time (if ${dim("show_prayer_times: true")} in settings)`,
|
|
4040
|
-
` ${cyan("•")} Time since last /log — so you never forget`,
|
|
4041
|
-
` ${cyan("•")} Rate limit bars with reset times`,
|
|
4273
|
+
`${dim("Basic tier")} ${dim("(rules only):")}`,
|
|
4274
|
+
` Continue Aider`,
|
|
4042
4275
|
"",
|
|
4043
|
-
|
|
4276
|
+
"Switch editors whenever. Your Engine follows you.",
|
|
4277
|
+
`${cyan("moveros sync")} keeps them all current.`,
|
|
4044
4278
|
],
|
|
4045
4279
|
},
|
|
4046
4280
|
{
|
|
4047
|
-
title: "
|
|
4281
|
+
title: "Get Started",
|
|
4048
4282
|
body: [
|
|
4049
|
-
`${bold("
|
|
4283
|
+
`${bold("Five minutes to a system that remembers everything.")}`,
|
|
4050
4284
|
"",
|
|
4051
|
-
` ${cyan("1.")} Run ${bold("/setup")} in
|
|
4285
|
+
` ${cyan("1.")} Run ${bold("/setup")} in your AI agent — it'll interview you`,
|
|
4052
4286
|
` ${cyan("2.")} Run ${bold("/morning")} to start your first session`,
|
|
4053
4287
|
` ${cyan("3.")} Work. Build. Ship.`,
|
|
4054
|
-
` ${cyan("4.")} Run ${bold("/log")}
|
|
4288
|
+
` ${cyan("4.")} Run ${bold("/log")} when you're done — captures everything`,
|
|
4055
4289
|
` ${cyan("5.")} Run ${bold("/plan-tomorrow")} before bed`,
|
|
4056
4290
|
"",
|
|
4057
|
-
`${dim("
|
|
4058
|
-
` ${
|
|
4059
|
-
` ${
|
|
4060
|
-
|
|
4061
|
-
|
|
4291
|
+
`${dim("Two things to know:")}`,
|
|
4292
|
+
` ${bold("Single Test")} — the one thing that makes today a win`,
|
|
4293
|
+
` ${bold("Sacrifice")} — what you won't do (just as important)`,
|
|
4294
|
+
"",
|
|
4295
|
+
"The system learns from you. The more you use it, the sharper it gets.",
|
|
4062
4296
|
"",
|
|
4063
|
-
`${dim("moveros.dev")}
|
|
4297
|
+
`${dim("moveros.dev")}`,
|
|
4064
4298
|
],
|
|
4065
4299
|
},
|
|
4066
4300
|
];
|
|
4067
4301
|
|
|
4068
4302
|
// Paginated display with keyboard navigation
|
|
4069
4303
|
let page = 0;
|
|
4304
|
+
const seen = new Set();
|
|
4070
4305
|
|
|
4071
|
-
function renderPage() {
|
|
4306
|
+
async function renderPage(animate) {
|
|
4072
4307
|
const p = pages[page];
|
|
4073
|
-
// Clear screen area
|
|
4074
4308
|
w("\x1b[2J\x1b[H"); // clear screen, cursor to top
|
|
4075
4309
|
ln();
|
|
4076
4310
|
|
|
@@ -4086,9 +4320,14 @@ async function cmdHelp(opts) {
|
|
|
4086
4320
|
ln(` ${S.cyan}└${"─".repeat(56)}┘${S.reset}`);
|
|
4087
4321
|
ln();
|
|
4088
4322
|
|
|
4089
|
-
// Body
|
|
4090
|
-
|
|
4091
|
-
|
|
4323
|
+
// Body — animate on first visit, instant on revisit
|
|
4324
|
+
if (animate && !seen.has(page)) {
|
|
4325
|
+
for (const line of p.body) {
|
|
4326
|
+
await typeLine(line, 6);
|
|
4327
|
+
}
|
|
4328
|
+
seen.add(page);
|
|
4329
|
+
} else {
|
|
4330
|
+
for (const line of p.body) ln(` ${line}`);
|
|
4092
4331
|
}
|
|
4093
4332
|
ln();
|
|
4094
4333
|
ln();
|
|
@@ -4103,7 +4342,6 @@ async function cmdHelp(opts) {
|
|
|
4103
4342
|
|
|
4104
4343
|
return new Promise((resolve) => {
|
|
4105
4344
|
if (!IS_TTY) {
|
|
4106
|
-
// Non-interactive: dump all pages
|
|
4107
4345
|
for (const p of pages) {
|
|
4108
4346
|
ln(bold(`\n## ${p.title}\n`));
|
|
4109
4347
|
for (const line of p.body) ln(` ${line}`);
|
|
@@ -4118,22 +4356,32 @@ async function cmdHelp(opts) {
|
|
|
4118
4356
|
stdin.setEncoding("utf8");
|
|
4119
4357
|
w(S.hide);
|
|
4120
4358
|
|
|
4121
|
-
|
|
4359
|
+
let navigating = false;
|
|
4360
|
+
const go = async (newPage) => {
|
|
4361
|
+
if (navigating) return;
|
|
4362
|
+
navigating = true;
|
|
4363
|
+
page = newPage;
|
|
4364
|
+
await renderPage(true);
|
|
4365
|
+
navigating = false;
|
|
4366
|
+
};
|
|
4367
|
+
|
|
4368
|
+
go(0); // initial render with animation
|
|
4122
4369
|
|
|
4123
4370
|
const handler = (data) => {
|
|
4371
|
+
if (navigating) return; // ignore input during animation
|
|
4124
4372
|
if (data === "\x1b[C" || data === "l" || data === " ") {
|
|
4125
|
-
if (page < pages.length - 1)
|
|
4373
|
+
if (page < pages.length - 1) go(page + 1);
|
|
4126
4374
|
} else if (data === "\x1b[D" || data === "h") {
|
|
4127
|
-
if (page > 0)
|
|
4375
|
+
if (page > 0) go(page - 1);
|
|
4128
4376
|
} else if (data === "q" || data === "\x1b" || data === "\x03") {
|
|
4129
4377
|
stdin.removeListener("data", handler);
|
|
4130
4378
|
stdin.setRawMode(false);
|
|
4131
4379
|
stdin.pause();
|
|
4132
4380
|
w(S.show);
|
|
4133
|
-
w("\x1b[2J\x1b[H");
|
|
4381
|
+
w("\x1b[2J\x1b[H");
|
|
4134
4382
|
resolve();
|
|
4135
4383
|
} else if (data === "\r" || data === "\n") {
|
|
4136
|
-
if (page < pages.length - 1)
|
|
4384
|
+
if (page < pages.length - 1) go(page + 1);
|
|
4137
4385
|
else {
|
|
4138
4386
|
stdin.removeListener("data", handler);
|
|
4139
4387
|
stdin.setRawMode(false);
|
|
@@ -4152,120 +4400,135 @@ async function cmdHelp(opts) {
|
|
|
4152
4400
|
// ─── moveros test (dev only) ────────────────────────────────────────────────
|
|
4153
4401
|
async function cmdTest(opts) { barLn(yellow("moveros test — not yet implemented.")); }
|
|
4154
4402
|
|
|
4155
|
-
// ───
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4403
|
+
// ─── What's New ──────────────────────────────────────────────────────────────
|
|
4404
|
+
const WHATS_NEW = {
|
|
4405
|
+
"4.2.0": {
|
|
4406
|
+
headline: "Engine Intelligence + Pattern Detection",
|
|
4407
|
+
features: [
|
|
4408
|
+
"Tiered workflow reads — Active_Context as cache, deep reads only when needed",
|
|
4409
|
+
"Active Patterns section in Active_Context — AI-detected behavioral patterns",
|
|
4410
|
+
"Identity Snapshot cached in Active_Context — no full file reads for light workflows",
|
|
4411
|
+
"Monthly review overdue detection in /review-week",
|
|
4412
|
+
"Critical Path Check in /analyse-day (DIRECT/PREREQUISITE/DRIFT)",
|
|
4413
|
+
],
|
|
4414
|
+
},
|
|
4415
|
+
"4.3.0": {
|
|
4416
|
+
headline: "Soul + Friction + Skills",
|
|
4417
|
+
features: [
|
|
4418
|
+
"Soul personality — sharp observations, humor, callbacks, earned respect",
|
|
4419
|
+
"Level 3 friction (Earn It) — AI holds the line on avoidance",
|
|
4420
|
+
"Pre-Escalation Gate — compound work detection, no false positives",
|
|
4421
|
+
"Curated skill packs — marketing, CRO, SEO, design, obsidian",
|
|
4422
|
+
"Skill evaluation system — A/B testing skills with benchmarks",
|
|
4423
|
+
],
|
|
4424
|
+
},
|
|
4425
|
+
"4.5.0": {
|
|
4426
|
+
headline: "Smart Menu + TUI + Comprehensive Update",
|
|
4427
|
+
features: [
|
|
4428
|
+
"Frecency-based menu — surfaces your most-used commands",
|
|
4429
|
+
"Interactive settings — toggle from the menu, no CLI flags",
|
|
4430
|
+
"Full-screen TUI — alternate screen buffer, clean transitions",
|
|
4431
|
+
"Comprehensive update — backup, agent management, What's New",
|
|
4432
|
+
"Install/Update separation — install is fresh only, update is interactive",
|
|
4433
|
+
"Uninstall as standalone command",
|
|
4434
|
+
],
|
|
4435
|
+
},
|
|
4436
|
+
};
|
|
4164
4437
|
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4438
|
+
function showWhatsNew(fromVer, toVer) {
|
|
4439
|
+
if (!fromVer || !toVer) return;
|
|
4440
|
+
const versions = Object.keys(WHATS_NEW)
|
|
4441
|
+
.filter(v => compareVersions(v, fromVer) > 0 && compareVersions(v, toVer) <= 0)
|
|
4442
|
+
.sort(compareVersions);
|
|
4443
|
+
if (versions.length === 0) return;
|
|
4444
|
+
|
|
4445
|
+
for (const v of versions) {
|
|
4446
|
+
const wn = WHATS_NEW[v];
|
|
4447
|
+
question(bold(`What's New in v${v}`));
|
|
4448
|
+
barLn(dim(wn.headline));
|
|
4449
|
+
barLn();
|
|
4450
|
+
for (const f of wn.features) {
|
|
4451
|
+
barLn(` ${green("+")} ${f}`);
|
|
4176
4452
|
}
|
|
4453
|
+
barLn();
|
|
4177
4454
|
}
|
|
4178
|
-
|
|
4179
|
-
question(`${bold("moveros")} ${dim("— choose a command")}`);
|
|
4180
|
-
barLn();
|
|
4181
|
-
const choice = await interactiveSelect(menuItems);
|
|
4182
|
-
if (!choice) {
|
|
4183
|
-
outro(dim("Cancelled."));
|
|
4184
|
-
process.exit(0);
|
|
4185
|
-
}
|
|
4186
|
-
return choice;
|
|
4187
4455
|
}
|
|
4188
4456
|
|
|
4189
|
-
// ───
|
|
4190
|
-
function
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
if (v.startsWith("~")) v = path.join(os.homedir(), v.slice(1));
|
|
4194
|
-
return path.resolve(v);
|
|
4195
|
-
}
|
|
4196
|
-
// Try config.json
|
|
4457
|
+
// ─── Interactive Main Menu (Frecency + Context-Aware) ─────────────────────────
|
|
4458
|
+
async function cmdMainMenu() {
|
|
4459
|
+
const usage = loadCliUsage();
|
|
4460
|
+
const hasVault = !!resolveVaultPath();
|
|
4197
4461
|
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
const v = JSON.parse(fs.readFileSync(cfgPath, "utf8")).vaultPath;
|
|
4201
|
-
if (v && fs.existsSync(v)) return v;
|
|
4202
|
-
} catch {}
|
|
4203
|
-
}
|
|
4204
|
-
// Try Obsidian detection
|
|
4205
|
-
const obsVaults = detectObsidianVaults();
|
|
4206
|
-
return obsVaults.find((p) => fs.existsSync(path.join(p, ".mover-version"))) || null;
|
|
4207
|
-
}
|
|
4208
|
-
|
|
4209
|
-
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
4210
|
-
async function main() {
|
|
4211
|
-
const opts = parseArgs();
|
|
4212
|
-
let bundleDir = path.resolve(__dirname);
|
|
4213
|
-
const startTime = Date.now();
|
|
4214
|
-
|
|
4215
|
-
// ── Intro ──
|
|
4216
|
-
await printHeader();
|
|
4217
|
-
|
|
4218
|
-
// ── Route: no command → interactive menu (persistent loop) ──
|
|
4219
|
-
const lightCommands = ["pulse", "warm", "capture", "who", "diff", "sync", "replay", "context", "settings", "backup", "restore", "doctor", "prayer", "help", "test"];
|
|
4462
|
+
const hasConfig = fs.existsSync(cfgPath);
|
|
4463
|
+
const hasPrayer = hasConfig && (() => { try { return !!JSON.parse(fs.readFileSync(cfgPath, "utf8")).settings?.show_prayer_times; } catch { return false; } })();
|
|
4220
4464
|
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
while (true) {
|
|
4224
|
-
opts.command = await cmdMainMenu();
|
|
4225
|
-
if (!opts.command) break; // user cancelled
|
|
4465
|
+
// All non-hidden commands
|
|
4466
|
+
const allCmds = Object.keys(CLI_COMMANDS).filter(c => !CLI_COMMANDS[c].hidden);
|
|
4226
4467
|
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
await new Promise((r) => { process.stdin.resume(); process.stdin.once("data", () => { process.stdin.pause(); r(); }); });
|
|
4234
|
-
continue;
|
|
4235
|
-
}
|
|
4236
|
-
break; // install/update break out of loop into pre-flight
|
|
4237
|
-
}
|
|
4238
|
-
if (!opts.command) return;
|
|
4239
|
-
}
|
|
4468
|
+
// Context filter: hide irrelevant commands
|
|
4469
|
+
const contextFilter = (cmd) => {
|
|
4470
|
+
if (!hasVault && ["pulse", "replay", "diff", "sync", "capture", "who", "context", "backup", "restore", "doctor"].includes(cmd)) return false;
|
|
4471
|
+
if (!hasPrayer && cmd === "prayer") return false;
|
|
4472
|
+
return true;
|
|
4473
|
+
};
|
|
4240
4474
|
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4475
|
+
const eligible = allCmds.filter(contextFilter);
|
|
4476
|
+
|
|
4477
|
+
// Pinned: always visible
|
|
4478
|
+
const pinned = ["settings", "help"];
|
|
4479
|
+
// Also pin install if no vault, update if vault exists
|
|
4480
|
+
if (!hasVault) pinned.unshift("install");
|
|
4481
|
+
else pinned.unshift("update");
|
|
4482
|
+
|
|
4483
|
+
// Score remaining by frecency
|
|
4484
|
+
const scored = eligible
|
|
4485
|
+
.filter(c => !pinned.includes(c))
|
|
4486
|
+
.map(c => ({ cmd: c, score: frecencyScore(usage, c) }))
|
|
4487
|
+
.sort((a, b) => b.score - a.score);
|
|
4488
|
+
|
|
4489
|
+
// Show top N frecency commands + pinned + "more..."
|
|
4490
|
+
const MAX_SURFACE = 5;
|
|
4491
|
+
const surfaced = scored.slice(0, MAX_SURFACE).map(s => s.cmd);
|
|
4492
|
+
const hidden = scored.slice(MAX_SURFACE).map(s => s.cmd);
|
|
4493
|
+
|
|
4494
|
+
// Build menu: pinned first, then frecency-surfaced, then "more..." if needed
|
|
4495
|
+
const topItems = [...new Set([...pinned, ...surfaced])];
|
|
4496
|
+
const menuItems = topItems.map(cmd => ({
|
|
4497
|
+
id: cmd,
|
|
4498
|
+
name: `${cmd.padEnd(12)} ${dim(CLI_COMMANDS[cmd].desc)}`,
|
|
4499
|
+
}));
|
|
4500
|
+
if (hidden.length > 0) {
|
|
4501
|
+
menuItems.push({ id: "__more__", name: `${"more...".padEnd(12)} ${dim(`${hidden.length} more commands`)}` });
|
|
4250
4502
|
}
|
|
4251
4503
|
|
|
4252
|
-
|
|
4253
|
-
barLn(gray("Pre-flight"));
|
|
4254
|
-
barLn();
|
|
4255
|
-
const checks = preflight();
|
|
4256
|
-
for (const c of checks) {
|
|
4257
|
-
const icon = c.status === "ok" ? green("\u2713") : c.status === "warn" ? yellow("\u25CB") : red("\u2717");
|
|
4258
|
-
barLn(`${icon} ${dim(`${c.label} ${c.detail}`)}`);
|
|
4259
|
-
}
|
|
4504
|
+
question(`${bold("moveros")} ${dim("— choose a command")}`);
|
|
4260
4505
|
barLn();
|
|
4506
|
+
let choice = await interactiveSelect(menuItems);
|
|
4261
4507
|
|
|
4262
|
-
if (
|
|
4263
|
-
|
|
4264
|
-
|
|
4508
|
+
if (choice === "__more__") {
|
|
4509
|
+
// Show full list
|
|
4510
|
+
const fullItems = eligible
|
|
4511
|
+
.filter(c => !c.startsWith("__"))
|
|
4512
|
+
.map(cmd => ({
|
|
4513
|
+
id: cmd,
|
|
4514
|
+
name: `${cmd.padEnd(12)} ${dim(CLI_COMMANDS[cmd].desc)}`,
|
|
4515
|
+
}));
|
|
4516
|
+
barLn();
|
|
4517
|
+
question(`${bold("moveros")} ${dim("— all commands")}`);
|
|
4518
|
+
barLn();
|
|
4519
|
+
choice = await interactiveSelect(fullItems);
|
|
4265
4520
|
}
|
|
4266
4521
|
|
|
4267
|
-
|
|
4268
|
-
|
|
4522
|
+
if (!choice) return null;
|
|
4523
|
+
return choice;
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
// ─── Comprehensive Update (10-step interactive) ─────────────────────────────
|
|
4527
|
+
async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
4528
|
+
const isQuick = opts.rest.includes("--quick");
|
|
4529
|
+
|
|
4530
|
+
// Step 1: CLI Self-Update
|
|
4531
|
+
if (!opts._selfUpdated) {
|
|
4269
4532
|
try {
|
|
4270
4533
|
const localVer = require("./package.json").version;
|
|
4271
4534
|
const npmVer = execSync("npm view mover-os version", { encoding: "utf8", timeout: 10000 }).trim();
|
|
@@ -4277,7 +4540,6 @@ async function main() {
|
|
|
4277
4540
|
sp.stop(`CLI updated to ${npmVer}`);
|
|
4278
4541
|
barLn(dim(" Re-running with updated CLI..."));
|
|
4279
4542
|
barLn();
|
|
4280
|
-
// Re-exec with new code — pass args through, add flag to prevent loop
|
|
4281
4543
|
const args = process.argv.slice(2).concat("--_self-updated");
|
|
4282
4544
|
const { spawnSync } = require("child_process");
|
|
4283
4545
|
const result = spawnSync(process.argv[0], [process.argv[1], ...args], {
|
|
@@ -4289,7 +4551,7 @@ async function main() {
|
|
|
4289
4551
|
barLn(dim(" Continuing with current version..."));
|
|
4290
4552
|
}
|
|
4291
4553
|
} else {
|
|
4292
|
-
|
|
4554
|
+
statusLine("ok", "CLI", `up to date (${localVer})`);
|
|
4293
4555
|
}
|
|
4294
4556
|
} catch {
|
|
4295
4557
|
barLn(dim(" Could not check for CLI updates (offline?)"));
|
|
@@ -4297,90 +4559,72 @@ async function main() {
|
|
|
4297
4559
|
barLn();
|
|
4298
4560
|
}
|
|
4299
4561
|
|
|
4300
|
-
//
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
try { updateKey = JSON.parse(fs.readFileSync(cfgPath, "utf8")).licenseKey; } catch {}
|
|
4308
|
-
}
|
|
4309
|
-
}
|
|
4310
|
-
if (!updateKey || !await validateKey(updateKey)) {
|
|
4311
|
-
outro(red("Valid license key required. Use: npx moveros --update --key YOUR_KEY"));
|
|
4312
|
-
process.exit(1);
|
|
4313
|
-
}
|
|
4314
|
-
|
|
4315
|
-
// Download payload if not bundled
|
|
4316
|
-
const hasSrcUpdate = fs.existsSync(path.join(bundleDir, "src", "workflows"));
|
|
4317
|
-
if (!hasSrcUpdate) {
|
|
4318
|
-
barLn(dim("Downloading payload..."));
|
|
4319
|
-
try {
|
|
4320
|
-
bundleDir = await downloadPayload(updateKey);
|
|
4321
|
-
} catch (err) {
|
|
4322
|
-
outro(red(`Download failed: ${err.message}`));
|
|
4323
|
-
process.exit(1);
|
|
4324
|
-
}
|
|
4325
|
-
}
|
|
4562
|
+
// Step 2: Vault Detection
|
|
4563
|
+
let vaultPath = resolveVaultPath(opts.vault);
|
|
4564
|
+
if (!vaultPath) {
|
|
4565
|
+
outro(red("No Mover OS vault found. Use: moveros update --vault /path"));
|
|
4566
|
+
process.exit(1);
|
|
4567
|
+
}
|
|
4568
|
+
statusLine("ok", "Vault", path.basename(vaultPath));
|
|
4326
4569
|
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
);
|
|
4334
|
-
if (!vaultPath) {
|
|
4335
|
-
outro(red("No Mover OS vault found. Use: npx moveros --update --vault /path"));
|
|
4336
|
-
process.exit(1);
|
|
4337
|
-
}
|
|
4570
|
+
// Step 3: Key Validation
|
|
4571
|
+
let updateKey = opts.key;
|
|
4572
|
+
if (!updateKey) {
|
|
4573
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
4574
|
+
if (fs.existsSync(cfgPath)) {
|
|
4575
|
+
try { updateKey = JSON.parse(fs.readFileSync(cfgPath, "utf8")).licenseKey; } catch {}
|
|
4338
4576
|
}
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4577
|
+
}
|
|
4578
|
+
if (!updateKey) {
|
|
4579
|
+
updateKey = await textInput({ label: "License key", mask: "\u25AA", placeholder: "MOVER-XXXX-XXXX" });
|
|
4580
|
+
if (!updateKey) return;
|
|
4581
|
+
}
|
|
4582
|
+
const sp1 = spinner("Validating license");
|
|
4583
|
+
if (!await validateKey(updateKey)) {
|
|
4584
|
+
sp1.stop(red("Invalid key"));
|
|
4585
|
+
outro(red("Valid license key required."));
|
|
4586
|
+
process.exit(1);
|
|
4587
|
+
}
|
|
4588
|
+
sp1.stop(green("License verified"));
|
|
4589
|
+
barLn();
|
|
4342
4590
|
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4591
|
+
// Download payload if not bundled
|
|
4592
|
+
const hasSrc = fs.existsSync(path.join(bundleDir, "src", "workflows"));
|
|
4593
|
+
if (!hasSrc) {
|
|
4594
|
+
const dlSp = spinner("Downloading Mover OS");
|
|
4595
|
+
try {
|
|
4596
|
+
bundleDir = await downloadPayload(updateKey);
|
|
4597
|
+
dlSp.stop(green("Downloaded"));
|
|
4598
|
+
} catch (err) {
|
|
4599
|
+
dlSp.stop(red("Download failed"));
|
|
4600
|
+
outro(red(err.message));
|
|
4347
4601
|
process.exit(1);
|
|
4348
4602
|
}
|
|
4349
|
-
const selectedIds = detectedAgents.map((a) => a.id);
|
|
4350
|
-
barLn(dim(`Agents: ${detectedAgents.map((a) => a.name).join(", ")}`));
|
|
4351
4603
|
barLn();
|
|
4604
|
+
}
|
|
4352
4605
|
|
|
4353
|
-
|
|
4606
|
+
// Read versions
|
|
4607
|
+
const vfPath = path.join(vaultPath, ".mover-version");
|
|
4608
|
+
const installedVer = fs.existsSync(vfPath) ? fs.readFileSync(vfPath, "utf8").trim() : null;
|
|
4609
|
+
let newVer = `V${VERSION}`;
|
|
4610
|
+
try {
|
|
4611
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(bundleDir, "package.json"), "utf8"));
|
|
4612
|
+
newVer = pkg.version || newVer;
|
|
4613
|
+
} catch {}
|
|
4614
|
+
|
|
4615
|
+
// Quick mode: skip interactive steps
|
|
4616
|
+
if (isQuick) {
|
|
4617
|
+
const detectedAgents = AGENTS.filter((a) => a.detect());
|
|
4618
|
+
if (detectedAgents.length === 0) { outro(red("No AI agents detected.")); process.exit(1); }
|
|
4619
|
+
const selectedIds = detectedAgents.map((a) => a.id);
|
|
4354
4620
|
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
4355
4621
|
const totalChanged = countChanges(changes);
|
|
4356
|
-
|
|
4357
|
-
// Read versions
|
|
4358
|
-
const vfPath = path.join(vaultPath, ".mover-version");
|
|
4359
|
-
const installedVer = fs.existsSync(vfPath) ? fs.readFileSync(vfPath, "utf8").trim() : null;
|
|
4360
|
-
let newVer = `V${VERSION}`;
|
|
4361
|
-
try {
|
|
4362
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(bundleDir, "package.json"), "utf8"));
|
|
4363
|
-
newVer = pkg.version || newVer;
|
|
4364
|
-
} catch {}
|
|
4365
|
-
|
|
4366
4622
|
displayChangeSummary(changes, installedVer, newVer);
|
|
4367
|
-
|
|
4368
|
-
if (totalChanged === 0) {
|
|
4369
|
-
outro(green("Already up to date."));
|
|
4370
|
-
return;
|
|
4371
|
-
}
|
|
4372
|
-
|
|
4373
|
-
// Apply all changes
|
|
4623
|
+
if (totalChanged === 0) { outro(green("Already up to date.")); return; }
|
|
4374
4624
|
barLn(bold("Updating..."));
|
|
4375
4625
|
barLn();
|
|
4376
|
-
|
|
4377
|
-
// Vault structure
|
|
4378
4626
|
createVaultStructure(vaultPath);
|
|
4379
|
-
|
|
4380
|
-
// Templates
|
|
4381
4627
|
installTemplateFiles(bundleDir, vaultPath);
|
|
4382
|
-
|
|
4383
|
-
// Per-agent installation
|
|
4384
4628
|
const writtenFiles = new Set();
|
|
4385
4629
|
const skillOpts = { install: true, categories: null, workflows: null };
|
|
4386
4630
|
for (const agent of detectedAgents) {
|
|
@@ -4389,20 +4633,325 @@ async function main() {
|
|
|
4389
4633
|
const sp = spinner(agent.name);
|
|
4390
4634
|
const steps = fn(bundleDir, vaultPath, skillOpts, writtenFiles, agent.id);
|
|
4391
4635
|
await sleep(200);
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4636
|
+
sp.stop(steps.length > 0 ? `${agent.name} ${dim(steps.join(", "))}` : `${agent.name} ${dim("configured")}`);
|
|
4637
|
+
}
|
|
4638
|
+
fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
|
|
4639
|
+
writeMoverConfig(vaultPath, selectedIds);
|
|
4640
|
+
barLn();
|
|
4641
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
4642
|
+
outro(`${green("Done.")} ${totalChanged} files updated in ${elapsed}s. Run ${bold("/update")} if version bumped.`);
|
|
4643
|
+
return;
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
// Step 4: What's New
|
|
4647
|
+
showWhatsNew(installedVer, newVer);
|
|
4648
|
+
|
|
4649
|
+
// Step 5: Backup Offer
|
|
4650
|
+
const engine = detectEngineFiles(vaultPath);
|
|
4651
|
+
if (engine.exists) {
|
|
4652
|
+
question("Back up before updating?");
|
|
4653
|
+
barLn(dim(" Select what to save. Esc to skip."));
|
|
4654
|
+
barLn();
|
|
4655
|
+
const backupItems = [
|
|
4656
|
+
{ id: "engine", name: "Engine files", tier: "Identity, Strategy, Goals, Context" },
|
|
4657
|
+
];
|
|
4658
|
+
if (fs.existsSync(path.join(vaultPath, "02_Areas"))) {
|
|
4659
|
+
backupItems.push({ id: "areas", name: "Full Areas folder", tier: "Everything in 02_Areas/" });
|
|
4660
|
+
}
|
|
4661
|
+
const detectedForBackup = AGENTS.filter((a) => a.detect()).map((a) => a.id);
|
|
4662
|
+
if (detectedForBackup.length > 0) {
|
|
4663
|
+
backupItems.push({ id: "agents", name: "Agent configs", tier: `Rules, skills from ${detectedForBackup.length} agent(s)` });
|
|
4664
|
+
}
|
|
4665
|
+
const backupChoices = await interactiveSelect(backupItems, { multi: true, preSelected: ["engine"] });
|
|
4666
|
+
if (backupChoices && backupChoices.length > 0) {
|
|
4667
|
+
const now = new Date();
|
|
4668
|
+
const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
4669
|
+
const archivesDir = path.join(vaultPath, "04_Archives");
|
|
4670
|
+
if (backupChoices.includes("engine")) {
|
|
4671
|
+
const engineDir = path.join(vaultPath, "02_Areas", "Engine");
|
|
4672
|
+
const backupDir = path.join(archivesDir, `Engine_Backup_${ts}`);
|
|
4673
|
+
try {
|
|
4674
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
4675
|
+
let backed = 0;
|
|
4676
|
+
for (const f of fs.readdirSync(engineDir).filter(f => fs.statSync(path.join(engineDir, f)).isFile())) {
|
|
4677
|
+
fs.copyFileSync(path.join(engineDir, f), path.join(backupDir, f));
|
|
4678
|
+
backed++;
|
|
4679
|
+
}
|
|
4680
|
+
statusLine("ok", "Backed up", `${backed} Engine files`);
|
|
4681
|
+
} catch (err) { barLn(yellow(` Backup failed: ${err.message}`)); }
|
|
4682
|
+
}
|
|
4683
|
+
if (backupChoices.includes("areas")) {
|
|
4684
|
+
try {
|
|
4685
|
+
copyDirRecursive(path.join(vaultPath, "02_Areas"), path.join(archivesDir, `Areas_Backup_${ts}`));
|
|
4686
|
+
statusLine("ok", "Backed up", "Full Areas folder");
|
|
4687
|
+
} catch (err) { barLn(yellow(` Areas backup failed: ${err.message}`)); }
|
|
4688
|
+
}
|
|
4689
|
+
if (backupChoices.includes("agents")) {
|
|
4690
|
+
try {
|
|
4691
|
+
const agentBackupDir = path.join(archivesDir, `Agent_Backup_${ts}`);
|
|
4692
|
+
fs.mkdirSync(agentBackupDir, { recursive: true });
|
|
4693
|
+
let agentsBacked = 0;
|
|
4694
|
+
for (const ag of AGENTS.filter((a) => a.detect())) {
|
|
4695
|
+
const agDir = path.join(agentBackupDir, ag.id);
|
|
4696
|
+
fs.mkdirSync(agDir, { recursive: true });
|
|
4697
|
+
for (const cp of (ag.configPaths || [])) {
|
|
4698
|
+
if (fs.existsSync(cp.src)) {
|
|
4699
|
+
try { fs.copyFileSync(cp.src, path.join(agDir, path.basename(cp.src))); agentsBacked++; } catch {}
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
statusLine("ok", "Backed up", `${agentsBacked} agent config files`);
|
|
4704
|
+
} catch (err) { barLn(yellow(` Agent backup failed: ${err.message}`)); }
|
|
4396
4705
|
}
|
|
4397
4706
|
}
|
|
4707
|
+
barLn();
|
|
4708
|
+
}
|
|
4398
4709
|
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4710
|
+
// Step 6: Agent Management
|
|
4711
|
+
const visibleAgents = AGENTS.filter((a) => !a.hidden);
|
|
4712
|
+
const detectedIds = visibleAgents.filter((a) => a.detect()).map((a) => a.id);
|
|
4713
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
4714
|
+
let currentAgents = [];
|
|
4715
|
+
if (fs.existsSync(cfgPath)) {
|
|
4716
|
+
try { currentAgents = JSON.parse(fs.readFileSync(cfgPath, "utf8")).agents || []; } catch {}
|
|
4717
|
+
}
|
|
4718
|
+
const preSelectedAgents = currentAgents.length > 0 ? currentAgents : detectedIds;
|
|
4719
|
+
|
|
4720
|
+
question(`Agents ${dim("(add or remove)")}`);
|
|
4721
|
+
barLn();
|
|
4722
|
+
const agentItems = visibleAgents.map((a) => ({
|
|
4723
|
+
...a,
|
|
4724
|
+
_detected: detectedIds.includes(a.id),
|
|
4725
|
+
}));
|
|
4726
|
+
const selectedIds = await interactiveSelect(agentItems, { multi: true, preSelected: preSelectedAgents });
|
|
4727
|
+
if (!selectedIds || selectedIds.length === 0) return;
|
|
4728
|
+
const selectedAgents = AGENTS.filter((a) => selectedIds.includes(a.id));
|
|
4729
|
+
barLn();
|
|
4730
|
+
|
|
4731
|
+
// Step 7: Change Detection
|
|
4732
|
+
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
4733
|
+
const totalChanged = countChanges(changes);
|
|
4734
|
+
question("Change Summary");
|
|
4735
|
+
barLn();
|
|
4736
|
+
displayChangeSummary(changes, installedVer, newVer);
|
|
4402
4737
|
|
|
4738
|
+
if (totalChanged === 0) {
|
|
4739
|
+
barLn(green(" Already up to date."));
|
|
4403
4740
|
barLn();
|
|
4404
4741
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
4405
|
-
outro(`${green("Done.")}
|
|
4742
|
+
outro(`${green("Done.")} No changes needed. ${dim(`(${elapsed}s)`)}`);
|
|
4743
|
+
return;
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
const applyChoice = await interactiveSelect([
|
|
4747
|
+
{ id: "all", name: "Yes, update all changed files", tier: "" },
|
|
4748
|
+
{ id: "select", name: "Select individually", tier: "" },
|
|
4749
|
+
{ id: "cancel", name: "Cancel", tier: "" },
|
|
4750
|
+
], { multi: false, defaultIndex: 0 });
|
|
4751
|
+
if (!applyChoice || applyChoice === "cancel") { outro("Cancelled."); return; }
|
|
4752
|
+
|
|
4753
|
+
let selectedWorkflows = null;
|
|
4754
|
+
let skipHooks = false, skipRules = false, skipTemplates = false;
|
|
4755
|
+
if (applyChoice === "select") {
|
|
4756
|
+
const changedItems = [];
|
|
4757
|
+
const changedPreSelected = [];
|
|
4758
|
+
for (const f of changes.workflows.filter((x) => x.status !== "unchanged")) {
|
|
4759
|
+
const id = `wf:${f.file}`;
|
|
4760
|
+
changedItems.push({ id, name: `/${f.file.replace(".md", "")}`, tier: dim(f.status === "new" ? "new" : "changed") });
|
|
4761
|
+
changedPreSelected.push(id);
|
|
4762
|
+
}
|
|
4763
|
+
for (const f of changes.hooks.filter((x) => x.status !== "unchanged")) {
|
|
4764
|
+
const id = `hook:${f.file}`;
|
|
4765
|
+
changedItems.push({ id, name: f.file, tier: dim(f.status === "new" ? "new hook" : "hook") });
|
|
4766
|
+
changedPreSelected.push(id);
|
|
4767
|
+
}
|
|
4768
|
+
if (changes.rules === "changed") {
|
|
4769
|
+
changedItems.push({ id: "rules", name: "Global Rules", tier: dim("rules") });
|
|
4770
|
+
changedPreSelected.push("rules");
|
|
4771
|
+
}
|
|
4772
|
+
for (const f of changes.templates.filter((x) => x.status !== "unchanged")) {
|
|
4773
|
+
const id = `tmpl:${f.file}`;
|
|
4774
|
+
changedItems.push({ id, name: f.file.replace(/\\/g, "/"), tier: dim(f.status === "new" ? "new" : "changed") });
|
|
4775
|
+
changedPreSelected.push(id);
|
|
4776
|
+
}
|
|
4777
|
+
if (changedItems.length > 0) {
|
|
4778
|
+
question("Select files to update");
|
|
4779
|
+
barLn();
|
|
4780
|
+
const selectedFileIds = await interactiveSelect(changedItems, { multi: true, preSelected: changedPreSelected });
|
|
4781
|
+
if (!selectedFileIds) return;
|
|
4782
|
+
const selectedWfFiles = selectedFileIds.filter((id) => id.startsWith("wf:")).map((id) => id.slice(3));
|
|
4783
|
+
if (selectedWfFiles.length < changes.workflows.filter((x) => x.status !== "unchanged").length) {
|
|
4784
|
+
selectedWorkflows = new Set(selectedWfFiles);
|
|
4785
|
+
}
|
|
4786
|
+
skipHooks = !selectedFileIds.some((id) => id.startsWith("hook:"));
|
|
4787
|
+
skipRules = !selectedFileIds.includes("rules");
|
|
4788
|
+
skipTemplates = !selectedFileIds.some((id) => id.startsWith("tmpl:"));
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
// Step 8: Apply Changes (with progress animation)
|
|
4793
|
+
barLn();
|
|
4794
|
+
question(bold("Applying updates"));
|
|
4795
|
+
barLn();
|
|
4796
|
+
|
|
4797
|
+
const installSteps = [];
|
|
4798
|
+
installSteps.push({ label: "Vault structure", fn: async () => { createVaultStructure(vaultPath); await sleep(100); } });
|
|
4799
|
+
if (!skipTemplates) {
|
|
4800
|
+
installSteps.push({ label: "Template files", fn: async () => { installTemplateFiles(bundleDir, vaultPath); await sleep(100); } });
|
|
4801
|
+
}
|
|
4802
|
+
|
|
4803
|
+
const writtenFiles = new Set();
|
|
4804
|
+
const skillOpts = { install: true, categories: null, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
|
|
4805
|
+
for (const agent of selectedAgents) {
|
|
4806
|
+
const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
|
|
4807
|
+
const targets = sel ? sel.targets : [agent.id];
|
|
4808
|
+
for (const targetId of targets) {
|
|
4809
|
+
const fn = AGENT_INSTALLERS[targetId];
|
|
4810
|
+
if (!fn) continue;
|
|
4811
|
+
const targetReg = AGENT_REGISTRY[targetId];
|
|
4812
|
+
const displayName = targetReg ? targetReg.name : agent.name;
|
|
4813
|
+
installSteps.push({ label: displayName, fn: async () => { fn(bundleDir, vaultPath, skillOpts, writtenFiles, targetId); await sleep(150); } });
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
await installProgress(installSteps);
|
|
4818
|
+
|
|
4819
|
+
// Step 9: Skills Refresh
|
|
4820
|
+
barLn();
|
|
4821
|
+
const allSkills = findSkills(bundleDir);
|
|
4822
|
+
if (allSkills.length > 0 && selectedAgents.some((a) => a.id !== "aider")) {
|
|
4823
|
+
question("Refresh skill categories?");
|
|
4824
|
+
barLn();
|
|
4825
|
+
const catCounts = {};
|
|
4826
|
+
for (const sk of allSkills) { catCounts[sk.category] = (catCounts[sk.category] || 0) + 1; }
|
|
4827
|
+
const categoryItems = CATEGORY_META.map((c) => ({
|
|
4828
|
+
id: c.id,
|
|
4829
|
+
name: `${c.name} ${dim(`(${catCounts[c.id] || 0})`)}`,
|
|
4830
|
+
tier: dim(c.desc),
|
|
4831
|
+
}));
|
|
4832
|
+
const selectedCatIds = await interactiveSelect(categoryItems, { multi: true, preSelected: ["development", "obsidian"] });
|
|
4833
|
+
if (selectedCatIds && selectedCatIds.length > 0) {
|
|
4834
|
+
const catSet = new Set(selectedCatIds);
|
|
4835
|
+
const refreshOpts = { install: true, categories: catSet, workflows: null };
|
|
4836
|
+
for (const agent of selectedAgents) {
|
|
4837
|
+
const fn = AGENT_INSTALLERS[agent.id];
|
|
4838
|
+
if (fn) fn(bundleDir, vaultPath, refreshOpts, writtenFiles, agent.id);
|
|
4839
|
+
}
|
|
4840
|
+
const skillCount = allSkills.filter((s) => s.category === "tools" || catSet.has(s.category)).length;
|
|
4841
|
+
statusLine("ok", "Skills refreshed", `${skillCount} across ${selectedAgents.length} agent(s)`);
|
|
4842
|
+
}
|
|
4843
|
+
barLn();
|
|
4844
|
+
}
|
|
4845
|
+
|
|
4846
|
+
// Update version marker + config
|
|
4847
|
+
fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
|
|
4848
|
+
writeMoverConfig(vaultPath, selectedIds, updateKey);
|
|
4849
|
+
|
|
4850
|
+
// Step 10: Summary + Success
|
|
4851
|
+
barLn();
|
|
4852
|
+
await successAnimation(`Mover OS updated — ${totalChanged} files`);
|
|
4853
|
+
|
|
4854
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
4855
|
+
outro(`${green("Done.")} ${dim(`${elapsed}s`)} Run ${bold("/update")} in your agent to sync Engine.`);
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
// ─── Vault Resolution Helper ─────────────────────────────────────────────────
|
|
4859
|
+
function resolveVaultPath(explicitVault) {
|
|
4860
|
+
if (explicitVault) {
|
|
4861
|
+
let v = explicitVault;
|
|
4862
|
+
if (v.startsWith("~")) v = path.join(os.homedir(), v.slice(1));
|
|
4863
|
+
return path.resolve(v);
|
|
4864
|
+
}
|
|
4865
|
+
// Try config.json
|
|
4866
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
4867
|
+
if (fs.existsSync(cfgPath)) {
|
|
4868
|
+
try {
|
|
4869
|
+
const v = JSON.parse(fs.readFileSync(cfgPath, "utf8")).vaultPath;
|
|
4870
|
+
if (v && fs.existsSync(v)) return v;
|
|
4871
|
+
} catch {}
|
|
4872
|
+
}
|
|
4873
|
+
// Try Obsidian detection
|
|
4874
|
+
const obsVaults = detectObsidianVaults();
|
|
4875
|
+
return obsVaults.find((p) => fs.existsSync(path.join(p, ".mover-version"))) || null;
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
4879
|
+
async function main() {
|
|
4880
|
+
const opts = parseArgs();
|
|
4881
|
+
let bundleDir = path.resolve(__dirname);
|
|
4882
|
+
const startTime = Date.now();
|
|
4883
|
+
|
|
4884
|
+
// ── TUI: Enter alternate screen ──
|
|
4885
|
+
enterAltScreen();
|
|
4886
|
+
|
|
4887
|
+
// ── Intro: Logo plays once ──
|
|
4888
|
+
await printHeader();
|
|
4889
|
+
|
|
4890
|
+
// ── Route: no command → interactive menu (persistent loop) ──
|
|
4891
|
+
const lightCommands = ["pulse", "capture", "who", "diff", "sync", "replay", "context", "settings", "backup", "restore", "doctor", "prayer", "help", "uninstall", "test"];
|
|
4892
|
+
|
|
4893
|
+
if (!opts.command) {
|
|
4894
|
+
// Interactive loop — stay open like a real app
|
|
4895
|
+
while (true) {
|
|
4896
|
+
clearContent();
|
|
4897
|
+
ln(compactHeader());
|
|
4898
|
+
ln();
|
|
4899
|
+
|
|
4900
|
+
opts.command = await cmdMainMenu();
|
|
4901
|
+
if (!opts.command) break; // user hit Esc — clean exit
|
|
4902
|
+
|
|
4903
|
+
if (lightCommands.includes(opts.command)) {
|
|
4904
|
+
await wipeDown(12);
|
|
4905
|
+
ln(compactHeader());
|
|
4906
|
+
ln();
|
|
4907
|
+
|
|
4908
|
+
recordCliUsage(opts.command);
|
|
4909
|
+
const handler = CLI_HANDLERS[opts.command];
|
|
4910
|
+
if (handler) await handler(opts);
|
|
4911
|
+
else barLn(yellow(`Command '${opts.command}' is not yet implemented.`));
|
|
4912
|
+
opts.command = ""; // reset for next loop
|
|
4913
|
+
|
|
4914
|
+
barLn(dim(" esc to go back"));
|
|
4915
|
+
await waitForEsc();
|
|
4916
|
+
continue;
|
|
4917
|
+
}
|
|
4918
|
+
// install/update — record usage and break out into pre-flight
|
|
4919
|
+
recordCliUsage(opts.command);
|
|
4920
|
+
break;
|
|
4921
|
+
}
|
|
4922
|
+
if (!opts.command) return;
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
// ── Route: direct CLI command (non-interactive) ──
|
|
4926
|
+
if (lightCommands.includes(opts.command)) {
|
|
4927
|
+
recordCliUsage(opts.command);
|
|
4928
|
+
const handler = CLI_HANDLERS[opts.command];
|
|
4929
|
+
if (handler) {
|
|
4930
|
+
await handler(opts);
|
|
4931
|
+
} else {
|
|
4932
|
+
barLn(yellow(`Command '${opts.command}' is not yet implemented.`));
|
|
4933
|
+
}
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
// ── Pre-flight (install + update only) ──
|
|
4938
|
+
barLn(gray("Pre-flight"));
|
|
4939
|
+
barLn();
|
|
4940
|
+
const checks = preflight();
|
|
4941
|
+
for (const c of checks) {
|
|
4942
|
+
const icon = c.status === "ok" ? green("\u2713") : c.status === "warn" ? yellow("\u25CB") : red("\u2717");
|
|
4943
|
+
barLn(`${icon} ${dim(`${c.label} ${c.detail}`)}`);
|
|
4944
|
+
}
|
|
4945
|
+
barLn();
|
|
4946
|
+
|
|
4947
|
+
if (checks.some((c) => c.status === "fail")) {
|
|
4948
|
+
outro(red("Pre-flight failed. Fix the issues above."));
|
|
4949
|
+
process.exit(1);
|
|
4950
|
+
}
|
|
4951
|
+
|
|
4952
|
+
// ── Comprehensive Update ──
|
|
4953
|
+
if (opts.command === "update") {
|
|
4954
|
+
await cmdUpdateComprehensive(opts, bundleDir, startTime);
|
|
4406
4955
|
return;
|
|
4407
4956
|
}
|
|
4408
4957
|
|
|
@@ -4542,213 +5091,21 @@ async function main() {
|
|
|
4542
5091
|
if (vaultPath.startsWith("~")) vaultPath = path.join(os.homedir(), vaultPath.slice(1));
|
|
4543
5092
|
vaultPath = path.resolve(vaultPath);
|
|
4544
5093
|
|
|
4545
|
-
// ──
|
|
5094
|
+
// ── Fresh install only — redirect if existing ──
|
|
4546
5095
|
const engine = detectEngineFiles(vaultPath);
|
|
4547
5096
|
const versionFile = path.join(vaultPath, ".mover-version");
|
|
4548
5097
|
const hasExistingInstall = fs.existsSync(versionFile) || engine.exists;
|
|
4549
5098
|
|
|
4550
|
-
let installMode = "fresh"; // fresh | update | uninstall
|
|
4551
|
-
|
|
4552
5099
|
if (hasExistingInstall) {
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
} else {
|
|
4557
|
-
barLn(yellow("Mover OS installed, but no Engine data yet."));
|
|
4558
|
-
}
|
|
5100
|
+
question(yellow("Mover OS is already installed in this vault."));
|
|
5101
|
+
barLn(dim(" Use " + bold("moveros update") + " to refresh agents, rules, and skills."));
|
|
5102
|
+
barLn(dim(" Use " + bold("moveros uninstall") + " to remove Mover OS."));
|
|
4559
5103
|
barLn();
|
|
4560
|
-
|
|
4561
|
-
question("What would you like to do?");
|
|
4562
|
-
barLn();
|
|
4563
|
-
|
|
4564
|
-
installMode = await interactiveSelect(
|
|
4565
|
-
[
|
|
4566
|
-
{ id: "update", name: "Update", tier: "Refreshes rules, commands, and skills. Your data stays safe." },
|
|
4567
|
-
{ id: "fresh", name: "Fresh Install", tier: "Full setup from scratch. Template files will be overwritten." },
|
|
4568
|
-
{ id: "uninstall", name: "Uninstall", tier: "Remove Mover OS files from your agents and vault." },
|
|
4569
|
-
],
|
|
4570
|
-
{ multi: false, defaultIndex: 0 }
|
|
4571
|
-
);
|
|
4572
|
-
if (!installMode) return;
|
|
4573
|
-
}
|
|
4574
|
-
|
|
4575
|
-
// ── Uninstall flow ──
|
|
4576
|
-
if (installMode === "uninstall") {
|
|
4577
|
-
await runUninstall(vaultPath);
|
|
5104
|
+
outro("Use update or uninstall instead.");
|
|
4578
5105
|
return;
|
|
4579
5106
|
}
|
|
4580
5107
|
|
|
4581
|
-
const updateMode =
|
|
4582
|
-
|
|
4583
|
-
// ── Backup (update mode only, if Engine files exist) ──
|
|
4584
|
-
if (updateMode && engine.exists) {
|
|
4585
|
-
barLn();
|
|
4586
|
-
question("Back up before updating?");
|
|
4587
|
-
barLn(dim(" Select what to save. Your data won't be overwritten, but backups are always safer."));
|
|
4588
|
-
barLn();
|
|
4589
|
-
|
|
4590
|
-
const backupItems = [
|
|
4591
|
-
{ id: "engine", name: "Engine files", tier: "Identity, Strategy, Goals, and all Engine data" },
|
|
4592
|
-
];
|
|
4593
|
-
|
|
4594
|
-
const areasDir = path.join(vaultPath, "02_Areas");
|
|
4595
|
-
if (fs.existsSync(areasDir)) {
|
|
4596
|
-
backupItems.push({ id: "areas", name: "Full Areas folder", tier: "Everything in 02_Areas/ including Dailies and Reviews" });
|
|
4597
|
-
}
|
|
4598
|
-
|
|
4599
|
-
// Only offer agent config backup if any agents are detected
|
|
4600
|
-
const detectedForBackup = AGENTS.filter((a) => a.detect()).map((a) => a.id);
|
|
4601
|
-
if (detectedForBackup.length > 0) {
|
|
4602
|
-
backupItems.push({ id: "agents", name: "Agent configs", tier: `Current rules, skills, and commands from ${detectedForBackup.length} detected agent(s)` });
|
|
4603
|
-
}
|
|
4604
|
-
|
|
4605
|
-
backupItems.push({ id: "skip", name: "Skip backup", tier: "Continue without backing up" });
|
|
4606
|
-
|
|
4607
|
-
const backupChoices = await interactiveSelect(backupItems, {
|
|
4608
|
-
multi: true,
|
|
4609
|
-
preSelected: ["engine"],
|
|
4610
|
-
});
|
|
4611
|
-
|
|
4612
|
-
if (backupChoices && backupChoices.length > 0 && !(backupChoices.length === 1 && backupChoices.includes("skip"))) {
|
|
4613
|
-
const now = new Date();
|
|
4614
|
-
const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
4615
|
-
const archivesDir = path.join(vaultPath, "04_Archives");
|
|
4616
|
-
|
|
4617
|
-
// Engine files backup
|
|
4618
|
-
if (backupChoices.includes("engine")) {
|
|
4619
|
-
const backupDir = path.join(archivesDir, `Engine_Backup_${ts}`);
|
|
4620
|
-
const engineDir = path.join(vaultPath, "02_Areas", "Engine");
|
|
4621
|
-
try {
|
|
4622
|
-
fs.mkdirSync(backupDir, { recursive: true });
|
|
4623
|
-
let backed = 0;
|
|
4624
|
-
for (const file of fs.readdirSync(engineDir)) {
|
|
4625
|
-
const src = path.join(engineDir, file);
|
|
4626
|
-
if (fs.statSync(src).isFile()) {
|
|
4627
|
-
fs.copyFileSync(src, path.join(backupDir, file));
|
|
4628
|
-
backed++;
|
|
4629
|
-
}
|
|
4630
|
-
}
|
|
4631
|
-
barLn(green(` Backed up ${backed} Engine files to 04_Archives/Engine_Backup_${ts}/`));
|
|
4632
|
-
} catch (err) {
|
|
4633
|
-
barLn(yellow(` Engine backup failed: ${err.message}. Continuing anyway.`));
|
|
4634
|
-
}
|
|
4635
|
-
}
|
|
4636
|
-
|
|
4637
|
-
// Full Areas folder backup
|
|
4638
|
-
if (backupChoices.includes("areas")) {
|
|
4639
|
-
const backupDir = path.join(archivesDir, `Areas_Backup_${ts}`);
|
|
4640
|
-
try {
|
|
4641
|
-
copyDirRecursive(path.join(vaultPath, "02_Areas"), backupDir);
|
|
4642
|
-
barLn(green(` Backed up full Areas folder to 04_Archives/Areas_Backup_${ts}/`));
|
|
4643
|
-
} catch (err) {
|
|
4644
|
-
barLn(yellow(` Areas backup failed: ${err.message}. Continuing anyway.`));
|
|
4645
|
-
}
|
|
4646
|
-
}
|
|
4647
|
-
|
|
4648
|
-
// Agent configs backup
|
|
4649
|
-
if (backupChoices.includes("agents")) {
|
|
4650
|
-
const home = os.homedir();
|
|
4651
|
-
const agentBackupDir = path.join(archivesDir, `Agent_Backup_${ts}`);
|
|
4652
|
-
const AGENT_CONFIG_PATHS = {
|
|
4653
|
-
"claude-code": [
|
|
4654
|
-
{ src: path.join(home, ".claude", "CLAUDE.md"), label: "CLAUDE.md" },
|
|
4655
|
-
{ src: path.join(home, ".claude", "commands"), label: "commands" },
|
|
4656
|
-
{ src: path.join(home, ".claude", "skills"), label: "skills" },
|
|
4657
|
-
{ src: path.join(home, ".claude", "hooks"), label: "hooks" },
|
|
4658
|
-
],
|
|
4659
|
-
cursor: [
|
|
4660
|
-
{ src: path.join(vaultPath, ".cursor", "rules"), label: "rules" },
|
|
4661
|
-
{ src: path.join(home, ".cursor", "commands"), label: "commands" },
|
|
4662
|
-
{ src: path.join(home, ".cursor", "skills"), label: "skills" },
|
|
4663
|
-
],
|
|
4664
|
-
cline: [
|
|
4665
|
-
{ src: path.join(vaultPath, ".clinerules"), label: ".clinerules" },
|
|
4666
|
-
{ src: path.join(vaultPath, ".cline", "skills"), label: "skills" },
|
|
4667
|
-
],
|
|
4668
|
-
windsurf: [
|
|
4669
|
-
{ src: path.join(vaultPath, ".windsurfrules"), label: ".windsurfrules" },
|
|
4670
|
-
{ src: path.join(vaultPath, ".windsurf", "rules"), label: "rules" },
|
|
4671
|
-
{ src: path.join(vaultPath, ".windsurf", "workflows"), label: "workflows" },
|
|
4672
|
-
{ src: path.join(home, ".windsurf", "skills"), label: "skills" },
|
|
4673
|
-
],
|
|
4674
|
-
"gemini-cli": [
|
|
4675
|
-
{ src: path.join(home, ".gemini", "GEMINI.md"), label: "GEMINI.md" },
|
|
4676
|
-
{ src: path.join(home, ".gemini", "commands"), label: "commands" },
|
|
4677
|
-
{ src: path.join(home, ".gemini", "skills"), label: "skills" },
|
|
4678
|
-
],
|
|
4679
|
-
antigravity: [
|
|
4680
|
-
{ src: path.join(home, ".gemini", "GEMINI.md"), label: "GEMINI.md" },
|
|
4681
|
-
{ src: path.join(home, ".gemini", "antigravity", "global_workflows"), label: "workflows" },
|
|
4682
|
-
{ src: path.join(home, ".gemini", "antigravity", "skills"), label: "skills" },
|
|
4683
|
-
],
|
|
4684
|
-
copilot: [
|
|
4685
|
-
{ src: path.join(vaultPath, ".github", "copilot-instructions.md"), label: "copilot-instructions.md" },
|
|
4686
|
-
{ src: path.join(vaultPath, ".github", "prompts"), label: "prompts" },
|
|
4687
|
-
{ src: path.join(vaultPath, ".github", "skills"), label: "skills" },
|
|
4688
|
-
],
|
|
4689
|
-
codex: [
|
|
4690
|
-
{ src: path.join(home, ".codex", "AGENTS.md"), label: "AGENTS.md" },
|
|
4691
|
-
{ src: path.join(home, ".codex", "skills"), label: "skills" },
|
|
4692
|
-
],
|
|
4693
|
-
"roo-code": [
|
|
4694
|
-
{ src: path.join(vaultPath, ".roo", "rules"), label: "rules" },
|
|
4695
|
-
{ src: path.join(vaultPath, ".roo", "commands"), label: "commands" },
|
|
4696
|
-
{ src: path.join(vaultPath, ".roo", "skills"), label: "skills" },
|
|
4697
|
-
],
|
|
4698
|
-
"amazon-q": [
|
|
4699
|
-
{ src: path.join(vaultPath, ".amazonq", "rules"), label: "rules" },
|
|
4700
|
-
{ src: path.join(home, ".aws", "amazonq", "cli-agents"), label: "cli-agents" },
|
|
4701
|
-
],
|
|
4702
|
-
"kilo-code": [
|
|
4703
|
-
{ src: path.join(vaultPath, ".kilocode", "rules"), label: "rules" },
|
|
4704
|
-
{ src: path.join(vaultPath, ".kilocode", "skills"), label: "skills" },
|
|
4705
|
-
{ src: path.join(vaultPath, ".kilocode", "commands"), label: "commands" },
|
|
4706
|
-
],
|
|
4707
|
-
amp: [
|
|
4708
|
-
{ src: path.join(vaultPath, "AGENTS.md"), label: "AGENTS.md" },
|
|
4709
|
-
{ src: path.join(vaultPath, ".agents", "skills"), label: "skills" },
|
|
4710
|
-
],
|
|
4711
|
-
"continue": [
|
|
4712
|
-
{ src: path.join(vaultPath, ".continue", "rules"), label: "rules" },
|
|
4713
|
-
{ src: path.join(vaultPath, ".continue", "prompts"), label: "prompts" },
|
|
4714
|
-
],
|
|
4715
|
-
opencode: [
|
|
4716
|
-
{ src: path.join(vaultPath, "AGENTS.md"), label: "AGENTS.md" },
|
|
4717
|
-
{ src: path.join(vaultPath, ".opencode", "agents"), label: "agents" },
|
|
4718
|
-
],
|
|
4719
|
-
aider: [
|
|
4720
|
-
{ src: path.join(vaultPath, "CONVENTIONS.md"), label: "CONVENTIONS.md" },
|
|
4721
|
-
],
|
|
4722
|
-
};
|
|
4723
|
-
|
|
4724
|
-
let agentsBacked = 0;
|
|
4725
|
-
for (const agentId of detectedForBackup) {
|
|
4726
|
-
const paths = AGENT_CONFIG_PATHS[agentId];
|
|
4727
|
-
if (!paths) continue;
|
|
4728
|
-
const agentDir = path.join(agentBackupDir, agentId);
|
|
4729
|
-
let hasContent = false;
|
|
4730
|
-
for (const { src, label } of paths) {
|
|
4731
|
-
try {
|
|
4732
|
-
if (!fs.existsSync(src)) continue;
|
|
4733
|
-
const stat = fs.statSync(src);
|
|
4734
|
-
if (stat.isDirectory()) {
|
|
4735
|
-
copyDirRecursive(src, path.join(agentDir, label));
|
|
4736
|
-
hasContent = true;
|
|
4737
|
-
} else {
|
|
4738
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
4739
|
-
fs.copyFileSync(src, path.join(agentDir, label));
|
|
4740
|
-
hasContent = true;
|
|
4741
|
-
}
|
|
4742
|
-
} catch { /* skip inaccessible paths */ }
|
|
4743
|
-
}
|
|
4744
|
-
if (hasContent) agentsBacked++;
|
|
4745
|
-
}
|
|
4746
|
-
if (agentsBacked > 0) {
|
|
4747
|
-
barLn(green(` Backed up configs from ${agentsBacked} agent(s) to 04_Archives/Agent_Backup_${ts}/`));
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
5108
|
+
const updateMode = false;
|
|
4752
5109
|
|
|
4753
5110
|
if (!fs.existsSync(vaultPath)) fs.mkdirSync(vaultPath, { recursive: true });
|
|
4754
5111
|
|
|
@@ -4781,102 +5138,9 @@ async function main() {
|
|
|
4781
5138
|
return;
|
|
4782
5139
|
}
|
|
4783
5140
|
|
|
4784
|
-
//
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
let skipRules = false;
|
|
4788
|
-
let skipTemplates = false;
|
|
4789
|
-
|
|
4790
|
-
if (updateMode) {
|
|
4791
|
-
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
4792
|
-
const totalChanged = countChanges(changes);
|
|
4793
|
-
|
|
4794
|
-
// Read versions for display
|
|
4795
|
-
const versionFilePath = path.join(vaultPath, ".mover-version");
|
|
4796
|
-
const installedVersion = fs.existsSync(versionFilePath)
|
|
4797
|
-
? fs.readFileSync(versionFilePath, "utf8").trim()
|
|
4798
|
-
: null;
|
|
4799
|
-
let newVersion = `V${VERSION}`;
|
|
4800
|
-
try {
|
|
4801
|
-
const pkgPath = path.join(bundleDir, "package.json");
|
|
4802
|
-
if (fs.existsSync(pkgPath)) {
|
|
4803
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
4804
|
-
newVersion = pkg.version || newVersion;
|
|
4805
|
-
}
|
|
4806
|
-
} catch {}
|
|
4807
|
-
|
|
4808
|
-
barLn();
|
|
4809
|
-
question("Change Summary");
|
|
4810
|
-
barLn();
|
|
4811
|
-
displayChangeSummary(changes, installedVersion, newVersion);
|
|
4812
|
-
|
|
4813
|
-
if (totalChanged === 0) {
|
|
4814
|
-
barLn(green(" Already up to date."));
|
|
4815
|
-
barLn();
|
|
4816
|
-
} else {
|
|
4817
|
-
const applyChoice = await interactiveSelect(
|
|
4818
|
-
[
|
|
4819
|
-
{ id: "all", name: "Yes, update all changed files", tier: "" },
|
|
4820
|
-
{ id: "select", name: "Select individually", tier: "" },
|
|
4821
|
-
{ id: "cancel", name: "Cancel", tier: "" },
|
|
4822
|
-
],
|
|
4823
|
-
{ multi: false, defaultIndex: 0 }
|
|
4824
|
-
);
|
|
4825
|
-
|
|
4826
|
-
if (!applyChoice || applyChoice === "cancel") {
|
|
4827
|
-
outro("Cancelled.");
|
|
4828
|
-
return;
|
|
4829
|
-
}
|
|
4830
|
-
|
|
4831
|
-
if (applyChoice === "select") {
|
|
4832
|
-
// Build list of only changed/new files for individual selection
|
|
4833
|
-
const changedItems = [];
|
|
4834
|
-
const changedPreSelected = [];
|
|
4835
|
-
for (const f of changes.workflows.filter((x) => x.status !== "unchanged")) {
|
|
4836
|
-
const id = `wf:${f.file}`;
|
|
4837
|
-
changedItems.push({ id, name: `/${f.file.replace(".md", "")}`, tier: dim(f.status === "new" ? "new workflow" : "workflow") });
|
|
4838
|
-
changedPreSelected.push(id);
|
|
4839
|
-
}
|
|
4840
|
-
for (const f of changes.hooks.filter((x) => x.status !== "unchanged")) {
|
|
4841
|
-
const id = `hook:${f.file}`;
|
|
4842
|
-
changedItems.push({ id, name: f.file, tier: dim(f.status === "new" ? "new hook" : "hook") });
|
|
4843
|
-
changedPreSelected.push(id);
|
|
4844
|
-
}
|
|
4845
|
-
if (changes.rules === "changed") {
|
|
4846
|
-
changedItems.push({ id: "rules", name: "Global Rules", tier: dim("rules") });
|
|
4847
|
-
changedPreSelected.push("rules");
|
|
4848
|
-
}
|
|
4849
|
-
for (const f of changes.templates.filter((x) => x.status !== "unchanged")) {
|
|
4850
|
-
const id = `tmpl:${f.file}`;
|
|
4851
|
-
changedItems.push({ id, name: f.file.replace(/\\/g, "/"), tier: dim(f.status === "new" ? "new template" : "template") });
|
|
4852
|
-
changedPreSelected.push(id);
|
|
4853
|
-
}
|
|
4854
|
-
|
|
4855
|
-
if (changedItems.length > 0) {
|
|
4856
|
-
question("Select files to update");
|
|
4857
|
-
barLn();
|
|
4858
|
-
const selectedFileIds = await interactiveSelect(changedItems, {
|
|
4859
|
-
multi: true,
|
|
4860
|
-
preSelected: changedPreSelected,
|
|
4861
|
-
});
|
|
4862
|
-
if (!selectedFileIds) return;
|
|
4863
|
-
|
|
4864
|
-
// Build workflow filter Set
|
|
4865
|
-
const selectedWfFiles = selectedFileIds
|
|
4866
|
-
.filter((id) => id.startsWith("wf:"))
|
|
4867
|
-
.map((id) => id.slice(3));
|
|
4868
|
-
if (selectedWfFiles.length < changes.workflows.filter((x) => x.status !== "unchanged").length) {
|
|
4869
|
-
selectedWorkflows = new Set(selectedWfFiles);
|
|
4870
|
-
}
|
|
4871
|
-
// Check if hooks/rules/templates were deselected
|
|
4872
|
-
skipHooks = !selectedFileIds.some((id) => id.startsWith("hook:"));
|
|
4873
|
-
skipRules = !selectedFileIds.includes("rules");
|
|
4874
|
-
skipTemplates = !selectedFileIds.some((id) => id.startsWith("tmpl:"));
|
|
4875
|
-
}
|
|
4876
|
-
}
|
|
4877
|
-
// "all" = selectedWorkflows stays null, skip flags stay false
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
5141
|
+
// Fresh install — no change detection needed
|
|
5142
|
+
const selectedWorkflows = null;
|
|
5143
|
+
const skipHooks = false, skipRules = false, skipTemplates = false;
|
|
4880
5144
|
|
|
4881
5145
|
// ── Skills ──
|
|
4882
5146
|
const allSkills = findSkills(bundleDir);
|
|
@@ -5020,9 +5284,9 @@ async function main() {
|
|
|
5020
5284
|
} else if (method === "fetch") {
|
|
5021
5285
|
barLn();
|
|
5022
5286
|
const city = await textInput({ label: "City (e.g. London, Watford, Istanbul)", placeholder: "London" });
|
|
5023
|
-
if (city === null)
|
|
5287
|
+
if (city === null) return;
|
|
5024
5288
|
const country = await textInput({ label: "Country", placeholder: "United Kingdom" });
|
|
5025
|
-
if (country === null)
|
|
5289
|
+
if (country === null) return;
|
|
5026
5290
|
barLn();
|
|
5027
5291
|
|
|
5028
5292
|
if (city && country) {
|
|
@@ -5043,9 +5307,44 @@ async function main() {
|
|
|
5043
5307
|
}
|
|
5044
5308
|
}
|
|
5045
5309
|
|
|
5310
|
+
// ── Settings step — let user configure before install ──
|
|
5311
|
+
{
|
|
5312
|
+
barLn();
|
|
5313
|
+
question("Configure settings " + dim("(esc to use defaults)"));
|
|
5314
|
+
barLn();
|
|
5315
|
+
const settingsItems = [
|
|
5316
|
+
{ id: "review_day", name: "review_day Sunday Weekly review day" },
|
|
5317
|
+
{ id: "track_food", name: "track_food on Track food in daily notes" },
|
|
5318
|
+
{ id: "track_sleep", name: "track_sleep on Track sleep in daily notes" },
|
|
5319
|
+
{ id: "friction_level", name: "friction_level 3 Max friction level (1-4)" },
|
|
5320
|
+
];
|
|
5321
|
+
const settingsPick = await interactiveSelect(settingsItems);
|
|
5322
|
+
if (settingsPick) {
|
|
5323
|
+
const meta = KNOWN_SETTINGS[settingsPick];
|
|
5324
|
+
if (meta) {
|
|
5325
|
+
const cfgPath = path.join(os.homedir(), ".mover", "config.json");
|
|
5326
|
+
let cfg = {};
|
|
5327
|
+
if (fs.existsSync(cfgPath)) { try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch {} }
|
|
5328
|
+
if (!cfg.settings) cfg.settings = {};
|
|
5329
|
+
if (meta.type === "boolean") {
|
|
5330
|
+
cfg.settings[settingsPick] = !(cfg.settings[settingsPick] !== undefined ? cfg.settings[settingsPick] : meta.defaults);
|
|
5331
|
+
statusLine("ok", settingsPick, cfg.settings[settingsPick] ? "on" : "off");
|
|
5332
|
+
} else {
|
|
5333
|
+
const answer = await textInput({ label: settingsPick, initial: String(meta.defaults) });
|
|
5334
|
+
if (answer !== null && answer.trim() !== "") {
|
|
5335
|
+
cfg.settings[settingsPick] = meta.type === "number" ? parseInt(answer.trim(), 10) : answer.trim();
|
|
5336
|
+
statusLine("ok", settingsPick, JSON.stringify(cfg.settings[settingsPick]));
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
5340
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5046
5345
|
// ── Install with animated spinners ──
|
|
5047
5346
|
barLn();
|
|
5048
|
-
question(
|
|
5347
|
+
question(bold("Installing..."));
|
|
5049
5348
|
barLn();
|
|
5050
5349
|
|
|
5051
5350
|
let totalSteps = 0;
|
|
@@ -5077,17 +5376,15 @@ async function main() {
|
|
|
5077
5376
|
|
|
5078
5377
|
// 2. Template files (runs in both modes — only creates missing files, never overwrites)
|
|
5079
5378
|
if (!skipTemplates) {
|
|
5080
|
-
sp = spinner(
|
|
5379
|
+
sp = spinner("Engine templates");
|
|
5081
5380
|
const templatesInstalled = installTemplateFiles(bundleDir, vaultPath);
|
|
5082
5381
|
await sleep(200);
|
|
5083
|
-
sp.stop(
|
|
5084
|
-
? `New template files${templatesInstalled > 0 ? dim(` ${templatesInstalled} added`) : dim(" all present")}`
|
|
5085
|
-
: `Engine templates${templatesInstalled > 0 ? dim(` ${templatesInstalled} files`) : dim(" up to date")}`);
|
|
5382
|
+
sp.stop(`Engine templates${templatesInstalled > 0 ? dim(` ${templatesInstalled} files`) : dim(" up to date")}`);
|
|
5086
5383
|
totalSteps++;
|
|
5087
5384
|
}
|
|
5088
5385
|
|
|
5089
|
-
// 3. CLAUDE.md
|
|
5090
|
-
|
|
5386
|
+
// 3. CLAUDE.md
|
|
5387
|
+
{
|
|
5091
5388
|
const vaultClaudeMd = path.join(vaultPath, "CLAUDE.md");
|
|
5092
5389
|
const bundleClaudeMd = path.join(bundleDir, "CLAUDE.md");
|
|
5093
5390
|
if (!fs.existsSync(vaultClaudeMd) && fs.existsSync(bundleClaudeMd)) {
|
|
@@ -5150,8 +5447,8 @@ async function main() {
|
|
|
5150
5447
|
}
|
|
5151
5448
|
}
|
|
5152
5449
|
|
|
5153
|
-
// 7. Git init Engine folder
|
|
5154
|
-
|
|
5450
|
+
// 7. Git init Engine folder
|
|
5451
|
+
{
|
|
5155
5452
|
const hasGit = cmdExists("git");
|
|
5156
5453
|
if (hasGit) {
|
|
5157
5454
|
const engineDir = path.join(vaultPath, "02_Areas", "Engine");
|
|
@@ -5177,9 +5474,9 @@ async function main() {
|
|
|
5177
5474
|
}
|
|
5178
5475
|
}
|
|
5179
5476
|
|
|
5180
|
-
// 8. Version stamp
|
|
5181
|
-
|
|
5182
|
-
fs.writeFileSync(path.join(vaultPath, ".mover-version"),
|
|
5477
|
+
// 8. Version stamp
|
|
5478
|
+
{
|
|
5479
|
+
fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
|
|
5183
5480
|
}
|
|
5184
5481
|
|
|
5185
5482
|
// 9. Write ~/.mover/config.json (both fresh + update)
|
|
@@ -5189,8 +5486,7 @@ async function main() {
|
|
|
5189
5486
|
|
|
5190
5487
|
// ── Done ──
|
|
5191
5488
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
5192
|
-
|
|
5193
|
-
outro(`${green("Done.")} Mover OS v${VERSION} ${verb}. ${dim(`${totalSteps} steps in ${elapsed}s`)}`);
|
|
5489
|
+
outro(`${green("Done.")} Mover OS v${VERSION} installed. ${dim(`${totalSteps} steps in ${elapsed}s`)}`);
|
|
5194
5490
|
|
|
5195
5491
|
// Size check on installed rules files
|
|
5196
5492
|
const rulesFile = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
@@ -5225,30 +5521,22 @@ async function main() {
|
|
|
5225
5521
|
ln();
|
|
5226
5522
|
ln(` ${bold("Next steps")}`);
|
|
5227
5523
|
ln();
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
}
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
ln(` ${cyan("4")} Run ${bold("/setup")}`);
|
|
5245
|
-
ln(` ${dim("Builds your Identity, Strategy, and Goals")}`);
|
|
5246
|
-
ln();
|
|
5247
|
-
ln(gray(" ─────────────────────────────────────────────"));
|
|
5248
|
-
ln();
|
|
5249
|
-
ln(` ${dim("Obsidian = view your files")}`);
|
|
5250
|
-
ln(` ${dim("Your AI agent = where you work")}`);
|
|
5251
|
-
}
|
|
5524
|
+
ln(` ${cyan("1")} Open your vault in ${bold("Obsidian")}`);
|
|
5525
|
+
ln(` ${dim("This is where you view and browse your files")}`);
|
|
5526
|
+
ln();
|
|
5527
|
+
ln(` ${cyan("2")} Open the vault folder in your AI agent`);
|
|
5528
|
+
ln(` ${dim("Installed: " + agentNames.join(", "))}`);
|
|
5529
|
+
ln();
|
|
5530
|
+
ln(` ${cyan("3")} Enable the Obsidian theme`);
|
|
5531
|
+
ln(` ${dim("Settings → Appearance → CSS snippets → minimal-theme")}`);
|
|
5532
|
+
ln();
|
|
5533
|
+
ln(` ${cyan("4")} Run ${bold("/setup")}`);
|
|
5534
|
+
ln(` ${dim("Builds your Identity, Strategy, and Goals")}`);
|
|
5535
|
+
ln();
|
|
5536
|
+
ln(gray(" ─────────────────────────────────────────────"));
|
|
5537
|
+
ln();
|
|
5538
|
+
ln(` ${dim("Obsidian = view your files")}`);
|
|
5539
|
+
ln(` ${dim("Your AI agent = where you work")}`);
|
|
5252
5540
|
ln();
|
|
5253
5541
|
ln(` ${dim("/morning → [work] → /log → /analyse-day → /plan-tomorrow")}`);
|
|
5254
5542
|
ln();
|
|
@@ -5259,8 +5547,7 @@ async function main() {
|
|
|
5259
5547
|
ln(` ${green("▸")} ${bold("moveros pulse")} ${dim("Dashboard — energy, tasks, streaks")}`);
|
|
5260
5548
|
ln(` ${green("▸")} ${bold("moveros doctor")} ${dim("Health check across all agents")}`);
|
|
5261
5549
|
ln(` ${green("▸")} ${bold("moveros capture")} ${dim("Quick inbox — tasks, links, ideas")}`);
|
|
5262
|
-
ln(` ${green("▸")} ${bold("moveros
|
|
5263
|
-
ln(` ${green("▸")} ${bold("moveros sync")} ${dim("Update all agents to latest")}`);
|
|
5550
|
+
ln(` ${green("▸")} ${bold("moveros update")} ${dim("Update agents, rules, and skills")}`);
|
|
5264
5551
|
ln(` ${green("▸")} ${bold("moveros")} ${dim("Full menu with all commands")}`);
|
|
5265
5552
|
ln();
|
|
5266
5553
|
}
|