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.
Files changed (48) hide show
  1. sentinel/__init__.py +0 -0
  2. sentinel/auth.py +40 -0
  3. sentinel/cli.py +9 -0
  4. sentinel/core/__init__.py +0 -0
  5. sentinel/core/agent.py +298 -0
  6. sentinel/core/audit.py +48 -0
  7. sentinel/core/cognitive.py +94 -0
  8. sentinel/core/config.py +99 -0
  9. sentinel/core/llm.py +143 -0
  10. sentinel/core/registry.py +351 -0
  11. sentinel/core/scheduler.py +61 -0
  12. sentinel/core/schema.py +11 -0
  13. sentinel/core/setup.py +101 -0
  14. sentinel/core/ui.py +112 -0
  15. sentinel/main.py +110 -0
  16. sentinel/paths.py +77 -0
  17. sentinel/tools/__init__.py +0 -0
  18. sentinel/tools/apps.py +462 -0
  19. sentinel/tools/audio.py +30 -0
  20. sentinel/tools/browser.py +66 -0
  21. sentinel/tools/calendar_ops.py +163 -0
  22. sentinel/tools/clock.py +25 -0
  23. sentinel/tools/context.py +40 -0
  24. sentinel/tools/desktop.py +116 -0
  25. sentinel/tools/email_ops.py +62 -0
  26. sentinel/tools/factory.py +125 -0
  27. sentinel/tools/file_ops.py +81 -0
  28. sentinel/tools/flights.py +62 -0
  29. sentinel/tools/gmail_auth.py +47 -0
  30. sentinel/tools/indexer.py +156 -0
  31. sentinel/tools/installer.py +69 -0
  32. sentinel/tools/macros.py +58 -0
  33. sentinel/tools/memory_ops.py +281 -0
  34. sentinel/tools/navigation.py +109 -0
  35. sentinel/tools/notes.py +78 -0
  36. sentinel/tools/office.py +67 -0
  37. sentinel/tools/organizer.py +150 -0
  38. sentinel/tools/smart_index.py +76 -0
  39. sentinel/tools/sql_index.py +186 -0
  40. sentinel/tools/system_ops.py +86 -0
  41. sentinel/tools/vision.py +94 -0
  42. sentinel/tools/weather_ops.py +59 -0
  43. sentinel_ai_os-1.0.dist-info/METADATA +282 -0
  44. sentinel_ai_os-1.0.dist-info/RECORD +48 -0
  45. sentinel_ai_os-1.0.dist-info/WHEEL +5 -0
  46. sentinel_ai_os-1.0.dist-info/entry_points.txt +2 -0
  47. sentinel_ai_os-1.0.dist-info/licenses/LICENSE +21 -0
  48. sentinel_ai_os-1.0.dist-info/top_level.txt +1 -0
