nc1709 1.15.4__py3-none-any.whl → 1.18.8__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 +1 -1
- nc1709/agent/core.py +172 -19
- nc1709/agent/permissions.py +2 -2
- nc1709/agent/tools/bash_tool.py +295 -8
- nc1709/cli.py +435 -19
- nc1709/cli_ui.py +137 -52
- nc1709/conversation_logger.py +416 -0
- nc1709/llm_adapter.py +62 -4
- nc1709/plugins/agents/database_agent.py +695 -0
- nc1709/plugins/agents/django_agent.py +11 -4
- nc1709/plugins/agents/docker_agent.py +11 -4
- nc1709/plugins/agents/fastapi_agent.py +11 -4
- nc1709/plugins/agents/git_agent.py +11 -4
- nc1709/plugins/agents/nextjs_agent.py +11 -4
- nc1709/plugins/agents/ollama_agent.py +574 -0
- nc1709/plugins/agents/test_agent.py +702 -0
- nc1709/prompts/unified_prompt.py +156 -14
- nc1709/requirements_tracker.py +526 -0
- nc1709/thinking_messages.py +337 -0
- nc1709/version_check.py +6 -2
- nc1709/web/server.py +63 -3
- nc1709/web/templates/index.html +819 -140
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/METADATA +10 -7
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/RECORD +28 -22
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/WHEEL +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/entry_points.txt +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/licenses/LICENSE +0 -0
- {nc1709-1.15.4.dist-info → nc1709-1.18.8.dist-info}/top_level.txt +0 -0
nc1709/agent/tools/bash_tool.py
CHANGED
|
@@ -24,15 +24,65 @@ SAFE_COMMANDS = {
|
|
|
24
24
|
"find", "grep", "rg", "ag", "fd", "which", "whereis", "locate",
|
|
25
25
|
# Git read-only
|
|
26
26
|
"git status", "git log", "git diff", "git branch", "git remote",
|
|
27
|
-
"git show", "git ls-files", "git rev-parse",
|
|
27
|
+
"git show", "git ls-files", "git rev-parse", "git tag",
|
|
28
28
|
# System info (read-only)
|
|
29
29
|
"whoami", "date", "uptime", "uname", "hostname", "id",
|
|
30
30
|
"df", "du", "free", "top -l 1", "ps",
|
|
31
|
-
# Package info (read-only)
|
|
32
|
-
"pip list", "pip show", "
|
|
33
|
-
"
|
|
31
|
+
# Package info (read-only) - Python
|
|
32
|
+
"pip list", "pip show", "pip freeze", "pip check",
|
|
33
|
+
"pip3 list", "pip3 show", "pip3 freeze",
|
|
34
|
+
"poetry show", "poetry version", "poetry env list",
|
|
35
|
+
"pipenv graph", "pipenv --version",
|
|
36
|
+
"uv pip list", "uv pip show",
|
|
37
|
+
"conda list", "conda info", "conda env list",
|
|
38
|
+
# Package info (read-only) - JavaScript/Node
|
|
39
|
+
"npm list", "npm ls", "npm outdated", "npm view", "npm version",
|
|
40
|
+
"yarn list", "yarn info", "yarn outdated", "yarn version",
|
|
41
|
+
"pnpm list", "pnpm ls", "pnpm outdated",
|
|
42
|
+
"bun pm ls",
|
|
43
|
+
# Package info (read-only) - Other languages
|
|
44
|
+
"cargo tree", "cargo metadata", "cargo version",
|
|
45
|
+
"go list", "go version", "go env",
|
|
46
|
+
"rustc --version", "rustup show",
|
|
47
|
+
"ruby --version", "gem list", "bundle list",
|
|
48
|
+
"composer show", "composer info",
|
|
49
|
+
"mix deps.tree", "mix hex.info",
|
|
50
|
+
# Package info (read-only) - System
|
|
51
|
+
"brew list", "brew info", "brew outdated",
|
|
52
|
+
"apt list", "dpkg -l",
|
|
34
53
|
# Environment
|
|
35
54
|
"env", "printenv", "echo",
|
|
55
|
+
# Ollama read-only (model listing)
|
|
56
|
+
"ollama list", "ollama ls", "ollama show", "ollama ps",
|
|
57
|
+
# Docker read-only
|
|
58
|
+
"docker ps", "docker images", "docker logs", "docker inspect",
|
|
59
|
+
"docker stats", "docker version", "docker info",
|
|
60
|
+
"docker-compose ps", "docker compose ps",
|
|
61
|
+
# Kubernetes read-only
|
|
62
|
+
"kubectl get", "kubectl describe", "kubectl logs", "kubectl top",
|
|
63
|
+
"kubectl version", "kubectl cluster-info", "kubectl config view",
|
|
64
|
+
"kubectl api-resources",
|
|
65
|
+
# Database read-only (listing/info only)
|
|
66
|
+
"psql --version", "mysql --version", "mongo --version",
|
|
67
|
+
"redis-cli --version", "sqlite3 --version",
|
|
68
|
+
"pg_isready",
|
|
69
|
+
# Cloud CLI read-only
|
|
70
|
+
"aws --version", "aws sts get-caller-identity",
|
|
71
|
+
"aws s3 ls", "aws ec2 describe-instances",
|
|
72
|
+
"gcloud --version", "gcloud config list", "gcloud projects list",
|
|
73
|
+
"az --version", "az account show", "az group list",
|
|
74
|
+
# Infrastructure read-only
|
|
75
|
+
"terraform version", "terraform providers", "terraform state list",
|
|
76
|
+
"terraform validate", "terraform fmt -check",
|
|
77
|
+
"ansible --version", "ansible-inventory --list",
|
|
78
|
+
# Testing frameworks (read-only)
|
|
79
|
+
"pytest --collect-only", "pytest --version",
|
|
80
|
+
"jest --version", "mocha --version", "vitest --version",
|
|
81
|
+
"go test -list",
|
|
82
|
+
# Misc tools
|
|
83
|
+
"make --version", "cmake --version",
|
|
84
|
+
"node --version", "python --version", "python3 --version",
|
|
85
|
+
"java --version", "javac --version",
|
|
36
86
|
}
|
|
37
87
|
|
|
38
88
|
# Dangerous commands that should always be blocked or warned about
|
|
@@ -58,6 +108,137 @@ CAUTIOUS_COMMANDS = {
|
|
|
58
108
|
"curl | sh", "wget | sh", "curl | bash", "wget | bash",
|
|
59
109
|
}
|
|
60
110
|
|
|
111
|
+
# Error patterns with helpful suggestions
|
|
112
|
+
ERROR_SUGGESTIONS = {
|
|
113
|
+
# Python errors
|
|
114
|
+
"ModuleNotFoundError": "Install the missing module with: pip install {module}",
|
|
115
|
+
"No module named": "Install the missing module with: pip install {module}",
|
|
116
|
+
"ImportError": "Check module installation or virtual environment activation",
|
|
117
|
+
# Node.js errors
|
|
118
|
+
"Cannot find module": "Install with: npm install {module}",
|
|
119
|
+
"MODULE_NOT_FOUND": "Run: npm install to install dependencies",
|
|
120
|
+
"ERR_MODULE_NOT_FOUND": "Run: npm install to install dependencies",
|
|
121
|
+
# Command not found
|
|
122
|
+
"command not found": "Install {command} or check your PATH",
|
|
123
|
+
"not found": "The command may not be installed. Try installing it first.",
|
|
124
|
+
# Permission errors
|
|
125
|
+
"Permission denied": "Try with sudo or check file permissions (chmod)",
|
|
126
|
+
"EACCES": "Permission denied. Check file/directory permissions.",
|
|
127
|
+
# Network errors
|
|
128
|
+
"Could not resolve host": "Check your network connection and DNS settings",
|
|
129
|
+
"Connection refused": "The service may not be running. Check if it's started.",
|
|
130
|
+
"ECONNREFUSED": "Connection refused. Is the service running?",
|
|
131
|
+
"Network is unreachable": "Check your network connection",
|
|
132
|
+
"Temporary failure in name resolution": "DNS resolution failed. Check network.",
|
|
133
|
+
# Git errors
|
|
134
|
+
"not a git repository": "Initialize with: git init",
|
|
135
|
+
"fatal: refusing to merge unrelated histories": "Use: git pull --allow-unrelated-histories",
|
|
136
|
+
"Your branch is behind": "Run: git pull to update your branch",
|
|
137
|
+
"CONFLICT": "Merge conflict detected. Resolve conflicts manually.",
|
|
138
|
+
# Docker errors
|
|
139
|
+
"Cannot connect to the Docker daemon": "Start Docker: sudo systemctl start docker",
|
|
140
|
+
"Error response from daemon": "Check Docker logs: docker logs <container>",
|
|
141
|
+
"port is already allocated": "Port in use. Stop the other service or use different port.",
|
|
142
|
+
# Database errors
|
|
143
|
+
"connection refused": "Database may not be running. Check service status.",
|
|
144
|
+
"FATAL: role": "Create the database user or check connection string",
|
|
145
|
+
"Access denied for user": "Check database username/password",
|
|
146
|
+
# Kubernetes errors
|
|
147
|
+
"Unable to connect to the server": "Check kubectl config: kubectl config view",
|
|
148
|
+
"error: the server doesn't have a resource type": "Invalid resource. Use: kubectl api-resources",
|
|
149
|
+
# File errors
|
|
150
|
+
"No such file or directory": "Check if the path exists. Use: ls {path}",
|
|
151
|
+
"Is a directory": "Expected a file but got a directory",
|
|
152
|
+
"Not a directory": "Expected a directory but got a file",
|
|
153
|
+
# Memory/Resource errors
|
|
154
|
+
"Cannot allocate memory": "System out of memory. Free up resources.",
|
|
155
|
+
"Killed": "Process killed (likely OOM). Try with less data or more memory.",
|
|
156
|
+
"ENOMEM": "Out of memory. Close other applications.",
|
|
157
|
+
# Disk errors
|
|
158
|
+
"No space left on device": "Disk full. Free up space: df -h",
|
|
159
|
+
"ENOSPC": "Disk full. Clean up files or expand storage.",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Commands that need extended timeouts (in seconds)
|
|
163
|
+
EXTENDED_TIMEOUT_COMMANDS = {
|
|
164
|
+
# AI/ML model downloads
|
|
165
|
+
"ollama pull": 1800, # 30 minutes for large model downloads
|
|
166
|
+
"ollama run": 600, # 10 minutes for model loading
|
|
167
|
+
# Docker operations
|
|
168
|
+
"docker pull": 900, # 15 minutes for docker images
|
|
169
|
+
"docker build": 1200, # 20 minutes for docker builds
|
|
170
|
+
"docker-compose up": 600, # 10 minutes for compose
|
|
171
|
+
"docker compose up": 600,
|
|
172
|
+
# Python package managers
|
|
173
|
+
"pip install": 600, # 10 minutes for pip
|
|
174
|
+
"pip3 install": 600,
|
|
175
|
+
"poetry install": 600, # 10 minutes for poetry
|
|
176
|
+
"poetry add": 300,
|
|
177
|
+
"pipenv install": 600,
|
|
178
|
+
"uv pip install": 300, # uv is faster
|
|
179
|
+
"conda install": 600, # 10 minutes for conda
|
|
180
|
+
"conda create": 600,
|
|
181
|
+
# JavaScript package managers
|
|
182
|
+
"npm install": 600, # 10 minutes for npm
|
|
183
|
+
"npm ci": 600,
|
|
184
|
+
"yarn install": 600, # 10 minutes for yarn
|
|
185
|
+
"yarn add": 300,
|
|
186
|
+
"pnpm install": 600,
|
|
187
|
+
"bun install": 300, # bun is faster
|
|
188
|
+
# Rust/Cargo
|
|
189
|
+
"cargo build": 1200, # 20 minutes for large Rust projects
|
|
190
|
+
"cargo install": 900,
|
|
191
|
+
"cargo test": 600,
|
|
192
|
+
# Go
|
|
193
|
+
"go build": 600,
|
|
194
|
+
"go install": 600,
|
|
195
|
+
"go mod download": 300,
|
|
196
|
+
# Java/JVM
|
|
197
|
+
"mvn install": 1200, # 20 minutes for Maven
|
|
198
|
+
"mvn clean install": 1200,
|
|
199
|
+
"mvn package": 900,
|
|
200
|
+
"gradle build": 1200, # 20 minutes for Gradle
|
|
201
|
+
"./gradlew build": 1200,
|
|
202
|
+
"sbt compile": 900, # 15 minutes for Scala
|
|
203
|
+
# Ruby
|
|
204
|
+
"bundle install": 600,
|
|
205
|
+
"gem install": 300,
|
|
206
|
+
# PHP
|
|
207
|
+
"composer install": 600,
|
|
208
|
+
"composer update": 600,
|
|
209
|
+
# Database operations
|
|
210
|
+
"pg_dump": 1800, # 30 minutes for large DB dumps
|
|
211
|
+
"pg_restore": 3600, # 1 hour for large restores
|
|
212
|
+
"mysqldump": 1800,
|
|
213
|
+
"mongodump": 1800,
|
|
214
|
+
"mongorestore": 3600,
|
|
215
|
+
# Cloud/Infrastructure
|
|
216
|
+
"terraform apply": 1800, # 30 minutes for infra deployment
|
|
217
|
+
"terraform plan": 600,
|
|
218
|
+
"terraform destroy": 1800,
|
|
219
|
+
"pulumi up": 1800,
|
|
220
|
+
"ansible-playbook": 1800,
|
|
221
|
+
"aws s3 sync": 1800, # 30 minutes for large S3 syncs
|
|
222
|
+
"aws s3 cp": 900,
|
|
223
|
+
"gcloud builds submit": 1200,
|
|
224
|
+
# Kubernetes
|
|
225
|
+
"kubectl apply": 600,
|
|
226
|
+
"kubectl rollout status": 600,
|
|
227
|
+
"helm install": 600,
|
|
228
|
+
"helm upgrade": 600,
|
|
229
|
+
# Testing (can take a while for large test suites)
|
|
230
|
+
"pytest": 1200, # 20 minutes for full test suite
|
|
231
|
+
"npm test": 900,
|
|
232
|
+
"yarn test": 900,
|
|
233
|
+
"go test": 900,
|
|
234
|
+
"cargo test": 900,
|
|
235
|
+
"make test": 900,
|
|
236
|
+
# Build systems
|
|
237
|
+
"make": 900,
|
|
238
|
+
"cmake --build": 1200,
|
|
239
|
+
"ninja": 900,
|
|
240
|
+
}
|
|
241
|
+
|
|
61
242
|
|
|
62
243
|
class BashTool(Tool):
|
|
63
244
|
"""Execute bash commands"""
|
|
@@ -122,8 +303,82 @@ class BashTool(Tool):
|
|
|
122
303
|
if two_word in SAFE_COMMANDS:
|
|
123
304
|
return True
|
|
124
305
|
|
|
306
|
+
# Check three-word safe commands (like "aws s3 ls", "docker compose ps")
|
|
307
|
+
if len(parts) >= 3:
|
|
308
|
+
three_word = f"{parts[0]} {parts[1]} {parts[2]}"
|
|
309
|
+
if three_word in SAFE_COMMANDS:
|
|
310
|
+
return True
|
|
311
|
+
|
|
125
312
|
return False
|
|
126
313
|
|
|
314
|
+
@staticmethod
|
|
315
|
+
def get_extended_timeout(command: str) -> Optional[int]:
|
|
316
|
+
"""Get extended timeout for commands that need more time.
|
|
317
|
+
|
|
318
|
+
Returns the extended timeout in seconds, or None if default should be used.
|
|
319
|
+
"""
|
|
320
|
+
cmd_lower = command.strip().lower()
|
|
321
|
+
|
|
322
|
+
for cmd_prefix, timeout in EXTENDED_TIMEOUT_COMMANDS.items():
|
|
323
|
+
if cmd_lower.startswith(cmd_prefix):
|
|
324
|
+
return timeout
|
|
325
|
+
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def get_error_suggestion(error_output: str, command: str = "") -> Optional[str]:
|
|
330
|
+
"""Get a helpful suggestion based on error output.
|
|
331
|
+
|
|
332
|
+
Analyzes error messages and returns actionable suggestions.
|
|
333
|
+
"""
|
|
334
|
+
import re
|
|
335
|
+
|
|
336
|
+
error_lower = error_output.lower()
|
|
337
|
+
|
|
338
|
+
for pattern, suggestion in ERROR_SUGGESTIONS.items():
|
|
339
|
+
if pattern.lower() in error_lower:
|
|
340
|
+
# Try to extract relevant context for placeholders
|
|
341
|
+
result = suggestion
|
|
342
|
+
|
|
343
|
+
# Extract module name for Python/Node errors
|
|
344
|
+
if "{module}" in suggestion:
|
|
345
|
+
# Python: No module named 'xyz'
|
|
346
|
+
match = re.search(r"no module named ['\"]?(\w+)", error_lower)
|
|
347
|
+
if match:
|
|
348
|
+
result = suggestion.replace("{module}", match.group(1))
|
|
349
|
+
else:
|
|
350
|
+
# Node: Cannot find module 'xyz'
|
|
351
|
+
match = re.search(r"cannot find module ['\"]?([^'\"]+)", error_lower)
|
|
352
|
+
if match:
|
|
353
|
+
result = suggestion.replace("{module}", match.group(1))
|
|
354
|
+
else:
|
|
355
|
+
result = suggestion.replace("{module}", "<module_name>")
|
|
356
|
+
|
|
357
|
+
# Extract command name for 'command not found'
|
|
358
|
+
if "{command}" in suggestion:
|
|
359
|
+
match = re.search(r"(\w+):\s*command not found", error_lower)
|
|
360
|
+
if match:
|
|
361
|
+
result = suggestion.replace("{command}", match.group(1))
|
|
362
|
+
else:
|
|
363
|
+
# Extract from original command
|
|
364
|
+
cmd_parts = command.split()
|
|
365
|
+
if cmd_parts:
|
|
366
|
+
result = suggestion.replace("{command}", cmd_parts[0])
|
|
367
|
+
else:
|
|
368
|
+
result = suggestion.replace("{command}", "<command>")
|
|
369
|
+
|
|
370
|
+
# Extract path for file errors
|
|
371
|
+
if "{path}" in suggestion:
|
|
372
|
+
match = re.search(r"['\"]?(/[^'\":\s]+|\.?/[^'\":\s]+)", error_output)
|
|
373
|
+
if match:
|
|
374
|
+
result = suggestion.replace("{path}", match.group(1))
|
|
375
|
+
else:
|
|
376
|
+
result = suggestion.replace("{path}", ".")
|
|
377
|
+
|
|
378
|
+
return result
|
|
379
|
+
|
|
380
|
+
return None
|
|
381
|
+
|
|
127
382
|
def get_effective_permission(self, command: str) -> ToolPermission:
|
|
128
383
|
"""Get the effective permission for a specific command."""
|
|
129
384
|
if self.is_safe_command(command):
|
|
@@ -140,6 +395,11 @@ class BashTool(Tool):
|
|
|
140
395
|
|
|
141
396
|
# Safety checks
|
|
142
397
|
safety_result = self._check_safety(command)
|
|
398
|
+
|
|
399
|
+
# Use extended timeout for certain commands if not explicitly specified
|
|
400
|
+
extended_timeout = self.get_extended_timeout(command)
|
|
401
|
+
if extended_timeout and timeout == 120: # Only if using default timeout
|
|
402
|
+
timeout = extended_timeout
|
|
143
403
|
if safety_result:
|
|
144
404
|
return safety_result
|
|
145
405
|
|
|
@@ -175,11 +435,22 @@ class BashTool(Tool):
|
|
|
175
435
|
except subprocess.TimeoutExpired:
|
|
176
436
|
process.kill()
|
|
177
437
|
stdout, stderr = process.communicate()
|
|
438
|
+
|
|
439
|
+
# Suggest a longer timeout or background execution
|
|
440
|
+
suggestion = (
|
|
441
|
+
f"The command took longer than {timeout}s. "
|
|
442
|
+
"Consider:\n"
|
|
443
|
+
" 1. Running with a longer timeout\n"
|
|
444
|
+
" 2. Running in background for long-running processes\n"
|
|
445
|
+
" 3. Breaking into smaller steps"
|
|
446
|
+
)
|
|
447
|
+
|
|
178
448
|
return ToolResult(
|
|
179
449
|
success=False,
|
|
180
450
|
output=stdout or "",
|
|
181
|
-
error=f"Command timed out after {timeout} seconds\n{stderr}",
|
|
451
|
+
error=f"Command timed out after {timeout} seconds\n{stderr}\n\n💡 Suggestion: {suggestion}",
|
|
182
452
|
target=command[:40],
|
|
453
|
+
data={"timeout": timeout, "suggestion": suggestion},
|
|
183
454
|
)
|
|
184
455
|
finally:
|
|
185
456
|
self._running_processes.discard(process)
|
|
@@ -207,20 +478,36 @@ class BashTool(Tool):
|
|
|
207
478
|
},
|
|
208
479
|
)
|
|
209
480
|
else:
|
|
481
|
+
# Build error message with suggestion if available
|
|
482
|
+
error_msg = f"Command failed with exit code {process.returncode}\n{stderr}"
|
|
483
|
+
suggestion = self.get_error_suggestion(stderr, command)
|
|
484
|
+
if suggestion:
|
|
485
|
+
error_msg += f"\n\n💡 Suggestion: {suggestion}"
|
|
486
|
+
|
|
210
487
|
return ToolResult(
|
|
211
488
|
success=False,
|
|
212
489
|
output=stdout,
|
|
213
|
-
error=
|
|
490
|
+
error=error_msg,
|
|
214
491
|
target=command[:40],
|
|
215
|
-
data={
|
|
492
|
+
data={
|
|
493
|
+
"return_code": process.returncode,
|
|
494
|
+
"suggestion": suggestion,
|
|
495
|
+
},
|
|
216
496
|
)
|
|
217
497
|
|
|
218
498
|
except Exception as e:
|
|
499
|
+
error_str = str(e)
|
|
500
|
+
suggestion = self.get_error_suggestion(error_str, command)
|
|
501
|
+
error_msg = f"Error executing command: {e}"
|
|
502
|
+
if suggestion:
|
|
503
|
+
error_msg += f"\n\n💡 Suggestion: {suggestion}"
|
|
504
|
+
|
|
219
505
|
return ToolResult(
|
|
220
506
|
success=False,
|
|
221
507
|
output="",
|
|
222
|
-
error=
|
|
508
|
+
error=error_msg,
|
|
223
509
|
target=command[:40],
|
|
510
|
+
data={"suggestion": suggestion} if suggestion else None,
|
|
224
511
|
)
|
|
225
512
|
|
|
226
513
|
def _check_safety(self, command: str) -> Optional[ToolResult]:
|