feat: Wave 1 — 10 new packs (75 → 85 rust packs)

User picked from the fishki roadmap; this commit ships the 10 easy/medium
ones in one batch. No Rust core changes — all pure Lua + jarvis.* APIs.

Pack catalog

magic_8ball
  - "ответь да или нет" / "магический шар" / "что скажешь"
  - 15 канонических ответов, random pick. Pure fun.

github_issues (2 cmds)
  - "какие issues" / "открытые issues" — gh issue list по сохранённому репо
  - "мои issues" / "что мне назначено" — gh issue list --assignee @me

weather_extended (2 cmds, Open-Meteo, no key)
  - "погода завтра" — temp min/max + conditions + rain note
  - "прогноз на неделю" — общий диапазон температур
  - Auto-detects location via ipinfo.io if not in memory; stores lat/lon/city.

rss_reader (3 cmds)
  - "добавь rss <url>" — extracts URL, persists via memory под ключом "rss.<domain>"
  - "что в ленте" — fetches first feed, parses <title> tags, speaks top 3
  - "какие у меня rss" — lists subscribed feed domains

ics_event
  - "добавь встречу завтра в 15:00 обсудить проект"
  - Parses time (HH:MM или "в HH") + date keywords (сегодня/завтра/послезавтра)
  - Writes valid iCalendar v2.0 file to ~/Documents/jarvis-events/
  - jarvis.system.open → дефолтный handler (Outlook/Mail/whatever)

backup
  - "сделай бекап" — PowerShell Compress-Archive всех state files из APPDATA/Jarvis
  - Outputs to ~/Documents/jarvis-backup-<YYYYMMDD-HHMMSS>.zip
  - Bundles: long_term_memory, schedule, macros, profiles/, active_profile, llm_backend, settings

clip_history (3 cmds, 20-slot rolling buffer)
  - "запиши буфер" — push current clipboard onto memory keys clip.0..clip.19
  - "что я копировал" — speak preview of last 3
  - "верни первый/второй/третий буфер" — restore clip.N to clipboard

notif_queue (2 cmds)
  - "что я пропустил" — enumerate memory keys "notif.*", speak last 5 by recency
  - "очисти уведомления" — purge all notif.* keys
  - Producers (scheduler/macros/llm) can later push to memory[notif.<ts>] = text

password_vault (3 cmds, Windows DPAPI)
  - "сохрани пароль от GitHub" — encrypts current clipboard content via DPAPI
    (CurrentUser scope), base64-stored in memory[vault.GitHub]. Clears clipboard.
    Password is NEVER spoken or written to disk in plaintext.
  - "пароль от GitHub" — decrypts via DPAPI, restores to clipboard for 30 sec,
    schedules auto-clear via jarvis.scheduler. Speaks only "Пароль от X в буфере."
  - "какие у меня пароли" — list of stored service names.

habit_streaks (2 cmds, integrates with habit_nudge)
  - "сколько дней подряд" / "статистика привычек" — reads memory keys
    "habit_streak.<habit>.<YYYY-MM-DD>", computes consecutive-day streak per habit.
  - "я попил воды" / "отметь привычку" — marks today's check-in.
    Maps voice to habit: воды→water, размял/зарядк→stretch, глаз→eyes.

Tests: 6 commands tests pass (auto-validate the 10 new packs).
This commit is contained in:
Bossiara13 2026-05-16 01:04:23 +03:00
parent 4d3d664abd
commit c4b22618f8
30 changed files with 1086 additions and 0 deletions

View file

@ -0,0 +1,52 @@
# Password vault — encrypted via Windows DPAPI. Each password tied to a name.
[[commands]]
id = "vault.save"
type = "lua"
script = "save.lua"
sandbox = "full"
timeout = 5000
[commands.phrases]
ru = [
"сохрани пароль от",
"запомни пароль от",
"пароль для",
]
en = ["save password for", "remember password for"]
ua = ["запам'ятай пароль від"]
[[commands]]
id = "vault.get"
type = "lua"
script = "get.lua"
sandbox = "full"
timeout = 5000
[commands.phrases]
ru = [
"пароль от",
"дай пароль",
"покажи пароль от",
"достань пароль",
]
en = ["password for", "get password"]
ua = ["пароль від"]
[[commands]]
id = "vault.list"
type = "lua"
script = "list.lua"
sandbox = "minimal"
timeout = 3000
[commands.phrases]
ru = [
"какие у меня пароли",
"список паролей",
"что в хранилище паролей",
]
en = ["list passwords", "what's in the vault"]
ua = ["список паролів"]

