@mindstudio-ai/remy 0.1.232 → 0.1.234

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.
@@ -4,9 +4,9 @@
4
4
 
5
5
  This is an automated action triggered by the user pressing "Publish" in the editor.
6
6
 
7
- The user wants to deploy their app. Pushing to the `main` branch triggers a production deploy.
7
+ Pressing Publish is the user's decision to ship. The work is finished, and they've watched it come together in the preview throughout the session — now they're asking you to deploy it to `main` (which triggers a production build). Your job is to describe what's going out and ship it.
8
8
 
9
- Review the current state of the working tree — what has changed since the last commit, what's been committed since the last push, and the overall shape of recent work. Write a user-friendly changelog with `presentPublishPlan` summarize what changed in plain language ("added vendor approval workflow", "fixed invoice totals", "updated the dashboard layout"). Reference specific code or file paths only when it helps clarity. This is what the user will see before deploying.
9
+ Read what's changed since the last push the diffs and commits and turn it into a user-friendly changelog with `presentPublishPlan`: a plain-language summary of what's new ("added vendor approval workflow", "fixed invoice totals", "updated the dashboard layout"). Reference specific code or file paths only when it helps clarity. This is what the user sees before deploying.
10
10
 
11
11
  If approved:
12
12
  - Stage and commit any uncommitted changes with a clean, descriptive commit message. If the committed work resolves any open issues (`mindstudio-prod issues`), reference them in the commit message with a closing keyword — `fixes #42`, `closes #7` — so the deploy closes them automatically once it goes live.
package/dist/headless.js CHANGED
@@ -478,7 +478,8 @@ var ALLOWED_MODELS_BY_TYPE = {
478
478
  "glm-5.2",
479
479
  "muse-spark-1.1",
480
480
  "kimi-k2-7-code",
481
- "kimi-k3"
481
+ "kimi-k3",
482
+ "deepseek-v4-flash-0731"
482
483
  ]
483
484
  // vision: undefined — unconstrained
484
485
  // image_generation: undefined — unconstrained
