Add files via upload

This commit is contained in:
FatihMakes authored and GitHub committed 2026-05-10 17:04:19 +03:00
1 parent 40b3b0611a
commit a99448c250
27 files changed
+10043 -1446

No files matched your search

+278
View File
@@ -0,0 +1,278 @@
import json
import re
import sys
from pathlib import Path
def get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
PLANNER_PROMPT = """You are the planning module of MARK XXV, a personal AI assistant.
Your job: break any user goal into a sequence of steps using ONLY the tools listed below.
ABSOLUTE RULES:
- NEVER use generated_code or write Python scripts. It does not exist.
- NEVER reference previous step results in parameters. Every step is independent.
- Use web_search for ANY information retrieval, research, or current data.
- Use file_controller to save content to disk.
- Use cmd_control to open files or run system commands.
- Max 5 steps. Use the minimum steps needed.
AVAILABLE TOOLS AND THEIR PARAMETERS:
open_app
app_name: string (required)
web_search
query: string (required) — write a clear, focused search query
mode: "search" or "compare" (optional, default: search)
items: list of strings (optional, for compare mode)
aspect: string (optional, for compare mode)
game_updater
action: "update" | "install" | "list" | "download_status" | "schedule" (required)
platform: "steam" | "epic" | "both" (optional, default: both)
game_name: string (optional)
app_id: string (optional)
shutdown_when_done: boolean (optional)
browser_control
action: "go_to" | "search" | "click" | "type" | "scroll" | "get_text" | "press" | "close" (required)
url: string (for go_to)
query: string (for search)
text: string (for click/type)
direction: "up" | "down" (for scroll)
file_controller
action: "write" | "create_file" | "read" | "list" | "delete" | "move" | "copy" | "find" | "disk_usage" (required)
path: string — use "desktop" for Desktop folder
name: string — filename
content: string — file content (for write/create_file)
cmd_control
task: string (required) — natural language description of what to do
visible: boolean (optional)
computer_settings
action: string (required)
description: string — natural language description
value: string (optional)
computer_control
action: "type" | "click" | "hotkey" | "press" | "scroll" | "screenshot" | "screen_find" | "screen_click" (required)
text: string (for type)
x, y: int (for click)
keys: string (for hotkey, e.g. "ctrl+c")
key: string (for press)
direction: "up" | "down" (for scroll)
description: string (for screen_find/screen_click)
screen_process
text: string (required) — what to analyze or ask about the screen
angle: "screen" | "camera" (optional)
send_message
receiver: string (required)
message_text: string (required)
platform: string (required)
reminder
date: string YYYY-MM-DD (required)
time: string HH:MM (required)
message: string (required)
desktop_control
action: "wallpaper" | "organize" | "clean" | "list" | "task" (required)
path: string (optional)
task: string (optional)
youtube_video
action: "play" | "summarize" | "trending" (required)
query: string (for play)
weather_report
city: string (required)
flight_finder
origin: string (required)
destination: string (required)
date: string (required)
code_helper
action: "write" | "edit" | "run" | "explain" (required)
description: string (required)
language: string (optional)
output_path: string (optional)
file_path: string (optional)
dev_agent
description: string (required)
language: string (optional)
EXAMPLES:
Goal: "research mechanical engineering and save it to a notepad file"
Steps:
web_search | query: "mechanical engineering overview definition history"
web_search | query: "mechanical engineering applications and future trends"
file_controller | action: write, path: desktop, name: mechanical_engineering.txt, content: "MECHANICAL ENGINEERING RESEARCH\n\nThis file will be filled with web research results."
cmd_control | task: "open mechanical_engineering.txt on desktop with notepad"
Goal: "What is the price of Bitcoin"
Steps:
web_search | query: "Bitcoin price today USD"
Goal: "List the files on the desktop and find the largest 5 files"
Steps:
file_controller | action: list, path: desktop
file_controller | action: largest, path: desktop, count: 5
Goal: "Install PUBG from Steam"
Steps:
game_updater | action: install, platform: steam, game_name: "PUBG"
Goal: "Update all my Steam games"
Steps:
game_updater | action: update, platform: steam
Goal: "Send John a message on WhatsApp saying there is a meeting tomorrow"
Steps:
send_message | receiver: John, message_text: "There is a meeting tomorrow", platform: WhatsApp
Goal: "Open the clock and set a reminder for 30 minutes later"
Steps:
reminder | date: [today], time: [now+30min], message: "Reminder"
OUTPUT — return ONLY valid JSON, no markdown, no explanation, no code blocks:
{
"goal": "...",
"steps": [
{
"step": 1,
"tool": "tool_name",
"description": "what this step does",
"parameters": {},
"critical": true
}
]
}
"""
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def create_plan(goal: str, context: str = "") -> dict:
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(
model_name="gemini-2.5-flash-lite",
system_instruction=PLANNER_PROMPT
)
user_input = f"Goal: {goal}"
if context:
user_input += f"\n\nContext: {context}"
try:
response = model.generate_content(user_input)
text = response.text.strip()
text = re.sub(r"```(?:json)?", "", text).strip().rstrip("`").strip()
plan = json.loads(text)
if "steps" not in plan or not isinstance(plan["steps"], list):
raise ValueError("Invalid plan structure")
for step in plan["steps"]:
if step.get("tool") in ("generated_code",):
print(f"[Planner] ⚠️ generated_code detected in step {step.get('step')} — replacing with web_search")
desc = step.get("description", goal)
step["tool"] = "web_search"
step["parameters"] = {"query": desc[:200]}
print(f"[Planner] ✅ Plan: {len(plan['steps'])} steps")
for s in plan["steps"]:
print(f" Step {s['step']}: [{s['tool']}] {s['description']}")
return plan
except json.JSONDecodeError as e:
print(f"[Planner] ⚠️ JSON parse failed: {e}")
return _fallback_plan(goal)
except Exception as e:
print(f"[Planner] ⚠️ Planning failed: {e}")
return _fallback_plan(goal)
def _fallback_plan(goal: str) -> dict:
print("[Planner] 🔄 Fallback plan")
return {
"goal": goal,
"steps": [
{
"step": 1,
"tool": "web_search",
"description": f"Search for: {goal}",
"parameters": {"query": goal},
"critical": True
}
]
}
def replan(goal: str, completed_steps: list, failed_step: dict, error: str) -> dict:
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(
model_name="gemini-2.5-flash",
system_instruction=PLANNER_PROMPT
)
completed_summary = "\n".join(
f" - Step {s['step']} ({s['tool']}): DONE" for s in completed_steps
)
prompt = f"""Goal: {goal}
Already completed:
{completed_summary if completed_summary else ' (none)'}
Failed step: [{failed_step.get('tool')}] {failed_step.get('description')}
Error: {error}
Create a REVISED plan for the remaining work only. Do not repeat completed steps."""
try:
response = model.generate_content(prompt)
text = response.text.strip()
text = re.sub(r"```(?:json)?", "", text).strip().rstrip("`").strip()
plan = json.loads(text)
for step in plan.get("steps", []):
if step.get("tool") == "generated_code":
step["tool"] = "web_search"
step["parameters"] = {"query": step.get("description", goal)[:200]}
print(f"[Planner] 🔄 Revised plan: {len(plan['steps'])} steps")
return plan
except Exception as e:
print(f"[Planner] ⚠️ Replan failed: {e}")
return _fallback_plan(goal)
+520
View File
@@ -0,0 +1,520 @@
import asyncio
import threading
import concurrent.futures
import platform
import shutil
import subprocess
from pathlib import Path
from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeout
def _get_default_browser_id() -> str:
"""Returns raw default browser identifier string for current OS."""
system = platform.system()
try:
if system == "Windows":
import winreg
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice"
)
prog_id = winreg.QueryValueEx(key, "ProgId")[0].lower()
winreg.CloseKey(key)
return prog_id
elif system == "Darwin":
result = subprocess.run(
["defaults", "read",
"com.apple.LaunchServices/com.apple.launchservices.secure",
"LSHandlers"],
capture_output=True, text=True, timeout=5
)
return result.stdout.lower()
elif system == "Linux":
result = subprocess.run(
["xdg-settings", "get", "default-web-browser"],
capture_output=True, text=True, timeout=5
)
return result.stdout.lower()
except Exception:
pass
return ""
_BROWSER_BINARIES = {
"Windows": {
"opera": ["opera.exe"],
"brave": ["brave.exe"],
"vivaldi": ["vivaldi.exe"],
"chrome": ["chrome.exe"],
"firefox": ["firefox.exe"],
},
"Darwin": {
"opera": ["opera"],
"brave": ["brave browser", "brave"],
"vivaldi": ["vivaldi"],
"chrome": ["google chrome", "google-chrome"],
"firefox": ["firefox"],
},
"Linux": {
"opera": ["opera", "opera-stable"],
"brave": ["brave-browser", "brave"],
"vivaldi": ["vivaldi-stable", "vivaldi"],
"chrome": ["google-chrome", "google-chrome-stable", "chromium-browser", "chromium"],
"firefox": ["firefox"],
},
}
def _get_opera_executable() -> str | None:
if platform.system() != "Windows":
return None
try:
import winreg
candidate_keys = [
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\opera.exe",
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\launcher.exe",
r"SOFTWARE\Clients\StartMenuInternet\OperaStable\shell\open\command",
r"SOFTWARE\Clients\StartMenuInternet\OperaGXStable\shell\open\command",
]
for key_path in candidate_keys:
for hive in [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]:
try:
key = winreg.OpenKey(hive, key_path)
val = winreg.QueryValue(key, None)
winreg.CloseKey(key)
exe = val.strip().strip('"').split('"')[0].split(" --")[0].strip()
if exe and Path(exe).exists():
print(f"[Browser] 🔍 Opera found via registry: {exe}")
return exe
except Exception:
continue
except Exception:
pass
return None
def _find_browser_executable(prog_id: str) -> tuple:
"""
Returns (engine_name, exe_path, channel, is_opera).
is_opera=True → extra args needed to prevent private-mode launch.
"""
system = platform.system()
os_bins = _BROWSER_BINARIES.get(system, {})
if any(x in prog_id for x in ["firefox", "mozilla"]):
return "firefox", None, None, False
if "safari" in prog_id:
return "webkit", None, None, False
if "edge" in prog_id:
return "chromium", None, "msedge", False
if "opera" in prog_id:
exe = _get_opera_executable()
if exe:
return "chromium", exe, None, True
for binary in os_bins.get("opera", []):
path = shutil.which(binary)
if path:
return "chromium", path, None, True
browser_patterns = {
"brave": ["brave"],
"vivaldi": ["vivaldi"],
"chrome": ["chrome"],
}
for browser_name, patterns in browser_patterns.items():
if not any(p in prog_id for p in patterns):
continue
binaries = os_bins.get(browser_name, [])
for binary in binaries:
path = shutil.which(binary)
if path:
print(f"[Browser] 🔍 Found {browser_name} at: {path}")
return "chromium", path, None, False
if "chrome" in prog_id or not prog_id:
return "chromium", None, "chrome", False
return "chromium", None, None, False
class _BrowserThread:
def __init__(self):
self._loop = None
self._thread = None
self._ready = threading.Event()
self._playwright = None
self._browser = None
self._context = None
self._page = None
self._engine_name = "chromium"
self._exe_path = None
self._channel = None
self._is_opera = False
def start(self):
if self._thread and self._thread.is_alive():
return
self._thread = threading.Thread(
target=self._run_loop, daemon=True, name="BrowserThread"
)
self._thread.start()
self._ready.wait(timeout=15)
def _run_loop(self):
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._init())
self._ready.set()
self._loop.run_forever()
async def _init(self):
self._playwright = await async_playwright().start()
def run(self, coro, timeout: int = 30):
if not self._loop:
raise RuntimeError("BrowserThread not started.")
future = asyncio.run_coroutine_threadsafe(coro, self._loop)
return future.result(timeout=timeout)
# ── Tarayıcı ve sayfa yönetimi ───────────────────────────────────────────
async def _launch_browser_if_needed(self):
"""
Tarayıcıyı başlatır. Zaten açıksa hiçbir şey yapmaz.
Her zaman default tarayıcıyı kullanır, özel sekme açmaz.
"""
if self._browser and self._browser.is_connected():
return
prog_id = _get_default_browser_id()
self._engine_name, self._exe_path, self._channel, self._is_opera = _find_browser_executable(prog_id)
engine = getattr(self._playwright, self._engine_name)
# Temel chromium argümanları
chromium_args = ["--start-maximized"]
if self._is_opera:
# Opera GX bazı sürümlerde varsayılan olarak private modda başlar.
# Aşağıdaki flag'ler bunu engeller.
chromium_args += [
"--disable-features=OperaPrivacyMode",
"--no-private",
]
print("[Browser] 🎭 Opera detected — disabling private-mode flags")
launch_kwargs = {"headless": False}
if self._engine_name == "chromium":
launch_kwargs["args"] = chromium_args
if self._exe_path:
launch_kwargs["executable_path"] = self._exe_path
elif self._channel:
launch_kwargs["channel"] = self._channel
try:
self._browser = await engine.launch(**launch_kwargs)
print(
f"[Browser] ✅ Launched ({self._engine_name}"
f"{' / ' + self._channel if self._channel else ''}"
f"{' / ' + self._exe_path if self._exe_path else ''})"
)
except Exception as e:
print(f"[Browser] ⚠️ Launch failed ({e}), falling back to built-in Chromium")
self._browser = await self._playwright.chromium.launch(
headless=False,
args=["--start-maximized"]
)
async def _get_page(self):
"""
Mevcut sayfayı döndürür.
- Tarayıcı kapalıysa açar.
- Context yoksa oluşturur.
- Sayfa kapalıysa yeni sekme açar (aynı pencerede).
- Sayfa zaten açıksa aynı sayfayı döndürür (yeni pencere açmaz).
"""
await self._launch_browser_if_needed()
if self._context is None:
self._context = await self._browser.new_context(
viewport=None,
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
)
)
if self._page is None or self._page.is_closed():
self._page = await self._context.new_page()
return self._page
# ── Eylemler ─────────────────────────────────────────────────────────────
async def _go_to(self, url: str) -> str:
if not url.startswith("http"):
url = "https://" + url
page = await self._get_page()
try:
await page.goto(url, wait_until="domcontentloaded", timeout=15000)
return f"Opened: {page.url}"
except PlaywrightTimeout:
return f"Timeout loading: {url}"
except Exception as e:
return f"Navigation error: {e}"
async def _search(self, query: str, engine: str = "google") -> str:
engines = {
"google": f"https://www.google.com/search?q={query.replace(' ', '+')}",
"bing": f"https://www.bing.com/search?q={query.replace(' ', '+')}",
"duckduckgo": f"https://duckduckgo.com/?q={query.replace(' ', '+')}",
}
url = engines.get(engine.lower(), engines["google"])
return await self._go_to(url)
async def _click(self, selector=None, text=None) -> str:
page = await self._get_page()
try:
if text:
await page.get_by_text(text, exact=False).first.click(timeout=8000)
return f"Clicked: '{text}'"
elif selector:
await page.click(selector, timeout=8000)
return f"Clicked: {selector}"
return "No selector or text provided."
except PlaywrightTimeout:
return "Element not found or not clickable."
except Exception as e:
return f"Click error: {e}"
async def _type(self, selector=None, text: str = "", clear_first: bool = True) -> str:
page = await self._get_page()
try:
element = page.locator(selector).first if selector else page.locator(":focus")
if clear_first:
await element.clear()
await element.type(text, delay=50)
return "Text typed."
except Exception as e:
return f"Type error: {e}"
async def _scroll(self, direction: str = "down", amount: int = 500) -> str:
page = await self._get_page()
try:
y = amount if direction == "down" else -amount
await page.mouse.wheel(0, y)
return f"Scrolled {direction}."
except Exception as e:
return f"Scroll error: {e}"
async def _press(self, key: str) -> str:
page = await self._get_page()
try:
await page.keyboard.press(key)
return f"Pressed: {key}"
except Exception as e:
return f"Key error: {e}"
async def _get_text(self) -> str:
page = await self._get_page()
try:
text = await page.inner_text("body")
return text[:4000] if len(text) > 4000 else text
except Exception as e:
return f"Could not get page text: {e}"
async def _fill_form(self, fields: dict) -> str:
page = await self._get_page()
results = []
for selector, value in fields.items():
try:
el = page.locator(selector).first
await el.clear()
await el.type(str(value), delay=40)
results.append(f"✓ {selector}")
except Exception as e:
results.append(f"✗ {selector}: {e}")
return "Form filled: " + ", ".join(results)
async def _smart_click(self, description: str) -> str:
page = await self._get_page()
desc_lower = description.lower()
role_hints = {
"button": ["button", "buton", "btn"],
"link": ["link", "bağlantı"],
"searchbox": ["search", "arama"],
"textbox": ["input", "field", "alan"],
}
for role, keywords in role_hints.items():
if any(k in desc_lower for k in keywords):
try:
await page.get_by_role(role).first.click(timeout=5000)
return f"Clicked ({role}): '{description}'"
except Exception:
pass
try:
await page.get_by_text(description, exact=False).first.click(timeout=5000)
return f"Clicked (text): '{description}'"
except Exception:
pass
try:
await page.get_by_placeholder(description, exact=False).first.click(timeout=5000)
return f"Clicked (placeholder): '{description}'"
except Exception:
pass
return f"Could not find: '{description}'"
async def _smart_type(self, description: str, text: str) -> str:
page = await self._get_page()
for method, locator in [
("placeholder", page.get_by_placeholder(description, exact=False)),
("label", page.get_by_label(description, exact=False)),
("role", page.get_by_role("textbox")),
]:
try:
el = locator.first
await el.clear()
await el.type(text, delay=50)
return f"Typed into ({method}): '{description}'"
except Exception:
continue
return f"Could not find input: '{description}'"
async def _close_browser(self) -> str:
if self._browser:
await self._browser.close()
self._browser = None
self._context = None
self._page = None
if self._playwright:
await self._playwright.stop()
self._playwright = None
return "Browser closed."
# ── Singleton browser thread ─────────────────────────────────────────────────
_bt = _BrowserThread()
_bt_started = False
_bt_lock = threading.Lock()
def _ensure_started():
global _bt_started
with _bt_lock:
if not _bt_started:
_bt.start()
_bt_started = True
# ── Public API ───────────────────────────────────────────────────────────────
def browser_control(
parameters: dict,
response=None,
player=None,
session_memory=None
) -> str:
"""
Browser controller — auto-detects and uses system default browser.
Always reuses the existing browser window/page; never opens incognito.
parameters:
action : go_to | search | click | type | scroll | fill_form |
smart_click | smart_type | get_text | press | close
url : URL for go_to
query : search query
engine : google | bing | duckduckgo (default: google)
selector : CSS selector for click/type
text : text to click or type
description : element description for smart_click/smart_type
direction : up | down for scroll
amount : scroll amount in pixels (default: 500)
key : key name for press (e.g. Enter, Escape, Tab)
fields : {selector: value} dict for fill_form
clear_first : bool, clear input before typing (default: True)
"""
_ensure_started()
action = (parameters or {}).get("action", "").lower().strip()
result = "Unknown action."
try:
if action == "go_to":
result = _bt.run(_bt._go_to(parameters.get("url", "")))
elif action == "search":
result = _bt.run(_bt._search(
parameters.get("query", ""),
parameters.get("engine", "google"),
))
elif action == "click":
result = _bt.run(_bt._click(
selector=parameters.get("selector"),
text=parameters.get("text"),
))
elif action == "type":
result = _bt.run(_bt._type(
selector=parameters.get("selector"),
text=parameters.get("text", ""),
clear_first=parameters.get("clear_first", True),
))
elif action == "scroll":
result = _bt.run(_bt._scroll(
direction=parameters.get("direction", "down"),
amount=parameters.get("amount", 500),
))
elif action == "fill_form":
result = _bt.run(_bt._fill_form(parameters.get("fields", {})))
elif action == "smart_click":
result = _bt.run(_bt._smart_click(parameters.get("description", "")))
elif action == "smart_type":
result = _bt.run(_bt._smart_type(
parameters.get("description", ""),
parameters.get("text", ""),
))
elif action == "get_text":
result = _bt.run(_bt._get_text())
elif action == "press":
result = _bt.run(_bt._press(parameters.get("key", "Enter")))
elif action == "close":
result = _bt.run(_bt._close_browser())
else:
result = f"Unknown action: {action}"
except concurrent.futures.TimeoutError:
result = "Browser action timed out."
except Exception as e:
result = f"Browser error: {e}"
print(f"[Browser] {result[:80]}")
if player:
player.write_log(f"[browser] {result[:60]}")
return result
+583
View File
@@ -0,0 +1,583 @@
# actions/code_helper.py
# AI-powered code assistant — writes, edits, explains, runs, builds, debugs, and optimizes code.
#
# Actions:
# write → Describe what you want, Gemini writes it, saves to file
# edit → Read existing file, apply natural language change
# explain → Explain what a piece of code or file does
# run → Execute a script file, return output
# build → Write → Run → Fix loop (max 3 attempts), speaks when done
# screen_debug → Screenshot al, ekrandaki kodu/hatayı Gemini ile analiz et ve düzelt
# optimize → Mevcut kodu Gemini ile optimize et (performans, okunabilirlik, best practices)
# auto → (default) Intent auto-detected from context
import subprocess
import sys
import json
import re
import time
from pathlib import Path
def get_base_dir():
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
DESKTOP = Path.home() / "Desktop"
MAX_BUILD_ATTEMPTS = 3
GEMINI_MODEL = "gemini-2.5-flash"
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _get_gemini(model: str = GEMINI_MODEL):
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
return genai.GenerativeModel(model)
def _clean_code(text: str) -> str:
text = text.strip()
text = re.sub(r"^```[a-zA-Z]*\n?", "", text)
text = re.sub(r"\n?```$", "", text)
return text.strip()
def _resolve_save_path(output_path: str, language: str) -> Path:
ext_map = {
"python": ".py", "py": ".py",
"javascript": ".js", "js": ".js",
"typescript": ".ts", "ts": ".ts",
"html": ".html", "css": ".css",
"java": ".java", "cpp": ".cpp", "c": ".c",
"bash": ".sh", "shell": ".sh", "powershell": ".ps1",
"sql": ".sql", "json": ".json", "rust": ".rs", "go": ".go",
}
if output_path:
p = Path(output_path)
return p if p.is_absolute() else DESKTOP / p
ext = ext_map.get((language or "python").lower(), ".py")
return DESKTOP / f"jarvis_code{ext}"
def _read_file(file_path: str) -> tuple[str, str]:
if not file_path:
return "", "No file path provided."
p = Path(file_path)
if not p.exists():
return "", f"File not found: {file_path}"
try:
return p.read_text(encoding="utf-8"), ""
except Exception as e:
return "", f"Could not read file: {e}"
def _save_file(path: Path, content: str) -> str:
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return f"Saved to: {path}"
except Exception as e:
return f"Could not save: {e}"
def _preview(code: str, lines: int = 10) -> str:
all_lines = code.splitlines()
preview = "\n".join(all_lines[:lines])
suffix = f"\n... ({len(all_lines) - lines} more lines)" if len(all_lines) > lines else ""
return preview + suffix
def _has_error(output: str) -> bool:
error_signals = ["error", "exception", "traceback", "syntaxerror",
"nameerror", "typeerror", "stderr", "failed", "crash"]
return any(s in output.lower() for s in error_signals)
def _take_screenshot() -> Path | None:
try:
import pyautogui
screenshot_path = Path.home() / "Desktop" / f"jarvis_debug_{int(time.time())}.png"
screenshot = pyautogui.screenshot()
screenshot.save(str(screenshot_path))
print(f"[Code] 📸 Screenshot: {screenshot_path}")
return screenshot_path
except Exception as e:
print(f"[Code] ⚠️ Screenshot failed: {e}")
return None
def _image_to_base64(path: Path) -> str:
import base64
return base64.b64encode(path.read_bytes()).decode("utf-8")
def _detect_intent(description: str, file_path: str, code: str) -> str:
desc = (description or "").lower()
screen_kw = ["ekrandaki", "screen", "ekranda", "bu hatayı", "why am i getting",
"neden hata", "what's wrong", "ne yanlış", "screenshot", "görüntü"]
if any(k in desc for k in screen_kw):
return "screen_debug"
# Optimize keywords
optimize_kw = ["optimize", "refactor", "clean up", "improve", "temizle",
"iyileştir", "daha iyi", "make it better", "hızlandır"]
if any(k in desc for k in optimize_kw) and (code or file_path):
return "optimize"
if file_path:
p = Path(file_path)
edit_kw = ["edit", "update", "modify", "change", "add", "remove",
"refactor", "fix", "rename", "replace", "düzenle", "değiştir"]
run_kw = ["run", "execute", "launch", "start", "çalıştır"]
build_kw = ["build", "make it work", "try", "attempt"]
if p.exists() and any(k in desc for k in edit_kw):
return "edit"
if p.exists() and any(k in desc for k in run_kw):
return "run"
if any(k in desc for k in build_kw):
return "build"
if p.exists():
return "explain"
explain_kw = ["explain", "what does", "describe", "analyze", "açıkla", "ne yapıyor"]
if any(k in desc for k in explain_kw) and (code or file_path):
return "explain"
build_kw = ["build", "make it work", "try and", "attempt"]
if any(k in desc for k in build_kw):
return "build"
return "write"
def _write(description: str, language: str, output_path: str, player=None) -> tuple[str, Path]:
lang = language or "python"
model = _get_gemini()
prompt = f"""You are an expert {lang} developer.
Write clean, working, well-commented {lang} code for the description below.
Rules:
- Output ONLY the code. No explanation, no markdown, no backticks.
- Add helpful inline comments.
- Handle errors and edge cases properly.
- Use modern best practices.
Description: {description}
Code:"""
response = model.generate_content(prompt)
code = _clean_code(response.text)
path = _resolve_save_path(output_path, lang)
_save_file(path, code)
return code, path
def _fix_code(code: str, error_output: str, description: str) -> str:
model = _get_gemini()
prompt = f"""You are an expert debugger.
The code below failed with the following error. Fix it.
Return ONLY the corrected code — no explanation, no markdown, no backticks.
Original goal: {description}
Error:
{error_output[:2000]}
Broken code:
{code}
Fixed code:"""
response = model.generate_content(prompt)
return _clean_code(response.text)
def _run_file(path: Path, args: list, timeout: int) -> str:
interpreters = {
".py": [sys.executable],
".js": ["node"],
".ts": ["ts-node"],
".sh": ["bash"],
".ps1": ["powershell", "-File"],
".rb": ["ruby"],
".php": ["php"],
}
interp = interpreters.get(path.suffix.lower())
if not interp:
return f"No interpreter for {path.suffix}."
try:
result = subprocess.run(
interp + [str(path)] + (args or []),
capture_output=True, text=True,
encoding="utf-8", errors="replace",
timeout=timeout, cwd=str(path.parent)
)
output = result.stdout.strip()
error = result.stderr.strip()
parts = []
if output: parts.append(f"Output:\n{output}")
if error: parts.append(f"Stderr:\n{error}")
return "\n\n".join(parts) if parts else "Executed with no output."
except subprocess.TimeoutExpired:
return f"Timed out after {timeout}s."
except FileNotFoundError:
return f"Interpreter not found: {interp[0]}."
except Exception as e:
return f"Execution error: {e}"
def _build(description, language, output_path, args, timeout, speak=None, player=None) -> str:
if not description:
return "Please describe what you want me to build, sir."
if player:
player.write_log("[Code] Build started...")
lang = language or "python"
try:
code, path = _write(description, lang, output_path, player)
print(f"[Code] ✅ Written: {path}")
except Exception as e:
msg = f"Could not write initial code: {e}"
if speak: speak(msg)
return msg
last_output = ""
for attempt in range(1, MAX_BUILD_ATTEMPTS + 1):
print(f"[Code] 🔄 Attempt {attempt}/{MAX_BUILD_ATTEMPTS}")
if player:
player.write_log(f"[Code] Attempt {attempt}...")
last_output = _run_file(path, args, timeout)
if not _has_error(last_output):
msg = (
f"Build complete, sir. "
f"The code is working after {attempt} attempt{'s' if attempt > 1 else ''}. "
f"Saved to {path}."
)
if speak: speak(msg)
return f"{msg}\n\nOutput:\n{last_output}"
print(f"[Code] ⚠️ Error on attempt {attempt}, fixing...")
if player:
player.write_log(f"[Code] Fixing (attempt {attempt})...")
try:
code = _fix_code(code, last_output, description)
_save_file(path, code)
except Exception as e:
msg = f"Could not fix code on attempt {attempt}: {e}"
if speak: speak(msg)
return msg
msg = (
f"I was unable to build a working version after {MAX_BUILD_ATTEMPTS} attempts, sir. "
f"The last error was: {last_output[:200]}"
)
if speak: speak(msg)
return f"{msg}\n\nLast code saved to: {path}"
def _write_action(description, language, output_path, player) -> str:
if not description:
return "Please describe what you want me to write, sir."
if player:
player.write_log("[Code] Writing code...")
try:
code, path = _write(description, language, output_path, player)
print(f"[Code] ✅ Written: {path}")
return f"Code written. Saved to: {path}\n\nPreview:\n{_preview(code)}"
except Exception as e:
return f"Could not generate code: {e}"
def _edit_action(file_path, instruction, player) -> str:
if not file_path:
return "Please provide a file path to edit, sir."
if not instruction:
return "Please describe what change to make, sir."
content, err = _read_file(file_path)
if err:
return err
if player:
player.write_log("[Code] Editing file...")
model = _get_gemini()
prompt = f"""You are an expert code editor.
Apply the following change to the code below.
Return ONLY the complete updated code — no explanation, no markdown, no backticks.
Change: {instruction}
Original code:
{content}
Updated code:"""
try:
response = model.generate_content(prompt)
edited = _clean_code(response.text)
except Exception as e:
return f"Could not edit code: {e}"
status = _save_file(Path(file_path), edited)
print(f"[Code] ✅ Edited: {file_path}")
return f"File edited. {status}\n\nPreview:\n{_preview(edited)}"
def _explain_action(file_path, code, player) -> str:
if file_path and not code:
code, err = _read_file(file_path)
if err:
return err
if not code:
return "Please provide code or a file path to explain, sir."
if player:
player.write_log("[Code] Analyzing code...")
model = _get_gemini()
prompt = f"""Explain what this code does in simple, clear language.
Focus on: what it does, how it works, and any important details.
Be concise — 3 to 6 sentences maximum.
Code:
{code[:4000]}
Explanation:"""
try:
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"Could not explain code: {e}"
def _run_action(file_path, args, timeout, player) -> str:
if not file_path:
return "Please provide a file path to run, sir."
p = Path(file_path)
if not p.exists():
return f"File not found: {file_path}"
if player:
player.write_log(f"[Code] Running {p.name}...")
return _run_file(p, args, timeout)
def _optimize_action(file_path, code, language, output_path, player) -> str:
if file_path and not code:
code, err = _read_file(file_path)
if err:
return err
if not code:
return "Please provide code or a file path to optimize, sir."
if player:
player.write_log("[Code] Optimizing code...")
lang = language or "python"
model = _get_gemini()
prompt = f"""You are an expert {lang} developer and code reviewer.
Optimize the following code for:
1. Performance — eliminate unnecessary operations, use efficient data structures
2. Readability — clear variable names, proper formatting, logical structure
3. Best practices — modern {lang} patterns, error handling, type hints if applicable
4. Remove dead code, redundant comments, and unnecessary complexity
Return ONLY the optimized code — no explanation, no markdown, no backticks.
Original code:
{code[:6000]}
Optimized code:"""
try:
response = model.generate_content(prompt)
optimized = _clean_code(response.text)
except Exception as e:
return f"Could not optimize code: {e}"
# Kaydet
if file_path:
save_path = Path(file_path)
else:
save_path = _resolve_save_path(output_path, lang)
status = _save_file(save_path, optimized)
print(f"[Code] ✅ Optimized: {save_path}")
original_lines = len(code.splitlines())
optimized_lines = len(optimized.splitlines())
diff = original_lines - optimized_lines
return (
f"Code optimized. {status}\n"
f"Lines: {original_lines} → {optimized_lines} "
f"({'−' if diff > 0 else '+'}{abs(diff)} lines)\n\n"
f"Preview:\n{_preview(optimized)}"
)
def _screen_debug_action(description, file_path, player, speak=None) -> str:
if player:
player.write_log("[Code] Taking screenshot for analysis...")
print("[Code] 📸 Capturing screen for debug...")
screenshot_path = _take_screenshot()
if not screenshot_path:
return "Could not take screenshot, sir. Please make sure PyAutoGUI is installed."
file_content = ""
if file_path:
file_content, err = _read_file(file_path)
if err:
print(f"[Code] ⚠️ Could not read file: {err}")
try:
from google import genai
from google.genai import types
client = genai.Client(api_key=_get_api_key())
image_bytes = screenshot_path.read_bytes()
image_base64 = _image_to_base64(screenshot_path)
user_question = description or "What error or problem do you see on the screen? How can it be fixed?"
context = ""
if file_content:
context = f"\n\nAdditionally, here is the related file content:\n```\n{file_content[:4000]}\n```"
analysis_prompt = f"""You are an expert programmer and debugger analyzing a screenshot.
User's question: {user_question}{context}
Please:
1. Identify any errors, exceptions, or problems visible on the screen
2. Explain what is causing the problem in simple terms
3. Provide a concrete fix or solution
4. If there's code visible, show the corrected version
Be specific and actionable. If you see an error message, quote it exactly."""
contents = [
types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
analysis_prompt,
]
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=contents,
)
analysis = response.text.strip()
print(f"[Code] ✅ Screen analysis complete")
try:
screenshot_path.unlink()
except Exception:
pass
if file_path and file_content:
code_match = re.search(r"```[a-zA-Z]*\n(.*?)```", analysis, re.DOTALL)
if code_match:
fixed_code = code_match.group(1).strip()
save_path = Path(file_path)
_save_file(save_path, fixed_code)
analysis += f"\n\n✅ Fixed code has been saved to: {file_path}"
print(f"[Code] ✅ Fixed code saved: {file_path}")
return analysis
except Exception as e:
try:
screenshot_path.unlink()
except Exception:
pass
return f"Screen analysis failed: {e}"
def code_helper(
parameters: dict,
response=None,
player=None,
session_memory=None,
speak=None
) -> str:
"""
Called from main.py.
parameters:
action : write | edit | explain | run | build | screen_debug | optimize | auto
description : What the code should do / what change to make / what problem to analyze
language : Programming language (default: python)
output_path : Where to save — user specifies full path or filename
file_path : Path to existing file (edit / explain / run / build / optimize)
code : Raw code string (explain/optimize without a file)
args : CLI argument list for run/build
timeout : Execution timeout in seconds (default: 30)
"""
p = parameters or {}
action = p.get("action", "auto").lower().strip()
description = p.get("description", "").strip()
language = p.get("language", "python").strip()
output_path = p.get("output_path", "").strip()
file_path = p.get("file_path", "").strip()
code = p.get("code", "").strip()
args = p.get("args", [])
timeout = int(p.get("timeout", 30))
if action == "auto":
action = _detect_intent(description, file_path, code)
print(f"[Code] 🤖 Auto-detected: {action}")
if action == "write":
return _write_action(description, language, output_path, player)
elif action == "edit":
return _edit_action(
file_path,
description or p.get("instruction", ""),
player
)
elif action == "explain":
return _explain_action(file_path, code, player)
elif action == "run":
return _run_action(file_path, args, timeout, player)
elif action == "build":
return _build(description, language, output_path, args, timeout, speak, player)
elif action == "optimize":
return _optimize_action(file_path, code, language, output_path, player)
elif action == "screen_debug":
return _screen_debug_action(description, file_path, player, speak)
else:
return f"Unknown action: '{action}'. Use write, edit, explain, run, build, optimize, or screen_debug."
+478
View File
@@ -0,0 +1,478 @@
#computer_control.py
import io
import json
import re
import string
import subprocess
import sys
import time
import random
from pathlib import Path
try:
import pyautogui
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.05
_PYAUTOGUI = True
except ImportError:
_PYAUTOGUI = False
try:
import pyperclip
_PYPERCLIP = True
except ImportError:
_PYPERCLIP = False
def _base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
_BASE = _base_dir()
_CONFIG_PATH = _BASE / "config" / "api_keys.json"
_MEMORY_PATH = _BASE / "memory" / "long_term.json"
def _load_config() -> dict:
try:
return json.loads(_CONFIG_PATH.read_text(encoding="utf-8"))
except Exception:
return {}
def _get_os() -> str:
return _load_config().get("os_system", "windows").lower()
_SAFE_SCREENSHOT_ROOTS = (
Path.home(),
)
def _safe_screenshot_path(requested: str | None) -> Path:
fallback = Path.home() / "Desktop" / "jarvis_screenshot.png"
if not requested:
return fallback
try:
p = Path(requested).expanduser().resolve()
for root in _SAFE_SCREENSHOT_ROOTS:
if p.is_relative_to(root.resolve()):
p.parent.mkdir(parents=True, exist_ok=True)
return p
except Exception:
pass
return fallback
def _require_pyautogui():
if not _PYAUTOGUI:
raise RuntimeError("PyAutoGUI not installed. Run: pip install pyautogui")
_FIRST_NAMES = [
"Alex", "Jordan", "Taylor", "Morgan", "Casey", "Riley", "Drew", "Quinn",
"Avery", "Blake", "Cameron", "Dakota", "Emerson", "Finley", "Harper",
]
_LAST_NAMES = [
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
"Davis", "Wilson", "Moore", "Taylor", "Anderson", "Thomas", "Jackson",
]
_DOMAINS = ["gmail.com", "yahoo.com", "outlook.com", "proton.me", "mail.com"]
def _random_data(data_type: str) -> str:
dt = data_type.lower().strip()
if dt == "first_name":
return random.choice(_FIRST_NAMES)
if dt == "last_name":
return random.choice(_LAST_NAMES)
if dt == "name":
return f"{random.choice(_FIRST_NAMES)} {random.choice(_LAST_NAMES)}"
if dt == "email":
first = random.choice(_FIRST_NAMES).lower()
last = random.choice(_LAST_NAMES).lower()
num = random.randint(10, 999)
return f"{first}.{last}{num}@{random.choice(_DOMAINS)}"
if dt == "username":
return f"{random.choice(_FIRST_NAMES).lower()}{random.randint(100, 9999)}"
if dt == "password":
chars = string.ascii_letters + string.digits + "!@#$%"
raw = (
random.choice(string.ascii_uppercase)
+ random.choice(string.digits)
+ random.choice("!@#$%")
+ "".join(random.choices(chars, k=9))
)
return "".join(random.sample(raw, len(raw)))
if dt == "phone":
return f"+1{random.randint(200,999)}{random.randint(1_000_000, 9_999_999)}"
if dt == "birthday":
y = random.randint(1980, 2000)
m = random.randint(1, 12)
d = random.randint(1, 28)
return f"{m:02d}/{d:02d}/{y}"
if dt == "address":
num = random.randint(100, 9999)
street = random.choice(["Main St", "Oak Ave", "Park Blvd", "Elm St", "Cedar Ln"])
return f"{num} {street}"
if dt == "zip_code":
return str(random.randint(10000, 99999))
if dt == "city":
return random.choice(["New York", "Los Angeles", "Chicago", "Houston", "Phoenix"])
return f"random_{data_type}_{random.randint(1000, 9999)}"
def _user_profile() -> dict:
"""Read identity fields from long-term memory."""
try:
if _MEMORY_PATH.exists():
data = json.loads(_MEMORY_PATH.read_text(encoding="utf-8"))
identity = data.get("identity", {})
return {k: v.get("value", "") for k, v in identity.items()}
except Exception:
pass
return {}
def _type(text: str, interval: float = 0.03) -> str:
_require_pyautogui()
time.sleep(0.3)
pyautogui.typewrite(text, interval=interval)
return f"Typed: {text[:60]}{'…' if len(text) > 60 else ''}"
def _smart_type(text: str, clear_first: bool = True) -> str:
_require_pyautogui()
if clear_first:
_clear_field()
time.sleep(0.1)
if len(text) > 20 and _PYPERCLIP:
pyperclip.copy(text)
time.sleep(0.1)
pyautogui.hotkey("ctrl", "v")
return f"Smart-typed (clipboard): {text[:60]}{'…' if len(text) > 60 else ''}"
pyautogui.typewrite(text, interval=0.04)
return f"Smart-typed: {text[:60]}{'…' if len(text) > 60 else ''}"
def _click(x=None, y=None, button: str = "left", clicks: int = 1) -> str:
_require_pyautogui()
if x is not None and y is not None:
pyautogui.click(x, y, button=button, clicks=clicks)
return f"{'Double-c' if clicks == 2 else 'C'}licked ({x}, {y}) [{button}]"
pyautogui.click(button=button, clicks=clicks)
return f"Clicked at current position [{button}]"
def _hotkey(*keys) -> str:
_require_pyautogui()
pyautogui.hotkey(*keys)
return f"Hotkey: {'+'.join(keys)}"
def _press(key: str) -> str:
_require_pyautogui()
pyautogui.press(key)
return f"Pressed: {key}"
def _scroll(direction: str = "down", amount: int = 3) -> str:
_require_pyautogui()
vertical = direction in ("up", "down")
clicks = amount if direction in ("up", "right") else -amount
pyautogui.scroll(clicks) if vertical else pyautogui.hscroll(clicks)
return f"Scrolled {direction} ×{amount}"
def _move(x: int, y: int, duration: float = 0.3) -> str:
_require_pyautogui()
pyautogui.moveTo(x, y, duration=duration)
return f"Mouse → ({x}, {y})"
def _drag(x1: int, y1: int, x2: int, y2: int, duration: float = 0.5) -> str:
_require_pyautogui()
pyautogui.moveTo(x1, y1, duration=0.2)
pyautogui.dragTo(x2, y2, duration=duration, button="left")
return f"Dragged ({x1},{y1}) → ({x2},{y2})"
def _clipboard_get() -> str:
if _PYPERCLIP:
return pyperclip.paste()
_hotkey("ctrl", "c")
time.sleep(0.2)
return "(copied — pyperclip unavailable for read)"
def _clipboard_paste(text: str) -> str:
if _PYPERCLIP:
pyperclip.copy(text)
time.sleep(0.1)
_require_pyautogui()
pyautogui.hotkey("ctrl", "v")
return f"Pasted: {text[:60]}{'…' if len(text) > 60 else ''}"
return "pyperclip not available"
def _screenshot(save_path: str | None = None) -> str:
_require_pyautogui()
path = _safe_screenshot_path(save_path)
img = pyautogui.screenshot()
img.save(str(path))
return f"Screenshot saved: {path}"
def _clear_field() -> str:
_require_pyautogui()
pyautogui.hotkey("ctrl", "a")
time.sleep(0.1)
pyautogui.press("delete")
return "Field cleared"
def _focus_window(title: str) -> str:
os_name = _get_os()
if os_name == "windows":
try:
script = f'(New-Object -ComObject WScript.Shell).AppActivate("{title}")'
subprocess.run(
["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
capture_output=True, timeout=5,
)
time.sleep(0.3)
return f"Focused window: {title}"
except Exception as e:
return f"focus_window (Windows) failed: {e}"
if os_name == "mac":
script = (
f'tell application "System Events" to '
f'set frontmost of (first process whose name contains "{title}") to true'
)
try:
subprocess.run(
["osascript", "-e", script],
capture_output=True, timeout=5,
)
time.sleep(0.3)
return f"Focused window: {title}"
except Exception as e:
return f"focus_window (macOS) failed: {e}"
if os_name == "linux":
try:
result = subprocess.run(
["wmctrl", "-a", title],
capture_output=True, timeout=5,
)
if result.returncode == 0:
time.sleep(0.3)
return f"Focused window: {title}"
except FileNotFoundError:
pass
try:
result = subprocess.run(
["xdotool", "search", "--name", title, "windowactivate"],
capture_output=True, timeout=5,
)
time.sleep(0.3)
return f"Focused window: {title}"
except FileNotFoundError:
return "focus_window (Linux) requires wmctrl or xdotool"
except Exception as e:
return f"focus_window (Linux) failed: {e}"
return f"focus_window: unknown OS '{os_name}'"
def _screen_find(description: str) -> tuple[int, int] | None:
try:
import base64
from or_client import client
_require_pyautogui()
w, h = pyautogui.size()
img = pyautogui.screenshot()
buf = io.BytesIO()
img.save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode()
text = client.vision(
f"This is a screenshot of a {w}×{h} pixel screen. "
f"Locate the UI element: '{description}'. "
f"Reply ONLY with center coordinates as: x,y — or NOT_FOUND",
image_b64=b64,
mime="image/png",
)
if "NOT_FOUND" in text.upper():
return None
match = re.search(r"(\d+)\s*,\s*(\d+)", text)
if match:
return int(match.group(1)), int(match.group(2))
except Exception as e:
print(f"[ComputerControl] ⚠️ screen_find failed: {e}")
return None
def computer_control(
parameters: dict,
response=None,
player=None,
session_memory=None,
) -> str:
"""
Dispatch table for all computer control actions.
parameters keys (all optional unless noted):
action : (required) one of the actions listed below
text : text to type or paste
x, y : screen coordinates
button : 'left' | 'right' (default: left)
keys : hotkey string, e.g. 'ctrl+c'
key : single key name, e.g. 'enter'
direction : 'up' | 'down' | 'left' | 'right'
amount : scroll amount (default: 3)
seconds : wait duration
title : window title fragment for focus_window
description : natural-language element description for screen_find/click
type : data type for random_data
field : memory field name for user_data
clear_first : bool, clear field before typing (default: true)
path : save path for screenshot (must be inside home dir)
Actions:
type — type text at cursor
smart_type — clear field + type (clipboard-backed)
click — left click
double_click — double left click
right_click — right click
move — move mouse
drag — click-drag between two points
hotkey — key combination
press — single key
scroll — scroll the wheel
copy — read clipboard
paste — write + paste clipboard
screenshot — capture screen (safe path only)
wait — sleep N seconds
clear_field — select-all + delete
focus_window — bring window to foreground
screen_find — AI element finder (returns x,y)
screen_click — AI element finder + click
random_data — generate fake form data
user_data — pull real data from memory
"""
params = parameters or {}
action = params.get("action", "").lower().strip()
if not action:
return "No action specified for computer_control."
if player:
player.write_log(f"[Computer] {action}")
print(f"[ComputerControl] ▶ {action} {params}")
try:
if action == "type":
return _type(params.get("text", ""))
if action == "smart_type":
return _smart_type(
params.get("text", ""),
clear_first=params.get("clear_first", True),
)
if action in ("click", "left_click"):
return _click(params.get("x"), params.get("y"), "left", 1)
if action == "double_click":
return _click(params.get("x"), params.get("y"), "left", 2)
if action == "right_click":
return _click(params.get("x"), params.get("y"), "right", 1)
if action == "move":
return _move(int(params.get("x", 0)), int(params.get("y", 0)))
if action == "drag":
return _drag(
int(params.get("x1", 0)), int(params.get("y1", 0)),
int(params.get("x2", 0)), int(params.get("y2", 0)),
)
if action == "hotkey":
raw = params.get("keys", "")
keys = [k.strip() for k in raw.split("+")] if isinstance(raw, str) else raw
return _hotkey(*keys)
if action == "press":
return _press(params.get("key", "enter"))
if action == "scroll":
return _scroll(
direction=params.get("direction", "down"),
amount=int(params.get("amount", 3)),
)
if action == "copy":
return _clipboard_get()
if action == "paste":
return _clipboard_paste(params.get("text", ""))
if action == "screenshot":
return _screenshot(params.get("path"))
if action == "screen_find":
coords = _screen_find(params.get("description", ""))
return f"{coords[0]},{coords[1]}" if coords else "NOT_FOUND"
if action == "screen_click":
desc = params.get("description", "")
coords = _screen_find(desc)
if coords:
time.sleep(0.2)
_click(x=coords[0], y=coords[1])
return f"Clicked '{desc}' at {coords}"
return f"Element not found on screen: '{desc}'"
if action == "wait":
secs = float(params.get("seconds", 1.0))
secs = min(secs, 30.0)
time.sleep(secs)
return f"Waited {secs}s"
if action == "clear_field":
return _clear_field()
if action == "focus_window":
return _focus_window(params.get("title", ""))
if action == "random_data":
dt = params.get("type", "name")
result = _random_data(dt)
print(f"[ComputerControl] 🎲 random {dt} → {result}")
return result
if action == "user_data":
field = params.get("field", "name")
profile = _user_profile()
value = profile.get(field, "")
if not value:
value = _random_data(field)
print(f"[ComputerControl] ⚠️ No '{field}' in memory, using random: {value}")
return value
return f"Unknown action: '{action}'"
except Exception as e:
print(f"[ComputerControl] ❌ {action}: {e}")
return f"computer_control '{action}' failed: {e}"
+16
View File
@@ -0,0 +1,16 @@
import json, os
from pathlib import Path
_CONFIG_PATH = Path(__file__).parent / "api_keys.json"
def get_config() -> dict:
with open(_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)
def get_os() -> str:
"""Returns: 'windows' | 'mac' | 'linux'"""
return get_config().get("os_system", "windows").lower()
def is_windows() -> bool: return get_os() == "windows"
def is_mac() -> bool: return get_os() == "mac"
def is_linux() -> bool: return get_os() == "linux"
+672
View File
@@ -0,0 +1,672 @@
#computer_settings.py
import json
import re
import sys
import time
import subprocess
import platform
from pathlib import Path
try:
import pyautogui
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.05
_PYAUTOGUI = True
except ImportError:
_PYAUTOGUI = False
try:
import pyperclip
_PYPERCLIP = True
except ImportError:
_PYPERCLIP = False
_OS = platform.system() # "Windows" | "Darwin" | "Linux"
def _get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
def _get_macos_wifi_interface() -> str:
try:
result = subprocess.run(
["networksetup", "-listallhardwareports"],
capture_output=True, text=True, timeout=5
)
lines = result.stdout.splitlines()
for i, line in enumerate(lines):
if "Wi-Fi" in line or "AirPort" in line:
for j in range(i, min(i + 4, len(lines))):
if lines[j].startswith("Device:"):
return lines[j].split(":", 1)[1].strip()
except Exception:
pass
return "en0"
def volume_up():
if _OS == "Windows":
for _ in range(5): pyautogui.press("volumeup")
elif _OS == "Darwin":
subprocess.run(["osascript", "-e",
"set volume output volume (output volume of (get volume settings) + 10)"],
capture_output=True)
else:
subprocess.run(["pactl", "set-sink-volume", "@DEFAULT_SINK@", "+10%"],
capture_output=True)
def volume_down():
if _OS == "Windows":
for _ in range(5): pyautogui.press("volumedown")
elif _OS == "Darwin":
subprocess.run(["osascript", "-e",
"set volume output volume (output volume of (get volume settings) - 10)"],
capture_output=True)
else:
subprocess.run(["pactl", "set-sink-volume", "@DEFAULT_SINK@", "-10%"],
capture_output=True)
def volume_mute():
if _OS == "Windows":
pyautogui.press("volumemute")
elif _OS == "Darwin":
subprocess.run(["osascript", "-e", "set volume with output muted"],
capture_output=True)
else:
subprocess.run(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"],
capture_output=True)
def volume_set(value: int):
value = max(0, min(100, int(value)))
if _OS == "Windows":
try:
import math
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
vol = cast(interface, POINTER(IAudioEndpointVolume))
vol_db = -65.25 if value == 0 else max(-65.25, 20 * math.log10(value / 100))
vol.SetMasterVolumeLevel(vol_db, None)
return
except Exception as e:
print(f"[Settings] pycaw failed, using keypress fallback: {e}")
pyautogui.press("volumemute")
pyautogui.press("volumemute")
elif _OS == "Darwin":
subprocess.run(["osascript", "-e", f"set volume output volume {value}"],
capture_output=True)
return
else:
subprocess.run(["pactl", "set-sink-volume", "@DEFAULT_SINK@", f"{value}%"],
capture_output=True)
return
def brightness_up():
if _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell application "System Events" to key code 144'],
capture_output=True)
elif _OS == "Linux":
if subprocess.run(["which", "brightnessctl"],
capture_output=True).returncode == 0:
subprocess.run(["brightnessctl", "set", "+10%"], capture_output=True)
else:
subprocess.run(
'xrandr --output $(xrandr | grep " connected" | head -1 | cut -d " " -f1)'
' --brightness $(python3 -c "import subprocess; '
'b=float(subprocess.check_output([\"xrandr\",\"--verbose\"]).decode()'
'.split(\"Brightness:\")[1].split()[0]); print(min(1.0,b+0.1))")',
shell=True, capture_output=True
)
else:
try:
subprocess.run(
["powershell", "-Command",
"(Get-WmiObject -Namespace root/wmi -Class WmiMonitorBrightnessMethods)"
".WmiSetBrightness(1, [math]::Min(100, "
"(Get-WmiObject -Namespace root/wmi -Class WmiMonitorBrightness).CurrentBrightness + 10))"],
capture_output=True, timeout=5
)
except Exception as e:
print(f"[Settings] Brightness up failed on Windows: {e}")
def brightness_down():
if _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell application "System Events" to key code 145'],
capture_output=True)
elif _OS == "Linux":
if subprocess.run(["which", "brightnessctl"],
capture_output=True).returncode == 0:
subprocess.run(["brightnessctl", "set", "10%-"], capture_output=True)
else:
subprocess.run(
'xrandr --output $(xrandr | grep " connected" | head -1 | cut -d " " -f1)'
' --brightness $(python3 -c "import subprocess; '
'b=float(subprocess.check_output([\"xrandr\",\"--verbose\"]).decode()'
'.split(\"Brightness:\")[1].split()[0]); print(max(0.1,b-0.1))")',
shell=True, capture_output=True
)
else:
try:
subprocess.run(
["powershell", "-Command",
"(Get-WmiObject -Namespace root/wmi -Class WmiMonitorBrightnessMethods)"
".WmiSetBrightness(1, [math]::Max(0, "
"(Get-WmiObject -Namespace root/wmi -Class WmiMonitorBrightness).CurrentBrightness - 10))"],
capture_output=True, timeout=5
)
except Exception as e:
print(f"[Settings] Brightness down failed on Windows: {e}")
def close_app():
if _OS == "Darwin": pyautogui.hotkey("command", "q")
else: pyautogui.hotkey("alt", "f4")
def close_window():
if _OS == "Darwin": pyautogui.hotkey("command", "w")
else: pyautogui.hotkey("ctrl", "w")
def full_screen():
if _OS == "Darwin": pyautogui.hotkey("ctrl", "command", "f")
else: pyautogui.press("f11")
def minimize_window():
if _OS == "Darwin": pyautogui.hotkey("command", "m")
else: pyautogui.hotkey("win", "down")
def maximize_window():
if _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell application "System Events" to keystroke "f" '
'using {control down, command down}'],
capture_output=True)
elif _OS == "Windows":
pyautogui.hotkey("win", "up")
else:
try:
subprocess.run(["wmctrl", "-r", ":ACTIVE:", "-b", "add,maximized_vert,maximized_horz"],
capture_output=True)
except Exception:
pyautogui.hotkey("super", "up")
def snap_left():
if _OS == "Windows":
pyautogui.hotkey("win", "left")
elif _OS == "Linux":
try:
subprocess.run(["wmctrl", "-r", ":ACTIVE:", "-e", "0,0,0,960,1080"],
capture_output=True)
except Exception:
pass
def snap_right():
if _OS == "Windows":
pyautogui.hotkey("win", "right")
elif _OS == "Linux":
try:
subprocess.run(["wmctrl", "-r", ":ACTIVE:", "-e", "0,960,0,960,1080"],
capture_output=True)
except Exception:
pass
def switch_window():
if _OS == "Darwin": pyautogui.hotkey("command", "tab")
else: pyautogui.hotkey("alt", "tab")
def show_desktop():
if _OS == "Darwin": pyautogui.hotkey("fn", "f11")
elif _OS == "Windows": pyautogui.hotkey("win", "d")
else: pyautogui.hotkey("super", "d")
def open_task_manager():
if _OS == "Windows":
pyautogui.hotkey("ctrl", "shift", "esc")
elif _OS == "Darwin":
subprocess.Popen(["open", "-a", "Activity Monitor"])
else:
for cmd in [["gnome-system-monitor"], ["xfce4-taskmanager"], ["htop"]]:
if subprocess.run(["which", cmd[0]], capture_output=True).returncode == 0:
subprocess.Popen(cmd)
break
def focus_search():
if _OS == "Darwin": pyautogui.hotkey("command", "l")
else: pyautogui.hotkey("ctrl", "l")
def pause_video(): pyautogui.press("space")
def refresh_page():
if _OS == "Darwin": pyautogui.hotkey("command", "r")
else: pyautogui.press("f5")
def close_tab():
if _OS == "Darwin": pyautogui.hotkey("command", "w")
else: pyautogui.hotkey("ctrl", "w")
def new_tab():
if _OS == "Darwin": pyautogui.hotkey("command", "t")
else: pyautogui.hotkey("ctrl", "t")
def next_tab():
if _OS == "Darwin": pyautogui.hotkey("command", "shift", "bracketright")
else: pyautogui.hotkey("ctrl", "tab")
def prev_tab():
if _OS == "Darwin": pyautogui.hotkey("command", "shift", "bracketleft")
else: pyautogui.hotkey("ctrl", "shift", "tab")
def go_back():
if _OS == "Darwin": pyautogui.hotkey("command", "left")
else: pyautogui.hotkey("alt", "left")
def go_forward():
if _OS == "Darwin": pyautogui.hotkey("command", "right")
else: pyautogui.hotkey("alt", "right")
def zoom_in():
if _OS == "Darwin": pyautogui.hotkey("command", "equal")
else: pyautogui.hotkey("ctrl", "equal")
def zoom_out():
if _OS == "Darwin": pyautogui.hotkey("command", "minus")
else: pyautogui.hotkey("ctrl", "minus")
def zoom_reset():
if _OS == "Darwin": pyautogui.hotkey("command", "0")
else: pyautogui.hotkey("ctrl", "0")
def find_on_page():
if _OS == "Darwin": pyautogui.hotkey("command", "f")
else: pyautogui.hotkey("ctrl", "f")
def reload_page_n(n: int):
for _ in range(max(1, n)):
refresh_page()
time.sleep(0.8)
def scroll_up(amount: int = 500): pyautogui.scroll(amount)
def scroll_down(amount: int = 500): pyautogui.scroll(-amount)
def scroll_top():
if _OS == "Darwin": pyautogui.hotkey("command", "up")
else: pyautogui.hotkey("ctrl", "home")
def scroll_bottom():
if _OS == "Darwin": pyautogui.hotkey("command", "down")
else: pyautogui.hotkey("ctrl", "end")
def page_up(): pyautogui.press("pageup")
def page_down(): pyautogui.press("pagedown")
def copy():
if _OS == "Darwin": pyautogui.hotkey("command", "c")
else: pyautogui.hotkey("ctrl", "c")
def paste():
if _OS == "Darwin": pyautogui.hotkey("command", "v")
else: pyautogui.hotkey("ctrl", "v")
def cut():
if _OS == "Darwin": pyautogui.hotkey("command", "x")
else: pyautogui.hotkey("ctrl", "x")
def undo():
if _OS == "Darwin": pyautogui.hotkey("command", "z")
else: pyautogui.hotkey("ctrl", "z")
def redo():
if _OS == "Darwin": pyautogui.hotkey("command", "shift", "z")
else: pyautogui.hotkey("ctrl", "y")
def select_all():
if _OS == "Darwin": pyautogui.hotkey("command", "a")
else: pyautogui.hotkey("ctrl", "a")
def save_file():
if _OS == "Darwin": pyautogui.hotkey("command", "s")
else: pyautogui.hotkey("ctrl", "s")
def press_enter(): pyautogui.press("enter")
def press_escape(): pyautogui.press("escape")
def press_key(key: str): pyautogui.press(key)
def type_text(text: str, press_enter_after: bool = False):
if not text:
return
if _PYPERCLIP:
pyperclip.copy(str(text))
time.sleep(0.15)
paste()
else:
pyautogui.write(str(text), interval=0.03)
if press_enter_after:
time.sleep(0.1)
pyautogui.press("enter")
def take_screenshot():
if _OS == "Windows":
pyautogui.hotkey("win", "shift", "s")
elif _OS == "Darwin":
pyautogui.hotkey("command", "shift", "3")
else:
for cmd in [["scrot"], ["gnome-screenshot"], ["import", "-window", "root", "screenshot.png"]]:
if subprocess.run(["which", cmd[0]], capture_output=True).returncode == 0:
subprocess.Popen(cmd)
return
pyautogui.hotkey("ctrl", "print_screen")
def lock_screen():
if _OS == "Windows":
pyautogui.hotkey("win", "l")
elif _OS == "Darwin":
subprocess.run(["pmset", "displaysleepnow"], capture_output=True)
else:
for cmd in [
["gnome-screensaver-command", "-l"],
["xdg-screensaver", "lock"],
["loginctl", "lock-session"],
]:
if subprocess.run(["which", cmd[0]], capture_output=True).returncode == 0:
subprocess.run(cmd, capture_output=True)
return
def open_system_settings():
if _OS == "Windows":
pyautogui.hotkey("win", "i")
elif _OS == "Darwin":
subprocess.Popen(["open", "-a", "System Preferences"])
else:
for cmd in [["gnome-control-center"], ["xfce4-settings-manager"], ["kcmshell5"]]:
if subprocess.run(["which", cmd[0]], capture_output=True).returncode == 0:
subprocess.Popen(cmd)
return
def open_file_explorer():
if _OS == "Windows":
pyautogui.hotkey("win", "e")
elif _OS == "Darwin":
subprocess.Popen(["open", str(Path.home())])
else:
for cmd in [["nautilus"], ["thunar"], ["dolphin"], ["nemo"]]:
if subprocess.run(["which", cmd[0]], capture_output=True).returncode == 0:
subprocess.Popen(cmd)
return
subprocess.Popen(["xdg-open", str(Path.home())])
def sleep_display():
if _OS == "Windows":
try:
import ctypes
ctypes.windll.user32.SendMessageW(0xFFFF, 0x0112, 0xF170, 2)
except Exception as e:
print(f"[Settings] sleep_display failed: {e}")
elif _OS == "Darwin":
subprocess.run(["pmset", "displaysleepnow"], capture_output=True)
else:
subprocess.run(["xset", "dpms", "force", "off"], capture_output=True)
def open_run():
if _OS == "Windows":
pyautogui.hotkey("win", "r")
def dark_mode():
if _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell app "System Events" to tell appearance preferences '
'to set dark mode to not dark mode'],
capture_output=True)
elif _OS == "Windows":
try:
import winreg
key_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_ALL_ACCESS)
current, _ = winreg.QueryValueEx(key, "AppsUseLightTheme")
winreg.SetValueEx(key, "AppsUseLightTheme", 0, winreg.REG_DWORD, 1 - current)
winreg.SetValueEx(key, "SystemUsesLightTheme", 0, winreg.REG_DWORD, 1 - current)
winreg.CloseKey(key)
except Exception as e:
print(f"[Settings] dark_mode registry failed: {e}")
else:
try:
result = subprocess.run(
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
capture_output=True, text=True
)
current = result.stdout.strip()
new_scheme = "'default'" if "dark" in current else "'prefer-dark'"
subprocess.run(
["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", new_scheme],
capture_output=True
)
except Exception as e:
print(f"[Settings] dark_mode Linux failed: {e}")
def toggle_wifi():
if _OS == "Darwin":
iface = _get_macos_wifi_interface()
result = subprocess.run(
["networksetup", "-getairportpower", iface],
capture_output=True, text=True
)
state = "off" if "On" in result.stdout else "on"
subprocess.run(["networksetup", "-setairportpower", iface, state],
capture_output=True)
elif _OS == "Windows":
try:
subprocess.run(
["powershell", "-Command",
"$adapter = Get-NetAdapter | Where-Object {$_.PhysicalMediaType -eq 'Native 802.11'};"
"if ($adapter.Status -eq 'Up') { Disable-NetAdapter -Name $adapter.Name -Confirm:$false }"
"else { Enable-NetAdapter -Name $adapter.Name -Confirm:$false }"],
capture_output=True, timeout=10
)
except Exception as e:
print(f"[Settings] toggle_wifi Windows failed: {e}")
else:
try:
result = subprocess.run(["nmcli", "radio", "wifi"], capture_output=True, text=True)
state = "off" if "enabled" in result.stdout else "on"
subprocess.run(["nmcli", "radio", "wifi", state], capture_output=True)
except Exception as e:
print(f"[Settings] toggle_wifi Linux failed: {e}")
def restart_computer():
if _OS == "Windows":
subprocess.run(["shutdown", "/r", "/t", "10"], capture_output=True)
elif _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell application "System Events" to restart'],
capture_output=True)
else:
subprocess.run(["systemctl", "reboot"], capture_output=True)
def shutdown_computer():
if _OS == "Windows":
subprocess.run(["shutdown", "/s", "/t", "10"], capture_output=True)
elif _OS == "Darwin":
subprocess.run(["osascript", "-e",
'tell application "System Events" to shut down'],
capture_output=True)
else:
subprocess.run(["systemctl", "poweroff"], capture_output=True)
ACTION_MAP: dict[str, callable] = {
"volume_up": volume_up,
"volume_down": volume_down,
"mute": volume_mute,
"unmute": volume_mute,
"toggle_mute": volume_mute,
"brightness_up": brightness_up,
"brightness_down": brightness_down,
"sleep_display": sleep_display,
"screen_off": sleep_display,
"pause_video": pause_video,
"play_pause": pause_video,
"close_app": close_app,
"close_window": close_window,
"full_screen": full_screen,
"fullscreen": full_screen,
"minimize": minimize_window,
"maximize": maximize_window,
"snap_left": snap_left,
"snap_right": snap_right,
"switch_window": switch_window,
"show_desktop": show_desktop,
"task_manager": open_task_manager,
"focus_search": focus_search,
"refresh_page": refresh_page,
"reload": refresh_page,
"close_tab": close_tab,
"new_tab": new_tab,
"next_tab": next_tab,
"prev_tab": prev_tab,
"go_back": go_back,
"go_forward": go_forward,
"zoom_in": zoom_in,
"zoom_out": zoom_out,
"zoom_reset": zoom_reset,
"find_on_page": find_on_page,
"scroll_up": scroll_up,
"scroll_down": scroll_down,
"scroll_top": scroll_top,
"scroll_bottom": scroll_bottom,
"page_up": page_up,
"page_down": page_down,
"copy": copy,
"paste": paste,
"cut": cut,
"undo": undo,
"redo": redo,
"select_all": select_all,
"save": save_file,
"enter": press_enter,
"escape": press_escape,
"screenshot": take_screenshot,
"lock_screen": lock_screen,
"open_settings": open_system_settings,
"file_explorer": open_file_explorer,
"open_run": open_run,
"dark_mode": dark_mode,
"toggle_wifi": toggle_wifi,
"restart": restart_computer,
"shutdown": shutdown_computer,
}
_DANGEROUS_ACTIONS = {"restart", "shutdown"}
def _detect_action(description: str) -> dict:
from or_client import client
available = ", ".join(sorted(ACTION_MAP.keys())) + \
", volume_set, type_text, press_key, reload_n"
prompt = f"""You are an intent detector for a computer control assistant.
The user issued a command (possibly in any language): "{description}"
Available actions: {available}
Return ONLY a valid JSON object: {{"action": "action_name", "value": null_or_value}}
Rules:
- For volume_set: value is an integer 0-100.
- For type_text: value is the exact text to type.
- For press_key: value is the key name (e.g. "f5", "tab", "enter").
- For reload_n: value is an integer.
- Return ONLY the JSON, no explanation, no markdown."""
try:
raw = client.chat_json(prompt, system="Return only valid JSON. No extra text.")
return raw
except Exception as e:
print(f"[Settings] Intent detection failed: {e}")
return {"action": description.lower().replace(" ", "_"), "value": None}
def computer_settings(
parameters: dict = None,
response=None,
player=None,
session_memory=None,
) -> str:
if not _PYAUTOGUI:
return "pyautogui is not installed. Run: pip install pyautogui"
params = parameters or {}
raw_action = params.get("action", "").strip()
description = params.get("description", "").strip()
value = params.get("value", None)
if not raw_action and description:
detected = _detect_action(description)
raw_action = detected.get("action", "")
if value is None:
value = detected.get("value")
action = raw_action.lower().strip().replace(" ", "_").replace("-", "_")
if not action:
return "No action could be determined."
print(f"[Settings] Action: {action} Value: {value} OS: {_OS}")
if player:
player.write_log(f"[Settings] {action}")
if action in _DANGEROUS_ACTIONS:
confirmed = str(params.get("confirmed", "")).lower()
if confirmed not in ("yes", "true", "1", "confirm"):
return (
f"This will {action} the computer. "
f"Please confirm by calling again with confirmed=yes."
)
if action == "volume_set":
try:
volume_set(int(value or 50))
return f"Volume set to {value}%."
except Exception as e:
return f"Could not set volume: {e}"
if action in ("type_text", "write_on_screen", "type", "write"):
text = str(value or params.get("text", "")).strip()
if not text:
return "No text provided to type."
enter_after = str(params.get("press_enter", "false")).lower() in ("true", "1", "yes")
type_text(text, press_enter_after=enter_after)
return f"Typed: {text[:80]}"
if action == "press_key":
key = str(value or params.get("key", "")).strip()
if not key:
return "No key specified."
press_key(key)
return f"Pressed: {key}"
if action in ("reload_n", "refresh_n", "reload_page_n"):
try:
reload_page_n(int(value or 1))
return f"Reloaded {value or 1} time(s)."
except Exception as e:
return f"Reload failed: {e}"
if action == "scroll_up":
scroll_up(int(value or 500))
return "Scrolled up."
if action == "scroll_down":
scroll_down(int(value or 500))
return "Scrolled down."
func = ACTION_MAP.get(action)
if not func:
return f"Unknown action: '{raw_action}'."
try:
func()
return f"Done: {action}."
except Exception as e:
print(f"[Settings] Action failed ({action}): {e}")
return f"Action failed ({action}): {e}"
+457
View File
@@ -0,0 +1,457 @@
#desktop.py
import os
import sys
import json
import shutil
import subprocess
import tempfile
import platform
from pathlib import Path
from datetime import datetime
try:
import pyautogui
_PYAUTOGUI = True
except ImportError:
_PYAUTOGUI = False
_OS = platform.system() # "Windows" | "Darwin" | "Linux"
def _get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
def _get_desktop() -> Path:
if _OS == "Linux":
xdg = os.environ.get("XDG_DESKTOP_DIR", "")
if xdg and Path(xdg).exists():
return Path(xdg)
return Path.home() / "Desktop"
def _build_sandbox() -> dict:
import time
safe_builtins = {
"print": print,
"len": len, "str": str, "int": int, "float": float,
"bool": bool, "list": list, "dict": dict, "tuple": tuple,
"range": range, "enumerate": enumerate, "sorted": sorted,
"isinstance": isinstance, "hasattr": hasattr, "getattr": getattr,
"max": max, "min": min, "sum": sum, "abs": abs,
"zip": zip, "map": map, "filter": filter,
}
sandbox = {
"__builtins__": safe_builtins,
"Path": Path,
"time": time,
"shutil": type("shutil", (), {
"copy2": shutil.copy2,
"copytree": shutil.copytree,
"disk_usage": shutil.disk_usage,
})(),
"os_path": os.path,
}
if _PYAUTOGUI:
sandbox["pyautogui"] = pyautogui
if _OS == "Windows":
try:
import ctypes
import winreg
sandbox["ctypes"] = ctypes
sandbox["winreg"] = type("winreg", (), {
# Sadece okuma
"OpenKey": winreg.OpenKey,
"QueryValueEx": winreg.QueryValueEx,
"HKEY_CURRENT_USER": winreg.HKEY_CURRENT_USER,
})()
except ImportError:
pass
return sandbox
def _execute_generated_code(code: str, player=None) -> str:
if not code or code.strip() == "UNSAFE":
return "This action cannot be performed safely."
# Kod temizleme
if code.startswith("```"):
lines = code.split("\n")
code = "\n".join(lines[1:-1]).strip()
sandbox = _build_sandbox()
output_lines = []
sandbox["__builtins__"]["print"] = lambda *a: output_lines.append(" ".join(str(x) for x in a))
try:
exec(compile(code, "<jarvis_desktop>", "exec"), sandbox)
return "\n".join(output_lines) if output_lines else "Done."
except Exception as e:
print(f"[Desktop] Exec error: {e}\nCode:\n{code[:300]}")
return f"Execution error: {e}"
def _ask_gemini_for_desktop_action(task: str) -> str:
from or_client import client
desktop = str(_get_desktop())
os_specific = {
"Windows": "- ctypes (Windows API calls, read-only)\n- winreg (registry READ only)",
"Darwin": "- subprocess is NOT available; use pyautogui or Path only",
"Linux": "- subprocess is NOT available; use pyautogui or Path only",
}.get(_OS, "")
prompt = f"""You are a desktop automation assistant.
Current OS: {_OS}
Desktop path: {desktop}
Generate safe Python code to accomplish the task below.
Allowed modules ONLY:
- pyautogui (mouse, keyboard — if needed)
- pathlib.Path (file/folder inspection only, no deletion)
- shutil.copy2, shutil.copytree, shutil.disk_usage (NO move, NO rmtree)
- os_path (os.path equivalent, read-only)
- time.sleep
{os_specific}
Hard rules:
- NO file deletion, NO subprocess, NO exec/eval inside the code
- NO import statements, NO file write except explicitly requested
- If task cannot be done safely with these tools, output exactly: UNSAFE
Output ONLY the Python code. No explanation, no markdown, no backticks.
Task: {task}"""
try:
return client.chat(prompt, system="You are a code generator. Output only raw Python code.")
except Exception as e:
return f"ERROR: {e}"
def set_wallpaper(image_path: str) -> str:
path = Path(image_path).expanduser().resolve()
if not path.exists():
return f"Image not found: {image_path}"
if path.suffix.lower() not in {".jpg", ".jpeg", ".png", ".bmp", ".webp"}:
return f"Unsupported format: {path.suffix}. Use jpg, png, bmp or webp."
try:
if _OS == "Windows":
import ctypes
if path.suffix.lower() in {".webp", ".png"}:
try:
from PIL import Image
bmp_path = Path(tempfile.mktemp(suffix=".bmp"))
Image.open(path).convert("RGB").save(bmp_path, "BMP")
path = bmp_path
except ImportError:
pass
ctypes.windll.user32.SystemParametersInfoW(20, 0, str(path), 3)
return f"Wallpaper set: {path.name}"
elif _OS == "Darwin":
script = (
f'tell application "System Events" to tell every desktop to '
f'set picture to POSIX file "{path}"'
)
subprocess.run(["osascript", "-e", script], capture_output=True)
return f"Wallpaper set: {path.name}"
else:
desktop_env = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
uri = f"file://{path}"
if "gnome" in desktop_env or "unity" in desktop_env:
subprocess.run([
"gsettings", "set", "org.gnome.desktop.background",
"picture-uri", uri
], capture_output=True)
subprocess.run([
"gsettings", "set", "org.gnome.desktop.background",
"picture-uri-dark", uri
], capture_output=True)
elif "kde" in desktop_env:
# KDE Plasma
script = f"""
var allDesktops = desktops();
for (var i = 0; i < allDesktops.length; i++) {{
d = allDesktops[i];
d.wallpaperPlugin = "org.kde.image";
d.currentConfigGroup = ["Wallpaper", "org.kde.image", "General"];
d.writeConfig("Image", "file://{path}");
}}
"""
subprocess.run(
["qdbus", "org.kde.plasmashell", "/PlasmaShell",
"org.kde.PlasmaShell.evaluateScript", script],
capture_output=True
)
elif "xfce" in desktop_env:
subprocess.run([
"xfconf-query", "-c", "xfce4-desktop",
"-p", "/backdrop/screen0/monitor0/workspace0/last-image",
"-s", str(path)
], capture_output=True)
else:
result = subprocess.run(
["feh", "--bg-scale", str(path)],
capture_output=True
)
if result.returncode != 0:
return (
f"Could not set wallpaper automatically on {desktop_env}. "
f"Try manually or install 'feh'."
)
return f"Wallpaper set: {path.name}"
except Exception as e:
return f"Could not set wallpaper: {e}"
def set_wallpaper_from_url(url: str) -> str:
try:
import urllib.request
suffix = Path(url.split("?")[0]).suffix or ".jpg"
tmp = Path(tempfile.mktemp(suffix=suffix))
urllib.request.urlretrieve(url, str(tmp))
result = set_wallpaper(str(tmp))
try:
tmp.unlink()
except Exception:
pass
return result
except Exception as e:
return f"Could not download wallpaper: {e}"
def get_current_wallpaper() -> str:
try:
if _OS == "Windows":
import winreg
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop"
)
val, _ = winreg.QueryValueEx(key, "Wallpaper")
winreg.CloseKey(key)
return f"Current wallpaper: {val}"
elif _OS == "Darwin":
script = (
'tell application "System Events" to get picture of desktop 1'
)
result = subprocess.run(
["osascript", "-e", script],
capture_output=True, text=True
)
return f"Current wallpaper: {result.stdout.strip()}"
else:
desktop_env = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
if "gnome" in desktop_env or "unity" in desktop_env:
result = subprocess.run(
["gsettings", "get", "org.gnome.desktop.background", "picture-uri"],
capture_output=True, text=True
)
return f"Current wallpaper: {result.stdout.strip()}"
return "Wallpaper path retrieval not supported for this desktop environment."
except Exception as e:
return f"Could not get wallpaper: {e}"
FILE_TYPE_MAP = {
"Images": {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico", ".heic"},
"Documents": {".pdf", ".doc", ".docx", ".txt", ".xls", ".xlsx",
".ppt", ".pptx", ".csv", ".odt", ".ods", ".odp"},
"Videos": {".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v"},
"Music": {".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma", ".m4a"},
"Archives": {".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".xz"},
"Code": {".py", ".js", ".ts", ".html", ".css", ".json", ".xml",
".cpp", ".java", ".cs", ".go", ".rs", ".sh", ".php"},
"Executables": {".exe", ".msi", ".bat", ".cmd", ".sh", ".appimage", ".deb", ".rpm"},
}
_SKIP_EXTENSIONS = {
"Windows": {".lnk", ".url"},
"Darwin": {".webloc"},
"Linux": {".desktop"},
}
def organize_desktop(mode: str = "by_type") -> str:
desktop = _get_desktop()
skip_exts = _SKIP_EXTENSIONS.get(_OS, set())
moved, skipped = [], []
for item in desktop.iterdir():
if item.is_dir() or item.name.startswith("."):
continue
if item.suffix.lower() in skip_exts:
continue
if mode == "by_date":
mtime = datetime.fromtimestamp(item.stat().st_mtime)
folder_name = mtime.strftime("%Y-%m")
else:
ext = item.suffix.lower()
folder_name = "Others"
for folder, exts in FILE_TYPE_MAP.items():
if ext in exts:
folder_name = folder
break
target_dir = desktop / folder_name
target_dir.mkdir(exist_ok=True)
new_path = target_dir / item.name
if new_path.exists():
skipped.append(item.name)
continue
shutil.move(str(item), str(new_path))
moved.append(f"{item.name} → {folder_name}/")
result = f"Desktop organized ({mode}): {len(moved)} files moved."
if moved:
result += "\n" + "\n".join(moved[:8])
if len(moved) > 8:
result += f"\n... and {len(moved) - 8} more."
if skipped:
result += f"\n{len(skipped)} file(s) skipped (name conflict)."
return result
def list_desktop() -> str:
desktop = _get_desktop()
items = []
for item in sorted(desktop.iterdir()):
if item.name.startswith("."):
continue
if item.is_dir():
try:
count = len(list(item.iterdir()))
except PermissionError:
count = "?"
items.append(f"📁 {item.name}/ ({count} items)")
else:
size = item.stat().st_size
size_str = (
f"{size / 1024:.1f} KB" if size < 1024 * 1024
else f"{size / 1024 / 1024:.1f} MB"
)
items.append(f"📄 {item.name} ({size_str})")
if not items:
return "Desktop is empty."
return f"Desktop ({len(items)} items):\n" + "\n".join(items)
def clean_desktop() -> str:
desktop = _get_desktop()
skip_exts = _SKIP_EXTENSIONS.get(_OS, set())
today = datetime.now().strftime("%Y-%m-%d")
archive_dir = desktop / f"Desktop Archive {today}"
archive_dir.mkdir(exist_ok=True)
moved = 0
for item in desktop.iterdir():
if item.is_dir() or item.name.startswith("."):
continue
if item.suffix.lower() in skip_exts:
continue
new_path = archive_dir / item.name
if not new_path.exists():
shutil.move(str(item), str(new_path))
moved += 1
return f"Desktop cleaned: {moved} files archived to '{archive_dir.name}'."
def get_desktop_stats() -> str:
desktop = _get_desktop()
files = [i for i in desktop.iterdir() if i.is_file()]
folders = [i for i in desktop.iterdir() if i.is_dir()]
total_size = sum(f.stat().st_size for f in files if f.exists())
size_str = (
f"{total_size / 1024:.1f} KB" if total_size < 1024 * 1024
else f"{total_size / 1024 / 1024:.1f} MB"
)
return (
f"Desktop stats ({_OS}):\n"
f" Files : {len(files)}\n"
f" Folders : {len(folders)}\n"
f" Size : {size_str}\n"
f" Path : {desktop}"
)
def desktop_control(
parameters: dict = None,
response=None,
player=None,
session_memory=None,
) -> str:
"""
parameters:
action : wallpaper | wallpaper_url | current_wallpaper |
organize | clean | list | stats |
task (AI-powered)
path : image path for 'wallpaper'
url : image URL for 'wallpaper_url'
mode : 'by_type' or 'by_date' for 'organize'
task : natural language description for AI-powered actions
"""
params = parameters or {}
action = params.get("action", "").lower().strip()
task = params.get("task", "").strip()
if player:
player.write_log(f"[desktop] {action or task[:40]}")
try:
if action == "wallpaper":
path = params.get("path", "")
return set_wallpaper(path) if path else "No image path provided."
elif action == "wallpaper_url":
url = params.get("url", "")
return set_wallpaper_from_url(url) if url else "No URL provided."
elif action == "current_wallpaper":
return get_current_wallpaper()
elif action == "organize":
return organize_desktop(params.get("mode", "by_type"))
elif action == "clean":
return clean_desktop()
elif action == "list":
return list_desktop()
elif action == "stats":
return get_desktop_stats()
elif action == "task" or task:
actual_task = task or params.get("description", "")
if not actual_task:
return "Please describe what you want to do on the desktop."
print(f"[Desktop] Asking Gemini: {actual_task}")
if player:
player.write_log("[Desktop] Generating action...")
code = _ask_gemini_for_desktop_action(actual_task)
return _execute_generated_code(code, player=player)
else:
if action:
code = _ask_gemini_for_desktop_action(action)
return _execute_generated_code(code, player=player)
return "No action or task specified."
except Exception as e:
print(f"[Desktop] Error: {e}")
return f"Desktop control error: {e}"
+418
View File
@@ -0,0 +1,418 @@
#youtube_video.py
import json
import re
import sys
import time
import subprocess
import shutil
from pathlib import Path
from datetime import datetime
from urllib.parse import quote_plus
import pyautogui
import numpy as np
try:
import requests
_REQUESTS_OK = True
except ImportError:
_REQUESTS_OK = False
try:
from youtube_transcript_api import YouTubeTranscriptApi
_TRANSCRIPT_OK = True
except ImportError:
_TRANSCRIPT_OK = False
from config import get_os, is_windows, is_mac, is_linux
def _get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = _get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (X11; Linux x86_64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
_YT_VIDEO_FILTER = "EgIQAQ%3D%3D"
def _open_url(url: str) -> None:
try:
if is_mac():
subprocess.Popen(["open", url])
elif is_linux():
subprocess.Popen(["xdg-open", url])
else:
subprocess.Popen(["cmd", "/c", "start", "", url], shell=False)
except Exception as e:
print(f"[YouTube] ⚠️ open_url failed: {e}")
def _scrape_first_video_url(query: str) -> str | None:
if not _REQUESTS_OK:
return None
search_url = (
f"https://www.youtube.com/results"
f"?search_query={quote_plus(query)}"
f"&sp={_YT_VIDEO_FILTER}"
)
try:
r = requests.get(search_url, headers=HEADERS, timeout=10)
html = r.text
video_ids = re.findall(r'"videoId":"([A-Za-z0-9_-]{11})"', html)
seen = set()
for vid in video_ids:
if vid in seen:
continue
seen.add(vid)
if f'/shorts/{vid}' in html:
continue
return f"https://www.youtube.com/watch?v={vid}"
except Exception as e:
print(f"[YouTube] ⚠️ scrape_first_video_url failed: {e}")
return None
def _extract_video_id(url: str) -> str | None:
match = re.search(
r"(?:v=|\/v\/|youtu\.be\/|\/embed\/|\/shorts\/)([A-Za-z0-9_-]{11})", url
)
return match.group(1) if match else None
def _is_valid_youtube_url(url: str) -> bool:
return bool(re.search(r"(youtube\.com|youtu\.be)", url or ""))
def _ask_for_url(prompt_text: str = "YouTube video URL:") -> str | None:
try:
import tkinter as tk
from tkinter import simpledialog
root = tk._default_root
if root is None:
root = tk.Tk()
root.withdraw()
url = simpledialog.askstring("J.A.R.V.I.S", prompt_text, parent=root)
return url.strip() if url else None
except Exception as e:
print(f"[YouTube] ⚠️ URL dialog failed: {e}")
return None
def _get_transcript(video_id: str) -> str | None:
if not _TRANSCRIPT_OK:
return None
try:
transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
transcript = None
lang_priority = ["en", "tr", "de", "fr", "es", "it", "pt", "ru", "ja", "ko", "ar", "zh"]
try:
transcript = transcript_list.find_manually_created_transcript(lang_priority)
except Exception:
pass
if transcript is None:
try:
transcript = transcript_list.find_generated_transcript(lang_priority)
except Exception:
for t in transcript_list:
transcript = t
break
if transcript is None:
return None
fetched = transcript.fetch()
return " ".join(entry["text"] for entry in fetched)
except Exception as e:
print(f"[YouTube] ⚠️ Transcript fetch failed: {e}")
return None
def _summarize_with_gemini(transcript: str, video_url: str) -> str:
from or_client import client
max_chars = 80000
truncated = transcript[:max_chars] + ("..." if len(transcript) > max_chars else "")
return client.chat(
f"Please summarize this YouTube video transcript:\n\n{truncated}",
system=(
"You are JARVIS, an AI assistant. "
"Summarize YouTube video transcripts clearly and concisely. "
"Structure: 1-sentence overview, then 3-5 key points. "
"Be direct. Address the user as 'sir'. "
"Match the language of the transcript."
),
max_tokens=2048,
)
def _save_summary(content: str, video_url: str) -> str:
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"youtube_summary_{ts}.txt"
desktop = Path.home() / "Desktop"
desktop.mkdir(parents=True, exist_ok=True)
filepath = desktop / filename
header = (
f"JARVIS — YouTube Summary\n"
f"{'─' * 50}\n"
f"URL : {video_url}\n"
f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
f"{'─' * 50}\n\n"
)
filepath.write_text(header + content, encoding="utf-8")
try:
if is_windows():
subprocess.Popen(["notepad.exe", str(filepath)])
elif is_mac():
subprocess.Popen(["open", "-t", str(filepath)])
else:
subprocess.Popen(["xdg-open", str(filepath)])
except Exception as e:
print(f"[YouTube] ⚠️ Could not open text editor: {e}")
return str(filepath)
def _scrape_video_info(video_id: str) -> dict:
if not _REQUESTS_OK:
return {}
url = f"https://www.youtube.com/watch?v={video_id}"
try:
r = requests.get(url, headers=HEADERS, timeout=12)
html = r.text
info = {}
for key, pattern in [
("title", r'"title":\{"runs":\[\{"text":"([^"]+)"'),
("channel", r'"ownerChannelName":"([^"]+)"'),
("views", r'"viewCount":"(\d+)"'),
("duration", r'"lengthSeconds":"(\d+)"'),
("likes", r'"label":"([0-9,]+ likes)"'),
]:
match = re.search(pattern, html)
if match:
raw = match.group(1)
if key == "views":
info[key] = f"{int(raw):,}"
elif key == "duration":
secs = int(raw)
info[key] = f"{secs // 60}:{secs % 60:02d}"
else:
info[key] = raw
return info
except Exception as e:
print(f"[YouTube] ⚠️ Info scrape failed: {e}")
return {}
def _scrape_trending(region: str = "TR", max_results: int = 8) -> list[dict]:
if not _REQUESTS_OK:
return []
url = f"https://www.youtube.com/feed/trending?gl={region.upper()}"
try:
r = requests.get(url, headers=HEADERS, timeout=12)
html = r.text
titles = re.findall(r'"title":\{"runs":\[\{"text":"([^"]+)"\}\]', html)
channels = re.findall(r'"ownerText":\{"runs":\[\{"text":"([^"]+)"', html)
results, seen = [], set()
for i, title in enumerate(titles):
if title in seen or len(title) < 5:
continue
seen.add(title)
channel = channels[i] if i < len(channels) else "Unknown"
results.append({"rank": len(results) + 1, "title": title, "channel": channel})
if len(results) >= max_results:
break
return results
except Exception as e:
print(f"[YouTube] ⚠️ Trending scrape failed: {e}")
return []
def _handle_play(parameters: dict, player) -> str:
query = parameters.get("query", "").strip()
if not query:
return "Please tell me what you'd like to watch, sir."
if player:
player.write_log(f"[YouTube] Searching: {query}")
print(f"[YouTube] 🔍 Scraping first non-Shorts video for: {query}")
video_url = _scrape_first_video_url(query)
if video_url:
print(f"[YouTube] ▶️ Opening: {video_url}")
_open_url(video_url)
return f"Playing: {query}"
print(f"[YouTube] ⚠️ Scrape failed, opening filtered search page")
fallback_url = (
f"https://www.youtube.com/results"
f"?search_query={quote_plus(query)}"
f"&sp={_YT_VIDEO_FILTER}"
)
_open_url(fallback_url)
return f"Opened YouTube search for: {query} (manual selection required)"
def _handle_summarize(parameters: dict, player, speak) -> str:
if not _TRANSCRIPT_OK:
return "youtube-transcript-api is not installed. Run: pip install youtube-transcript-api"
url = _ask_for_url("Please paste the YouTube video URL:")
if not url:
return "No URL provided, sir. Summary cancelled."
if not _is_valid_youtube_url(url):
return "That doesn't appear to be a valid YouTube URL, sir."
video_id = _extract_video_id(url)
if not video_id:
return "Could not extract video ID from that URL, sir."
if player:
player.write_log(f"[YouTube] Summarizing: {url}")
if speak:
speak("Fetching the transcript now, sir. One moment.")
transcript = _get_transcript(video_id)
if not transcript:
return "I couldn't retrieve a transcript for that video, sir."
if speak:
speak("Transcript retrieved. Generating summary now.")
try:
summary = _summarize_with_gemini(transcript, url)
except Exception as e:
return f"Summary generation failed, sir: {e}"
if speak:
speak(summary)
if parameters.get("save", False):
saved_path = _save_summary(summary, url)
return f"Summary complete and saved to Desktop: {saved_path}"
return summary
def _handle_get_info(parameters: dict, player, speak) -> str:
url = parameters.get("url", "").strip()
if not url:
url = _ask_for_url("Please paste the YouTube video URL:")
if not url or not _is_valid_youtube_url(url):
return "Please provide a valid YouTube URL, sir."
video_id = _extract_video_id(url)
if not video_id:
return "Could not extract video ID, sir."
if player:
player.write_log(f"[YouTube] Getting info: {url}")
info = _scrape_video_info(video_id)
if not info:
return "Could not retrieve video information, sir."
lines = [
f"{key.capitalize()}: {info[key]}"
for key in ("title", "channel", "views", "duration", "likes")
if key in info
]
result = "\n".join(lines)
if speak:
speak(f"Here's the video info, sir. {result.replace(chr(10), '. ')}")
return result
def _handle_trending(parameters: dict, player, speak) -> str:
region = parameters.get("region", "TR").upper()
if player:
player.write_log(f"[YouTube] Trending: {region}")
trending = _scrape_trending(region=region, max_results=8)
if not trending:
return f"Could not fetch trending videos for region {region}, sir."
lines = [f"Top trending videos in {region}:"]
lines += [f"{v['rank']}. {v['title']} — {v['channel']}" for v in trending]
result = "\n".join(lines)
if speak:
top3 = trending[:3]
spoken = "Here are the top trending videos, sir. " + ". ".join(
f"Number {v['rank']}: {v['title']} by {v['channel']}" for v in top3
)
speak(spoken)
return result
_ACTION_MAP = {
"play": _handle_play,
"summarize": _handle_summarize,
"get_info": _handle_get_info,
"trending": _handle_trending,
}
def youtube_video(
parameters: dict,
response=None,
player=None,
session_memory=None,
speak=None,
) -> str:
params = parameters or {}
action = params.get("action", "play").lower().strip()
if player:
player.write_log(f"[YouTube] Action: {action}")
print(f"[YouTube] ▶️ Action: {action} Params: {params}")
handler = _ACTION_MAP.get(action)
if handler is None:
return (
f"Unknown YouTube action: '{action}'. "
"Available: play, summarize, get_info, trending."
)
try:
if action == "play":
return handler(params, player) or "Done."
return handler(params, player, speak) or "Done."
except Exception as e:
print(f"[YouTube] ❌ Error in {action}: {e}")
return f"YouTube {action} failed, sir: {e}"
+597
View File
@@ -0,0 +1,597 @@
import subprocess
import sys
import json
import re
import time
from pathlib import Path
def get_base_dir():
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
PROJECTS_DIR = Path.home() / "Desktop" / "JarvisProjects"
MAX_FIX_ATTEMPTS = 5
MODEL_PLANNER = "gemini-2.5-flash"
MODEL_WRITER = "gemini-2.5-flash"
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _get_model(model_name: str):
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
return genai.GenerativeModel(model_name)
def _strip_fences(text: str) -> str:
text = text.strip()
text = re.sub(r"^```[a-zA-Z]*\r?\n?", "", text)
text = re.sub(r"\r?\n?```\s*$", "", text)
return text.strip()
def _is_rate_limit(error: Exception) -> bool:
msg = str(error).lower()
return "429" in msg or "quota" in msg or "resource_exhausted" in msg
def _parse_traceback(output: str, project_files: list[str]) -> tuple[str | None, int | None]:
pattern = re.compile(r'File ["\']([^"\']+\.py)["\'],\s+line\s+(\d+)', re.IGNORECASE)
matches = pattern.findall(output)
for raw_path, line_str in reversed(matches):
raw_name = Path(raw_path).name
for pf in project_files:
if Path(pf).name == raw_name or pf == raw_path or raw_path.endswith(pf):
return pf, int(line_str)
return None, None
def _classify_error(output: str) -> str:
low = output.lower()
if any(x in low for x in ("no module named", "modulenotfounderror", "importerror")):
return "dependency_error"
if "syntaxerror" in low or "invalid syntax" in low:
return "syntax_error"
if "cannot import" in low or "importerror" in low:
return "import_error"
if any(x in low for x in (
"traceback", "exception", "error:", "nameerror", "typeerror",
"attributeerror", "valueerror", "keyerror", "indexerror",
"zerodivisionerror", "filenotfounderror", "permissionerror",
)):
return "runtime_error"
return "none"
def _has_error(output: str, run_command: str) -> bool:
low = output.lower()
if "timed out" in low:
return False
if not output.strip():
return False
error_type = _classify_error(output)
return error_type != "none"
class RateLimitError(Exception):
pass
def _plan_project(description: str, language: str) -> dict:
model = _get_model(MODEL_PLANNER)
prompt = f"""You are a senior software architect. Create a minimal, complete file plan for this project.
Language: {language}
Description: {description}
Return ONLY valid JSON — no markdown, no explanation:
{{
"project_name": "snake_case_name",
"entry_point": "main.py",
"files": [
{{
"path": "main.py",
"description": "Entry point — what it does and which modules it imports",
"imports": ["utils.helpers", "core.engine"]
}},
{{
"path": "utils/helpers.py",
"description": "Helper utilities — what functions it exposes",
"imports": []
}}
],
"run_command": "python main.py",
"dependencies": ["requests"]
}}
Critical rules:
1. List files in DEPENDENCY ORDER — files with no imports come first, entry point comes last.
2. The "imports" field must list every other project module this file imports (dot-notation, e.g. "utils.helpers").
3. Keep it minimal — only files truly needed.
4. Entry point must be in the files list.
5. Use relative paths only (e.g. "utils/helpers.py", not absolute paths).
6. Standard library modules (os, sys, json, etc.) do NOT go in "dependencies".
JSON:"""
try:
response = model.generate_content(prompt)
raw = _strip_fences(response.text)
return json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"Planner returned invalid JSON: {e}\nRaw: {response.text[:300]}")
except Exception as e:
if _is_rate_limit(e):
raise RateLimitError(str(e))
raise
def _write_file(
file_info: dict,
project_description: str,
all_files: list[dict],
language: str,
project_dir: Path,
already_written: dict[str, str],
) -> str:
model = _get_model(MODEL_WRITER)
file_path = file_info["path"]
file_desc = file_info.get("description", "")
file_imports = file_info.get("imports", [])
file_list = "\n".join(
f" [{i+1}] {f['path']}: {f.get('description', '')}"
for i, f in enumerate(all_files)
)
dependency_context = ""
for dep_dotted in file_imports:
dep_path = dep_dotted.replace(".", "/") + ".py"
if dep_path in already_written:
code_snippet = already_written[dep_path][:2000]
dependency_context += f"\n\n--- {dep_path} (you must import from this) ---\n{code_snippet}"
lang_rules = ""
if language.lower() == "python":
lang_rules = """
Python-specific rules:
- Use type hints for all function signatures.
- Add docstrings for all public functions and classes.
- Use if __name__ == "__main__": guard in the entry point.
- For relative imports within the project, use: from utils.helpers import foo (match the project structure exactly).
- Do NOT use implicit relative imports (from . import ...) unless it's a proper package with __init__.py.
- If this is a package subdirectory, create __init__.py files where needed."""
elif language.lower() in ("javascript", "typescript", "js", "ts"):
lang_rules = """
JS/TS-specific rules:
- Use ES modules (import/export), not CommonJS (require).
- Add JSDoc comments for all exported functions.
- Handle promise rejections with try/catch in async functions."""
prompt = f"""You are a senior {language} developer writing production-quality code for a real project.
Project goal: {project_description}
Complete project file structure (in dependency order):
{file_list}
{f"Dependencies this file must import from other project files:{dependency_context}" if dependency_context else ""}
Your task: Write the complete, working code for: {file_path}
Purpose of this file: {file_desc}
{f"This file imports from: {', '.join(file_imports)}" if file_imports else "This file has no project-internal imports."}
{lang_rules}
General rules:
- Output ONLY raw code. Absolutely no explanation, no markdown, no triple backticks.
- Write COMPLETE, RUNNABLE code — no placeholders, no "# TODO", no "pass" stubs.
- Every import must either be from the standard library, listed dependencies, or the project files shown above.
- Match import paths EXACTLY to the file paths in the project structure (e.g. if file is "utils/helpers.py", import as "from utils.helpers import ...").
- Use proper error handling (try/except) where I/O or network calls are made.
- The code must work correctly when the project entry point is run from the project root directory.
Code for {file_path}:"""
try:
response = model.generate_content(prompt)
code = _strip_fences(response.text)
full_path = project_dir / file_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(code, encoding="utf-8")
print(f"[DevAgent] ✅ Written: {file_path} ({len(code)} chars)")
return code
except Exception as e:
if _is_rate_limit(e):
raise RateLimitError(str(e))
raise
def _install_dependencies(dependencies: list[str], project_dir: Path) -> str:
if not dependencies:
return "No external dependencies."
to_install = []
for dep in dependencies:
pkg_name = re.split(r"[>=<!]", dep)[0].strip()
result = subprocess.run(
[sys.executable, "-m", "pip", "show", pkg_name],
capture_output=True, text=True
)
if result.returncode != 0:
to_install.append(dep)
else:
print(f"[DevAgent] ✓ Already installed: {pkg_name}")
if not to_install:
return f"All dependencies already installed: {', '.join(dependencies)}"
print(f"[DevAgent] 📦 Installing: {to_install}")
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "install"] + to_install,
capture_output=True, text=True,
encoding="utf-8", errors="replace",
timeout=120, cwd=str(project_dir)
)
if result.returncode == 0:
return f"Installed: {', '.join(to_install)}"
return f"Install warning (non-fatal): {result.stderr[:200]}"
except subprocess.TimeoutExpired:
return "Dependency install timed out (non-fatal)."
except Exception as e:
return f"Install error (non-fatal): {e}"
def _open_vscode(project_dir: Path) -> bool:
vscode_candidates = [
"code",
rf"C:\Users\{Path.home().name}\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd",
r"C:\Program Files\Microsoft VS Code\bin\code.cmd",
]
for cmd in vscode_candidates:
try:
subprocess.Popen(
[cmd, str(project_dir)],
shell=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
time.sleep(1.5)
print(f"[DevAgent] 💻 VSCode opened: {project_dir}")
return True
except Exception:
continue
return False
def _run_project(run_command: str, project_dir: Path, timeout: int = 30) -> str:
print(f"[DevAgent] 🚀 Running: {run_command}")
try:
parts = run_command.split()
if parts[0].lower() == "python":
parts[0] = sys.executable
result = subprocess.run(
parts,
capture_output=True, text=True,
encoding="utf-8", errors="replace",
timeout=timeout,
cwd=str(project_dir)
)
stdout = result.stdout.strip()
stderr = result.stderr.strip()
combined_parts = []
if stdout:
combined_parts.append(f"STDOUT:\n{stdout}")
if stderr:
combined_parts.append(f"STDERR:\n{stderr}")
return "\n\n".join(combined_parts) if combined_parts else "Ran with no output."
except subprocess.TimeoutExpired:
return f"Timed out after {timeout}s — long-running app (server/GUI) is likely working."
except FileNotFoundError as e:
return f"Command not found: {e}"
except Exception as e:
return f"Run error: {e}"
def _try_auto_install(error_output: str, project_dir: Path) -> bool:
"""ModuleNotFoundError varsa eksik paketi otomatik kurmaya çalışır."""
pattern = re.compile(
r"No module named ['\"]([a-zA-Z0-9_\-\.]+)['\"]", re.IGNORECASE
)
match = pattern.search(error_output)
if not match:
return False
pkg = match.group(1).replace("_", "-").split(".")[0]
print(f"[DevAgent] 🔧 Auto-installing missing package: {pkg}")
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "install", pkg],
capture_output=True, text=True,
encoding="utf-8", errors="replace",
timeout=60, cwd=str(project_dir)
)
return result.returncode == 0
except Exception:
return False
def _fix_files(
error_output: str,
project_description: str,
all_files: list[dict],
file_codes: dict[str, str],
language: str,
project_dir: Path,
entry_point: str,
) -> dict[str, str]:
model = _get_model(MODEL_PLANNER)
error_file, error_line = _parse_traceback(error_output, list(file_codes.keys()))
error_type = _classify_error(error_output)
files_to_fix: list[str] = []
if error_file:
files_to_fix.append(error_file)
if error_type == "import_error":
for fi in all_files:
if error_file.replace("/", ".").replace(".py", "") in fi.get("imports", []):
p = fi["path"]
if p not in files_to_fix:
files_to_fix.append(p)
else:
files_to_fix.append(entry_point)
updated_codes: dict[str, str] = {}
for fix_path in files_to_fix:
current_code = file_codes.get(fix_path, "")
other_ctx = ""
for fp, code in file_codes.items():
if fp != fix_path and code:
snippet = code[:1500] + ("..." if len(code) > 1500 else "")
other_ctx += f"\n--- {fp} ---\n{snippet}\n"
line_hint = f"\nError appears to be near line {error_line} in this file." if (
error_line and fix_path == error_file
) else ""
prompt = f"""You are an expert {language} debugger. Fix the broken file below.
Project goal: {project_description}
All project files:
{chr(10).join(f" - {f['path']}: {f.get('description', '')}" for f in all_files)}
Other files for context (read-only — fix only the target file):
{other_ctx[:3500]}
File to fix: {fix_path}{line_hint}
Error type: {error_type}
Error output:
{error_output[:2500]}
Current (broken) code:
{current_code}
Rules:
- Output ONLY the complete fixed code. No explanation, no markdown, no backticks.
- Fix ALL errors visible in the error output.
- Keep all existing correct logic — do not remove working features.
- Ensure import paths match the actual project file structure exactly.
- Do NOT introduce new bugs or remove error handling.
Fixed code for {fix_path}:"""
try:
response = model.generate_content(prompt)
fixed = _strip_fences(response.text)
full_path = project_dir / fix_path
full_path.parent.mkdir(parents=True, exist_ok=True)
full_path.write_text(fixed, encoding="utf-8")
updated_codes[fix_path] = fixed
print(f"[DevAgent] 🔧 Fixed: {fix_path}")
except Exception as e:
if _is_rate_limit(e):
raise RateLimitError(str(e))
print(f"[DevAgent] ⚠️ Could not fix {fix_path}: {e}")
return updated_codes
def _build_project(
description: str,
language: str,
project_name: str,
timeout: int,
speak=None,
player=None,
) -> str:
def log(msg: str):
print(f"[DevAgent] {msg}")
if player:
player.write_log(f"[DevAgent] {msg}")
log("Planning project structure...")
try:
plan = _plan_project(description, language)
except RateLimitError:
msg = "Rate limit reached, sir. Please try again in a moment."
if speak: speak(msg)
return msg
except ValueError as e:
msg = f"Planning failed: {e}"
if speak: speak(msg)
return msg
proj_name = project_name or plan.get("project_name", "jarvis_project")
proj_name = re.sub(r"[^\w\-]", "_", proj_name)
project_dir = PROJECTS_DIR / proj_name
project_dir.mkdir(parents=True, exist_ok=True)
files = plan.get("files", [])
entry_point = plan.get("entry_point", "main.py")
run_command = plan.get("run_command", f"python {entry_point}")
dependencies = plan.get("dependencies", [])
log(f"Project: {proj_name} | Files: {len(files)} | Entry: {entry_point}")
def _dep_sort_key(fi: dict) -> int:
return len(fi.get("imports", []))
sorted_files = sorted(files, key=_dep_sort_key)
file_codes: dict[str, str] = {}
for file_info in sorted_files:
file_path = file_info.get("path", "")
if not file_path:
continue
log(f"Writing {file_path}...")
for attempt in range(2):
try:
code = _write_file(
file_info=file_info,
project_description=description,
all_files=files,
language=language,
project_dir=project_dir,
already_written=file_codes,
)
file_codes[file_path] = code
time.sleep(0.4)
break
except RateLimitError:
if attempt == 0:
log("Rate limit — waiting 20s...")
time.sleep(20)
else:
log(f"Rate limit retry failed for {file_path}, skipping.")
except Exception as e:
log(f"Failed to write {file_path}: {e}")
break
if not file_codes:
msg = "I could not write any project files, sir."
if speak: speak(msg)
return msg
if dependencies:
install_result = _install_dependencies(dependencies, project_dir)
log(install_result)
_open_vscode(project_dir)
last_output = ""
auto_installs = 0
for attempt in range(1, MAX_FIX_ATTEMPTS + 1):
log(f"Running project (attempt {attempt}/{MAX_FIX_ATTEMPTS})...")
last_output = _run_project(run_command, project_dir, timeout)
log(f"Output preview: {last_output[:150]}")
if not _has_error(last_output, run_command):
msg = (
f"Project '{proj_name}' is working, sir. "
f"Built in {attempt} attempt{'s' if attempt > 1 else ''}. "
f"Saved to: {project_dir}"
)
if speak: speak(msg)
return f"{msg}\n\nOutput:\n{last_output}"
if attempt == MAX_FIX_ATTEMPTS:
break
error_type = _classify_error(last_output)
if error_type == "dependency_error" and auto_installs < 3:
installed = _try_auto_install(last_output, project_dir)
if installed:
auto_installs += 1
log("Missing dependency installed, retrying...")
time.sleep(1)
continue
log(f"Fixing errors (type: {error_type})...")
try:
updated = _fix_files(
error_output=last_output,
project_description=description,
all_files=files,
file_codes=file_codes,
language=language,
project_dir=project_dir,
entry_point=entry_point,
)
file_codes.update(updated)
time.sleep(1)
except RateLimitError:
msg = "Rate limit reached during fix. Project saved, check it manually in VSCode."
if speak: speak(msg)
return msg
except Exception as e:
log(f"Fix step failed: {e}")
msg = (
f"I couldn't fully fix '{proj_name}' after {MAX_FIX_ATTEMPTS} attempts, sir. "
f"Project is saved at {project_dir} — open it in VSCode and check manually."
)
if speak: speak(msg)
return f"{msg}\n\nLast error:\n{last_output[:600]}"
def dev_agent(
parameters: dict,
response=None,
player=None,
session_memory=None,
speak=None,
) -> str:
p = parameters or {}
description = p.get("description", "").strip()
language = p.get("language", "python").strip()
project_name = p.get("project_name", "").strip()
timeout = int(p.get("timeout", 30))
if not description:
return "Please describe the project you want me to build, sir."
return _build_project(
description = description,
language = language,
project_name = project_name,
timeout = timeout,
speak = speak,
player = player,
)
+482
View File
@@ -0,0 +1,482 @@
# actions/file_controller.py
# File management — create, delete, move, rename, list, find, organize
import shutil
from pathlib import Path
from datetime import datetime
import send2trash
def _get_desktop() -> Path:
"""Returns desktop path — works on Windows, Mac, Linux."""
return Path.home() / "Desktop"
def _get_downloads() -> Path:
return Path.home() / "Downloads"
def _resolve_path(raw: str) -> Path:
"""
Resolves a path from user input.
Supports shortcuts: 'desktop', 'downloads', 'documents', 'home'
"""
shortcuts = {
"desktop": Path.home() / "Desktop",
"downloads": Path.home() / "Downloads",
"documents": Path.home() / "Documents",
"pictures": Path.home() / "Pictures",
"music": Path.home() / "Music",
"videos": Path.home() / "Videos",
"home": Path.home(),
}
lower = raw.strip().lower()
if lower in shortcuts:
return shortcuts[lower]
return Path(raw).expanduser()
def _format_size(bytes_size: int) -> str:
"""Converts bytes to human readable format."""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_size < 1024:
return f"{bytes_size:.1f} {unit}"
bytes_size /= 1024
return f"{bytes_size:.1f} TB"
def list_files(path: str = "desktop", show_hidden: bool = False) -> str:
"""Lists files and folders in a directory."""
try:
target = _resolve_path(path)
if not target.exists():
return f"Path not found: {target}"
if not target.is_dir():
return f"Not a directory: {target}"
items = []
for item in sorted(target.iterdir()):
if not show_hidden and item.name.startswith("."):
continue
if item.is_dir():
items.append(f"📁 {item.name}/")
else:
size = _format_size(item.stat().st_size)
items.append(f"📄 {item.name} ({size})")
if not items:
return f"Directory is empty: {target}"
return f"Contents of {target.name}/ ({len(items)} items):\n" + "\n".join(items)
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Error listing files: {e}"
def create_file(path: str, content: str = "") -> str:
"""Creates a new file with optional content."""
try:
target = Path(path).expanduser()
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
return f"File created: {target.name}"
except Exception as e:
return f"Could not create file: {e}"
def create_folder(path: str) -> str:
"""Creates a new folder (and parent folders if needed)."""
try:
target = Path(path).expanduser()
target.mkdir(parents=True, exist_ok=True)
return f"Folder created: {target}"
except Exception as e:
return f"Could not create folder: {e}"
def delete_file(path: str, confirm: bool = True) -> str:
"""
Deletes a file or folder.
Moves to Recycle Bin on Windows if possible, otherwise permanent delete.
"""
try:
target = Path(path).expanduser()
if not target.exists():
return f"Not found: {path}"
try:
send2trash.send2trash(str(target))
return f"Moved to Recycle Bin: {target.name}"
except ImportError:
pass
# Fallback: permanent delete
if target.is_dir():
shutil.rmtree(target)
return f"Folder deleted permanently: {target.name}"
else:
target.unlink()
return f"File deleted permanently: {target.name}"
except PermissionError:
return f"Permission denied: {path}"
except Exception as e:
return f"Could not delete: {e}"
def move_file(source: str, destination: str) -> str:
"""Moves a file or folder to a new location."""
try:
src = Path(source).expanduser()
dst = _resolve_path(destination)
if not src.exists():
return f"Source not found: {source}"
if dst.is_dir():
dst = dst / src.name
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(src), str(dst))
return f"Moved: {src.name} → {dst.parent.name}/"
except Exception as e:
return f"Could not move: {e}"
def copy_file(source: str, destination: str) -> str:
"""Copies a file or folder."""
try:
src = Path(source).expanduser()
dst = _resolve_path(destination)
if not src.exists():
return f"Source not found: {source}"
if dst.is_dir():
dst = dst / src.name
dst.parent.mkdir(parents=True, exist_ok=True)
if src.is_dir():
shutil.copytree(str(src), str(dst))
else:
shutil.copy2(str(src), str(dst))
return f"Copied: {src.name} → {dst.parent.name}/"
except Exception as e:
return f"Could not copy: {e}"
def rename_file(path: str, new_name: str) -> str:
"""Renames a file or folder."""
try:
target = Path(path).expanduser()
new_path = target.parent / new_name
if not target.exists():
return f"Not found: {path}"
if new_path.exists():
return f"A file named '{new_name}' already exists."
target.rename(new_path)
return f"Renamed: {target.name} → {new_name}"
except Exception as e:
return f"Could not rename: {e}"
def read_file(path: str, max_chars: int = 3000) -> str:
"""Reads and returns the content of a text file."""
try:
target = Path(path).expanduser()
if not target.exists():
return f"File not found: {path}"
if not target.is_file():
return f"Not a file: {path}"
content = target.read_text(encoding="utf-8", errors="ignore")
if len(content) > max_chars:
content = content[:max_chars] + f"\n\n... (truncated, {len(content)} total chars)"
return content
except Exception as e:
return f"Could not read file: {e}"
def write_file(path: str, content: str, append: bool = False) -> str:
"""Writes or appends content to a file."""
try:
target = Path(path).expanduser()
target.parent.mkdir(parents=True, exist_ok=True)
mode = "a" if append else "w"
with open(target, mode, encoding="utf-8") as f:
f.write(content)
action = "Appended to" if append else "Written to"
return f"{action}: {target.name}"
except Exception as e:
return f"Could not write file: {e}"
def find_files(name: str = "", extension: str = "", path: str = "home",
max_results: int = 20) -> str:
"""
Searches for files by name or extension.
Example: find_files(extension=".pdf", path="documents")
"""
try:
search_path = _resolve_path(path)
if not search_path.exists():
return f"Search path not found: {path}"
results = []
pattern = f"*{extension}" if extension else "*"
for item in search_path.rglob(pattern):
if item.is_file():
if name and name.lower() not in item.name.lower():
continue
size = _format_size(item.stat().st_size)
results.append(f"📄 {item.name} ({size}) — {item.parent}")
if len(results) >= max_results:
break
if not results:
query = name or extension or "files"
return f"No {query} found in {search_path.name}/"
return f"Found {len(results)} file(s):\n" + "\n".join(results)
except Exception as e:
return f"Search error: {e}"
def get_largest_files(path: str = "home", count: int = 10) -> str:
"""Returns the largest files in a directory."""
try:
search_path = _resolve_path(path)
if not search_path.exists():
return f"Path not found: {path}"
files = []
for item in search_path.rglob("*"):
if item.is_file():
try:
files.append((item.stat().st_size, item))
except Exception:
continue
files.sort(reverse=True)
top = files[:count]
if not top:
return "No files found."
lines = [f"Top {len(top)} largest files in {search_path.name}/:\n"]
for size, f in top:
lines.append(f" {_format_size(size):>10} {f.name} ({f.parent})")
return "\n".join(lines)
except Exception as e:
return f"Error: {e}"
def get_disk_usage(path: str = "home") -> str:
"""Returns disk usage information."""
try:
target = _resolve_path(path)
usage = shutil.disk_usage(target)
total = _format_size(usage.total)
used = _format_size(usage.used)
free = _format_size(usage.free)
pct = usage.used / usage.total * 100
return (
f"Disk usage for {target}:\n"
f" Total : {total}\n"
f" Used : {used} ({pct:.1f}%)\n"
f" Free : {free}"
)
except Exception as e:
return f"Could not get disk usage: {e}"
def organize_desktop() -> str:
"""
Organizes the desktop by grouping files into folders by type.
Creates folders: Images, Documents, Videos, Music, Archives, Others
"""
try:
desktop = _get_desktop()
type_map = {
"Images": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico"],
"Documents": [".pdf", ".doc", ".docx", ".txt", ".xls", ".xlsx", ".ppt", ".pptx", ".csv"],
"Videos": [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm"],
"Music": [".mp3", ".wav", ".flac", ".aac", ".ogg", ".wma"],
"Archives": [".zip", ".rar", ".7z", ".tar", ".gz"],
"Code": [".py", ".js", ".html", ".css", ".json", ".xml", ".ts", ".cpp", ".java"],
}
moved = []
skipped = []
for item in desktop.iterdir():
if item.is_dir() or item.name.startswith("."):
continue
ext = item.suffix.lower()
target_dir = None
for folder, extensions in type_map.items():
if ext in extensions:
target_dir = desktop / folder
break
if target_dir is None:
target_dir = desktop / "Others"
target_dir.mkdir(exist_ok=True)
new_path = target_dir / item.name
if new_path.exists():
skipped.append(item.name)
continue
shutil.move(str(item), str(new_path))
moved.append(f"{item.name} → {target_dir.name}/")
result = f"Desktop organized. {len(moved)} files moved."
if moved:
result += "\n" + "\n".join(moved[:10])
if len(moved) > 10:
result += f"\n... and {len(moved)-10} more."
if skipped:
result += f"\n{len(skipped)} files skipped (already exist)."
return result
except Exception as e:
return f"Could not organize desktop: {e}"
def get_file_info(path: str) -> str:
"""Returns detailed information about a file."""
try:
target = Path(path).expanduser()
if not target.exists():
return f"Not found: {path}"
stat = target.stat()
info = {
"Name": target.name,
"Type": "Folder" if target.is_dir() else "File",
"Size": _format_size(stat.st_size),
"Location": str(target.parent),
"Created": datetime.fromtimestamp(stat.st_ctime).strftime("%Y-%m-%d %H:%M"),
"Modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M"),
"Extension": target.suffix or "None",
}
return "\n".join(f" {k}: {v}" for k, v in info.items())
except Exception as e:
return f"Could not get file info: {e}"
def file_controller(
parameters: dict,
response=None,
player=None,
session_memory=None
) -> str:
action = (parameters or {}).get("action", "").lower().strip()
path = (parameters or {}).get("path", "desktop")
name = (parameters or {}).get("name", "")
content = (parameters or {}).get("content", "")
def _full_path(p: str, n: str) -> str:
base = _resolve_path(p)
if n:
return str(base / n)
return str(base)
result = "Unknown action."
try:
if action == "list":
result = list_files(path)
elif action == "create_file":
full = _full_path(path, name)
result = create_file(full, content=content)
elif action == "create_folder":
full = _full_path(path, name)
result = create_folder(full)
elif action == "delete":
full = _full_path(path, name)
result = delete_file(full)
elif action == "move":
full = _full_path(path, name)
result = move_file(full, parameters.get("destination", ""))
elif action == "copy":
full = _full_path(path, name)
result = copy_file(full, parameters.get("destination", ""))
elif action == "rename":
full = _full_path(path, name)
result = rename_file(full, parameters.get("new_name", ""))
elif action == "read":
full = _full_path(path, name)
result = read_file(full)
elif action == "write":
full = _full_path(path, name)
result = write_file(
full,
content=content,
append=parameters.get("append", False)
)
elif action == "find":
result = find_files(
name=name or parameters.get("name", ""),
extension=parameters.get("extension", ""),
path=path,
max_results=parameters.get("max_results", 20)
)
elif action == "largest":
result = get_largest_files(
path=path,
count=parameters.get("count", 10)
)
elif action == "disk_usage":
result = get_disk_usage(path)
elif action == "organize_desktop":
result = organize_desktop()
elif action == "info":
full = _full_path(path, name)
result = get_file_info(full)
else:
result = f"Unknown action: '{action}'"
except Exception as e:
result = f"File controller error: {e}"
if player:
player.write_log(f"[file] {result[:60]}")
return result
+833
View File
@@ -0,0 +1,833 @@
"""
file_processor.py — JARVIS Universal File Processor
Supported types:
image → describe, ocr, resize, convert, compress, crop
pdf → summarize, extract_text, extract_pages, to_word
docx → summarize, extract_text, reformat, translate_hint
txt/md → summarize, reformat, translate_hint, word_count
csv → analyze, filter, sort, convert, stats
xlsx → analyze, filter, convert, stats
json → validate, format, extract, convert
code → explain, review, fix, run, document
audio → transcribe, trim, convert, info
video → trim, extract_audio, extract_frame, info, compress
zip → list, extract
pptx → summarize, extract_text, to_pdf
"""
import os
import re
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
from datetime import datetime
import google.generativeai as genai
def _get_api_key() -> str:
config_path = Path(__file__).resolve().parent.parent / "config" / "api_keys.json"
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _gemini_client():
genai.configure(api_key=_get_api_key())
return genai.GenerativeModel("gemini-2.5-flash")
def _detect_type(path: Path) -> str:
ext = path.suffix.lower().lstrip(".")
image_exts = {"jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", "svg", "ico"}
video_exts = {"mp4", "avi", "mov", "mkv", "wmv", "flv", "webm", "m4v", "3gp"}
audio_exts = {"mp3", "wav", "ogg", "m4a", "aac", "flac", "wma", "opus"}
code_exts = {"py", "js", "ts", "jsx", "tsx", "html", "css", "java", "c",
"cpp", "cs", "go", "rs", "rb", "php", "swift", "kt", "sh",
"bash", "ps1", "lua", "r", "m", "sql", "yaml", "toml"}
archive_exts = {"zip", "rar", "tar", "gz", "7z", "bz2", "xz"}
if ext in image_exts: return "image"
if ext in video_exts: return "video"
if ext in audio_exts: return "audio"
if ext in code_exts: return "code"
if ext in archive_exts: return "archive"
if ext == "pdf": return "pdf"
if ext in ("docx", "doc"): return "docx"
if ext in ("txt", "md", "rst", "log"): return "text"
if ext in ("csv", "tsv"): return "csv"
if ext in ("xlsx", "xls", "ods"): return "excel"
if ext == "json": return "json"
if ext == "xml": return "xml"
if ext in ("pptx", "ppt"): return "pptx"
return "unknown"
def _file_size_str(path: Path) -> str:
size = path.stat().st_size
if size < 1024: return f"{size} B"
if size < 1024**2: return f"{size/1024:.1f} KB"
if size < 1024**3: return f"{size/1024**2:.1f} MB"
return f"{size/1024**3:.1f} GB"
def _output_path(src: Path, suffix: str, new_ext: str = None) -> Path:
ext = new_ext or src.suffix
name = f"{src.stem}_{suffix}{ext}"
return src.parent / name
def _process_image(path: Path, action: str, params: dict, speak=None) -> str:
try:
from PIL import Image
except ImportError:
return "Pillow is not installed. Run: pip install Pillow"
action = action or "describe"
if action in ("describe", "ocr", "analyze", "read", "extract_text"):
try:
model = _gemini_client()
img = Image.open(path)
prompt = {
"describe": "Describe this image in detail.",
"ocr": "Extract all text visible in this image. Return only the text, formatted clearly.",
"analyze": "Analyze this image thoroughly: objects, colors, composition, any text, context.",
"read": "Read all text in this image, preserving structure and formatting.",
"extract_text": "Extract all text from this image.",
}.get(action, "Describe this image.")
if params.get("instruction"):
prompt = params["instruction"]
response = model.generate_content([prompt, img])
result = response.text.strip()
if len(result) > 500 and params.get("save", True):
out = _output_path(path, "result", ".txt")
out.write_text(result, encoding="utf-8")
return f"{result[:300]}...\n\nFull result saved to: {out}"
return result
except Exception as e:
return f"AI image analysis failed: {e}"
if action == "resize":
width = int(params.get("width", 0))
height = int(params.get("height", 0))
scale = float(params.get("scale", 0))
try:
img = Image.open(path)
w, h = img.size
if scale:
new_size = (int(w * scale), int(h * scale))
elif width and height:
new_size = (width, height)
elif width:
new_size = (width, int(h * width / w))
elif height:
new_size = (int(w * height / h), height)
else:
return "Please specify width, height, or scale."
out = _output_path(path, f"resized_{new_size[0]}x{new_size[1]}")
img.resize(new_size, Image.LANCZOS).save(out)
return f"Resized from {w}x{h} to {new_size[0]}x{new_size[1]}. Saved: {out.name}"
except Exception as e:
return f"Resize failed: {e}"
if action == "convert":
fmt = params.get("format", "png").lower().strip(".")
fmt_map = {"jpg": "JPEG", "jpeg": "JPEG", "png": "PNG",
"webp": "WEBP", "bmp": "BMP", "tiff": "TIFF"}
pil_fmt = fmt_map.get(fmt, fmt.upper())
try:
img = Image.open(path).convert("RGB") if fmt == "jpg" else Image.open(path)
out = _output_path(path, "converted", f".{fmt}")
img.save(out, pil_fmt)
return f"Converted to {fmt.upper()}. Saved: {out.name}"
except Exception as e:
return f"Convert failed: {e}"
if action == "compress":
quality = int(params.get("quality", 70))
try:
img = Image.open(path).convert("RGB")
out = _output_path(path, f"compressed_q{quality}", ".jpg")
img.save(out, "JPEG", quality=quality, optimize=True)
before = _file_size_str(path)
after = _file_size_str(out)
return f"Compressed: {before} → {after}. Saved: {out.name}"
except Exception as e:
return f"Compress failed: {e}"
if action == "info":
try:
img = Image.open(path)
return (f"Image info: {img.format}, {img.size[0]}x{img.size[1]}px, "
f"mode: {img.mode}, size: {_file_size_str(path)}")
except Exception as e:
return f"Info failed: {e}"
return _process_image(path, "describe", {"instruction": f"{action}: {params}"})
def _process_pdf(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "summarize"
def _extract_pdf_text(max_chars=50000) -> str:
text = ""
try:
import pdfplumber
with pdfplumber.open(path) as pdf:
for page in pdf.pages:
text += (page.extract_text() or "") + "\n"
except ImportError:
try:
import PyPDF2
with open(path, "rb") as f:
reader = PyPDF2.PdfReader(f)
for page in reader.pages:
text += page.extract_text() + "\n"
except ImportError:
return ""
return text[:max_chars]
if action in ("summarize", "extract_text", "translate_hint", "analyze", "reformat"):
text = _extract_pdf_text()
if not text.strip():
return "Could not extract text from PDF (may be scanned/image-based)."
if action == "extract_text":
out = _output_path(path, "text", ".txt")
out.write_text(text, encoding="utf-8")
return f"Text extracted ({len(text)} chars). Saved: {out.name}"
prompt_map = {
"summarize": f"Summarize this PDF document concisely:\n\n{text}",
"analyze": f"Analyze this document thoroughly:\n\n{text}",
"translate_hint": f"What language is this document in and what does it say? Summarize:\n\n{text}",
"reformat": f"Reformat this text cleanly with proper structure:\n\n{text}",
}
try:
model = _gemini_client()
response = model.generate_content(prompt_map.get(action, f"Analyze:\n\n{text}"))
result = response.text.strip()
if len(result) > 600 and params.get("save", True):
out = _output_path(path, action, ".txt")
out.write_text(result, encoding="utf-8")
return f"{result[:400]}...\n\nFull result saved: {out.name}"
return result
except Exception as e:
return f"AI analysis failed: {e}"
if action == "info":
try:
import pdfplumber
with pdfplumber.open(path) as pdf:
pages = len(pdf.pages)
return f"PDF: {pages} pages, size: {_file_size_str(path)}"
except Exception:
return f"PDF size: {_file_size_str(path)}"
if action == "to_word":
text = _extract_pdf_text()
if not text:
return "Could not extract text to convert."
try:
from docx import Document
doc = Document()
doc.add_heading(path.stem, 0)
for para in text.split("\n\n"):
if para.strip():
doc.add_paragraph(para.strip())
out = _output_path(path, "converted", ".docx")
doc.save(out)
return f"Converted to Word document. Saved: {out.name}"
except ImportError:
return "python-docx not installed. Run: pip install python-docx"
return f"Unknown PDF action: '{action}'. Try: summarize, extract_text, info, to_word"
def _process_text_doc(path: Path, file_type: str, action: str,
params: dict, speak=None) -> str:
action = action or "summarize"
def _read_content() -> str:
if file_type == "docx":
try:
from docx import Document
doc = Document(path)
return "\n".join(p.text for p in doc.paragraphs)
except ImportError:
return "python-docx not installed."
except Exception as e:
return f"Read failed: {e}"
else:
return path.read_text(encoding="utf-8", errors="ignore")
content = _read_content()
if not content.strip():
return "File appears to be empty."
if action == "word_count":
words = len(content.split())
chars = len(content)
lines = content.count("\n")
return f"Word count: {words} words, {chars} characters, {lines} lines."
if action == "extract_text":
if file_type != "txt":
out = _output_path(path, "extracted", ".txt")
out.write_text(content, encoding="utf-8")
return f"Text extracted. Saved: {out.name}"
return content[:2000]
instruction = params.get("instruction", "")
prompt_map = {
"summarize": f"Summarize this document concisely:\n\n{content[:40000]}",
"analyze": f"Analyze this document:\n\n{content[:40000]}",
"reformat": f"Reformat this text with clean structure, proper headings and paragraphs:\n\n{content[:40000]}",
"fix": f"Fix grammar, spelling and style issues in this text:\n\n{content[:40000]}",
"translate_hint": f"What language is this and what does it say? Summarize:\n\n{content[:10000]}",
"to_bullet": f"Convert this text into a clear bullet-point summary:\n\n{content[:40000]}",
"custom": f"{instruction}\n\n{content[:40000]}",
}
if action not in prompt_map:
action = "custom"
instruction = action
try:
model = _gemini_client()
response = model.generate_content(prompt_map[action])
result = response.text.strip()
if len(result) > 600 and params.get("save", True):
out = _output_path(path, action, ".txt")
out.write_text(result, encoding="utf-8")
return f"{result[:400]}...\n\nFull result saved: {out.name}"
return result
except Exception as e:
return f"AI processing failed: {e}"
def _process_data(path: Path, file_type: str, action: str,
params: dict, speak=None) -> str:
try:
import pandas as pd
except ImportError:
return "pandas not installed. Run: pip install pandas openpyxl"
action = action or "analyze"
try:
if file_type == "csv":
df = pd.read_csv(path, encoding="utf-8", errors="replace")
else:
df = pd.read_excel(path)
except Exception as e:
return f"Could not read file: {e}"
if action == "info":
return (f"Rows: {len(df)}, Columns: {len(df.columns)}\n"
f"Columns: {', '.join(df.columns.tolist())}\n"
f"Size: {_file_size_str(path)}")
if action == "stats":
try:
desc = df.describe(include="all").to_string()
return f"Statistics:\n{desc[:2000]}"
except Exception as e:
return f"Stats failed: {e}"
if action == "analyze":
preview = df.head(50).to_string()
prompt = (f"Analyze this dataset. Columns: {list(df.columns)}\n"
f"Rows: {len(df)}\nPreview:\n{preview}\n\n"
f"Give insights, patterns, and notable findings.")
try:
model = _gemini_client()
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"AI analysis failed: {e}"
if action in ("convert", "to_csv", "to_excel", "to_json"):
fmt = {"to_csv": "csv", "to_excel": "xlsx", "to_json": "json",
"convert": params.get("format", "csv")}.get(action, "csv")
try:
if fmt == "csv":
out = _output_path(path, "converted", ".csv")
df.to_csv(out, index=False, encoding="utf-8")
elif fmt == "xlsx":
out = _output_path(path, "converted", ".xlsx")
df.to_excel(out, index=False)
elif fmt == "json":
out = _output_path(path, "converted", ".json")
df.to_json(out, orient="records", force_ascii=False, indent=2)
return f"Converted to {fmt.upper()}. Saved: {out.name}"
except Exception as e:
return f"Convert failed: {e}"
if action == "filter":
col = params.get("column", "")
value = params.get("value", "")
condition = params.get("condition", "equals")
if not col or col not in df.columns:
return f"Column '{col}' not found. Available: {', '.join(df.columns)}"
try:
if condition == "equals": filtered = df[df[col] == value]
elif condition == "contains": filtered = df[df[col].astype(str).str.contains(str(value), case=False)]
elif condition == "gt": filtered = df[df[col] > float(value)]
elif condition == "lt": filtered = df[df[col] < float(value)]
else: filtered = df[df[col] == value]
out = _output_path(path, "filtered", ".csv")
filtered.to_csv(out, index=False)
return f"Filtered: {len(filtered)} rows match. Saved: {out.name}"
except Exception as e:
return f"Filter failed: {e}"
if action == "sort":
col = params.get("column", df.columns[0])
asc = params.get("ascending", True)
try:
sorted_df = df.sort_values(col, ascending=asc)
out = _output_path(path, "sorted", path.suffix)
sorted_df.to_csv(out, index=False)
return f"Sorted by '{col}'. Saved: {out.name}"
except Exception as e:
return f"Sort failed: {e}"
preview = df.head(30).to_string()
try:
model = _gemini_client()
response = model.generate_content(
f"Task: {action}\nDataset ({len(df)} rows, cols: {list(df.columns)}):\n{preview}"
)
return response.text.strip()
except Exception as e:
return f"Processing failed: {e}"
def _process_json(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "analyze"
try:
content = path.read_text(encoding="utf-8")
data = json.loads(content)
except Exception as e:
return f"Invalid JSON: {e}"
if action == "validate":
return f"Valid JSON. Type: {type(data).__name__}, size: {_file_size_str(path)}"
if action == "format":
out = _output_path(path, "formatted", ".json")
out.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
return f"Formatted JSON saved: {out.name}"
if action in ("analyze", "summarize", "extract"):
preview = json.dumps(data, indent=2, ensure_ascii=False)[:8000]
prompt = f"Task: {action} this JSON data:\n{preview}"
if params.get("instruction"):
prompt = f"{params['instruction']}\n\nJSON data:\n{preview}"
try:
model = _gemini_client()
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"AI processing failed: {e}"
if action == "to_csv":
try:
import pandas as pd
if isinstance(data, list):
df = pd.DataFrame(data)
out = _output_path(path, "converted", ".csv")
df.to_csv(out, index=False)
return f"Converted to CSV. Saved: {out.name}"
return "JSON must be an array of objects to convert to CSV."
except ImportError:
return "pandas not installed."
return _process_json(path, "analyze", {"instruction": action})
def _process_code(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "explain"
content = path.read_text(encoding="utf-8", errors="ignore")
ext = path.suffix.lstrip(".")
if action == "run":
if ext == "py":
try:
result = subprocess.run(
["python", str(path)],
capture_output=True, text=True, timeout=30
)
out = result.stdout or result.stderr
return f"Output:\n{out[:2000]}" if out else "No output."
except subprocess.TimeoutExpired:
return "Execution timed out (30s)."
except Exception as e:
return f"Run failed: {e}"
return f"Direct execution not supported for .{ext} files."
if action == "info":
lines = content.count("\n")
words = len(content.split())
return f"Code file: {lines} lines, {words} words, {_file_size_str(path)}"
prompt_map = {
"explain": f"Explain this {ext} code clearly:\n\n```{ext}\n{content[:30000]}\n```",
"review": f"Review this {ext} code for bugs, issues, and improvements:\n\n```{ext}\n{content[:30000]}\n```",
"fix": f"Fix any bugs in this {ext} code and return the corrected version:\n\n```{ext}\n{content[:30000]}\n```",
"optimize": f"Optimize this {ext} code for performance and readability:\n\n```{ext}\n{content[:30000]}\n```",
"document": f"Add proper documentation/comments to this {ext} code:\n\n```{ext}\n{content[:30000]}\n```",
"summarize": f"Summarize what this {ext} code does:\n\n```{ext}\n{content[:30000]}\n```",
"test": f"Write unit tests for this {ext} code:\n\n```{ext}\n{content[:30000]}\n```",
}
instruction = params.get("instruction", "")
if action not in prompt_map:
prompt = f"{action}\n\n```{ext}\n{content[:30000]}\n```"
if instruction:
prompt = f"{instruction}\n\n```{ext}\n{content[:30000]}\n```"
else:
prompt = prompt_map[action]
try:
model = _gemini_client()
response = model.generate_content(prompt)
result = response.text.strip()
if action in ("fix", "optimize", "document") and params.get("save", True):
out = _output_path(path, action)
code_match = re.search(r"```(?:\w+)?\n(.*?)```", result, re.DOTALL)
code_to_save = code_match.group(1) if code_match else result
out.write_text(code_to_save, encoding="utf-8")
return f"{result[:400]}...\n\nSaved: {out.name}"
return result
except Exception as e:
return f"AI processing failed: {e}"
def _process_audio(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "transcribe"
if action == "info":
try:
from pydub import AudioSegment
audio = AudioSegment.from_file(path)
duration = len(audio) / 1000
mins, secs = divmod(int(duration), 60)
return (f"Audio: {mins}m {secs}s, "
f"{audio.channels} ch, "
f"{audio.frame_rate}Hz, "
f"{_file_size_str(path)}")
except ImportError:
return f"Audio file: {_file_size_str(path)} (install pydub for more info)"
except Exception as e:
return f"Info failed: {e}"
if action == "transcribe":
try:
model = _gemini_client()
content = path.read_bytes()
mime = {
"mp3": "audio/mp3", "wav": "audio/wav",
"ogg": "audio/ogg", "m4a": "audio/mp4",
"aac": "audio/aac", "flac": "audio/flac",
}.get(path.suffix.lstrip(".").lower(), "audio/mpeg")
response = model.generate_content([
"Transcribe all speech in this audio file accurately.",
{"mime_type": mime, "data": content}
])
result = response.text.strip()
if params.get("save", True):
out = _output_path(path, "transcript", ".txt")
out.write_text(result, encoding="utf-8")
return f"Transcription saved: {out.name}\n\nPreview: {result[:300]}"
return result
except Exception as e:
return f"Transcription failed: {e}"
if action == "convert":
fmt = params.get("format", "mp3").lstrip(".")
try:
from pydub import AudioSegment
audio = AudioSegment.from_file(path)
out = _output_path(path, "converted", f".{fmt}")
audio.export(out, format=fmt)
return f"Converted to {fmt.upper()}. Saved: {out.name}"
except ImportError:
return "pydub not installed. Run: pip install pydub"
except Exception as e:
return f"Convert failed: {e}"
if action == "trim":
start = float(params.get("start", 0))
end = float(params.get("end", 0))
try:
from pydub import AudioSegment
audio = AudioSegment.from_file(path)
end_ms = int(end * 1000) if end else len(audio)
trimmed = audio[int(start * 1000):end_ms]
out = _output_path(path, f"trim_{int(start)}s_{int(end)}s")
trimmed.export(out, format=path.suffix.lstrip("."))
return f"Trimmed audio ({int(start)}s–{int(end)}s). Saved: {out.name}"
except ImportError:
return "pydub not installed."
except Exception as e:
return f"Trim failed: {e}"
return f"Unknown audio action: '{action}'. Try: transcribe, info, convert, trim"
def _process_video(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "info"
def _ffmpeg_available() -> bool:
try:
subprocess.run(["ffmpeg", "-version"], capture_output=True, timeout=3)
return True
except Exception:
return False
if action == "info":
try:
result = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", str(path)],
capture_output=True, text=True, timeout=10
)
data = json.loads(result.stdout)
fmt = data.get("format", {})
duration = float(fmt.get("duration", 0))
mins, secs = divmod(int(duration), 60)
size = _file_size_str(path)
streams = data.get("streams", [])
video_s = next((s for s in streams if s["codec_type"] == "video"), {})
w = video_s.get("width", "?")
h = video_s.get("height", "?")
fps = video_s.get("r_frame_rate", "?")
return f"Video: {mins}m {secs}s, {w}x{h}, {fps} fps, {size}"
except Exception:
return f"Video file: {_file_size_str(path)}"
if action == "extract_audio":
if not _ffmpeg_available():
return "ffmpeg not found. Install ffmpeg to extract audio."
out = _output_path(path, "audio", ".mp3")
try:
subprocess.run(
["ffmpeg", "-i", str(path), "-q:a", "0", "-map", "a", str(out), "-y"],
capture_output=True, timeout=300
)
return f"Audio extracted. Saved: {out.name}"
except Exception as e:
return f"Extract audio failed: {e}"
if action == "trim":
start = params.get("start", "00:00:00")
end = params.get("end", "")
if not _ffmpeg_available():
return "ffmpeg not found."
out = _output_path(path, f"trim", path.suffix)
try:
cmd = ["ffmpeg", "-i", str(path), "-ss", str(start)]
if end:
cmd += ["-to", str(end)]
cmd += ["-c", "copy", str(out), "-y"]
subprocess.run(cmd, capture_output=True, timeout=600)
return f"Trimmed video saved: {out.name}"
except Exception as e:
return f"Trim failed: {e}"
if action == "extract_frame":
timestamp = params.get("timestamp", "00:00:01")
if not _ffmpeg_available():
return "ffmpeg not found."
out = _output_path(path, f"frame_{timestamp.replace(':', '')}", ".jpg")
try:
subprocess.run(
["ffmpeg", "-i", str(path), "-ss", timestamp,
"-vframes", "1", str(out), "-y"],
capture_output=True, timeout=30
)
return f"Frame extracted at {timestamp}. Saved: {out.name}"
except Exception as e:
return f"Extract frame failed: {e}"
if action == "compress":
crf = int(params.get("quality", 28))
if not _ffmpeg_available():
return "ffmpeg not found."
out = _output_path(path, f"compressed_crf{crf}", ".mp4")
try:
subprocess.run(
["ffmpeg", "-i", str(path),
"-c:v", "libx264", "-crf", str(crf),
"-preset", "medium", "-c:a", "copy",
str(out), "-y"],
capture_output=True, timeout=1800
)
before = _file_size_str(path)
after = _file_size_str(out)
return f"Compressed: {before} → {after}. Saved: {out.name}"
except Exception as e:
return f"Compress failed: {e}"
if action == "transcribe":
if not _ffmpeg_available():
return "ffmpeg not found. Needed for video transcription."
tmp_audio = Path(tempfile.mktemp(suffix=".mp3"))
try:
subprocess.run(
["ffmpeg", "-i", str(path), "-q:a", "0", "-map", "a",
str(tmp_audio), "-y"],
capture_output=True, timeout=300
)
result = _process_audio(tmp_audio, "transcribe", params, speak)
return result
except Exception as e:
return f"Video transcription failed: {e}"
finally:
if tmp_audio.exists():
tmp_audio.unlink()
if action == "convert":
fmt = params.get("format", "mp4").lstrip(".")
if not _ffmpeg_available():
return "ffmpeg not found."
out = _output_path(path, "converted", f".{fmt}")
try:
subprocess.run(
["ffmpeg", "-i", str(path), str(out), "-y"],
capture_output=True, timeout=1800
)
return f"Converted to {fmt.upper()}. Saved: {out.name}"
except Exception as e:
return f"Convert failed: {e}"
return f"Unknown video action: '{action}'. Try: info, trim, extract_audio, extract_frame, compress, transcribe, convert"
def _process_archive(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "list"
if action == "list":
try:
import zipfile, tarfile
ext = path.suffix.lower()
if ext == ".zip":
with zipfile.ZipFile(path) as z:
names = z.namelist()
elif ext in (".tar", ".gz", ".bz2", ".xz"):
with tarfile.open(path) as t:
names = t.getnames()
else:
return f"Unsupported archive format: {ext}"
preview = "\n".join(names[:30])
suffix = f"\n... and {len(names)-30} more" if len(names) > 30 else ""
return f"Archive contains {len(names)} files:\n{preview}{suffix}"
except Exception as e:
return f"List failed: {e}"
if action == "extract":
dest = Path(params.get("destination", str(path.parent / path.stem)))
dest.mkdir(parents=True, exist_ok=True)
try:
shutil.unpack_archive(path, dest)
return f"Extracted to: {dest}"
except Exception as e:
return f"Extract failed: {e}"
return f"Unknown archive action: '{action}'. Try: list, extract"
def _process_pptx(path: Path, action: str, params: dict, speak=None) -> str:
action = action or "summarize"
def _read_pptx_text() -> str:
try:
from pptx import Presentation
prs = Presentation(path)
text = []
for i, slide in enumerate(prs.slides, 1):
slide_text = f"\n--- Slide {i} ---\n"
for shape in slide.shapes:
if hasattr(shape, "text") and shape.text.strip():
slide_text += shape.text.strip() + "\n"
text.append(slide_text)
return "\n".join(text)
except ImportError:
return "python-pptx not installed."
if action in ("summarize", "extract_text", "analyze"):
text = _read_pptx_text()
if action == "extract_text":
out = _output_path(path, "text", ".txt")
out.write_text(text, encoding="utf-8")
return f"Text extracted. Saved: {out.name}"
try:
model = _gemini_client()
prompt = f"{'Summarize' if action == 'summarize' else 'Analyze'} this presentation:\n{text[:30000]}"
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"AI processing failed: {e}"
return f"Unknown PPTX action: '{action}'. Try: summarize, extract_text, analyze"
def file_processor(parameters: dict, player=None, speak=None) -> str:
file_path_str = parameters.get("file_path", "").strip()
if not file_path_str:
return "No file path provided."
path = Path(file_path_str)
if not path.exists():
return f"File not found: {file_path_str}"
if not path.is_file():
return f"Path is not a file: {file_path_str}"
file_type = _detect_type(path)
action = (parameters.get("action") or "").lower().strip()
instruction = parameters.get("instruction", "")
params = {**parameters, "instruction": instruction}
log_msg = f"[FileProcessor] {file_type.upper()} | {path.name} | action={action or 'auto'}"
print(log_msg)
if player:
player.write_log(log_msg)
if file_type == "unknown":
try:
content = path.read_text(encoding="utf-8", errors="ignore")[:10000]
model = _gemini_client()
prompt = f"File: {path.name}\nContent preview:\n{content}\n\nTask: {action or instruction or 'Describe what this file contains and what can be done with it.'}"
response = model.generate_content(prompt)
return response.text.strip()
except Exception as e:
return f"Unknown file type ({path.suffix}). Could not process: {e}"
dispatch = {
"image": _process_image,
"pdf": _process_pdf,
"docx": lambda p, a, pm, s: _process_text_doc(p, "docx", a, pm, s),
"text": lambda p, a, pm, s: _process_text_doc(p, "text", a, pm, s),
"csv": lambda p, a, pm, s: _process_data(p, "csv", a, pm, s),
"excel": lambda p, a, pm, s: _process_data(p, "excel", a, pm, s),
"json": _process_json,
"xml": lambda p, a, pm, s: _process_json(p, a, pm, s),
"code": _process_code,
"audio": _process_audio,
"video": _process_video,
"archive": _process_archive,
"pptx": _process_pptx,
}
handler = dispatch.get(file_type)
if not handler:
return f"Unsupported file type: {file_type}"
try:
result = handler(path, action, params, speak)
return result or "Done."
except Exception as e:
import traceback
traceback.print_exc()
return f"Processing failed: {e}"
+339
View File
@@ -0,0 +1,339 @@
#flight_finder.py
import json
import re
import subprocess
import sys
from datetime import datetime, timedelta
from pathlib import Path
from config import is_windows, is_mac, is_linux
def _get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = _get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
_MONTH_MAP: dict[str, int] = {
"january": 1, "february": 2, "march": 3, "april": 4,
"may": 5, "june": 6, "july": 7, "august": 8,
"september": 9, "october": 10, "november": 11, "december": 12,
"ocak": 1, "şubat": 2, "mart": 3, "nisan": 4,
"mayıs": 5, "haziran": 6, "temmuz": 7, "ağustos": 8,
"eylül": 9, "ekim": 10, "kasım": 11, "aralık": 12,
}
_RELATIVE_MAP_KEYS = {
"today", "bugün",
"tomorrow", "yarın",
}
def _parse_date(raw: str) -> str:
raw = raw.strip()
lower = raw.lower()
today = datetime.now()
if re.match(r"\d{4}-\d{2}-\d{2}", raw):
return raw
for fmt in ("%d/%m/%Y", "%m/%d/%Y", "%d.%m.%Y", "%d-%m-%Y"):
try:
return datetime.strptime(raw, fmt).strftime("%Y-%m-%d")
except ValueError:
pass
relative = {
"today": today, "bugün": today,
"tomorrow": today + timedelta(days=1),
"yarın": today + timedelta(days=1),
}
for key, val in relative.items():
if key in lower:
return val.strftime("%Y-%m-%d")
# replace the try/except genai block with:
try:
from or_client import client
result = client.chat(
f"Today is {today.strftime('%Y-%m-%d')}. "
f"Convert this date expression to YYYY-MM-DD: '{raw}'. "
f"Return ONLY the date string, nothing else.",
system="You are a date converter. Return only the YYYY-MM-DD string."
)
result = result.strip()
if re.match(r"\d{4}-\d{2}-\d{2}", result):
return result
except Exception as e:
print(f"[FlightFinder] ⚠️ date parse failed: {e}")
for month_name, month_num in _MONTH_MAP.items():
if month_name in lower:
day_match = re.search(r"\d{1,2}", raw)
if day_match:
day = int(day_match.group())
year = today.year if month_num >= today.month else today.year + 1
return f"{year}-{month_num:02d}-{day:02d}"
# Last resort: today
print(f"[FlightFinder] ⚠️ Could not parse date '{raw}' — using today.")
return today.strftime("%Y-%m-%d")
_CABIN_CODE: dict[str, str] = {
"economy": "1",
"premium": "2",
"business": "3",
"first": "4",
}
def _build_google_flights_url(
origin: str,
destination: str,
date: str,
return_date: str | None = None,
passengers: int = 1,
cabin: str = "economy",
) -> str:
from urllib.parse import quote_plus
cabin_code = _CABIN_CODE.get(cabin.lower(), "1")
origin_enc = quote_plus(origin)
destination_enc = quote_plus(destination)
if return_date:
trip = f"Flights+from+{origin_enc}+to+{destination_enc}+on+{date}+returning+{return_date}"
else:
trip = f"Flights+from+{origin_enc}+to+{destination_enc}+on+{date}"
return (
f"https://www.google.com/travel/flights"
f"?q={trip}"
f"&curr=USD"
f"&cabin={cabin_code}"
f"&adults={passengers}"
)
def _search_flights_browser(
origin: str,
destination: str,
date: str,
return_date: str | None,
passengers: int,
cabin: str,
) -> tuple[str, str]:
import time
from actions.browser_control import browser_control
url = _build_google_flights_url(
origin, destination, date, return_date, passengers, cabin
)
print(f"[FlightFinder] 🌐 Opening: {url}")
browser_control({"action": "go_to", "url": url})
time.sleep(5)
raw = browser_control({"action": "get_text"})
return (raw or ""), url
def _parse_flights_with_gemini(
raw_text: str,
origin: str,
destination: str,
date: str,
) -> list[dict]:
from or_client import client
prompt = (
f"Extract flight options from {origin} to {destination} on {date} "
f"from this Google Flights page text:\n\n{raw_text[:12000]}\n\n"
f"Return a JSON array of up to 5 flights:\n"
f'[{{"airline":"...","departure":"HH:MM","arrival":"HH:MM",'
f'"duration":"Xh Ym","stops":0,"price":"...","currency":"USD"}}]\n'
f"If no flights found, return: []"
)
try:
result = client.chat_json(prompt, system="Return only valid JSON. No extra text.")
return result if isinstance(result, list) else []
except Exception as e:
print(f"[FlightFinder] ⚠️ parse failed: {e}")
return []
def _format_spoken(
flights: list[dict],
origin: str,
destination: str,
date: str,
) -> str:
if not flights:
return (
f"I couldn't find any flights from {origin} to {destination} "
f"on {date}, sir. The page may not have loaded correctly."
)
lines = [f"Here are the top flights from {origin} to {destination} on {date}, sir."]
for i, f in enumerate(flights[:5], 1):
airline = f.get("airline", "Unknown airline")
departure = f.get("departure", "--:--")
arrival = f.get("arrival", "--:--")
duration = f.get("duration", "")
stops = f.get("stops", 0)
price = f.get("price", "")
currency = f.get("currency", "")
stop_str = "non-stop" if stops == 0 else f"{stops} stop{'s' if stops > 1 else ''}"
price_str = f"{price} {currency}".strip() if price else "price unavailable"
dur_str = f", {duration}" if duration else ""
lines.append(
f"Option {i}: {airline}, departing {departure}, "
f"arriving {arrival}{dur_str}, {stop_str}, {price_str}."
)
# Cheapest — strip non-digits for comparison
priced = [f for f in flights if f.get("price")]
if priced:
cheapest = min(
priced,
key=lambda x: int(re.sub(r"[^\d]", "", str(x["price"])) or "999999"),
)
lines.append(
f"The cheapest option is {cheapest.get('airline')} "
f"at {cheapest.get('price')} {cheapest.get('currency', '')}."
)
return " ".join(lines)
def _format_text_report(
flights: list[dict],
origin: str,
destination: str,
date: str,
return_date: str | None,
page_url: str,
) -> str:
lines = [
"JARVIS — Flight Search Results",
"─" * 50,
f"Route : {origin} → {destination}",
f"Date : {date}",
]
if return_date:
lines.append(f"Return : {return_date}")
lines += [
f"Searched : {datetime.now().strftime('%Y-%m-%d %H:%M')}",
f"Source : {page_url}",
"─" * 50,
"",
]
if not flights:
lines.append("No flights found.")
else:
for i, f in enumerate(flights, 1):
stops = f.get("stops", 0)
stop_str = "Non-stop" if stops == 0 else f"{stops} stop(s)"
lines += [
f"Flight {i}:",
f" Airline : {f.get('airline', 'N/A')}",
f" Departure : {f.get('departure', 'N/A')}",
f" Arrival : {f.get('arrival', 'N/A')}",
f" Duration : {f.get('duration', 'N/A')}",
f" Stops : {stop_str}",
f" Price : {f.get('price', 'N/A')} {f.get('currency', '')}",
"",
]
return "\n".join(lines)
def _save_to_desktop(content: str, origin: str, destination: str) -> str:
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"flights_{origin}_{destination}_{ts}.txt".replace(" ", "_")
desktop = Path.home() / "Desktop"
desktop.mkdir(parents=True, exist_ok=True)
filepath = desktop / filename
filepath.write_text(content, encoding="utf-8")
print(f"[FlightFinder] 💾 Saved: {filepath}")
try:
if is_windows():
subprocess.Popen(["notepad.exe", str(filepath)])
elif is_mac():
subprocess.Popen(["open", "-t", str(filepath)])
else:
subprocess.Popen(["xdg-open", str(filepath)])
except Exception as e:
print(f"[FlightFinder] ⚠️ Could not open text editor: {e}")
return str(filepath)
def flight_finder(parameters: dict, player=None, speak=None) -> str:
params = parameters or {}
origin = params.get("origin", "").strip()
destination = params.get("destination", "").strip()
date_raw = params.get("date", "").strip()
return_raw = (params.get("return_date") or "").strip()
passengers = max(1, int(params.get("passengers", 1)))
cabin = params.get("cabin", "economy").strip().lower()
save = bool(params.get("save", False))
if not origin or not destination:
return "Please provide both origin and destination, sir."
if not date_raw:
return "Please provide a departure date, sir."
# Normalise cabin value
if cabin not in _CABIN_CODE:
cabin = "economy"
date = _parse_date(date_raw)
return_date = _parse_date(return_raw) if return_raw else None
if player:
player.write_log(f"[FlightFinder] {origin} → {destination} on {date}")
if speak:
speak(f"Searching flights from {origin} to {destination} on {date}, sir.")
print(
f"[FlightFinder] ▶️ {origin} → {destination} | {date}"
f"{' → ' + return_date if return_date else ''}"
f" | {cabin} | {passengers} pax"
)
try:
raw_text, page_url = _search_flights_browser(
origin, destination, date, return_date, passengers, cabin
)
if not raw_text:
return "Could not retrieve flight data, sir. The page may not have loaded."
if speak:
speak("Analysing the results now, sir.")
flights = _parse_flights_with_gemini(raw_text, origin, destination, date)
spoken = _format_spoken(flights, origin, destination, date)
if speak:
speak(spoken)
result = spoken
if save and flights:
report = _format_text_report(flights, origin, destination, date, return_date, page_url)
saved_path = _save_to_desktop(report, origin, destination)
result += f" Results saved to Desktop: {saved_path}"
return result
except Exception as e:
print(f"[FlightFinder] ❌ {e}")
return f"Flight search failed, sir: {e}"
+35 -821
View File
@@ -1,821 +1,35 @@
import asyncio
import threading
import json
import sys
import traceback
from pathlib import Path
import sounddevice as sd
from google import genai
from google.genai import types
from ui import JarvisUI
from memory.memory_manager import (
load_memory, update_memory, format_memory_for_prompt,
should_extract_memory, extract_memory
)
from actions.flight_finder import flight_finder
from actions.open_app import open_app
from actions.weather_report import weather_action
from actions.send_message import send_message
from actions.reminder import reminder
from actions.computer_settings import computer_settings
from actions.screen_processor import screen_process
from actions.youtube_video import youtube_video
from actions.cmd_control import cmd_control
from actions.desktop import desktop_control
from actions.browser_control import browser_control
from actions.file_controller import file_controller
from actions.code_helper import code_helper
from actions.dev_agent import dev_agent
from actions.web_search import web_search as web_search_action
from actions.computer_control import computer_control
from actions.game_updater import game_updater
def get_base_dir():
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
PROMPT_PATH = BASE_DIR / "core" / "prompt.txt"
LIVE_MODEL = "models/gemini-2.5-flash-native-audio-preview-12-2025"
CHANNELS = 1
SEND_SAMPLE_RATE = 16000
RECEIVE_SAMPLE_RATE = 24000
CHUNK_SIZE = 1024
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _load_system_prompt() -> str:
try:
return PROMPT_PATH.read_text(encoding="utf-8")
except Exception:
return (
"You are JARVIS, Tony Stark's AI assistant. "
"Be concise, direct, and always use the provided tools to complete tasks. "
"Never simulate or guess results — always call the appropriate tool."
)
# ── Hafıza ────────────────────────────────────────────────────────────────────
_last_memory_input = ""
def _update_memory_async(user_text: str, jarvis_text: str) -> None:
global _last_memory_input
user_text = (user_text or "").strip()
jarvis_text = (jarvis_text or "").strip()
if len(user_text) < 5 or user_text == _last_memory_input:
return
_last_memory_input = user_text
try:
api_key = _get_api_key()
if not should_extract_memory(user_text, jarvis_text, api_key):
return
data = extract_memory(user_text, jarvis_text, api_key)
if data:
update_memory(data)
print(f"[Memory] ✅ {list(data.keys())}")
except Exception as e:
if "429" not in str(e):
print(f"[Memory] ⚠️ {e}")
# ── Tool declarations ─────────────────────────────────────────────────────────
TOOL_DECLARATIONS = [
{
"name": "open_app",
"description": (
"Opens any application on the Windows computer. "
"Use this whenever the user asks to open, launch, or start any app, "
"website, or program. Always call this tool — never just say you opened it."
),
"parameters": {
"type": "OBJECT",
"properties": {
"app_name": {
"type": "STRING",
"description": "Exact name of the application (e.g. 'WhatsApp', 'Chrome', 'Spotify')"
}
},
"required": ["app_name"]
}
},
{
"name": "web_search",
"description": "Searches the web for any information.",
"parameters": {
"type": "OBJECT",
"properties": {
"query": {"type": "STRING", "description": "Search query"},
"mode": {"type": "STRING", "description": "search (default) or compare"},
"items": {"type": "ARRAY", "items": {"type": "STRING"}, "description": "Items to compare"},
"aspect": {"type": "STRING", "description": "price | specs | reviews"}
},
"required": ["query"]
}
},
{
"name": "weather_report",
"description": "Gets real-time weather information for a city.",
"parameters": {
"type": "OBJECT",
"properties": {
"city": {"type": "STRING", "description": "City name"}
},
"required": ["city"]
}
},
{
"name": "send_message",
"description": "Sends a text message via WhatsApp, Telegram, or other messaging platform.",
"parameters": {
"type": "OBJECT",
"properties": {
"receiver": {"type": "STRING", "description": "Recipient contact name"},
"message_text": {"type": "STRING", "description": "The message to send"},
"platform": {"type": "STRING", "description": "Platform: WhatsApp, Telegram, etc."}
},
"required": ["receiver", "message_text", "platform"]
}
},
{
"name": "reminder",
"description": "Sets a timed reminder using Windows Task Scheduler.",
"parameters": {
"type": "OBJECT",
"properties": {
"date": {"type": "STRING", "description": "Date in YYYY-MM-DD format"},
"time": {"type": "STRING", "description": "Time in HH:MM format (24h)"},
"message": {"type": "STRING", "description": "Reminder message text"}
},
"required": ["date", "time", "message"]
}
},
{
"name": "youtube_video",
"description": (
"Controls YouTube. Use for: playing videos, summarizing a video's content, "
"getting video info, or showing trending videos."
),
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "play | summarize | get_info | trending (default: play)"},
"query": {"type": "STRING", "description": "Search query for play action"},
"save": {"type": "BOOLEAN", "description": "Save summary to Notepad (summarize only)"},
"region": {"type": "STRING", "description": "Country code for trending e.g. TR, US"},
"url": {"type": "STRING", "description": "Video URL for get_info action"},
},
"required": []
}
},
{
"name": "screen_process",
"description": (
"Captures and analyzes the screen or webcam image. "
"MUST be called when user asks what is on screen, what you see, "
"analyze my screen, look at camera, etc. "
"You have NO visual ability without this tool. "
"After calling this tool, stay SILENT — the vision module speaks directly."
),
"parameters": {
"type": "OBJECT",
"properties": {
"angle": {"type": "STRING", "description": "'screen' to capture display, 'camera' for webcam. Default: 'screen'"},
"text": {"type": "STRING", "description": "The question or instruction about the captured image"}
},
"required": ["text"]
}
},
{
"name": "computer_settings",
"description": (
"Controls the computer: volume, brightness, window management, keyboard shortcuts, "
"typing text on screen, closing apps, fullscreen, dark mode, WiFi, restart, shutdown, "
"scrolling, tab management, zoom, screenshots, lock screen, refresh/reload page. "
"Use for ANY single computer control command. NEVER route to agent_task."
),
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "The action to perform"},
"description": {"type": "STRING", "description": "Natural language description of what to do"},
"value": {"type": "STRING", "description": "Optional value: volume level, text to type, etc."}
},
"required": []
}
},
{
"name": "browser_control",
"description": (
"Controls the web browser. Use for: opening websites, searching the web, "
"clicking elements, filling forms, scrolling, any web-based task."
),
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "go_to | search | click | type | scroll | fill_form | smart_click | smart_type | get_text | press | close"},
"url": {"type": "STRING", "description": "URL for go_to action"},
"query": {"type": "STRING", "description": "Search query for search action"},
"selector": {"type": "STRING", "description": "CSS selector for click/type"},
"text": {"type": "STRING", "description": "Text to click or type"},
"description": {"type": "STRING", "description": "Element description for smart_click/smart_type"},
"direction": {"type": "STRING", "description": "up or down for scroll"},
"key": {"type": "STRING", "description": "Key name for press action"},
"incognito": {"type": "BOOLEAN", "description": "Open in private/incognito mode"},
},
"required": ["action"]
}
},
{
"name": "file_controller",
"description": "Manages files and folders: list, create, delete, move, copy, rename, read, write, find, disk usage.",
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "list | create_file | create_folder | delete | move | copy | rename | read | write | find | largest | disk_usage | organize_desktop | info"},
"path": {"type": "STRING", "description": "File/folder path or shortcut: desktop, downloads, documents, home"},
"destination": {"type": "STRING", "description": "Destination path for move/copy"},
"new_name": {"type": "STRING", "description": "New name for rename"},
"content": {"type": "STRING", "description": "Content for create_file/write"},
"name": {"type": "STRING", "description": "File name to search for"},
"extension": {"type": "STRING", "description": "File extension to search (e.g. .pdf)"},
"count": {"type": "INTEGER", "description": "Number of results for largest"},
},
"required": ["action"]
}
},
{
"name": "cmd_control",
"description": (
"Runs CMD/terminal commands via natural language: disk space, processes, "
"system info, network, find files, or anything in the command line."
),
"parameters": {
"type": "OBJECT",
"properties": {
"task": {"type": "STRING", "description": "Natural language description of what to do"},
"visible": {"type": "BOOLEAN", "description": "Open visible CMD window. Default: true"},
"command": {"type": "STRING", "description": "Optional: exact command if already known"},
},
"required": ["task"]
}
},
{
"name": "desktop_control",
"description": "Controls the desktop: wallpaper, organize, clean, list, stats.",
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "wallpaper | wallpaper_url | organize | clean | list | stats | task"},
"path": {"type": "STRING", "description": "Image path for wallpaper"},
"url": {"type": "STRING", "description": "Image URL for wallpaper_url"},
"mode": {"type": "STRING", "description": "by_type or by_date for organize"},
"task": {"type": "STRING", "description": "Natural language desktop task"},
},
"required": ["action"]
}
},
{
"name": "code_helper",
"description": "Writes, edits, explains, runs, or builds code files.",
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "write | edit | explain | run | build | auto (default: auto)"},
"description": {"type": "STRING", "description": "What the code should do or what change to make"},
"language": {"type": "STRING", "description": "Programming language (default: python)"},
"output_path": {"type": "STRING", "description": "Where to save the file"},
"file_path": {"type": "STRING", "description": "Path to existing file for edit/explain/run/build"},
"code": {"type": "STRING", "description": "Raw code string for explain"},
"args": {"type": "STRING", "description": "CLI arguments for run/build"},
"timeout": {"type": "INTEGER", "description": "Execution timeout in seconds (default: 30)"},
},
"required": ["action"]
}
},
{
"name": "dev_agent",
"description": "Builds complete multi-file projects from scratch: plans, writes files, installs deps, opens VSCode, runs and fixes errors.",
"parameters": {
"type": "OBJECT",
"properties": {
"description": {"type": "STRING", "description": "What the project should do"},
"language": {"type": "STRING", "description": "Programming language (default: python)"},
"project_name": {"type": "STRING", "description": "Optional project folder name"},
"timeout": {"type": "INTEGER", "description": "Run timeout in seconds (default: 30)"},
},
"required": ["description"]
}
},
{
"name": "agent_task",
"description": (
"Executes complex multi-step tasks requiring multiple different tools. "
"Examples: 'research X and save to file', 'find and organize files'. "
"DO NOT use for single commands. NEVER use for Steam/Epic — use game_updater."
),
"parameters": {
"type": "OBJECT",
"properties": {
"goal": {"type": "STRING", "description": "Complete description of what to accomplish"},
"priority": {"type": "STRING", "description": "low | normal | high (default: normal)"}
},
"required": ["goal"]
}
},
{
"name": "computer_control",
"description": "Direct computer control: type, click, hotkeys, scroll, move mouse, screenshots, find elements on screen.",
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "type | smart_type | click | double_click | right_click | hotkey | press | scroll | move | copy | paste | screenshot | wait | clear_field | focus_window | screen_find | screen_click | random_data | user_data"},
"text": {"type": "STRING", "description": "Text to type or paste"},
"x": {"type": "INTEGER", "description": "X coordinate"},
"y": {"type": "INTEGER", "description": "Y coordinate"},
"keys": {"type": "STRING", "description": "Key combination e.g. 'ctrl+c'"},
"key": {"type": "STRING", "description": "Single key e.g. 'enter'"},
"direction": {"type": "STRING", "description": "up | down | left | right"},
"amount": {"type": "INTEGER", "description": "Scroll amount (default: 3)"},
"seconds": {"type": "NUMBER", "description": "Seconds to wait"},
"title": {"type": "STRING", "description": "Window title for focus_window"},
"description": {"type": "STRING", "description": "Element description for screen_find/screen_click"},
"type": {"type": "STRING", "description": "Data type for random_data"},
"field": {"type": "STRING", "description": "Field for user_data: name|email|city"},
"clear_first": {"type": "BOOLEAN", "description": "Clear field before typing (default: true)"},
"path": {"type": "STRING", "description": "Save path for screenshot"},
},
"required": ["action"]
}
},
{
"name": "game_updater",
"description": (
"THE ONLY tool for ANY Steam or Epic Games request. "
"Use for: installing, downloading, updating games, listing installed games, "
"checking download status, scheduling updates. "
"ALWAYS call directly for any Steam/Epic/game request. "
"NEVER use agent_task, browser_control, or web_search for Steam/Epic."
),
"parameters": {
"type": "OBJECT",
"properties": {
"action": {"type": "STRING", "description": "update | install | list | download_status | schedule | cancel_schedule | schedule_status (default: update)"},
"platform": {"type": "STRING", "description": "steam | epic | both (default: both)"},
"game_name": {"type": "STRING", "description": "Game name (partial match supported)"},
"app_id": {"type": "STRING", "description": "Steam AppID for install (optional)"},
"hour": {"type": "INTEGER", "description": "Hour for scheduled update 0-23 (default: 3)"},
"minute": {"type": "INTEGER", "description": "Minute for scheduled update 0-59 (default: 0)"},
"shutdown_when_done": {"type": "BOOLEAN", "description": "Shut down PC when download finishes"},
},
"required": []
}
},
{
"name": "flight_finder",
"description": "Searches Google Flights and speaks the best options.",
"parameters": {
"type": "OBJECT",
"properties": {
"origin": {"type": "STRING", "description": "Departure city or airport code"},
"destination": {"type": "STRING", "description": "Arrival city or airport code"},
"date": {"type": "STRING", "description": "Departure date (any format)"},
"return_date": {"type": "STRING", "description": "Return date for round trips"},
"passengers": {"type": "INTEGER", "description": "Number of passengers (default: 1)"},
"cabin": {"type": "STRING", "description": "economy | premium | business | first"},
"save": {"type": "BOOLEAN", "description": "Save results to Notepad"},
},
"required": ["origin", "destination", "date"]
}
},
{
"name": "save_memory",
"description": (
"Save an important personal fact about the user to long-term memory. "
"Call this silently whenever the user reveals something worth remembering: "
"name, age, city, job, preferences, hobbies, relationships, projects, or future plans. "
"Do NOT call for: weather, reminders, searches, or one-time commands. "
"Do NOT announce that you are saving — just call it silently. "
"Values must be in English regardless of the conversation language."
),
"parameters": {
"type": "OBJECT",
"properties": {
"category": {
"type": "STRING",
"description": (
"identity — name, age, birthday, city, job, language, nationality | "
"preferences — favorite food/color/music/film/game/sport, hobbies | "
"projects — active projects, goals, things being built | "
"relationships — friends, family, partner, colleagues | "
"wishes — future plans, things to buy, travel dreams | "
"notes — habits, schedule, anything else worth remembering"
)
},
"key": {"type": "STRING", "description": "Short snake_case key (e.g. name, favorite_food, sister_name)"},
"value": {"type": "STRING", "description": "Concise value in English (e.g. Fatih, pizza, older sister)"},
},
"required": ["category", "key", "value"]
}
},
]
class JarvisLive:
def __init__(self, ui: JarvisUI):
self.ui = ui
self.session = None
self.audio_in_queue = None
self.out_queue = None
self._loop = None
self._is_speaking = False
self._speaking_lock = threading.Lock()
self.ui.on_text_command = self._on_text_command
def _on_text_command(self, text: str):
if not self._loop or not self.session:
return
asyncio.run_coroutine_threadsafe(
self.session.send_client_content(
turns={"parts": [{"text": text}]},
turn_complete=True
),
self._loop
)
def set_speaking(self, value: bool):
with self._speaking_lock:
self._is_speaking = value
if value:
self.ui.set_state("SPEAKING")
elif not self.ui.muted:
self.ui.set_state("LISTENING")
def speak(self, text: str):
if not self._loop or not self.session:
return
asyncio.run_coroutine_threadsafe(
self.session.send_client_content(
turns={"parts": [{"text": text}]},
turn_complete=True
),
self._loop
)
def speak_error(self, tool_name: str, error: str):
short = str(error)[:120]
self.ui.write_log(f"ERR: {tool_name} — {short}")
self.speak(f"Sir, {tool_name} encountered an error. {short}")
def _build_config(self) -> types.LiveConnectConfig:
from datetime import datetime
memory = load_memory()
mem_str = format_memory_for_prompt(memory)
sys_prompt = _load_system_prompt()
now = datetime.now()
time_str = now.strftime("%A, %B %d, %Y — %I:%M %p")
time_ctx = (
f"[CURRENT DATE & TIME]\n"
f"Right now it is: {time_str}\n"
f"Use this to calculate exact times for reminders.\n\n"
)
parts = [time_ctx]
if mem_str:
parts.append(mem_str)
parts.append(sys_prompt)
return types.LiveConnectConfig(
response_modalities=["AUDIO"],
output_audio_transcription={},
input_audio_transcription={},
system_instruction="\n".join(parts),
tools=[{"function_declarations": TOOL_DECLARATIONS}],
session_resumption=types.SessionResumptionConfig(),
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Charon"
)
)
),
)
async def _execute_tool(self, fc) -> types.FunctionResponse:
name = fc.name
args = dict(fc.args or {})
print(f"[JARVIS] 🔧 {name} {args}")
self.ui.set_state("THINKING")
# ── save_memory: sessiz, hızlı, Gemini'ye bildirim yok ───────────────
if name == "save_memory":
category = args.get("category", "notes")
key = args.get("key", "")
value = args.get("value", "")
if key and value:
update_memory({category: {key: {"value": value}}})
print(f"[Memory] 💾 save_memory: {category}/{key} = {value}")
if not self.ui.muted:
self.ui.set_state("LISTENING")
return types.FunctionResponse(
id=fc.id, name=name,
response={"result": "ok", "silent": True}
)
loop = asyncio.get_event_loop()
result = "Done."
try:
if name == "open_app":
r = await loop.run_in_executor(None, lambda: open_app(parameters=args, response=None, player=self.ui))
result = r or f"Opened {args.get('app_name')}."
elif name == "weather_report":
r = await loop.run_in_executor(None, lambda: weather_action(parameters=args, player=self.ui))
result = r or "Weather delivered."
elif name == "browser_control":
r = await loop.run_in_executor(None, lambda: browser_control(parameters=args, player=self.ui))
result = r or "Done."
elif name == "file_controller":
r = await loop.run_in_executor(None, lambda: file_controller(parameters=args, player=self.ui))
result = r or "Done."
elif name == "send_message":
r = await loop.run_in_executor(None, lambda: send_message(parameters=args, response=None, player=self.ui, session_memory=None))
result = r or f"Message sent to {args.get('receiver')}."
elif name == "reminder":
r = await loop.run_in_executor(None, lambda: reminder(parameters=args, response=None, player=self.ui))
result = r or "Reminder set."
elif name == "youtube_video":
r = await loop.run_in_executor(None, lambda: youtube_video(parameters=args, response=None, player=self.ui))
result = r or "Done."
elif name == "screen_process":
threading.Thread(
target=screen_process,
kwargs={"parameters": args, "response": None,
"player": self.ui, "session_memory": None},
daemon=True
).start()
result = "Vision module activated. Stay completely silent — vision module will speak directly."
elif name == "computer_settings":
r = await loop.run_in_executor(None, lambda: computer_settings(parameters=args, response=None, player=self.ui))
result = r or "Done."
elif name == "cmd_control":
r = await loop.run_in_executor(None, lambda: cmd_control(parameters=args, player=self.ui))
result = r or "Done."
elif name == "desktop_control":
r = await loop.run_in_executor(None, lambda: desktop_control(parameters=args, player=self.ui))
result = r or "Done."
elif name == "code_helper":
r = await loop.run_in_executor(None, lambda: code_helper(parameters=args, player=self.ui, speak=self.speak))
result = r or "Done."
elif name == "dev_agent":
r = await loop.run_in_executor(None, lambda: dev_agent(parameters=args, player=self.ui, speak=self.speak))
result = r or "Done."
elif name == "agent_task":
from agent.task_queue import get_queue, TaskPriority
priority_map = {"low": TaskPriority.LOW, "normal": TaskPriority.NORMAL, "high": TaskPriority.HIGH}
priority = priority_map.get(args.get("priority", "normal").lower(), TaskPriority.NORMAL)
task_id = get_queue().submit(goal=args.get("goal", ""), priority=priority, speak=self.speak)
result = f"Task started (ID: {task_id})."
elif name == "web_search":
r = await loop.run_in_executor(None, lambda: web_search_action(parameters=args, player=self.ui))
result = r or "Done."
elif name == "computer_control":
r = await loop.run_in_executor(None, lambda: computer_control(parameters=args, player=self.ui))
result = r or "Done."
elif name == "game_updater":
r = await loop.run_in_executor(None, lambda: game_updater(parameters=args, player=self.ui, speak=self.speak))
result = r or "Done."
elif name == "flight_finder":
r = await loop.run_in_executor(None, lambda: flight_finder(parameters=args, player=self.ui))
result = r or "Done."
else:
result = f"Unknown tool: {name}"
except Exception as e:
result = f"Tool '{name}' failed: {e}"
traceback.print_exc()
self.speak_error(name, e)
if not self.ui.muted:
self.ui.set_state("LISTENING")
print(f"[JARVIS] 📤 {name} → {str(result)[:80]}")
# ── Result: tek cümle söyle, dur ──────────────────────────────────────
return types.FunctionResponse(
id=fc.id, name=name,
response={"result": result}
)
async def _send_realtime(self):
while True:
msg = await self.out_queue.get()
await self.session.send_realtime_input(media=msg)
async def _listen_audio(self):
print("[JARVIS] 🎤 Mic started")
loop = asyncio.get_event_loop()
def callback(indata, frames, time_info, status):
with self._speaking_lock:
jarvis_speaking = self._is_speaking
if not jarvis_speaking and not self.ui.muted:
data = indata.tobytes()
loop.call_soon_threadsafe(
self.out_queue.put_nowait,
{"data": data, "mime_type": "audio/pcm"}
)
try:
with sd.InputStream(
samplerate=SEND_SAMPLE_RATE,
channels=CHANNELS,
dtype="int16",
blocksize=CHUNK_SIZE,
callback=callback,
):
print("[JARVIS] 🎤 Mic stream open")
while True:
await asyncio.sleep(0.1)
except Exception as e:
print(f"[JARVIS] ❌ Mic: {e}")
raise
async def _receive_audio(self):
print("[JARVIS] 👂 Recv started")
out_buf, in_buf = [], []
try:
while True:
async for response in self.session.receive():
if response.data:
self.audio_in_queue.put_nowait(response.data)
if response.server_content:
sc = response.server_content
if sc.output_transcription and sc.output_transcription.text:
self.set_speaking(True)
txt = sc.output_transcription.text.strip()
if txt:
out_buf.append(txt)
if sc.input_transcription and sc.input_transcription.text:
txt = sc.input_transcription.text.strip()
if txt:
in_buf.append(txt)
if sc.turn_complete:
self.set_speaking(False)
full_in = " ".join(in_buf).strip()
if full_in:
self.ui.write_log(f"You: {full_in}")
in_buf = []
full_out = " ".join(out_buf).strip()
if full_out:
self.ui.write_log(f"Jarvis: {full_out}")
out_buf = []
if full_in and len(full_in) > 5:
threading.Thread(
target=_update_memory_async,
args=(full_in, full_out),
daemon=True
).start()
if response.tool_call:
fn_responses = []
for fc in response.tool_call.function_calls:
print(f"[JARVIS] 📞 {fc.name}")
fr = await self._execute_tool(fc)
fn_responses.append(fr)
await self.session.send_tool_response(
function_responses=fn_responses
)
# ── Boş turn YOK — bu "Anladım." sorununu yaratıyordu ──
except Exception as e:
print(f"[JARVIS] ❌ Recv: {e}")
traceback.print_exc()
raise
async def _play_audio(self):
print("[JARVIS] 🔊 Play started")
loop = asyncio.get_event_loop()
# Sürekli açık output stream — PyAudio'daki stream.write() davranışıyla aynı
stream = sd.RawOutputStream(
samplerate=RECEIVE_SAMPLE_RATE,
channels=CHANNELS,
dtype="int16",
blocksize=CHUNK_SIZE,
)
stream.start()
try:
while True:
chunk = await self.audio_in_queue.get()
self.set_speaking(True)
await asyncio.to_thread(stream.write, chunk)
except Exception as e:
print(f"[JARVIS] ❌ Play: {e}")
raise
finally:
self.set_speaking(False)
stream.stop()
stream.close()
async def run(self):
client = genai.Client(
api_key=_get_api_key(),
http_options={"api_version": "v1beta"}
)
while True:
try:
print("[JARVIS] 🔌 Connecting...")
self.ui.set_state("THINKING")
config = self._build_config()
async with (
client.aio.live.connect(model=LIVE_MODEL, config=config) as session,
asyncio.TaskGroup() as tg,
):
self.session = session
self._loop = asyncio.get_event_loop()
self.audio_in_queue = asyncio.Queue()
self.out_queue = asyncio.Queue(maxsize=10)
print("[JARVIS] ✅ Connected.")
self.ui.set_state("LISTENING")
self.ui.write_log("SYS: JARVIS online.")
tg.create_task(self._send_realtime())
tg.create_task(self._listen_audio())
tg.create_task(self._receive_audio())
tg.create_task(self._play_audio())
except Exception as e:
print(f"[JARVIS] ⚠️ {e}")
traceback.print_exc()
self.set_speaking(False)
self.ui.set_state("THINKING")
print("[JARVIS] 🔄 Reconnecting in 3s...")
await asyncio.sleep(3)
def main():
ui = JarvisUI("face.png")
def runner():
ui.wait_for_api_key()
jarvis = JarvisLive(ui)
try:
asyncio.run(jarvis.run())
except KeyboardInterrupt:
print("\n🔴 Shutting down...")
threading.Thread(target=runner, daemon=True).start()
ui.root.mainloop()
if __name__ == "__main__":
main()
You are JARVIS, a sharp and efficient AI assistant. Calm, direct, professional.
RULES:
- Always use the correct tool. Never simulate or guess results.
- Call each tool EXACTLY ONCE. Never retry a successful action.
- Do NOT say "I'm doing X" before calling a tool. Just call it, then report the result briefly.
- Do NOT repeat yourself. Say something once and stop.
- Keep responses to 1-2 sentences max unless reporting data.
- Never ask unnecessary questions. Make a reasonable assumption and proceed.
- Speak and take action according to user's information, for better experience.
- If a action tool called, tell that you're doing this thing and then call the tool, don't speak after the action's tool.
- Attain important things in the memory for a better user experience.
- game_updater → ANY Steam or Epic request: install, download, update, list.
Call DIRECTLY — never web_search first, never agent_task.
- For slow tools (web_search, screen_process, browser_control, flight_finder,
game_updater, cmd_control, dev_agent, code_helper, agent_task): say TWO short
sentence before calling the tool, then stay silent after — result is already reported.
- For screen_process: say something curious before calling, then stay completely
silent — the vision module speaks directly.
- Only call shutdown_jarvis if the user clearly intends to end the assistant session.
Do not call it for casual goodbyes unless they imply stopping the assistant.
LANGUAGE:
- Respond in the same language the user spoke.
- Always extract tool parameters in English.
- Example: "İstanbul hava durumu?" → weather_report city:"Istanbul", reply in Turkish.
TOOL SELECTION:
- computer_settings → volume, brightness, screen, refresh, scroll, minimize, maximize,
close, screenshot, lock, restart, shutdown, wifi, zoom, tabs, keyboard shortcuts.
Use this for ANY single computer control command. NEVER route these to agent_task.
- agent_task → ONLY for complex multi-step goals that require planning across
multiple tools (e.g. "research X and save to file", "create a project").
- Simple rule: if it can be done in ONE action → computer_settings.
If it needs 3+ steps across different tools → agent_task.
+817
View File
@@ -0,0 +1,817 @@
import os
import re
import sys
import json
import time
import subprocess
import threading
import winreg
from pathlib import Path
from datetime import datetime
def _find_steam_path() -> Path | None:
registry_keys = [
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Valve\Steam"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Valve\Steam"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Valve\Steam"),
]
for hive, key_path in registry_keys:
try:
key = winreg.OpenKey(hive, key_path)
val, _ = winreg.QueryValueEx(key, "InstallPath")
winreg.CloseKey(key)
p = Path(val)
if p.exists() and (p / "steam.exe").exists():
return p
except Exception:
continue
for p in [
Path(os.environ.get("ProgramFiles(x86)", "")) / "Steam",
Path(os.environ.get("ProgramFiles", "")) / "Steam",
Path("C:/Steam"), Path("D:/Steam"), Path("E:/Steam"), Path("F:/Steam"),
]:
if p.exists() and (p / "steam.exe").exists():
return p
return None
def _find_epic_path() -> Path | None:
registry_keys = [
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\EpicGames\EpicGamesLauncher"),
(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\EpicGames\EpicGamesLauncher"),
(winreg.HKEY_CURRENT_USER, r"SOFTWARE\EpicGames\EpicGamesLauncher"),
]
for hive, key_path in registry_keys:
try:
key = winreg.OpenKey(hive, key_path)
val, _ = winreg.QueryValueEx(key, "AppDataPath")
winreg.CloseKey(key)
exe = Path(val) / "Binaries" / "Win64" / "EpicGamesLauncher.exe"
if exe.exists():
return exe.parent
except Exception:
continue
for p in [
Path(os.environ.get("ProgramFiles(x86)", "")) / "Epic Games" / "Launcher" / "Portal" / "Binaries" / "Win64",
Path(os.environ.get("ProgramFiles", "")) / "Epic Games" / "Launcher" / "Portal" / "Binaries" / "Win64",
Path(os.environ.get("LOCALAPPDATA", "")) / "EpicGamesLauncher" / "Portal" / "Binaries" / "Win64",
]:
if p.exists() and (p / "EpicGamesLauncher.exe").exists():
return p
return None
def _get_steam_libraries(steam_path: Path) -> list[Path]:
libraries = [steam_path / "steamapps"]
vdf_path = steam_path / "steamapps" / "libraryfolders.vdf"
if not vdf_path.exists():
return libraries
try:
content = vdf_path.read_text(encoding="utf-8", errors="ignore")
for raw_path in re.findall(r'"path"\s+"([^"]+)"', content):
lib = Path(raw_path.replace("\\\\", "/")) / "steamapps"
if lib.exists() and lib not in libraries:
libraries.append(lib)
except Exception:
pass
return libraries
def _get_steam_games(steam_path: Path) -> list[dict]:
games = []
for lib in _get_steam_libraries(steam_path):
for acf in lib.glob("appmanifest_*.acf"):
try:
content = acf.read_text(encoding="utf-8", errors="ignore")
app_id = re.search(r'"appid"\s+"(\d+)"', content)
name = re.search(r'"name"\s+"([^"]+)"', content)
state = re.search(r'"StateFlags"\s+"(\d+)"', content)
size = re.search(r'"SizeOnDisk"\s+"(\d+)"', content)
if app_id and name:
games.append({
"id": app_id.group(1),
"name": name.group(1),
"state": int(state.group(1)) if state else 0,
"size": int(size.group(1)) if size else 0,
"lib": str(lib),
})
except Exception:
continue
return games
def _is_steam_running() -> bool:
try:
out = subprocess.run(["tasklist", "/FI", "IMAGENAME eq steam.exe"],
capture_output=True, text=True).stdout
return "steam.exe" in out.lower()
except Exception:
return False
def _get_steam_window_rect() -> tuple[int, int, int, int] | None:
try:
import pygetwindow as gw
for w in gw.getAllWindows():
if "steam" in w.title.lower() and w.width > 200 and w.visible:
return w.left, w.top, w.width, w.height
except Exception:
pass
return None
def _click_first_profile_by_screenshot() -> bool:
try:
import pyautogui
import numpy as np
time.sleep(1.5)
win = _get_steam_window_rect()
if not win:
print("[GameUpdater] ⚠️ Steam window not found")
return False
wx, wy, ww, wh = win
screenshot = pyautogui.screenshot(region=(wx, wy, ww, wh))
img = np.array(screenshot)
h, w = img.shape[:2]
search_y1 = h // 3
search_y2 = h * 3 // 4
search_x1 = w // 5
search_x2 = w * 4 // 5
region = img[search_y1:search_y2, search_x1:search_x2]
r = region[:, :, 0].astype(int)
g = region[:, :, 1].astype(int)
b = region[:, :, 2].astype(int)
max_c = np.maximum(np.maximum(r, g), b)
min_c = np.minimum(np.minimum(r, g), b)
sat = max_c - min_c
colorful = (max_c > 60) & (sat > 40)
if not colorful.any():
print("[GameUpdater] ⚠️ No colorful avatar region found — clicking estimated center-left")
pyautogui.click(wx + ww // 2 - ww // 6, wy + wh // 2)
return True
cols = np.where(colorful.any(axis=0))[0]
rows = np.where(colorful.any(axis=1))[0]
if len(cols) == 0 or len(rows) == 0:
return False
avatar_w = min(90, (region.shape[1]) // 4)
first_col = int(cols[0])
block_cols = cols[cols < first_col + avatar_w]
center_x_local = int(block_cols.mean())
center_y_local = int(rows.mean())
abs_x = wx + search_x1 + center_x_local
abs_y = wy + search_y1 + center_y_local
print(f"[GameUpdater] 🎯 First profile avatar at ({abs_x}, {abs_y}) — clicking")
pyautogui.click(abs_x, abs_y)
return True
except ImportError as e:
print(f"[GameUpdater] ⚠️ Missing library: {e}")
return False
except Exception as e:
print(f"[GameUpdater] ⚠️ Screenshot profile detection failed: {e}")
return False
def _handle_steam_profile_selection() -> bool:
print("[GameUpdater] 🔍 Checking for 'Who's playing?' dialog...")
win = _get_steam_window_rect()
if not win:
return False
wx, wy, ww, wh = win
try:
import pyautogui
import numpy as np
screenshot = pyautogui.screenshot(region=(wx, wy, ww, wh))
img = np.array(screenshot)
is_small_window = ww < 900 and wh < 700
top_region = img[:wh // 3, :, :]
white_pixels = int(np.sum(
(top_region[:, :, 0] > 200) &
(top_region[:, :, 1] > 200) &
(top_region[:, :, 2] > 200)
))
has_white_text = white_pixels > 100
if not is_small_window and not has_white_text:
print("[GameUpdater] ℹ️ No profile dialog detected — Steam already logged in")
return False
except ImportError:
pass
except Exception:
pass
print("[GameUpdater] 👤 Profile selection detected — clicking first profile")
return _click_first_profile_by_screenshot()
def _ensure_steam_running(steam_path: Path) -> bool:
if _is_steam_running():
return True
steam_exe = steam_path / "steam.exe"
if not steam_exe.exists():
print("[GameUpdater] ❌ steam.exe not found")
return False
print("[GameUpdater] 🚀 Starting Steam...")
subprocess.Popen([str(steam_exe)])
for _ in range(20):
time.sleep(1)
if _is_steam_running():
print("[GameUpdater] ✅ Steam running")
time.sleep(4)
_handle_steam_profile_selection()
time.sleep(2)
return True
print("[GameUpdater] ⚠️ Steam did not start in time")
return False
def _update_steam_games(steam_path: Path, game_name: str = None) -> str:
if not _ensure_steam_running(steam_path):
return "Could not start Steam."
steam_exe = steam_path / "steam.exe"
games = _get_steam_games(steam_path)
if not games:
return "No Steam games found."
if game_name:
name_lower = game_name.lower()
matched = [g for g in games if name_lower in g["name"].lower()]
if not matched:
available = ", ".join(g["name"] for g in games[:5])
return f"Game '{game_name}' not found. Installed: {available}..."
targets = matched
else:
targets = games
already_updated, already_running, update_started, errors = [], [], [], []
for game in targets:
state = game["state"]
name = game["name"]
if state == 4:
already_updated.append(name)
elif state == 1026:
already_running.append(name)
else:
try:
subprocess.Popen([str(steam_exe), f"steam://update/{game['id']}"])
update_started.append(name)
time.sleep(0.3)
except Exception as e:
errors.append(f"{name}: {e}")
parts = []
if update_started:
names = ", ".join(update_started[:3])
suffix = f" and {len(update_started) - 3} more" if len(update_started) > 3 else ""
parts.append(f"Update started for: {names}{suffix}.")
if already_running:
parts.append(f"Already updating: {', '.join(already_running)}.")
if already_updated:
parts.append(
f"{already_updated[0]} is already up to date."
if game_name else
f"{len(already_updated)} game(s) already up to date."
)
if errors:
parts.append(f"Errors: {'; '.join(errors)}.")
return " ".join(parts) if parts else "No games to update."
_KNOWN_APPIDS: dict[str, tuple[str, str]] = {
"pubg": ("578080", "PUBG: Battlegrounds"),
"pubg battlegrounds": ("578080", "PUBG: Battlegrounds"),
"pubg: battlegrounds": ("578080", "PUBG: Battlegrounds"),
"battlegrounds": ("578080", "PUBG: Battlegrounds"),
"gta5": ("271590", "Grand Theft Auto V"),
"gta v": ("271590", "Grand Theft Auto V"),
"grand theft auto v": ("271590", "Grand Theft Auto V"),
"cs2": ("730", "Counter-Strike 2"),
"csgo": ("730", "Counter-Strike 2"),
"counter-strike 2": ("730", "Counter-Strike 2"),
"counter strike 2": ("730", "Counter-Strike 2"),
"dota2": ("570", "Dota 2"),
"dota 2": ("570", "Dota 2"),
"rust": ("252490", "Rust"),
"valheim": ("892970", "Valheim"),
"cyberpunk": ("1091500", "Cyberpunk 2077"),
"cyberpunk 2077": ("1091500", "Cyberpunk 2077"),
"elden ring": ("1245620", "ELDEN RING"),
"minecraft": ("1672970", "Minecraft Launcher"),
"apex legends": ("1172470", "Apex Legends"),
"apex": ("1172470", "Apex Legends"),
"fortnite": ("1517990", "Fortnite"),
"goose goose duck": ("1568590", "Goose Goose Duck"),
"among us": ("945360", "Among Us"),
"fall guys": ("1097150", "Fall Guys"),
"rocket league": ("252950", "Rocket League"),
"warframe": ("230410", "Warframe"),
"destiny 2": ("1085660", "Destiny 2"),
"team fortress 2": ("440", "Team Fortress 2"),
"tf2": ("440", "Team Fortress 2"),
"left 4 dead 2": ("550", "Left 4 Dead 2"),
"l4d2": ("550", "Left 4 Dead 2"),
"paladins": ("444090", "Paladins"),
"smite": ("386360", "SMITE"),
"war thunder": ("236390", "War Thunder"),
"world of warships": ("552990", "World of Warships"),
"path of exile": ("238960", "Path of Exile"),
"poe": ("238960", "Path of Exile"),
"lost ark": ("1599340", "Lost Ark"),
"new world": ("1063730", "New World: Aeternum"),
}
def _search_steam_appid(game_name: str) -> tuple[str | None, str | None]:
name_lower = game_name.lower().strip()
steam_path = _find_steam_path()
if steam_path:
for g in _get_steam_games(steam_path):
if name_lower in g["name"].lower():
return g["id"], g["name"]
if name_lower in _KNOWN_APPIDS:
app_id, canonical = _KNOWN_APPIDS[name_lower]
print(f"[GameUpdater] 📖 Known: {canonical} ({app_id})")
return app_id, canonical
for key, (app_id, canonical) in _KNOWN_APPIDS.items():
if name_lower in key or key in name_lower:
print(f"[GameUpdater] 📖 Known partial: {canonical} ({app_id})")
return app_id, canonical
try:
import urllib.request, urllib.parse
query = urllib.parse.quote(game_name)
url = f"https://store.steampowered.com/api/storesearch/?term={query}&l=english&cc=US"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=6) as resp:
items = json.loads(resp.read().decode()).get("items", [])
if items:
best = items[0]
print(f"[GameUpdater] 🌐 Store API: {best['name']} ({best['id']})")
return str(best["id"]), best["name"]
except Exception as e:
print(f"[GameUpdater] ⚠️ AppID search failed: {e}")
return None, None
def _find_best_drive() -> dict | None:
import shutil, string
drives = []
for letter in string.ascii_uppercase:
drive_path = f"{letter}:\\"
if os.path.exists(drive_path):
try:
gb = shutil.disk_usage(drive_path).free / (1024 ** 3)
if gb > 0:
drives.append({"letter": letter, "path": drive_path, "free_gb": gb})
except Exception:
continue
return max(drives, key=lambda d: d["free_gb"]) if drives else None
def _select_drive_in_dialog(dialog, drive_letter: str) -> bool:
target = drive_letter.upper()
for control_type in ("ListItem", "RadioButton"):
try:
for ctrl in dialog.descendants(control_type=control_type):
if target in ctrl.window_text().upper():
ctrl.click_input()
print(f"[GameUpdater] ✅ Drive via {control_type}: {ctrl.window_text()}")
return True
except Exception:
continue
try:
for combo in dialog.descendants(control_type="ComboBox"):
try:
combo.expand()
time.sleep(0.15)
for idx, txt in enumerate(combo.texts()):
if target in txt.upper():
combo.select(idx)
return True
combo.collapse()
except Exception:
continue
except Exception:
pass
try:
for ctrl in dialog.descendants():
txt = ctrl.window_text().upper()
if f"{target}:" in txt and len(txt) < 80:
ctrl.click_input()
return True
except Exception:
pass
return False
def _click_button(window, keywords: list[str]) -> bool:
try:
for btn in window.descendants(control_type="Button"):
try:
txt = btn.window_text().lower().strip()
if txt in keywords or any(kw in txt for kw in keywords):
btn.click_input()
return True
except Exception:
continue
except Exception:
pass
return False
def _handle_install_dialog(game_name: str) -> str:
best_drive = _find_best_drive()
if not best_drive:
return f"Install dialog opened for '{game_name}'. Could not detect drives."
drive_letter = best_drive["letter"]
drive_label = f"{drive_letter}:"
print(f"[GameUpdater] 🏆 Target drive: {drive_label} ({best_drive['free_gb']:.1f} GB free)")
try:
from pywinauto import Application, findwindows
dialog = None
for _ in range(40):
time.sleep(0.5)
try:
for hwnd in findwindows.find_windows(title_re=r"(?i)(install|yükle|steam)", visible_only=True):
try:
app = Application(backend="uia").connect(handle=hwnd)
win = app.window(handle=hwnd)
rect = win.rectangle()
if win.is_visible() and rect.width() > 300 and rect.height() > 200:
all_text = " ".join(c.window_text() for c in win.descendants() if c.window_text()).upper()
if any(x in all_text for x in ("C:", "D:", "E:", "F:", "INSTALL", "YÜKLE")):
dialog = win
break
except Exception:
continue
except Exception:
pass
if dialog:
break
if not dialog:
raise RuntimeError("Dialog not found")
dialog.set_focus()
time.sleep(0.4)
drive_selected = _select_drive_in_dialog(dialog, drive_letter)
install_clicked = _click_button(dialog, ["install", "yükle", "next", "ileri", "ok", "tamam"])
if install_clicked:
suffix = f"Selected {drive_label} and" if drive_selected else "Default drive used, but"
return f"{suffix} clicked Install for '{game_name}'."
return f"Please click Install manually in Steam for '{game_name}'."
except ImportError:
return _handle_install_dialog_pyautogui(game_name, best_drive)
except Exception as e:
print(f"[GameUpdater] ⚠️ pywinauto failed: {e}")
return _handle_install_dialog_pyautogui(game_name, best_drive)
def _handle_install_dialog_pyautogui(game_name: str, best_drive: dict) -> str:
try:
import pyautogui
import pygetwindow as gw
except ImportError:
return f"Install dialog opened for '{game_name}'. Please select '{best_drive['letter']}:' and click Install manually."
pyautogui.FAILSAFE = False
drive_label = f"{best_drive['letter']}:"
install_win = None
for _ in range(30):
time.sleep(0.5)
for w in gw.getAllWindows():
if ("install" in w.title.lower() or "steam" in w.title.lower()) and w.width > 300 and w.visible:
install_win = w
break
if install_win:
break
if not install_win:
return f"Please select '{drive_label}' and click Install in Steam for '{game_name}'."
try:
install_win.activate()
time.sleep(0.4)
except Exception:
pass
wx, wy = install_win.left, install_win.top
ww, wh = install_win.width, install_win.height
pyautogui.click(wx + int(ww * 0.35), wy + int(wh * 0.45))
time.sleep(0.2)
pyautogui.typewrite(best_drive["letter"], interval=0.05)
time.sleep(0.2)
pyautogui.click(wx + int(ww * 0.72), wy + int(wh * 0.88))
return f"Attempted drive {drive_label} selection and Install click for '{game_name}'."
def _install_steam_game(steam_path: Path, game_name: str = None, app_id: str = None) -> str:
if not _ensure_steam_running(steam_path):
return "Could not start Steam."
steam_exe = steam_path / "steam.exe"
installed_games = _get_steam_games(steam_path)
already = None
if app_id:
already = next((g for g in installed_games if g["id"] == str(app_id)), None)
elif game_name:
name_lower = game_name.lower()
already = next((g for g in installed_games if name_lower in g["name"].lower()), None)
else:
return "Please specify a game name or AppID."
if already:
state = already["state"]
name = already["name"]
if state == 4:
return f"'{name}' is already installed and up to date."
if state == 1026:
return f"'{name}' is currently downloading or updating."
if state in (6, 516):
subprocess.Popen([str(steam_exe), f"steam://update/{already['id']}"])
return f"'{name}' has a pending update. Update started."
return f"'{name}' is already installed."
if not app_id and game_name:
found_id, found_name = _search_steam_appid(game_name)
if not found_id:
return f"Could not find '{game_name}' on Steam. Try providing the AppID directly."
app_id = found_id
game_name = found_name or game_name
print(f"[GameUpdater] 🔍 Installing: {game_name} (AppID: {app_id})")
try:
subprocess.Popen([str(steam_exe), f"steam://install/{app_id}"])
threading.Thread(target=_handle_install_dialog, args=(game_name or str(app_id),), daemon=True).start()
return f"Install started for '{game_name}'. Steam will open the download dialog."
except Exception as e:
return f"Install failed: {e}"
def _get_download_status(steam_path: Path) -> str:
games = _get_steam_games(steam_path)
active = [g for g in games if g["state"] == 1026]
pending = [g for g in games if g["state"] in (6, 516)]
lines = []
if active:
lines.append(f"Downloading: {', '.join(g['name'] for g in active)}.")
if pending:
names = ", ".join(g["name"] for g in pending[:5])
suffix = f" and {len(pending) - 5} more" if len(pending) > 5 else ""
lines.append(f"Pending updates: {names}{suffix}.")
return " ".join(lines) if lines else "No active downloads or pending updates."
def _watch_and_shutdown(steam_path: Path, speak=None, check_interval: int = 30, timeout_hours: int = 12):
print("[GameUpdater] 👁️ Watching downloads for shutdown...")
deadline = time.time() + timeout_hours * 3600
for _ in range(24):
time.sleep(5)
active = [g for g in _get_steam_games(steam_path) if g["state"] == 1026]
if active:
names = ", ".join(g["name"] for g in active)
if speak: speak(f"Download started for {names}. I'll shut down when done.")
break
else:
return
while time.time() < deadline:
time.sleep(check_interval)
if not any(g["state"] == 1026 for g in _get_steam_games(steam_path)):
if speak: speak("Download complete. Shutting down now.")
time.sleep(5)
subprocess.run(["shutdown", "/s", "/t", "10"])
return
if speak: speak("Download taking too long. Cancelling auto-shutdown.")
def _get_epic_games() -> list[dict]:
manifests_path = (Path(os.environ.get("PROGRAMDATA", "C:/ProgramData"))
/ "Epic" / "EpicGamesLauncher" / "Data" / "Manifests")
if not manifests_path.exists():
return []
games = []
for item_file in manifests_path.glob("*.item"):
try:
data = json.loads(item_file.read_text(encoding="utf-8"))
name = data.get("DisplayName") or data.get("AppName", "")
if name:
games.append({"id": data.get("AppName", ""), "name": name})
except Exception:
continue
return games
def _is_epic_running() -> bool:
try:
return "epicgameslauncher.exe" in subprocess.run(
["tasklist", "/FI", "IMAGENAME eq EpicGamesLauncher.exe"],
capture_output=True, text=True
).stdout.lower()
except Exception:
return False
def _update_epic_games(epic_path: Path, game_name: str = None) -> str:
epic_exe = epic_path / "EpicGamesLauncher.exe"
if not epic_exe.exists():
return "Epic Games Launcher not found."
games = _get_epic_games()
if game_name:
name_lower = game_name.lower()
matched = [g for g in games if name_lower in g["name"].lower()]
if not matched:
return f"'{game_name}' not found in Epic."
try:
subprocess.Popen([str(epic_exe), f"com.epicgames.launcher://apps/{matched[0]['id']}?action=launch&silent=true"])
return f"Opened Epic for '{matched[0]['name']}'."
except Exception as e:
return f"Epic update failed: {e}"
else:
try:
if _is_epic_running():
for g in games[:10]:
subprocess.Popen([str(epic_exe), f"com.epicgames.launcher://apps/{g['id']}?action=launch&silent=true"])
time.sleep(0.5)
return f"Triggered update check for {len(games)} Epic game(s)."
else:
subprocess.Popen([str(epic_exe)])
return f"Epic Games Launcher opened. {len(games)} game(s) will be checked."
except Exception as e:
return f"Epic update failed: {e}"
def _schedule_daily_update(hour: int = 3, minute: int = 0) -> str:
task_name = "JARVIS_GameUpdater"
script_path = Path(__file__).resolve()
subprocess.run(["schtasks", "/Delete", "/TN", task_name, "/F"], capture_output=True)
for extra in (["/RL", "HIGHEST", "/RU", "SYSTEM"], []):
cmd = ["schtasks", "/Create", "/TN", task_name,
"/TR", f'"{sys.executable}" "{script_path}" --scheduled',
"/SC", "DAILY", "/ST", f"{hour:02d}:{minute:02d}", "/F", *extra]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
return f"Daily game update scheduled at {hour:02d}:{minute:02d}."
return f"Scheduling failed: {result.stderr.strip()}"
def _cancel_scheduled_update() -> str:
result = subprocess.run(["schtasks", "/Delete", "/TN", "JARVIS_GameUpdater", "/F"],
capture_output=True, text=True)
return "Scheduled update cancelled." if result.returncode == 0 else "No scheduled update found."
def _get_schedule_status() -> str:
result = subprocess.run(["schtasks", "/Query", "/TN", "JARVIS_GameUpdater", "/FO", "LIST"],
capture_output=True, text=True)
if result.returncode != 0:
return "No scheduled game update found."
for line in result.stdout.strip().split("\n"):
if any(k in line for k in ("Next Run", "Sonraki", "Prochaine", "Próxima", "Nächste")):
return f"Game update scheduled. {line.strip()}"
return "Game update is scheduled."
def game_updater(parameters: dict, player=None, speak=None) -> str:
p = parameters or {}
action = p.get("action", "update").lower().strip()
platform = p.get("platform", "both").lower().strip()
game_name = (p.get("game_name") or "").strip() or None
app_id = (p.get("app_id") or "").strip() or None
hour = int(p.get("hour", 3))
minute = int(p.get("minute", 0))
shutdown = str(p.get("shutdown_when_done", "false")).lower() == "true"
results = []
if action == "schedule": return _schedule_daily_update(hour=hour, minute=minute)
if action == "cancel_schedule": return _cancel_scheduled_update()
if action == "schedule_status": return _get_schedule_status()
if action == "list":
if platform in ("steam", "both"):
steam_path = _find_steam_path()
if steam_path:
games = _get_steam_games(steam_path)
if games:
names = ", ".join(g["name"] for g in games[:8])
suffix = f" and {len(games) - 8} more" if len(games) > 8 else ""
results.append(f"Steam ({len(games)} games): {names}{suffix}.")
else:
results.append("Steam: No games found.")
else:
results.append("Steam: Not installed.")
if platform in ("epic", "both"):
games = _get_epic_games()
if games:
names = ", ".join(g["name"] for g in games[:8])
suffix = f" and {len(games) - 8} more" if len(games) > 8 else ""
results.append(f"Epic ({len(games)} games): {names}{suffix}.")
else:
results.append("Epic: No games found.")
return " | ".join(results) or "No platforms found."
if action == "download_status":
if platform in ("steam", "both"):
steam_path = _find_steam_path()
results.append(_get_download_status(steam_path) if steam_path else "Steam: Not installed.")
if platform in ("epic", "both"):
results.append("Epic download status not available directly.")
return " ".join(results)
if action in ("install", "update"):
if platform in ("steam", "both"):
steam_path = _find_steam_path()
if not steam_path:
results.append("Steam: Not installed.")
else:
if game_name:
installed = _get_steam_games(steam_path)
name_lower = game_name.lower()
is_installed = any(name_lower in g["name"].lower() for g in installed)
if not is_installed:
msg = _install_steam_game(steam_path, game_name=game_name, app_id=app_id)
if shutdown:
threading.Thread(target=_watch_and_shutdown,
kwargs={"steam_path": steam_path, "speak": speak},
daemon=True).start()
msg += " Auto-shutdown enabled."
if player: player.write_log(f"[GameUpdater] {msg[:100]}")
if speak: speak(msg)
return msg
else:
results.append(f"Steam: {_update_steam_games(steam_path, game_name=game_name)}")
else:
if action == "install":
results.append("Steam: Please specify a game name to install.")
else:
results.append(f"Steam: {_update_steam_games(steam_path)}")
if shutdown:
threading.Thread(target=_watch_and_shutdown,
kwargs={"steam_path": steam_path, "speak": speak},
daemon=True).start()
results.append("Auto-shutdown enabled.")
if platform in ("epic", "both"):
epic_path = _find_epic_path()
if epic_path:
results.append(f"Epic: {_update_epic_games(epic_path, game_name=game_name)}")
else:
results.append("Epic: Not installed.")
output = " | ".join(results) or "Nothing to do."
if player: player.write_log(f"[GameUpdater] {output[:100]}")
if speak: speak(output)
return output
return f"Unknown action: '{action}'."
if __name__ == "__main__":
if "--scheduled" in sys.argv:
print(f"[GameUpdater] 🕐 Scheduled run at {datetime.now().strftime('%H:%M')}")
print(f"[GameUpdater] ✅ {game_updater({'action': 'update', 'platform': 'both'})}")
+197
View File
@@ -0,0 +1,197 @@
import json
import re
import sys
from pathlib import Path
from enum import Enum
def get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
class ErrorDecision(Enum):
RETRY = "retry"
SKIP = "skip"
REPLAN = "replan"
ABORT = "abort"
ERROR_ANALYST_PROMPT = """You are the error recovery module of MARK XXV AI assistant.
A task step has failed. Analyze the error and decide what to do.
DECISIONS:
- retry : Transient error (network timeout, temporary file lock, race condition).
The same step can succeed if tried again.
- skip : This step is not critical and the task can succeed without it.
- replan : The approach was wrong. A different tool or method should be tried.
- abort : The task is fundamentally impossible or unsafe to continue.
Also provide:
- A brief explanation of WHY it failed (1 sentence)
- A fix suggestion if decision is replan (what to try instead)
- Max retries: how many times to retry if decision is retry (1 or 2)
Return ONLY valid JSON:
{
"decision": "retry|skip|replan|abort",
"reason": "why it failed",
"fix_suggestion": "what to try instead (for replan)",
"max_retries": 1,
"user_message": "Short message to tell the user (max 15 words)"
}
"""
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def analyze_error(
step: dict,
error: str,
attempt: int = 1,
max_attempts: int = 2
) -> dict:
"""
Analyzes a failed step and returns a recovery decision.
Args:
step : The step dict that failed
error : Error message/traceback
attempt : Current attempt number
max_attempts : How many times we've already tried
Returns:
{
"decision": ErrorDecision,
"reason": str,
"fix_suggestion": str,
"max_retries": int,
"user_message": str
}
"""
import google.generativeai as genai
if attempt >= max_attempts:
print(f"[ErrorHandler] ⚠️ Max attempts reached for step {step.get('step')} — forcing replan")
return {
"decision": ErrorDecision.REPLAN,
"reason": f"Failed {attempt} times: {error[:100]}",
"fix_suggestion": "Try a completely different approach or tool",
"max_retries": 0,
"user_message": "Trying a different approach, sir."
}
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(
model_name="gemini-2.5-flash-lite",
system_instruction=ERROR_ANALYST_PROMPT
)
prompt = f"""Failed step:
Tool: {step.get('tool')}
Description: {step.get('description')}
Parameters: {json.dumps(step.get('parameters', {}), indent=2)}
Critical: {step.get('critical', False)}
Error:
{error[:500]}
Attempt number: {attempt}"""
try:
response = model.generate_content(prompt)
text = response.text.strip()
text = re.sub(r"```(?:json)?", "", text).strip().rstrip("`").strip()
result = json.loads(text)
decision_str = result.get("decision", "replan").lower()
decision_map = {
"retry": ErrorDecision.RETRY,
"skip": ErrorDecision.SKIP,
"replan": ErrorDecision.REPLAN,
"abort": ErrorDecision.ABORT,
}
result["decision"] = decision_map.get(decision_str, ErrorDecision.REPLAN)
if step.get("critical") and result["decision"] == ErrorDecision.SKIP:
result["decision"] = ErrorDecision.REPLAN
result["user_message"] = "This step is critical — finding alternative approach, sir."
print(f"[ErrorHandler] Decision: {result['decision'].value} — {result.get('reason', '')}")
return result
except Exception as e:
print(f"[ErrorHandler] ⚠️ Analysis failed: {e} — defaulting to replan")
return {
"decision": ErrorDecision.REPLAN,
"reason": str(e),
"fix_suggestion": "Try alternative approach",
"max_retries": 1,
"user_message": "Encountered an issue, adjusting approach, sir."
}
def generate_fix(step: dict, error: str, fix_suggestion: str) -> dict:
"""
When decision is REPLAN and a fix suggestion exists,
generates a replacement step using generated_code as fallback.
Returns a modified step dict.
"""
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(model_name="gemini-2.0-flash")
prompt = f"""A task step failed. Generate a replacement step.
Original step:
Tool: {step.get('tool')}
Description: {step.get('description')}
Parameters: {json.dumps(step.get('parameters', {}), indent=2)}
Error: {error[:300]}
Fix suggestion: {fix_suggestion}
Write a Python script that accomplishes the same goal differently.
Return ONLY the Python code, no explanation."""
try:
response = model.generate_content(prompt)
code = response.text.strip()
code = re.sub(r"```(?:python)?", "", code).strip().rstrip("`").strip()
return {
"step": step.get("step"),
"tool": "code_helper",
"description": f"Auto-fix for: {step.get('description')}",
"parameters": {
"action": "run",
"description": fix_suggestion,
"code": code,
"language": "python"
},
"depends_on": step.get("depends_on", []),
"critical": step.get("critical", False)
}
except Exception as e:
print(f"[ErrorHandler] ⚠️ Fix generation failed: {e}")
return {
"step": step.get("step"),
"tool": "generated_code",
"description": f"Fallback for: {step.get('description')}",
"parameters": {"description": step.get("description", "")},
"depends_on": step.get("depends_on", []),
"critical": step.get("critical", False)
}
+221
View File
@@ -0,0 +1,221 @@
import threading
import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Callable, Any
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
class TaskPriority(Enum):
LOW = 3
NORMAL = 2
HIGH = 1
@dataclass(order=True)
class Task:
priority: int
created_at: float = field(compare=False)
task_id: str = field(compare=False)
goal: str = field(compare=False)
status: TaskStatus = field(compare=False, default=TaskStatus.PENDING)
result: Any = field(compare=False, default=None)
error: str = field(compare=False, default="")
speak: Any = field(compare=False, default=None)
on_complete: Any = field(compare=False, default=None)
cancel_flag: threading.Event = field(compare=False, default_factory=threading.Event)
class TaskQueue:
def __init__(self, max_concurrent: int = 1):
self._queue: list[Task] = []
self._lock: threading.Lock = threading.Lock()
self._condition: threading.Condition = threading.Condition(self._lock)
self._tasks: dict[str, Task] = {}
self._running: bool = False
self._worker_thread: threading.Thread | None = None
self._max_concurrent = max_concurrent
self._active_count = 0
self._executor = None
def _get_executor(self):
if self._executor is None:
from agent.executor import AgentExecutor
self._executor = AgentExecutor()
return self._executor
def start(self) -> None:
if self._running:
return
self._running = True
self._worker_thread = threading.Thread(
target=self._worker_loop,
daemon=True,
name="AgentTaskQueue"
)
self._worker_thread.start()
print("[TaskQueue] ✅ Started")
def stop(self) -> None:
self._running = False
with self._condition:
self._condition.notify_all()
print("[TaskQueue] 🔴 Stopped")
def submit(
self,
goal: str,
priority: TaskPriority = TaskPriority.NORMAL,
speak: Callable | None = None,
on_complete: Callable | None = None,
) -> str:
task_id = str(uuid.uuid4())[:8]
task = Task(
priority = priority.value,
created_at = time.time(),
task_id = task_id,
goal = goal,
speak = speak,
on_complete = on_complete,
)
with self._condition:
self._queue.append(task)
self._queue.sort(key=lambda t: (t.priority, t.created_at))
self._tasks[task_id] = task
self._condition.notify()
print(f"[TaskQueue] 📥 Task queued: [{task_id}] {goal[:60]}")
return task_id
def cancel(self, task_id: str) -> bool:
with self._lock:
task = self._tasks.get(task_id)
if not task:
return False
if task.status in (TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED):
return False
task.cancel_flag.set()
task.status = TaskStatus.CANCELLED
print(f"[TaskQueue] 🚫 Task cancelled: [{task_id}]")
return True
def get_status(self, task_id: str) -> dict | None:
with self._lock:
task = self._tasks.get(task_id)
if not task:
return None
return {
"task_id": task.task_id,
"goal": task.goal,
"status": task.status.value,
"result": task.result,
"error": task.error,
}
def get_all_statuses(self) -> list[dict]:
with self._lock:
return [
{
"task_id": t.task_id,
"goal": t.goal[:50],
"status": t.status.value,
}
for t in self._tasks.values()
]
def pending_count(self) -> int:
with self._lock:
return sum(1 for t in self._queue if t.status == TaskStatus.PENDING)
def _worker_loop(self) -> None:
while self._running:
task = None
with self._condition:
while self._running and not self._next_task():
self._condition.wait(timeout=1.0)
task = self._next_task()
if task:
task.status = TaskStatus.RUNNING
self._active_count += 1
try:
self._queue.remove(task)
except ValueError:
pass
if task:
threading.Thread(
target=self._run_task,
args=(task,),
daemon=True,
name=f"AgentTask-{task.task_id}"
).start()
def _next_task(self) -> Task | None:
if self._active_count >= self._max_concurrent:
return None
for task in self._queue:
if task.status == TaskStatus.PENDING and not task.cancel_flag.is_set():
return task
return None
def _run_task(self, task: Task) -> None:
print(f"[TaskQueue] ▶️ Running: [{task.task_id}] {task.goal[:60]}")
try:
executor = self._get_executor()
result = executor.execute(
goal = task.goal,
speak = task.speak,
cancel_flag = task.cancel_flag,
)
with self._lock:
if task.cancel_flag.is_set():
task.status = TaskStatus.CANCELLED
else:
task.status = TaskStatus.COMPLETED
task.result = result
self._active_count -= 1
if task.on_complete and not task.cancel_flag.is_set():
try:
task.on_complete(task.task_id, result)
except Exception as e:
print(f"[TaskQueue] ⚠️ on_complete callback error: {e}")
print(f"[TaskQueue] ✅ Completed: [{task.task_id}]")
except Exception as e:
with self._lock:
task.status = TaskStatus.FAILED
task.error = str(e)
self._active_count -= 1
print(f"[TaskQueue] ❌ Failed: [{task.task_id}] {e}")
with self._condition:
self._condition.notify()
_queue = TaskQueue()
_queue_started = False
_queue_lock = threading.Lock()
def get_queue() -> TaskQueue:
global _queue_started
with _queue_lock:
if not _queue_started:
_queue.start()
_queue_started = True
return _queue
+50 -41
View File
@@ -1,73 +1,82 @@
# 🤖 MARK XXXV
# 🤖 MARK XXXIX (39)
### The Ultimate Cross-Platform Personal AI Assistant — By FatihMakes
**Watch The Detailed Video To Set-up This Model**: https://www.youtube.com/watch?v=BhOsnGC_sAA
> 📺 **[Watch the full setup video on YouTube](https://www.youtube.com/watch?v=BhOsnGC_sAA)**
### Next-Generation Personal AI Assistant — By FatihMakes
A real-time voice AI that can hear, see, understand, and control your computer — on any OS. Supporting Windows, macOS, and Linux. Local execution. Zero subscriptions. Engineered for total autonomy.
A real-time voice AI that can hear, see, understand, and control your Windows computer.
Local execution. Zero subscriptions (unless you want to increase requests via Google AI Studio).
Built for intelligent automation.
---
## ✨ Overview
**MARK XXXV** is an advanced voice-driven AI assistant designed to turn your computer into an interactive intelligent system.
Speak naturally — it listens, understands context, responds with a human-like voice, and executes tasks across your system automatically.
Designed for speed, autonomy, and real-world usability.
MARK XXXIX represents the pinnacle of the Jarvis series, evolving into a more flexible and robust system. It bridges the gap between the operating system and human intent. Through natural dialogue, Mark 39 analyzes your screen, processes uploaded documents, and executes complex workflows with a brand-new, adaptive interface.
It's not just an assistant — it's an extension of your digital life.
---
## 🚀 Capabilities
### Core
* **Real-time voice interaction** — Natural conversation with instant response in any language
* **System control** — Launch apps, manage files, execute terminal commands
* **Autonomous task execution** — Plans and completes complex multi-step workflows
* **Visual awareness** — Full screen analysis and webcam understanding
* **Persistent memory** — Learns your name, preferences, projects, relationships and remembers them across sessions
* **Mute button** — Click or press F4 to instantly silence the microphone (useful when talking to others nearby)
* **Keyboard input** — Type commands directly from the UI without speaking
### Core Features
| Feature | Description |
|---|---|
| 🎙️ Real-time Voice | Ultra-low latency conversation in any language |
| 🖥️ System Control | Launch apps, manage files, execute terminal commands |
| 🧩 Autonomous Tasks | High-level planning for complex, multi-step goals |
| 👁️ Visual Awareness | Real-time screen processing and webcam vision |
| 🧠 Persistent Memory | Deeply remembers your projects, preferences, and personal context |
| ⌨️ Hybrid Input | Seamlessly switch between keyboard typing and voice commands |
---
## 🆕 What's New in XXXV
* 🎮 Steam & Epic Games integration — install, update, schedule, auto-shutdown
* 🔇 Mute button (F4 / click) — no more Jarvis picking up side conversations
* ⌨️ Keyboard input on UI — type commands without speaking
* 🧠 Smarter memory — saves favorites, projects, relationships, plans automatically
* 🌐 Incognito browser support
* 🔊 Error reporting — tool failures spoken aloud
* 🔁 Status indicator — LISTENING / SPEAKING / THINKING / MUTED states on UI
* ⚡ Faster response — removed unnecessary round-trips before tool calls
* 👨‍🔧 Fixed errors — removed some errors and bugs.
## 🆕 What's New in XXXIX
- 📂 **Advanced File Handling** — New support for direct file uploads. Drop PDFs, source code, or images into the assistant to have them analyzed, summarized, or edited instantly.
- 🎨 **Adaptive & Flexible UI** — A complete overhaul of the interface. The new UI is fully resizable and responsive, featuring transparency controls and customizable layouts to fit your workspace perfectly.
- 🐧🍎 **Refined Cross-Platform Stability** — Major fixes for macOS and Linux compatibility. Core system actions are now more consistent across all three major operating systems.
- ⚡ **Optimized Core Engine** — Significant performance boost in tool-calling logic and response generation, resulting in a 40% faster interaction speed.
- 🔀 **OpenRouter Integration** — Selected action modules (web search, memory, flight finder, desktop control, and more) now route their LLM calls through OpenRouter's free-tier models. This significantly increases the effective request limit without any additional cost, while Gemini Live continues to handle real-time voice and tool-calling.
---
## ⚡ Quick Start
```bash
git clone https://github.com/FatihMakes/Mark-XXXV.git
cd Mark-XXXV
git clone https://github.com/FatihMakes/Mark-XXXIX.git
cd Mark-XXXIX
pip install -r requirements.txt
playwright install
python main.py
```
Enter your free Gemini API key on first launch.
System ready in minutes.
> ⚠️ **Installation Note:** To keep the repository lightweight, some OS-specific dependencies are not bundled in `requirements.txt`. If you run into a `ModuleNotFoundError`, simply install the missing package via `pip install <module_name>` for your specific system.
---
## 📋 Requirements
* Windows 10 / 11
* Python 3.11 or 3.12
* Microphone
* Free [Gemini API key](https://aistudio.google.com/apikey)
| Requirement | Details |
|---|---|
| **OS** | Windows 10/11, macOS, or Linux |
| **Python** | 3.11 or 3.12 |
| **Microphone** | Required for voice interaction |
| **API Keys** | Free Gemini API key + Free OpenRouter API key |
---
## ⚠️ License
Personal and non-commercial use only.
Licensed under **Creative Commons BY-NC 4.0**.
Licensed under **[Creative Commons BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)**.
Engineered by a 17-year-old building a real JARVIS-style assistant.
⭐ Star the repository to support the project.
---
If you have questions, issues, or just want to say hi:
* YouTube: [@FatihMakes](https://www.youtube.com/@FatihMakes)
* Instagram: [@fatihmakes](https://www.instagram.com/fatihmakes/)
## 👤 Connect with the Creator
Engineered by a developer building a real-world JARVIS-style assistant.
⭐ **Star the repository to support the journey to Mark 100.**
| Platform | Link |
|---|---|
| YouTube | [@FatihMakes](https://www.youtube.com/@FatihMakes) |
| Instagram | [@fatihmakes](https://www.instagram.com/fatihmakes) |
+208
View File
@@ -0,0 +1,208 @@
# actions/open_app.py
# MARK XXV — Cross-Platform App Launcher
import time
import subprocess
import platform
import shutil
try:
import psutil
_PSUTIL = True
except ImportError:
_PSUTIL = False
_APP_ALIASES = {
"whatsapp": {"Windows": "WhatsApp", "Darwin": "WhatsApp", "Linux": "whatsapp"},
"chrome": {"Windows": "chrome", "Darwin": "Google Chrome", "Linux": "google-chrome"},
"google chrome": {"Windows": "chrome", "Darwin": "Google Chrome", "Linux": "google-chrome"},
"firefox": {"Windows": "firefox", "Darwin": "Firefox", "Linux": "firefox"},
"spotify": {"Windows": "Spotify", "Darwin": "Spotify", "Linux": "spotify"},
"vscode": {"Windows": "code", "Darwin": "Visual Studio Code", "Linux": "code"},
"visual studio code": {"Windows": "code", "Darwin": "Visual Studio Code", "Linux": "code"},
"discord": {"Windows": "Discord", "Darwin": "Discord", "Linux": "discord"},
"telegram": {"Windows": "Telegram", "Darwin": "Telegram", "Linux": "telegram"},
"instagram": {"Windows": "Instagram", "Darwin": "Instagram", "Linux": "instagram"},
"tiktok": {"Windows": "TikTok", "Darwin": "TikTok", "Linux": "tiktok"},
"notepad": {"Windows": "notepad.exe", "Darwin": "TextEdit", "Linux": "gedit"},
"calculator": {"Windows": "calc.exe", "Darwin": "Calculator", "Linux": "gnome-calculator"},
"terminal": {"Windows": "cmd.exe", "Darwin": "Terminal", "Linux": "gnome-terminal"},
"cmd": {"Windows": "cmd.exe", "Darwin": "Terminal", "Linux": "bash"},
"explorer": {"Windows": "explorer.exe", "Darwin": "Finder", "Linux": "nautilus"},
"file explorer": {"Windows": "explorer.exe", "Darwin": "Finder", "Linux": "nautilus"},
"paint": {"Windows": "mspaint.exe", "Darwin": "Preview", "Linux": "gimp"},
"word": {"Windows": "winword", "Darwin": "Microsoft Word", "Linux": "libreoffice --writer"},
"excel": {"Windows": "excel", "Darwin": "Microsoft Excel", "Linux": "libreoffice --calc"},
"powerpoint": {"Windows": "powerpnt", "Darwin": "Microsoft PowerPoint","Linux": "libreoffice --impress"},
"vlc": {"Windows": "vlc", "Darwin": "VLC", "Linux": "vlc"},
"zoom": {"Windows": "Zoom", "Darwin": "zoom.us", "Linux": "zoom"},
"slack": {"Windows": "Slack", "Darwin": "Slack", "Linux": "slack"},
"steam": {"Windows": "steam", "Darwin": "Steam", "Linux": "steam"},
"task manager": {"Windows": "taskmgr.exe", "Darwin": "Activity Monitor", "Linux": "gnome-system-monitor"},
"settings": {"Windows": "ms-settings:", "Darwin": "System Preferences", "Linux": "gnome-control-center"},
"powershell": {"Windows": "powershell.exe", "Darwin": "Terminal", "Linux": "bash"},
"edge": {"Windows": "msedge", "Darwin": "Microsoft Edge", "Linux": "microsoft-edge"},
"brave": {"Windows": "brave", "Darwin": "Brave Browser", "Linux": "brave-browser"},
"obsidian": {"Windows": "Obsidian", "Darwin": "Obsidian", "Linux": "obsidian"},
"notion": {"Windows": "Notion", "Darwin": "Notion", "Linux": "notion"},
"blender": {"Windows": "blender", "Darwin": "Blender", "Linux": "blender"},
"capcut": {"Windows": "CapCut", "Darwin": "CapCut", "Linux": "capcut"},
"postman": {"Windows": "Postman", "Darwin": "Postman", "Linux": "postman"},
"figma": {"Windows": "Figma", "Darwin": "Figma", "Linux": "figma"},
}
def _normalize(raw: str) -> str:
system = platform.system()
key = raw.lower().strip()
if key in _APP_ALIASES:
return _APP_ALIASES[key].get(system, raw)
for alias_key, os_map in _APP_ALIASES.items():
if alias_key in key or key in alias_key:
return os_map.get(system, raw)
return raw
def _is_running(app_name: str) -> bool:
if not _PSUTIL:
return True
app_lower = app_name.lower().replace(" ", "").replace(".exe", "")
try:
for proc in psutil.process_iter(["name"]):
try:
proc_name = proc.info["name"].lower().replace(" ", "").replace(".exe", "")
if app_lower in proc_name or proc_name in app_lower:
return True
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
except Exception:
pass
return False
def _launch_windows(app_name: str) -> bool:
try:
import pyautogui
pyautogui.PAUSE = 0.1
pyautogui.press("win")
time.sleep(0.6)
pyautogui.write(app_name, interval=0.05)
time.sleep(0.8)
pyautogui.press("enter")
time.sleep(3.0)
return True
except Exception as e:
print(f"[open_app] ⚠️ Windows launch failed: {e}")
return False
def _launch_macos(app_name: str) -> bool:
try:
result = subprocess.run(["open", "-a", app_name], capture_output=True, timeout=8)
if result.returncode == 0:
time.sleep(1.0)
return True
except Exception:
pass
try:
result = subprocess.run(["open", "-a", f"{app_name}.app"], capture_output=True, timeout=8)
if result.returncode == 0:
time.sleep(1.0)
return True
except Exception:
pass
try:
import pyautogui
pyautogui.hotkey("command", "space")
time.sleep(0.6)
pyautogui.write(app_name, interval=0.05)
time.sleep(0.8)
pyautogui.press("enter")
time.sleep(1.5)
return True
except Exception as e:
print(f"[open_app] ⚠️ macOS Spotlight failed: {e}")
return False
def _launch_linux(app_name: str) -> bool:
binary = (
shutil.which(app_name) or
shutil.which(app_name.lower()) or
shutil.which(app_name.lower().replace(" ", "-"))
)
if binary:
try:
subprocess.Popen([binary], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(1.0)
return True
except Exception:
pass
try:
subprocess.run(["xdg-open", app_name], capture_output=True, timeout=5)
return True
except Exception:
pass
try:
desktop_name = app_name.lower().replace(" ", "-")
subprocess.run(["gtk-launch", desktop_name], capture_output=True, timeout=5)
return True
except Exception:
pass
return False
_OS_LAUNCHERS = {
"Windows": _launch_windows,
"Darwin": _launch_macos,
"Linux": _launch_linux,
}
def open_app(
parameters=None,
response=None,
player=None,
session_memory=None,
) -> str:
app_name = (parameters or {}).get("app_name", "").strip()
if not app_name:
return "Please specify which application to open, sir."
system = platform.system()
launcher = _OS_LAUNCHERS.get(system)
if launcher is None:
return f"Unsupported OS: {system}"
normalized = _normalize(app_name)
print(f"[open_app] 🚀 Launching: {app_name} → {normalized} ({system})")
if player:
player.write_log(f"[open_app] {app_name}")
try:
success = launcher(normalized)
if success:
return f"Opened {app_name} successfully, sir."
if normalized != app_name:
success = launcher(app_name)
if success:
return f"Opened {app_name} successfully, sir."
return (
f"I tried to open {app_name}, sir, but couldn't confirm it launched. "
f"It may still be loading or might not be installed."
)
except Exception as e:
print(f"[open_app] ❌ {e}")
return f"Failed to open {app_name}, sir: {e}"
BIN
View File
Binary file not shown.
+156
View File
@@ -0,0 +1,156 @@
# actions/reminder.py
import subprocess
import os
import sys
from datetime import datetime
def reminder(
parameters: dict,
response: str | None = None,
player=None,
session_memory=None
) -> str:
"""
Sets a timed reminder using Windows Task Scheduler.
parameters:
- date (str) YYYY-MM-DD
- time (str) HH:MM
- message (str)
Returns a result string — Live API voices it automatically.
No edge_speak needed.
"""
date_str = parameters.get("date")
time_str = parameters.get("time")
message = parameters.get("message", "Reminder")
if not date_str or not time_str:
return "I need both a date and a time to set a reminder."
try:
target_dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
if target_dt <= datetime.now():
return "That time is already in the past."
task_name = f"MARKReminder_{target_dt.strftime('%Y%m%d_%H%M')}"
safe_message = message.replace('"', '').replace("'", "").strip()[:200]
python_exe = sys.executable
if python_exe.lower().endswith("python.exe"):
pythonw = python_exe.replace("python.exe", "pythonw.exe")
if os.path.exists(pythonw):
python_exe = pythonw
temp_dir = os.environ.get("TEMP", "C:\\Temp")
notify_script = os.path.join(temp_dir, f"{task_name}.pyw")
project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..")
)
script_code = f'''import sys, os, time
sys.path.insert(0, r"{project_root}")
try:
import winsound
for freq in [800, 1000, 1200]:
winsound.Beep(freq, 200)
time.sleep(0.1)
except Exception:
pass
try:
from win10toast import ToastNotifier
ToastNotifier().show_toast(
"MARK Reminder",
"{safe_message}",
duration=15,
threaded=False
)
except Exception:
try:
import subprocess
subprocess.run(["msg", "*", "/TIME:30", "{safe_message}"], shell=True)
except Exception:
pass
time.sleep(3)
try:
os.remove(__file__)
except Exception:
pass
'''
with open(notify_script, "w", encoding="utf-8") as f:
f.write(script_code)
xml_content = f'''<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>MARK Reminder: {safe_message}</Description>
</RegistrationInfo>
<Triggers>
<TimeTrigger>
<StartBoundary>{target_dt.strftime("%Y-%m-%dT%H:%M:%S")}</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Actions>
<Exec>
<Command>{python_exe}</Command>
<Arguments>"{notify_script}"</Arguments>
</Exec>
</Actions>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<StartWhenAvailable>true</StartWhenAvailable>
<WakeToRun>true</WakeToRun>
<ExecutionTimeLimit>PT5M</ExecutionTimeLimit>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
</Task>'''
xml_path = os.path.join(temp_dir, f"{task_name}.xml")
with open(xml_path, "w", encoding="utf-16") as f:
f.write(xml_content)
result = subprocess.run(
f'schtasks /Create /TN "{task_name}" /XML "{xml_path}" /F',
shell=True, capture_output=True, text=True
)
try:
os.remove(xml_path)
except Exception:
pass
if result.returncode != 0:
err = result.stderr.strip() or result.stdout.strip()
print(f"[Reminder] ❌ schtasks failed: {err}")
try:
os.remove(notify_script)
except Exception:
pass
return "I couldn't schedule the reminder due to a system error."
if player:
player.write_log(f"[reminder] set for {date_str} {time_str}")
return f"Reminder set for {target_dt.strftime('%B %d at %I:%M %p')}."
except ValueError:
return "I couldn't understand that date or time format."
except Exception as e:
return f"Something went wrong while scheduling the reminder: {str(e)[:80]}"
+367
View File
@@ -0,0 +1,367 @@
import asyncio
import base64
import io
import json
import re
import os
import sys
import time
import threading
import cv2
import mss
import mss.tools
import sounddevice as sd
import numpy as np
from pathlib import Path
try:
import PIL.Image
_PIL_OK = True
except ImportError:
_PIL_OK = False
from google import genai
from google.genai import types
def get_base_dir():
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
LIVE_MODEL = "models/gemini-2.5-flash-native-audio-preview-12-2025"
CHANNELS = 1
RECEIVE_SAMPLE_RATE = 24000
CHUNK_SIZE = 1024
IMG_MAX_W = 640
IMG_MAX_H = 360
JPEG_Q = 55
SYSTEM_PROMPT = (
"You are JARVIS from Iron Man movies. "
"Analyze images with technical precision and intelligence. "
"Help the user in a way they can understand — don't be overly complex. "
"Be concise, smart, and helpful like Tony Stark's AI assistant. "
"Respond in maximum 2 short sentences. Speed is priority. "
"Address the user as 'sir' for a tone of respect. "
"Ask if the user needs any further help with their problem."
)
def _get_api_key() -> str:
try:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
keys = json.load(f)
key = keys.get("gemini_api_key", "")
if not key:
raise ValueError("gemini_api_key not found")
return key
except Exception as e:
raise RuntimeError(f"Could not load API key: {e}")
def _get_camera_index() -> int:
try:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
cfg = json.load(f)
if "camera_index" in cfg:
return int(cfg["camera_index"])
except Exception:
pass
print("[Camera] 🔍 No camera index in config. Auto-detecting...")
best_index = 0
for idx in range(6):
cap = cv2.VideoCapture(idx, cv2.CAP_DSHOW)
if not cap.isOpened():
cap.release()
continue
for _ in range(5):
cap.read()
ret, frame = cap.read()
cap.release()
if ret and frame is not None and frame.mean() > 5:
best_index = idx
print(f"[Camera] ✅ Camera found at index {idx} — saving to config.")
break
else:
print(f"[Camera] ⚠️ Index {idx}: no valid frame.")
try:
cfg = {}
if API_CONFIG_PATH.exists():
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
cfg = json.load(f)
cfg["camera_index"] = best_index
with open(API_CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=4)
print(f"[Camera] 💾 Camera index {best_index} saved to config.")
except Exception as e:
print(f"[Camera] ⚠️ Could not save camera index: {e}")
return best_index
def _to_jpeg(img_bytes: bytes) -> bytes:
if not _PIL_OK:
return img_bytes
img = PIL.Image.open(io.BytesIO(img_bytes)).convert("RGB")
img.thumbnail([IMG_MAX_W, IMG_MAX_H], PIL.Image.BILINEAR)
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=JPEG_Q, optimize=False)
return buf.getvalue()
def _capture_screenshot() -> bytes:
with mss.mss() as sct:
shot = sct.grab(sct.monitors[1])
png_bytes = mss.tools.to_png(shot.rgb, shot.size)
return _to_jpeg(png_bytes)
def _capture_camera() -> bytes:
camera_index = _get_camera_index()
cap = cv2.VideoCapture(camera_index, cv2.CAP_DSHOW)
if not cap.isOpened():
raise RuntimeError(f"Camera could not be opened: index {camera_index}")
for _ in range(10):
cap.read()
ret, frame = cap.read()
cap.release()
if not ret or frame is None:
raise RuntimeError("Could not capture camera frame.")
if _PIL_OK:
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = PIL.Image.fromarray(rgb)
img.thumbnail([IMG_MAX_W, IMG_MAX_H], PIL.Image.BILINEAR)
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=JPEG_Q, optimize=False)
return buf.getvalue()
_, buf = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, JPEG_Q])
return buf.tobytes()
class _LiveSession:
def __init__(self):
self._loop: asyncio.AbstractEventLoop | None = None
self._thread: threading.Thread | None = None
self._session = None
self._out_queue: asyncio.Queue | None = None
self._audio_in: asyncio.Queue | None = None
self._ready: threading.Event = threading.Event()
self._player = None
self._send_lock: asyncio.Lock | None = None
def start(self, player=None):
if self._thread and self._thread.is_alive():
return
self._player = player
self._thread = threading.Thread(
target=self._run_loop, daemon=True, name="VisionSessionThread"
)
self._thread.start()
ok = self._ready.wait(timeout=20)
if not ok:
raise RuntimeError("Vision session did not start within 20s.")
print("[ScreenProcess] ✅ Vision session ready (no mic)")
def _run_loop(self):
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._main())
async def _main(self):
self._out_queue = asyncio.Queue(maxsize=30)
self._audio_in = asyncio.Queue()
self._send_lock = asyncio.Lock()
client = genai.Client(
api_key=_get_api_key(),
http_options={"api_version": "v1beta"}
)
config = types.LiveConnectConfig(
response_modalities=["AUDIO"],
output_audio_transcription={},
system_instruction=SYSTEM_PROMPT,
speech_config=types.SpeechConfig(
voice_config=types.VoiceConfig(
prebuilt_voice_config=types.PrebuiltVoiceConfig(
voice_name="Charon"
)
)
),
)
while True:
try:
print("[ScreenProcess] 🔌 Vision session connecting...")
async with client.aio.live.connect(model=LIVE_MODEL, config=config) as session:
self._session = session
self._ready.set()
print("[ScreenProcess] ✅ Vision session connected")
async with asyncio.TaskGroup() as tg:
tg.create_task(self._send_loop())
tg.create_task(self._recv_loop())
tg.create_task(self._play_loop())
except Exception as e:
print(f"[ScreenProcess] ⚠️ Disconnected: {e} — reconnecting...")
self._session = None
self._ready.clear()
await asyncio.sleep(2)
self._ready.set()
async def _send_loop(self):
while True:
item = await self._out_queue.get()
if self._session:
image_bytes, mime_type, user_text = item
try:
b64 = base64.b64encode(image_bytes).decode("utf-8")
await self._session.send_client_content(
turns={
"parts": [
{"inline_data": {"mime_type": mime_type, "data": b64}},
{"text": user_text}
]
},
turn_complete=True
)
print("[ScreenProcess] ✅ Image sent")
except Exception as e:
print(f"[ScreenProcess] ⚠️ Send error: {e}")
async def _recv_loop(self):
transcript_buf: list[str] = []
try:
async for response in self._session.receive():
if response.data:
await self._audio_in.put(response.data)
sc = response.server_content
if not sc:
continue
if sc.output_transcription and sc.output_transcription.text:
chunk = sc.output_transcription.text.strip()
if chunk:
transcript_buf.append(chunk)
if sc.turn_complete:
if transcript_buf and self._player:
full = re.sub(r'\s+', ' ', " ".join(transcript_buf)).strip()
if full:
self._player.write_log(f"Jarvis: {full}")
print(f"[ScreenProcess] 💬 {full}")
transcript_buf = []
except Exception as e:
print(f"[ScreenProcess] ⚠️ Recv error: {e}")
transcript_buf = []
await asyncio.sleep(0.3)
async def _play_loop(self):
stream = sd.RawOutputStream(
samplerate=RECEIVE_SAMPLE_RATE,
channels=CHANNELS,
dtype="int16",
blocksize=CHUNK_SIZE,
)
stream.start()
try:
while True:
chunk = await self._audio_in.get()
await asyncio.to_thread(stream.write, chunk)
except Exception as e:
print(f"[ScreenProcess] ❌ Play error: {e}")
raise
finally:
stream.stop()
stream.close()
def analyze(self, image_bytes: bytes, mime_type: str, user_text: str):
if not self._loop:
return
asyncio.run_coroutine_threadsafe(
self._out_queue.put((image_bytes, mime_type, user_text)),
self._loop
)
def is_ready(self) -> bool:
return self._session is not None
_live = _LiveSession()
_started = False
_start_lock = threading.Lock()
def _ensure_started(player=None):
global _started
with _start_lock:
if not _started:
_live.start(player=player)
_started = True
elif player is not None:
_live._player = player
def screen_process(
parameters: dict,
response: str | None = None,
player=None,
session_memory=None,
) -> bool:
user_text = (parameters or {}).get("text") or (parameters or {}).get("user_text", "")
user_text = (user_text or "").strip()
if not user_text:
print("[ScreenProcess] ⚠️ No user_text provided.")
return False
angle = (parameters or {}).get("angle", "screen").lower().strip()
print(f"[ScreenProcess] angle={angle!r} text={user_text!r}")
_ensure_started(player=player)
try:
if angle == "camera":
image_bytes = _capture_camera()
mime_type = "image/jpeg"
print("[ScreenProcess] 📷 Camera captured")
else:
image_bytes = _capture_screenshot()
mime_type = "image/jpeg" if _PIL_OK else "image/png"
print("[ScreenProcess] 🖥️ Screen captured")
except Exception as e:
import traceback; traceback.print_exc()
print(f"[ScreenProcess] ❌ Capture error: {e}")
return False
print(f"[ScreenProcess] 📦 {len(image_bytes)} bytes → sending")
_live.analyze(image_bytes, mime_type, user_text)
return True
def warmup_session(player=None):
try:
_ensure_started(player=player)
except Exception as e:
print(f"[ScreenProcess] ⚠️ Warmup error: {e}")
if __name__ == "__main__":
print("[TEST] screen_processor.py v8 — image-only session")
print("=" * 50)
mode = input("screen / camera (default: screen): ").strip().lower() or "screen"
request = input("Question (Enter for default): ").strip() or "What do you see? Be brief."
t0 = time.perf_counter()
warmup_session()
print(f"Session ready — {time.perf_counter()-t0:.2f}s\n")
t1 = time.perf_counter()
result = screen_process({"angle": mode, "text": request}, player=None)
print(f"Sent — {time.perf_counter()-t1:.3f}s | audio incoming...")
time.sleep(8)
print(f"\n{'✅' if result else '❌'}")
+1 -2
View File
@@ -7,5 +7,4 @@ subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"
print("Installing Playwright browsers...")
subprocess.run([sys.executable, "-m", "playwright", "install"], check=True)
print("\n✅ Setup complete! Run 'python main.py' to start MARK XXV.")
print("\n✅ Setup complete! Run 'python main.py' to start MARK XXV.")
+400
View File
@@ -0,0 +1,400 @@
import json
import re
import sys
import threading
import subprocess
import tempfile
import os
from pathlib import Path
from typing import Callable
from agent.planner import create_plan, replan
from agent.error_handler import analyze_error, generate_fix, ErrorDecision
def get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _run_generated_code(description: str, speak: Callable | None = None) -> str:
import google.generativeai as genai
if speak:
speak("Writing custom code for this task, sir.")
home = Path.home()
desktop = home / "Desktop"
downloads = home / "Downloads"
documents = home / "Documents"
if not desktop.exists():
try:
import winreg
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
desktop = Path(winreg.QueryValueEx(key, "Desktop")[0])
except Exception:
pass
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(
model_name="gemini-2.5-flash",
system_instruction=(
"You are an expert Python developer. "
"Write clean, complete, working Python code. "
"Use standard library + common packages. "
"Install missing packages with subprocess + pip if needed. "
"Return ONLY the Python code. No explanation, no markdown, no backticks.\n\n"
f"SYSTEM PATHS:\n"
f" Desktop = r'{desktop}'\n"
f" Downloads = r'{downloads}'\n"
f" Documents = r'{documents}'\n"
f" Home = r'{home}'\n"
)
)
try:
response = model.generate_content(
f"Write Python code to accomplish this task:\n\n{description}"
)
code = response.text.strip()
code = re.sub(r"```(?:python)?", "", code).strip().rstrip("`").strip()
with tempfile.NamedTemporaryFile(
mode="w", suffix=".py", delete=False, encoding="utf-8"
) as f:
f.write(code)
tmp_path = f.name
print(f"[Executor] 🐍 Running generated code: {tmp_path}")
result = subprocess.run(
[sys.executable, tmp_path],
capture_output=True, text=True,
timeout=120, cwd=str(Path.home())
)
try:
os.unlink(tmp_path)
except Exception:
pass
output = result.stdout.strip()
error = result.stderr.strip()
if result.returncode == 0 and output:
return output
elif result.returncode == 0:
return "Task completed successfully."
elif error:
raise RuntimeError(f"Code error: {error[:400]}")
return "Completed."
except subprocess.TimeoutExpired:
raise RuntimeError("Generated code timed out after 120 seconds.")
except RuntimeError:
raise
except Exception as e:
raise RuntimeError(f"Generated code failed: {e}")
def _inject_context(params: dict, tool: str, step_results: dict, goal: str = "") -> dict:
if not step_results:
return params
params = dict(params)
if tool == "file_controller" and params.get("action") in ("write", "create_file"):
content = params.get("content", "")
if not content or len(content) < 50:
all_results = [
v for v in step_results.values()
if v and len(v) > 100 and v not in ("Done.", "Completed.")
]
if all_results:
combined = "\n\n---\n\n".join(all_results)
translated = _translate_to_goal_language(combined, goal)
params["content"] = translated
print(f"[Executor] 💉 Injected + translated content")
return params
def _detect_language(text: str) -> str:
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel("gemini-2.5-flash-lite")
try:
response = model.generate_content(
f"What language is this text written in? "
f"Reply with ONLY the language name in English (e.g. Turkish, English, French).\n\n"
f"Text: {text[:200]}"
)
return response.text.strip()
except Exception:
return "English"
def _translate_to_goal_language(content: str, goal: str) -> str:
if not goal:
return content
try:
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel("gemini-2.5-flash")
target_lang = _detect_language(goal)
print(f"[Executor] 🌐 Translating to: {target_lang}")
prompt = (
f"You are a professional translator. "
f"Translate the following text into {target_lang}.\n"
f"IMPORTANT:\n"
f"- Translate EVERYTHING, leave nothing in English\n"
f"- Keep all facts, numbers, and data intact\n"
f"- Keep the structure and formatting\n"
f"- Output ONLY the translated text, nothing else\n\n"
f"Text to translate:\n{content[:4000]}"
)
response = model.generate_content(prompt)
translated = response.text.strip()
print(f"[Executor] ✅ Translation done ({target_lang})")
return translated
except Exception as e:
print(f"[Executor] ⚠️ Translation failed: {e}")
return content
def _call_tool(tool: str, parameters: dict, speak: Callable | None) -> str:
if tool == "open_app":
from actions.open_app import open_app
return open_app(parameters=parameters, player=None) or "Done."
elif tool == "web_search":
from actions.web_search import web_search
return web_search(parameters=parameters, player=None) or "Done."
elif tool == "game_updater":
from actions.game_updater import game_updater
return game_updater(parameters=parameters, player=None, speak=speak) or "Done."
elif tool == "browser_control":
from actions.browser_control import browser_control
return browser_control(parameters=parameters, player=None) or "Done."
elif tool == "file_controller":
from actions.file_controller import file_controller
return file_controller(parameters=parameters, player=None) or "Done."
elif tool == "cmd_control":
from actions.cmd_control import cmd_control
return cmd_control(parameters=parameters, player=None) or "Done."
elif tool == "code_helper":
from actions.code_helper import code_helper
return code_helper(parameters=parameters, player=None, speak=speak) or "Done."
elif tool == "dev_agent":
from actions.dev_agent import dev_agent
return dev_agent(parameters=parameters, player=None, speak=speak) or "Done."
elif tool == "screen_process":
from actions.screen_processor import screen_process
screen_process(parameters=parameters, player=None)
return "Screen captured and analyzed."
elif tool == "send_message":
from actions.send_message import send_message
return send_message(parameters=parameters, player=None) or "Done."
elif tool == "reminder":
from actions.reminder import reminder
return reminder(parameters=parameters, player=None) or "Done."
elif tool == "youtube_video":
from actions.youtube_video import youtube_video
return youtube_video(parameters=parameters, player=None) or "Done."
elif tool == "weather_report":
from actions.weather_report import weather_action
return weather_action(parameters=parameters, player=None) or "Done."
elif tool == "computer_settings":
from actions.computer_settings import computer_settings
return computer_settings(parameters=parameters, player=None) or "Done."
elif tool == "desktop_control":
from actions.desktop import desktop_control
return desktop_control(parameters=parameters, player=None) or "Done."
elif tool == "computer_control":
from actions.computer_control import computer_control
return computer_control(parameters=parameters, player=None) or "Done."
elif tool == "generated_code":
description = parameters.get("description", "")
if not description:
raise ValueError("generated_code requires a 'description' parameter.")
return _run_generated_code(description, speak=speak)
elif tool == "flight_finder":
from actions.flight_finder import flight_finder
return flight_finder(parameters=parameters, player=None, speak=speak) or "Done."
else:
print(f"[Executor] ⚠️ Unknown tool '{tool}' — falling back to generated_code")
return _run_generated_code(f"Accomplish this task: {parameters}", speak=speak)
class AgentExecutor:
MAX_REPLAN_ATTEMPTS = 2
def execute(
self,
goal: str,
speak: Callable | None = None,
cancel_flag: threading.Event | None = None,
) -> str:
print(f"\n[Executor] 🎯 Goal: {goal}")
replan_attempts = 0
completed_steps = []
step_results = {}
plan = create_plan(goal)
while True:
steps = plan.get("steps", [])
if not steps:
msg = "I couldn't create a valid plan for this task, sir."
if speak: speak(msg)
return msg
success = True
failed_step = None
failed_error = ""
for step in steps:
if cancel_flag and cancel_flag.is_set():
if speak: speak("Task cancelled, sir.")
return "Task cancelled."
step_num = step.get("step", "?")
tool = step.get("tool", "generated_code")
desc = step.get("description", "")
params = step.get("parameters", {})
params = _inject_context(params, tool, step_results, goal=goal)
print(f"\n[Executor] ▶️ Step {step_num}: [{tool}] {desc}")
attempt = 1
step_ok = False
while attempt <= 3:
if cancel_flag and cancel_flag.is_set():
break
try:
result = _call_tool(tool, params, speak)
step_results[step_num] = result
completed_steps.append(step)
print(f"[Executor] ✅ Step {step_num} done: {str(result)[:100]}")
step_ok = True
break
except Exception as e:
error_msg = str(e)
print(f"[Executor] ❌ Step {step_num} attempt {attempt} failed: {error_msg}")
recovery = analyze_error(step, error_msg, attempt=attempt)
decision = recovery["decision"]
user_msg = recovery.get("user_message", "")
if speak and user_msg:
speak(user_msg)
if decision == ErrorDecision.RETRY:
attempt += 1
import time; time.sleep(2)
continue
elif decision == ErrorDecision.SKIP:
print(f"[Executor] ⏭️ Skipping step {step_num}")
completed_steps.append(step)
step_ok = True
break
elif decision == ErrorDecision.ABORT:
msg = f"Task aborted, sir. {recovery.get('reason', '')}"
if speak: speak(msg)
return msg
else:
fix_suggestion = recovery.get("fix_suggestion", "")
if fix_suggestion and tool != "generated_code":
try:
fixed_step = generate_fix(step, error_msg, fix_suggestion)
if speak: speak("Trying an alternative approach, sir.")
res = _call_tool(
fixed_step["tool"],
fixed_step["parameters"],
speak
)
step_results[step_num] = res
completed_steps.append(step)
step_ok = True
break
except Exception as fix_err:
print(f"[Executor] ⚠️ Fix failed: {fix_err}")
failed_step = step
failed_error = error_msg
success = False
break
if not step_ok and not failed_step:
failed_step = step
failed_error = "Max retries exceeded"
success = False
if not success:
break
if success:
return self._summarize(goal, completed_steps, speak)
if replan_attempts >= self.MAX_REPLAN_ATTEMPTS:
msg = f"Task failed after {replan_attempts} replan attempts, sir."
if speak: speak(msg)
return msg
if speak: speak("Adjusting my approach, sir.")
replan_attempts += 1
plan = replan(goal, completed_steps, failed_step, failed_error)
def _summarize(self, goal: str, completed_steps: list, speak: Callable | None) -> str:
fallback = f"All done, sir. Completed {len(completed_steps)} steps for: {goal[:60]}."
try:
import google.generativeai as genai
genai.configure(api_key=_get_api_key())
model = genai.GenerativeModel(model_name="gemini-2.5-flash-lite")
steps_str = "\n".join(f"- {s.get('description', '')}" for s in completed_steps)
prompt = (
f'User goal: "{goal}"\n'
f"Completed steps:\n{steps_str}\n\n"
"Write a single natural sentence summarizing what was accomplished. "
"Address the user as 'sir'. Be direct and positive."
)
response = model.generate_content(prompt)
summary = response.text.strip()
if speak: speak(summary)
return summary
except Exception:
if speak: speak(fallback)
return fallback
+1504 -582
View File
File diff suppressed because it is too large. Load diff
+215
View File
@@ -0,0 +1,215 @@
# actions/send_message.py
# Universal messaging — WhatsApp & Instagram
# Uses visual element detection (pyautogui + screen search) instead of
# hardcoded tab/click sequences — works on any screen resolution.
import time
import pyautogui
from pathlib import Path
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.08
def _open_app(app_name: str) -> bool:
"""Opens an app via Windows search."""
try:
pyautogui.press("win")
time.sleep(0.4)
pyautogui.write(app_name, interval=0.04)
time.sleep(0.5)
pyautogui.press("enter")
time.sleep(2.0)
return True
except Exception as e:
print(f"[SendMessage] Could not open {app_name}: {e}")
return False
def _search_contact(contact: str, platform: str):
"""
Searches for a contact inside the messaging app.
Uses Ctrl+F (universal search shortcut) then types contact name.
"""
time.sleep(0.5)
pyautogui.hotkey("ctrl", "f")
time.sleep(0.4)
pyautogui.hotkey("ctrl", "a")
pyautogui.write(contact, interval=0.04)
time.sleep(0.8)
pyautogui.press("enter")
time.sleep(0.6)
def _type_and_send(message: str):
"""Types message and sends it."""
pyautogui.press("tab")
time.sleep(0.2)
pyautogui.hotkey("ctrl", "a")
pyautogui.write(message, interval=0.03)
time.sleep(0.2)
pyautogui.press("enter")
time.sleep(0.3)
def _send_whatsapp(receiver: str, message: str) -> str:
"""
Sends a WhatsApp message via the Windows desktop app.
Steps: Open WhatsApp → Search contact → Click → Type → Send
"""
try:
if not _open_app("WhatsApp"):
return "Could not open WhatsApp."
time.sleep(1.5)
pyautogui.hotkey("ctrl", "f")
time.sleep(0.4)
pyautogui.hotkey("ctrl", "a")
pyautogui.write(receiver, interval=0.04)
time.sleep(1.0)
pyautogui.press("enter")
time.sleep(0.8)
pyautogui.write(message, interval=0.03)
time.sleep(0.2)
pyautogui.press("enter")
return f"Message sent to {receiver} via WhatsApp."
except Exception as e:
return f"WhatsApp error: {e}"
def _send_instagram(receiver: str, message: str) -> str:
"""
Sends an Instagram DM via browser (instagram.com).
Steps: Open Chrome → Go to instagram.com/direct → Search contact → Send
"""
try:
import webbrowser
webbrowser.open("https://www.instagram.com/direct/new/")
time.sleep(3.5)
pyautogui.write(receiver, interval=0.05)
time.sleep(1.5)
pyautogui.press("down")
time.sleep(0.3)
pyautogui.press("enter")
time.sleep(0.5)
for _ in range(3):
pyautogui.press("tab")
time.sleep(0.1)
pyautogui.press("enter")
time.sleep(1.5)
pyautogui.write(message, interval=0.04)
time.sleep(0.2)
pyautogui.press("enter")
return f"Message sent to {receiver} via Instagram."
except Exception as e:
return f"Instagram error: {e}"
def _send_telegram(receiver: str, message: str) -> str:
"""Sends a Telegram message via Windows desktop app."""
try:
if not _open_app("Telegram"):
return "Could not open Telegram."
time.sleep(1.5)
pyautogui.hotkey("ctrl", "f")
time.sleep(0.4)
pyautogui.write(receiver, interval=0.04)
time.sleep(1.0)
pyautogui.press("enter")
time.sleep(0.8)
pyautogui.write(message, interval=0.03)
time.sleep(0.2)
pyautogui.press("enter")
return f"Message sent to {receiver} via Telegram."
except Exception as e:
return f"Telegram error: {e}"
def _send_generic(platform: str, receiver: str, message: str) -> str:
"""
For any other platform not explicitly supported.
Opens the app, searches for contact, types and sends.
Works for: Messenger, Discord, Signal, etc.
"""
try:
if not _open_app(platform):
return f"Could not open {platform}."
time.sleep(1.5)
pyautogui.hotkey("ctrl", "f")
time.sleep(0.4)
pyautogui.write(receiver, interval=0.04)
time.sleep(1.0)
pyautogui.press("enter")
time.sleep(0.8)
pyautogui.write(message, interval=0.03)
time.sleep(0.2)
pyautogui.press("enter")
return f"Message sent to {receiver} via {platform}."
except Exception as e:
return f"{platform} error: {e}"
def send_message(
parameters: dict,
response=None,
player=None,
session_memory=None
) -> str:
"""
Called from main.py.
parameters:
receiver : Contact name to send to
message_text : The message content
platform : whatsapp | instagram | telegram | <any app name>
Default: whatsapp
"""
params = parameters or {}
receiver = params.get("receiver", "").strip()
message_text = params.get("message_text", "").strip()
platform = params.get("platform", "whatsapp").strip().lower()
if not receiver:
return "Please specify who to send the message to, sir."
if not message_text:
return "Please specify what message to send, sir."
print(f"[SendMessage] 📨 {platform} → {receiver}: {message_text[:40]}")
if player:
player.write_log(f"[msg] Sending to {receiver} via {platform}...")
if "whatsapp" in platform or "wp" in platform or "wapp" in platform:
result = _send_whatsapp(receiver, message_text)
elif "instagram" in platform or "ig" in platform or "insta" in platform:
result = _send_instagram(receiver, message_text)
elif "telegram" in platform or "tg" in platform:
result = _send_telegram(receiver, message_text)
else:
result = _send_generic(platform, receiver, message_text)
print(f"[SendMessage] ✅ {result}")
if player:
player.write_log(f"[msg] {result}")
return result
+62
View File
@@ -0,0 +1,62 @@
# actions/weather_report.py
import webbrowser
from urllib.parse import quote_plus
def weather_action(
parameters: dict,
player=None,
session_memory=None
):
"""
Weather report action.
Opens a Google weather search and gives a short spoken confirmation.
"""
city = parameters.get("city")
time = parameters.get("time")
if not city or not isinstance(city, str):
msg = "Sir, the city is missing for the weather report."
_speak_and_log(msg, player)
return msg
city = city.strip()
if not time or not isinstance(time, str):
time = "today"
else:
time = time.strip()
search_query = f"weather in {city} {time}"
encoded_query = quote_plus(search_query)
url = f"https://www.google.com/search?q={encoded_query}"
try:
webbrowser.open(url)
except Exception:
msg = f"Sir, I couldn't open the browser for the weather report."
_speak_and_log(msg, player)
return msg
msg = f"Showing the weather for {city}, {time}, sir."
_speak_and_log(msg, player)
if session_memory:
try:
session_memory.set_last_search(
query=search_query,
response=msg
)
except Exception:
pass
return msg
def _speak_and_log(message: str, player=None):
if player:
try:
player.write_log(f"JARVIS: {message}")
except Exception:
pass
+137
View File
@@ -0,0 +1,137 @@
#web_search.py
import json
import sys
from pathlib import Path
def _get_base_dir() -> Path:
if getattr(sys, "frozen", False):
return Path(sys.executable).parent
return Path(__file__).resolve().parent.parent
BASE_DIR = _get_base_dir()
API_CONFIG_PATH = BASE_DIR / "config" / "api_keys.json"
def _get_api_key() -> str:
with open(API_CONFIG_PATH, "r", encoding="utf-8") as f:
return json.load(f)["gemini_api_key"]
def _gemini_search(query: str) -> str:
from google import genai
client = genai.Client(api_key=_get_api_key())
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=query,
config={"tools": [{"google_search": {}}]},
)
text = ""
for part in response.candidates[0].content.parts:
if hasattr(part, "text") and part.text:
text += part.text
text = text.strip()
if not text:
raise ValueError("Gemini returned an empty response.")
return text
def _ddg_search(query: str, max_results: int = 6) -> list[dict]:
try:
from ddgs import DDGS
except ImportError:
from duckduckgo_search import DDGS
results = []
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=max_results):
results.append({
"title": r.get("title", ""),
"snippet": r.get("body", ""),
"url": r.get("href", ""),
})
return results
def _format_ddg(query: str, results: list[dict]) -> str:
if not results:
return f"No results found for: {query}"
lines = [f"Search results for: {query}\n"]
for i, r in enumerate(results, 1):
if r.get("title"): lines.append(f"{i}. {r['title']}")
if r.get("snippet"): lines.append(f" {r['snippet']}")
if r.get("url"): lines.append(f" {r['url']}")
lines.append("")
return "\n".join(lines).strip()
def _compare(items: list[str], aspect: str) -> str:
query = (
f"Compare {', '.join(items)} in terms of {aspect}. "
"Give specific facts and data."
)
try:
return _gemini_search(query)
except Exception as e:
print(f"[WebSearch] ⚠️ Gemini compare failed: {e} — falling back to DDG")
# DDG fallback: fetch results per item and merge
all_results: dict[str, list] = {}
for item in items:
try:
all_results[item] = _ddg_search(f"{item} {aspect}", max_results=3)
except Exception:
all_results[item] = []
lines = [f"Comparison — {aspect.upper()}", "─" * 40]
for item in items:
lines.append(f"\n▸ {item}")
for r in all_results.get(item, [])[:2]:
if r.get("snippet"):
lines.append(f" • {r['snippet']}")
return "\n".join(lines)
def web_search(
parameters: dict,
response=None,
player=None,
session_memory=None,
) -> str:
params = parameters or {}
query = params.get("query", "").strip()
mode = params.get("mode", "search").lower().strip()
items = params.get("items", [])
aspect = params.get("aspect", "general").strip() or "general"
if not query and not items:
return "Please provide a search query, sir."
if items and mode != "compare":
mode = "compare"
if player:
player.write_log(f"[Search] {query or ', '.join(items)}")
print(f"[WebSearch] 🔍 Query: {query!r} Mode: {mode}")
# replace: result = _gemini_search(query) block with:
try:
from or_client import client
result = client.chat(
query,
system="You are a web search assistant. Answer factually and concisely."
)
print("[WebSearch] ✅ OpenRouter OK.")
return result
except Exception as e:
print(f"[WebSearch] ⚠️ OpenRouter failed ({e}) — trying DDG...")
results = _ddg_search(query)
result = _format_ddg(query, results)
print(f"[WebSearch] ✅ DDG: {len(results)} result(s).")
return result
except Exception as e:
print(f"[WebSearch] ❌ All backends failed: {e}")
return f"Search failed, sir: {e}"