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,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)