aiptx 2.0.7__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 (187) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +46 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/exploit_agent.py +688 -0
  8. aipt_v2/agents/ptt.py +406 -0
  9. aipt_v2/agents/state.py +168 -0
  10. aipt_v2/app.py +957 -0
  11. aipt_v2/browser/__init__.py +31 -0
  12. aipt_v2/browser/automation.py +458 -0
  13. aipt_v2/browser/crawler.py +453 -0
  14. aipt_v2/cli.py +2933 -0
  15. aipt_v2/compliance/__init__.py +71 -0
  16. aipt_v2/compliance/compliance_report.py +449 -0
  17. aipt_v2/compliance/framework_mapper.py +424 -0
  18. aipt_v2/compliance/nist_mapping.py +345 -0
  19. aipt_v2/compliance/owasp_mapping.py +330 -0
  20. aipt_v2/compliance/pci_mapping.py +297 -0
  21. aipt_v2/config.py +341 -0
  22. aipt_v2/core/__init__.py +43 -0
  23. aipt_v2/core/agent.py +630 -0
  24. aipt_v2/core/llm.py +395 -0
  25. aipt_v2/core/memory.py +305 -0
  26. aipt_v2/core/ptt.py +329 -0
  27. aipt_v2/database/__init__.py +14 -0
  28. aipt_v2/database/models.py +232 -0
  29. aipt_v2/database/repository.py +384 -0
  30. aipt_v2/docker/__init__.py +23 -0
  31. aipt_v2/docker/builder.py +260 -0
  32. aipt_v2/docker/manager.py +222 -0
  33. aipt_v2/docker/sandbox.py +371 -0
  34. aipt_v2/evasion/__init__.py +58 -0
  35. aipt_v2/evasion/request_obfuscator.py +272 -0
  36. aipt_v2/evasion/tls_fingerprint.py +285 -0
  37. aipt_v2/evasion/ua_rotator.py +301 -0
  38. aipt_v2/evasion/waf_bypass.py +439 -0
  39. aipt_v2/execution/__init__.py +23 -0
  40. aipt_v2/execution/executor.py +302 -0
  41. aipt_v2/execution/parser.py +544 -0
  42. aipt_v2/execution/terminal.py +337 -0
  43. aipt_v2/health.py +437 -0
  44. aipt_v2/intelligence/__init__.py +194 -0
  45. aipt_v2/intelligence/adaptation.py +474 -0
  46. aipt_v2/intelligence/auth.py +520 -0
  47. aipt_v2/intelligence/chaining.py +775 -0
  48. aipt_v2/intelligence/correlation.py +536 -0
  49. aipt_v2/intelligence/cve_aipt.py +334 -0
  50. aipt_v2/intelligence/cve_info.py +1111 -0
  51. aipt_v2/intelligence/knowledge_graph.py +590 -0
  52. aipt_v2/intelligence/learning.py +626 -0
  53. aipt_v2/intelligence/llm_analyzer.py +502 -0
  54. aipt_v2/intelligence/llm_tool_selector.py +518 -0
  55. aipt_v2/intelligence/payload_generator.py +562 -0
  56. aipt_v2/intelligence/rag.py +239 -0
  57. aipt_v2/intelligence/scope.py +442 -0
  58. aipt_v2/intelligence/searchers/__init__.py +5 -0
  59. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  60. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  61. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  62. aipt_v2/intelligence/tools.json +443 -0
  63. aipt_v2/intelligence/triage.py +670 -0
  64. aipt_v2/interactive_shell.py +559 -0
  65. aipt_v2/interface/__init__.py +5 -0
  66. aipt_v2/interface/cli.py +230 -0
  67. aipt_v2/interface/main.py +501 -0
  68. aipt_v2/interface/tui.py +1276 -0
  69. aipt_v2/interface/utils.py +583 -0
  70. aipt_v2/llm/__init__.py +39 -0
  71. aipt_v2/llm/config.py +26 -0
  72. aipt_v2/llm/llm.py +514 -0
  73. aipt_v2/llm/memory.py +214 -0
  74. aipt_v2/llm/request_queue.py +89 -0
  75. aipt_v2/llm/utils.py +89 -0
  76. aipt_v2/local_tool_installer.py +1467 -0
  77. aipt_v2/models/__init__.py +15 -0
  78. aipt_v2/models/findings.py +295 -0
  79. aipt_v2/models/phase_result.py +224 -0
  80. aipt_v2/models/scan_config.py +207 -0
  81. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  82. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  83. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  84. aipt_v2/monitoring/prometheus.yml +60 -0
  85. aipt_v2/orchestration/__init__.py +52 -0
  86. aipt_v2/orchestration/pipeline.py +398 -0
  87. aipt_v2/orchestration/progress.py +300 -0
  88. aipt_v2/orchestration/scheduler.py +296 -0
  89. aipt_v2/orchestrator.py +2427 -0
  90. aipt_v2/payloads/__init__.py +27 -0
  91. aipt_v2/payloads/cmdi.py +150 -0
  92. aipt_v2/payloads/sqli.py +263 -0
  93. aipt_v2/payloads/ssrf.py +204 -0
  94. aipt_v2/payloads/templates.py +222 -0
  95. aipt_v2/payloads/traversal.py +166 -0
  96. aipt_v2/payloads/xss.py +204 -0
  97. aipt_v2/prompts/__init__.py +60 -0
  98. aipt_v2/proxy/__init__.py +29 -0
  99. aipt_v2/proxy/history.py +352 -0
  100. aipt_v2/proxy/interceptor.py +452 -0
  101. aipt_v2/recon/__init__.py +44 -0
  102. aipt_v2/recon/dns.py +241 -0
  103. aipt_v2/recon/osint.py +367 -0
  104. aipt_v2/recon/subdomain.py +372 -0
  105. aipt_v2/recon/tech_detect.py +311 -0
  106. aipt_v2/reports/__init__.py +17 -0
  107. aipt_v2/reports/generator.py +313 -0
  108. aipt_v2/reports/html_report.py +378 -0
  109. aipt_v2/runtime/__init__.py +53 -0
  110. aipt_v2/runtime/base.py +30 -0
  111. aipt_v2/runtime/docker.py +401 -0
  112. aipt_v2/runtime/local.py +346 -0
  113. aipt_v2/runtime/tool_server.py +205 -0
  114. aipt_v2/runtime/vps.py +830 -0
  115. aipt_v2/scanners/__init__.py +28 -0
  116. aipt_v2/scanners/base.py +273 -0
  117. aipt_v2/scanners/nikto.py +244 -0
  118. aipt_v2/scanners/nmap.py +402 -0
  119. aipt_v2/scanners/nuclei.py +273 -0
  120. aipt_v2/scanners/web.py +454 -0
  121. aipt_v2/scripts/security_audit.py +366 -0
  122. aipt_v2/setup_wizard.py +941 -0
  123. aipt_v2/skills/__init__.py +80 -0
  124. aipt_v2/skills/agents/__init__.py +14 -0
  125. aipt_v2/skills/agents/api_tester.py +706 -0
  126. aipt_v2/skills/agents/base.py +477 -0
  127. aipt_v2/skills/agents/code_review.py +459 -0
  128. aipt_v2/skills/agents/security_agent.py +336 -0
  129. aipt_v2/skills/agents/web_pentest.py +818 -0
  130. aipt_v2/skills/prompts/__init__.py +647 -0
  131. aipt_v2/system_detector.py +539 -0
  132. aipt_v2/telemetry/__init__.py +7 -0
  133. aipt_v2/telemetry/tracer.py +347 -0
  134. aipt_v2/terminal/__init__.py +28 -0
  135. aipt_v2/terminal/executor.py +400 -0
  136. aipt_v2/terminal/sandbox.py +350 -0
  137. aipt_v2/tools/__init__.py +44 -0
  138. aipt_v2/tools/active_directory/__init__.py +78 -0
  139. aipt_v2/tools/active_directory/ad_config.py +238 -0
  140. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  141. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  142. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  143. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  144. aipt_v2/tools/agents_graph/__init__.py +19 -0
  145. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  146. aipt_v2/tools/api_security/__init__.py +76 -0
  147. aipt_v2/tools/api_security/api_discovery.py +608 -0
  148. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  149. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  150. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  151. aipt_v2/tools/browser/__init__.py +5 -0
  152. aipt_v2/tools/browser/browser_actions.py +238 -0
  153. aipt_v2/tools/browser/browser_instance.py +535 -0
  154. aipt_v2/tools/browser/tab_manager.py +344 -0
  155. aipt_v2/tools/cloud/__init__.py +70 -0
  156. aipt_v2/tools/cloud/cloud_config.py +273 -0
  157. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  158. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  159. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  160. aipt_v2/tools/executor.py +307 -0
  161. aipt_v2/tools/parser.py +408 -0
  162. aipt_v2/tools/proxy/__init__.py +5 -0
  163. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  164. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  165. aipt_v2/tools/registry.py +196 -0
  166. aipt_v2/tools/scanners/__init__.py +343 -0
  167. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  168. aipt_v2/tools/scanners/burp_tool.py +631 -0
  169. aipt_v2/tools/scanners/config.py +156 -0
  170. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  171. aipt_v2/tools/scanners/zap_tool.py +612 -0
  172. aipt_v2/tools/terminal/__init__.py +5 -0
  173. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  174. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  175. aipt_v2/tools/terminal/terminal_session.py +449 -0
  176. aipt_v2/tools/tool_processing.py +108 -0
  177. aipt_v2/utils/__init__.py +17 -0
  178. aipt_v2/utils/logging.py +202 -0
  179. aipt_v2/utils/model_manager.py +187 -0
  180. aipt_v2/utils/searchers/__init__.py +269 -0
  181. aipt_v2/verify_install.py +793 -0
  182. aiptx-2.0.7.dist-info/METADATA +345 -0
  183. aiptx-2.0.7.dist-info/RECORD +187 -0
  184. aiptx-2.0.7.dist-info/WHEEL +5 -0
  185. aiptx-2.0.7.dist-info/entry_points.txt +7 -0
  186. aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
  187. aiptx-2.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,559 @@
