mover-os 4.6.0 → 4.6.2

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 +137 -37
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -199,15 +199,23 @@ async function printHeader(animate = IS_TTY) {
199
199
  ln(` ${dim(`v${pkgVer}`)} ${gray("the agentic operating system for obsidian")}${infoRight ? ` ${infoRight}` : ""}`);
200
200
 
201
201
  // Non-blocking update check
202
+ let _updateAvailable = null;
202
203
  try {
203
204
  const latest = execSync("npm view mover-os version", { encoding: "utf8", timeout: 5000 }).trim();
204
205
  if (latest && latest !== pkgVer && compareVersions(latest, pkgVer) > 0) {
205
- ln(` ${yellow(`Update available: v${pkgVer} → v${latest}`)} ${dim(`run ${bold("moveros update")}`)}`);
206
+ _updateAvailable = latest;
206
207
  }
207
208
  } catch {}
208
209
 
209
210
  ln();
210
- ln(gray(" ─────────────────────────────────────────────"));
211
+ if (_updateAvailable) {
212
+ ln(` ${yellow("┌──────────────────────────────────────────┐")}`);
213
+ ln(` ${yellow("│")} Update available: ${dim(`v${pkgVer}`)} ${dim("\u2192")} ${green(`v${_updateAvailable}`)}${" ".repeat(Math.max(0, 16 - _updateAvailable.length - pkgVer.length))}${yellow("│")}`);
214
+ ln(` ${yellow("│")} Run ${bold("moveros update")} to get the latest ${yellow("│")}`);
215
+ ln(` ${yellow("└──────────────────────────────────────────┘")}`);
216
+ } else {
217
+ ln(gray(" ─────────────────────────────────────────────"));
218
+ }
211
219
  ln();
212
220
  }
213
221
 
@@ -1670,9 +1678,14 @@ async function runUninstall(vaultPath) {
1670
1678
  barLn(`${dim("Could not reach Polar — license not deactivated")}`);
1671
1679
  }
1672
1680
  }
1673
- // Remove config file
1674
- fs.unlinkSync(configPath);
1675
- barLn(`${green("\u2713")} ${dim("~/.mover/config.json")}`);
1681
+ // Remove config file but preserve license key for reinstall
1682
+ if (cfg.licenseKey) {
1683
+ fs.writeFileSync(configPath, JSON.stringify({ licenseKey: cfg.licenseKey }, null, 2), "utf8");
1684
+ barLn(`${green("\u2713")} ${dim("~/.mover/config.json (license key preserved for reinstall)")}`);
1685
+ } else {
1686
+ fs.unlinkSync(configPath);
1687
+ barLn(`${green("\u2713")} ${dim("~/.mover/config.json")}`);
1688
+ }
1676
1689
  removed++;
1677
1690
  } catch {}
1678
1691
  }
@@ -1880,13 +1893,14 @@ const AGENT_REGISTRY = {
1880
1893
  },
1881
1894
  };
1882
1895
 
1883
- // User-selectable agents (14 selections). Each maps to 1+ install targets.
1896
+ // User-selectable agents (15 selections). Each maps to 1+ install targets.
1884
1897
  const AGENT_SELECTIONS = [
1885
1898
  { id: "claude-code", targets: ["claude-code"], name: "Claude Code" },
1886
1899
  { id: "cursor", targets: ["cursor"], name: "Cursor" },
1887
1900
  { id: "cline", targets: ["cline"], name: "Cline" },
1888
1901
  { id: "windsurf", targets: ["windsurf"], name: "Windsurf" },
1889
- { id: "gemini-cli", targets: ["gemini-cli", "antigravity"], name: "Gemini CLI + Antigravity" },
1902
+ { id: "gemini-cli", targets: ["gemini-cli"], name: "Gemini CLI" },
1903
+ { id: "antigravity", targets: ["antigravity"], name: "Antigravity" },
1890
1904
  { id: "copilot", targets: ["copilot"], name: "GitHub Copilot" },
1891
1905
  { id: "codex", targets: ["codex"], name: "Codex" },
1892
1906
  { id: "amazon-q", targets: ["amazon-q"], name: "Amazon Q Developer" },
@@ -5093,6 +5107,21 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5093
5107
  return;
5094
5108
  }
5095
5109
 
5110
+ // ── Confirmation gate ──
5111
+ barLn();
5112
+ const updateConfirm = await interactiveSelect(
5113
+ [
5114
+ { id: "yes", name: "Apply updates", tier: `${totalChanged} file${totalChanged > 1 ? "s" : ""} will be updated` },
5115
+ { id: "no", name: "Cancel", tier: "No changes will be made" },
5116
+ ],
5117
+ { multi: false, defaultIndex: 0 }
5118
+ );
5119
+ if (!updateConfirm || updateConfirm === "no") {
5120
+ outro("Update cancelled.");
5121
+ return;
5122
+ }
5123
+ barLn();
5124
+
5096
5125
  // ── Apply safe system files directly (no user customizations in these) ──
