@agi-cli/sdk 0.1.156 → 0.1.158
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/package.json +1 -1
- package/src/core/src/terminals/manager.ts +5 -1
- package/src/core/src/tools/bin-manager.ts +197 -0
- package/src/core/src/tools/builtin/bash.ts +2 -0
- package/src/core/src/tools/builtin/fs/ls.ts +16 -24
- package/src/core/src/tools/builtin/fs/tree.ts +80 -20
- package/src/core/src/tools/builtin/grep.ts +32 -25
- package/src/core/src/tools/builtin/ripgrep.ts +3 -1
- package/src/prompts/src/providers/anthropic.txt +2 -2
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import type { PtyOptions } from './bun-pty.ts';
|
|
|
3
3
|
import { spawn as spawnPty } from './bun-pty.ts';
|
|
4
4
|
import { Terminal } from './terminal.ts';
|
|
5
5
|
import { logger } from '../utils/logger.ts';
|
|
6
|
+
import { getAugmentedPath } from '../tools/bin-manager.ts';
|
|
6
7
|
|
|
7
8
|
const MAX_TERMINALS = 10;
|
|
8
9
|
const CLEANUP_DELAY_MS = 5 * 60 * 1000;
|
|
@@ -41,7 +42,10 @@ export class TerminalManager {
|
|
|
41
42
|
cols: 80,
|
|
42
43
|
rows: 30,
|
|
43
44
|
cwd: options.cwd,
|
|
44
|
-
env: process.env as Record<
|
|
45
|
+
env: { ...process.env, PATH: getAugmentedPath() } as Record<
|
|
46
|
+
string,
|
|
47
|
+
string
|
|
48
|
+
>,
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const pty = spawnPty(options.command, options.args || [], ptyOptions);
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
const AGI_BIN_DIR_NAME = 'bin';
|
|
6
|
+
|
|
7
|
+
let cachedBinDir: string | null = null;
|
|
8
|
+
const resolvedPaths = new Map<string, string>();
|
|
9
|
+
|
|
10
|
+
function getConfigHome(): string {
|
|
11
|
+
const cfgHome = process.env.XDG_CONFIG_HOME;
|
|
12
|
+
if (cfgHome?.trim()) return cfgHome.replace(/\\/g, '/');
|
|
13
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
14
|
+
return join(home, '.config');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAgiBinDir(): string {
|
|
18
|
+
if (cachedBinDir) return cachedBinDir;
|
|
19
|
+
cachedBinDir = join(getConfigHome(), 'agi', AGI_BIN_DIR_NAME);
|
|
20
|
+
return cachedBinDir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getPlatformKey(): string {
|
|
24
|
+
const platform = process.platform;
|
|
25
|
+
const arch = process.arch;
|
|
26
|
+
const os =
|
|
27
|
+
platform === 'darwin'
|
|
28
|
+
? 'darwin'
|
|
29
|
+
: platform === 'win32'
|
|
30
|
+
? 'windows'
|
|
31
|
+
: 'linux';
|
|
32
|
+
const cpu = arch === 'arm64' ? 'arm64' : 'x64';
|
|
33
|
+
return `${os}-${cpu}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getBinaryFileName(name: string): string {
|
|
37
|
+
if (process.platform === 'win32') return `${name}.exe`;
|
|
38
|
+
return name;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function ensureDir(dir: string): Promise<void> {
|
|
42
|
+
await fs.mkdir(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function fileExists(p: string): Promise<boolean> {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(p);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function isExecutable(p: string): Promise<boolean> {
|
|
55
|
+
try {
|
|
56
|
+
await fs.access(p, 0o1);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function makeExecutable(p: string): Promise<void> {
|
|
64
|
+
if (process.platform === 'win32') return;
|
|
65
|
+
try {
|
|
66
|
+
await fs.chmod(p, 0o755);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function whichBinary(name: string): Promise<string | null> {
|
|
71
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const proc = spawn(cmd, [name], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
74
|
+
let stdout = '';
|
|
75
|
+
proc.stdout.on('data', (d) => {
|
|
76
|
+
stdout += d.toString();
|
|
77
|
+
});
|
|
78
|
+
proc.on('close', (code) => {
|
|
79
|
+
if (code === 0 && stdout.trim()) resolve(stdout.trim().split('\n')[0]);
|
|
80
|
+
else resolve(null);
|
|
81
|
+
});
|
|
82
|
+
proc.on('error', () => resolve(null));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getVendorSearchPaths(binaryName: string): string[] {
|
|
87
|
+
const platformKey = getPlatformKey();
|
|
88
|
+
const paths: string[] = [];
|
|
89
|
+
|
|
90
|
+
const tauriResource = process.env.TAURI_RESOURCE_DIR;
|
|
91
|
+
if (tauriResource) {
|
|
92
|
+
paths.push(join(tauriResource, 'vendor', 'bin', platformKey, binaryName));
|
|
93
|
+
paths.push(join(tauriResource, 'vendor', 'bin', binaryName));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const exePath = process.execPath;
|
|
98
|
+
if (exePath) {
|
|
99
|
+
const exeDir = join(exePath, '..');
|
|
100
|
+
paths.push(join(exeDir, 'vendor', 'bin', platformKey, binaryName));
|
|
101
|
+
paths.push(
|
|
102
|
+
join(
|
|
103
|
+
exeDir,
|
|
104
|
+
'..',
|
|
105
|
+
'Resources',
|
|
106
|
+
'vendor',
|
|
107
|
+
'bin',
|
|
108
|
+
platformKey,
|
|
109
|
+
binaryName,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
} catch {}
|
|
114
|
+
|
|
115
|
+
if (process.env.CARGO_MANIFEST_DIR) {
|
|
116
|
+
paths.push(
|
|
117
|
+
join(
|
|
118
|
+
process.env.CARGO_MANIFEST_DIR,
|
|
119
|
+
'resources',
|
|
120
|
+
'vendor',
|
|
121
|
+
'bin',
|
|
122
|
+
platformKey,
|
|
123
|
+
binaryName,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const cwd = process.cwd();
|
|
129
|
+
paths.push(join(cwd, 'vendor', 'bin', platformKey, binaryName));
|
|
130
|
+
|
|
131
|
+
return paths;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function extractFromVendor(name: string): Promise<string | null> {
|
|
135
|
+
const binaryName = getBinaryFileName(name);
|
|
136
|
+
const binDir = getAgiBinDir();
|
|
137
|
+
const targetPath = join(binDir, binaryName);
|
|
138
|
+
|
|
139
|
+
if ((await fileExists(targetPath)) && (await isExecutable(targetPath))) {
|
|
140
|
+
return targetPath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const searchPaths = getVendorSearchPaths(binaryName);
|
|
144
|
+
|
|
145
|
+
for (const src of searchPaths) {
|
|
146
|
+
if (await fileExists(src)) {
|
|
147
|
+
await ensureDir(binDir);
|
|
148
|
+
await fs.copyFile(src, targetPath);
|
|
149
|
+
await makeExecutable(targetPath);
|
|
150
|
+
return targetPath;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function resolveBinary(name: string): Promise<string> {
|
|
158
|
+
const cached = resolvedPaths.get(name);
|
|
159
|
+
if (cached) return cached;
|
|
160
|
+
|
|
161
|
+
const binaryName = getBinaryFileName(name);
|
|
162
|
+
const binDir = getAgiBinDir();
|
|
163
|
+
const installedPath = join(binDir, binaryName);
|
|
164
|
+
if (
|
|
165
|
+
(await fileExists(installedPath)) &&
|
|
166
|
+
(await isExecutable(installedPath))
|
|
167
|
+
) {
|
|
168
|
+
resolvedPaths.set(name, installedPath);
|
|
169
|
+
return installedPath;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const vendorPath = await extractFromVendor(name);
|
|
173
|
+
if (vendorPath) {
|
|
174
|
+
resolvedPaths.set(name, vendorPath);
|
|
175
|
+
return vendorPath;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const systemPath = await whichBinary(binaryName);
|
|
179
|
+
if (systemPath) {
|
|
180
|
+
resolvedPaths.set(name, systemPath);
|
|
181
|
+
return systemPath;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return binaryName;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function clearBinaryCache(): void {
|
|
188
|
+
resolvedPaths.clear();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getAugmentedPath(): string {
|
|
192
|
+
const binDir = getAgiBinDir();
|
|
193
|
+
const current = process.env.PATH || '';
|
|
194
|
+
const commonPaths = ['/opt/homebrew/bin', '/usr/local/bin'];
|
|
195
|
+
const parts = [binDir, ...commonPaths, current];
|
|
196
|
+
return parts.join(process.platform === 'win32' ? ';' : ':');
|
|
197
|
+
}
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod/v3';
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import DESCRIPTION from './bash.txt' with { type: 'text' };
|
|
5
5
|
import { createToolError, type ToolResponse } from '../error.ts';
|
|
6
|
+
import { getAugmentedPath } from '../bin-manager.ts';
|
|
6
7
|
|
|
7
8
|
function normalizePath(p: string) {
|
|
8
9
|
const parts = p.replace(/\\/g, '/').split('/');
|
|
@@ -73,6 +74,7 @@ export function buildBashTool(projectRoot: string): {
|
|
|
73
74
|
cwd: absCwd,
|
|
74
75
|
shell: true,
|
|
75
76
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
77
|
+
env: { ...process.env, PATH: getAugmentedPath() },
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
let stdout = '';
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
|
-
import {
|
|
4
|
-
import { promisify } from 'node:util';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
5
4
|
import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
|
|
6
5
|
import DESCRIPTION from './ls.txt' with { type: 'text' };
|
|
7
6
|
import { toIgnoredBasenames } from '../ignore.ts';
|
|
8
7
|
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
9
8
|
|
|
10
|
-
const execAsync = promisify(exec);
|
|
11
|
-
|
|
12
|
-
// description imported above
|
|
13
|
-
|
|
14
9
|
export function buildLsTool(projectRoot: string): { name: string; tool: Tool } {
|
|
15
10
|
const ls = tool({
|
|
16
11
|
description: DESCRIPTION,
|
|
@@ -45,32 +40,29 @@ export function buildLsTool(projectRoot: string): { name: string; tool: Tool } {
|
|
|
45
40
|
const ignored = toIgnoredBasenames(ignore);
|
|
46
41
|
|
|
47
42
|
try {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.map((line) => line.trim())
|
|
55
|
-
.filter((line) => line.length > 0 && !line.startsWith('.'))
|
|
56
|
-
.map((line) => ({
|
|
57
|
-
name: line.replace(/\/$/, ''),
|
|
58
|
-
type: line.endsWith('/') ? 'dir' : 'file',
|
|
43
|
+
const dirents = await fs.readdir(abs, { withFileTypes: true });
|
|
44
|
+
const entries = dirents
|
|
45
|
+
.filter((d) => !String(d.name).startsWith('.'))
|
|
46
|
+
.map((d) => ({
|
|
47
|
+
name: String(d.name),
|
|
48
|
+
type: d.isDirectory() ? 'dir' : 'file',
|
|
59
49
|
}))
|
|
60
|
-
.filter(
|
|
61
|
-
|
|
62
|
-
);
|
|
50
|
+
.filter((entry) => !(entry.type === 'dir' && ignored.has(entry.name)))
|
|
51
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
63
52
|
return { ok: true, path: req, entries };
|
|
64
53
|
} catch (error: unknown) {
|
|
65
|
-
const err = error as {
|
|
66
|
-
const message =
|
|
54
|
+
const err = error as { code?: string; message?: string };
|
|
55
|
+
const message = err.message || 'ls failed';
|
|
67
56
|
return createToolError(
|
|
68
57
|
`ls failed for ${req}: ${message}`,
|
|
69
|
-
'execution',
|
|
58
|
+
err.code === 'ENOENT' ? 'not_found' : 'execution',
|
|
70
59
|
{
|
|
71
60
|
parameter: 'path',
|
|
72
61
|
value: req,
|
|
73
|
-
suggestion:
|
|
62
|
+
suggestion:
|
|
63
|
+
err.code === 'ENOENT'
|
|
64
|
+
? 'Check if the directory exists'
|
|
65
|
+
: 'Check if the directory exists and is accessible',
|
|
74
66
|
},
|
|
75
67
|
);
|
|
76
68
|
}
|
|
@@ -1,15 +1,72 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
5
|
import { expandTilde, isAbsoluteLike, resolveSafePath } from './util.ts';
|
|
6
6
|
import DESCRIPTION from './tree.txt' with { type: 'text' };
|
|
7
7
|
import { toIgnoredBasenames } from '../ignore.ts';
|
|
8
8
|
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
async function walkTree(
|
|
11
|
+
dir: string,
|
|
12
|
+
ignored: Set<string>,
|
|
13
|
+
maxDepth: number | null,
|
|
14
|
+
currentDepth: number,
|
|
15
|
+
prefix: string,
|
|
16
|
+
): Promise<{ lines: string[]; dirs: number; files: number }> {
|
|
17
|
+
let dirs = 0;
|
|
18
|
+
let files = 0;
|
|
19
|
+
const lines: string[] = [];
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
if (maxDepth !== null && currentDepth >= maxDepth)
|
|
22
|
+
return { lines, dirs, files };
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const rawEntries = await fs.readdir(dir, { withFileTypes: true });
|
|
26
|
+
const entries = rawEntries.map((e) => ({
|
|
27
|
+
name: String(e.name),
|
|
28
|
+
isDir: e.isDirectory(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const filtered = entries
|
|
32
|
+
.filter((e) => !e.name.startsWith('.'))
|
|
33
|
+
.filter((e) => !(e.isDir && ignored.has(e.name)))
|
|
34
|
+
.sort((a, b) => {
|
|
35
|
+
if (a.isDir && !b.isDir) return -1;
|
|
36
|
+
if (!a.isDir && b.isDir) return 1;
|
|
37
|
+
return a.name.localeCompare(b.name);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
41
|
+
const entry = filtered[i];
|
|
42
|
+
const isLast = i === filtered.length - 1;
|
|
43
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
44
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
45
|
+
|
|
46
|
+
if (entry.isDir) {
|
|
47
|
+
dirs++;
|
|
48
|
+
lines.push(`${prefix}${connector}${entry.name}`);
|
|
49
|
+
const sub = await walkTree(
|
|
50
|
+
join(dir, entry.name),
|
|
51
|
+
ignored,
|
|
52
|
+
maxDepth,
|
|
53
|
+
currentDepth + 1,
|
|
54
|
+
`${prefix}${childPrefix}`,
|
|
55
|
+
);
|
|
56
|
+
lines.push(...sub.lines);
|
|
57
|
+
dirs += sub.dirs;
|
|
58
|
+
files += sub.files;
|
|
59
|
+
} else {
|
|
60
|
+
files++;
|
|
61
|
+
lines.push(`${prefix}${connector}${entry.name}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
return { lines, dirs, files };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { lines, dirs, files };
|
|
69
|
+
}
|
|
13
70
|
|
|
14
71
|
export function buildTreeTool(projectRoot: string): {
|
|
15
72
|
name: string;
|
|
@@ -48,32 +105,35 @@ export function buildTreeTool(projectRoot: string): {
|
|
|
48
105
|
: resolveSafePath(projectRoot, req || '.');
|
|
49
106
|
const ignored = toIgnoredBasenames(ignore);
|
|
50
107
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
try {
|
|
109
|
+
await fs.access(start);
|
|
110
|
+
} catch {
|
|
111
|
+
return createToolError(
|
|
112
|
+
`tree failed for ${req}: directory not found`,
|
|
113
|
+
'not_found',
|
|
114
|
+
{
|
|
115
|
+
parameter: 'path',
|
|
116
|
+
value: req,
|
|
117
|
+
suggestion: 'Check if the directory exists',
|
|
118
|
+
},
|
|
119
|
+
);
|
|
56
120
|
}
|
|
57
|
-
cmd += ' .';
|
|
58
121
|
|
|
59
122
|
try {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const output = stdout.trimEnd();
|
|
123
|
+
const result = await walkTree(start, ignored, depth ?? null, 0, '');
|
|
124
|
+
const header = '.';
|
|
125
|
+
const summary = `\n${result.dirs} director${result.dirs === 1 ? 'y' : 'ies'}, ${result.files} file${result.files === 1 ? '' : 's'}`;
|
|
126
|
+
const output = [header, ...result.lines, summary].join('\n');
|
|
65
127
|
return { ok: true, path: req, depth: depth ?? null, tree: output };
|
|
66
128
|
} catch (error: unknown) {
|
|
67
|
-
const err = error as {
|
|
68
|
-
const message = (err.stderr || err.stdout || 'tree failed').trim();
|
|
129
|
+
const err = error as { message?: string };
|
|
69
130
|
return createToolError(
|
|
70
|
-
`tree failed for ${req}: ${message}`,
|
|
131
|
+
`tree failed for ${req}: ${err.message || 'unknown error'}`,
|
|
71
132
|
'execution',
|
|
72
133
|
{
|
|
73
134
|
parameter: 'path',
|
|
74
135
|
value: req,
|
|
75
|
-
suggestion:
|
|
76
|
-
'Check if the directory exists and tree command is installed',
|
|
136
|
+
suggestion: 'Check if the directory exists and is accessible',
|
|
77
137
|
},
|
|
78
138
|
);
|
|
79
139
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
|
-
import {
|
|
4
|
-
import { promisify } from 'node:util';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
5
4
|
import { join } from 'node:path';
|
|
6
5
|
import DESCRIPTION from './grep.txt' with { type: 'text' };
|
|
7
6
|
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
8
7
|
import { createToolError, type ToolResponse } from '../error.ts';
|
|
9
|
-
|
|
10
|
-
const execAsync = promisify(exec);
|
|
8
|
+
import { resolveBinary } from '../bin-manager.ts';
|
|
11
9
|
|
|
12
10
|
function expandTilde(p: string) {
|
|
13
11
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
@@ -63,35 +61,44 @@ export function buildGrepTool(projectRoot: string): {
|
|
|
63
61
|
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
64
62
|
const searchPath = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
const rgBin = await resolveBinary('rg');
|
|
65
|
+
const args: string[] = ['-n', '--color', 'never'];
|
|
67
66
|
for (const g of defaultIgnoreGlobs(params.ignore)) {
|
|
68
|
-
|
|
67
|
+
args.push('--glob', g);
|
|
69
68
|
}
|
|
70
69
|
if (params.include) {
|
|
71
|
-
|
|
70
|
+
args.push('--glob', params.include);
|
|
72
71
|
}
|
|
73
|
-
|
|
72
|
+
args.push(pattern, searchPath);
|
|
74
73
|
|
|
75
74
|
let output = '';
|
|
76
75
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
});
|
|
79
94
|
} catch (error: unknown) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'execution',
|
|
88
|
-
{
|
|
89
|
-
parameter: 'pattern',
|
|
90
|
-
value: pattern,
|
|
91
|
-
suggestion:
|
|
92
|
-
'Check if ripgrep (rg) is installed and the pattern is valid',
|
|
93
|
-
},
|
|
94
|
-
);
|
|
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
|
+
});
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
const lines = output.trim().split('\n');
|
|
@@ -4,6 +4,7 @@ import { spawn } from 'node:child_process';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import DESCRIPTION from './ripgrep.txt' with { type: 'text' };
|
|
6
6
|
import { createToolError, type ToolResponse } from '../error.ts';
|
|
7
|
+
import { resolveBinary } from '../bin-manager.ts';
|
|
7
8
|
|
|
8
9
|
export function buildRipgrepTool(projectRoot: string): {
|
|
9
10
|
name: string;
|
|
@@ -61,8 +62,9 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
61
62
|
args.push(target);
|
|
62
63
|
|
|
63
64
|
try {
|
|
65
|
+
const rgBin = await resolveBinary('rg');
|
|
64
66
|
return await new Promise((resolve) => {
|
|
65
|
-
const proc = spawn(
|
|
67
|
+
const proc = spawn(rgBin, args, { cwd: projectRoot });
|
|
66
68
|
let stdout = '';
|
|
67
69
|
let stderr = '';
|
|
68
70
|
|
|
@@ -3,8 +3,8 @@ You are an interactive CLI tool that helps users with software engineering tasks
|
|
|
3
3
|
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
|
|
4
4
|
|
|
5
5
|
If the user asks for help or wants to give feedback inform them of the following:
|
|
6
|
-
- /help: Get help with using
|
|
7
|
-
- To give feedback, users should report the issue at https://github.com/
|
|
6
|
+
- /help: Get help with using agi
|
|
7
|
+
- To give feedback, users should report the issue at https://github.com/nitishxyz/agi/issues
|
|
8
8
|
|
|
9
9
|
# Tone and style
|
|
10
10
|
You should be concise, direct, and to the point.
|