tunacode-cli 0.0.55__py3-none-any.whl → 0.0.78.6__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.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- tunacode/cli/commands/__init__.py +2 -2
- tunacode/cli/commands/implementations/__init__.py +2 -3
- tunacode/cli/commands/implementations/command_reload.py +48 -0
- tunacode/cli/commands/implementations/debug.py +2 -2
- tunacode/cli/commands/implementations/development.py +10 -8
- tunacode/cli/commands/implementations/model.py +357 -29
- tunacode/cli/commands/implementations/quickstart.py +43 -0
- tunacode/cli/commands/implementations/system.py +96 -3
- tunacode/cli/commands/implementations/template.py +0 -2
- tunacode/cli/commands/registry.py +139 -5
- tunacode/cli/commands/slash/__init__.py +32 -0
- tunacode/cli/commands/slash/command.py +157 -0
- tunacode/cli/commands/slash/loader.py +135 -0
- tunacode/cli/commands/slash/processor.py +294 -0
- tunacode/cli/commands/slash/types.py +93 -0
- tunacode/cli/commands/slash/validator.py +400 -0
- tunacode/cli/main.py +23 -2
- tunacode/cli/repl.py +217 -190
- tunacode/cli/repl_components/command_parser.py +38 -4
- tunacode/cli/repl_components/error_recovery.py +85 -4
- tunacode/cli/repl_components/output_display.py +12 -1
- tunacode/cli/repl_components/tool_executor.py +1 -1
- tunacode/configuration/defaults.py +12 -3
- tunacode/configuration/key_descriptions.py +284 -0
- tunacode/configuration/settings.py +0 -1
- tunacode/constants.py +12 -40
- tunacode/core/agents/__init__.py +43 -2
- tunacode/core/agents/agent_components/__init__.py +7 -0
- tunacode/core/agents/agent_components/agent_config.py +249 -55
- tunacode/core/agents/agent_components/agent_helpers.py +43 -13
- tunacode/core/agents/agent_components/node_processor.py +179 -139
- tunacode/core/agents/agent_components/response_state.py +123 -6
- tunacode/core/agents/agent_components/state_transition.py +116 -0
- tunacode/core/agents/agent_components/streaming.py +296 -0
- tunacode/core/agents/agent_components/task_completion.py +19 -6
- tunacode/core/agents/agent_components/tool_buffer.py +21 -1
- tunacode/core/agents/agent_components/tool_executor.py +10 -0
- tunacode/core/agents/main.py +522 -370
- tunacode/core/agents/main_legact.py +538 -0
- tunacode/core/agents/prompts.py +66 -0
- tunacode/core/agents/utils.py +29 -121
- tunacode/core/code_index.py +83 -29
- tunacode/core/setup/__init__.py +0 -2
- tunacode/core/setup/config_setup.py +110 -20
- tunacode/core/setup/config_wizard.py +230 -0
- tunacode/core/setup/coordinator.py +14 -5
- tunacode/core/state.py +16 -20
- tunacode/core/token_usage/usage_tracker.py +5 -3
- tunacode/core/tool_authorization.py +352 -0
- tunacode/core/tool_handler.py +67 -40
- tunacode/exceptions.py +119 -5
- tunacode/prompts/system.xml +751 -0
- tunacode/services/mcp.py +125 -7
- tunacode/setup.py +5 -25
- tunacode/tools/base.py +163 -0
- tunacode/tools/bash.py +110 -1
- tunacode/tools/glob.py +332 -34
- tunacode/tools/grep.py +179 -82
- tunacode/tools/grep_components/result_formatter.py +98 -4
- tunacode/tools/list_dir.py +132 -2
- tunacode/tools/prompts/bash_prompt.xml +72 -0
- tunacode/tools/prompts/glob_prompt.xml +45 -0
- tunacode/tools/prompts/grep_prompt.xml +98 -0
- tunacode/tools/prompts/list_dir_prompt.xml +31 -0
- tunacode/tools/prompts/react_prompt.xml +23 -0
- tunacode/tools/prompts/read_file_prompt.xml +54 -0
- tunacode/tools/prompts/run_command_prompt.xml +64 -0
- tunacode/tools/prompts/update_file_prompt.xml +53 -0
- tunacode/tools/prompts/write_file_prompt.xml +37 -0
- tunacode/tools/react.py +153 -0
- tunacode/tools/read_file.py +91 -0
- tunacode/tools/run_command.py +114 -0
- tunacode/tools/schema_assembler.py +167 -0
- tunacode/tools/update_file.py +94 -0
- tunacode/tools/write_file.py +86 -0
- tunacode/tools/xml_helper.py +83 -0
- tunacode/tutorial/__init__.py +9 -0
- tunacode/tutorial/content.py +98 -0
- tunacode/tutorial/manager.py +182 -0
- tunacode/tutorial/steps.py +124 -0
- tunacode/types.py +20 -27
- tunacode/ui/completers.py +434 -50
- tunacode/ui/config_dashboard.py +585 -0
- tunacode/ui/console.py +63 -11
- tunacode/ui/input.py +20 -3
- tunacode/ui/keybindings.py +7 -4
- tunacode/ui/model_selector.py +395 -0
- tunacode/ui/output.py +40 -19
- tunacode/ui/panels.py +212 -43
- tunacode/ui/path_heuristics.py +91 -0
- tunacode/ui/prompt_manager.py +5 -1
- tunacode/ui/tool_ui.py +33 -10
- tunacode/utils/api_key_validation.py +93 -0
- tunacode/utils/config_comparator.py +340 -0
- tunacode/utils/json_utils.py +206 -0
- tunacode/utils/message_utils.py +14 -4
- tunacode/utils/models_registry.py +593 -0
- tunacode/utils/ripgrep.py +332 -9
- tunacode/utils/text_utils.py +18 -1
- tunacode/utils/user_configuration.py +45 -0
- tunacode_cli-0.0.78.6.dist-info/METADATA +260 -0
- tunacode_cli-0.0.78.6.dist-info/RECORD +158 -0
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +1 -2
- tunacode/cli/commands/implementations/todo.py +0 -217
- tunacode/context.py +0 -71
- tunacode/core/setup/git_safety_setup.py +0 -182
- tunacode/prompts/system.md +0 -731
- tunacode/tools/read_file_async_poc.py +0 -196
- tunacode/tools/todo.py +0 -349
- tunacode_cli-0.0.55.dist-info/METADATA +0 -322
- tunacode_cli-0.0.55.dist-info/RECORD +0 -126
- tunacode_cli-0.0.55.dist-info/top_level.txt +0 -1
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""Security validation for slash command execution."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from .types import SecurityLevel, SecurityViolation, ValidationResult
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CommandValidator:
|
|
14
|
+
"""Comprehensive security validation system for slash commands."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, security_level: SecurityLevel = SecurityLevel.MODERATE):
|
|
17
|
+
self.security_level = security_level
|
|
18
|
+
self._init_security_rules()
|
|
19
|
+
|
|
20
|
+
def _init_security_rules(self) -> None:
|
|
21
|
+
"""Initialize security rules based on security level."""
|
|
22
|
+
|
|
23
|
+
# Always blocked commands (all security levels)
|
|
24
|
+
self.ALWAYS_BLOCKED = {
|
|
25
|
+
"rm",
|
|
26
|
+
"rmdir",
|
|
27
|
+
"del",
|
|
28
|
+
"format",
|
|
29
|
+
"fdisk",
|
|
30
|
+
"mkfs",
|
|
31
|
+
"sudo",
|
|
32
|
+
"su",
|
|
33
|
+
"passwd",
|
|
34
|
+
"useradd",
|
|
35
|
+
"userdel",
|
|
36
|
+
"chmod",
|
|
37
|
+
"chown",
|
|
38
|
+
"chgrp",
|
|
39
|
+
"setfacl",
|
|
40
|
+
"iptables",
|
|
41
|
+
"firewall-cmd",
|
|
42
|
+
"ufw",
|
|
43
|
+
"systemctl",
|
|
44
|
+
"service",
|
|
45
|
+
"initctl",
|
|
46
|
+
"wget",
|
|
47
|
+
"curl",
|
|
48
|
+
"nc",
|
|
49
|
+
"netcat",
|
|
50
|
+
"telnet",
|
|
51
|
+
"dd",
|
|
52
|
+
"mount",
|
|
53
|
+
"umount",
|
|
54
|
+
"fsck",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Dangerous patterns (all security levels)
|
|
58
|
+
self.DANGEROUS_PATTERNS = [
|
|
59
|
+
r"rm\s+.*-[rf]", # Recursive/force delete
|
|
60
|
+
r">\s*/dev/", # Device access
|
|
61
|
+
r"\|\s*sh\b", # Pipe to shell
|
|
62
|
+
r"\|\s*bash\b", # Pipe to bash
|
|
63
|
+
r"`[^`]*`", # Command substitution
|
|
64
|
+
r"\$\([^)]*\)", # Command substitution
|
|
65
|
+
r"(?:;|\|\||&&)", # Command chaining (;, ||, &&)
|
|
66
|
+
r"[|&]+\s*$", # Trailing pipes
|
|
67
|
+
r"eval\s+", # Dynamic evaluation
|
|
68
|
+
r"exec\s+", # Process replacement
|
|
69
|
+
r"source\s+", # Script sourcing
|
|
70
|
+
r"\.\s+/", # Script sourcing (dot)
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Security level specific rules
|
|
74
|
+
if self.security_level == SecurityLevel.STRICT:
|
|
75
|
+
self.ALLOWED_COMMANDS = {
|
|
76
|
+
"git": ["status", "branch", "log", "show"],
|
|
77
|
+
"ls": ["-la", "-l", "-a"],
|
|
78
|
+
"cat": [],
|
|
79
|
+
"echo": [],
|
|
80
|
+
"date": [],
|
|
81
|
+
"pwd": [],
|
|
82
|
+
}
|
|
83
|
+
elif self.security_level == SecurityLevel.MODERATE:
|
|
84
|
+
self.ALLOWED_COMMANDS = {
|
|
85
|
+
"git": ["status", "branch", "log", "diff", "show", "remote", "config"],
|
|
86
|
+
"npm": ["list", "info", "view", "outdated", "audit", "install"],
|
|
87
|
+
"python": ["-c", "-m", "--version", "-V"],
|
|
88
|
+
"node": ["--version", "-v", "-e"],
|
|
89
|
+
"ls": ["-la", "-l", "-a", "-h", "-R"],
|
|
90
|
+
"cat": [],
|
|
91
|
+
"head": [],
|
|
92
|
+
"tail": [],
|
|
93
|
+
"echo": [],
|
|
94
|
+
"date": [],
|
|
95
|
+
"pwd": [],
|
|
96
|
+
"whoami": [],
|
|
97
|
+
"grep": ["-r", "-n", "-i", "-v"],
|
|
98
|
+
"find": ["-name", "-type", "-size"],
|
|
99
|
+
"wc": ["-l", "-w", "-c"],
|
|
100
|
+
"sort": [],
|
|
101
|
+
"uniq": [],
|
|
102
|
+
"cut": [],
|
|
103
|
+
}
|
|
104
|
+
else: # PERMISSIVE
|
|
105
|
+
self.ALLOWED_COMMANDS = {
|
|
106
|
+
# All moderate commands plus more
|
|
107
|
+
"git": ["status", "branch", "log", "diff", "show", "remote", "config"],
|
|
108
|
+
"npm": ["list", "info", "view", "outdated", "audit"],
|
|
109
|
+
"python": ["-c", "-m", "--version", "-V"],
|
|
110
|
+
"node": ["--version", "-v", "-e"],
|
|
111
|
+
"ls": ["-la", "-l", "-a", "-h", "-R"],
|
|
112
|
+
"cat": [],
|
|
113
|
+
"head": [],
|
|
114
|
+
"tail": [],
|
|
115
|
+
"echo": [],
|
|
116
|
+
"date": [],
|
|
117
|
+
"pwd": [],
|
|
118
|
+
"whoami": [],
|
|
119
|
+
"grep": ["-r", "-n", "-i", "-v"],
|
|
120
|
+
"find": ["-name", "-type", "-size"],
|
|
121
|
+
"wc": ["-l", "-w", "-c"],
|
|
122
|
+
"sort": [],
|
|
123
|
+
"uniq": [],
|
|
124
|
+
"cut": [],
|
|
125
|
+
"make": ["--version", "-n"], # Dry run only
|
|
126
|
+
"docker": ["ps", "images", "info", "version"],
|
|
127
|
+
"kubectl": ["get", "describe", "logs", "version"],
|
|
128
|
+
"terraform": ["version", "validate", "plan"],
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def validate_shell_command(self, command: str) -> ValidationResult:
|
|
132
|
+
"""Comprehensive command validation with detailed results."""
|
|
133
|
+
command = command.strip()
|
|
134
|
+
violations = []
|
|
135
|
+
|
|
136
|
+
if not command:
|
|
137
|
+
violations.append(
|
|
138
|
+
SecurityViolation(
|
|
139
|
+
type="empty_command",
|
|
140
|
+
message="Empty command not allowed",
|
|
141
|
+
command=command,
|
|
142
|
+
severity="error",
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
return ValidationResult(False, violations)
|
|
146
|
+
|
|
147
|
+
# Parse command
|
|
148
|
+
parts = command.split()
|
|
149
|
+
base_command = parts[0].lower()
|
|
150
|
+
|
|
151
|
+
# Check for always blocked commands
|
|
152
|
+
if base_command in self.ALWAYS_BLOCKED:
|
|
153
|
+
violations.append(
|
|
154
|
+
SecurityViolation(
|
|
155
|
+
type="blocked_command",
|
|
156
|
+
message=f"Command '{base_command}' is always blocked",
|
|
157
|
+
command=command,
|
|
158
|
+
severity="error",
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
return ValidationResult(False, violations)
|
|
162
|
+
|
|
163
|
+
# Check dangerous patterns
|
|
164
|
+
for pattern in self.DANGEROUS_PATTERNS:
|
|
165
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
166
|
+
violations.append(
|
|
167
|
+
SecurityViolation(
|
|
168
|
+
type="dangerous_pattern",
|
|
169
|
+
message=f"Command matches dangerous pattern: {pattern}",
|
|
170
|
+
command=command,
|
|
171
|
+
severity="error",
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
return ValidationResult(False, violations)
|
|
175
|
+
|
|
176
|
+
# Check against whitelist
|
|
177
|
+
if base_command not in self.ALLOWED_COMMANDS:
|
|
178
|
+
violations.append(
|
|
179
|
+
SecurityViolation(
|
|
180
|
+
type="unknown_command",
|
|
181
|
+
message=f"Command '{base_command}' not in whitelist",
|
|
182
|
+
command=command,
|
|
183
|
+
severity="warning"
|
|
184
|
+
if self.security_level == SecurityLevel.PERMISSIVE
|
|
185
|
+
else "error",
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if self.security_level != SecurityLevel.PERMISSIVE:
|
|
190
|
+
return ValidationResult(False, violations)
|
|
191
|
+
else:
|
|
192
|
+
# Validate subcommands if specified
|
|
193
|
+
allowed_subcommands = self.ALLOWED_COMMANDS[base_command]
|
|
194
|
+
if allowed_subcommands and len(parts) > 1:
|
|
195
|
+
subcommand = parts[1]
|
|
196
|
+
if subcommand not in allowed_subcommands:
|
|
197
|
+
# Allow python scripts
|
|
198
|
+
if base_command == "python" and subcommand.endswith(".py"):
|
|
199
|
+
pass
|
|
200
|
+
elif base_command == "grep":
|
|
201
|
+
pass
|
|
202
|
+
else:
|
|
203
|
+
violations.append(
|
|
204
|
+
SecurityViolation(
|
|
205
|
+
type="invalid_subcommand",
|
|
206
|
+
message=f"Subcommand '{subcommand}' not allowed "
|
|
207
|
+
f"for '{base_command}'",
|
|
208
|
+
command=command,
|
|
209
|
+
severity="error",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
return ValidationResult(False, violations)
|
|
213
|
+
|
|
214
|
+
# Additional validation checks
|
|
215
|
+
violations.extend(self._check_file_access_patterns(command))
|
|
216
|
+
violations.extend(self._check_network_patterns(command))
|
|
217
|
+
violations.extend(self._check_privilege_escalation(command))
|
|
218
|
+
|
|
219
|
+
# Determine if command is allowed
|
|
220
|
+
error_violations = [v for v in violations if v.severity == "error"]
|
|
221
|
+
allowed = len(error_violations) == 0
|
|
222
|
+
|
|
223
|
+
return ValidationResult(allowed, violations)
|
|
224
|
+
|
|
225
|
+
def _check_file_access_patterns(self, command: str) -> List[SecurityViolation]:
|
|
226
|
+
"""Check for suspicious file access patterns."""
|
|
227
|
+
violations = []
|
|
228
|
+
|
|
229
|
+
# Sensitive file patterns
|
|
230
|
+
sensitive_patterns = [
|
|
231
|
+
r"/etc/passwd",
|
|
232
|
+
r"/etc/shadow",
|
|
233
|
+
r"/etc/hosts",
|
|
234
|
+
r"/home/[^/]+/\.ssh",
|
|
235
|
+
r"~/.ssh",
|
|
236
|
+
r"/var/log/",
|
|
237
|
+
r"/proc/",
|
|
238
|
+
r"/sys/",
|
|
239
|
+
r"\.env",
|
|
240
|
+
r"\.secret",
|
|
241
|
+
r"\.key",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
for pattern in sensitive_patterns:
|
|
245
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
246
|
+
violations.append(
|
|
247
|
+
SecurityViolation(
|
|
248
|
+
type="sensitive_file_access",
|
|
249
|
+
message=f"Potential access to sensitive file: {pattern}",
|
|
250
|
+
command=command,
|
|
251
|
+
severity="warning",
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return violations
|
|
256
|
+
|
|
257
|
+
def _check_network_patterns(self, command: str) -> List[SecurityViolation]:
|
|
258
|
+
"""Check for network access patterns."""
|
|
259
|
+
violations = []
|
|
260
|
+
|
|
261
|
+
network_patterns = [
|
|
262
|
+
r"curl\s+",
|
|
263
|
+
r"wget\s+",
|
|
264
|
+
r"nc\s+",
|
|
265
|
+
r"netcat\s+",
|
|
266
|
+
r"ssh\s+",
|
|
267
|
+
r"scp\s+",
|
|
268
|
+
r"rsync\s+.*:",
|
|
269
|
+
r"ftp\s+",
|
|
270
|
+
r"telnet\s+",
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
for pattern in network_patterns:
|
|
274
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
275
|
+
violations.append(
|
|
276
|
+
SecurityViolation(
|
|
277
|
+
type="network_access",
|
|
278
|
+
message=f"Network access detected: {pattern}",
|
|
279
|
+
command=command,
|
|
280
|
+
severity="error",
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return violations
|
|
285
|
+
|
|
286
|
+
def _check_privilege_escalation(self, command: str) -> List[SecurityViolation]:
|
|
287
|
+
"""Check for privilege escalation attempts."""
|
|
288
|
+
violations = []
|
|
289
|
+
|
|
290
|
+
privilege_patterns = [
|
|
291
|
+
r"sudo\s+",
|
|
292
|
+
r"su\s+",
|
|
293
|
+
r"doas\s+",
|
|
294
|
+
r"pkexec\s+",
|
|
295
|
+
r"runuser\s+",
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
for pattern in privilege_patterns:
|
|
299
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
300
|
+
violations.append(
|
|
301
|
+
SecurityViolation(
|
|
302
|
+
type="privilege_escalation",
|
|
303
|
+
message=f"Privilege escalation attempt: {pattern}",
|
|
304
|
+
command=command,
|
|
305
|
+
severity="error",
|
|
306
|
+
)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return violations
|
|
310
|
+
|
|
311
|
+
def validate_file_path(self, file_path: str, base_path: Path) -> ValidationResult:
|
|
312
|
+
"""Enhanced file path validation."""
|
|
313
|
+
violations = []
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# Basic path traversal check
|
|
317
|
+
resolved = (base_path / file_path).resolve()
|
|
318
|
+
base_resolved = base_path.resolve()
|
|
319
|
+
|
|
320
|
+
if not str(resolved).startswith(str(base_resolved)):
|
|
321
|
+
violations.append(
|
|
322
|
+
SecurityViolation(
|
|
323
|
+
type="path_traversal",
|
|
324
|
+
message=f"Path traversal detected: {file_path}",
|
|
325
|
+
command=file_path,
|
|
326
|
+
severity="error",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
return ValidationResult(False, violations)
|
|
330
|
+
|
|
331
|
+
# Check for sensitive file access
|
|
332
|
+
sensitive_dirs = [".ssh", ".git", ".env", "node_modules"]
|
|
333
|
+
path_parts = Path(file_path).parts
|
|
334
|
+
|
|
335
|
+
for sensitive in sensitive_dirs:
|
|
336
|
+
if sensitive in path_parts:
|
|
337
|
+
violations.append(
|
|
338
|
+
SecurityViolation(
|
|
339
|
+
type="sensitive_directory",
|
|
340
|
+
message=f"Access to sensitive directory: {sensitive}",
|
|
341
|
+
command=file_path,
|
|
342
|
+
severity="warning",
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return ValidationResult(True, violations)
|
|
347
|
+
|
|
348
|
+
except (OSError, ValueError) as e:
|
|
349
|
+
violations.append(
|
|
350
|
+
SecurityViolation(
|
|
351
|
+
type="invalid_path",
|
|
352
|
+
message=f"Invalid file path: {str(e)}",
|
|
353
|
+
command=file_path,
|
|
354
|
+
severity="error",
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
return ValidationResult(False, violations)
|
|
358
|
+
|
|
359
|
+
def validate_glob_pattern(self, pattern: str) -> ValidationResult:
|
|
360
|
+
"""Enhanced glob pattern validation."""
|
|
361
|
+
violations = []
|
|
362
|
+
|
|
363
|
+
# Dangerous glob patterns
|
|
364
|
+
dangerous_globs = [
|
|
365
|
+
r"\.\./.*", # Parent directory traversal
|
|
366
|
+
r"/etc/.*",
|
|
367
|
+
r"/home/.*",
|
|
368
|
+
r"/root/.*", # System directories
|
|
369
|
+
r"/var/.*",
|
|
370
|
+
r"/usr/bin/.*",
|
|
371
|
+
r"/bin/.*",
|
|
372
|
+
r".*\.key$",
|
|
373
|
+
r".*\.secret$",
|
|
374
|
+
r".*\.pem$", # Sensitive files
|
|
375
|
+
]
|
|
376
|
+
|
|
377
|
+
for dangerous in dangerous_globs:
|
|
378
|
+
if re.match(dangerous, pattern, re.IGNORECASE):
|
|
379
|
+
violations.append(
|
|
380
|
+
SecurityViolation(
|
|
381
|
+
type="dangerous_glob",
|
|
382
|
+
message=f"Dangerous glob pattern: {dangerous}",
|
|
383
|
+
command=pattern,
|
|
384
|
+
severity="error",
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
return ValidationResult(False, violations)
|
|
388
|
+
|
|
389
|
+
# Check for overly broad patterns
|
|
390
|
+
if pattern in ["**/*", "*", "**"]:
|
|
391
|
+
violations.append(
|
|
392
|
+
SecurityViolation(
|
|
393
|
+
type="broad_glob",
|
|
394
|
+
message="Overly broad glob pattern",
|
|
395
|
+
command=pattern,
|
|
396
|
+
severity="warning",
|
|
397
|
+
)
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
return ValidationResult(True, violations)
|
tunacode/cli/main.py
CHANGED
|
@@ -16,6 +16,7 @@ from tunacode.core.tool_handler import ToolHandler
|
|
|
16
16
|
from tunacode.exceptions import UserAbortError
|
|
17
17
|
from tunacode.setup import setup
|
|
18
18
|
from tunacode.ui import console as ui
|
|
19
|
+
from tunacode.ui.config_dashboard import show_config_dashboard
|
|
19
20
|
from tunacode.utils.system import check_for_updates
|
|
20
21
|
|
|
21
22
|
app_settings = ApplicationSettings()
|
|
@@ -27,6 +28,12 @@ state_manager = StateManager()
|
|
|
27
28
|
def main(
|
|
28
29
|
version: bool = typer.Option(False, "--version", "-v", help="Show version and exit."),
|
|
29
30
|
run_setup: bool = typer.Option(False, "--setup", help="Run setup process."),
|
|
31
|
+
wizard: bool = typer.Option(
|
|
32
|
+
False, "--wizard", help="Run interactive setup wizard for guided configuration."
|
|
33
|
+
),
|
|
34
|
+
show_config: bool = typer.Option(
|
|
35
|
+
False, "--show-config", help="Show configuration dashboard and exit."
|
|
36
|
+
),
|
|
30
37
|
baseurl: str = typer.Option(
|
|
31
38
|
None, "--baseurl", help="API base URL (e.g., https://openrouter.ai/api/v1)"
|
|
32
39
|
),
|
|
@@ -46,6 +53,11 @@ def main(
|
|
|
46
53
|
await ui.version()
|
|
47
54
|
return
|
|
48
55
|
|
|
56
|
+
if show_config:
|
|
57
|
+
await ui.banner()
|
|
58
|
+
show_config_dashboard()
|
|
59
|
+
return
|
|
60
|
+
|
|
49
61
|
await ui.banner()
|
|
50
62
|
|
|
51
63
|
# Start update check in background
|
|
@@ -60,7 +72,7 @@ def main(
|
|
|
60
72
|
cli_config = {k: v for k, v in cli_config.items() if v is not None}
|
|
61
73
|
|
|
62
74
|
try:
|
|
63
|
-
await setup(run_setup, state_manager, cli_config)
|
|
75
|
+
await setup(run_setup or wizard, state_manager, cli_config, wizard_mode=wizard)
|
|
64
76
|
|
|
65
77
|
# Initialize ToolHandler after setup
|
|
66
78
|
tool_handler = ToolHandler(state_manager)
|
|
@@ -74,7 +86,8 @@ def main(
|
|
|
74
86
|
from tunacode.exceptions import ConfigurationError
|
|
75
87
|
|
|
76
88
|
if isinstance(e, ConfigurationError):
|
|
77
|
-
#
|
|
89
|
+
# Display the configuration error message
|
|
90
|
+
await ui.error(str(e))
|
|
78
91
|
update_task.cancel() # Cancel the update check
|
|
79
92
|
return
|
|
80
93
|
import traceback
|
|
@@ -84,6 +97,14 @@ def main(
|
|
|
84
97
|
has_update, latest_version = await update_task
|
|
85
98
|
if has_update:
|
|
86
99
|
await ui.update_available(latest_version)
|
|
100
|
+
else:
|
|
101
|
+
# Normal exit - cleanup MCP servers
|
|
102
|
+
try:
|
|
103
|
+
from tunacode.core.agents import cleanup_mcp_servers
|
|
104
|
+
|
|
105
|
+
await cleanup_mcp_servers()
|
|
106
|
+
except Exception:
|
|
107
|
+
pass # Best effort cleanup
|
|
87
108
|
|
|
88
109
|
asyncio.run(async_main())
|
|
89
110
|
|