@theokit/sdk-tools 0.20.1 → 0.21.0

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/index.cjs CHANGED
@@ -4,105 +4,17 @@ var promises = require('fs/promises');
4
4
  var path = require('path');
5
5
  var sdk = require('@theokit/sdk');
6
6
  var zod = require('zod');
7
- var fs = require('fs');
8
7
  var pathSafety = require('@theokit/sdk/path-safety');
9
8
  var persistence = require('@theokit/sdk/persistence');
10
9
  var filesystem = require('@theokit/sdk/filesystem');
11
10
  var child_process = require('child_process');
11
+ var fs = require('fs');
12
12
  var sandbox = require('@theokit/sdk/sandbox');
13
13
  var interactive = require('@theokit/sdk/interactive');
14
14
  var promises$1 = require('dns/promises');
15
15
  var net = require('net');
16
16
 
17
17
  // src/apply-patch.ts
18
- var PathTraversalError = class extends sdk.ConfigurationError {
19
- name = "PathTraversalError";
20
- constructor(input, resolvedPath) {
21
- super(`Path traversal attempt: ${input} \u2192 ${resolvedPath}`, {
22
- code: "path_traversal"
23
- });
24
- }
25
- };
26
- var ForbiddenPathError = class extends sdk.ConfigurationError {
27
- name = "ForbiddenPathError";
28
- constructor(path) {
29
- super(
30
- `Path '${path}' is in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files)`,
31
- {
32
- code: "forbidden_path"
33
- }
34
- );
35
- }
36
- };
37
- function safePathJoin(base, ...parts) {
38
- if (base === "") {
39
- throw new Error("safePathJoin: base must be non-empty");
40
- }
41
- const baseResolved = path.resolve(base);
42
- const target = path.resolve(base, ...parts);
43
- if (target !== baseResolved && !target.startsWith(baseResolved + path.sep)) {
44
- throw new PathTraversalError(parts.join("/"), target);
45
- }
46
- return target;
47
- }
48
- function assertNoSymlinkEscape(path$1, base) {
49
- let baseResolved;
50
- try {
51
- baseResolved = fs.realpathSync(base);
52
- } catch {
53
- baseResolved = path.resolve(base);
54
- }
55
- const resolved = realpathOfDeepestExisting(path$1);
56
- if (resolved === void 0) return;
57
- if (resolved !== baseResolved && !resolved.startsWith(baseResolved + path.sep)) {
58
- throw new PathTraversalError(`symlink ${path$1}`, resolved);
59
- }
60
- }
61
- function realpathOfDeepestExisting(path$1) {
62
- try {
63
- return fs.realpathSync(path$1);
64
- } catch {
65
- }
66
- try {
67
- const stat3 = fs.lstatSync(path$1);
68
- if (stat3.isSymbolicLink()) {
69
- const target = fs.readlinkSync(path$1);
70
- const parentReal = realpathOfDeepestExisting(path.dirname(path$1));
71
- const parentBase = parentReal ?? path.dirname(path$1);
72
- return path.resolve(parentBase, target);
73
- }
74
- } catch {
75
- }
76
- let cursor = path.dirname(path$1);
77
- let suffix = path$1.slice(cursor.length);
78
- while (cursor !== path.dirname(cursor)) {
79
- try {
80
- const real = fs.realpathSync(cursor);
81
- return path.resolve(real, `.${suffix}`);
82
- } catch {
83
- suffix = path$1.slice(path.dirname(cursor).length);
84
- cursor = path.dirname(cursor);
85
- }
86
- }
87
- return void 0;
88
- }
89
- var LOCK_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]);
90
- function isForbiddenPath(input) {
91
- const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
92
- if (normalized.length === 0) return false;
93
- const segments = normalized.split("/").filter((s) => s.length > 0);
94
- if (segments.length === 0) return false;
95
- const first = segments[0];
96
- if (first === ".env.example") return false;
97
- if (first === ".env") return true;
98
- if (/^\.env\./.test(first)) return true;
99
- if (first === ".git") return true;
100
- if (first === "node_modules") return true;
101
- if (first === ".theo") return true;
102
- const basename = segments[segments.length - 1];
103
- if (LOCK_FILES.has(basename)) return true;
104
- return false;
105
- }
106
18
 
