mason-context 0.14.0 → 0.14.1
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 +10 -0
- package/README.md +2 -2
- package/dist/mason-auto.js +1 -1
- package/dist/mason-mcp.js +2 -2
- package/dist/mason.js +269 -69
- package/dist/mason.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.1 — 2026-09-07
|
|
4
|
+
|
|
5
|
+
- Configure PATH automatically during standalone installation for Bash, Zsh, POSIX sh, and Windows. Supported installations finish with a simple instruction to open a new terminal and run setup.
|
|
6
|
+
- Preserve existing shell settings, profile symlinks, Windows PATH entries and registry value types. Track owned additions across repeat installs and upgrades; uninstall removes unchanged additions and retains user edits.
|
|
7
|
+
- Avoid a same-name PowerShell script shadowing the Windows command under restricted script policy. Migrate the old owned helper without changing user or machine execution policy.
|
|
8
|
+
- Support `MASON_PROFILE` for custom shell profiles and `MASON_NO_MODIFY_PATH=1` for opt-out. Unsupported or unwritable settings receive explicit manual instructions.
|
|
9
|
+
- Test fresh-terminal discovery, duplicate prevention, and PATH cleanup in native package checks. Bound retries for transient Windows test-directory locks and report smoke success only after teardown completes.
|
|
10
|
+
|
|
11
|
+
Run the [installer](docs/distribution.md) again to upgrade from 0.14.0 and configure PATH. Existing project integrations remain pinned until you rerun `mason setup --host codex` or `--host claude` in each project.
|
|
12
|
+
|
|
3
13
|
## 0.14.0 — 2026-09-07
|
|
4
14
|
|
|
5
15
|
Install Mason without installing Node or npm. Standalone downloads bundle the runtime for macOS, Linux, and Windows.
|
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ Windows PowerShell:
|
|
|
26
26
|
irm https://github.com/adrianczuczka/mason/releases/latest/download/install.ps1 | iex
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
No Node or npm required. Git is required.
|
|
29
|
+
No Node or npm required. Git is required. The installer configures PATH for Bash, Zsh, and Windows. [Platform details](docs/distribution.md) · [npm installation](docs/setup.md#unified-project-setup)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Open a new terminal, then run this in your Git repository:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
mason setup --host codex
|
package/dist/mason-auto.js
CHANGED
|
@@ -2398,7 +2398,7 @@ var init_evidence = __esm({
|
|
|
2398
2398
|
init_dead_command();
|
|
2399
2399
|
init_storage();
|
|
2400
2400
|
exec7 = promisify7(execFile7);
|
|
2401
|
-
engineVersion = true ? "0.14.
|
|
2401
|
+
engineVersion = true ? "0.14.1" : "development";
|
|
2402
2402
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2403
2403
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
2404
2404
|
cacheSchema = z5.object({ version: z5.literal(1), entries: z5.record(z5.object({ key: z5.string(), result: checkResultSchema })), digest: z5.string() });
|
package/dist/mason-mcp.js
CHANGED
|
@@ -4163,7 +4163,7 @@ var init_evidence2 = __esm({
|
|
|
4163
4163
|
init_dead_command();
|
|
4164
4164
|
init_storage();
|
|
4165
4165
|
exec14 = promisify14(execFile14);
|
|
4166
|
-
engineVersion = true ? "0.14.
|
|
4166
|
+
engineVersion = true ? "0.14.1" : "development";
|
|
4167
4167
|
hash = (value) => createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
4168
4168
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
4169
4169
|
cacheSchema = z11.object({ version: z11.literal(1), entries: z11.record(z11.object({ key: z11.string(), result: checkResultSchema })), digest: z11.string() });
|
|
@@ -8113,7 +8113,7 @@ function createMcpServer() {
|
|
|
8113
8113
|
const server = new McpServer(
|
|
8114
8114
|
{
|
|
8115
8115
|
name: "mason",
|
|
8116
|
-
version: "0.14.
|
|
8116
|
+
version: "0.14.1"
|
|
8117
8117
|
},
|
|
8118
8118
|
{
|
|
8119
8119
|
instructions: 'Mason retrieves recorded decisions, file impact, and optional feature/flow maps. Use get_context with the task and known files, and get_impact before editing. Save learned rationale and constraints with save_decision; review and commit the local records through the project workflow. These tools work without initialization or a concept map. Consult trust and diagnostics: changed or unknown freshness needs source inspection, failed verification needs correction, and proposals are suggestions and legacy records are unreviewed. Accepted decisions are recorded team constraints, still subject to freshness checks. Use review_decision to prepare code evidence and record only authorized acceptance, reaffirmation, or retirement. Never invent a reviewer or treat these recorded identities as authenticated approval. mason_init returns documentation audit and committed-diff review findings with a quickstart guide. Use mode: "map" only when a full architecture map is requested. A missing map is not a setup failure; use decisions and source evidence. get_snapshot provides architecture navigation when a map is available. The mason-audit and mason-review CLIs also work without setup.'
|
package/dist/mason.js
CHANGED
|
@@ -262,15 +262,15 @@ function parseExecution(raw) {
|
|
|
262
262
|
function automationFailure(error) {
|
|
263
263
|
const recorded = failureSchema.safeParse(error?.failure);
|
|
264
264
|
if (recorded.success) return recorded.data;
|
|
265
|
-
const
|
|
265
|
+
const message3 = (error instanceof Error ? error.message : String(error)).replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").slice(0, 700);
|
|
266
266
|
const codes = /* @__PURE__ */ new Set();
|
|
267
267
|
let cause = error;
|
|
268
268
|
for (let i = 0; cause && i < 8; i++) {
|
|
269
269
|
codes.add(String(cause.code));
|
|
270
270
|
cause = cause.cause;
|
|
271
271
|
}
|
|
272
|
-
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(
|
|
273
|
-
return { code, message:
|
|
272
|
+
const code = codes.has("ENOSPC") || codes.has("EDQUOT") ? "storage-full" : /changed during|changed while|changed between/.test(message3) ? "inputs-changed" : /Automation is busy/.test(message3) ? "busy" : /not a git repository|unknown revision|bad revision|different history|unreachable/.test(message3) ? "history-unavailable" : error instanceof z.ZodError || error instanceof SyntaxError || /Hook input|Expected one command|Unknown automation command|--host/.test(message3) ? "invalid-input" : /store|baseline|modified|symbolic link|Symlink|automation state|state belongs/.test(message3) ? "invalid-evidence" : [...codes].some((c) => /^E[A-Z]+$/.test(c)) ? "io-error" : "internal";
|
|
273
|
+
return { code, message: message3, retryable: ["inputs-changed", "storage-full", "busy", "io-error"].includes(code), receiptRecorded: false };
|
|
274
274
|
}
|
|
275
275
|
function failureMessage(error) {
|
|
276
276
|
const failure = automationFailure(error);
|
|
@@ -1736,7 +1736,7 @@ var init_provenance = __esm({
|
|
|
1736
1736
|
sources: z3.array(decisionSourceSchema).max(20),
|
|
1737
1737
|
history: z3.array(eventSchema).min(1)
|
|
1738
1738
|
}).superRefine((record, ctx) => {
|
|
1739
|
-
const invalid = (
|
|
1739
|
+
const invalid = (message3) => ctx.addIssue({ code: "custom", message: message3 });
|
|
1740
1740
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
1741
1741
|
let previous;
|
|
1742
1742
|
for (const event of record.history) {
|
|
@@ -2398,7 +2398,7 @@ var init_evidence = __esm({
|
|
|
2398
2398
|
init_dead_command();
|
|
2399
2399
|
init_storage();
|
|
2400
2400
|
exec7 = promisify7(execFile7);
|
|
2401
|
-
engineVersion = true ? "0.14.
|
|
2401
|
+
engineVersion = true ? "0.14.1" : "development";
|
|
2402
2402
|
hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
2403
2403
|
internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
|
|
2404
2404
|
cacheSchema = z5.object({ version: z5.literal(1), entries: z5.record(z5.object({ key: z5.string(), result: checkResultSchema })), digest: z5.string() });
|
|
@@ -2897,8 +2897,8 @@ async function runAutomationHook(host, stdin) {
|
|
|
2897
2897
|
}
|
|
2898
2898
|
return { hookSpecificOutput: { hookEventName: name, additionalContext: result.message } };
|
|
2899
2899
|
} catch (error) {
|
|
2900
|
-
const
|
|
2901
|
-
return { systemMessage:
|
|
2900
|
+
const message3 = failureMessage(error);
|
|
2901
|
+
return { systemMessage: message3, ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message3 } } : {} };
|
|
2902
2902
|
}
|
|
2903
2903
|
}
|
|
2904
2904
|
function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
|
|
@@ -3217,8 +3217,8 @@ var init_paths2 = __esm({
|
|
|
3217
3217
|
});
|
|
3218
3218
|
|
|
3219
3219
|
// src/review/evidence/types.ts
|
|
3220
|
-
function messagePreview(
|
|
3221
|
-
return { message:
|
|
3220
|
+
function messagePreview(message3) {
|
|
3221
|
+
return { message: message3.slice(0, 4e3), ...message3.length > 4e3 ? { truncated: true } : {} };
|
|
3222
3222
|
}
|
|
3223
3223
|
var MAX_FINDINGS;
|
|
3224
3224
|
var init_types2 = __esm({
|
|
@@ -4363,7 +4363,7 @@ function summarizeActivation(status) {
|
|
|
4363
4363
|
` Runtime: ${state.runtime}; MCP: ${state.mcp}; instructions: ${state.instructions}.`,
|
|
4364
4364
|
` Hooks: ${state.hookConfiguration}; observed: ${state.observedEvents.join(", ") || "none"}.`,
|
|
4365
4365
|
` Task context requests: ${state.contextCalls}; verification: ${state.verificationStatus}.`,
|
|
4366
|
-
...state.pending.map((
|
|
4366
|
+
...state.pending.map((message3) => " Next: " + message3)
|
|
4367
4367
|
);
|
|
4368
4368
|
}
|
|
4369
4369
|
if ("decisionRecords" in status) lines.push(`Decision records: ${status.decisionRecords}.`);
|
|
@@ -4520,6 +4520,185 @@ var init_setup = __esm({
|
|
|
4520
4520
|
}
|
|
4521
4521
|
});
|
|
4522
4522
|
|
|
4523
|
+
// src/distribution/path.ts
|
|
4524
|
+
import fs18 from "fs/promises";
|
|
4525
|
+
import os3 from "os";
|
|
4526
|
+
import path23 from "path";
|
|
4527
|
+
import { execFile as execFile11 } from "child_process";
|
|
4528
|
+
import { promisify as promisify11 } from "util";
|
|
4529
|
+
import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
|
|
4530
|
+
import { z as z17 } from "zod";
|
|
4531
|
+
async function configurePath(bin, previous, save, options = {}) {
|
|
4532
|
+
const env = options.env ?? process.env, platform = options.platform ?? process.platform;
|
|
4533
|
+
const changes = [...previous];
|
|
4534
|
+
const manual = (reason2) => ({ status: "manual", changes, message: `${reason2}
|
|
4535
|
+
Add ${bin} to your PATH, then run: mason setup --host codex` });
|
|
4536
|
+
if (env.MASON_NO_MODIFY_PATH === "1") return manual("Automatic PATH setup is disabled (MASON_NO_MODIFY_PATH=1).");
|
|
4537
|
+
if (/[\r\n\0]/.test(bin) || bin.includes(platform === "win32" ? ";" : ":")) return manual("This installation path cannot be represented safely in PATH.");
|
|
4538
|
+
try {
|
|
4539
|
+
if (platform === "win32") {
|
|
4540
|
+
const current = await windowsPath("read");
|
|
4541
|
+
if (!hasWindowsEntry(current.value, bin)) {
|
|
4542
|
+
if (!changes.some((c) => c.kind === "windows" && c.entry === bin)) {
|
|
4543
|
+
changes.push({ kind: "windows", entry: bin, created: current.value === null });
|
|
4544
|
+
await save(changes);
|
|
4545
|
+
}
|
|
4546
|
+
await windowsPath("add", bin, current);
|
|
4547
|
+
}
|
|
4548
|
+
return { status: "configured", changes, message: message2 };
|
|
4549
|
+
}
|
|
4550
|
+
const shell = path23.basename(env.SHELL ?? os3.userInfo().shell ?? "");
|
|
4551
|
+
const userHome = options.userHome ?? os3.homedir();
|
|
4552
|
+
let profiles;
|
|
4553
|
+
if (!["bash", "zsh", "sh"].includes(shell)) return manual(`Automatic PATH setup is unavailable for ${shell || "an unknown shell"}.`);
|
|
4554
|
+
if (env.MASON_PROFILE) profiles = [path23.resolve(env.MASON_PROFILE)];
|
|
4555
|
+
else if (shell === "zsh") profiles = [path23.join(env.ZDOTDIR || userHome, ".zshrc")];
|
|
4556
|
+
else if (shell === "sh") profiles = [path23.join(userHome, ".profile")];
|
|
4557
|
+
else {
|
|
4558
|
+
const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path23.join(userHome, name));
|
|
4559
|
+
let login = candidates[2];
|
|
4560
|
+
for (const file of candidates) if (await fs18.lstat(file).then(() => true, (error) => {
|
|
4561
|
+
if (error.code === "ENOENT") return false;
|
|
4562
|
+
throw error;
|
|
4563
|
+
})) {
|
|
4564
|
+
login = file;
|
|
4565
|
+
break;
|
|
4566
|
+
}
|
|
4567
|
+
profiles = [path23.join(userHome, ".bashrc"), login];
|
|
4568
|
+
}
|
|
4569
|
+
const marker = `# >>> mason PATH ${createHash6("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
|
|
4570
|
+
for (const profile of profiles) {
|
|
4571
|
+
const stat = await fs18.lstat(profile).catch((error) => {
|
|
4572
|
+
if (error.code === "ENOENT") return null;
|
|
4573
|
+
throw error;
|
|
4574
|
+
});
|
|
4575
|
+
const file = stat?.isSymbolicLink() ? await fs18.realpath(profile) : path23.resolve(profile);
|
|
4576
|
+
const original = await fs18.readFile(file).catch((error) => {
|
|
4577
|
+
if (error.code === "ENOENT" && !stat) return null;
|
|
4578
|
+
throw error;
|
|
4579
|
+
});
|
|
4580
|
+
if (original && !Buffer.from(original.toString("utf8")).equals(original)) return manual(`Shell profile is not UTF-8: ${profile}.`);
|
|
4581
|
+
const text2 = original?.toString("utf8") ?? "";
|
|
4582
|
+
const owned = changes.find((c) => c.kind === "profile" && c.file === file);
|
|
4583
|
+
if (owned && text2.includes(owned.block)) continue;
|
|
4584
|
+
if (text2.includes(marker) || owned && text2.includes("# <<< mason PATH")) return manual(`Mason's PATH block was edited; retained ${profile}.`);
|
|
4585
|
+
const newline = text2.includes("\r\n") ? "\r\n" : "\n";
|
|
4586
|
+
const block = owned?.block ?? ("\n" + marker + '\ncase ":${PATH-}:" in\n *:' + quote(bin) + ":*) ;;\n *) export PATH=" + quote(bin) + ':"${PATH-}" ;;\nesac\n# <<< mason PATH <<<\n').replaceAll("\n", newline);
|
|
4587
|
+
if (!owned) {
|
|
4588
|
+
changes.push({ kind: "profile", file, block, created: original === null });
|
|
4589
|
+
await save(changes);
|
|
4590
|
+
}
|
|
4591
|
+
await fs18.mkdir(path23.dirname(file), { recursive: true });
|
|
4592
|
+
const handle = await fs18.open(file, original === null ? "ax+" : "a+", 384);
|
|
4593
|
+
try {
|
|
4594
|
+
if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
|
|
4595
|
+
await handle.writeFile(block);
|
|
4596
|
+
await handle.sync();
|
|
4597
|
+
} finally {
|
|
4598
|
+
await handle.close();
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
return { status: "configured", changes, message: message2 };
|
|
4602
|
+
} catch (error) {
|
|
4603
|
+
return manual(`Could not configure PATH: ${error instanceof Error ? error.message : String(error)}`);
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
async function removePath(changes) {
|
|
4607
|
+
const warnings = [];
|
|
4608
|
+
for (const change of changes) {
|
|
4609
|
+
try {
|
|
4610
|
+
if (change.kind === "windows") {
|
|
4611
|
+
const current = await windowsPath("read");
|
|
4612
|
+
if (!(current.value ?? "").split(";").includes(change.entry) && hasWindowsEntry(current.value, change.entry)) throw new Error("PATH entry was edited");
|
|
4613
|
+
await windowsPath("remove", change.entry, current, change.created);
|
|
4614
|
+
continue;
|
|
4615
|
+
}
|
|
4616
|
+
const stat = await fs18.lstat(change.file).catch((error) => {
|
|
4617
|
+
if (error.code === "ENOENT") return null;
|
|
4618
|
+
throw error;
|
|
4619
|
+
});
|
|
4620
|
+
if (!stat) continue;
|
|
4621
|
+
if (!stat.isFile()) throw new Error("profile type changed");
|
|
4622
|
+
const bytes = await fs18.readFile(change.file), text2 = bytes.toString("utf8");
|
|
4623
|
+
if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
|
|
4624
|
+
const index2 = text2.indexOf(change.block);
|
|
4625
|
+
if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
|
|
4626
|
+
const prefix = text2.slice(0, index2), suffix = text2.slice(index2 + change.block.length);
|
|
4627
|
+
const separator = prefix && suffix && !prefix.endsWith("\n") ? change.block.startsWith("\r\n") ? "\r\n" : "\n" : "";
|
|
4628
|
+
const next = Buffer.from(prefix + separator + suffix);
|
|
4629
|
+
const temporary = change.file + ".mason-" + randomUUID8();
|
|
4630
|
+
try {
|
|
4631
|
+
if (stat.nlink > 1) throw new Error("profile has multiple hard links");
|
|
4632
|
+
const handle = await fs18.open(temporary, "wx", stat.mode & 511);
|
|
4633
|
+
try {
|
|
4634
|
+
await handle.writeFile(next);
|
|
4635
|
+
await handle.chmod(stat.mode & 511);
|
|
4636
|
+
if (process.platform !== "win32") await handle.chown(stat.uid, stat.gid);
|
|
4637
|
+
await handle.sync();
|
|
4638
|
+
} finally {
|
|
4639
|
+
await handle.close();
|
|
4640
|
+
}
|
|
4641
|
+
const current = await fs18.lstat(change.file);
|
|
4642
|
+
if (!current.isFile() || current.ino !== stat.ino || !(await fs18.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
|
|
4643
|
+
await fs18.rename(temporary, change.file);
|
|
4644
|
+
} finally {
|
|
4645
|
+
await fs18.rm(temporary, { force: true });
|
|
4646
|
+
}
|
|
4647
|
+
} catch (error) {
|
|
4648
|
+
warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
return warnings;
|
|
4652
|
+
}
|
|
4653
|
+
async function windowsPath(action, entry = "", expected, removeEmpty = false) {
|
|
4654
|
+
const payload = Buffer.from(JSON.stringify({ action, entry, expected, removeEmpty })).toString("base64");
|
|
4655
|
+
const script = `$ErrorActionPreference = 'Stop'
|
|
4656
|
+
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
|
|
4657
|
+
$p = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${payload}')) | ConvertFrom-Json
|
|
4658
|
+
$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
|
|
4659
|
+
try {
|
|
4660
|
+
$value = $key.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
|
4661
|
+
$kind = if ($null -eq $value) { $null } else { $key.GetValueKind('Path').ToString() }
|
|
4662
|
+
if ($null -ne $kind -and $kind -notin @('String','ExpandString')) { throw 'User PATH is not a string.' }
|
|
4663
|
+
if ($p.action -eq 'read') { @{ value = $value; kind = $kind } | ConvertTo-Json -Compress; exit 0 }
|
|
4664
|
+
if ($p.expected -and ($value -cne $p.expected.value -or $kind -cne $p.expected.kind)) { throw 'User PATH changed concurrently; retry the operation.' }
|
|
4665
|
+
$parts = if ($null -eq $value -or $value -eq '') { @() } else { @($value.Split(';')) }
|
|
4666
|
+
if ($p.action -eq 'add') { $next = (@($p.entry) + $parts) -join ';' }
|
|
4667
|
+
else { $next = (@($parts | Where-Object { $_ -cne $p.entry })) -join ';' }
|
|
4668
|
+
if ($next -cne $value) {
|
|
4669
|
+
$writeKind = if ($kind) { [Microsoft.Win32.RegistryValueKind]$kind } else { [Microsoft.Win32.RegistryValueKind]::ExpandString }
|
|
4670
|
+
if ($p.action -eq 'remove' -and $p.removeEmpty -and $next -eq '') { $key.DeleteValue('Path', $false) }
|
|
4671
|
+
else { $key.SetValue('Path', $next, $writeKind) }
|
|
4672
|
+
Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class MasonEnvironment { [DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern IntPtr SendMessageTimeout(IntPtr h, uint m, UIntPtr w, string l, uint f, uint t, out UIntPtr r); }'
|
|
4673
|
+
$result = [UIntPtr]::Zero
|
|
4674
|
+
[void][MasonEnvironment]::SendMessageTimeout([IntPtr]0xffff, 0x1a, [UIntPtr]::Zero, 'Environment', 2, 1000, [ref]$result)
|
|
4675
|
+
}
|
|
4676
|
+
@{ value = $next; kind = $kind } | ConvertTo-Json -Compress
|
|
4677
|
+
} finally { $key.Dispose() }
|
|
4678
|
+
`;
|
|
4679
|
+
const { stdout } = await execute("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { windowsHide: true, timeout: 15e3, maxBuffer: 1024 * 1024 });
|
|
4680
|
+
return JSON.parse(stdout.replace(/^\uFEFF/, "").trim());
|
|
4681
|
+
}
|
|
4682
|
+
var execute, pathChangeSchema, quote, message2, normalizeWindowsEntry, hasWindowsEntry;
|
|
4683
|
+
var init_path = __esm({
|
|
4684
|
+
"src/distribution/path.ts"() {
|
|
4685
|
+
"use strict";
|
|
4686
|
+
execute = promisify11(execFile11);
|
|
4687
|
+
pathChangeSchema = z17.discriminatedUnion("kind", [
|
|
4688
|
+
z17.object({ kind: z17.literal("profile"), file: z17.string(), block: z17.string(), created: z17.boolean() }),
|
|
4689
|
+
z17.object({ kind: z17.literal("windows"), entry: z17.string(), created: z17.boolean() })
|
|
4690
|
+
]);
|
|
4691
|
+
quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4692
|
+
message2 = "Open a new terminal, then run: mason setup --host codex";
|
|
4693
|
+
normalizeWindowsEntry = (value) => path23.win32.normalize(value.trim().replace(/^"(.*)"$/, "$1")).replace(/[\\/]+$/, "").toLowerCase();
|
|
4694
|
+
hasWindowsEntry = (value, entry, env = process.env) => {
|
|
4695
|
+
const variables = new Map(Object.entries(env).map(([key, value2]) => [key.toLowerCase(), value2]));
|
|
4696
|
+
const expand = (part) => part.replace(/%([^%]+)%/g, (match, key) => variables.get(key.toLowerCase()) ?? match);
|
|
4697
|
+
return (value ?? "").split(";").some((part) => normalizeWindowsEntry(expand(part)) === normalizeWindowsEntry(entry));
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4700
|
+
});
|
|
4701
|
+
|
|
4523
4702
|
// src/distribution/install.ts
|
|
4524
4703
|
var install_exports2 = {};
|
|
4525
4704
|
__export(install_exports2, {
|
|
@@ -4529,55 +4708,55 @@ __export(install_exports2, {
|
|
|
4529
4708
|
uninstallStandalone: () => uninstallStandalone,
|
|
4530
4709
|
upgradeStandalone: () => upgradeStandalone
|
|
4531
4710
|
});
|
|
4532
|
-
import
|
|
4533
|
-
import
|
|
4534
|
-
import
|
|
4711
|
+
import fs19 from "fs/promises";
|
|
4712
|
+
import os4 from "os";
|
|
4713
|
+
import path24 from "path";
|
|
4535
4714
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4536
4715
|
import { spawn } from "child_process";
|
|
4537
|
-
import { randomUUID as
|
|
4538
|
-
import { z as
|
|
4716
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
4717
|
+
import { z as z18 } from "zod";
|
|
4539
4718
|
async function replace(file, content2, mode = 384) {
|
|
4540
|
-
const temp = file + ".tmp-" +
|
|
4719
|
+
const temp = file + ".tmp-" + randomUUID9();
|
|
4541
4720
|
try {
|
|
4542
|
-
await
|
|
4543
|
-
await
|
|
4721
|
+
await fs19.writeFile(temp, content2, { mode, flag: "wx" });
|
|
4722
|
+
await fs19.rename(temp, file);
|
|
4544
4723
|
} finally {
|
|
4545
|
-
await
|
|
4724
|
+
await fs19.rm(temp, { force: true });
|
|
4546
4725
|
}
|
|
4547
4726
|
}
|
|
4548
4727
|
async function installStandalone(source) {
|
|
4549
4728
|
const bundle = await verifyBundle(source);
|
|
4550
|
-
const selectedHome =
|
|
4551
|
-
await
|
|
4552
|
-
const home = await
|
|
4553
|
-
const selectedBin =
|
|
4729
|
+
const selectedHome = path24.resolve(defaultHome());
|
|
4730
|
+
await fs19.mkdir(selectedHome, { recursive: true });
|
|
4731
|
+
const home = await fs19.realpath(selectedHome);
|
|
4732
|
+
const selectedBin = path24.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path24.join(home, "bin") : path24.join(os4.homedir(), ".local/bin")));
|
|
4554
4733
|
return withLock(home, ".install-lock", async () => {
|
|
4555
4734
|
const raw = await readStoreJson(home, "install.json");
|
|
4556
4735
|
const previous = raw === null ? null : recordSchema2.parse(raw);
|
|
4557
|
-
if (!previous) for (const file of await
|
|
4736
|
+
if (!previous) for (const file of await fs19.readdir(home)) {
|
|
4558
4737
|
if (file === ".install-lock") continue;
|
|
4559
|
-
const directory =
|
|
4560
|
-
const stat = await
|
|
4561
|
-
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await
|
|
4738
|
+
const directory = path24.join(home, file);
|
|
4739
|
+
const stat = await fs19.lstat(directory);
|
|
4740
|
+
if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs19.readdir(directory)).length) {
|
|
4562
4741
|
throw new Error("Mason installation directory is not empty and has no ownership receipt.");
|
|
4563
4742
|
}
|
|
4564
4743
|
}
|
|
4565
|
-
await
|
|
4566
|
-
const bin = await
|
|
4744
|
+
await fs19.mkdir(selectedBin, { recursive: true });
|
|
4745
|
+
const bin = await fs19.realpath(selectedBin);
|
|
4567
4746
|
if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
|
|
4568
4747
|
const id = bundle.manifestHash.slice(0, 24);
|
|
4569
4748
|
const destination = await storePath(home, "versions/" + id);
|
|
4570
4749
|
const launchers = process.platform === "win32" ? {
|
|
4571
4750
|
"mason.cmd": WINDOWS_BATCH_LAUNCHER,
|
|
4572
|
-
"mason.ps1": `$ErrorActionPreference = 'Stop'
|
|
4751
|
+
"mason-launcher.ps1": `$ErrorActionPreference = 'Stop'
|
|
4573
4752
|
$previousToken = $env:MASON_UNINSTALL_TOKEN
|
|
4574
4753
|
$token = [Guid]::NewGuid().ToString()
|
|
4575
4754
|
$env:MASON_UNINSTALL_TOKEN = $token
|
|
4576
4755
|
try {
|
|
4577
|
-
& ${psQuote(
|
|
4756
|
+
& ${psQuote(path24.join(destination, "node.exe"))} ${psQuote(path24.join(destination, "app/dist/mason.js"))} @args
|
|
4578
4757
|
$code = $LASTEXITCODE
|
|
4579
4758
|
if ($code -eq 0 -and $args.Count -eq 1 -and $args[0] -eq 'uninstall') {
|
|
4580
|
-
if ([IO.File]::ReadAllText(${psQuote(
|
|
4759
|
+
if ([IO.File]::ReadAllText(${psQuote(path24.join(home, ".uninstall-request.txt"))}) -cne $token) { throw 'Mason did not authorize installation cleanup.' }
|
|
4581
4760
|
Remove-Item -LiteralPath ${psQuote(home)} -Recurse -Force
|
|
4582
4761
|
Write-Output ${psQuote("Removed standalone installation: " + home)}
|
|
4583
4762
|
}
|
|
@@ -4586,48 +4765,66 @@ try {
|
|
|
4586
4765
|
finally { $env:MASON_UNINSTALL_TOKEN = $previousToken }
|
|
4587
4766
|
`
|
|
4588
4767
|
} : { mason: `#!/bin/sh
|
|
4589
|
-
exec ${shQuote(
|
|
4768
|
+
exec ${shQuote(path24.join(destination, "node"))} ${shQuote(path24.join(destination, "app/dist/mason.js"))} "$@"
|
|
4590
4769
|
` };
|
|
4591
4770
|
for (const [name, content2] of Object.entries(launchers)) {
|
|
4592
4771
|
const file = await storePath(bin, name);
|
|
4593
|
-
const old = await
|
|
4772
|
+
const old = await fs19.readFile(file, "utf8").catch((error) => {
|
|
4594
4773
|
if (error.code === "ENOENT") return null;
|
|
4595
4774
|
throw error;
|
|
4596
4775
|
});
|
|
4597
4776
|
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);
|
|
4598
4777
|
}
|
|
4599
|
-
const
|
|
4778
|
+
const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
|
|
4779
|
+
const checkRetired = async (name, expected) => {
|
|
4780
|
+
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
4781
|
+
const actual = await fs19.readFile(await storePath(bin, name), "utf8").catch((error) => {
|
|
4782
|
+
if (error.code === "ENOENT") return null;
|
|
4783
|
+
throw error;
|
|
4784
|
+
});
|
|
4785
|
+
if (actual !== null && actual !== expected) throw new Error("Retired launcher was edited; retained: " + name);
|
|
4786
|
+
};
|
|
4787
|
+
for (const [name, expected] of retired) await checkRetired(name, expected);
|
|
4788
|
+
const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
|
|
4600
4789
|
await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
|
|
4601
|
-
const stage = await storePath(home, "versions/.install-" +
|
|
4602
|
-
await
|
|
4790
|
+
const stage = await storePath(home, "versions/.install-" + randomUUID9(), true);
|
|
4791
|
+
await fs19.mkdir(stage);
|
|
4603
4792
|
try {
|
|
4604
4793
|
for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
|
|
4605
4794
|
const from = await storePath(source, file), to = await storePath(stage, file, true);
|
|
4606
|
-
await
|
|
4607
|
-
await
|
|
4795
|
+
await fs19.copyFile(from, to);
|
|
4796
|
+
await fs19.chmod(to, (await fs19.stat(from)).mode & 511);
|
|
4608
4797
|
}
|
|
4609
4798
|
await verifyBundle(stage, bundle.manifestHash);
|
|
4610
|
-
const exists2 = await
|
|
4799
|
+
const exists2 = await fs19.lstat(destination).then(() => true, (error) => {
|
|
4611
4800
|
if (error.code === "ENOENT") return false;
|
|
4612
4801
|
throw error;
|
|
4613
4802
|
});
|
|
4614
4803
|
if (exists2) await verifyBundle(destination, bundle.manifestHash);
|
|
4615
|
-
else await
|
|
4804
|
+
else await fs19.rename(stage, destination);
|
|
4616
4805
|
for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
|
|
4806
|
+
for (const [name, expected] of retired) {
|
|
4807
|
+
await checkRetired(name, expected);
|
|
4808
|
+
await fs19.rm(await storePath(bin, name), { force: true });
|
|
4809
|
+
}
|
|
4617
4810
|
await writeStoreJson(home, "install.json", receipt);
|
|
4618
4811
|
} finally {
|
|
4619
|
-
await
|
|
4812
|
+
await fs19.rm(stage, { recursive: true, force: true });
|
|
4620
4813
|
}
|
|
4621
|
-
|
|
4814
|
+
const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
|
|
4815
|
+
receipt.pathChanges = changes;
|
|
4816
|
+
await writeStoreJson(home, "install.json", receipt);
|
|
4817
|
+
});
|
|
4818
|
+
return { home, bin, version: bundle.manifest.version, path: pathResult };
|
|
4622
4819
|
});
|
|
4623
4820
|
}
|
|
4624
4821
|
async function currentInstallation() {
|
|
4625
|
-
const app =
|
|
4626
|
-
const bundle =
|
|
4822
|
+
const app = path24.dirname(path24.dirname(fileURLToPath2(import.meta.url)));
|
|
4823
|
+
const bundle = path24.dirname(app), home = path24.dirname(path24.dirname(bundle));
|
|
4627
4824
|
const raw = await readStoreJson(home, "install.json");
|
|
4628
4825
|
if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
|
|
4629
4826
|
const record = recordSchema2.parse(raw);
|
|
4630
|
-
if (record.home !== await
|
|
4827
|
+
if (record.home !== await fs19.realpath(home) || path24.dirname(bundle) !== path24.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
|
|
4631
4828
|
return { home, bundle, record };
|
|
4632
4829
|
}
|
|
4633
4830
|
async function upgradeStandalone(version) {
|
|
@@ -4635,7 +4832,7 @@ async function upgradeStandalone(version) {
|
|
|
4635
4832
|
const { home, bundle, record } = await currentInstallation();
|
|
4636
4833
|
await verifyBundle(bundle);
|
|
4637
4834
|
const windows = process.platform === "win32";
|
|
4638
|
-
const script =
|
|
4835
|
+
const script = path24.join(bundle, windows ? "install.ps1" : "install.sh");
|
|
4639
4836
|
return new Promise((resolve, reject) => {
|
|
4640
4837
|
const child = spawn(
|
|
4641
4838
|
windows ? "powershell.exe" : "sh",
|
|
@@ -4654,40 +4851,44 @@ async function uninstallStandalone() {
|
|
|
4654
4851
|
}
|
|
4655
4852
|
await withLock(home, ".install-lock", async () => {
|
|
4656
4853
|
for (const [name, expected] of Object.entries(record.launchers)) {
|
|
4657
|
-
if (!
|
|
4854
|
+
if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
|
|
4658
4855
|
const file = await storePath(record.bin, name);
|
|
4659
|
-
const actual = await
|
|
4856
|
+
const actual = await fs19.readFile(file, "utf8").catch((error) => {
|
|
4660
4857
|
if (error.code === "ENOENT") return null;
|
|
4661
4858
|
throw error;
|
|
4662
4859
|
});
|
|
4663
4860
|
if (actual !== null && actual !== expected) throw new Error("Launcher was edited; retained: " + file);
|
|
4664
4861
|
}
|
|
4862
|
+
for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
|
|
4665
4863
|
if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
|
|
4666
|
-
for (const name of Object.keys(record.launchers)) await
|
|
4864
|
+
for (const name of Object.keys(record.launchers)) await fs19.rm(await storePath(record.bin, name), { force: true });
|
|
4667
4865
|
});
|
|
4668
|
-
if (process.platform !== "win32") await
|
|
4866
|
+
if (process.platform !== "win32") await fs19.rm(home, { recursive: true, force: true });
|
|
4669
4867
|
return home;
|
|
4670
4868
|
}
|
|
4671
|
-
var recordSchema2, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, defaultHome;
|
|
4869
|
+
var recordSchema2, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames, defaultHome;
|
|
4672
4870
|
var init_install2 = __esm({
|
|
4673
4871
|
"src/distribution/install.ts"() {
|
|
4674
4872
|
"use strict";
|
|
4675
4873
|
init_bundle();
|
|
4676
4874
|
init_storage();
|
|
4677
4875
|
init_store();
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4876
|
+
init_path();
|
|
4877
|
+
recordSchema2 = z18.object({
|
|
4878
|
+
format: z18.literal(1),
|
|
4879
|
+
home: z18.string(),
|
|
4880
|
+
bin: z18.string(),
|
|
4881
|
+
current: z18.string().regex(/^[a-f0-9]{24}$/),
|
|
4882
|
+
version: z18.string(),
|
|
4883
|
+
launchers: z18.record(z18.string()),
|
|
4884
|
+
previousLaunchers: z18.record(z18.string()).optional(),
|
|
4885
|
+
pathChanges: z18.array(pathChangeSchema).optional()
|
|
4686
4886
|
});
|
|
4687
4887
|
shQuote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
4688
4888
|
psQuote = (s) => "'" + s.replace(/'/g, "''") + "'";
|
|
4689
|
-
WINDOWS_BATCH_LAUNCHER = '@echo off\r\ngoto #_mason_handoff_# 2>NUL || title %COMSPEC% & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mason.ps1" %*\r\n';
|
|
4690
|
-
|
|
4889
|
+
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';
|
|
4890
|
+
launcherNames = ["mason", "mason.cmd", "mason.ps1", "mason-launcher.ps1"];
|
|
4891
|
+
defaultHome = () => process.env.MASON_HOME ?? (process.platform === "win32" ? path24.join(process.env.LOCALAPPDATA ?? os4.homedir(), "Mason") : path24.join(os4.homedir(), ".local/share/mason"));
|
|
4691
4892
|
}
|
|
4692
4893
|
});
|
|
4693
4894
|
|
|
@@ -4776,20 +4977,20 @@ async function runAutomationCli(argv, stdin = "", io = {
|
|
|
4776
4977
|
io.out(values.json ? JSON.stringify(report, null, 2) : summarize(report));
|
|
4777
4978
|
return report.status === "verified" ? 0 : report.status === "issues-remain" ? 1 : 2;
|
|
4778
4979
|
} catch (error) {
|
|
4779
|
-
const
|
|
4980
|
+
const message3 = action === "setup" ? "Mason setup incomplete: " + automationFailure(error).message + ". Rerun the same setup command to resume; retained audit evidence is preserved." : failureMessage(error);
|
|
4780
4981
|
if (action === "hook" || isHookCommand(argv)) {
|
|
4781
|
-
io.out(JSON.stringify({ systemMessage:
|
|
4982
|
+
io.out(JSON.stringify({ systemMessage: message3 }));
|
|
4782
4983
|
return 0;
|
|
4783
4984
|
}
|
|
4784
4985
|
if (argv.includes("--json")) io.out(JSON.stringify({ version: 1, ...action === "setup" ? { action, status: "incomplete", next: "Rerun the same setup command to resume." } : { status: "unavailable" }, failure: automationFailure(error) }));
|
|
4785
|
-
else io.err(
|
|
4986
|
+
else io.err(message3);
|
|
4786
4987
|
return 2;
|
|
4787
4988
|
}
|
|
4788
4989
|
}
|
|
4789
4990
|
|
|
4790
4991
|
// bin/mason.ts
|
|
4791
4992
|
var [command, ...args] = process.argv.slice(2);
|
|
4792
|
-
var usage = `Mason ${"0.14.
|
|
4993
|
+
var usage = `Mason ${"0.14.1"}
|
|
4793
4994
|
|
|
4794
4995
|
Usage: mason <command> [options]
|
|
4795
4996
|
|
|
@@ -4807,7 +5008,7 @@ Use mason <command> --help for options. npm users can keep using mason-auto,
|
|
|
4807
5008
|
mason-audit, mason-review, mason-drift, mason-hook and mason-mcp.`;
|
|
4808
5009
|
try {
|
|
4809
5010
|
if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
|
|
4810
|
-
else if (["--version", "-v"].includes(command)) console.log("0.14.
|
|
5011
|
+
else if (["--version", "-v"].includes(command)) console.log("0.14.1");
|
|
4811
5012
|
else if (command === "internal-install") {
|
|
4812
5013
|
if (args.length) throw new Error("internal-install takes no arguments.");
|
|
4813
5014
|
const { sourceRuntime: sourceRuntime2 } = await Promise.resolve().then(() => (init_runtime2(), runtime_exports));
|
|
@@ -4816,8 +5017,7 @@ try {
|
|
|
4816
5017
|
const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));
|
|
4817
5018
|
const installed = await installStandalone2(selected.bundleRoot);
|
|
4818
5019
|
console.log(`Installed Mason ${installed.version} in ${installed.home}.
|
|
4819
|
-
|
|
4820
|
-
Add that directory to PATH if needed, then run: mason setup --host codex`);
|
|
5020
|
+
${installed.path.message}`);
|
|
4821
5021
|
} else if (command === "upgrade" || command === "uninstall") {
|
|
4822
5022
|
if (args.includes("--help")) console.log(command === "upgrade" ? "Usage: mason upgrade [version]. Project versions change only when you rerun setup." : "Usage: mason uninstall. Removes the standalone user installation; project runtimes, configuration and knowledge are retained.");
|
|
4823
5023
|
else {
|