feat: tier-1 PC tools parity — window mgmt, clipboard, notes, process killer, web search

Mirrors the new rust packs from the same iteration. Total commands in
commands.yaml is now 49 (was 33). yaml.safe_load + ast.parse both pass.

Window management (8 commands) — uses the existing `keys` action with
pyautogui hotkey strings: winleft+d (show_desktop), winleft+up/down
(maximize/minimize), winleft+left/right (snap left/right), alt+f4
(close_window), winleft+shift+m (restore_all), winleft+tab (task_view).
No new action type needed.

Clipboard read aloud — new clipboard_read action. Shells to
PowerShell Get-Clipboard, caps preview at 400 chars, speaks via the
existing tts.va_speak (which already handles recorder.stop/start).

Notes — new notes_add + notes_open actions. notes_add strips the
trigger phrase, appends "[YYYY-MM-DD HH:MM] body\n" to
%USERPROFILE%\Documents\jarvis-notes.txt, creates the dir if needed.
notes_open shells "cmd /c start" so .txt opens in the user-default
editor.

Process killer — new process_kill action. Strips the trigger ("убей
процесс"/"закрой программу"/etc.), looks up the remainder in the same
alias map as the rust version (хром→chrome, телега→telegram, спотифай
→spotify, едж/эдж/edge→msedge, дискорд→discord, стим→steam, обс→obs64,
проводник/блокнот/калькулятор/диспетчер задач, vs code→code, etc.),
appends .exe if needed, runs taskkill /F /IM, speaks success or
"не нашёл процесс N".

Web search — new websearch action with a `service` param. Bases:
google → https://www.google.com/search?q=
youtube → https://www.youtube.com/results?search_query=
wiki → https://ru.wikipedia.org/wiki/Special:Search?search=
yandex → https://yandex.ru/search/?text=
Strips trigger, urllib.parse.quote-s the rest, opens via webbrowser.open
(respects the user'\''s default browser; config.BROWSER_PATHS only kicks in
for the older url-action commands).

Portability: USERPROFILE used for notes path, no other hardcoded paths.
This commit is contained in:
Bossiara13 2026-05-15 11:27:19 +03:00
parent 80a685230c
commit 7d83bf8299
2 changed files with 256 additions and 0 deletions

View file

@ -277,6 +277,136 @@ read_screen:
- сними текст с экрана
action: {type: ocr_screen}
# Window management via pyautogui hotkeys
show_desktop:
phrases:
- покажи рабочий стол
- свернуть всё
- сверни все окна
- покажи десктоп
action: {type: keys, sequence: 'winleft+d'}
maximize_window:
phrases:
- разверни окно
- развернуть окно
- на весь экран
- раскрой окно
action: {type: keys, sequence: 'winleft+up'}
minimize_window:
phrases:
- сверни окно
- свернуть окно
- убери окно
action: {type: keys, sequence: 'winleft+down'}
snap_left:
phrases:
- окно влево
- сдвинь влево
- прижми влево
action: {type: keys, sequence: 'winleft+left'}
snap_right:
phrases:
- окно вправо
- сдвинь вправо
- прижми вправо
action: {type: keys, sequence: 'winleft+right'}
close_window:
phrases:
- закрой окно
- закрой вкладку
- закрой текущее
action: {type: keys, sequence: 'alt+f4'}
restore_all:
phrases:
- восстанови окна
- верни окна
- разверни окна
action: {type: keys, sequence: 'winleft+shift+m'}
task_view:
phrases:
- открой все окна
- обзор окон
- таск вью
- покажи все окна
action: {type: keys, sequence: 'winleft+tab'}
# Clipboard read aloud
read_clipboard:
phrases:
- прочитай буфер
- что в буфере
- прочитай из буфера
- озвучь буфер
action: {type: clipboard_read}
# Notes
add_note:
phrases:
- запиши
- запиши заметку
- запиши идею
- запомни
- запомни идею
- сделай заметку
action: {type: notes_add}
open_notes:
phrases:
- покажи заметки
- открой заметки
- что я записал
action: {type: notes_open}
# Process killer
kill_process:
phrases:
- убей процесс
- закрой программу
- завершить процесс
- выруби процесс
action: {type: process_kill}
# Web search shortcuts
search_google:
phrases:
- загугли
- поиск в гугл
- поищи в гугл
- найди в гугл
- ищи в гугл
action: {type: websearch, service: google}
search_youtube:
phrases:
- найди на ютубе
- поиск на ютубе
- ищи на ютубе
- ютуб поиск
action: {type: websearch, service: youtube}
search_wiki:
phrases:
- найди в википедии
- поиск в википедии
- википедия
- вики поиск
action: {type: websearch, service: wiki}
search_yandex:
phrases:
- поищи в яндекс
- найди в яндекс
- яндекс поиск
- пробей в яндекс
action: {type: websearch, service: yandex}
thanks:
phrases:
- спасибо

126
main.py
View file

@ -395,6 +395,122 @@ def do_file_search(action, voice: str):
_speak(f"Найдено {len(lines)}, открываю первое: {os.path.basename(first)}.")
_NOTES_TRIGGERS = (
'запиши заметку', 'запиши идею', 'сделай заметку', 'запомни идею',
'запиши', 'запомни',
)
_PROCESS_KILL_TRIGGERS = (
'завершить процесс', 'выруби процесс', 'убей процесс', 'kill процесс',
'закрой программу',
)
_WEBSEARCH_TRIGGERS = (
'загугли', 'поиск в гугл', 'поищи в гугл', 'найди в гугл', 'ищи в гугл',
'найди на ютубе', 'поиск на ютубе', 'ищи на ютубе', 'ютуб поиск',
'найди в википедии', 'поиск в википедии', 'википедия', 'вики поиск',
'поищи в яндекс', 'найди в яндекс', 'яндекс поиск', 'пробей в яндекс',
)
_PROCESS_ALIASES = {
'хром': 'chrome', 'хрома': 'chrome',
'спотифай': 'spotify',
'едж': 'msedge', 'эдж': 'msedge', 'edge': 'msedge',
'файрфокс': 'firefox', 'фаерфокс': 'firefox',
'вскод': 'code', 'vs code': 'code',
'дискорд': 'discord',
'телега': 'telegram', 'телеграм': 'telegram',
'стим': 'steam',
'обс': 'obs64',
'проводник': 'explorer',
'блокнот': 'notepad',
'калькулятор': 'calculatorapp',
'диспетчер задач': 'taskmgr',
}
_WEBSEARCH_BASES = {
'google': 'https://www.google.com/search?q=',
'youtube': 'https://www.youtube.com/results?search_query=',
'wiki': 'https://ru.wikipedia.org/wiki/Special:Search?search=',
'yandex': 'https://yandex.ru/search/?text=',
}
def do_clipboard_read(action, voice: str):
res = subprocess.run(
['powershell', '-NoProfile', '-Command', 'Get-Clipboard'],
capture_output=True, text=True, encoding='utf-8', timeout=10,
)
text = (res.stdout or '').strip()
if not text:
_speak("Буфер пуст.")
return
to_speak = text[:400]
if len(text) > 400:
to_speak += " ... и так далее"
_speak(to_speak.replace('\r', ' ').replace('\n', ' '))
def do_notes_add(action, voice: str):
body = _strip_trigger(voice, _NOTES_TRIGGERS).lstrip(' ,.:').strip()
if not body:
_speak("Что записать, сэр?")
return
user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~'))
notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt')
stamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
line = f"[{stamp}] {body}\n"
try:
os.makedirs(os.path.dirname(notes_path), exist_ok=True)
with open(notes_path, 'a', encoding='utf-8') as f:
f.write(line)
except OSError as e:
print(f"notes append failed: {e}")
_speak("Не удалось записать.")
return
_speak(f"Записал: {body[:120]}")
def do_notes_open(action, voice: str):
user_profile = os.environ.get('USERPROFILE', os.path.expanduser('~'))
notes_path = os.path.join(user_profile, 'Documents', 'jarvis-notes.txt')
if not os.path.isfile(notes_path):
_speak("Заметок пока нет.")
return
subprocess.Popen(['cmd', '/c', 'start', '', notes_path], shell=False)
_speak("Открываю заметки.")
def do_process_kill(action, voice: str):
name = _strip_trigger(voice, _PROCESS_KILL_TRIGGERS).strip().lower()
target = _PROCESS_ALIASES.get(name, name)
if not target:
_speak("Какой процесс?")
return
exe = target if target.endswith('.exe') else target + '.exe'
res = subprocess.run(
['taskkill', '/F', '/IM', exe],
capture_output=True, text=True, timeout=10,
)
if res.returncode == 0:
_speak(f"Закрыл {exe}.")
else:
_speak(f"Не нашёл процесс {exe}.")
def do_websearch(action, voice: str):
import urllib.parse
svc = action.get('service', 'google')
base = _WEBSEARCH_BASES.get(svc, _WEBSEARCH_BASES['google'])
query = _strip_trigger(voice, _WEBSEARCH_TRIGGERS).strip()
if not query:
_speak(f"Что искать в {svc}?")
return
url = base + urllib.parse.quote(query)
webbrowser.open(url)
_speak(f"Открываю {svc}: {query[:100]}")
def do_audio_devices_panel(action, voice: str):
# Full programmatic switching needs IPolicyConfig + IMMDeviceEnumerator
# via comtypes; out of scope for this pass. Opens Sound control panel so
@ -489,6 +605,16 @@ def run_action(action):
do_file_search(action, _current_voice or '')
elif t == 'audio_devices_panel':
do_audio_devices_panel(action, _current_voice or '')
elif t == 'clipboard_read':
do_clipboard_read(action, _current_voice or '')
elif t == 'notes_add':
do_notes_add(action, _current_voice or '')
elif t == 'notes_open':
do_notes_open(action, _current_voice or '')
elif t == 'process_kill':
do_process_kill(action, _current_voice or '')
elif t == 'websearch':
do_websearch(action, _current_voice or '')
_current_voice: str = ''