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 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 (requires ripgrep). Default: false.")
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. Use this instead of running grep commands directly.
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
- let paginationSuffix = "";
1069
- if (offset > 0) {
1070
- paginationSuffix += ` | tail -n +${offset + 1}`;
1071
- }
1072
- if (head_limit && head_limit > 0) {
1073
- paginationSuffix += ` | head -${head_limit}`;
1074
- }
1075
- let cmd;
1076
- if (useRipgrep) {
1077
- cmd = buildRipgrepCommand({
1078
- pattern,
1079
- searchPath,
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
- const files = result.stdout.split(`
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
- const lines = result.stdout.split(`
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
- if (!result.stdout.trim()) {
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
- if (opts.output_mode === "files_with_matches") {
1187
- return `rg -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1188
- } else if (opts.output_mode === "count") {
1189
- return `rg -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1190
- } else {
1191
- return `rg ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
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 buildGrepCommand(opts) {
1195
- const flags = ["-r"];
1196
- if (opts.caseInsensitive)
1197
- flags.push("-i");
1198
- if (opts.output_mode === "content") {
1199
- if (opts.showLineNumbers)
1200
- flags.push("-n");
1201
- if (opts.context) {
1202
- flags.push(`-C ${opts.context}`);
1203
- } else {
1204
- if (opts.beforeContext)
1205
- flags.push(`-B ${opts.beforeContext}`);
1206
- if (opts.afterContext)
1207
- flags.push(`-A ${opts.afterContext}`);
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
- if (opts.glob)
1211
- flags.push(`--include="${opts.glob}"`);
1212
- if (opts.type)
1213
- flags.push(`--include="*.${opts.type}"`);
1214
- const flagStr = flags.join(" ");
1215
- if (opts.output_mode === "files_with_matches") {
1216
- return `grep -l ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
1217
- } else if (opts.output_mode === "count") {
1218
- return `grep -c ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null | grep -v ':0$'${opts.paginationSuffix}`;
1219
- } else {
1220
- return `grep ${flagStr} "${opts.pattern}" ${opts.searchPath} 2>/dev/null${opts.paginationSuffix}`;
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",
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",