@mindstudio-ai/remy 0.1.233 → 0.1.235

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
@@ -380,14 +380,14 @@ async function fetchRemyContext(config) {
380
380
  // src/models/surfaces.ts
381
381
  var MODEL_SURFACES = {
382
382
  parent: {
383
- default: "claude-5-opus",
383
+ default: "claude-4-8-opus",
384
384
  label: "Remy",
385
385
  description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
386
386
  modelType: "text",
387
387
  userPickable: true
388
388
  },
389
389
  visualDesignExpert: {
390
- default: "claude-5-opus",
390
+ default: "claude-4-8-opus",
391
391
  label: "Design Agent",
392
392
  description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
393
393
  modelType: "text",
@@ -1895,7 +1895,7 @@ var compactConversationTool = {
1895
1895
 
1896
1896
  // src/tools/code/readFile.ts
1897
1897
  import fs12 from "fs/promises";
1898
- var DEFAULT_MAX_LINES2 = 500;
1898
+ var DEFAULT_WINDOW = 500;
1899
1899
  function isBinary(buffer) {
1900
1900
  const sample = buffer.subarray(0, 8192);
1901
1901
  for (let i = 0; i < sample.length; i++) {
@@ -1909,7 +1909,7 @@ var readFileTool = {
1909
1909
  clearable: true,
1910
1910
  definition: {
1911
1911
  name: "readFile",
1912
- 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.",
1913
1913
  inputSchema: {
1914
1914
  type: "object",
1915
1915
  properties: {
@@ -1917,13 +1917,17 @@ var readFileTool = {
1917
1917
  type: "string",
1918
1918
  description: "The file path to read, relative to the project root."
1919
1919
  },
1920
- offset: {
1920
+ startLine: {
1921
1921
  type: "number",
1922
- 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."
1923
1923
  },
1924
- maxLines: {
1924
+ endLine: {
1925
1925
  type: "number",
1926
- 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."
1927
1931
  }
1928
1932
  },
1929
1933
  required: ["path"]
@@ -1940,22 +1944,37 @@ var readFileTool = {
1940
1944
  const content = buffer.toString("utf-8");
1941
1945
  const allLines = content.split("\n");
1942
1946
  const totalLines = allLines.length;
1943
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
1947
+ const tail = input.tail != null ? Math.floor(Number(input.tail)) : void 0;
1944
1948
  let startIdx;
1945
- if (input.offset && input.offset < 0) {
1946
- 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;
1947
1953
  } else {
1948
- 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
+ }
1949
1968
  }
1950
- const sliced = allLines.slice(startIdx, startIdx + maxLines);
1969
+ const sliced = allLines.slice(startIdx, endIdxExclusive);
1951
1970
  const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
1952
1971
  let result = numbered;
1953
- const endLine = startIdx + sliced.length;
1954
1972
  const displayStart = startIdx + 1;
1955
- if (endLine < totalLines) {
1973
+ const displayEnd = startIdx + sliced.length;
1974
+ if (displayStart > 1 || displayEnd < totalLines) {
1956
1975
  result += `
1957
1976
 
1958
- (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)`;
1959
1978
  }
1960
1979
  return result;
1961
1980
  } catch (err) {
@@ -2123,7 +2142,7 @@ ${unifiedDiff(input.path, content, updated)}`;
2123
2142
  // src/tools/code/bash.ts
2124
2143
  import { spawn as spawn2 } from "child_process";
2125
2144
  var DEFAULT_TIMEOUT_MS = 12e4;
2126
- var DEFAULT_MAX_LINES3 = 500;
2145
+ var DEFAULT_MAX_LINES2 = 500;
2127
2146
  var MAX_OUTPUT_BYTES = 3e4;
2128
2147
  var bashTool = {
2129
2148
  clearable: true,
@@ -2154,7 +2173,7 @@ var bashTool = {
2154
2173
  }
2155
2174
  },
2156
2175
  async execute(input, context) {
2157
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
2176
+ const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2158
2177
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
2159
2178
  return new Promise((resolve2) => {
2160
2179
  const child = spawn2("sh", ["-c", input.command], {
@@ -2227,8 +2246,16 @@ var bashTool = {
2227
2246
  // src/tools/code/grep.ts
2228
2247
  import { exec } from "child_process";
2229
2248
  var DEFAULT_MAX = 50;
2230
- function formatResults(stdout, max) {
2231
- 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");
2232
2259
  let result = lines.join("\n");
2233
2260
  if (lines.length >= max) {
2234
2261
  result += `
@@ -2241,7 +2268,7 @@ var grepTool = {
2241
2268
  clearable: true,
2242
2269
  definition: {
2243
2270
  name: "grep",
2244
- 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.",
2245
2272
  inputSchema: {
2246
2273
  type: "object",
2247
2274
  properties: {
@@ -2260,6 +2287,27 @@ var grepTool = {
2260
2287
  maxResults: {
2261
2288
  type: "number",
2262
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."
2263
2311
  }
2264
2312
  },
2265
2313
  required: ["pattern"]
@@ -2267,20 +2315,55 @@ var grepTool = {
2267
2315
  },
2268
2316
  async execute(input) {
2269
2317
  const searchPath = input.path || ".";
2270
- const max = input.maxResults || DEFAULT_MAX;
2318
+ const max = Math.max(
2319
+ 1,
2320
+ Math.floor(Number(input.maxResults) || DEFAULT_MAX)
2321
+ );
2271
2322
  const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
2272
2323
  const escaped = input.pattern.replace(/'/g, "'\\''");
2273
- const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
2274
- 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'`;
2275
2358
  return new Promise((resolve2) => {
2276
2359
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
2277
2360
  if (stdout?.trim()) {
2278
- resolve2(formatResults(stdout, max));
2361
+ resolve2(formatResults(stdout, max, mode));
2279
2362
  return;
2280
2363
  }
2281
2364
  exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
2282
2365
  if (grepStdout?.trim()) {
2283
- resolve2(formatResults(grepStdout, max));
2366
+ resolve2(formatResults(grepStdout, max, mode));
2284
2367
  } else {
2285
2368
  resolve2("No matches found.");
2286
2369
  }
@@ -3960,85 +4043,10 @@ var screenshotTool = {
3960
4043
 
3961
4044
  // src/subagents/common/tools.ts
3962
4045
  var COMMON_READ_TOOLS = [
3963
- {
3964
- name: "readFile",
3965
- 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).",
3966
- inputSchema: {
3967
- type: "object",
3968
- properties: {
3969
- path: {
3970
- type: "string",
3971
- description: "The file path to read, relative to the project root."
3972
- },
3973
- offset: {
3974
- type: "number",
3975
- 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."
3976
- },
3977
- maxLines: {
3978
- type: "number",
3979
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
3980
- }
3981
- },
3982
- required: ["path"]
3983
- }
3984
- },
3985
- {
3986
- name: "listDir",
3987
- 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.",
3988
- inputSchema: {
3989
- type: "object",
3990
- properties: {
3991
- path: {
3992
- type: "string",
3993
- description: 'Directory path to list, relative to project root. Defaults to ".".'
3994
- }
3995
- }
3996
- }
3997
- },
3998
- {
3999
- name: "grep",
4000
- 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.",
4001
- inputSchema: {
4002
- type: "object",
4003
- properties: {
4004
- pattern: {
4005
- type: "string",
4006
- description: "The search pattern (regex supported)."
4007
- },
4008
- path: {
4009
- type: "string",
4010
- description: "Directory or file to search in. Defaults to current directory."
4011
- },
4012
- glob: {
4013
- type: "string",
4014
- description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
4015
- },
4016
- maxResults: {
4017
- type: "number",
4018
- description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
4019
- }
4020
- },
4021
- required: ["pattern"]
4022
- }
4023
- },
4024
- {
4025
- name: "glob",
4026
- 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.',
4027
- inputSchema: {
4028
- type: "object",
4029
- properties: {
4030
- pattern: {
4031
- type: "string",
4032
- description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
4033
- },
4034
- maxResults: {
4035
- type: "number",
4036
- description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
4037
- }
4038
- },
4039
- required: ["pattern"]
4040
- }
4041
- }
4046
+ readFileTool.definition,
4047
+ listDirTool.definition,
4048
+ grepTool.definition,
4049
+ globTool.definition
4042
4050
  ];
4043
4051
  var COMMON_READ_TOOL_NAMES = new Set(
4044
4052
  COMMON_READ_TOOLS.map((t) => t.name)
package/dist/index.js CHANGED
@@ -1996,14 +1996,14 @@ var init_surfaces = __esm({
1996
1996
  "use strict";
1997
1997
  MODEL_SURFACES = {
1998
1998
  parent: {
1999
- default: "claude-5-opus",
1999
+ default: "claude-4-8-opus",
2000
2000
  label: "Remy",
2001
2001
  description: "The main Remy agent you chat with about your product. Writes code and manages delegation to other agents.",
2002
2002
  modelType: "text",
2003
2003
  userPickable: true
2004
2004
  },
2005
2005
  visualDesignExpert: {
2006
- default: "claude-5-opus",
2006
+ default: "claude-4-8-opus",
2007
2007
  label: "Design Agent",
2008
2008
  description: "Designs your product's interfaces, including components, layouts, typography, color, and visual identity.",
2009
2009
  modelType: "text",
@@ -2409,16 +2409,16 @@ function isBinary(buffer) {
2409
2409
  }
2410
2410
  return false;
2411
2411
  }
2412
- var DEFAULT_MAX_LINES2, readFileTool;
2412
+ var DEFAULT_WINDOW, readFileTool;
2413
2413
  var init_readFile = __esm({
2414
2414
  "src/tools/code/readFile.ts"() {
2415
2415
  "use strict";
2416
- DEFAULT_MAX_LINES2 = 500;
2416
+ DEFAULT_WINDOW = 500;
2417
2417
  readFileTool = {
2418
2418
  clearable: true,
2419
2419
  definition: {
2420
2420
  name: "readFile",
2421
- 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.",
2422
2422
  inputSchema: {
2423
2423
  type: "object",
2424
2424
  properties: {
@@ -2426,13 +2426,17 @@ var init_readFile = __esm({
2426
2426
  type: "string",
2427
2427
  description: "The file path to read, relative to the project root."
2428
2428
  },
2429
- offset: {
2429
+ startLine: {
2430
2430
  type: "number",
2431
- 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."
2432
2432
  },
2433
- maxLines: {
2433
+ endLine: {
2434
2434
  type: "number",
2435
- 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."
2436
2440
  }
2437
2441
  },
2438
2442
  required: ["path"]
@@ -2449,22 +2453,37 @@ var init_readFile = __esm({
2449
2453
  const content = buffer.toString("utf-8");
2450
2454
  const allLines = content.split("\n");
2451
2455
  const totalLines = allLines.length;
2452
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2456
+ const tail = input.tail != null ? Math.floor(Number(input.tail)) : void 0;
2453
2457
  let startIdx;
2454
- if (input.offset && input.offset < 0) {
2455
- 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;
2456
2462
  } else {
2457
- 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
+ }
2458
2477
  }
2459
- const sliced = allLines.slice(startIdx, startIdx + maxLines);
2478
+ const sliced = allLines.slice(startIdx, endIdxExclusive);
2460
2479
  const numbered = sliced.map((line, i) => `${String(startIdx + i + 1).padStart(4)} ${line}`).join("\n");
2461
2480
  let result = numbered;
2462
- const endLine = startIdx + sliced.length;
2463
2481
  const displayStart = startIdx + 1;
2464
- if (endLine < totalLines) {
2482
+ const displayEnd = startIdx + sliced.length;
2483
+ if (displayStart > 1 || displayEnd < totalLines) {
2465
2484
  result += `
2466
2485
 
2467
- (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)`;
2468
2487
  }
2469
2488
  return result;
2470
2489
  } catch (err) {
@@ -2650,12 +2669,12 @@ ${unifiedDiff(input.path, content, updated)}`;
2650
2669
 
2651
2670
  // src/tools/code/bash.ts
2652
2671
  import { spawn as spawn2 } from "child_process";
2653
- var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES3, MAX_OUTPUT_BYTES, bashTool;
2672
+ var DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINES2, MAX_OUTPUT_BYTES, bashTool;
2654
2673
  var init_bash = __esm({
2655
2674
  "src/tools/code/bash.ts"() {
2656
2675
  "use strict";
2657
2676
  DEFAULT_TIMEOUT_MS = 12e4;
2658
- DEFAULT_MAX_LINES3 = 500;
2677
+ DEFAULT_MAX_LINES2 = 500;
2659
2678
  MAX_OUTPUT_BYTES = 3e4;
2660
2679
  bashTool = {
2661
2680
  clearable: true,
@@ -2686,7 +2705,7 @@ var init_bash = __esm({
2686
2705
  }
2687
2706
  },
2688
2707
  async execute(input, context) {
2689
- const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES3;
2708
+ const maxLines = input.maxLines === 0 ? Infinity : input.maxLines || DEFAULT_MAX_LINES2;
2690
2709
  const timeoutMs = input.timeout ? input.timeout * 1e3 : DEFAULT_TIMEOUT_MS;
2691
2710
  return new Promise((resolve2) => {
2692
2711
  const child = spawn2("sh", ["-c", input.command], {
@@ -2760,8 +2779,16 @@ var init_bash = __esm({
2760
2779
 
2761
2780
  // src/tools/code/grep.ts
2762
2781
  import { exec } from "child_process";
2763
- function formatResults(stdout, max) {
2764
- 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");
2765
2792
  let result = lines.join("\n");
2766
2793
  if (lines.length >= max) {
2767
2794
  result += `
@@ -2779,7 +2806,7 @@ var init_grep = __esm({
2779
2806
  clearable: true,
2780
2807
  definition: {
2781
2808
  name: "grep",
2782
- 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.",
2783
2810
  inputSchema: {
2784
2811
  type: "object",
2785
2812
  properties: {
@@ -2798,6 +2825,27 @@ var init_grep = __esm({
2798
2825
  maxResults: {
2799
2826
  type: "number",
2800
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."
2801
2849
  }
2802
2850
  },
2803
2851
  required: ["pattern"]
@@ -2805,20 +2853,55 @@ var init_grep = __esm({
2805
2853
  },
2806
2854
  async execute(input) {
2807
2855
  const searchPath = input.path || ".";
2808
- const max = input.maxResults || DEFAULT_MAX;
2856
+ const max = Math.max(
2857
+ 1,
2858
+ Math.floor(Number(input.maxResults) || DEFAULT_MAX)
2859
+ );
2809
2860
  const globFlag = input.glob ? ` --glob '${input.glob}'` : "";
2810
2861
  const escaped = input.pattern.replace(/'/g, "'\\''");
2811
- const rgCmd = `rg -n --no-heading --max-count=${max}${globFlag} '${escaped}' ${searchPath}`;
2812
- 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'`;
2813
2896
  return new Promise((resolve2) => {
2814
2897
  exec(rgCmd, { maxBuffer: 512 * 1024 }, (err, stdout) => {
2815
2898
  if (stdout?.trim()) {
2816
- resolve2(formatResults(stdout, max));
2899
+ resolve2(formatResults(stdout, max, mode));
2817
2900
  return;
2818
2901
  }
2819
2902
  exec(grepCmd, { maxBuffer: 512 * 1024 }, (_err, grepStdout) => {
2820
2903
  if (grepStdout?.trim()) {
2821
- resolve2(formatResults(grepStdout, max));
2904
+ resolve2(formatResults(grepStdout, max, mode));
2822
2905
  } else {
2823
2906
  resolve2("No matches found.");
2824
2907
  }
@@ -4667,86 +4750,15 @@ var COMMON_READ_TOOLS, COMMON_READ_TOOL_NAMES;
4667
4750
  var init_tools2 = __esm({
4668
4751
  "src/subagents/common/tools.ts"() {
4669
4752
  "use strict";
4753
+ init_readFile();
4754
+ init_listDir();
4755
+ init_grep();
4756
+ init_glob();
4670
4757
  COMMON_READ_TOOLS = [
4671
- {
4672
- name: "readFile",
4673
- 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).",
4674
- inputSchema: {
4675
- type: "object",
4676
- properties: {
4677
- path: {
4678
- type: "string",
4679
- description: "The file path to read, relative to the project root."
4680
- },
4681
- offset: {
4682
- type: "number",
4683
- 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."
4684
- },
4685
- maxLines: {
4686
- type: "number",
4687
- description: "Maximum number of lines to return. Defaults to 500. Set to 0 for no limit."
4688
- }
4689
- },
4690
- required: ["path"]
4691
- }
4692
- },
4693
- {
4694
- name: "listDir",
4695
- 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.",
4696
- inputSchema: {
4697
- type: "object",
4698
- properties: {
4699
- path: {
4700
- type: "string",
4701
- description: 'Directory path to list, relative to project root. Defaults to ".".'
4702
- }
4703
- }
4704
- }
4705
- },
4706
- {
4707
- name: "grep",
4708
- 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.",
4709
- inputSchema: {
4710
- type: "object",
4711
- properties: {
4712
- pattern: {
4713
- type: "string",
4714
- description: "The search pattern (regex supported)."
4715
- },
4716
- path: {
4717
- type: "string",
4718
- description: "Directory or file to search in. Defaults to current directory."
4719
- },
4720
- glob: {
4721
- type: "string",
4722
- description: 'File glob to filter (e.g., "*.ts"). Only used with ripgrep.'
4723
- },
4724
- maxResults: {
4725
- type: "number",
4726
- description: "Maximum number of matching lines to return. Defaults to 50. Increase if you need more comprehensive results."
4727
- }
4728
- },
4729
- required: ["pattern"]
4730
- }
4731
- },
4732
- {
4733
- name: "glob",
4734
- 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.',
4735
- inputSchema: {
4736
- type: "object",
4737
- properties: {
4738
- pattern: {
4739
- type: "string",
4740
- description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json").'
4741
- },
4742
- maxResults: {
4743
- type: "number",
4744
- description: "Maximum number of file paths to return. Defaults to 200. Increase if you need the complete list."
4745
- }
4746
- },
4747
- required: ["pattern"]
4748
- }
4749
- }
4758
+ readFileTool.definition,
4759
+ listDirTool.definition,
4760
+ grepTool.definition,
4761
+ globTool.definition
4750
4762
  ];
4751
4763
  COMMON_READ_TOOL_NAMES = new Set(
4752
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.233",
3
+ "version": "0.1.235",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",