@ottocode/sdk 0.1.173

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.
Files changed (125) hide show
  1. package/README.md +338 -0
  2. package/package.json +128 -0
  3. package/src/agent/types.ts +19 -0
  4. package/src/auth/src/copilot-oauth.ts +190 -0
  5. package/src/auth/src/index.ts +100 -0
  6. package/src/auth/src/oauth.ts +234 -0
  7. package/src/auth/src/openai-oauth.ts +394 -0
  8. package/src/auth/src/wallet.ts +51 -0
  9. package/src/browser.ts +32 -0
  10. package/src/config/src/index.ts +110 -0
  11. package/src/config/src/manager.ts +181 -0
  12. package/src/config/src/paths.ts +98 -0
  13. package/src/core/src/errors.ts +102 -0
  14. package/src/core/src/index.ts +108 -0
  15. package/src/core/src/providers/resolver.ts +244 -0
  16. package/src/core/src/streaming/artifacts.ts +41 -0
  17. package/src/core/src/terminals/bun-pty.ts +13 -0
  18. package/src/core/src/terminals/circular-buffer.ts +30 -0
  19. package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
  20. package/src/core/src/terminals/index.ts +8 -0
  21. package/src/core/src/terminals/manager.ts +158 -0
  22. package/src/core/src/terminals/rust-libs.ts +30 -0
  23. package/src/core/src/terminals/terminal.ts +132 -0
  24. package/src/core/src/tools/bin-manager.ts +250 -0
  25. package/src/core/src/tools/builtin/bash.ts +155 -0
  26. package/src/core/src/tools/builtin/bash.txt +7 -0
  27. package/src/core/src/tools/builtin/file-cache.ts +39 -0
  28. package/src/core/src/tools/builtin/finish.ts +12 -0
  29. package/src/core/src/tools/builtin/finish.txt +10 -0
  30. package/src/core/src/tools/builtin/fs/cd.ts +19 -0
  31. package/src/core/src/tools/builtin/fs/cd.txt +5 -0
  32. package/src/core/src/tools/builtin/fs/index.ts +20 -0
  33. package/src/core/src/tools/builtin/fs/ls.ts +72 -0
  34. package/src/core/src/tools/builtin/fs/ls.txt +8 -0
  35. package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
  36. package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
  37. package/src/core/src/tools/builtin/fs/read.ts +119 -0
  38. package/src/core/src/tools/builtin/fs/read.txt +8 -0
  39. package/src/core/src/tools/builtin/fs/tree.ts +149 -0
  40. package/src/core/src/tools/builtin/fs/tree.txt +11 -0
  41. package/src/core/src/tools/builtin/fs/util.ts +95 -0
  42. package/src/core/src/tools/builtin/fs/write.ts +106 -0
  43. package/src/core/src/tools/builtin/fs/write.txt +11 -0
  44. package/src/core/src/tools/builtin/git.commit.txt +6 -0
  45. package/src/core/src/tools/builtin/git.diff.txt +5 -0
  46. package/src/core/src/tools/builtin/git.status.txt +5 -0
  47. package/src/core/src/tools/builtin/git.ts +151 -0
  48. package/src/core/src/tools/builtin/glob.ts +128 -0
  49. package/src/core/src/tools/builtin/glob.txt +10 -0
  50. package/src/core/src/tools/builtin/grep.ts +136 -0
  51. package/src/core/src/tools/builtin/grep.txt +9 -0
  52. package/src/core/src/tools/builtin/ignore.ts +45 -0
  53. package/src/core/src/tools/builtin/patch/apply.ts +546 -0
  54. package/src/core/src/tools/builtin/patch/constants.ts +5 -0
  55. package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
  56. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
  57. package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
  58. package/src/core/src/tools/builtin/patch/parse.ts +28 -0
  59. package/src/core/src/tools/builtin/patch/text.ts +23 -0
  60. package/src/core/src/tools/builtin/patch/types.ts +82 -0
  61. package/src/core/src/tools/builtin/patch.ts +167 -0
  62. package/src/core/src/tools/builtin/patch.txt +207 -0
  63. package/src/core/src/tools/builtin/progress.ts +55 -0
  64. package/src/core/src/tools/builtin/progress.txt +7 -0
  65. package/src/core/src/tools/builtin/ripgrep.ts +125 -0
  66. package/src/core/src/tools/builtin/ripgrep.txt +7 -0
  67. package/src/core/src/tools/builtin/terminal.ts +300 -0
  68. package/src/core/src/tools/builtin/terminal.txt +93 -0
  69. package/src/core/src/tools/builtin/todos.ts +66 -0
  70. package/src/core/src/tools/builtin/todos.txt +7 -0
  71. package/src/core/src/tools/builtin/websearch.ts +250 -0
  72. package/src/core/src/tools/builtin/websearch.txt +12 -0
  73. package/src/core/src/tools/error.ts +67 -0
  74. package/src/core/src/tools/loader.ts +421 -0
  75. package/src/core/src/types/index.ts +11 -0
  76. package/src/core/src/types/types.ts +4 -0
  77. package/src/core/src/utils/ansi.ts +27 -0
  78. package/src/core/src/utils/debug.ts +40 -0
  79. package/src/core/src/utils/logger.ts +150 -0
  80. package/src/index.ts +313 -0
  81. package/src/prompts/src/agents/build.txt +89 -0
  82. package/src/prompts/src/agents/general.txt +15 -0
  83. package/src/prompts/src/agents/plan.txt +10 -0
  84. package/src/prompts/src/agents/research.txt +50 -0
  85. package/src/prompts/src/base.txt +24 -0
  86. package/src/prompts/src/debug.ts +104 -0
  87. package/src/prompts/src/index.ts +1 -0
  88. package/src/prompts/src/modes/oneshot.txt +9 -0
  89. package/src/prompts/src/providers/anthropic.txt +247 -0
  90. package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
  91. package/src/prompts/src/providers/default.txt +466 -0
  92. package/src/prompts/src/providers/google.txt +230 -0
  93. package/src/prompts/src/providers/moonshot.txt +24 -0
  94. package/src/prompts/src/providers/openai.txt +414 -0
  95. package/src/prompts/src/providers.ts +143 -0
  96. package/src/providers/src/anthropic-caching.ts +202 -0
  97. package/src/providers/src/anthropic-oauth-client.ts +157 -0
  98. package/src/providers/src/authorization.ts +17 -0
  99. package/src/providers/src/catalog-manual.ts +135 -0
  100. package/src/providers/src/catalog-merged.ts +9 -0
  101. package/src/providers/src/catalog.ts +8329 -0
  102. package/src/providers/src/copilot-client.ts +39 -0
  103. package/src/providers/src/env.ts +31 -0
  104. package/src/providers/src/google-client.ts +16 -0
  105. package/src/providers/src/index.ts +75 -0
  106. package/src/providers/src/moonshot-client.ts +25 -0
  107. package/src/providers/src/oauth-models.ts +39 -0
  108. package/src/providers/src/openai-oauth-client.ts +108 -0
  109. package/src/providers/src/opencode-client.ts +64 -0
  110. package/src/providers/src/openrouter-client.ts +31 -0
  111. package/src/providers/src/pricing.ts +178 -0
  112. package/src/providers/src/setu-client.ts +643 -0
  113. package/src/providers/src/utils.ts +210 -0
  114. package/src/providers/src/validate.ts +39 -0
  115. package/src/providers/src/zai-client.ts +47 -0
  116. package/src/skills/index.ts +34 -0
  117. package/src/skills/loader.ts +152 -0
  118. package/src/skills/parser.ts +108 -0
  119. package/src/skills/tool.ts +87 -0
  120. package/src/skills/types.ts +41 -0
  121. package/src/skills/validator.ts +110 -0
  122. package/src/types/src/auth.ts +33 -0
  123. package/src/types/src/config.ts +36 -0
  124. package/src/types/src/index.ts +20 -0
  125. package/src/types/src/provider.ts +71 -0
