eminiar 0.8.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eminiar",
3
3
  "displayName": "Eminiar",
4
- "version": "0.8.0",
4
+ "version": "0.8.1",
5
5
  "description": "Local-first context, memory and knowledge graph for Claude Code: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
6
6
  "author": {
7
7
  "name": "Ryan Dick"
package/README.md CHANGED
@@ -58,7 +58,8 @@ Everything runs locally in one SQLite database per project.
58
58
  `em_execute`, `em_batch_execute` and `em_execute_file` run shell, JavaScript and TypeScript, plus
59
59
  Python, Ruby, Go, PHP, Perl, R, Elixir, Rust and C# when they are installed. Output over 5 KB is
60
60
  indexed. Claude gets back only the sections that match its `intent`. Snippets obey your Claude
61
- Code `permissions.deny` rules.
61
+ Code `Bash` deny rules, and `em_execute_file` refuses a `path` that one of your `Read` deny rules
62
+ matches ([which rule forms](docs/how-it-works.md#sandbox)).
62
63
 
63
64
  ### 🔎 Hybrid search — keyword and meaning together
64
65
 
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
@@ -51937,7 +51937,7 @@ import { join as join17 } from "path";
51937
51937
 
51938
51938
  // src/permissions/settings.ts
51939
51939
  init_esm_shims();
51940
- 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";
51941
51941
  import { homedir as homedir2 } from "os";
51942
51942
  import { join as join15 } from "path";
51943
51943
 
@@ -51978,15 +51978,24 @@ function matchBash(rule, command) {
51978
51978
  if (rule.pattern === void 0) return true;
51979
51979
  return bashRegex(rule.pattern).test(command);
51980
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
+ }
51981
51993
  function matchRead(rule, absPath) {
51982
51994
  if (rule.tool !== "Read") return false;
51983
51995
  if (rule.pattern === void 0) return true;
51984
51996
  let p = rule.pattern;
51985
51997
  if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
51986
- const re = new RegExp(
51987
- `^${p.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
51988
- );
51989
- return re.test(absPath);
51998
+ return readRegex(p).test(absPath);
51990
51999
  }
51991
52000
 
51992
52001
  // src/permissions/split.ts
@@ -52148,6 +52157,17 @@ function checkCommand(rules, command) {
52148
52157
  }
52149
52158
  return { allowed: true };
52150
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
+ }
52151
52171
 
52152
52172
  // src/sandbox/env.ts
52153
52173
  init_esm_shims();
@@ -57097,16 +57117,18 @@ init_esm_shims();
57097
57117
 
57098
57118
  // src/sandbox/file.ts
57099
57119
  init_esm_shims();
57100
- import { realpathSync as realpathSync2 } from "fs";
57120
+ import { realpathSync as realpathSync3 } from "fs";
57101
57121
  import { isAbsolute, relative as relative9, resolve as resolve7 } from "path";
57102
57122
  function confinePath(root, target, rules) {
57103
57123
  const abs = resolve7(root, target);
57124
+ refuse(checkRead(rules, [abs]), target);
57104
57125
  let real;
57105
57126
  try {
57106
- real = realpathSync2(abs);
57127
+ real = realpathSync3(abs);
57107
57128
  } catch {
57108
57129
  throw new Error(`file not found: ${target}`);
57109
57130
  }
57131
+ refuse(checkReadPath(rules, abs), target);
57110
57132
  const rel = relative9(root, real);
57111
57133
  const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
57112
57134
  if (inside) return real;
@@ -57115,11 +57137,15 @@ function confinePath(root, target, rules) {
57115
57137
  `${target} is outside the project root; add a Read(${real}) rule to permissions.allow to opt it in`
57116
57138
  );
57117
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
+ }
57118
57144
 
57119
57145
  // src/tools/em_execute_file.ts
57120
57146
  var emExecuteFile = defineTool({
57121
57147
  name: "em_execute_file",
57122
- 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).",
57123
57149
  schema: {
57124
57150
  path: external_exports.string().describe("File to process, relative to the project root or absolute"),
57125
57151
  language: LANG_ENUM.optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eminiar",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Local-first context, memory and knowledge-graph plugin for Claude Code. Sandboxed execution, FTS5 + vector search, session continuity, and a code graph — nothing leaves your machine.",
5
5
  "type": "module",
6
6
  "license": "MIT",