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,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bash Execution Tool
|
|
3
|
+
|
|
4
|
+
Tool for executing shell commands with safety controls.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import shlex
|
|
10
|
+
import threading
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional, Set
|
|
13
|
+
|
|
14
|
+
from .base import Tool, ToolResult, ToolParameter, ToolPermission
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Safe read-only commands that can auto-execute without permission
|
|
18
|
+
SAFE_COMMANDS = {
|
|
19
|
+
# Directory listing and navigation
|
|
20
|
+
"ls", "ll", "la", "dir", "pwd", "tree",
|
|
21
|
+
# File viewing (read-only)
|
|
22
|
+
"cat", "head", "tail", "less", "more", "wc",
|
|
23
|
+
# Search and find (read-only)
|
|
24
|
+
"find", "grep", "rg", "ag", "fd", "which", "whereis", "locate",
|
|
25
|
+
# Git read-only
|
|
26
|
+
"git status", "git log", "git diff", "git branch", "git remote",
|
|
27
|
+
"git show", "git ls-files", "git rev-parse",
|
|
28
|
+
# System info (read-only)
|
|
29
|
+
"whoami", "date", "uptime", "uname", "hostname", "id",
|
|
30
|
+
"df", "du", "free", "top -l 1", "ps",
|
|
31
|
+
# Package info (read-only)
|
|
32
|
+
"pip list", "pip show", "npm list", "npm ls", "yarn list",
|
|
33
|
+
"brew list", "brew info",
|
|
34
|
+
# Environment
|
|
35
|
+
"env", "printenv", "echo",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Dangerous commands that should always be blocked or warned about
|
|
39
|
+
DANGEROUS_COMMANDS = {
|
|
40
|
+
# Destructive commands
|
|
41
|
+
"rm -rf /", "rm -rf /*", "rm -rf ~", "rm -rf ~/*",
|
|
42
|
+
"mkfs", "dd if=/dev/zero", "dd if=/dev/random",
|
|
43
|
+
"> /dev/sda", "chmod -R 777 /", "chown -R",
|
|
44
|
+
|
|
45
|
+
# System modification
|
|
46
|
+
"shutdown", "reboot", "init 0", "init 6",
|
|
47
|
+
"halt", "poweroff",
|
|
48
|
+
|
|
49
|
+
# Dangerous patterns
|
|
50
|
+
":(){:|:&};:", # Fork bomb
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Commands that need extra caution (will still ask for confirmation)
|
|
54
|
+
CAUTIOUS_COMMANDS = {
|
|
55
|
+
"rm", "rmdir", "mv", "dd", "chmod", "chown",
|
|
56
|
+
"kill", "killall", "pkill",
|
|
57
|
+
"sudo", "su",
|
|
58
|
+
"curl | sh", "wget | sh", "curl | bash", "wget | bash",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BashTool(Tool):
|
|
63
|
+
"""Execute bash commands"""
|
|
64
|
+
|
|
65
|
+
name = "Bash"
|
|
66
|
+
description = (
|
|
67
|
+
"Execute a bash command in the shell. "
|
|
68
|
+
"Use for running scripts, git commands, package managers, etc. "
|
|
69
|
+
"Commands run in the current working directory."
|
|
70
|
+
)
|
|
71
|
+
category = "execution"
|
|
72
|
+
permission = ToolPermission.ASK # Default, but overridden by is_safe_command
|
|
73
|
+
|
|
74
|
+
parameters = [
|
|
75
|
+
ToolParameter(
|
|
76
|
+
name="command",
|
|
77
|
+
description="The bash command to execute",
|
|
78
|
+
type="string",
|
|
79
|
+
required=True,
|
|
80
|
+
),
|
|
81
|
+
ToolParameter(
|
|
82
|
+
name="timeout",
|
|
83
|
+
description="Maximum execution time in seconds (default: 120)",
|
|
84
|
+
type="integer",
|
|
85
|
+
required=False,
|
|
86
|
+
default=120,
|
|
87
|
+
),
|
|
88
|
+
ToolParameter(
|
|
89
|
+
name="cwd",
|
|
90
|
+
description="Working directory for the command (default: current directory)",
|
|
91
|
+
type="string",
|
|
92
|
+
required=False,
|
|
93
|
+
),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
def __init__(self):
|
|
97
|
+
super().__init__()
|
|
98
|
+
self._running_processes: Set[subprocess.Popen] = set()
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def is_safe_command(command: str) -> bool:
|
|
102
|
+
"""Check if a command is safe to execute without user approval.
|
|
103
|
+
|
|
104
|
+
Safe commands are read-only operations that don't modify the system.
|
|
105
|
+
"""
|
|
106
|
+
cmd = command.strip().lower()
|
|
107
|
+
|
|
108
|
+
# Extract the base command (first word or first two words for compound commands)
|
|
109
|
+
parts = cmd.split()
|
|
110
|
+
if not parts:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
base_cmd = parts[0]
|
|
114
|
+
|
|
115
|
+
# Check single-word safe commands
|
|
116
|
+
if base_cmd in SAFE_COMMANDS:
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
# Check two-word safe commands (like "git status", "pip list")
|
|
120
|
+
if len(parts) >= 2:
|
|
121
|
+
two_word = f"{parts[0]} {parts[1]}"
|
|
122
|
+
if two_word in SAFE_COMMANDS:
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def get_effective_permission(self, command: str) -> ToolPermission:
|
|
128
|
+
"""Get the effective permission for a specific command."""
|
|
129
|
+
if self.is_safe_command(command):
|
|
130
|
+
return ToolPermission.AUTO
|
|
131
|
+
return ToolPermission.ASK
|
|
132
|
+
|
|
133
|
+
def execute(
|
|
134
|
+
self,
|
|
135
|
+
command: str,
|
|
136
|
+
timeout: int = 120,
|
|
137
|
+
cwd: str = None,
|
|
138
|
+
) -> ToolResult:
|
|
139
|
+
"""Execute a bash command"""
|
|
140
|
+
|
|
141
|
+
# Safety checks
|
|
142
|
+
safety_result = self._check_safety(command)
|
|
143
|
+
if safety_result:
|
|
144
|
+
return safety_result
|
|
145
|
+
|
|
146
|
+
# Resolve working directory
|
|
147
|
+
if cwd:
|
|
148
|
+
work_dir = Path(cwd).expanduser()
|
|
149
|
+
if not work_dir.exists():
|
|
150
|
+
return ToolResult(
|
|
151
|
+
success=False,
|
|
152
|
+
output="",
|
|
153
|
+
error=f"Working directory not found: {cwd}",
|
|
154
|
+
target=command[:40],
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
work_dir = Path.cwd()
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Execute command
|
|
161
|
+
process = subprocess.Popen(
|
|
162
|
+
command,
|
|
163
|
+
shell=True,
|
|
164
|
+
stdout=subprocess.PIPE,
|
|
165
|
+
stderr=subprocess.PIPE,
|
|
166
|
+
cwd=str(work_dir),
|
|
167
|
+
text=True,
|
|
168
|
+
env={**os.environ, "TERM": "dumb"}, # Avoid color codes
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self._running_processes.add(process)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
stdout, stderr = process.communicate(timeout=timeout)
|
|
175
|
+
except subprocess.TimeoutExpired:
|
|
176
|
+
process.kill()
|
|
177
|
+
stdout, stderr = process.communicate()
|
|
178
|
+
return ToolResult(
|
|
179
|
+
success=False,
|
|
180
|
+
output=stdout or "",
|
|
181
|
+
error=f"Command timed out after {timeout} seconds\n{stderr}",
|
|
182
|
+
target=command[:40],
|
|
183
|
+
)
|
|
184
|
+
finally:
|
|
185
|
+
self._running_processes.discard(process)
|
|
186
|
+
|
|
187
|
+
# Truncate output if too large
|
|
188
|
+
max_output = 50000
|
|
189
|
+
if len(stdout) > max_output:
|
|
190
|
+
stdout = stdout[:max_output] + f"\n... (output truncated, {len(stdout)} total chars)"
|
|
191
|
+
|
|
192
|
+
if len(stderr) > max_output:
|
|
193
|
+
stderr = stderr[:max_output] + f"\n... (stderr truncated)"
|
|
194
|
+
|
|
195
|
+
# Format output
|
|
196
|
+
if process.returncode == 0:
|
|
197
|
+
output = stdout
|
|
198
|
+
if stderr:
|
|
199
|
+
output += f"\n\nStderr:\n{stderr}"
|
|
200
|
+
return ToolResult(
|
|
201
|
+
success=True,
|
|
202
|
+
output=output or "(no output)",
|
|
203
|
+
target=command[:40],
|
|
204
|
+
data={
|
|
205
|
+
"return_code": process.returncode,
|
|
206
|
+
"cwd": str(work_dir),
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
return ToolResult(
|
|
211
|
+
success=False,
|
|
212
|
+
output=stdout,
|
|
213
|
+
error=f"Command failed with exit code {process.returncode}\n{stderr}",
|
|
214
|
+
target=command[:40],
|
|
215
|
+
data={"return_code": process.returncode},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
except Exception as e:
|
|
219
|
+
return ToolResult(
|
|
220
|
+
success=False,
|
|
221
|
+
output="",
|
|
222
|
+
error=f"Error executing command: {e}",
|
|
223
|
+
target=command[:40],
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _check_safety(self, command: str) -> Optional[ToolResult]:
|
|
227
|
+
"""Check if command is safe to execute"""
|
|
228
|
+
cmd_lower = command.lower().strip()
|
|
229
|
+
|
|
230
|
+
# Check for dangerous commands
|
|
231
|
+
for dangerous in DANGEROUS_COMMANDS:
|
|
232
|
+
if dangerous in cmd_lower:
|
|
233
|
+
return ToolResult(
|
|
234
|
+
success=False,
|
|
235
|
+
output="",
|
|
236
|
+
error=f"Dangerous command blocked: {command[:50]}",
|
|
237
|
+
target=command[:40],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Check for common dangerous patterns
|
|
241
|
+
if cmd_lower.startswith("rm ") and " -rf" in cmd_lower:
|
|
242
|
+
# Check if it's trying to delete important paths
|
|
243
|
+
dangerous_paths = ["/", "/*", "~", "~/*", "/home", "/usr", "/etc", "/var"]
|
|
244
|
+
for path in dangerous_paths:
|
|
245
|
+
if path in cmd_lower:
|
|
246
|
+
return ToolResult(
|
|
247
|
+
success=False,
|
|
248
|
+
output="",
|
|
249
|
+
error=f"Refusing to delete system path: {path}",
|
|
250
|
+
target=command[:40],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
def kill_all(self) -> None:
|
|
256
|
+
"""Kill all running processes"""
|
|
257
|
+
for process in list(self._running_processes):
|
|
258
|
+
try:
|
|
259
|
+
process.kill()
|
|
260
|
+
except Exception:
|
|
261
|
+
pass
|
|
262
|
+
self._running_processes.clear()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class BackgroundBashTool(Tool):
|
|
266
|
+
"""Execute bash commands in the background"""
|
|
267
|
+
|
|
268
|
+
name = "BackgroundBash"
|
|
269
|
+
description = (
|
|
270
|
+
"Execute a bash command in the background. "
|
|
271
|
+
"Returns immediately while command runs asynchronously. "
|
|
272
|
+
"Use for long-running processes like servers."
|
|
273
|
+
)
|
|
274
|
+
category = "execution"
|
|
275
|
+
permission = ToolPermission.ASK
|
|
276
|
+
|
|
277
|
+
parameters = [
|
|
278
|
+
ToolParameter(
|
|
279
|
+
name="command",
|
|
280
|
+
description="The bash command to execute in background",
|
|
281
|
+
type="string",
|
|
282
|
+
required=True,
|
|
283
|
+
),
|
|
284
|
+
ToolParameter(
|
|
285
|
+
name="cwd",
|
|
286
|
+
description="Working directory for the command",
|
|
287
|
+
type="string",
|
|
288
|
+
required=False,
|
|
289
|
+
),
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
_background_processes: dict = {} # class-level storage
|
|
293
|
+
_next_id: int = 1
|
|
294
|
+
|
|
295
|
+
def execute(self, command: str, cwd: str = None) -> ToolResult:
|
|
296
|
+
"""Execute command in background"""
|
|
297
|
+
|
|
298
|
+
work_dir = Path(cwd).expanduser() if cwd else Path.cwd()
|
|
299
|
+
|
|
300
|
+
if not work_dir.exists():
|
|
301
|
+
return ToolResult(
|
|
302
|
+
success=False,
|
|
303
|
+
output="",
|
|
304
|
+
error=f"Working directory not found: {cwd}",
|
|
305
|
+
target=command[:40],
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
process = subprocess.Popen(
|
|
310
|
+
command,
|
|
311
|
+
shell=True,
|
|
312
|
+
stdout=subprocess.PIPE,
|
|
313
|
+
stderr=subprocess.PIPE,
|
|
314
|
+
cwd=str(work_dir),
|
|
315
|
+
text=True,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Store process
|
|
319
|
+
process_id = f"bg_{BackgroundBashTool._next_id}"
|
|
320
|
+
BackgroundBashTool._next_id += 1
|
|
321
|
+
BackgroundBashTool._background_processes[process_id] = {
|
|
322
|
+
"process": process,
|
|
323
|
+
"command": command,
|
|
324
|
+
"cwd": str(work_dir),
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return ToolResult(
|
|
328
|
+
success=True,
|
|
329
|
+
output=f"Started background process: {process_id}\nPID: {process.pid}\nCommand: {command[:50]}",
|
|
330
|
+
target=command[:40],
|
|
331
|
+
data={
|
|
332
|
+
"process_id": process_id,
|
|
333
|
+
"pid": process.pid,
|
|
334
|
+
},
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
except Exception as e:
|
|
338
|
+
return ToolResult(
|
|
339
|
+
success=False,
|
|
340
|
+
output="",
|
|
341
|
+
error=f"Error starting background process: {e}",
|
|
342
|
+
target=command[:40],
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def get_process(cls, process_id: str):
|
|
347
|
+
"""Get a background process by ID"""
|
|
348
|
+
return cls._background_processes.get(process_id)
|
|
349
|
+
|
|
350
|
+
@classmethod
|
|
351
|
+
def kill_process(cls, process_id: str) -> bool:
|
|
352
|
+
"""Kill a background process"""
|
|
353
|
+
info = cls._background_processes.get(process_id)
|
|
354
|
+
if info:
|
|
355
|
+
try:
|
|
356
|
+
info["process"].kill()
|
|
357
|
+
del cls._background_processes[process_id]
|
|
358
|
+
return True
|
|
359
|
+
except Exception:
|
|
360
|
+
return False
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def register_bash_tools(registry):
|
|
365
|
+
"""Register bash tools with a registry"""
|
|
366
|
+
registry.register_class(BashTool)
|
|
367
|
+
registry.register_class(BackgroundBashTool)
|