sentinel-ai-os 1.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.
- sentinel/__init__.py +0 -0
- sentinel/auth.py +40 -0
- sentinel/cli.py +9 -0
- sentinel/core/__init__.py +0 -0
- sentinel/core/agent.py +298 -0
- sentinel/core/audit.py +48 -0
- sentinel/core/cognitive.py +94 -0
- sentinel/core/config.py +99 -0
- sentinel/core/llm.py +143 -0
- sentinel/core/registry.py +351 -0
- sentinel/core/scheduler.py +61 -0
- sentinel/core/schema.py +11 -0
- sentinel/core/setup.py +101 -0
- sentinel/core/ui.py +112 -0
- sentinel/main.py +110 -0
- sentinel/paths.py +77 -0
- sentinel/tools/__init__.py +0 -0
- sentinel/tools/apps.py +462 -0
- sentinel/tools/audio.py +30 -0
- sentinel/tools/browser.py +66 -0
- sentinel/tools/calendar_ops.py +163 -0
- sentinel/tools/clock.py +25 -0
- sentinel/tools/context.py +40 -0
- sentinel/tools/desktop.py +116 -0
- sentinel/tools/email_ops.py +62 -0
- sentinel/tools/factory.py +125 -0
- sentinel/tools/file_ops.py +81 -0
- sentinel/tools/flights.py +62 -0
- sentinel/tools/gmail_auth.py +47 -0
- sentinel/tools/indexer.py +156 -0
- sentinel/tools/installer.py +69 -0
- sentinel/tools/macros.py +58 -0
- sentinel/tools/memory_ops.py +281 -0
- sentinel/tools/navigation.py +109 -0
- sentinel/tools/notes.py +78 -0
- sentinel/tools/office.py +67 -0
- sentinel/tools/organizer.py +150 -0
- sentinel/tools/smart_index.py +76 -0
- sentinel/tools/sql_index.py +186 -0
- sentinel/tools/system_ops.py +86 -0
- sentinel/tools/vision.py +94 -0
- sentinel/tools/weather_ops.py +59 -0
- sentinel_ai_os-1.0.dist-info/METADATA +282 -0
- sentinel_ai_os-1.0.dist-info/RECORD +48 -0
- sentinel_ai_os-1.0.dist-info/WHEEL +5 -0
- sentinel_ai_os-1.0.dist-info/entry_points.txt +2 -0
- sentinel_ai_os-1.0.dist-info/licenses/LICENSE +21 -0
- sentinel_ai_os-1.0.dist-info/top_level.txt +1 -0
sentinel/tools/apps.py
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from sentinel.tools.smart_index import index_file
|
|
3
|
+
import shutil
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
import webbrowser
|
|
7
|
+
import difflib
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
# Ensure APP_CACHE exists (from your global scope)
|
|
11
|
+
APP_CACHE = globals().get("APP_CACHE", {})
|
|
12
|
+
|
|
13
|
+
def _native_open(target):
|
|
14
|
+
"""
|
|
15
|
+
Cross-platform helper to open a file, folder, or URI using the OS default handler.
|
|
16
|
+
"""
|
|
17
|
+
system = platform.system()
|
|
18
|
+
try:
|
|
19
|
+
if system == "Windows":
|
|
20
|
+
os.startfile(target)
|
|
21
|
+
elif system == "Darwin": # macOS
|
|
22
|
+
subprocess.Popen(["open", target])
|
|
23
|
+
else: # Linux / Unix
|
|
24
|
+
subprocess.Popen(["xdg-open", target])
|
|
25
|
+
return True
|
|
26
|
+
except Exception as e:
|
|
27
|
+
logging.error(f"Native open failed for {target}: {e}")
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _run_command(cmd_str):
|
|
32
|
+
"""
|
|
33
|
+
Safely runs a command string cross-platform.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
# On Windows, shell=True is often needed for system aliases (like 'dir' or 'start')
|
|
37
|
+
# On Unix, we prefer passing a list, but for complex aliases we use shell=True safely.
|
|
38
|
+
subprocess.Popen(cmd_str, shell=True)
|
|
39
|
+
return True
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logging.error(f"Command failed {cmd_str}: {e}")
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_os_aliases():
|
|
46
|
+
"""
|
|
47
|
+
Returns a dictionary of app aliases specific to the current operating system.
|
|
48
|
+
"""
|
|
49
|
+
system = platform.system()
|
|
50
|
+
|
|
51
|
+
# 1. Common Aliases (Work if binary is in PATH)
|
|
52
|
+
aliases = {
|
|
53
|
+
"code": "code",
|
|
54
|
+
"python": "python",
|
|
55
|
+
"git": "git",
|
|
56
|
+
"firefox": "firefox",
|
|
57
|
+
"vlc": "vlc",
|
|
58
|
+
"discord": "discord",
|
|
59
|
+
"spotify": "spotify",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# 2. Windows Specifics
|
|
63
|
+
if system == "Windows":
|
|
64
|
+
return {**aliases, **{
|
|
65
|
+
# --- BROWSERS ---
|
|
66
|
+
"chrome": "chrome",
|
|
67
|
+
"google chrome": "chrome",
|
|
68
|
+
"firefox": "firefox",
|
|
69
|
+
"edge": "msedge",
|
|
70
|
+
"microsoft edge": "msedge",
|
|
71
|
+
"brave": "brave",
|
|
72
|
+
"opera": "opera",
|
|
73
|
+
|
|
74
|
+
# --- MICROSOFT OFFICE ---
|
|
75
|
+
"word": "winword",
|
|
76
|
+
"excel": "excel",
|
|
77
|
+
"powerpoint": "powerpnt",
|
|
78
|
+
"ppt": "powerpnt",
|
|
79
|
+
"outlook": "outlook",
|
|
80
|
+
"onenote": "onenote",
|
|
81
|
+
"teams": "ms-teams:", # Deep link is often more reliable than exe
|
|
82
|
+
"access": "msaccess",
|
|
83
|
+
|
|
84
|
+
# --- SYSTEM TOOLS ---
|
|
85
|
+
"explorer": "explorer",
|
|
86
|
+
"file explorer": "explorer",
|
|
87
|
+
"settings": "start ms-settings:",
|
|
88
|
+
"control panel": "control",
|
|
89
|
+
"task manager": "taskmgr",
|
|
90
|
+
"cmd": "start cmd", # 'start' ensures it opens a NEW window
|
|
91
|
+
"command prompt": "start cmd",
|
|
92
|
+
"terminal": "wt", # Windows Terminal
|
|
93
|
+
"powershell": "start powershell",
|
|
94
|
+
"system info": "msinfo32",
|
|
95
|
+
"registry": "regedit",
|
|
96
|
+
"services": "services.msc",
|
|
97
|
+
|
|
98
|
+
# --- DIRECTORY SHORTCUTS ---
|
|
99
|
+
"downloads": "explorer shell:downloads",
|
|
100
|
+
"documents": "explorer shell:personal",
|
|
101
|
+
"desktop": "explorer shell:desktop",
|
|
102
|
+
"pictures": "explorer shell:my pictures",
|
|
103
|
+
"startup": "explorer shell:startup",
|
|
104
|
+
|
|
105
|
+
# --- DEV TOOLS ---
|
|
106
|
+
"code": "code",
|
|
107
|
+
"vscode": "code",
|
|
108
|
+
"visual studio": "devenv",
|
|
109
|
+
"pycharm": "pycharm64",
|
|
110
|
+
"intellij": "idea64",
|
|
111
|
+
"docker": "start \"Docker Desktop\" \"C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe\"",
|
|
112
|
+
# Hard path often needed
|
|
113
|
+
"wsl": "wsl",
|
|
114
|
+
"git": "git-bash",
|
|
115
|
+
|
|
116
|
+
# --- MEDIA & UTILITIES ---
|
|
117
|
+
"spotify": "spotify",
|
|
118
|
+
"vlc": "vlc",
|
|
119
|
+
"notepad": "notepad",
|
|
120
|
+
"paint": "mspaint",
|
|
121
|
+
"calc": "calc",
|
|
122
|
+
"calculator": "calc",
|
|
123
|
+
"snipping tool": "snippingtool",
|
|
124
|
+
"screenshot": "snippingtool", # Logic alias
|
|
125
|
+
"photos": "ms-photos:",
|
|
126
|
+
"camera": "microsoft.windows.camera:",
|
|
127
|
+
"clock": "ms-clock:",
|
|
128
|
+
"alarms": "ms-clock:",
|
|
129
|
+
"weather": "ms-weather:",
|
|
130
|
+
"maps": "bingmaps:",
|
|
131
|
+
|
|
132
|
+
# --- COMMUNICATION ---
|
|
133
|
+
"discord": "discord",
|
|
134
|
+
"slack": "slack",
|
|
135
|
+
"whatsapp": "start whatsapp:",
|
|
136
|
+
"zoom": "zoom",
|
|
137
|
+
"telegram": "telegram",
|
|
138
|
+
|
|
139
|
+
# --- STORE & UWP ---
|
|
140
|
+
"store": "ms-windows-store:",
|
|
141
|
+
"mail": "outlookmail:",
|
|
142
|
+
"calendar": "outlookcal:",
|
|
143
|
+
"xbox": "xbox:",
|
|
144
|
+
}}
|
|
145
|
+
|
|
146
|
+
# 3. macOS Specifics (Darwin)
|
|
147
|
+
# 3. macOS Specifics (Darwin)
|
|
148
|
+
elif system == "Darwin":
|
|
149
|
+
return {**aliases, **{
|
|
150
|
+
# --- BROWSERS ---
|
|
151
|
+
"chrome": "open -a 'Google Chrome'",
|
|
152
|
+
"google chrome": "open -a 'Google Chrome'",
|
|
153
|
+
"firefox": "open -a Firefox",
|
|
154
|
+
"safari": "open -a Safari",
|
|
155
|
+
"edge": "open -a 'Microsoft Edge'",
|
|
156
|
+
"brave": "open -a 'Brave Browser'",
|
|
157
|
+
"opera": "open -a Opera",
|
|
158
|
+
"arc": "open -a Arc",
|
|
159
|
+
|
|
160
|
+
# --- MICROSOFT OFFICE ---
|
|
161
|
+
"word": "open -a 'Microsoft Word'",
|
|
162
|
+
"excel": "open -a 'Microsoft Excel'",
|
|
163
|
+
"powerpoint": "open -a 'Microsoft PowerPoint'",
|
|
164
|
+
"onenote": "open -a 'Microsoft OneNote'",
|
|
165
|
+
"outlook": "open -a 'Microsoft Outlook'",
|
|
166
|
+
"teams": "open -a 'Microsoft Teams'",
|
|
167
|
+
|
|
168
|
+
# --- DEV TOOLS ---
|
|
169
|
+
"vscode": "open -a 'Visual Studio Code'",
|
|
170
|
+
"code": "open -a 'Visual Studio Code'", # Fallback if 'code' CLI isn't in PATH
|
|
171
|
+
"sublime": "open -a 'Sublime Text'",
|
|
172
|
+
"iterm": "open -a iTerm",
|
|
173
|
+
"terminal": "open -a Terminal",
|
|
174
|
+
"docker": "open -a Docker",
|
|
175
|
+
"pycharm": "open -a 'PyCharm CE'", # Or 'PyCharm' depending on edition
|
|
176
|
+
"intellij": "open -a 'IntelliJ IDEA CE'",
|
|
177
|
+
"xcode": "open -a Xcode",
|
|
178
|
+
|
|
179
|
+
# --- COMMUNICATION ---
|
|
180
|
+
"slack": "open -a Slack",
|
|
181
|
+
"discord": "open -a Discord",
|
|
182
|
+
"whatsapp": "open -a WhatsApp",
|
|
183
|
+
"telegram": "open -a Telegram",
|
|
184
|
+
"signal": "open -a Signal",
|
|
185
|
+
"zoom": "open -a zoom.us", # Zoom internal name is often zoom.us
|
|
186
|
+
"messages": "open -a Messages",
|
|
187
|
+
"imessage": "open -a Messages",
|
|
188
|
+
"mail": "open -a Mail",
|
|
189
|
+
|
|
190
|
+
# --- CREATIVE & MEDIA ---
|
|
191
|
+
"spotify": "open -a Spotify",
|
|
192
|
+
"vlc": "open -a VLC",
|
|
193
|
+
"quicktime": "open -a 'QuickTime Player'",
|
|
194
|
+
"preview": "open -a Preview",
|
|
195
|
+
"photos": "open -a Photos",
|
|
196
|
+
"photoshop": "open -a 'Adobe Photoshop'",
|
|
197
|
+
"premiere": "open -a 'Adobe Premiere Pro'",
|
|
198
|
+
|
|
199
|
+
# --- PRODUCTIVITY ---
|
|
200
|
+
"notion": "open -a Notion",
|
|
201
|
+
"obsidian": "open -a Obsidian",
|
|
202
|
+
"evernote": "open -a Evernote",
|
|
203
|
+
"notes": "open -a Notes",
|
|
204
|
+
"reminders": "open -a Reminders",
|
|
205
|
+
"calendar": "open -a Calendar",
|
|
206
|
+
"contacts": "open -a Contacts",
|
|
207
|
+
"maps": "open -a Maps",
|
|
208
|
+
|
|
209
|
+
# --- SYSTEM UTILITIES ---
|
|
210
|
+
"finder": "open .",
|
|
211
|
+
"explorer": "open .", # For Windows muscle memory
|
|
212
|
+
"settings": "open -b com.apple.systempreferences", # Robust bundle ID method
|
|
213
|
+
"preferences": "open -b com.apple.systempreferences",
|
|
214
|
+
"app store": "open -a 'App Store'",
|
|
215
|
+
"activity monitor": "open -a 'Activity Monitor'",
|
|
216
|
+
"task manager": "open -a 'Activity Monitor'", # Windows muscle memory
|
|
217
|
+
"disk utility": "open -a 'Disk Utility'",
|
|
218
|
+
"calculator": "open -a Calculator",
|
|
219
|
+
"calc": "open -a Calculator",
|
|
220
|
+
"screenshot": "open -a Screenshot",
|
|
221
|
+
"textedit": "open -a TextEdit",
|
|
222
|
+
"facetime": "open -a FaceTime",
|
|
223
|
+
}}
|
|
224
|
+
|
|
225
|
+
# 4. Linux Specifics
|
|
226
|
+
else:
|
|
227
|
+
return {**aliases, **{
|
|
228
|
+
# --- BROWSERS ---
|
|
229
|
+
"chrome": "google-chrome",
|
|
230
|
+
"chromium": "chromium-browser",
|
|
231
|
+
"firefox": "firefox",
|
|
232
|
+
"brave": "brave-browser",
|
|
233
|
+
"edge": "microsoft-edge", # Yes, it exists on Linux!
|
|
234
|
+
"opera": "opera",
|
|
235
|
+
|
|
236
|
+
# --- OFFICE (LibreOffice Suite) ---
|
|
237
|
+
"word": "libreoffice --writer",
|
|
238
|
+
"writer": "libreoffice --writer",
|
|
239
|
+
"excel": "libreoffice --calc",
|
|
240
|
+
"calc": "libreoffice --calc",
|
|
241
|
+
"powerpoint": "libreoffice --impress",
|
|
242
|
+
"impress": "libreoffice --impress",
|
|
243
|
+
"teams": "teams-for-linux", # Common community wrapper
|
|
244
|
+
|
|
245
|
+
# --- DEV TOOLS ---
|
|
246
|
+
"vscode": "code",
|
|
247
|
+
"code": "code",
|
|
248
|
+
"sublime": "subl",
|
|
249
|
+
"vim": "vim",
|
|
250
|
+
"nano": "nano",
|
|
251
|
+
"docker": "docker",
|
|
252
|
+
# "terminal" usually defaults to x-terminal-emulator,
|
|
253
|
+
# but we explicitly try common ones if that fails:
|
|
254
|
+
"terminal": "gnome-terminal",
|
|
255
|
+
"konsole": "konsole",
|
|
256
|
+
|
|
257
|
+
# --- COMMUNICATION ---
|
|
258
|
+
"discord": "discord",
|
|
259
|
+
"slack": "slack",
|
|
260
|
+
"telegram": "telegram-desktop",
|
|
261
|
+
"signal": "signal-desktop",
|
|
262
|
+
"zoom": "zoom",
|
|
263
|
+
"skype": "skypeforlinux",
|
|
264
|
+
|
|
265
|
+
# --- MEDIA ---
|
|
266
|
+
"vlc": "vlc",
|
|
267
|
+
"spotify": "spotify",
|
|
268
|
+
"rhythmbox": "rhythmbox",
|
|
269
|
+
"mpv": "mpv",
|
|
270
|
+
"photos": "eog", # Eye of GNOME (common image viewer)
|
|
271
|
+
"gimp": "gimp",
|
|
272
|
+
"obs": "obs",
|
|
273
|
+
|
|
274
|
+
# --- SYSTEM UTILITIES ---
|
|
275
|
+
"explorer": "xdg-open .",
|
|
276
|
+
"finder": "xdg-open .",
|
|
277
|
+
"nautilus": "nautilus", # GNOME Files
|
|
278
|
+
"dolphin": "dolphin", # KDE Files
|
|
279
|
+
"thunar": "thunar", # XFCE Files
|
|
280
|
+
|
|
281
|
+
# "Settings" varies wildly, but these covers 80% of users:
|
|
282
|
+
"settings": "gnome-control-center",
|
|
283
|
+
"control panel": "gnome-control-center",
|
|
284
|
+
|
|
285
|
+
# Task Manager equivalents
|
|
286
|
+
"task manager": "gnome-system-monitor",
|
|
287
|
+
"system monitor": "gnome-system-monitor",
|
|
288
|
+
"htop": "x-terminal-emulator -e htop", # Opens htop in a new window
|
|
289
|
+
|
|
290
|
+
"calculator": "gnome-calculator",
|
|
291
|
+
"screenshot": "gnome-screenshot",
|
|
292
|
+
"textedit": "gedit",
|
|
293
|
+
"gedit": "gedit",
|
|
294
|
+
}}
|
|
295
|
+
|
|
296
|
+
def refresh_app_cache():
|
|
297
|
+
"""
|
|
298
|
+
Cross-platform indexer.
|
|
299
|
+
Scans system directories to build a map of {app_name: path}.
|
|
300
|
+
"""
|
|
301
|
+
global APP_CACHE
|
|
302
|
+
APP_CACHE = {}
|
|
303
|
+
system = platform.system()
|
|
304
|
+
|
|
305
|
+
# --- WINDOWS INDEXING ---
|
|
306
|
+
if system == "Windows":
|
|
307
|
+
paths = [
|
|
308
|
+
r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs",
|
|
309
|
+
os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
|
|
310
|
+
]
|
|
311
|
+
for path in paths:
|
|
312
|
+
if not os.path.exists(path): continue
|
|
313
|
+
for root, _, files in os.walk(path):
|
|
314
|
+
for file in files:
|
|
315
|
+
if file.lower().endswith(".lnk"):
|
|
316
|
+
name = file.lower().replace(".lnk", "")
|
|
317
|
+
APP_CACHE[name] = os.path.join(root, file)
|
|
318
|
+
|
|
319
|
+
# --- MACOS INDEXING ---
|
|
320
|
+
elif system == "Darwin":
|
|
321
|
+
# Scan standard application folders
|
|
322
|
+
app_dirs = ["/Applications", "/System/Applications", os.path.expanduser("~/Applications")]
|
|
323
|
+
for app_dir in app_dirs:
|
|
324
|
+
if not os.path.exists(app_dir): continue
|
|
325
|
+
try:
|
|
326
|
+
# macOS apps are folders ending in .app
|
|
327
|
+
for item in os.listdir(app_dir):
|
|
328
|
+
if item.endswith(".app"):
|
|
329
|
+
name = item.replace(".app", "").lower()
|
|
330
|
+
full_path = os.path.join(app_dir, item)
|
|
331
|
+
APP_CACHE[name] = full_path
|
|
332
|
+
except PermissionError:
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
# --- LINUX INDEXING ---
|
|
336
|
+
elif system == "Linux":
|
|
337
|
+
# Scan for .desktop files
|
|
338
|
+
desktop_dirs = ["/usr/share/applications", os.path.expanduser("~/.local/share/applications")]
|
|
339
|
+
for d_dir in desktop_dirs:
|
|
340
|
+
if not os.path.exists(d_dir): continue
|
|
341
|
+
try:
|
|
342
|
+
for item in os.listdir(d_dir):
|
|
343
|
+
if item.endswith(".desktop"):
|
|
344
|
+
name = item.replace(".desktop", "").lower()
|
|
345
|
+
full_path = os.path.join(d_dir, item)
|
|
346
|
+
# We map the simple name to the .desktop file
|
|
347
|
+
# xdg-open handles .desktop files automatically
|
|
348
|
+
APP_CACHE[name] = full_path
|
|
349
|
+
except PermissionError:
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# Run indexing on import
|
|
354
|
+
refresh_app_cache()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def list_all_apps():
|
|
358
|
+
"""Returns a list of all indexed applications."""
|
|
359
|
+
if not APP_CACHE:
|
|
360
|
+
return "No apps found in index. (Is this Windows?)"
|
|
361
|
+
|
|
362
|
+
# Sort alphabetically
|
|
363
|
+
sorted_apps = sorted(APP_CACHE.keys())
|
|
364
|
+
return "Indexed Apps:\n" + "\n".join([f"- {app}" for app in sorted_apps])
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def open_app(name):
|
|
368
|
+
"""
|
|
369
|
+
Smart Launcher (Cross-Platform).
|
|
370
|
+
Priority:
|
|
371
|
+
1. Direct Path (Absolute paths)
|
|
372
|
+
2. System Aliases (OS-specific shortcuts)
|
|
373
|
+
3. Dynamic App Cache (Fuzzy Match)
|
|
374
|
+
4. Web URL fallback
|
|
375
|
+
"""
|
|
376
|
+
name = name.strip()
|
|
377
|
+
lower_name = name.lower()
|
|
378
|
+
|
|
379
|
+
# --- 1. DIRECT PATH ---
|
|
380
|
+
# If the user provides a full path that exists
|
|
381
|
+
if os.path.exists(name):
|
|
382
|
+
# Optional: index_file(name) if available in your scope
|
|
383
|
+
if 'index_file' in globals(): globals()['index_file'](name)
|
|
384
|
+
|
|
385
|
+
if _native_open(name):
|
|
386
|
+
return f"Launched file: {name}"
|
|
387
|
+
|
|
388
|
+
# --- 2. SYSTEM ALIASES ---
|
|
389
|
+
aliases = _get_os_aliases()
|
|
390
|
+
|
|
391
|
+
if lower_name in aliases:
|
|
392
|
+
cmd = aliases[lower_name]
|
|
393
|
+
if _run_command(cmd):
|
|
394
|
+
return f"Launched system tool: {lower_name}"
|
|
395
|
+
return f"Failed to launch alias: {lower_name}"
|
|
396
|
+
|
|
397
|
+
# --- 3. DYNAMIC SEARCH (Fuzzy Match) ---
|
|
398
|
+
# Works if APP_CACHE is populated (Windows mostly, unless you add Linux/Mac indexers)
|
|
399
|
+
if APP_CACHE:
|
|
400
|
+
# Exact Match
|
|
401
|
+
if lower_name in APP_CACHE:
|
|
402
|
+
path = APP_CACHE[lower_name]
|
|
403
|
+
_native_open(path)
|
|
404
|
+
return f"Launched {lower_name} from Index."
|
|
405
|
+
|
|
406
|
+
# Fuzzy Match
|
|
407
|
+
matches = difflib.get_close_matches(lower_name, APP_CACHE.keys(), n=1, cutoff=0.6)
|
|
408
|
+
if matches:
|
|
409
|
+
best_match = matches[0]
|
|
410
|
+
path = APP_CACHE[best_match]
|
|
411
|
+
_native_open(path)
|
|
412
|
+
return f"Launched {best_match} (matched to '{name}')"
|
|
413
|
+
|
|
414
|
+
# --- 4. EXECUTABLE IN PATH ---
|
|
415
|
+
# Check if 'name' is a command available in the system PATH (e.g., 'npm', 'docker')
|
|
416
|
+
if shutil.which(lower_name):
|
|
417
|
+
_run_command(lower_name)
|
|
418
|
+
return f"Executed command: {lower_name}"
|
|
419
|
+
|
|
420
|
+
# --- 5. WEB URL FALLBACK ---
|
|
421
|
+
# If it looks like a URL or domain
|
|
422
|
+
if "." in name and " " not in name:
|
|
423
|
+
url = name if name.startswith(("http://", "https://")) else f"https://{name}"
|
|
424
|
+
webbrowser.open(url)
|
|
425
|
+
return f"Opened website: {url}"
|
|
426
|
+
|
|
427
|
+
return f"Could not find app, file, or command: '{name}'"
|
|
428
|
+
|
|
429
|
+
def play_music(song_name):
|
|
430
|
+
"""Plays music on YouTube Music."""
|
|
431
|
+
query = song_name.replace(" ", "+")
|
|
432
|
+
webbrowser.open(f"https://music.youtube.com/search?q={query}")
|
|
433
|
+
return f"Playing {song_name}..."
|
|
434
|
+
|
|
435
|
+
def close_app(name):
|
|
436
|
+
"""
|
|
437
|
+
Attempts to close a running application by name.
|
|
438
|
+
Uses graceful terminate first, then force kill if needed.
|
|
439
|
+
"""
|
|
440
|
+
name = name.lower().strip()
|
|
441
|
+
|
|
442
|
+
system = platform.system()
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
if system == "Windows":
|
|
446
|
+
# Try graceful close first
|
|
447
|
+
subprocess.call(f"taskkill /IM {name}.exe", shell=True)
|
|
448
|
+
return f"Closed application: {name}"
|
|
449
|
+
|
|
450
|
+
elif system == "Darwin": # macOS
|
|
451
|
+
subprocess.call(["pkill", "-f", name])
|
|
452
|
+
return f"Closed application: {name}"
|
|
453
|
+
|
|
454
|
+
elif system == "Linux":
|
|
455
|
+
subprocess.call(["pkill", "-f", name])
|
|
456
|
+
return f"Closed application: {name}"
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
return "Unsupported OS."
|
|
460
|
+
|
|
461
|
+
except Exception as e:
|
|
462
|
+
return f"Failed to close app '{name}': {e}"
|
sentinel/tools/audio.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import speech_recognition as sr
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def listen(timeout=5):
|
|
6
|
+
def _listen():
|
|
7
|
+
recognizer = sr.Recognizer()
|
|
8
|
+
with sr.Microphone() as source:
|
|
9
|
+
recognizer.adjust_for_ambient_noise(source, duration=0.5)
|
|
10
|
+
audio = recognizer.listen(source, timeout=timeout, phrase_time_limit=8)
|
|
11
|
+
return recognizer.recognize_google(audio)
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import concurrent.futures
|
|
15
|
+
with concurrent.futures.ThreadPoolExecutor() as ex:
|
|
16
|
+
future = ex.submit(_listen)
|
|
17
|
+
return f"User said: '{future.result(timeout=timeout+2)}'"
|
|
18
|
+
except concurrent.futures.TimeoutError:
|
|
19
|
+
return "Voice timeout."
|
|
20
|
+
except Exception as e:
|
|
21
|
+
return f"Voice error: {e}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def listen_background(callback):
|
|
26
|
+
"""
|
|
27
|
+
Starts a background listener for a hotword (Advanced).
|
|
28
|
+
"""
|
|
29
|
+
# This requires a loop and is best left for Phase 2
|
|
30
|
+
pass
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# FILE: tools/browser.py
|
|
2
|
+
import requests
|
|
3
|
+
from bs4 import BeautifulSoup
|
|
4
|
+
from ddgs import DDGS
|
|
5
|
+
from sentinel.core.config import ConfigManager
|
|
6
|
+
|
|
7
|
+
cfg = ConfigManager()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def search_web(query):
|
|
11
|
+
"""
|
|
12
|
+
Smart Search: Tries Tavily (Advanced RAG), falls back to DuckDuckGo.
|
|
13
|
+
"""
|
|
14
|
+
# 1. Try Tavily
|
|
15
|
+
tavily_key = cfg.get_key("tavily")
|
|
16
|
+
if tavily_key:
|
|
17
|
+
try:
|
|
18
|
+
from tavily import TavilyClient
|
|
19
|
+
client = TavilyClient(api_key=tavily_key)
|
|
20
|
+
# 'search_context' returns optimized text for AI
|
|
21
|
+
response = client.get_search_context(query=query, search_depth="basic", max_tokens=1500)
|
|
22
|
+
return f"[Source: Tavily]\n{response}"
|
|
23
|
+
except ImportError:
|
|
24
|
+
pass
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
# 2. Fallback to DuckDuckGo
|
|
29
|
+
try:
|
|
30
|
+
results = DDGS().text(query, max_results=4)
|
|
31
|
+
summary = []
|
|
32
|
+
for r in results:
|
|
33
|
+
summary.append(f"Title: {r.get('title')}\nLink: {r.get('href')}\nSnippet: {r.get('body')}\n")
|
|
34
|
+
|
|
35
|
+
if not summary:
|
|
36
|
+
return "No results found on DuckDuckGo."
|
|
37
|
+
return "[Source: DuckDuckGo]\n" + "\n".join(summary)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return f"Search completely failed: {e}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def open_url(url):
|
|
43
|
+
"""Scrapes text content from a URL."""
|
|
44
|
+
try:
|
|
45
|
+
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
|
|
46
|
+
resp = requests.get(url, headers=headers, timeout=10)
|
|
47
|
+
|
|
48
|
+
if resp.status_code != 200:
|
|
49
|
+
return f"Error: Status code {resp.status_code}"
|
|
50
|
+
|
|
51
|
+
soup = BeautifulSoup(resp.content, 'html.parser')
|
|
52
|
+
for script in soup(["script", "style", "nav", "footer"]):
|
|
53
|
+
script.extract()
|
|
54
|
+
|
|
55
|
+
text = soup.get_text()
|
|
56
|
+
lines = (line.strip() for line in text.splitlines())
|
|
57
|
+
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
|
58
|
+
clean_text = '\n'.join(chunk for chunk in chunks if chunk)
|
|
59
|
+
|
|
60
|
+
return clean_text[:4000] + "..."
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return f"Error reading page: {e}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_webpage(url):
|
|
66
|
+
return open_url(url)
|