mover-os 4.4.0 → 4.4.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 +114 -12
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -343,7 +343,16 @@ function textInput({ label = "", initial = "", mask = null, placeholder = "" })
343
343
  value = value.slice(0, pos) + value.slice(pos + 1);
344
344
  }
345
345
  }
346
- else if (data.startsWith("\x1b")) { /* ignore escape sequences */ }
346
+ else if (data === "\x1b") {
347
+ // Escape — cancel input
348
+ stdin.removeListener("data", handler);
349
+ stdin.setRawMode(false);
350
+ stdin.pause();
351
+ ln();
352
+ resolve(null);
353
+ return;
354
+ }
355
+ else if (data.startsWith("\x1b")) { /* ignore other escape sequences */ }
347
356
  else {
348
357
  for (const ch of data) {
349
358
  if (ch.charCodeAt(0) >= 32) {
@@ -420,8 +429,8 @@ function interactiveSelect(items, { multi = false, preSelected = [], defaultInde
420
429
  lines++;
421
430
 
422
431
  const hint = multi
423
- ? dim(" ↑↓ navigate space select a all enter confirm")
424
- : dim(" ↑↓ navigate enter select");
432
+ ? dim(" ↑↓ navigate space select a all enter confirm esc back")
433
+ : dim(" ↑↓ navigate enter select esc back");
425
434
  w(`\x1b[2K${BAR_COLOR}│${S.reset}${hint}\n`);
426
435
  lines++;
427
436
 
@@ -467,6 +476,20 @@ function interactiveSelect(items, { multi = false, preSelected = [], defaultInde
467
476
  }
468
477
  return;
469
478
  }
479
+ else if (data === "\x1b" || data === "\x1b\x1b" || (!multi && data === "q")) {
480
+ // Escape — go back / cancel
481
+ stdin.removeListener("data", handler);
482
+ stdin.setRawMode(false);
483
+ stdin.pause();
484
+ if (prevLines > 0) {
485
+ w(`\x1b[${prevLines}A`);
486
+ for (let i = 0; i < prevLines; i++) w("\x1b[2K\n");
487
+ w(`\x1b[${prevLines}A`);
488
+ }
489
+ w(S.show);
490
+ resolve(null);
491
+ return;
492
+ }
470
493
  else if (data === "\x03") { cleanup(); ln(); process.exit(0); }
471
494
 
472
495
  render();
@@ -655,6 +678,7 @@ function parseArgs() {
655
678
  if (a === "--key" && args[i + 1]) { opts.key = args[++i]; continue; }
656
679
  // Backward compat: --update / -u → command 'update'
657
680
  if (a === "--update" || a === "-u") { opts.command = "update"; continue; }
681
+ if (a === "--_self-updated") { opts._selfUpdated = true; continue; }
658
682
  if (a === "--help" || a === "-h") {
659
683
  ln();
660
684
  ln(` ${bold("moveros")} ${dim("— the Mover OS companion CLI")}`);
@@ -708,6 +732,17 @@ function detectObsidianVaults() {
708
732
  }
709
733
  }
710
734
 
735
+ function compareVersions(a, b) {
736
+ const pa = a.split(".").map(Number);
737
+ const pb = b.split(".").map(Number);
738
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
739
+ const va = pa[i] || 0, vb = pb[i] || 0;
740
+ if (va > vb) return 1;
741
+ if (va < vb) return -1;
742
+ }
743
+ return 0;
744
+ }
745
+
711
746
  // ─── Change detection (update mode) ─────────────────────────────────────────
712
747
  function detectChanges(bundleDir, vaultPath, selectedAgentIds) {
713
748
  const home = os.homedir();
@@ -2995,8 +3030,10 @@ async function cmdCapture(opts) {
2995
3030
  { id: "link", name: "Link", tier: "URL with optional note" },
2996
3031
  { id: "dump", name: "Brain dump", tier: "Free-form text" },
2997
3032
  ], { multi: false });
3033
+ if (!type) return;
2998
3034
  }
2999
3035
  content = await textInput({ label: `Enter ${type}:` });
3036
+ if (content === null) return;
3000
3037
  }
3001
3038
  if (!type) type = "task";
3002
3039
 
@@ -3052,6 +3089,7 @@ async function cmdWho(opts) {
3052
3089
  { id: "yes", name: "Create stub", tier: `Creates ${name}.md in People/` },
3053
3090
  { id: "no", name: "Skip", tier: "" },
3054
3091
  ], { multi: false });
3092
+ if (!create) return;
3055
3093
  if (create === "yes") {
3056
3094
  const peopleDir = path.join(entitiesDir, "People");
3057
3095
  fs.mkdirSync(peopleDir, { recursive: true });
@@ -3503,10 +3541,14 @@ async function cmdPrayer(opts) {
3503
3541
  barLn();
3504
3542
  const choice = await interactiveSelect(items, { multi: false });
3505
3543
 
3544
+ if (!choice || choice === "back") return;
3545
+
3506
3546
  if (choice === "fetch") {
3507
3547
  barLn();
3508
3548
  const city = await textInput({ label: "City", placeholder: "London" });
3549
+ if (city === null) return;
3509
3550
  const country = await textInput({ label: "Country", placeholder: "United Kingdom" });
3551
+ if (country === null) return;
3510
3552
  barLn();
3511
3553
 
3512
3554
  if (city && country) {
@@ -3532,7 +3574,7 @@ async function cmdPrayer(opts) {
3532
3574
  barLn(dim(" Format examples:"));
3533
3575
  barLn(dim(" 2026-03-08 05:20 13:00 16:15 18:01 19:45"));
3534
3576
  barLn(dim(" March 8: Fajr 05:20, Dhuhr 13:00, Asr 16:15, Maghrib 18:01, Isha 19:45"));
3535
- barLn(dim(" Type 'done' on a new line when finished."));
3577
+ barLn(dim(" Type 'done' or press Enter on empty line to finish. 'back' to cancel."));
3536
3578
  barLn();
3537
3579
 
3538
3580
  const lines = [];
@@ -3540,7 +3582,14 @@ async function cmdPrayer(opts) {
3540
3582
  await new Promise((resolve) => {
3541
3583
  const ask = () => {
3542
3584
  rl.question(`${BAR_COLOR}\u2502${S.reset} `, (line) => {
3543
- if (line.trim().toLowerCase() === "done" || line.trim() === "") {
3585
+ const t = line.trim().toLowerCase();
3586
+ if (t === "done" || t === "") {
3587
+ rl.close();
3588
+ resolve();
3589
+ return;
3590
+ }
3591
+ if (t === "back" || t === "cancel") {
3592
+ lines.length = 0; // clear
3544
3593
  rl.close();
3545
3594
  resolve();
3546
3595
  return;
@@ -3649,7 +3698,7 @@ async function cmdBackup(opts) {
3649
3698
  ];
3650
3699
 
3651
3700
  const choices = await interactiveSelect(items, { multi: true, preSelected: ["engine"] });
3652
- if (choices.length === 0) { barLn(dim(" Cancelled.")); return; }
3701
+ if (!choices || choices.length === 0) { barLn(dim(" Cancelled.")); return; }
3653
3702
 
3654
3703
  const now = new Date();
3655
3704
  const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
@@ -4215,6 +4264,39 @@ async function main() {
4215
4264
  process.exit(1);
4216
4265
  }
4217
4266
 
4267
+ // ── CLI self-update check ──
4268
+ if (opts.command === "update" && !opts._selfUpdated) {
4269
+ try {
4270
+ const localVer = require("./package.json").version;
4271
+ const npmVer = execSync("npm view mover-os version", { encoding: "utf8", timeout: 10000 }).trim();
4272
+ if (npmVer && npmVer !== localVer && compareVersions(npmVer, localVer) > 0) {
4273
+ barLn(`${yellow("CLI update available:")} ${dim(localVer)} ${dim("\u2192")} ${green(npmVer)}`);
4274
+ const sp = spinner("Updating CLI");
4275
+ try {
4276
+ execSync("npm i -g mover-os", { stdio: "ignore", timeout: 60000 });
4277
+ sp.stop(`CLI updated to ${npmVer}`);
4278
+ barLn(dim(" Re-running with updated CLI..."));
4279
+ barLn();
4280
+ // Re-exec with new code — pass args through, add flag to prevent loop
4281
+ const args = process.argv.slice(2).concat("--_self-updated");
4282
+ const { spawnSync } = require("child_process");
4283
+ const result = spawnSync(process.argv[0], [process.argv[1], ...args], {
4284
+ stdio: "inherit", cwd: process.cwd(),
4285
+ });
4286
+ process.exit(result.status || 0);
4287
+ } catch (e) {
4288
+ sp.stop(yellow(`CLI self-update failed: ${e.message}`));
4289
+ barLn(dim(" Continuing with current version..."));
4290
+ }
4291
+ } else {
4292
+ barLn(`${green("\u2713")} ${dim("CLI is up to date")} ${dim(`(${localVer})`)}`);
4293
+ }
4294
+ } catch {
4295
+ barLn(dim(" Could not check for CLI updates (offline?)"));
4296
+ }
4297
+ barLn();
4298
+ }
4299
+
4218
4300
  // ── Headless quick update ──
4219
4301
  if (opts.command === "update") {
4220
4302
  // Validate stored key
@@ -4347,6 +4429,7 @@ async function main() {
4347
4429
  mask: "\u25AA",
4348
4430
  placeholder: "MOVER-XXXX-XXXX",
4349
4431
  });
4432
+ if (key === null) return;
4350
4433
 
4351
4434
  const sp = spinner("Validating...");
4352
4435
  const valid = await validateKey(key);
@@ -4433,12 +4516,14 @@ async function main() {
4433
4516
  });
4434
4517
 
4435
4518
  const selected = await interactiveSelect(vaultItems, { multi: false });
4519
+ if (!selected) return;
4436
4520
 
4437
4521
  if (selected === "__manual__") {
4438
4522
  vaultPath = await textInput({
4439
4523
  label: "Where is your Obsidian vault?",
4440
4524
  initial: path.join(os.homedir(), "Mover-OS"),
4441
4525
  });
4526
+ if (vaultPath === null) return;
4442
4527
  } else {
4443
4528
  vaultPath = selected;
4444
4529
  }
@@ -4447,6 +4532,7 @@ async function main() {
4447
4532
  label: "Where is your Obsidian vault?",
4448
4533
  initial: path.join(os.homedir(), "Mover-OS"),
4449
4534
  });
4535
+ if (vaultPath === null) return;
4450
4536
  }
4451
4537
  } else {
4452
4538
  barLn(dim(`Vault: ${vaultPath}`));
@@ -4483,6 +4569,7 @@ async function main() {
4483
4569
  ],
4484
4570
  { multi: false, defaultIndex: 0 }
4485
4571
  );
4572
+ if (!installMode) return;
4486
4573
  }
4487
4574
 
4488
4575
  // ── Uninstall flow ──
@@ -4522,7 +4609,7 @@ async function main() {
4522
4609
  preSelected: ["engine"],
4523
4610
  });
4524
4611
 
4525
- if (backupChoices.length > 0 && !(backupChoices.length === 1 && backupChoices.includes("skip"))) {
4612
+ if (backupChoices && backupChoices.length > 0 && !(backupChoices.length === 1 && backupChoices.includes("skip"))) {
4526
4613
  const now = new Date();
4527
4614
  const ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}_${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
4528
4615
  const archivesDir = path.join(vaultPath, "04_Archives");
@@ -4685,12 +4772,13 @@ async function main() {
4685
4772
  multi: true,
4686
4773
  preSelected: detectedIds,
4687
4774
  });
4775
+ if (!selectedIds) return;
4688
4776
  const selectedAgents = AGENTS.filter((a) => selectedIds.includes(a.id));
4689
4777
 
4690
4778
  if (selectedAgents.length === 0) {
4691
4779
  barLn(yellow("No agents selected."));
4692
4780
  outro("Cancelled.");
4693
- process.exit(0);
4781
+ return;
4694
4782
  }
4695
4783
 
4696
4784
  // ── Change detection + selection (update mode only) ──
@@ -4735,9 +4823,9 @@ async function main() {
4735
4823
  { multi: false, defaultIndex: 0 }
4736
4824
  );
4737
4825
 
4738
- if (applyChoice === "cancel") {
4826
+ if (!applyChoice || applyChoice === "cancel") {
4739
4827
  outro("Cancelled.");
4740
- process.exit(0);
4828
+ return;
4741
4829
  }
4742
4830
 
4743
4831
  if (applyChoice === "select") {
@@ -4771,6 +4859,7 @@ async function main() {
4771
4859
  multi: true,
4772
4860
  preSelected: changedPreSelected,
4773
4861
  });
4862
+ if (!selectedFileIds) return;
4774
4863
 
4775
4864
  // Build workflow filter Set
4776
4865
  const selectedWfFiles = selectedFileIds
@@ -4817,6 +4906,7 @@ async function main() {
4817
4906
  multi: true,
4818
4907
  preSelected,
4819
4908
  });
4909
+ if (!selectedCatIds) return;
4820
4910
 
4821
4911
  installSkills = selectedCatIds.length > 0;
4822
4912
  if (installSkills) {
@@ -4843,6 +4933,7 @@ async function main() {
4843
4933
  ],
4844
4934
  { multi: false, defaultIndex: 0 }
4845
4935
  );
4936
+ if (!slChoice) return;
4846
4937
  installStatusLine = slChoice === "yes";
4847
4938
  }
4848
4939
 
@@ -4862,6 +4953,7 @@ async function main() {
4862
4953
  ],
4863
4954
  { multi: false, defaultIndex: 1 }
4864
4955
  );
4956
+ if (!ptChoice) return;
4865
4957
  if (ptChoice === "yes") {
4866
4958
  prayerSetup = true;
4867
4959
  barLn();
@@ -4876,6 +4968,7 @@ async function main() {
4876
4968
  ],
4877
4969
  { multi: false, defaultIndex: 0 }
4878
4970
  );
4971
+ if (!method || method === "later") { /* skip */ }
4879
4972
 
4880
4973
  const moverDir = path.join(os.homedir(), ".mover");
4881
4974
  if (!fs.existsSync(moverDir)) fs.mkdirSync(moverDir, { recursive: true, mode: 0o700 });
@@ -4887,7 +4980,7 @@ async function main() {
4887
4980
  barLn(dim(" 2026-03-08 05:20 13:00 16:15 18:01 19:45"));
4888
4981
  barLn(dim(" March 8: Fajr 05:20, Dhuhr 13:00, Asr 16:15, Maghrib 18:01, Isha 19:45"));
4889
4982
  barLn(dim(" Or paste a whole table — the system will parse it."));
4890
- barLn(dim(" When done, type 'done' on a new line and press Enter."));
4983
+ barLn(dim(" Type 'done' or press Enter on empty line to finish. 'back' to cancel."));
4891
4984
  barLn();
4892
4985
 
4893
4986
  const lines = [];
@@ -4895,7 +4988,14 @@ async function main() {
4895
4988
  await new Promise((resolve) => {
4896
4989
  const ask = () => {
4897
4990
  rl.question(`${BAR_COLOR}\u2502${S.reset} `, (line) => {
4898
- if (line.trim().toLowerCase() === "done" || line.trim() === "") {
4991
+ const t = line.trim().toLowerCase();
4992
+ if (t === "done" || t === "") {
4993
+ rl.close();
4994
+ resolve();
4995
+ return;
4996
+ }
4997
+ if (t === "back" || t === "cancel") {
4998
+ lines.length = 0;
4899
4999
  rl.close();
4900
5000
  resolve();
4901
5001
  return;
@@ -4920,7 +5020,9 @@ async function main() {
4920
5020
  } else if (method === "fetch") {
4921
5021
  barLn();
4922
5022
  const city = await textInput({ label: "City (e.g. London, Watford, Istanbul)", placeholder: "London" });
5023
+ if (city === null) break;
4923
5024
  const country = await textInput({ label: "Country", placeholder: "United Kingdom" });
5025
+ if (country === null) break;
4924
5026
  barLn();
4925
5027
 
4926
5028
  if (city && country) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mover-os",
3
- "version": "4.4.0",
3
+ "version": "4.4.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"