5097
5126
  const home = os.homedir();
5098
5127
  let appliedCount = 0;
@@ -5677,8 +5706,9 @@ async function main() {
5677
5706
  if (selectedIds.includes("claude-code")) {
5678
5707
  barLn();
5679
5708
  question("Install Claude Code status line?");
5680
- barLn(dim(" Shows model, project, context usage, and session cost in your terminal."));
5681
- barLn(dim(" Example: [Opus 4.6] my-project | Context: 42% | $3.50"));
5709
+ barLn(dim(" Live status bar with model, context %, project, session cost, and Mover OS data."));
5710
+ barLn(dim(" Example: Opus 4.6 · 24% · my-project (main) · 2h14m · $12.50"));
5711
+ barLn(dim(" ▸ next task · 2/5 done · Sleep by 22:00 · logged 30m ago"));
5682
5712
  barLn();
5683
5713
 
5684
5714
  const slChoice = await interactiveSelect(
@@ -5805,37 +5835,107 @@ async function main() {
5805
5835
  }
5806
5836
  }
5807
5837
 
5808
- // ── Settings steplet user configure before install ──
5838
+ // ── Confirmation gatereview and optionally change selections ──
5809
5839
  {
5810
- barLn();
5811
- question("Configure settings " + dim("(esc to use defaults)"));
5812
- barLn();
5813
- const settingsItems = [
5814
- { id: "review_day", name: "review_day Sunday Weekly review day" },
5815
- { id: "track_food", name: "track_food on Track food in daily notes" },
5816
- { id: "track_sleep", name: "track_sleep on Track sleep in daily notes" },
5817
- { id: "friction_level", name: "friction_level 3 Max friction level (1-4)" },
5818
- ];
5819
- const settingsPick = await interactiveSelect(settingsItems);
5820
- if (settingsPick) {
5821
- const meta = KNOWN_SETTINGS[settingsPick];
5822
- if (meta) {
5823
- const cfgPath = path.join(os.homedir(), ".mover", "config.json");
5824
- let cfg = {};
5825
- if (fs.existsSync(cfgPath)) { try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch {} }
5826
- if (!cfg.settings) cfg.settings = {};
5827
- if (meta.type === "boolean") {
5828
- cfg.settings[settingsPick] = !(cfg.settings[settingsPick] !== undefined ? cfg.settings[settingsPick] : meta.defaults);
5829
- statusLine("ok", settingsPick, cfg.settings[settingsPick] ? "on" : "off");
5830
- } else {
5831
- const answer = await textInput({ label: settingsPick, initial: String(meta.defaults) });
5832
- if (answer !== null && answer.trim() !== "") {
5833
- cfg.settings[settingsPick] = meta.type === "number" ? parseInt(answer.trim(), 10) : answer.trim();
5834
- statusLine("ok", settingsPick, JSON.stringify(cfg.settings[settingsPick]));
5840
+ let confirmed = false;
5841
+ while (!confirmed) {
5842
+ barLn();
5843
+ question(bold("Review your selections") + dim(" (enter to change, esc to cancel)"));
5844
+ barLn();
5845
+
5846
+ const agentNames = selectedAgents.map((a) => a.name).join(", ");
5847
+ const skillsLabel = installSkills && selectedCategories
5848
+ ? [...selectedCategories].join(", ")
5849
+ : installSkills ? "all categories" : "none";
5850
+ const slLabel = selectedIds.includes("claude-code") ? (installStatusLine ? "yes" : "no") : null;
5851
+
5852
+ const reviewItems = [
5853
+ { id: "_install", name: green(bold(" Install")), tier: "Proceed with the selections above" },
5854
+ { id: "agents", name: `Agents ${dim(agentNames)}`, tier: "Change which AI agents to install for" },
5855
+ { id: "skills", name: `Skills ${dim(skillsLabel)}`, tier: "Change skill categories" },
5856
+ ];
5857
+ if (slLabel !== null) {
5858
+ reviewItems.push({ id: "statusline", name: `Status line ${dim(slLabel)}`, tier: "Toggle Claude Code status line" });
5859
+ }
5860
+ reviewItems.push({ id: "settings", name: `Settings ${dim("enter to configure")}`, tier: "Configure review_day, tracking, friction level" });
5861
+
5862
+ const pick = await interactiveSelect(reviewItems, { defaultIndex: 0 });
5863
+ if (!pick) { outro("Cancelled."); return; }
5864
+
5865
+ if (pick === "_install") {
5866
+ confirmed = true;
5867
+ } else if (pick === "agents") {
5868
+ barLn();
5869
+ question("Select your AI agents");
5870
+ const agentItems = AGENTS.map((a) => {
5871
+ const detected = a.detect();
5872
+ return { id: a.id, name: `${a.name}${detected ? dim(" (detected)") : ""}`, tier: dim(a.tier), _detected: detected };
5873
+ });
5874
+ const newIds = await interactiveSelect(agentItems, { multi: true, preSelected: selectedIds });
5875
+ if (newIds && newIds.length > 0) {
5876
+ selectedIds.length = 0;
5877
+ newIds.forEach((id) => selectedIds.push(id));
5878
+ selectedAgents.length = 0;
5879
+ AGENTS.filter((a) => selectedIds.includes(a.id)).forEach((a) => selectedAgents.push(a));
5880
+ }
5881
+ } else if (pick === "skills") {
5882
+ barLn();
5883
+ const allSkills = findSkills(bundleDir);
5884
+ const catCounts = {};
5885
+ for (const sk of allSkills) catCounts[sk.category] = (catCounts[sk.category] || 0) + 1;
5886
+ const categoryItems = CATEGORY_META.map((c) => ({
5887
+ id: c.id, name: `${c.name} ${dim(`(${catCounts[c.id] || 0})`)}`, tier: dim(c.desc),
5888
+ }));
5889
+ question("Select skill categories:");
5890
+ const newCats = await interactiveSelect(categoryItems, { multi: true, preSelected: selectedCategories ? [...selectedCategories] : ["development", "obsidian"] });
5891
+ if (newCats) {
5892
+ installSkills = newCats.length > 0;
5893
+ selectedCategories = installSkills ? new Set(newCats) : null;
5894
+ }
5895
+ } else if (pick === "statusline") {
5896
+ installStatusLine = !installStatusLine;
5897
+ } else if (pick === "settings") {
5898
+ // Re-enter settings loop
5899
+ barLn();
5900
+ question("Configure settings " + dim("(enter to toggle/edit, esc to go back)"));
5901
+ barLn();
5902
+ const sCfgPath = path.join(os.homedir(), ".mover", "config.json");
5903
+ let sCfg = {};
5904
+ if (fs.existsSync(sCfgPath)) { try { sCfg = JSON.parse(fs.readFileSync(sCfgPath, "utf8")); } catch {} }
5905
+ if (!sCfg.settings) sCfg.settings = {};
5906
+ const installSettings = [
5907
+ { key: "track_food", label: "Track food", desc: "Track meals in daily notes" },
5908
+ { key: "track_sleep", label: "Track sleep", desc: "Track sleep in daily notes" },
5909
+ { key: "friction_level", label: "Friction level", desc: "AI pushback intensity (1=gentle, 4=hard block)" },
5910
+ { key: "review_day", label: "Review day", desc: "Day for weekly review" },
5911
+ ];
5912
+ let sLoop = true;
5913
+ while (sLoop) {
5914
+ const sItems = installSettings.map((s) => {
5915
+ const meta = KNOWN_SETTINGS[s.key];
5916
+ const val = sCfg.settings[s.key] !== undefined ? sCfg.settings[s.key] : meta.defaults;
5917
+ const display = meta.type === "boolean" ? (val ? green("on") : dim("off")) : String(val);
5918
+ return { id: s.key, name: `${s.label.padEnd(18)}${display}`, tier: s.desc };
5919
+ });
5920
+ const sPick = await interactiveSelect(sItems);
5921
+ if (!sPick) { sLoop = false; break; }
5922
+ const sMeta = KNOWN_SETTINGS[sPick];
5923
+ if (sMeta) {
5924
+ if (sMeta.type === "boolean") {
5925
+ sCfg.settings[sPick] = !(sCfg.settings[sPick] !== undefined ? sCfg.settings[sPick] : sMeta.defaults);
5926
+ } else if (sPick === "friction_level") {
5927
+ const cur = sCfg.settings[sPick] || sMeta.defaults;
5928
+ sCfg.settings[sPick] = cur >= 4 ? 1 : cur + 1;
5929
+ } else if (sPick === "review_day") {
5930
+ const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
5931
+ const cur = sCfg.settings[sPick] || sMeta.defaults;
5932
+ const idx = days.indexOf(cur);
5933
+ sCfg.settings[sPick] = days[(idx + 1) % days.length];
5934
+ }
5935
+ fs.mkdirSync(path.dirname(sCfgPath), { recursive: true });
5936
+ fs.writeFileSync(sCfgPath, JSON.stringify(sCfg, null, 2), "utf8");
5835
5937
  }
5836
5938
  }
5837
- fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
5838
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
5839
5939
  }
5840
5940
  }
5841
5941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "The self-improving OS for AI agents. Turns Obsidian into an execution engine.",
5
5
  "bin": {
6
6
  "moveros": "install.js"