sentinel-ai-os 1.0__tar.gz → 1.0.2__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.
Files changed (57) hide show
  1. sentinel_ai_os-1.0.2/MANIFEST.in +2 -0
  2. {sentinel_ai_os-1.0/sentinel_ai_os.egg-info → sentinel_ai_os-1.0.2}/PKG-INFO +4 -5
  3. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/README.md +0 -2
  4. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/pyproject.toml +10 -3
  5. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/agent.py +2 -13
  6. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/audit.py +2 -4
  7. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/config.py +2 -7
  8. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/llm.py +0 -8
  9. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/registry.py +0 -17
  10. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/scheduler.py +0 -4
  11. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/schema.py +0 -2
  12. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/setup.py +0 -3
  13. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/ui.py +0 -2
  14. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/paths.py +2 -31
  15. sentinel_ai_os-1.0.2/sentinel/scripts/factory_reset.bat +17 -0
  16. sentinel_ai_os-1.0.2/sentinel/scripts/wipe_vector.bat +7 -0
  17. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/apps.py +17 -34
  18. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/audio.py +1 -1
  19. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/browser.py +0 -2
  20. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/context.py +0 -1
  21. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/factory.py +0 -6
  22. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/file_ops.py +0 -2
  23. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/flights.py +1 -1
  24. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/gmail_auth.py +1 -9
  25. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/indexer.py +1 -9
  26. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/notes.py +0 -1
  27. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/smart_index.py +1 -1
  28. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/sql_index.py +4 -18
  29. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/system_ops.py +0 -1
  30. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/weather_ops.py +0 -1
  31. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2/sentinel_ai_os.egg-info}/PKG-INFO +4 -5
  32. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel_ai_os.egg-info/SOURCES.txt +2 -0
  33. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel_ai_os.egg-info/requires.txt +1 -0
  34. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel_ai_os.egg-info/top_level.txt +1 -0
  35. sentinel_ai_os-1.0/MANIFEST.in +0 -1
  36. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/LICENSE +0 -0
  37. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/__init__.py +0 -0
  38. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/auth.py +0 -0
  39. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/cli.py +0 -0
  40. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/__init__.py +0 -0
  41. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/core/cognitive.py +0 -0
  42. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/main.py +0 -0
  43. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/__init__.py +0 -0
  44. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/calendar_ops.py +0 -0
  45. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/clock.py +0 -0
  46. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/desktop.py +0 -0
  47. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/email_ops.py +0 -0
  48. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/installer.py +0 -0
  49. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/macros.py +0 -0
  50. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/memory_ops.py +0 -0
  51. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/navigation.py +0 -0
  52. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/office.py +0 -0
  53. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/organizer.py +0 -0
  54. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel/tools/vision.py +0 -0
  55. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel_ai_os.egg-info/dependency_links.txt +0 -0
  56. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/sentinel_ai_os.egg-info/entry_points.txt +0 -0
  57. {sentinel_ai_os-1.0 → sentinel_ai_os-1.0.2}/setup.cfg +0 -0
