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.
Files changed (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. 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)