feat: Wave 11 — birthdays tracker pack
Some checks failed
Rust CI / cargo test (jarvis-core) (push) Has been cancelled
Rust CI / cargo clippy (push) Has been cancelled
Rust CI / cargo check (workspace) (push) Has been cancelled

resources/commands/birthdays/ (2 commands):
- birthdays.add  "запомни день рождения мама 15 марта" → memory key
  "birthday.мама" = "15.03". Accepts both DD.MM and "DD <month>"
  formats; recognises all 12 RU + 12 EN month names.
- birthdays.next "ближайший день рождения" → reads every "birthday.*"
  memory key, computes days-until each entry (with year wrap), speaks
  the nearest one in human form ("сегодня" / "завтра" / "через N дней").

Storage piggybacks on the existing long-term memory store, so
birthdays survive restart, sync to disk atomically, and don't need a
new persistence layer. Naming convention "birthday.<name>" keeps the
data discoverable in /memory GUI.

Pack count: 99 → 100 functional packs.
Tests: 140 rust, unchanged.
This commit is contained in:
Bossiara13 2026-05-16 14:59:50 +03:00
parent 87bb186e94
commit 05b75feee4
3 changed files with 185 additions and 0 deletions

View file

@ -0,0 +1,80 @@
-- Voice "запомни день рождения мама 15 марта" → memory["birthday.мама"]="15.03"
-- We accept three date formats: "15.03", "15 марта", and "15 march".
local lang = jarvis.context.language
local raw = (jarvis.context.phrase or ""):lower()
local triggers = {
"запомни день рождения", "день рождения у", "добавь день рождения",
"запиши день рождения",
"remember birthday", "add birthday", "save birthday",
}
local rest = jarvis.text.strip_trigger(raw, triggers) or raw
rest = rest:gsub("^%s+", ""):gsub("%s+$", "")
-- Russian month names → "MM"
local months_ru = {
["января"]=1, ["январь"]=1,
["февраля"]=2, ["февраль"]=2,
["марта"]=3, ["март"]=3,
["апреля"]=4, ["апрель"]=4,
["мая"]=5, ["май"]=5,
["июня"]=6, ["июнь"]=6,
["июля"]=7, ["июль"]=7,
["августа"]=8, ["август"]=8,
["сентября"]=9, ["сентябрь"]=9,
["октября"]=10, ["октябрь"]=10,
["ноября"]=11, ["ноябрь"]=11,
["декабря"]=12, ["декабрь"]=12,
}
local months_en = {
january=1, february=2, march=3, april=4, may=5, june=6,
july=7, august=8, september=9, october=10, november=11, december=12,
}
-- Try patterns in order. Each pattern leaves `name` and `date` populated.
local name, date_str = nil, nil
-- 1. "<name> DD.MM"
local n, d, m = rest:match("^(.-)%s+(%d?%d)%.(%d?%d)")
if n and d and m then
name = n
date_str = string.format("%02d.%02d", tonumber(d), tonumber(m))
end
-- 2. "<name> DD <month_ru/en>"
if not name then
local n2, d2, mword = rest:match("^(.-)%s+(%d?%d)%s+(%S+)")
if n2 and d2 and mword then
local m_num = months_ru[mword] or months_en[mword]
if m_num then
name = n2
date_str = string.format("%02d.%02d", tonumber(d2), m_num)
end
end
end
if not name or not date_str then
return jarvis.cmd.not_found(lang == "ru"
and "Скажи в формате 'имя 15 марта' или 'имя 15.03'."
or "Say in the form 'name 15 march' or 'name 15.03'.")
end
name = name:gsub("^%s+", ""):gsub("%s+$", "")
if name == "" then
return jarvis.cmd.not_found(lang == "ru" and "Чьё именно?" or "Whose?")
end
-- Strip leading prepositions left from "у мамы" → "мамы"
name = name:gsub("^у%s+", ""):gsub("^of%s+", ""):gsub("^for%s+", "")
local ok, err = pcall(function()
jarvis.memory.remember("birthday." .. name, date_str)
end)
if not ok then
return jarvis.cmd.error(tostring(err))
end
return jarvis.cmd.ok(string.format(lang == "ru"
and "Запомнил: %s — %s."
or "Got it: %s — %s.", name, date_str))