mover-os 4.6.1 → 4.6.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 +115 -73
  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
 
@@ -5248,8 +5256,8 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5248
5256
  const targets = sel ? sel.targets : [agent.id];
5249
5257
  for (const tid of targets) {
5250
5258
  const reg = AGENT_REGISTRY[tid];
5251
- if (reg && reg.rules) {
5252
- const destPath = typeof reg.rules === "function" ? reg.rules() : reg.rules;
5259
+ if (reg && reg.rules && reg.rules.dest) {
5260
+ const destPath = typeof reg.rules.dest === "function" ? reg.rules.dest(vaultPath) : reg.rules.dest;
5253
5261
  installRules(bundleDir, destPath, tid);
5254
5262
  }
5255
5263
  }
@@ -5268,10 +5276,10 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
5268
5276
  const targets = sel ? sel.targets : [agent.id];
5269
5277
  for (const tid of targets) {
5270
5278
  const reg = AGENT_REGISTRY[tid];
5271
- if (reg && reg.skills) {
5272
- const destDir = typeof reg.skills === "function" ? reg.skills() : reg.skills;
5279
+ if (reg && reg.skills && reg.skills.dest) {
5280
+ const destDir = typeof reg.skills.dest === "function" ? reg.skills.dest(vaultPath) : reg.skills.dest;
5273
5281
  if (destDir && !writtenFiles.has(destDir)) {
5274
- installSkills(bundleDir, destDir, null, null);
5282
+ installSkillPacks(bundleDir, destDir, null);
5275
5283
  writtenFiles.add(destDir);
5276
5284
  }
5277
5285
  }
@@ -5656,7 +5664,7 @@ async function main() {
5656
5664
 
5657
5665
  // ── Skills ──
5658
5666
  const allSkills = findSkills(bundleDir);
5659
- let installSkills = false;
5667
+ let doInstallSkills = false;
5660
5668
  let selectedCategories = null; // null = all, Set = filtered
5661
5669
 
5662
5670
  if (allSkills.length > 0 && selectedAgents.some((a) => a.id !== "aider")) {
@@ -5684,8 +5692,8 @@ async function main() {
5684
5692
  });
5685
5693
  if (!selectedCatIds) return;
5686
5694
 
5687
- installSkills = selectedCatIds.length > 0;
5688
- if (installSkills) {
5695
+ doInstallSkills = selectedCatIds.length > 0;
5696
+ if (doInstallSkills) {
5689
5697
  selectedCategories = new Set(selectedCatIds);
5690
5698
  // Count how many skills will be installed (selected categories + tools)
5691
5699
  const skillCount = allSkills.filter((s) => s.category === "tools" || selectedCategories.has(s.category)).length;
@@ -5827,74 +5835,108 @@ async function main() {
5827
5835
  }
5828
5836
  }
5829
5837
 
5830
- // ── Settings steplet user configure before install (loops until esc) ──
5838
+ // ── Confirmation gatereview and optionally change selections ──
5831
5839
  {
5832
- barLn();
5833
- question("Configure settings " + dim("(esc to continue with defaults)"));
5834
- barLn();
5835
- let editingSettings = true;
5836
- while (editingSettings) {
5837
- const cfgPath = path.join(os.homedir(), ".mover", "config.json");
5838
- let cfg = {};
5839
- if (fs.existsSync(cfgPath)) { try { cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch {} }
5840
- if (!cfg.settings) cfg.settings = {};
5840
+ let confirmed = false;
5841
+ while (!confirmed) {
5842
+ barLn();
5843
+ question(bold("Review your selections") + dim(" (enter to change, esc to cancel)"));
5844
+ barLn();
5841
5845
 
5842
- const settingsItems = [
5843
- { id: "review_day", name: `review_day ${(cfg.settings.review_day || "Sunday").toString().padEnd(12)}Weekly review day` },
5844
- { id: "track_food", name: `track_food ${(cfg.settings.track_food !== undefined ? (cfg.settings.track_food ? "on" : "off") : "on").padEnd(12)}Track food in daily notes` },
5845
- { id: "track_sleep", name: `track_sleep ${(cfg.settings.track_sleep !== undefined ? (cfg.settings.track_sleep ? "on" : "off") : "on").padEnd(12)}Track sleep in daily notes` },
5846
- { id: "friction_level", name: `friction_level ${(cfg.settings.friction_level || 3).toString().padEnd(12)}Max friction level (1-4)` },
5846
+ const agentNames = selectedAgents.map((a) => a.name).join(", ");
5847
+ const skillsLabel = doInstallSkills && selectedCategories
5848
+ ? [...selectedCategories].join(", ")
5849
+ : doInstallSkills ? "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" },
5847
5856
  ];
5848
- const settingsPick = await interactiveSelect(settingsItems);
5849
- if (!settingsPick) { editingSettings = false; break; }
5850
- const meta = KNOWN_SETTINGS[settingsPick];
5851
- if (meta) {
5852
- if (meta.type === "boolean") {
5853
- cfg.settings[settingsPick] = !(cfg.settings[settingsPick] !== undefined ? cfg.settings[settingsPick] : meta.defaults);
5854
- statusLine("ok", settingsPick, cfg.settings[settingsPick] ? "on" : "off");
5855
- } else {
5856
- const answer = await textInput({ label: settingsPick, initial: String(cfg.settings[settingsPick] !== undefined ? cfg.settings[settingsPick] : meta.defaults) });
5857
- if (answer !== null && answer.trim() !== "") {
5858
- cfg.settings[settingsPick] = meta.type === "number" ? parseInt(answer.trim(), 10) : answer.trim();
5859
- statusLine("ok", settingsPick, JSON.stringify(cfg.settings[settingsPick]));
5860
- }
5861
- }
5862
- fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
5863
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2), "utf8");
5857
+ if (slLabel !== null) {
5858
+ reviewItems.push({ id: "statusline", name: `Status line ${dim(slLabel)}`, tier: "Toggle Claude Code status line" });
5864
5859
  }
5865
- }
5866
- }
5860
+ reviewItems.push({ id: "settings", name: `Settings ${dim("enter to configure")}`, tier: "Configure review_day, tracking, friction level" });
5867
5861
 
5868
- // ── Confirmation gate show what will be installed ──
5869
- {
5870
- barLn();
5871
- question(bold("Review your selections:"));
5872
- barLn();
5873
- barLn(` ${bold("Vault:")} ${vaultPath}`);
5874
- barLn(` ${bold("Agents:")} ${selectedAgents.map((a) => a.name).join(", ")}`);
5875
- if (installSkills && selectedCategories) {
5876
- barLn(` ${bold("Skills:")} ${[...selectedCategories].join(", ")}`);
5877
- } else if (installSkills) {
5878
- barLn(` ${bold("Skills:")} all categories`);
5879
- } else {
5880
- barLn(` ${bold("Skills:")} none`);
5881
- }
5882
- if (selectedIds.includes("claude-code")) {
5883
- barLn(` ${bold("Status line:")} ${installStatusLine ? "yes" : "no"}`);
5884
- }
5885
- barLn(` ${bold("Prayer:")} ${prayerSetup ? "yes" : "no"}`);
5886
- barLn();
5862
+ const pick = await interactiveSelect(reviewItems, { defaultIndex: 0 });
5863
+ if (!pick) { outro("Cancelled."); return; }
5887
5864
 
5888
- const confirmChoice = await interactiveSelect(
5889
- [
5890
- { id: "yes", name: "Install", tier: "Proceed with the selections above" },
5891
- { id: "no", name: "Cancel", tier: "Go back and start over" },
5892
- ],
5893
- { multi: false, defaultIndex: 0 }
5894
- );
5895
- if (!confirmChoice || confirmChoice === "no") {
5896
- outro("Cancelled.");
5897
- return;
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
+ doInstallSkills = newCats.length > 0;
5893
+ selectedCategories = doInstallSkills ? 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");
5937
+ }
5938
+ }
5939
+ }
5898
5940
  }
5899
5941
  }
5900
5942
 
@@ -5954,7 +5996,7 @@ async function main() {
5954
5996
 
5955
5997
  // 4. Per-agent installation
5956
5998
  const writtenFiles = new Set(); // Track shared files to avoid double-writes (e.g. GEMINI.md)
5957
- const skillOpts = { install: installSkills, categories: selectedCategories, statusLine: installStatusLine, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
5999
+ const skillOpts = { install: doInstallSkills, categories: selectedCategories, statusLine: installStatusLine, workflows: selectedWorkflows, skipHooks, skipRules, skipTemplates };
5958
6000
  for (const agent of selectedAgents) {
5959
6001
  // Expand selections to targets (e.g. "gemini-cli" → ["gemini-cli", "antigravity"])
5960
6002
  const sel = AGENT_SELECTIONS.find((s) => s.id === agent.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.6.1",
3
+ "version": "4.6.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"