agent2win 1.0.0__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.
agent2win/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # agent2win - Universal Bridge Between Web/Cloud AI Agents & Windows OS
2
+ __version__ = "1.0.0"
3
+ __author__ = "agent2win"
agent2win/audio.py ADDED
@@ -0,0 +1,160 @@
1
+ """
2
+ Windows Arena AI — Audio Control
3
+ Control system volume, mute, and audio devices.
4
+ """
5
+ import subprocess
6
+ import ctypes
7
+ from typing import Optional
8
+ from .config import Settings
9
+ from .logger import audit_log
10
+
11
+ try:
12
+ from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
13
+ from comtypes import CLSCTX_ALL, CoCreateInstance
14
+ from ctypes import POINTER
15
+ HAS_PYCAW = True
16
+ except ImportError:
17
+ HAS_PYCAW = False
18
+
19
+
20
+ class AudioController:
21
+ """System audio control."""
22
+
23
+ def __init__(self, settings: Settings, logger):
24
+ self.settings = settings
25
+ self.logger = logger
26
+ self._volume_interface = None
27
+
28
+ def _get_volume_interface(self):
29
+ if self._volume_interface is None and HAS_PYCAW:
30
+ try:
31
+ devices = AudioUtilities.GetSpeakers()
32
+ interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
33
+ self._volume_interface = interface.QueryInterface(POINTER(IAudioEndpointVolume))
34
+ except Exception:
35
+ pass
36
+ return self._volume_interface
37
+
38
+ async def get_volume(self) -> dict:
39
+ """Get current system volume (0-100)."""
40
+ audit_log(self.settings, "get_volume", {})
41
+ if HAS_PYCAW:
42
+ try:
43
+ vol = self._get_volume_interface()
44
+ if vol:
45
+ level = vol.GetMasterVolumeLevelScalar()
46
+ muted = vol.GetMute()
47
+ return {"success": True, "volume": round(level * 100), "muted": bool(muted), "level_scalar": round(level, 3)}
48
+ except Exception as e:
49
+ pass
50
+
51
+ # Fallback: nircmd or PowerShell
52
+ try:
53
+ result = subprocess.run(
54
+ ["powershell", "-command",
55
+ "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('')"],
56
+ capture_output=True, text=True, timeout=5,
57
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
58
+ )
59
+ return {"success": True, "note": "Install pycaw for precise volume control: pip install pycaw"}
60
+ except Exception:
61
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
62
+
63
+ async def set_volume(self, level: int) -> dict:
64
+ """Set system volume (0-100)."""
65
+ audit_log(self.settings, "set_volume", {"level": level})
66
+ level = max(0, min(100, level))
67
+
68
+ if HAS_PYCAW:
69
+ try:
70
+ vol = self._get_volume_interface()
71
+ if vol:
72
+ vol.SetMasterVolumeLevelScalar(level / 100.0, None)
73
+ return {"success": True, "volume": level}
74
+ except Exception:
75
+ pass
76
+
77
+ # Fallback: nircmd
78
+ try:
79
+ vol_val = int(level * 65535 / 100)
80
+ subprocess.run(
81
+ ["nircmd", "setsysvolume", str(vol_val)],
82
+ capture_output=True, timeout=5,
83
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
84
+ )
85
+ return {"success": True, "volume": level, "method": "nircmd"}
86
+ except Exception:
87
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
88
+
89
+ async def mute(self) -> dict:
90
+ """Mute system audio."""
91
+ audit_log(self.settings, "mute", {})
92
+ if HAS_PYCAW:
93
+ try:
94
+ vol = self._get_volume_interface()
95
+ if vol:
96
+ vol.SetMute(True, None)
97
+ return {"success": True, "muted": True}
98
+ except Exception:
99
+ pass
100
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
101
+
102
+ async def unmute(self) -> dict:
103
+ """Unmute system audio."""
104
+ audit_log(self.settings, "unmute", {})
105
+ if HAS_PYCAW:
106
+ try:
107
+ vol = self._get_volume_interface()
108
+ if vol:
109
+ vol.SetMute(False, None)
110
+ return {"success": True, "muted": False}
111
+ except Exception:
112
+ pass
113
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
114
+
115
+ async def toggle_mute(self) -> dict:
116
+ """Toggle mute state."""
117
+ if HAS_PYCAW:
118
+ try:
119
+ vol = self._get_volume_interface()
120
+ if vol:
121
+ current = vol.GetMute()
122
+ vol.SetMute(not current, None)
123
+ return {"success": True, "muted": not bool(current)}
124
+ except Exception:
125
+ pass
126
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
127
+
128
+ async def volume_up(self, step: int = 5) -> dict:
129
+ """Increase volume by step."""
130
+ current = await self.get_volume()
131
+ if current.get("success"):
132
+ new_level = min(100, current["volume"] + step)
133
+ return await self.set_volume(new_level)
134
+ return current
135
+
136
+ async def volume_down(self, step: int = 5) -> dict:
137
+ """Decrease volume by step."""
138
+ current = await self.get_volume()
139
+ if current.get("success"):
140
+ new_level = max(0, current["volume"] - step)
141
+ return await self.set_volume(new_level)
142
+ return current
143
+
144
+ async def list_devices(self) -> dict:
145
+ """List audio devices."""
146
+ audit_log(self.settings, "list_audio_devices", {})
147
+ if HAS_PYCAW:
148
+ try:
149
+ devices = AudioUtilities.GetAllDevices()
150
+ result = []
151
+ for d in devices:
152
+ result.append({
153
+ "id": str(d.id) if d.id else "",
154
+ "name": str(d.FriendlyName) if d.FriendlyName else "",
155
+ "state": str(d.State) if d.State else "",
156
+ })
157
+ return {"success": True, "devices": result, "count": len(result)}
158
+ except Exception as e:
159
+ return {"success": False, "error": str(e)}
160
+ return {"success": False, "error": "Install pycaw: pip install pycaw"}
agent2win/cli.py ADDED
@@ -0,0 +1,113 @@
1
+ """
2
+ agent2win — CLI Entry Point for PyPI package
3
+ """
4
+ import asyncio
5
+ import argparse
6
+ import sys
7
+ import os
8
+
9
+ from .config import Settings
10
+ from .server import ArenaServer
11
+ from .tray import TrayApp
12
+ from .gui import SettingsGUI
13
+
14
+
15
+ def parse_args():
16
+ parser = argparse.ArgumentParser(
17
+ description="agent2win — Universal Bridge Between Web/Cloud AI Agents & Windows OS",
18
+ formatter_class=argparse.RawDescriptionHelpFormatter,
19
+ epilog="""
20
+ Examples:
21
+ agent2win Start server on port 7770
22
+ agent2win --port 8080 --key abc Custom port with API key
23
+ agent2win --unrestricted No approval prompts (⚠️ use carefully)
24
+ agent2win --settings Open settings GUI
25
+ agent2win --tunnel cloudflared Use Cloudflare tunnel
26
+ agent2win --tunnel ngrok Use ngrok tunnel
27
+ agent2win --no-tunnel Disable tunnels
28
+ """,
29
+ )
30
+ parser.add_argument("--port", type=int, help="Server port (default: 7770)")
31
+ parser.add_argument("--host", type=str, help="Bind address (default: 0.0.0.0)")
32
+ parser.add_argument("--key", type=str, help="API key for authentication")
33
+ parser.add_argument("--unrestricted", action="store_true", help="Enable unrestricted mode (no approval prompts)")
34
+ parser.add_argument("--no-tray", action="store_true", help="Don't show system tray icon")
35
+ parser.add_argument("--no-tunnel", action="store_true", help="Disable tunnel")
36
+ parser.add_argument("--tunnel", type=str, choices=["cloudflared", "ngrok"], help="Tunnel provider")
37
+ parser.add_argument("--settings", action="store_true", help="Open settings GUI and exit")
38
+ parser.add_argument("--config", type=str, help="Path to config file")
39
+ return parser.parse_args()
40
+
41
+
42
+ def main():
43
+ args = parse_args()
44
+
45
+ # Load settings
46
+ settings = Settings.load()
47
+
48
+ # Apply CLI overrides
49
+ if args.port:
50
+ settings.port = args.port
51
+ if args.host:
52
+ settings.host = args.host
53
+ if args.key:
54
+ settings.api_key = args.key
55
+ if args.unrestricted:
56
+ settings.unrestricted_mode = True
57
+ if args.no_tunnel:
58
+ settings.tunnel_provider = "none"
59
+ if args.tunnel:
60
+ settings.tunnel_provider = args.tunnel
61
+
62
+ settings.save()
63
+
64
+ # Settings GUI mode
65
+ if args.settings:
66
+ gui = SettingsGUI(settings)
67
+ gui.show()
68
+ return
69
+
70
+ # Print banner
71
+ print(r"""
72
+ ╔═══════════════════════════════════════════════════════╗
73
+ ║ 🚀 agent2win v1.0.0 ║
74
+ ║ Bridge Web/Cloud AI (ChatGPT, Gemini, Grok) to Win ║
75
+ ╠═══════════════════════════════════════════════════════╣
76
+ ║ Server: http://0.0.0.0:{port:<5} ║
77
+ ║ Mode: {mode:<20} ║
78
+ ║ Tunnel: {tunnel:<20} ║
79
+ ╚═══════════════════════════════════════════════════════╝
80
+ """.format(
81
+ port=settings.port,
82
+ mode="UNRESTRICTED ⚠️" if settings.unrestricted_mode else "SECURE 🔒",
83
+ tunnel=settings.tunnel_provider,
84
+ ))
85
+
86
+ server = ArenaServer(settings)
87
+
88
+ # Start system tray
89
+ tray = None
90
+ if not args.no_tray:
91
+ try:
92
+ tray = TrayApp(settings, server.notifications, settings.port)
93
+ tray.start()
94
+ print(" ✅ System tray icon active")
95
+ except Exception as e:
96
+ print(f" ⚠️ System tray unavailable: {e}")
97
+
98
+ # Start server
99
+ print(f" 🚀 Starting server on port {settings.port}...")
100
+ print(f" 📖 Open http://localhost:{settings.port} for API docs")
101
+ print(f" ⏹️ Press Ctrl+C to stop\n")
102
+
103
+ try:
104
+ asyncio.run(server.start())
105
+ except KeyboardInterrupt:
106
+ print("\n 👋 Shutting down...")
107
+ finally:
108
+ if tray:
109
+ tray.stop()
110
+
111
+
112
+ if __name__ == "__main__":
113
+ main()
agent2win/clipboard.py ADDED
@@ -0,0 +1,90 @@
1
+ """
2
+ Windows Arena AI — Clipboard Manager
3
+ Read and write the system clipboard (text, images).
4
+ """
5
+ import subprocess
6
+ import time
7
+ from typing import Optional
8
+ from .config import Settings
9
+ from .logger import audit_log
10
+
11
+ try:
12
+ import pyperclip
13
+ HAS_PYPERCLIP = True
14
+ except ImportError:
15
+ HAS_PYPERCLIP = False
16
+
17
+
18
+ class ClipboardManager:
19
+ """System clipboard operations."""
20
+
21
+ def __init__(self, settings: Settings, logger):
22
+ self.settings = settings
23
+ self.logger = logger
24
+
25
+ async def get_text(self) -> dict:
26
+ """Get current clipboard text."""
27
+ audit_log(self.settings, "clipboard_get", {})
28
+ if HAS_PYPERCLIP:
29
+ try:
30
+ text = pyperclip.paste()
31
+ return {"success": True, "text": text, "length": len(text)}
32
+ except Exception as e:
33
+ return {"success": False, "error": str(e)}
34
+
35
+ # Fallback: PowerShell
36
+ try:
37
+ result = subprocess.run(
38
+ ["powershell", "-command", "Get-Clipboard"],
39
+ capture_output=True, text=True, timeout=5,
40
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
41
+ )
42
+ return {"success": True, "text": result.stdout.strip(), "length": len(result.stdout.strip())}
43
+ except Exception as e:
44
+ return {"success": False, "error": str(e)}
45
+
46
+ async def set_text(self, text: str) -> dict:
47
+ """Set clipboard text."""
48
+ audit_log(self.settings, "clipboard_set", {"length": len(text)})
49
+ if HAS_PYPERCLIP:
50
+ try:
51
+ pyperclip.copy(text)
52
+ return {"success": True, "text_length": len(text)}
53
+ except Exception as e:
54
+ return {"success": False, "error": str(e)}
55
+
56
+ try:
57
+ subprocess.run(
58
+ ["powershell", "-command", f'Set-Clipboard -Value @"\\n{text}\\n"@'],
59
+ capture_output=True, text=True, timeout=5,
60
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
61
+ )
62
+ return {"success": True, "text_length": len(text)}
63
+ except Exception as e:
64
+ return {"success": False, "error": str(e)}
65
+
66
+ async def get_history(self) -> dict:
67
+ """Get clipboard history (Windows 10+)."""
68
+ audit_log(self.settings, "clipboard_history", {})
69
+ try:
70
+ result = subprocess.run(
71
+ ["powershell", "-command", "Get-Clipboard -TextFormatType Text"],
72
+ capture_output=True, text=True, timeout=5,
73
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
74
+ )
75
+ return {"success": True, "history": result.stdout.strip()}
76
+ except Exception as e:
77
+ return {"success": False, "error": str(e)}
78
+
79
+ async def clear(self) -> dict:
80
+ """Clear the clipboard."""
81
+ audit_log(self.settings, "clipboard_clear", {})
82
+ try:
83
+ subprocess.run(
84
+ ["powershell", "-command", "Clear-Clipboard"],
85
+ capture_output=True, text=True, timeout=5,
86
+ creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
87
+ )
88
+ return {"success": True}
89
+ except Exception as e:
90
+ return {"success": False, "error": str(e)}
agent2win/commands.py ADDED
@@ -0,0 +1,89 @@
1
+ """
2
+ Windows Arena AI — Command Execution Engine
3
+ Runs shell commands with safety checks, timeouts, and approval flow.
4
+ """
5
+ import subprocess
6
+ import shlex
7
+ import time
8
+ import threading
9
+ from typing import Optional
10
+ from .config import Settings
11
+ from .logger import audit_log
12
+
13
+ class CommandEngine:
14
+ def __init__(self, settings: Settings, logger, approval_callback=None):
15
+ self.settings = settings
16
+ self.logger = logger
17
+ self.approval_callback = approval_callback # async fn(action, details) -> bool
18
+
19
+ def _is_blocked(self, cmd: str) -> bool:
20
+ cmd_lower = cmd.lower().strip()
21
+ for blocked in self.settings.blocked_commands:
22
+ if blocked.lower() in cmd_lower:
23
+ return True
24
+ return False
25
+
26
+ def _needs_approval(self, cmd: str) -> bool:
27
+ if self.settings.unrestricted_mode:
28
+ return False
29
+ if not self.settings.require_approval:
30
+ return False
31
+ cmd_lower = cmd.lower().strip()
32
+ # Known safe read-only commands pass without approval
33
+ safe_prefixes = ("dir", "cd", "echo", "whoami", "hostname", "ipconfig",
34
+ "systeminfo", "tasklist", "where", "type", "tree",
35
+ "netstat", "ping", "tracert", "ver", "date", "time")
36
+ for safe in safe_prefixes:
37
+ if cmd_lower.startswith(safe):
38
+ return False
39
+ return True
40
+
41
+ async def execute(self, cmd: str, cwd: Optional[str] = None, timeout: Optional[int] = None) -> dict:
42
+ """
43
+ Execute a Windows command. Returns dict with:
44
+ success, stdout, stderr, returncode, duration_sec, approved
45
+ """
46
+ if self._is_blocked(cmd):
47
+ audit_log(self.settings, "command_blocked", {"cmd": cmd}, approved=False)
48
+ return {"success": False, "error": f"Command blocked by security policy: {cmd}", "stdout": "", "stderr": "", "returncode": -1}
49
+
50
+ approved = True
51
+ if self._needs_approval(cmd):
52
+ if self.approval_callback:
53
+ approved = await self.approval_callback("command", {"cmd": cmd, "cwd": cwd})
54
+ else:
55
+ approved = False
56
+ if not approved:
57
+ audit_log(self.settings, "command_denied", {"cmd": cmd}, approved=False)
58
+ return {"success": False, "error": "Command denied by user", "stdout": "", "stderr": "", "returncode": -1}
59
+
60
+ audit_log(self.settings, "command_executed", {"cmd": cmd, "cwd": cwd}, approved=True)
61
+ self.logger.info(f"Executing: {cmd}")
62
+
63
+ effective_timeout = timeout or self.settings.max_command_timeout_sec
64
+ t0 = time.time()
65
+
66
+ try:
67
+ proc = subprocess.run(
68
+ cmd,
69
+ shell=True,
70
+ capture_output=True,
71
+ text=True,
72
+ cwd=cwd,
73
+ timeout=effective_timeout,
74
+ encoding="utf-8",
75
+ errors="replace",
76
+ )
77
+ duration = round(time.time() - t0, 3)
78
+ return {
79
+ "success": proc.returncode == 0,
80
+ "stdout": proc.stdout,
81
+ "stderr": proc.stderr,
82
+ "returncode": proc.returncode,
83
+ "duration_sec": duration,
84
+ "approved": approved,
85
+ }
86
+ except subprocess.TimeoutExpired:
87
+ return {"success": False, "error": f"Command timed out after {effective_timeout}s", "stdout": "", "stderr": "", "returncode": -1}
88
+ except Exception as e:
89
+ return {"success": False, "error": str(e), "stdout": "", "stderr": "", "returncode": -1}
agent2win/config.py ADDED
@@ -0,0 +1,64 @@
1
+ """
2
+ agent2win — Configuration & Settings Manager
3
+ """
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ from dataclasses import dataclass, field, asdict
8
+ from typing import Optional
9
+
10
+ CONFIG_DIR = Path(os.environ.get("APPDATA", "~")) / "agent2win"
11
+ CONFIG_FILE = CONFIG_DIR / "config.json"
12
+
13
+ @dataclass
14
+ class Settings:
15
+ # Server
16
+ host: str = "0.0.0.0"
17
+ port: int = 7770
18
+ api_key: str = ""
19
+ auto_start: bool = False
20
+
21
+ # Security
22
+ unrestricted_mode: bool = False # True = no approval prompts
23
+ require_approval: bool = True # Show notification before actions
24
+ approval_timeout_sec: int = 60 # Auto-deny after timeout
25
+ allowed_commands: list = field(default_factory=lambda: ["dir", "cd", "type", "echo", "whoami", "hostname", "ipconfig", "systeminfo", "tasklist", "where"])
26
+ blocked_commands: list = field(default_factory=lambda: ["format", "del /s", "rd /s", "reg delete", "bcdedit"])
27
+ max_command_timeout_sec: int = 30
28
+
29
+ # Screen
30
+ screen_capture_fps: int = 2 # Frames per second for live view
31
+ screen_quality: int = 60 # JPEG quality 1-100
32
+ screen_scale: float = 0.75 # Downscale factor
33
+
34
+ # Tunnel
35
+ tunnel_provider: str = "cloudflared" # cloudflared | ngrok | none
36
+ tunnel_custom_url: str = "" # If you have your own tunnel
37
+
38
+ # Logging
39
+ log_file: str = str(CONFIG_DIR / "agent2win.log")
40
+ log_level: str = "INFO"
41
+ audit_log: str = str(CONFIG_DIR / "audit.jsonl")
42
+
43
+ def save(self):
44
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
45
+ CONFIG_FILE.write_text(json.dumps(asdict(self), indent=2, ensure_ascii=False), encoding="utf-8")
46
+
47
+ @classmethod
48
+ def load(cls) -> "Settings":
49
+ if CONFIG_FILE.exists():
50
+ try:
51
+ data = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
52
+ known = {f.name for f in cls.__dataclass_fields__.values()}
53
+ return cls(**{k: v for k, v in data.items() if k in known})
54
+ except Exception:
55
+ pass
56
+ s = cls()
57
+ s.save()
58
+ return s
59
+
60
+ def update(self, **kwargs):
61
+ for k, v in kwargs.items():
62
+ if hasattr(self, k):
63
+ setattr(self, k, v)
64
+ self.save()