mover-os 4.6.2 → 4.6.4
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 +19 -17
- package/install.js +310 -131
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Mover OS turns Obsidian into an AI-powered execution engine. It audits your behavior against your stated strategy, extracts reusable knowledge from daily work, and evolves based on your corrections.
|
|
8
8
|
|
|
9
|
-
**Works best with Claude Code.** Supports
|
|
9
|
+
**Works best with Claude Code.** Supports 15 AI coding agents.
|
|
10
10
|
|
|
11
11
|
**Version:** 4.3 | **Status:** Production
|
|
12
12
|
|
|
@@ -91,22 +91,24 @@ bash src/install/link.sh
|
|
|
91
91
|
|
|
92
92
|
## Supported Agents
|
|
93
93
|
|
|
94
|
-
Works with **
|
|
94
|
+
Works with **15 AI coding agents.** The installer auto-detects and configures each one:
|
|
95
95
|
|
|
96
96
|
| Agent | What Gets Installed |
|
|
97
97
|
|-------|---------------------|
|
|
98
|
-
| Claude Code | Rules +
|
|
99
|
-
| Cursor | Rules +
|
|
100
|
-
| Cline | Rules +
|
|
101
|
-
| Windsurf | Rules +
|
|
102
|
-
| Gemini CLI | Rules +
|
|
103
|
-
| GitHub Copilot | Rules +
|
|
104
|
-
| Codex
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
| Amp | Rules +
|
|
98
|
+
| Claude Code | Rules + 23 commands + 62 skills + 18 hooks |
|
|
99
|
+
| Cursor | Rules + 23 commands + 62 skills |
|
|
100
|
+
| Cline | Rules + 62 skills |
|
|
101
|
+
| Windsurf | Rules + 62 skills |
|
|
102
|
+
| Gemini CLI | Rules + 23 workflows + 62 skills |
|
|
103
|
+
| GitHub Copilot | Rules + 62 skills |
|
|
104
|
+
| Codex | Rules + 62 skills |
|
|
105
|
+
| Antigravity | Rules + 23 workflows |
|
|
106
|
+
| Amazon Q | Rules + 62 skills |
|
|
107
|
+
| OpenCode | Rules + 62 skills |
|
|
108
|
+
| Kilo Code | Rules + 62 skills |
|
|
109
|
+
| Amp | Rules + 62 skills |
|
|
110
|
+
| Roo Code | Rules + 62 skills |
|
|
111
|
+
| Continue.dev | Rules + 62 skills |
|
|
110
112
|
| Aider | Rules |
|
|
111
113
|
|
|
112
114
|
> Claude Code gets the deepest integration — hooks, native slash commands, and the full correction lifecycle. Every other agent gets the core system.
|
|
@@ -196,10 +198,10 @@ Your `02_Areas/Engine/` folder is the brain. Core files that the AI reads before
|
|
|
196
198
|
```
|
|
197
199
|
Mover OS Bundle/
|
|
198
200
|
src/
|
|
199
|
-
workflows/ #
|
|
201
|
+
workflows/ # 23 AI command handlers (markdown)
|
|
200
202
|
system/ # Global Rules + Install Manifest
|
|
201
|
-
hooks/ #
|
|
202
|
-
skills/ #
|
|
203
|
+
hooks/ # 18 hook files (11 registered in settings.json)
|
|
204
|
+
skills/ # 62 curated skill packs
|
|
203
205
|
install/ # link.sh (hard-link re-linker)
|
|
204
206
|
structure/ # Vault template (PARA + Engine)
|
|
205
207
|
```
|
package/install.js
CHANGED
|
@@ -17,6 +17,39 @@ const { execSync } = require("child_process");
|
|
|
17
17
|
|
|
18
18
|
const VERSION = "4";
|
|
19
19
|
|
|
20
|
+
// ─── JSON output helper ──────────────────────────────────────────────────────
|
|
21
|
+
function jsonOut(command, data, ok = true) {
|
|
22
|
+
const envelope = {
|
|
23
|
+
ok,
|
|
24
|
+
command,
|
|
25
|
+
version: require("./package.json").version,
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
data,
|
|
28
|
+
};
|
|
29
|
+
if (!ok && data.error) envelope.error = data.error;
|
|
30
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Output budget (30K chars = Anthropic bash tool truncation limit) ────────
|
|
34
|
+
const MAX_OUTPUT_CHARS = 28000; // Leave 2K buffer below 30K limit
|
|
35
|
+
let _outputCharCount = 0;
|
|
36
|
+
const _origWrite = process.stdout.write.bind(process.stdout);
|
|
37
|
+
if (!IS_TTY) {
|
|
38
|
+
// In non-TTY (agent) mode, track output size and warn before truncation
|
|
39
|
+
process.stdout.write = function(chunk, ...args) {
|
|
40
|
+
const str = typeof chunk === "string" ? chunk : chunk.toString();
|
|
41
|
+
_outputCharCount += str.length;
|
|
42
|
+
if (_outputCharCount > MAX_OUTPUT_CHARS && !_outputCharCount._warned) {
|
|
43
|
+
_origWrite("\n... output truncated (approaching 30K char limit) ...\n");
|
|
44
|
+
_outputCharCount._warned = true;
|
|
45
|
+
}
|
|
46
|
+
return _origWrite(chunk, ...args);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Exit codes (semantic, for agent parsing) ────────────────────────────────
|
|
51
|
+
const EXIT = { OK: 0, ERROR: 1, USAGE: 2, NOT_FOUND: 3, PERMISSION: 4, CONFLICT: 5, NETWORK: 6 };
|
|
52
|
+
|
|
20
53
|
// ─── ANSI ────────────────────────────────────────────────────────────────────
|
|
21
54
|
const IS_TTY = process.stdout.isTTY && process.stdin.isTTY;
|
|
22
55
|
const S = IS_TTY
|
|
@@ -342,6 +375,25 @@ async function shimmerText(text, duration = 400) {
|
|
|
342
375
|
w(`\r\x1b[2K ${text}\n`);
|
|
343
376
|
}
|
|
344
377
|
|
|
378
|
+
async function installProgress(steps) {
|
|
379
|
+
const total = steps.length;
|
|
380
|
+
for (let i = 0; i < total; i++) {
|
|
381
|
+
const step = steps[i];
|
|
382
|
+
w(`\x1b[2K\r ${progressBar(i, total, 30)} ${S.dim}${step.label}${S.reset}`);
|
|
383
|
+
await step.fn();
|
|
384
|
+
w(`\x1b[2K\r ${progressBar(i + 1, total, 30)} ${S.green}\u2713${S.reset} ${step.label}\n`);
|
|
385
|
+
await sleep(60);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function successAnimation(message) {
|
|
390
|
+
if (!IS_TTY) { ln(`\n ${message}\n`); return; }
|
|
391
|
+
ln();
|
|
392
|
+
await shimmerText(green(" \u2713"), 150);
|
|
393
|
+
await shimmerText(bold(message), 300);
|
|
394
|
+
ln();
|
|
395
|
+
}
|
|
396
|
+
|
|
345
397
|
// ─── TUI: waitForEsc ────────────────────────────────────────────────────────
|
|
346
398
|
function waitForEsc() {
|
|
347
399
|
return new Promise((r) => {
|
|
@@ -363,37 +415,6 @@ function waitForEsc() {
|
|
|
363
415
|
});
|
|
364
416
|
}
|
|
365
417
|
|
|
366
|
-
// ─── TUI: Animated progress for install/update ──────────────────────────────
|
|
367
|
-
async function installProgress(steps) {
|
|
368
|
-
const total = steps.length;
|
|
369
|
-
for (let i = 0; i < total; i++) {
|
|
370
|
-
const step = steps[i];
|
|
371
|
-
w(`\x1b[2K\r ${progressBar(i, total, 30)} ${S.dim}${step.label}${S.reset}`);
|
|
372
|
-
await step.fn();
|
|
373
|
-
w(`\x1b[2K\r ${progressBar(i + 1, total, 30)} ${S.green}\u2713${S.reset} ${step.label}\n`);
|
|
374
|
-
await sleep(60);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// ─── TUI: Success animation ─────────────────────────────────────────────────
|
|
379
|
-
async function successAnimation(message) {
|
|
380
|
-
if (!IS_TTY) { ln(message); return; }
|
|
381
|
-
const check = [
|
|
382
|
-
" \u2713",
|
|
383
|
-
" \u2713",
|
|
384
|
-
" \u2713",
|
|
385
|
-
" \u2713 \u2713",
|
|
386
|
-
" \u2713",
|
|
387
|
-
];
|
|
388
|
-
ln();
|
|
389
|
-
for (const line of check) {
|
|
390
|
-
await shimmerText(green(line), 200);
|
|
391
|
-
}
|
|
392
|
-
ln();
|
|
393
|
-
await shimmerText(bold(message), 400);
|
|
394
|
-
ln();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
418
|
// ─── Clack-style frame ──────────────────────────────────────────────────────
|
|
398
419
|
const BAR_COLOR = S.cyan;
|
|
399
420
|
const bar = () => w(`${BAR_COLOR}│${S.reset}`);
|
|
@@ -583,8 +604,8 @@ function interactiveSelect(items, { multi = false, preSelected = [], defaultInde
|
|
|
583
604
|
lines++;
|
|
584
605
|
|
|
585
606
|
const hint = multi
|
|
586
|
-
? dim(
|
|
587
|
-
: dim(" ↑↓
|
|
607
|
+
? dim(` ↑↓ move space toggle a all enter done esc cancel${selected.size > 0 ? ` (${selected.size} selected)` : ""}`)
|
|
608
|
+
: dim(" ↑↓ move enter select esc cancel");
|
|
588
609
|
w(`\x1b[2K${BAR_COLOR}│${S.reset}${hint}\n`);
|
|
589
610
|
lines++;
|
|
590
611
|
|
|
@@ -658,7 +679,7 @@ function interactiveSelect(items, { multi = false, preSelected = [], defaultInde
|
|
|
658
679
|
// No hardcoded keys — every key must be a real Polar license.
|
|
659
680
|
|
|
660
681
|
async function validateKey(key) {
|
|
661
|
-
if (!key) return false;
|
|
682
|
+
if (!key) return { valid: false, reason: "empty" };
|
|
662
683
|
const k = key.trim();
|
|
663
684
|
|
|
664
685
|
// Polar license key validation
|
|
@@ -679,15 +700,19 @@ async function validateKey(key) {
|
|
|
679
700
|
try { resolve(JSON.parse(data)); } catch { reject(new Error("Invalid response")); }
|
|
680
701
|
});
|
|
681
702
|
});
|
|
682
|
-
req.on("error", reject);
|
|
683
|
-
req.on("timeout", () => { req.destroy(); reject(new Error("Timeout")); });
|
|
703
|
+
req.on("error", (err) => reject(Object.assign(err, { _isNetwork: true })));
|
|
704
|
+
req.on("timeout", () => { req.destroy(); reject(Object.assign(new Error("Timeout"), { _isNetwork: true })); });
|
|
684
705
|
req.write(body);
|
|
685
706
|
req.end();
|
|
686
707
|
});
|
|
687
|
-
return result.status === "granted"
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
708
|
+
return result.status === "granted"
|
|
709
|
+
? { valid: true }
|
|
710
|
+
: { valid: false, reason: "rejected" };
|
|
711
|
+
} catch (err) {
|
|
712
|
+
if (err._isNetwork || err.code === "ENOTFOUND" || err.code === "ECONNREFUSED" || err.code === "ETIMEDOUT") {
|
|
713
|
+
return { valid: false, reason: "network", error: err.message };
|
|
714
|
+
}
|
|
715
|
+
return { valid: false, reason: "rejected" };
|
|
691
716
|
}
|
|
692
717
|
}
|
|
693
718
|
|
|
@@ -877,6 +902,7 @@ const CLI_COMMANDS = {
|
|
|
877
902
|
backup: { desc: "Manual backup wizard", alias: [] },
|
|
878
903
|
restore: { desc: "Restore from backup", alias: [] },
|
|
879
904
|
prayer: { desc: "Manage prayer times", alias: [] },
|
|
905
|
+
status: { desc: "Full system state (vault, agents, version)", alias: [] },
|
|
880
906
|
help: { desc: "Interactive guide to Mover OS", alias: ["-h"] },
|
|
881
907
|
test: { desc: "Run integration tests (dev)", alias: [], hidden: true },
|
|
882
908
|
};
|
|
@@ -893,6 +919,10 @@ function parseArgs() {
|
|
|
893
919
|
// Backward compat: --update / -u → command 'update'
|
|
894
920
|
if (a === "--update" || a === "-u") { opts.command = "update"; continue; }
|
|
895
921
|
if (a === "--_self-updated") { opts._selfUpdated = true; continue; }
|
|
922
|
+
if (a === "--yes" || a === "-y") { opts.yes = true; continue; }
|
|
923
|
+
if (a === "--json") { opts.json = true; continue; }
|
|
924
|
+
if (a === "--quiet" || a === "-q") { opts.quiet = true; continue; }
|
|
925
|
+
if (a === "--dry-run") { opts.dryRun = true; continue; }
|
|
896
926
|
if (a === "--help" || a === "-h") {
|
|
897
927
|
ln();
|
|
898
928
|
ln(` ${bold("moveros")} ${dim("— the Mover OS companion CLI")}`);
|
|
@@ -908,6 +938,10 @@ function parseArgs() {
|
|
|
908
938
|
ln(` ${dim("Options")}`);
|
|
909
939
|
ln(` --key KEY License key (skip interactive prompt)`);
|
|
910
940
|
ln(` --vault PATH Obsidian vault path (skip detection)`);
|
|
941
|
+
ln(` --yes, -y Accept defaults (non-interactive mode)`);
|
|
942
|
+
ln(` --json Structured JSON output (for status command)`);
|
|
943
|
+
ln(` --quiet, -q Minimal output, one value per line`);
|
|
944
|
+
ln(` --dry-run Show what would change without doing it`);
|
|
911
945
|
ln(` --update, -u Quick update (backward compat)`);
|
|
912
946
|
ln();
|
|
913
947
|
ln(` ${dim("Run")} moveros ${dim("with no args for interactive menu")}`);
|
|
@@ -2401,9 +2435,8 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey, opts = {}) {
|
|
|
2401
2435
|
vaultPath: vaultPath,
|
|
2402
2436
|
agents: agentIds,
|
|
2403
2437
|
feedbackWebhook: "https://moveros.dev/api/feedback",
|
|
2404
|
-
track_food: true,
|
|
2405
|
-
track_sleep: true,
|
|
2406
2438
|
installedAt: new Date().toISOString(),
|
|
2439
|
+
settings: { track_food: true, track_sleep: true, friction_level: 3, review_day: "Sunday" },
|
|
2407
2440
|
};
|
|
2408
2441
|
if (licenseKey) config.licenseKey = licenseKey;
|
|
2409
2442
|
// If config exists, preserve existing values
|
|
@@ -2413,10 +2446,15 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey, opts = {}) {
|
|
|
2413
2446
|
if (existing.installedAt) config.installedAt = existing.installedAt;
|
|
2414
2447
|
if (existing.licenseKey && !licenseKey) config.licenseKey = existing.licenseKey;
|
|
2415
2448
|
if (existing.feedbackWebhook) config.feedbackWebhook = existing.feedbackWebhook;
|
|
2416
|
-
|
|
2417
|
-
if (existing.
|
|
2449
|
+
// Migrate root-level track_food/track_sleep to settings block (legacy compat)
|
|
2450
|
+
if (existing.track_food !== undefined && !existing.settings?.track_food) {
|
|
2451
|
+
config.settings.track_food = existing.track_food;
|
|
2452
|
+
}
|
|
2453
|
+
if (existing.track_sleep !== undefined && !existing.settings?.track_sleep) {
|
|
2454
|
+
config.settings.track_sleep = existing.track_sleep;
|
|
2455
|
+
}
|
|
2418
2456
|
// Preserve settings block (prayer times, review_day, etc.)
|
|
2419
|
-
if (existing.settings) config.settings = { ...existing.settings };
|
|
2457
|
+
if (existing.settings) config.settings = { ...config.settings, ...existing.settings };
|
|
2420
2458
|
// Preserve prayer_times fallback
|
|
2421
2459
|
if (existing.prayer_times) config.prayer_times = existing.prayer_times;
|
|
2422
2460
|
// Preserve frecency data
|
|
@@ -3300,6 +3338,7 @@ const CLI_HANDLERS = {
|
|
|
3300
3338
|
settings: async (opts) => { await cmdSettings(opts); },
|
|
3301
3339
|
backup: async (opts) => { await cmdBackup(opts); },
|
|
3302
3340
|
restore: async (opts) => { await cmdRestore(opts); },
|
|
3341
|
+
status: async (opts) => { await cmdStatus(opts); },
|
|
3303
3342
|
doctor: async (opts) => { await cmdDoctor(opts); },
|
|
3304
3343
|
prayer: async (opts) => { await cmdPrayer(opts); },
|
|
3305
3344
|
help: async (opts) => { await cmdHelp(opts); },
|
|
@@ -3307,11 +3346,54 @@ const CLI_HANDLERS = {
|
|
|
3307
3346
|
test: async (opts) => { await cmdTest(opts); },
|
|
3308
3347
|
};
|
|
3309
3348
|
|
|
3349
|
+
// ─── moveros status ──────────────────────────────────────────────────────────
|
|
3350
|
+
async function cmdStatus(opts) {
|
|
3351
|
+
const vault = requireVault(opts.vault);
|
|
3352
|
+
if (!vault) return;
|
|
3353
|
+
const home = os.homedir();
|
|
3354
|
+
const cfgPath = path.join(home, ".mover", "config.json");
|
|
3355
|
+
let cfg = {};
|
|
3356
|
+
if (fs.existsSync(cfgPath)) { try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch {} }
|
|
3357
|
+
const agents = cfg.agents || [];
|
|
3358
|
+
const ver = fs.existsSync(path.join(vault, ".mover-version")) ? fs.readFileSync(path.join(vault, ".mover-version"), "utf8").trim() : "unknown";
|
|
3359
|
+
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3360
|
+
const engineFiles = ["Identity_Prime.md", "Strategy.md", "Active_Context.md", "Goals.md", "Mover_Dossier.md", "Auto_Learnings.md"];
|
|
3361
|
+
const engineOk = engineFiles.filter((f) => fs.existsSync(path.join(engineDir, f))).length;
|
|
3362
|
+
|
|
3363
|
+
const data = {
|
|
3364
|
+
vault: vault,
|
|
3365
|
+
version: ver,
|
|
3366
|
+
agents: agents,
|
|
3367
|
+
agentCount: agents.length,
|
|
3368
|
+
engineFiles: { present: engineOk, total: engineFiles.length },
|
|
3369
|
+
settings: cfg.settings || {},
|
|
3370
|
+
licenseKey: cfg.licenseKey ? cfg.licenseKey.substring(0, 12) + "..." : null,
|
|
3371
|
+
hasManifest: fs.existsSync(path.join(home, ".mover", "manifest.json")),
|
|
3372
|
+
};
|
|
3373
|
+
|
|
3374
|
+
if (opts.json) {
|
|
3375
|
+
jsonOut("status", data);
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
barLn(bold(" System Status"));
|
|
3380
|
+
barLn();
|
|
3381
|
+
barLn(` ${bold("Vault:")} ${vault}`);
|
|
3382
|
+
barLn(` ${bold("Version:")} ${ver}`);
|
|
3383
|
+
barLn(` ${bold("Agents:")} ${agents.length} (${agents.join(", ")})`);
|
|
3384
|
+
barLn(` ${bold("Engine:")} ${engineOk}/${engineFiles.length} files present`);
|
|
3385
|
+
barLn(` ${bold("License:")} ${data.licenseKey || dim("not set")}`);
|
|
3386
|
+
barLn(` ${bold("Manifest:")} ${data.hasManifest ? green("present") : yellow("missing (legacy install)")}`);
|
|
3387
|
+
barLn(` ${bold("Settings:")} friction=${cfg.settings?.friction_level || 3}, review=${cfg.settings?.review_day || "Sunday"}, food=${cfg.settings?.track_food !== false ? "on" : "off"}, sleep=${cfg.settings?.track_sleep !== false ? "on" : "off"}`);
|
|
3388
|
+
barLn();
|
|
3389
|
+
barLn(dim(" Run moveros doctor for detailed health check."));
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3310
3392
|
// ─── moveros doctor ─────────────────────────────────────────────────────────
|
|
3311
3393
|
async function cmdDoctor(opts) {
|
|
3312
3394
|
const vault = resolveVaultPath(opts.vault);
|
|
3313
3395
|
if (!vault) {
|
|
3314
|
-
|
|
3396
|
+
// handled by requireVault()
|
|
3315
3397
|
return;
|
|
3316
3398
|
}
|
|
3317
3399
|
const home = os.homedir();
|
|
@@ -3395,13 +3477,16 @@ async function cmdDoctor(opts) {
|
|
|
3395
3477
|
|
|
3396
3478
|
barLn();
|
|
3397
3479
|
barLn(dim(" Run moveros install or moveros update to fix any issues."));
|
|
3480
|
+
|
|
3481
|
+
barLn();
|
|
3482
|
+
barLn(dim(" Fix any issues above, then run /mover-check in your AI agent for deeper validation."));
|
|
3398
3483
|
}
|
|
3399
3484
|
|
|
3400
3485
|
// ─── moveros pulse ──────────────────────────────────────────────────────────
|
|
3401
3486
|
async function cmdPulse(opts) {
|
|
3402
3487
|
const vault = resolveVaultPath(opts.vault);
|
|
3403
3488
|
if (!vault) {
|
|
3404
|
-
|
|
3489
|
+
// handled by requireVault()
|
|
3405
3490
|
return;
|
|
3406
3491
|
}
|
|
3407
3492
|
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
@@ -3507,14 +3592,16 @@ async function cmdPulse(opts) {
|
|
|
3507
3592
|
} catch {}
|
|
3508
3593
|
}
|
|
3509
3594
|
barLn();
|
|
3595
|
+
|
|
3596
|
+
barLn();
|
|
3597
|
+
barLn(dim(" Run /morning for full session primer or moveros capture for quick capture."));
|
|
3510
3598
|
}
|
|
3511
3599
|
|
|
3512
3600
|
// cmdWarm removed — hooks + rules + /morning handle session priming
|
|
3513
3601
|
|
|
3514
3602
|
// ─── moveros capture ────────────────────────────────────────────────────────
|
|
3515
3603
|
async function cmdCapture(opts) {
|
|
3516
|
-
const vault =
|
|
3517
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3604
|
+
const vault = requireVault(opts.vault);
|
|
3518
3605
|
|
|
3519
3606
|
const now = new Date();
|
|
3520
3607
|
const ymd = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
@@ -3590,8 +3677,7 @@ async function cmdCapture(opts) {
|
|
|
3590
3677
|
|
|
3591
3678
|
// ─── moveros who ────────────────────────────────────────────────────────────
|
|
3592
3679
|
async function cmdWho(opts) {
|
|
3593
|
-
const vault =
|
|
3594
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3680
|
+
const vault = requireVault(opts.vault);
|
|
3595
3681
|
|
|
3596
3682
|
const name = opts.rest.join(" ").trim();
|
|
3597
3683
|
if (!name) { barLn(yellow("Usage: moveros who <name>")); return; }
|
|
@@ -3638,12 +3724,14 @@ async function cmdWho(opts) {
|
|
|
3638
3724
|
for (const l of lines) barLn(` ${l}`);
|
|
3639
3725
|
barLn();
|
|
3640
3726
|
}
|
|
3727
|
+
|
|
3728
|
+
barLn();
|
|
3729
|
+
barLn(dim(" Entities live in 03_Library/Entities/. Run /harvest to extract people from sessions."));
|
|
3641
3730
|
}
|
|
3642
3731
|
|
|
3643
3732
|
// ─── moveros diff ───────────────────────────────────────────────────────────
|
|
3644
3733
|
async function cmdDiff(opts) {
|
|
3645
|
-
const vault =
|
|
3646
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3734
|
+
const vault = requireVault(opts.vault);
|
|
3647
3735
|
if (!cmdExists("git")) { barLn(red("Git required for moveros diff.")); return; }
|
|
3648
3736
|
|
|
3649
3737
|
const target = opts.rest[0] || "strategy";
|
|
@@ -3697,12 +3785,14 @@ async function cmdDiff(opts) {
|
|
|
3697
3785
|
barLn(dim(" Not a git repo or no history available."));
|
|
3698
3786
|
}
|
|
3699
3787
|
barLn();
|
|
3788
|
+
|
|
3789
|
+
barLn();
|
|
3790
|
+
barLn(dim(" Run /history in your AI agent for full Engine evolution analysis."));
|
|
3700
3791
|
}
|
|
3701
3792
|
|
|
3702
3793
|
// ─── moveros sync ───────────────────────────────────────────────────────────
|
|
3703
3794
|
async function cmdSync(opts) {
|
|
3704
|
-
const vault =
|
|
3705
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3795
|
+
const vault = requireVault(opts.vault);
|
|
3706
3796
|
const apply = opts.rest.includes("--apply");
|
|
3707
3797
|
const home = os.homedir();
|
|
3708
3798
|
|
|
@@ -3775,12 +3865,14 @@ async function cmdSync(opts) {
|
|
|
3775
3865
|
barLn(dim(` ${staleCount} agent(s) need updating. Run: moveros sync --apply`));
|
|
3776
3866
|
}
|
|
3777
3867
|
barLn();
|
|
3868
|
+
|
|
3869
|
+
barLn();
|
|
3870
|
+
barLn(dim(" Run moveros update for comprehensive update with merge support."));
|
|
3778
3871
|
}
|
|
3779
3872
|
|
|
3780
3873
|
// ─── moveros replay ─────────────────────────────────────────────────────────
|
|
3781
3874
|
async function cmdReplay(opts) {
|
|
3782
|
-
const vault =
|
|
3783
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3875
|
+
const vault = requireVault(opts.vault);
|
|
3784
3876
|
|
|
3785
3877
|
const engineDir = path.join(vault, "02_Areas", "Engine");
|
|
3786
3878
|
let dateStr = opts.rest.find((a) => a.startsWith("--date="))?.split("=")[1];
|
|
@@ -3837,12 +3929,14 @@ async function cmdReplay(opts) {
|
|
|
3837
3929
|
barLn(` ${progressBar(done, tasks.length, 25, "Completion")}`);
|
|
3838
3930
|
barLn();
|
|
3839
3931
|
}
|
|
3932
|
+
|
|
3933
|
+
barLn();
|
|
3934
|
+
barLn(dim(" Run /log in your AI agent for full session capture."));
|
|
3840
3935
|
}
|
|
3841
3936
|
|
|
3842
3937
|
// ─── moveros context ────────────────────────────────────────────────────────
|
|
3843
3938
|
async function cmdContext(opts) {
|
|
3844
|
-
const vault =
|
|
3845
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
3939
|
+
const vault = requireVault(opts.vault);
|
|
3846
3940
|
|
|
3847
3941
|
const target = opts.rest[0];
|
|
3848
3942
|
const home = os.homedir();
|
|
@@ -4230,23 +4324,57 @@ async function cmdSettings(opts) {
|
|
|
4230
4324
|
};
|
|
4231
4325
|
});
|
|
4232
4326
|
|
|
4327
|
+
items.push({ id: "_reset", name: dim("Reset all to defaults"), tier: "Resets every setting to its default value" });
|
|
4233
4328
|
question(dim(" Select a setting to edit (esc to go back)"));
|
|
4234
4329
|
barLn();
|
|
4235
4330
|
const picked = await interactiveSelect(items);
|
|
4236
4331
|
if (!picked) return;
|
|
4237
4332
|
|
|
4333
|
+
if (picked === "_reset") {
|
|
4334
|
+
cfg.settings = {};
|
|
4335
|
+
for (const [k, m] of Object.entries(KNOWN_SETTINGS)) {
|
|
4336
|
+
cfg.settings[k] = m.defaults;
|
|
4337
|
+
}
|
|
4338
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
4339
|
+
statusLine("ok", "All settings", "reset to defaults");
|
|
4340
|
+
barLn();
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4238
4344
|
const meta = KNOWN_SETTINGS[picked];
|
|
4239
4345
|
const current = settings[picked] !== undefined ? settings[picked] : meta.defaults;
|
|
4240
4346
|
|
|
4241
4347
|
if (meta.type === "boolean") {
|
|
4242
|
-
// Toggle immediately
|
|
4243
4348
|
const newVal = !current;
|
|
4244
4349
|
if (!cfg.settings) cfg.settings = {};
|
|
4245
4350
|
cfg.settings[picked] = newVal;
|
|
4246
4351
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
4247
4352
|
statusLine("ok", picked, newVal ? "on" : "off");
|
|
4353
|
+
} else if (picked === "friction_level") {
|
|
4354
|
+
const flItems = [
|
|
4355
|
+
{ id: "1", name: "1 — Gentle", tier: "Surface conflicts, easy to override" },
|
|
4356
|
+
{ id: "2", name: "2 — Moderate", tier: "Ask for justification on off-plan work" },
|
|
4357
|
+
{ id: "3", name: "3 — Firm", tier: "Hold the line, identify avoidance (recommended)" },
|
|
4358
|
+
{ id: "4", name: "4 — Hard block", tier: "Refuse destructive actions without confirmation" },
|
|
4359
|
+
];
|
|
4360
|
+
const flPick = await interactiveSelect(flItems, { defaultIndex: (current || 3) - 1 });
|
|
4361
|
+
if (flPick) {
|
|
4362
|
+
if (!cfg.settings) cfg.settings = {};
|
|
4363
|
+
cfg.settings[picked] = parseInt(flPick, 10);
|
|
4364
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
4365
|
+
statusLine("ok", picked, flPick);
|
|
4366
|
+
}
|
|
4367
|
+
} else if (picked === "review_day") {
|
|
4368
|
+
const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
4369
|
+
const dayItems = days.map((d) => ({ id: d, name: d, tier: d === "Sunday" ? "Default — end-of-week reflection" : "" }));
|
|
4370
|
+
const dayPick = await interactiveSelect(dayItems, { defaultIndex: days.indexOf(current || "Sunday") });
|
|
4371
|
+
if (dayPick) {
|
|
4372
|
+
if (!cfg.settings) cfg.settings = {};
|
|
4373
|
+
cfg.settings[picked] = dayPick;
|
|
4374
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
|
|
4375
|
+
statusLine("ok", picked, dayPick);
|
|
4376
|
+
}
|
|
4248
4377
|
} else {
|
|
4249
|
-
// Prompt for new value
|
|
4250
4378
|
const answer = await textInput({ label: `${picked}`, initial: String(current) });
|
|
4251
4379
|
if (answer === null || answer.trim() === "" || answer === String(current)) return;
|
|
4252
4380
|
let val = answer.trim();
|
|
@@ -4261,8 +4389,7 @@ async function cmdSettings(opts) {
|
|
|
4261
4389
|
|
|
4262
4390
|
// ─── moveros backup ─────────────────────────────────────────────────────────
|
|
4263
4391
|
async function cmdBackup(opts) {
|
|
4264
|
-
const vault =
|
|
4265
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
4392
|
+
const vault = requireVault(opts.vault);
|
|
4266
4393
|
const home = os.homedir();
|
|
4267
4394
|
|
|
4268
4395
|
barLn(bold(" Backup"));
|
|
@@ -4359,8 +4486,7 @@ async function cmdBackup(opts) {
|
|
|
4359
4486
|
|
|
4360
4487
|
// ─── moveros restore ────────────────────────────────────────────────────────
|
|
4361
4488
|
async function cmdRestore(opts) {
|
|
4362
|
-
const vault =
|
|
4363
|
-
if (!vault) { barLn(red("No vault found.")); return; }
|
|
4489
|
+
const vault = requireVault(opts.vault);
|
|
4364
4490
|
|
|
4365
4491
|
const archivesDir = path.join(vault, "04_Archives");
|
|
4366
4492
|
if (!fs.existsSync(archivesDir)) { barLn(yellow(" No archives found.")); return; }
|
|
@@ -4957,6 +5083,16 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
4957
5083
|
const npmVer = execSync("npm view mover-os version", { encoding: "utf8", timeout: 10000 }).trim();
|
|
4958
5084
|
if (npmVer && npmVer !== localVer && compareVersions(npmVer, localVer) > 0) {
|
|
4959
5085
|
barLn(`${yellow("CLI update available:")} ${dim(localVer)} ${dim("\u2192")} ${green(npmVer)}`);
|
|
5086
|
+
const selfUpdateChoice = IS_TTY ? await interactiveSelect(
|
|
5087
|
+
[
|
|
5088
|
+
{ id: "yes", name: "Update CLI first", tier: `Downloads v${npmVer} before proceeding` },
|
|
5089
|
+
{ id: "no", name: "Skip, continue with current", tier: `Stay on v${localVer}` },
|
|
5090
|
+
],
|
|
5091
|
+
{ multi: false, defaultIndex: 0 }
|
|
5092
|
+
) : "yes";
|
|
5093
|
+
if (selfUpdateChoice === "no" || !selfUpdateChoice) {
|
|
5094
|
+
statusLine("ok", "CLI", `${localVer} (update skipped)`);
|
|
5095
|
+
} else {
|
|
4960
5096
|
const sp = spinner("Updating CLI");
|
|
4961
5097
|
try {
|
|
4962
5098
|
try {
|
|
@@ -4979,6 +5115,7 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
4979
5115
|
sp.stop(yellow(`CLI self-update failed: ${e.message}`));
|
|
4980
5116
|
barLn(dim(" Try: sudo npm i -g mover-os"));
|
|
4981
5117
|
}
|
|
5118
|
+
} // end selfUpdateChoice === "yes"
|
|
4982
5119
|
} else {
|
|
4983
5120
|
statusLine("ok", "CLI", `up to date (${localVer})`);
|
|
4984
5121
|
}
|
|
@@ -5010,13 +5147,20 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5010
5147
|
if (!updateKey) return;
|
|
5011
5148
|
}
|
|
5012
5149
|
const sp1 = spinner("Validating license");
|
|
5013
|
-
const
|
|
5014
|
-
if (!
|
|
5150
|
+
const keyResult = await validateKey(updateKey);
|
|
5151
|
+
if (!keyResult.valid && keyResult.reason === "network" && keyFromConfig) {
|
|
5015
5152
|
sp1.stop(dim("License check skipped (offline — using stored key)"));
|
|
5016
|
-
} else if (!
|
|
5017
|
-
sp1.stop(
|
|
5018
|
-
|
|
5153
|
+
} else if (!keyResult.valid && keyResult.reason === "network") {
|
|
5154
|
+
sp1.stop(yellow("Could not connect"));
|
|
5155
|
+
barLn(yellow(" Could not reach license server. Check your internet connection and try again."));
|
|
5156
|
+
outro("Update cancelled.");
|
|
5019
5157
|
process.exit(1);
|
|
5158
|
+
} else if (!keyResult.valid) {
|
|
5159
|
+
sp1.stop(red("Key not recognized"));
|
|
5160
|
+
barLn(red(" This license key was not recognized. Check your email from Polar for the correct key."));
|
|
5161
|
+
barLn(dim(" Purchase at https://moveros.dev if you need a key."));
|
|
5162
|
+
outro("Update cancelled.");
|
|
5163
|
+
process.exit(EXIT.PERMISSION);
|
|
5020
5164
|
} else {
|
|
5021
5165
|
sp1.stop(green("License verified"));
|
|
5022
5166
|
}
|
|
@@ -5055,7 +5199,7 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5055
5199
|
|
|
5056
5200
|
// Quick mode: force-apply (CI/headless — no user customizations to protect)
|
|
5057
5201
|
if (isQuick) {
|
|
5058
|
-
if (detectedAgents.length === 0) { outro(red("No AI agents detected.")); process.exit(
|
|
5202
|
+
if (detectedAgents.length === 0) { outro(red("No AI agents detected.")); process.exit(EXIT.NOT_FOUND); }
|
|
5059
5203
|
const changes = detectChanges(bundleDir, vaultPath, selectedIds);
|
|
5060
5204
|
const totalChanged = countChanges(changes);
|
|
5061
5205
|
displayChangeSummary(changes, installedVer, newVer);
|
|
@@ -5085,7 +5229,7 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5085
5229
|
writeMoverConfig(vaultPath, selectedIds);
|
|
5086
5230
|
barLn();
|
|
5087
5231
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
5088
|
-
|
|
5232
|
+
await successAnimation(`${totalChanged} files updated in ${elapsed}s.`);
|
|
5089
5233
|
return;
|
|
5090
5234
|
}
|
|
5091
5235
|
|
|
@@ -5256,8 +5400,8 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5256
5400
|
const targets = sel ? sel.targets : [agent.id];
|
|
5257
5401
|
for (const tid of targets) {
|
|
5258
5402
|
const reg = AGENT_REGISTRY[tid];
|
|
5259
|
-
if (reg && reg.rules) {
|
|
5260
|
-
const destPath = typeof reg.rules === "function" ? reg.rules() : reg.rules;
|
|
5403
|
+
if (reg && reg.rules && reg.rules.dest) {
|
|
5404
|
+
const destPath = typeof reg.rules.dest === "function" ? reg.rules.dest(vaultPath) : reg.rules.dest;
|
|
5261
5405
|
installRules(bundleDir, destPath, tid);
|
|
5262
5406
|
}
|
|
5263
5407
|
}
|
|
@@ -5276,10 +5420,10 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5276
5420
|
const targets = sel ? sel.targets : [agent.id];
|
|
5277
5421
|
for (const tid of targets) {
|
|
5278
5422
|
const reg = AGENT_REGISTRY[tid];
|
|
5279
|
-
if (reg && reg.skills) {
|
|
5280
|
-
const destDir = typeof reg.skills === "function" ? reg.skills() : reg.skills;
|
|
5423
|
+
if (reg && reg.skills && reg.skills.dest) {
|
|
5424
|
+
const destDir = typeof reg.skills.dest === "function" ? reg.skills.dest(vaultPath) : reg.skills.dest;
|
|
5281
5425
|
if (destDir && !writtenFiles.has(destDir)) {
|
|
5282
|
-
|
|
5426
|
+
installSkillPacks(bundleDir, destDir, null);
|
|
5283
5427
|
writtenFiles.add(destDir);
|
|
5284
5428
|
}
|
|
5285
5429
|
}
|
|
@@ -5394,6 +5538,16 @@ function resolveVaultPath(explicitVault) {
|
|
|
5394
5538
|
return obsVaults.find((p) => fs.existsSync(path.join(p, ".mover-version"))) || null;
|
|
5395
5539
|
}
|
|
5396
5540
|
|
|
5541
|
+
function requireVault(explicitVault) {
|
|
5542
|
+
const v = resolveVaultPath(explicitVault);
|
|
5543
|
+
if (!v) {
|
|
5544
|
+
barLn(red(" No Mover OS vault found."));
|
|
5545
|
+
barLn(dim(" Use --vault /path or run moveros install to set up."));
|
|
5546
|
+
return null;
|
|
5547
|
+
}
|
|
5548
|
+
return v;
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5397
5551
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
5398
5552
|
async function main() {
|
|
5399
5553
|
const opts = parseArgs();
|
|
@@ -5489,6 +5643,7 @@ async function main() {
|
|
|
5489
5643
|
}
|
|
5490
5644
|
|
|
5491
5645
|
if (!key) {
|
|
5646
|
+
barLn(dim(" Purchased at moveros.dev — check your email for the key."));
|
|
5492
5647
|
let validated = false;
|
|
5493
5648
|
let attempts = 0;
|
|
5494
5649
|
while (attempts < 3) {
|
|
@@ -5500,15 +5655,21 @@ async function main() {
|
|
|
5500
5655
|
if (key === null) return;
|
|
5501
5656
|
|
|
5502
5657
|
const sp = spinner("Validating...");
|
|
5503
|
-
const
|
|
5504
|
-
if (valid) {
|
|
5658
|
+
const keyResult = await validateKey(key);
|
|
5659
|
+
if (keyResult.valid) {
|
|
5505
5660
|
sp.stop(green("License verified"));
|
|
5506
5661
|
await activateKey(key);
|
|
5507
5662
|
validated = true;
|
|
5508
5663
|
break;
|
|
5509
5664
|
}
|
|
5510
5665
|
|
|
5511
|
-
|
|
5666
|
+
if (keyResult.reason === "network") {
|
|
5667
|
+
sp.stop(yellow("Could not connect"));
|
|
5668
|
+
barLn(yellow(" Check your internet connection and try again."));
|
|
5669
|
+
} else {
|
|
5670
|
+
sp.stop(red("Key not recognized"));
|
|
5671
|
+
barLn(red(" Check your email from Polar for the correct key."));
|
|
5672
|
+
}
|
|
5512
5673
|
attempts++;
|
|
5513
5674
|
if (attempts < 3) {
|
|
5514
5675
|
barLn(red("Try again."));
|
|
@@ -5526,15 +5687,19 @@ async function main() {
|
|
|
5526
5687
|
}
|
|
5527
5688
|
} else {
|
|
5528
5689
|
const sp = spinner("Validating license...");
|
|
5529
|
-
|
|
5530
|
-
|
|
5690
|
+
const storedKeyResult = await validateKey(key);
|
|
5691
|
+
if (!storedKeyResult.valid && storedKeyResult.reason === "network") {
|
|
5692
|
+
sp.stop(dim("Offline — using stored key"));
|
|
5693
|
+
} else if (!storedKeyResult.valid) {
|
|
5694
|
+
sp.stop(red("Stored key not recognized"));
|
|
5531
5695
|
barLn();
|
|
5532
5696
|
barLn(dim("Get a key at https://moveros.dev"));
|
|
5533
5697
|
outro("Cancelled.");
|
|
5534
5698
|
process.exit(1);
|
|
5699
|
+
} else {
|
|
5700
|
+
sp.stop(green("License verified"));
|
|
5701
|
+
await activateKey(key);
|
|
5535
5702
|
}
|
|
5536
|
-
sp.stop(green("License verified"));
|
|
5537
|
-
await activateKey(key);
|
|
5538
5703
|
barLn();
|
|
5539
5704
|
}
|
|
5540
5705
|
|
|
@@ -5547,9 +5712,26 @@ async function main() {
|
|
|
5547
5712
|
dlSp.stop(green("Downloaded"));
|
|
5548
5713
|
} catch (err) {
|
|
5549
5714
|
dlSp.stop(red("Download failed"));
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5715
|
+
const msg = err.message || "";
|
|
5716
|
+
if (msg.includes("Timeout") || msg.includes("timeout")) {
|
|
5717
|
+
barLn(red(" Connection timed out."));
|
|
5718
|
+
barLn(dim(" Check your internet connection and try again."));
|
|
5719
|
+
} else if (msg.includes("401") || msg.includes("rejected")) {
|
|
5720
|
+
barLn(red(" License key rejected by server."));
|
|
5721
|
+
barLn(dim(" Has your key expired? Check polar.sh or email support@moveros.dev."));
|
|
5722
|
+
} else if (msg.includes("500") || msg.includes("502") || msg.includes("503")) {
|
|
5723
|
+
barLn(red(" Server error."));
|
|
5724
|
+
barLn(dim(" Try again in a few minutes. If it persists, email support@moveros.dev."));
|
|
5725
|
+
} else if (msg.includes("extract") || msg.includes("tar")) {
|
|
5726
|
+
barLn(red(" Failed to extract payload."));
|
|
5727
|
+
barLn(dim(" Ensure 'tar' is available. On Windows, install Git Bash."));
|
|
5728
|
+
} else if (msg.includes("Untrusted")) {
|
|
5729
|
+
barLn(red(" Unexpected download redirect."));
|
|
5730
|
+
barLn(dim(" This may be a bug. Report at github.com/azkhh/Mover-OS/issues."));
|
|
5731
|
+
} else {
|
|
5732
|
+
barLn(red(` ${msg}`));
|
|
5733
|
+
barLn(dim(" Check your internet connection and try again."));
|
|
5734
|
+
}
|
|
5553
5735
|
outro("Cancelled.");
|
|
5554
5736
|
process.exit(1);
|
|
5555
5737
|
}
|
|
@@ -5642,7 +5824,7 @@ async function main() {
|
|
|
5642
5824
|
barLn(dim("You need at least one to use Mover OS. Recommended: Claude Code (claude.ai/code)"));
|
|
5643
5825
|
barLn();
|
|
5644
5826
|
}
|
|
5645
|
-
question(`Select your AI agents${detectedIds.length > 0 ? dim(" (detected agents pre-selected)") : ""}`);
|
|
5827
|
+
question(`[1/4] Select your AI agents${detectedIds.length > 0 ? dim(" (detected agents pre-selected)") : ""}`);
|
|
5646
5828
|
barLn();
|
|
5647
5829
|
|
|
5648
5830
|
const selectedIds = await interactiveSelect(agentItems, {
|
|
@@ -5664,7 +5846,7 @@ async function main() {
|
|
|
5664
5846
|
|
|
5665
5847
|
// ── Skills ──
|
|
5666
5848
|
const allSkills = findSkills(bundleDir);
|
|
5667
|
-
let
|
|
5849
|
+
let doInstallSkills = false;
|
|
5668
5850
|
let selectedCategories = null; // null = all, Set = filtered
|
|
5669
5851
|
|
|
5670
5852
|
if (allSkills.length > 0 && selectedAgents.some((a) => a.id !== "aider")) {
|
|
@@ -5674,7 +5856,7 @@ async function main() {
|
|
|
5674
5856
|
catCounts[sk.category] = (catCounts[sk.category] || 0) + 1;
|
|
5675
5857
|
}
|
|
5676
5858
|
|
|
5677
|
-
question(
|
|
5859
|
+
question(`[2/4] ${bold(String(allSkills.length))} skill packs available. Select categories:`);
|
|
5678
5860
|
barLn();
|
|
5679
5861
|
|
|
5680
5862
|
const categoryItems = CATEGORY_META.map((c) => ({
|
|
@@ -5692,8 +5874,8 @@ async function main() {
|
|
|
5692
5874
|
});
|
|
5693
5875
|
if (!selectedCatIds) return;
|
|
5694
5876
|
|
|
5695
|
-
|
|
5696
|
-
if (
|
|
5877
|
+
doInstallSkills = selectedCatIds.length > 0;
|
|
5878
|
+
if (doInstallSkills) {
|
|
5697
5879
|
selectedCategories = new Set(selectedCatIds);
|
|
5698
5880
|
// Count how many skills will be installed (selected categories + tools)
|
|
5699
5881
|
const skillCount = allSkills.filter((s) => s.category === "tools" || selectedCategories.has(s.category)).length;
|
|
@@ -5705,7 +5887,7 @@ async function main() {
|
|
|
5705
5887
|
let installStatusLine = false;
|
|
5706
5888
|
if (selectedIds.includes("claude-code")) {
|
|
5707
5889
|
barLn();
|
|
5708
|
-
question("Install Claude Code status line?");
|
|
5890
|
+
question("[3/4] Install Claude Code status line?");
|
|
5709
5891
|
barLn(dim(" Live status bar with model, context %, project, session cost, and Mover OS data."));
|
|
5710
5892
|
barLn(dim(" Example: Opus 4.6 · 24% · my-project (main) · 2h14m · $12.50"));
|
|
5711
5893
|
barLn(dim(" ▸ next task · 2/5 done · Sleep by 22:00 · logged 30m ago"));
|
|
@@ -5722,24 +5904,10 @@ async function main() {
|
|
|
5722
5904
|
installStatusLine = slChoice === "yes";
|
|
5723
5905
|
}
|
|
5724
5906
|
|
|
5725
|
-
// ── Prayer times (
|
|
5907
|
+
// ── Prayer times — moved out of install flow (available via: moveros prayer) ──
|
|
5726
5908
|
let prayerSetup = false;
|
|
5727
|
-
{
|
|
5728
|
-
|
|
5729
|
-
question("Would you like prayer time reminders?");
|
|
5730
|
-
barLn(dim(" Shows next prayer time in the status line and Daily Notes."));
|
|
5731
|
-
barLn(dim(" Designed for Muslims — skip if not relevant to you."));
|
|
5732
|
-
barLn();
|
|
5733
|
-
|
|
5734
|
-
const ptChoice = await interactiveSelect(
|
|
5735
|
-
[
|
|
5736
|
-
{ id: "yes", name: "Yes, set up prayer times", tier: "You can paste your mosque's timetable or fetch by city" },
|
|
5737
|
-
{ id: "no", name: "No, skip", tier: "You can enable later with: moveros prayer" },
|
|
5738
|
-
],
|
|
5739
|
-
{ multi: false, defaultIndex: 1 }
|
|
5740
|
-
);
|
|
5741
|
-
if (!ptChoice || ptChoice === "no") { /* skip prayer setup */ }
|
|
5742
|
-
else if (ptChoice === "yes") {
|
|
5909
|
+
if (false) { // Prayer setup deferred to post-install: moveros prayer
|
|
5910
|
+
{
|
|
5743
5911
|
prayerSetup = true;
|
|
5744
5912
|
barLn();
|
|
5745
5913
|
question("How would you like to set up prayer times?");
|
|
@@ -5840,13 +6008,13 @@ async function main() {
|
|
|
5840
6008
|
let confirmed = false;
|
|
5841
6009
|
while (!confirmed) {
|
|
5842
6010
|
barLn();
|
|
5843
|
-
question(bold("Review your selections") + dim(" (enter to change, esc to cancel)"));
|
|
6011
|
+
question(bold("[4/4] Review your selections") + dim(" (enter to change, esc to cancel)"));
|
|
5844
6012
|
barLn();
|
|
5845
6013
|
|
|
5846
6014
|
const agentNames = selectedAgents.map((a) => a.name).join(", ");
|
|
5847
|
-
const skillsLabel =
|
|
6015
|
+
const skillsLabel = doInstallSkills && selectedCategories
|
|
5848
6016
|
? [...selectedCategories].join(", ")
|
|
5849
|
-
:
|
|
6017
|
+
: doInstallSkills ? "all categories" : "none";
|
|
5850
6018
|
const slLabel = selectedIds.includes("claude-code") ? (installStatusLine ? "yes" : "no") : null;
|
|
5851
6019
|
|
|
5852
6020
|
const reviewItems = [
|
|
@@ -5889,8 +6057,8 @@ async function main() {
|
|
|
5889
6057
|
question("Select skill categories:");
|
|
5890
6058
|
const newCats = await interactiveSelect(categoryItems, { multi: true, preSelected: selectedCategories ? [...selectedCategories] : ["development", "obsidian"] });
|
|
5891
6059
|
if (newCats) {
|
|
5892
|
-
|
|
5893
|
-
selectedCategories =
|
|
6060
|
+
doInstallSkills = newCats.length > 0;
|
|
6061
|
+
selectedCategories = doInstallSkills ? new Set(newCats) : null;
|
|
5894
6062
|
}
|
|
5895
6063
|
} else if (pick === "statusline") {
|
|
5896
6064
|
installStatusLine = !installStatusLine;
|
|
@@ -5925,12 +6093,20 @@ async function main() {
|
|
|
5925
6093
|
sCfg.settings[sPick] = !(sCfg.settings[sPick] !== undefined ? sCfg.settings[sPick] : sMeta.defaults);
|
|
5926
6094
|
} else if (sPick === "friction_level") {
|
|
5927
6095
|
const cur = sCfg.settings[sPick] || sMeta.defaults;
|
|
5928
|
-
|
|
6096
|
+
const flItems = [
|
|
6097
|
+
{ id: "1", name: "1 — Gentle", tier: "Surface conflicts, easy to override" },
|
|
6098
|
+
{ id: "2", name: "2 — Moderate", tier: "Ask for justification on off-plan work" },
|
|
6099
|
+
{ id: "3", name: "3 — Firm", tier: "Hold the line, identify avoidance (recommended)" },
|
|
6100
|
+
{ id: "4", name: "4 — Hard block", tier: "Refuse destructive actions without confirmation" },
|
|
6101
|
+
];
|
|
6102
|
+
const flPick = await interactiveSelect(flItems, { defaultIndex: cur - 1 });
|
|
6103
|
+
if (flPick) sCfg.settings[sPick] = parseInt(flPick, 10);
|
|
5929
6104
|
} else if (sPick === "review_day") {
|
|
5930
6105
|
const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
5931
6106
|
const cur = sCfg.settings[sPick] || sMeta.defaults;
|
|
5932
|
-
const
|
|
5933
|
-
|
|
6107
|
+
const dayItems = days.map((d) => ({ id: d, name: d, tier: d === "Sunday" ? "Default — end-of-week reflection" : "" }));
|
|
6108
|
+
const dayPick = await interactiveSelect(dayItems, { defaultIndex: days.indexOf(cur) });
|
|
6109
|
+
if (dayPick) sCfg.settings[sPick] = dayPick;
|
|
5934
6110
|
}
|
|
5935
6111
|
fs.mkdirSync(path.dirname(sCfgPath), { recursive: true });
|
|
5936
6112
|
fs.writeFileSync(sCfgPath, JSON.stringify(sCfg, null, 2), "utf8");
|
|
@@ -5996,7 +6172,7 @@ async function main() {
|
|
|
5996
6172
|
|
|
5997
6173
|
// 4. Per-agent installation
|
|
5998
6174
|
const writtenFiles = new Set(); // Track shared files to avoid double-writes (e.g. GEMINI.md)
|
|
5999
|
-
const skillOpts = { install:
|
|
6175
|
+
const skillOpts = { install: doInstallSkills, categories: selectedCategories, statusLine: installStatusLine, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
|
|
6000
6176
|
for (const agent of selectedAgents) {
|
|
6001
6177
|
// Expand selections to targets (e.g. "gemini-cli" → ["gemini-cli", "antigravity"])
|
|
6002
6178
|
const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
|
|
@@ -6106,7 +6282,7 @@ async function main() {
|
|
|
6106
6282
|
|
|
6107
6283
|
// ── Done ──
|
|
6108
6284
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
6109
|
-
|
|
6285
|
+
await successAnimation(`Mover OS v${VERSION} installed. ${dim(`${totalSteps} steps in ${elapsed}s`)}`);
|
|
6110
6286
|
|
|
6111
6287
|
// Size check on installed rules files
|
|
6112
6288
|
const rulesFile = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
|
|
@@ -6128,25 +6304,27 @@ async function main() {
|
|
|
6128
6304
|
ln(` ${bold("What was installed")}`);
|
|
6129
6305
|
ln();
|
|
6130
6306
|
ln(` ${green("▸")} ${bold("23")} workflows ${dim("slash commands for daily rhythm, projects, strategy")}`);
|
|
6131
|
-
ln(` ${green("▸")} ${bold("
|
|
6307
|
+
ln(` ${green("▸")} ${bold("62")} skills ${dim("curated packs for dev, marketing, CRO, design")}`);
|
|
6132
6308
|
if (selectedIds.includes("claude-code")) {
|
|
6133
|
-
ln(` ${green("▸")} ${bold("
|
|
6309
|
+
ln(` ${green("▸")} ${bold("18")} hooks ${dim("lifecycle guards (engine protection, git safety)")}`);
|
|
6134
6310
|
}
|
|
6135
6311
|
ln(` ${green("▸")} ${bold(String(selectedAgents.length))} agent${selectedAgents.length > 1 ? "s" : ""} ${dim(agentNames.join(", "))}`);
|
|
6136
6312
|
ln(` ${green("▸")} PARA vault ${dim("folders, templates, Engine scaffold")}`);
|
|
6137
6313
|
ln();
|
|
6138
6314
|
|
|
6139
|
-
// ── Next steps ──
|
|
6315
|
+
// ── Next steps (animated slide-in) ──
|
|
6140
6316
|
ln(gray(" ─────────────────────────────────────────────"));
|
|
6141
6317
|
ln();
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6318
|
+
await slideIn([
|
|
6319
|
+
` ${bold("Next steps")}`,
|
|
6320
|
+
"",
|
|
6321
|
+
` ${cyan("1")} Open your vault in ${bold("Obsidian")}`,
|
|
6322
|
+
` ${dim("This is where you view and browse your files")}`,
|
|
6323
|
+
"",
|
|
6324
|
+
` ${cyan("2")} Open the vault folder in your AI agent`,
|
|
6325
|
+
` ${dim("Installed: " + agentNames.join(", "))}`,
|
|
6326
|
+
"",
|
|
6327
|
+
]);
|
|
6150
6328
|
ln(` ${cyan("3")} Enable the Obsidian theme`);
|
|
6151
6329
|
ln(` ${dim("Settings → Appearance → CSS snippets → minimal-theme")}`);
|
|
6152
6330
|
ln();
|
|
@@ -6167,6 +6345,7 @@ async function main() {
|
|
|
6167
6345
|
ln(` ${green("▸")} ${bold("moveros pulse")} ${dim("Dashboard — energy, tasks, streaks")}`);
|
|
6168
6346
|
ln(` ${green("▸")} ${bold("moveros doctor")} ${dim("Health check across all agents")}`);
|
|
6169
6347
|
ln(` ${green("▸")} ${bold("moveros capture")} ${dim("Quick inbox — tasks, links, ideas")}`);
|
|
6348
|
+
ln(` ${green("▸")} ${bold("moveros prayer")} ${dim("Set up prayer time reminders")}`);
|
|
6170
6349
|
ln(` ${green("▸")} ${bold("moveros update")} ${dim("Update agents, rules, and skills")}`);
|
|
6171
6350
|
ln(` ${green("▸")} ${bold("moveros")} ${dim("Full menu with all commands")}`);
|
|
6172
6351
|
ln();
|