mason-context 0.17.4 → 0.19.0
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/CHANGELOG.md +19 -0
- package/README.md +2 -0
- package/dist/mason-audit.js +132 -68
- package/dist/mason-audit.js.map +1 -1
- package/dist/mason-auto.js +165 -45
- package/dist/mason-auto.js.map +1 -1
- package/dist/mason-drift.js +10 -4
- package/dist/mason-drift.js.map +1 -1
- package/dist/mason-hook.js +15 -9
- package/dist/mason-hook.js.map +1 -1
- package/dist/mason-mcp.js +1177 -348
- package/dist/mason-mcp.js.map +1 -1
- package/dist/mason-review.js +11 -5
- package/dist/mason-review.js.map +1 -1
- package/dist/mason-runtime.js +337 -0
- package/dist/mason-runtime.js.map +1 -0
- package/dist/mason.js +964 -238
- package/dist/mason.js.map +1 -1
- package/package.json +2 -1
package/dist/mason.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
if (!import.meta.url.endsWith("/mason-runtime.js")) await import("./mason-runtime.js").then(m => m.registerRuntime());
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -305,18 +306,34 @@ var init_storage = __esm({
|
|
|
305
306
|
}
|
|
306
307
|
});
|
|
307
308
|
|
|
308
|
-
// src/
|
|
309
|
+
// src/utils/store-lock.ts
|
|
309
310
|
import fs3 from "fs/promises";
|
|
310
311
|
import os from "os";
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
async function lockDiagnostic(file) {
|
|
313
|
+
let reason2;
|
|
313
314
|
try {
|
|
314
|
-
|
|
315
|
+
const owner = JSON.parse(await fs3.readFile(file, "utf8"));
|
|
316
|
+
if (!owner || typeof owner.host !== "string" || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
317
|
+
reason2 = "Lock owner data is incomplete or malformed.";
|
|
318
|
+
} else if (owner.host !== os.hostname()) {
|
|
319
|
+
reason2 = "The lock belongs to another host; its process cannot be checked locally.";
|
|
320
|
+
} else {
|
|
321
|
+
try {
|
|
322
|
+
process.kill(owner.pid, 0);
|
|
323
|
+
reason2 = `Local owner PID ${owner.pid} is still running.`;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
reason2 = error.code === "ESRCH" ? `Local owner PID ${owner.pid} has exited, but lock recovery has not completed.` : `Local owner PID ${owner.pid} could not be checked.`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
315
328
|
} catch (error) {
|
|
316
|
-
|
|
329
|
+
reason2 = error instanceof SyntaxError ? "Lock owner data is incomplete or malformed." : "Lock owner data could not be read; the lock may have changed.";
|
|
330
|
+
}
|
|
331
|
+
if (await fs3.lstat(file + ".reclaim").then(() => true, () => false)) {
|
|
332
|
+
reason2 += " A recovery guard also needs inspection: " + file + ".reclaim.";
|
|
317
333
|
}
|
|
334
|
+
return reason2 + " Retry if a writer is active. For manual recovery, stop all Mason writers for this checkout, confirm they have exited (including other hosts), then remove only confirmed abandoned lock and lock.reclaim files. See docs/reference.md#snapshot-repairs-and-verification.";
|
|
318
335
|
}
|
|
319
|
-
async function
|
|
336
|
+
async function withStoreLock(root, directory, run, waitMs = 5e3, label = "Mason store") {
|
|
320
337
|
const file = await storePath(root, directory + "/lock", true);
|
|
321
338
|
const deadline = Date.now() + waitMs;
|
|
322
339
|
let handle;
|
|
@@ -359,7 +376,7 @@ async function withLock(root, directory, run, waitMs = 5e3) {
|
|
|
359
376
|
}
|
|
360
377
|
} catch {
|
|
361
378
|
}
|
|
362
|
-
if (Date.now() >= deadline) throw new Error("
|
|
379
|
+
if (Date.now() >= deadline) throw new Error(label + " is busy or its lock needs inspection: " + file + ". " + await lockDiagnostic(file));
|
|
363
380
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
364
381
|
}
|
|
365
382
|
}
|
|
@@ -370,11 +387,30 @@ async function withLock(root, directory, run, waitMs = 5e3) {
|
|
|
370
387
|
await fs3.unlink(file);
|
|
371
388
|
}
|
|
372
389
|
}
|
|
390
|
+
var init_store_lock = __esm({
|
|
391
|
+
"src/utils/store-lock.ts"() {
|
|
392
|
+
"use strict";
|
|
393
|
+
init_storage();
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// src/automation/store.ts
|
|
398
|
+
import { z } from "zod";
|
|
399
|
+
function parseState(raw) {
|
|
400
|
+
try {
|
|
401
|
+
return stateSchema.parse(raw);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
throw new Error("Invalid automation state; original evidence was retained.", { cause: error });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function withLock(root, directory, run, waitMs = 5e3) {
|
|
407
|
+
return withStoreLock(root, directory, run, waitMs, "Automation");
|
|
408
|
+
}
|
|
373
409
|
var hostSchema, events, stateSchema;
|
|
374
410
|
var init_store = __esm({
|
|
375
411
|
"src/automation/store.ts"() {
|
|
376
412
|
"use strict";
|
|
377
|
-
|
|
413
|
+
init_store_lock();
|
|
378
414
|
hostSchema = z.enum(["claude", "codex"]);
|
|
379
415
|
events = ["session_start", "turn_start", "before_tool", "after_tool", "task_end"];
|
|
380
416
|
stateSchema = z.object({
|
|
@@ -1204,6 +1240,14 @@ var init_scope = __esm({
|
|
|
1204
1240
|
}
|
|
1205
1241
|
});
|
|
1206
1242
|
|
|
1243
|
+
// src/snapshot/lock.ts
|
|
1244
|
+
var init_lock = __esm({
|
|
1245
|
+
"src/snapshot/lock.ts"() {
|
|
1246
|
+
"use strict";
|
|
1247
|
+
init_store_lock();
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1207
1251
|
// src/test-map.ts
|
|
1208
1252
|
import path9 from "path";
|
|
1209
1253
|
async function buildTestMap(dir) {
|
|
@@ -1313,6 +1357,7 @@ var init_snapshot = __esm({
|
|
|
1313
1357
|
init_files();
|
|
1314
1358
|
init_files();
|
|
1315
1359
|
init_storage();
|
|
1360
|
+
init_lock();
|
|
1316
1361
|
init_paths();
|
|
1317
1362
|
init_test_map();
|
|
1318
1363
|
repoPath = z2.string().refine((value) => normalizeRepoPath(value) !== null, "Expected a relative repository path");
|
|
@@ -1320,6 +1365,7 @@ var init_snapshot = __esm({
|
|
|
1320
1365
|
refreshedHash: z2.string().optional(),
|
|
1321
1366
|
verifiedAt: z2.string().optional(),
|
|
1322
1367
|
verifiedHash: z2.string().optional(),
|
|
1368
|
+
verificationToken: z2.string().optional(),
|
|
1323
1369
|
verificationFailed: z2.boolean().optional(),
|
|
1324
1370
|
verificationNote: z2.string().optional()
|
|
1325
1371
|
};
|
|
@@ -1942,6 +1988,10 @@ var init_release_metadata = __esm({
|
|
|
1942
1988
|
});
|
|
1943
1989
|
|
|
1944
1990
|
// src/audit/checks/deps-changed.ts
|
|
1991
|
+
function hasDependencyContent(content2) {
|
|
1992
|
+
const text2 = content2.replace(/<!-- mason:start -->[\s\S]*?<!-- mason:end -->/g, "").replace(/<!--[\s\S]*?-->/g, "");
|
|
1993
|
+
return /\b(?:dependenc(?:y|ies)|librar(?:y|ies)|frameworks?|tech(?:nology)? stack|prerequisites|requirements)\b/i.test(text2) || /\b(?:package\.json|build\.gradle(?:\.kts)?|settings\.gradle(?:\.kts)?|libs\.versions\.toml|Cargo\.toml|go\.mod|pyproject\.toml|requirements\.txt|Gemfile|composer\.json)\b/i.test(text2) || /\b(?:node(?:\.js)?|npm|pnpm|yarn|bun|deno|python|ruby|rust|go|java|jdk|kotlin|gradle|swift|php|react|vue|angular|compose|ktor|room)\s*(?:version\s*)?[`*:=>~^v\s-]*\d+(?:\.\d+)*\b/i.test(text2) || /\b(?:npm|pnpm|yarn|pip3?|cargo|gem|composer)\s+(?:install|add|require)\b/i.test(text2);
|
|
1994
|
+
}
|
|
1945
1995
|
function manifestPathspecs(doc) {
|
|
1946
1996
|
const scope = documentScope(doc);
|
|
1947
1997
|
if (scope === ".") return MANIFEST_PATHSPECS;
|
|
@@ -1968,6 +2018,19 @@ async function checkDepsChanged(ctx) {
|
|
|
1968
2018
|
reason: `${doc.path} has uncommitted edits \u2013 suppressed while in flight`
|
|
1969
2019
|
});
|
|
1970
2020
|
}
|
|
2021
|
+
let content2 = doc.content;
|
|
2022
|
+
if (doc.dirty) {
|
|
2023
|
+
try {
|
|
2024
|
+
content2 = (await inspectionGit(
|
|
2025
|
+
["show", `${doc.lastCommit.hash}:${doc.path}`],
|
|
2026
|
+
{ cwd: ctx.root, maxBuffer: 10 * 1024 * 1024, timeout: 1e4 }
|
|
2027
|
+
)).stdout;
|
|
2028
|
+
} catch {
|
|
2029
|
+
result.skipped.push({ check: "deps-changed", doc: doc.path, reason: `${doc.path}: committed dependency content is unavailable` });
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
if (!hasDependencyContent(content2)) continue;
|
|
1971
2034
|
const range = await commitsTouchingSince(
|
|
1972
2035
|
ctx.root,
|
|
1973
2036
|
doc.lastCommit.hash,
|
|
@@ -2013,6 +2076,7 @@ var init_deps_changed = __esm({
|
|
|
2013
2076
|
init_docs();
|
|
2014
2077
|
init_release_metadata();
|
|
2015
2078
|
init_git();
|
|
2079
|
+
init_inspection();
|
|
2016
2080
|
init_checks();
|
|
2017
2081
|
MANIFEST_COMMITS_CAP = 10;
|
|
2018
2082
|
MANIFEST_PATHSPECS = [
|
|
@@ -2908,7 +2972,7 @@ async function verifyOriginal(rootDir, baselinePath, options, session) {
|
|
|
2908
2972
|
return {
|
|
2909
2973
|
...base,
|
|
2910
2974
|
status: "review-required",
|
|
2911
|
-
reason: "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
|
|
2975
|
+
reason: (now ? "The current check still reports this advisory. " : finding.type === "deps-changed" ? "The current check no longer reports this condition; the historical advisory remains awaiting assessment. " : "The current check no longer reports the original finding; retained evidence still requires assessment. ") + "An audit cannot establish that this advisory was reviewed. Retain its original evidence and report a separate assessment; editing or committing the doc is not approval."
|
|
2912
2976
|
};
|
|
2913
2977
|
}
|
|
2914
2978
|
return { ...base, status: "resolved", reason: "The original check ran and no longer reports this claim. Inspect the edit for semantic correctness." };
|
|
@@ -3086,7 +3150,7 @@ async function collectInputs(root) {
|
|
|
3086
3150
|
git3(root, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--", ".", ":(exclude).mason/reports"),
|
|
3087
3151
|
git3(root, "ls-files", "--stage", "-z", "--", ".", ":(exclude).mason/reports")
|
|
3088
3152
|
]) : ["", ""];
|
|
3089
|
-
const common = [
|
|
3153
|
+
const common = [6, engineVersion, head, shallow, replacements, docContents];
|
|
3090
3154
|
const keys = {
|
|
3091
3155
|
"deleted-reference": hash([common, claims, docStatus]),
|
|
3092
3156
|
"new-module": hash([common, modules]),
|
|
@@ -3146,7 +3210,7 @@ var init_evidence = __esm({
|
|
|
3146
3210
|
init_dead_command();
|
|
3147
3211
|
init_storage();
|
|
3148
3212
|
init_advisory_review();
|
|
3149
|
-
engineVersion = true ? "0.
|
|
3213
|
+
engineVersion = true ? "0.19.0" : "development";
|
|
3150
3214
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
3151
3215
|
content = readAuditInput;
|
|
3152
3216
|
cacheSchema = z8.object({ version: z8.literal(1), entries: z8.record(z8.object({ key: z8.string(), result: checkResultSchema })), digest: z8.string() });
|
|
@@ -3360,13 +3424,17 @@ async function observeReadOnlyTool(dir, event) {
|
|
|
3360
3424
|
}
|
|
3361
3425
|
function summarize(report) {
|
|
3362
3426
|
const open = report.findings.filter((f) => f.status !== "resolved");
|
|
3427
|
+
const onlyReview = report.status === "incomplete" && !report.diagnostics.length && !report.counts.unresolved && !report.counts.unverified && report.counts["review-required"] > 0;
|
|
3363
3428
|
return [
|
|
3364
|
-
`Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
|
|
3365
|
-
...open.slice(0, 4).map((f) =>
|
|
3429
|
+
onlyReview ? `Mason: ${report.counts["review-required"]} advisor${report.counts["review-required"] === 1 ? "y" : "ies"} awaiting review; no unresolved issues.` : `Mason: ${report.status}; ${report.counts.unresolved} unresolved, ${report.counts["review-required"]} need review, ${report.counts.unverified} unverified.`,
|
|
3430
|
+
...open.slice(0, 4).map((f) => {
|
|
3431
|
+
const detail = f.current ? f.current.message : f.status === "review-required" ? `${f.original.type}: current check no longer reports ${f.original.type === "deps-changed" ? "this condition" : "the original finding"}; historical evidence awaits assessment.` : `Original evidence: ${f.original.message}. ${f.reason}`;
|
|
3432
|
+
return `[${f.status}] ${cleanText(f.original.anchor.doc)}: ${cleanText(detail)}`;
|
|
3433
|
+
}),
|
|
3366
3434
|
...open.length > 4 ? [`${open.length - 4} more findings in the report.`] : [],
|
|
3367
3435
|
...report.diagnostics.slice(0, 2).map(cleanText),
|
|
3368
3436
|
`Evidence: ${report.reportPath}. Resume/check with mason_automation(action: "check") or mason-auto check.`,
|
|
3369
|
-
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings and
|
|
3437
|
+
"Keep original evidence. Address findings relevant to the authorized task; report unrelated findings with a brief scope reason and suggested follow-up. Use review_advisory for authorized assessments; otherwise leave advisories open without approving them."
|
|
3370
3438
|
].join("\n");
|
|
3371
3439
|
}
|
|
3372
3440
|
async function loadState(ws) {
|
|
@@ -3590,7 +3658,7 @@ async function automate(dir, event) {
|
|
|
3590
3658
|
if (event.event === "after_tool" && event.mutating) {
|
|
3591
3659
|
session.mutationObserved = true;
|
|
3592
3660
|
if (!event.toolId || !session.pending[event.toolId]) {
|
|
3593
|
-
const gap = "A tool completed without an observed matching pre-tool capture; pre-edit coverage is unknown.";
|
|
3661
|
+
const gap = "A tool completed without an observed matching pre-tool capture in this checkout; pre-edit coverage is unknown. The tool may have changed checkouts; captures are kept separately for each worktree.";
|
|
3594
3662
|
if (!session.coverageGaps.includes(gap)) session.coverageGaps.push(gap);
|
|
3595
3663
|
}
|
|
3596
3664
|
if (event.toolId) delete session.pending[event.toolId];
|
|
@@ -3733,11 +3801,39 @@ var init_hook_root = __esm({
|
|
|
3733
3801
|
|
|
3734
3802
|
// src/setup/model.ts
|
|
3735
3803
|
import { z as z11 } from "zod";
|
|
3804
|
+
import fs16 from "fs/promises";
|
|
3805
|
+
import path23 from "path";
|
|
3736
3806
|
async function loadSetup(root) {
|
|
3737
3807
|
const raw = await readStoreJson(root, SETUP_PATH);
|
|
3738
3808
|
if (raw === null) return null;
|
|
3739
3809
|
return setupSchema.parse(raw);
|
|
3740
3810
|
}
|
|
3811
|
+
async function effectiveSetup(root) {
|
|
3812
|
+
const local = await loadSetup(root);
|
|
3813
|
+
if (local) return { root, setup: local };
|
|
3814
|
+
const common = await fs16.realpath(path23.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim()));
|
|
3815
|
+
const own = await fs16.realpath((await git3(root, "rev-parse", "--absolute-git-dir")).trim());
|
|
3816
|
+
if (common === own) return { root, setup: null };
|
|
3817
|
+
const primary = (await git3(root, "worktree", "list", "--porcelain", "-z")).split("\0\0")[0].split("\0");
|
|
3818
|
+
const candidate = primary.find((field) => field.startsWith("worktree "))?.slice(9);
|
|
3819
|
+
if (!candidate || primary.includes("bare")) return { root, setup: null };
|
|
3820
|
+
let inheritedRoot;
|
|
3821
|
+
try {
|
|
3822
|
+
inheritedRoot = await fs16.realpath((await git3(candidate, "rev-parse", "--show-toplevel")).trim());
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
if (/must be run in a work tree/.test(String(error.stderr))) return { root, setup: null };
|
|
3825
|
+
throw error;
|
|
3826
|
+
}
|
|
3827
|
+
const inheritedCommon = await fs16.realpath(path23.resolve(
|
|
3828
|
+
inheritedRoot,
|
|
3829
|
+
(await git3(inheritedRoot, "rev-parse", "--git-common-dir")).trim()
|
|
3830
|
+
));
|
|
3831
|
+
if (inheritedCommon !== common) throw new Error("Linked worktree setup does not belong to the same Git repository.");
|
|
3832
|
+
const setup = await loadSetup(inheritedRoot);
|
|
3833
|
+
const inactive = await readStoreJson(root, INACTIVE_HOSTS_PATH);
|
|
3834
|
+
if (inactive && setup) for (const host of inactiveHostsSchema.parse(inactive).hosts) delete setup.hosts[host];
|
|
3835
|
+
return { root: inheritedRoot, setup };
|
|
3836
|
+
}
|
|
3741
3837
|
async function loadSetupReceipt(root, directory, host) {
|
|
3742
3838
|
const raw = await readStoreJson(root, directory + "/setup-" + host + ".json");
|
|
3743
3839
|
if (raw === null) return null;
|
|
@@ -3745,10 +3841,11 @@ async function loadSetupReceipt(root, directory, host) {
|
|
|
3745
3841
|
if (receipt.root !== root || receipt.host !== host) throw new Error("Setup receipt belongs to another installation.");
|
|
3746
3842
|
return receipt;
|
|
3747
3843
|
}
|
|
3748
|
-
var setupHostSchema, setupSchema, SETUP_PATH, receiptSchema;
|
|
3844
|
+
var setupHostSchema, setupSchema, SETUP_PATH, INACTIVE_HOSTS_PATH, inactiveHostsSchema, receiptSchema;
|
|
3749
3845
|
var init_model = __esm({
|
|
3750
3846
|
"src/setup/model.ts"() {
|
|
3751
3847
|
"use strict";
|
|
3848
|
+
init_evidence();
|
|
3752
3849
|
init_storage();
|
|
3753
3850
|
setupHostSchema = z11.object({
|
|
3754
3851
|
configuredVersion: z11.string(),
|
|
@@ -3762,6 +3859,8 @@ var init_model = __esm({
|
|
|
3762
3859
|
claude: setupHostSchema.optional()
|
|
3763
3860
|
}) });
|
|
3764
3861
|
SETUP_PATH = ".mason/local/setup.json";
|
|
3862
|
+
INACTIVE_HOSTS_PATH = ".mason/local/inactive-hosts.json";
|
|
3863
|
+
inactiveHostsSchema = z11.object({ version: z11.literal(1), hosts: z11.array(z11.enum(["claude", "codex"])) });
|
|
3765
3864
|
receiptSchema = z11.object({
|
|
3766
3865
|
version: z11.literal(1),
|
|
3767
3866
|
host: z11.enum(["codex", "claude"]),
|
|
@@ -3788,7 +3887,7 @@ __export(launcher_exports, {
|
|
|
3788
3887
|
});
|
|
3789
3888
|
import { execFile as execFile2 } from "child_process";
|
|
3790
3889
|
import { promisify as promisify2 } from "util";
|
|
3791
|
-
import
|
|
3890
|
+
import fs17 from "fs/promises";
|
|
3792
3891
|
function globalCommand(args2, platform = process.platform) {
|
|
3793
3892
|
return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
|
|
3794
3893
|
}
|
|
@@ -3808,8 +3907,8 @@ async function installedCommand() {
|
|
|
3808
3907
|
}
|
|
3809
3908
|
}
|
|
3810
3909
|
async function prepareLaunch(host, options = {}) {
|
|
3811
|
-
const root = await
|
|
3812
|
-
const entry = (await
|
|
3910
|
+
const root = await fs17.realpath((await git3(options.dir ?? process.cwd(), "rev-parse", "--show-toplevel")).trim());
|
|
3911
|
+
const entry = (await effectiveSetup(root)).setup?.hosts[host];
|
|
3813
3912
|
process.env.MASON_SETUP_ROOT = root;
|
|
3814
3913
|
process.env.MASON_SETUP_HOST = host;
|
|
3815
3914
|
delete process.env.MASON_SETUP_REVISION;
|
|
@@ -3831,7 +3930,7 @@ var init_launcher = __esm({
|
|
|
3831
3930
|
"use strict";
|
|
3832
3931
|
init_evidence();
|
|
3833
3932
|
init_model();
|
|
3834
|
-
executingVersion = () => true ? "0.
|
|
3933
|
+
executingVersion = () => true ? "0.19.0" : "development";
|
|
3835
3934
|
mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
|
|
3836
3935
|
hookCommand = (host) => `mason --setup-host ${host} auto`;
|
|
3837
3936
|
}
|
|
@@ -3844,7 +3943,7 @@ __export(observations_exports, {
|
|
|
3844
3943
|
observeActivation: () => observeActivation,
|
|
3845
3944
|
readObservation: () => readObservation
|
|
3846
3945
|
});
|
|
3847
|
-
import
|
|
3946
|
+
import fs18 from "fs/promises";
|
|
3848
3947
|
import { z as z12 } from "zod";
|
|
3849
3948
|
function observationPath(directory, host, revision, version = executingVersion()) {
|
|
3850
3949
|
return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
|
|
@@ -3861,15 +3960,15 @@ async function observeActivation(dir, event, options = {}) {
|
|
|
3861
3960
|
if (host !== "codex" && host !== "claude" || !process.env.MASON_SETUP_ROOT) return null;
|
|
3862
3961
|
try {
|
|
3863
3962
|
if (!revision) {
|
|
3864
|
-
if ((await
|
|
3963
|
+
if ((await effectiveSetup(process.env.MASON_SETUP_ROOT)).setup?.hosts[host]) {
|
|
3865
3964
|
return "Mason setup is now present. Restart the assistant to connect this MCP session to the configured integration; activation has not been established by this session.";
|
|
3866
3965
|
}
|
|
3867
3966
|
return inactiveSetupMessage();
|
|
3868
3967
|
}
|
|
3869
3968
|
const capturedDirectory = options.directory?.match(/^\.mason\/reports\/automation\/[a-f0-9]{24}$/)?.[0] ?? options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
|
|
3870
|
-
const ws = capturedDirectory ? { root: await
|
|
3871
|
-
if (ws.root !== await
|
|
3872
|
-
const setup = await
|
|
3969
|
+
const ws = capturedDirectory ? { root: await fs18.realpath(dir), directory: capturedDirectory } : await workspace(dir);
|
|
3970
|
+
if (ws.root !== await fs18.realpath(process.env.MASON_SETUP_ROOT)) return null;
|
|
3971
|
+
const { setup } = await effectiveSetup(ws.root);
|
|
3873
3972
|
if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
|
|
3874
3973
|
const directory = ws.directory + "/activation";
|
|
3875
3974
|
await withLock(ws.root, directory, async () => {
|
|
@@ -4046,8 +4145,8 @@ __export(files_exports, {
|
|
|
4046
4145
|
managedBlock: () => managedBlock,
|
|
4047
4146
|
readText: () => readText
|
|
4048
4147
|
});
|
|
4049
|
-
import
|
|
4050
|
-
import
|
|
4148
|
+
import fs19 from "fs/promises";
|
|
4149
|
+
import path24 from "path";
|
|
4051
4150
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
4052
4151
|
async function readText(root, file) {
|
|
4053
4152
|
try {
|
|
@@ -4076,14 +4175,14 @@ async function applyEdit(root, edit) {
|
|
|
4076
4175
|
if (edit.after === null) {
|
|
4077
4176
|
const file2 = await storePath(root, edit.path);
|
|
4078
4177
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
|
|
4079
|
-
await
|
|
4178
|
+
await fs19.unlink(file2);
|
|
4080
4179
|
return true;
|
|
4081
4180
|
}
|
|
4082
4181
|
const file = await storePath(root, edit.path, true);
|
|
4083
|
-
const temporary =
|
|
4182
|
+
const temporary = path24.join(path24.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
|
|
4084
4183
|
try {
|
|
4085
|
-
const mode = await
|
|
4086
|
-
const handle = await
|
|
4184
|
+
const mode = await fs19.stat(file).then((s) => s.mode & 511, () => 384);
|
|
4185
|
+
const handle = await fs19.open(temporary, "wx", mode);
|
|
4087
4186
|
try {
|
|
4088
4187
|
await handle.writeFile(edit.after, "utf8");
|
|
4089
4188
|
await handle.sync();
|
|
@@ -4091,10 +4190,10 @@ async function applyEdit(root, edit) {
|
|
|
4091
4190
|
await handle.close();
|
|
4092
4191
|
}
|
|
4093
4192
|
if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
|
|
4094
|
-
await
|
|
4193
|
+
await fs19.rename(temporary, file);
|
|
4095
4194
|
return true;
|
|
4096
4195
|
} finally {
|
|
4097
|
-
await
|
|
4196
|
+
await fs19.rm(temporary, { force: true });
|
|
4098
4197
|
}
|
|
4099
4198
|
}
|
|
4100
4199
|
var init_files2 = __esm({
|
|
@@ -4200,7 +4299,7 @@ Mason provides recorded decisions and file impact over MCP. A concept map is opt
|
|
|
4200
4299
|
- Before editing a file \u2192 \`get_impact\` to check references, tests, and historical change partners.
|
|
4201
4300
|
- Investigation or correction reveals a reusable project lesson \u2192 \`save_decision\` before finishing. Capture only the lasting constraint and reason, with anchors and known sources, owner, and recorder. This creates a proposal; never infer approval. Preserve uncertain causes and unknown attribution. Route project lessons here; keep personal preferences in agent memory. Skip code summaries, current checkout status, temporary task permissions, session trivia, and secrets. No qualifying lesson means no record.
|
|
4202
4301
|
- Before saving, compare the lesson with matching \`get_context\` records and any pending proposal. If new evidence changes the same lesson's assumptions, scope, or recommended action, revise it with \`save_decision(id: existingId)\`, including when it is accepted. Preserve supported rationale and sources, replace obsolete instructions, and add the new evidence. Create a separate record for a genuinely distinct lesson; skip unchanged restatements. Revision preserves history and leaves the accepted version operative until authorized review.
|
|
4203
|
-
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, and failed
|
|
4302
|
+
- Consult trust metadata before relying on entries: unknown or changed freshness requires inspection, stale or unknown map verification requires a fresh evidence review, and a recorded failed verdict means the description must be corrected. Check approval too: proposals are suggestions, legacy records are unreviewed, and accepted decisions are recorded constraints subject to freshness checks. An accepted revision remains operative while a pending proposal is reviewed; keep both versions and their freshness distinct.
|
|
4204
4303
|
- Asked to review or re-verify a decision \u2192 \`review_decision\` first to inspect content, sources, history, and code changes. Record acceptance, reaffirmation, or retirement only when authorized by the user or a cited team review, with the actual reviewer and reason. Never infer approval from unchanged code. Review and commit the local record through the normal project workflow.
|
|
4205
4304
|
- For an architectural overview, use \`get_snapshot\` if a map is available. If \`map.status\` is missing or invalid, use available decisions and source evidence; do not start building a map unless requested.
|
|
4206
4305
|
- \`mason_init\` returns documentation audit and committed-diff review results, plus a short setup guide. Pass \`evidence\` with local CI manifest paths to include test and analysis results; the CLI equivalent is \`mason-review --evidence <manifest>\`. State skipped, unavailable, stale, or unknown checks explicitly. Related accepted decisions identify review context, not proven violations.
|
|
@@ -4502,6 +4601,8 @@ __export(teardown_exports, {
|
|
|
4502
4601
|
teardownProject: () => teardownProject
|
|
4503
4602
|
});
|
|
4504
4603
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
4604
|
+
import fs20 from "fs/promises";
|
|
4605
|
+
import path25 from "path";
|
|
4505
4606
|
import { parse as parse3, stringify as stringify2 } from "smol-toml";
|
|
4506
4607
|
import fg6 from "fast-glob";
|
|
4507
4608
|
import { z as z18 } from "zod";
|
|
@@ -4523,6 +4624,11 @@ async function planTeardown(root, selected) {
|
|
|
4523
4624
|
const ownership = ownershipBefore === null ? { version: 1, files: {} } : ownershipSchema.parse(JSON.parse(ownershipBefore));
|
|
4524
4625
|
const setupBefore = await read(SETUP_PATH);
|
|
4525
4626
|
const setup = setupBefore === null ? null : setupSchema.parse(JSON.parse(setupBefore));
|
|
4627
|
+
const linked = (await workspace(root)).gitDir !== await fs20.realpath(
|
|
4628
|
+
path25.resolve(root, (await git3(root, "rev-parse", "--git-common-dir")).trim())
|
|
4629
|
+
);
|
|
4630
|
+
const inactiveBefore = linked && !setup ? await read(INACTIVE_HOSTS_PATH) : null;
|
|
4631
|
+
const inactive = inactiveBefore === null ? { version: 1, hosts: [] } : inactiveHostsSchema.parse(JSON.parse(inactiveBefore));
|
|
4526
4632
|
const automationBefore = await read(AUTOMATION_PATH);
|
|
4527
4633
|
const automation = automationBefore === null ? null : recordSchema2.parse(JSON.parse(automationBefore));
|
|
4528
4634
|
const commands2 = (host) => new Set([
|
|
@@ -4666,7 +4772,16 @@ async function planTeardown(root, selected) {
|
|
|
4666
4772
|
delete ownership.files[configPath(host)];
|
|
4667
4773
|
}
|
|
4668
4774
|
if (!remainingHost) for (const file of DOC_CANDIDATES) delete ownership.files[file];
|
|
4669
|
-
if (setupBefore !== null) cleanup.push({
|
|
4775
|
+
if (setupBefore !== null) cleanup.push({
|
|
4776
|
+
path: SETUP_PATH,
|
|
4777
|
+
before: setupBefore,
|
|
4778
|
+
after: linked || Object.keys(setup.hosts).length ? json(setup) : null
|
|
4779
|
+
});
|
|
4780
|
+
else if (linked) cleanup.push({
|
|
4781
|
+
path: INACTIVE_HOSTS_PATH,
|
|
4782
|
+
before: inactiveBefore,
|
|
4783
|
+
after: json({ version: 1, hosts: [.../* @__PURE__ */ new Set([...inactive.hosts, ...selected])].sort() })
|
|
4784
|
+
});
|
|
4670
4785
|
if (automationBefore !== null) cleanup.push({ path: AUTOMATION_PATH, before: automationBefore, after: Object.keys(automation.hosts).length ? json(automation) : null });
|
|
4671
4786
|
if (ownershipBefore !== null) cleanup.push({ path: OWNERSHIP_PATH, before: ownershipBefore, after: Object.keys(ownership.files).length ? json(ownership) : null });
|
|
4672
4787
|
edits.push(...cleanup);
|
|
@@ -5107,10 +5222,10 @@ var init_check = __esm({
|
|
|
5107
5222
|
});
|
|
5108
5223
|
|
|
5109
5224
|
// src/review/evidence.ts
|
|
5110
|
-
import
|
|
5225
|
+
import path26 from "path";
|
|
5111
5226
|
import { z as z22 } from "zod";
|
|
5112
5227
|
function relativeArtifact(root, file) {
|
|
5113
|
-
const relative =
|
|
5228
|
+
const relative = path26.isAbsolute(file) ? path26.relative(root, file) : file;
|
|
5114
5229
|
const normalized = normalizeRepoPath(relative);
|
|
5115
5230
|
if (!normalized) throw new Error(`Evidence artifact must be inside the repository: ${file}`);
|
|
5116
5231
|
return normalized;
|
|
@@ -5207,7 +5322,7 @@ async function collectReviewEvidence(root, manifests, changedFiles, decisions, d
|
|
|
5207
5322
|
const report = await readStoreJson(root, file);
|
|
5208
5323
|
if (report === null) throw new Error(`Report artifact is missing: ${file}`);
|
|
5209
5324
|
const sourceRoot = check.sourceRoot ?? root;
|
|
5210
|
-
if (!
|
|
5325
|
+
if (!path26.posix.isAbsolute(sourceRoot) && !/^[A-Za-z]:[\\/]/.test(sourceRoot)) throw new Error("sourceRoot must identify the absolute checkout root on the check runner.");
|
|
5211
5326
|
const parsed = check.report.format === "vitest-json" ? parseVitest(report, sourceRoot) : check.report.format === "mason-check-json" ? parseCheck(report, sourceRoot) : parseSarif(report, sourceRoot);
|
|
5212
5327
|
result.outcome = parsed.outcome;
|
|
5213
5328
|
result.counts = parsed.counts;
|
|
@@ -5328,8 +5443,8 @@ var init_evidence2 = __esm({
|
|
|
5328
5443
|
});
|
|
5329
5444
|
|
|
5330
5445
|
// src/review/review.ts
|
|
5331
|
-
import
|
|
5332
|
-
import
|
|
5446
|
+
import fs21 from "fs/promises";
|
|
5447
|
+
import path27 from "path";
|
|
5333
5448
|
import { execFile as execFile4 } from "child_process";
|
|
5334
5449
|
import { promisify as promisify4 } from "util";
|
|
5335
5450
|
async function resolveMergeBase(resolvedRoot, base, head) {
|
|
@@ -5358,7 +5473,7 @@ function anchorsTouched(record, changedFiles) {
|
|
|
5358
5473
|
return matchingPaths(record.files, changedFiles);
|
|
5359
5474
|
}
|
|
5360
5475
|
async function computeReview(rootDir, base, options = {}) {
|
|
5361
|
-
const resolvedRoot =
|
|
5476
|
+
const resolvedRoot = path27.resolve(rootDir);
|
|
5362
5477
|
const head = await getCurrentGitHash(resolvedRoot);
|
|
5363
5478
|
if (head === "unknown") return null;
|
|
5364
5479
|
const mergeBase = await resolveMergeBase(resolvedRoot, base, head);
|
|
@@ -5408,7 +5523,7 @@ async function computeReview(rootDir, base, options = {}) {
|
|
|
5408
5523
|
analyzed,
|
|
5409
5524
|
async (relPath) => {
|
|
5410
5525
|
try {
|
|
5411
|
-
await
|
|
5526
|
+
await fs21.access(path27.join(resolvedRoot, relPath));
|
|
5412
5527
|
return true;
|
|
5413
5528
|
} catch {
|
|
5414
5529
|
return false;
|
|
@@ -5551,8 +5666,9 @@ __export(status_exports, {
|
|
|
5551
5666
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
5552
5667
|
async function setupStatus(dir) {
|
|
5553
5668
|
const ws = await workspace(dir);
|
|
5554
|
-
const
|
|
5555
|
-
|
|
5669
|
+
const activation = await effectiveSetup(ws.root);
|
|
5670
|
+
const setup = activation.setup;
|
|
5671
|
+
if (!setup || !Object.keys(setup.hosts).length) {
|
|
5556
5672
|
const partial = await Promise.all(["codex", "claude"].map(async (host) => ({
|
|
5557
5673
|
host,
|
|
5558
5674
|
receipt: await loadSetupReceipt(ws.root, ws.directory, host)
|
|
@@ -5586,7 +5702,7 @@ async function setupStatus(dir) {
|
|
|
5586
5702
|
]));
|
|
5587
5703
|
const hooks = isDeepStrictEqual3(compatibleHooks, expectedHooks) && !config.disabled;
|
|
5588
5704
|
const local = await loadSetupReceipt(ws.root, ws.directory, host);
|
|
5589
|
-
const configured = local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5705
|
+
const configured = activation.root !== ws.root || local?.status === "configured" && local.root === ws.root && local.revision === entry.revision;
|
|
5590
5706
|
const observation = await readObservation(ws.root, ws.directory, host, entry.revision, installed.version ?? "unavailable");
|
|
5591
5707
|
const sessions = Object.values(observation?.sessions ?? {}).sort((a, b) => b.at.localeCompare(a.at));
|
|
5592
5708
|
const complete = sessions.find((session) => events.every((event) => session.events.includes(event)));
|
|
@@ -5618,6 +5734,7 @@ async function setupStatus(dir) {
|
|
|
5618
5734
|
status: statuses.length && statuses.every((status) => status === "active") ? "active" : statuses.includes("attention") ? "attention" : "pending",
|
|
5619
5735
|
root: ws.root,
|
|
5620
5736
|
hosts: hosts2,
|
|
5737
|
+
activationRoot: activation.root,
|
|
5621
5738
|
decisionRecords: decisions.records.length,
|
|
5622
5739
|
diagnostics: decisions.diagnostics,
|
|
5623
5740
|
scope: "Activation receipts record observed use after setup, not complete interception, correct repairs, or measured usefulness. Host trust and higher-priority settings may prevent execution. Receipts are local to this worktree and branch."
|
|
@@ -5625,6 +5742,9 @@ async function setupStatus(dir) {
|
|
|
5625
5742
|
}
|
|
5626
5743
|
function summarizeActivation(status) {
|
|
5627
5744
|
const lines = ["Mason setup: " + status.status + "."];
|
|
5745
|
+
if (status.activationRoot && status.activationRoot !== status.root) {
|
|
5746
|
+
lines.push("Activation inherited from " + status.activationRoot + "; evidence belongs to this worktree.");
|
|
5747
|
+
}
|
|
5628
5748
|
for (const [host, state] of Object.entries(status.hosts)) {
|
|
5629
5749
|
lines.push(
|
|
5630
5750
|
`${host}: ${state.status}`,
|
|
@@ -5786,66 +5906,19 @@ var init_setup = __esm({
|
|
|
5786
5906
|
}
|
|
5787
5907
|
});
|
|
5788
5908
|
|
|
5789
|
-
// src/distribution/bundle.ts
|
|
5790
|
-
import fs20 from "fs/promises";
|
|
5791
|
-
import path26 from "path";
|
|
5792
|
-
import { createHash as createHash4 } from "crypto";
|
|
5793
|
-
import { z as z23 } from "zod";
|
|
5794
|
-
async function readBundle(root) {
|
|
5795
|
-
const bytes = await fs20.readFile(await storePath(root, "bundle.json"));
|
|
5796
|
-
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
5797
|
-
const windows = manifest.target.startsWith("win32-");
|
|
5798
|
-
for (const file of Object.keys(manifest.files)) {
|
|
5799
|
-
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path26.isAbsolute(file)) {
|
|
5800
|
-
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
5801
|
-
}
|
|
5802
|
-
}
|
|
5803
|
-
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
|
|
5804
|
-
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
5805
|
-
}
|
|
5806
|
-
return { manifest, manifestHash: sha256(bytes) };
|
|
5807
|
-
}
|
|
5808
|
-
async function verifyBundle(root, expectedHash) {
|
|
5809
|
-
const bundle = await readBundle(root);
|
|
5810
|
-
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
5811
|
-
if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
|
|
5812
|
-
const files = Object.keys(bundle.manifest.files);
|
|
5813
|
-
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
5814
|
-
if (sha256(await fs20.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
5815
|
-
}));
|
|
5816
|
-
return bundle;
|
|
5817
|
-
}
|
|
5818
|
-
var targets, digest2, bundleSchema, sha256;
|
|
5819
|
-
var init_bundle = __esm({
|
|
5820
|
-
"src/distribution/bundle.ts"() {
|
|
5821
|
-
"use strict";
|
|
5822
|
-
init_storage();
|
|
5823
|
-
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
5824
|
-
digest2 = z23.string().regex(/^[a-f0-9]{64}$/);
|
|
5825
|
-
bundleSchema = z23.object({
|
|
5826
|
-
format: z23.literal(1),
|
|
5827
|
-
version: z23.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
5828
|
-
target: z23.enum(targets),
|
|
5829
|
-
nodeVersion: z23.string(),
|
|
5830
|
-
files: z23.record(digest2)
|
|
5831
|
-
}).strict();
|
|
5832
|
-
sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
5833
|
-
}
|
|
5834
|
-
});
|
|
5835
|
-
|
|
5836
5909
|
// src/distribution/path.ts
|
|
5837
|
-
import
|
|
5910
|
+
import fs22 from "fs/promises";
|
|
5838
5911
|
import os3 from "os";
|
|
5839
|
-
import
|
|
5912
|
+
import path28 from "path";
|
|
5840
5913
|
import { execFile as execFile5 } from "child_process";
|
|
5841
5914
|
import { promisify as promisify5 } from "util";
|
|
5842
|
-
import { createHash as
|
|
5843
|
-
import { z as
|
|
5915
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "crypto";
|
|
5916
|
+
import { z as z23 } from "zod";
|
|
5844
5917
|
async function configurePath(bin, previous, save, options = {}) {
|
|
5845
5918
|
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
5846
5919
|
const changes = [...previous];
|
|
5847
5920
|
const currentPath = platform === "win32" ? Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] : env.PATH;
|
|
5848
|
-
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) =>
|
|
5921
|
+
const alreadyOnPath = platform === "win32" ? hasWindowsEntry(currentPath ?? null, bin, env) : (currentPath ?? "").split(":").some((entry) => path28.posix.isAbsolute(entry) && path28.posix.resolve(entry) === path28.posix.resolve(bin));
|
|
5849
5922
|
const setupHint = "To connect a project, run one of these inside its directory:\n Codex: mason setup --host codex\n Claude Code: mason setup --host claude";
|
|
5850
5923
|
const message2 = (alreadyOnPath ? "" : "Open a new terminal to use mason.\n") + setupHint;
|
|
5851
5924
|
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
@@ -5865,33 +5938,33 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5865
5938
|
}
|
|
5866
5939
|
return { status: "configured", changes, message: message2 };
|
|
5867
5940
|
}
|
|
5868
|
-
const shell =
|
|
5941
|
+
const shell = path28.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
5869
5942
|
const userHome = options.userHome ?? os3.homedir();
|
|
5870
5943
|
let profiles2;
|
|
5871
5944
|
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
5872
|
-
if (env.MASON_PROFILE) profiles2 = [
|
|
5873
|
-
else if (shell === "zsh") profiles2 = [
|
|
5874
|
-
else if (shell === "sh") profiles2 = [
|
|
5945
|
+
if (env.MASON_PROFILE) profiles2 = [path28.resolve(env.MASON_PROFILE)];
|
|
5946
|
+
else if (shell === "zsh") profiles2 = [path28.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
5947
|
+
else if (shell === "sh") profiles2 = [path28.join(userHome, ".profile")];
|
|
5875
5948
|
else {
|
|
5876
|
-
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) =>
|
|
5949
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path28.join(userHome, name));
|
|
5877
5950
|
let login = candidates[2];
|
|
5878
|
-
for (const file of candidates) if (await
|
|
5951
|
+
for (const file of candidates) if (await fs22.lstat(file).then(() => true, (error) => {
|
|
5879
5952
|
if (error.code === "ENOENT") return false;
|
|
5880
5953
|
throw error;
|
|
5881
5954
|
})) {
|
|
5882
5955
|
login = file;
|
|
5883
5956
|
break;
|
|
5884
5957
|
}
|
|
5885
|
-
profiles2 = [
|
|
5958
|
+
profiles2 = [path28.join(userHome, ".bashrc"), login];
|
|
5886
5959
|
}
|
|
5887
|
-
const marker = `# >>> mason PATH ${
|
|
5960
|
+
const marker = `# >>> mason PATH ${createHash4("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
5888
5961
|
for (const profile of profiles2) {
|
|
5889
|
-
const stat = await
|
|
5962
|
+
const stat = await fs22.lstat(profile).catch((error) => {
|
|
5890
5963
|
if (error.code === "ENOENT") return null;
|
|
5891
5964
|
throw error;
|
|
5892
5965
|
});
|
|
5893
|
-
const file = stat?.isSymbolicLink() ? await
|
|
5894
|
-
const original = await
|
|
5966
|
+
const file = stat?.isSymbolicLink() ? await fs22.realpath(profile) : path28.resolve(profile);
|
|
5967
|
+
const original = await fs22.readFile(file).catch((error) => {
|
|
5895
5968
|
if (error.code === "ENOENT" && !stat) return null;
|
|
5896
5969
|
throw error;
|
|
5897
5970
|
});
|
|
@@ -5906,8 +5979,8 @@ ${alreadyOnPath ? "" : `Add ${bin} to your PATH.
|
|
|
5906
5979
|
changes.push({ kind: "profile", file, block, created: original === null });
|
|
5907
5980
|
await save(changes);
|
|
5908
5981
|
}
|
|
5909
|
-
await
|
|
5910
|
-
const handle = await
|
|
5982
|
+
await fs22.mkdir(path28.dirname(file), { recursive: true });
|
|
5983
|
+
const handle = await fs22.open(file, original === null ? "ax+" : "a+", 384);
|
|
5911
5984
|
try {
|
|
5912
5985
|
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
5913
5986
|
await handle.writeFile(block);
|
|
@@ -5931,13 +6004,13 @@ async function removePath(changes) {
|
|
|
5931
6004
|
await windowsPath("remove", change.entry, current, change.created);
|
|
5932
6005
|
continue;
|
|
5933
6006
|
}
|
|
5934
|
-
const stat = await
|
|
6007
|
+
const stat = await fs22.lstat(change.file).catch((error) => {
|
|
5935
6008
|
if (error.code === "ENOENT") return null;
|
|
5936
6009
|
throw error;
|
|
5937
6010
|
});
|
|
5938
6011
|
if (!stat) continue;
|
|
5939
6012
|
if (!stat.isFile()) throw new Error("profile type changed");
|
|
5940
|
-
const bytes = await
|
|
6013
|
+
const bytes = await fs22.readFile(change.file), text2 = bytes.toString("utf8");
|
|
5941
6014
|
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
5942
6015
|
const index2 = text2.indexOf(change.block);
|
|
5943
6016
|
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
@@ -5947,7 +6020,7 @@ async function removePath(changes) {
|
|
|
5947
6020
|
const temporary = change.file + ".mason-" + randomUUID7();
|
|
5948
6021
|
try {
|
|
5949
6022
|
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
5950
|
-
const handle = await
|
|
6023
|
+
const handle = await fs22.open(temporary, "wx", stat.mode & 511);
|
|
5951
6024
|
try {
|
|
5952
6025
|
await handle.writeFile(next);
|
|
5953
6026
|
await handle.chmod(stat.mode & 511);
|
|
@@ -5956,11 +6029,11 @@ async function removePath(changes) {
|
|
|
5956
6029
|
} finally {
|
|
5957
6030
|
await handle.close();
|
|
5958
6031
|
}
|
|
5959
|
-
const current = await
|
|
5960
|
-
if (!current.isFile() || current.ino !== stat.ino || !(await
|
|
5961
|
-
await
|
|
6032
|
+
const current = await fs22.lstat(change.file);
|
|
6033
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs22.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
6034
|
+
await fs22.rename(temporary, change.file);
|
|
5962
6035
|
} finally {
|
|
5963
|
-
await
|
|
6036
|
+
await fs22.rm(temporary, { force: true });
|
|
5964
6037
|
}
|
|
5965
6038
|
} catch (error) {
|
|
5966
6039
|
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6002,12 +6075,12 @@ var init_path = __esm({
|
|
|
6002
6075
|
"src/distribution/path.ts"() {
|
|
6003
6076
|
"use strict";
|
|
6004
6077
|
execute2 = promisify5(execFile5);
|
|
6005
|
-
pathChangeSchema =
|
|
6006
|
-
|
|
6007
|
-
|
|
6078
|
+
pathChangeSchema = z23.discriminatedUnion("kind", [
|
|
6079
|
+
z23.object({ kind: z23.literal("profile"), file: z23.string(), block: z23.string(), created: z23.boolean() }),
|
|
6080
|
+
z23.object({ kind: z23.literal("windows"), entry: z23.string(), created: z23.boolean() })
|
|
6008
6081
|
]);
|
|
6009
6082
|
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6010
|
-
normalizeWindowsEntry = (value) =>
|
|
6083
|
+
normalizeWindowsEntry = (value) => path28.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
6011
6084
|
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
6012
6085
|
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
6013
6086
|
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
@@ -6016,65 +6089,346 @@ var init_path = __esm({
|
|
|
6016
6089
|
}
|
|
6017
6090
|
});
|
|
6018
6091
|
|
|
6092
|
+
// src/distribution/state.ts
|
|
6093
|
+
var state_exports = {};
|
|
6094
|
+
__export(state_exports, {
|
|
6095
|
+
automaticUpdatesAllowed: () => automaticUpdatesAllowed,
|
|
6096
|
+
currentInstallation: () => currentInstallation,
|
|
6097
|
+
defaultHome: () => defaultHome,
|
|
6098
|
+
newerStableVersion: () => newerStableVersion,
|
|
6099
|
+
readInstallation: () => readInstallation,
|
|
6100
|
+
recordSchema: () => recordSchema3,
|
|
6101
|
+
releaseVersion: () => releaseVersion,
|
|
6102
|
+
standaloneLocation: () => standaloneLocation,
|
|
6103
|
+
updatePolicySchema: () => updatePolicySchema
|
|
6104
|
+
});
|
|
6105
|
+
import os4 from "os";
|
|
6106
|
+
import path29 from "path";
|
|
6107
|
+
import { fileURLToPath } from "url";
|
|
6108
|
+
import fs23 from "fs/promises";
|
|
6109
|
+
import { z as z24 } from "zod";
|
|
6110
|
+
async function readInstallation(home) {
|
|
6111
|
+
const raw = await readStoreJson(home, "install.json");
|
|
6112
|
+
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6113
|
+
const record = recordSchema3.parse(raw);
|
|
6114
|
+
if (record.home !== await fs23.realpath(home)) throw new Error("Invalid standalone installation receipt.");
|
|
6115
|
+
return record;
|
|
6116
|
+
}
|
|
6117
|
+
function standaloneLocation() {
|
|
6118
|
+
const bundle = path29.resolve(path29.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
6119
|
+
const home = path29.dirname(path29.dirname(bundle));
|
|
6120
|
+
if (path29.dirname(bundle) !== path29.join(home, "versions") || !/^[a-f0-9]{24}$/.test(path29.basename(bundle))) {
|
|
6121
|
+
return null;
|
|
6122
|
+
}
|
|
6123
|
+
return { home, bundle };
|
|
6124
|
+
}
|
|
6125
|
+
async function currentInstallation() {
|
|
6126
|
+
const location2 = standaloneLocation();
|
|
6127
|
+
if (!location2) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
6128
|
+
const { home, bundle } = location2;
|
|
6129
|
+
const record = await readInstallation(home);
|
|
6130
|
+
return { home, bundle, record };
|
|
6131
|
+
}
|
|
6132
|
+
function automaticUpdatesAllowed(record, env = process.env) {
|
|
6133
|
+
return record.updates?.enabled === true && !record.updates.pinnedVersion && !record.updates.mirror && !env.CI && env.MASON_NO_AUTO_UPDATE !== "1" && !env.MASON_RELEASE_BASE && !env.MASON_VERSION;
|
|
6134
|
+
}
|
|
6135
|
+
function newerStableVersion(candidate, installed) {
|
|
6136
|
+
if (!/^\d+\.\d+\.\d+$/.test(candidate)) return false;
|
|
6137
|
+
const a = candidate.split(".").map(BigInt), b = installed.split("-")[0].split(".").map(BigInt);
|
|
6138
|
+
for (let i = 0; i < 3; i++) {
|
|
6139
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
6140
|
+
}
|
|
6141
|
+
return installed.includes("-");
|
|
6142
|
+
}
|
|
6143
|
+
var releaseVersion, bundleId, updatePolicySchema, recordSchema3, defaultHome;
|
|
6144
|
+
var init_state = __esm({
|
|
6145
|
+
"src/distribution/state.ts"() {
|
|
6146
|
+
"use strict";
|
|
6147
|
+
init_storage();
|
|
6148
|
+
init_path();
|
|
6149
|
+
releaseVersion = z24.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/);
|
|
6150
|
+
bundleId = z24.string().regex(/^[a-f0-9]{24}$/);
|
|
6151
|
+
updatePolicySchema = z24.object({
|
|
6152
|
+
enabled: z24.boolean(),
|
|
6153
|
+
revision: z24.string(),
|
|
6154
|
+
pinnedVersion: releaseVersion.optional(),
|
|
6155
|
+
mirror: z24.string().optional()
|
|
6156
|
+
});
|
|
6157
|
+
recordSchema3 = z24.object({
|
|
6158
|
+
format: z24.literal(1),
|
|
6159
|
+
home: z24.string(),
|
|
6160
|
+
bin: z24.string(),
|
|
6161
|
+
current: bundleId,
|
|
6162
|
+
version: releaseVersion,
|
|
6163
|
+
launchers: z24.record(z24.string()),
|
|
6164
|
+
previousLaunchers: z24.record(z24.string()).optional(),
|
|
6165
|
+
pathChanges: z24.array(pathChangeSchema).optional(),
|
|
6166
|
+
updates: updatePolicySchema.optional(),
|
|
6167
|
+
previous: z24.object({ id: bundleId, version: releaseVersion }).optional(),
|
|
6168
|
+
pending: z24.object({ id: bundleId, version: releaseVersion, policyRevision: z24.string() }).optional()
|
|
6169
|
+
});
|
|
6170
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path29.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path29.join(os4.homedir(), ".local/share/mason"));
|
|
6171
|
+
}
|
|
6172
|
+
});
|
|
6173
|
+
|
|
6174
|
+
// src/distribution/bundle.ts
|
|
6175
|
+
import fs24 from "fs/promises";
|
|
6176
|
+
import path30 from "path";
|
|
6177
|
+
import { createHash as createHash5 } from "crypto";
|
|
6178
|
+
import { z as z25 } from "zod";
|
|
6179
|
+
async function readBundle(root) {
|
|
6180
|
+
const bytes = await fs24.readFile(await storePath(root, "bundle.json"));
|
|
6181
|
+
const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6182
|
+
const windows = manifest.target.startsWith("win32-");
|
|
6183
|
+
for (const file of Object.keys(manifest.files)) {
|
|
6184
|
+
if (file.includes("\\") || file.split("/").some((p) => !p || p === "." || p === "..") || path30.isAbsolute(file)) {
|
|
6185
|
+
throw new Error("Unsafe path in Mason bundle: " + file);
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6188
|
+
for (const required of [windows ? "node.exe" : "node", "app/package.json", "app/dist/mason.js", "app/dist/mason-auto.js", "app/dist/mason-mcp.js"]) {
|
|
6189
|
+
if (!manifest.files[required]) throw new Error("Incomplete Mason bundle: " + required);
|
|
6190
|
+
}
|
|
6191
|
+
return { manifest, manifestHash: sha256(bytes) };
|
|
6192
|
+
}
|
|
6193
|
+
async function verifyBundle(root, expectedHash) {
|
|
6194
|
+
const bundle = await readBundle(root);
|
|
6195
|
+
if (expectedHash && bundle.manifestHash !== expectedHash) throw new Error("Mason bundle manifest changed.");
|
|
6196
|
+
if (bundle.manifest.target !== `${process.platform}-${process.arch}`) throw new Error("Mason bundle is for another platform. Install this platform's bundle and rerun setup.");
|
|
6197
|
+
const files = Object.keys(bundle.manifest.files);
|
|
6198
|
+
for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
|
|
6199
|
+
if (sha256(await fs24.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
|
|
6200
|
+
}));
|
|
6201
|
+
return bundle;
|
|
6202
|
+
}
|
|
6203
|
+
var targets, digest2, bundleSchema, sha256;
|
|
6204
|
+
var init_bundle = __esm({
|
|
6205
|
+
"src/distribution/bundle.ts"() {
|
|
6206
|
+
"use strict";
|
|
6207
|
+
init_storage();
|
|
6208
|
+
targets = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "win32-arm64", "win32-x64"];
|
|
6209
|
+
digest2 = z25.string().regex(/^[a-f0-9]{64}$/);
|
|
6210
|
+
bundleSchema = z25.object({
|
|
6211
|
+
format: z25.literal(1),
|
|
6212
|
+
version: z25.string().regex(/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/),
|
|
6213
|
+
target: z25.enum(targets),
|
|
6214
|
+
nodeVersion: z25.string(),
|
|
6215
|
+
files: z25.record(digest2)
|
|
6216
|
+
}).strict();
|
|
6217
|
+
sha256 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
|
|
6221
|
+
// src/distribution/runtime.ts
|
|
6222
|
+
import fs25 from "fs/promises";
|
|
6223
|
+
import { unlinkSync } from "fs";
|
|
6224
|
+
import os5 from "os";
|
|
6225
|
+
import path31 from "path";
|
|
6226
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
6227
|
+
import { z as z26 } from "zod";
|
|
6228
|
+
async function pruneUnusedVersions(home) {
|
|
6229
|
+
const versions = await storePath(home, "versions");
|
|
6230
|
+
const candidates = [];
|
|
6231
|
+
const initial = await readInstallation(home);
|
|
6232
|
+
const initialKeep = /* @__PURE__ */ new Set([initial.current, initial.previous?.id, initial.pending?.id]);
|
|
6233
|
+
for (const name of await fs25.readdir(versions)) {
|
|
6234
|
+
const retired = /^\.retired-([a-f0-9]{24})-[a-f0-9-]+$/.exec(name);
|
|
6235
|
+
const id = retired?.[1] ?? name;
|
|
6236
|
+
if (!/^[a-f0-9]{24}$/.test(id) || initialKeep.has(id)) continue;
|
|
6237
|
+
try {
|
|
6238
|
+
const directory = await storePath(home, `versions/${name}`);
|
|
6239
|
+
await fs25.access(await storePath(directory, "app/dist/mason-runtime.js"));
|
|
6240
|
+
const bundle = await verifyBundle(directory);
|
|
6241
|
+
if (bundle.manifestHash.slice(0, 24) === id && bundle.manifest.files["app/dist/mason-runtime.js"])
|
|
6242
|
+
candidates.push({ id, directory, retired: !!retired });
|
|
6243
|
+
} catch {
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
if (!candidates.length) return;
|
|
6247
|
+
const garbage = [];
|
|
6248
|
+
await withLock(home, ".install-lock", async () => {
|
|
6249
|
+
const record = await readInstallation(home);
|
|
6250
|
+
const keep = /* @__PURE__ */ new Set([record.current, record.previous?.id, record.pending?.id]);
|
|
6251
|
+
const directory = await storePath(home, "runtimes");
|
|
6252
|
+
const leases = await fs25.readdir(directory).catch((error) => {
|
|
6253
|
+
if (error.code === "ENOENT") return [];
|
|
6254
|
+
throw error;
|
|
6255
|
+
});
|
|
6256
|
+
for (const name of leases) {
|
|
6257
|
+
if (!/^[a-f0-9-]+\.json$/.test(name)) return;
|
|
6258
|
+
try {
|
|
6259
|
+
const raw = await readStoreJson(home, `runtimes/${name}`);
|
|
6260
|
+
if (raw === null) continue;
|
|
6261
|
+
const lease = leaseSchema.parse(raw);
|
|
6262
|
+
if (lease.host !== os5.hostname()) return;
|
|
6263
|
+
try {
|
|
6264
|
+
process.kill(lease.pid, 0);
|
|
6265
|
+
keep.add(lease.bundle);
|
|
6266
|
+
} catch (error) {
|
|
6267
|
+
if (error.code !== "ESRCH") return;
|
|
6268
|
+
await fs25.rm(await storePath(home, `runtimes/${name}`), { force: true });
|
|
6269
|
+
}
|
|
6270
|
+
} catch {
|
|
6271
|
+
return;
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
for (const candidate of candidates) {
|
|
6275
|
+
if (keep.has(candidate.id)) continue;
|
|
6276
|
+
try {
|
|
6277
|
+
const retired = candidate.retired ? candidate.directory : await storePath(home, `versions/.retired-${candidate.id}-${randomUUID8()}`);
|
|
6278
|
+
if (!candidate.retired) await fs25.rename(candidate.directory, retired);
|
|
6279
|
+
garbage.push(retired);
|
|
6280
|
+
} catch {
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6283
|
+
});
|
|
6284
|
+
for (const directory of garbage) await fs25.rm(directory, { recursive: true }).catch(() => {
|
|
6285
|
+
});
|
|
6286
|
+
}
|
|
6287
|
+
var leaseSchema;
|
|
6288
|
+
var init_runtime2 = __esm({
|
|
6289
|
+
"src/distribution/runtime.ts"() {
|
|
6290
|
+
"use strict";
|
|
6291
|
+
init_store();
|
|
6292
|
+
init_storage();
|
|
6293
|
+
init_state();
|
|
6294
|
+
init_bundle();
|
|
6295
|
+
leaseSchema = z26.object({ pid: z26.number().int().positive(), host: z26.string(), bundle: z26.string().regex(/^[a-f0-9]{24}$/) });
|
|
6296
|
+
}
|
|
6297
|
+
});
|
|
6298
|
+
|
|
6299
|
+
// src/distribution/sessions.ts
|
|
6300
|
+
import fs26 from "fs/promises";
|
|
6301
|
+
import os6 from "os";
|
|
6302
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
6303
|
+
import { z as z27 } from "zod";
|
|
6304
|
+
async function hasRunningMcp(home) {
|
|
6305
|
+
const directory = await storePath(home, "sessions");
|
|
6306
|
+
const files = await fs26.readdir(directory).catch((error) => {
|
|
6307
|
+
if (error.code === "ENOENT") return [];
|
|
6308
|
+
throw error;
|
|
6309
|
+
});
|
|
6310
|
+
for (const name of files) {
|
|
6311
|
+
if (!/^[a-f0-9-]+\.json$/.test(name)) return true;
|
|
6312
|
+
const relative = "sessions/" + name;
|
|
6313
|
+
try {
|
|
6314
|
+
const raw = await readStoreJson(home, relative);
|
|
6315
|
+
if (raw === null) continue;
|
|
6316
|
+
const lease = leaseSchema2.parse(raw);
|
|
6317
|
+
if (lease.host !== os6.hostname()) return true;
|
|
6318
|
+
try {
|
|
6319
|
+
process.kill(lease.pid, 0);
|
|
6320
|
+
return true;
|
|
6321
|
+
} catch (error) {
|
|
6322
|
+
if (error.code !== "ESRCH") return true;
|
|
6323
|
+
}
|
|
6324
|
+
await fs26.rm(await storePath(home, relative), { force: true });
|
|
6325
|
+
} catch {
|
|
6326
|
+
return true;
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
return false;
|
|
6330
|
+
}
|
|
6331
|
+
async function registerMcp(home) {
|
|
6332
|
+
const name = `sessions/${randomUUID9()}.json`;
|
|
6333
|
+
await writeStoreJson(home, name, { pid: process.pid, host: os6.hostname() });
|
|
6334
|
+
return async () => {
|
|
6335
|
+
await fs26.rm(await storePath(home, name), { force: true }).catch(() => {
|
|
6336
|
+
});
|
|
6337
|
+
};
|
|
6338
|
+
}
|
|
6339
|
+
var leaseSchema2;
|
|
6340
|
+
var init_sessions = __esm({
|
|
6341
|
+
"src/distribution/sessions.ts"() {
|
|
6342
|
+
"use strict";
|
|
6343
|
+
init_storage();
|
|
6344
|
+
leaseSchema2 = z27.object({ pid: z27.number().int().positive(), host: z27.string() });
|
|
6345
|
+
}
|
|
6346
|
+
});
|
|
6347
|
+
|
|
6019
6348
|
// src/distribution/install.ts
|
|
6020
6349
|
var install_exports2 = {};
|
|
6021
6350
|
__export(install_exports2, {
|
|
6022
6351
|
WINDOWS_BATCH_LAUNCHER: () => WINDOWS_BATCH_LAUNCHER,
|
|
6023
6352
|
defaultHome: () => defaultHome,
|
|
6024
6353
|
installStandalone: () => installStandalone,
|
|
6354
|
+
prepareStandaloneMcp: () => prepareStandaloneMcp,
|
|
6355
|
+
rollbackStandalone: () => rollbackStandalone,
|
|
6025
6356
|
uninstallStandalone: () => uninstallStandalone,
|
|
6026
6357
|
upgradeStandalone: () => upgradeStandalone
|
|
6027
6358
|
});
|
|
6028
|
-
import
|
|
6029
|
-
import
|
|
6030
|
-
import
|
|
6031
|
-
import {
|
|
6032
|
-
import {
|
|
6033
|
-
import { randomUUID as
|
|
6034
|
-
import { z as z25 } from "zod";
|
|
6359
|
+
import fs27 from "fs/promises";
|
|
6360
|
+
import os7 from "os";
|
|
6361
|
+
import path32 from "path";
|
|
6362
|
+
import { execFile as execFile6, spawn } from "child_process";
|
|
6363
|
+
import { promisify as promisify6 } from "util";
|
|
6364
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
6035
6365
|
async function replace(file, content2, mode = 384) {
|
|
6036
|
-
const temp = file + ".tmp-" +
|
|
6366
|
+
const temp = file + ".tmp-" + randomUUID10();
|
|
6037
6367
|
try {
|
|
6038
|
-
await
|
|
6039
|
-
await
|
|
6368
|
+
await fs27.writeFile(temp, content2, { mode, flag: "wx" });
|
|
6369
|
+
await fs27.rename(temp, file);
|
|
6040
6370
|
} finally {
|
|
6041
|
-
await
|
|
6371
|
+
await fs27.rm(temp, { force: true });
|
|
6042
6372
|
}
|
|
6043
6373
|
}
|
|
6044
|
-
async function installStandalone(source, progress2) {
|
|
6374
|
+
async function installStandalone(source, progress2, options = {}) {
|
|
6045
6375
|
progress2?.step("Checking installation files and location");
|
|
6046
6376
|
const bundle = await verifyBundle(source);
|
|
6047
|
-
const selectedHome =
|
|
6048
|
-
await
|
|
6049
|
-
const home = await
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
const
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6377
|
+
const selectedHome = path32.resolve(defaultHome());
|
|
6378
|
+
if (!options.stageOnly) await fs27.mkdir(selectedHome, { recursive: true });
|
|
6379
|
+
const home = await fs27.realpath(selectedHome);
|
|
6380
|
+
if (options.stageOnly) {
|
|
6381
|
+
assertUpdatePolicy(await readInstallation(home), bundle.manifest.version, options.policyRevision);
|
|
6382
|
+
const destination = await storePath(home, "versions/" + bundle.manifestHash.slice(0, 24));
|
|
6383
|
+
await copyBundle(home, source, destination, bundle);
|
|
6384
|
+
const { stdout } = await promisify6(execFile6)(
|
|
6385
|
+
path32.join(destination, process.platform === "win32" ? "node.exe" : "node"),
|
|
6386
|
+
[path32.join(destination, "app/dist/mason.js"), "--version"],
|
|
6387
|
+
{ timeout: 3e4, windowsHide: true, env: { ...process.env, MASON_NO_AUTO_UPDATE: "1" } }
|
|
6388
|
+
);
|
|
6389
|
+
if (stdout.trim() !== bundle.manifest.version) throw new Error("Downloaded Mason failed its version health check.");
|
|
6390
|
+
}
|
|
6391
|
+
return withLock(home, ".install-lock", () => installLocked2(home, source, bundle, progress2, options));
|
|
6392
|
+
}
|
|
6393
|
+
function assertUpdatePolicy(record, version, revision) {
|
|
6394
|
+
if (!record || !automaticUpdatesAllowed(record, { ...process.env, MASON_VERSION: "" }) || record.updates?.revision !== revision || !newerStableVersion(version, record.version)) {
|
|
6395
|
+
throw new Error("Update policy or installed version changed; download was not activated.");
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
async function installLocked2(home, source, bundle, progress2, options, alreadyStaged = false) {
|
|
6399
|
+
const raw = await readStoreJson(home, "install.json");
|
|
6400
|
+
const previous = raw === null ? null : recordSchema3.parse(raw);
|
|
6401
|
+
if (options.stageOnly) assertUpdatePolicy(previous, bundle.manifest.version, options.policyRevision);
|
|
6402
|
+
if (!previous) for (const file of await fs27.readdir(home)) {
|
|
6403
|
+
if (file === ".install-lock") continue;
|
|
6404
|
+
const directory = path32.join(home, file);
|
|
6405
|
+
const stat = await fs27.lstat(directory);
|
|
6406
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs27.readdir(directory)).length) {
|
|
6407
|
+
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
const selectedBin = path32.resolve(process.env.MASON_BIN_DIR ?? previous?.bin ?? (process.platform === "win32" ? path32.join(home, "bin") : path32.join(os7.homedir(), ".local/bin")));
|
|
6411
|
+
await fs27.mkdir(selectedBin, { recursive: true });
|
|
6412
|
+
const bin = await fs27.realpath(selectedBin);
|
|
6413
|
+
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
6414
|
+
const id = bundle.manifestHash.slice(0, 24);
|
|
6415
|
+
const destination = await storePath(home, "versions/" + id);
|
|
6416
|
+
if (options.stageOnly) {
|
|
6417
|
+
const receipt2 = { ...previous, pending: { id, version: bundle.manifest.version, policyRevision: options.policyRevision } };
|
|
6418
|
+
await writeStoreJson(home, "install.json", receipt2);
|
|
6419
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous.version, path: { message: "Update staged for a future MCP launch." } };
|
|
6420
|
+
}
|
|
6421
|
+
const launchers = process.platform === "win32" ? {
|
|
6422
|
+
"mason.cmd": WINDOWS_BATCH_LAUNCHER,
|
|
6423
|
+
"mason-launcher.ps1": `$ErrorActionPreference = 'Stop'
|
|
6070
6424
|
$previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
6071
6425
|
$token = [Guid]::NewGuid().ToString()
|
|
6072
6426
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
6073
6427
|
try {
|
|
6074
|
-
& ${psQuote(
|
|
6428
|
+
& ${psQuote(path32.join(destination, "node.exe"))} ${psQuote(path32.join(destination, "app/dist/mason.js"))} @args
|
|
6075
6429
|
$code = $LASTEXITCODE
|
|
6076
6430
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
6077
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
6431
|
+
if ([IO.File]::ReadAllText(${psQuote(path32.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
6078
6432
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
6079
6433
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
6080
6434
|
}
|
|
@@ -6082,70 +6436,146 @@ try {
|
|
|
6082
6436
|
} catch { [Console]::Error.WriteLine($_.Exception.Message); exit 2 }
|
|
6083
6437
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
6084
6438
|
`
|
|
6085
|
-
|
|
6086
|
-
exec ${shQuote(
|
|
6439
|
+
} : { mason: `#!/bin/sh
|
|
6440
|
+
exec ${shQuote(path32.join(destination, "node"))} ${shQuote(path32.join(destination, "app/dist/mason.js"))} "$@"
|
|
6087
6441
|
` };
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6442
|
+
for (const [name, content2] of Object.entries(launchers)) {
|
|
6443
|
+
const file = await storePath(bin, name);
|
|
6444
|
+
const old = await fs27.readFile(file, "utf8").catch((error) => {
|
|
6445
|
+
if (error.code === "ENOENT") return null;
|
|
6446
|
+
throw error;
|
|
6447
|
+
});
|
|
6448
|
+
if (old !== null && old !== previous?.launchers[name] && old !== previous?.previousLaunchers?.[name] && old !== content2) throw new Error("Refusing to replace an unrelated or edited launcher: " + file);
|
|
6449
|
+
}
|
|
6450
|
+
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
6451
|
+
const checkRetired = async (name, expected) => {
|
|
6452
|
+
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
6453
|
+
const actual = await fs27.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
6454
|
+
if (error.code === "ENOENT") return null;
|
|
6455
|
+
throw error;
|
|
6456
|
+
});
|
|
6457
|
+
if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
|
|
6458
|
+
};
|
|
6459
|
+
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
6460
|
+
progress2?.step(`Installing Mason ${bundle.manifest.version}`);
|
|
6461
|
+
const updates = options.preservePolicy && previous?.updates ? { ...previous.updates } : {
|
|
6462
|
+
...previous?.updates ?? { enabled: !previous && !process.env.CI && process.env.MASON_NO_AUTO_UPDATE !== "1" },
|
|
6463
|
+
revision: randomUUID10(),
|
|
6464
|
+
pinnedVersion: process.env.MASON_VERSION?.replace(/^v/, "") || void 0,
|
|
6465
|
+
mirror: process.env.MASON_RELEASE_BASE || previous?.updates?.mirror
|
|
6466
|
+
};
|
|
6467
|
+
if (options.rollback) {
|
|
6468
|
+
updates.pinnedVersion = bundle.manifest.version;
|
|
6469
|
+
updates.revision = randomUUID10();
|
|
6470
|
+
}
|
|
6471
|
+
const receipt = {
|
|
6472
|
+
format: 1,
|
|
6473
|
+
home,
|
|
6474
|
+
bin,
|
|
6475
|
+
current: id,
|
|
6476
|
+
version: bundle.manifest.version,
|
|
6477
|
+
launchers,
|
|
6478
|
+
pathChanges: previous?.pathChanges ?? [],
|
|
6479
|
+
updates,
|
|
6480
|
+
previous: previous && previous.current !== id ? { id: previous.current, version: previous.version } : previous?.previous
|
|
6481
|
+
};
|
|
6482
|
+
await writeStoreJson(home, "install.json", previous ? { ...previous, previousLaunchers: launchers } : receipt);
|
|
6483
|
+
try {
|
|
6484
|
+
if (!alreadyStaged) await copyBundle(home, source, destination, bundle);
|
|
6485
|
+
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6486
|
+
for (const [name, expected] of retired) {
|
|
6487
|
+
await checkRetired(name, expected);
|
|
6488
|
+
await fs27.rm(await storePath(bin, name), { force: true });
|
|
6095
6489
|
}
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
const
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
await writeStoreJson(home, "install.json",
|
|
6109
|
-
|
|
6110
|
-
|
|
6490
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
6491
|
+
} catch (error) {
|
|
6492
|
+
if (previous) {
|
|
6493
|
+
for (const [name, content2] of Object.entries(previous.launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6494
|
+
await writeStoreJson(home, "install.json", previous);
|
|
6495
|
+
}
|
|
6496
|
+
throw error;
|
|
6497
|
+
}
|
|
6498
|
+
if (options.preservePolicy) return { home, bin, version: receipt.version, previousVersion: previous?.version, path: { message: "Installation selected." } };
|
|
6499
|
+
progress2?.step("Checking terminal command availability");
|
|
6500
|
+
const pathResult = await configurePath(bin, receipt.pathChanges ?? [], async (changes) => {
|
|
6501
|
+
receipt.pathChanges = changes;
|
|
6502
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
6503
|
+
});
|
|
6504
|
+
return { home, bin, version: bundle.manifest.version, previousVersion: previous?.version, path: pathResult };
|
|
6505
|
+
}
|
|
6506
|
+
async function copyBundle(home, source, destination, bundle) {
|
|
6507
|
+
const exists = await fs27.lstat(destination).then(() => true, (error) => {
|
|
6508
|
+
if (error.code === "ENOENT") return false;
|
|
6509
|
+
throw error;
|
|
6510
|
+
});
|
|
6511
|
+
if (exists) {
|
|
6512
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
6513
|
+
return;
|
|
6514
|
+
}
|
|
6515
|
+
const stage = await storePath(home, "versions/.install-" + randomUUID10(), true);
|
|
6516
|
+
await fs27.mkdir(stage);
|
|
6517
|
+
try {
|
|
6518
|
+
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
6519
|
+
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
6520
|
+
await fs27.copyFile(from, to);
|
|
6521
|
+
await fs27.chmod(to, (await fs27.stat(from)).mode & 511);
|
|
6522
|
+
}
|
|
6523
|
+
await verifyBundle(stage, bundle.manifestHash);
|
|
6111
6524
|
try {
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
await
|
|
6116
|
-
}
|
|
6117
|
-
await verifyBundle(stage, bundle.manifestHash);
|
|
6118
|
-
const exists = await fs22.lstat(destination).then(() => true, (error) => {
|
|
6119
|
-
if (error.code === "ENOENT") return false;
|
|
6525
|
+
await fs27.rename(stage, destination);
|
|
6526
|
+
} catch (error) {
|
|
6527
|
+
try {
|
|
6528
|
+
await verifyBundle(destination, bundle.manifestHash);
|
|
6529
|
+
} catch {
|
|
6120
6530
|
throw error;
|
|
6121
|
-
});
|
|
6122
|
-
if (exists) await verifyBundle(destination, bundle.manifestHash);
|
|
6123
|
-
else await fs22.rename(stage, destination);
|
|
6124
|
-
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
6125
|
-
for (const [name, expected] of retired) {
|
|
6126
|
-
await checkRetired(name, expected);
|
|
6127
|
-
await fs22.rm(await storePath(bin, name), { force: true });
|
|
6128
6531
|
}
|
|
6129
|
-
await writeStoreJson(home, "install.json", receipt);
|
|
6130
|
-
} finally {
|
|
6131
|
-
await fs22.rm(stage, { recursive: true, force: true });
|
|
6132
6532
|
}
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6533
|
+
} finally {
|
|
6534
|
+
await fs27.rm(stage, { recursive: true, force: true });
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
async function prepareStandaloneMcp(home) {
|
|
6538
|
+
const before = await readInstallation(home), candidate = before.pending;
|
|
6539
|
+
let verified, verificationError;
|
|
6540
|
+
if (candidate && automaticUpdatesAllowed(before) && !await hasRunningMcp(home)) {
|
|
6541
|
+
try {
|
|
6542
|
+
verified = await verifyBundle(await storePath(home, "versions/" + candidate.id));
|
|
6543
|
+
if (verified.manifestHash.slice(0, 24) !== candidate.id || verified.manifest.version !== candidate.version) throw new Error("Staged update changed.");
|
|
6544
|
+
} catch (error) {
|
|
6545
|
+
verificationError = error;
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
return withLock(home, ".install-lock", async () => {
|
|
6549
|
+
let record = await readInstallation(home);
|
|
6550
|
+
const pending = record.pending;
|
|
6551
|
+
if (pending && candidate?.id === pending.id && (verified || verificationError) && automaticUpdatesAllowed(record) && pending.policyRevision === record.updates?.revision && newerStableVersion(pending.version, record.version) && !await hasRunningMcp(home)) {
|
|
6552
|
+
try {
|
|
6553
|
+
if (verificationError) throw verificationError;
|
|
6554
|
+
const source = await storePath(home, "versions/" + pending.id);
|
|
6555
|
+
await installLocked2(home, source, verified, void 0, { preservePolicy: true }, true);
|
|
6556
|
+
record = await readInstallation(home);
|
|
6557
|
+
} catch (error) {
|
|
6558
|
+
record = await readInstallation(home);
|
|
6559
|
+
delete record.pending;
|
|
6560
|
+
await writeStoreJson(home, "install.json", record);
|
|
6561
|
+
await writeStoreJson(home, "update-status.json", { lastAttempt: Date.now(), error: String(error) });
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
const release = await registerMcp(home);
|
|
6565
|
+
return { bundle: await storePath(home, "versions/" + record.current), release };
|
|
6139
6566
|
});
|
|
6140
6567
|
}
|
|
6141
|
-
async function
|
|
6142
|
-
const
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6568
|
+
async function rollbackStandalone() {
|
|
6569
|
+
const { home } = await currentInstallation();
|
|
6570
|
+
return withLock(home, ".install-lock", async () => {
|
|
6571
|
+
const record = await readInstallation(home);
|
|
6572
|
+
if (!record.previous) throw new Error("No previous installation is available.");
|
|
6573
|
+
if (await hasRunningMcp(home)) throw new Error("Close running Mason MCP sessions before rolling back.");
|
|
6574
|
+
const source = await storePath(home, "versions/" + record.previous.id);
|
|
6575
|
+
const bundle = await verifyBundle(source);
|
|
6576
|
+
if (bundle.manifestHash.slice(0, 24) !== record.previous.id || bundle.manifest.version !== record.previous.version) throw new Error("Previous installation changed.");
|
|
6577
|
+
return installLocked2(home, source, bundle, void 0, { preservePolicy: true, rollback: true });
|
|
6578
|
+
});
|
|
6149
6579
|
}
|
|
6150
6580
|
async function upgradeStandalone(version, progress2) {
|
|
6151
6581
|
if (version && !/^v?\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(version)) throw new Error("Expected a release version such as 0.14.0.");
|
|
@@ -6153,13 +6583,21 @@ async function upgradeStandalone(version, progress2) {
|
|
|
6153
6583
|
const { home, bundle, record } = await currentInstallation();
|
|
6154
6584
|
await verifyBundle(bundle);
|
|
6155
6585
|
const windows = process.platform === "win32";
|
|
6156
|
-
const script =
|
|
6586
|
+
const script = path32.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6157
6587
|
progress2?.stop();
|
|
6158
6588
|
return new Promise((resolve, reject) => {
|
|
6159
6589
|
const child = spawn(
|
|
6160
6590
|
windows ? "powershell.exe" : "sh",
|
|
6161
6591
|
windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script],
|
|
6162
|
-
{ stdio: "inherit", windowsHide: true, env: {
|
|
6592
|
+
{ stdio: "inherit", windowsHide: true, env: {
|
|
6593
|
+
...process.env,
|
|
6594
|
+
MASON_HOME: home,
|
|
6595
|
+
MASON_BIN_DIR: record.bin,
|
|
6596
|
+
MASON_RELEASE_BASE: process.env.MASON_RELEASE_BASE || record.updates?.mirror || "",
|
|
6597
|
+
MASON_VERSION: version ?? "",
|
|
6598
|
+
MASON_UPDATE_POLICY_REVISION: "",
|
|
6599
|
+
MASON_EXPECTED_SHA256: ""
|
|
6600
|
+
} }
|
|
6163
6601
|
);
|
|
6164
6602
|
child.on("error", reject);
|
|
6165
6603
|
child.on("exit", (code) => resolve(code ?? 2));
|
|
@@ -6175,7 +6613,7 @@ async function uninstallStandalone() {
|
|
|
6175
6613
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
6176
6614
|
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
6177
6615
|
const file = await storePath(record.bin, name);
|
|
6178
|
-
const actual = await
|
|
6616
|
+
const actual = await fs27.readFile(file, "utf8").catch((error) => {
|
|
6179
6617
|
if (error.code === "ENOENT") return null;
|
|
6180
6618
|
throw error;
|
|
6181
6619
|
});
|
|
@@ -6183,12 +6621,12 @@ async function uninstallStandalone() {
|
|
|
6183
6621
|
}
|
|
6184
6622
|
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
6185
6623
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
6186
|
-
for (const name of Object.keys(record.launchers)) await
|
|
6624
|
+
for (const name of Object.keys(record.launchers)) await fs27.rm(await storePath(record.bin, name), { force: true });
|
|
6187
6625
|
});
|
|
6188
|
-
if (process.platform !== "win32") await
|
|
6626
|
+
if (process.platform !== "win32") await fs27.rm(home, { recursive: true, force: true });
|
|
6189
6627
|
return home;
|
|
6190
6628
|
}
|
|
6191
|
-
var
|
|
6629
|
+
var shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames;
|
|
6192
6630
|
var init_install2 = __esm({
|
|
6193
6631
|
"src/distribution/install.ts"() {
|
|
6194
6632
|
"use strict";
|
|
@@ -6196,21 +6634,254 @@ var init_install2 = __esm({
|
|
|
6196
6634
|
init_storage();
|
|
6197
6635
|
init_store();
|
|
6198
6636
|
init_path();
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
bin: z25.string(),
|
|
6203
|
-
current: z25.string().regex(/^[a-f0-9]{24}$/),
|
|
6204
|
-
version: z25.string(),
|
|
6205
|
-
launchers: z25.record(z25.string()),
|
|
6206
|
-
previousLaunchers: z25.record(z25.string()).optional(),
|
|
6207
|
-
pathChanges: z25.array(pathChangeSchema).optional()
|
|
6208
|
-
});
|
|
6637
|
+
init_state();
|
|
6638
|
+
init_sessions();
|
|
6639
|
+
init_state();
|
|
6209
6640
|
shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
6210
6641
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
6211
6642
|
WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason-launcher.ps1" %*\r\n';
|
|
6212
6643
|
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
6213
|
-
|
|
6644
|
+
}
|
|
6645
|
+
});
|
|
6646
|
+
|
|
6647
|
+
// src/distribution/updates.ts
|
|
6648
|
+
var updates_exports = {};
|
|
6649
|
+
__export(updates_exports, {
|
|
6650
|
+
CHECK_INTERVAL_MS: () => CHECK_INTERVAL_MS,
|
|
6651
|
+
RELEASE_AGE_MS: () => RELEASE_AGE_MS,
|
|
6652
|
+
fetchVerifiedRelease: () => fetchVerifiedRelease,
|
|
6653
|
+
runAutomaticUpdate: () => runAutomaticUpdate,
|
|
6654
|
+
scheduleAutomaticUpdate: () => scheduleAutomaticUpdate,
|
|
6655
|
+
startMcpWithUpdates: () => startMcpWithUpdates,
|
|
6656
|
+
updateManifestSchema: () => updateManifestSchema,
|
|
6657
|
+
updateSettings: () => updateSettings,
|
|
6658
|
+
updateStatus: () => updateStatus
|
|
6659
|
+
});
|
|
6660
|
+
import fs28 from "fs/promises";
|
|
6661
|
+
import path33 from "path";
|
|
6662
|
+
import { spawn as spawn2 } from "child_process";
|
|
6663
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
6664
|
+
import { z as z28 } from "zod";
|
|
6665
|
+
async function boundedDownload(url) {
|
|
6666
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(15e3), redirect: "follow" });
|
|
6667
|
+
if (!response.ok || !response.body) throw new Error(`Update metadata request failed (HTTP ${response.status}).`);
|
|
6668
|
+
if (Number(response.headers.get("content-length")) > 1024 * 1024) throw new Error("Update metadata is too large.");
|
|
6669
|
+
const reader = response.body.getReader(), chunks = [];
|
|
6670
|
+
let size = 0;
|
|
6671
|
+
try {
|
|
6672
|
+
for (; ; ) {
|
|
6673
|
+
const { value, done } = await reader.read();
|
|
6674
|
+
if (done) break;
|
|
6675
|
+
size += value.length;
|
|
6676
|
+
if (size > 1024 * 1024) throw new Error("Update metadata is too large.");
|
|
6677
|
+
chunks.push(value);
|
|
6678
|
+
}
|
|
6679
|
+
} finally {
|
|
6680
|
+
await reader.cancel();
|
|
6681
|
+
}
|
|
6682
|
+
return Buffer.concat(chunks);
|
|
6683
|
+
}
|
|
6684
|
+
async function fetchVerifiedRelease(home, now = Date.now()) {
|
|
6685
|
+
const releases = releaseListSchema.parse(JSON.parse((await boundedDownload("https://api.github.com/repos/adrianczuczka/mason/releases?per_page=30")).toString("utf8")));
|
|
6686
|
+
const installed = (await readInstallation(home)).version;
|
|
6687
|
+
let selected;
|
|
6688
|
+
for (const release of releases) {
|
|
6689
|
+
const version = release.tag_name.replace(/^v/, "");
|
|
6690
|
+
if (release.tag_name !== "v" + version || release.draft || release.prerelease || !release.published_at || !(now - Date.parse(release.published_at) >= RELEASE_AGE_MS) || !newerStableVersion(version, installed)) continue;
|
|
6691
|
+
if (!selected || newerStableVersion(version, selected)) selected = version;
|
|
6692
|
+
}
|
|
6693
|
+
if (!selected) return null;
|
|
6694
|
+
const bytes = await boundedDownload(`${releaseBase}/download/v${selected}/update.json`);
|
|
6695
|
+
const manifest = updateManifestSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
6696
|
+
if (manifest.version !== selected) throw new Error("Signed manifest does not match the selected release.");
|
|
6697
|
+
const signature = JSON.parse((await boundedDownload(`${releaseBase}/download/v${manifest.version}/update.sigstore.json`)).toString("utf8"));
|
|
6698
|
+
if (!signature?.messageSignature || signature.dsseEnvelope) throw new Error("Update metadata requires a detached message signature.");
|
|
6699
|
+
const { verify } = await import("sigstore");
|
|
6700
|
+
await verify(signature, bytes, {
|
|
6701
|
+
certificateIdentityURI: `https://github.com/adrianczuczka/mason/.github/workflows/publish.yml@refs/tags/v${manifest.version}`,
|
|
6702
|
+
certificateIssuer: "https://token.actions.githubusercontent.com",
|
|
6703
|
+
tufCachePath: await storePath(home, "sigstore-cache"),
|
|
6704
|
+
timeout: 1e4,
|
|
6705
|
+
retry: 0,
|
|
6706
|
+
ctLogThreshold: 1,
|
|
6707
|
+
tlogThreshold: 1
|
|
6708
|
+
});
|
|
6709
|
+
return manifest;
|
|
6710
|
+
}
|
|
6711
|
+
async function runInstaller(home, version, digest3, revision) {
|
|
6712
|
+
const record = await readInstallation(home);
|
|
6713
|
+
const bundle = await storePath(home, "versions/" + record.current);
|
|
6714
|
+
await verifyBundle(bundle);
|
|
6715
|
+
const windows = process.platform === "win32", script = path33.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
6716
|
+
await new Promise((resolve, reject) => {
|
|
6717
|
+
const child = spawn2(windows ? "powershell.exe" : "sh", windows ? ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script] : [script], {
|
|
6718
|
+
cwd: home,
|
|
6719
|
+
windowsHide: true,
|
|
6720
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
6721
|
+
timeout: 10 * 60 * 1e3,
|
|
6722
|
+
env: {
|
|
6723
|
+
...process.env,
|
|
6724
|
+
MASON_HOME: home,
|
|
6725
|
+
MASON_BIN_DIR: record.bin,
|
|
6726
|
+
MASON_VERSION: version,
|
|
6727
|
+
MASON_RELEASE_BASE: "",
|
|
6728
|
+
MASON_EXPECTED_SHA256: digest3,
|
|
6729
|
+
MASON_UPDATE_POLICY_REVISION: revision
|
|
6730
|
+
}
|
|
6731
|
+
});
|
|
6732
|
+
let diagnostic = "";
|
|
6733
|
+
child.stderr.on("data", (bytes) => {
|
|
6734
|
+
diagnostic = (diagnostic + bytes).slice(-4e3);
|
|
6735
|
+
});
|
|
6736
|
+
child.on("error", reject);
|
|
6737
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`Update download failed (${code}): ${diagnostic.trim()}`)));
|
|
6738
|
+
});
|
|
6739
|
+
}
|
|
6740
|
+
async function runAutomaticUpdate(home, dependencies = { fetchRelease: fetchVerifiedRelease, install: runInstaller }, now = Date.now()) {
|
|
6741
|
+
await withLock(home, ".update-lock", async () => {
|
|
6742
|
+
const record = await readInstallation(home);
|
|
6743
|
+
if (!automaticUpdatesAllowed(record)) return;
|
|
6744
|
+
const old = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
|
|
6745
|
+
if (old.success && now - old.data.lastAttempt < CHECK_INTERVAL_MS && old.data.lastAttempt <= now) return;
|
|
6746
|
+
const status = { lastAttempt: now };
|
|
6747
|
+
await writeStoreJson(home, "update-status.json", status);
|
|
6748
|
+
try {
|
|
6749
|
+
await pruneUnusedVersions(home).catch(() => {
|
|
6750
|
+
});
|
|
6751
|
+
const release = await dependencies.fetchRelease(home);
|
|
6752
|
+
if (!release) return;
|
|
6753
|
+
status.availableVersion = release.version;
|
|
6754
|
+
const age = now - Date.parse(release.publishedAt);
|
|
6755
|
+
if (age >= RELEASE_AGE_MS && newerStableVersion(release.version, record.version) && (!record.pending || newerStableVersion(release.version, record.pending.version))) {
|
|
6756
|
+
const asset = `mason-${process.platform}-${process.arch}.${process.platform === "win32" ? "zip" : "tar.gz"}`;
|
|
6757
|
+
const digest3 = release.assets[asset];
|
|
6758
|
+
if (!digest3) throw new Error("Signed release has no archive for this platform.");
|
|
6759
|
+
const fresh = await readInstallation(home);
|
|
6760
|
+
if (automaticUpdatesAllowed(fresh) && fresh.updates?.revision === record.updates.revision && newerStableVersion(release.version, fresh.version)) {
|
|
6761
|
+
await dependencies.install(home, release.version, digest3, record.updates.revision);
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
} catch (error) {
|
|
6765
|
+
status.error = error instanceof Error ? error.message : String(error);
|
|
6766
|
+
}
|
|
6767
|
+
if (await fs28.access(path33.join(home, "install.json")).then(() => true, () => false)) await writeStoreJson(home, "update-status.json", status);
|
|
6768
|
+
}, 0);
|
|
6769
|
+
}
|
|
6770
|
+
async function scheduleAutomaticUpdate() {
|
|
6771
|
+
try {
|
|
6772
|
+
const { home, record } = await currentInstallation();
|
|
6773
|
+
if (!automaticUpdatesAllowed(record)) return;
|
|
6774
|
+
const status = statusSchema2.safeParse(await readStoreJson(home, "update-status.json"));
|
|
6775
|
+
const now = Date.now();
|
|
6776
|
+
if (status.success && status.data.lastAttempt <= now && now - status.data.lastAttempt < CHECK_INTERVAL_MS) return;
|
|
6777
|
+
const bundle = await storePath(home, "versions/" + record.current);
|
|
6778
|
+
const child = spawn2(
|
|
6779
|
+
path33.join(bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
6780
|
+
[path33.join(bundle, "app/dist/mason.js"), "internal-update"],
|
|
6781
|
+
{ cwd: home, env: process.env, stdio: "ignore", detached: true, windowsHide: true }
|
|
6782
|
+
);
|
|
6783
|
+
child.on("error", () => {
|
|
6784
|
+
});
|
|
6785
|
+
child.unref();
|
|
6786
|
+
} catch {
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
async function updateSettings(home, action) {
|
|
6790
|
+
return withLock(home, ".install-lock", async () => {
|
|
6791
|
+
const record = await readInstallation(home);
|
|
6792
|
+
const updates = { ...record.updates ?? { enabled: false }, revision: randomUUID11() };
|
|
6793
|
+
if (action === "enable") updates.enabled = true;
|
|
6794
|
+
if (action === "disable") updates.enabled = false;
|
|
6795
|
+
if (action === "pin") updates.pinnedVersion = record.version;
|
|
6796
|
+
if (action === "unpin") delete updates.pinnedVersion;
|
|
6797
|
+
record.updates = updates;
|
|
6798
|
+
delete record.pending;
|
|
6799
|
+
await writeStoreJson(home, "install.json", record);
|
|
6800
|
+
return record;
|
|
6801
|
+
});
|
|
6802
|
+
}
|
|
6803
|
+
async function updateStatus(home) {
|
|
6804
|
+
const record = await readInstallation(home);
|
|
6805
|
+
const lastCheck = statusSchema2.parse(await readStoreJson(home, "update-status.json") ?? { lastAttempt: 0 });
|
|
6806
|
+
return {
|
|
6807
|
+
version: record.version,
|
|
6808
|
+
enabled: record.updates?.enabled ?? false,
|
|
6809
|
+
effective: automaticUpdatesAllowed(record),
|
|
6810
|
+
pinnedVersion: record.updates?.pinnedVersion ?? null,
|
|
6811
|
+
mirror: record.updates?.mirror ?? null,
|
|
6812
|
+
pendingVersion: record.pending?.version ?? null,
|
|
6813
|
+
lastCheck: lastCheck.lastAttempt ? lastCheck : null
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
async function startMcpWithUpdates(start) {
|
|
6817
|
+
const current = standaloneLocation();
|
|
6818
|
+
if (!current) {
|
|
6819
|
+
await start();
|
|
6820
|
+
return;
|
|
6821
|
+
}
|
|
6822
|
+
const session = await prepareStandaloneMcp(current.home);
|
|
6823
|
+
if (session.bundle !== current.bundle) {
|
|
6824
|
+
const child = spawn2(
|
|
6825
|
+
path33.join(session.bundle, process.platform === "win32" ? "node.exe" : "node"),
|
|
6826
|
+
[path33.join(session.bundle, "app/dist/mason-mcp.js"), ...process.argv.slice(2)],
|
|
6827
|
+
{ stdio: "inherit", windowsHide: true }
|
|
6828
|
+
);
|
|
6829
|
+
const interrupt = () => {
|
|
6830
|
+
child.kill("SIGINT");
|
|
6831
|
+
}, terminate = () => {
|
|
6832
|
+
child.kill("SIGTERM");
|
|
6833
|
+
};
|
|
6834
|
+
process.on("SIGINT", interrupt);
|
|
6835
|
+
process.on("SIGTERM", terminate);
|
|
6836
|
+
try {
|
|
6837
|
+
process.exitCode = await new Promise((resolve, reject) => {
|
|
6838
|
+
child.on("error", reject);
|
|
6839
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
6840
|
+
});
|
|
6841
|
+
} finally {
|
|
6842
|
+
process.off("SIGINT", interrupt);
|
|
6843
|
+
process.off("SIGTERM", terminate);
|
|
6844
|
+
await session.release();
|
|
6845
|
+
}
|
|
6846
|
+
return;
|
|
6847
|
+
}
|
|
6848
|
+
const timer = setInterval(() => {
|
|
6849
|
+
void scheduleAutomaticUpdate();
|
|
6850
|
+
}, 60 * 60 * 1e3);
|
|
6851
|
+
timer.unref();
|
|
6852
|
+
const stop = () => {
|
|
6853
|
+
clearInterval(timer);
|
|
6854
|
+
void session?.release();
|
|
6855
|
+
};
|
|
6856
|
+
try {
|
|
6857
|
+
await start(stop);
|
|
6858
|
+
void scheduleAutomaticUpdate();
|
|
6859
|
+
} catch (error) {
|
|
6860
|
+
stop();
|
|
6861
|
+
throw error;
|
|
6862
|
+
}
|
|
6863
|
+
}
|
|
6864
|
+
var CHECK_INTERVAL_MS, RELEASE_AGE_MS, releaseBase, releaseListSchema, updateManifestSchema, statusSchema2;
|
|
6865
|
+
var init_updates = __esm({
|
|
6866
|
+
"src/distribution/updates.ts"() {
|
|
6867
|
+
"use strict";
|
|
6868
|
+
init_store();
|
|
6869
|
+
init_storage();
|
|
6870
|
+
init_state();
|
|
6871
|
+
init_bundle();
|
|
6872
|
+
init_runtime2();
|
|
6873
|
+
init_install2();
|
|
6874
|
+
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
6875
|
+
RELEASE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
6876
|
+
releaseBase = "https://github.com/adrianczuczka/mason/releases";
|
|
6877
|
+
releaseListSchema = z28.array(z28.object({ tag_name: z28.string(), draft: z28.boolean(), prerelease: z28.boolean(), published_at: z28.string().nullable() }));
|
|
6878
|
+
updateManifestSchema = z28.object({
|
|
6879
|
+
format: z28.literal(1),
|
|
6880
|
+
version: z28.string().regex(/^\d+\.\d+\.\d+$/),
|
|
6881
|
+
publishedAt: z28.string().datetime(),
|
|
6882
|
+
assets: z28.record(z28.string().regex(/^[a-f0-9]{64}$/))
|
|
6883
|
+
}).strict();
|
|
6884
|
+
statusSchema2 = z28.object({ lastAttempt: z28.number(), availableVersion: z28.string().optional(), error: z28.string().optional() });
|
|
6214
6885
|
}
|
|
6215
6886
|
});
|
|
6216
6887
|
|
|
@@ -6407,7 +7078,7 @@ var inputArgs = process.argv.slice(2);
|
|
|
6407
7078
|
var managedLaunch = inputArgs[0] === "--setup-host";
|
|
6408
7079
|
var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
|
|
6409
7080
|
var [command, ...args] = inputArgs;
|
|
6410
|
-
var usage = `Mason ${"0.
|
|
7081
|
+
var usage = `Mason ${"0.19.0"}
|
|
6411
7082
|
|
|
6412
7083
|
Usage: mason <command> [options]
|
|
6413
7084
|
|
|
@@ -6420,6 +7091,9 @@ Usage: mason <command> [options]
|
|
|
6420
7091
|
drift Check an optional architecture map
|
|
6421
7092
|
mcp Run the MCP server (stdio)
|
|
6422
7093
|
upgrade [version] Upgrade the standalone user installation
|
|
7094
|
+
updates [status|enable|disable|pin|unpin]
|
|
7095
|
+
Manage automatic standalone updates
|
|
7096
|
+
rollback Restore and pin the previous standalone version
|
|
6423
7097
|
uninstall Remove that installation; retain project data
|
|
6424
7098
|
|
|
6425
7099
|
Use mason <command> --help for options. npm users can keep using mason-auto,
|
|
@@ -6433,19 +7107,67 @@ try {
|
|
|
6433
7107
|
await prepareLaunch2(setupHost, { allowInactive: command === "mcp" });
|
|
6434
7108
|
}
|
|
6435
7109
|
}
|
|
6436
|
-
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.
|
|
7110
|
+
if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.19.0" }));
|
|
6437
7111
|
else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
6438
|
-
else if (["--version", "-v"].includes(command)) console.log("0.
|
|
6439
|
-
else if (command === "internal-
|
|
7112
|
+
else if (["--version", "-v"].includes(command)) console.log("0.19.0");
|
|
7113
|
+
else if (command === "internal-update") {
|
|
7114
|
+
if (args.length) throw new Error("internal-update takes no arguments.");
|
|
7115
|
+
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7116
|
+
const { runAutomaticUpdate: runAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7117
|
+
await runAutomaticUpdate2((await currentInstallation2()).home);
|
|
7118
|
+
} else if (command === "internal-install") {
|
|
6440
7119
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
6441
7120
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
6442
7121
|
const bundleRoot = fileURLToPath2(new URL("../..", import.meta.url));
|
|
6443
7122
|
progress = createProgress();
|
|
6444
|
-
const
|
|
7123
|
+
const policyRevision = process.env.MASON_UPDATE_POLICY_REVISION;
|
|
7124
|
+
if (policyRevision && !/^[a-f0-9]{64}$/.test(process.env.MASON_EXPECTED_SHA256 ?? "")) throw new Error("Automatic staging requires a verified release checksum.");
|
|
7125
|
+
const installed = await installStandalone2(bundleRoot, progress, { stageOnly: !!policyRevision, policyRevision });
|
|
6445
7126
|
progress.stop();
|
|
6446
7127
|
const outcome = installed.previousVersion && installed.previousVersion !== installed.version ? `Updated Mason ${installed.previousVersion} \u2192 ${installed.version}` : `Installed Mason ${installed.version}`;
|
|
6447
|
-
console.log(
|
|
6448
|
-
|
|
7128
|
+
if (policyRevision) console.log(`Staged Mason ${installed.version}. It will activate at a future MCP launch after existing servers exit.`);
|
|
7129
|
+
else {
|
|
7130
|
+
const { updateStatus: updateStatus2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7131
|
+
const status = await updateStatus2(installed.home);
|
|
7132
|
+
const policy = status.pinnedVersion ? `pinned to ${status.pinnedVersion}` : status.mirror ? "managed by your mirror" : status.enabled ? "enabled" : "disabled";
|
|
7133
|
+
console.log(`${outcome} in ${installed.home}.
|
|
7134
|
+
${installed.path.message}
|
|
7135
|
+
Automatic updates: ${policy}. Manage with mason updates.`);
|
|
7136
|
+
}
|
|
7137
|
+
} else if (command === "updates") {
|
|
7138
|
+
if (args.includes("--help")) console.log("Usage: mason updates [status|enable|disable|pin|unpin] [--json]. Pin holds the installed version; mason upgrade <version> installs and pins a specific release. Environment and mirror restrictions still apply.");
|
|
7139
|
+
else {
|
|
7140
|
+
const action = args.filter((arg) => arg !== "--json")[0] ?? "status";
|
|
7141
|
+
if (args.filter((arg) => arg !== "--json").length > 1 || !["status", "enable", "disable", "pin", "unpin"].includes(action)) throw new Error("Usage: mason updates [status|enable|disable|pin|unpin] [--json]");
|
|
7142
|
+
const { currentInstallation: currentInstallation2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7143
|
+
const { updateSettings: updateSettings2, updateStatus: updateStatus2, scheduleAutomaticUpdate: scheduleAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7144
|
+
const { home } = await currentInstallation2();
|
|
7145
|
+
if (action !== "status") await updateSettings2(home, action);
|
|
7146
|
+
const status = await updateStatus2(home);
|
|
7147
|
+
if (args.includes("--json")) console.log(JSON.stringify(status));
|
|
7148
|
+
else {
|
|
7149
|
+
console.log(`Mason ${status.version}
|
|
7150
|
+
Automatic updates: ${status.effective ? "enabled" : "inactive"} (preference: ${status.enabled ? "enabled" : "disabled"}).`);
|
|
7151
|
+
if (status.pinnedVersion) console.log(`Pinned to ${status.pinnedVersion}. Run mason updates unpin to follow stable releases.`);
|
|
7152
|
+
if (status.mirror) console.log("Company mirror installation: automatic public updates are disabled.");
|
|
7153
|
+
if (!status.effective && status.enabled && !status.pinnedVersion && !status.mirror) console.log("Suppressed by CI, MASON_NO_AUTO_UPDATE, MASON_VERSION, or MASON_RELEASE_BASE.");
|
|
7154
|
+
if (status.pendingVersion) console.log(`Downloaded ${status.pendingVersion}; activates on a future MCP launch after existing servers exit.`);
|
|
7155
|
+
if (status.lastCheck) {
|
|
7156
|
+
console.log(`Last check: ${new Date(status.lastCheck.lastAttempt).toISOString()}`);
|
|
7157
|
+
if (status.lastCheck.availableVersion) console.log(`Available stable release: ${status.lastCheck.availableVersion}`);
|
|
7158
|
+
if (status.lastCheck.error) console.log(`Last update problem: ${status.lastCheck.error}`);
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
if (action === "enable" || action === "unpin") void scheduleAutomaticUpdate2();
|
|
7162
|
+
}
|
|
7163
|
+
} else if (command === "rollback") {
|
|
7164
|
+
if (args.includes("--help")) console.log("Usage: mason rollback. Close running Mason MCP servers, then restore and pin the previous installed version.");
|
|
7165
|
+
else {
|
|
7166
|
+
if (args.length) throw new Error("rollback takes no arguments.");
|
|
7167
|
+
const { rollbackStandalone: rollbackStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
7168
|
+
const restored = await rollbackStandalone2();
|
|
7169
|
+
console.log(`Restored Mason ${restored.version} and pinned it. Run mason updates unpin to resume automatic updates.`);
|
|
7170
|
+
}
|
|
6449
7171
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
6450
7172
|
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Configured projects use the upgraded command on their next launch. Restart running assistants." : "Usage: mason uninstall. Removes the standalone user installation; project configuration and knowledge are retained. Run mason teardown in each project first to disconnect its integrations.");
|
|
6451
7173
|
else {
|
|
@@ -6470,6 +7192,10 @@ ${installed.path.message}`);
|
|
|
6470
7192
|
void 0,
|
|
6471
7193
|
{ managedHost: managedLaunch ? setupHost : void 0 }
|
|
6472
7194
|
);
|
|
7195
|
+
if (command === "setup" && process.exitCode === 0 && !args.includes("--help")) {
|
|
7196
|
+
const { scheduleAutomaticUpdate: scheduleAutomaticUpdate2 } = await Promise.resolve().then(() => (init_updates(), updates_exports));
|
|
7197
|
+
void scheduleAutomaticUpdate2();
|
|
7198
|
+
}
|
|
6473
7199
|
} else if (["audit", "review", "drift", "hook", "mcp"].includes(command)) {
|
|
6474
7200
|
const binary = new URL(`./mason-${command}.js`, import.meta.url);
|
|
6475
7201
|
process.argv = [process.execPath, fileURLToPath2(binary), ...args];
|