remotescout 1.0.0__tar.gz
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.
- remotescout-1.0.0/.gitignore +35 -0
- remotescout-1.0.0/PKG-INFO +108 -0
- remotescout-1.0.0/README.md +84 -0
- remotescout-1.0.0/pyproject.toml +36 -0
- remotescout-1.0.0/remotescout/__init__.py +35 -0
- remotescout-1.0.0/remotescout/agents/__init__.py +3 -0
- remotescout-1.0.0/remotescout/agents/manager.py +197 -0
- remotescout-1.0.0/remotescout/ai/__init__.py +13 -0
- remotescout-1.0.0/remotescout/ai/chat_engine.py +758 -0
- remotescout-1.0.0/remotescout/ai/models.py +41 -0
- remotescout-1.0.0/remotescout/ai/providers.py +427 -0
- remotescout-1.0.0/remotescout/ai/scripts.py +133 -0
- remotescout-1.0.0/remotescout/ai/tools.py +555 -0
- remotescout-1.0.0/remotescout/api.py +1217 -0
- remotescout-1.0.0/remotescout/events.py +64 -0
- remotescout-1.0.0/remotescout/models.py +301 -0
- remotescout-1.0.0/remotescout/screenshot_cache.py +73 -0
- remotescout-1.0.0/remotescout/services/__init__.py +3 -0
- remotescout-1.0.0/remotescout/services/mqtt_client.py +408 -0
- remotescout-1.0.0/remotescout/services/mqtt_tunnel.py +135 -0
- remotescout-1.0.0/remotescout/utils/__init__.py +3 -0
- remotescout-1.0.0/remotescout/utils/totp.py +45 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
*.egg
|
|
7
|
+
|
|
8
|
+
# Build
|
|
9
|
+
build/
|
|
10
|
+
build_log*.txt
|
|
11
|
+
|
|
12
|
+
# Config & data (contains credentials/user data)
|
|
13
|
+
config/
|
|
14
|
+
data/cache/
|
|
15
|
+
data/downloads/
|
|
16
|
+
logs/
|
|
17
|
+
|
|
18
|
+
# Keep uploads structure but ignore large files
|
|
19
|
+
data/uploads/*/Remote/
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
desktop.ini
|
|
23
|
+
.megaignore
|
|
24
|
+
Thumbs.db
|
|
25
|
+
.DS_Store
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
|
|
31
|
+
# Legacy reference file
|
|
32
|
+
main-sinAPI.py
|
|
33
|
+
|
|
34
|
+
# Claude Code
|
|
35
|
+
.claude/
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: remotescout
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python library for managing IoT agents via MQTT — screenshots, commands, file transfers, tunnels, cameras
|
|
5
|
+
Project-URL: Homepage, https://github.com/Pedro77339/remotescout-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/Pedro77339/remotescoutapp
|
|
7
|
+
Author: Pedro Camacho
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agent,iot,mcp,mqtt,remote-management
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: paho-mqtt>=1.6
|
|
21
|
+
Requires-Dist: pillow>=9.0
|
|
22
|
+
Requires-Dist: requests>=2.28
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# remotescout
|
|
26
|
+
|
|
27
|
+
Python library for managing IoT agents via MQTT. Provides a unified API for remote device management: screenshots, shell commands, file transfers, TCP tunnels, camera feeds, and OTA deployments.
|
|
28
|
+
|
|
29
|
+
Built for the [Remote Scout MCP Server](https://github.com/Pedro77339/remotescout-mcp) but can be used standalone.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install remotescout
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
### Option 1: Environment variables
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
export RS_MQTT_GLOBAL_PASSWORD="your_global_password"
|
|
43
|
+
export RS_MQTT_ROUTER_PASSWORD="your_router_password"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Option 2: settings.json
|
|
47
|
+
|
|
48
|
+
Create a `config/settings.json` in your base path:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mqtt_router_host": "mrouter.ci24.com",
|
|
53
|
+
"mqtt_router_port": 8001,
|
|
54
|
+
"mqtt_router_user": "ci24",
|
|
55
|
+
"mqtt_router_password": "YOUR_PASSWORD",
|
|
56
|
+
"mqtt_global_host": "mgovernor.ci24.com",
|
|
57
|
+
"mqtt_global_port": 1883,
|
|
58
|
+
"mqtt_global_user": "ci24",
|
|
59
|
+
"mqtt_global_password": "YOUR_PASSWORD"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> Contact the project admin for MQTT broker credentials.
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from remotescout import RemoteScoutAPI
|
|
69
|
+
|
|
70
|
+
api = RemoteScoutAPI(base_path="./my_base")
|
|
71
|
+
api.initialize()
|
|
72
|
+
api.connect()
|
|
73
|
+
|
|
74
|
+
# Login
|
|
75
|
+
api.login("your_username")
|
|
76
|
+
|
|
77
|
+
# List agents
|
|
78
|
+
agents = api.get_agents_by_domain("BLX")
|
|
79
|
+
for a in agents:
|
|
80
|
+
print(f"{a.mac} - {a.id} ({a.status.value})")
|
|
81
|
+
|
|
82
|
+
# Execute command
|
|
83
|
+
result = api.execute_command("d83addc8721e", "uptime")
|
|
84
|
+
print(result.output)
|
|
85
|
+
|
|
86
|
+
# Screenshot
|
|
87
|
+
img_bytes = api.get_screenshot("d83addc8721e", ratio=3, timeout=10)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Features
|
|
91
|
+
|
|
92
|
+
| Feature | Method |
|
|
93
|
+
|---------|--------|
|
|
94
|
+
| Agent discovery | `get_agents_by_domain()`, `get_agent()` |
|
|
95
|
+
| Shell commands | `execute_command()` |
|
|
96
|
+
| Screenshots | `get_screenshot()` |
|
|
97
|
+
| File browser | `list_directory()`, `get_file_bytes()` |
|
|
98
|
+
| File transfer | `download_file()` |
|
|
99
|
+
| TCP tunnels | `open_tunnel()`, `close_all_tunnels()` |
|
|
100
|
+
| Camera feeds | `discover_cameras()`, `get_camera_frame()` |
|
|
101
|
+
| OTA packages | `list_packages()`, `upload_package()` |
|
|
102
|
+
| OTP tokens | `get_otp()` |
|
|
103
|
+
|
|
104
|
+
## Requirements
|
|
105
|
+
|
|
106
|
+
- Python 3.10+
|
|
107
|
+
- Access to the MQTT broker (credentials from admin)
|
|
108
|
+
- User permissions file or governor-validated credentials
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# remotescout
|
|
2
|
+
|
|
3
|
+
Python library for managing IoT agents via MQTT. Provides a unified API for remote device management: screenshots, shell commands, file transfers, TCP tunnels, camera feeds, and OTA deployments.
|
|
4
|
+
|
|
5
|
+
Built for the [Remote Scout MCP Server](https://github.com/Pedro77339/remotescout-mcp) but can be used standalone.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install remotescout
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
### Option 1: Environment variables
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export RS_MQTT_GLOBAL_PASSWORD="your_global_password"
|
|
19
|
+
export RS_MQTT_ROUTER_PASSWORD="your_router_password"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Option 2: settings.json
|
|
23
|
+
|
|
24
|
+
Create a `config/settings.json` in your base path:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mqtt_router_host": "mrouter.ci24.com",
|
|
29
|
+
"mqtt_router_port": 8001,
|
|
30
|
+
"mqtt_router_user": "ci24",
|
|
31
|
+
"mqtt_router_password": "YOUR_PASSWORD",
|
|
32
|
+
"mqtt_global_host": "mgovernor.ci24.com",
|
|
33
|
+
"mqtt_global_port": 1883,
|
|
34
|
+
"mqtt_global_user": "ci24",
|
|
35
|
+
"mqtt_global_password": "YOUR_PASSWORD"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> Contact the project admin for MQTT broker credentials.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from remotescout import RemoteScoutAPI
|
|
45
|
+
|
|
46
|
+
api = RemoteScoutAPI(base_path="./my_base")
|
|
47
|
+
api.initialize()
|
|
48
|
+
api.connect()
|
|
49
|
+
|
|
50
|
+
# Login
|
|
51
|
+
api.login("your_username")
|
|
52
|
+
|
|
53
|
+
# List agents
|
|
54
|
+
agents = api.get_agents_by_domain("BLX")
|
|
55
|
+
for a in agents:
|
|
56
|
+
print(f"{a.mac} - {a.id} ({a.status.value})")
|
|
57
|
+
|
|
58
|
+
# Execute command
|
|
59
|
+
result = api.execute_command("d83addc8721e", "uptime")
|
|
60
|
+
print(result.output)
|
|
61
|
+
|
|
62
|
+
# Screenshot
|
|
63
|
+
img_bytes = api.get_screenshot("d83addc8721e", ratio=3, timeout=10)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
| Feature | Method |
|
|
69
|
+
|---------|--------|
|
|
70
|
+
| Agent discovery | `get_agents_by_domain()`, `get_agent()` |
|
|
71
|
+
| Shell commands | `execute_command()` |
|
|
72
|
+
| Screenshots | `get_screenshot()` |
|
|
73
|
+
| File browser | `list_directory()`, `get_file_bytes()` |
|
|
74
|
+
| File transfer | `download_file()` |
|
|
75
|
+
| TCP tunnels | `open_tunnel()`, `close_all_tunnels()` |
|
|
76
|
+
| Camera feeds | `discover_cameras()`, `get_camera_frame()` |
|
|
77
|
+
| OTA packages | `list_packages()`, `upload_package()` |
|
|
78
|
+
| OTP tokens | `get_otp()` |
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
- Python 3.10+
|
|
83
|
+
- Access to the MQTT broker (credentials from admin)
|
|
84
|
+
- User permissions file or governor-validated credentials
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "remotescout"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python library for managing IoT agents via MQTT — screenshots, commands, file transfers, tunnels, cameras"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "Pedro Camacho"}]
|
|
13
|
+
keywords = ["iot", "mqtt", "remote-management", "mcp", "agent"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: System :: Monitoring",
|
|
23
|
+
"Topic :: System :: Systems Administration",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"paho-mqtt>=1.6",
|
|
27
|
+
"pillow>=9.0",
|
|
28
|
+
"requests>=2.28",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/Pedro77339/remotescout-mcp"
|
|
33
|
+
Repository = "https://github.com/Pedro77339/remotescoutapp"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["remotescout"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""RemoteScout — Agent management via MQTT."""
|
|
2
|
+
|
|
3
|
+
from remotescout.api import RemoteScoutAPI
|
|
4
|
+
from remotescout.models import (
|
|
5
|
+
AgentInfo,
|
|
6
|
+
AgentStatus,
|
|
7
|
+
UserPermissions,
|
|
8
|
+
Settings,
|
|
9
|
+
MqttConfig,
|
|
10
|
+
FileEntry,
|
|
11
|
+
CommandResult,
|
|
12
|
+
CameraInfo,
|
|
13
|
+
PortForwardInfo,
|
|
14
|
+
TransferProgress,
|
|
15
|
+
)
|
|
16
|
+
from remotescout.events import EventEmitter, EventType, Event
|
|
17
|
+
|
|
18
|
+
__version__ = "1.0.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"RemoteScoutAPI",
|
|
22
|
+
"AgentInfo",
|
|
23
|
+
"AgentStatus",
|
|
24
|
+
"UserPermissions",
|
|
25
|
+
"Settings",
|
|
26
|
+
"MqttConfig",
|
|
27
|
+
"FileEntry",
|
|
28
|
+
"CommandResult",
|
|
29
|
+
"CameraInfo",
|
|
30
|
+
"PortForwardInfo",
|
|
31
|
+
"TransferProgress",
|
|
32
|
+
"EventEmitter",
|
|
33
|
+
"EventType",
|
|
34
|
+
"Event",
|
|
35
|
+
]
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""AgentManager: Centralized agent state management.
|
|
2
|
+
|
|
3
|
+
Copied from remotev3/agents/agent_manager.py — no import changes needed.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import queue
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Dict, Optional, List
|
|
12
|
+
from os.path import exists
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AgentData:
|
|
17
|
+
"""Datos de un agente"""
|
|
18
|
+
mac: str
|
|
19
|
+
id: str
|
|
20
|
+
domain: str
|
|
21
|
+
ip_public: str
|
|
22
|
+
ip_local: str
|
|
23
|
+
description: str
|
|
24
|
+
ver: str = "2.0"
|
|
25
|
+
cpu: str = ""
|
|
26
|
+
os: str = ""
|
|
27
|
+
rotate: int = 90
|
|
28
|
+
login: str = "root"
|
|
29
|
+
ts: float = field(default_factory=lambda: datetime.now().timestamp())
|
|
30
|
+
frozen: int = 0
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def status(self) -> str:
|
|
34
|
+
"""Calcular estado basado en timestamp"""
|
|
35
|
+
elapsed = datetime.now().timestamp() - self.ts
|
|
36
|
+
if elapsed > 60:
|
|
37
|
+
return "frozen"
|
|
38
|
+
elif elapsed > 30:
|
|
39
|
+
return "offline"
|
|
40
|
+
else:
|
|
41
|
+
return "online"
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def status_color(self) -> str:
|
|
45
|
+
status_map = {
|
|
46
|
+
"online": "#4CAF50",
|
|
47
|
+
"offline": "#FF5722",
|
|
48
|
+
"frozen": "#9E9E9E"
|
|
49
|
+
}
|
|
50
|
+
return status_map.get(self.status, "#9E9E9E")
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def status_icon(self) -> str:
|
|
54
|
+
status_map = {
|
|
55
|
+
"online": "check_circle",
|
|
56
|
+
"offline": "error",
|
|
57
|
+
"frozen": "snowflake"
|
|
58
|
+
}
|
|
59
|
+
return status_map.get(self.status, "help")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class AgentManager:
|
|
63
|
+
"""Gestiona el estado de todos los agentes"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, user: str = ""):
|
|
66
|
+
self.user = user
|
|
67
|
+
self.agents: Dict[str, AgentData] = {}
|
|
68
|
+
self.agents_lock = threading.Lock()
|
|
69
|
+
self.update_queue = queue.Queue()
|
|
70
|
+
self.agents_status: Dict[str, dict] = {}
|
|
71
|
+
self.screenshot_cache: Dict[str, bytes] = {}
|
|
72
|
+
self.screenshot_lock = threading.Lock()
|
|
73
|
+
|
|
74
|
+
def add_agent(self, mac: str, agent_data) -> bool:
|
|
75
|
+
is_new = False
|
|
76
|
+
with self.agents_lock:
|
|
77
|
+
if isinstance(agent_data, dict):
|
|
78
|
+
agent_obj = AgentData(**agent_data)
|
|
79
|
+
else:
|
|
80
|
+
agent_obj = agent_data
|
|
81
|
+
if mac not in self.agents:
|
|
82
|
+
is_new = True
|
|
83
|
+
self.agents[mac] = agent_obj
|
|
84
|
+
self.update_queue.put(('add', mac, agent_obj))
|
|
85
|
+
return is_new
|
|
86
|
+
|
|
87
|
+
def update_agent_status(self, mac: str, status_data: dict):
|
|
88
|
+
with self.agents_lock:
|
|
89
|
+
if mac in self.agents:
|
|
90
|
+
if mac not in self.agents_status:
|
|
91
|
+
self.agents_status[mac] = {}
|
|
92
|
+
self.agents_status[mac].update(status_data)
|
|
93
|
+
self.update_queue.put(('status', mac, status_data))
|
|
94
|
+
|
|
95
|
+
def update_screenshot(self, mac: str, screenshot_data: bytes):
|
|
96
|
+
with self.screenshot_lock:
|
|
97
|
+
self.screenshot_cache[mac] = screenshot_data
|
|
98
|
+
self.update_queue.put(('screenshot', mac, screenshot_data))
|
|
99
|
+
|
|
100
|
+
def get_agent(self, mac: str) -> Optional[AgentData]:
|
|
101
|
+
with self.agents_lock:
|
|
102
|
+
return self.agents.get(mac)
|
|
103
|
+
|
|
104
|
+
def get_screenshot(self, mac: str) -> Optional[bytes]:
|
|
105
|
+
with self.screenshot_lock:
|
|
106
|
+
return self.screenshot_cache.get(mac)
|
|
107
|
+
|
|
108
|
+
def get_agents_by_domain(self, domain_path: str = "") -> List[tuple]:
|
|
109
|
+
with self.agents_lock:
|
|
110
|
+
agents = []
|
|
111
|
+
for mac, agent in self.agents.items():
|
|
112
|
+
if domain_path == "" or agent.domain == domain_path:
|
|
113
|
+
agents.append((mac, agent))
|
|
114
|
+
agents.sort(key=lambda x: x[1].id.lower())
|
|
115
|
+
return agents
|
|
116
|
+
|
|
117
|
+
def get_domain_hierarchy(self) -> dict:
|
|
118
|
+
hierarchy = {}
|
|
119
|
+
with self.agents_lock:
|
|
120
|
+
for mac, agent in self.agents.items():
|
|
121
|
+
domain = agent.domain
|
|
122
|
+
domain_parts = domain.split('/') if domain else ['']
|
|
123
|
+
current_level = hierarchy
|
|
124
|
+
for i, part in enumerate(domain_parts):
|
|
125
|
+
if part not in current_level:
|
|
126
|
+
current_level[part] = {'agents': [], 'subdomains': {}}
|
|
127
|
+
if i == len(domain_parts) - 1:
|
|
128
|
+
current_level[part]['agents'].append((mac, agent))
|
|
129
|
+
else:
|
|
130
|
+
current_level = current_level[part]['subdomains']
|
|
131
|
+
return hierarchy
|
|
132
|
+
|
|
133
|
+
def get_main_domains(self) -> Dict[str, int]:
|
|
134
|
+
hierarchy = self.get_domain_hierarchy()
|
|
135
|
+
main_domains = {}
|
|
136
|
+
|
|
137
|
+
def count_agents_recursive(data: dict) -> int:
|
|
138
|
+
count = len(data.get('agents', []))
|
|
139
|
+
for subdomain_data in data.get('subdomains', {}).values():
|
|
140
|
+
count += count_agents_recursive(subdomain_data)
|
|
141
|
+
return count
|
|
142
|
+
|
|
143
|
+
for domain, data in hierarchy.items():
|
|
144
|
+
main_domains[domain] = count_agents_recursive(data)
|
|
145
|
+
return dict(sorted(main_domains.items()))
|
|
146
|
+
|
|
147
|
+
def get_subdomains(self, domain_path: str) -> Dict[str, int]:
|
|
148
|
+
hierarchy = self.get_domain_hierarchy()
|
|
149
|
+
path_parts = domain_path.split('/') if domain_path else []
|
|
150
|
+
current_level = hierarchy
|
|
151
|
+
for part in path_parts:
|
|
152
|
+
if part in current_level:
|
|
153
|
+
current_level = current_level[part].get('subdomains', {})
|
|
154
|
+
else:
|
|
155
|
+
return {}
|
|
156
|
+
|
|
157
|
+
def count_agents_recursive(data: dict) -> int:
|
|
158
|
+
count = len(data.get('agents', []))
|
|
159
|
+
for subdomain_data in data.get('subdomains', {}).values():
|
|
160
|
+
count += count_agents_recursive(subdomain_data)
|
|
161
|
+
return count
|
|
162
|
+
|
|
163
|
+
subdomains = {}
|
|
164
|
+
for subdomain, data in current_level.items():
|
|
165
|
+
subdomains[subdomain] = count_agents_recursive(data)
|
|
166
|
+
return dict(sorted(subdomains.items()))
|
|
167
|
+
|
|
168
|
+
def count_agents(self) -> int:
|
|
169
|
+
with self.agents_lock:
|
|
170
|
+
return len(self.agents)
|
|
171
|
+
|
|
172
|
+
def get_visible_agents(self, domain_path: str = "", limit: int = 50) -> List[tuple]:
|
|
173
|
+
agents = self.get_agents_by_domain(domain_path)
|
|
174
|
+
return agents[:limit]
|
|
175
|
+
|
|
176
|
+
def save_to_file(self, filepath: str):
|
|
177
|
+
with self.agents_lock:
|
|
178
|
+
agents_dict = {}
|
|
179
|
+
for mac, agent in self.agents.items():
|
|
180
|
+
agents_dict[mac] = {
|
|
181
|
+
'mac': agent.mac, 'id': agent.id, 'domain': agent.domain,
|
|
182
|
+
'ip_public': agent.ip_public, 'ip_local': agent.ip_local,
|
|
183
|
+
'description': agent.description, 'ver': agent.ver,
|
|
184
|
+
'cpu': agent.cpu, 'os': agent.os, 'rotate': agent.rotate,
|
|
185
|
+
'login': agent.login, 'ts': agent.ts, 'frozen': agent.frozen,
|
|
186
|
+
}
|
|
187
|
+
with open(filepath, 'w') as f:
|
|
188
|
+
json.dump(agents_dict, f, indent=4)
|
|
189
|
+
|
|
190
|
+
def load_from_file(self, filepath: str):
|
|
191
|
+
if not exists(filepath):
|
|
192
|
+
return
|
|
193
|
+
with open(filepath, 'r') as f:
|
|
194
|
+
agents_dict = json.load(f)
|
|
195
|
+
with self.agents_lock:
|
|
196
|
+
for mac, agent_data in agents_dict.items():
|
|
197
|
+
self.agents[mac] = AgentData(**agent_data)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""AI Chat module for Remote Scout."""
|
|
2
|
+
|
|
3
|
+
from remotescout.ai.chat_engine import AIChatEngine
|
|
4
|
+
from remotescout.ai.providers import LLMProvider, HttpApiProvider, HttpOllamaProvider, MqttOllamaProvider
|
|
5
|
+
from remotescout.ai.models import ChatMessage, MessageRole, ToolCall, ToolResult
|
|
6
|
+
from remotescout.ai.tools import TOOLS, ToolExecutor
|
|
7
|
+
from remotescout.ai.scripts import ScriptIndex
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AIChatEngine", "LLMProvider", "HttpApiProvider", "HttpOllamaProvider", "MqttOllamaProvider",
|
|
11
|
+
"ChatMessage", "MessageRole", "ToolCall", "ToolResult",
|
|
12
|
+
"TOOLS", "ToolExecutor", "ScriptIndex",
|
|
13
|
+
]
|