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,322 @@
1
+ """
2
+ Search Tools
3
+
4
+ Tools for searching file contents:
5
+ - Grep: Search for patterns in files
6
+ """
7
+
8
+ import re
9
+ import subprocess
10
+ from pathlib import Path
11
+ from typing import Optional, List
12
+
13
+ from .base import Tool, ToolResult, ToolParameter, ToolPermission
14
+
15
+
16
+ class GrepTool(Tool):
17
+ """Search for patterns in files"""
18
+
19
+ name = "Grep"
20
+ description = (
21
+ "Search for a pattern in files using regex. "
22
+ "Returns matching lines with file paths and line numbers. "
23
+ "Supports full regex syntax."
24
+ )
25
+ category = "search"
26
+ permission = ToolPermission.AUTO # Safe to auto-execute
27
+
28
+ parameters = [
29
+ ToolParameter(
30
+ name="pattern",
31
+ description="Regular expression pattern to search for",
32
+ type="string",
33
+ required=True,
34
+ ),
35
+ ToolParameter(
36
+ name="path",
37
+ description="File or directory to search in (defaults to current directory)",
38
+ type="string",
39
+ required=False,
40
+ default=".",
41
+ ),
42
+ ToolParameter(
43
+ name="glob",
44
+ description="Glob pattern to filter files (e.g., '*.py', '*.ts')",
45
+ type="string",
46
+ required=False,
47
+ ),
48
+ ToolParameter(
49
+ name="case_insensitive",
50
+ description="Perform case-insensitive search",
51
+ type="boolean",
52
+ required=False,
53
+ default=False,
54
+ ),
55
+ ToolParameter(
56
+ name="context_lines",
57
+ description="Number of context lines to show before and after matches",
58
+ type="integer",
59
+ required=False,
60
+ default=0,
61
+ ),
62
+ ToolParameter(
63
+ name="max_results",
64
+ description="Maximum number of results to return",
65
+ type="integer",
66
+ required=False,
67
+ default=50,
68
+ ),
69
+ ]
70
+
71
+ def execute(
72
+ self,
73
+ pattern: str,
74
+ path: str = ".",
75
+ glob: str = None,
76
+ case_insensitive: bool = False,
77
+ context_lines: int = 0,
78
+ max_results: int = 50,
79
+ ) -> ToolResult:
80
+ """Search for pattern in files"""
81
+
82
+ base_path = Path(path).expanduser()
83
+
84
+ if not base_path.exists():
85
+ return ToolResult(
86
+ success=False,
87
+ output="",
88
+ error=f"Path not found: {path}",
89
+ target=pattern,
90
+ )
91
+
92
+ try:
93
+ # Compile regex
94
+ flags = re.IGNORECASE if case_insensitive else 0
95
+ try:
96
+ regex = re.compile(pattern, flags)
97
+ except re.error as e:
98
+ return ToolResult(
99
+ success=False,
100
+ output="",
101
+ error=f"Invalid regex pattern: {e}",
102
+ target=pattern,
103
+ )
104
+
105
+ # Find files to search
106
+ if base_path.is_file():
107
+ files = [base_path]
108
+ else:
109
+ if glob:
110
+ files = list(base_path.glob(f"**/{glob}"))
111
+ else:
112
+ # Search common code files by default
113
+ files = []
114
+ for ext in ["*.py", "*.js", "*.ts", "*.tsx", "*.jsx", "*.go", "*.rs",
115
+ "*.java", "*.c", "*.cpp", "*.h", "*.hpp", "*.md", "*.txt",
116
+ "*.json", "*.yaml", "*.yml", "*.toml", "*.sh", "*.bash"]:
117
+ files.extend(base_path.glob(f"**/{ext}"))
118
+
119
+ files = [f for f in files if f.is_file()]
120
+
121
+ # Skip binary files and very large files
122
+ max_file_size = 1_000_000 # 1MB
123
+ text_files = []
124
+ for f in files:
125
+ try:
126
+ if f.stat().st_size <= max_file_size:
127
+ text_files.append(f)
128
+ except OSError:
129
+ continue
130
+
131
+ # Search files
132
+ results = []
133
+ total_matches = 0
134
+
135
+ for file_path in text_files:
136
+ if total_matches >= max_results:
137
+ break
138
+
139
+ try:
140
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
141
+ lines = f.readlines()
142
+
143
+ for i, line in enumerate(lines):
144
+ if regex.search(line):
145
+ total_matches += 1
146
+
147
+ # Get context
148
+ start = max(0, i - context_lines)
149
+ end = min(len(lines), i + context_lines + 1)
150
+
151
+ context = []
152
+ for j in range(start, end):
153
+ prefix = ">" if j == i else " "
154
+ context.append(f"{prefix} {j+1:4}: {lines[j].rstrip()}")
155
+
156
+ rel_path = file_path.relative_to(base_path) if file_path.is_relative_to(base_path) else file_path
157
+ results.append({
158
+ "file": str(rel_path),
159
+ "line": i + 1,
160
+ "content": line.rstrip(),
161
+ "context": "\n".join(context) if context_lines > 0 else None,
162
+ })
163
+
164
+ if total_matches >= max_results:
165
+ break
166
+
167
+ except (OSError, UnicodeDecodeError):
168
+ continue
169
+
170
+ # Format output
171
+ if not results:
172
+ return ToolResult(
173
+ success=True,
174
+ output=f"No matches found for pattern: {pattern}",
175
+ target=pattern,
176
+ data={"count": 0, "matches": []},
177
+ )
178
+
179
+ output_lines = [f"Found {len(results)} match(es) for '{pattern}':"]
180
+ for r in results:
181
+ output_lines.append(f"\n{r['file']}:{r['line']}")
182
+ if r.get("context"):
183
+ output_lines.append(r["context"])
184
+ else:
185
+ output_lines.append(f" {r['content'][:200]}")
186
+
187
+ if total_matches >= max_results:
188
+ output_lines.append(f"\n... (limited to {max_results} results)")
189
+
190
+ return ToolResult(
191
+ success=True,
192
+ output="\n".join(output_lines),
193
+ target=pattern,
194
+ data={"count": len(results), "matches": results},
195
+ )
196
+
197
+ except Exception as e:
198
+ return ToolResult(
199
+ success=False,
200
+ output="",
201
+ error=f"Error searching: {e}",
202
+ target=pattern,
203
+ )
204
+
205
+
206
+ class RipgrepTool(Tool):
207
+ """Fast search using ripgrep (if available)"""
208
+
209
+ name = "Ripgrep"
210
+ description = (
211
+ "Fast file search using ripgrep (rg). "
212
+ "Falls back to Python grep if ripgrep is not installed."
213
+ )
214
+ category = "search"
215
+ permission = ToolPermission.AUTO
216
+
217
+ parameters = [
218
+ ToolParameter(
219
+ name="pattern",
220
+ description="Search pattern (regex)",
221
+ type="string",
222
+ required=True,
223
+ ),
224
+ ToolParameter(
225
+ name="path",
226
+ description="Path to search in",
227
+ type="string",
228
+ required=False,
229
+ default=".",
230
+ ),
231
+ ToolParameter(
232
+ name="type",
233
+ description="File type to search (e.g., 'py', 'js', 'ts')",
234
+ type="string",
235
+ required=False,
236
+ ),
237
+ ToolParameter(
238
+ name="case_insensitive",
239
+ description="Case-insensitive search",
240
+ type="boolean",
241
+ required=False,
242
+ default=False,
243
+ ),
244
+ ]
245
+
246
+ def execute(
247
+ self,
248
+ pattern: str,
249
+ path: str = ".",
250
+ type: str = None,
251
+ case_insensitive: bool = False,
252
+ ) -> ToolResult:
253
+ """Search using ripgrep"""
254
+
255
+ # Build command
256
+ cmd = ["rg", "--line-number", "--no-heading", "--color=never"]
257
+
258
+ if case_insensitive:
259
+ cmd.append("-i")
260
+
261
+ if type:
262
+ cmd.extend(["-t", type])
263
+
264
+ cmd.extend(["--max-count=100", pattern, path])
265
+
266
+ try:
267
+ result = subprocess.run(
268
+ cmd,
269
+ capture_output=True,
270
+ text=True,
271
+ timeout=30,
272
+ )
273
+
274
+ if result.returncode == 0:
275
+ output = result.stdout
276
+ lines = output.strip().split("\n") if output.strip() else []
277
+ return ToolResult(
278
+ success=True,
279
+ output=output if output else "No matches found",
280
+ target=pattern,
281
+ data={"count": len(lines)},
282
+ )
283
+ elif result.returncode == 1:
284
+ # No matches
285
+ return ToolResult(
286
+ success=True,
287
+ output="No matches found",
288
+ target=pattern,
289
+ data={"count": 0},
290
+ )
291
+ else:
292
+ return ToolResult(
293
+ success=False,
294
+ output="",
295
+ error=result.stderr or "ripgrep error",
296
+ target=pattern,
297
+ )
298
+
299
+ except FileNotFoundError:
300
+ # ripgrep not installed, fall back to grep tool
301
+ grep = GrepTool()
302
+ return grep.execute(pattern=pattern, path=path, case_insensitive=case_insensitive)
303
+ except subprocess.TimeoutExpired:
304
+ return ToolResult(
305
+ success=False,
306
+ output="",
307
+ error="Search timed out",
308
+ target=pattern,
309
+ )
310
+ except Exception as e:
311
+ return ToolResult(
312
+ success=False,
313
+ output="",
314
+ error=f"Search error: {e}",
315
+ target=pattern,
316
+ )
317
+
318
+
319
+ def register_search_tools(registry):
320
+ """Register all search tools with a registry"""
321
+ registry.register_class(GrepTool)
322
+ registry.register_class(RipgrepTool)
@@ -0,0 +1,284 @@
1
+ """
2
+ Task Tool - Sub-agent Spawning
3
+
4
+ Allows spawning sub-agents for complex, multi-step tasks.
5
+ Each sub-agent can use tools and work autonomously.
6
+ """
7
+
8
+ from typing import Optional, Dict, Any, TYPE_CHECKING
9
+ import threading
10
+ import queue
11
+
12
+ from .base import Tool, ToolResult, ToolParameter, ToolPermission
13
+
14
+ if TYPE_CHECKING:
15
+ from ..core import Agent
16
+
17
+
18
+ class TaskTool(Tool):
19
+ """Spawn a sub-agent for complex tasks"""
20
+
21
+ name = "Task"
22
+ description = (
23
+ "Launch a sub-agent to handle a complex, multi-step task autonomously. "
24
+ "The sub-agent has access to all tools and can work independently. "
25
+ "Use for tasks that require multiple steps or exploration."
26
+ )
27
+ category = "agent"
28
+ permission = ToolPermission.ASK # Ask before spawning
29
+
30
+ parameters = [
31
+ ToolParameter(
32
+ name="prompt",
33
+ description="Detailed task description for the sub-agent",
34
+ type="string",
35
+ required=True,
36
+ ),
37
+ ToolParameter(
38
+ name="description",
39
+ description="Short description of the task (3-5 words)",
40
+ type="string",
41
+ required=True,
42
+ ),
43
+ ToolParameter(
44
+ name="agent_type",
45
+ description="Type of agent to spawn",
46
+ type="string",
47
+ required=False,
48
+ default="general",
49
+ enum=["general", "explore", "code", "research"],
50
+ ),
51
+ ]
52
+
53
+ def __init__(self, parent_agent: "Agent" = None):
54
+ super().__init__()
55
+ self.parent_agent = parent_agent
56
+ self._running_tasks: Dict[str, Dict] = {}
57
+ self._task_counter = 0
58
+
59
+ def set_parent_agent(self, agent: "Agent") -> None:
60
+ """Set the parent agent for spawning sub-agents"""
61
+ self.parent_agent = agent
62
+
63
+ def execute(
64
+ self,
65
+ prompt: str,
66
+ description: str,
67
+ agent_type: str = "general",
68
+ ) -> ToolResult:
69
+ """Spawn a sub-agent to handle a task"""
70
+
71
+ if not self.parent_agent:
72
+ return ToolResult(
73
+ success=False,
74
+ output="",
75
+ error="No parent agent configured for task spawning",
76
+ target=description,
77
+ )
78
+
79
+ try:
80
+ # Create sub-agent with same configuration
81
+ from ..core import Agent, AgentConfig
82
+
83
+ # Configure sub-agent based on type
84
+ config = AgentConfig(
85
+ max_iterations=20, # Limit sub-agent iterations
86
+ tool_permissions=self.parent_agent.config.tool_permissions.copy(),
87
+ )
88
+
89
+ # Create sub-agent
90
+ sub_agent = Agent(
91
+ llm=self.parent_agent.llm,
92
+ config=config,
93
+ parent_agent=self.parent_agent,
94
+ )
95
+
96
+ # Add context about this being a sub-agent
97
+ context = f"""You are a sub-agent spawned to handle a specific task.
98
+ Your task: {description}
99
+
100
+ Detailed instructions:
101
+ {prompt}
102
+
103
+ Work autonomously to complete this task. Use available tools as needed.
104
+ When complete, provide a clear summary of what you accomplished.
105
+ """
106
+
107
+ # Run sub-agent synchronously for now
108
+ # (could be made async for parallel tasks)
109
+ result = sub_agent.run(context)
110
+
111
+ return ToolResult(
112
+ success=True,
113
+ output=f"Sub-agent completed task: {description}\n\nResult:\n{result}",
114
+ target=description,
115
+ data={
116
+ "agent_type": agent_type,
117
+ "iterations": sub_agent.iteration_count,
118
+ },
119
+ )
120
+
121
+ except Exception as e:
122
+ return ToolResult(
123
+ success=False,
124
+ output="",
125
+ error=f"Error running sub-agent: {e}",
126
+ target=description,
127
+ )
128
+
129
+
130
+ class TodoWriteTool(Tool):
131
+ """Manage a task list for tracking progress"""
132
+
133
+ name = "TodoWrite"
134
+ description = (
135
+ "Create or update a todo list to track task progress. "
136
+ "Use this to plan complex tasks and show progress to the user."
137
+ )
138
+ category = "agent"
139
+ permission = ToolPermission.AUTO # Safe to auto-execute
140
+
141
+ parameters = [
142
+ ToolParameter(
143
+ name="todos",
144
+ description="List of todo items with status",
145
+ type="array",
146
+ required=True,
147
+ ),
148
+ ]
149
+
150
+ # Class-level todo storage
151
+ _todos: list = []
152
+
153
+ def execute(self, todos: list) -> ToolResult:
154
+ """Update the todo list"""
155
+
156
+ try:
157
+ # Validate todo format
158
+ validated_todos = []
159
+ for item in todos:
160
+ if isinstance(item, dict):
161
+ validated_todos.append({
162
+ "content": item.get("content", ""),
163
+ "status": item.get("status", "pending"),
164
+ "activeForm": item.get("activeForm", item.get("content", "")),
165
+ })
166
+ elif isinstance(item, str):
167
+ validated_todos.append({
168
+ "content": item,
169
+ "status": "pending",
170
+ "activeForm": item,
171
+ })
172
+
173
+ TodoWriteTool._todos = validated_todos
174
+
175
+ # Format output
176
+ output_lines = ["Todo list updated:"]
177
+ for i, todo in enumerate(validated_todos, 1):
178
+ status_icon = {
179
+ "pending": "○",
180
+ "in_progress": "●",
181
+ "completed": "✓",
182
+ }.get(todo["status"], "○")
183
+ output_lines.append(f" {status_icon} {i}. {todo['content']}")
184
+
185
+ return ToolResult(
186
+ success=True,
187
+ output="\n".join(output_lines),
188
+ target=f"{len(validated_todos)} items",
189
+ data={"todos": validated_todos},
190
+ )
191
+
192
+ except Exception as e:
193
+ return ToolResult(
194
+ success=False,
195
+ output="",
196
+ error=f"Error updating todos: {e}",
197
+ target="todos",
198
+ )
199
+
200
+ @classmethod
201
+ def get_todos(cls) -> list:
202
+ """Get current todo list"""
203
+ return cls._todos.copy()
204
+
205
+ @classmethod
206
+ def clear_todos(cls) -> None:
207
+ """Clear the todo list"""
208
+ cls._todos = []
209
+
210
+
211
+ class AskUserTool(Tool):
212
+ """Ask the user a question"""
213
+
214
+ name = "AskUser"
215
+ description = (
216
+ "Ask the user a question to clarify requirements or get a decision. "
217
+ "Use when you need user input to proceed."
218
+ )
219
+ category = "agent"
220
+ permission = ToolPermission.AUTO # Safe - just asks question
221
+
222
+ parameters = [
223
+ ToolParameter(
224
+ name="question",
225
+ description="The question to ask the user",
226
+ type="string",
227
+ required=True,
228
+ ),
229
+ ToolParameter(
230
+ name="options",
231
+ description="Optional list of choices for the user",
232
+ type="array",
233
+ required=False,
234
+ ),
235
+ ]
236
+
237
+ def __init__(self, input_callback=None):
238
+ super().__init__()
239
+ self.input_callback = input_callback or input
240
+
241
+ def execute(self, question: str, options: list = None) -> ToolResult:
242
+ """Ask user a question"""
243
+
244
+ try:
245
+ # Display question
246
+ print(f"\n❓ {question}")
247
+
248
+ if options:
249
+ for i, opt in enumerate(options, 1):
250
+ print(f" {i}. {opt}")
251
+ print()
252
+
253
+ # Get user input
254
+ response = self.input_callback("> ").strip()
255
+
256
+ # If options provided and user entered a number, map to option
257
+ if options and response.isdigit():
258
+ idx = int(response) - 1
259
+ if 0 <= idx < len(options):
260
+ response = options[idx]
261
+
262
+ return ToolResult(
263
+ success=True,
264
+ output=f"User response: {response}",
265
+ target=question[:30],
266
+ data={"response": response},
267
+ )
268
+
269
+ except Exception as e:
270
+ return ToolResult(
271
+ success=False,
272
+ output="",
273
+ error=f"Error getting user input: {e}",
274
+ target=question[:30],
275
+ )
276
+
277
+
278
+ def register_task_tools(registry, parent_agent=None):
279
+ """Register task management tools with a registry"""
280
+ task_tool = TaskTool(parent_agent)
281
+ registry.register(task_tool)
282
+ registry.register_class(TodoWriteTool)
283
+ registry.register_class(AskUserTool)
284
+ return task_tool # Return so parent_agent can be set later