1
+ """
2
+ AIPTX Interactive Shell
3
+ =======================
4
+
5
+ Provides an interactive shell environment for running security tools
6
+ with enhanced features like command history, output logging, and
7
+ real-time display.
8
+
9
+ Features:
10
+ - Interactive command execution
11
+ - Tab completion for tool names
12
+ - Command history with readline
13
+ - Real-time output streaming
14
+ - Session logging
15
+ - Tool discovery and help
16
+ - Environment management
17
+
18
+ Usage:
19
+ aiptx shell # Start interactive shell
20
+ aiptx shell --log session.log # With logging
21
+ """
22
+
23
+ import asyncio
24
+ import os
25
+ import pty
26
+ import readline
27
+ import select
28
+ import shlex
29
+ import shutil
30
+ import signal
31
+ import subprocess
32
+ import sys
33
+ import termios
34
+ import tty
35
+ from datetime import datetime
36
+ from pathlib import Path
37
+ from typing import Dict, List, Optional, Callable, Tuple
38
+
39
+ from rich.console import Console
40
+ from rich.panel import Panel
41
+ from rich.table import Table
42
+ from rich.syntax import Syntax
43
+ from rich.live import Live
44
+ from rich.text import Text
45
+ from rich import box
46
+
47
+
48
+ console = Console()
49
+
50
+
51
+ # Command history file
52
+ HISTORY_FILE = Path.home() / ".aiptx" / "shell_history"
53
+
54
+
55
+ class ToolCompleter:
56
+ """Tab completion for tool names and commands."""
57
+
58
+ def __init__(self, tools: List[str]):
59
+ self.tools = sorted(tools)
60
+ self.commands = [
61
+ "help", "tools", "exit", "quit", "clear", "history",
62
+ "env", "cd", "pwd", "run", "scan", "export", "log"
63
+ ]
64
+ self.all_completions = self.commands + self.tools
65
+
66
+ def complete(self, text: str, state: int) -> Optional[str]:
67
+ """Readline completion function."""
68
+ if state == 0:
69
+ if text:
70
+ self.matches = [
71
+ s for s in self.all_completions
72
+ if s.startswith(text)
73
+ ]
74
+ else:
75
+ self.matches = self.all_completions[:]
76
+
77
+ try:
78
+ return self.matches[state]
79
+ except IndexError:
80
+ return None
81
+
82
+
83
+ class InteractiveShell:
84
+ """
85
+ Interactive shell for running security tools.
86
+
87
+ Provides a REPL-like environment with:
88
+ - Tool execution with real-time output
89
+ - Command history and tab completion
90
+ - Session logging
91
+ - Environment management
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ log_file: Optional[Path] = None,
97
+ working_dir: Optional[Path] = None,
98
+ ):
99
+ """
100
+ Initialize the interactive shell.
101
+
102
+ Args:
103
+ log_file: Optional file to log session output
104
+ working_dir: Working directory (defaults to current)
105
+ """
106
+ self.log_file = log_file
107
+ self.working_dir = working_dir or Path.cwd()
108
+ self.running = False
109
+ self.env = os.environ.copy()
110
+ self.history: List[str] = []
111
+ self._tools: Dict[str, str] = {}
112
+ self._load_tools()
113
+ self._setup_readline()
114
+
115
+ def _load_tools(self):
116
+ """Load available tools from the installer."""
117
+ try:
118
+ from aipt_v2.local_tool_installer import TOOLS
119
+ self._tools = {
120
+ name: tool.description
121
+ for name, tool in TOOLS.items()
122
+ }
123
+ except ImportError:
124
+ self._tools = {}
125
+
126
+ def _setup_readline(self):
127
+ """Configure readline for history and completion."""
128
+ # Load history
129
+ HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
130
+ if HISTORY_FILE.exists():
131
+ try:
132
+ readline.read_history_file(str(HISTORY_FILE))
133
+ except Exception:
134
+ pass
135
+
136
+ # Set up completion
137
+ tool_names = list(self._tools.keys())
138
+ completer = ToolCompleter(tool_names)
139
+ readline.set_completer(completer.complete)
140
+ readline.parse_and_bind("tab: complete")
141
+ readline.set_completer_delims(" \t\n;")
142
+
143
+ # Limit history size
144
+ readline.set_history_length(1000)
145
+
146
+ def _save_history(self):
147
+ """Save command history to file."""
148
+ try:
149
+ readline.write_history_file(str(HISTORY_FILE))
150
+ except Exception:
151
+ pass
152
+
153
+ def _log(self, message: str):
154
+ """Log message to file if logging is enabled."""
155
+ if self.log_file:
156
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
157
+ with open(self.log_file, "a") as f:
158
+ f.write(f"[{timestamp}] {message}\n")
159
+
160
+ def print_banner(self):
161
+ """Print welcome banner."""
162
+ banner = """
163
+ [bold cyan]╔═══════════════════════════════════════════════════════════════╗
164
+ ║ AIPTX Interactive Shell ║
165
+ ║ AI-Powered Penetration Testing ║
166
+ ╚═══════════════════════════════════════════════════════════════╝[/bold cyan]
167
+ """
168
+ console.print(banner)
169
+ console.print("[dim]Type 'help' for commands, 'tools' to list available tools[/dim]")
170
+ console.print(f"[dim]Working directory: {self.working_dir}[/dim]")
171
+ console.print()
172
+
173
+ def print_help(self):
174
+ """Print help information."""
175
+ help_text = """
176
+ [bold cyan]AIPTX Interactive Shell Commands[/bold cyan]
177
+
178
+ [bold]Built-in Commands:[/bold]
179
+ help Show this help message
180
+ tools List available security tools
181
+ tools <category> List tools in a category
182
+ clear Clear the screen
183
+ history Show command history
184
+ env Show environment variables
185
+ env SET KEY=VAL Set an environment variable
186
+ cd <path> Change working directory
187
+ pwd Print working directory
188
+ log <file> Start logging to file
189
+ exit, quit Exit the shell
190
+
191
+ [bold]Running Tools:[/bold]
192
+ Just type the tool name and arguments:
193
+ nmap -sV target.com
194
+ nuclei -u https://target.com
195
+ sqlmap -u "http://target.com?id=1"
196
+
197
+ [bold]Tips:[/bold]
198
+ • Use TAB for auto-completion
199
+ • Use UP/DOWN arrows for history
200
+ • Ctrl+C interrupts running command
201
+ • Ctrl+D exits the shell
202
+ """
203
+ console.print(help_text)
204
+
205
+ def list_tools(self, category: Optional[str] = None):
206
+ """List available tools, optionally filtered by category."""
207
+ try:
208
+ from aipt_v2.local_tool_installer import TOOLS, ToolCategory
209
+
210
+ table = Table(title="Available Security Tools", box=box.ROUNDED)
211
+ table.add_column("Tool", style="cyan")
212
+ table.add_column("Category", style="dim")
213
+ table.add_column("Installed", justify="center")
214
+ table.add_column("Description")
215
+
216
+ for name, tool in sorted(TOOLS.items()):
217
+ if category and tool.category.value != category:
218
+ continue
219
+
220
+ is_installed = shutil.which(name) is not None
221
+ status = "[green]✓[/green]" if is_installed else "[dim]○[/dim]"
222
+
223
+ desc = tool.description[:45] + "..." if len(tool.description) > 45 else tool.description
224
+
225
+ table.add_row(name, tool.category.value, status, desc)
226
+
227
+ console.print()
228
+ console.print(table)
229
+ console.print()
230
+
231
+ if not category:
232
+ console.print("[dim]Filter by category: tools <category>[/dim]")
233
+ categories = sorted(set(t.category.value for t in TOOLS.values()))
234
+ console.print(f"[dim]Categories: {', '.join(categories)}[/dim]")
235
+
236
+ except ImportError:
237
+ console.print("[yellow]Tool catalog not available[/yellow]")
238
+
239
+ def show_history(self, count: int = 20):
240
+ """Show command history."""
241
+ history_len = readline.get_current_history_length()
242
+ start = max(1, history_len - count + 1)
243
+
244
+ console.print(f"\n[bold]Last {min(count, history_len)} commands:[/bold]")
245
+ for i in range(start, history_len + 1):
246
+ cmd = readline.get_history_item(i)
247
+ console.print(f" {i:4d} {cmd}")
248
+ console.print()
249
+
250
+ def show_env(self):
251
+ """Show relevant environment variables."""
252
+ relevant_prefixes = ["AIPT", "PATH", "HOME", "USER", "SHELL", "GOPATH", "GOBIN"]
253
+ table = Table(title="Environment Variables", box=box.ROUNDED)
254
+ table.add_column("Variable", style="cyan")
255
+ table.add_column("Value", style="green", overflow="fold")
256
+
257
+ for key, value in sorted(self.env.items()):
258
+ if any(key.startswith(p) for p in relevant_prefixes):
259
+ # Truncate long values
260
+ display_val = value[:80] + "..." if len(value) > 80 else value
261
+ table.add_row(key, display_val)
262
+
263
+ console.print()
264
+ console.print(table)
265
+ console.print()
266
+
267
+ def set_env(self, key: str, value: str):
268
+ """Set an environment variable."""
269
+ self.env[key] = value
270
+ os.environ[key] = value
271
+ console.print(f"[green]Set {key}={value}[/green]")
272
+
273
+ def change_dir(self, path: str):
274
+ """Change working directory."""
275
+ try:
276
+ new_path = Path(path).expanduser().resolve()
277
+ if new_path.is_dir():
278
+ os.chdir(new_path)
279
+ self.working_dir = new_path
280
+ console.print(f"[green]Changed to: {new_path}[/green]")
281
+ else:
282
+ console.print(f"[red]Not a directory: {path}[/red]")
283
+ except Exception as e:
284
+ console.print(f"[red]Error: {e}[/red]")
285
+
286
+ async def run_command(self, command: str) -> int:
287
+ """
288
+ Run a command with real-time output.
289
+
290
+ Uses PTY for proper terminal handling, allowing interactive
291
+ tools to work correctly.
292
+
293
+ Args:
294
+ command: Command to execute
295
+
296
+ Returns:
297
+ Exit code of the command
298
+ """
299
+ self._log(f"$ {command}")
300
+
301
+ # Parse the command
302
+ try:
303
+ parts = shlex.split(command)
304
+ if not parts:
305
+ return 0
306
+ program = parts[0]
307
+ except ValueError as e:
308
+ console.print(f"[red]Parse error: {e}[/red]")
309
+ return 1
310
+
311
+ # Check if tool exists
312
+ if not shutil.which(program):
313
+ console.print(f"[yellow]Command not found: {program}[/yellow]")
314
+
315
+ # Check if it's a known tool that's not installed
316
+ if program in self._tools:
317
+ console.print(f"[dim]This tool is not installed. Run: aiptx tools install -t {program}[/dim]")
318
+ return 127
319
+
320
+ # Run with PTY for full terminal support
321
+ console.print()
322
+
323
+ try:
324
+ # Create pseudo-terminal
325
+ master_fd, slave_fd = pty.openpty()
326
+
327
+ # Start process
328
+ process = subprocess.Popen(
329
+ command,
330
+ shell=True,
331
+ stdin=slave_fd,
332
+ stdout=slave_fd,
333
+ stderr=slave_fd,
334
+ env=self.env,
335
+ cwd=str(self.working_dir),
336
+ preexec_fn=os.setsid,
337
+ )
338
+
339
+ os.close(slave_fd)
340
+
341
+ # Read output in real-time
342
+ output_lines = []
343
+ try:
344
+ while True:
345
+ ready, _, _ = select.select([master_fd], [], [], 0.1)
346
+ if ready:
347
+ try:
348
+ data = os.read(master_fd, 1024)
349
+ if not data:
350
+ break
351
+ text = data.decode("utf-8", errors="replace")
352
+ sys.stdout.write(text)
353
+ sys.stdout.flush()
354
+ output_lines.append(text)
355
+ self._log(text.rstrip())
356
+ except OSError:
357
+ break
358
+
359
+ # Check if process finished
360
+ if process.poll() is not None:
361
+ # Read any remaining output
362
+ try:
363
+ while True:
364
+ ready, _, _ = select.select([master_fd], [], [], 0.1)
365
+ if not ready:
366
+ break
367
+ data = os.read(master_fd, 1024)
368
+ if not data:
369
+ break
370
+ text = data.decode("utf-8", errors="replace")
371
+ sys.stdout.write(text)
372
+ sys.stdout.flush()
373
+ output_lines.append(text)
374
+ except OSError:
375
+ pass
376
+ break
377
+
378
+ except KeyboardInterrupt:
379
+ # Send SIGINT to process group
380
+ os.killpg(os.getpgid(process.pid), signal.SIGINT)
381
+ console.print("\n[yellow]Interrupted[/yellow]")
382
+
383
+ finally:
384
+ os.close(master_fd)
385
+ process.wait()
386
+
387
+ return process.returncode or 0
388
+
389
+ except Exception as e:
390
+ console.print(f"[red]Error running command: {e}[/red]")
391
+ return 1
392
+
393
+ def process_builtin(self, command: str) -> Tuple[bool, int]:
394
+ """
395
+ Process built-in commands.
396
+
397
+ Returns:
398
+ Tuple of (is_builtin, exit_code)
399
+ """
400
+ parts = command.strip().split(maxsplit=1)
401
+ if not parts:
402
+ return True, 0
403
+
404
+ cmd = parts[0].lower()
405
+ args = parts[1] if len(parts) > 1 else ""
406
+
407
+ if cmd in ("exit", "quit"):
408
+ self.running = False
409
+ return True, 0
410
+
411
+ elif cmd == "help":
412
+ self.print_help()
413
+ return True, 0
414
+
415
+ elif cmd == "tools":
416
+ self.list_tools(args if args else None)
417
+ return True, 0
418
+
419
+ elif cmd == "clear":
420
+ console.clear()
421
+ return True, 0
422
+
423
+ elif cmd == "history":
424
+ count = int(args) if args.isdigit() else 20
425
+ self.show_history(count)
426
+ return True, 0
427
+
428
+ elif cmd == "env":
429
+ if args.startswith("SET ") or args.startswith("set "):
430
+ # Set environment variable
431
+ var_part = args[4:].strip()
432
+ if "=" in var_part:
433
+ key, value = var_part.split("=", 1)
434
+ self.set_env(key.strip(), value.strip())
435
+ else:
436
+ console.print("[yellow]Usage: env SET KEY=VALUE[/yellow]")
437
+ else:
438
+ self.show_env()
439
+ return True, 0
440
+
441
+ elif cmd == "cd":
442
+ if args:
443
+ self.change_dir(args)
444
+ else:
445
+ self.change_dir(str(Path.home()))
446
+ return True, 0
447
+
448
+ elif cmd == "pwd":
449
+ console.print(str(self.working_dir))
450
+ return True, 0
451
+
452
+ elif cmd == "log":
453
+ if args:
454
+ self.log_file = Path(args).expanduser()
455
+ console.print(f"[green]Logging to: {self.log_file}[/green]")
456
+ else:
457
+ if self.log_file:
458
+ console.print(f"[dim]Currently logging to: {self.log_file}[/dim]")
459
+ else:
460
+ console.print("[dim]Logging disabled. Use: log <filename>[/dim]")
461
+ return True, 0
462
+
463
+ return False, 0
464
+
465
+ async def run(self):
466
+ """Run the interactive shell."""
467
+ self.running = True
468
+ self.print_banner()
469
+
470
+ while self.running:
471
+ try:
472
+ # Build prompt
473
+ cwd = str(self.working_dir)
474
+ home = str(Path.home())
475
+ if cwd.startswith(home):
476
+ cwd = "~" + cwd[len(home):]
477
+
478
+ prompt = f"\n[bold green]aiptx[/bold green]:[bold blue]{cwd}[/bold blue]$ "
479
+ console.print(prompt, end="")
480
+
481
+ # Read input
482
+ try:
483
+ command = input().strip()
484
+ except EOFError:
485
+ console.print()
486
+ break
487
+
488
+ if not command:
489
+ continue
490
+
491
+ # Add to history
492
+ self.history.append(command)
493
+
494
+ # Check for built-in commands
495
+ is_builtin, exit_code = self.process_builtin(command)
496
+ if is_builtin:
497
+ continue
498
+
499
+ # Run external command
500
+ exit_code = await self.run_command(command)
501
+
502
+ if exit_code != 0:
503
+ console.print(f"[dim]Exit code: {exit_code}[/dim]")
504
+
505
+ except KeyboardInterrupt:
506
+ console.print("\n[dim]Use 'exit' or Ctrl+D to quit[/dim]")
507
+ continue
508
+
509
+ # Save history on exit
510
+ self._save_history()
511
+ console.print("\n[dim]Goodbye![/dim]")
512
+
513
+
514
+ async def start_interactive_shell(
515
+ log_file: Optional[str] = None,
516
+ working_dir: Optional[str] = None,
517
+ ) -> int:
518
+ """
519
+ Start the interactive shell.
520
+
521
+ Args:
522
+ log_file: Optional log file path
523
+ working_dir: Optional working directory
524
+
525
+ Returns:
526
+ Exit code
527
+ """
528
+ shell = InteractiveShell(
529
+ log_file=Path(log_file) if log_file else None,
530
+ working_dir=Path(working_dir) if working_dir else None,
531
+ )
532
+ await shell.run()
533
+ return 0
534
+
535
+
536
+ def main():
537
+ """CLI entry point for interactive shell."""
538
+ import argparse
539
+
540
+ parser = argparse.ArgumentParser(description="AIPTX Interactive Shell")
541
+ parser.add_argument(
542
+ "--log", "-l",
543
+ help="Log session to file"
544
+ )
545
+ parser.add_argument(
546
+ "--dir", "-d",
547
+ help="Working directory"
548
+ )
549
+
550
+ args = parser.parse_args()
551
+
552
+ asyncio.run(start_interactive_shell(
553
+ log_file=args.log,
554
+ working_dir=args.dir,
555
+ ))
556
+
557
+
558
+ if __name__ == "__main__":
559
+ main()
@@ -0,0 +1,5 @@
1
+ """
2
+ AIPT Interface Module - TUI and CLI interfaces
3
+ """
4
+
5
+ __all__ = []