@oh-my-pi/pi-coding-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { readFileSync, type Stats, statSync } from "node:fs";
|
|
2
|
+
import nodePath from "node:path";
|
|
3
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import type { Subprocess } from "bun";
|
|
6
|
+
import grepDescription from "../../prompts/tools/grep.md" with { type: "text" };
|
|
7
|
+
import { ensureTool } from "../../utils/tools-manager";
|
|
8
|
+
import { resolveToCwd } from "./path-utils";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_MAX_BYTES,
|
|
11
|
+
formatSize,
|
|
12
|
+
GREP_MAX_LINE_LENGTH,
|
|
13
|
+
type TruncationResult,
|
|
14
|
+
truncateHead,
|
|
15
|
+
truncateLine,
|
|
16
|
+
} from "./truncate";
|
|
17
|
+
|
|
18
|
+
const grepSchema = Type.Object({
|
|
19
|
+
pattern: Type.String({ description: "Search pattern (regex or literal string)" }),
|
|
20
|
+
path: Type.Optional(Type.String({ description: "Directory or file to search (default: current directory)" })),
|
|
21
|
+
glob: Type.Optional(Type.String({ description: "Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'" })),
|
|
22
|
+
type: Type.Optional(Type.String({ description: "File type filter (e.g., 'ts', 'rust', 'py')" })),
|
|
23
|
+
ignoreCase: Type.Optional(
|
|
24
|
+
Type.Boolean({ description: "Force case-insensitive search (default: false, uses smart-case otherwise)" }),
|
|
25
|
+
),
|
|
26
|
+
caseSensitive: Type.Optional(
|
|
27
|
+
Type.Boolean({ description: "Force case-sensitive search (default: false, disables smart-case)" }),
|
|
28
|
+
),
|
|
29
|
+
literal: Type.Optional(
|
|
30
|
+
Type.Boolean({ description: "Treat pattern as literal string instead of regex (default: false)" }),
|
|
31
|
+
),
|
|
32
|
+
multiline: Type.Optional(
|
|
33
|
+
Type.Boolean({ description: "Enable multiline matching for cross-line patterns (default: false)" }),
|
|
34
|
+
),
|
|
35
|
+
context: Type.Optional(
|
|
36
|
+
Type.Number({ description: "Number of lines to show before and after each match (default: 0)" }),
|
|
37
|
+
),
|
|
38
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of matches to return (default: 100)" })),
|
|
39
|
+
outputMode: Type.Optional(
|
|
40
|
+
Type.Union([Type.Literal("content"), Type.Literal("files_with_matches"), Type.Literal("count")], {
|
|
41
|
+
description:
|
|
42
|
+
"Output mode: 'content' shows matching lines, 'files_with_matches' shows only file paths, 'count' shows match counts per file (default: 'content')",
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
headLimit: Type.Optional(Type.Number({ description: "Limit output to first N results (default: unlimited)" })),
|
|
46
|
+
offset: Type.Optional(Type.Number({ description: "Skip first N results before applying headLimit (default: 0)" })),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const DEFAULT_LIMIT = 100;
|
|
50
|
+
|
|
51
|
+
export interface GrepToolDetails {
|
|
52
|
+
truncation?: TruncationResult;
|
|
53
|
+
matchLimitReached?: number;
|
|
54
|
+
headLimitReached?: number;
|
|
55
|
+
linesTruncated?: boolean;
|
|
56
|
+
// Fields for TUI rendering
|
|
57
|
+
scopePath?: string;
|
|
58
|
+
matchCount?: number;
|
|
59
|
+
fileCount?: number;
|
|
60
|
+
files?: string[];
|
|
61
|
+
fileMatches?: Array<{ path: string; count: number }>;
|
|
62
|
+
mode?: "content" | "files_with_matches" | "count";
|
|
63
|
+
truncated?: boolean;
|
|
64
|
+
error?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
|
68
|
+
return {
|
|
69
|
+
name: "grep",
|
|
70
|
+
label: "Grep",
|
|
71
|
+
description: grepDescription,
|
|
72
|
+
parameters: grepSchema,
|
|
73
|
+
execute: async (
|
|
74
|
+
_toolCallId: string,
|
|
75
|
+
{
|
|
76
|
+
pattern,
|
|
77
|
+
path: searchDir,
|
|
78
|
+
glob,
|
|
79
|
+
type,
|
|
80
|
+
ignoreCase,
|
|
81
|
+
caseSensitive,
|
|
82
|
+
literal,
|
|
83
|
+
multiline,
|
|
84
|
+
context,
|
|
85
|
+
limit,
|
|
86
|
+
outputMode,
|
|
87
|
+
headLimit,
|
|
88
|
+
offset,
|
|
89
|
+
}: {
|
|
90
|
+
pattern: string;
|
|
91
|
+
path?: string;
|
|
92
|
+
glob?: string;
|
|
93
|
+
type?: string;
|
|
94
|
+
ignoreCase?: boolean;
|
|
95
|
+
caseSensitive?: boolean;
|
|
96
|
+
literal?: boolean;
|
|
97
|
+
multiline?: boolean;
|
|
98
|
+
context?: number;
|
|
99
|
+
limit?: number;
|
|
100
|
+
outputMode?: "content" | "files_with_matches" | "count";
|
|
101
|
+
headLimit?: number;
|
|
102
|
+
offset?: number;
|
|
103
|
+
},
|
|
104
|
+
signal?: AbortSignal,
|
|
105
|
+
) => {
|
|
106
|
+
if (signal?.aborted) {
|
|
107
|
+
throw new Error("Operation aborted");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const rgPath = await ensureTool("rg", true);
|
|
111
|
+
if (!rgPath) {
|
|
112
|
+
throw new Error("ripgrep (rg) is not available and could not be downloaded");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
|
116
|
+
const scopePath = (() => {
|
|
117
|
+
const relative = nodePath.relative(cwd, searchPath).replace(/\\/g, "/");
|
|
118
|
+
return relative.length === 0 ? "." : relative;
|
|
119
|
+
})();
|
|
120
|
+
let searchStat: Stats;
|
|
121
|
+
try {
|
|
122
|
+
searchStat = statSync(searchPath);
|
|
123
|
+
} catch (_err) {
|
|
124
|
+
throw new Error(`Path not found: ${searchPath}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const isDirectory = searchStat.isDirectory();
|
|
128
|
+
const contextValue = context && context > 0 ? context : 0;
|
|
129
|
+
const effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);
|
|
130
|
+
const effectiveOutputMode = outputMode ?? "content";
|
|
131
|
+
const effectiveOffset = offset && offset > 0 ? offset : 0;
|
|
132
|
+
const hasHeadLimit = headLimit !== undefined && headLimit > 0;
|
|
133
|
+
|
|
134
|
+
const formatPath = (filePath: string): string => {
|
|
135
|
+
if (isDirectory) {
|
|
136
|
+
const relative = nodePath.relative(searchPath, filePath);
|
|
137
|
+
if (relative && !relative.startsWith("..")) {
|
|
138
|
+
return relative.replace(/\\/g, "/");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return nodePath.basename(filePath);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const fileCache = new Map<string, string[]>();
|
|
145
|
+
const getFileLines = (filePath: string): string[] => {
|
|
146
|
+
let lines = fileCache.get(filePath);
|
|
147
|
+
if (!lines) {
|
|
148
|
+
try {
|
|
149
|
+
const content = readFileSync(filePath, "utf-8");
|
|
150
|
+
lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
151
|
+
} catch {
|
|
152
|
+
lines = [];
|
|
153
|
+
}
|
|
154
|
+
fileCache.set(filePath, lines);
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const args: string[] = [];
|
|
160
|
+
|
|
161
|
+
// Base arguments depend on output mode
|
|
162
|
+
if (effectiveOutputMode === "files_with_matches") {
|
|
163
|
+
args.push("--files-with-matches", "--color=never", "--hidden");
|
|
164
|
+
} else if (effectiveOutputMode === "count") {
|
|
165
|
+
args.push("--count", "--color=never", "--hidden");
|
|
166
|
+
} else {
|
|
167
|
+
args.push("--json", "--line-number", "--color=never", "--hidden");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (caseSensitive) {
|
|
171
|
+
args.push("--case-sensitive");
|
|
172
|
+
} else if (ignoreCase) {
|
|
173
|
+
args.push("--ignore-case");
|
|
174
|
+
} else {
|
|
175
|
+
args.push("--smart-case");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (multiline) {
|
|
179
|
+
args.push("--multiline");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (literal) {
|
|
183
|
+
args.push("--fixed-strings");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (glob) {
|
|
187
|
+
args.push("--glob", glob);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (type) {
|
|
191
|
+
args.push("--type", type);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
args.push(pattern, searchPath);
|
|
195
|
+
|
|
196
|
+
const child: Subprocess = Bun.spawn([rgPath, ...args], {
|
|
197
|
+
stdin: "ignore",
|
|
198
|
+
stdout: "pipe",
|
|
199
|
+
stderr: "pipe",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
let stderr = "";
|
|
203
|
+
let matchCount = 0;
|
|
204
|
+
let matchLimitReached = false;
|
|
205
|
+
let linesTruncated = false;
|
|
206
|
+
let aborted = false;
|
|
207
|
+
let killedDueToLimit = false;
|
|
208
|
+
const outputLines: string[] = [];
|
|
209
|
+
const files = new Set<string>();
|
|
210
|
+
const fileList: string[] = [];
|
|
211
|
+
const fileMatchCounts = new Map<string, number>();
|
|
212
|
+
|
|
213
|
+
const recordFile = (filePath: string) => {
|
|
214
|
+
const relative = formatPath(filePath);
|
|
215
|
+
if (!files.has(relative)) {
|
|
216
|
+
files.add(relative);
|
|
217
|
+
fileList.push(relative);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const recordFileMatch = (filePath: string) => {
|
|
222
|
+
const relative = formatPath(filePath);
|
|
223
|
+
fileMatchCounts.set(relative, (fileMatchCounts.get(relative) ?? 0) + 1);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const stopChild = (dueToLimit: boolean = false) => {
|
|
227
|
+
killedDueToLimit = dueToLimit;
|
|
228
|
+
child.kill();
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const onAbort = () => {
|
|
232
|
+
aborted = true;
|
|
233
|
+
stopChild();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (signal) {
|
|
237
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// For simple output modes (files_with_matches, count), process text directly
|
|
241
|
+
if (effectiveOutputMode === "files_with_matches" || effectiveOutputMode === "count") {
|
|
242
|
+
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
243
|
+
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
244
|
+
const decoder = new TextDecoder();
|
|
245
|
+
let stdout = "";
|
|
246
|
+
|
|
247
|
+
await Promise.all([
|
|
248
|
+
(async () => {
|
|
249
|
+
while (true) {
|
|
250
|
+
const { done, value } = await stdoutReader.read();
|
|
251
|
+
if (done) break;
|
|
252
|
+
stdout += decoder.decode(value, { stream: true });
|
|
253
|
+
}
|
|
254
|
+
})(),
|
|
255
|
+
(async () => {
|
|
256
|
+
while (true) {
|
|
257
|
+
const { done, value } = await stderrReader.read();
|
|
258
|
+
if (done) break;
|
|
259
|
+
stderr += decoder.decode(value, { stream: true });
|
|
260
|
+
}
|
|
261
|
+
})(),
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
const exitCode = await child.exited;
|
|
265
|
+
|
|
266
|
+
if (signal) {
|
|
267
|
+
signal.removeEventListener("abort", onAbort);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (aborted) {
|
|
271
|
+
throw new Error("Operation aborted");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (exitCode !== 0 && exitCode !== 1) {
|
|
275
|
+
const errorMsg = stderr.trim() || `ripgrep exited with code ${exitCode}`;
|
|
276
|
+
throw new Error(errorMsg);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const lines = stdout
|
|
280
|
+
.trim()
|
|
281
|
+
.split("\n")
|
|
282
|
+
.filter((line) => line.length > 0);
|
|
283
|
+
|
|
284
|
+
if (lines.length === 0) {
|
|
285
|
+
return {
|
|
286
|
+
content: [{ type: "text", text: "No matches found" }],
|
|
287
|
+
details: {
|
|
288
|
+
scopePath,
|
|
289
|
+
matchCount: 0,
|
|
290
|
+
fileCount: 0,
|
|
291
|
+
files: [],
|
|
292
|
+
mode: effectiveOutputMode,
|
|
293
|
+
truncated: false,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Apply offset and headLimit
|
|
299
|
+
let processedLines = lines;
|
|
300
|
+
if (effectiveOffset > 0) {
|
|
301
|
+
processedLines = processedLines.slice(effectiveOffset);
|
|
302
|
+
}
|
|
303
|
+
if (hasHeadLimit) {
|
|
304
|
+
processedLines = processedLines.slice(0, headLimit);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let simpleMatchCount = 0;
|
|
308
|
+
let fileCount = 0;
|
|
309
|
+
const simpleFiles = new Set<string>();
|
|
310
|
+
const simpleFileList: string[] = [];
|
|
311
|
+
const simpleFileMatchCounts = new Map<string, number>();
|
|
312
|
+
|
|
313
|
+
const recordSimpleFile = (filePath: string) => {
|
|
314
|
+
const relative = formatPath(filePath);
|
|
315
|
+
if (!simpleFiles.has(relative)) {
|
|
316
|
+
simpleFiles.add(relative);
|
|
317
|
+
simpleFileList.push(relative);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const recordSimpleFileMatch = (filePath: string, count: number) => {
|
|
322
|
+
const relative = formatPath(filePath);
|
|
323
|
+
simpleFileMatchCounts.set(relative, count);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (effectiveOutputMode === "files_with_matches") {
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
recordSimpleFile(line);
|
|
329
|
+
}
|
|
330
|
+
fileCount = simpleFiles.size;
|
|
331
|
+
simpleMatchCount = fileCount;
|
|
332
|
+
} else {
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
const separatorIndex = line.lastIndexOf(":");
|
|
335
|
+
const filePart = separatorIndex === -1 ? line : line.slice(0, separatorIndex);
|
|
336
|
+
const countPart = separatorIndex === -1 ? "" : line.slice(separatorIndex + 1);
|
|
337
|
+
const count = Number.parseInt(countPart, 10);
|
|
338
|
+
recordSimpleFile(filePart);
|
|
339
|
+
if (!Number.isNaN(count)) {
|
|
340
|
+
simpleMatchCount += count;
|
|
341
|
+
recordSimpleFileMatch(filePart, count);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
fileCount = simpleFiles.size;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const truncatedByHeadLimit = hasHeadLimit && processedLines.length < lines.length;
|
|
348
|
+
|
|
349
|
+
// For count mode, format as "path:count"
|
|
350
|
+
if (effectiveOutputMode === "count") {
|
|
351
|
+
const formatted = processedLines.map((line) => {
|
|
352
|
+
const separatorIndex = line.lastIndexOf(":");
|
|
353
|
+
const relative = formatPath(separatorIndex === -1 ? line : line.slice(0, separatorIndex));
|
|
354
|
+
const count = separatorIndex === -1 ? "0" : line.slice(separatorIndex + 1);
|
|
355
|
+
return `${relative}:${count}`;
|
|
356
|
+
});
|
|
357
|
+
const output = formatted.join("\n");
|
|
358
|
+
return {
|
|
359
|
+
content: [{ type: "text", text: output }],
|
|
360
|
+
details: {
|
|
361
|
+
scopePath,
|
|
362
|
+
matchCount: simpleMatchCount,
|
|
363
|
+
fileCount,
|
|
364
|
+
files: simpleFileList,
|
|
365
|
+
fileMatches: simpleFileList.map((path) => ({
|
|
366
|
+
path,
|
|
367
|
+
count: simpleFileMatchCounts.get(path) ?? 0,
|
|
368
|
+
})),
|
|
369
|
+
mode: effectiveOutputMode,
|
|
370
|
+
truncated: truncatedByHeadLimit,
|
|
371
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// For files_with_matches, format paths
|
|
377
|
+
const formatted = processedLines.map((line) => formatPath(line));
|
|
378
|
+
const output = formatted.join("\n");
|
|
379
|
+
return {
|
|
380
|
+
content: [{ type: "text", text: output }],
|
|
381
|
+
details: {
|
|
382
|
+
scopePath,
|
|
383
|
+
matchCount: simpleMatchCount,
|
|
384
|
+
fileCount,
|
|
385
|
+
files: simpleFileList,
|
|
386
|
+
mode: effectiveOutputMode,
|
|
387
|
+
truncated: truncatedByHeadLimit,
|
|
388
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Content mode - existing JSON processing
|
|
394
|
+
const formatBlock = (filePath: string, lineNumber: number): string[] => {
|
|
395
|
+
const relativePath = formatPath(filePath);
|
|
396
|
+
const lines = getFileLines(filePath);
|
|
397
|
+
if (!lines.length) {
|
|
398
|
+
return [`${relativePath}:${lineNumber}: (unable to read file)`];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const block: string[] = [];
|
|
402
|
+
const start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;
|
|
403
|
+
const end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;
|
|
404
|
+
|
|
405
|
+
for (let current = start; current <= end; current++) {
|
|
406
|
+
const lineText = lines[current - 1] ?? "";
|
|
407
|
+
const sanitized = lineText.replace(/\r/g, "");
|
|
408
|
+
const isMatchLine = current === lineNumber;
|
|
409
|
+
|
|
410
|
+
const { text: truncatedText, wasTruncated } = truncateLine(sanitized);
|
|
411
|
+
if (wasTruncated) {
|
|
412
|
+
linesTruncated = true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (isMatchLine) {
|
|
416
|
+
block.push(`${relativePath}:${current}: ${truncatedText}`);
|
|
417
|
+
} else {
|
|
418
|
+
block.push(`${relativePath}-${current}- ${truncatedText}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return block;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const processLine = (line: string) => {
|
|
426
|
+
if (!line.trim() || matchCount >= effectiveLimit) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let event: { type: string; data?: { path?: { text?: string }; line_number?: number } };
|
|
431
|
+
try {
|
|
432
|
+
event = JSON.parse(line);
|
|
433
|
+
} catch {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (event.type === "match") {
|
|
438
|
+
matchCount++;
|
|
439
|
+
const filePath = event.data?.path?.text;
|
|
440
|
+
const lineNumber = event.data?.line_number;
|
|
441
|
+
|
|
442
|
+
if (filePath && typeof lineNumber === "number") {
|
|
443
|
+
recordFile(filePath);
|
|
444
|
+
recordFileMatch(filePath);
|
|
445
|
+
outputLines.push(...formatBlock(filePath, lineNumber));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (matchCount >= effectiveLimit) {
|
|
449
|
+
matchLimitReached = true;
|
|
450
|
+
stopChild(true);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
// Read streams using Bun's ReadableStream API
|
|
456
|
+
const stdoutReader = (child.stdout as ReadableStream<Uint8Array>).getReader();
|
|
457
|
+
const stderrReader = (child.stderr as ReadableStream<Uint8Array>).getReader();
|
|
458
|
+
const decoder = new TextDecoder();
|
|
459
|
+
let stdoutBuffer = "";
|
|
460
|
+
|
|
461
|
+
await Promise.all([
|
|
462
|
+
// Process stdout line by line
|
|
463
|
+
(async () => {
|
|
464
|
+
while (true) {
|
|
465
|
+
const { done, value } = await stdoutReader.read();
|
|
466
|
+
if (done) break;
|
|
467
|
+
|
|
468
|
+
stdoutBuffer += decoder.decode(value, { stream: true });
|
|
469
|
+
const lines = stdoutBuffer.split("\n");
|
|
470
|
+
// Keep the last incomplete line in the buffer
|
|
471
|
+
stdoutBuffer = lines.pop() ?? "";
|
|
472
|
+
|
|
473
|
+
for (const line of lines) {
|
|
474
|
+
processLine(line);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Process any remaining content
|
|
478
|
+
if (stdoutBuffer.trim()) {
|
|
479
|
+
processLine(stdoutBuffer);
|
|
480
|
+
}
|
|
481
|
+
})(),
|
|
482
|
+
// Collect stderr
|
|
483
|
+
(async () => {
|
|
484
|
+
while (true) {
|
|
485
|
+
const { done, value } = await stderrReader.read();
|
|
486
|
+
if (done) break;
|
|
487
|
+
stderr += decoder.decode(value, { stream: true });
|
|
488
|
+
}
|
|
489
|
+
})(),
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
const exitCode = await child.exited;
|
|
493
|
+
|
|
494
|
+
// Cleanup
|
|
495
|
+
if (signal) {
|
|
496
|
+
signal.removeEventListener("abort", onAbort);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (aborted) {
|
|
500
|
+
throw new Error("Operation aborted");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!killedDueToLimit && exitCode !== 0 && exitCode !== 1) {
|
|
504
|
+
const errorMsg = stderr.trim() || `ripgrep exited with code ${exitCode}`;
|
|
505
|
+
throw new Error(errorMsg);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (matchCount === 0) {
|
|
509
|
+
return {
|
|
510
|
+
content: [{ type: "text", text: "No matches found" }],
|
|
511
|
+
details: {
|
|
512
|
+
scopePath,
|
|
513
|
+
matchCount: 0,
|
|
514
|
+
fileCount: 0,
|
|
515
|
+
files: [],
|
|
516
|
+
mode: effectiveOutputMode,
|
|
517
|
+
truncated: false,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Apply offset and headLimit to output lines
|
|
523
|
+
let processedLines = outputLines;
|
|
524
|
+
if (effectiveOffset > 0) {
|
|
525
|
+
processedLines = processedLines.slice(effectiveOffset);
|
|
526
|
+
}
|
|
527
|
+
if (hasHeadLimit) {
|
|
528
|
+
processedLines = processedLines.slice(0, headLimit);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Apply byte truncation (no line limit since we already have match limit)
|
|
532
|
+
const rawOutput = processedLines.join("\n");
|
|
533
|
+
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
534
|
+
|
|
535
|
+
let output = truncation.content;
|
|
536
|
+
const truncatedByHeadLimit = hasHeadLimit && processedLines.length < outputLines.length;
|
|
537
|
+
const details: GrepToolDetails = {
|
|
538
|
+
scopePath,
|
|
539
|
+
matchCount,
|
|
540
|
+
fileCount: files.size,
|
|
541
|
+
files: fileList,
|
|
542
|
+
fileMatches: fileList.map((path) => ({
|
|
543
|
+
path,
|
|
544
|
+
count: fileMatchCounts.get(path) ?? 0,
|
|
545
|
+
})),
|
|
546
|
+
mode: effectiveOutputMode,
|
|
547
|
+
truncated: matchLimitReached || truncation.truncated || truncatedByHeadLimit,
|
|
548
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// Build notices
|
|
552
|
+
const notices: string[] = [];
|
|
553
|
+
|
|
554
|
+
if (matchLimitReached) {
|
|
555
|
+
notices.push(
|
|
556
|
+
`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,
|
|
557
|
+
);
|
|
558
|
+
details.matchLimitReached = effectiveLimit;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (truncation.truncated) {
|
|
562
|
+
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
563
|
+
details.truncation = truncation;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (linesTruncated) {
|
|
567
|
+
notices.push(`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`);
|
|
568
|
+
details.linesTruncated = true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (notices.length > 0) {
|
|
572
|
+
output += `\n\n[${notices.join(". ")}]`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
content: [{ type: "text", text: output }],
|
|
577
|
+
details: Object.keys(details).length > 0 ? details : undefined,
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** Default grep tool using process.cwd() - for backwards compatibility */
|
|
584
|
+
export const grepTool = createGrepTool(process.cwd());
|