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,300 @@
1
+ """
2
+ Custom Slash Commands for NC1709
3
+
4
+ Allows users to define their own slash commands in:
5
+ - ~/.nc1709/commands/ (personal commands)
6
+ - .nc1709/commands/ (project commands)
7
+
8
+ Similar to Claude Code's custom command system.
9
+ """
10
+
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Optional, List, Dict, Any
14
+ from dataclasses import dataclass
15
+
16
+
17
+ @dataclass
18
+ class CustomCommand:
19
+ """A user-defined custom command"""
20
+ name: str # Command name (without /)
21
+ content: str # Command content/prompt
22
+ description: str # Optional description (first line of file)
23
+ file_path: Path # Path to the command file
24
+ scope: str # "personal" or "project"
25
+
26
+
27
+ class CustomCommandManager:
28
+ """
29
+ Manages custom slash commands defined in markdown files.
30
+
31
+ Commands are loaded from:
32
+ - ~/.nc1709/commands/*.md (personal commands, available everywhere)
33
+ - .nc1709/commands/*.md (project commands, available in current project)
34
+
35
+ The filename becomes the command name (e.g., fix-bug.md -> /project:fix-bug)
36
+ The first line starting with # is used as the description.
37
+ The rest is the command content/prompt.
38
+ """
39
+
40
+ def __init__(self, project_path: Optional[str] = None):
41
+ """
42
+ Initialize the custom command manager.
43
+
44
+ Args:
45
+ project_path: Path to current project (defaults to cwd)
46
+ """
47
+ self.project_path = Path(project_path) if project_path else Path.cwd()
48
+ self.personal_dir = Path.home() / ".nc1709" / "commands"
49
+ self.project_dir = self.project_path / ".nc1709" / "commands"
50
+
51
+ # Ensure personal commands directory exists
52
+ self.personal_dir.mkdir(parents=True, exist_ok=True)
53
+
54
+ # Cache loaded commands
55
+ self._commands: Dict[str, CustomCommand] = {}
56
+ self._loaded = False
57
+
58
+ def _load_commands(self) -> None:
59
+ """Load all custom commands from disk"""
60
+ self._commands = {}
61
+
62
+ # Load personal commands
63
+ if self.personal_dir.exists():
64
+ for file_path in self.personal_dir.glob("*.md"):
65
+ cmd = self._load_command_file(file_path, scope="personal")
66
+ if cmd:
67
+ self._commands[cmd.name] = cmd
68
+
69
+ # Load project commands (override personal if same name)
70
+ if self.project_dir.exists():
71
+ for file_path in self.project_dir.glob("*.md"):
72
+ cmd = self._load_command_file(file_path, scope="project")
73
+ if cmd:
74
+ # Use project: prefix to avoid collisions
75
+ self._commands[f"project:{cmd.name}"] = cmd
76
+
77
+ self._loaded = True
78
+
79
+ def _load_command_file(self, file_path: Path, scope: str) -> Optional[CustomCommand]:
80
+ """Load a single command file"""
81
+ try:
82
+ content = file_path.read_text(encoding='utf-8')
83
+ lines = content.strip().split('\n')
84
+
85
+ # Extract description from first heading line
86
+ description = ""
87
+ content_start = 0
88
+
89
+ for i, line in enumerate(lines):
90
+ if line.startswith('#'):
91
+ # Extract text after # (the description)
92
+ description = line.lstrip('#').strip()
93
+ content_start = i + 1
94
+ break
95
+ elif line.strip():
96
+ # First non-empty, non-heading line - no description
97
+ break
98
+
99
+ # Command name is filename without extension
100
+ name = file_path.stem
101
+
102
+ # Content is everything after the description
103
+ command_content = '\n'.join(lines[content_start:]).strip()
104
+
105
+ return CustomCommand(
106
+ name=name,
107
+ content=command_content,
108
+ description=description or f"Custom command: {name}",
109
+ file_path=file_path,
110
+ scope=scope
111
+ )
112
+
113
+ except Exception:
114
+ return None
115
+
116
+ def reload(self) -> None:
117
+ """Force reload all commands"""
118
+ self._loaded = False
119
+ self._load_commands()
120
+
121
+ def get_command(self, name: str) -> Optional[CustomCommand]:
122
+ """
123
+ Get a custom command by name.
124
+
125
+ Args:
126
+ name: Command name (with or without /)
127
+
128
+ Returns:
129
+ CustomCommand if found, None otherwise
130
+ """
131
+ if not self._loaded:
132
+ self._load_commands()
133
+
134
+ # Remove leading / if present
135
+ if name.startswith('/'):
136
+ name = name[1:]
137
+
138
+ return self._commands.get(name)
139
+
140
+ def list_commands(self) -> List[CustomCommand]:
141
+ """Get all available custom commands"""
142
+ if not self._loaded:
143
+ self._load_commands()
144
+
145
+ return list(self._commands.values())
146
+
147
+ def get_commands_for_autocomplete(self) -> List[Dict[str, str]]:
148
+ """Get commands formatted for autocomplete"""
149
+ if not self._loaded:
150
+ self._load_commands()
151
+
152
+ return [
153
+ {
154
+ "name": cmd.name,
155
+ "description": cmd.description,
156
+ "scope": cmd.scope
157
+ }
158
+ for cmd in self._commands.values()
159
+ ]
160
+
161
+ def create_command(
162
+ self,
163
+ name: str,
164
+ content: str,
165
+ description: str = "",
166
+ scope: str = "personal"
167
+ ) -> CustomCommand:
168
+ """
169
+ Create a new custom command.
170
+
171
+ Args:
172
+ name: Command name (without /)
173
+ content: Command content/prompt
174
+ description: Optional description
175
+ scope: "personal" or "project"
176
+
177
+ Returns:
178
+ The created CustomCommand
179
+ """
180
+ # Determine target directory
181
+ if scope == "project":
182
+ target_dir = self.project_dir
183
+ else:
184
+ target_dir = self.personal_dir
185
+
186
+ target_dir.mkdir(parents=True, exist_ok=True)
187
+
188
+ # Create the file content
189
+ file_content = ""
190
+ if description:
191
+ file_content = f"# {description}\n\n"
192
+ file_content += content
193
+
194
+ # Write the file
195
+ file_path = target_dir / f"{name}.md"
196
+ file_path.write_text(file_content, encoding='utf-8')
197
+
198
+ # Create and cache the command
199
+ cmd = CustomCommand(
200
+ name=name if scope == "personal" else f"project:{name}",
201
+ content=content,
202
+ description=description or f"Custom command: {name}",
203
+ file_path=file_path,
204
+ scope=scope
205
+ )
206
+
207
+ self._commands[cmd.name] = cmd
208
+ return cmd
209
+
210
+ def delete_command(self, name: str) -> bool:
211
+ """
212
+ Delete a custom command.
213
+
214
+ Args:
215
+ name: Command name
216
+
217
+ Returns:
218
+ True if deleted, False if not found
219
+ """
220
+ cmd = self.get_command(name)
221
+ if cmd and cmd.file_path.exists():
222
+ cmd.file_path.unlink()
223
+ del self._commands[cmd.name]
224
+ return True
225
+ return False
226
+
227
+ def get_example_commands(self) -> str:
228
+ """Get example custom commands for users"""
229
+ return """# Example Custom Commands
230
+
231
+ Create markdown files in ~/.nc1709/commands/ or .nc1709/commands/
232
+
233
+ ## Example: fix-bug.md
234
+
235
+ ```markdown
236
+ # Fix a bug in the codebase
237
+
238
+ Look at the error message or description provided.
239
+ Find the relevant code files.
240
+ Analyze the root cause.
241
+ Propose and implement a fix.
242
+ Test that the fix works.
243
+ ```
244
+
245
+ ## Example: add-tests.md
246
+
247
+ ```markdown
248
+ # Add unit tests for a file
249
+
250
+ Read the specified file.
251
+ Identify all public functions and classes.
252
+ Generate comprehensive unit tests.
253
+ Include edge cases and error handling tests.
254
+ Write tests to a corresponding test file.
255
+ ```
256
+
257
+ ## Example: review-pr.md
258
+
259
+ ```markdown
260
+ # Review a pull request
261
+
262
+ Check the diff for:
263
+ - Code style and best practices
264
+ - Potential bugs or edge cases
265
+ - Security vulnerabilities
266
+ - Performance issues
267
+ - Missing tests
268
+
269
+ Provide actionable feedback.
270
+ ```
271
+ """
272
+
273
+
274
+ # Global custom command manager
275
+ _custom_command_manager: Optional[CustomCommandManager] = None
276
+
277
+
278
+ def get_custom_command_manager() -> CustomCommandManager:
279
+ """Get or create the global custom command manager"""
280
+ global _custom_command_manager
281
+ if _custom_command_manager is None:
282
+ _custom_command_manager = CustomCommandManager()
283
+ return _custom_command_manager
284
+
285
+
286
+ def execute_custom_command(name: str) -> Optional[str]:
287
+ """
288
+ Get the prompt content for a custom command.
289
+
290
+ Args:
291
+ name: Command name
292
+
293
+ Returns:
294
+ Command prompt content if found, None otherwise
295
+ """
296
+ manager = get_custom_command_manager()
297
+ cmd = manager.get_command(name)
298
+ if cmd:
299
+ return cmd.content
300
+ return None
nc1709/executor.py ADDED
@@ -0,0 +1,333 @@
1
+ """
2
+ Execution Sandbox for Safe Command Execution
3
+ Validates and executes shell commands with safety checks
4
+ """
5
+ import subprocess
6
+ import shlex
7
+ import os
8
+ from typing import Tuple, List, Optional
9
+ from pathlib import Path
10
+
11
+ from .config import get_config
12
+
13
+
14
+ class CommandExecutor:
15
+ """Executes shell commands with safety validation"""
16
+
17
+ def __init__(self):
18
+ """Initialize the command executor"""
19
+ self.config = get_config()
20
+ self.execution_log: List[dict] = []
21
+
22
+ def execute(
23
+ self,
24
+ command: str,
25
+ cwd: Optional[str] = None,
26
+ timeout: Optional[int] = None,
27
+ confirm: bool = True
28
+ ) -> Tuple[int, str, str]:
29
+ """Execute a shell command with safety checks
30
+
31
+ Args:
32
+ command: Command to execute
33
+ cwd: Working directory (default: current directory)
34
+ timeout: Timeout in seconds (default: from config)
35
+ confirm: Whether to ask for confirmation
36
+
37
+ Returns:
38
+ Tuple of (return_code, stdout, stderr)
39
+
40
+ Raises:
41
+ ValueError: If command is not allowed
42
+ subprocess.TimeoutExpired: If command times out
43
+ """
44
+ # Validate command
45
+ if not self._is_command_allowed(command):
46
+ raise ValueError(f"Command not allowed: {command}")
47
+
48
+ # Check for destructive operations
49
+ if self._is_destructive(command):
50
+ if not self._confirm_destructive(command):
51
+ return (-1, "", "Command cancelled by user")
52
+
53
+ # Confirm execution if required
54
+ if confirm and self.config.get("safety.confirm_commands", True):
55
+ print(f"\n💻 About to execute: {command}")
56
+ if cwd:
57
+ print(f" Working directory: {cwd}")
58
+ response = input("Execute this command? [y/N]: ").strip().lower()
59
+ if response != 'y':
60
+ print("Execution cancelled.")
61
+ return (-1, "", "Command cancelled by user")
62
+
63
+ # Set timeout
64
+ if timeout is None:
65
+ timeout = self.config.get("execution.command_timeout", 60)
66
+
67
+ # Set working directory
68
+ if cwd is None:
69
+ cwd = os.getcwd()
70
+
71
+ # Execute command
72
+ try:
73
+ print(f"\n🔄 Executing: {command}")
74
+
75
+ result = subprocess.run(
76
+ command,
77
+ shell=True,
78
+ cwd=cwd,
79
+ timeout=timeout,
80
+ capture_output=True,
81
+ text=True
82
+ )
83
+
84
+ # Log execution
85
+ self._log_execution(command, cwd, result.returncode, result.stdout, result.stderr)
86
+
87
+ # Print output
88
+ if result.stdout:
89
+ print("\n📤 Output:")
90
+ print(result.stdout)
91
+
92
+ if result.stderr:
93
+ print("\n⚠️ Errors/Warnings:")
94
+ print(result.stderr)
95
+
96
+ if result.returncode == 0:
97
+ print("✅ Command completed successfully")
98
+ else:
99
+ print(f"❌ Command failed with exit code {result.returncode}")
100
+
101
+ return (result.returncode, result.stdout, result.stderr)
102
+
103
+ except subprocess.TimeoutExpired:
104
+ error_msg = f"Command timed out after {timeout} seconds"
105
+ print(f"\n⏱️ {error_msg}")
106
+ self._log_execution(command, cwd, -1, "", error_msg)
107
+ return (-1, "", error_msg)
108
+
109
+ except Exception as e:
110
+ error_msg = f"Execution error: {str(e)}"
111
+ print(f"\n❌ {error_msg}")
112
+ self._log_execution(command, cwd, -1, "", error_msg)
113
+ return (-1, "", error_msg)
114
+
115
+ def execute_multiple(
116
+ self,
117
+ commands: List[str],
118
+ cwd: Optional[str] = None,
119
+ stop_on_error: bool = True
120
+ ) -> List[Tuple[int, str, str]]:
121
+ """Execute multiple commands in sequence
122
+
123
+ Args:
124
+ commands: List of commands to execute
125
+ cwd: Working directory
126
+ stop_on_error: Whether to stop if a command fails
127
+
128
+ Returns:
129
+ List of (return_code, stdout, stderr) tuples
130
+ """
131
+ results = []
132
+
133
+ for i, command in enumerate(commands, 1):
134
+ print(f"\n{'='*60}")
135
+ print(f"Command {i}/{len(commands)}")
136
+ print(f"{'='*60}")
137
+
138
+ result = self.execute(command, cwd=cwd, confirm=False)
139
+ results.append(result)
140
+
141
+ # Stop on error if requested
142
+ if stop_on_error and result[0] != 0:
143
+ print(f"\n⚠️ Stopping execution due to error in command {i}")
144
+ break
145
+
146
+ return results
147
+
148
+ def _is_command_allowed(self, command: str) -> bool:
149
+ """Check if a command is allowed
150
+
151
+ Args:
152
+ command: Command to check
153
+
154
+ Returns:
155
+ True if command is allowed
156
+ """
157
+ # Parse command to get the base command
158
+ try:
159
+ parts = shlex.split(command)
160
+ if not parts:
161
+ return False
162
+ base_command = parts[0]
163
+ except ValueError:
164
+ # If parsing fails, be conservative and reject
165
+ print("⛔ Command parsing failed - rejecting for safety")
166
+ return False
167
+
168
+ # Normalize the command for security checks
169
+ normalized_cmd = command.lower().replace("\\", "")
170
+
171
+ # Check against blocked commands with improved pattern matching
172
+ blocked = self.config.get("execution.blocked_commands", [])
173
+ for blocked_cmd in blocked:
174
+ blocked_lower = blocked_cmd.lower()
175
+ # Check for exact match or as part of a command chain
176
+ if (blocked_lower in normalized_cmd or
177
+ normalized_cmd.startswith(blocked_lower) or
178
+ f"; {blocked_lower}" in normalized_cmd or
179
+ f"&& {blocked_lower}" in normalized_cmd or
180
+ f"| {blocked_lower}" in normalized_cmd or
181
+ f"|| {blocked_lower}" in normalized_cmd):
182
+ print(f"⛔ Blocked command detected: {blocked_cmd}")
183
+ return False
184
+
185
+ # Check for shell injection patterns
186
+ dangerous_patterns = [
187
+ "$(", "`", # Command substitution
188
+ "${", # Variable expansion that could be exploited
189
+ "; rm", "&& rm", # Chained destructive commands
190
+ "| sh", "| bash", # Piping to shell
191
+ "> /dev/sd", # Writing to block devices
192
+ "eval ", "exec ", # Dangerous execution primitives
193
+ ]
194
+ for pattern in dangerous_patterns:
195
+ if pattern in command:
196
+ print(f"⛔ Potentially dangerous pattern detected: {pattern}")
197
+ return False
198
+
199
+ # Check against allowed commands (if whitelist is enabled)
200
+ allowed = self.config.get("execution.allowed_commands", [])
201
+ if allowed:
202
+ # Extract just the command name (without path)
203
+ cmd_name = os.path.basename(base_command)
204
+ if cmd_name not in allowed and base_command not in allowed:
205
+ print(f"⛔ Command not in whitelist: {base_command}")
206
+ print(f" Allowed commands: {', '.join(allowed)}")
207
+ return False
208
+
209
+ return True
210
+
211
+ def _is_destructive(self, command: str) -> bool:
212
+ """Check if a command is potentially destructive
213
+
214
+ Args:
215
+ command: Command to check
216
+
217
+ Returns:
218
+ True if command is destructive
219
+ """
220
+ destructive_patterns = [
221
+ "rm ", "rmdir", "del ", "format", "mkfs",
222
+ "dd ", ">", "truncate", "shred"
223
+ ]
224
+
225
+ return any(pattern in command for pattern in destructive_patterns)
226
+
227
+ def _confirm_destructive(self, command: str) -> bool:
228
+ """Ask for confirmation for destructive commands
229
+
230
+ Args:
231
+ command: Command to confirm
232
+
233
+ Returns:
234
+ True if user confirms
235
+ """
236
+ if not self.config.get("safety.confirm_destructive", True):
237
+ return True
238
+
239
+ print(f"\n⚠️ WARNING: Potentially destructive command detected!")
240
+ print(f" Command: {command}")
241
+ print(f" This command may delete or modify data.")
242
+ response = input("Are you absolutely sure you want to execute this? [yes/NO]: ").strip().lower()
243
+
244
+ return response == "yes"
245
+
246
+ def _log_execution(
247
+ self,
248
+ command: str,
249
+ cwd: str,
250
+ return_code: int,
251
+ stdout: str,
252
+ stderr: str
253
+ ) -> None:
254
+ """Log command execution
255
+
256
+ Args:
257
+ command: Executed command
258
+ cwd: Working directory
259
+ return_code: Exit code
260
+ stdout: Standard output
261
+ stderr: Standard error
262
+ """
263
+ from datetime import datetime
264
+
265
+ log_entry = {
266
+ "timestamp": datetime.now().isoformat(),
267
+ "command": command,
268
+ "cwd": cwd,
269
+ "return_code": return_code,
270
+ "stdout_length": len(stdout),
271
+ "stderr_length": len(stderr),
272
+ "success": return_code == 0
273
+ }
274
+
275
+ self.execution_log.append(log_entry)
276
+
277
+ # Keep only last N entries
278
+ max_log_size = 1000
279
+ if len(self.execution_log) > max_log_size:
280
+ self.execution_log = self.execution_log[-max_log_size:]
281
+
282
+ def get_execution_history(self, limit: int = 10) -> List[dict]:
283
+ """Get recent command execution history
284
+
285
+ Args:
286
+ limit: Number of recent entries to return
287
+
288
+ Returns:
289
+ List of execution log entries
290
+ """
291
+ return self.execution_log[-limit:]
292
+
293
+ def validate_command(self, command: str) -> Tuple[bool, str]:
294
+ """Validate a command without executing it
295
+
296
+ Args:
297
+ command: Command to validate
298
+
299
+ Returns:
300
+ Tuple of (is_valid, message)
301
+ """
302
+ if not command.strip():
303
+ return (False, "Empty command")
304
+
305
+ if not self._is_command_allowed(command):
306
+ return (False, "Command not allowed by security policy")
307
+
308
+ if self._is_destructive(command):
309
+ return (True, "Command is valid but potentially destructive")
310
+
311
+ return (True, "Command is valid")
312
+
313
+ def suggest_safe_alternative(self, command: str) -> Optional[str]:
314
+ """Suggest a safer alternative for a command
315
+
316
+ Args:
317
+ command: Original command
318
+
319
+ Returns:
320
+ Suggested alternative command or None
321
+ """
322
+ # Common dangerous patterns and their safer alternatives
323
+ alternatives = {
324
+ "rm -rf /": "# This command would delete your entire system! Never run this.",
325
+ "rm -rf": "rm -ri", # Interactive mode
326
+ "dd if=/dev/zero": "# This would wipe data. Use with extreme caution.",
327
+ }
328
+
329
+ for pattern, alternative in alternatives.items():
330
+ if pattern in command:
331
+ return alternative
332
+
333
+ return None