mover-os 4.7.2 → 4.7.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.
- package/install.js +49 -3
- package/package.json +8 -1
package/install.js
CHANGED
|
@@ -749,10 +749,32 @@ const POLAR_ORG_ID = process.env.POLAR_ORG_ID || "ba863394-6bca-4965-952a-06b7c0
|
|
|
749
749
|
// ─── Payload Download ────────────────────────────────────────────────────────
|
|
750
750
|
const DOWNLOAD_URL = "https://moveros.dev/api/download";
|
|
751
751
|
|
|
752
|
+
// Generates or reads a stable machine ID. Hardware-derived hash so the same
|
|
753
|
+
// physical machine produces the same ID even if the file is deleted (defeats
|
|
754
|
+
// casual sharing where someone copies just the license key but not the file).
|
|
755
|
+
function getMachineId(moverDir) {
|
|
756
|
+
const idPath = path.join(moverDir, ".machine-id");
|
|
757
|
+
// If file exists and is well-formed, reuse it
|
|
758
|
+
try {
|
|
759
|
+
const existing = fs.readFileSync(idPath, "utf8").trim();
|
|
760
|
+
if (existing && /^[a-f0-9]{32,}$/i.test(existing)) return existing;
|
|
761
|
+
} catch { /* file doesn't exist, fall through */ }
|
|
762
|
+
// Generate hardware-derived ID: hash(cpu_model + hostname + os_release + platform)
|
|
763
|
+
const crypto = require("crypto");
|
|
764
|
+
const cpuModel = (os.cpus()[0] && os.cpus()[0].model) || "unknown-cpu";
|
|
765
|
+
const fingerprint = `${cpuModel}|${os.hostname()}|${os.release()}|${os.platform()}|${os.arch()}`;
|
|
766
|
+
const id = crypto.createHash("sha256").update(fingerprint).digest("hex");
|
|
767
|
+
try {
|
|
768
|
+
fs.writeFileSync(idPath, id, { mode: 0o600 });
|
|
769
|
+
} catch { /* non-fatal — we'll just regenerate next run */ }
|
|
770
|
+
return id;
|
|
771
|
+
}
|
|
772
|
+
|
|
752
773
|
async function downloadPayload(key) {
|
|
753
774
|
const https = require("https");
|
|
754
775
|
const moverDir = path.join(os.homedir(), ".mover");
|
|
755
776
|
fs.mkdirSync(moverDir, { recursive: true, mode: 0o700 });
|
|
777
|
+
const machineId = getMachineId(moverDir);
|
|
756
778
|
// Clean old staged src/ before extracting fresh
|
|
757
779
|
const stagedSrc = path.join(moverDir, "src");
|
|
758
780
|
if (fs.existsSync(stagedSrc)) {
|
|
@@ -767,12 +789,16 @@ async function downloadPayload(key) {
|
|
|
767
789
|
hostname: url.hostname,
|
|
768
790
|
path: url.pathname,
|
|
769
791
|
method: "GET",
|
|
770
|
-
headers: { "X-License-Key": key.trim() },
|
|
792
|
+
headers: { "X-License-Key": key.trim(), "X-Machine-Id": machineId },
|
|
771
793
|
timeout: 60000,
|
|
772
794
|
}, (res) => {
|
|
773
795
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
774
796
|
// Follow redirect — only to trusted domains
|
|
775
797
|
const redirectUrl = new URL(res.headers.location);
|
|
798
|
+
if (redirectUrl.protocol !== 'https:') {
|
|
799
|
+
reject(new Error('Refusing non-HTTPS redirect'));
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
776
802
|
const trusted = ["github.com", "objects.githubusercontent.com", "moveros.dev"];
|
|
777
803
|
if (!trusted.some((d) => redirectUrl.hostname === d || redirectUrl.hostname.endsWith("." + d))) {
|
|
778
804
|
reject(new Error("Untrusted redirect domain"));
|
|
@@ -802,6 +828,12 @@ async function downloadPayload(key) {
|
|
|
802
828
|
res.on("end", () => reject(new Error("License key rejected by server")));
|
|
803
829
|
return;
|
|
804
830
|
}
|
|
831
|
+
if (res.statusCode === 403) {
|
|
832
|
+
let body = "";
|
|
833
|
+
res.on("data", (c) => body += c);
|
|
834
|
+
res.on("end", () => reject(new Error("License at activation cap. Email support@moveros.dev to free a slot.")));
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
805
837
|
if (res.statusCode !== 200) {
|
|
806
838
|
reject(new Error(`Download failed (HTTP ${res.statusCode})`));
|
|
807
839
|
return;
|
|
@@ -818,6 +850,15 @@ async function downloadPayload(key) {
|
|
|
818
850
|
req.end();
|
|
819
851
|
});
|
|
820
852
|
|
|
853
|
+
// Validate tar contents before extraction (zip-slip prevention)
|
|
854
|
+
const listing = execSync(`tar -tzf "${tarPath}"`, { encoding: 'utf8' });
|
|
855
|
+
const entries = listing.split('\n').filter(Boolean);
|
|
856
|
+
const badPaths = entries.filter(e => e.startsWith('/') || e.includes('..'));
|
|
857
|
+
if (badPaths.length > 0) {
|
|
858
|
+
fs.unlinkSync(tarPath);
|
|
859
|
+
throw new Error('Payload contains unsafe paths: ' + badPaths.join(', '));
|
|
860
|
+
}
|
|
861
|
+
|
|
821
862
|
// Extract tarball into ~/.mover/ (produces ~/.mover/src/)
|
|
822
863
|
try {
|
|
823
864
|
execSync(`tar -xzf "${tarPath}" -C "${moverDir}"`, { stdio: "ignore" });
|
|
@@ -5340,8 +5381,10 @@ async function cmdUpdateComprehensive(opts, bundleDir, startTime) {
|
|
|
5340
5381
|
createVaultStructure(vaultPath);
|
|
5341
5382
|
// NOTE: templates are now handled via smart merge below (after manifest load), NOT installTemplateFiles()
|
|
5342
5383
|
|
|
5343
|
-
//
|
|
5344
|
-
|
|
5384
|
+
// Version marker is NOT stamped here during interactive update.
|
|
5385
|
+
// /update workflow stamps it after migrations complete.
|
|
5386
|
+
// Stamping early would make /update see current-version == target-version
|
|
5387
|
+
// and skip all migrations (the "V4→V4" bug).
|
|
5345
5388
|
|
|
5346
5389
|
// ── Smart Update: hash-based change detection + auto-merge ──
|
|
5347
5390
|
const manifest = loadUpdateManifest();
|
|
@@ -5796,6 +5839,9 @@ async function main() {
|
|
|
5796
5839
|
} else if (msg.includes("401") || msg.includes("rejected")) {
|
|
5797
5840
|
barLn(red(" License key rejected by server."));
|
|
5798
5841
|
barLn(dim(" Has your key expired? Check polar.sh or email support@moveros.dev."));
|
|
5842
|
+
} else if (msg.includes("activation cap") || msg.includes("403")) {
|
|
5843
|
+
barLn(red(" License is registered to other devices."));
|
|
5844
|
+
barLn(dim(" Email support@moveros.dev to deactivate one and free a slot."));
|
|
5799
5845
|
} else if (msg.includes("500") || msg.includes("502") || msg.includes("503")) {
|
|
5800
5846
|
barLn(red(" Server error."));
|
|
5801
5847
|
barLn(dim(" Try again in a few minutes. If it persists, email support@moveros.dev."));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mover-os",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.3",
|
|
4
4
|
"description": "Your AI co-founder. Remembers your goals, pushes back when you drift. Works with 15 AI agents.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"moveros": "install.js"
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"install.js"
|
|
10
10
|
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node test/runner.mjs",
|
|
13
|
+
"test:smoke": "node test/smoke.mjs",
|
|
14
|
+
"test:structural": "node test/structural.mjs",
|
|
15
|
+
"test:machineid": "node test/getMachineId.test.mjs",
|
|
16
|
+
"test:full": "node test/runner.mjs --include-slow"
|
|
17
|
+
},
|
|
11
18
|
"keywords": [
|
|
12
19
|
"obsidian",
|
|
13
20
|
"productivity",
|