View file

@ -0,0 +1,67 @@
-- "Пароль от GitHub" — decrypts via DPAPI, copies to clipboard for 30 seconds,
-- speaks ONLY the service name (never the password).
local phrase = (jarvis.context.phrase or ""):lower()
local name = jarvis.text.strip_trigger(phrase, {
"покажи пароль от",
"достань пароль от",
"пароль от",
"дай пароль",
"password for",
"get password",
"пароль від",
})
name = name:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if name == "" then
return jarvis.cmd.error("От чего пароль?")
end
local b64 = jarvis.memory.recall("vault." .. name)
if not b64 or b64 == "" then
return jarvis.cmd.not_found("Пароля для " .. name .. " нет.")
end
local ps = string.format([[
Add-Type -AssemblyName System.Security;
$bytes = [Convert]::FromBase64String('%s');
try {
$dec = [System.Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);
$text = [System.Text.Encoding]::UTF8.GetString($dec);
Set-Clipboard -Value $text;
Write-Output 'OK'
} catch {
Write-Output 'ERR'
}
]], b64)
local res = jarvis.system.exec(string.format(
'powershell -NoProfile -Command "%s"', ps:gsub('"', '\\"'):gsub("\r?\n", "; ")
))
local out = res and (res.stdout or ""):gsub("%s+", "") or ""
if out ~= "OK" then
return jarvis.cmd.error("Не получилось расшифровать.")
end
-- Schedule clipboard clear in 30 seconds via the scheduler.
local cmd_path = jarvis.context.command_path
local clear_script = cmd_path .. "\\_clear_clip.lua"
clear_script = clear_script:gsub("/", "\\")
if not jarvis.fs.is_file(clear_script) then
jarvis.fs.write(clear_script, [[
jarvis.system.clipboard.set("")
return { chain = false }
]])
end
local ok, _ = pcall(function()
jarvis.scheduler.remove("vault_clip_clear")
jarvis.scheduler.add({
id = "vault_clip_clear",
name = "Clear clipboard after vault paste",
schedule = "in 30 seconds",
action = { type = "lua", script_path = clear_script },
})
end)
return jarvis.cmd.ok("Пароль от " .. name .. " в буфере на 30 секунд.")

View file

@ -0,0 +1,14 @@
local all = jarvis.memory.all()
local names = {}
for _, rec in ipairs(all) do
if rec.key:sub(1, 6) == "vault." then
table.insert(names, rec.key:sub(7))
end
end
if #names == 0 then
return jarvis.cmd.ok("В хранилище паролей пусто.")
end
return jarvis.cmd.ok(string.format("Паролей сохранено %d: %s.",
#names, table.concat(names, ", ")))

View file

@ -0,0 +1,48 @@
-- "Сохрани пароль от GitHub" — takes the password from clipboard (so user
-- never has to speak it), encrypts via DPAPI, stores in memory.
local phrase = (jarvis.context.phrase or ""):lower()
local name = jarvis.text.strip_trigger(phrase, {
"сохрани пароль от",
"запомни пароль от",
"пароль для",
"save password for",
"remember password for",
"запам'ятай пароль від",
})
name = name:gsub("^[%s,:%.]+", ""):gsub("%s+$", "")
if name == "" then
return jarvis.cmd.error("Имя сервиса не указано.")
end
local plaintext = jarvis.system.clipboard.get()
if not plaintext or plaintext == "" then
return jarvis.cmd.error("Скопируйте пароль в буфер сначала.")
end
-- Encrypt via DPAPI (current user scope) and base64-encode the ciphertext.
local escaped = plaintext:gsub("'", "''")
local ps = string.format([[
Add-Type -AssemblyName System.Security;
$bytes = [System.Text.Encoding]::UTF8.GetBytes('%s');
$enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser);
Write-Output ([Convert]::ToBase64String($enc))
]], escaped)
local res = jarvis.system.exec(string.format(
'powershell -NoProfile -Command "%s"', ps:gsub('"', '\\"'):gsub("\r?\n", "; ")
))
if not res.success then
return jarvis.cmd.error("Не получилось зашифровать.")
end
local b64 = (res.stdout or ""):gsub("%s+", "")
if b64 == "" then
return jarvis.cmd.error("Шифр пустой.")
end
jarvis.memory.remember("vault." .. name, b64)
-- Clear clipboard for safety.
jarvis.system.clipboard.set("")
return jarvis.cmd.ok("Сохранил пароль от " .. name .. ".")