Repository navigation
Configuracao
English · Português (Brasil)
| Caminho | O que é |
|---|---|
config.yaml |
config global: perfis (defaults/alertas/ações), ui, schedule, evidence
|
selections/<nome>.json |
seleções de ROI (uma por alvo), com overrides opcionais |
state.json |
estado leve: last_selection, profile, language, action_selection, evidence_enabled, alerts_muted, alerts_snooze_until
|
logs/ |
alerts.jsonl (alertas) e actions.jsonl (auditoria de ações) |
Base: %APPDATA%\screen_watch (Windows), ~/.config/screen_watch (Linux),
~/Library/Application Support/screen_watch (macOS) — ou o override SCREEN_WATCH_HOME.
Veja os caminhos efetivos com python -m screen_watch show-paths.
O YAML é global: define um perfil ativo, um ou mais perfis nomeados, e as seções ui,
schedule e evidence. Os alvos vivem em selections/*.json (não mais no YAML).
version: 2
profile: default # perfil ativo; trocável com --profile / seletor da GUI
profiles:
default:
defaults:
mode: "advanced" # "light" | "default" | "advanced"
poll_interval_s: 2.0 # mínimo 1.0
rearm: true
humanize: # ruído pseudo-humano das ações
mouse_steps: 24
key_interval_ms: 60
jitter_px: 3
wait_jitter_ms: 150
seed: null # só para testes determinísticos
compare_options:
light: { threshold: 12.0 }
default: { hash_size: 8, threshold: 6 }
advanced: { similarity_threshold: 0.92, psm: 6, lang: "por+eng", upscale: 2,
tesseract_cmd: null }
escalation: { enabled: false, severity_min: 2 } # repete até o Ciente
alerts:
- { type: "sound", enabled: true, severity_min: 1, cooldown_s: 30, file: "alert.mp3" }
- { type: "popup", enabled: true, severity_min: 1, cooldown_s: 30 }
- { type: "telegram", enabled: true, severity_min: 2, cooldown_s: 60,
bot_token_env: "TELEGRAM_BOT_TOKEN", chat_id: "123456789", attach_roi: true }
- { type: "log", enabled: true, severity_min: 1, cooldown_s: 0 } # opcional
actions: [] # ver wiki/Acoes-Pseudo-Humanas.md
trabalho:
defaults: { mode: "default", poll_interval_s: 1.0 }
ui:
hotkeys: { arm: "<ctrl>+<alt>+a", disarm: "<ctrl>+<alt>+d", toggle: "<ctrl>+<alt>+<space>",
rearm: "<ctrl>+<alt>+r", abort: "<esc>" } # acknowledge é opcional
arm_durations_min: [1, 5, 15, 30]
snooze_minutes: [5, 15, 30, 60] # menu Soneca da GUI/tray (minutos positivos)
max_sessions: 4 # sessões simultâneas na GUI (1..16)
language: auto # auto | pt-BR | en-US | tag descoberta em i18n/*.json
schedule: { enabled: false, days: [mon, tue, wed, thu, fri], windows: ["08:00-12:00"] }
evidence: { enabled: false, dir: null, keep_per_target: 50, max_total_mb: 200,
on_baseline: true, on_change: true, per_step: false }Regras:
-
Segredos nunca no YAML — apenas o nome da variável de ambiente (
bot_token_env). -
versionaceita 1 ou 2; outro valor é erro. O v2 exigeprofiles. -
profileinexistente é erro de validação. -
versionausente comtargets:é tratado como v1 legado (com aviso) e convertido pormigrate-config. -
ui.languagedesconhecido gera aviso e volta paraauto(não é erro). -
ui.snooze_minutesexige uma lista de inteiros positivos (config.snooze_minutes_not_list/config.snooze_minutes_positive);ui.max_sessionsaceita 1..16 (config.max_sessions_range). -
ui.hotkeys.acknowledge(Ciente) é opcional; sem ele o reconhecimento fica no botão/tray. - O app regrava o YAML sem preservar comentários; a escrita é atômica (temp +
os.replace) e deixa um backupconfig.yaml.bak. - Ajuste os limites (
light/default) e oadvanced.similarity_thresholdcom ocompare-modesdo CLI ou com o gráfico ao vivo Calibração… da GUI (score × limite, export CSV).
Cada item da lista alerts: do perfil aceita type, enabled, severity_min, cooldown_s e um id opcional
(default type; type#n quando repetido). O id é a chave de cooldown e o nome usado na seleção do
teste de envio. Os quatro tipos originais (sound/popup/telegram/log) usam campos planos; os
demais (webhook, http_post, syslog, ntfy, smtp e mqtt) usam um bloco aninhado options::
alerts:
- type: webhook
id: teams
severity_min: 2
cooldown_s: 60
options:
url_env: TEAMS_WEBHOOK # ou url: "https://..."
method: POST # POST | PUT | PATCH
headers: { Content-Type: "application/json" }
payload: { text: "Mudança em ${target}: ${strategy} sev=${severity}" } # ou payload_raw: "..."
timeout_s: 5
verify_tls: true
- type: http_post
id: erp-api
options: { scheme: http, host: 10.0.0.20, port: 8080, path: /alerta }
- type: syslog
id: siem
options: { host: 10.0.0.9, port: 514, protocol: udp, facility: local0 }
- type: ntfy
id: celular
options: { server: "https://ntfy.sh", topic: "meu-topico", token_env: NTFY_TOKEN,
attach_roi: true }
- type: smtp
id: email
options: { host: "smtp.example.com", port: 587, security: starttls,
from_addr: "watch@example.com", to: ["oncall@example.com"],
username_env: SMTP_USERNAME, password_env: SMTP_PASSWORD }
- type: mqtt
id: barramento
options: { host: "10.0.0.30", topic: "screen-watch/default", qos: 1,
username_env: MQTT_USERNAME, password_env: MQTT_PASSWORD }Regras (validadas com código estável config.alert_*):
-
webhook/http_postexigemurlouurl_env; a URL deve começar comhttp:///https://. -
ntfyexigetopic;serverdefaulthttps://ntfy.sh;token_envé opcional (vazio = tópico anônimo). -
smtpexigehost,from_addreto(lista não vazia);securityéstarttls(default),sslounone; as credenciais vêm só deusername_env/password_env. -
mqttexigehostetopic;portdefault 1883 (8883 comtls: true);qosé 0/1/2;payloadXORpayload_raw. Sem o extramqtt(pip install -e ".[mqtt]") o canal falha comalert.mqtt_missing_extra. -
payloadepayload_rawsão mutuamente exclusivos; placeholder${...}desconhecido é erro. -
portdeve ser 1..65535;protocoléudp/tcp;facilityprecisa ser uma facility syslog conhecida;methodéPOST/PUT/PATCH. -
typedesconhecido é erro (um canal "mudo" deixa de passar batido). - O seletor de som da GUI grava o
file:no alertasounddo perfil ativo (criado se não existir) com escrita atômica +.bak; config v1 precisa ser migrada antes. - Detalhes e a lista completa de placeholders: Alertas.
Perfis nomeados (profiles.<nome>.defaults + .alerts + .actions) permitem alternar conjuntos de
parâmetros. A troca (seletor da GUI, submenu do tray ou --profile) vale no próximo start — o
loop ativo não muda — e é gravada em state.json.profile.
defaults cobre mode, poll_interval_s, rearm, compare_options, humanize (abaixo) e
escalation.
Usada pelas ações pseudo-humanas:
| Chave | Default | Efeito |
|---|---|---|
mouse_steps |
24 | pontos intermediários no movimento do mouse (>= 1) |
key_interval_ms |
60 | intervalo default da digitação (type, quando o passo não define interval_ms) |
jitter_px |
3 | ruído em pixels no movimento |
wait_jitter_ms |
150 | ruído nas pausas (wait/settle_s) |
seed |
null |
semente fixa para testes determinísticos |
Não há UI para humanização: edite o YAML.
Repete o alerta até o reconhecimento (Ciente); vale por seleção via overrides.escalation:
| Chave | Default | Efeito |
|---|---|---|
enabled |
false |
liga a escalação (desligada preserva o comportamento anterior) |
severity_min |
2 | só escala mudanças com severidade >= este valor |
Com um fired que atenda o severity_min, o baseline não avança e o alerta repete na cadência
do cooldown_s de cada canal até o Ciente, que re-arma o baseline. Detalhes em
Alertas.
Alerta somente quando um texto aparece ou desaparece na ROI — somente no modo
advanced. Pode ficar no perfil (paridade para quem usa só CLI) ou no overrides da seleção (a
linha Verificar texto da GUI grava lá; o override tem precedência):
compare_options:
advanced:
text_watch: { text: "CONCLUÍDO", expect: "appears",
case_sensitive: false, ignore_accents: true }"overrides": { "text_watch": { "text": "CONCLUÍDO", "expect": "appears",
"case_sensitive": false, "ignore_accents": true } }-
expect:appears(default) oudisappears;texté obrigatório (não vazio). - Casamento por substring;
case_sensitive: falseeignore_accents: truepor padrão (NFKD + remoção de diacríticos nos dois lados — robusto para OCR em pt-BR). -
Somente no
advanced: fora dele é erro de validação (config.text_watch_needs_advanced); a GUI limpa o override ao trocar o modo. - Com o filtro configurado o gate de phash é bypassado (o OCR roda a cada tick e o veredito do filtro é autoritativo) e as ações também só rodam na transição — detalhes nas notas da v0.6.0.
Cada seleção pode trazer overrides que substituem (não somam) os valores do perfil para aquele
alvo: mode, poll_interval_s, rearm, masks, alerts, actions, text_watch e escalation.
{
"version": 2,
"window_handle": 123456,
"window_title_hint": "ERP - Estoque",
"app_name": "ERP",
"name": "verificando download",
"origin_at_selection": [100, 200],
"roi_relative": [120, 340, 400, 80],
"mode": "advanced",
"masks": [],
"overrides": { "poll_interval_s": 1.5, "rearm": false,
"text_watch": { "text": "CONCLUÍDO", "expect": "appears" } }
}-
Precedência do modo: modo explícito (seletor da GUI) >
overrides.mode>selection.mode. - Overrides de
actionssão validados com o modo resolvido (filtrostext_*exigemadvanced);text_watchexigeadvanced(config.text_watch_needs_advanced). - Seleções
version: 1continuam carregando (sem overrides/app_name/name). -
window_handleé a chave de lookup;roi_relativeé a fonte de verdade da ROI a cada tick. -
name(opcional) é o nome de exibição editado na GUI (prefixo no rótulo e norun). Renomear gravanamee move o arquivo para o slug do nome (verificando download→verificando-download.json), nunca sobrescrevendo outra seleção;--selection <nome>passa a usar o novo nome do arquivo. A reedição da região (duplo clique) preservaname/mode/overridese limpa as máscaras (relativas à ROI antiga). - Máscaras vivem só no JSON da seleção:
overrides.maskstem precedência sobremasks(o perfil não tem máscaras). O editor de máscaras da GUI grava onde as máscaras efetivas vivem:overrides.masksexistente, senãomasksnão vazio, senão criaoverrides.masks.
python -m screen_watch migrate-config --dry-run # imprime o plano
python -m screen_watch migrate-config # grava selections/*.json + config.yaml v2 (.bak)
python -m screen_watch list-selections # lista as seleções (marca a última usada)A migração aborta se já existir uma seleção com o mesmo nome de algum target (remova/renomeie primeiro). Nomes duplicados no v1 também abortam.
| Chave | Para quê |
|---|---|
last_selection |
seleção usada por último (default do --selection) |
profile |
perfil escolhido (aplica no próximo start) |
language |
idioma escolhido no seletor da GUI |
action_selection |
subconjunto de ações por seleção (chave ausente = todas; lista vazia = nenhuma) |
evidence_enabled |
toggle do checkbox "Gravar prints" (tem precedência sobre o YAML) |
alerts_muted |
silêncio dos alertas (Silenciar/Reativar na GUI/tray) |
alerts_snooze_until |
fim da soneca (epoch); expirado é ignorado no start |
O state.json é gravado de forma atômica e sem backup; é estado descartável (apagar não quebra).
schedule:
enabled: true
days: [mon, tue, wed, thu, fri]
windows: ["08:00-12:00", "13:30-18:00"] # janelas que cruzam a meia-noite são aceitasFora da janela de horário o monitoramento e os alertas seguem normais, mas as ações ficam
suspensas (registrado como suspended_schedule na auditoria). Agendador ligado sem days/windows
não restringe nada.
O agendador é apenas um portão de suspensão: ele nunca dispara ações. O disparo é decidido pelo
gatilho de cada ação — change (padrão) ou os gatilhos de tempo at/every/after
(Ações pseudo-humanas); um gatilho de tempo que vence fora da janela é
consumido pela suspensão e não é repetido quando a janela reabre.
Configuradas na seção evidence: do YAML ou pelo checkbox da GUI (que tem precedência). Detalhes
em Evidências.
-
Ações pseudo-humanas — o formato de
actions: -
Alertas — o formato de
alerts: -
Uso (CLI) —
validate-config,migrate-config,--profile - doc/00 §12 — schema e decisões de design