@@ -0,0 +1,167 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import DESCRIPTION from './patch.txt' with { type: 'text' };
4
+ import { createToolError, type ToolResponse } from '../error.ts';
5
+ import { applyPatchOperations } from './patch/apply.ts';
6
+ import { parsePatchInput } from './patch/parse.ts';
7
+ import type {
8
+ AppliedPatchOperation,
9
+ PatchOperation,
10
+ RejectedPatch,
11
+ } from './patch/types.ts';
12
+
13
+ function serializeChanges(operations: AppliedPatchOperation[]) {
14
+ return operations.map((operation) => ({
15
+ filePath: operation.filePath,
16
+ kind: operation.kind,
17
+ hunks: operation.hunks.map((hunk) => ({
18
+ oldStart: hunk.oldStart,
19
+ oldLines: hunk.oldLines,
20
+ newStart: hunk.newStart,
21
+ newLines: hunk.newLines,
22
+ additions: hunk.additions,
23
+ deletions: hunk.deletions,
24
+ context: hunk.header.context,
25
+ })),
26
+ }));
27
+ }
28
+
29
+ function serializeRejected(rejected: RejectedPatch[]) {
30
+ if (rejected.length === 0) return undefined;
31
+ return rejected.map((item) => ({
32
+ filePath: item.filePath,
33
+ kind: item.kind,
34
+ reason: item.reason,
35
+ hunks:
36
+ item.operation.kind === 'update'
37
+ ? item.operation.hunks.map((hunk) => ({
38
+ oldStart: hunk.header.oldStart,
39
+ oldLines: hunk.header.oldLines,
40
+ newStart: hunk.header.newStart,
41
+ newLines: hunk.header.newLines,
42
+ context: hunk.header.context,
43
+ lines: hunk.lines.map((line) => ({
44
+ kind: line.kind,
45
+ content: line.content,
46
+ })),
47
+ }))
48
+ : undefined,
49
+ }));
50
+ }
51
+
52
+ export function buildApplyPatchTool(projectRoot: string): {
53
+ name: string;
54
+ tool: Tool;
55
+ } {
56
+ const applyPatch = tool({
57
+ description: DESCRIPTION,
58
+ inputSchema: z.object({
59
+ patch: z.string().min(1).describe('Unified diff patch content'),
60
+ allowRejects: z
61
+ .boolean()
62
+ .optional()
63
+ .default(false)
64
+ .describe(
65
+ 'Allow hunks to be rejected without failing the whole operation',
66
+ ),
67
+ fuzzyMatch: z
68
+ .boolean()
69
+ .optional()
70
+ .default(true)
71
+ .describe(
72
+ 'Enable fuzzy matching with whitespace normalization (converts tabs to spaces for matching)',
73
+ ),
74
+ }),
75
+ async execute({
76
+ patch,
77
+ allowRejects = false,
78
+ fuzzyMatch = true,
79
+ }: {
80
+ patch: string;
81
+ allowRejects?: boolean;
82
+ fuzzyMatch?: boolean;
83
+ }): Promise<
84
+ ToolResponse<{
85
+ output: string;
86
+ changes: unknown[];
87
+ artifact: unknown;
88
+ rejected?: unknown[];
89
+ }>
90
+ > {
91
+ if (!patch || patch.trim().length === 0) {
92
+ return createToolError(
93
+ 'Missing required parameter: patch',
94
+ 'validation',
95
+ {
96
+ parameter: 'patch',
97
+ value: patch,
98
+ suggestion: 'Provide patch content in enveloped format',
99
+ },
100
+ );
101
+ }
102
+
103
+ let operations: PatchOperation[];
104
+ try {
105
+ const parsed = parsePatchInput(patch);
106
+ operations = parsed.operations;
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ return createToolError(message, 'validation', {
110
+ parameter: 'patch',
111
+ suggestion:
112
+ 'Provide patch content using the enveloped format (*** Begin Patch ... *** End Patch) or standard unified diff format (---/+++ headers).',
113
+ });
114
+ }
115
+
116
+ try {
117
+ const result = await applyPatchOperations(projectRoot, operations, {
118
+ useFuzzy: fuzzyMatch,
119
+ allowRejects,
120
+ });
121
+
122
+ const changes = serializeChanges(result.operations);
123
+ const rejected = serializeRejected(result.rejected);
124
+
125
+ const output: string[] = [];
126
+ if (result.operations.length > 0) {
127
+ output.push(
128
+ `Applied ${result.operations.length} operation${result.operations.length === 1 ? '' : 's'}`,
129
+ );
130
+ }
131
+ if (allowRejects && result.rejected.length > 0) {
132
+ output.push(
133
+ `Skipped ${result.rejected.length} operation${result.rejected.length === 1 ? '' : 's'} due to mismatches`,
134
+ );
135
+ }
136
+ if (output.length === 0) {
137
+ output.push('No changes applied');
138
+ }
139
+
140
+ return {
141
+ ok: true,
142
+ output: output.join('; '),
143
+ changes,
144
+ artifact: {
145
+ kind: 'file_diff',
146
+ patch: result.normalizedPatch,
147
+ summary: result.summary,
148
+ },
149
+ rejected,
150
+ };
151
+ } catch (error) {
152
+ const errorMessage =
153
+ error instanceof Error ? error.message : String(error);
154
+ return createToolError(
155
+ `Failed to apply patch: ${errorMessage}`,
156
+ 'execution',
157
+ {
158
+ suggestion:
159
+ 'Check that the patch format is correct and target files exist',
160
+ },
161
+ );
162
+ }
163
+ },
164
+ });
165
+
166
+ return { name: 'apply_patch', tool: applyPatch };
167
+ }
@@ -0,0 +1,207 @@
1
+ Apply a patch to modify one or more files. The tool accepts the **enveloped patch format** (preferred) and will also auto-convert standard unified diffs (`--- / +++`) if you provide one.
2
+
3
+ **Quick checklist before you call the tool:**
4
+ - Finished patch must include both `*** Begin Patch` and `*** End Patch`
5
+ - Each change needs a directive (`*** Add File`, `*** Update File`, `*** Delete File`)
6
+ - Include real context lines (prefixed with space) around your changes
7
+ - Keep paths relative to the project root
8
+ - **Use multiple `@@` hunks for multiple edits to the same file - do NOT make separate tool calls**
9
+
10
+ **RECOMMENDED: Use apply_patch for targeted file edits to avoid rewriting entire files and wasting tokens.**
11
+
12
+ **FUZZY MATCHING**: By default, fuzzy matching is enabled to handle whitespace differences (tabs vs spaces).
13
+ Exact matching is tried first, then normalized matching if exact fails. Disable with `fuzzyMatch: false` if needed.
14
+
15
+ Use `apply_patch` only when:
16
+ - You want to make targeted edits to specific lines (primary use case)
17
+ - You want to make multiple related changes across different files in a single operation
18
+ - You need to add/delete entire files along with modifications
19
+ - You have JUST read the file immediately before (within the same response) and are confident the content hasn't changed
20
+
21
+ **CRITICAL - ALWAYS READ BEFORE PATCHING**: You MUST read the file content immediately before creating a patch.
22
+ Never rely on memory or previous reads. Even with fuzzy matching enabled (tolerates tabs vs spaces),
23
+ If the file content has changed significantly since you last read it, the patch may still fail.
24
+
25
+ **ALLOW REJECTS**: If you are applying a patch that might include stale hunks, set `allowRejects: true`.
26
+ The tool will apply the hunks it can and skip the rest, returning the skipped hunks with reasons.
27
+
28
+ **ALREADY APPLIED?**: If the removal lines are already gone or the additions are already present, the tool will treat the hunk as applied and move on—no need to resend the same change.
29
+
30
+ **Alternative: Use the `edit` tool if you need fuzzy matching or structured operations.**
31
+
32
+ ## ⚠️ CRITICAL: Multiple Edits to Same File
33
+
34
+ **DO NOT make separate `apply_patch` calls for the same file!**
35
+
36
+ Instead, use multiple `@@` hunks in a single patch:
37
+
38
+ ```
39
+ *** Begin Patch
40
+ *** Update File: src/app.ts
41
+ @@ first change - line 10
42
+ function init() {
43
+ - const port = 3000;
44
+ + const port = 8080;
45
+ return port;
46
+ }
47
+ @@ second change - line 25
48
+ function start() {
49
+ - console.log("Starting...");
50
+ + console.log("Server starting...");
51
+ init();
52
+ }
53
+ @@ third change - line 40
54
+ function cleanup() {
55
+ - // TODO
56
+ + console.log("Cleaning up...");
57
+ }
58
+ *** End Patch
59
+ ```
60
+
61
+ **This makes ONE tool call instead of three, saving tokens and reducing latency.**
62
+
63
+ ## Patch Formats
64
+
65
+ ### Preferred: Enveloped Patch
66
+
67
+ All patches must be wrapped in markers and use explicit file directives:
68
+
69
+ ```
70
+ *** Begin Patch
71
+ *** Add File: path/to/file.txt
72
+ +line 1
73
+ +line 2
74
+ *** Update File: path/to/other.txt
75
+ -old line
76
+ +new line
77
+ *** Delete File: path/to/delete.txt
78
+ *** End Patch
79
+ ```
80
+
81
+ ### Also Supported: Standard Unified Diff
82
+
83
+ You can paste a regular `git diff` style patch:
84
+
85
+ ```
86
+ diff --git a/src/app.ts b/src/app.ts
87
+ --- a/src/app.ts
88
+ +++ b/src/app.ts
89
+ @@
90
+ -const PORT = 3000;
91
+ +const PORT = 8080;
92
+ ```
93
+
94
+ The tool will convert it automatically, but the enveloped format is still recommended because it is more explicit and easier to control.
95
+
96
+ ## File Operations
97
+
98
+ ### Add a new file:
99
+ ```
100
+ *** Begin Patch
101
+ *** Add File: src/hello.ts
102
+ +export function hello() {
103
+ + console.log("Hello!");
104
+ +}
105
+ *** End Patch
106
+ ```
107
+
108
+ ### Update an existing file (simple replacement):
109
+ ```
110
+ *** Begin Patch
111
+ *** Update File: src/config.ts
112
+ -const PORT = 3000;
113
+ +const PORT = 8080;
114
+ *** End Patch
115
+ ```
116
+
117
+ **CRITICAL**: The `-` lines must match EXACTLY what's in the file, character-for-character. If you're not 100% certain, use the `edit` tool instead.
118
+
119
+ ### Update with context (recommended for precision):
120
+ ```
121
+ *** Begin Patch
122
+ *** Update File: src/app.ts
123
+ @@ function main()
124
+ function main() {
125
+ - console.log("old");
126
+ + console.log("new");
127
+ }
128
+ *** End Patch
129
+ ```
130
+
131
+ **IMPORTANT**:
132
+ - The `@@` line is an OPTIONAL hint to help locate the change - it's a comment, not parsed as context
133
+ - REQUIRED: Actual context lines (starting with space ` `) that match the file exactly
134
+ - The context lines with space prefix are what the tool uses to find the location
135
+ - The `@@` line just helps humans/AI understand what section you're editing
136
+
137
+
138
+ ### Update multiple locations in the same file:
139
+ ```
140
+ *** Begin Patch
141
+ *** Update File: src/app.ts
142
+ @@ first section - near line 10
143
+ function init() {
144
+ - const port = 3000;
145
+ + const port = 8080;
146
+ return port;
147
+ }
148
+ @@ second section - near line 25
149
+ function start() {
150
+ - console.log("Starting...");
151
+ + console.log("Server starting...");
152
+ init();
153
+ }
154
+ *** End Patch
155
+ ```
156
+
157
+ **IMPORTANT**: Use separate `@@` headers for each non-consecutive change location. This allows multiple edits to the same file in one patch, saving tokens and reducing tool calls.
158
+
159
+ ### Delete a file:
160
+ ```
161
+ *** Begin Patch
162
+ *** Delete File: old/unused.ts
163
+ *** End Patch
164
+ ```
165
+
166
+ ### Multiple operations in one patch:
167
+ ```
168
+ *** Begin Patch
169
+ *** Add File: new.txt
170
+ +New content
171
+ *** Update File: existing.txt
172
+ -old
173
+ +new
174
+ *** Delete File: obsolete.txt
175
+ *** End Patch
176
+ ```
177
+
178
+ ## Line Prefixes
179
+
180
+ - Lines starting with `+` are added
181
+ - Lines starting with `-` are removed
182
+ - Lines starting with ` ` (space) are context (kept unchanged)
183
+ - Lines starting with `@@` are optional hints/comments (not parsed as context)
184
+
185
+ ## Common Errors
186
+
187
+ **"Missing *** End Patch marker"**: Every patch MUST end with `*** End Patch` on its own line.
188
+ This is the most common error. Double-check your patch ends with:
189
+ ```
190
+ *** End Patch
191
+ ```
192
+
193
+ **"Failed to find expected lines"**: The file content doesn't match your patch. Common causes:
194
+ - Missing context lines (lines with space prefix)
195
+ - Using `@@` line as context instead of real context lines
196
+ - The file content has changed since you read it
197
+ - Whitespace/indentation mismatch
198
+
199
+ **Solution**: Always read the file immediately before patching and include actual context lines with space prefix.
200
+
201
+ ## Important Notes
202
+
203
+ - **Patches are fragile**: Any mismatch in whitespace, indentation, or content will cause failure
204
+ - **Use `edit` for reliability**: The `edit` tool can make targeted changes without requiring exact matches
205
+ - All file paths are relative to the project root
206
+ - The enveloped format is the most reliable; unified diffs are converted automatically if provided
207
+ - Always wrap patches with `*** Begin Patch` and `*** End Patch` when you write them manually
@@ -0,0 +1,55 @@
1
+ import { tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import DESCRIPTION from './progress.txt' with { type: 'text' };
4
+
5
+ // Progress update tool: allows the model to emit lightweight status signals
6
+ // without revealing chain-of-thought. The runner/UI should surface these
7
+ // messages immediately.
8
+ const StageEnum = z.enum([
9
+ 'planning',
10
+ 'discovering',
11
+ 'generating',
12
+ 'preparing',
13
+ 'writing',
14
+ 'verifying',
15
+ ]);
16
+
17
+ export const progressUpdateTool = tool({
18
+ description: DESCRIPTION,
19
+ inputSchema: z.object({
20
+ message: z
21
+ .string()
22
+ .min(1)
23
+ .max(200)
24
+ .describe('Short, user-facing status message (<= 200 chars).'),
25
+ pct: z
26
+ .number()
27
+ .min(0)
28
+ .max(100)
29
+ .optional()
30
+ .describe('Optional overall progress percent 0-100.'),
31
+ stage: StageEnum.optional().default('planning'),
32
+ }),
33
+ async execute({
34
+ message,
35
+ pct,
36
+ stage,
37
+ }: {
38
+ message: string;
39
+ pct?: number;
40
+ stage?: z.infer<typeof StageEnum>;
41
+ }) {
42
+ // Keep the tool lightweight; no side effects beyond the event itself.
43
+ // Returning the normalized payload allows generic renderers to inspect it if needed.
44
+ const normalizedPct =
45
+ typeof pct === 'number'
46
+ ? Math.min(100, Math.max(0, Math.round(pct)))
47
+ : undefined;
48
+ return {
49
+ ok: true,
50
+ message,
51
+ pct: normalizedPct,
52
+ stage: stage ?? 'planning',
53
+ } as const;
54
+ },
55
+ });
@@ -0,0 +1,7 @@
1
+ - Emit a short, user-facing progress/status update
2
+ - Supports optional `pct` (0–100) and `stage` indicators
3
+ - Lightweight; intended for immediate UI display
4
+
5
+ Usage tips:
6
+ - Keep messages short (<= 200 chars) and informative
7
+ - Use multiple updates during long-running tasks
@@ -0,0 +1,125 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import { spawn } from 'node:child_process';
4
+ import { join } from 'node:path';
5
+ import DESCRIPTION from './ripgrep.txt' with { type: 'text' };
6
+ import { createToolError, type ToolResponse } from '../error.ts';
7
+ import { resolveBinary } from '../bin-manager.ts';
8
+
9
+ export function buildRipgrepTool(projectRoot: string): {
10
+ name: string;
11
+ tool: Tool;
12
+ } {
13
+ const rg = tool({
14
+ description: DESCRIPTION,
15
+ inputSchema: z.object({
16
+ query: z.string().min(1).describe('Search pattern (regex by default)'),
17
+ path: z
18
+ .string()
19
+ .optional()
20
+ .default('.')
21
+ .describe('Relative path to search in'),
22
+ ignoreCase: z.boolean().optional().default(false),
23
+ glob: z
24
+ .array(z.string())
25
+ .optional()
26
+ .describe('One or more glob patterns to include'),
27
+ maxResults: z.number().int().min(1).max(5000).optional().default(500),
28
+ }),
29
+ async execute({
30
+ query,
31
+ path = '.',
32
+ ignoreCase,
33
+ glob,
34
+ maxResults = 500,
35
+ }: {
36
+ query: string;
37
+ path?: string;
38
+ ignoreCase?: boolean;
39
+ glob?: string[];
40
+ maxResults?: number;
41
+ }): Promise<
42
+ ToolResponse<{
43
+ count: number;
44
+ matches: Array<{ file: string; line: number; text: string }>;
45
+ }>
46
+ > {
47
+ function expandTilde(p: string) {
48
+ const home = process.env.HOME || process.env.USERPROFILE || '';
49
+ if (!home) return p;
50
+ if (p === '~') return home;
51
+ if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
52
+ return p;
53
+ }
54
+ const p = expandTilde(String(path ?? '.')).trim();
55
+ const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
56
+ const target = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
57
+ const args = ['--no-heading', '--line-number', '--color=never'];
58
+ if (ignoreCase) args.push('-i');
59
+ if (Array.isArray(glob)) for (const g of glob) args.push('-g', g);
60
+ args.push('--max-count', String(maxResults));
61
+ args.push(query);
62
+ args.push(target);
63
+
64
+ try {
65
+ const rgBin = await resolveBinary('rg');
66
+ return await new Promise((resolve) => {
67
+ const proc = spawn(rgBin, args, { cwd: projectRoot });
68
+ let stdout = '';
69
+ let stderr = '';
70
+
71
+ proc.stdout.on('data', (data) => {
72
+ stdout += data.toString();
73
+ });
74
+
75
+ proc.stderr.on('data', (data) => {
76
+ stderr += data.toString();
77
+ });
78
+
79
+ proc.on('close', (code) => {
80
+ if (code !== 0 && code !== 1) {
81
+ resolve(
82
+ createToolError(
83
+ stderr.trim() || 'ripgrep failed',
84
+ 'execution',
85
+ {
86
+ suggestion:
87
+ 'Check if ripgrep (rg) is installed and the query is valid',
88
+ },
89
+ ),
90
+ );
91
+ return;
92
+ }
93
+
94
+ const lines = stdout
95
+ .split('\n')
96
+ .filter(Boolean)
97
+ .slice(0, maxResults);
98
+ const matches = lines.map((l) => {
99
+ const parts = l.split(':');
100
+ if (parts.length < 3) return { file: '', line: 0, text: l };
101
+ const file = parts[0];
102
+ const line = Number.parseInt(parts[1], 10);
103
+ const text = parts.slice(2).join(':');
104
+ return { file, line, text };
105
+ });
106
+ resolve({ ok: true, count: matches.length, matches });
107
+ });
108
+
109
+ proc.on('error', (err) => {
110
+ resolve(
111
+ createToolError(String(err), 'execution', {
112
+ suggestion: 'Ensure ripgrep (rg) is installed',
113
+ }),
114
+ );
115
+ });
116
+ });
117
+ } catch (err) {
118
+ return createToolError(String(err), 'execution', {
119
+ suggestion: 'Ensure ripgrep (rg) is installed',
120
+ });
121
+ }
122
+ },
123
+ });
124
+ return { name: 'ripgrep', tool: rg };
125
+ }
@@ -0,0 +1,7 @@
1
+ - Search files using ripgrep (rg) with regex patterns
2
+ - Returns a flat list of matches with `file`, `line`, and `text`
3
+ - Supports include globs and case-insensitive search
4
+
5
+ Usage tips:
6
+ - Use the Grep tool for a friendly summary grouped by file
7
+ - Use the Glob tool first to limit the search set if needed