protoagent 0.0.1

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.
@@ -0,0 +1,177 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ // Current working directory for file operations
4
+ const workingDirectory = process.cwd();
5
+ // Security utilities
6
+ function normalizePath(p) {
7
+ return path.normalize(p);
8
+ }
9
+ async function validatePath(requestedPath) {
10
+ const absolute = path.isAbsolute(requestedPath)
11
+ ? path.resolve(requestedPath)
12
+ : path.resolve(workingDirectory, requestedPath);
13
+ const normalizedRequested = normalizePath(absolute);
14
+ // Check if path is within working directory
15
+ if (!normalizedRequested.startsWith(workingDirectory)) {
16
+ throw new Error(`Access denied - path outside working directory: ${absolute}`);
17
+ }
18
+ // Handle symlinks by checking their real path
19
+ try {
20
+ const realPath = await fs.realpath(absolute);
21
+ const normalizedReal = normalizePath(realPath);
22
+ if (!normalizedReal.startsWith(workingDirectory)) {
23
+ throw new Error(`Access denied - symlink target outside working directory: ${realPath}`);
24
+ }
25
+ return realPath;
26
+ }
27
+ catch (error) {
28
+ // For new files that don't exist yet, verify parent directory
29
+ if (error.code === 'ENOENT') {
30
+ const parentDir = path.dirname(absolute);
31
+ try {
32
+ const realParentPath = await fs.realpath(parentDir);
33
+ const normalizedParent = normalizePath(realParentPath);
34
+ if (!normalizedParent.startsWith(workingDirectory)) {
35
+ throw new Error(`Access denied - parent directory outside working directory: ${realParentPath}`);
36
+ }
37
+ return absolute;
38
+ }
39
+ catch {
40
+ throw new Error(`Parent directory does not exist: ${parentDir}`);
41
+ }
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+ // Search utility function
47
+ async function searchInFile(filePath, searchTerm, caseSensitive = false) {
48
+ try {
49
+ const content = await fs.readFile(filePath, 'utf-8');
50
+ const lines = content.split('\n');
51
+ const matchingLines = [];
52
+ let totalMatches = 0;
53
+ const searchPattern = caseSensitive ? searchTerm : searchTerm.toLowerCase();
54
+ lines.forEach((line, index) => {
55
+ const searchLine = caseSensitive ? line : line.toLowerCase();
56
+ if (searchLine.includes(searchPattern)) {
57
+ totalMatches++;
58
+ matchingLines.push(`${index + 1}: ${line}`);
59
+ }
60
+ });
61
+ return { matches: totalMatches, lines: matchingLines };
62
+ }
63
+ catch (error) {
64
+ return { matches: 0, lines: [] };
65
+ }
66
+ }
67
+ async function searchInDirectory(dirPath, searchTerm, caseSensitive = false, fileExtensions = []) {
68
+ const validPath = await validatePath(dirPath);
69
+ const results = [];
70
+ async function searchRecursive(currentPath, depth = 0) {
71
+ if (depth > 10)
72
+ return; // Prevent infinite recursion
73
+ try {
74
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ const fullPath = path.join(currentPath, entry.name);
77
+ // Skip common directories that should be ignored
78
+ if (entry.isDirectory() && ['node_modules', '.git', '.DS_Store', 'dist', 'build'].includes(entry.name)) {
79
+ continue;
80
+ }
81
+ if (entry.isDirectory()) {
82
+ await searchRecursive(fullPath, depth + 1);
83
+ }
84
+ else if (entry.isFile()) {
85
+ // Check file extension filter
86
+ if (fileExtensions.length > 0) {
87
+ const ext = path.extname(entry.name).toLowerCase();
88
+ if (!fileExtensions.includes(ext)) {
89
+ continue;
90
+ }
91
+ }
92
+ try {
93
+ const searchResult = await searchInFile(fullPath, searchTerm, caseSensitive);
94
+ if (searchResult.matches > 0) {
95
+ const relativePath = path.relative(workingDirectory, fullPath);
96
+ results.push(`\nšŸ“ ${relativePath} (${searchResult.matches} matches):`);
97
+ results.push(...searchResult.lines.map(line => ` ${line}`));
98
+ }
99
+ }
100
+ catch (error) {
101
+ // Skip files we can't read
102
+ }
103
+ }
104
+ }
105
+ }
106
+ catch (error) {
107
+ // Skip directories we can't read
108
+ }
109
+ }
110
+ await searchRecursive(validPath);
111
+ if (results.length === 0) {
112
+ return `No matches found for "${searchTerm}" in ${dirPath}`;
113
+ }
114
+ return results.join('\n');
115
+ }
116
+ export async function searchFiles(searchTerm, dirPath = '.', caseSensitive = false, fileExtensions = []) {
117
+ try {
118
+ if (!searchTerm.trim()) {
119
+ throw new Error('Search term cannot be empty');
120
+ }
121
+ const validPath = await validatePath(dirPath);
122
+ // Check if path exists and is a directory
123
+ const stats = await fs.stat(validPath);
124
+ if (!stats.isDirectory()) {
125
+ throw new Error('Search path must be a directory');
126
+ }
127
+ const results = await searchInDirectory(validPath, searchTerm, caseSensitive, fileExtensions);
128
+ const searchInfo = [
129
+ `šŸ” Searching for: "${searchTerm}"`,
130
+ `šŸ“‚ In directory: ${path.relative(workingDirectory, validPath) || '.'}`,
131
+ `šŸ“ Case sensitive: ${caseSensitive ? 'Yes' : 'No'}`,
132
+ fileExtensions.length > 0 ? `šŸŽÆ File types: ${fileExtensions.join(', ')}` : 'šŸŽÆ All file types',
133
+ '',
134
+ ].join('\n');
135
+ return searchInfo + results;
136
+ }
137
+ catch (error) {
138
+ if (error instanceof Error) {
139
+ throw new Error(`Failed to search files: ${error.message}`);
140
+ }
141
+ throw new Error('Failed to search files: Unknown error');
142
+ }
143
+ }
144
+ // Tool definition
145
+ export const searchFilesTool = {
146
+ type: 'function',
147
+ function: {
148
+ name: 'search_files',
149
+ description: 'Search for a text string within files in a directory. Supports case sensitivity and file type filtering. Returns the list of files and matching lines. Use this to find specific code, configuration settings, or documentation within your project files.',
150
+ parameters: {
151
+ type: 'object',
152
+ properties: {
153
+ search_term: {
154
+ type: 'string',
155
+ description: 'The text string to search for within files.'
156
+ },
157
+ directory_path: {
158
+ type: 'string',
159
+ description: 'The directory to search within, relative to the current working directory. Use "." for the current directory.'
160
+ },
161
+ case_sensitive: {
162
+ type: 'boolean',
163
+ description: 'Whether the search should be case sensitive.'
164
+ },
165
+ file_extensions: {
166
+ type: 'array',
167
+ items: {
168
+ type: 'string',
169
+ description: 'File extension to include in the search (e.g., "js", "txt"). Leave empty to search all file types.'
170
+ },
171
+ description: 'List of file extensions to include in the search.'
172
+ }
173
+ },
174
+ required: ['search_term', 'directory_path']
175
+ }
176
+ }
177
+ };
@@ -0,0 +1,179 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ // Current working directory for file operations
4
+ const workingDirectory = process.cwd();
5
+ // Security utilities
6
+ function normalizePath(p) {
7
+ return path.normalize(p);
8
+ }
9
+ async function validatePath(requestedPath) {
10
+ const absolute = path.isAbsolute(requestedPath)
11
+ ? path.resolve(requestedPath)
12
+ : path.resolve(workingDirectory, requestedPath);
13
+ const normalizedRequested = normalizePath(absolute);
14
+ // Check if path is within working directory
15
+ if (!normalizedRequested.startsWith(workingDirectory)) {
16
+ throw new Error(`Access denied - path outside working directory: ${absolute}`);
17
+ }
18
+ // Handle symlinks by checking their real path
19
+ try {
20
+ const realPath = await fs.realpath(absolute);
21
+ const normalizedReal = normalizePath(realPath);
22
+ if (!normalizedReal.startsWith(workingDirectory)) {
23
+ throw new Error(`Access denied - symlink target outside working directory: ${realPath}`);
24
+ }
25
+ return realPath;
26
+ }
27
+ catch (error) {
28
+ // For new files that don't exist yet, verify parent directory
29
+ if (error.code === 'ENOENT') {
30
+ const parentDir = path.dirname(absolute);
31
+ try {
32
+ const realParentPath = await fs.realpath(parentDir);
33
+ const normalizedParent = normalizePath(realParentPath);
34
+ if (!normalizedParent.startsWith(workingDirectory)) {
35
+ throw new Error(`Access denied - parent directory outside working directory: ${realParentPath}`);
36
+ }
37
+ return absolute;
38
+ }
39
+ catch {
40
+ throw new Error(`Parent directory does not exist: ${parentDir}`);
41
+ }
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+ // Search utility function
47
+ async function searchInFile(filePath, searchTerm, caseSensitive = false) {
48
+ try {
49
+ const content = await fs.readFile(filePath, 'utf-8');
50
+ const lines = content.split('\n');
51
+ const matchingLines = [];
52
+ let totalMatches = 0;
53
+ const searchPattern = caseSensitive ? searchTerm : searchTerm.toLowerCase();
54
+ lines.forEach((line, index) => {
55
+ const searchLine = caseSensitive ? line : line.toLowerCase();
56
+ if (searchLine.includes(searchPattern)) {
57
+ totalMatches++;
58
+ matchingLines.push(`${index + 1}: ${line}`);
59
+ }
60
+ });
61
+ return { matches: totalMatches, lines: matchingLines };
62
+ }
63
+ catch (error) {
64
+ return { matches: 0, lines: [] };
65
+ }
66
+ }
67
+ async function searchInDirectory(dirPath, searchTerm, caseSensitive = false, fileExtensions = []) {
68
+ const validPath = await validatePath(dirPath);
69
+ const results = [];
70
+ async function searchRecursive(currentPath, depth = 0) {
71
+ if (depth > 10)
72
+ return; // Prevent infinite recursion
73
+ try {
74
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ const fullPath = path.join(currentPath, entry.name);
77
+ // Skip common directories that should be ignored
78
+ if (entry.isDirectory() && ['node_modules', '.git', '.DS_Store', 'dist', 'build'].includes(entry.name)) {
79
+ continue;
80
+ }
81
+ if (entry.isDirectory()) {
82
+ await searchRecursive(fullPath, depth + 1);
83
+ }
84
+ else if (entry.isFile()) {
85
+ // Check file extension filter
86
+ if (fileExtensions.length > 0) {
87
+ const ext = path.extname(entry.name).toLowerCase();
88
+ if (!fileExtensions.includes(ext)) {
89
+ continue;
90
+ }
91
+ }
92
+ try {
93
+ const searchResult = await searchInFile(fullPath, searchTerm, caseSensitive);
94
+ if (searchResult.matches > 0) {
95
+ const relativePath = path.relative(workingDirectory, fullPath);
96
+ results.push(`\nšŸ“ ${relativePath} (${searchResult.matches} matches):`);
97
+ results.push(...searchResult.lines.map(line => ` ${line}`));
98
+ }
99
+ }
100
+ catch (error) {
101
+ // Skip files we can't read
102
+ }
103
+ }
104
+ }
105
+ }
106
+ catch (error) {
107
+ // Skip directories we can't read
108
+ }
109
+ }
110
+ await searchRecursive(validPath);
111
+ if (results.length === 0) {
112
+ return `No matches found for "${searchTerm}" in ${dirPath}`;
113
+ }
114
+ return results.join('\n');
115
+ }
116
+ export async function searchFiles(searchTerm, dirPath = '.', caseSensitive = false, fileExtensions = []) {
117
+ try {
118
+ if (!searchTerm.trim()) {
119
+ throw new Error('Search term cannot be empty');
120
+ }
121
+ const validPath = await validatePath(dirPath);
122
+ // Check if path exists and is a directory
123
+ const stats = await fs.stat(validPath);
124
+ if (!stats.isDirectory()) {
125
+ throw new Error('Search path must be a directory');
126
+ }
127
+ const results = await searchInDirectory(validPath, searchTerm, caseSensitive, fileExtensions);
128
+ const searchInfo = [
129
+ `šŸ” Searching for: "${searchTerm}"`,
130
+ `šŸ“‚ In directory: ${path.relative(workingDirectory, validPath) || '.'}`,
131
+ `šŸ“ Case sensitive: ${caseSensitive ? 'Yes' : 'No'}`,
132
+ fileExtensions.length > 0 ? `šŸŽÆ File types: ${fileExtensions.join(', ')}` : 'šŸŽÆ All file types',
133
+ '',
134
+ ].join('\n');
135
+ return searchInfo + results;
136
+ }
137
+ catch (error) {
138
+ if (error instanceof Error) {
139
+ throw new Error(`Failed to search files: ${error.message}`);
140
+ }
141
+ throw new Error('Failed to search files: Unknown error');
142
+ }
143
+ }
144
+ // Tool definitions
145
+ export const searchTools = [
146
+ {
147
+ type: 'function',
148
+ function: {
149
+ name: 'search_files',
150
+ description: 'Search for a text string within files in a directory. Supports case sensitivity and file type filtering. Returns the list of files and matching lines. Use this to find specific code, configuration settings, or documentation within your project files.',
151
+ parameters: {
152
+ type: 'object',
153
+ properties: {
154
+ search_term: {
155
+ type: 'string',
156
+ description: 'The text string to search for within files.'
157
+ },
158
+ directory_path: {
159
+ type: 'string',
160
+ description: 'The directory to search within, relative to the current working directory. Use "." for the current directory.'
161
+ },
162
+ case_sensitive: {
163
+ type: 'boolean',
164
+ description: 'Whether the search should be case sensitive.'
165
+ },
166
+ file_extensions: {
167
+ type: 'array',
168
+ items: {
169
+ type: 'string',
170
+ description: 'File extension to include in the search (e.g., "js", "txt"). Leave empty to search all file types.'
171
+ },
172
+ description: 'List of file extensions to include in the search.'
173
+ }
174
+ },
175
+ required: ['search_term', 'directory_path']
176
+ }
177
+ }
178
+ }
179
+ ];