nc1709 1.15.4__py3-none-any.whl
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.
- nc1709/__init__.py +13 -0
- nc1709/agent/__init__.py +36 -0
- nc1709/agent/core.py +505 -0
- nc1709/agent/mcp_bridge.py +245 -0
- nc1709/agent/permissions.py +298 -0
- nc1709/agent/tools/__init__.py +21 -0
- nc1709/agent/tools/base.py +440 -0
- nc1709/agent/tools/bash_tool.py +367 -0
- nc1709/agent/tools/file_tools.py +454 -0
- nc1709/agent/tools/notebook_tools.py +516 -0
- nc1709/agent/tools/search_tools.py +322 -0
- nc1709/agent/tools/task_tool.py +284 -0
- nc1709/agent/tools/web_tools.py +555 -0
- nc1709/agents/__init__.py +17 -0
- nc1709/agents/auto_fix.py +506 -0
- nc1709/agents/test_generator.py +507 -0
- nc1709/checkpoints.py +372 -0
- nc1709/cli.py +3380 -0
- nc1709/cli_ui.py +1080 -0
- nc1709/cognitive/__init__.py +149 -0
- nc1709/cognitive/anticipation.py +594 -0
- nc1709/cognitive/context_engine.py +1046 -0
- nc1709/cognitive/council.py +824 -0
- nc1709/cognitive/learning.py +761 -0
- nc1709/cognitive/router.py +583 -0
- nc1709/cognitive/system.py +519 -0
- nc1709/config.py +155 -0
- nc1709/custom_commands.py +300 -0
- nc1709/executor.py +333 -0
- nc1709/file_controller.py +354 -0
- nc1709/git_integration.py +308 -0
- nc1709/github_integration.py +477 -0
- nc1709/image_input.py +446 -0
- nc1709/linting.py +519 -0
- nc1709/llm_adapter.py +667 -0
- nc1709/logger.py +192 -0
- nc1709/mcp/__init__.py +18 -0
- nc1709/mcp/client.py +370 -0
- nc1709/mcp/manager.py +407 -0
- nc1709/mcp/protocol.py +210 -0
- nc1709/mcp/server.py +473 -0
- nc1709/memory/__init__.py +20 -0
- nc1709/memory/embeddings.py +325 -0
- nc1709/memory/indexer.py +474 -0
- nc1709/memory/sessions.py +432 -0
- nc1709/memory/vector_store.py +451 -0
- nc1709/models/__init__.py +86 -0
- nc1709/models/detector.py +377 -0
- nc1709/models/formats.py +315 -0
- nc1709/models/manager.py +438 -0
- nc1709/models/registry.py +497 -0
- nc1709/performance/__init__.py +343 -0
- nc1709/performance/cache.py +705 -0
- nc1709/performance/pipeline.py +611 -0
- nc1709/performance/tiering.py +543 -0
- nc1709/plan_mode.py +362 -0
- nc1709/plugins/__init__.py +17 -0
- nc1709/plugins/agents/__init__.py +18 -0
- nc1709/plugins/agents/django_agent.py +912 -0
- nc1709/plugins/agents/docker_agent.py +623 -0
- nc1709/plugins/agents/fastapi_agent.py +887 -0
- nc1709/plugins/agents/git_agent.py +731 -0
- nc1709/plugins/agents/nextjs_agent.py +867 -0
- nc1709/plugins/base.py +359 -0
- nc1709/plugins/manager.py +411 -0
- nc1709/plugins/registry.py +337 -0
- nc1709/progress.py +443 -0
- nc1709/prompts/__init__.py +22 -0
- nc1709/prompts/agent_system.py +180 -0
- nc1709/prompts/task_prompts.py +340 -0
- nc1709/prompts/unified_prompt.py +133 -0
- nc1709/reasoning_engine.py +541 -0
- nc1709/remote_client.py +266 -0
- nc1709/shell_completions.py +349 -0
- nc1709/slash_commands.py +649 -0
- nc1709/task_classifier.py +408 -0
- nc1709/version_check.py +177 -0
- nc1709/web/__init__.py +8 -0
- nc1709/web/server.py +950 -0
- nc1709/web/templates/index.html +1127 -0
- nc1709-1.15.4.dist-info/METADATA +858 -0
- nc1709-1.15.4.dist-info/RECORD +86 -0
- nc1709-1.15.4.dist-info/WHEEL +5 -0
- nc1709-1.15.4.dist-info/entry_points.txt +2 -0
- nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
- nc1709-1.15.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Operation Tools
|
|
3
|
+
|
|
4
|
+
Tools for reading, writing, and editing files:
|
|
5
|
+
- Read: Read file contents
|
|
6
|
+
- Write: Write/create files
|
|
7
|
+
- Edit: Make precise edits to files
|
|
8
|
+
|
|
9
|
+
Includes checkpoint integration for undo/rewind functionality.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
import difflib
|
|
16
|
+
|
|
17
|
+
from .base import Tool, ToolResult, ToolParameter, ToolPermission
|
|
18
|
+
|
|
19
|
+
# Import checkpoint system
|
|
20
|
+
try:
|
|
21
|
+
from ...checkpoints import get_checkpoint_manager, checkpoint_before_edit, checkpoint_before_write
|
|
22
|
+
HAS_CHECKPOINTS = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
HAS_CHECKPOINTS = False
|
|
25
|
+
|
|
26
|
+
# Import git integration
|
|
27
|
+
try:
|
|
28
|
+
from ...git_integration import get_git_integration, auto_commit_after_edit
|
|
29
|
+
HAS_GIT = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
HAS_GIT = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ReadTool(Tool):
|
|
35
|
+
"""Read contents of a file"""
|
|
36
|
+
|
|
37
|
+
name = "Read"
|
|
38
|
+
description = "Read the contents of a file. Use this to examine code, configuration, or any text file."
|
|
39
|
+
category = "file"
|
|
40
|
+
permission = ToolPermission.AUTO # Safe to auto-execute
|
|
41
|
+
|
|
42
|
+
parameters = [
|
|
43
|
+
ToolParameter(
|
|
44
|
+
name="file_path",
|
|
45
|
+
description="The absolute path to the file to read",
|
|
46
|
+
type="string",
|
|
47
|
+
required=True,
|
|
48
|
+
),
|
|
49
|
+
ToolParameter(
|
|
50
|
+
name="offset",
|
|
51
|
+
description="Line number to start reading from (1-indexed). Use for large files.",
|
|
52
|
+
type="integer",
|
|
53
|
+
required=False,
|
|
54
|
+
default=None,
|
|
55
|
+
),
|
|
56
|
+
ToolParameter(
|
|
57
|
+
name="limit",
|
|
58
|
+
description="Maximum number of lines to read. Use for large files.",
|
|
59
|
+
type="integer",
|
|
60
|
+
required=False,
|
|
61
|
+
default=None,
|
|
62
|
+
),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def execute(self, file_path: str, offset: int = None, limit: int = None) -> ToolResult:
|
|
66
|
+
"""Read file contents"""
|
|
67
|
+
path = Path(file_path).expanduser()
|
|
68
|
+
|
|
69
|
+
# Check if file exists
|
|
70
|
+
if not path.exists():
|
|
71
|
+
return ToolResult(
|
|
72
|
+
success=False,
|
|
73
|
+
output="",
|
|
74
|
+
error=f"File not found: {file_path}",
|
|
75
|
+
target=file_path,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Check if it's a file
|
|
79
|
+
if not path.is_file():
|
|
80
|
+
return ToolResult(
|
|
81
|
+
success=False,
|
|
82
|
+
output="",
|
|
83
|
+
error=f"Not a file: {file_path}",
|
|
84
|
+
target=file_path,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Read the file
|
|
89
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
90
|
+
lines = f.readlines()
|
|
91
|
+
|
|
92
|
+
total_lines = len(lines)
|
|
93
|
+
|
|
94
|
+
# Apply offset and limit
|
|
95
|
+
start = (offset - 1) if offset else 0
|
|
96
|
+
start = max(0, min(start, total_lines))
|
|
97
|
+
|
|
98
|
+
if limit:
|
|
99
|
+
end = start + limit
|
|
100
|
+
else:
|
|
101
|
+
end = total_lines
|
|
102
|
+
|
|
103
|
+
selected_lines = lines[start:end]
|
|
104
|
+
|
|
105
|
+
# Format output with line numbers
|
|
106
|
+
output_lines = []
|
|
107
|
+
for i, line in enumerate(selected_lines, start=start + 1):
|
|
108
|
+
# Truncate very long lines
|
|
109
|
+
if len(line) > 2000:
|
|
110
|
+
line = line[:2000] + "... (truncated)\n"
|
|
111
|
+
output_lines.append(f"{i:6}→{line.rstrip()}")
|
|
112
|
+
|
|
113
|
+
output = "\n".join(output_lines)
|
|
114
|
+
|
|
115
|
+
# Add info about truncation
|
|
116
|
+
if start > 0 or end < total_lines:
|
|
117
|
+
output = f"Showing lines {start + 1}-{end} of {total_lines}\n\n{output}"
|
|
118
|
+
|
|
119
|
+
return ToolResult(
|
|
120
|
+
success=True,
|
|
121
|
+
output=output,
|
|
122
|
+
target=file_path,
|
|
123
|
+
data={
|
|
124
|
+
"total_lines": total_lines,
|
|
125
|
+
"lines_shown": len(selected_lines),
|
|
126
|
+
"start_line": start + 1,
|
|
127
|
+
"end_line": end,
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return ToolResult(
|
|
133
|
+
success=False,
|
|
134
|
+
output="",
|
|
135
|
+
error=f"Error reading file: {e}",
|
|
136
|
+
target=file_path,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class WriteTool(Tool):
|
|
141
|
+
"""Write content to a file"""
|
|
142
|
+
|
|
143
|
+
name = "Write"
|
|
144
|
+
description = "Write content to a file. Creates the file if it doesn't exist, or overwrites if it does."
|
|
145
|
+
category = "file"
|
|
146
|
+
permission = ToolPermission.ASK # Ask before writing
|
|
147
|
+
|
|
148
|
+
parameters = [
|
|
149
|
+
ToolParameter(
|
|
150
|
+
name="file_path",
|
|
151
|
+
description="The absolute path to the file to write",
|
|
152
|
+
type="string",
|
|
153
|
+
required=True,
|
|
154
|
+
),
|
|
155
|
+
ToolParameter(
|
|
156
|
+
name="content",
|
|
157
|
+
description="The content to write to the file",
|
|
158
|
+
type="string",
|
|
159
|
+
required=True,
|
|
160
|
+
),
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
def execute(self, file_path: str, content: str) -> ToolResult:
|
|
164
|
+
"""Write content to file"""
|
|
165
|
+
path = Path(file_path).expanduser()
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Create checkpoint before writing
|
|
169
|
+
if HAS_CHECKPOINTS:
|
|
170
|
+
checkpoint_before_write(str(path.absolute()))
|
|
171
|
+
|
|
172
|
+
# Create parent directories if needed
|
|
173
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
174
|
+
|
|
175
|
+
# Check if file exists for reporting
|
|
176
|
+
existed = path.exists()
|
|
177
|
+
old_size = path.stat().st_size if existed else 0
|
|
178
|
+
|
|
179
|
+
# Write the file
|
|
180
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
181
|
+
f.write(content)
|
|
182
|
+
|
|
183
|
+
new_size = path.stat().st_size
|
|
184
|
+
action = "Updated" if existed else "Created"
|
|
185
|
+
|
|
186
|
+
return ToolResult(
|
|
187
|
+
success=True,
|
|
188
|
+
output=f"{action} {file_path} ({new_size} bytes, {len(content.splitlines())} lines)",
|
|
189
|
+
target=file_path,
|
|
190
|
+
data={
|
|
191
|
+
"action": action.lower(),
|
|
192
|
+
"size": new_size,
|
|
193
|
+
"lines": len(content.splitlines()),
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
except PermissionError:
|
|
198
|
+
return ToolResult(
|
|
199
|
+
success=False,
|
|
200
|
+
output="",
|
|
201
|
+
error=f"Permission denied: {file_path}",
|
|
202
|
+
target=file_path,
|
|
203
|
+
)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return ToolResult(
|
|
206
|
+
success=False,
|
|
207
|
+
output="",
|
|
208
|
+
error=f"Error writing file: {e}",
|
|
209
|
+
target=file_path,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class EditTool(Tool):
|
|
214
|
+
"""Make precise edits to a file"""
|
|
215
|
+
|
|
216
|
+
name = "Edit"
|
|
217
|
+
description = (
|
|
218
|
+
"Make a precise edit to a file by replacing an exact string with new content. "
|
|
219
|
+
"The old_string must match exactly (including whitespace and indentation). "
|
|
220
|
+
"Use replace_all=true to replace all occurrences."
|
|
221
|
+
)
|
|
222
|
+
category = "file"
|
|
223
|
+
permission = ToolPermission.ASK # Ask before editing
|
|
224
|
+
|
|
225
|
+
parameters = [
|
|
226
|
+
ToolParameter(
|
|
227
|
+
name="file_path",
|
|
228
|
+
description="The absolute path to the file to edit",
|
|
229
|
+
type="string",
|
|
230
|
+
required=True,
|
|
231
|
+
),
|
|
232
|
+
ToolParameter(
|
|
233
|
+
name="old_string",
|
|
234
|
+
description="The exact string to find and replace (must match exactly)",
|
|
235
|
+
type="string",
|
|
236
|
+
required=True,
|
|
237
|
+
),
|
|
238
|
+
ToolParameter(
|
|
239
|
+
name="new_string",
|
|
240
|
+
description="The new string to replace it with",
|
|
241
|
+
type="string",
|
|
242
|
+
required=True,
|
|
243
|
+
),
|
|
244
|
+
ToolParameter(
|
|
245
|
+
name="replace_all",
|
|
246
|
+
description="Replace all occurrences instead of just the first",
|
|
247
|
+
type="boolean",
|
|
248
|
+
required=False,
|
|
249
|
+
default=False,
|
|
250
|
+
),
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
def execute(
|
|
254
|
+
self,
|
|
255
|
+
file_path: str,
|
|
256
|
+
old_string: str,
|
|
257
|
+
new_string: str,
|
|
258
|
+
replace_all: bool = False,
|
|
259
|
+
) -> ToolResult:
|
|
260
|
+
"""Edit file by replacing string"""
|
|
261
|
+
path = Path(file_path).expanduser()
|
|
262
|
+
|
|
263
|
+
# Check if file exists
|
|
264
|
+
if not path.exists():
|
|
265
|
+
return ToolResult(
|
|
266
|
+
success=False,
|
|
267
|
+
output="",
|
|
268
|
+
error=f"File not found: {file_path}",
|
|
269
|
+
target=file_path,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
# Create checkpoint before editing
|
|
274
|
+
if HAS_CHECKPOINTS:
|
|
275
|
+
checkpoint_before_edit(str(path.absolute()))
|
|
276
|
+
|
|
277
|
+
# Read current content
|
|
278
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
279
|
+
content = f.read()
|
|
280
|
+
|
|
281
|
+
# Check if old_string exists
|
|
282
|
+
if old_string not in content:
|
|
283
|
+
# Try to find similar strings for helpful error
|
|
284
|
+
lines = content.split("\n")
|
|
285
|
+
similar = []
|
|
286
|
+
for i, line in enumerate(lines):
|
|
287
|
+
if any(word in line for word in old_string.split()[:3]):
|
|
288
|
+
similar.append(f" Line {i+1}: {line[:80]}")
|
|
289
|
+
if len(similar) >= 3:
|
|
290
|
+
break
|
|
291
|
+
|
|
292
|
+
error_msg = f"String not found in file: {file_path}"
|
|
293
|
+
if similar:
|
|
294
|
+
error_msg += f"\n\nSimilar lines found:\n" + "\n".join(similar)
|
|
295
|
+
|
|
296
|
+
return ToolResult(
|
|
297
|
+
success=False,
|
|
298
|
+
output="",
|
|
299
|
+
error=error_msg,
|
|
300
|
+
target=file_path,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Count occurrences
|
|
304
|
+
count = content.count(old_string)
|
|
305
|
+
|
|
306
|
+
# Check for ambiguity
|
|
307
|
+
if count > 1 and not replace_all:
|
|
308
|
+
return ToolResult(
|
|
309
|
+
success=False,
|
|
310
|
+
output="",
|
|
311
|
+
error=(
|
|
312
|
+
f"Found {count} occurrences of the string. "
|
|
313
|
+
"Either make old_string more specific to match uniquely, "
|
|
314
|
+
"or set replace_all=true to replace all occurrences."
|
|
315
|
+
),
|
|
316
|
+
target=file_path,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Perform replacement
|
|
320
|
+
if replace_all:
|
|
321
|
+
new_content = content.replace(old_string, new_string)
|
|
322
|
+
replaced_count = count
|
|
323
|
+
else:
|
|
324
|
+
new_content = content.replace(old_string, new_string, 1)
|
|
325
|
+
replaced_count = 1
|
|
326
|
+
|
|
327
|
+
# Write back
|
|
328
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
329
|
+
f.write(new_content)
|
|
330
|
+
|
|
331
|
+
# Generate diff for output
|
|
332
|
+
old_lines = content.splitlines(keepends=True)
|
|
333
|
+
new_lines = new_content.splitlines(keepends=True)
|
|
334
|
+
diff = difflib.unified_diff(
|
|
335
|
+
old_lines, new_lines,
|
|
336
|
+
fromfile=f"a/{path.name}",
|
|
337
|
+
tofile=f"b/{path.name}",
|
|
338
|
+
lineterm=""
|
|
339
|
+
)
|
|
340
|
+
diff_text = "".join(list(diff)[:50]) # Limit diff size
|
|
341
|
+
|
|
342
|
+
return ToolResult(
|
|
343
|
+
success=True,
|
|
344
|
+
output=f"Replaced {replaced_count} occurrence(s) in {file_path}\n\n{diff_text}",
|
|
345
|
+
target=file_path,
|
|
346
|
+
data={
|
|
347
|
+
"replacements": replaced_count,
|
|
348
|
+
"file": file_path,
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
return ToolResult(
|
|
354
|
+
success=False,
|
|
355
|
+
output="",
|
|
356
|
+
error=f"Error editing file: {e}",
|
|
357
|
+
target=file_path,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class GlobTool(Tool):
|
|
362
|
+
"""Find files matching a pattern"""
|
|
363
|
+
|
|
364
|
+
name = "Glob"
|
|
365
|
+
description = (
|
|
366
|
+
"Find files matching a glob pattern. "
|
|
367
|
+
"Examples: '**/*.py' for all Python files, 'src/**/*.ts' for TypeScript in src/."
|
|
368
|
+
)
|
|
369
|
+
category = "search"
|
|
370
|
+
permission = ToolPermission.AUTO # Safe to auto-execute
|
|
371
|
+
|
|
372
|
+
parameters = [
|
|
373
|
+
ToolParameter(
|
|
374
|
+
name="pattern",
|
|
375
|
+
description="Glob pattern to match files (e.g., '**/*.py', 'src/*.ts')",
|
|
376
|
+
type="string",
|
|
377
|
+
required=True,
|
|
378
|
+
),
|
|
379
|
+
ToolParameter(
|
|
380
|
+
name="path",
|
|
381
|
+
description="Base directory to search in (defaults to current directory)",
|
|
382
|
+
type="string",
|
|
383
|
+
required=False,
|
|
384
|
+
default=".",
|
|
385
|
+
),
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
def execute(self, pattern: str, path: str = ".") -> ToolResult:
|
|
389
|
+
"""Find files matching pattern"""
|
|
390
|
+
base_path = Path(path).expanduser()
|
|
391
|
+
|
|
392
|
+
if not base_path.exists():
|
|
393
|
+
return ToolResult(
|
|
394
|
+
success=False,
|
|
395
|
+
output="",
|
|
396
|
+
error=f"Path not found: {path}",
|
|
397
|
+
target=pattern,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
# Find matching files
|
|
402
|
+
matches = list(base_path.glob(pattern))
|
|
403
|
+
|
|
404
|
+
# Filter out directories, sort by modification time
|
|
405
|
+
files = [m for m in matches if m.is_file()]
|
|
406
|
+
files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
|
|
407
|
+
|
|
408
|
+
# Limit results
|
|
409
|
+
max_results = 100
|
|
410
|
+
truncated = len(files) > max_results
|
|
411
|
+
files = files[:max_results]
|
|
412
|
+
|
|
413
|
+
if not files:
|
|
414
|
+
return ToolResult(
|
|
415
|
+
success=True,
|
|
416
|
+
output=f"No files found matching pattern: {pattern}",
|
|
417
|
+
target=pattern,
|
|
418
|
+
data={"count": 0, "files": []},
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Format output
|
|
422
|
+
output_lines = [f"Found {len(files)} file(s) matching '{pattern}':"]
|
|
423
|
+
file_paths = []
|
|
424
|
+
for f in files:
|
|
425
|
+
rel_path = f.relative_to(base_path) if f.is_relative_to(base_path) else f
|
|
426
|
+
output_lines.append(f" {rel_path}")
|
|
427
|
+
file_paths.append(str(rel_path))
|
|
428
|
+
|
|
429
|
+
if truncated:
|
|
430
|
+
output_lines.append(f"\n ... (truncated, showing first {max_results})")
|
|
431
|
+
|
|
432
|
+
return ToolResult(
|
|
433
|
+
success=True,
|
|
434
|
+
output="\n".join(output_lines),
|
|
435
|
+
target=pattern,
|
|
436
|
+
data={"count": len(files), "files": file_paths},
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
except Exception as e:
|
|
440
|
+
return ToolResult(
|
|
441
|
+
success=False,
|
|
442
|
+
output="",
|
|
443
|
+
error=f"Error searching files: {e}",
|
|
444
|
+
target=pattern,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# Register tools
|
|
449
|
+
def register_file_tools(registry):
|
|
450
|
+
"""Register all file tools with a registry"""
|
|
451
|
+
registry.register_class(ReadTool)
|
|
452
|
+
registry.register_class(WriteTool)
|
|
453
|
+
registry.register_class(EditTool)
|
|
454
|
+
registry.register_class(GlobTool)
|