J.A.R.V.I.S-rust/resources/commands/outlook/send_clipboard.lua
Bossiara13 df799b6cd2
Some checks are pending
Rust CI / cargo test (jarvis-core) (push) Waiting to run
Rust CI / cargo clippy (push) Waiting to run
Rust CI / cargo check (workspace) (push) Waiting to run
feat: Wave 5 — sysinfo speaks, smart reminders, outlook COM, time tracker, WOL, conversation tools
Voice sysinfo overhaul (6 scripts: battery/cpu/ram/disk/time/all):
- Now SPEAK the result via jarvis.cmd.ok instead of notify-only. Toast
  still fires for visual reference, but real Jarvis tells you out loud.
- `all` joins lines with commas so TTS doesn't over-pause on linebreaks.

Smart reminders (`reminders/set.lua` rewrite):
- Was: detached `Start-Sleep` PowerShell subprocesses that died on
  jarvis-app restart and spawned a hidden process per timer.
- Now: routes through `jarvis.scheduler.add(... type=speak ...)`. Persists
  to schedule.json, survives restart, no zombie processes. Trade-off:
  sub-minute timers round up to 1 min (rare in voice UX).
- Drops UA triggers from earlier UA-removal sweep.

Translation polish (`translate/translate.lua` + `clipboard.lua`):
- Drops UA target language; adds Italian, Polish, Turkish, French ru
  triggers + English target detectors.
- `language_iso()` helper picks the SAPI voice locale for the TARGET
  language (German translation → German SAPI voice if installed).

LLM system prompt (config.rs):
- New LLM_SYSTEM_PROMPT_EN counterpart. Both languages now mention the
  assistant's tooling (memory / scheduler / profiles / macros / vision /
  clipboard) so the LLM knows it can DO things, not suggest the user
  install something.
- `get_llm_system_prompt(lang)` picks EN/RU.

Conversation pack (NEW `conversation/`):
- 2 commands. `conversation.summary` ("о чём мы говорили") pulls the
  last 12 turns from persisted history, asks LLM for a 1-3 sentence
  recap in Jarvis tone. Falls back gracefully when offline.
- `conversation.repeat` ("повтори") re-speaks the last assistant message.
- New Lua API `jarvis.llm_history()` returns array of {role, content}
  tables, excluding the system prompt.

WOL pack (NEW `wol/`):
- Voice "разбуди сервер" / "wake server" fires a magic packet at a MAC
  stored in long-term memory under `wol_<alias>` (default alias "server").
- Setup via voice: "запомни wol_server AA:BB:CC:DD:EE:FF". Falls back to
  `JARVIS_WOL_TARGET` env if memory empty. Handles every standard MAC
  format (colons / dashes / dots / bare). Broadcast on UDP 9 via
  PowerShell System.Net.Sockets.UdpClient.

Outlook COM pack (NEW `outlook/`, via subagent):
- 4 commands: unread_count, latest, send_clipboard, summarize_inbox.
- PowerShell COM bridge `_outlook.ps1` with tab-delimited line protocol.
- summarize_inbox calls LLM for a one-paragraph summary. Graceful
  failure when Outlook isn't running.

Time tracker pack (NEW `time_tracker/`, via subagent):
- 5 commands: start, stop, today, week, reset.
- Persists via `jarvis.state` (structured object: current_session_start
  + sessions array of {start, end}).
- Local-calendar "today" boundary, full Russian pluralisation
  (час/часа/часов, минута/минуты/минут, сессия/сессии/сессий).

Tests: 139 rust (no regression), 81 python (60 → 81, +14 time_tracker
+7 outlook). Release build green for jarvis-app and jarvis-cli.

Pack count: 88 → 92 rust packs.
2026-05-16 14:34:29 +03:00

