eminiar 0.7.0 → 0.8.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/dist/hook.js CHANGED
@@ -3941,11 +3941,11 @@ var require_out = __commonJS({
3941
3941
  async.read(path2, getSettings(optionsOrSettingsOrCallback), callback);
3942
3942
  }
3943
3943
  exports.stat = stat;
3944
- function statSync5(path2, optionsOrSettings) {
3944
+ function statSync6(path2, optionsOrSettings) {
3945
3945
  const settings = getSettings(optionsOrSettings);
3946
3946
  return sync.read(path2, settings);
3947
3947
  }
3948
- exports.statSync = statSync5;
3948
+ exports.statSync = statSync6;
3949
3949
  function getSettings(settingsOrOptions = {}) {
3950
3950
  if (settingsOrOptions instanceof settings_1.default) {
3951
3951
  return settingsOrOptions;
@@ -4403,7 +4403,7 @@ var require_queue = __commonJS({
4403
4403
  if (!(_concurrency >= 1)) {
4404
4404
  throw new Error("fastqueue concurrency must be equal to or greater than 1");
4405
4405
  }
4406
- var cache2 = reusify(Task);
4406
+ var cache3 = reusify(Task);
4407
4407
  var queueHead = null;
4408
4408
  var queueTail = null;
4409
4409
  var _running = 0;
@@ -4482,7 +4482,7 @@ var require_queue = __commonJS({
4482
4482
  return _running === 0 && self.length() === 0;
4483
4483
  }
4484
4484
  function push(value, done) {
4485
- var current = cache2.get();
4485
+ var current = cache3.get();
4486
4486
  current.context = context;
4487
4487
  current.release = release;
4488
4488
  current.value = value;
@@ -4503,7 +4503,7 @@ var require_queue = __commonJS({
4503
4503
  }
4504
4504
  }
4505
4505
  function unshift(value, done) {
4506
- var current = cache2.get();
4506
+ var current = cache3.get();
4507
4507
  current.context = context;
4508
4508
  current.release = release;
4509
4509
  current.value = value;
@@ -4525,7 +4525,7 @@ var require_queue = __commonJS({
4525
4525
  }
4526
4526
  function release(holder) {
4527
4527
  if (holder) {
4528
- cache2.release(holder);
4528
+ cache3.release(holder);
4529
4529
  }
4530
4530
  var next = queueHead;
4531
4531
  if (next && _running <= _concurrency) {
@@ -4575,7 +4575,7 @@ var require_queue = __commonJS({
4575
4575
  errorHandler2(new Error("abort"), val);
4576
4576
  }
4577
4577
  callback.call(context2, new Error("abort"));
4578
- cache2.release(current);
4578
+ cache3.release(current);
4579
4579
  current = next;
4580
4580
  }
4581
4581
  self.drain = noop;
@@ -25733,21 +25733,21 @@ function visit(schema, fnOrHandlers) {
25733
25733
  const h = fnOrHandlers[node2._zod.def.type];
25734
25734
  return h ? h(node2, rewritten) : node2;
25735
25735
  };
25736
- const cache2 = /* @__PURE__ */ new Map();
25736
+ const cache3 = /* @__PURE__ */ new Map();
25737
25737
  function run(s) {
25738
- const cached3 = cache2.get(s);
25738
+ const cached3 = cache3.get(s);
25739
25739
  if (cached3 === RESOLVING) {
25740
25740
  return new $ZodLazy({
25741
25741
  type: "lazy",
25742
- getter: () => cache2.get(s)
25742
+ getter: () => cache3.get(s)
25743
25743
  });
25744
25744
  }
25745
25745
  if (cached3 !== void 0)
25746
25746
  return cached3;
25747
- cache2.set(s, RESOLVING);
25747
+ cache3.set(s, RESOLVING);
25748
25748
  const inner = mapInner(s);
25749
25749
  const mapped = fn(inner, inner !== s);
25750
- cache2.set(s, mapped);
25750
+ cache3.set(s, mapped);
25751
25751
  return mapped;
25752
25752
  }
25753
25753
  function mapInner(s) {
@@ -28947,8 +28947,8 @@ var preCompact = (input2) => {
28947
28947
 
28948
28948
  // src/hooks/routing.ts
28949
28949
  init_esm_shims();
28950
- import { realpathSync as realpathSync3, statSync as statSync3 } from "fs";
28951
- import { extname as extname2, join as join10, relative as relative4, resolve as resolve3 } from "path";
28950
+ import { realpathSync as realpathSync4, statSync as statSync4 } from "fs";
28951
+ import { extname as extname2, join as join11, relative as relative4, resolve as resolve3 } from "path";
28952
28952
 
28953
28953
  // src/docs/manifest.ts
28954
28954
  init_esm_shims();
@@ -28998,10 +28998,107 @@ function readManifest(ctx) {
28998
28998
  return readManifestAt(manifestPath(ctx));
28999
28999
  }
29000
29000
 
29001
+ // src/permissions/settings.ts
29002
+ init_esm_shims();
29003
+ import { existsSync as existsSync4, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
29004
+ import { homedir as homedir2 } from "os";
29005
+ import { join as join5 } from "path";
29006
+
29007
+ // src/permissions/rules.ts
29008
+ init_esm_shims();
29009
+ function parseRule(raw) {
29010
+ const m = /^([A-Za-z][\w-]*)(?:\((.*)\))?$/s.exec(raw.trim());
29011
+ if (!m) return void 0;
29012
+ return { raw, tool: m[1], pattern: m[2] };
29013
+ }
29014
+ function escapeRe(s) {
29015
+ return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
29016
+ }
29017
+ var readRegexCache = /* @__PURE__ */ new Map();
29018
+ function readRegex(pattern) {
29019
+ let re = readRegexCache.get(pattern);
29020
+ if (!re) {
29021
+ if (readRegexCache.size >= 1024) readRegexCache.clear();
29022
+ re = new RegExp(
29023
+ `^${pattern.split("**").map((seg) => seg.split("*").map(escapeRe).join("[^/]*")).join(".*")}$`
29024
+ );
29025
+ readRegexCache.set(pattern, re);
29026
+ }
29027
+ return re;
29028
+ }
29029
+ function matchRead(rule, absPath) {
29030
+ if (rule.tool !== "Read") return false;
29031
+ if (rule.pattern === void 0) return true;
29032
+ let p = rule.pattern;
29033
+ if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
29034
+ return readRegex(p).test(absPath);
29035
+ }
29036
+
29037
+ // src/permissions/split.ts
29038
+ init_esm_shims();
29039
+
29040
+ // src/permissions/settings.ts
29041
+ function readRules(file3) {
29042
+ if (!existsSync4(file3)) return {};
29043
+ try {
29044
+ const s = JSON.parse(readFileSync4(file3, "utf8"));
29045
+ return s.permissions ?? {};
29046
+ } catch {
29047
+ return {};
29048
+ }
29049
+ }
29050
+ function stamp(file3) {
29051
+ try {
29052
+ return statSync2(file3).mtimeMs;
29053
+ } catch {
29054
+ return -1;
29055
+ }
29056
+ }
29057
+ var cache2 = /* @__PURE__ */ new Map();
29058
+ function loadPermissionRules(root, extraDeny = []) {
29059
+ const files = [
29060
+ join5(homedir2(), ".claude", "settings.json"),
29061
+ join5(root, ".claude", "settings.json"),
29062
+ join5(root, ".claude", "settings.local.json")
29063
+ ];
29064
+ const key = `${root}\0${extraDeny.join("\0")}`;
29065
+ const stamps = files.map(stamp);
29066
+ const hit = cache2.get(key);
29067
+ if (hit?.stamps.every((s, i) => s === stamps[i])) return hit.rules;
29068
+ const out = { allow: [], deny: [], ask: [] };
29069
+ const push = (list, raws) => {
29070
+ for (const raw of raws ?? []) {
29071
+ const r = parseRule(raw);
29072
+ if (r) list.push(r);
29073
+ }
29074
+ };
29075
+ for (const f of files) {
29076
+ const p = readRules(f);
29077
+ push(out.allow, p.allow);
29078
+ push(out.deny, p.deny);
29079
+ push(out.ask, p.ask);
29080
+ }
29081
+ push(out.deny, extraDeny);
29082
+ if (cache2.size >= 16) cache2.clear();
29083
+ cache2.set(key, { stamps, rules: out });
29084
+ return out;
29085
+ }
29086
+ function checkRead(rules, absPaths) {
29087
+ for (const kind of ["deny", "ask"]) {
29088
+ for (const r of rules[kind]) {
29089
+ if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
29090
+ }
29091
+ }
29092
+ return { allowed: true };
29093
+ }
29094
+ function checkReadPath(rules, abs) {
29095
+ return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync3(abs), realpathSync3.native(abs)])]);
29096
+ }
29097
+
29001
29098
  // src/session/brief.ts
29002
29099
  init_esm_shims();
29003
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
29004
- import { join as join6 } from "path";
29100
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
29101
+ import { join as join7 } from "path";
29005
29102
 
29006
29103
  // src/memory/frontmatter.ts
29007
29104
  init_esm_shims();
@@ -29035,15 +29132,15 @@ function parseMemoryFile(text) {
29035
29132
  init_esm_shims();
29036
29133
  var import_fast_glob2 = __toESM(require_out4(), 1);
29037
29134
  import {
29038
- existsSync as existsSync4,
29135
+ existsSync as existsSync5,
29039
29136
  mkdirSync as mkdirSync3,
29040
- readFileSync as readFileSync4,
29137
+ readFileSync as readFileSync5,
29041
29138
  renameSync,
29042
- statSync as statSync2,
29139
+ statSync as statSync3,
29043
29140
  unlinkSync,
29044
29141
  writeFileSync as writeFileSync2
29045
29142
  } from "fs";
29046
- import { dirname as dirname3, join as join5, relative as relative3, sep } from "path";
29143
+ import { dirname as dirname3, join as join6, relative as relative3, sep } from "path";
29047
29144
 
29048
29145
  // src/storage/types.ts
29049
29146
  init_esm_shims();
@@ -29055,7 +29152,7 @@ function memoriesRel(ctx) {
29055
29152
  return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
29056
29153
  }
29057
29154
  function memoriesDir(ctx) {
29058
- return join5(ctx.root, memoriesRel(ctx));
29155
+ return join6(ctx.root, memoriesRel(ctx));
29059
29156
  }
29060
29157
 
29061
29158
  // src/session/brief.ts
@@ -29098,10 +29195,10 @@ function historyLine(db) {
29098
29195
  return `${parts.join("; ")}. em_history {action:"file", path} before editing, {action:"risk"} before committing.`;
29099
29196
  }
29100
29197
  function overviewParagraph(ctx) {
29101
- const file3 = join6(memoriesDir(ctx), "onboarding", "overview.md");
29102
- if (!existsSync5(file3)) return void 0;
29198
+ const file3 = join7(memoriesDir(ctx), "onboarding", "overview.md");
29199
+ if (!existsSync6(file3)) return void 0;
29103
29200
  try {
29104
- const body = parseMemoryFile(readFileSync5(file3, "utf8")).body.trim();
29201
+ const body = parseMemoryFile(readFileSync6(file3, "utf8")).body.trim();
29105
29202
  const para = body.split(/\n\s*\n/).map(
29106
29203
  (p) => p.replace(/^#+\s.*$/m, "").replace(/\s+/g, " ").trim()
29107
29204
  ).find((p) => p.length > 20);
@@ -29150,10 +29247,10 @@ function fitBudget(lines, budget) {
29150
29247
 
29151
29248
  // src/session/server-alive.ts
29152
29249
  init_esm_shims();
29153
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
29154
- import { join as join7 } from "path";
29250
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
29251
+ import { join as join8 } from "path";
29155
29252
  function markersDir(ctx) {
29156
- return join7(projectDir(ctx.id), "servers");
29253
+ return join8(projectDir(ctx.id), "servers");
29157
29254
  }
29158
29255
  function processAlive(pid) {
29159
29256
  try {
@@ -29165,13 +29262,13 @@ function processAlive(pid) {
29165
29262
  }
29166
29263
  function liveServers(ctx) {
29167
29264
  const dir = markersDir(ctx);
29168
- if (!existsSync6(dir)) return [];
29265
+ if (!existsSync7(dir)) return [];
29169
29266
  const live2 = [];
29170
29267
  for (const name of readdirSync(dir)) {
29171
29268
  const pid = Number(name);
29172
29269
  if (!Number.isInteger(pid) || pid <= 0) continue;
29173
29270
  if (processAlive(pid)) live2.push(pid);
29174
- else rmSync(join7(dir, name), { force: true });
29271
+ else rmSync(join8(dir, name), { force: true });
29175
29272
  }
29176
29273
  return live2;
29177
29274
  }
@@ -29181,30 +29278,30 @@ function serverAlive(ctx) {
29181
29278
 
29182
29279
  // src/hooks/coexist.ts
29183
29280
  init_esm_shims();
29184
- import { readFileSync as readFileSync7 } from "fs";
29185
- import { join as join9 } from "path";
29281
+ import { readFileSync as readFileSync8 } from "fs";
29282
+ import { join as join10 } from "path";
29186
29283
 
29187
29284
  // src/install/claude-config.ts
29188
29285
  init_esm_shims();
29189
29286
  import {
29190
29287
  copyFileSync,
29191
- existsSync as existsSync7,
29288
+ existsSync as existsSync8,
29192
29289
  mkdirSync as mkdirSync5,
29193
29290
  readdirSync as readdirSync2,
29194
- readFileSync as readFileSync6,
29291
+ readFileSync as readFileSync7,
29195
29292
  writeFileSync as writeFileSync4
29196
29293
  } from "fs";
29197
- import { homedir as homedir2 } from "os";
29198
- import { dirname as dirname4, join as join8 } from "path";
29199
- var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join8(homedir2(), ".claude");
29200
- var pluginsDir = () => join8(claudeDir(), "plugins");
29294
+ import { homedir as homedir3 } from "os";
29295
+ import { dirname as dirname4, join as join9 } from "path";
29296
+ var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join9(homedir3(), ".claude");
29297
+ var pluginsDir = () => join9(claudeDir(), "plugins");
29201
29298
 
29202
29299
  // src/hooks/coexist.ts
29203
29300
  var cached2;
29204
29301
  function contextModeInstalled() {
29205
29302
  if (cached2 !== void 0) return cached2;
29206
29303
  try {
29207
- const reg = JSON.parse(readFileSync7(join9(pluginsDir(), "installed_plugins.json"), "utf8"));
29304
+ const reg = JSON.parse(readFileSync8(join10(pluginsDir(), "installed_plugins.json"), "utf8"));
29208
29305
  cached2 = Object.keys(reg.plugins ?? {}).some((id) => id.startsWith("context-mode@"));
29209
29306
  } catch {
29210
29307
  cached2 = false;
@@ -29259,7 +29356,7 @@ function coverageHint(db, rel) {
29259
29356
  }
29260
29357
  function docsSources(root) {
29261
29358
  try {
29262
- return readManifestAt(join10(root, ".eminiar", "docs.json")).sources;
29359
+ return readManifestAt(join11(root, ".eminiar", "docs.json")).sources;
29263
29360
  } catch {
29264
29361
  return [];
29265
29362
  }
@@ -29318,9 +29415,11 @@ function suggest(toolName, input2, root) {
29318
29415
  const fp = String(input2.file_path ?? "");
29319
29416
  if (!fp) return void 0;
29320
29417
  try {
29321
- const st = statSync3(resolve3(root, fp));
29418
+ const abs = resolve3(root, fp);
29419
+ const st = statSync4(abs);
29322
29420
  const ext = extname2(fp).toLowerCase();
29323
29421
  if (st.size > 200 * 1024 || BIG_TEXT_EXT.has(ext) && st.size > 20 * 1024) {
29422
+ if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
29324
29423
  return {
29325
29424
  tool: "em_execute_file",
29326
29425
  reason: `${fp} is ${Math.round(st.size / 1024)} KB; em_execute_file {path, code} processes it in the sandbox so only what you print enters context`
@@ -29372,7 +29471,7 @@ function projectRel(ctx, fp) {
29372
29471
  if (!fp) return void 0;
29373
29472
  let abs = resolve3(ctx.root, fp);
29374
29473
  try {
29375
- abs = realpathSync3(abs);
29474
+ abs = realpathSync4(abs);
29376
29475
  } catch {
29377
29476
  }
29378
29477
  const rel = relative4(ctx.root, abs).split("\\").join("/");
@@ -29573,15 +29672,15 @@ function annotate(s, note = "") {
29573
29672
 
29574
29673
  // src/hooks/session-start.ts
29575
29674
  init_esm_shims();
29576
- import { appendFileSync as appendFileSync2, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
29577
- import { join as join12 } from "path";
29675
+ import { appendFileSync as appendFileSync2, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
29676
+ import { join as join13 } from "path";
29578
29677
 
29579
29678
  // src/session/current.ts
29580
29679
  init_esm_shims();
29581
- import { readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
29582
- import { join as join11 } from "path";
29680
+ import { readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
29681
+ import { join as join12 } from "path";
29583
29682
  function file2(ctx) {
29584
- return join11(projectDir(ctx.id), "current-session");
29683
+ return join12(projectDir(ctx.id), "current-session");
29585
29684
  }
29586
29685
  function setCurrentSession(ctx, sessionId) {
29587
29686
  writeFileSync5(file2(ctx), sessionId);
@@ -29664,8 +29763,8 @@ ${guide}`);
29664
29763
  } catch {
29665
29764
  }
29666
29765
  try {
29667
- const onboarding = join12(memoriesDir(ctx), "onboarding");
29668
- const onboarded = existsSync8(onboarding) && readdirSync3(onboarding).some((f) => f.endsWith(".md"));
29766
+ const onboarding = join13(memoriesDir(ctx), "onboarding");
29767
+ const onboarded = existsSync9(onboarding) && readdirSync3(onboarding).some((f) => f.endsWith(".md"));
29669
29768
  if (!onboarded)
29670
29769
  parts.push(
29671
29770
  "No onboarding memories yet: run /eminiar:em-onboard once so future sessions start with the project's structure, build and test commands."
package/dist/server.js CHANGED
@@ -18023,6 +18023,7 @@ __export(store_exports, {
18023
18023
  isValidName: () => isValidName,
18024
18024
  list: () => list,
18025
18025
  memoriesDir: () => memoriesDir,
18026
+ memoriesRel: () => memoriesRel,
18026
18027
  memoryLinks: () => memoryLinks,
18027
18028
  normalizeRelated: () => normalizeRelated,
18028
18029
  read: () => read,
@@ -51936,7 +51937,7 @@ import { join as join17 } from "path";
51936
51937
 
51937
51938
  // src/permissions/settings.ts
51938
51939
  init_esm_shims();
51939
- import { existsSync as existsSync10, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
51940
+ import { existsSync as existsSync10, readFileSync as readFileSync11, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
51940
51941
  import { homedir as homedir2 } from "os";
51941
51942
  import { join as join15 } from "path";
51942
51943
 
@@ -51977,15 +51978,24 @@ function matchBash(rule, command) {
51977
51978
  if (rule.pattern === void 0) return true;
51978
51979
  return bashRegex(rule.pattern).test(command);
51979
51980
  }
51981
+ var readRegexCache = /* @__PURE__ */ new Map();
51982
+ function readRegex(pattern2) {
51983
+ let re = readRegexCache.get(pattern2);
51984
+ if (!re) {
51985
+ if (readRegexCache.size >= 1024) readRegexCache.clear();
51986
+ re = new RegExp(
51987
+ `^${pattern2.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
51988
+ );
51989
+ readRegexCache.set(pattern2, re);
51990
+ }
51991
+ return re;
51992
+ }
51980
51993
  function matchRead(rule, absPath) {
51981
51994
  if (rule.tool !== "Read") return false;
51982
51995
  if (rule.pattern === void 0) return true;
51983
51996
  let p = rule.pattern;
51984
51997
  if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
51985
- const re = new RegExp(
51986
- `^${p.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
51987
- );
51988
- return re.test(absPath);
51998
+ return readRegex(p).test(absPath);
51989
51999
  }
51990
52000
 
51991
52001
  // src/permissions/split.ts
@@ -52147,6 +52157,17 @@ function checkCommand(rules, command) {
52147
52157
  }
52148
52158
  return { allowed: true };
52149
52159
  }
52160
+ function checkRead(rules, absPaths) {
52161
+ for (const kind of ["deny", "ask"]) {
52162
+ for (const r of rules[kind]) {
52163
+ if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
52164
+ }
52165
+ }
52166
+ return { allowed: true };
52167
+ }
52168
+ function checkReadPath(rules, abs) {
52169
+ return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync2(abs), realpathSync2.native(abs)])]);
52170
+ }
52150
52171
 
52151
52172
  // src/sandbox/env.ts
52152
52173
  init_esm_shims();
@@ -57096,16 +57117,18 @@ init_esm_shims();
57096
57117
 
57097
57118
  // src/sandbox/file.ts
57098
57119
  init_esm_shims();
57099
- import { realpathSync as realpathSync2 } from "fs";
57120
+ import { realpathSync as realpathSync3 } from "fs";
57100
57121
  import { isAbsolute, relative as relative9, resolve as resolve7 } from "path";
57101
57122
  function confinePath(root, target, rules) {
57102
57123
  const abs = resolve7(root, target);
57124
+ refuse(checkRead(rules, [abs]), target);
57103
57125
  let real;
57104
57126
  try {
57105
- real = realpathSync2(abs);
57127
+ real = realpathSync3(abs);
57106
57128
  } catch {
57107
57129
  throw new Error(`file not found: ${target}`);
57108
57130
  }
57131
+ refuse(checkReadPath(rules, abs), target);
57109
57132
  const rel = relative9(root, real);
57110
57133
  const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
57111
57134
  if (inside) return real;
@@ -57114,11 +57137,15 @@ function confinePath(root, target, rules) {
57114
57137
  `${target} is outside the project root; add a Read(${real}) rule to permissions.allow to opt it in`
57115
57138
  );
57116
57139
  }
57140
+ function refuse(check3, target) {
57141
+ if (!check3.allowed)
57142
+ throw new Error(`refused by permission rule ${check3.rule} (${check3.kind}) on path: ${target}`);
57143
+ }
57117
57144
 
57118
57145
  // src/tools/em_execute_file.ts
57119
57146
  var emExecuteFile = defineTool({
57120
57147
  name: "em_execute_file",
57121
- description: "Process a file in the sandbox without reading it into context. Your code receives the absolute path as $EM_FILE (shell) / process.env.EM_FILE (node) / os.environ['EM_FILE'] (python); print only what you need. The file must be inside the project root unless a Read(<path>) allow rule opts it in. Omit `code` to get a quick summary (size, lines, head).",
57148
+ description: "Process a file in the sandbox without reading it into context. Your code receives the absolute path as $EM_FILE (shell) / process.env.EM_FILE (node) / os.environ['EM_FILE'] (python); print only what you need. The file must be inside the project root unless a Read(<path>) allow rule opts it in; a matching Read deny or ask rule refuses the path, inside the root or out. Omit `code` to get a quick summary (size, lines, head).",
57122
57149
  schema: {
57123
57150
  path: external_exports.string().describe("File to process, relative to the project root or absolute"),
57124
57151
  language: LANG_ENUM.optional(),