mover-os 4.5.1 → 4.5.3

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.
Files changed (2) hide show
  1. package/install.js +206 -60
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -92,17 +92,10 @@ function waveGradient(text, frame, totalFrames) {
92
92
  // ─── TUI: Alternate screen buffer ────────────────────────────────────────────
93
93
  let _inAltScreen = false;
94
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;
95
+ // Disabled: keep output in terminal scrollback history
100
96
  }
101
97
  function exitAltScreen() {
102
- if (!IS_TTY || !_inAltScreen) return;
103
- w("\x1b[?1049l"); // Restore original screen
104
- w(S.show);
105
- _inAltScreen = false;
98
+ // Disabled: no alt screen to exit
106
99
  }
107
100
  function clearContent() {
108
101
  w("\x1b[2J\x1b[H"); // Clear screen + cursor to top
@@ -954,14 +947,17 @@ function compareVersions(a, b) {
954
947
  // ─── Change detection (update mode) ─────────────────────────────────────────
955
948
  function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
956
949
  const home = os.homedir();
957
- const result = { workflows: [], hooks: [], rules: null, templates: [] };
950
+ const result = { workflows: [], hooks: [], rules: null, templates: [], skills: [], statusline: "unchanged" };
951
+
952
+ // Expand selection IDs to target IDs (e.g. "gemini-cli" → includes "antigravity")
953
+ const targetIds = expandTargetIds(selectedAgentIds);
958
954
 
959
955
  // --- Workflows: compare source vs first installed destination ---
960
956
  const wfSrc = path.join(bundleDir, "src", "workflows");
961
957
  const wfDests = [
962
- selectedAgentIds.includes("claude-code") && path.join(home, ".claude", "commands"),
963
- selectedAgentIds.includes("cursor") && path.join(home, ".cursor", "commands"),
964
- selectedAgentIds.includes("antigravity") && path.join(home, ".gemini", "antigravity", "global_workflows"),
958
+ targetIds.includes("claude-code") && path.join(home, ".claude", "commands"),
959
+ targetIds.includes("cursor") && path.join(home, ".cursor", "commands"),
960
+ targetIds.includes("antigravity") && path.join(home, ".gemini", "antigravity", "global_workflows"),
965
961
  ].filter(Boolean);
966
962
  const wfDest = wfDests.find((d) => fs.existsSync(d));
967
963
 
@@ -984,7 +980,7 @@ function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
984
980
  // --- Hooks: compare with CRLF normalization ---
985
981
  const hooksSrc = path.join(bundleDir, "src", "hooks");
986
982
  const hooksDest = path.join(home, ".claude", "hooks");
987
- if (fs.existsSync(hooksSrc) && selectedAgentIds.includes("claude-code")) {
983
+ if (fs.existsSync(hooksSrc) && targetIds.includes("claude-code")) {
988
984
  for (const file of fs.readdirSync(hooksSrc).filter((f) => f.endsWith(".sh") || f.endsWith(".md"))) {
989
985
  const srcContent = fs.readFileSync(path.join(hooksSrc, file), "utf8")
990
986
  .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -1005,9 +1001,9 @@ function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
1005
1001
  // --- Rules: compare source vs first installed destination ---
1006
1002
  const rulesSrc = path.join(bundleDir, "src", "system", "Mover_Global_Rules.md");
1007
1003
  const rulesDests = [
1008
- selectedAgentIds.includes("claude-code") && path.join(home, ".claude", "CLAUDE.md"),
1009
- selectedAgentIds.includes("cursor") && path.join(home, ".cursor", "rules", "mover-os.mdc"),
1010
- selectedAgentIds.includes("gemini-cli") && path.join(home, ".gemini", "GEMINI.md"),
1004
+ targetIds.includes("claude-code") && path.join(home, ".claude", "CLAUDE.md"),
1005
+ targetIds.includes("cursor") && path.join(home, ".cursor", "rules", "mover-os.mdc"),
1006
+ targetIds.includes("gemini-cli") && path.join(home, ".gemini", "GEMINI.md"),
1011
1007
  ].filter(Boolean);
1012
1008
  const rulesDest = rulesDests.find((d) => fs.existsSync(d));
1013
1009
  if (fs.existsSync(rulesSrc) && rulesDest) {
@@ -1046,6 +1042,49 @@ function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
1046
1042
  walkTemplates(structDir, "");
1047
1043
  }
1048
1044
 
1045
+ // --- Skills: compare source vs installed ---
1046
+ const skillsSrc = path.join(bundleDir, "src", "skills");
1047
+ const skillsDests = [
1048
+ targetIds.includes("claude-code") && path.join(home, ".claude", "skills"),
1049
+ targetIds.includes("cursor") && path.join(home, ".cursor", "skills"),
1050
+ targetIds.includes("cline") && path.join(home, ".cline", "skills"),
1051
+ ].filter(Boolean);
1052
+ const skillsDest = skillsDests.find((d) => fs.existsSync(d));
1053
+ if (fs.existsSync(skillsSrc) && skillsDest) {
1054
+ const walkSkills = (dir) => {
1055
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
1056
+ const full = path.join(dir, entry.name);
1057
+ if (entry.isDirectory()) {
1058
+ const skillFile = path.join(full, "SKILL.md");
1059
+ if (fs.existsSync(skillFile)) {
1060
+ const srcContent = fs.readFileSync(skillFile, "utf8");
1061
+ const destSkill = path.join(skillsDest, entry.name, "SKILL.md");
1062
+ if (!fs.existsSync(destSkill)) {
1063
+ result.skills.push({ file: entry.name, status: "new" });
1064
+ } else {
1065
+ const destContent = fs.readFileSync(destSkill, "utf8");
1066
+ result.skills.push({ file: entry.name, status: srcContent === destContent ? "unchanged" : "changed" });
1067
+ }
1068
+ } else {
1069
+ walkSkills(full);
1070
+ }
1071
+ }
1072
+ }
1073
+ };
1074
+ walkSkills(skillsSrc);
1075
+ }
1076
+
1077
+ // --- Statusline ---
1078
+ const slSrc = path.join(bundleDir, "src", "hooks", "statusline.js");
1079
+ const slDest = path.join(home, ".claude", "statusline.js");
1080
+ if (fs.existsSync(slSrc) && fs.existsSync(slDest) && targetIds.includes("claude-code")) {
1081
+ const srcSl = fs.readFileSync(slSrc, "utf8").replace(/\r\n/g, "\n");
1082
+ const destSl = fs.readFileSync(slDest, "utf8").replace(/\r\n/g, "\n");
1083
+ result.statusline = srcSl === destSl ? "unchanged" : "changed";
1084
+ } else if (fs.existsSync(slSrc) && !fs.existsSync(slDest) && targetIds.includes("claude-code")) {
1085
+ result.statusline = "new";
1086
+ }
1087
+
1049
1088
  return result;
1050
1089
  }
1051
1090
 
@@ -1055,6 +1094,8 @@ function countChanges(changes) {
1055
1094
  n += changes.hooks.filter((f) => f.status !== "unchanged").length;
1056
1095
  if (changes.rules === "changed") n++;
1057
1096
  n += changes.templates.filter((f) => f.status !== "unchanged").length;
1097
+ n += (changes.skills || []).filter((f) => f.status !== "unchanged").length;
1098
+ if (changes.statusline === "changed" || changes.statusline === "new") n++;
1058
1099
  return n;
1059
1100
  }
1060
1101
 
@@ -1102,6 +1143,22 @@ function displayChangeSummary(changes, installedVersion, newVersion) {
1102
1143
  } else {
1103
1144
  barLn(` Templates: ${dim("unchanged")}`);
1104
1145
  }
1146
+
1147
+ // Skills
1148
+ const skChanged = (changes.skills || []).filter((f) => f.status === "changed");
1149
+ const skNew = (changes.skills || []).filter((f) => f.status === "new");
1150
+ const skUnchanged = (changes.skills || []).filter((f) => f.status === "unchanged");
1151
+ if (skChanged.length > 0 || skNew.length > 0 || skUnchanged.length > 0) {
1152
+ barLn(` Skills ${dim(`(${skChanged.length + skNew.length} changed, ${skUnchanged.length} unchanged)`)}:`);
1153
+ for (const f of skChanged) barLn(` ${yellow("✦")} ${f.file}`);
1154
+ for (const f of skNew) barLn(` ${green("+")} ${f.file} ${dim("(new)")}`);
1155
+ if (skChanged.length === 0 && skNew.length === 0) barLn(` ${dim("all up to date")}`);
1156
+ }
1157
+
1158
+ // Statusline
1159
+ if (changes.statusline === "changed") barLn(` Statusline: ${yellow("changed")}`);
1160
+ else if (changes.statusline === "new") barLn(` Statusline: ${green("new")}`);
1161
+
1105
1162
  barLn();
1106
1163
  }
1107
1164
 
@@ -1566,6 +1623,17 @@ const AGENTS = AGENT_SELECTIONS.map((s) => ({
1566
1623
  detect: AGENT_REGISTRY[s.targets[0]].detect,
1567
1624
  }));
1568
1625
 
1626
+ // Expand selection IDs to install target IDs (e.g. "gemini-cli" → ["gemini-cli", "antigravity"])
1627
+ function expandTargetIds(selectionIds) {
1628
+ const result = [];
1629
+ for (const id of selectionIds) {
1630
+ const sel = AGENT_SELECTIONS.find((s) => s.id === id);
1631
+ if (sel) { for (const t of sel.targets) result.push(t); }
1632
+ else result.push(id);
1633
+ }
1634
+ return [...new Set(result)];
1635
+ }
1636
+
1569
1637
  // ─── Utility functions ──────────────────────────────────────────────────────
1570
1638
  function cmdExists(cmd) {
1571
1639
  try {
@@ -2052,6 +2120,8 @@ function writeMoverConfig(vaultPath, agentIds, licenseKey, opts = {}) {
2052
2120
  if (existing.settings) config.settings = { ...existing.settings };
2053
2121
  // Preserve prayer_times fallback
2054
2122
  if (existing.prayer_times) config.prayer_times = existing.prayer_times;
2123
+ // Preserve frecency data
2124
+ if (existing.cli_usage) config.cli_usage = existing.cli_usage;
2055
2125
  config.updatedAt = new Date().toISOString();
2056
2126
  } catch {}
2057
2127
  }
@@ -2970,7 +3040,8 @@ async function cmdDoctor(opts) {
2970
3040
  // Per-agent checks
2971
3041
  barLn();
2972
3042
  barLn(dim(" Agents:"));
2973
- const cfgData = fs.existsSync(cfg) ? JSON.parse(fs.readFileSync(cfg, "utf8")) : {};
3043
+ let cfgData = {};
3044
+ if (fs.existsSync(cfg)) { try { cfgData = JSON.parse(fs.readFileSync(cfg, "utf8")); } catch { statusLine("warn", "Config", "corrupt JSON"); } }
2974
3045
  const installedAgents = cfgData.agents || [];
2975
3046
  if (installedAgents.length === 0) {
2976
3047
  barLn(dim(" No agents recorded in config."));
@@ -2996,7 +3067,7 @@ async function cmdDoctor(opts) {
2996
3067
  const hasCmds = fs.existsSync(cp) && fs.readdirSync(cp).length > 0;
2997
3068
  checks.push(hasCmds ? "commands" : dim("no commands"));
2998
3069
  }
2999
- const allOk = checks.every((c) => !c.includes("missing"));
3070
+ const allOk = checks.every((c) => c === strip(c)); // styled text = problem
3000
3071
  statusLine(allOk ? "ok" : "warn", ` ${reg.name}`, checks.join(", "));
3001
3072
  }
3002
3073
 
@@ -3041,7 +3112,7 @@ async function cmdPulse(opts) {
3041
3112
  const focusMatch = ac.match(/\*\*Focus:\*\*\s*(.+)/i) || ac.match(/Single Test:\s*(.+)/i);
3042
3113
  if (focusMatch) focus = focusMatch[1].trim();
3043
3114
  // Blockers
3044
- const blockerSection = ac.match(/##.*Blocker[s]?[\s\S]*?(?=\n##|\n---|\Z)/i);
3115
+ const blockerSection = ac.match(/##.*Blocker[s]?[\s\S]*?(?=\n##|\n---|$)/i);
3045
3116
  if (blockerSection) {
3046
3117
  const lines = blockerSection[0].split("\n").filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*"));
3047
3118
  blockers = lines.map((l) => l.replace(/^[\s*-]+/, "").trim()).filter(Boolean).slice(0, 3);
@@ -3430,7 +3501,7 @@ async function cmdReplay(opts) {
3430
3501
  const daily = fs.readFileSync(dailyPath, "utf8");
3431
3502
 
3432
3503
  // Extract session log entries
3433
- const logMatch = daily.match(/##\s*Session Log[\s\S]*?(?=\n## [^#]|\Z)/i);
3504
+ const logMatch = daily.match(/##\s*Session Log[\s\S]*?(?=\n## [^#]|$)/i);
3434
3505
  if (!logMatch) {
3435
3506
  barLn(dim(" No session log found in this Daily Note."));
3436
3507
  return;
@@ -3985,10 +4056,11 @@ async function cmdRestore(opts) {
3985
4056
  const archivesDir = path.join(vault, "04_Archives");
3986
4057
  if (!fs.existsSync(archivesDir)) { barLn(yellow(" No archives found.")); return; }
3987
4058
 
4059
+ const backupPrefixes = ["Backup_", "Engine_Backup_", "Areas_Backup_", "Agent_Backup_"];
3988
4060
  const backups = fs.readdirSync(archivesDir)
3989
4061
  .filter((d) => {
3990
4062
  const full = path.join(archivesDir, d);
3991
- return fs.statSync(full).isDirectory() && (d.startsWith("Backup_") || d.startsWith("Engine_Backup_"));
4063
+ return fs.statSync(full).isDirectory() && backupPrefixes.some((p) => d.startsWith(p));
3992
4064
  })
3993
4065
  .sort()
3994
4066
  .reverse();
@@ -4016,25 +4088,67 @@ async function cmdRestore(opts) {
4016
4088
 
4017
4089
  const backupPath = path.join(archivesDir, selected);
4018
4090
 
4019
- // Check for engine dir in backup
4020
- const engPath = path.join(backupPath, "engine");
4021
- if (fs.existsSync(engPath)) {
4022
- const engineDir = path.join(vault, "02_Areas", "Engine");
4023
- let restored = 0;
4024
- for (const f of fs.readdirSync(engPath).filter((f) => fs.statSync(path.join(engPath, f)).isFile())) {
4025
- fs.copyFileSync(path.join(engPath, f), path.join(engineDir, f));
4026
- restored++;
4091
+ let totalRestored = 0;
4092
+
4093
+ if (selected.startsWith("Areas_Backup_")) {
4094
+ // Areas backup: restore entire 02_Areas folder
4095
+ const areasTarget = path.join(vault, "02_Areas");
4096
+ copyDirRecursive(backupPath, areasTarget);
4097
+ statusLine("ok", "Areas", "folder restored");
4098
+ totalRestored++;
4099
+ } else if (selected.startsWith("Agent_Backup_")) {
4100
+ // Agent backup: restore rules/skills per agent
4101
+ let agentCount = 0;
4102
+ for (const agDir of fs.readdirSync(backupPath).filter((d) => fs.statSync(path.join(backupPath, d)).isDirectory())) {
4103
+ const reg = AGENT_REGISTRY[agDir];
4104
+ if (!reg) continue;
4105
+ const srcDir = path.join(backupPath, agDir);
4106
+ // Restore rules file
4107
+ if (reg.rules && reg.rules.dest) {
4108
+ const rulesFile = fs.readdirSync(srcDir).find((f) => !f.startsWith("skills") && fs.statSync(path.join(srcDir, f)).isFile());
4109
+ if (rulesFile) {
4110
+ try {
4111
+ const dest = reg.rules.dest(vault);
4112
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
4113
+ fs.copyFileSync(path.join(srcDir, rulesFile), dest);
4114
+ agentCount++;
4115
+ } catch {}
4116
+ }
4117
+ }
4118
+ // Restore skills directory
4119
+ const skillsSrc = path.join(srcDir, "skills");
4120
+ if (fs.existsSync(skillsSrc) && reg.skills && reg.skills.dest) {
4121
+ try {
4122
+ copyDirRecursive(skillsSrc, reg.skills.dest(vault));
4123
+ agentCount++;
4124
+ } catch {}
4125
+ }
4027
4126
  }
4028
- statusLine("ok", "Engine", `${restored} files restored`);
4127
+ statusLine("ok", "Agents", `${agentCount} items restored`);
4128
+ totalRestored = agentCount;
4029
4129
  } else {
4030
- // Legacy backup format (files directly in backup dir)
4031
- const engineDir = path.join(vault, "02_Areas", "Engine");
4032
- let restored = 0;
4033
- for (const f of fs.readdirSync(backupPath).filter((f) => f.endsWith(".md") && f !== ".backup-manifest.json")) {
4034
- fs.copyFileSync(path.join(backupPath, f), path.join(engineDir, f));
4035
- restored++;
4130
+ // Engine backup (Engine_Backup_ or legacy Backup_)
4131
+ const engPath = path.join(backupPath, "engine");
4132
+ if (fs.existsSync(engPath)) {
4133
+ const engineDir = path.join(vault, "02_Areas", "Engine");
4134
+ let restored = 0;
4135
+ for (const f of fs.readdirSync(engPath).filter((f) => fs.statSync(path.join(engPath, f)).isFile())) {
4136
+ fs.copyFileSync(path.join(engPath, f), path.join(engineDir, f));
4137
+ restored++;
4138
+ }
4139
+ statusLine("ok", "Engine", `${restored} files restored`);
4140
+ totalRestored = restored;
4141
+ } else {
4142
+ // Legacy backup format (files directly in backup dir)
4143
+ const engineDir = path.join(vault, "02_Areas", "Engine");
4144
+ let restored = 0;
4145
+ for (const f of fs.readdirSync(backupPath).filter((f) => f.endsWith(".md") && f !== ".backup-manifest.json")) {
4146
+ fs.copyFileSync(path.join(backupPath, f), path.join(engineDir, f));
4147
+ restored++;
4148
+ }
4149
+ if (restored > 0) statusLine("ok", "Engine", `${restored} files restored`);
4150
+ totalRestored = restored;
4036
4151
  }
4037
- if (restored > 0) statusLine("ok", "Engine", `${restored} files restored`);
4038
4152
  }
4039
4153
 
4040
4154
  barLn();
@@ -4631,14 +4745,20 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
4631
4745
  createVaultStructure(vaultPath);
4632
4746
  installTemplateFiles(bundleDir, vaultPath);
4633
4747
  const writtenFiles = new Set();
4634
- const skillOpts = { install: true, categories: null, workflows: null };
4748
+ const skillOpts = { install: true, categories: null, workflows: null, statusLine: changes.statusline !== "unchanged" };
4635
4749
  for (const agent of detectedAgents) {
4636
- const fn = AGENT_INSTALLERS[agent.id];
4637
- if (!fn) continue;
4638
- const sp = spinner(agent.name);
4639
- const steps = fn(bundleDir, vaultPath, skillOpts, writtenFiles, agent.id);
4640
- await sleep(200);
4641
- sp.stop(steps.length > 0 ? `${agent.name} ${dim(steps.join(", "))}` : `${agent.name} ${dim("configured")}`);
4750
+ const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
4751
+ const targets = sel ? sel.targets : [agent.id];
4752
+ for (const targetId of targets) {
4753
+ const fn = AGENT_INSTALLERS[targetId];
4754
+ if (!fn) continue;
4755
+ const targetReg = AGENT_REGISTRY[targetId];
4756
+ const displayName = targetReg ? targetReg.name : agent.name;
4757
+ const sp = spinner(displayName);
4758
+ const steps = fn(bundleDir, vaultPath, skillOpts, writtenFiles, targetId);
4759
+ await sleep(200);
4760
+ sp.stop(steps.length > 0 ? `${displayName} ${dim(steps.join(", "))}` : `${displayName} ${dim("configured")}`);
4761
+ }
4642
4762
  }
4643
4763
  fs.writeFileSync(path.join(vaultPath, ".mover-version"), `${require("./package.json").version}\n`, "utf8");
4644
4764
  writeMoverConfig(vaultPath, selectedIds);
@@ -4697,15 +4817,36 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
4697
4817
  fs.mkdirSync(agentBackupDir, { recursive: true });
4698
4818
  let agentsBacked = 0;
4699
4819
  for (const ag of AGENTS.filter((a) => a.detect())) {
4700
- const agDir = path.join(agentBackupDir, ag.id);
4701
- fs.mkdirSync(agDir, { recursive: true });
4702
- for (const cp of (ag.configPaths || [])) {
4703
- if (fs.existsSync(cp.src)) {
4704
- try { fs.copyFileSync(cp.src, path.join(agDir, path.basename(cp.src))); agentsBacked++; } catch {}
4820
+ const sel = AGENT_SELECTIONS.find((s) => s.id === ag.id);
4821
+ const targets = sel ? sel.targets : [ag.id];
4822
+ for (const targetId of targets) {
4823
+ const reg = AGENT_REGISTRY[targetId];
4824
+ if (!reg) continue;
4825
+ const agDir = path.join(agentBackupDir, targetId);
4826
+ fs.mkdirSync(agDir, { recursive: true });
4827
+ // Back up rules file
4828
+ if (reg.rules && reg.rules.dest) {
4829
+ try {
4830
+ const rulesPath = reg.rules.dest(vaultPath);
4831
+ if (fs.existsSync(rulesPath)) {
4832
+ fs.copyFileSync(rulesPath, path.join(agDir, path.basename(rulesPath)));
4833
+ agentsBacked++;
4834
+ }
4835
+ } catch {}
4836
+ }
4837
+ // Back up skills directory
4838
+ if (reg.skills && reg.skills.dest) {
4839
+ try {
4840
+ const skillsDir = reg.skills.dest(vaultPath);
4841
+ if (fs.existsSync(skillsDir)) {
4842
+ copyDirRecursive(skillsDir, path.join(agDir, "skills"));
4843
+ agentsBacked++;
4844
+ }
4845
+ } catch {}
4705
4846
  }
4706
4847
  }
4707
4848
  }
4708
- statusLine("ok", "Backed up", `${agentsBacked} agent config files`);
4849
+ statusLine("ok", "Backed up", `${agentsBacked} agent config items`);
4709
4850
  } catch (err) { barLn(yellow(` Agent backup failed: ${err.message}`)); }
4710
4851
  }
4711
4852
  }
@@ -4806,7 +4947,7 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
4806
4947
  }
4807
4948
 
4808
4949
  const writtenFiles = new Set();
4809
- const skillOpts = { install: true, categories: null, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
4950
+ const skillOpts = { install: true, categories: null, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates, statusLine: changes.statusline !== "unchanged" };
4810
4951
  for (const agent of selectedAgents) {
4811
4952
  const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
4812
4953
  const targets = sel ? sel.targets : [agent.id];
@@ -4839,8 +4980,12 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
4839
4980
  const catSet = new Set(selectedCatIds);
4840
4981
  const refreshOpts = { install: true, categories: catSet, workflows: null };
4841
4982
  for (const agent of selectedAgents) {
4842
- const fn = AGENT_INSTALLERS[agent.id];
4843
- if (fn) fn(bundleDir, vaultPath, refreshOpts, writtenFiles, agent.id);
4983
+ const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
4984
+ const targets = sel ? sel.targets : [agent.id];
4985
+ for (const targetId of targets) {
4986
+ const fn = AGENT_INSTALLERS[targetId];
4987
+ if (fn) fn(bundleDir, vaultPath, refreshOpts, writtenFiles, targetId);
4988
+ }
4844
4989
  }
4845
4990
  const skillCount = allSkills.filter((s) => s.category === "tools" || catSet.has(s.category)).length;
4846
4991
  statusLine("ok", "Skills refreshed", `${skillCount} across ${selectedAgents.length} agent(s)`);
@@ -5223,8 +5368,8 @@ async function main() {
5223
5368
  ],
5224
5369
  { multi: false, defaultIndex: 1 }
5225
5370
  );
5226
- if (!ptChoice) return;
5227
- if (ptChoice === "yes") {
5371
+ if (!ptChoice || ptChoice === "no") { /* skip prayer setup */ }
5372
+ else if (ptChoice === "yes") {
5228
5373
  prayerSetup = true;
5229
5374
  barLn();
5230
5375
  question("How would you like to set up prayer times?");
@@ -5238,7 +5383,9 @@ async function main() {
5238
5383
  ],
5239
5384
  { multi: false, defaultIndex: 0 }
5240
5385
  );
5241
- if (!method || method === "later") { /* skip */ }
5386
+ if (!method || method === "later") {
5387
+ // User cancelled method pick — still enable the setting, no timetable
5388
+ } else {
5242
5389
 
5243
5390
  const moverDir = path.join(os.homedir(), ".mover");
5244
5391
  if (!fs.existsSync(moverDir)) fs.mkdirSync(moverDir, { recursive: true, mode: 0o700 });
@@ -5290,9 +5437,7 @@ async function main() {
5290
5437
  } else if (method === "fetch") {
5291
5438
  barLn();
5292
5439
  const city = await textInput({ label: "City (e.g. London, Watford, Istanbul)", placeholder: "London" });
5293
- if (city === null) return;
5294
- const country = await textInput({ label: "Country", placeholder: "United Kingdom" });
5295
- if (country === null) return;
5440
+ const country = city ? await textInput({ label: "Country", placeholder: "United Kingdom" }) : null;
5296
5441
  barLn();
5297
5442
 
5298
5443
  if (city && country) {
@@ -5310,6 +5455,7 @@ async function main() {
5310
5455
  }
5311
5456
  }
5312
5457
  // method === "later" → just enable the setting, no timetable yet
5458
+ } // end if method !== "later"
5313
5459
  }
5314
5460
  }
5315
5461
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.5.1",
3
+ "version": "4.5.3",
4
4
  "description": "The self-improving OS for AI agents. Turns Obsidian into an execution engine.",
5
5
  "bin": {
6
6
  "moveros": "install.js"