@theokit/sdk-tools 0.7.0 → 0.9.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/CHANGELOG.md +22 -0
- package/dist/index.cjs +167 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -4
- package/dist/index.d.ts +94 -4
- package/dist/index.js +164 -27
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3af329f: **SE31 — `Filesystem` provider seam (`@theokit/sdk/filesystem`).**
|
|
8
|
+
|
|
9
|
+
A pluggable filesystem _storage_ provider, the storage-side twin of `@theokit/sdk/sandbox`. `FilesystemBackend` is an abstract class with four methods (`readFile` / `writeFile` / `stat` / `list`), an `exists()` derived on the base, a boundary `basePath`, a `readOnly` flag, structured `stat().mtimeMs` (the read-before-write oracle for SE32), and typed errors (`FileNotFoundError` / `FilesystemSecurityError` / `FilesystemReadOnlyError` / `StaleFileError`). `LocalFilesystem` is the local-process implementation, boundary-enforced by reusing the core path-guard (traversal + symlink escape → `FilesystemSecurityError`). `FilesystemProvider` + `resolveFilesystem` support a per-request resolver `(ctx) => FilesystemBackend` for multi-tenant roots.
|
|
10
|
+
|
|
11
|
+
Unlike `SandboxBackend` (whose file ops shell out via `execute`, require command execution, and give no structured `stat`), a `FilesystemBackend` serves a filesystem-only workspace with no sandbox — see ADR 0011 for why file ops are NOT routed through `SandboxBackend`. `@theokit/sdk-tools`' `createWriteFileTool` now accepts an optional `filesystem` backend (writes route through it; omitted ⇒ identical local-`projectRoot` behavior). This is the backend seam, NOT a bundled `Workspace` and NOT a new toolset — bring-your-own-tools stands; `mounts`/FUSE, S3/GCS, and LSP remain out of core. From the Mastra Workspaces comparison (SDK Evolution roadmap SE31).
|
|
12
|
+
|
|
13
|
+
- 84df83a: **SE32 — read-before-write safety (`requireReadBeforeWrite` + `ReadTracker`).**
|
|
14
|
+
|
|
15
|
+
An opt-in guard on `createWriteFileTool` that refuses to blindly overwrite a file the agent has not seen. A per-run `ReadTracker` (exported from `@theokit/sdk-tools`) records each file's mtime when `createReadFileTool` reads it; when `createWriteFileTool` is created with `{ requireReadBeforeWrite: true, readTracker }`, a write is refused with `read_required` if the existing file was never read, or `stale_file` if it changed on disk since it was read. A NEW file writes freely (nothing to clobber). Default OFF — omitting the flag preserves current behavior exactly.
|
|
16
|
+
|
|
17
|
+
Works on both the local `projectRoot` path and the SE31 `filesystem` backend path (the backend also gets `expectedMtime` forwarded so it re-checks at write time — TOCTOU defense). The tracker is deliberately per-instance, not a global singleton, so state never leaks across runs. `edit_file` already has implicit read-before-write safety via `old_string` content matching, so the guard targets the blind-overwrite path (`write_file`). Mirrors Mastra Workspaces' read-before-write (`FileReadRequiredError` / `StaleFileError`). From the Mastra Workspaces comparison (SDK Evolution roadmap SE32).
|
|
18
|
+
|
|
19
|
+
## 0.8.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- ac3f77d: @theokit/sdk: resolveModelCapabilities catalog gains cheap OpenRouter slugs (qwen3-coder, deepseek v4-flash/v3.2, glm-4.7-flash, gemini-2.5-flash-lite/pro) so they resolve real context windows instead of the 4096 default. @theokit/sdk-tools: new createGenericHttpSearchAdapter (env-keyed generic HTTP WebSearchCallback alongside Brave); buildEnvContext gains git-branch detection + an injectable clock. @theokit/sdk-cache: ships createLexicalEmbedder (zero-dependency token-hash lexical embedder built-in).
|
|
24
|
+
|
|
3
25
|
## 0.7.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -10,6 +10,7 @@ var pathSafety = require('@theokit/sdk/path-safety');
|
|
|
10
10
|
var child_process = require('child_process');
|
|
11
11
|
var promises$1 = require('dns/promises');
|
|
12
12
|
var net = require('net');
|
|
13
|
+
var filesystem = require('@theokit/sdk/filesystem');
|
|
13
14
|
|
|
14
15
|
// src/apply-patch.ts
|
|
15
16
|
var PathTraversalError = class extends sdk.ConfigurationError {
|
|
@@ -887,15 +888,26 @@ function safeReadHead(p, n) {
|
|
|
887
888
|
return "";
|
|
888
889
|
}
|
|
889
890
|
}
|
|
890
|
-
function
|
|
891
|
+
function gitBranch(headPath) {
|
|
892
|
+
try {
|
|
893
|
+
const head = fs.readFileSync(headPath, "utf-8").trim();
|
|
894
|
+
const match = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
895
|
+
return match ? match[1] : void 0;
|
|
896
|
+
} catch {
|
|
897
|
+
return void 0;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function buildEnvContext(cwd, opts = {}) {
|
|
891
901
|
const lines = [
|
|
892
902
|
"<env>",
|
|
893
903
|
` Working directory: ${cwd}`,
|
|
894
904
|
` Platform: ${process.platform} (${process.arch})`,
|
|
895
905
|
` Node: ${process.version}`,
|
|
896
|
-
` Is git repo: ${safeExists(path.join(cwd, ".git")) ? "yes" : "no"}
|
|
897
|
-
` Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}`
|
|
906
|
+
` Is git repo: ${safeExists(path.join(cwd, ".git")) ? "yes" : "no"}`
|
|
898
907
|
];
|
|
908
|
+
const branch = gitBranch(opts.gitHeadPath ?? path.join(cwd, ".git", "HEAD"));
|
|
909
|
+
if (branch) lines.push(` Branch: ${branch}`);
|
|
910
|
+
lines.push(` Today's date: ${(opts.now ?? /* @__PURE__ */ new Date()).toDateString()}`);
|
|
899
911
|
const docs = PROJECT_DOCS.filter((d) => safeExists(path.join(cwd, d)));
|
|
900
912
|
if (docs.length > 0) {
|
|
901
913
|
lines.push(` Project docs: ${docs.join(", ")}`);
|
|
@@ -1043,7 +1055,10 @@ function withToolResultGuidance(tool, guidance) {
|
|
|
1043
1055
|
name: tool.name,
|
|
1044
1056
|
description: tool.description,
|
|
1045
1057
|
inputSchema: tool.inputSchema,
|
|
1046
|
-
handler: async (input) =>
|
|
1058
|
+
handler: async (input) => {
|
|
1059
|
+
const out = await tool.handler(input);
|
|
1060
|
+
return typeof out === "string" ? injectGuidance(out, guidance) : out;
|
|
1061
|
+
}
|
|
1047
1062
|
};
|
|
1048
1063
|
}
|
|
1049
1064
|
function withDefaultGuidance(tool) {
|
|
@@ -1057,6 +1072,7 @@ function withShellExitGuidance(tool) {
|
|
|
1057
1072
|
inputSchema: tool.inputSchema,
|
|
1058
1073
|
handler: async (input) => {
|
|
1059
1074
|
const out = await tool.handler(input);
|
|
1075
|
+
if (typeof out !== "string") return out;
|
|
1060
1076
|
let parsed;
|
|
1061
1077
|
try {
|
|
1062
1078
|
parsed = JSON.parse(out);
|
|
@@ -1253,7 +1269,7 @@ function createQuestionTool(opts) {
|
|
|
1253
1269
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
1254
1270
|
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
1255
1271
|
function createReadFileTool(opts) {
|
|
1256
|
-
const { projectRoot } = opts;
|
|
1272
|
+
const { projectRoot, readTracker } = opts;
|
|
1257
1273
|
return sdk.defineTool({
|
|
1258
1274
|
name: "read_file",
|
|
1259
1275
|
description: "Read a project-relative text file as UTF-8. ALWAYS read a file before you edit it (edit_file) or overwrite it (write_file), so your old_string / new content matches the real bytes exactly. Returns the WHOLE file (there is no offset or line-range parameter); to locate a symbol inside a large file, use search_text instead of re-reading. Refuses paths that escape the project root, sensitive files (.env, .git/, node_modules/, .theo/, lock files), and binary files (null byte in the first 8 KB); caps at 5 MB. Returns { ok, content, size } or { ok: false, error }.",
|
|
@@ -1269,7 +1285,11 @@ function createReadFileTool(opts) {
|
|
|
1269
1285
|
const opened = await openHandleSafe(boundary.absolutePath, path);
|
|
1270
1286
|
if ("error" in opened) return opened.error;
|
|
1271
1287
|
try {
|
|
1272
|
-
return await readContent(
|
|
1288
|
+
return await readContent(
|
|
1289
|
+
opened.handle,
|
|
1290
|
+
path,
|
|
1291
|
+
(mtimeMs) => readTracker?.record(path, mtimeMs)
|
|
1292
|
+
);
|
|
1273
1293
|
} finally {
|
|
1274
1294
|
await opened.handle.close();
|
|
1275
1295
|
}
|
|
@@ -1300,7 +1320,7 @@ async function openHandleSafe(absolutePath, path) {
|
|
|
1300
1320
|
throw err;
|
|
1301
1321
|
}
|
|
1302
1322
|
}
|
|
1303
|
-
async function readContent(handle, path) {
|
|
1323
|
+
async function readContent(handle, path, onRead) {
|
|
1304
1324
|
const stat2 = await handle.stat();
|
|
1305
1325
|
if (stat2.size > MAX_FILE_SIZE) {
|
|
1306
1326
|
return JSON.stringify({
|
|
@@ -1315,6 +1335,7 @@ async function readContent(handle, path) {
|
|
|
1315
1335
|
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1316
1336
|
}
|
|
1317
1337
|
const content = await handle.readFile({ encoding: "utf-8" });
|
|
1338
|
+
onRead?.(stat2.mtimeMs);
|
|
1318
1339
|
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1319
1340
|
}
|
|
1320
1341
|
async function isBinaryProbe(handle, size) {
|
|
@@ -1327,6 +1348,26 @@ async function isBinaryProbe(handle, size) {
|
|
|
1327
1348
|
}
|
|
1328
1349
|
return false;
|
|
1329
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
// src/read-tracker.ts
|
|
1353
|
+
var ReadTracker = class {
|
|
1354
|
+
seen = /* @__PURE__ */ new Map();
|
|
1355
|
+
/** Record the mtime observed when `path` was read. */
|
|
1356
|
+
record(path, mtimeMs) {
|
|
1357
|
+
this.seen.set(path, mtimeMs);
|
|
1358
|
+
}
|
|
1359
|
+
/** The mtime last recorded for `path`, or `undefined` if never read. */
|
|
1360
|
+
expected(path) {
|
|
1361
|
+
return this.seen.get(path);
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
function evaluateReadBeforeWrite(tracker, path, currentMtimeMs) {
|
|
1365
|
+
if (currentMtimeMs === null) return "ok";
|
|
1366
|
+
const recorded = tracker.expected(path);
|
|
1367
|
+
if (recorded === void 0) return "read_required";
|
|
1368
|
+
if (recorded !== currentMtimeMs) return "stale";
|
|
1369
|
+
return "ok";
|
|
1370
|
+
}
|
|
1330
1371
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
1331
1372
|
var DEFAULT_MAX_STDOUT_BYTES2 = 10 * 1024 * 1024;
|
|
1332
1373
|
function createRunVitestTool(opts) {
|
|
@@ -1934,40 +1975,136 @@ function createBraveWebSearchAdapter(opts = {}) {
|
|
|
1934
1975
|
}));
|
|
1935
1976
|
};
|
|
1936
1977
|
}
|
|
1978
|
+
|
|
1979
|
+
// src/web-search-http.ts
|
|
1980
|
+
function createGenericHttpSearchAdapter(opts = {}) {
|
|
1981
|
+
const apiKey = opts.apiKey ?? process.env.THEOKIT_SEARCH_API_KEY;
|
|
1982
|
+
const endpoint = opts.endpoint ?? process.env.THEOKIT_SEARCH_API_URL;
|
|
1983
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
1984
|
+
return async (query, maxResults) => {
|
|
1985
|
+
if (!apiKey || !endpoint) return [];
|
|
1986
|
+
try {
|
|
1987
|
+
const url = `${endpoint}?q=${encodeURIComponent(query)}&n=${maxResults}`;
|
|
1988
|
+
const res = await fetchImpl(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
1989
|
+
if (!res.ok) return [];
|
|
1990
|
+
const data = await res.json();
|
|
1991
|
+
return (data?.results ?? []).slice(0, maxResults).map((r) => ({
|
|
1992
|
+
title: String(r?.title ?? ""),
|
|
1993
|
+
url: String(r?.url ?? ""),
|
|
1994
|
+
snippet: String(r?.snippet ?? "")
|
|
1995
|
+
}));
|
|
1996
|
+
} catch {
|
|
1997
|
+
return [];
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
1937
2001
|
var BINARY_PROBE_BYTES3 = 8 * 1024;
|
|
1938
2002
|
function createWriteFileTool(opts) {
|
|
1939
|
-
const { projectRoot } = opts;
|
|
2003
|
+
const { projectRoot, filesystem: filesystem$1 } = opts;
|
|
2004
|
+
if (opts.requireReadBeforeWrite && !opts.readTracker) {
|
|
2005
|
+
throw new Error(
|
|
2006
|
+
"createWriteFileTool: requireReadBeforeWrite is true but no readTracker was provided \u2014 pass the same ReadTracker instance to createReadFileTool and createWriteFileTool."
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
const guard = opts.requireReadBeforeWrite ? opts.readTracker : void 0;
|
|
1940
2010
|
return sdk.defineTool({
|
|
1941
2011
|
name: "write_file",
|
|
1942
|
-
description: "Write UTF-8 content to a project-relative file, creating parent directories as needed. OVERWRITES any existing file at the path. Prefer editing an existing file with edit_file over rewriting it; use write_file to create a NEW file or fully replace a small one. If the file already exists, read_file it first so you do not discard content you have not seen. Refuses paths that escape the
|
|
2012
|
+
description: "Write UTF-8 content to a project-relative file, creating parent directories as needed. OVERWRITES any existing file at the path. Prefer editing an existing file with edit_file over rewriting it; use write_file to create a NEW file or fully replace a small one. If the file already exists, read_file it first so you do not discard content you have not seen. Refuses paths that escape the write root and sensitive files (.env, .git/, node_modules/, .theo/, lock files); the default local root also refuses binary-file overwrites. Returns { ok, path, bytes } or { ok: false, error }.",
|
|
1943
2013
|
inputSchema: zod.z.object({
|
|
1944
2014
|
path: zod.z.string().min(1).describe("Project-relative file path."),
|
|
1945
2015
|
content: zod.z.string().describe("UTF-8 content to write.")
|
|
1946
2016
|
}),
|
|
1947
|
-
handler: async ({ path
|
|
1948
|
-
if (isForbiddenPath(path
|
|
1949
|
-
return JSON.stringify({ ok: false, error: "forbidden_path", path
|
|
1950
|
-
}
|
|
1951
|
-
let absolutePath;
|
|
1952
|
-
try {
|
|
1953
|
-
absolutePath = safePathJoin(projectRoot, path$1);
|
|
1954
|
-
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
1955
|
-
} catch (err) {
|
|
1956
|
-
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
1957
|
-
return JSON.stringify({ ok: false, error: "path_traversal", path: path$1 });
|
|
1958
|
-
}
|
|
1959
|
-
throw err;
|
|
2017
|
+
handler: async ({ path, content }, ctx) => {
|
|
2018
|
+
if (isForbiddenPath(path)) {
|
|
2019
|
+
return JSON.stringify({ ok: false, error: "forbidden_path", path });
|
|
1960
2020
|
}
|
|
1961
|
-
if (
|
|
1962
|
-
|
|
2021
|
+
if (filesystem$1) {
|
|
2022
|
+
const backend = await filesystem.resolveFilesystem(filesystem$1, ctx ?? {});
|
|
2023
|
+
return writeViaBackend(backend, path, content, guard);
|
|
1963
2024
|
}
|
|
1964
|
-
|
|
1965
|
-
await promises.writeFile(absolutePath, content, "utf-8");
|
|
1966
|
-
const bytes = Buffer.byteLength(content, "utf-8");
|
|
1967
|
-
return JSON.stringify({ ok: true, path: path$1, bytes });
|
|
2025
|
+
return writeViaLocalFs(projectRoot, path, content, guard);
|
|
1968
2026
|
}
|
|
1969
2027
|
});
|
|
1970
2028
|
}
|
|
2029
|
+
function readBeforeWriteError(guard, path, currentMtimeMs) {
|
|
2030
|
+
if (!guard) return null;
|
|
2031
|
+
const decision = evaluateReadBeforeWrite(guard, path, currentMtimeMs);
|
|
2032
|
+
if (decision === "read_required")
|
|
2033
|
+
return JSON.stringify({ ok: false, error: "read_required", path });
|
|
2034
|
+
if (decision === "stale") return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2035
|
+
return null;
|
|
2036
|
+
}
|
|
2037
|
+
async function writeViaLocalFs(projectRoot, path$1, content, guard) {
|
|
2038
|
+
let absolutePath;
|
|
2039
|
+
try {
|
|
2040
|
+
absolutePath = safePathJoin(projectRoot, path$1);
|
|
2041
|
+
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
2042
|
+
} catch (err) {
|
|
2043
|
+
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
2044
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path: path$1 });
|
|
2045
|
+
}
|
|
2046
|
+
throw err;
|
|
2047
|
+
}
|
|
2048
|
+
const rbw = readBeforeWriteError(guard, path$1, await statMtimeOrNull(absolutePath));
|
|
2049
|
+
if (rbw) return rbw;
|
|
2050
|
+
if (await isBinaryFile(absolutePath)) {
|
|
2051
|
+
return JSON.stringify({ ok: false, error: "binary_file", path: path$1 });
|
|
2052
|
+
}
|
|
2053
|
+
await promises.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
2054
|
+
await promises.writeFile(absolutePath, content, "utf-8");
|
|
2055
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
2056
|
+
return JSON.stringify({ ok: true, path: path$1, bytes });
|
|
2057
|
+
}
|
|
2058
|
+
async function statMtimeOrNull(absolutePath) {
|
|
2059
|
+
try {
|
|
2060
|
+
return (await promises.stat(absolutePath)).mtimeMs;
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
if (err.code === "ENOENT") return null;
|
|
2063
|
+
throw err;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
async function backendMtimeOrNull(backend, path) {
|
|
2067
|
+
try {
|
|
2068
|
+
return (await backend.stat(path)).mtimeMs;
|
|
2069
|
+
} catch (err) {
|
|
2070
|
+
if (err instanceof filesystem.FileNotFoundError) return null;
|
|
2071
|
+
throw err;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
async function writeViaBackend(backend, path, content, guard) {
|
|
2075
|
+
try {
|
|
2076
|
+
let expectedMtime;
|
|
2077
|
+
if (guard) {
|
|
2078
|
+
const current = await backendMtimeOrNull(backend, path);
|
|
2079
|
+
const rbw = readBeforeWriteError(guard, path, current);
|
|
2080
|
+
if (rbw) return rbw;
|
|
2081
|
+
expectedMtime = current ?? void 0;
|
|
2082
|
+
}
|
|
2083
|
+
const stat2 = await backend.writeFile(
|
|
2084
|
+
path,
|
|
2085
|
+
content,
|
|
2086
|
+
expectedMtime !== void 0 ? { expectedMtime } : void 0
|
|
2087
|
+
);
|
|
2088
|
+
return JSON.stringify({ ok: true, path, bytes: stat2.size });
|
|
2089
|
+
} catch (err) {
|
|
2090
|
+
return backendErrorToJson(err, path);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
function backendErrorToJson(err, path) {
|
|
2094
|
+
if (err instanceof filesystem.FilesystemSecurityError) {
|
|
2095
|
+
return JSON.stringify({ ok: false, error: "path_traversal", path });
|
|
2096
|
+
}
|
|
2097
|
+
if (err instanceof filesystem.FilesystemReadOnlyError) {
|
|
2098
|
+
return JSON.stringify({ ok: false, error: "read_only", path });
|
|
2099
|
+
}
|
|
2100
|
+
if (err instanceof filesystem.StaleFileError) {
|
|
2101
|
+
return JSON.stringify({ ok: false, error: "stale_file", path });
|
|
2102
|
+
}
|
|
2103
|
+
if (err instanceof filesystem.FilesystemError) {
|
|
2104
|
+
return JSON.stringify({ ok: false, error: "write_failed", path });
|
|
2105
|
+
}
|
|
2106
|
+
throw err;
|
|
2107
|
+
}
|
|
1971
2108
|
async function isBinaryFile(absolutePath) {
|
|
1972
2109
|
let handle;
|
|
1973
2110
|
try {
|
|
@@ -1992,6 +2129,7 @@ async function isBinaryFile(absolutePath) {
|
|
|
1992
2129
|
|
|
1993
2130
|
exports.CatastrophicCommandError = CatastrophicCommandError;
|
|
1994
2131
|
exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
|
|
2132
|
+
exports.ReadTracker = ReadTracker;
|
|
1995
2133
|
exports.RedirectBlockedError = RedirectBlockedError;
|
|
1996
2134
|
exports.SsrfBlockedError = SsrfBlockedError;
|
|
1997
2135
|
exports.buildEnvContext = buildEnvContext;
|
|
@@ -2001,6 +2139,7 @@ exports.commandDenialReason = commandDenialReason;
|
|
|
2001
2139
|
exports.createApplyPatchTool = createApplyPatchTool;
|
|
2002
2140
|
exports.createBraveWebSearchAdapter = createBraveWebSearchAdapter;
|
|
2003
2141
|
exports.createEditFileTool = createEditFileTool;
|
|
2142
|
+
exports.createGenericHttpSearchAdapter = createGenericHttpSearchAdapter;
|
|
2004
2143
|
exports.createGitDiffTool = createGitDiffTool;
|
|
2005
2144
|
exports.createGlobTool = createGlobTool;
|
|
2006
2145
|
exports.createListDirTool = createListDirTool;
|