mason-context 0.16.1 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mason.js CHANGED
@@ -2521,7 +2521,7 @@ var init_evidence = __esm({
2521
2521
  init_dead_command();
2522
2522
  init_storage();
2523
2523
  exec7 = promisify8(execFile8);
2524
- engineVersion = true ? "0.16.1" : "development";
2524
+ engineVersion = true ? "0.16.2" : "development";
2525
2525
  hash = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
2526
2526
  content = readAuditInput;
2527
2527
  internal = (file) => file === ".mason" || file === ".mason/reports" || file.startsWith(".mason/reports/");
@@ -2628,6 +2628,25 @@ var init_store = __esm({
2628
2628
 
2629
2629
  // src/automation/runtime.ts
2630
2630
  import { randomUUID as randomUUID4 } from "crypto";
2631
+ async function observeReadOnlyTool(dir, event) {
2632
+ const ws = await workspace(dir);
2633
+ await withLock(ws.root, ws.directory, async () => {
2634
+ const statePath = ws.directory + "/state.json";
2635
+ const raw = await readStoreJson(ws.root, statePath);
2636
+ if (raw === null) return;
2637
+ const state = parseState(raw);
2638
+ if (state.root !== ws.root || state.gitDir !== ws.gitDir || state.branch !== ws.branch) {
2639
+ throw new Error("Automation state belongs to another branch or worktree; original evidence was retained.");
2640
+ }
2641
+ const session = event.host && event.sessionId ? state.sessions[hash([event.host, event.sessionId])] : null;
2642
+ if (!session) return;
2643
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2644
+ session.lastUsed = now;
2645
+ session.events[event.event] = { at: now, count: (session.events[event.event]?.count ?? 0) + 1 };
2646
+ await writeStoreJson(ws.root, statePath, state);
2647
+ });
2648
+ return { root: ws.root, directory: ws.directory };
2649
+ }
2631
2650
  function summarize(report) {
2632
2651
  const open = report.findings.filter((f) => f.status !== "resolved");
2633
2652
  return [
@@ -2905,6 +2924,7 @@ __export(launcher_exports, {
2905
2924
  });
2906
2925
  import { execFile as execFile9 } from "child_process";
2907
2926
  import { promisify as promisify9 } from "util";
2927
+ import fs13 from "fs/promises";
2908
2928
  function globalCommand(args2, platform = process.platform) {
2909
2929
  return platform === "win32" ? { command: "cmd.exe", args: ["/d", "/s", "/c", "mason", ...args2] } : { command: "mason", args: args2 };
2910
2930
  }
@@ -2923,13 +2943,17 @@ async function installedCommand() {
2923
2943
  return { available: false, version: null, message: "An up-to-date mason command is not available on this process's PATH. Install or upgrade Mason, then reopen your terminal or desktop assistant and rerun mason setup." };
2924
2944
  }
2925
2945
  }
2926
- async function prepareLaunch(host) {
2927
- const ws = await workspace(process.cwd());
2928
- const entry = (await loadSetup(ws.root))?.hosts[host];
2929
- if (!entry) throw new Error(`Mason is not configured locally for ${host}. Run mason setup --host ${host} in this checkout.`);
2930
- process.env.MASON_SETUP_ROOT = ws.root;
2946
+ async function prepareLaunch(host, options = {}) {
2947
+ const root = await fs13.realpath((await git2(process.cwd(), "rev-parse", "--show-toplevel")).trim());
2948
+ const entry = (await loadSetup(root))?.hosts[host];
2949
+ if (!entry) {
2950
+ if (options.allowInactive) return false;
2951
+ throw new Error(`Mason is not configured locally for ${host}. Run mason setup --host ${host} in this checkout.`);
2952
+ }
2953
+ process.env.MASON_SETUP_ROOT = root;
2931
2954
  process.env.MASON_SETUP_HOST = host;
2932
2955
  process.env.MASON_SETUP_REVISION = entry.revision;
2956
+ return true;
2933
2957
  }
2934
2958
  var executingVersion, mcpCommand, hookCommand;
2935
2959
  var init_launcher = __esm({
@@ -2937,7 +2961,7 @@ var init_launcher = __esm({
2937
2961
  "use strict";
2938
2962
  init_evidence();
2939
2963
  init_model();
2940
- executingVersion = () => true ? "0.16.1" : "development";
2964
+ executingVersion = () => true ? "0.16.2" : "development";
2941
2965
  mcpCommand = (host) => globalCommand(["--setup-host", host, "mcp"]);
2942
2966
  hookCommand = (host) => `mason --setup-host ${host} auto`;
2943
2967
  }
@@ -2950,7 +2974,7 @@ __export(observations_exports, {
2950
2974
  observeActivation: () => observeActivation,
2951
2975
  readObservation: () => readObservation
2952
2976
  });
2953
- import fs13 from "fs/promises";
2977
+ import fs14 from "fs/promises";
2954
2978
  import { z as z8 } from "zod";
2955
2979
  function observationPath(directory, host, revision, version = executingVersion()) {
2956
2980
  return `${directory}/activation/${host}-${revision}-${hash(version).slice(0, 12)}.json`;
@@ -2966,9 +2990,9 @@ async function observeActivation(dir, event, options = {}) {
2966
2990
  const host = process.env.MASON_SETUP_HOST, revision = process.env.MASON_SETUP_REVISION;
2967
2991
  if (host !== "codex" && host !== "claude" || !revision || !process.env.MASON_SETUP_ROOT) return null;
2968
2992
  try {
2969
- const capturedDirectory = options.reportPath?.match(/^(\.mason\/reports\/automation\/[a-f0-9]{24})\/checks\//)?.[1];
2970
- const ws = capturedDirectory ? { root: await fs13.realpath(dir), directory: capturedDirectory } : await workspace(dir);
2971
- if (ws.root !== await fs13.realpath(process.env.MASON_SETUP_ROOT)) return null;
2993
+ 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];
2994
+ const ws = capturedDirectory ? { root: await fs14.realpath(dir), directory: capturedDirectory } : await workspace(dir);
2995
+ if (ws.root !== await fs14.realpath(process.env.MASON_SETUP_ROOT)) return null;
2972
2996
  const setup = await loadSetup(ws.root);
2973
2997
  if (setup?.hosts[host]?.revision !== revision) return "Mason setup changed; restart the assistant to observe the current integration.";
2974
2998
  const directory = ws.directory + "/activation";
@@ -3039,13 +3063,13 @@ import { z as z9 } from "zod";
3039
3063
  function normalizeHook(host, raw) {
3040
3064
  hostSchema.parse(host);
3041
3065
  const input = inputSchema.parse(raw);
3042
- const readOnly = host === "claude" ? /^(Read|Glob|Grep|WebSearch|WebFetch)$/ : /^(read_file|list_dir|grep_files)$/;
3043
- return { cwd: input.cwd, name: input.hook_event_name, event: {
3066
+ const readOnly = (host === "claude" ? /^(Read|Glob|Grep|WebSearch|WebFetch)$/ : /^(read_file|list_dir|grep_files|Read|Glob|Grep)$/).test(input.tool_name ?? "");
3067
+ return { cwd: input.cwd, name: input.hook_event_name, readOnly, event: {
3044
3068
  event: lifecycle[input.hook_event_name],
3045
3069
  host,
3046
3070
  sessionId: input.session_id,
3047
3071
  toolId: input.tool_use_id,
3048
- mutating: !!input.tool_name && !readOnly.test(input.tool_name),
3072
+ mutating: !!input.tool_name && !readOnly,
3049
3073
  stopHookActive: input.stop_hook_active || input.permission_mode === "plan"
3050
3074
  } };
3051
3075
  }
@@ -3053,8 +3077,20 @@ async function runAutomationHook(host, stdin) {
3053
3077
  let name = "";
3054
3078
  try {
3055
3079
  if (Buffer.byteLength(stdin) > 1024 * 1024) throw new Error("Hook input exceeds 1 MiB.");
3056
- const input = normalizeHook(host, JSON.parse(stdin));
3080
+ let raw;
3081
+ try {
3082
+ raw = JSON.parse(stdin);
3083
+ } catch {
3084
+ throw new Error("Hook input is not valid JSON.");
3085
+ }
3086
+ const input = normalizeHook(host, raw);
3057
3087
  name = input.name;
3088
+ if (["before_tool", "after_tool"].includes(input.event.event) && input.readOnly) {
3089
+ const observed = await observeReadOnlyTool(input.cwd, input.event);
3090
+ const { observeActivation: observeActivation3 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
3091
+ const warning2 = await observeActivation3(observed.root, input.event.event, { sessionId: input.event.sessionId, directory: observed.directory });
3092
+ return warning2 ? { systemMessage: warning2 } : null;
3093
+ }
3058
3094
  const result = await automate(input.cwd, input.event);
3059
3095
  const { observeActivation: observeActivation2 } = await Promise.resolve().then(() => (init_observations(), observations_exports));
3060
3096
  const warning = await observeActivation2(result.report.root, input.event.event, {
@@ -3073,8 +3109,15 @@ async function runAutomationHook(host, stdin) {
3073
3109
  return { systemMessage: message2, ...["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].includes(name) ? { hookSpecificOutput: { hookEventName: name, additionalContext: message2 } } : {} };
3074
3110
  }
3075
3111
  }
3112
+ function managedHookCommand(host, platform = process.platform) {
3113
+ const command2 = `mason --setup-host ${host} auto hook --host ${host}`;
3114
+ return platform === "win32" ? `cmd.exe /d /s /c "where mason >nul 2>nul & if errorlevel 1 (exit /b 0) else (${command2})"` : `if command -v mason >/dev/null 2>&1; then ${command2}; fi`;
3115
+ }
3116
+ function knownManagedHookCommands(host) {
3117
+ return [managedHookCommand(host, "linux"), managedHookCommand(host, "win32"), `mason --setup-host ${host} auto hook --host ${host}`];
3118
+ }
3076
3119
  function hookConfig(host, command2 = "npx --no-install --package mason-context mason-auto") {
3077
- const handler = { type: "command", command: command2 + " hook --host " + host, timeout: 30 };
3120
+ const handler = { type: "command", command: command2 === `mason --setup-host ${host} auto` ? managedHookCommand(host) : command2 + " hook --host " + host, timeout: 30 };
3078
3121
  return { hooks: Object.fromEntries(HOOK_EVENTS.map((name) => [
3079
3122
  name,
3080
3123
  [{ ...["PreToolUse", "PostToolUse"].includes(name) ? { matcher: ".*" } : {}, hooks: [{ ...handler }] }]
@@ -3115,7 +3158,7 @@ __export(files_exports, {
3115
3158
  managedBlock: () => managedBlock,
3116
3159
  readText: () => readText
3117
3160
  });
3118
- import fs14 from "fs/promises";
3161
+ import fs15 from "fs/promises";
3119
3162
  import path18 from "path";
3120
3163
  import { randomUUID as randomUUID5 } from "crypto";
3121
3164
  async function readText(root, file) {
@@ -3145,14 +3188,14 @@ async function applyEdit(root, edit) {
3145
3188
  if (edit.after === null) {
3146
3189
  const file2 = await storePath(root, edit.path);
3147
3190
  if (await readText(root, edit.path) !== edit.before) throw new Error("Input changed during removal: " + edit.path);
3148
- await fs14.unlink(file2);
3191
+ await fs15.unlink(file2);
3149
3192
  return true;
3150
3193
  }
3151
3194
  const file = await storePath(root, edit.path, true);
3152
3195
  const temporary = path18.join(path18.dirname(file), ".mason-setup-" + randomUUID5() + ".tmp");
3153
3196
  try {
3154
- const mode = await fs14.stat(file).then((s) => s.mode & 511, () => 384);
3155
- const handle = await fs14.open(temporary, "wx", mode);
3197
+ const mode = await fs15.stat(file).then((s) => s.mode & 511, () => 384);
3198
+ const handle = await fs15.open(temporary, "wx", mode);
3156
3199
  try {
3157
3200
  await handle.writeFile(edit.after, "utf8");
3158
3201
  await handle.sync();
@@ -3160,10 +3203,10 @@ async function applyEdit(root, edit) {
3160
3203
  await handle.close();
3161
3204
  }
3162
3205
  if (await readText(root, edit.path) !== edit.before) throw new Error("Integration input changed during update: " + edit.path);
3163
- await fs14.rename(temporary, file);
3206
+ await fs15.rename(temporary, file);
3164
3207
  return true;
3165
3208
  } finally {
3166
- await fs14.rm(temporary, { force: true });
3209
+ await fs15.rm(temporary, { force: true });
3167
3210
  }
3168
3211
  }
3169
3212
  var init_files2 = __esm({
@@ -3519,7 +3562,7 @@ async function planAutomationInstall(root, host, command2) {
3519
3562
  const previous = record.hosts[host]?.command;
3520
3563
  const ownershipText = await readText(root, OWNERSHIP_PATH);
3521
3564
  const pending = ownershipText === null ? [] : ownershipSchema.parse(JSON.parse(ownershipText)).files[file]?.hookCommands ?? [];
3522
- const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand]);
3565
+ const owned = /* @__PURE__ */ new Set([previous, ...pending, newCommand, ...knownManagedHookCommands(host)]);
3523
3566
  const hooks = existing.hooks ?? {};
3524
3567
  for (const event of HOOK_EVENTS) {
3525
3568
  hooks[event] = (hooks[event] ?? []).map((group) => ({
@@ -3623,6 +3666,7 @@ async function planTeardown(root, selected) {
3623
3666
  const commands = (host) => new Set([
3624
3667
  automation?.hosts[host]?.command,
3625
3668
  ...ownership.files[configPath(host)]?.hookCommands ?? [],
3669
+ ...knownManagedHookCommands(host),
3626
3670
  hookConfig(host, hookCommand(host)).hooks.SessionStart[0].hooks[0].command,
3627
3671
  hookConfig(host).hooks.SessionStart[0].hooks[0].command
3628
3672
  ].filter((command2) => !!command2));
@@ -4352,7 +4396,7 @@ var init_evidence2 = __esm({
4352
4396
  });
4353
4397
 
4354
4398
  // src/review/review.ts
4355
- import fs15 from "fs/promises";
4399
+ import fs16 from "fs/promises";
4356
4400
  import path20 from "path";
4357
4401
  import { execFile as execFile12 } from "child_process";
4358
4402
  import { promisify as promisify12 } from "util";
@@ -4432,7 +4476,7 @@ async function computeReview(rootDir, base, options = {}) {
4432
4476
  analyzed,
4433
4477
  async (relPath) => {
4434
4478
  try {
4435
- await fs15.access(path20.join(resolvedRoot, relPath));
4479
+ await fs16.access(path20.join(resolvedRoot, relPath));
4436
4480
  return true;
4437
4481
  } catch {
4438
4482
  return false;
@@ -4586,7 +4630,7 @@ async function setupStatus(dir) {
4586
4630
  version: 1,
4587
4631
  status: pending.length ? "incomplete" : "not-configured",
4588
4632
  hosts: {},
4589
- next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Run mason-auto setup --host codex or --host claude."
4633
+ next: pending.length ? "Setup did not finish. Rerun mason-auto setup --host " + pending[0] + " to resume using the retained original evidence." : "Project hooks are inactive until local setup. Run mason setup --host codex or --host claude."
4590
4634
  };
4591
4635
  }
4592
4636
  const hosts2 = {};
@@ -4795,12 +4839,12 @@ var init_setup = __esm({
4795
4839
  });
4796
4840
 
4797
4841
  // src/distribution/bundle.ts
4798
- import fs16 from "fs/promises";
4842
+ import fs17 from "fs/promises";
4799
4843
  import path21 from "path";
4800
4844
  import { createHash as createHash4 } from "crypto";
4801
4845
  import { z as z18 } from "zod";
4802
4846
  async function readBundle(root) {
4803
- const bytes = await fs16.readFile(await storePath(root, "bundle.json"));
4847
+ const bytes = await fs17.readFile(await storePath(root, "bundle.json"));
4804
4848
  const manifest = bundleSchema.parse(JSON.parse(bytes.toString("utf8")));
4805
4849
  const windows = manifest.target.startsWith("win32-");
4806
4850
  for (const file of Object.keys(manifest.files)) {
@@ -4819,7 +4863,7 @@ async function verifyBundle(root, expectedHash) {
4819
4863
  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.");
4820
4864
  const files = Object.keys(bundle.manifest.files);
4821
4865
  for (let i = 0; i < files.length; i += 8) await Promise.all(files.slice(i, i + 8).map(async (file) => {
4822
- if (sha256(await fs16.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
4866
+ if (sha256(await fs17.readFile(await storePath(root, file))) !== bundle.manifest.files[file]) throw new Error("Mason bundle checksum mismatch: " + file);
4823
4867
  }));
4824
4868
  return bundle;
4825
4869
  }
@@ -4842,7 +4886,7 @@ var init_bundle = __esm({
4842
4886
  });
4843
4887
 
4844
4888
  // src/distribution/path.ts
4845
- import fs17 from "fs/promises";
4889
+ import fs18 from "fs/promises";
4846
4890
  import os3 from "os";
4847
4891
  import path22 from "path";
4848
4892
  import { execFile as execFile13 } from "child_process";
@@ -4881,7 +4925,7 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
4881
4925
  else {
4882
4926
  const candidates = [".bash_profile", ".bash_login", ".profile"].map((name) => path22.join(userHome, name));
4883
4927
  let login = candidates[2];
4884
- for (const file of candidates) if (await fs17.lstat(file).then(() => true, (error) => {
4928
+ for (const file of candidates) if (await fs18.lstat(file).then(() => true, (error) => {
4885
4929
  if (error.code === "ENOENT") return false;
4886
4930
  throw error;
4887
4931
  })) {
@@ -4892,12 +4936,12 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
4892
4936
  }
4893
4937
  const marker = `# >>> mason PATH ${createHash5("sha256").update(bin).digest("hex").slice(0, 12)} >>>`;
4894
4938
  for (const profile of profiles) {
4895
- const stat = await fs17.lstat(profile).catch((error) => {
4939
+ const stat = await fs18.lstat(profile).catch((error) => {
4896
4940
  if (error.code === "ENOENT") return null;
4897
4941
  throw error;
4898
4942
  });
4899
- const file = stat?.isSymbolicLink() ? await fs17.realpath(profile) : path22.resolve(profile);
4900
- const original = await fs17.readFile(file).catch((error) => {
4943
+ const file = stat?.isSymbolicLink() ? await fs18.realpath(profile) : path22.resolve(profile);
4944
+ const original = await fs18.readFile(file).catch((error) => {
4901
4945
  if (error.code === "ENOENT" && !stat) return null;
4902
4946
  throw error;
4903
4947
  });
@@ -4912,8 +4956,8 @@ ${alreadyOnPath ? message2 : `Add ${bin} to your PATH, then run: mason setup --h
4912
4956
  changes.push({ kind: "profile", file, block, created: original === null });
4913
4957
  await save(changes);
4914
4958
  }
4915
- await fs17.mkdir(path22.dirname(file), { recursive: true });
4916
- const handle = await fs17.open(file, original === null ? "ax+" : "a+", 384);
4959
+ await fs18.mkdir(path22.dirname(file), { recursive: true });
4960
+ const handle = await fs18.open(file, original === null ? "ax+" : "a+", 384);
4917
4961
  try {
4918
4962
  if (!(await handle.readFile()).equals(original ?? Buffer.alloc(0))) throw new Error(`Shell profile changed during installation: ${profile}`);
4919
4963
  await handle.writeFile(block);
@@ -4937,13 +4981,13 @@ async function removePath(changes) {
4937
4981
  await windowsPath("remove", change.entry, current, change.created);
4938
4982
  continue;
4939
4983
  }
4940
- const stat = await fs17.lstat(change.file).catch((error) => {
4984
+ const stat = await fs18.lstat(change.file).catch((error) => {
4941
4985
  if (error.code === "ENOENT") return null;
4942
4986
  throw error;
4943
4987
  });
4944
4988
  if (!stat) continue;
4945
4989
  if (!stat.isFile()) throw new Error("profile type changed");
4946
- const bytes = await fs17.readFile(change.file), text2 = bytes.toString("utf8");
4990
+ const bytes = await fs18.readFile(change.file), text2 = bytes.toString("utf8");
4947
4991
  if (!Buffer.from(text2).equals(bytes)) throw new Error("profile encoding changed");
4948
4992
  const index2 = text2.indexOf(change.block);
4949
4993
  if (index2 < 0 || text2.indexOf(change.block, index2 + change.block.length) >= 0) throw new Error("PATH block was edited or removed");
@@ -4953,7 +4997,7 @@ async function removePath(changes) {
4953
4997
  const temporary = change.file + ".mason-" + randomUUID7();
4954
4998
  try {
4955
4999
  if (stat.nlink > 1) throw new Error("profile has multiple hard links");
4956
- const handle = await fs17.open(temporary, "wx", stat.mode & 511);
5000
+ const handle = await fs18.open(temporary, "wx", stat.mode & 511);
4957
5001
  try {
4958
5002
  await handle.writeFile(next);
4959
5003
  await handle.chmod(stat.mode & 511);
@@ -4962,11 +5006,11 @@ async function removePath(changes) {
4962
5006
  } finally {
4963
5007
  await handle.close();
4964
5008
  }
4965
- const current = await fs17.lstat(change.file);
4966
- if (!current.isFile() || current.ino !== stat.ino || !(await fs17.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
4967
- await fs17.rename(temporary, change.file);
5009
+ const current = await fs18.lstat(change.file);
5010
+ if (!current.isFile() || current.ino !== stat.ino || !(await fs18.readFile(change.file)).equals(bytes)) throw new Error("profile changed during uninstall");
5011
+ await fs18.rename(temporary, change.file);
4968
5012
  } finally {
4969
- await fs17.rm(temporary, { force: true });
5013
+ await fs18.rm(temporary, { force: true });
4970
5014
  }
4971
5015
  } catch (error) {
4972
5016
  warnings.push(`Retained PATH setting ${change.kind === "profile" ? change.file : change.entry}: ${error instanceof Error ? error.message : String(error)}`);
@@ -5031,7 +5075,7 @@ __export(install_exports2, {
5031
5075
  uninstallStandalone: () => uninstallStandalone,
5032
5076
  upgradeStandalone: () => upgradeStandalone
5033
5077
  });
5034
- import fs18 from "fs/promises";
5078
+ import fs19 from "fs/promises";
5035
5079
  import os4 from "os";
5036
5080
  import path23 from "path";
5037
5081
  import { fileURLToPath } from "url";
@@ -5041,31 +5085,31 @@ import { z as z20 } from "zod";
5041
5085
  async function replace(file, content2, mode = 384) {
5042
5086
  const temp = file + ".tmp-" + randomUUID8();
5043
5087
  try {
5044
- await fs18.writeFile(temp, content2, { mode, flag: "wx" });
5045
- await fs18.rename(temp, file);
5088
+ await fs19.writeFile(temp, content2, { mode, flag: "wx" });
5089
+ await fs19.rename(temp, file);
5046
5090
  } finally {
5047
- await fs18.rm(temp, { force: true });
5091
+ await fs19.rm(temp, { force: true });
5048
5092
  }
5049
5093
  }
5050
5094
  async function installStandalone(source) {
5051
5095
  const bundle = await verifyBundle(source);
5052
5096
  const selectedHome = path23.resolve(defaultHome());
5053
- await fs18.mkdir(selectedHome, { recursive: true });
5054
- const home = await fs18.realpath(selectedHome);
5097
+ await fs19.mkdir(selectedHome, { recursive: true });
5098
+ const home = await fs19.realpath(selectedHome);
5055
5099
  const selectedBin = path23.resolve(process.env.MASON_BIN_DIR ?? (process.platform === "win32" ? path23.join(home, "bin") : path23.join(os4.homedir(), ".local/bin")));
5056
5100
  return withLock(home, ".install-lock", async () => {
5057
5101
  const raw = await readStoreJson(home, "install.json");
5058
5102
  const previous = raw === null ? null : recordSchema2.parse(raw);
5059
- if (!previous) for (const file of await fs18.readdir(home)) {
5103
+ if (!previous) for (const file of await fs19.readdir(home)) {
5060
5104
  if (file === ".install-lock") continue;
5061
5105
  const directory = path23.join(home, file);
5062
- const stat = await fs18.lstat(directory);
5063
- if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs18.readdir(directory)).length) {
5106
+ const stat = await fs19.lstat(directory);
5107
+ if (!["versions", "bin"].includes(file) || !stat.isDirectory() || (await fs19.readdir(directory)).length) {
5064
5108
  throw new Error("Mason installation directory is not empty and has no ownership receipt.");
5065
5109
  }
5066
5110
  }
5067
- await fs18.mkdir(selectedBin, { recursive: true });
5068
- const bin = await fs18.realpath(selectedBin);
5111
+ await fs19.mkdir(selectedBin, { recursive: true });
5112
+ const bin = await fs19.realpath(selectedBin);
5069
5113
  if (previous && (previous.home !== home || previous.bin !== bin)) throw new Error("Installation paths differ from the existing Mason receipt.");
5070
5114
  const id = bundle.manifestHash.slice(0, 24);
5071
5115
  const destination = await storePath(home, "versions/" + id);
@@ -5092,7 +5136,7 @@ exec ${shQuote(path23.join(destination, "node"))} ${shQuote(path23.join(destinat
5092
5136
  ` };
5093
5137
  for (const [name, content2] of Object.entries(launchers)) {
5094
5138
  const file = await storePath(bin, name);
5095
- const old = await fs18.readFile(file, "utf8").catch((error) => {
5139
+ const old = await fs19.readFile(file, "utf8").catch((error) => {
5096
5140
  if (error.code === "ENOENT") return null;
5097
5141
  throw error;
5098
5142
  });
@@ -5101,7 +5145,7 @@ exec ${shQuote(path23.join(destination, "node"))} ${shQuote(path23.join(destinat
5101
5145
  const retired = Object.entries({ ...previous?.previousLaunchers, ...previous?.launchers }).filter(([name]) => !(name in launchers));
5102
5146
  const checkRetired = async (name, expected) => {
5103
5147
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
5104
- const actual = await fs18.readFile(await storePath(bin, name), "utf8").catch((error) => {
5148
+ const actual = await fs19.readFile(await storePath(bin, name), "utf8").catch((error) => {
5105
5149
  if (error.code === "ENOENT") return null;
5106
5150
  throw error;
5107
5151
  });
@@ -5111,28 +5155,28 @@ exec ${shQuote(path23.join(destination, "node"))} ${shQuote(path23.join(destinat
5111
5155
  const receipt = { format: 1, home, bin, current: id, version: bundle.manifest.version, launchers, pathChanges: previous?.pathChanges ?? [] };
5112
5156
  await writeStoreJson(home, "install.json", { ...receipt, previousLaunchers: previous?.previousLaunchers ?? previous?.launchers });
5113
5157
  const stage = await storePath(home, "versions/.install-" + randomUUID8(), true);
5114
- await fs18.mkdir(stage);
5158
+ await fs19.mkdir(stage);
5115
5159
  try {
5116
5160
  for (const file of ["bundle.json", ...Object.keys(bundle.manifest.files)]) {
5117
5161
  const from = await storePath(source, file), to = await storePath(stage, file, true);
5118
- await fs18.copyFile(from, to);
5119
- await fs18.chmod(to, (await fs18.stat(from)).mode & 511);
5162
+ await fs19.copyFile(from, to);
5163
+ await fs19.chmod(to, (await fs19.stat(from)).mode & 511);
5120
5164
  }
5121
5165
  await verifyBundle(stage, bundle.manifestHash);
5122
- const exists2 = await fs18.lstat(destination).then(() => true, (error) => {
5166
+ const exists2 = await fs19.lstat(destination).then(() => true, (error) => {
5123
5167
  if (error.code === "ENOENT") return false;
5124
5168
  throw error;
5125
5169
  });
5126
5170
  if (exists2) await verifyBundle(destination, bundle.manifestHash);
5127
- else await fs18.rename(stage, destination);
5171
+ else await fs19.rename(stage, destination);
5128
5172
  for (const [name, content2] of Object.entries(launchers)) await replace(await storePath(bin, name, true), content2, 493);
5129
5173
  for (const [name, expected] of retired) {
5130
5174
  await checkRetired(name, expected);
5131
- await fs18.rm(await storePath(bin, name), { force: true });
5175
+ await fs19.rm(await storePath(bin, name), { force: true });
5132
5176
  }
5133
5177
  await writeStoreJson(home, "install.json", receipt);
5134
5178
  } finally {
5135
- await fs18.rm(stage, { recursive: true, force: true });
5179
+ await fs19.rm(stage, { recursive: true, force: true });
5136
5180
  }
5137
5181
  const pathResult = await configurePath(bin, receipt.pathChanges, async (changes) => {
5138
5182
  receipt.pathChanges = changes;
@@ -5147,7 +5191,7 @@ async function currentInstallation() {
5147
5191
  const raw = await readStoreJson(home, "install.json");
5148
5192
  if (!raw) throw new Error("Run this command from a standalone installation. npm users can upgrade with npm.");
5149
5193
  const record = recordSchema2.parse(raw);
5150
- if (record.home !== await fs18.realpath(home) || path23.dirname(bundle) !== path23.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
5194
+ if (record.home !== await fs19.realpath(home) || path23.dirname(bundle) !== path23.join(home, "versions")) throw new Error("Invalid standalone installation receipt.");
5151
5195
  return { home, bundle, record };
5152
5196
  }
5153
5197
  async function upgradeStandalone(version) {
@@ -5176,7 +5220,7 @@ async function uninstallStandalone() {
5176
5220
  for (const [name, expected] of Object.entries(record.launchers)) {
5177
5221
  if (!launcherNames.includes(name)) throw new Error("Invalid owned launcher name.");
5178
5222
  const file = await storePath(record.bin, name);
5179
- const actual = await fs18.readFile(file, "utf8").catch((error) => {
5223
+ const actual = await fs19.readFile(file, "utf8").catch((error) => {
5180
5224
  if (error.code === "ENOENT") return null;
5181
5225
  throw error;
5182
5226
  });
@@ -5184,9 +5228,9 @@ async function uninstallStandalone() {
5184
5228
  }
5185
5229
  for (const warning of await removePath(record.pathChanges ?? [])) console.error(warning);
5186
5230
  if (process.platform === "win32") await replace(await storePath(home, ".uninstall-request.txt", true), token);
5187
- for (const name of Object.keys(record.launchers)) await fs18.rm(await storePath(record.bin, name), { force: true });
5231
+ for (const name of Object.keys(record.launchers)) await fs19.rm(await storePath(record.bin, name), { force: true });
5188
5232
  });
5189
- if (process.platform !== "win32") await fs18.rm(home, { recursive: true, force: true });
5233
+ if (process.platform !== "win32") await fs19.rm(home, { recursive: true, force: true });
5190
5234
  return home;
5191
5235
  }
5192
5236
  var recordSchema2, shQuote, psQuote, WINDOWS_BATCH_LAUNCHER, launcherNames, defaultHome;
@@ -5341,7 +5385,7 @@ var inputArgs = process.argv.slice(2);
5341
5385
  var managedLaunch = inputArgs[0] === "--setup-host";
5342
5386
  var setupHost = managedLaunch ? inputArgs.splice(0, 2)[1] : void 0;
5343
5387
  var [command, ...args] = inputArgs;
5344
- var usage = `Mason ${"0.16.1"}
5388
+ var usage = `Mason ${"0.16.2"}
5345
5389
 
5346
5390
  Usage: mason <command> [options]
5347
5391
 
@@ -5362,11 +5406,12 @@ try {
5362
5406
  if (managedLaunch) {
5363
5407
  if (!setupHost || !["codex", "claude"].includes(setupHost) || !["mcp", "auto"].includes(command)) throw new Error("Invalid setup invocation.");
5364
5408
  const { prepareLaunch: prepareLaunch2 } = await Promise.resolve().then(() => (init_launcher(), launcher_exports));
5365
- await prepareLaunch2(setupHost);
5409
+ const active = await prepareLaunch2(setupHost, { allowInactive: command === "auto" && isHookCommand(args) });
5410
+ if (!active) process.exit(0);
5366
5411
  }
5367
- if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.16.1" }));
5412
+ if (command === "internal-integration-version") console.log(JSON.stringify({ protocol: 1, version: "0.16.2" }));
5368
5413
  else if (!command || ["--help", "-h", "help"].includes(command)) console.log(usage);
5369
- else if (["--version", "-v"].includes(command)) console.log("0.16.1");
5414
+ else if (["--version", "-v"].includes(command)) console.log("0.16.2");
5370
5415
  else if (command === "internal-install") {
5371
5416
  if (args.length) throw new Error("internal-install takes no arguments.");
5372
5417
  const { installStandalone: installStandalone2 } = await Promise.resolve().then(() => (init_install2(), install_exports2));