sentinel/core/llm.py ADDED
@@ -0,0 +1,143 @@
1
+ # FILE: core/llm.py
2
+
3
+ import os
4
+ from openai import OpenAI
5
+ import anthropic
6
+ from sentinel.core.ui import UI
7
+ from sentinel.core.audit import audit
8
+ import time
9
+
10
+ try:
11
+ from groq import Groq
12
+ except ImportError:
13
+ Groq = None
14
+
15
+
16
+ class LLMEngine:
17
+ def __init__(self, config_manager, verbose=True):
18
+ """
19
+ Args:
20
+ verbose (bool): If True, prints "Brain Loaded" on init.
21
+ Set False for background tasks.
22
+ """
23
+ self.cfg_manager = config_manager
24
+ self.provider = None
25
+ self.model = None
26
+ self.api_key = None
27
+ self.reload_config(verbose=verbose)
28
+
29
+ def reload_config(self, verbose=False):
30
+ """Reloads settings and credentials from the config manager."""
31
+ settings = self.cfg_manager.load()
32
+ llm_settings = settings.get("llm", {})
33
+
34
+ self.provider = llm_settings.get("provider", "openai").lower()
35
+ self.model = llm_settings.get("model", "gpt-4o")
36
+ self.api_key = self.cfg_manager.get_key(self.provider)
37
+
38
+ if verbose:
39
+ is_ready = self.api_key is not None or self.provider == "ollama"
40
+ if is_ready:
41
+ # We can comment this out if we want it strictly in the UI panel now
42
+ # UI.print_system(f"Brain Loaded: [bold cyan]{self.provider.upper()}[/bold cyan] | Model: [bold white]{self.model}[/bold white]")
43
+ pass
44
+
45
+ def stream_query(self, system_prompt, history):
46
+ # Hot-reload config (Silent)
47
+ self.reload_config(verbose=False)
48
+
49
+ if not self.api_key and self.provider != "ollama":
50
+ yield f"\n[bold red]Error:[/bold red] No API key found for '{self.provider}'.\n"
51
+ yield f"Please run: [cyan]/setkey {self.provider} YOUR_KEY_HERE[/cyan]"
52
+ return
53
+
54
+ # --- PREPARE MESSAGES ---
55
+ # 1. System Prompt is separate
56
+ messages = []
57
+ for msg in history:
58
+ # Anthropic hates "system" role in messages list
59
+ if msg["role"] != "system":
60
+ messages.append(msg)
61
+
62
+ try:
63
+ if self.provider == "groq":
64
+ if not Groq:
65
+ yield "Error: 'groq' library not installed. Run 'pip install groq'."
66
+ return
67
+ # Groq (via OpenAI client) EXPECTS system message in list
68
+ groq_msgs = [{"role": "system", "content": system_prompt}] + history
69
+
70
+ client = Groq(api_key=self.api_key)
71
+ stream = client.chat.completions.create(
72
+ messages=groq_msgs, model=self.model, temperature=0.1, stream=True
73
+ )
74
+ for chunk in stream:
75
+ if chunk.choices[0].delta.content:
76
+ yield chunk.choices[0].delta.content
77
+
78
+ elif self.provider == "openai":
79
+ # OpenAI EXPECTS system message in list
80
+ openai_msgs = [{"role": "system", "content": system_prompt}] + history
81
+
82
+ client = OpenAI(api_key=self.api_key)
83
+ stream = client.chat.completions.create(
84
+ model=self.model, messages=openai_msgs, temperature=0.1, stream=True
85
+ )
86
+ for chunk in stream:
87
+ if chunk.choices[0].delta.content:
88
+ yield chunk.choices[0].delta.content
89
+
90
+ elif self.provider == "anthropic":
91
+ # --- ANTHROPIC SPECIFIC FIX ---
92
+ # Pass 'system' as a top-level parameter
93
+ client = anthropic.Anthropic(api_key=self.api_key)
94
+
95
+ with client.messages.stream(
96
+ max_tokens=4096,
97
+ system=system_prompt,
98
+ messages=messages,
99
+ model=self.model
100
+ ) as stream:
101
+ for text in stream.text_stream:
102
+ yield text
103
+
104
+ elif self.provider == "ollama":
105
+ # Ollama likes system message in list
106
+ ollama_msgs = [{"role": "system", "content": system_prompt}] + history
107
+ import requests
108
+ import json
109
+ payload = {"model": self.model, "messages": ollama_msgs, "stream": True}
110
+ with requests.post("http://localhost:11434/api/chat", json=payload, stream=True) as r:
111
+ for line in r.iter_lines():
112
+ if line:
113
+ body = json.loads(line)
114
+ if "message" in body and "content" in body["message"]:
115
+ yield body["message"]["content"]
116
+
117
+ except Exception as e:
118
+ error_str = str(e)
119
+ if "401" in error_str or "invalid_api_key" in error_str:
120
+ yield f"\n[bold red]🔑 Authentication Failed:[/bold red] The API key for [cyan]{self.provider.upper()}[/cyan] is invalid or expired."
121
+ elif "429" in error_str or "rate_limit_exceeded" in error_str:
122
+ yield f"\n[bold yellow]⏳ Rate Limit Reached:[/bold yellow] {self.provider.upper()} is busy."
123
+ else:
124
+ yield f"\n[bold red]System Error ({self.provider}):[/bold red] {error_str}"
125
+
126
+ def query(self, system_prompt, history):
127
+ start_time = time.time()
128
+ full_response = ""
129
+ for token in self.stream_query(system_prompt, history):
130
+ full_response += token
131
+
132
+ duration = (time.time() - start_time) * 1000
133
+
134
+ audit.log_event(
135
+ event_type="LLM_QUERY",
136
+ provider=self.provider,
137
+ model=self.model,
138
+ input_data=[{"role": "system", "content": system_prompt}] + history,
139
+ output_data=full_response,
140
+ duration_ms=duration
141
+ )
142
+
143
+ return full_response
@@ -0,0 +1,351 @@
1
+ # FILE: core/registry.py
2
+
3
+ import platform
4
+ import datetime
5
+ import threading
6
+ import os
7
+ from sentinel.core.config import ConfigManager
8
+ import schedule
9
+ from sentinel.tools.smart_index import smart_find
10
+
11
+ # Import all tools
12
+ from sentinel.tools import (
13
+ apps, browser, clock, email_ops, file_ops, indexer, notes, office,
14
+ system_ops, navigation, flights, sql_index, desktop, vision, audio,
15
+ context, calendar_ops, memory_ops, weather_ops, organizer, macros,
16
+ factory, installer
17
+ )
18
+ from sentinel.core import scheduler, cognitive
19
+
20
+ # --- DETECT OS ---
21
+ CURRENT_OS = platform.system()
22
+ OS_VERSION = platform.release()
23
+ NOW = datetime.datetime.now()
24
+
25
+ # --- LOAD CONFIG ---
26
+ cfg = ConfigManager()
27
+ settings = cfg.load() if cfg.exists() else {}
28
+
29
+
30
+ def initialize_tools():
31
+ print("\n[System] 🔄 Initializing File Systems...")
32
+
33
+ # 1. Fast Filename Indexer (Metadata Only)
34
+ t1 = threading.Thread(target=sql_index.build_index, args=(True,))
35
+ t1.daemon = True
36
+ t1.start()
37
+
38
+ # REMOVE: The content indexer thread (t2)
39
+
40
+ # 3. Schedule recurring updates (Metadata only)
41
+ schedule.every(60).minutes.do(sql_index.build_index, silent=True)
42
+
43
+ scheduler.start_scheduler_service()
44
+
45
+
46
+ # --- SAFETY WRAPPERS ---
47
+
48
+ def ask_permission(tool_name, func, **kwargs):
49
+ """
50
+ Intervention Layer: Pauses execution to ask the user for confirmation.
51
+ """
52
+ print(f"\n[🛑 SECURITY ALERT] Agent wants to run: {tool_name}")
53
+ # Hide agent_config from display
54
+ display_args = {k: v for k, v in kwargs.items() if k != 'agent_config'}
55
+ print(f" Arguments: {display_args}")
56
+
57
+ choice = input(f" >>> Allow this? (y/N): ").lower()
58
+
59
+ if choice == 'y':
60
+ try:
61
+ # Log successful dangerous actions to memory
62
+ log_args = {k: v for k, v in kwargs.items() if k != 'agent_config'}
63
+ memory_ops.log_activity(tool_name, str(log_args))
64
+ except:
65
+ pass
66
+ return func(**kwargs)
67
+ else:
68
+ return f"Action '{tool_name}' denied by user."
69
+
70
+
71
+ def safe_run_cmd(cmd):
72
+ """Extra guardrails for terminal commands."""
73
+ dangerous_keywords = ["del", "rm", "format", "shutdown", "reboot", ">"]
74
+ if any(k in cmd.lower() for k in dangerous_keywords):
75
+ print(f"\n[⚠️ HIGH RISK] Command contains dangerous keywords: '{cmd}'")
76
+ confirm = input(" >>> TYPE 'CONFIRM' TO EXECUTE: ")
77
+ if confirm != "CONFIRM":
78
+ return "Safety block: Command denied."
79
+
80
+ return ask_permission("run_cmd", system_ops.run_cmd, cmd=cmd)
81
+
82
+
83
+ def draft_code(filename, content):
84
+ """Safe Coding: Writes to 'drafts/' instead of executing."""
85
+ safe_path = os.path.join("drafts", os.path.basename(filename))
86
+ try:
87
+ with open(safe_path, "w", encoding="utf-8") as f:
88
+ f.write(content)
89
+ return f"Code saved to '{safe_path}'. Review it manually before running."
90
+ except Exception as e:
91
+ return f"Error drafting code: {e}"
92
+
93
+
94
+ # --- TOOL MAPPING ---
95
+ TOOLS = {
96
+ # System & Apps
97
+ "open_app": apps.open_app,
98
+ "close_app": lambda **k: ask_permission("close_app", apps.close_app, **k),
99
+ "run_cmd": safe_run_cmd,
100
+ "get_clipboard": system_ops.get_clipboard,
101
+ "kill_process": lambda **k: ask_permission("kill_process", system_ops.kill_process, **k),
102
+ "get_system_stats": system_ops.get_system_stats,
103
+ "play_music": apps.play_music,
104
+
105
+ # Browser
106
+ "search_web": browser.search_web,
107
+ "open_url": browser.open_url,
108
+ "read_webpage": browser.read_webpage,
109
+
110
+ # Files & Index
111
+ "read_file": file_ops.read_file,
112
+ "write_file": file_ops.write_file,
113
+ "draft_code": draft_code,
114
+ "build_index": indexer.build_index,
115
+ "search_index": indexer.search_index,
116
+ "find_file": sql_index.search_db,
117
+ "rebuild_memory": sql_index.build_index,
118
+ "organize_files": lambda **k: ask_permission("organize_files", organizer.organize_files, **k),
119
+ "bulk_rename": lambda **k: ask_permission("bulk_rename", organizer.bulk_rename, **k),
120
+
121
+ # Office & Documents
122
+ "create_word": office.create_word,
123
+ "create_excel": office.create_excel,
124
+ "append_excel": office.append_excel,
125
+ "read_excel": office.read_excel,
126
+ "create_document": factory.create_document,
127
+
128
+ # Time & Email
129
+ "get_time": clock.get_time,
130
+ "set_timer": clock.set_timer,
131
+ "set_alarm": clock.set_alarm,
132
+ "send_email": lambda **k: ask_permission("send_email", email_ops.send_email, **k),
133
+ "read_emails": email_ops.read_emails,
134
+
135
+ # Memory & Cognitive
136
+ "add_note": notes.add_note,
137
+ "list_notes": notes.list_notes,
138
+ "store_fact": memory_ops.store_fact,
139
+ "delete_fact": memory_ops.delete_fact,
140
+ "retrieve_knowledge": lambda **kwargs: memory_ops.retrieve_relevant_context(
141
+ query=" ".join([str(v) for v in kwargs.values() if v])
142
+ ),
143
+
144
+ "reflect_on_day": memory_ops.reflect_on_day,
145
+ "daily_briefing": lambda: cognitive.get_daily_briefing(cfg),
146
+
147
+ # Navigation & Flights
148
+ "geocode": navigation.geocode,
149
+ "reverse_geocode": navigation.reverse_geocode,
150
+ "calc_distance": navigation.calc_distance,
151
+ "get_directions": navigation.get_directions,
152
+ "find_nearby": navigation.find_nearby,
153
+ "search_flights": flights.search_flights,
154
+
155
+ # Desktop Control
156
+ "set_volume": desktop.set_volume,
157
+ "set_brightness": desktop.set_brightness,
158
+ "minimize_window": desktop.minimize_window,
159
+ "maximize_window": desktop.maximize_window,
160
+ "type_text": desktop.type_text,
161
+ "speak": desktop.speak,
162
+
163
+ # Perception
164
+ "listen": audio.listen,
165
+ "analyze_screen": vision.analyze_screen,
166
+ "capture_webcam": vision.capture_webcam,
167
+ "get_active_app": context.get_active_app,
168
+ "get_weather": weather_ops.get_current_weather,
169
+
170
+ # Autonomy (Scheduler)
171
+ "schedule_task": lambda interval, task: ask_permission(
172
+ "schedule_task",
173
+ scheduler.schedule_task,
174
+ interval_minutes=interval,
175
+ task_description=task,
176
+ agent_config=settings
177
+ ),
178
+ "stop_tasks": scheduler.stop_all_jobs,
179
+
180
+ # Calendar
181
+ "list_calendar_events": calendar_ops.list_upcoming_events,
182
+ "get_calendar_range": calendar_ops.get_events_in_frame,
183
+ "create_calendar_event": lambda **k: ask_permission("create_event", calendar_ops.create_event, **k),
184
+ "calendar_quick_add": lambda **k: ask_permission("quick_add", calendar_ops.quick_add, **k),
185
+
186
+ # Macros & Installers
187
+ "run_macro": macros.run_macro,
188
+ "install_software": installer.install_software,
189
+ "list_installed_apps": installer.list_installed,
190
+
191
+ "find_my_file": lambda query: "\n".join(smart_find(query)),
192
+
193
+ }
194
+
195
+ # --- PROMPT ---
196
+ # Note: JSON examples use DOUBLE BRACES {{ }} to escape f-string formatting
197
+ SYSTEM_PROMPT = f"""
198
+ You are **Sentinel**, an autonomous AI Operating System layer.
199
+ Your role is to translate user intent into safe, deterministic system actions.
200
+
201
+ System Context:
202
+ - OS: {CURRENT_OS} {OS_VERSION}
203
+ - Current Time: {NOW}
204
+
205
+ USER PROFILE:
206
+ Name: {cfg.get("user.name")}
207
+ Location: {cfg.get("user.location")}
208
+
209
+ CORE BEHAVIOR:
210
+ 1. You operate in **command mode**, not conversation mode.
211
+ 2. Your output must be valid JSON for tool execution.
212
+ 3. Never explain tools unless explicitly asked.
213
+ 4. Prefer direct action over discussion.
214
+
215
+ DECISION POLICY:
216
+ - If the user intent is ambiguous → ask a minimal clarifying question.
217
+ - If the intent maps to a tool → call it immediately.
218
+ - If no tool fits → respond with {{"tool": "response", "args": {{"text": "..."}}}}
219
+
220
+ CRITICAL SAFETY PROTOCOLS:
221
+ 1. **NO CHITCHAT**: Output JSON only.
222
+ 2. **HUMAN-IN-THE-LOOP**:
223
+ - Killing processes
224
+ - Running shell commands
225
+ - Sending emails
226
+ - Scheduling tasks
227
+ Must go through `ask_permission`.
228
+ 3. **CODE SAFETY**:
229
+ - NEVER execute generated code.
230
+ - ALWAYS use `draft_code`.
231
+
232
+ MEMORY PROTOCOLS:
233
+ 1. **Active Listening**:
234
+ If the user reveals a stable preference, habit, goal, or identity:
235
+ → Immediately call `store_fact`.
236
+
237
+ 2. **Context Recall**:
238
+ Before solving complex tasks:
239
+ → Check `retrieve_knowledge` or `list_notes`.
240
+
241
+ 3. **Persistence Triggers**:
242
+ Phrases like:
243
+ - "remember this"
244
+ - "from now on"
245
+ - "in the future"
246
+ Must result in memory storage.
247
+
248
+ AUTONOMY RULES:
249
+ - Background agents must be:
250
+ - Explicitly approved
251
+ - Clearly scoped
252
+ - Easy to stop
253
+
254
+ AVAILABLE TOOLS:
255
+ (only use tools listed below, no hallucinations)
256
+
257
+ SYSTEM & APPS:
258
+ - open_app(name): launch an application
259
+ - close_app(name): close a running application [REQUIRES APPROVAL]
260
+ - run_cmd(cmd): run shell command [REQUIRES APPROVAL]
261
+ - get_clipboard(): get clipboard text
262
+ - kill_process(name): kill a running process [REQUIRES APPROVAL]
263
+ - get_system_stats(): CPU, RAM, battery status
264
+ - play_music(song_name): play music
265
+
266
+ BROWSER:
267
+ - search_web(query): search internet
268
+ - open_url(url): open website
269
+ - read_webpage(url): extract webpage text
270
+
271
+ FILES & INDEX:
272
+ - read_file(path): read file
273
+ - write_file(path, content): write file
274
+ - draft_code(filename, content): save code safely
275
+ - build_index(verbose): scan filesystem
276
+ - search_index(query): semantic file search
277
+ - find_file(query): exact filename search (SQL, literal)
278
+ - find_my_file(query): semantic + recency search (vague queries like "that pdf I edited last week")
279
+ - rebuild_memory(verbose): rebuild index
280
+ - organize_files(directory, strategy): organize files [REQUIRES APPROVAL]
281
+ - bulk_rename(directory, pattern, replace_with): rename files [REQUIRES APPROVAL]
282
+
283
+ OFFICE & DOCUMENTS:
284
+ - create_word(filename, content)
285
+ - create_excel(filename, data_list)
286
+ - append_excel(filename, data_list)
287
+ - read_excel(filename)
288
+ - create_document(filename, blocks)
289
+
290
+ TIME & EMAIL:
291
+ - get_time()
292
+ - set_timer(seconds)
293
+ - set_alarm(time_str)
294
+ - send_email(to, subject, body) [REQUIRES APPROVAL]
295
+ - read_emails(limit)
296
+
297
+ MEMORY & COGNITIVE:
298
+ - add_note(category, content)
299
+ - list_notes(category)
300
+ - store_fact(subject, predicate, obj)
301
+ - delete_fact(subject, predicate, obj): remove stored memory facts
302
+ - retrieve_knowledge(subject, predicate, obj)
303
+ - reflect_on_day(date_str)
304
+ - daily_briefing()
305
+
306
+ NAVIGATION & FLIGHTS:
307
+ - geocode(address)
308
+ - reverse_geocode(lat, lon)
309
+ - calc_distance(origin, destination, mode)
310
+ - get_directions(origin, destination)
311
+ - find_nearby(lat, lon, type)
312
+ - search_flights(departure_id, arrival_id, date)
313
+
314
+ DESKTOP CONTROL:
315
+ - set_volume(level)
316
+ - set_brightness(level)
317
+ - minimize_window(app_name)
318
+ - maximize_window(app_name)
319
+ - type_text(text)
320
+ - speak(text)
321
+
322
+ PERCEPTION:
323
+ - listen(timeout)
324
+ - analyze_screen(prompt)
325
+ - capture_webcam(prompt)
326
+ - get_active_app()
327
+ - get_weather(location): retrieve the current location first/else ask user if cannot find
328
+
329
+ AUTONOMY:
330
+ - schedule_task(interval, task) [REQUIRES APPROVAL]
331
+ - stop_tasks()
332
+
333
+ CALENDAR:
334
+ - list_calendar_events(max_results)
335
+ - get_calendar_range(start_iso, end_iso)
336
+ - create_calendar_event(summary, start_time, duration_mins, description) [REQUIRES APPROVAL]
337
+ - calendar_quick_add(text) [REQUIRES APPROVAL]
338
+
339
+ MACROS & INSTALLERS:
340
+ - run_macro(name)
341
+ - install_software(package_list): install Windows apps via winget
342
+ - list_installed_apps(): list installed applications
343
+
344
+
345
+ FINAL RULE:
346
+ Your response MUST ALWAYS be one of:
347
+ 1. Tool call JSON
348
+ 2. {{"tool": "response", "args": {{"text": "..."}}}}
349
+
350
+ NO prose. NO markdown. NO explanations.
351
+ """
@@ -0,0 +1,61 @@
1
+ import schedule
2
+ import time
3
+ import threading
4
+ from sentinel.core.llm import LLMEngine
5
+
6
+ # Global registry
7
+ ACTIVE_JOBS = {}
8
+
9
+
10
+ def _job_runner(task_description, agent_config):
11
+ """Runs the background task."""
12
+ print(f"\n[Scheduler] ⏰ Executing background task: {task_description}")
13
+
14
+ # Create a transient brain for this task
15
+ brain = LLMEngine(agent_config)
16
+
17
+ # Security: Background agents get a strictly limited prompt
18
+ sys_prompt = f"You are a background monitoring agent. Current Task: {task_description}. Output JSON only."
19
+
20
+ try:
21
+ # We pass an empty history because background tasks should be stateless
22
+ response = brain.query(sys_prompt, [])
23
+ print(f"[Scheduler Result]: {response}")
24
+ except Exception as e:
25
+ print(f"[Scheduler Error]: {e}")
26
+
27
+
28
+ def schedule_task(interval_minutes, task_description, agent_config):
29
+ """Schedules a new task."""
30
+ job_id = f"job_{len(ACTIVE_JOBS) + 1}"
31
+
32
+ # Schedule the job
33
+ job = schedule.every(int(interval_minutes)).minutes.do(
34
+ _job_runner, task_description, agent_config
35
+ )
36
+
37
+ ACTIVE_JOBS[job_id] = job
38
+ return f"✅ Scheduled: '{task_description}' every {interval_minutes}m. (ID: {job_id})"
39
+
40
+
41
+ def stop_all_jobs():
42
+ """
43
+ EMERGENCY STOP: Clears all background schedules.
44
+ """
45
+ count = len(ACTIVE_JOBS)
46
+ schedule.clear()
47
+ ACTIVE_JOBS.clear()
48
+ return f"🛑 STOPPED {count} background jobs. Scheduler is now empty."
49
+
50
+
51
+ def start_scheduler_service():
52
+ """Starts the loop in a background thread."""
53
+
54
+ def loop():
55
+ while True:
56
+ schedule.run_pending()
57
+ time.sleep(1)
58
+
59
+ t = threading.Thread(target=loop, daemon=True)
60
+ t.start()
61
+ print(">> System: Scheduler Service Online.")
@@ -0,0 +1,11 @@
1
+ # FILE: core/schema.py
2
+ from pydantic import BaseModel, Field, ValidationError
3
+ from typing import Dict, Any, Optional
4
+
5
+ class AgentAction(BaseModel):
6
+ tool: str
7
+ args: Dict[str, Any] = Field(default_factory=dict)
8
+
9
+ class Config:
10
+ # Allows the model to ignore extra fields if the LLM hallucinates them
11
+ extra = "ignore"
sentinel/core/setup.py ADDED
@@ -0,0 +1,101 @@
1
+ # sentinel/core/setup.py
2
+
3
+ import time
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from rich.prompt import Prompt, Confirm
7
+ from rich.progress import Progress, SpinnerColumn, TextColumn
8
+
9
+ from sentinel.core.config import ConfigManager
10
+ from sentinel.paths import CREDENTIALS_PATH as CREDS_FILE
11
+
12
+ console = Console()
13
+ cfg = ConfigManager()
14
+
15
+
16
+ def print_step(title):
17
+ console.print(f"\n[bold cyan]🔹 {title}[/bold cyan]")
18
+
19
+
20
+ def setup_wizard():
21
+ console.clear()
22
+ console.print(Panel.fit(
23
+ "[bold white]Welcome to Sentinel OS[/bold white]\n[dim]Autonomous AI Agent System[/dim]",
24
+ border_style="cyan",
25
+ padding=(1, 4)
26
+ ))
27
+
28
+ console.print("\n[italic]Let's get your system configured. Press Enter to skip optional fields.[/italic]\n")
29
+
30
+ # 1. User Profile
31
+ print_step("User Profile")
32
+ name = Prompt.ask("What should I call you?", default="User")
33
+ location = Prompt.ask("What is your city? (for weather/time)", default="New York")
34
+
35
+ cfg.set("user.name", name)
36
+ cfg.set("user.location", location)
37
+
38
+ # 2. Intelligence Engine
39
+ print_step("Intelligence Engine")
40
+ provider = Prompt.ask(
41
+ "Select Primary Brain",
42
+ choices=["openai", "anthropic", "groq", "ollama"],
43
+ default="openai"
44
+ )
45
+
46
+ api_key = ""
47
+ if provider != "ollama":
48
+ api_key = Prompt.ask(f"Enter API Key for [cyan]{provider}[/cyan]", password=True)
49
+
50
+ cfg.set("llm.provider", provider)
51
+
52
+ if api_key:
53
+ cfg.set_key(provider, api_key)
54
+
55
+ # 3. Search
56
+ print_step("Search Capabilities")
57
+ console.print("Sentinel uses [bold green]Tavily[/bold green] for advanced research, falling back to DuckDuckGo.")
58
+
59
+ tavily_key = Prompt.ask("Enter Tavily API Key (Optional)", password=True)
60
+ if tavily_key:
61
+ cfg.set_key("tavily", tavily_key)
62
+
63
+ serp_key = Prompt.ask("Enter SerpAPI Key (Optional - for Flights)", password=True)
64
+ if serp_key:
65
+ cfg.set_key("serp_api", serp_key)
66
+
67
+ gmaps_key = Prompt.ask("Enter Google Maps API Key (Optional - for Navigation)", password=True)
68
+ if gmaps_key:
69
+ cfg.set_key("google_maps", gmaps_key)
70
+
71
+ # 4. Google Workspace
72
+ print_step("Google Workspace")
73
+ if Confirm.ask("Do you want to link Gmail/Calendar now?"):
74
+ if CREDS_FILE.exists():
75
+ console.print("[green]✔ credentials.json found.[/green]")
76
+ console.print("Authentication will trigger automatically on first use.")
77
+ else:
78
+ console.print("[red]❌ credentials.json not found.[/red]\n")
79
+ console.print("1. Go to Google Cloud Console")
80
+ console.print("2. Enable Gmail + Calendar APIs")
81
+ console.print("3. Create OAuth Desktop App")
82
+ console.print("4. Download credentials.json")
83
+ console.print("\nPlace it here:\n")
84
+ console.print(f"[bold cyan]{CREDS_FILE}[/bold cyan]")
85
+ console.print("\nThen run: sentinel auth")
86
+
87
+ # 5. Finalize
88
+ with Progress(
89
+ SpinnerColumn(),
90
+ TextColumn("[progress.description]{task.description}"),
91
+ transient=True,
92
+ ) as progress:
93
+ task = progress.add_task("Initializing Neural Interfaces...", total=100)
94
+ time.sleep(0.5)
95
+ progress.update(task, advance=50)
96
+ cfg.set("system.setup_completed", True)
97
+ time.sleep(0.5)
98
+ progress.update(task, advance=50)
99
+
100
+ console.print("\n[bold green]✔ Setup Complete.[/bold green] Launching Sentinel...")
101
+ time.sleep(1)