@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.
- package/README.md +338 -0
- package/package.json +128 -0
- package/src/agent/types.ts +19 -0
- package/src/auth/src/copilot-oauth.ts +190 -0
- package/src/auth/src/index.ts +100 -0
- package/src/auth/src/oauth.ts +234 -0
- package/src/auth/src/openai-oauth.ts +394 -0
- package/src/auth/src/wallet.ts +51 -0
- package/src/browser.ts +32 -0
- package/src/config/src/index.ts +110 -0
- package/src/config/src/manager.ts +181 -0
- package/src/config/src/paths.ts +98 -0
- package/src/core/src/errors.ts +102 -0
- package/src/core/src/index.ts +108 -0
- package/src/core/src/providers/resolver.ts +244 -0
- package/src/core/src/streaming/artifacts.ts +41 -0
- package/src/core/src/terminals/bun-pty.ts +13 -0
- package/src/core/src/terminals/circular-buffer.ts +30 -0
- package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
- package/src/core/src/terminals/index.ts +8 -0
- package/src/core/src/terminals/manager.ts +158 -0
- package/src/core/src/terminals/rust-libs.ts +30 -0
- package/src/core/src/terminals/terminal.ts +132 -0
- package/src/core/src/tools/bin-manager.ts +250 -0
- package/src/core/src/tools/builtin/bash.ts +155 -0
- package/src/core/src/tools/builtin/bash.txt +7 -0
- package/src/core/src/tools/builtin/file-cache.ts +39 -0
- package/src/core/src/tools/builtin/finish.ts +12 -0
- package/src/core/src/tools/builtin/finish.txt +10 -0
- package/src/core/src/tools/builtin/fs/cd.ts +19 -0
- package/src/core/src/tools/builtin/fs/cd.txt +5 -0
- package/src/core/src/tools/builtin/fs/index.ts +20 -0
- package/src/core/src/tools/builtin/fs/ls.ts +72 -0
- package/src/core/src/tools/builtin/fs/ls.txt +8 -0
- package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
- package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
- package/src/core/src/tools/builtin/fs/read.ts +119 -0
- package/src/core/src/tools/builtin/fs/read.txt +8 -0
- package/src/core/src/tools/builtin/fs/tree.ts +149 -0
- package/src/core/src/tools/builtin/fs/tree.txt +11 -0
- package/src/core/src/tools/builtin/fs/util.ts +95 -0
- package/src/core/src/tools/builtin/fs/write.ts +106 -0
- package/src/core/src/tools/builtin/fs/write.txt +11 -0
- package/src/core/src/tools/builtin/git.commit.txt +6 -0
- package/src/core/src/tools/builtin/git.diff.txt +5 -0
- package/src/core/src/tools/builtin/git.status.txt +5 -0
- package/src/core/src/tools/builtin/git.ts +151 -0
- package/src/core/src/tools/builtin/glob.ts +128 -0
- package/src/core/src/tools/builtin/glob.txt +10 -0
- package/src/core/src/tools/builtin/grep.ts +136 -0
- package/src/core/src/tools/builtin/grep.txt +9 -0
- package/src/core/src/tools/builtin/ignore.ts +45 -0
- package/src/core/src/tools/builtin/patch/apply.ts +546 -0
- package/src/core/src/tools/builtin/patch/constants.ts +5 -0
- package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
- package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
- package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
- package/src/core/src/tools/builtin/patch/parse.ts +28 -0
- package/src/core/src/tools/builtin/patch/text.ts +23 -0
- package/src/core/src/tools/builtin/patch/types.ts +82 -0
- package/src/core/src/tools/builtin/patch.ts +167 -0
- package/src/core/src/tools/builtin/patch.txt +207 -0
- package/src/core/src/tools/builtin/progress.ts +55 -0
- package/src/core/src/tools/builtin/progress.txt +7 -0
- package/src/core/src/tools/builtin/ripgrep.ts +125 -0
- package/src/core/src/tools/builtin/ripgrep.txt +7 -0
- package/src/core/src/tools/builtin/terminal.ts +300 -0
- package/src/core/src/tools/builtin/terminal.txt +93 -0
- package/src/core/src/tools/builtin/todos.ts +66 -0
- package/src/core/src/tools/builtin/todos.txt +7 -0
- package/src/core/src/tools/builtin/websearch.ts +250 -0
- package/src/core/src/tools/builtin/websearch.txt +12 -0
- package/src/core/src/tools/error.ts +67 -0
- package/src/core/src/tools/loader.ts +421 -0
- package/src/core/src/types/index.ts +11 -0
- package/src/core/src/types/types.ts +4 -0
- package/src/core/src/utils/ansi.ts +27 -0
- package/src/core/src/utils/debug.ts +40 -0
- package/src/core/src/utils/logger.ts +150 -0
- package/src/index.ts +313 -0
- package/src/prompts/src/agents/build.txt +89 -0
- package/src/prompts/src/agents/general.txt +15 -0
- package/src/prompts/src/agents/plan.txt +10 -0
- package/src/prompts/src/agents/research.txt +50 -0
- package/src/prompts/src/base.txt +24 -0
- package/src/prompts/src/debug.ts +104 -0
- package/src/prompts/src/index.ts +1 -0
- package/src/prompts/src/modes/oneshot.txt +9 -0
- package/src/prompts/src/providers/anthropic.txt +247 -0
- package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
- package/src/prompts/src/providers/default.txt +466 -0
- package/src/prompts/src/providers/google.txt +230 -0
- package/src/prompts/src/providers/moonshot.txt +24 -0
- package/src/prompts/src/providers/openai.txt +414 -0
- package/src/prompts/src/providers.ts +143 -0
- package/src/providers/src/anthropic-caching.ts +202 -0
- package/src/providers/src/anthropic-oauth-client.ts +157 -0
- package/src/providers/src/authorization.ts +17 -0
- package/src/providers/src/catalog-manual.ts +135 -0
- package/src/providers/src/catalog-merged.ts +9 -0
- package/src/providers/src/catalog.ts +8329 -0
- package/src/providers/src/copilot-client.ts +39 -0
- package/src/providers/src/env.ts +31 -0
- package/src/providers/src/google-client.ts +16 -0
- package/src/providers/src/index.ts +75 -0
- package/src/providers/src/moonshot-client.ts +25 -0
- package/src/providers/src/oauth-models.ts +39 -0
- package/src/providers/src/openai-oauth-client.ts +108 -0
- package/src/providers/src/opencode-client.ts +64 -0
- package/src/providers/src/openrouter-client.ts +31 -0
- package/src/providers/src/pricing.ts +178 -0
- package/src/providers/src/setu-client.ts +643 -0
- package/src/providers/src/utils.ts +210 -0
- package/src/providers/src/validate.ts +39 -0
- package/src/providers/src/zai-client.ts +47 -0
- package/src/skills/index.ts +34 -0
- package/src/skills/loader.ts +152 -0
- package/src/skills/parser.ts +108 -0
- package/src/skills/tool.ts +87 -0
- package/src/skills/types.ts +41 -0
- package/src/skills/validator.ts +110 -0
- package/src/types/src/auth.ts +33 -0
- package/src/types/src/config.ts +36 -0
- package/src/types/src/index.ts +20 -0
- package/src/types/src/provider.ts +71 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { tool, type Tool } from 'ai';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import GIT_STATUS_DESCRIPTION from './git.status.txt' with { type: 'text' };
|
|
6
|
+
import GIT_DIFF_DESCRIPTION from './git.diff.txt' with { type: 'text' };
|
|
7
|
+
import GIT_COMMIT_DESCRIPTION from './git.commit.txt' with { type: 'text' };
|
|
8
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
export function buildGitTools(
|
|
13
|
+
projectRoot: string,
|
|
14
|
+
): Array<{ name: string; tool: Tool }> {
|
|
15
|
+
// Helper to find git root directory
|
|
16
|
+
async function findGitRoot(): Promise<string> {
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await execAsync(
|
|
19
|
+
`git -C "${projectRoot}" rev-parse --show-toplevel`,
|
|
20
|
+
);
|
|
21
|
+
return stdout.trim() || projectRoot;
|
|
22
|
+
} catch {
|
|
23
|
+
return projectRoot;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function inRepo(): Promise<boolean> {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await execAsync(
|
|
30
|
+
`git -C "${projectRoot}" rev-parse --is-inside-work-tree`,
|
|
31
|
+
);
|
|
32
|
+
return stdout.trim() === 'true';
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const git_status = tool({
|
|
39
|
+
description: GIT_STATUS_DESCRIPTION,
|
|
40
|
+
inputSchema: z.object({}),
|
|
41
|
+
async execute(): Promise<
|
|
42
|
+
ToolResponse<{ staged: number; unstaged: number; raw: string[] }>
|
|
43
|
+
> {
|
|
44
|
+
if (!(await inRepo())) {
|
|
45
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
46
|
+
suggestion: 'Initialize a git repository with git init',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const gitRoot = await findGitRoot();
|
|
50
|
+
const { stdout } = await execAsync(
|
|
51
|
+
`git -C "${gitRoot}" status --porcelain=v1`,
|
|
52
|
+
);
|
|
53
|
+
const lines = stdout.split('\n').filter(Boolean);
|
|
54
|
+
let staged = 0;
|
|
55
|
+
let unstaged = 0;
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const x = line[0];
|
|
58
|
+
const y = line[1];
|
|
59
|
+
if (!x || !y) continue;
|
|
60
|
+
if (x === '!' && y === '!') continue; // ignored files
|
|
61
|
+
const isUntracked = x === '?' && y === '?';
|
|
62
|
+
if (x !== ' ' && !isUntracked) staged += 1;
|
|
63
|
+
if (isUntracked || y !== ' ') unstaged += 1;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
staged,
|
|
68
|
+
unstaged,
|
|
69
|
+
raw: lines.slice(0, 200),
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const git_diff = tool({
|
|
75
|
+
description: GIT_DIFF_DESCRIPTION,
|
|
76
|
+
inputSchema: z.object({ all: z.boolean().optional().default(false) }),
|
|
77
|
+
async execute({
|
|
78
|
+
all,
|
|
79
|
+
}: {
|
|
80
|
+
all?: boolean;
|
|
81
|
+
}): Promise<ToolResponse<{ all: boolean; patch: string }>> {
|
|
82
|
+
if (!(await inRepo())) {
|
|
83
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
84
|
+
suggestion: 'Initialize a git repository with git init',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const gitRoot = await findGitRoot();
|
|
88
|
+
// When all=true, show full working tree diff relative to HEAD
|
|
89
|
+
// so both staged and unstaged changes are included. Otherwise,
|
|
90
|
+
// show only the staged diff (index vs HEAD).
|
|
91
|
+
const cmd = all
|
|
92
|
+
? `git -C "${gitRoot}" diff HEAD`
|
|
93
|
+
: `git -C "${gitRoot}" diff --staged`;
|
|
94
|
+
const { stdout } = await execAsync(cmd, { maxBuffer: 10 * 1024 * 1024 });
|
|
95
|
+
const limited = stdout.split('\n').slice(0, 5000).join('\n');
|
|
96
|
+
return { ok: true, all: !!all, patch: limited };
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const git_commit = tool({
|
|
101
|
+
description: GIT_COMMIT_DESCRIPTION,
|
|
102
|
+
inputSchema: z.object({
|
|
103
|
+
message: z.string().min(5),
|
|
104
|
+
amend: z.boolean().optional().default(false),
|
|
105
|
+
signoff: z.boolean().optional().default(false),
|
|
106
|
+
}),
|
|
107
|
+
async execute({
|
|
108
|
+
message,
|
|
109
|
+
amend,
|
|
110
|
+
signoff,
|
|
111
|
+
}: {
|
|
112
|
+
message: string;
|
|
113
|
+
amend?: boolean;
|
|
114
|
+
signoff?: boolean;
|
|
115
|
+
}): Promise<ToolResponse<{ result: string }>> {
|
|
116
|
+
if (!(await inRepo())) {
|
|
117
|
+
return createToolError('Not a git repository', 'not_found', {
|
|
118
|
+
suggestion: 'Initialize a git repository with git init',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const gitRoot = await findGitRoot();
|
|
122
|
+
const args = [
|
|
123
|
+
'git',
|
|
124
|
+
'-C',
|
|
125
|
+
`"${gitRoot}"`,
|
|
126
|
+
'commit',
|
|
127
|
+
'-m',
|
|
128
|
+
`"${message.replace(/"/g, '\\"')}"`,
|
|
129
|
+
];
|
|
130
|
+
if (amend) args.push('--amend');
|
|
131
|
+
if (signoff) args.push('--signoff');
|
|
132
|
+
try {
|
|
133
|
+
const { stdout } = await execAsync(args.join(' '));
|
|
134
|
+
return { ok: true, result: stdout.trim() };
|
|
135
|
+
} catch (error: unknown) {
|
|
136
|
+
const err = error as { stderr?: string; message?: string };
|
|
137
|
+
const txt = err.stderr || err.message || 'git commit failed';
|
|
138
|
+
return createToolError(txt, 'execution', {
|
|
139
|
+
suggestion:
|
|
140
|
+
'Check if there are staged changes and the commit message is valid',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return [
|
|
147
|
+
{ name: 'git_status', tool: git_status },
|
|
148
|
+
{ name: 'git_diff', tool: git_diff },
|
|
149
|
+
{ name: 'git_commit', tool: git_commit },
|
|
150
|
+
];
|
|
151
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { tool, type Tool } from 'ai';
|
|
2
|
+
import { z } from 'zod/v3';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { stat } from 'node:fs/promises';
|
|
6
|
+
import DESCRIPTION from './glob.txt' with { type: 'text' };
|
|
7
|
+
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
8
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
9
|
+
|
|
10
|
+
function expandTilde(p: string) {
|
|
11
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
12
|
+
if (!home) return p;
|
|
13
|
+
if (p === '~') return home;
|
|
14
|
+
if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildGlobTool(projectRoot: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
tool: Tool;
|
|
21
|
+
} {
|
|
22
|
+
const globTool = tool({
|
|
23
|
+
description: DESCRIPTION,
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
pattern: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1)
|
|
28
|
+
.describe(
|
|
29
|
+
'Glob pattern to match files (e.g., "*.ts", "**/*.tsx", "src/**/*.{js,ts}")',
|
|
30
|
+
),
|
|
31
|
+
path: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Directory to search in (default: project root)'),
|
|
35
|
+
ignore: z
|
|
36
|
+
.array(z.string())
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Additional glob patterns to exclude'),
|
|
39
|
+
limit: z
|
|
40
|
+
.number()
|
|
41
|
+
.int()
|
|
42
|
+
.min(1)
|
|
43
|
+
.max(1000)
|
|
44
|
+
.optional()
|
|
45
|
+
.default(100)
|
|
46
|
+
.describe('Maximum number of files to return'),
|
|
47
|
+
}),
|
|
48
|
+
async execute({
|
|
49
|
+
pattern,
|
|
50
|
+
path = '.',
|
|
51
|
+
ignore,
|
|
52
|
+
limit = 100,
|
|
53
|
+
}: {
|
|
54
|
+
pattern: string;
|
|
55
|
+
path?: string;
|
|
56
|
+
ignore?: string[];
|
|
57
|
+
limit?: number;
|
|
58
|
+
}): Promise<
|
|
59
|
+
ToolResponse<{
|
|
60
|
+
count: number;
|
|
61
|
+
total: number;
|
|
62
|
+
files: string[];
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
}>
|
|
65
|
+
> {
|
|
66
|
+
const p = expandTilde(String(path || '.')).trim();
|
|
67
|
+
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
68
|
+
const searchPath = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
69
|
+
|
|
70
|
+
// Build ignore patterns
|
|
71
|
+
const ignorePatterns = defaultIgnoreGlobs(ignore);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Use fast-glob to find matching files
|
|
75
|
+
const files = await fg(pattern, {
|
|
76
|
+
cwd: searchPath,
|
|
77
|
+
ignore: ignorePatterns,
|
|
78
|
+
onlyFiles: true,
|
|
79
|
+
absolute: false,
|
|
80
|
+
dot: false,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Get file stats for sorting by modification time
|
|
84
|
+
const filesWithStats = await Promise.all(
|
|
85
|
+
files.map(async (file) => {
|
|
86
|
+
const fullPath = join(searchPath, file);
|
|
87
|
+
try {
|
|
88
|
+
const stats = await stat(fullPath);
|
|
89
|
+
return {
|
|
90
|
+
file,
|
|
91
|
+
mtime: stats.mtime.getTime(),
|
|
92
|
+
};
|
|
93
|
+
} catch {
|
|
94
|
+
return {
|
|
95
|
+
file,
|
|
96
|
+
mtime: 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Sort by modification time (most recent first) and limit
|
|
103
|
+
filesWithStats.sort((a, b) => b.mtime - a.mtime);
|
|
104
|
+
const limitedFiles = filesWithStats.slice(0, limit).map((f) => f.file);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
ok: true,
|
|
108
|
+
count: limitedFiles.length,
|
|
109
|
+
total: files.length,
|
|
110
|
+
files: limitedFiles,
|
|
111
|
+
truncated: files.length > limit,
|
|
112
|
+
};
|
|
113
|
+
} catch (error: unknown) {
|
|
114
|
+
const err = error as { message?: string };
|
|
115
|
+
return createToolError(
|
|
116
|
+
`Glob search failed: ${err.message || String(error)}`,
|
|
117
|
+
'execution',
|
|
118
|
+
{
|
|
119
|
+
parameter: 'pattern',
|
|
120
|
+
value: pattern,
|
|
121
|
+
suggestion: 'Check if the pattern syntax is valid',
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return { name: 'glob', tool: globTool };
|
|
128
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
- Find files matching glob patterns (e.g., "*.ts", "**/*.tsx", "src/**/*.{js,ts}")
|
|
2
|
+
- Returns a list of matching file paths relative to the search directory
|
|
3
|
+
- Supports standard glob syntax: * (any chars), ** (any dirs), {a,b} (alternatives), [abc] (character sets)
|
|
4
|
+
- Automatically excludes common build/cache folders (node_modules, dist, .git, etc.)
|
|
5
|
+
- Results sorted by modification time (most recent first)
|
|
6
|
+
|
|
7
|
+
Usage tips:
|
|
8
|
+
- Use this tool to find files by name or extension patterns
|
|
9
|
+
- Use Grep or Ripgrep tools to search file contents
|
|
10
|
+
- Combine with path parameter to search in specific directories only
|
|
@@ -0,0 +1,136 @@
|
|
|
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 './grep.txt' with { type: 'text' };
|
|
6
|
+
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
7
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
|
+
import { resolveBinary } from '../bin-manager.ts';
|
|
9
|
+
|
|
10
|
+
function expandTilde(p: string) {
|
|
11
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
12
|
+
if (!home) return p;
|
|
13
|
+
if (p === '~') return home;
|
|
14
|
+
if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildGrepTool(projectRoot: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
tool: Tool;
|
|
21
|
+
} {
|
|
22
|
+
const grep = tool({
|
|
23
|
+
description: DESCRIPTION,
|
|
24
|
+
inputSchema: z.object({
|
|
25
|
+
pattern: z
|
|
26
|
+
.string()
|
|
27
|
+
.describe('Regex pattern to search for in file contents'),
|
|
28
|
+
path: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Directory to search in (default: project root).'),
|
|
32
|
+
include: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('File glob to include (e.g., "*.js", "*.{ts,tsx}")'),
|
|
36
|
+
ignore: z
|
|
37
|
+
.array(z.string())
|
|
38
|
+
.optional()
|
|
39
|
+
.describe('Glob patterns to exclude from search'),
|
|
40
|
+
}),
|
|
41
|
+
async execute(params: {
|
|
42
|
+
pattern: string;
|
|
43
|
+
path?: string;
|
|
44
|
+
include?: string;
|
|
45
|
+
ignore?: string[];
|
|
46
|
+
}): Promise<
|
|
47
|
+
ToolResponse<{
|
|
48
|
+
count: number;
|
|
49
|
+
matches: Array<{ file: string; line: number; text: string }>;
|
|
50
|
+
}>
|
|
51
|
+
> {
|
|
52
|
+
const pattern = String(params.pattern || '');
|
|
53
|
+
if (!pattern) {
|
|
54
|
+
return createToolError('pattern is required', 'validation', {
|
|
55
|
+
parameter: 'pattern',
|
|
56
|
+
suggestion: 'Provide a regex pattern to search for',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const p = expandTilde(String(params.path || '')).trim();
|
|
61
|
+
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
62
|
+
const searchPath = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
63
|
+
|
|
64
|
+
const rgBin = await resolveBinary('rg');
|
|
65
|
+
const args: string[] = ['-n', '--color', 'never'];
|
|
66
|
+
for (const g of defaultIgnoreGlobs(params.ignore)) {
|
|
67
|
+
args.push('--glob', g);
|
|
68
|
+
}
|
|
69
|
+
if (params.include) {
|
|
70
|
+
args.push('--glob', params.include);
|
|
71
|
+
}
|
|
72
|
+
args.push(pattern, searchPath);
|
|
73
|
+
|
|
74
|
+
let output = '';
|
|
75
|
+
try {
|
|
76
|
+
output = await new Promise<string>((resolve, reject) => {
|
|
77
|
+
const proc = spawn(rgBin, args, { cwd: projectRoot });
|
|
78
|
+
let stdout = '';
|
|
79
|
+
let stderr = '';
|
|
80
|
+
proc.stdout.on('data', (d) => {
|
|
81
|
+
stdout += d.toString();
|
|
82
|
+
});
|
|
83
|
+
proc.stderr.on('data', (d) => {
|
|
84
|
+
stderr += d.toString();
|
|
85
|
+
});
|
|
86
|
+
proc.on('close', (code) => {
|
|
87
|
+
if (code === 1) resolve('');
|
|
88
|
+
else if (code !== 0)
|
|
89
|
+
reject(new Error(stderr.trim() || 'ripgrep failed'));
|
|
90
|
+
else resolve(stdout);
|
|
91
|
+
});
|
|
92
|
+
proc.on('error', reject);
|
|
93
|
+
});
|
|
94
|
+
} catch (error: unknown) {
|
|
95
|
+
const err2 = error as { message?: string };
|
|
96
|
+
return createToolError(`ripgrep failed: ${err2.message}`, 'execution', {
|
|
97
|
+
parameter: 'pattern',
|
|
98
|
+
value: pattern,
|
|
99
|
+
suggestion:
|
|
100
|
+
'Check if ripgrep (rg) is installed and the pattern is valid',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const lines = output.trim().split('\n');
|
|
105
|
+
const matches: Array<{
|
|
106
|
+
file: string;
|
|
107
|
+
line: number;
|
|
108
|
+
text: string;
|
|
109
|
+
}> = [];
|
|
110
|
+
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (!line) continue;
|
|
113
|
+
const idx1 = line.indexOf(':');
|
|
114
|
+
const idx2 = idx1 === -1 ? -1 : line.indexOf(':', idx1 + 1);
|
|
115
|
+
if (idx1 === -1 || idx2 === -1) continue;
|
|
116
|
+
const filePath = line.slice(0, idx1);
|
|
117
|
+
const lineNumStr = line.slice(idx1 + 1, idx2);
|
|
118
|
+
const lineText = line.slice(idx2 + 1);
|
|
119
|
+
const lineNum = parseInt(lineNumStr, 10);
|
|
120
|
+
if (!filePath || !Number.isFinite(lineNum)) continue;
|
|
121
|
+
matches.push({ file: filePath, line: lineNum, text: lineText });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const limit = 500;
|
|
125
|
+
const truncated = matches.length > limit;
|
|
126
|
+
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
count: finalMatches.length,
|
|
131
|
+
matches: finalMatches,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
return { name: 'grep', tool: grep };
|
|
136
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
- Fast content search tool powered by ripgrep (rg)
|
|
2
|
+
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
3
|
+
- Optional include glob to filter files (e.g., "*.js", "*.{ts,tsx}")
|
|
4
|
+
- Returns files with at least one match and line previews, sorted by modification time
|
|
5
|
+
- Skips common build and cache folders by default; add 'ignore' patterns to refine
|
|
6
|
+
|
|
7
|
+
Usage tips:
|
|
8
|
+
- For counting matches, use the Bash tool with rg directly (do not use grep)
|
|
9
|
+
- Batch multiple searches when exploring a codebase broadly
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const IGNORE_PATTERNS: string[] = [
|
|
2
|
+
'node_modules/',
|
|
3
|
+
'__pycache__/',
|
|
4
|
+
'.git/',
|
|
5
|
+
'dist/',
|
|
6
|
+
'build/',
|
|
7
|
+
'target/',
|
|
8
|
+
'vendor/',
|
|
9
|
+
'bin/',
|
|
10
|
+
'obj/',
|
|
11
|
+
'.idea/',
|
|
12
|
+
'.vscode/',
|
|
13
|
+
'.zig-cache/',
|
|
14
|
+
'zig-out',
|
|
15
|
+
'.coverage',
|
|
16
|
+
'coverage/',
|
|
17
|
+
'vendor/',
|
|
18
|
+
'tmp/',
|
|
19
|
+
'temp/',
|
|
20
|
+
'.cache/',
|
|
21
|
+
'cache/',
|
|
22
|
+
'logs/',
|
|
23
|
+
'.venv/',
|
|
24
|
+
'venv/',
|
|
25
|
+
'env/',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function defaultIgnoreGlobs(extra?: string[]): string[] {
|
|
29
|
+
const base = IGNORE_PATTERNS.map((p) => `!${p}*`);
|
|
30
|
+
if (Array.isArray(extra) && extra.length) return base.concat(extra);
|
|
31
|
+
return base;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function toIgnoredBasenames(extra?: string[]): Set<string> {
|
|
35
|
+
const names = new Set<string>();
|
|
36
|
+
for (const p of IGNORE_PATTERNS) {
|
|
37
|
+
const n = p.replace(/\/$/, '');
|
|
38
|
+
if (n) names.add(n);
|
|
39
|
+
}
|
|
40
|
+
for (const p of extra ?? []) {
|
|
41
|
+
const n = String(p).replace(/^!/, '').replace(/\/$/, '');
|
|
42
|
+
if (n) names.add(n);
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|