107
19
  // src/internal/v4a-patch.ts
108
20
  var BEGIN = "*** Begin Patch";
@@ -446,15 +358,15 @@ function isForbiddenRel(rel) {
446
358
  function v4aScope(projectRoot, file) {
447
359
  let abs;
448
360
  try {
449
- abs = safePathJoin(projectRoot, file);
450
- assertNoSymlinkEscape(abs, projectRoot);
361
+ abs = pathSafety.safePathJoin(projectRoot, file);
362
+ pathSafety.assertNoSymlinkEscape(abs, projectRoot);
451
363
  } catch (err) {
452
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
364
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
453
365
  return { error: JSON.stringify({ ok: false, error: "path_traversal", path: file }) };
454
366
  }
455
367
  throw err;
456
368
  }
457
- if (isForbiddenPath(file) || isForbiddenRel(path.relative(projectRoot, abs))) {
369
+ if (pathSafety.isForbiddenPath(file) || isForbiddenRel(path.relative(projectRoot, abs))) {
458
370
  return { error: JSON.stringify({ ok: false, error: "forbidden_path", path: file }) };
459
371
  }
460
372
  return { abs };
@@ -665,7 +577,7 @@ async function editViaBackend(filesystem$1, ctx, path, old_string, new_string) {
665
577
  return JSON.stringify({ ok: true, replacements: 1 });
666
578
  }
667
579
  async function editViaLocal(projectRoot, path, old_string, new_string) {
668
- const absolutePath = safePathJoin(projectRoot, path);
580
+ const absolutePath = pathSafety.safePathJoin(projectRoot, path);
669
581
  let content;
670
582
  try {
671
583
  content = await promises.readFile(absolutePath, "utf-8");
@@ -683,14 +595,14 @@ async function editViaLocal(projectRoot, path, old_string, new_string) {
683
595
  return JSON.stringify({ ok: true, replacements: 1 });
684
596
  }
685
597
  function editScopeError(path, projectRoot) {
686
- if (isForbiddenPath(path)) {
598
+ if (pathSafety.isForbiddenPath(path)) {
687
599
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
688
600
  }
689
601
  try {
690
- assertNoSymlinkEscape(safePathJoin(projectRoot, path), projectRoot);
602
+ pathSafety.assertNoSymlinkEscape(pathSafety.safePathJoin(projectRoot, path), projectRoot);
691
603
  return null;
692
604
  } catch (err) {
693
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
605
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
694
606
  return JSON.stringify({ ok: false, error: "path_traversal", path });
695
607
  }
696
608
  throw err;
@@ -776,11 +688,11 @@ function formatError(message, code) {
776
688
  function checkPathScope(path, projectRoot) {
777
689
  if (path === void 0 || path === "") return null;
778
690
  try {
779
- const abs = safePathJoin(projectRoot, path);
780
- assertNoSymlinkEscape(abs, projectRoot);
691
+ const abs = pathSafety.safePathJoin(projectRoot, path);
692
+ pathSafety.assertNoSymlinkEscape(abs, projectRoot);
781
693
  return null;
782
694
  } catch (err) {
783
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
695
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
784
696
  return JSON.stringify({ ok: false, error: "path_traversal", path });
785
697
  }
786
698
  throw err;
@@ -800,25 +712,25 @@ function createSettleGate(timer) {
800
712
  }
801
713
  };
802
714
  }
803
- function armTimeoutKill(child, timeoutMs, onTimeout, resolve2) {
715
+ function armTimeoutKill(child, timeoutMs, onTimeout, resolve) {
804
716
  const timer = setTimeout(() => {
805
717
  gate.fire(() => {
806
718
  try {
807
719
  process.kill(-(child.pid ?? 0), "SIGKILL");
808
720
  } catch {
809
721
  }
810
- resolve2(onTimeout());
722
+ resolve(onTimeout());
811
723
  });
812
724
  }, timeoutMs);
813
725
  const gate = createSettleGate(timer);
814
726
  return gate;
815
727
  }
816
- function attachChildSettlers(child, gate, onClose, onError, resolve2) {
728
+ function attachChildSettlers(child, gate, onClose, onError, resolve) {
817
729
  child.on("close", (code) => {
818
- gate.fire(() => resolve2(onClose(code)));
730
+ gate.fire(() => resolve(onClose(code)));
819
731
  });
820
732
  child.on("error", (err) => {
821
- gate.fire(() => resolve2(onError(err)));
733
+ gate.fire(() => resolve(onError(err)));
822
734
  });
823
735
  }
824
736
 
@@ -885,7 +797,7 @@ function formatGitResult(result, timeoutMs) {
885
797
  return JSON.stringify({ ok: true, diff: result.stdout, truncated: result.truncated });
886
798
  }
887
799
  function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
888
- return new Promise((resolve2) => {
800
+ return new Promise((resolve) => {
889
801
  const child = child_process.spawn("git", args, { cwd, detached: true, stdio: ["ignore", "pipe", "pipe"] });
890
802
  const stdoutChunks = [];
891
803
  const stderrChunks = [];
@@ -895,7 +807,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
895
807
  child,
896
808
  timeoutMs,
897
809
  () => ({ kind: "timeout" }),
898
- resolve2
810
+ resolve
899
811
  );
900
812
  child.stdout.on("data", (chunk) => {
901
813
  if (gate.settled()) return;
@@ -925,7 +837,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
925
837
  return code === 0 ? { kind: "ok", stdout, truncated } : { kind: "error", stderr };
926
838
  },
927
839
  (err) => ({ kind: "error", stderr: err.message }),
928
- resolve2
840
+ resolve
929
841
  );
930
842
  });
931
843
  }
@@ -951,7 +863,7 @@ function createGlobTool(opts) {
951
863
  await walkDirBackend(backend, searchRel, searchRel, regex, found, 0);
952
864
  return JSON.stringify({ ok: true, files: found.sort(), count: found.length });
953
865
  }
954
- const searchRoot = cwd ? safePathJoin(projectRoot, cwd) : projectRoot;
866
+ const searchRoot = cwd ? pathSafety.safePathJoin(projectRoot, cwd) : projectRoot;
955
867
  const files = [];
956
868
  await walkDir(searchRoot, searchRoot, regex, files);
957
869
  const relativePaths = files.map((f) => path.relative(projectRoot, f)).sort();
@@ -962,10 +874,10 @@ function createGlobTool(opts) {
962
874
  function globScopeError(cwd, projectRoot) {
963
875
  if (!cwd) return null;
964
876
  try {
965
- assertNoSymlinkEscape(safePathJoin(projectRoot, cwd), projectRoot);
877
+ pathSafety.assertNoSymlinkEscape(pathSafety.safePathJoin(projectRoot, cwd), projectRoot);
966
878
  return null;
967
879
  } catch (err) {
968
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
880
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
969
881
  return JSON.stringify({ ok: false, error: "path_traversal", path: cwd });
970
882
  }
971
883
  throw err;
@@ -1605,7 +1517,7 @@ function createListDirTool(opts) {
1605
1517
  }),
1606
1518
  handler: async ({ path }, ctx) => {
1607
1519
  const relative3 = path === "" || path === "." ? "." : path;
1608
- if (relative3 !== "." && isForbiddenPath(relative3)) {
1520
+ if (relative3 !== "." && pathSafety.isForbiddenPath(relative3)) {
1609
1521
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
1610
1522
  }
1611
1523
  if (filesystem$1) {
@@ -1653,11 +1565,11 @@ async function listViaBackend(backend, relative3, originalPath, max) {
1653
1565
  }
1654
1566
  function resolveDirBoundary(relative3, projectRoot, originalPath) {
1655
1567
  try {
1656
- const absolutePath = relative3 === "." ? projectRoot : safePathJoin(projectRoot, relative3);
1657
- assertNoSymlinkEscape(absolutePath, projectRoot);
1568
+ const absolutePath = relative3 === "." ? projectRoot : pathSafety.safePathJoin(projectRoot, relative3);
1569
+ pathSafety.assertNoSymlinkEscape(absolutePath, projectRoot);
1658
1570
  return { absolutePath };
1659
1571
  } catch (err) {
1660
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
1572
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
1661
1573
  return { error: JSON.stringify({ ok: false, error: "path_traversal", path: originalPath }) };
1662
1574
  }
1663
1575
  throw err;
@@ -1819,7 +1731,7 @@ function isForbiddenAtAnyDepth(path) {
1819
1731
  });
1820
1732
  }
1821
1733
  function forbiddenReadError(path$1, allowAbsolute) {
1822
- if (isForbiddenPath(path$1)) {
1734
+ if (pathSafety.isForbiddenPath(path$1)) {
1823
1735
  return JSON.stringify({ ok: false, error: "forbidden_path", path: path$1 });
1824
1736
  }
1825
1737
  if (allowAbsolute && path.isAbsolute(path$1) && isForbiddenAtAnyDepth(path$1)) {
@@ -1906,11 +1818,11 @@ function resolveBoundary(path$1, projectRoot, allowAbsolute) {
1906
1818
  return { absolutePath: path$1 };
1907
1819
  }
1908
1820
  try {
1909
- const absolutePath = safePathJoin(projectRoot, path$1);
1910
- assertNoSymlinkEscape(absolutePath, projectRoot);
1821
+ const absolutePath = pathSafety.safePathJoin(projectRoot, path$1);
1822
+ pathSafety.assertNoSymlinkEscape(absolutePath, projectRoot);
1911
1823
  return { absolutePath };
1912
1824
  } catch (err) {
1913
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
1825
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
1914
1826
  return { error: JSON.stringify({ ok: false, error: "path_traversal", path: path$1 }) };
1915
1827
  }
1916
1828
  throw err;
@@ -2032,7 +1944,7 @@ function createRunVitestTool(opts) {
2032
1944
  });
2033
1945
  }
2034
1946
  function validateVitestScope(path, projectRoot) {
2035
- if (path !== void 0 && path !== "" && isForbiddenPath(path)) {
1947
+ if (path !== void 0 && path !== "" && pathSafety.isForbiddenPath(path)) {
2036
1948
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
2037
1949
  }
2038
1950
  return checkPathScope(path, projectRoot);
@@ -2078,7 +1990,7 @@ function appendCapped(chunks, chunk, current, cap) {
2078
1990
  return current + chunk.length;
2079
1991
  }
2080
1992
  function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
2081
- return new Promise((resolve2) => {
1993
+ return new Promise((resolve) => {
2082
1994
  const child = child_process.spawn(command, args, {
2083
1995
  cwd,
2084
1996
  detached: true,
@@ -2091,7 +2003,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
2091
2003
  child,
2092
2004
  timeoutMs,
2093
2005
  () => ({ kind: "timeout" }),
2094
- resolve2
2006
+ resolve
2095
2007
  );
2096
2008
  child.stdout.on("data", (chunk) => {
2097
2009
  if (gate.settled()) return;
@@ -2110,7 +2022,7 @@ function runProcess(cwd, command, args, timeoutMs, maxStdoutBytes) {
2110
2022
  exitCode: code ?? 0
2111
2023
  }),
2112
2024
  (err) => ({ kind: "spawn_error", message: err.message }),
2113
- resolve2
2025
+ resolve
2114
2026
  );
2115
2027
  });
2116
2028
  }
@@ -2191,11 +2103,11 @@ function resolveSearchScope(path$1, projectRoot, allowAbsolute) {
2191
2103
  return { scopeAbs: scopeRel };
2192
2104
  }
2193
2105
  try {
2194
- const scopeAbs = scopeRel === "." ? projectRoot : safePathJoin(projectRoot, scopeRel);
2195
- assertNoSymlinkEscape(scopeAbs, projectRoot);
2106
+ const scopeAbs = scopeRel === "." ? projectRoot : pathSafety.safePathJoin(projectRoot, scopeRel);
2107
+ pathSafety.assertNoSymlinkEscape(scopeAbs, projectRoot);
2196
2108
  return { scopeAbs };
2197
2109
  } catch (err) {
2198
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
2110
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
2199
2111
  return { error: JSON.stringify({ ok: false, error: "path_traversal", path: path$1 }) };
2200
2112
  }
2201
2113
  throw err;
@@ -2204,7 +2116,7 @@ function resolveSearchScope(path$1, projectRoot, allowAbsolute) {
2204
2116
  async function handleEntry(entry, absDir, state) {
2205
2117
  const entryAbs = path.join(absDir, entry.name);
2206
2118
  const entryRel = path.relative(state.projectRoot, entryAbs);
2207
- if (isForbiddenPath(entryRel)) return;
2119
+ if (pathSafety.isForbiddenPath(entryRel)) return;
2208
2120
  if (entry.isDirectory()) {
2209
2121
  await walk(entryAbs, state);
2210
2122
  return;
@@ -2270,10 +2182,10 @@ function resolveScopeRel(path$1, projectRoot, allowAbsolute) {
2270
2182
  if (scopeRel === "") return { rel: "" };
2271
2183
  if (allowAbsolute && path.isAbsolute(scopeRel)) return { rel: scopeRel };
2272
2184
  try {
2273
- assertNoSymlinkEscape(safePathJoin(projectRoot, scopeRel), projectRoot);
2185
+ pathSafety.assertNoSymlinkEscape(pathSafety.safePathJoin(projectRoot, scopeRel), projectRoot);
2274
2186
  return { rel: scopeRel };
2275
2187
  } catch (err) {
2276
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
2188
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
2277
2189
  return { error: JSON.stringify({ ok: false, error: "path_traversal", path: path$1 }) };
2278
2190
  }
2279
2191
  throw err;
@@ -2295,7 +2207,7 @@ async function walkBackend(backend, dirRel, state, depth) {
2295
2207
  }
2296
2208
  async function handleBackendEntry(backend, dirRel, name, state, depth) {
2297
2209
  const entryRel = dirRel === "" ? name : `${dirRel}/${name}`;
2298
- if (isForbiddenPath(entryRel)) return;
2210
+ if (pathSafety.isForbiddenPath(entryRel)) return;
2299
2211
  let st;
2300
2212
  try {
2301
2213
  st = await backend.stat(entryRel);
@@ -2361,7 +2273,7 @@ function createShellTool(opts) {
2361
2273
  });
2362
2274
  }
2363
2275
  function runShell(cwd, command, timeoutMs) {
2364
- return new Promise((resolve2) => {
2276
+ return new Promise((resolve) => {
2365
2277
  const child = child_process.spawn("/bin/sh", ["-c", command], {
2366
2278
  cwd,
2367
2279
  detached: true,
@@ -2375,7 +2287,7 @@ function runShell(cwd, command, timeoutMs) {
2375
2287
  child,
2376
2288
  timeoutMs,
2377
2289
  () => ({ kind: "timeout" }),
2378
- (result) => resolve2(formatResult(result, timeoutMs))
2290
+ (result) => resolve(formatResult(result, timeoutMs))
2379
2291
  );
2380
2292
  child.stdout.on("data", (chunk) => {
2381
2293
  if (gate.settled()) return;
@@ -2413,7 +2325,7 @@ function runShell(cwd, command, timeoutMs) {
2413
2325
  exitCode: code
2414
2326
  }),
2415
2327
  (err) => ({ kind: "error", message: err.message }),
2416
- (result) => resolve2(formatResult(result, timeoutMs))
2328
+ (result) => resolve(formatResult(result, timeoutMs))
2417
2329
  );
2418
2330
  });
2419
2331
  }
@@ -2795,7 +2707,7 @@ function createWriteFileTool(opts) {
2795
2707
  content: zod.z.string().describe("UTF-8 content to write.")
2796
2708
  }),
2797
2709
  handler: async ({ path, content }, ctx) => {
2798
- if (isForbiddenPath(path)) {
2710
+ if (pathSafety.isForbiddenPath(path)) {
2799
2711
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
2800
2712
  }
2801
2713
  if (filesystem$1) {
@@ -2817,10 +2729,10 @@ function readBeforeWriteError(guard, path, currentMtimeMs) {
2817
2729
  async function writeViaLocalFs(projectRoot, path$1, content, guard) {
2818
2730
  let absolutePath;
2819
2731
  try {
2820
- absolutePath = safePathJoin(projectRoot, path$1);
2821
- assertNoSymlinkEscape(absolutePath, projectRoot);
2732
+ absolutePath = pathSafety.safePathJoin(projectRoot, path$1);
2733
+ pathSafety.assertNoSymlinkEscape(absolutePath, projectRoot);
2822
2734
  } catch (err) {
2823
- if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
2735
+ if (err instanceof pathSafety.PathTraversalError || err instanceof pathSafety.ForbiddenPathError) {
2824
2736
  return JSON.stringify({ ok: false, error: "path_traversal", path: path$1 });
2825
2737
  }
2826
2738
  throw err;