bashkit 0.2.3 → 0.2.4
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.js +147 -129
- package/dist/types.d.ts +1 -4
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -991,6 +991,7 @@ function createGlobTool(sandbox, config) {
|
|
|
991
991
|
// src/tools/grep.ts
|
|
992
992
|
import { tool as tool7, zodSchema as zodSchema7 } from "ai";
|
|
993
993
|
import { z as z7 } from "zod";
|
|
994
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
994
995
|
var grepInputSchema = z7.object({
|
|
995
996
|
pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
|
|
996
997
|
path: z7.string().optional().describe("File or directory to search in (defaults to cwd)"),
|
|
@@ -1004,13 +1005,13 @@ var grepInputSchema = z7.object({
|
|
|
1004
1005
|
"-C": z7.number().optional().describe("Number of lines to show before and after each match. Requires output_mode: 'content'."),
|
|
1005
1006
|
head_limit: z7.number().optional().describe("Limit output to first N lines/entries. Works across all output modes. Defaults to 0 (unlimited)."),
|
|
1006
1007
|
offset: z7.number().optional().describe("Skip first N lines/entries before applying head_limit. Works across all output modes. Defaults to 0."),
|
|
1007
|
-
multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines
|
|
1008
|
+
multiline: z7.boolean().optional().describe("Enable multiline mode where patterns can span lines. Default: false.")
|
|
1008
1009
|
});
|
|
1009
|
-
var GREP_DESCRIPTION = `A powerful content search tool with regex support.
|
|
1010
|
+
var GREP_DESCRIPTION = `A powerful content search tool built on ripgrep with regex support.
|
|
1010
1011
|
|
|
1011
1012
|
**Usage:**
|
|
1012
1013
|
- ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command.
|
|
1013
|
-
- Supports regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
1014
|
+
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
1014
1015
|
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
|
|
1015
1016
|
|
|
1016
1017
|
**Output modes:**
|
|
@@ -1025,11 +1026,8 @@ var GREP_DESCRIPTION = `A powerful content search tool with regex support. Use t
|
|
|
1025
1026
|
|
|
1026
1027
|
**Pagination:**
|
|
1027
1028
|
- Use offset to skip results (useful for pagination)
|
|
1028
|
-
- Use head_limit to limit total results returned
|
|
1029
|
-
|
|
1030
|
-
**Note:** Set useRipgrep: true in config for better performance and multiline support (requires ripgrep installed).`;
|
|
1029
|
+
- Use head_limit to limit total results returned`;
|
|
1031
1030
|
function createGrepTool(sandbox, config) {
|
|
1032
|
-
const useRipgrep = config?.useRipgrep ?? false;
|
|
1033
1031
|
return tool7({
|
|
1034
1032
|
description: GREP_DESCRIPTION,
|
|
1035
1033
|
inputSchema: zodSchema7(grepInputSchema),
|
|
@@ -1044,7 +1042,6 @@ function createGrepTool(sandbox, config) {
|
|
|
1044
1042
|
type,
|
|
1045
1043
|
output_mode = "files_with_matches",
|
|
1046
1044
|
"-i": caseInsensitive,
|
|
1047
|
-
"-n": showLineNumbers = true,
|
|
1048
1045
|
"-B": beforeContext,
|
|
1049
1046
|
"-A": afterContext,
|
|
1050
1047
|
"-C": context,
|
|
@@ -1059,98 +1056,26 @@ function createGrepTool(sandbox, config) {
|
|
|
1059
1056
|
return { error: `Path not allowed: ${searchPath}` };
|
|
1060
1057
|
}
|
|
1061
1058
|
}
|
|
1062
|
-
if (multiline && !useRipgrep) {
|
|
1063
|
-
return {
|
|
1064
|
-
error: "Multiline mode requires ripgrep. Set useRipgrep: true in config."
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
1059
|
try {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
output_mode,
|
|
1081
|
-
caseInsensitive,
|
|
1082
|
-
showLineNumbers,
|
|
1083
|
-
beforeContext,
|
|
1084
|
-
afterContext,
|
|
1085
|
-
context,
|
|
1086
|
-
glob,
|
|
1087
|
-
type,
|
|
1088
|
-
multiline,
|
|
1089
|
-
paginationSuffix
|
|
1090
|
-
});
|
|
1091
|
-
} else {
|
|
1092
|
-
cmd = buildGrepCommand({
|
|
1093
|
-
pattern,
|
|
1094
|
-
searchPath,
|
|
1095
|
-
output_mode,
|
|
1096
|
-
caseInsensitive,
|
|
1097
|
-
showLineNumbers,
|
|
1098
|
-
beforeContext,
|
|
1099
|
-
afterContext,
|
|
1100
|
-
context,
|
|
1101
|
-
glob,
|
|
1102
|
-
type,
|
|
1103
|
-
paginationSuffix
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1060
|
+
const cmd = buildRipgrepCommand({
|
|
1061
|
+
pattern,
|
|
1062
|
+
searchPath,
|
|
1063
|
+
output_mode,
|
|
1064
|
+
caseInsensitive,
|
|
1065
|
+
beforeContext,
|
|
1066
|
+
afterContext,
|
|
1067
|
+
context,
|
|
1068
|
+
glob,
|
|
1069
|
+
type,
|
|
1070
|
+
multiline
|
|
1071
|
+
});
|
|
1106
1072
|
const result = await sandbox.exec(cmd, { timeout: config?.timeout });
|
|
1107
1073
|
if (output_mode === "files_with_matches") {
|
|
1108
|
-
|
|
1109
|
-
`).filter(Boolean);
|
|
1110
|
-
return {
|
|
1111
|
-
files,
|
|
1112
|
-
count: files.length
|
|
1113
|
-
};
|
|
1074
|
+
return parseFilesOutput(result.stdout);
|
|
1114
1075
|
} else if (output_mode === "count") {
|
|
1115
|
-
|
|
1116
|
-
`).filter(Boolean);
|
|
1117
|
-
const counts = lines.map((line) => {
|
|
1118
|
-
const lastColon = line.lastIndexOf(":");
|
|
1119
|
-
return {
|
|
1120
|
-
file: line.slice(0, lastColon),
|
|
1121
|
-
count: parseInt(line.slice(lastColon + 1), 10)
|
|
1122
|
-
};
|
|
1123
|
-
});
|
|
1124
|
-
const total = counts.reduce((sum, c) => sum + c.count, 0);
|
|
1125
|
-
return {
|
|
1126
|
-
counts,
|
|
1127
|
-
total
|
|
1128
|
-
};
|
|
1076
|
+
return parseCountOutput(result.stdout);
|
|
1129
1077
|
} else {
|
|
1130
|
-
|
|
1131
|
-
return {
|
|
1132
|
-
matches: [],
|
|
1133
|
-
total_matches: 0
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
const lines = result.stdout.split(`
|
|
1137
|
-
`).filter(Boolean);
|
|
1138
|
-
const matches = [];
|
|
1139
|
-
for (const line of lines) {
|
|
1140
|
-
const colonMatch = line.match(/^(.+?):(\d+)[:|-](.*)$/);
|
|
1141
|
-
if (colonMatch) {
|
|
1142
|
-
const [, file, lineNum, content] = colonMatch;
|
|
1143
|
-
matches.push({
|
|
1144
|
-
file,
|
|
1145
|
-
line_number: parseInt(lineNum, 10),
|
|
1146
|
-
line: content
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return {
|
|
1151
|
-
matches,
|
|
1152
|
-
total_matches: matches.length
|
|
1153
|
-
};
|
|
1078
|
+
return parseContentOutput(result.stdout, head_limit, offset);
|
|
1154
1079
|
}
|
|
1155
1080
|
} catch (error) {
|
|
1156
1081
|
return {
|
|
@@ -1161,14 +1086,12 @@ function createGrepTool(sandbox, config) {
|
|
|
1161
1086
|
});
|
|
1162
1087
|
}
|
|
1163
1088
|
function buildRipgrepCommand(opts) {
|
|
1164
|
-
const flags = [];
|
|
1089
|
+
const flags = ["--json"];
|
|
1165
1090
|
if (opts.caseInsensitive)
|
|
1166
1091
|
flags.push("-i");
|
|
1167
1092
|
if (opts.multiline)
|
|
1168
1093
|
flags.push("-U", "--multiline-dotall");
|
|
1169
1094
|
if (opts.output_mode === "content") {
|
|
1170
|
-
if (opts.showLineNumbers)
|
|
1171
|
-
flags.push("-n");
|
|
1172
1095
|
if (opts.context) {
|
|
1173
1096
|
flags.push(`-C ${opts.context}`);
|
|
1174
1097
|
} else {
|
|
@@ -1183,42 +1106,137 @@ function buildRipgrepCommand(opts) {
|
|
|
1183
1106
|
if (opts.type)
|
|
1184
1107
|
flags.push(`-t ${opts.type}`);
|
|
1185
1108
|
const flagStr = flags.join(" ");
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1109
|
+
return `${rgPath} ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null`;
|
|
1110
|
+
}
|
|
1111
|
+
function parseFilesOutput(stdout) {
|
|
1112
|
+
const files = new Set;
|
|
1113
|
+
for (const line of stdout.split(`
|
|
1114
|
+
`).filter(Boolean)) {
|
|
1115
|
+
try {
|
|
1116
|
+
const msg = JSON.parse(line);
|
|
1117
|
+
if (msg.type === "begin") {
|
|
1118
|
+
const data = msg.data;
|
|
1119
|
+
files.add(data.path.text);
|
|
1120
|
+
}
|
|
1121
|
+
} catch {}
|
|
1192
1122
|
}
|
|
1123
|
+
return {
|
|
1124
|
+
files: Array.from(files),
|
|
1125
|
+
count: files.size
|
|
1126
|
+
};
|
|
1193
1127
|
}
|
|
1194
|
-
function
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1128
|
+
function parseCountOutput(stdout) {
|
|
1129
|
+
const counts = new Map;
|
|
1130
|
+
for (const line of stdout.split(`
|
|
1131
|
+
`).filter(Boolean)) {
|
|
1132
|
+
try {
|
|
1133
|
+
const msg = JSON.parse(line);
|
|
1134
|
+
if (msg.type === "end") {
|
|
1135
|
+
const data = msg.data;
|
|
1136
|
+
counts.set(data.path.text, data.stats.matches);
|
|
1137
|
+
}
|
|
1138
|
+
} catch {}
|
|
1139
|
+
}
|
|
1140
|
+
const countsArray = Array.from(counts.entries()).map(([file, count]) => ({
|
|
1141
|
+
file,
|
|
1142
|
+
count
|
|
1143
|
+
}));
|
|
1144
|
+
const total = countsArray.reduce((sum, c) => sum + c.count, 0);
|
|
1145
|
+
return {
|
|
1146
|
+
counts: countsArray,
|
|
1147
|
+
total
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function parseContentOutput(stdout, head_limit, offset = 0) {
|
|
1151
|
+
const fileData = new Map;
|
|
1152
|
+
for (const line of stdout.split(`
|
|
1153
|
+
`).filter(Boolean)) {
|
|
1154
|
+
try {
|
|
1155
|
+
const msg = JSON.parse(line);
|
|
1156
|
+
if (msg.type === "begin") {
|
|
1157
|
+
const data = msg.data;
|
|
1158
|
+
fileData.set(data.path.text, { matches: [], contexts: [] });
|
|
1159
|
+
} else if (msg.type === "context") {
|
|
1160
|
+
const data = msg.data;
|
|
1161
|
+
const fd = fileData.get(data.path.text);
|
|
1162
|
+
if (fd) {
|
|
1163
|
+
fd.contexts.push({
|
|
1164
|
+
line_number: data.line_number,
|
|
1165
|
+
text: data.lines.text.replace(/\n$/, "")
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
} else if (msg.type === "match") {
|
|
1169
|
+
const data = msg.data;
|
|
1170
|
+
const fd = fileData.get(data.path.text);
|
|
1171
|
+
if (fd) {
|
|
1172
|
+
fd.matches.push({
|
|
1173
|
+
line_number: data.line_number,
|
|
1174
|
+
text: data.lines.text.replace(/\n$/, "")
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
} catch {}
|
|
1179
|
+
}
|
|
1180
|
+
const allMatches = [];
|
|
1181
|
+
for (const [file, { matches, contexts }] of fileData) {
|
|
1182
|
+
matches.sort((a, b) => a.line_number - b.line_number);
|
|
1183
|
+
contexts.sort((a, b) => a.line_number - b.line_number);
|
|
1184
|
+
const matchContexts = new Map;
|
|
1185
|
+
for (const match of matches) {
|
|
1186
|
+
matchContexts.set(match.line_number, { before: [], after: [] });
|
|
1187
|
+
}
|
|
1188
|
+
for (const ctx of contexts) {
|
|
1189
|
+
let bestMatch = null;
|
|
1190
|
+
let bestDistance = Infinity;
|
|
1191
|
+
let isBefore = false;
|
|
1192
|
+
for (const match of matches) {
|
|
1193
|
+
const distance = Math.abs(ctx.line_number - match.line_number);
|
|
1194
|
+
if (distance < bestDistance) {
|
|
1195
|
+
bestDistance = distance;
|
|
1196
|
+
bestMatch = match;
|
|
1197
|
+
isBefore = ctx.line_number < match.line_number;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
if (bestMatch) {
|
|
1201
|
+
const mc = matchContexts.get(bestMatch.line_number);
|
|
1202
|
+
if (mc) {
|
|
1203
|
+
if (isBefore) {
|
|
1204
|
+
mc.before.push(ctx.text);
|
|
1205
|
+
} else {
|
|
1206
|
+
mc.after.push(ctx.text);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
for (const match of matches) {
|
|
1212
|
+
const mc = matchContexts.get(match.line_number);
|
|
1213
|
+
allMatches.push({
|
|
1214
|
+
file,
|
|
1215
|
+
line_number: match.line_number,
|
|
1216
|
+
line: match.text,
|
|
1217
|
+
before_context: mc?.before ?? [],
|
|
1218
|
+
after_context: mc?.after ?? []
|
|
1219
|
+
});
|
|
1208
1220
|
}
|
|
1209
1221
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1222
|
+
const grepMatches = allMatches.map((m) => ({
|
|
1223
|
+
file: m.file,
|
|
1224
|
+
line_number: m.line_number,
|
|
1225
|
+
line: m.line,
|
|
1226
|
+
before_context: m.before_context.length > 0 ? m.before_context : undefined,
|
|
1227
|
+
after_context: m.after_context.length > 0 ? m.after_context : undefined
|
|
1228
|
+
}));
|
|
1229
|
+
let result = grepMatches;
|
|
1230
|
+
if (offset > 0) {
|
|
1231
|
+
result = result.slice(offset);
|
|
1232
|
+
}
|
|
1233
|
+
if (head_limit && head_limit > 0) {
|
|
1234
|
+
result = result.slice(0, head_limit);
|
|
1221
1235
|
}
|
|
1236
|
+
return {
|
|
1237
|
+
matches: result,
|
|
1238
|
+
total_matches: result.length
|
|
1239
|
+
};
|
|
1222
1240
|
}
|
|
1223
1241
|
|
|
1224
1242
|
// src/tools/read.ts
|
package/dist/types.d.ts
CHANGED
|
@@ -21,10 +21,7 @@ export type ToolConfig = {
|
|
|
21
21
|
allowedPaths?: string[];
|
|
22
22
|
blockedCommands?: string[];
|
|
23
23
|
} & SDKToolOptions;
|
|
24
|
-
export type GrepToolConfig = ToolConfig
|
|
25
|
-
/** Use ripgrep (rg) instead of grep. Requires ripgrep to be installed. Default: false */
|
|
26
|
-
useRipgrep?: boolean;
|
|
27
|
-
};
|
|
24
|
+
export type GrepToolConfig = ToolConfig;
|
|
28
25
|
/**
|
|
29
26
|
* Supported web search providers.
|
|
30
27
|
* Currently only 'parallel' is implemented.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bashkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Agentic coding tools for the Vercel AI SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "bun run src/index.ts",
|
|
24
24
|
"build": "bun run build:js && bun run build:cli && bun run build:types",
|
|
25
|
-
"build:js": "bun build src/index.ts --outdir dist --target node --format esm --external ai --external zod --external @ai-sdk/* --external @vercel/sandbox --external @e2b/code-interpreter --external parallel-web",
|
|
25
|
+
"build:js": "bun build src/index.ts --outdir dist --target node --format esm --external ai --external zod --external @ai-sdk/* --external @vercel/sandbox --external @e2b/code-interpreter --external parallel-web --external @vscode/ripgrep",
|
|
26
26
|
"build:cli": "bun build src/cli/init.ts --outdir dist/cli --target node --format esm --external @clack/prompts && chmod +x dist/cli/init.js",
|
|
27
27
|
"build:types": "tsc -p tsconfig.build.json",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"url": "https://github.com/jbreite/bashkit"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@clack/prompts": "^0.7.0"
|
|
54
|
+
"@clack/prompts": "^0.7.0",
|
|
55
|
+
"@vscode/ripgrep": "^1.17.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@ai-sdk/anthropic": "^3.0.1",
|