@@ -0,0 +1,2 @@
1
+ include src/sentinel/scripts/*
2
+ recursive-include src/sentinel/scripts *
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentinel-ai-os
3
- Version: 1.0
3
+ Version: 1.0.2
4
4
  Summary: Autonomous AI Operating System layer
5
5
  Author-email: Sam Selvaraj <samselvaraj1801@gmail.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Keywords: ai,agent,automation,llm
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
10
+ Requires-Python: <3.14,>=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: rich
@@ -49,6 +49,7 @@ Requires-Dist: comtypes
49
49
  Requires-Dist: pyperclip
50
50
  Requires-Dist: plyer
51
51
  Requires-Dist: pyttsx3
52
+ Requires-Dist: sentence-transformers
52
53
  Requires-Dist: pillow
53
54
  Requires-Dist: opencv-python
54
55
  Requires-Dist: SpeechRecognition
@@ -76,7 +77,6 @@ Dynamic: license-file
76
77
  ---
77
78
 
78
79
  ## 🔭 Overview
79
- <img width="1127" height="550" alt="image" src="https://github.com/user-attachments/assets/9ac617d6-a959-4a8c-b245-ff2c98f083ea" />
80
80
 
81
81
  Sentinel is not just a chatbot; it is an **Action Engine**. Unlike web-based LLMs that live in a sandbox, Sentinel runs locally on your machine with access to your file system, applications, and peripherals.
82
82
 
@@ -91,7 +91,6 @@ It operates on a **Think-Plan-Act** loop:
91
91
  ---
92
92
 
93
93
  ## ✨ Key Features
94
- <img width="2836" height="1338" alt="image" src="https://github.com/user-attachments/assets/877e5ec8-f398-4c04-b0b0-0c45b12d08c8" />
95
94
 
96
95
  * **🧠 Multi-Brain Support:** Powered by `litellm`, Sentinel can seamlessly switch between **OpenAI**, **Anthropic**, **Groq**, or run locally with **Ollama**.
97
96
  * **💾 Hybrid Memory Architecture:**
@@ -19,7 +19,6 @@
19
19
  ---
20
20
 
21
21
  ## 🔭 Overview
22
- <img width="1127" height="550" alt="image" src="https://github.com/user-attachments/assets/9ac617d6-a959-4a8c-b245-ff2c98f083ea" />
23
22
 
24
23
  Sentinel is not just a chatbot; it is an **Action Engine**. Unlike web-based LLMs that live in a sandbox, Sentinel runs locally on your machine with access to your file system, applications, and peripherals.
25
24
 
@@ -34,7 +33,6 @@ It operates on a **Think-Plan-Act** loop:
34
33
  ---
35
34
 
36
35
  ## ✨ Key Features
37
- <img width="2836" height="1338" alt="image" src="https://github.com/user-attachments/assets/877e5ec8-f398-4c04-b0b0-0c45b12d08c8" />
38
36
 
39
37
  * **🧠 Multi-Brain Support:** Powered by `litellm`, Sentinel can seamlessly switch between **OpenAI**, **Anthropic**, **Groq**, or run locally with **Ollama**.
40
38
  * **💾 Hybrid Memory Architecture:**
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sentinel-ai-os"
7
- version = "1.0"
7
+ version = "1.0.2"
8
8
  description = "Autonomous AI Operating System layer"
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
11
- license = {text = "MIT"}
10
+ requires-python = ">=3.9, <3.14"
11
+ license = "MIT"
12
12
  authors = [
13
13
  { name="Sam Selvaraj", email="samselvaraj1801@gmail.com" }
14
14
  ]
@@ -69,6 +69,7 @@ dependencies = [
69
69
  "pyperclip",
70
70
  "plyer",
71
71
  "pyttsx3",
72
+ "sentence-transformers",
72
73
 
73
74
  # Vision / Audio
74
75
  "pillow",
@@ -77,6 +78,12 @@ dependencies = [
77
78
  "pyaudio"
78
79
  ]
79
80
 
81
+ [tool.setuptools.packages.find]
82
+ where = ["."]
83
+
84
+ [tool.setuptools.package-data]
85
+ sentinel = ["scripts/*", "*.json"]
86
+
80
87
  [project.scripts]
81
88
  sentinel = "sentinel.main:app"
82
89
 
@@ -1,5 +1,3 @@
1
- # FILE: core/agent.py
2
-
3
1
  import sys
4
2
  import json
5
3
  import os
@@ -13,7 +11,6 @@ from sentinel.core.schema import AgentAction
13
11
  from sentinel.tools import memory_ops
14
12
  from sentinel.paths import USER_DATA_DIR, DB_PATH, VECTOR_PATH, AUDIT_LOG_PATH as AUDIT_LOG
15
13
 
16
- # Absolute path to scripts folder
17
14
  BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18
15
  SCRIPTS_DIR = os.path.join(BASE_DIR, "scripts")
19
16
 
@@ -33,7 +30,6 @@ class SentinelAgent:
33
30
  try:
34
31
  json_data = json.loads(text)
35
32
  except:
36
- # Extract first valid JSON object using a stack
37
33
  start = text.find("{")
38
34
  if start == -1:
39
35
  return None
@@ -93,7 +89,7 @@ class SentinelAgent:
93
89
  if input("⚠️ ARE YOU SURE? This will delete ALL long-term memories. (y/n): ").lower() == 'y':
94
90
  UI.print_system("Initiating Wipe...")
95
91
 
96
- # 1. Stop background processes
92
+
97
93
  try:
98
94
  from sentinel.core import scheduler
99
95
  scheduler.stop_all_jobs()
@@ -106,7 +102,7 @@ class SentinelAgent:
106
102
  except:
107
103
  pass
108
104
 
109
- # 3. Delete the specific files (Cross-Platform)
105
+ # 3. Delete the specific files
110
106
  UI.print_system(f"Wiping data from {USER_DATA_DIR}...")
111
107
 
112
108
  # Delete Vector DB Folder
@@ -148,7 +144,6 @@ class SentinelAgent:
148
144
  sys.exit(0)
149
145
  except Exception as e:
150
146
  print(f"Reset failed: {e}")
151
- # Fallback: If locked, tell user to delete manually
152
147
  print(f"Please manually delete this folder: {USER_DATA_DIR}")
153
148
  return True
154
149
 
@@ -169,7 +164,6 @@ class SentinelAgent:
169
164
  return True
170
165
 
171
166
  if cmd in ["switch", "model"]:
172
- # (Keeping it brief for copy-paste)
173
167
  provider = args[0] if args else "openai"
174
168
  model = args[1] if len(args) > 1 else None
175
169
  from sentinel.tools import system_ops
@@ -228,7 +222,6 @@ class SentinelAgent:
228
222
  if self.process_slash_command(user_input):
229
223
  continue
230
224
 
231
- # ---- Recall Memory ----
232
225
  relevant_context = memory_ops.retrieve_relevant_context(user_input)
233
226
  current_sys = SYSTEM_PROMPT
234
227
  if relevant_context:
@@ -242,7 +235,6 @@ class SentinelAgent:
242
235
  full_resp = self.brain.query(current_sys, messages)
243
236
  action = self._parse_action(full_resp)
244
237
 
245
- # ---- Normal LLM response ----
246
238
  if not action:
247
239
  clean = full_resp.replace("```json", "").replace("```", "").strip()
248
240
 
@@ -257,7 +249,6 @@ class SentinelAgent:
257
249
 
258
250
  tool, args = action.tool, action.args
259
251
 
260
- # ---- Explicit response tool ----
261
252
  if tool == "response":
262
253
  text = args.get("text", "").strip()
263
254
  if not text:
@@ -267,13 +258,11 @@ class SentinelAgent:
267
258
  self.history.append({"role": "assistant", "content": action.model_dump_json()})
268
259
  break
269
260
 
270
- # ---- Tool execution ----
271
261
  if tool in TOOLS:
272
262
  UI.print_tool(tool)
273
263
  try:
274
264
  res = TOOLS[tool](**args)
275
265
 
276
- # UX fallback
277
266
  if not res or not str(res).strip():
278
267
  res = "No long-term memories stored about you yet."
279
268
 
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
 
8
8
  BASE_DIR = Path.home() / ".sentinel-1"
9
9
  BASE_DIR.mkdir(exist_ok=True)
10
- LOG_FILE = BASE_DIR / "audit_log.jsonl" # JSON Lines for easy parsing
10
+ LOG_FILE = BASE_DIR / "audit_log.jsonl"
11
11
 
12
12
 
13
13
  class AuditLogger:
@@ -15,7 +15,6 @@ class AuditLogger:
15
15
  self.cfg = ConfigManager()
16
16
 
17
17
  def is_enabled(self):
18
- # Check config (default to False if not set)
19
18
  return self.cfg.get("system.audit_logging", False)
20
19
 
21
20
  def toggle(self, state: bool):
@@ -33,7 +32,7 @@ class AuditLogger:
33
32
  "provider": provider,
34
33
  "model": model,
35
34
  "duration_ms": round(duration_ms, 2),
36
- "input": str(input_data)[:2000], # Truncate massive inputs
35
+ "input": str(input_data)[:2000],
37
36
  "output": str(output_data)
38
37
  }
39
38
 
@@ -43,6 +42,5 @@ class AuditLogger:
43
42
  except Exception as e:
44
43
  print(f"Logger Error: {e}")
45
44
 
46
-
47
45
  # Global Instance
48
46
  audit = AuditLogger()
@@ -1,19 +1,16 @@
1
- # FILE: sentinel/core/config.py
2
1
  import json
3
2
  import os
4
3
  import keyring
5
4
  from typing import Any, Optional
6
- from sentinel.paths import CONFIG_PATH # <-- IMPORTED FROM CENTRAL PATHS
7
-
8
- APP_NAME = "sentinel-1"
5
+ from sentinel.paths import CONFIG_PATH
9
6
 
7
+ APP_NAME = "sentinel-ai"
10
8
 
11
9
  class ConfigManager:
12
10
  def __init__(self):
13
11
  self._ensure_config_exists()
14
12
 
15
13
  def _ensure_config_exists(self):
16
- # Check if the file exists at ~/.sentinel/config.json
17
14
  if not CONFIG_PATH.exists():
18
15
  default_config = {
19
16
  "user": {"name": "User", "location": "New York"},
@@ -28,14 +25,12 @@ class ConfigManager:
28
25
 
29
26
  def load(self) -> dict:
30
27
  try:
31
- # CONFIG_PATH is a Path object, open() handles it natively
32
28
  with open(CONFIG_PATH, "r") as f:
33
29
  return json.load(f)
34
30
  except (FileNotFoundError, json.JSONDecodeError):
35
31
  return {}
36
32
 
37
33
  def save(self, data: dict):
38
- # Ensure the directory exists (just in case the folder was deleted manually)
39
34
  CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
40
35
 
41
36
  with open(CONFIG_PATH, "w") as f:
@@ -1,6 +1,3 @@
1
- # FILE: core/llm.py
2
-
3
- import os
4
1
  from openai import OpenAI
5
2
  import anthropic
6
3
  from sentinel.core.ui import UI
@@ -38,12 +35,9 @@ class LLMEngine:
38
35
  if verbose:
39
36
  is_ready = self.api_key is not None or self.provider == "ollama"
40
37
  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
38
  pass
44
39
 
45
40
  def stream_query(self, system_prompt, history):
46
- # Hot-reload config (Silent)
47
41
  self.reload_config(verbose=False)
48
42
 
49
43
  if not self.api_key and self.provider != "ollama":
@@ -51,8 +45,6 @@ class LLMEngine:
51
45
  yield f"Please run: [cyan]/setkey {self.provider} YOUR_KEY_HERE[/cyan]"
52
46
  return
53
47
 
54
- # --- PREPARE MESSAGES ---
55
- # 1. System Prompt is separate
56
48
  messages = []
57
49
  for msg in history:
58
50
  # Anthropic hates "system" role in messages list
@@ -1,5 +1,3 @@
1
- # FILE: core/registry.py
2
-
3
1
  import platform
4
2
  import datetime
5
3
  import threading
@@ -8,7 +6,6 @@ from sentinel.core.config import ConfigManager
8
6
  import schedule
9
7
  from sentinel.tools.smart_index import smart_find
10
8
 
11
- # Import all tools
12
9
  from sentinel.tools import (
13
10
  apps, browser, clock, email_ops, file_ops, indexer, notes, office,
14
11
  system_ops, navigation, flights, sql_index, desktop, vision, audio,
@@ -17,12 +14,10 @@ from sentinel.tools import (
17
14
  )
18
15
  from sentinel.core import scheduler, cognitive
19
16
 
20
- # --- DETECT OS ---
21
17
  CURRENT_OS = platform.system()
22
18
  OS_VERSION = platform.release()
23
19
  NOW = datetime.datetime.now()
24
20
 
25
- # --- LOAD CONFIG ---
26
21
  cfg = ConfigManager()
27
22
  settings = cfg.load() if cfg.exists() else {}
28
23
 
@@ -30,21 +25,13 @@ settings = cfg.load() if cfg.exists() else {}
30
25
  def initialize_tools():
31
26
  print("\n[System] 🔄 Initializing File Systems...")
32
27
 
33
- # 1. Fast Filename Indexer (Metadata Only)
34
28
  t1 = threading.Thread(target=sql_index.build_index, args=(True,))
35
29
  t1.daemon = True
36
30
  t1.start()
37
31
 
38
- # REMOVE: The content indexer thread (t2)
39
-
40
- # 3. Schedule recurring updates (Metadata only)
41
32
  schedule.every(60).minutes.do(sql_index.build_index, silent=True)
42
-
43
33
  scheduler.start_scheduler_service()
44
34
 
45
-
46
- # --- SAFETY WRAPPERS ---
47
-
48
35
  def ask_permission(tool_name, func, **kwargs):
49
36
  """
50
37
  Intervention Layer: Pauses execution to ask the user for confirmation.
@@ -58,7 +45,6 @@ def ask_permission(tool_name, func, **kwargs):
58
45
 
59
46
  if choice == 'y':
60
47
  try:
61
- # Log successful dangerous actions to memory
62
48
  log_args = {k: v for k, v in kwargs.items() if k != 'agent_config'}
63
49
  memory_ops.log_activity(tool_name, str(log_args))
64
50
  except:
@@ -90,8 +76,6 @@ def draft_code(filename, content):
90
76
  except Exception as e:
91
77
  return f"Error drafting code: {e}"
92
78
 
93
-
94
- # --- TOOL MAPPING ---
95
79
  TOOLS = {
96
80
  # System & Apps
97
81
  "open_app": apps.open_app,
@@ -193,7 +177,6 @@ TOOLS = {
193
177
  }
194
178
 
195
179
  # --- PROMPT ---
196
- # Note: JSON examples use DOUBLE BRACES {{ }} to escape f-string formatting
197
180
  SYSTEM_PROMPT = f"""
198
181
  You are **Sentinel**, an autonomous AI Operating System layer.
199
182
  Your role is to translate user intent into safe, deterministic system actions.
@@ -3,7 +3,6 @@ import time
3
3
  import threading
4
4
  from sentinel.core.llm import LLMEngine
5
5
 
6
- # Global registry
7
6
  ACTIVE_JOBS = {}
8
7
 
9
8
 
@@ -11,14 +10,11 @@ def _job_runner(task_description, agent_config):
11
10
  """Runs the background task."""
12
11
  print(f"\n[Scheduler] ⏰ Executing background task: {task_description}")
13
12
 
14
- # Create a transient brain for this task
15
13
  brain = LLMEngine(agent_config)
16
14
 
17
- # Security: Background agents get a strictly limited prompt
18
15
  sys_prompt = f"You are a background monitoring agent. Current Task: {task_description}. Output JSON only."
19
16
 
20
17
  try:
21
- # We pass an empty history because background tasks should be stateless
22
18
  response = brain.query(sys_prompt, [])
23
19
  print(f"[Scheduler Result]: {response}")
24
20
  except Exception as e:
@@ -1,4 +1,3 @@
1
- # FILE: core/schema.py
2
1
  from pydantic import BaseModel, Field, ValidationError
3
2
  from typing import Dict, Any, Optional
4
3
 
@@ -7,5 +6,4 @@ class AgentAction(BaseModel):
7
6
  args: Dict[str, Any] = Field(default_factory=dict)
8
7
 
9
8
  class Config:
10
- # Allows the model to ignore extra fields if the LLM hallucinates them
11
9
  extra = "ignore"
@@ -1,5 +1,3 @@
1
- # sentinel/core/setup.py
2
-
3
1
  import time
4
2
  from rich.console import Console
5
3
  from rich.panel import Panel
@@ -84,7 +82,6 @@ def setup_wizard():
84
82
  console.print(f"[bold cyan]{CREDS_FILE}[/bold cyan]")
85
83
  console.print("\nThen run: sentinel auth")
86
84
 
87
- # 5. Finalize
88
85
  with Progress(
89
86
  SpinnerColumn(),
90
87
  TextColumn("[progress.description]{task.description}"),
@@ -1,4 +1,3 @@
1
- # FILE: core/ui.py
2
1
  from rich.console import Console
3
2
  from rich.panel import Panel
4
3
  from rich.markdown import Markdown
@@ -50,7 +49,6 @@ class UI:
50
49
  if not isinstance(text, Markdown):
51
50
  text = Markdown(str(text))
52
51
 
53
- # Add Model Info to Title
54
52
  title = "[bold cyan]SENTINEL[/bold cyan]"
55
53
  if model:
56
54
  title += f" [dim]({model})[/dim]"
@@ -1,73 +1,44 @@
1
- # FILE: sentinel/core/paths.py
2
1
  import os
3
2
  import sys
4
3
  from pathlib import Path
5
4
 
6
- # ==========================================
7
- # 1. USER DATA STORAGE (Mutable)
8
- # Stores DBs, Configs, Logs, and Auth Tokens
9
- # Location: ~/.sentinel (Cross-platform)
10
- # ==========================================
11
-
12
5
  USER_DATA_DIR = Path.home() / ".sentinel"
13
6
  USER_DATA_DIR.mkdir(parents=True, exist_ok=True)
14
-
15
- # Subdirectories
16
7
  LOGS_DIR = USER_DATA_DIR / "logs"
17
8
  LOGS_DIR.mkdir(exist_ok=True)
18
9
 
19
10
  DRAFTS_DIR = USER_DATA_DIR / "drafts"
20
11
  DRAFTS_DIR.mkdir(exist_ok=True)
21
-
22
- # Critical System Files
23
12
  CONFIG_PATH = USER_DATA_DIR / "config.json"
24
13
  DB_PATH = USER_DATA_DIR / "brain.db"
25
14
  VECTOR_PATH = USER_DATA_DIR / "brain_vectors" # ChromaDB folder
26
15
  AUDIT_LOG_PATH = USER_DATA_DIR / "audit_log.jsonl"
27
16
  MEMORY_FILE = USER_DATA_DIR / "memory.json"
28
-
29
- # Search Indexes
30
17
  FILE_INDEX_DB = USER_DATA_DIR / "file_index.db"
31
18
  SMART_INDEX_DB = USER_DATA_DIR / "smart_files.db"
32
19
 
33
- # Authentication
20
+
34
21
  CREDENTIALS_PATH = USER_DATA_DIR / "credentials.json"
35
22
  TOKEN_PATH = USER_DATA_DIR / "token.json"
36
23
 
37
-
38
- # ==========================================
39
- # 2. PACKAGE ASSETS (Immutable)
40
- # Locates scripts/tools bundled inside the pip package
41
- # Location: .../site-packages/sentinel/scripts/
42
- # ==========================================
43
-
44
24
  def get_script_path(filename: str) -> str:
45
25
  """
46
26
  Returns the absolute path to a script bundled inside the pip package.
47
27
  Expects scripts to be in: src/sentinel/scripts/
48
28
  """
49
- # This file is in: .../sentinel/core/paths.py
50
- # We want: .../sentinel/scripts/filename
51
29
 
52
- # Get the directory of THIS file (core/)
53
30
  current_dir = os.path.dirname(os.path.abspath(__file__))
54
-
55
- # Go up one level to the package root (sentinel/)
56
31
  pkg_root = os.path.dirname(current_dir)
57
-
58
- # Target path
59
32
  script_path = os.path.join(pkg_root, "scripts", filename)
60
33
 
61
34
  if not os.path.exists(script_path):
62
- # Fallback for Development Mode (running from source without pip install)
63
- # In dev repo: sentinel/core/paths.py -> ../../../scripts/
35
+
64
36
  repo_root = os.path.dirname(os.path.dirname(pkg_root))
65
37
  dev_path = os.path.join(repo_root, "scripts", filename)
66
38
 
67
39
  if os.path.exists(dev_path):
68
40
  return dev_path
69
41
 
70
- # If we still can't find it, that's a build error
71
42
  raise FileNotFoundError(
72
43
  f"Could not find bundled script '{filename}'.\n"
73
44
  f"Checked: {script_path}\n"
@@ -0,0 +1,17 @@
1
+ @echo off
2
+ title Sentinel Factory Reset
3
+
4
+ echo.
5
+ echo !!! THIS WILL DELETE ALL SENTINEL DATA !!!
6
+ echo Location:
7
+ echo %USERPROFILE%\.sentinel
8
+ echo.
9
+ pause
10
+
11
+ rmdir /s /q "%USERPROFILE%\.sentinel"
12
+ mkdir "%USERPROFILE%\.sentinel"
13
+
14
+ echo.
15
+ echo Sentinel has been FACTORY RESET.
16
+ echo All memory, vectors, tokens, logs deleted.
17
+ exit
@@ -0,0 +1,7 @@
1
+ @echo off
2
+ echo Wiping Long-term memory only...
3
+ rmdir /s /q "%USERPROFILE%\.sentinel\brain_vectors"
4
+ rmdir /s /q "%USERPROFILE%\.sentinel\.chroma"
5
+ del /f /q "%USERPROFILE%\.sentinel\memory.json"
6
+ echo Done.
7
+ exit
@@ -7,7 +7,6 @@ import webbrowser
7
7
  import difflib
8
8
  import logging
9
9
 
10
- # Ensure APP_CACHE exists (from your global scope)
11
10
  APP_CACHE = globals().get("APP_CACHE", {})
12
11
 
13
12
  def _native_open(target):
@@ -33,8 +32,6 @@ def _run_command(cmd_str):
33
32
  Safely runs a command string cross-platform.
34
33
  """
35
34
  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
35
  subprocess.Popen(cmd_str, shell=True)
39
36
  return True
40
37
  except Exception as e:
@@ -78,7 +75,7 @@ def _get_os_aliases():
78
75
  "ppt": "powerpnt",
79
76
  "outlook": "outlook",
80
77
  "onenote": "onenote",
81
- "teams": "ms-teams:", # Deep link is often more reliable than exe
78
+ "teams": "ms-teams:",
82
79
  "access": "msaccess",
83
80
 
84
81
  # --- SYSTEM TOOLS ---
@@ -87,7 +84,7 @@ def _get_os_aliases():
87
84
  "settings": "start ms-settings:",
88
85
  "control panel": "control",
89
86
  "task manager": "taskmgr",
90
- "cmd": "start cmd", # 'start' ensures it opens a NEW window
87
+ "cmd": "start cmd", #
91
88
  "command prompt": "start cmd",
92
89
  "terminal": "wt", # Windows Terminal
93
90
  "powershell": "start powershell",
@@ -109,7 +106,6 @@ def _get_os_aliases():
109
106
  "pycharm": "pycharm64",
110
107
  "intellij": "idea64",
111
108
  "docker": "start \"Docker Desktop\" \"C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe\"",
112
- # Hard path often needed
113
109
  "wsl": "wsl",
114
110
  "git": "git-bash",
115
111
 
@@ -121,7 +117,7 @@ def _get_os_aliases():
121
117
  "calc": "calc",
122
118
  "calculator": "calc",
123
119
  "snipping tool": "snippingtool",
124
- "screenshot": "snippingtool", # Logic alias
120
+ "screenshot": "snippingtool",
125
121
  "photos": "ms-photos:",
126
122
  "camera": "microsoft.windows.camera:",
127
123
  "clock": "ms-clock:",
@@ -144,7 +140,6 @@ def _get_os_aliases():
144
140
  }}
145
141
 
146
142
  # 3. macOS Specifics (Darwin)
147
- # 3. macOS Specifics (Darwin)
148
143
  elif system == "Darwin":
149
144
  return {**aliases, **{
150
145
  # --- BROWSERS ---
@@ -167,12 +162,12 @@ def _get_os_aliases():
167
162
 
168
163
  # --- DEV TOOLS ---
169
164
  "vscode": "open -a 'Visual Studio Code'",
170
- "code": "open -a 'Visual Studio Code'", # Fallback if 'code' CLI isn't in PATH
165
+ "code": "open -a 'Visual Studio Code'",
171
166
  "sublime": "open -a 'Sublime Text'",
172
167
  "iterm": "open -a iTerm",
173
168
  "terminal": "open -a Terminal",
174
169
  "docker": "open -a Docker",
175
- "pycharm": "open -a 'PyCharm CE'", # Or 'PyCharm' depending on edition
170
+ "pycharm": "open -a 'PyCharm CE'",
176
171
  "intellij": "open -a 'IntelliJ IDEA CE'",
177
172
  "xcode": "open -a Xcode",
178
173
 
@@ -182,7 +177,7 @@ def _get_os_aliases():
182
177
  "whatsapp": "open -a WhatsApp",
183
178
  "telegram": "open -a Telegram",
184
179
  "signal": "open -a Signal",
185
- "zoom": "open -a zoom.us", # Zoom internal name is often zoom.us
180
+ "zoom": "open -a zoom.us",
186
181
  "messages": "open -a Messages",
187
182
  "imessage": "open -a Messages",
188
183
  "mail": "open -a Mail",
@@ -208,12 +203,12 @@ def _get_os_aliases():
208
203
 
209
204
  # --- SYSTEM UTILITIES ---
210
205
  "finder": "open .",
211
- "explorer": "open .", # For Windows muscle memory
212
- "settings": "open -b com.apple.systempreferences", # Robust bundle ID method
206
+ "explorer": "open .",
207
+ "settings": "open -b com.apple.systempreferences",
213
208
  "preferences": "open -b com.apple.systempreferences",
214
209
  "app store": "open -a 'App Store'",
215
210
  "activity monitor": "open -a 'Activity Monitor'",
216
- "task manager": "open -a 'Activity Monitor'", # Windows muscle memory
211
+ "task manager": "open -a 'Activity Monitor'",
217
212
  "disk utility": "open -a 'Disk Utility'",
218
213
  "calculator": "open -a Calculator",
219
214
  "calc": "open -a Calculator",
@@ -230,7 +225,7 @@ def _get_os_aliases():
230
225
  "chromium": "chromium-browser",
231
226
  "firefox": "firefox",
232
227
  "brave": "brave-browser",
233
- "edge": "microsoft-edge", # Yes, it exists on Linux!
228
+ "edge": "microsoft-edge",
234
229
  "opera": "opera",
235
230
 
236
231
  # --- OFFICE (LibreOffice Suite) ---
@@ -240,7 +235,7 @@ def _get_os_aliases():
240
235
  "calc": "libreoffice --calc",
241
236
  "powerpoint": "libreoffice --impress",
242
237
  "impress": "libreoffice --impress",
243
- "teams": "teams-for-linux", # Common community wrapper
238
+ "teams": "teams-for-linux",
244
239
 
245
240
  # --- DEV TOOLS ---
246
241
  "vscode": "code",
@@ -249,8 +244,6 @@ def _get_os_aliases():
249
244
  "vim": "vim",
250
245
  "nano": "nano",
251
246
  "docker": "docker",
252
- # "terminal" usually defaults to x-terminal-emulator,
253
- # but we explicitly try common ones if that fails:
254
247
  "terminal": "gnome-terminal",
255
248
  "konsole": "konsole",
256
249
 
@@ -267,25 +260,25 @@ def _get_os_aliases():
267
260
  "spotify": "spotify",
268
261
  "rhythmbox": "rhythmbox",
269
262
  "mpv": "mpv",
270
- "photos": "eog", # Eye of GNOME (common image viewer)
263
+ "photos": "eog",
271
264
  "gimp": "gimp",
272
265
  "obs": "obs",
273
266
 
274
267
  # --- SYSTEM UTILITIES ---
275
268
  "explorer": "xdg-open .",
276
269
  "finder": "xdg-open .",
277
- "nautilus": "nautilus", # GNOME Files
278
- "dolphin": "dolphin", # KDE Files
279
- "thunar": "thunar", # XFCE Files
270
+ "nautilus": "nautilus",
271
+ "dolphin": "dolphin",
272
+ "thunar": "thunar",
273
+
280
274
 
281
- # "Settings" varies wildly, but these covers 80% of users:
282
275
  "settings": "gnome-control-center",
283
276
  "control panel": "gnome-control-center",
284
277
 
285
278
  # Task Manager equivalents
286
279
  "task manager": "gnome-system-monitor",
287
280
  "system monitor": "gnome-system-monitor",
288
- "htop": "x-terminal-emulator -e htop", # Opens htop in a new window
281
+ "htop": "x-terminal-emulator -e htop",
289
282
 
290
283
  "calculator": "gnome-calculator",
291
284
  "screenshot": "gnome-screenshot",
@@ -323,7 +316,6 @@ def refresh_app_cache():
323
316
  for app_dir in app_dirs:
324
317
  if not os.path.exists(app_dir): continue
325
318
  try:
326
- # macOS apps are folders ending in .app
327
319
  for item in os.listdir(app_dir):
328
320
  if item.endswith(".app"):
329
321
  name = item.replace(".app", "").lower()
@@ -334,7 +326,6 @@ def refresh_app_cache():
334
326
 
335
327
  # --- LINUX INDEXING ---
336
328
  elif system == "Linux":
337
- # Scan for .desktop files
338
329
  desktop_dirs = ["/usr/share/applications", os.path.expanduser("~/.local/share/applications")]
339
330
  for d_dir in desktop_dirs:
340
331
  if not os.path.exists(d_dir): continue
@@ -343,14 +334,11 @@ def refresh_app_cache():
343
334
  if item.endswith(".desktop"):
344
335
  name = item.replace(".desktop", "").lower()
345
336
  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
337
  APP_CACHE[name] = full_path
349
338
  except PermissionError:
350
339
  continue
351
340
 
352
341
 
353
- # Run indexing on import
354
342
  refresh_app_cache()
355
343
 
356
344
 
@@ -377,9 +365,7 @@ def open_app(name):
377
365
  lower_name = name.lower()
378
366
 
379
367
  # --- 1. DIRECT PATH ---
380
- # If the user provides a full path that exists
381
368
  if os.path.exists(name):
382
- # Optional: index_file(name) if available in your scope
383
369
  if 'index_file' in globals(): globals()['index_file'](name)
384
370
 
385
371
  if _native_open(name):
@@ -395,7 +381,6 @@ def open_app(name):
395
381
  return f"Failed to launch alias: {lower_name}"
396
382
 
397
383
  # --- 3. DYNAMIC SEARCH (Fuzzy Match) ---
398
- # Works if APP_CACHE is populated (Windows mostly, unless you add Linux/Mac indexers)
399
384
  if APP_CACHE:
400
385
  # Exact Match
401
386
  if lower_name in APP_CACHE:
@@ -412,13 +397,11 @@ def open_app(name):
412
397
  return f"Launched {best_match} (matched to '{name}')"
413
398
 
414
399
  # --- 4. EXECUTABLE IN PATH ---
415
- # Check if 'name' is a command available in the system PATH (e.g., 'npm', 'docker')
416
400
  if shutil.which(lower_name):
417
401
  _run_command(lower_name)
418
402
  return f"Executed command: {lower_name}"
419
403
 
420
404
  # --- 5. WEB URL FALLBACK ---
421
- # If it looks like a URL or domain
422
405
  if "." in name and " " not in name:
423
406
  url = name if name.startswith(("http://", "https://")) else f"https://{name}"
424
407
  webbrowser.open(url)
@@ -26,5 +26,5 @@ def listen_background(callback):
26
26
  """
27
27
  Starts a background listener for a hotword (Advanced).
28
28
  """
29
- # This requires a loop and is best left for Phase 2
29
+ # Future
30
30
  pass
@@ -17,7 +17,6 @@ def search_web(query):
17
17
  try:
18
18
  from tavily import TavilyClient
19
19
  client = TavilyClient(api_key=tavily_key)
20
- # 'search_context' returns optimized text for AI
21
20
  response = client.get_search_context(query=query, search_depth="basic", max_tokens=1500)
22
21
  return f"[Source: Tavily]\n{response}"
23
22
  except ImportError:
@@ -25,7 +24,6 @@ def search_web(query):
25
24
  except Exception:
26
25
  pass
27
26
 
28
- # 2. Fallback to DuckDuckGo
29
27
  try:
30
28
  results = DDGS().text(query, max_results=4)
31
29
  summary = []
@@ -17,7 +17,6 @@ def get_active_app():
17
17
  return {
18
18
  "title": window.title,
19
19
  "app_name": "Unknown (Window API limitation)",
20
- # Getting precise .exe from window is complex in pure python without win32gui
21
20
  "status": "Active"
22
21
  }
23
22
  else:
@@ -13,14 +13,10 @@ def _convert_to_pdf(docx_path):
13
13
  For this MVP, we will try a pure Python fallback if system tools aren't found.
14
14
  """
15
15
  try:
16
- # 1. Try LibreOffice (Best Quality, Cross Platform)
17
- # This requires LibreOffice to be in your PATH (soffice/libreoffice)
18
16
  cmd = f"soffice --headless --convert-to pdf \"{docx_path}\" --outdir \"{os.path.dirname(docx_path)}\""
19
17
  if os.system(cmd) == 0:
20
18
  return docx_path.replace(".docx", ".pdf")
21
19
 
22
- # 2. Try Microsoft Word (Windows Only, requires 'docx2pdf')
23
- # pip install docx2pdf
24
20
  try:
25
21
  from docx2pdf import convert
26
22
  convert(docx_path)
@@ -103,7 +99,6 @@ def create_document(filename, blocks):
103
99
  if i < len(row_cells):
104
100
  row_cells[i].text = str(cell_text)
105
101
 
106
- # --- BREAKS ---
107
102
  elif b_type == "page_break":
108
103
  doc.add_page_break()
109
104
 
@@ -116,7 +111,6 @@ def create_document(filename, blocks):
116
111
 
117
112
  doc.save(docx_path)
118
113
 
119
- # Try converting to PDF automatically
120
114
  pdf_result = _convert_to_pdf(docx_path)
121
115
 
122
116
  return f"Document created: {docx_path} (PDF Status: {pdf_result})"
@@ -1,4 +1,3 @@
1
- # FILE: tools/file_ops.py
2
1
  import os
3
2
  import fitz # PyMuPDF
4
3
  import pandas as pd
@@ -19,7 +18,6 @@ def read_file(path):
19
18
  ext = os.path.splitext(path)[1].lower()
20
19
 
21
20
  try:
22
- # --- PDF (Smart Read using PyMuPDF) ---
23
21
  if ext == '.pdf':
24
22
  text = ""
25
23
  try:
@@ -43,7 +43,7 @@ def search_flights(departure_id, arrival_id, date, travel_type=2):
43
43
 
44
44
  for i, f in enumerate(flights[:5], 1):
45
45
  price = f.get("price", "N/A")
46
- duration = f.get("total_duration", "N/A") # FIXED
46
+ duration = f.get("total_duration", "N/A")
47
47
 
48
48
  leg = f["flights"][0]
49
49
  airline = leg.get("airline", "Unknown")
@@ -3,10 +3,8 @@ from google.auth.transport.requests import Request
3
3
  from google.oauth2.credentials import Credentials
4
4
  from google_auth_oauthlib.flow import InstalledAppFlow
5
5
 
6
- # --- FIX: IMPORT PATHS FROM CENTRAL MODULE ---
7
6
  from sentinel.paths import CREDENTIALS_PATH, TOKEN_PATH
8
7
 
9
- # The permissions the AI needs
10
8
  SCOPES = [
11
9
  'https://www.googleapis.com/auth/gmail.readonly',
12
10
  'https://www.googleapis.com/auth/gmail.send'
@@ -16,32 +14,26 @@ SCOPES = [
16
14
  def get_gmail_service():
17
15
  creds = None
18
16
 
19
- # 1. Check if we already logged in (using the centralized token path)
20
17
  if os.path.exists(TOKEN_PATH):
21
18
  creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
22
19
 
23
- # 2. If not logged in or token expired, log in
24
20
  if not creds or not creds.valid:
25
21
  if creds and creds.expired and creds.refresh_token:
26
22
  creds.refresh(Request())
27
23
  else:
28
- # Check if credentials file exists in the user data folder
24
+
29
25
  if not os.path.exists(CREDENTIALS_PATH):
30
26
  print(f"Error: credentials.json not found at {CREDENTIALS_PATH}")
31
27
  return None
32
28
 
33
- # This opens your browser to login
34
29
  flow = InstalledAppFlow.from_client_secrets_file(
35
30
  CREDENTIALS_PATH, SCOPES)
36
31
  creds = flow.run_local_server(port=0)
37
32
 
38
- # Save the token for next time
39
- # Ensure the directory exists
40
33
  TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)
41
34
 
42
35
  with open(TOKEN_PATH, 'w') as token:
43
36
  token.write(creds.to_json())
44
37
 
45
- # 3. Return the API Service
46
38
  from googleapiclient.discovery import build
47
39
  return build('gmail', 'v1', credentials=creds)
@@ -1,4 +1,3 @@
1
- # FILE: tools/indexer.py
2
1
  import os
3
2
  import sqlite3
4
3
  import time
@@ -82,7 +81,6 @@ def build_index(root=None, verbose=False):
82
81
  count = 0
83
82
  updated = 0
84
83
 
85
- # We scan these specific user folders to stay efficient
86
84
  target_dirs = [
87
85
  os.path.join(root, "Documents"),
88
86
  os.path.join(root, "Desktop"),
@@ -93,11 +91,9 @@ def build_index(root=None, verbose=False):
93
91
  if not os.path.exists(target): continue
94
92
 
95
93
  for r, dirs, files in os.walk(target):
96
- # Clean skip dirs in-place
97
94
  dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith(".")]
98
95
 
99
96
  for file in files:
100
- # CPU BREATHING ROOM
101
97
  time.sleep(THROTTLE_SEC)
102
98
 
103
99
  ext = os.path.splitext(file)[1].lower()
@@ -111,23 +107,20 @@ def build_index(root=None, verbose=False):
111
107
  stats = os.stat(path)
112
108
  current_mtime = stats.st_mtime
113
109
 
114
- # Skip unchanged
115
110
  if path in existing_meta and existing_meta[path] == current_mtime:
116
111
  continue
117
112
 
118
113
  content = _get_text_from_file(path, ext)
119
114
  if not content or len(content) < 5: continue
120
115
 
121
- # Update Database
122
116
  c.execute("DELETE FROM files WHERE path = ?", (path,))
123
117
  c.execute("INSERT INTO files (path, name, content) VALUES (?,?,?)", (path, file, content))
124
118
  c.execute("INSERT OR REPLACE INTO file_meta (path, mtime) VALUES (?,?)", (path, current_mtime))
125
- conn.commit() # Commit often to save progress
119
+ conn.commit()
126
120
 
127
121
  updated += 1
128
122
  count += 1
129
123
 
130
- # LOGGING: Only print every 10th file to reduce spam
131
124
  if verbose and updated % 10 == 0:
132
125
  print(f" [Indexer] Indexed: {file} ({updated} total)")
133
126
 
@@ -144,7 +137,6 @@ def search_index(query):
144
137
  if not os.path.exists(DB_FILE): return "Index missing."
145
138
  conn = sqlite3.connect(DB_FILE)
146
139
  try:
147
- # Search content and return snippets
148
140
  res = conn.execute(
149
141
  "SELECT path, snippet(files, 2, '[', ']', '...', 10) FROM files WHERE files MATCH ? LIMIT 5",
150
142
  (query,)
@@ -64,7 +64,6 @@ def list_notes(category=None):
64
64
  def delete_note(note_id):
65
65
  """Removes a note by ID."""
66
66
  notes = _load_notes()
67
- # Handle string vs int ID mismatch
68
67
  try:
69
68
  note_id = int(note_id)
70
69
  except:
@@ -35,7 +35,7 @@ def index_file(path):
35
35
 
36
36
  name = os.path.basename(path)
37
37
  ext = os.path.splitext(path)[1]
38
- text = name # IMPORTANT: only embed filename (fast)
38
+ text = name
39
39
 
40
40
  emb = embed(text)
41
41
 
@@ -10,8 +10,7 @@ BASE_DIR.mkdir(exist_ok=True)
10
10
 
11
11
  DB_FILE = BASE_DIR / "sentinel-1.db"
12
12
 
13
- # --- CONFIGURATION: WHAT TO IGNORE ---
14
- # These are the "System Ones" we skip. Everything else is fair game.
13
+
15
14
  SKIP_DIRS = {
16
15
  # System Folders
17
16
  "Windows", "Program Files", "Program Files (x86)",
@@ -19,7 +18,7 @@ SKIP_DIRS = {
19
18
  "$RECYCLE.BIN", "System Volume Information", "Recovery",
20
19
  "boot", "efi",
21
20
 
22
- # Dev Junk (Optional - keep these to avoid noise, or remove to index code)
21
+ # Dev Junk
23
22
  "node_modules", "venv", ".venv", ".git", "__pycache__", ".idea", ".vscode"
24
23
  }
25
24
 
@@ -68,10 +67,8 @@ def build_index(silent=True):
68
67
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_name ON files(name)")
69
68
 
70
69
  # 2. Identify Scan Targets
71
- # Strategy: Scan the User Profile on C:, plus the ROOT of every other drive.
72
70
  targets = []
73
71
 
74
- # A. Always get standard user folders (Safest way to scan C:)
75
72
  home = os.path.expanduser("~")
76
73
  targets.extend([
77
74
  os.path.join(home, "Desktop"),
@@ -80,23 +77,17 @@ def build_index(silent=True):
80
77
  os.path.join(home, "Pictures"),
81
78
  os.path.join(home, "Music"),
82
79
  os.path.join(home, "Videos"),
83
- # Add your code folder explicitly if it's not in the above
84
80
  r"D:\Python Projects"
85
81
  ])
86
82
 
87
- # B. Add ALL other drives (D:, E:, etc)
88
83
  all_drives = get_all_drives()
89
84
  for drive in all_drives:
90
- # We generally avoid scanning C:\ root because it's 90% OS junk.
91
- # But D:\, E:\, etc. are usually pure data, so we scan their ROOT.
92
85
  if "C:" in drive.upper():
93
86
  continue
94
87
  targets.append(drive)
95
88
 
96
- # Remove duplicates
97
89
  targets = list(set(targets))
98
90
 
99
- # 3. Load Cache (for speed)
100
91
  if not silent: print("[System] Loading existing file index...")
101
92
  cursor.execute("SELECT path, mtime_raw FROM files")
102
93
  existing_files = {row['path']: row['mtime_raw'] for row in cursor.fetchall()}
@@ -107,17 +98,15 @@ def build_index(silent=True):
107
98
  if not silent:
108
99
  print(f"[System] Scanning {len(targets)} locations:\n" + "\n".join([f" - {t}" for t in targets]))
109
100
 
110
- # 4. The Scan Loop
111
101
  for folder in targets:
112
102
  if not os.path.exists(folder): continue
113
103
 
114
104
  for root, dirs, filenames in os.walk(folder):
115
- # --- FILTERING: This is where we skip "System Ones" ---
116
- # We modify 'dirs' in-place so os.walk doesn't even enter them.
105
+
117
106
  dirs[:] = [d for d in dirs if d not in SKIP_DIRS and not d.startswith(".")]
118
107
 
119
108
  for f in filenames:
120
- # Ignore temp/hidden files
109
+
121
110
  if f.startswith("~$") or f.startswith("."): continue
122
111
 
123
112
  fullpath = os.path.join(root, f)
@@ -128,7 +117,6 @@ def build_index(silent=True):
128
117
  stats = os.stat(fullpath)
129
118
  current_mtime = stats.st_mtime
130
119
 
131
- # Optimization: Skip if file hasn't changed since last scan
132
120
  if fullpath in existing_files and abs(existing_files[fullpath] - current_mtime) < 1.0:
133
121
  continue
134
122
 
@@ -149,7 +137,6 @@ def build_index(silent=True):
149
137
  VALUES (?, ?, ?, ?, ?, ?)
150
138
  ''', updates)
151
139
 
152
- # 6. Cleanup (Remove files that were deleted from disk)
153
140
  deleted_paths = set(existing_files.keys()) - seen_paths
154
141
  if deleted_paths:
155
142
  deleted_list = list(deleted_paths)
@@ -169,7 +156,6 @@ def search_db(query):
169
156
 
170
157
  sql_query = f"%{query}%"
171
158
 
172
- # Prioritize exact matches in name, then partial matches
173
159
  cursor.execute('''
174
160
  SELECT name, path, modified_date
175
161
  FROM files
@@ -52,7 +52,6 @@ def switch_model(provider, model=None):
52
52
  return f"⚡ Brain updated to [bold green]{provider.upper()}[/bold green] | Model: {model}"
53
53
 
54
54
 
55
- # ... (Keep get_clipboard, get_system_stats, run_cmd, kill_process as they were) ...
56
55
  def get_clipboard():
57
56
  try:
58
57
  return pyperclip.paste()
@@ -7,7 +7,6 @@ def get_current_weather(location=""):
7
7
  Uses wttr.in (No API key required).
8
8
  """
9
9
  try:
10
- # Format 3 gives a concise one-line output: "Paris: ⛅️ +14°C"
11
10
  url = f"https://wttr.in/{location}?format=4"
12
11
  response = requests.get(url, timeout=5)
13
12
 
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentinel-ai-os
3
- Version: 1.0
3
+ Version: 1.0.2
4
4
  Summary: Autonomous AI Operating System layer
5
5
  Author-email: Sam Selvaraj <samselvaraj1801@gmail.com>
6
- License: MIT
6
+ License-Expression: MIT
7
7
  Keywords: ai,agent,automation,llm
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
10
+ Requires-Python: <3.14,>=3.9
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: rich
@@ -49,6 +49,7 @@ Requires-Dist: comtypes
49
49
  Requires-Dist: pyperclip
50
50
  Requires-Dist: plyer
51
51
  Requires-Dist: pyttsx3
52
+ Requires-Dist: sentence-transformers
52
53
  Requires-Dist: pillow
53
54
  Requires-Dist: opencv-python
54
55
  Requires-Dist: SpeechRecognition
@@ -76,7 +77,6 @@ Dynamic: license-file
76
77
  ---
77
78
 
78
79
  ## 🔭 Overview
79
- <img width="1127" height="550" alt="image" src="https://github.com/user-attachments/assets/9ac617d6-a959-4a8c-b245-ff2c98f083ea" />
80
80
 
81
81
  Sentinel is not just a chatbot; it is an **Action Engine**. Unlike web-based LLMs that live in a sandbox, Sentinel runs locally on your machine with access to your file system, applications, and peripherals.
82
82
 
@@ -91,7 +91,6 @@ It operates on a **Think-Plan-Act** loop:
91
91
  ---
92
92
 
93
93
  ## ✨ Key Features
94
- <img width="2836" height="1338" alt="image" src="https://github.com/user-attachments/assets/877e5ec8-f398-4c04-b0b0-0c45b12d08c8" />
95
94
 
96
95
  * **🧠 Multi-Brain Support:** Powered by `litellm`, Sentinel can seamlessly switch between **OpenAI**, **Anthropic**, **Groq**, or run locally with **Ollama**.
97
96
  * **💾 Hybrid Memory Architecture:**
@@ -18,6 +18,8 @@ sentinel/core/scheduler.py
18
18
  sentinel/core/schema.py
19
19
  sentinel/core/setup.py
20
20
  sentinel/core/ui.py
21
+ sentinel/scripts/factory_reset.bat
22
+ sentinel/scripts/wipe_vector.bat
21
23
  sentinel/tools/__init__.py
22
24
  sentinel/tools/apps.py
23
25
  sentinel/tools/audio.py
@@ -37,6 +37,7 @@ comtypes
37
37
  pyperclip
38
38
  plyer
39
39
  pyttsx3
40
+ sentence-transformers
40
41
  pillow
41
42
  opencv-python
42
43
  SpeechRecognition
@@ -1 +0,0 @@
1
- recursive-include src/sentinel/scripts *
File without changes
File without changes