spacecode 2.5.2 → 2.5.3

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/repl.mjs CHANGED
@@ -4580,9 +4580,22 @@ function guardHost(inner, opts) {
4580
4580
  await log(kind, subject, d, false);
4581
4581
  throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
4582
4582
  }
4583
+ const verdict = opts.rules?.evaluate(kind === "exec" ? "bash" : kind, subject);
4584
+ const ruleTag = verdict?.rule ? `${verdict.rule} [${verdict.scope}]` : "";
4585
+ if (verdict?.effect === "deny") {
4586
+ d = { action: "deny", rule: "rule-deny", reason: `denied by imported rule ${ruleTag}` };
4587
+ await log(kind, subject, d, false);
4588
+ throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
4589
+ }
4583
4590
  if (kind !== "exec" && opts.permissions?.mode === "default") {
4584
4591
  d = { action: "confirm", rule: "confirm-edit", reason: "modifies a file" };
4585
4592
  }
4593
+ if (verdict?.effect === "ask") {
4594
+ d = { action: "confirm", rule: "rule-ask", reason: `imported rule ${ruleTag} asks first${opts.confirm ? "" : "; no way to ask (headless), so refused"}` };
4595
+ } else if (verdict?.effect === "allow" && d.action === "confirm") {
4596
+ await log(kind, subject, { action: "allow", rule: "rule-allow", reason: `allowed by imported rule ${ruleTag} (would be ${d.rule})` }, true);
4597
+ return;
4598
+ }
4586
4599
  if (d.action === "confirm" && kind === "exec" && opts.permissions?.allowsCommand(subject)) {
4587
4600
  await log(kind, subject, { action: "allow", rule: "allow-session", reason: "approved earlier this session" }, true);
4588
4601
  return;
@@ -4606,7 +4619,15 @@ function guardHost(inner, opts) {
4606
4619
  return {
4607
4620
  kind: inner.kind,
4608
4621
  files: {
4609
- readFile: (p) => inner.files.readFile(p),
4622
+ readFile: async (p) => {
4623
+ const v2 = opts.rules?.evaluate("read_file", p.replace(/^\.\//, ""));
4624
+ if (v2?.effect === "deny") {
4625
+ const d = { action: "deny", rule: "rule-deny", reason: `denied by imported rule ${v2.rule} [${v2.scope}]` };
4626
+ await log("read_file", p, d, false);
4627
+ throw new PolicyViolation(d.rule, `Blocked by policy (${d.rule}): ${d.reason}`);
4628
+ }
4629
+ return inner.files.readFile(p);
4630
+ },
4610
4631
  listDirectory: (p) => inner.files.listDirectory(p),
4611
4632
  writeFile: async (p, c) => {
4612
4633
  await gate("write_file", p, allow, { oldText: "", newText: c });
@@ -5572,6 +5593,132 @@ var init_imports_config = __esm({
5572
5593
  }
5573
5594
  });
5574
5595
 
5596
+ // ../agent/src/import/permission-rules.ts
5597
+ function splitCommand(cmd) {
5598
+ const segments2 = [];
5599
+ let cur = "";
5600
+ let quote = null;
5601
+ let compound = false;
5602
+ const flush = () => {
5603
+ const s = cur.trim();
5604
+ if (s) segments2.push(s);
5605
+ cur = "";
5606
+ };
5607
+ for (let i = 0; i < cmd.length; i++) {
5608
+ const ch = cmd[i];
5609
+ if (quote) {
5610
+ cur += ch;
5611
+ if (ch === quote) quote = null;
5612
+ } else if (ch === '"' || ch === "'") {
5613
+ quote = ch;
5614
+ cur += ch;
5615
+ } else if (";&|\n`()<>".includes(ch) || ch === "$" && cmd[i + 1] === "(") {
5616
+ compound = true;
5617
+ flush();
5618
+ } else {
5619
+ cur += ch;
5620
+ }
5621
+ }
5622
+ if (quote) return { compound: true, segments: [cmd.trim()] };
5623
+ flush();
5624
+ return compound ? { compound, segments: segments2 } : { compound, segments: [cmd.trim()] };
5625
+ }
5626
+ function matchesBash(pattern, cmd, effect) {
5627
+ const { compound, segments: segments2 } = splitCommand(cmd);
5628
+ if (compound && effect === "allow") return false;
5629
+ const prefix = pattern.endsWith(":*") ? pattern.slice(0, -2).trim() : null;
5630
+ const hit = (s) => prefix === null ? s === pattern.trim() : s === prefix || s.startsWith(prefix) && /\s/.test(s[prefix.length] ?? "");
5631
+ return segments2.some(hit);
5632
+ }
5633
+ function globToRegExp(glob) {
5634
+ let re3 = "";
5635
+ for (let i = 0; i < glob.length; i++) {
5636
+ const ch = glob[i];
5637
+ if (ch === "*") {
5638
+ if (glob[i + 1] === "*") {
5639
+ re3 += ".*";
5640
+ i++;
5641
+ if (glob[i + 1] === "/") i++;
5642
+ } else re3 += "[^/]*";
5643
+ } else if (ch === "?") re3 += "[^/]";
5644
+ else re3 += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
5645
+ }
5646
+ return new RegExp("^" + re3 + "$");
5647
+ }
5648
+ function compile(text, effect, scope) {
5649
+ const m2 = /^([A-Za-z0-9_-]+)(?:\((.*)\))?$/s.exec(text.trim());
5650
+ if (!m2) return "malformed rule";
5651
+ const [, name, rawArg] = m2;
5652
+ const arg = rawArg === void 0 || rawArg.trim() === "" ? null : rawArg.trim();
5653
+ const tools = name.startsWith("mcp__") ? [name] : TOOL_MAP[name];
5654
+ if (!tools) return `unsupported tool ${name}`;
5655
+ if (arg === null) return { effect, scope, text, tools, arg, test: () => true };
5656
+ if (name === "Bash") return { effect, scope, text, tools, arg, test: (s, e) => matchesBash(arg, s, e) };
5657
+ if (name.startsWith("mcp__")) return "argument patterns are not supported for MCP tools";
5658
+ const re3 = globToRegExp(arg);
5659
+ return { effect, scope, text, tools, arg, test: (s) => re3.test(s) };
5660
+ }
5661
+ function buildRuleSet(sources) {
5662
+ const rules = [];
5663
+ const skipped = [];
5664
+ const counts = {};
5665
+ for (const s of ["user", "repo", "local"]) counts[s] = { allow: 0, deny: 0, ask: 0 };
5666
+ for (const src of sources) {
5667
+ for (const effect of ["deny", "ask", "allow"]) {
5668
+ for (const text of src[effect] ?? []) {
5669
+ const c = typeof text === "string" ? compile(text, effect, src.scope) : "malformed rule";
5670
+ if (typeof c === "string") skipped.push({ text: String(text), scope: src.scope, reason: c });
5671
+ else {
5672
+ rules.push(c);
5673
+ counts[src.scope][effect]++;
5674
+ }
5675
+ }
5676
+ }
5677
+ }
5678
+ const frozen = Object.freeze(rules);
5679
+ return {
5680
+ skipped,
5681
+ counts,
5682
+ evaluate(tool, subject) {
5683
+ for (const effect of ["deny", "ask", "allow"]) {
5684
+ const r = frozen.find((c) => c.effect === effect && c.tools.includes(tool) && c.test(subject, effect));
5685
+ if (r) return { effect, rule: r.text, scope: r.scope };
5686
+ }
5687
+ return { effect: "none" };
5688
+ }
5689
+ };
5690
+ }
5691
+ function defaultModeFor(mode) {
5692
+ switch (mode) {
5693
+ case void 0:
5694
+ case "default":
5695
+ return { mode: "default" };
5696
+ case "acceptEdits":
5697
+ return { mode: "accept-edits" };
5698
+ case "plan":
5699
+ return { mode: "plan" };
5700
+ case "bypassPermissions":
5701
+ return { mode: "yolo" };
5702
+ default:
5703
+ return { mode: null, reason: `unmappable defaultMode "${mode}"` };
5704
+ }
5705
+ }
5706
+ var TOOL_MAP;
5707
+ var init_permission_rules = __esm({
5708
+ "../agent/src/import/permission-rules.ts"() {
5709
+ "use strict";
5710
+ TOOL_MAP = {
5711
+ Bash: ["bash"],
5712
+ Read: ["read_file"],
5713
+ Edit: ["edit_file", "write_file"],
5714
+ Write: ["edit_file", "write_file"],
5715
+ MultiEdit: ["edit_file", "write_file"],
5716
+ Glob: ["list_directory"],
5717
+ Grep: ["search"]
5718
+ };
5719
+ }
5720
+ });
5721
+
5575
5722
  // ../agent/src/index.ts
5576
5723
  var init_src = __esm({
5577
5724
  "../agent/src/index.ts"() {
@@ -5611,13 +5758,14 @@ var init_src = __esm({
5611
5758
  init_instructions_tool();
5612
5759
  init_trust();
5613
5760
  init_imports_config();
5761
+ init_permission_rules();
5614
5762
  }
5615
5763
  });
5616
5764
 
5617
5765
  // src/api-key.ts
5618
5766
  import { join } from "node:path";
5619
5767
  async function resolveApiKey(deps) {
5620
- const { env: env3, readFile: readFile6, homedir: homedir3 } = deps;
5768
+ const { env: env3, readFile: readFile7, homedir: homedir3 } = deps;
5621
5769
  for (const name of ["SPACECODE_API_KEY", "ANTHROPIC_API_KEY"]) {
5622
5770
  const value = env3[name]?.trim();
5623
5771
  if (value) return value;
@@ -5626,7 +5774,7 @@ async function resolveApiKey(deps) {
5626
5774
  const file = join(dir, "auth.json");
5627
5775
  let raw;
5628
5776
  try {
5629
- raw = await readFile6(file);
5777
+ raw = await readFile7(file);
5630
5778
  } catch (err) {
5631
5779
  if (err.code === "ENOENT") return null;
5632
5780
  throw new Error(`Cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
@@ -34655,7 +34803,7 @@ var init_source = __esm({
34655
34803
  object.level = options.level === void 0 ? colorLevel : options.level;
34656
34804
  };
34657
34805
  chalkFactory = (options) => {
34658
- const chalk2 = (...strings) => strings.join(" ");
34806
+ const chalk2 = (...strings2) => strings2.join(" ");
34659
34807
  applyOptions(chalk2, options);
34660
34808
  Object.setPrototypeOf(chalk2, createChalk.prototype);
34661
34809
  return chalk2;
@@ -41089,7 +41237,7 @@ var require_core = __commonJS({
41089
41237
  return match && match.index === 0;
41090
41238
  }
41091
41239
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
41092
- function join6(regexps, separator = "|") {
41240
+ function join7(regexps, separator = "|") {
41093
41241
  let numCaptures = 0;
41094
41242
  return regexps.map((regex2) => {
41095
41243
  numCaptures += 1;
@@ -41393,7 +41541,7 @@ var require_core = __commonJS({
41393
41541
  this.exec = () => null;
41394
41542
  }
41395
41543
  const terminators = this.regexes.map((el) => el[1]);
41396
- this.matcherRe = langRe(join6(terminators), true);
41544
+ this.matcherRe = langRe(join7(terminators), true);
41397
41545
  this.lastIndex = 0;
41398
41546
  }
41399
41547
  /** @param {string} s */
@@ -84320,12 +84468,12 @@ var require_source = __commonJS({
84320
84468
  return openAll + string + closeAll;
84321
84469
  };
84322
84470
  var template;
84323
- var chalkTag = (chalk3, ...strings) => {
84324
- const [firstString] = strings;
84471
+ var chalkTag = (chalk3, ...strings2) => {
84472
+ const [firstString] = strings2;
84325
84473
  if (!isArray(firstString) || !isArray(firstString.raw)) {
84326
- return strings.join(" ");
84474
+ return strings2.join(" ");
84327
84475
  }
84328
- const arguments_ = strings.slice(1);
84476
+ const arguments_ = strings2.slice(1);
84329
84477
  const parts = [firstString.raw[0]];
84330
84478
  for (let i = 1; i < firstString.length; i++) {
84331
84479
  parts.push(
@@ -100673,6 +100821,7 @@ function createLocalToolset(opts) {
100673
100821
  audit: new FileAuditLog(auditFile),
100674
100822
  mode: opts.yolo ? "yolo" : "enforce",
100675
100823
  permissions: opts.permissions,
100824
+ ...opts.rules ? { rules: opts.rules } : {},
100676
100825
  confirm: opts.confirm
100677
100826
  });
100678
100827
  const { handlers: githubHandlers } = createGitHubTools((command) => host.exec(command));
@@ -101104,10 +101253,83 @@ var init_import_gate = __esm({
101104
101253
  }
101105
101254
  });
101106
101255
 
101256
+ // src/permission-rules-loader.ts
101257
+ import { readFile as readFile5 } from "node:fs/promises";
101258
+ import { join as join5 } from "node:path";
101259
+ async function readOrNull2(path12) {
101260
+ try {
101261
+ return await readFile5(path12, "utf8");
101262
+ } catch (err) {
101263
+ if (err.code === "ENOENT") return null;
101264
+ throw err;
101265
+ }
101266
+ }
101267
+ async function loadPermissionRules(deps) {
101268
+ const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
101269
+ const configPath = join5(deps.configDir, "imports.json");
101270
+ try {
101271
+ const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
101272
+ if (loaded.error) return none("permission rules: off (imports.json unreadable)");
101273
+ if (!loaded.config.categories.permissions) return none("permission rules: off (imports.json has permissions off)");
101274
+ } catch (err) {
101275
+ return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
101276
+ }
101277
+ const lines2 = [];
101278
+ const files = [{ scope: "user", path: join5(deps.homeDir, ".claude", "settings.json") }];
101279
+ try {
101280
+ const sources2 = {};
101281
+ for (const name of REPO_SOURCE_FILES) {
101282
+ const text = await readOrNull2(join5(deps.cwd, name));
101283
+ if (text !== null) sources2[name] = text;
101284
+ }
101285
+ if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
101286
+ const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
101287
+ if (status === "trusted" || status === "session") {
101288
+ files.push({ scope: "repo", path: join5(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join5(deps.cwd, ".claude", "settings.local.json") });
101289
+ } else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
101290
+ }
101291
+ } catch (err) {
101292
+ lines2.push(`permission rules: repo settings NOT loaded (trust check failed: ${err instanceof Error ? err.message : String(err)})`);
101293
+ }
101294
+ const sources = [];
101295
+ let defaultMode;
101296
+ for (const f of files) {
101297
+ try {
101298
+ const text = await readOrNull2(f.path);
101299
+ if (text === null) continue;
101300
+ const parsed = JSON.parse(text);
101301
+ const perms = parsed?.permissions;
101302
+ if (!perms || typeof perms !== "object") continue;
101303
+ sources.push({ scope: f.scope, allow: strings(perms.allow), deny: strings(perms.deny), ask: strings(perms.ask) });
101304
+ if (typeof perms.defaultMode === "string") {
101305
+ const m2 = defaultModeFor(perms.defaultMode);
101306
+ if (m2.mode) defaultMode = m2.mode;
101307
+ else lines2.push(`permission rules: ${f.path}: ${m2.reason}`);
101308
+ }
101309
+ } catch (err) {
101310
+ lines2.push(`permission rules: ${f.path} NOT loaded (${err instanceof Error ? err.message : String(err)})`);
101311
+ }
101312
+ }
101313
+ const rules = buildRuleSet(sources);
101314
+ const parts = ["user", "repo", "local"].filter((s) => sources.some((x2) => x2.scope === s)).map((s) => `${s} ${rules.counts[s].allow} allow/${rules.counts[s].deny} deny/${rules.counts[s].ask} ask`);
101315
+ lines2.unshift(`permission rules: ${parts.length ? parts.join(", ") : "none found"}${defaultMode ? `; default mode ${defaultMode}` : ""}`);
101316
+ for (const s of rules.skipped) lines2.push(`permission rules: skipped [${s.scope}] "${s.text}" (${s.reason})`);
101317
+ return { rules, defaultMode, statusLines: lines2 };
101318
+ }
101319
+ var strings;
101320
+ var init_permission_rules_loader = __esm({
101321
+ "src/permission-rules-loader.ts"() {
101322
+ "use strict";
101323
+ init_src();
101324
+ init_import_gate();
101325
+ strings = (v2) => Array.isArray(v2) ? v2.filter((x2) => typeof x2 === "string") : void 0;
101326
+ }
101327
+ });
101328
+
101107
101329
  // src/useReplController.ts
101108
- import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
101330
+ import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir4 } from "node:fs/promises";
101109
101331
  import { homedir as homedir2 } from "node:os";
101110
- import { join as join5 } from "node:path";
101332
+ import { join as join6 } from "node:path";
101111
101333
  import { execFile as execFile3 } from "node:child_process";
101112
101334
  function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
101113
101335
  const { exit } = use_app_default();
@@ -101149,6 +101371,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
101149
101371
  const abortRef = (0, import_react30.useRef)(null);
101150
101372
  const pendingRef = (0, import_react30.useRef)(null);
101151
101373
  const toolsetRef = (0, import_react30.useRef)(null);
101374
+ const rulesRef = (0, import_react30.useRef)(void 0);
101152
101375
  (0, import_react30.useEffect)(() => {
101153
101376
  sessionTokensRef.current = sessionTokens;
101154
101377
  }, [sessionTokens]);
@@ -101201,6 +101424,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
101201
101424
  cwd: process.cwd(),
101202
101425
  permissions,
101203
101426
  onTodos: setTodos,
101427
+ ...rulesRef.current ? { rules: rulesRef.current } : {},
101204
101428
  ...profileRef.current && profileRef.current.tools.length > 0 ? { extra: { tools: profileRef.current.tools, handlers: profileRef.current.handlers } } : {},
101205
101429
  confirm: (req) => new Promise((resolve2) => {
101206
101430
  const item = {
@@ -101400,7 +101624,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
101400
101624
  }
101401
101625
  setIsRunning(true);
101402
101626
  try {
101403
- const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile5(p, "utf-8"), homedir: homedir2 });
101627
+ const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 });
101404
101628
  if (!apiKey2) {
101405
101629
  notice(MISSING_KEY_MESSAGE);
101406
101630
  return;
@@ -101422,7 +101646,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
101422
101646
  }
101423
101647
  case "model": {
101424
101648
  try {
101425
- const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile5(p, "utf-8"), homedir: homedir2 });
101649
+ const apiKey2 = await resolveApiKey({ env: process.env, readFile: (p) => readFile6(p, "utf-8"), homedir: homedir2 });
101426
101650
  if (!apiKey2) {
101427
101651
  notice(MISSING_KEY_MESSAGE);
101428
101652
  return;
@@ -101464,7 +101688,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
101464
101688
  try {
101465
101689
  apiKey = await resolveApiKey({
101466
101690
  env: process.env,
101467
- readFile: (p) => readFile5(p, "utf-8"),
101691
+ readFile: (p) => readFile6(p, "utf-8"),
101468
101692
  homedir: homedir2
101469
101693
  });
101470
101694
  } catch (err) {
@@ -101500,7 +101724,7 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
101500
101724
  try {
101501
101725
  profilePromiseRef.current ??= prepareProfile({
101502
101726
  env: process.env,
101503
- readFile: (p) => readFile5(p, "utf-8"),
101727
+ readFile: (p) => readFile6(p, "utf-8"),
101504
101728
  writeFile: (p, d) => writeFile4(p, d, { encoding: "utf-8", mode: 384 }),
101505
101729
  mkdir: async (p) => {
101506
101730
  await mkdir4(p, { recursive: true });
@@ -101509,11 +101733,14 @@ ${tools.map((t) => ` ${t.name}`).join("\n")}`);
101509
101733
  fetch,
101510
101734
  cwd: process.cwd(),
101511
101735
  gitRoot: gitTopLevel,
101512
- importGate: () => importGateStatus({
101513
- configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join5(homedir2(), ".spacecode"),
101514
- cwd: process.cwd(),
101515
- interactive: Boolean(process.stdin.isTTY)
101516
- })
101736
+ importGate: async () => {
101737
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join6(homedir2(), ".spacecode");
101738
+ const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
101739
+ const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
101740
+ rulesRef.current = loaded.rules;
101741
+ if (loaded.defaultMode && !yolo2) permissions.set(loaded.defaultMode);
101742
+ return [...gate, ...loaded.statusLines];
101743
+ }
101517
101744
  });
101518
101745
  profileRef.current = await profilePromiseRef.current;
101519
101746
  const toolset = createToolset();
@@ -101641,6 +101868,7 @@ var init_useReplController = __esm({
101641
101868
  init_api_key();
101642
101869
  init_profile_runtime();
101643
101870
  init_import_gate();
101871
+ init_permission_rules_loader();
101644
101872
  init_slash_commands();
101645
101873
  init_useEventAccumulator();
101646
101874
  gitTopLevel = () => new Promise((resolve2) => {
@@ -102225,7 +102453,7 @@ function clearOnResize(stdout) {
102225
102453
  var import_jsx_runtime14 = __toESM(require_jsx_runtime(), 1);
102226
102454
  if (process.argv[2] === "import-claude") {
102227
102455
  const { runImportClaude: runImportClaude2 } = await Promise.resolve().then(() => (init_import_claude(), import_claude_exports));
102228
- const { readFile: readFile6, readdir: readdir3 } = await import("node:fs/promises");
102456
+ const { readFile: readFile7, readdir: readdir3 } = await import("node:fs/promises");
102229
102457
  const { execFile: execFile4 } = await import("node:child_process");
102230
102458
  const gitRoot = () => new Promise((resolve2) => {
102231
102459
  execFile4("git", ["rev-parse", "--show-toplevel"], { cwd: process.cwd() }, (err, stdout) => resolve2(err ? null : stdout.trim() || null));
@@ -102233,8 +102461,8 @@ if (process.argv[2] === "import-claude") {
102233
102461
  const code = await runImportClaude2({
102234
102462
  argv: process.argv.slice(2),
102235
102463
  env: process.env,
102236
- fs: { readFile: (p) => readFile6(p, "utf-8"), readdir: (p) => readdir3(p) },
102237
- readFile: (p) => readFile6(p, "utf-8"),
102464
+ fs: { readFile: (p) => readFile7(p, "utf-8"), readdir: (p) => readdir3(p) },
102465
+ readFile: (p) => readFile7(p, "utf-8"),
102238
102466
  homedir: os5.homedir,
102239
102467
  cwd: process.cwd(),
102240
102468
  gitRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecode",
3
- "version": "2.5.2",
3
+ "version": "2.5.3",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",