103 lines
4.5 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Send clipboard contents as an email to {name} via Outlook COM. The recipient
-- is resolved against the Global Address List + Contacts (Outlook's Recipient
-- .Resolve API). Subject = first line of the body truncated to 60 chars, or
-- "From J.A.R.V.I.S." for an empty clipboard.
local lang = jarvis.context.language
local slots = jarvis.context.slots or {}
local recipient = slots.name or ""
recipient = tostring(recipient):gsub("^%s+", ""):gsub("%s+$", "")
if recipient == "" then
return jarvis.cmd.not_found(lang == "ru"
and "Кому отправить? Назови имя."
or "Whom to send to? Please name them.")
end
local body = jarvis.system.clipboard.get() or ""
body = body:gsub("^%s+", ""):gsub("%s+$", "")
-- Write the body to a temp file so we don't have to escape it through the
-- PowerShell command-line. The PS helper reads it back via -Body parameter
-- value (passed as an arg). For very large bodies (>32 KB) the cmd-line
-- approach would break; in that case we read the file from inside PS.
local temp = jarvis.system.env("TEMP") or jarvis.system.env("TMP") or "C:\\Windows\\Temp"
local ts = jarvis.context.time.year .. jarvis.context.time.month .. jarvis.context.time.day
.. "_" .. jarvis.context.time.hour .. jarvis.context.time.minute .. jarvis.context.time.second
local body_path = temp .. "\\jarvis_outlook_body_" .. ts .. ".txt"
jarvis.fs.write(body_path, body)
-- Pass body via the file: the helper reads $Body if set, else falls back to
-- reading the saved file. To keep the helper simple we just inline the body
-- through -Body only when small; otherwise we still pass via -Body but quote
-- the path and let PS read it. We use the latter: PS reads its arg literally.
-- We instead encode the body so passing through args is safe.
local helper = jarvis.context.command_path .. "\\_outlook.ps1"
-- Build a single-line PowerShell expression that reads the body from the
-- temp file and invokes the helper. This avoids any shell-escape issues
-- with quotes/newlines in the clipboard.
local body_path_ps = body_path:gsub("'", "''")
local recipient_ps = recipient:gsub("'", "''")
local helper_ps = helper:gsub("'", "''")
local ps_expr = string.format(
"$b = Get-Content -LiteralPath '%s' -Raw -Encoding UTF8; if (-not $b) { $b = '' } else { $b = $b.TrimEnd() }; & '%s' -Action send_clipboard -Recipient '%s' -Body $b",
body_path_ps, helper_ps, recipient_ps
)
local cmd = string.format(
'powershell -NoProfile -ExecutionPolicy Bypass -Command "%s"',
ps_expr:gsub('"', '\\"')
)
local res = jarvis.system.exec(cmd)
jarvis.fs.remove(body_path)
local out = (res.stdout or ""):gsub("\r", "")
if not res.success or out == "" then
jarvis.log("error", "outlook send_clipboard exec failed: " .. tostring(res.stderr))
return jarvis.cmd.error(lang == "ru"
and "Outlook недоступен — запусти Outlook сначала."
or "Outlook is unavailable — start Outlook first.")
end
local lines = {}
for line in out:gmatch("[^\n]+") do table.insert(lines, line) end
local status = lines[1] or ""
if status:sub(1, 3) == "ERR" then
if status:find("unresolved", 1, true) then
return jarvis.cmd.not_found(lang == "ru"
and (string.format("Не нашёл контакт «%s».", recipient))
or (string.format("Could not find contact '%s'.", recipient)))
elseif status:find("no_recipient", 1, true) then
return jarvis.cmd.not_found(lang == "ru"
and "Кому отправить? Назови имя."
or "Whom to send to? Please name them.")
end
jarvis.log("warn", "outlook send_clipboard: " .. status)
return jarvis.cmd.error(lang == "ru"
and "Не получилось отправить — Outlook отказал."
or "Send failed — Outlook refused.")
end
local to_name, addr, subject = "", "", ""
for i = 2, #lines do
local key, val = lines[i]:match("^([A-Z]+)\t(.*)$")
if key == "TO" then to_name = val or "" end
if key == "ADDR" then addr = val or "" end
if key == "SUBJECT" then subject = val or "" end
end
local speech
if lang == "ru" then
speech = string.format("Отправил %s. Тема: %s.", to_name ~= "" and to_name or recipient, subject)
else
speech = string.format("Sent to %s. Subject: %s.", to_name ~= "" and to_name or recipient, subject)
end
jarvis.system.notify(
lang == "ru" and "Письмо отправлено" or "Email sent",
string.format("%s <%s>\n%s", to_name, addr, subject)
)
return jarvis.cmd.ok(speech)