@@ -1894,7 +1895,7 @@ var compactConversationTool = {
1894
1895
 
1895
1896
  // src/tools/code/readFile.ts
1896
1897
  import fs12 from "fs/promises";
1897
- var DEFAULT_MAX_LINES2 = 500;
1898
+ var DEFAULT_WINDOW = 500;
1898
1899
  function isBinary(buffer) {
1899
1900
  const sample = buffer.subarray(0, 8192);
1900
1901
  for (let i = 0; i < sample.length; i++) {
@@ -1908,7 +1909,7 @@ var readFileTool = {
1908
1909
  clearable: true,
1909
1910
  definition: {
1910
1911
  name: "readFile",
1911
- description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
1912
+ description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
1912
1913
  inputSchema: {
1913
1914
  type: "object",
1914
1915
  properties: {
@@ -1916,13 +1917,17 @@ var readFileTool = {
1916
1917
  type: "string",
1917
1918
  description: "The file path to read, relative to the project root."
1918
1919
  },
1919
- offset: {
1920
+ startLine: {
1920
1921
  type: "number",
1921
- description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
1922
+ description: "First line to read (1-indexed, inclusive). Defaults to 1. Pair with endLine to read an exact range \u2014 e.g. startLine: 253, endLine: 343 reads lines 253\u2013343."
1922
1923
  },
1923
- maxLines: {
1924
+ endLine: {
1924
1925
  type: "number",
1925
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
1926
+ description: "Last line to read (1-indexed, inclusive). Defaults to a 500-line window from startLine, capped at the end of the file. Use with startLine to read an exact range."
1927
+ },
1928
+ tail: {
1929
+ type: "number",
1930
+ description: "Read only the last N lines of the file (useful for logs). When set, startLine and endLine are ignored."
1926
1931
  }
1927
1932
  },
1928
1933
  required: ["path"]
@@ -1939,22 +1944,37 @@ var readFileTool = {
1939
1944
  const content = buffer.toString("utf-8");
1940
1945
  const allLines = content.split("\n");
1941
1946
  const totalLines = allLines.length;
1942
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
1947
+ const tail = input.tail != null ? Math.floor(Number(input.tail)) : void 0;
1943
1948
  let startIdx;
1944
- if (input.offset && input.offset < 0) {
1945
- startIdx = Math.max(0, totalLines + input.offset);
1949
+ let endIdxExclusive;
1950
+ if (tail != null && tail > 0) {
1951
+ startIdx = Math.max(0, totalLines - tail);
1952
+ endIdxExclusive = totalLines;
1946
1953
  } else {
1947
- startIdx = Math.max(0, (input.offset || 1) - 1);
1954
+ const startLine = Math.max(1, Math.floor(Number(input.startLine) || 1));
1955
+ startIdx = startLine - 1;
1956
+ if (startIdx >= totalLines) {
1957
+ return `${input.path} has ${totalLines} lines \u2014 startLine ${startLine} is past the end of the file.`;
1958
+ }
1959
+ if (input.endLine != null) {
1960
+ const endLine = Math.min(
1961
+ totalLines,
1962
+ Math.max(startLine, Math.floor(Number(input.endLine)))
1963
+ );
1964
+ endIdxExclusive = endLine;
1965
+ } else {
1966
+ endIdxExclusive = Math.min(startIdx + DEFAULT_WINDOW, totalLines);
1967
+ }
1948
1968
  }
1949
- const sliced = allLines.slice(startIdx, startIdx + maxLines);
1969
+ const sliced = allLines.slice(startIdx, endIdxExclusive);
1950
1970
  const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
1951
1971
  let result = numbered;
1952
- const endLine = startIdx + sliced.length;
1953
1972
  const displayStart = startIdx + 1;
1954
- if (endLine < totalLines) {
1973
+ const displayEnd = startIdx + sliced.length;
1974
+ if (displayStart > 1 || displayEnd < totalLines) {
1955
1975
  result += `
1956
1976
 
1957
- (showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
1977
+ (showing lines ${displayStart}\u2013${displayEnd} of ${totalLines} \u2014 pass startLine/endLine to read a different range)`;
1958
1978
  }
1959
1979
  return result;
1960
1980
  } catch (err) {
@@ -2122,7 +2142,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2122
2142
  // src/tools/code/bash.ts
2123
2143
  import { spawn as spawn2 } from "child_process";
2124
2144
  var DEFAULT_TIMEOUT_MS = 12e4;
2125
- var DEFAULT_MAX_LINES3 = 500;
2145
+ var DEFAULT_MAX_LINES2 = 500;
2126
2146
  var MAX_OUTPUT_BYTES = 3e4;
2127
2147
  var bashTool = {
2128
2148
  clearable: true,
@@ -2153,7 +2173,7 @@ var bashTool = {
2153
2173
  }
2154
2174
  },
2155
2175
  async execute(input, context) {
2156
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
2176
+ const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2157
2177
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
2158
2178
  return new Promise((resolve2) => {
2159
2179
  const child = spawn2("sh", ["-c", input.command], {
@@ -2226,8 +2246,16 @@ var bashTool = {
2226
2246
  // src/tools/code/grep.ts
2227
2247
  import { exec } from "child_process";
2228
2248
  var DEFAULT_MAX = 50;
2229
- function formatResults(stdout, max) {
2230
- const lines = stdout.trim().split("\n");
2249
+ function clampContext(v) {
2250
+ const n = Math.floor(Number(v));
2251
+ return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
2252
+ }
2253
+ function formatResults(stdout, max, mode) {
2254
+ const trimmed = stdout.trim();
2255
+ if (mode !== "content") {
2256
+ return trimmed;
2257
+ }
2258
+ const lines = trimmed.split("\n");
2231
2259
  let result = lines.join("\n");
2232
2260
  if (lines.length >= max) {
2233
2261
  result += `
@@ -2240,7 +2268,7 @@ var grepTool = {
2240
2268
  clearable: true,
2241
2269
  definition: {
2242
2270
  name: "grep",
2243
- description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
2271
+ description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. Set outputMode to 'count' for per-file match counts (like grep -c) or 'filesWithMatches' for just the file paths (like grep -l). Add context (like grep -C), or contextBefore/contextAfter (like grep -B/-A), to include surrounding lines. Set caseInsensitive (like grep -i) for a case-insensitive search. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
2244
2272
  inputSchema: {
2245
2273
  type: "object",
2246
2274
  properties: {
@@ -2259,6 +2287,27 @@ var grepTool = {
2259
2287
  maxResults: {
2260
2288
  type: "number",
2261
2289
  description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
2290
+ },
2291
+ outputMode: {
2292
+ type: "string",
2293
+ enum: ["content", "count", "filesWithMatches"],
2294
+ description: "What to return. 'content' (default): matching lines with line numbers. 'count': number of matches per file, like grep -c. 'filesWithMatches': just the paths of files that contain a match, like grep -l."
2295
+ },
2296
+ context: {
2297
+ type: "number",
2298
+ description: "Lines of context to show before AND after each match, like grep -C. Content mode only."
2299
+ },
2300
+ contextBefore: {
2301
+ type: "number",
2302
+ description: "Lines of context before each match, like grep -B. Content mode only; ignored if context is set."
2303
+ },
2304
+ contextAfter: {
2305
+ type: "number",
2306
+ description: "Lines of context after each match, like grep -A. Content mode only; ignored if context is set."
2307
+ },
2308
+ caseInsensitive: {
2309
+ type: "boolean",
2310
+ description: "Case-insensitive search, like grep -i."
2262
2311
  }
2263
2312
  },
2264
2313
  required: ["pattern"]
@@ -2266,20 +2315,55 @@ var grepTool = {
2266
2315
  },
2267
2316
  async execute(input) {
2268
2317
  const searchPath = input.path || ".";
2269
- const max = input.maxResults || DEFAULT_MAX;
2318
+ const max = Math.max(
2319
+ 1,
2320
+ Math.floor(Number(input.maxResults) || DEFAULT_MAX)
2321
+ );
2270
2322
  const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
2271
2323
  const escaped = input.pattern.replace(/'/g, "'\\''");
2272
- const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
2273
- const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
2324
+ const mode = input.outputMode === "count" || input.outputMode === "filesWithMatches" ? input.outputMode : "content";
2325
+ const ci = input.caseInsensitive ? " -i" : "";
2326
+ let ctx = "";
2327
+ if (mode === "content") {
2328
+ if (input.context != null) {
2329
+ const c = clampContext(input.context);
2330
+ if (c > 0) {
2331
+ ctx = ` -C ${c}`;
2332
+ }
2333
+ } else {
2334
+ const b = input.contextBefore != null ? clampContext(input.contextBefore) : 0;
2335
+ const a = input.contextAfter != null ? clampContext(input.contextAfter) : 0;
2336
+ if (b > 0) {
2337
+ ctx += ` -B ${b}`;
2338
+ }
2339
+ if (a > 0) {
2340
+ ctx += ` -A ${a}`;
2341
+ }
2342
+ }
2343
+ }
2344
+ let rgFlags;
2345
+ let grepFlags;
2346
+ if (mode === "count") {
2347
+ rgFlags = `--count${ci}`;
2348
+ grepFlags = `-rc${ci}`;
2349
+ } else if (mode === "filesWithMatches") {
2350
+ rgFlags = `-l${ci}`;
2351
+ grepFlags = `-rl${ci}`;
2352
+ } else {
2353
+ rgFlags = `-n --no-heading${ci}${ctx} --max-count=${max}`;
2354
+ grepFlags = `-rn${ci}${ctx} --max-count=${max}`;
2355
+ }
2356
+ const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
2357
+ const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
2274
2358
  return new Promise((resolve2) => {
2275
2359
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
2276
2360
  if (stdout?.trim()) {
2277
- resolve2(formatResults(stdout, max));
2361
+ resolve2(formatResults(stdout, max, mode));
2278
2362
  return;
2279
2363
  }
2280
2364
  exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
2281
2365
  if (grepStdout?.trim()) {
2282
- resolve2(formatResults(grepStdout, max));
2366
+ resolve2(formatResults(grepStdout, max, mode));
2283
2367
  } else {
2284
2368
  resolve2("No matches found.");
2285
2369
  }
@@ -3959,85 +4043,10 @@ var screenshotTool = {
3959
4043
 
3960
4044
  // src/subagents/common/tools.ts
3961
4045
  var COMMON_READ_TOOLS = [
3962
- {
3963
- name: "readFile",
3964
- description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
3965
- inputSchema: {
3966
- type: "object",
3967
- properties: {
3968
- path: {
3969
- type: "string",
3970
- description: "The file path to read, relative to the project root."
3971
- },
3972
- offset: {
3973
- type: "number",
3974
- description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
3975
- },
3976
- maxLines: {
3977
- type: "number",
3978
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
3979
- }
3980
- },
3981
- required: ["path"]
3982
- }
3983
- },
3984
- {
3985
- name: "listDir",
3986
- description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
3987
- inputSchema: {
3988
- type: "object",
3989
- properties: {
3990
- path: {
3991
- type: "string",
3992
- description: 'Directory path to list, relative to project root. Defaults to ".".'
3993
- }
3994
- }
3995
- }
3996
- },
3997
- {
3998
- name: "grep",
3999
- description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
4000
- inputSchema: {
4001
- type: "object",
4002
- properties: {
4003
- pattern: {
4004
- type: "string",
4005
- description: "The search pattern (regex supported)."
4006
- },
4007
- path: {
4008
- type: "string",
4009
- description: "Directory or file to search in. Defaults to current directory."
4010
- },
4011
- glob: {
4012
- type: "string",
4013
- description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
4014
- },
4015
- maxResults: {
4016
- type: "number",
4017
- description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
4018
- }
4019
- },
4020
- required: ["pattern"]
4021
- }
4022
- },
4023
- {
4024
- name: "glob",
4025
- description: 'Find files matching a glob pattern. Returns matching file paths sorted alphabetically (default 200 results). Use this to discover project structure, find files by name or extension, or check if a file exists. Common patterns: "**/*.ts" (all TypeScript files), "src/**/*.tsx" (React components in src), "*.json" (root-level JSON files). Automatically excludes node_modules and .git.',
4026
- inputSchema: {
4027
- type: "object",
4028
- properties: {
4029
- pattern: {
4030
- type: "string",
4031
- description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
4032
- },
4033
- maxResults: {
4034
- type: "number",
4035
- description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
4036
- }
4037
- },
4038
- required: ["pattern"]
4039
- }
4040
- }
4046
+ readFileTool.definition,
4047
+ listDirTool.definition,
4048
+ grepTool.definition,
4049
+ globTool.definition
4041
4050
  ];
4042
4051
  var COMMON_READ_TOOL_NAMES = new Set(
4043
4052
  COMMON_READ_TOOLS.map((t) => t.name)
package/dist/index.js CHANGED
@@ -2094,7 +2094,8 @@ var init_surfaces = __esm({
2094
2094
  "glm-5.2",
2095
2095
  "muse-spark-1.1",
2096
2096
  "kimi-k2-7-code",
2097
- "kimi-k3"
2097
+ "kimi-k3",
2098
+ "deepseek-v4-flash-0731"
2098
2099
  ]
2099
2100
  // vision: undefined — unconstrained
2100
2101
  // image_generation: undefined — unconstrained
@@ -2408,16 +2409,16 @@ function isBinary(buffer) {
2408
2409
  }
2409
2410
  return false;
2410
2411
  }
2411
- var DEFAULT_MAX_LINES2, readFileTool;
2412
+ var DEFAULT_WINDOW, readFileTool;
2412
2413
  var init_readFile = __esm({
2413
2414
  "src/tools/code/readFile.ts"() {
2414
2415
  "use strict";
2415
- DEFAULT_MAX_LINES2 = 500;
2416
+ DEFAULT_WINDOW = 500;
2416
2417
  readFileTool = {
2417
2418
  clearable: true,
2418
2419
  definition: {
2419
2420
  name: "readFile",
2420
- description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
2421
+ description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. By default returns the first 500 lines. To read a specific range, pass startLine and endLine (1-indexed, inclusive) \u2014 e.g. to read lines 253\u2013343, pass startLine: 253, endLine: 343. To read the end of a file or log, pass tail (the number of lines from the end). Line numbers in the output correspond to what editFile expects. For a large file, locate the relevant section first (symbols or grep), then read just that range.",
2421
2422
  inputSchema: {
2422
2423
  type: "object",
2423
2424
  properties: {
@@ -2425,13 +2426,17 @@ var init_readFile = __esm({
2425
2426
  type: "string",
2426
2427
  description: "The file path to read, relative to the project root."
2427
2428
  },
2428
- offset: {
2429
+ startLine: {
2429
2430
  type: "number",
2430
- description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
2431
+ description: "First line to read (1-indexed, inclusive). Defaults to 1. Pair with endLine to read an exact range \u2014 e.g. startLine: 253, endLine: 343 reads lines 253\u2013343."
2431
2432
  },
2432
- maxLines: {
2433
+ endLine: {
2433
2434
  type: "number",
2434
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
2435
+ description: "Last line to read (1-indexed, inclusive). Defaults to a 500-line window from startLine, capped at the end of the file. Use with startLine to read an exact range."
2436
+ },
2437
+ tail: {
2438
+ type: "number",
2439
+ description: "Read only the last N lines of the file (useful for logs). When set, startLine and endLine are ignored."
2435
2440
  }
2436
2441
  },
2437
2442
  required: ["path"]
@@ -2448,22 +2453,37 @@ var init_readFile = __esm({
2448
2453
  const content = buffer.toString("utf-8");
2449
2454
  const allLines = content.split("\n");
2450
2455
  const totalLines = allLines.length;
2451
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2456
+ const tail = input.tail != null ? Math.floor(Number(input.tail)) : void 0;
2452
2457
  let startIdx;
2453
- if (input.offset && input.offset < 0) {
2454
- startIdx = Math.max(0, totalLines + input.offset);
2458
+ let endIdxExclusive;
2459
+ if (tail != null && tail > 0) {
2460
+ startIdx = Math.max(0, totalLines - tail);
2461
+ endIdxExclusive = totalLines;
2455
2462
  } else {
2456
- startIdx = Math.max(0, (input.offset || 1) - 1);
2463
+ const startLine = Math.max(1, Math.floor(Number(input.startLine) || 1));
2464
+ startIdx = startLine - 1;
2465
+ if (startIdx >= totalLines) {
2466
+ return `${input.path} has ${totalLines} lines \u2014 startLine ${startLine} is past the end of the file.`;
2467
+ }
2468
+ if (input.endLine != null) {
2469
+ const endLine = Math.min(
2470
+ totalLines,
2471
+ Math.max(startLine, Math.floor(Number(input.endLine)))
2472
+ );
2473
+ endIdxExclusive = endLine;
2474
+ } else {
2475
+ endIdxExclusive = Math.min(startIdx + DEFAULT_WINDOW, totalLines);
2476
+ }
2457
2477
  }
2458
- const sliced = allLines.slice(startIdx, startIdx + maxLines);
2478
+ const sliced = allLines.slice(startIdx, endIdxExclusive);
2459
2479
  const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
2460
2480
  let result = numbered;
2461
- const endLine = startIdx + sliced.length;
2462
2481
  const displayStart = startIdx + 1;
2463
- if (endLine < totalLines) {
2482
+ const displayEnd = startIdx + sliced.length;
2483
+ if (displayStart > 1 || displayEnd < totalLines) {
2464
2484
  result += `
2465
2485
 
2466
- (showing lines ${displayStart}\u2013${endLine} of ${totalLines} \u2014 use offset and maxLines to read more)`;
2486
+ (showing lines ${displayStart}\u2013${displayEnd} of ${totalLines} \u2014 pass startLine/endLine to read a different range)`;
2467
2487
  }
2468
2488
  return result;
2469
2489
  } catch (err) {
@@ -2649,12 +2669,12 @@ ${unifiedDiff(input.path, content, updated)}`;
2649
2669
 
2650
2670
  // src/tools/code/bash.ts
2651
2671
  import { spawn as spawn2 } from "child_process";
2652
- var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES3, MAX_OUTPUT_BYTES, bashTool;
2672
+ var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES2, MAX_OUTPUT_BYTES, bashTool;
2653
2673
  var init_bash = __esm({
2654
2674
  "src/tools/code/bash.ts"() {
2655
2675
  "use strict";
2656
2676
  DEFAULT_TIMEOUT_MS = 12e4;
2657
- DEFAULT_MAX_LINES3 = 500;
2677
+ DEFAULT_MAX_LINES2 = 500;
2658
2678
  MAX_OUTPUT_BYTES = 3e4;
2659
2679
  bashTool = {
2660
2680
  clearable: true,
@@ -2685,7 +2705,7 @@ var init_bash = __esm({
2685
2705
  }
2686
2706
  },
2687
2707
  async execute(input, context) {
2688
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
2708
+ const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2689
2709
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
2690
2710
  return new Promise((resolve2) => {
2691
2711
  const child = spawn2("sh", ["-c", input.command], {
@@ -2759,8 +2779,16 @@ var init_bash = __esm({
2759
2779
 
2760
2780
  // src/tools/code/grep.ts
2761
2781
  import { exec } from "child_process";
2762
- function formatResults(stdout, max) {
2763
- const lines = stdout.trim().split("\n");
2782
+ function clampContext(v) {
2783
+ const n = Math.floor(Number(v));
2784
+ return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : 0;
2785
+ }
2786
+ function formatResults(stdout, max, mode) {
2787
+ const trimmed = stdout.trim();
2788
+ if (mode !== "content") {
2789
+ return trimmed;
2790
+ }
2791
+ const lines = trimmed.split("\n");
2764
2792
  let result = lines.join("\n");
2765
2793
  if (lines.length >= max) {
2766
2794
  result += `
@@ -2778,7 +2806,7 @@ var init_grep = __esm({
2778
2806
  clearable: true,
2779
2807
  definition: {
2780
2808
  name: "grep",
2781
- description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
2809
+ description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. Set outputMode to 'count' for per-file match counts (like grep -c) or 'filesWithMatches' for just the file paths (like grep -l). Add context (like grep -C), or contextBefore/contextAfter (like grep -B/-A), to include surrounding lines. Set caseInsensitive (like grep -i) for a case-insensitive search. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
2782
2810
  inputSchema: {
2783
2811
  type: "object",
2784
2812
  properties: {
@@ -2797,6 +2825,27 @@ var init_grep = __esm({
2797
2825
  maxResults: {
2798
2826
  type: "number",
2799
2827
  description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
2828
+ },
2829
+ outputMode: {
2830
+ type: "string",
2831
+ enum: ["content", "count", "filesWithMatches"],
2832
+ description: "What to return. 'content' (default): matching lines with line numbers. 'count': number of matches per file, like grep -c. 'filesWithMatches': just the paths of files that contain a match, like grep -l."
2833
+ },
2834
+ context: {
2835
+ type: "number",
2836
+ description: "Lines of context to show before AND after each match, like grep -C. Content mode only."
2837
+ },
2838
+ contextBefore: {
2839
+ type: "number",
2840
+ description: "Lines of context before each match, like grep -B. Content mode only; ignored if context is set."
2841
+ },
2842
+ contextAfter: {
2843
+ type: "number",
2844
+ description: "Lines of context after each match, like grep -A. Content mode only; ignored if context is set."
2845
+ },
2846
+ caseInsensitive: {
2847
+ type: "boolean",
2848
+ description: "Case-insensitive search, like grep -i."
2800
2849
  }
2801
2850
  },
2802
2851
  required: ["pattern"]
@@ -2804,20 +2853,55 @@ var init_grep = __esm({
2804
2853
  },
2805
2854
  async execute(input) {
2806
2855
  const searchPath = input.path || ".";
2807
- const max = input.maxResults || DEFAULT_MAX;
2856
+ const max = Math.max(
2857
+ 1,
2858
+ Math.floor(Number(input.maxResults) || DEFAULT_MAX)
2859
+ );
2808
2860
  const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
2809
2861
  const escaped = input.pattern.replace(/'/g, "'\\''");
2810
- const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
2811
- const grepCmd = `grep -rn --max-count=${max} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
2862
+ const mode = input.outputMode === "count" || input.outputMode === "filesWithMatches" ? input.outputMode : "content";
2863
+ const ci = input.caseInsensitive ? " -i" : "";
2864
+ let ctx = "";
2865
+ if (mode === "content") {
2866
+ if (input.context != null) {
2867
+ const c = clampContext(input.context);
2868
+ if (c > 0) {
2869
+ ctx = ` -C ${c}`;
2870
+ }
2871
+ } else {
2872
+ const b = input.contextBefore != null ? clampContext(input.contextBefore) : 0;
2873
+ const a = input.contextAfter != null ? clampContext(input.contextAfter) : 0;
2874
+ if (b > 0) {
2875
+ ctx += ` -B ${b}`;
2876
+ }
2877
+ if (a > 0) {
2878
+ ctx += ` -A ${a}`;
2879
+ }
2880
+ }
2881
+ }
2882
+ let rgFlags;
2883
+ let grepFlags;
2884
+ if (mode === "count") {
2885
+ rgFlags = `--count${ci}`;
2886
+ grepFlags = `-rc${ci}`;
2887
+ } else if (mode === "filesWithMatches") {
2888
+ rgFlags = `-l${ci}`;
2889
+ grepFlags = `-rl${ci}`;
2890
+ } else {
2891
+ rgFlags = `-n --no-heading${ci}${ctx} --max-count=${max}`;
2892
+ grepFlags = `-rn${ci}${ctx} --max-count=${max}`;
2893
+ }
2894
+ const rgCmd = `rg ${rgFlags}${globFlag} '${escaped}' ${searchPath}`;
2895
+ const grepCmd = `grep ${grepFlags} '${escaped}' ${searchPath} --include='*.ts' --include='*.tsx' --include='*.js' --include='*.json' --include='*.md'`;
2812
2896
  return new Promise((resolve2) => {
2813
2897
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
2814
2898
  if (stdout?.trim()) {
2815
- resolve2(formatResults(stdout, max));
2899
+ resolve2(formatResults(stdout, max, mode));
2816
2900
  return;
2817
2901
  }
2818
2902
  exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
2819
2903
  if (grepStdout?.trim()) {
2820
- resolve2(formatResults(grepStdout, max));
2904
+ resolve2(formatResults(grepStdout, max, mode));
2821
2905
  } else {
2822
2906
  resolve2("No matches found.");
2823
2907
  }
@@ -4666,86 +4750,15 @@ var COMMON_READ_TOOLS, COMMON_READ_TOOL_NAMES;
4666
4750
  var init_tools2 = __esm({
4667
4751
  "src/subagents/common/tools.ts"() {
4668
4752
  "use strict";
4753
+ init_readFile();
4754
+ init_listDir();
4755
+ init_grep();
4756
+ init_glob();
4669
4757
  COMMON_READ_TOOLS = [
4670
- {
4671
- name: "readFile",
4672
- description: "Read a file's contents with line numbers. Always read a file before editing it \u2014 never guess at contents. For large files, consider using symbols first to identify the relevant section, then use offset and maxLines to read just that section. Line numbers in the output correspond to what editFile expects. Defaults to first 500 lines. Use a negative offset to read from the end of the file (e.g., offset: -50 reads the last 50 lines).",
4673
- inputSchema: {
4674
- type: "object",
4675
- properties: {
4676
- path: {
4677
- type: "string",
4678
- description: "The file path to read, relative to the project root."
4679
- },
4680
- offset: {
4681
- type: "number",
4682
- description: "Line number to start reading from (1-indexed). Use a negative number to read from the end (e.g., -50 reads the last 50 lines). Defaults to 1."
4683
- },
4684
- maxLines: {
4685
- type: "number",
4686
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
4687
- }
4688
- },
4689
- required: ["path"]
4690
- }
4691
- },
4692
- {
4693
- name: "listDir",
4694
- description: "List the contents of a directory with one level of subdirectory expansion. Shows file sizes and collapses single-child directory chains (a/b/c/ shown as one entry). Use this for a quick overview of a directory's structure. For finding files across the whole project, use glob instead.",
4695
- inputSchema: {
4696
- type: "object",
4697
- properties: {
4698
- path: {
4699
- type: "string",
4700
- description: 'Directory path to list, relative to project root. Defaults to ".".'
4701
- }
4702
- }
4703
- }
4704
- },
4705
- {
4706
- name: "grep",
4707
- description: "Search file contents for a regex pattern. Returns matching lines with file paths and line numbers (default 50 results). Use this to find where something is used, locate function definitions, or search for patterns across the codebase. For finding a symbol's definition precisely, prefer the definition tool if LSP is available. Automatically excludes node_modules and .git.",
4708
- inputSchema: {
4709
- type: "object",
4710
- properties: {
4711
- pattern: {
4712
- type: "string",
4713
- description: "The search pattern (regex supported)."
4714
- },
4715
- path: {
4716
- type: "string",
4717
- description: "Directory or file to search in. Defaults to current directory."
4718
- },
4719
- glob: {
4720
- type: "string",
4721
- description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
4722
- },
4723
- maxResults: {
4724
- type: "number",
4725
- description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
4726
- }
4727
- },
4728
- required: ["pattern"]
4729
- }
4730
- },
4731
- {
4732
- name: "glob",
4733
- description: 'Find files matching a glob pattern. Returns matching file paths sorted alphabetically (default 200 results). Use this to discover project structure, find files by name or extension, or check if a file exists. Common patterns: "**/*.ts" (all TypeScript files), "src/**/*.tsx" (React components in src), "*.json" (root-level JSON files). Automatically excludes node_modules and .git.',
4734
- inputSchema: {
4735
- type: "object",
4736
- properties: {
4737
- pattern: {
4738
- type: "string",
4739
- description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
4740
- },
4741
- maxResults: {
4742
- type: "number",
4743
- description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
4744
- }
4745
- },
4746
- required: ["pattern"]
4747
- }
4748
- }
4758
+ readFileTool.definition,
4759
+ listDirTool.definition,
4760
+ grepTool.definition,
4761
+ globTool.definition
4749
4762
  ];
4750
4763
  COMMON_READ_TOOL_NAMES = new Set(
4751
4764
  COMMON_READ_TOOLS.map((t) => t.name)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.232",
3
+ "version": "0.1.234",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",